Compare commits
94 commits
pre-1.6-me
...
local/yoda
| Author | SHA1 | Date | |
|---|---|---|---|
| 30d8868cca | |||
| 51c6cfedf6 | |||
|
|
0fbd4574ef | ||
|
|
d36480c1b5 | ||
|
|
50c170c355 | ||
|
|
26daf75d93 | ||
|
|
cd881a4df2 | ||
|
|
651f701f84 | ||
|
|
a7349b72e3 | ||
|
|
f9e02f4321 | ||
| a103e7619b | |||
| a69f659983 | |||
|
|
a55785993e | ||
|
|
7d1daab7bc | ||
|
|
6026520bd3 | ||
|
|
8ad541dbe3 | ||
|
|
f61c0f9f76 | ||
|
|
0f56d214b9 | ||
|
|
057e7b72cc | ||
|
|
e8caf335b3 | ||
|
|
f8344126a1 | ||
|
|
bd0b834128 | ||
|
|
1c8a0070c0 | ||
|
|
e563dc85bf | ||
|
|
2a3b4dca5f | ||
|
|
0e15b14ab2 | ||
|
|
6bf75e1c10 | ||
|
|
bb95a57f5a | ||
|
|
87d73dbce3 | ||
|
|
f00ff834c9 | ||
|
|
b8824128a0 | ||
|
|
e65f6608a4 | ||
|
|
85564f0336 | ||
|
|
a66f629a4e | ||
|
|
67aa6a0c49 | ||
|
|
9125f8a2a7 | ||
|
|
6467f79025 | ||
|
|
ca9dd27350 | ||
|
|
8ec1ffb740 | ||
|
|
0d75928579 | ||
|
|
e62e35e5cc | ||
|
|
8691956644 | ||
|
|
9059873c84 | ||
|
|
e93ec67a11 | ||
|
|
a7d31a75d2 | ||
|
|
b407ed560a | ||
|
|
a830784f11 | ||
|
|
9fe22c86b6 | ||
|
|
94178034dd | ||
|
|
158e2b7f91 | ||
| af6d31f413 | |||
|
|
5c93094574 | ||
|
|
31827ed240 | ||
|
|
80b8eb47d7 | ||
|
|
01b79c3eae | ||
|
|
8304b18886 | ||
|
|
738e0b3124 | ||
| 8ac198de15 | |||
| be1fab51ea | |||
|
|
f20de50e41 | ||
|
|
04026259e0 | ||
| f60c573f4d | |||
|
|
314fc670a8 | ||
|
|
f1d1c00257 | ||
|
|
8806436f81 | ||
|
|
861a460e6c | ||
|
|
c423e82820 | ||
|
|
f4ee52c46a | ||
|
|
e939a72b30 | ||
|
|
861b732671 | ||
|
|
18ae082615 | ||
|
|
0cf70521ff | ||
|
|
6af1dba69b | ||
|
|
f97a852e95 | ||
|
|
3b8bac8335 | ||
|
|
abfb25a672 | ||
|
|
647228b9ce | ||
|
|
f89c78b9bd | ||
|
|
58c018dd40 | ||
|
|
d3ffa81494 | ||
|
|
789e0b3634 | ||
|
|
091583ac84 | ||
|
|
81cd5fdbaa | ||
|
|
d687285338 | ||
|
|
ffeda3375a | ||
|
|
8d450d4977 | ||
|
|
9514b4945e | ||
|
|
5ce21fb4f3 | ||
|
|
44de3063ee | ||
|
|
6108cef8b3 | ||
|
|
24d29008f0 | ||
|
|
53a8d0bea2 | ||
|
|
9718699ec7 | ||
|
|
e4bb42f2c0 |
79 changed files with 6022 additions and 1554 deletions
567
Cargo.lock
generated
567
Cargo.lock
generated
File diff suppressed because it is too large
Load diff
|
|
@ -44,7 +44,6 @@ iced_graphics = { path = "../libcosmic/iced/graphics", features = ["geometry"] }
|
|||
indexmap = "2.13"
|
||||
keyframe = "1.1.1"
|
||||
cosmic = { package = "libcosmic-yoda", path = "../libcosmic", default-features = false, features = [
|
||||
"tokio",
|
||||
"wayland",
|
||||
"multi-window",
|
||||
] }
|
||||
|
|
@ -53,6 +52,7 @@ log-panics = { version = "2", features = ["with-backtrace"] }
|
|||
ordered-float = "5.1"
|
||||
png = "0.18"
|
||||
regex = "1"
|
||||
resvg = "0.45"
|
||||
ron = "0.12"
|
||||
rust-embed = { version = "8.11", features = ["debug-embed"] }
|
||||
sanitize-filename = "0.6.0"
|
||||
|
|
@ -78,7 +78,6 @@ xdg = "^3.0"
|
|||
xdg-user = "0.2.1"
|
||||
xkbcommon = "0.9"
|
||||
zbus = "5.14.0"
|
||||
tokio = { version = "1", features = ["rt-multi-thread"] }
|
||||
profiling = { version = "1.0" }
|
||||
rustix = { version = "1.1.4", features = ["process"] }
|
||||
rand = "0.10"
|
||||
|
|
@ -102,6 +101,7 @@ features = [
|
|||
"backend_drm",
|
||||
"backend_gbm",
|
||||
"backend_egl",
|
||||
"backend_libei",
|
||||
"backend_libinput",
|
||||
"backend_session_libseat",
|
||||
"backend_udev",
|
||||
|
|
@ -177,4 +177,5 @@ dnd = { path = "../window_clipboard/dnd" }
|
|||
mime = { path = "../window_clipboard/mime" }
|
||||
|
||||
[patch.crates-io]
|
||||
smithay = { git = "https://github.com/smithay/smithay.git", rev = "1ed69cb" }
|
||||
smithay = { git = "https://github.com/smithay/smithay.git", rev = "e3d461a" }
|
||||
smithay-egui = { git = "https://github.com/Smithay/smithay-egui.git", rev = "6511552" }
|
||||
|
|
|
|||
|
|
@ -66,6 +66,13 @@ impl Default for AppearanceConfig {
|
|||
}
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Debug, Default, PartialEq, Eq, Serialize, Deserialize)]
|
||||
pub enum DecorationPreference {
|
||||
#[default]
|
||||
ClientSide,
|
||||
ServerSide,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, CosmicConfigEntry)]
|
||||
#[version = 1]
|
||||
pub struct CosmicCompConfig {
|
||||
|
|
@ -101,7 +108,10 @@ pub struct CosmicCompConfig {
|
|||
pub appearance_settings: AppearanceConfig,
|
||||
/// Hide the cursor after this many seconds of pointer inactivity (None disables)
|
||||
pub cursor_hide_timeout: Option<u32>,
|
||||
/// Briefly magnify the cursor when the pointer is shaken, to help locate it
|
||||
pub cursor_shake_to_find: bool,
|
||||
pub activation_policy: ActivationPolicy,
|
||||
pub decoration_preference: DecorationPreference,
|
||||
}
|
||||
|
||||
impl Default for CosmicCompConfig {
|
||||
|
|
@ -139,7 +149,9 @@ impl Default for CosmicCompConfig {
|
|||
accessibility_zoom: ZoomConfig::default(),
|
||||
appearance_settings: AppearanceConfig::default(),
|
||||
cursor_hide_timeout: None,
|
||||
cursor_shake_to_find: true,
|
||||
activation_policy: ActivationPolicy::default(),
|
||||
decoration_preference: DecorationPreference::default(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -195,6 +207,10 @@ pub struct ZoomConfig {
|
|||
pub enable_mouse_zoom_shortcuts: bool,
|
||||
}
|
||||
|
||||
impl ZoomConfig {
|
||||
pub const ZOOM_INCREMENT_PRESETS: &[u32] = &[10, 25, 50, 75, 100, 150, 200];
|
||||
}
|
||||
|
||||
impl Default for ZoomConfig {
|
||||
fn default() -> Self {
|
||||
ZoomConfig {
|
||||
|
|
|
|||
|
|
@ -0,0 +1,28 @@
|
|||
a11y-zoom-move-continuously = Visningen bevæger sig kontinuerligt med markøren
|
||||
a11y-zoom-move-onedge = Vis bevægelser når markør rammer kant
|
||||
a11y-zoom-move-centered = Vis bevægelser, for at holde markør centreret
|
||||
a11y-zoom-settings = Forstørrelsesglas-indstillinger...
|
||||
grow-window = Gro
|
||||
shrink-window = Skrump
|
||||
swap-windows = Byt Vinduer
|
||||
stack-windows = Stabel Vinduer
|
||||
window-menu-tiled = Gør vindue flydende
|
||||
window-menu-screenshot = Tag skærmbillede
|
||||
window-menu-move = Flyt
|
||||
window-menu-resize = Skift størrelse
|
||||
window-menu-move-prev-workspace = Flyt til forrige arbejdsområde
|
||||
window-menu-move-next-workspace = Flyt til næste arbejdsområde
|
||||
window-menu-stack = Opret vinduestak
|
||||
window-menu-unstack-all = Udtag vinduer af stak
|
||||
window-menu-unstack = Udtag vindue af stak
|
||||
window-menu-sticky = Fastgjort vindue
|
||||
window-menu-close = Luk
|
||||
window-menu-close-all = Luk alle vinduer
|
||||
window-menu-resize-edge-top = Top
|
||||
window-menu-resize-edge-left = Venstre
|
||||
window-menu-resize-edge-right = Højre
|
||||
window-menu-resize-edge-bottom = Bund
|
||||
window-menu-minimize = Minimér
|
||||
window-menu-maximize = Maksimér
|
||||
window-menu-fullscreen = Fuld skærm
|
||||
unknown-keybinding = <uangiven>
|
||||
|
|
@ -0,0 +1,28 @@
|
|||
a11y-zoom-move-continuously = View moves continuously with pointer
|
||||
a11y-zoom-move-onedge = View moves when pointer reaches edge
|
||||
a11y-zoom-move-centered = View moves to keep pointer centred
|
||||
a11y-zoom-settings = Magnifier settings...
|
||||
grow-window = Grow
|
||||
shrink-window = Shrink
|
||||
swap-windows = Swap Windows
|
||||
stack-windows = Stack Windows
|
||||
unknown-keybinding = <unset>
|
||||
window-menu-minimize = Minimise
|
||||
window-menu-maximize = Maximise
|
||||
window-menu-fullscreen = Fullscreen
|
||||
window-menu-tiled = Float window
|
||||
window-menu-screenshot = Take screenshot
|
||||
window-menu-move = Move
|
||||
window-menu-resize = Resize
|
||||
window-menu-move-prev-workspace = Move to previous workspace
|
||||
window-menu-move-next-workspace = Move to next workspace
|
||||
window-menu-stack = Create window stack
|
||||
window-menu-unstack-all = Unstack windows
|
||||
window-menu-unstack = Unstack window
|
||||
window-menu-sticky = Sticky window
|
||||
window-menu-close = Close
|
||||
window-menu-close-all = Close all windows
|
||||
window-menu-resize-edge-top = Top
|
||||
window-menu-resize-edge-left = Left
|
||||
window-menu-resize-edge-right = Right
|
||||
window-menu-resize-edge-bottom = Bottom
|
||||
0
resources/i18n/fil/cosmic_comp.ftl
Normal file
0
resources/i18n/fil/cosmic_comp.ftl
Normal file
28
resources/i18n/gl/cosmic_comp.ftl
Normal file
28
resources/i18n/gl/cosmic_comp.ftl
Normal file
|
|
@ -0,0 +1,28 @@
|
|||
a11y-zoom-move-continuously = A vista móvese continuamente co punteiro
|
||||
a11y-zoom-move-onedge = A vista móvese cando o punteiro chega ao bordo
|
||||
a11y-zoom-move-centered = Mover a vista para manter o cursor centrado
|
||||
a11y-zoom-settings = Configuración da lupa...
|
||||
grow-window = Aumentar
|
||||
shrink-window = Encoller
|
||||
swap-windows = Cambiar xanelas
|
||||
stack-windows = Apilar xanelas
|
||||
unknown-keybinding = <unset>
|
||||
window-menu-minimize = Minimizar
|
||||
window-menu-maximize = Maximizar
|
||||
window-menu-fullscreen = Pantalla Completa
|
||||
window-menu-tiled = Xanela flotante
|
||||
window-menu-screenshot = Capturar pantalla
|
||||
window-menu-move = Mover
|
||||
window-menu-resize = Redimensionar
|
||||
window-menu-move-prev-workspace = Mover ao espazo de traballo anterior
|
||||
window-menu-move-next-workspace = Mover ao seguinte espazo de traballo
|
||||
window-menu-stack = Crear pila de xanelas
|
||||
window-menu-unstack-all = Desapilar xanelas
|
||||
window-menu-unstack = Desapilar xanela
|
||||
window-menu-sticky = Xanela persistente
|
||||
window-menu-close = Pechar
|
||||
window-menu-close-all = Pechar todas as xanelas
|
||||
window-menu-resize-edge-top = Arriba
|
||||
window-menu-resize-edge-left = Esquerda
|
||||
window-menu-resize-edge-right = Dereita
|
||||
window-menu-resize-edge-bottom = Fondo
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
grow-window = Ingrandisci
|
||||
grow-window = Crescere
|
||||
shrink-window = Riduci
|
||||
swap-windows = Scambia le finestre
|
||||
stack-windows = Sovrapponi le finestre
|
||||
|
|
@ -6,7 +6,7 @@ unknown-keybinding = <unset>
|
|||
window-menu-minimize = Minimizza
|
||||
window-menu-maximize = Massimizza
|
||||
window-menu-tiled = Finestra flottante
|
||||
window-menu-screenshot = Cattura una schermata
|
||||
window-menu-screenshot = Scatta uno screenshot
|
||||
window-menu-move = Sposta
|
||||
window-menu-resize = Ridimensiona
|
||||
window-menu-move-prev-workspace = Sposta all'area di lavoro precedente
|
||||
|
|
@ -22,7 +22,7 @@ window-menu-resize-edge-left = Sinistra
|
|||
window-menu-resize-edge-right = Destra
|
||||
window-menu-resize-edge-bottom = In basso
|
||||
window-menu-fullscreen = Schermo intero
|
||||
a11y-zoom-settings = Opzioni lente d'ingrandimento...
|
||||
a11y-zoom-settings = Impostazioni lente d'ingrandimento...
|
||||
a11y-zoom-move-continuously = Segue sempre il cursorse
|
||||
a11y-zoom-move-centered = Mantiene il cursore centrato
|
||||
a11y-zoom-move-onedge = Segue il cursore ai bordi
|
||||
|
|
|
|||
|
|
@ -0,0 +1,20 @@
|
|||
a11y-zoom-move-continuously = La vista bolega de contunh amb lo ponchador
|
||||
a11y-zoom-move-onedge = La lista bolega quand lo ponchador atenh un caire
|
||||
a11y-zoom-move-centered = La vista bolega per gardar lo ponchador al centre
|
||||
a11y-zoom-settings = Paramètres de la lópia...
|
||||
unknown-keybinding = <unset>
|
||||
window-menu-tiled = Fenèstras bandejanta
|
||||
window-menu-screenshot = Prendre una captura d’ecran
|
||||
window-menu-resize = Redimensionar
|
||||
window-menu-move-prev-workspace = Desplaçar a l’espaci de trabalh precedent
|
||||
window-menu-move-next-workspace = Desplaçar a l'espaci de trabalh seguent
|
||||
window-menu-stack = Crear una pila de fenèstras
|
||||
window-menu-unstack-all = Desapilar totas las fenèstras
|
||||
window-menu-unstack = Desapilar la fenèstra
|
||||
window-menu-sticky = Fenèstra adesiva
|
||||
window-menu-close = Tampar
|
||||
window-menu-close-all = Tampar totas las fenèstras
|
||||
window-menu-resize-edge-top = Amont
|
||||
window-menu-resize-edge-left = Esquèrra
|
||||
window-menu-resize-edge-right = Drecha
|
||||
window-menu-resize-edge-bottom = Aval
|
||||
0
resources/i18n/sat/cosmic_comp.ftl
Normal file
0
resources/i18n/sat/cosmic_comp.ftl
Normal file
|
|
@ -59,7 +59,7 @@ use std::{
|
|||
time::Duration,
|
||||
};
|
||||
|
||||
use super::{drm_helpers, socket::Socket, surface::Surface};
|
||||
use super::{drm_helpers, surface::Surface};
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct EGLInternals {
|
||||
|
|
@ -98,14 +98,12 @@ pub struct Device {
|
|||
|
||||
pub texture_formats: FormatSet,
|
||||
event_token: Option<RegistrationToken>,
|
||||
pub socket: Option<Socket>,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
struct ReusableDevice {
|
||||
leasing_global: Option<DrmLeaseState>,
|
||||
active_clients: HashSet<ClientId>,
|
||||
socket: Option<Socket>,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
|
|
@ -593,7 +591,7 @@ impl State {
|
|||
}
|
||||
}
|
||||
|
||||
pub fn device_removed(&mut self, dev: dev_t, dh: &DisplayHandle) -> Result<()> {
|
||||
pub fn device_removed(&mut self, dev: dev_t, _dh: &DisplayHandle) -> Result<()> {
|
||||
let backend = self.backend.kms();
|
||||
// we can't use DrmNode::from_node_id, because that assumes the node is still on sysfs
|
||||
let drm_node = backend
|
||||
|
|
@ -616,13 +614,6 @@ impl State {
|
|||
if let Some(token) = device.event_token.take() {
|
||||
self.common.event_loop_handle.remove(token);
|
||||
}
|
||||
if let Some(socket) = device.socket.take() {
|
||||
self.common.event_loop_handle.remove(socket.token);
|
||||
self.common
|
||||
.dmabuf_state
|
||||
.destroy_global::<State>(dh, socket.dmabuf_global);
|
||||
dh.remove_global::<State>(socket.drm_global);
|
||||
}
|
||||
backend.api.as_mut().remove_node(&device.inner.render_node);
|
||||
backend
|
||||
.primary_node
|
||||
|
|
@ -702,7 +693,7 @@ impl Device {
|
|||
)
|
||||
.with_context(|| {
|
||||
format!(
|
||||
"Failed to optain file descriptor for drm device: {}",
|
||||
"Failed to obtain file descriptor for drm device: {}",
|
||||
path.display()
|
||||
)
|
||||
})?,
|
||||
|
|
@ -755,23 +746,7 @@ impl Device {
|
|||
let ReusableDevice {
|
||||
leasing_global,
|
||||
active_clients,
|
||||
socket,
|
||||
} = reuse.unwrap_or_else(|| {
|
||||
let socket = match (!is_software)
|
||||
.then(|| common.create_socket(dh, render_node, texture_formats.clone()))
|
||||
.transpose()
|
||||
{
|
||||
Ok(socket) => socket,
|
||||
Err(err) => {
|
||||
warn!(
|
||||
?err,
|
||||
"Failed to initialize hardware-acceleration for clients on {}.",
|
||||
render_node,
|
||||
);
|
||||
None
|
||||
}
|
||||
};
|
||||
|
||||
let leasing_global = match (!is_software)
|
||||
.then(|| DrmLeaseState::new::<State>(dh, &dev_node))
|
||||
.transpose()
|
||||
|
|
@ -790,7 +765,6 @@ impl Device {
|
|||
ReusableDevice {
|
||||
leasing_global,
|
||||
active_clients: HashSet::new(),
|
||||
socket,
|
||||
}
|
||||
});
|
||||
|
||||
|
|
@ -831,7 +805,6 @@ impl Device {
|
|||
|
||||
texture_formats,
|
||||
event_token: Some(token),
|
||||
socket,
|
||||
})
|
||||
}
|
||||
|
||||
|
|
@ -892,7 +865,6 @@ impl Device {
|
|||
let device = ReusableDevice {
|
||||
leasing_global: self.inner.leasing_global,
|
||||
active_clients: self.inner.active_clients,
|
||||
socket: self.socket,
|
||||
};
|
||||
|
||||
let state = OldDeviceState {
|
||||
|
|
@ -1269,26 +1241,15 @@ fn populate_modes(
|
|||
) -> Result<()> {
|
||||
let conn_info = drm.get_connector(conn, false)?;
|
||||
let max_bpc = drm_helpers::get_max_bpc(drm, conn)?.map(|(_val, range)| range.end.min(16));
|
||||
let preferred = conn_info
|
||||
let Some(mode) = conn_info
|
||||
.modes()
|
||||
.iter()
|
||||
.find(|mode| mode.mode_type().contains(ModeTypeFlags::PREFERRED))
|
||||
.copied()
|
||||
.or(conn_info.modes().first().copied());
|
||||
let Some(preferred) = preferred else {
|
||||
.or(conn_info.modes().first().copied())
|
||||
else {
|
||||
anyhow::bail!("No mode found");
|
||||
};
|
||||
// Prefer the highest refresh rate at the native resolution so that
|
||||
// high-refresh panels (120 Hz, 144 Hz, …) are used out-of-the-box
|
||||
// without a pre-existing outputs.ron config.
|
||||
let preferred_size = preferred.size();
|
||||
let mode = conn_info
|
||||
.modes()
|
||||
.iter()
|
||||
.filter(|m| m.size() == preferred_size)
|
||||
.max_by_key(|m| drm_helpers::calculate_refresh_rate(**m))
|
||||
.copied()
|
||||
.unwrap_or(preferred);
|
||||
|
||||
let refresh_rate = drm_helpers::calculate_refresh_rate(mode);
|
||||
let output_mode = OutputMode {
|
||||
|
|
|
|||
|
|
@ -75,6 +75,7 @@ pub fn display_configuration(
|
|||
// And then cleanup
|
||||
if device.is_atomic() {
|
||||
let mut req = AtomicModeReq::new();
|
||||
let mut has_changes = false;
|
||||
let plane_handles = device.plane_handles()?;
|
||||
|
||||
// We cannot just shortcut and use the legacy api for all cleanups because of this.
|
||||
|
|
@ -95,10 +96,15 @@ pub fn display_configuration(
|
|||
let fb_id = get_prop(device, plane, "FB_ID")?;
|
||||
req.add_property(plane, crtc_id, property::Value::CRTC(None));
|
||||
req.add_property(plane, fb_id, property::Value::Framebuffer(None));
|
||||
has_changes = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
device.atomic_commit(AtomicCommitFlags::ALLOW_MODESET, req)?;
|
||||
// Skip an empty commit: a no-op modeset that also fails with EPERM
|
||||
// without DRM master (e.g. a render-only secondary GPU).
|
||||
if has_changes {
|
||||
device.atomic_commit(AtomicCommitFlags::ALLOW_MODESET, req)?;
|
||||
}
|
||||
} else {
|
||||
for crtc in res_handles.crtcs() {
|
||||
#[allow(deprecated)]
|
||||
|
|
|
|||
|
|
@ -4,12 +4,15 @@ use crate::{
|
|||
config::{CompOutputConfig, ScreenFilter},
|
||||
shell::Shell,
|
||||
state::BackendData,
|
||||
utils::{env::dev_var, prelude::*},
|
||||
wayland::protocols::output_power::OutputPowerState,
|
||||
utils::{env::dev_var, global::remove_global_with_timer, prelude::*},
|
||||
wayland::protocols::{drm::WlDrmState, output_power::OutputPowerState},
|
||||
};
|
||||
|
||||
use anyhow::{Context, Result};
|
||||
use calloop::LoopSignal;
|
||||
use anyhow::{self, Context, Result};
|
||||
use calloop::{
|
||||
LoopSignal,
|
||||
timer::{TimeoutAction, Timer},
|
||||
};
|
||||
use cosmic_comp_config::output::comp::{AdaptiveSync, OutputState};
|
||||
use indexmap::IndexMap;
|
||||
use render::gles::GbmGlowBackend;
|
||||
|
|
@ -29,14 +32,19 @@ use smithay::{
|
|||
calloop::{Dispatcher, EventLoop, LoopHandle},
|
||||
drm::{
|
||||
Device as _,
|
||||
control::{Device as _, connector::Interface, crtc},
|
||||
control::{
|
||||
Device as _,
|
||||
connector::{Interface, State as ConnectorState},
|
||||
crtc,
|
||||
},
|
||||
},
|
||||
input::{self, Libinput},
|
||||
wayland_protocols::wp::linux_dmabuf::zv1::server::zwp_linux_dmabuf_feedback_v1::TrancheFlags,
|
||||
wayland_server::{Client, DisplayHandle},
|
||||
},
|
||||
utils::{Clock, DevPath, Monotonic, Size},
|
||||
wayland::{
|
||||
dmabuf::DmabufGlobal,
|
||||
dmabuf::{DmabufFeedbackBuilder, DmabufGlobal},
|
||||
drm_syncobj::{DrmSyncobjState, supports_syncobj_eventfd},
|
||||
relative_pointer::RelativePointerManagerState,
|
||||
},
|
||||
|
|
@ -48,12 +56,12 @@ use std::{
|
|||
collections::{HashMap, HashSet},
|
||||
path::Path,
|
||||
sync::{Arc, RwLock, atomic::AtomicBool},
|
||||
time::Duration,
|
||||
};
|
||||
|
||||
mod device;
|
||||
mod drm_helpers;
|
||||
pub mod render;
|
||||
mod socket;
|
||||
mod surface;
|
||||
use device::*;
|
||||
pub(crate) use surface::Surface;
|
||||
|
|
@ -74,6 +82,7 @@ pub struct KmsState {
|
|||
libinput: Libinput,
|
||||
|
||||
pub syncobj_state: Option<DrmSyncobjState>,
|
||||
pub dmabuf_global: Option<DmabufGlobal>,
|
||||
}
|
||||
|
||||
pub struct KmsGuard<'a> {
|
||||
|
|
@ -131,6 +140,7 @@ pub fn init_backend(
|
|||
libinput: libinput_context,
|
||||
|
||||
syncobj_state: None,
|
||||
dmabuf_global: None,
|
||||
});
|
||||
|
||||
// manually add already present gpus
|
||||
|
|
@ -142,9 +152,10 @@ pub fn init_backend(
|
|||
}
|
||||
}
|
||||
|
||||
if let Err(err) = state.backend.kms().select_primary_gpu(dh) {
|
||||
if let Err(err) = state.select_primary_gpu(dh) {
|
||||
warn!("Failed to determine primary gpu: {}", err);
|
||||
}
|
||||
state.update_default_feedback();
|
||||
|
||||
if let Err(err) = state.refresh_output_config() {
|
||||
info!(
|
||||
|
|
@ -204,7 +215,7 @@ fn init_libinput(
|
|||
state.backend.kms().input_devices.remove(&*device.name());
|
||||
}
|
||||
|
||||
state.process_input_event(event);
|
||||
state.process_input_event(event, crate::input::InputBackendId::Normal);
|
||||
|
||||
for output in state.common.shell.read().outputs() {
|
||||
state.backend.kms().schedule_render(output);
|
||||
|
|
@ -243,13 +254,18 @@ fn determine_primary_gpu(
|
|||
|
||||
// try to find builtin display
|
||||
for dev in drm_devices.values() {
|
||||
if dev.inner.surfaces.values().any(|s| {
|
||||
if let Ok(conn_info) = dev.drm.device().get_connector(s.connector, false) {
|
||||
let i = conn_info.interface();
|
||||
i == Interface::EmbeddedDisplayPort || i == Interface::LVDS || i == Interface::DSI
|
||||
} else {
|
||||
false
|
||||
}
|
||||
let drm = dev.drm.device();
|
||||
let res_handles = drm.resource_handles()?;
|
||||
let connectors = res_handles.connectors();
|
||||
if connectors.iter().any(|conn| {
|
||||
let Ok(conn_info) = drm.get_connector(*conn, false) else {
|
||||
return false;
|
||||
};
|
||||
let i = conn_info.interface();
|
||||
conn_info.state() == ConnectorState::Connected
|
||||
&& (i == Interface::EmbeddedDisplayPort
|
||||
|| i == Interface::LVDS
|
||||
|| i == Interface::DSI)
|
||||
}) {
|
||||
return Ok(Some(dev.inner.render_node));
|
||||
}
|
||||
|
|
@ -317,11 +333,13 @@ fn init_udev(
|
|||
|
||||
{
|
||||
let backend = state.backend.kms();
|
||||
if matches!(event, UdevEvent::Added { .. } | UdevEvent::Removed { .. })
|
||||
&& backend.primary_node.read().unwrap().is_none()
|
||||
&& let Err(err) = state.backend.kms().select_primary_gpu(&dh)
|
||||
{
|
||||
warn!("Failed to determine a new primary gpu: {}", err);
|
||||
if matches!(event, UdevEvent::Added { .. } | UdevEvent::Removed { .. }) {
|
||||
if backend.primary_node.read().unwrap().is_none()
|
||||
&& let Err(err) = state.select_primary_gpu(&dh)
|
||||
{
|
||||
warn!("Failed to determine a new primary gpu: {}", err);
|
||||
}
|
||||
state.update_default_feedback();
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -465,50 +483,118 @@ impl State {
|
|||
device.drm.pause();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl KmsState {
|
||||
fn select_primary_gpu(&mut self, dh: &DisplayHandle) -> Result<()> {
|
||||
// We don't have to check the allow/blocklist here,
|
||||
// as any disallowed devices won't be in `self.drm_devices`.
|
||||
|
||||
let mut primary_node = self.primary_node.write().unwrap();
|
||||
let kms = self.backend.kms();
|
||||
let mut primary_node = kms.primary_node.write().unwrap();
|
||||
let _ = primary_node.take(); // if we error don't leave an old node in place
|
||||
*primary_node = determine_primary_gpu(&self.drm_devices, self.session.seat())?;
|
||||
*primary_node = determine_primary_gpu(&kms.drm_devices, kms.session.seat())?;
|
||||
|
||||
if let Some(node) = *primary_node {
|
||||
info!("Using {} as primary gpu for rendering.", node);
|
||||
self.software_renderer.take();
|
||||
} else if self.software_renderer.is_none() {
|
||||
kms.software_renderer.take();
|
||||
|
||||
// setup minimal feedback. We will update it in `update_default_feedback`
|
||||
let primary_formats = kms
|
||||
.drm_devices
|
||||
.values()
|
||||
.find(|dev| dev.inner.render_node == node)
|
||||
.unwrap()
|
||||
.texture_formats
|
||||
.clone();
|
||||
let feedback = DmabufFeedbackBuilder::new(node.dev_id(), primary_formats.clone())
|
||||
.build()
|
||||
.unwrap();
|
||||
|
||||
if let Some(global) = kms.dmabuf_global.as_ref() {
|
||||
self.common
|
||||
.dmabuf_state
|
||||
.set_default_feedback(global, &feedback);
|
||||
} else {
|
||||
let dmabuf_global = self
|
||||
.common
|
||||
.dmabuf_state
|
||||
.create_global_with_default_feedback::<State>(dh, &feedback);
|
||||
kms.dmabuf_global = Some(dmabuf_global);
|
||||
};
|
||||
|
||||
let device_path = node
|
||||
.dev_path_with_type(NodeType::Render)
|
||||
.or_else(|| node.dev_path())
|
||||
.ok_or(anyhow::anyhow!(
|
||||
"Could not determine path for gpu node: {}",
|
||||
node
|
||||
))?;
|
||||
|
||||
if let Some(drm) = self.common.wl_drm_state.as_mut() {
|
||||
drm.update_device(device_path, primary_formats);
|
||||
} else {
|
||||
self.common.wl_drm_state = Some(WlDrmState::new::<State>(
|
||||
dh,
|
||||
device_path,
|
||||
primary_formats,
|
||||
kms.dmabuf_global.as_ref().unwrap(),
|
||||
));
|
||||
}
|
||||
} else if kms.software_renderer.is_none() {
|
||||
info!("Failed to find a suitable gpu, using software renderingr");
|
||||
self.software_renderer = match software_renderer() {
|
||||
kms.software_renderer = match software_renderer() {
|
||||
Ok(renderer) => Some(renderer),
|
||||
Err(err) => {
|
||||
error!(?err, "Failed to initialize software EGL renderer.");
|
||||
None
|
||||
}
|
||||
};
|
||||
|
||||
if let Some(drm) = self.common.wl_drm_state.take() {
|
||||
remove_global_with_timer(dh, &self.common.event_loop_handle, drm.global().clone());
|
||||
}
|
||||
if let Some(global) = kms.dmabuf_global.take() {
|
||||
self.common
|
||||
.dmabuf_state
|
||||
.disable_global::<State>(dh, &global);
|
||||
let source = Timer::from_duration(Duration::from_secs(5));
|
||||
let res =
|
||||
self.common
|
||||
.event_loop_handle
|
||||
.insert_source(source, move |_, _, state| {
|
||||
state
|
||||
.common
|
||||
.dmabuf_state
|
||||
.destroy_global::<State>(&state.common.display_handle, global);
|
||||
TimeoutAction::Drop
|
||||
});
|
||||
if let Err(err) = res {
|
||||
tracing::error!(
|
||||
"failed to insert timer source to destroy output global: {}",
|
||||
err
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if !crate::utils::env::bool_var("COSMIC_DISABLE_SYNCOBJ").unwrap_or(false) {
|
||||
if let Some(primary_node) = primary_node
|
||||
.as_ref()
|
||||
.and_then(|node| node.node_with_type(NodeType::Primary).and_then(|x| x.ok()))
|
||||
&& let Some(device) = self.drm_devices.get(&primary_node)
|
||||
&& let Some(device) = kms.drm_devices.get(&primary_node)
|
||||
{
|
||||
let import_device = device.drm.device().device_fd().clone();
|
||||
if supports_syncobj_eventfd(&import_device) {
|
||||
if let Some(state) = self.syncobj_state.as_mut() {
|
||||
if let Some(state) = kms.syncobj_state.as_mut() {
|
||||
state.update_device(import_device);
|
||||
} else {
|
||||
let syncobj_state = DrmSyncobjState::new::<State>(dh, import_device);
|
||||
self.syncobj_state = Some(syncobj_state);
|
||||
kms.syncobj_state = Some(syncobj_state);
|
||||
}
|
||||
return Ok(());
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(old_state) = self.syncobj_state.take() {
|
||||
if let Some(old_state) = kms.syncobj_state.take() {
|
||||
dh.remove_global::<State>(old_state.into_global());
|
||||
}
|
||||
}
|
||||
|
|
@ -516,6 +602,45 @@ impl KmsState {
|
|||
Ok(())
|
||||
}
|
||||
|
||||
fn update_default_feedback(&mut self) {
|
||||
let kms = self.backend.kms();
|
||||
let primary_node = kms.primary_node.read().unwrap();
|
||||
if let Some(primary_node) = *primary_node {
|
||||
let primary_formats = kms
|
||||
.drm_devices
|
||||
.values()
|
||||
.find(|dev| dev.inner.render_node == primary_node)
|
||||
.unwrap()
|
||||
.texture_formats
|
||||
.clone();
|
||||
|
||||
let mut feedback =
|
||||
DmabufFeedbackBuilder::new(primary_node.dev_id(), primary_formats.clone());
|
||||
for dev in kms
|
||||
.drm_devices
|
||||
.values()
|
||||
.filter(|dev| dev.inner.render_node != primary_node)
|
||||
{
|
||||
feedback = feedback.add_preference_tranche(
|
||||
dev.inner.render_node.dev_id(),
|
||||
TrancheFlags::Sampling,
|
||||
dev.texture_formats.iter().cloned(),
|
||||
6..=6,
|
||||
);
|
||||
}
|
||||
|
||||
let default_feedback = feedback.build().unwrap();
|
||||
self.common.dmabuf_state.set_default_feedback(
|
||||
kms.dmabuf_global
|
||||
.as_ref()
|
||||
.expect("Primary node but no dmabuf global?"),
|
||||
&default_feedback,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl KmsState {
|
||||
pub fn switch_vt(&mut self, num: i32) -> Result<(), anyhow::Error> {
|
||||
self.session.change_vt(num).map_err(Into::into)
|
||||
}
|
||||
|
|
@ -523,29 +648,25 @@ impl KmsState {
|
|||
pub fn dmabuf_imported(
|
||||
&mut self,
|
||||
client: Option<Client>,
|
||||
global: &DmabufGlobal,
|
||||
_global: &DmabufGlobal,
|
||||
dmabuf: Dmabuf,
|
||||
) -> Result<DrmNode> {
|
||||
let device_node = dmabuf
|
||||
.node()
|
||||
.unwrap_or_else(|| self.primary_node.read().unwrap().unwrap());
|
||||
let mut device = self
|
||||
.drm_devices
|
||||
.values_mut()
|
||||
.find(|device| {
|
||||
device
|
||||
.socket
|
||||
.as_ref()
|
||||
.map(|s| &s.dmabuf_global == global)
|
||||
.unwrap_or(false)
|
||||
})
|
||||
.context("Couldn't find gpu for dmabuf global")?;
|
||||
.find(|dev| dev.inner.render_node == device_node)
|
||||
.ok_or(anyhow::anyhow!(
|
||||
"Unable to find device for node: {}",
|
||||
device_node
|
||||
))?;
|
||||
|
||||
// If device advertised to client doesn't support format/modifier, select
|
||||
// first device that does. This is needed for image-copy from
|
||||
// output/toplevel on a different node.
|
||||
//
|
||||
// TODO: After
|
||||
// https://gitlab.freedesktop.org/wayland/wayland-protocols/-/merge_requests/268,
|
||||
// only try the device specified explicitly by the client, if set.
|
||||
if !device.texture_formats.contains(&dmabuf.format()) {
|
||||
if dmabuf.node().is_none() && !device.texture_formats.contains(&dmabuf.format()) {
|
||||
device = self
|
||||
.drm_devices
|
||||
.values_mut()
|
||||
|
|
|
|||
|
|
@ -1006,12 +1006,16 @@ impl SurfaceThreadState {
|
|||
&self.shell.read(),
|
||||
);
|
||||
|
||||
// Acquiring a renderer can fail transiently when the underlying DRM
|
||||
// device is lost (e.g. after a GPU reset).
|
||||
let mut renderer = if render_node != self.target_node {
|
||||
self.api
|
||||
.renderer(&render_node, &self.target_node, compositor.format())
|
||||
.unwrap()
|
||||
.map_err(|err| anyhow::format_err!("Failed to create renderer: {:?}", err))?
|
||||
} else {
|
||||
self.api.single_renderer(&self.target_node).unwrap()
|
||||
self.api
|
||||
.single_renderer(&self.target_node)
|
||||
.map_err(|err| anyhow::format_err!("Failed to create renderer: {:?}", err))?
|
||||
};
|
||||
|
||||
self.timings.start_render(&self.clock);
|
||||
|
|
@ -1267,7 +1271,10 @@ impl SurfaceThreadState {
|
|||
})
|
||||
.context("Failed to draw to offscreen render target")?;
|
||||
|
||||
renderer = self.api.single_renderer(&self.target_node).unwrap();
|
||||
renderer = self
|
||||
.api
|
||||
.single_renderer(&self.target_node)
|
||||
.map_err(|err| anyhow::format_err!("Failed to create renderer: {:?}", err))?;
|
||||
|
||||
elements = postprocess_elements(
|
||||
&mut renderer,
|
||||
|
|
@ -1522,7 +1529,7 @@ fn get_surface_dmabuf_feedback(
|
|||
render_node: DrmNode,
|
||||
target_node: DrmNode,
|
||||
render_formats: FormatSet,
|
||||
_target_formats: FormatSet,
|
||||
target_formats: FormatSet,
|
||||
primary_plane_formats: FormatSet,
|
||||
overlay_plane_formats: Option<FormatSet>,
|
||||
) -> SurfaceDmabufFeedback {
|
||||
|
|
@ -1540,60 +1547,40 @@ fn get_surface_dmabuf_feedback(
|
|||
.cloned()
|
||||
.collect::<FormatSet>()
|
||||
});
|
||||
let builder = DmabufFeedbackBuilder::new(render_node.dev_id(), render_formats);
|
||||
|
||||
/*
|
||||
// Sadly no implementation would pick this up as a preferred render tranche,
|
||||
// where the combined formats would increase our chances of doing a dmabuf copy.
|
||||
// .. So we should probably not advertise this on the off-chance it actually triggers bugs.
|
||||
//
|
||||
let mut builder = DmabufFeedbackBuilder::new(render_node.dev_id(), render_formats.clone());
|
||||
|
||||
let combined_formats = render_formats.intersection(&target_formats).cloned().collect::<FormatSet>();
|
||||
if target_node != render_node.dev_id() && !combined_formats.is_empty() {
|
||||
builder = builder.add_preference_tranche(
|
||||
render_node.dev_id(),
|
||||
None,
|
||||
combined_formats,
|
||||
);
|
||||
};
|
||||
|
||||
// We also can't advertise scan out tranches for the actual display device,
|
||||
// as e.g. the nvidia driver might then send us dmabufs, that makes e.g. the iris hangs on import...
|
||||
if target_node != render_node.dev_id() && !combined_formats.is_empty() {
|
||||
if target_node != render_node {
|
||||
builder = builder.add_preference_tranche(
|
||||
target_node.dev_id(),
|
||||
Some(zwp_linux_dmabuf_feedback_v1::TrancheFlags::Scanout),
|
||||
combined_formats,
|
||||
zwp_linux_dmabuf_feedback_v1::TrancheFlags::Sampling,
|
||||
target_formats,
|
||||
6..=6,
|
||||
);
|
||||
};
|
||||
|
||||
// So no fun combinations, we gotta wait for dmabuf-v6
|
||||
*/
|
||||
|
||||
let render_feedback = builder.clone().build().unwrap();
|
||||
let primary_scanout_feedback = (target_node == render_node).then(|| {
|
||||
|
||||
let primary_scanout_feedback = builder
|
||||
.clone()
|
||||
.add_preference_tranche(
|
||||
target_node.dev_id(),
|
||||
zwp_linux_dmabuf_feedback_v1::TrancheFlags::Scanout,
|
||||
primary_plane_formats,
|
||||
4..=6,
|
||||
)
|
||||
.build()
|
||||
.unwrap();
|
||||
let overlay_scanout_feedback = overlay_plane_formats.map(|formats| {
|
||||
builder
|
||||
.clone()
|
||||
.add_preference_tranche(
|
||||
render_node.dev_id(),
|
||||
Some(zwp_linux_dmabuf_feedback_v1::TrancheFlags::Scanout),
|
||||
primary_plane_formats,
|
||||
target_node.dev_id(),
|
||||
zwp_linux_dmabuf_feedback_v1::TrancheFlags::Scanout,
|
||||
formats,
|
||||
4..=6,
|
||||
)
|
||||
.build()
|
||||
.unwrap()
|
||||
});
|
||||
let overlay_scanout_feedback = overlay_plane_formats
|
||||
.filter(|_| target_node == render_node)
|
||||
.map(|formats| {
|
||||
builder
|
||||
.add_preference_tranche(
|
||||
render_node.dev_id(),
|
||||
Some(zwp_linux_dmabuf_feedback_v1::TrancheFlags::Scanout),
|
||||
formats,
|
||||
)
|
||||
.build()
|
||||
.unwrap()
|
||||
});
|
||||
|
||||
SurfaceDmabufFeedback {
|
||||
render_feedback,
|
||||
|
|
|
|||
|
|
@ -8,6 +8,9 @@ use crate::{
|
|||
utils::prelude::*,
|
||||
wayland::handlers::compositor::FRAME_TIME_FILTER,
|
||||
};
|
||||
use keyframe::{ease, functions::EaseInOutCubic};
|
||||
use resvg::{tiny_skia, usvg};
|
||||
use serde::Deserialize;
|
||||
use smithay::{
|
||||
backend::{
|
||||
allocator::Fourcc,
|
||||
|
|
@ -38,7 +41,7 @@ use smithay::{
|
|||
wayland::compositor::{get_role, with_states},
|
||||
};
|
||||
use std::{
|
||||
collections::HashMap,
|
||||
collections::{HashMap, VecDeque},
|
||||
io::Read,
|
||||
sync::Mutex,
|
||||
time::{Duration, Instant},
|
||||
|
|
@ -51,14 +54,43 @@ use xcursor::{
|
|||
|
||||
static FALLBACK_CURSOR_DATA: &[u8] = include_bytes!("../../../resources/cursor.rgba");
|
||||
|
||||
/// A single frame of a scalable SVG cursor: the parsed SVG plus its metadata.
|
||||
#[derive(Debug, Clone)]
|
||||
struct SvgFrame {
|
||||
tree: usvg::Tree,
|
||||
/// The nominal (logical) size the SVG is authored for.
|
||||
nominal_size: f32,
|
||||
/// Hotspot coordinates, in the SVG's own (nominal) coordinate space.
|
||||
hotspot_x: f32,
|
||||
hotspot_y: f32,
|
||||
/// Delay to the next frame in milliseconds (0 for static cursors).
|
||||
delay: u32,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
enum CursorKind {
|
||||
/// Legacy raster XCursor frames.
|
||||
Xcursor(Vec<Image>),
|
||||
/// Scalable SVG frames, rasterized on demand.
|
||||
Svg(Vec<SvgFrame>),
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct Cursor {
|
||||
icons: Vec<Image>,
|
||||
kind: CursorKind,
|
||||
size: u32,
|
||||
}
|
||||
|
||||
impl Cursor {
|
||||
pub fn load(theme: &CursorTheme, shape: CursorIcon, size: u32) -> Cursor {
|
||||
// Prefer a scalable SVG cursor when the theme provides one.
|
||||
if let Some(frames) = load_svg_icon(theme, shape) {
|
||||
return Cursor {
|
||||
kind: CursorKind::Svg(frames),
|
||||
size,
|
||||
};
|
||||
}
|
||||
|
||||
let icons = load_icon(theme, shape)
|
||||
.map_err(|err| warn!(?err, "Unable to load xcursor, using fallback cursor"))
|
||||
.or_else(|_| load_icon(theme, CursorIcon::Default))
|
||||
|
|
@ -75,44 +107,134 @@ impl Cursor {
|
|||
}]
|
||||
});
|
||||
|
||||
Cursor { icons, size }
|
||||
Cursor {
|
||||
kind: CursorKind::Xcursor(icons),
|
||||
size,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_image(&self, scale: u32, millis: u32) -> Image {
|
||||
let size = self.size * scale;
|
||||
frame(millis, size, &self.icons)
|
||||
let idx = self.frame_index(size, millis);
|
||||
self.render_frame(size, idx)
|
||||
}
|
||||
|
||||
/// Selects the index of the frame to display at nominal size `size` (in px)
|
||||
/// and elapsed `millis`.
|
||||
fn frame_index(&self, size: u32, millis: u32) -> usize {
|
||||
match &self.kind {
|
||||
CursorKind::Xcursor(images) => xcursor_frame_index(millis, size, images),
|
||||
CursorKind::Svg(frames) => svg_frame_index(millis, frames),
|
||||
}
|
||||
}
|
||||
|
||||
/// Produces the RGBA image for frame `idx`, rasterizing SVG cursors at the
|
||||
/// requested nominal size `size` (in px).
|
||||
fn render_frame(&self, size: u32, idx: usize) -> Image {
|
||||
match &self.kind {
|
||||
CursorKind::Xcursor(images) => images[idx].clone(),
|
||||
CursorKind::Svg(frames) => rasterize_svg_frame(&frames[idx], size),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn nearest_images(size: u32, images: &[Image]) -> impl Iterator<Item = &Image> {
|
||||
// Follow the nominal size of the cursor to choose the nearest
|
||||
/// Rasterize a scalable cursor frame at nominal pixel size `size`. Per the KDE
|
||||
/// SVG cursor format, the SVG canvas and hotspot are scaled by
|
||||
/// `size / nominal_size`. The returned [`Image`] uses the same premultiplied
|
||||
/// byte order (BGRA / little-endian ARGB) as xcursor images.
|
||||
fn rasterize_svg_frame(frame: &SvgFrame, size: u32) -> Image {
|
||||
let factor = size as f32 / frame.nominal_size;
|
||||
let svg_size = frame.tree.size();
|
||||
let width = ((svg_size.width() * factor).floor() as u32).max(1);
|
||||
let height = ((svg_size.height() * factor).floor() as u32).max(1);
|
||||
|
||||
let mut image = Image {
|
||||
size,
|
||||
width,
|
||||
height,
|
||||
xhot: (frame.hotspot_x * factor).floor() as u32,
|
||||
yhot: (frame.hotspot_y * factor).floor() as u32,
|
||||
delay: frame.delay,
|
||||
pixels_rgba: Vec::new(),
|
||||
pixels_argb: Vec::new(), // unused
|
||||
};
|
||||
|
||||
match tiny_skia::Pixmap::new(width, height) {
|
||||
Some(mut pixmap) => {
|
||||
resvg::render(
|
||||
&frame.tree,
|
||||
tiny_skia::Transform::from_scale(factor, factor),
|
||||
&mut pixmap.as_mut(),
|
||||
);
|
||||
// tiny-skia produces premultiplied RGBA; xcursor images consume the
|
||||
// raw little-endian ARGB byte order (premultiplied BGRA). Swap R<->B.
|
||||
let mut pixels = pixmap.take();
|
||||
for px in pixels.as_chunks_mut::<4>().0 {
|
||||
px.swap(0, 2);
|
||||
}
|
||||
image.pixels_rgba = pixels;
|
||||
}
|
||||
None => {
|
||||
warn!(width, height, "Failed to allocate cursor pixmap");
|
||||
image.pixels_rgba = vec![0; (width as usize) * (height as usize) * 4];
|
||||
}
|
||||
}
|
||||
|
||||
image
|
||||
}
|
||||
|
||||
/// Indices (into `images`) of all frames sharing the resolution nearest to `size`.
|
||||
fn nearest_image_indices(size: u32, images: &[Image]) -> Vec<usize> {
|
||||
// Follow the nominal size of the cursor to choose the nearest.
|
||||
let nearest_image = images
|
||||
.iter()
|
||||
.min_by_key(|image| u32::abs_diff(size, image.size))
|
||||
.unwrap();
|
||||
let (width, height) = (nearest_image.width, nearest_image.height);
|
||||
|
||||
images.iter().filter(move |image| {
|
||||
image.width == nearest_image.width && image.height == nearest_image.height
|
||||
})
|
||||
images
|
||||
.iter()
|
||||
.enumerate()
|
||||
.filter(|(_, image)| image.width == width && image.height == height)
|
||||
.map(|(i, _)| i)
|
||||
.collect()
|
||||
}
|
||||
|
||||
fn frame(mut millis: u32, size: u32, images: &[Image]) -> Image {
|
||||
let total = nearest_images(size, images).fold(0, |acc, image| acc + image.delay);
|
||||
fn xcursor_frame_index(mut millis: u32, size: u32, images: &[Image]) -> usize {
|
||||
let indices = nearest_image_indices(size, images);
|
||||
let total: u32 = indices.iter().map(|&i| images[i].delay).sum();
|
||||
|
||||
if total == 0 {
|
||||
millis = 0;
|
||||
} else {
|
||||
millis %= total;
|
||||
return indices[0];
|
||||
}
|
||||
millis %= total;
|
||||
|
||||
for img in nearest_images(size, images) {
|
||||
if millis <= img.delay {
|
||||
return img.clone();
|
||||
for &i in &indices {
|
||||
if millis <= images[i].delay {
|
||||
return i;
|
||||
}
|
||||
millis -= img.delay;
|
||||
millis -= images[i].delay;
|
||||
}
|
||||
|
||||
unreachable!()
|
||||
*indices.last().unwrap()
|
||||
}
|
||||
|
||||
fn svg_frame_index(mut millis: u32, frames: &[SvgFrame]) -> usize {
|
||||
let total: u32 = frames.iter().map(|frame| frame.delay).sum();
|
||||
|
||||
if total == 0 {
|
||||
return 0;
|
||||
}
|
||||
millis %= total;
|
||||
|
||||
for (i, frame) in frames.iter().enumerate() {
|
||||
if millis <= frame.delay {
|
||||
return i;
|
||||
}
|
||||
millis -= frame.delay;
|
||||
}
|
||||
|
||||
frames.len() - 1
|
||||
}
|
||||
|
||||
#[derive(thiserror::Error, Debug)]
|
||||
|
|
@ -174,6 +296,79 @@ fn load_icon(theme: &CursorTheme, shape: CursorIcon) -> Result<Vec<Image>, Error
|
|||
Err(Error::NoDefaultCursor)
|
||||
}
|
||||
|
||||
/// A frame entry in a `cursors_scalable/<shape>/metadata.json` file, per the
|
||||
/// KDE SVG cursor format specification.
|
||||
#[derive(Debug, Clone, Deserialize)]
|
||||
struct SvgCursorMeta {
|
||||
filename: String,
|
||||
nominal_size: f32,
|
||||
hotspot_x: f32,
|
||||
hotspot_y: f32,
|
||||
/// Only present for animated cursors; defaults to 0 for static ones.
|
||||
#[serde(default)]
|
||||
delay: u32,
|
||||
}
|
||||
|
||||
/// Resolves a scalable (SVG) cursor for `shape`
|
||||
fn load_svg_icon(theme: &CursorTheme, shape: CursorIcon) -> Option<Vec<SvgFrame>> {
|
||||
let shape_name = shape.to_string();
|
||||
let options = usvg::Options::default();
|
||||
for name in cursor_aliases(&shape_name)
|
||||
.iter()
|
||||
.copied()
|
||||
.chain(std::iter::once(shape_name.as_str()))
|
||||
{
|
||||
if let Some(dir) = theme.load_scalable(name)
|
||||
&& let Some(frames) = parse_svg_dir(&dir, &options)
|
||||
{
|
||||
return Some(frames);
|
||||
}
|
||||
}
|
||||
|
||||
None
|
||||
}
|
||||
|
||||
/// Reads a `cursors_scalable/<shape>` directory (its `metadata.json` and the
|
||||
/// referenced SVG files) into a list of parsed frames.
|
||||
fn parse_svg_dir(dir: &std::path::Path, options: &usvg::Options) -> Option<Vec<SvgFrame>> {
|
||||
let metadata = std::fs::read(dir.join("metadata.json")).ok()?;
|
||||
let metas: Vec<SvgCursorMeta> = match serde_json::from_slice(&metadata) {
|
||||
Ok(metas) => metas,
|
||||
Err(err) => {
|
||||
warn!(?dir, ?err, "Malformed SVG cursor metadata");
|
||||
return None;
|
||||
}
|
||||
};
|
||||
|
||||
let mut frames = Vec::with_capacity(metas.len());
|
||||
for meta in metas {
|
||||
let svg_path = dir.join(&meta.filename);
|
||||
let svg_data = match std::fs::read(&svg_path) {
|
||||
Ok(data) => data,
|
||||
Err(err) => {
|
||||
warn!(?svg_path, ?err, "Unable to read SVG cursor");
|
||||
break;
|
||||
}
|
||||
};
|
||||
let tree = match usvg::Tree::from_data(&svg_data, options) {
|
||||
Ok(tree) => tree,
|
||||
Err(err) => {
|
||||
warn!(?svg_path, ?err, "Unable to parse SVG cursor");
|
||||
break;
|
||||
}
|
||||
};
|
||||
frames.push(SvgFrame {
|
||||
tree,
|
||||
nominal_size: meta.nominal_size,
|
||||
hotspot_x: meta.hotspot_x,
|
||||
hotspot_y: meta.hotspot_y,
|
||||
delay: meta.delay,
|
||||
});
|
||||
}
|
||||
|
||||
(!frames.is_empty()).then_some(frames)
|
||||
}
|
||||
|
||||
render_elements! {
|
||||
pub CursorRenderElement<R> where R: ImportAll + ImportMem + AsGlowRenderer, R::TextureId: Send;
|
||||
Static=MemoryRenderBufferRenderElement<R>,
|
||||
|
|
@ -265,11 +460,62 @@ pub struct CursorStateInner {
|
|||
|
||||
cursors: HashMap<CursorIcon, Cursor>,
|
||||
current_image: Option<Image>,
|
||||
image_cache: Vec<(Image, MemoryRenderBuffer)>,
|
||||
image_cache: Vec<CachedFrame>,
|
||||
|
||||
hidden: bool,
|
||||
idle_timer: Option<RegistrationToken>,
|
||||
last_armed: Option<Instant>,
|
||||
|
||||
// shake-to-find
|
||||
shake_path: VecDeque<PathSample>,
|
||||
shake_path_position: Point<f64, Logical>,
|
||||
magnify_until: Option<Instant>,
|
||||
magnify_target: f32,
|
||||
magnification: f32,
|
||||
anim_from: f32,
|
||||
anim_start: Option<Instant>,
|
||||
rest_started: Option<Instant>,
|
||||
}
|
||||
|
||||
/// A rasterized cursor frame, keyed by `(shape, pixel size, frame index)`.
|
||||
struct CachedFrame {
|
||||
key: (CursorIcon, u32, usize),
|
||||
image: Image,
|
||||
buffer: MemoryRenderBuffer,
|
||||
unmagnified: bool,
|
||||
}
|
||||
|
||||
/// One sampled pointer position on the recent motion path.
|
||||
#[derive(Clone, Copy)]
|
||||
struct PathSample {
|
||||
position: Point<f64, Logical>,
|
||||
time: Instant,
|
||||
}
|
||||
|
||||
/// How long everything must stay unmagnified before the enlarged frames go.
|
||||
const MAGNIFIED_FRAME_GRACE: Duration = Duration::from_secs(10);
|
||||
|
||||
/// How far back the motion path is considered when looking for a shake.
|
||||
const SHAKE_INTERVAL: Duration = Duration::from_millis(1000);
|
||||
/// Path-length / bounding-box-diagonal ratio required to count as a shake.
|
||||
const SHAKE_SENSITIVITY: f64 = 4.0;
|
||||
/// Minimum bounding-box diagonal (logical px) before a shake is considered.
|
||||
const SHAKE_DIAGONAL_MIN: f64 = 100.0;
|
||||
/// Two deltas count as "the same direction" if both lie within this tolerance.
|
||||
const SHAKE_SAME_SIGN_TOLERANCE: f64 = 1.0;
|
||||
/// Keep the cursor enlarged for this long after the last detected shake.
|
||||
const SHAKE_HOLD: Duration = Duration::from_millis(2000);
|
||||
/// Extra magnification added by each shake, growing from the normal cursor size.
|
||||
const OVER_MAGNIFICATION: f32 = 1.0;
|
||||
/// Upper bound on the nominal size (in px) a cursor frame is rasterized at.
|
||||
const MAX_RASTER_SIZE: u32 = 512;
|
||||
/// Duration of the grow/shrink animation.
|
||||
const MAGNIFICATION_ANIM: Duration = Duration::from_millis(200);
|
||||
|
||||
/// small movement is ignored and direction stays the same
|
||||
fn same_direction(a: f64, b: f64) -> bool {
|
||||
(a >= -SHAKE_SAME_SIGN_TOLERANCE && b >= -SHAKE_SAME_SIGN_TOLERANCE)
|
||||
|| (a <= SHAKE_SAME_SIGN_TOLERANCE && b <= SHAKE_SAME_SIGN_TOLERANCE)
|
||||
}
|
||||
|
||||
impl CursorStateInner {
|
||||
|
|
@ -282,14 +528,141 @@ impl CursorStateInner {
|
|||
}
|
||||
|
||||
pub fn get_named_cursor(&mut self, shape: CursorIcon) -> &Cursor {
|
||||
let cursor_theme = &self.cursor_theme;
|
||||
let cursor_size = self.cursor_size;
|
||||
self.cursors
|
||||
.entry(shape)
|
||||
.or_insert_with(|| Cursor::load(&self.cursor_theme, shape, self.cursor_size))
|
||||
.or_insert_with(|| Cursor::load(cursor_theme, shape, cursor_size))
|
||||
}
|
||||
|
||||
pub fn size(&self) -> u32 {
|
||||
self.cursor_size
|
||||
}
|
||||
|
||||
/// Drop the rasterizations only a magnified cursor needed, once nothing has
|
||||
/// magnified it for [`MAGNIFIED_FRAME_GRACE`].
|
||||
pub fn drop_magnified_frames(&mut self, now: Instant, zoomed: bool) {
|
||||
if zoomed || self.is_magnifying() {
|
||||
self.rest_started = None;
|
||||
return;
|
||||
}
|
||||
let rest_started = *self.rest_started.get_or_insert(now);
|
||||
if now.duration_since(rest_started) < MAGNIFIED_FRAME_GRACE {
|
||||
return;
|
||||
}
|
||||
|
||||
self.image_cache.retain(|frame| frame.unmagnified);
|
||||
}
|
||||
|
||||
/// Feed one relative-motion event into the shake detector.
|
||||
pub fn detect_shake(&mut self, delta: Point<f64, Logical>, now: Instant) {
|
||||
// Drop samples that have aged out of the time window.
|
||||
while let Some(oldest) = self.shake_path.front() {
|
||||
if now.duration_since(oldest.time) >= SHAKE_INTERVAL {
|
||||
self.shake_path.pop_front();
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if delta.x != 0.0 || delta.y != 0.0 {
|
||||
self.shake_path_position += delta;
|
||||
let sample = PathSample {
|
||||
position: self.shake_path_position,
|
||||
time: now,
|
||||
};
|
||||
|
||||
if self.shake_path.len() >= 2 {
|
||||
let last = self.shake_path[self.shake_path.len() - 1].position;
|
||||
let prev = self.shake_path[self.shake_path.len() - 2].position;
|
||||
let last_delta = last - prev;
|
||||
if same_direction(last_delta.x, delta.x) && same_direction(last_delta.y, delta.y) {
|
||||
*self.shake_path.back_mut().unwrap() = sample;
|
||||
} else {
|
||||
self.shake_path.push_back(sample);
|
||||
}
|
||||
} else {
|
||||
self.shake_path.push_back(sample);
|
||||
}
|
||||
}
|
||||
|
||||
if self.shake_path.len() < 2 {
|
||||
return;
|
||||
}
|
||||
|
||||
let first = self.shake_path[0].position;
|
||||
let (mut left, mut top, mut right, mut bottom) = (first.x, first.y, first.x, first.y);
|
||||
let mut path_length = 0.0;
|
||||
for i in 1..self.shake_path.len() {
|
||||
let p = self.shake_path[i].position;
|
||||
left = left.min(p.x);
|
||||
top = top.min(p.y);
|
||||
right = right.max(p.x);
|
||||
bottom = bottom.max(p.y);
|
||||
|
||||
let step = p - self.shake_path[i - 1].position;
|
||||
path_length += step.x.hypot(step.y);
|
||||
}
|
||||
|
||||
let diagonal = (right - left).hypot(bottom - top);
|
||||
if diagonal < SHAKE_DIAGONAL_MIN {
|
||||
return;
|
||||
}
|
||||
|
||||
// Path noticeably longer than the diagonal => a shake gesture.
|
||||
if path_length / diagonal > SHAKE_SENSITIVITY {
|
||||
self.grow(now);
|
||||
self.shake_path.clear();
|
||||
}
|
||||
}
|
||||
|
||||
/// grow the cursor by one more increment (unbounded)
|
||||
fn grow(&mut self, now: Instant) {
|
||||
self.animate_to(self.magnify_target + OVER_MAGNIFICATION, now);
|
||||
self.magnify_until = Some(now + SHAKE_HOLD);
|
||||
}
|
||||
|
||||
/// Start a 200ms `InOutCubic` tween from the current size to `target`.
|
||||
fn animate_to(&mut self, target: f32, now: Instant) {
|
||||
if (target - self.magnify_target).abs() < f32::EPSILON {
|
||||
return;
|
||||
}
|
||||
self.anim_from = self.magnification;
|
||||
self.anim_start = Some(now);
|
||||
self.magnify_target = target;
|
||||
}
|
||||
|
||||
/// Advance the magnification animation and return the current factor.
|
||||
pub fn animated_magnification(&mut self, now: Instant) -> f32 {
|
||||
// Begin shrinking back once the hold window elapses.
|
||||
if let Some(until) = self.magnify_until
|
||||
&& now >= until
|
||||
{
|
||||
self.magnify_until = None;
|
||||
self.animate_to(1.0, now);
|
||||
}
|
||||
|
||||
self.magnification = match self.anim_start {
|
||||
Some(start) => {
|
||||
// `ease` clamps the time to `0.0..=1.0` for us.
|
||||
let t = now.duration_since(start).as_secs_f32() / MAGNIFICATION_ANIM.as_secs_f32();
|
||||
if t >= 1.0 {
|
||||
self.anim_start = None;
|
||||
}
|
||||
ease(EaseInOutCubic, self.anim_from, self.magnify_target, t)
|
||||
}
|
||||
None => self.magnify_target,
|
||||
};
|
||||
self.magnification
|
||||
}
|
||||
|
||||
/// Whether the cursor is currently magnified or pending; drives continued redraws.
|
||||
pub fn is_magnifying(&self) -> bool {
|
||||
self.magnify_until.is_some()
|
||||
|| self.anim_start.is_some()
|
||||
|| self.magnification > 1.001
|
||||
|| self.magnify_target > 1.001
|
||||
}
|
||||
}
|
||||
|
||||
pub fn load_cursor_env() -> (String, u32) {
|
||||
|
|
@ -324,10 +697,34 @@ impl Default for CursorStateInner {
|
|||
hidden: false,
|
||||
idle_timer: None,
|
||||
last_armed: None,
|
||||
|
||||
shake_path: VecDeque::new(),
|
||||
shake_path_position: Point::from((0.0, 0.0)),
|
||||
magnify_until: None,
|
||||
magnify_target: 1.0,
|
||||
magnification: 1.0,
|
||||
anim_from: 1.0,
|
||||
anim_start: None,
|
||||
rest_started: None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Pick the size a cursor frame is rasterized at, given the size the output wants
|
||||
/// (`needed`) and the size it would want unmagnified (`base`).
|
||||
///
|
||||
/// Rasterizations are restricted to `base * 2^n`, rounded up, and clamped to
|
||||
/// [`MAX_RASTER_SIZE`].
|
||||
fn raster_size(needed: u32, base: u32) -> u32 {
|
||||
let base = base.max(1);
|
||||
let cap = MAX_RASTER_SIZE.max(base);
|
||||
let mut rung = base;
|
||||
while rung < needed && rung.saturating_mul(2) <= cap {
|
||||
rung *= 2;
|
||||
}
|
||||
rung
|
||||
}
|
||||
|
||||
#[profiling::function]
|
||||
pub fn draw_cursor<R>(
|
||||
renderer: &mut R,
|
||||
|
|
@ -363,32 +760,53 @@ pub fn draw_cursor<R>(
|
|||
return;
|
||||
}
|
||||
|
||||
let integer_scale = (scale.x.max(scale.y) * buffer_scale).ceil() as u32;
|
||||
let frame = state
|
||||
.get_named_cursor(current_cursor)
|
||||
.get_image(integer_scale, time.as_millis());
|
||||
let actual_scale = (frame.size / state.size()).max(1);
|
||||
let output_scale = scale.x.max(scale.y);
|
||||
let integer_scale = (output_scale * buffer_scale).ceil() as u32;
|
||||
let unmagnified_px = state.size() * (output_scale.ceil() as u32);
|
||||
let size_px = raster_size(state.size() * integer_scale, unmagnified_px);
|
||||
|
||||
let pointer_images = &mut state.image_cache;
|
||||
let maybe_image = pointer_images
|
||||
.iter()
|
||||
.find_map(|(image, texture)| if image == &frame { Some(texture) } else { None });
|
||||
let pointer_image = match maybe_image {
|
||||
Some(image) => image,
|
||||
// Pick the frame to display without rasterizing, so a cache hit avoids
|
||||
// any SVG rendering. The `&Cursor` borrow is scoped to this block.
|
||||
let frame_idx = {
|
||||
let cursor = state.get_named_cursor(current_cursor);
|
||||
cursor.frame_index(size_px, time.as_millis())
|
||||
};
|
||||
let key = (current_cursor, size_px, frame_idx);
|
||||
|
||||
// Rasterize and upload this (shape, size, frame) only if not cached.
|
||||
let index = match state.image_cache.iter().position(|frame| frame.key == key) {
|
||||
Some(index) => index,
|
||||
None => {
|
||||
let image = {
|
||||
let cursor = state.get_named_cursor(current_cursor);
|
||||
cursor.render_frame(size_px, frame_idx)
|
||||
};
|
||||
let actual_scale = (image.size / state.size()).max(1);
|
||||
let buffer = MemoryRenderBuffer::from_slice(
|
||||
&frame.pixels_rgba,
|
||||
&image.pixels_rgba,
|
||||
Fourcc::Argb8888,
|
||||
(frame.width as i32, frame.height as i32),
|
||||
(image.width as i32, image.height as i32),
|
||||
actual_scale as i32,
|
||||
Transform::Normal,
|
||||
None,
|
||||
);
|
||||
pointer_images.push((frame.clone(), buffer));
|
||||
pointer_images.last().map(|(_, i)| i).unwrap()
|
||||
state.image_cache.push(CachedFrame {
|
||||
key,
|
||||
image,
|
||||
buffer,
|
||||
unmagnified: size_px == unmagnified_px,
|
||||
});
|
||||
state.image_cache.len() - 1
|
||||
}
|
||||
};
|
||||
|
||||
let (frame, pointer_image) = {
|
||||
let entry = &mut state.image_cache[index];
|
||||
entry.unmagnified |= size_px == unmagnified_px;
|
||||
(entry.image.clone(), entry.buffer.clone())
|
||||
};
|
||||
let actual_scale = (frame.size / state.size()).max(1);
|
||||
|
||||
let hotspot = Point::<i32, BufferCoords>::from((frame.xhot as i32, frame.yhot as i32))
|
||||
.to_logical(
|
||||
actual_scale as i32,
|
||||
|
|
@ -402,7 +820,7 @@ pub fn draw_cursor<R>(
|
|||
MemoryRenderBufferRenderElement::from_buffer(
|
||||
renderer,
|
||||
location.to_physical(scale),
|
||||
pointer_image,
|
||||
&pointer_image,
|
||||
None,
|
||||
None,
|
||||
None,
|
||||
|
|
|
|||
|
|
@ -41,7 +41,9 @@ where
|
|||
Workspace(
|
||||
RelocateRenderElement<CropRenderElement<RescaleRenderElement<WorkspaceRenderElement<R>>>>,
|
||||
),
|
||||
Cursor(RescaleRenderElement<RelocateRenderElement<CursorRenderElement<R>>>),
|
||||
Cursor(
|
||||
RescaleRenderElement<RescaleRenderElement<RelocateRenderElement<CursorRenderElement<R>>>>,
|
||||
),
|
||||
Dnd(SurfaceRenderElement<R>),
|
||||
MoveGrab(RescaleRenderElement<CosmicMappedRenderElement<R>>),
|
||||
Postprocess(
|
||||
|
|
|
|||
|
|
@ -286,10 +286,10 @@ impl IndicatorShader {
|
|||
Uniform::new(
|
||||
"radius",
|
||||
[
|
||||
outer_radius[3] as f32,
|
||||
outer_radius[1] as f32,
|
||||
outer_radius[0] as f32,
|
||||
outer_radius[1] as f32,
|
||||
outer_radius[2] as f32,
|
||||
outer_radius[3] as f32,
|
||||
],
|
||||
),
|
||||
Uniform::new("scale", scale as f32),
|
||||
|
|
@ -510,22 +510,35 @@ pub fn cursor_elements<'a, 'frame, R>(
|
|||
};
|
||||
let location = pointer.current_location() - output.current_location().to_f64();
|
||||
|
||||
// Shake-to-find magnification, applied around the pointer tip.
|
||||
let cursor_magnification = seat
|
||||
.user_data()
|
||||
.get::<cursor::CursorState>()
|
||||
.map_or(1.0, |s| {
|
||||
s.lock().unwrap().animated_magnification(Instant::now())
|
||||
});
|
||||
let cursor_center = location.to_physical(scale).to_i32_round();
|
||||
|
||||
if mode != CursorMode::None {
|
||||
cursor::draw_cursor(
|
||||
renderer,
|
||||
seat,
|
||||
location,
|
||||
scale.into(),
|
||||
zoom_scale,
|
||||
zoom_scale * cursor_magnification as f64,
|
||||
now,
|
||||
blur_strength,
|
||||
mode != CursorMode::NotDefault,
|
||||
&mut |elem, hotspot| {
|
||||
push(CosmicElement::Cursor(RescaleRenderElement::from_element(
|
||||
RelocateRenderElement::from_element(
|
||||
elem,
|
||||
Point::from((-hotspot.x, -hotspot.y)),
|
||||
Relocate::Relative,
|
||||
RescaleRenderElement::from_element(
|
||||
RelocateRenderElement::from_element(
|
||||
elem,
|
||||
Point::from((-hotspot.x, -hotspot.y)),
|
||||
Relocate::Relative,
|
||||
),
|
||||
cursor_center,
|
||||
cursor_magnification as f64,
|
||||
),
|
||||
focal_point
|
||||
.as_logical()
|
||||
|
|
@ -886,8 +899,7 @@ where
|
|||
let geometry = geometry.to_local(output).as_logical();
|
||||
|
||||
let padded = with_states(layer.wl_surface(), |states| {
|
||||
surface_padding(states, geometry.size)
|
||||
.and_then(|padding| pad_rect(geometry, &padding))
|
||||
surface_padding(states).and_then(|padding| pad_rect(geometry, &padding))
|
||||
})
|
||||
.unwrap_or(geometry);
|
||||
let radii = with_states(layer.wl_surface(), |states| {
|
||||
|
|
|
|||
|
|
@ -86,12 +86,6 @@ impl ShadowShader {
|
|||
let offset = [0., 5.];
|
||||
let color = [0., 0., 0., if dark_mode { 0.45 } else { 0.35 }];
|
||||
let radius = radius.map(|r| ceil(r as f64));
|
||||
let radius = [
|
||||
radius[3], // top_left
|
||||
radius[1], // top_right
|
||||
radius[0], // bottom_right
|
||||
radius[2], // bottom_left
|
||||
];
|
||||
|
||||
let width = softness;
|
||||
let sigma = width / 2.;
|
||||
|
|
|
|||
|
|
@ -247,10 +247,10 @@ impl BlurElement {
|
|||
Uniform::new(
|
||||
"corner_radius",
|
||||
[
|
||||
radii[3] as f32,
|
||||
radii[1] as f32,
|
||||
radii[0] as f32,
|
||||
radii[1] as f32,
|
||||
radii[2] as f32,
|
||||
radii[3] as f32,
|
||||
],
|
||||
),
|
||||
Uniform::new(
|
||||
|
|
|
|||
|
|
@ -99,10 +99,10 @@ where
|
|||
Uniform::new(
|
||||
"corner_radius",
|
||||
[
|
||||
radius[3] as f32,
|
||||
radius[1] as f32,
|
||||
radius[0] as f32,
|
||||
radius[1] as f32,
|
||||
radius[2] as f32,
|
||||
radius[3] as f32,
|
||||
],
|
||||
),
|
||||
Uniform::new(
|
||||
|
|
@ -145,10 +145,10 @@ where
|
|||
geo: Rectangle<f64, Logical>,
|
||||
radius: [u8; 4],
|
||||
) -> [Rectangle<f64, Logical>; 4] {
|
||||
let top_left = radius[3] as f64;
|
||||
let top_left = radius[0] as f64;
|
||||
let top_right = radius[1] as f64;
|
||||
let bottom_right = radius[0] as f64;
|
||||
let bottom_left = radius[2] as f64;
|
||||
let bottom_right = radius[2] as f64;
|
||||
let bottom_left = radius[3] as f64;
|
||||
|
||||
[
|
||||
Rectangle::new(geo.loc, Size::from((top_left, top_left))),
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@ use crate::{
|
|||
shell::{Devices, SeatExt},
|
||||
state::{BackendData, Common},
|
||||
utils::prelude::*,
|
||||
wayland::protocols::drm::WlDrmState,
|
||||
};
|
||||
use anyhow::{Context, Result, anyhow};
|
||||
use cosmic_comp_config::output::comp::{OutputConfig, TransformDef};
|
||||
|
|
@ -273,7 +274,7 @@ fn init_egl_client_side(
|
|||
.create_global_with_default_feedback::<State>(dh, &feedback);
|
||||
|
||||
let render_node = render_node.unwrap().unwrap();
|
||||
let _drm_global_id = state.common.wl_drm_state.create_global::<State>(
|
||||
state.common.wl_drm_state = Some(WlDrmState::new::<State>(
|
||||
dh,
|
||||
render_node
|
||||
.dev_path_with_type(NodeType::Render)
|
||||
|
|
@ -284,7 +285,7 @@ fn init_egl_client_side(
|
|||
))?,
|
||||
dmabuf_formats,
|
||||
&dmabuf_global,
|
||||
);
|
||||
));
|
||||
|
||||
info!("EGL hardware-acceleration enabled.");
|
||||
}
|
||||
|
|
@ -309,7 +310,9 @@ impl State {
|
|||
WinitEvent::Focus(true) => {
|
||||
for seat in self.common.shell.read().seats.iter() {
|
||||
let devices = seat.user_data().get::<Devices>().unwrap();
|
||||
if devices.has_device(&WinitVirtualDevice) {
|
||||
if devices
|
||||
.has_device(&WinitVirtualDevice, &crate::input::InputBackendId::Normal)
|
||||
{
|
||||
seat.set_active_output(&self.backend.winit().output);
|
||||
break;
|
||||
}
|
||||
|
|
@ -339,7 +342,9 @@ impl State {
|
|||
render_ping.ping();
|
||||
}
|
||||
WinitEvent::Redraw => render_ping.ping(),
|
||||
WinitEvent::Input(event) => self.process_input_event(event),
|
||||
WinitEvent::Input(event) => {
|
||||
self.process_input_event(event, crate::input::InputBackendId::Normal)
|
||||
}
|
||||
WinitEvent::CloseRequested => {
|
||||
self.common.should_stop = true;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@ use crate::{
|
|||
shell::{Devices, SeatExt},
|
||||
state::{BackendData, Common},
|
||||
utils::prelude::*,
|
||||
wayland::protocols::drm::WlDrmState,
|
||||
};
|
||||
use anyhow::{Context, Result, anyhow};
|
||||
use cosmic_comp_config::output::comp::OutputConfig;
|
||||
|
|
@ -497,7 +498,7 @@ where
|
|||
.common
|
||||
.dmabuf_state
|
||||
.create_global_with_default_feedback::<State>(dh, &default_feedback);
|
||||
let _drm_global_id = state.common.wl_drm_state.create_global::<State>(
|
||||
state.common.wl_drm_state = Some(WlDrmState::new::<State>(
|
||||
dh,
|
||||
render_node
|
||||
.dev_path_with_type(NodeType::Render)
|
||||
|
|
@ -508,7 +509,7 @@ where
|
|||
))?,
|
||||
renderer.dmabuf_formats(),
|
||||
&dmabuf_global,
|
||||
);
|
||||
));
|
||||
|
||||
info!("EGL hardware-acceleration enabled.");
|
||||
|
||||
|
|
@ -533,14 +534,14 @@ impl State {
|
|||
let device = event.device();
|
||||
for seat in self.common.shell.read().seats.iter() {
|
||||
let devices = seat.user_data().get::<Devices>().unwrap();
|
||||
if devices.has_device(&device) {
|
||||
if devices.has_device(&device, &crate::input::InputBackendId::Normal) {
|
||||
seat.set_active_output(&output);
|
||||
break;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
self.process_input_event(event);
|
||||
self.process_input_event(event, crate::input::InputBackendId::Normal);
|
||||
// TODO actually figure out the output
|
||||
for output in self.common.shell.read().outputs() {
|
||||
self.backend.x11().schedule_render(output);
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
// SPDX-License-Identifier: GPL-3.0-only
|
||||
|
||||
use crate::{
|
||||
input::InputBackendId,
|
||||
shell::Shell,
|
||||
state::{BackendData, State},
|
||||
utils::prelude::OutputExt,
|
||||
|
|
@ -13,8 +14,11 @@ use cosmic_config::{ConfigGet, CosmicConfigEntry};
|
|||
use cosmic_settings_config::window_rules::ApplicationException;
|
||||
use cosmic_settings_config::{Shortcuts, shortcuts, window_rules};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use smithay::utils::{Clock, Monotonic};
|
||||
use smithay::wayland::xdg_activation::XdgActivationState;
|
||||
use smithay::{
|
||||
backend::input::InputTime,
|
||||
utils::{Clock, Monotonic},
|
||||
};
|
||||
pub use smithay::{
|
||||
backend::input::{self as smithay_input, KeyState},
|
||||
input::keyboard::{Keysym, ModifiersState, keysyms as KeySyms},
|
||||
|
|
@ -45,8 +49,8 @@ mod types;
|
|||
use cosmic::config::CosmicTk;
|
||||
pub use cosmic_comp_config::EdidProduct;
|
||||
use cosmic_comp_config::{
|
||||
ActivationPolicy, AppearanceConfig, CosmicCompConfig, KeyboardConfig, TileBehavior, XkbConfig,
|
||||
XwaylandDescaling, XwaylandEavesdropping, ZoomConfig,
|
||||
ActivationPolicy, AppearanceConfig, CosmicCompConfig, DecorationPreference, KeyboardConfig,
|
||||
TileBehavior, XkbConfig, XwaylandDescaling, XwaylandEavesdropping, ZoomConfig,
|
||||
input::{DeviceState as InputDeviceState, InputConfig, TouchpadOverride},
|
||||
output::comp::{
|
||||
OutputConfig, OutputInfo, OutputState, OutputsConfig, TransformDef, load_outputs,
|
||||
|
|
@ -168,7 +172,7 @@ pub enum ColorFilter {
|
|||
}
|
||||
|
||||
impl Config {
|
||||
pub fn load(loop_handle: &LoopHandle<'_, State>) -> Config {
|
||||
pub fn load(loop_handle: &LoopHandle<'_, State>, kiosk_mode: bool) -> Config {
|
||||
let config = cosmic_config::Config::new("com.system76.CosmicComp", 1).unwrap();
|
||||
let source = cosmic_config::calloop::ConfigWatchSource::new(&config).unwrap();
|
||||
loop_handle
|
||||
|
|
@ -244,40 +248,48 @@ impl Config {
|
|||
|
||||
// Source key bindings from com.system76.CosmicSettings.Shortcuts
|
||||
let settings_context = shortcuts::context().expect("Failed to load shortcuts config");
|
||||
let system_actions = shortcuts::system_actions(&settings_context);
|
||||
let shortcuts = shortcuts::shortcuts(&settings_context);
|
||||
let mut system_actions = Default::default();
|
||||
let mut shortcuts = Default::default();
|
||||
// Kiosk mode disables shortcuts
|
||||
if !kiosk_mode {
|
||||
system_actions = shortcuts::system_actions(&settings_context);
|
||||
shortcuts = shortcuts::shortcuts(&settings_context);
|
||||
|
||||
// Listen for updates to the keybindings config.
|
||||
match cosmic_config::calloop::ConfigWatchSource::new(&settings_context) {
|
||||
Ok(source) => {
|
||||
if let Err(err) = loop_handle.insert_source(source, |(config, keys), (), state| {
|
||||
for key in keys {
|
||||
match key.as_str() {
|
||||
// Reload the keyboard shortcuts config.
|
||||
"custom" | "defaults" => {
|
||||
state.common.config.shortcuts = shortcuts::shortcuts(&config);
|
||||
// Listen for updates to the keybindings config.
|
||||
match cosmic_config::calloop::ConfigWatchSource::new(&settings_context) {
|
||||
Ok(source) => {
|
||||
if let Err(err) =
|
||||
loop_handle.insert_source(source, |(config, keys), (), state| {
|
||||
for key in keys {
|
||||
match key.as_str() {
|
||||
// Reload the keyboard shortcuts config.
|
||||
"custom" | "defaults" => {
|
||||
state.common.config.shortcuts =
|
||||
shortcuts::shortcuts(&config);
|
||||
}
|
||||
|
||||
"system_actions" => {
|
||||
state.common.config.system_actions =
|
||||
shortcuts::system_actions(&config);
|
||||
}
|
||||
|
||||
_ => (),
|
||||
}
|
||||
}
|
||||
|
||||
"system_actions" => {
|
||||
state.common.config.system_actions =
|
||||
shortcuts::system_actions(&config);
|
||||
}
|
||||
|
||||
_ => (),
|
||||
}
|
||||
})
|
||||
{
|
||||
warn!(
|
||||
?err,
|
||||
"Failed to watch com.system76.CosmicSettings.Shortcuts config"
|
||||
);
|
||||
}
|
||||
}) {
|
||||
warn!(
|
||||
?err,
|
||||
"Failed to watch com.system76.CosmicSettings.Shortcuts config"
|
||||
);
|
||||
}
|
||||
}
|
||||
Err(err) => warn!(
|
||||
?err,
|
||||
"failed to create config watch source for com.system76.CosmicSettings.Shortcuts"
|
||||
),
|
||||
};
|
||||
Err(err) => warn!(
|
||||
?err,
|
||||
"failed to create config watch source for com.system76.CosmicSettings.Shortcuts"
|
||||
),
|
||||
};
|
||||
}
|
||||
|
||||
let window_rules_context =
|
||||
window_rules::context().expect("Failed to load window rules config");
|
||||
|
|
@ -537,7 +549,13 @@ impl Config {
|
|||
primary.config_mut().xwayland_primary = true;
|
||||
}
|
||||
}
|
||||
for output in outputs.iter().filter(|o| o.mirroring().is_none()) {
|
||||
// sort by connector name for a deterministic layout independent of hotplug order
|
||||
let mut sorted_outputs = outputs
|
||||
.iter()
|
||||
.filter(|o| o.mirroring().is_none())
|
||||
.collect::<Vec<_>>();
|
||||
sorted_outputs.sort_by_key(|o| o.name());
|
||||
for output in sorted_outputs {
|
||||
{
|
||||
let mut config = output.config_mut();
|
||||
config.position = (w, 0);
|
||||
|
|
@ -769,13 +787,12 @@ pub fn change_modifier_state(
|
|||
const X11_KEYCODE_OFFSET: u32 = 8;
|
||||
|
||||
let mut input = |key_state, scan_code| {
|
||||
let time = state.common.clock.now().as_millis();
|
||||
let _ = keyboard.input(
|
||||
state,
|
||||
smithay_input::Keycode::new(scan_code + X11_KEYCODE_OFFSET),
|
||||
key_state,
|
||||
SERIAL_COUNTER.next_serial(),
|
||||
time,
|
||||
InputTime::now(),
|
||||
|_, _, _| smithay::input::keyboard::FilterResult::<()>::Forward,
|
||||
);
|
||||
};
|
||||
|
|
@ -820,6 +837,29 @@ fn config_changed(config: cosmic_config::Config, keys: Vec<String>, state: &mut
|
|||
}
|
||||
}
|
||||
}
|
||||
let ei_connections = state
|
||||
.common
|
||||
.ei_keyboard_source
|
||||
.keys()
|
||||
.cloned()
|
||||
.collect::<Vec<_>>();
|
||||
for conn in &ei_connections {
|
||||
state.release_ei_keyboard(conn);
|
||||
state.clear_input_source_state(&InputBackendId::Ei(conn.clone()));
|
||||
}
|
||||
for ei_seat in state.common.ei_seats.values() {
|
||||
if let Err(err) =
|
||||
ei_seat.add_keyboard("virtual keyboard", xkb_config_to_wl(&value))
|
||||
{
|
||||
warn!(?err, "Failed to update libei keyboard keymap");
|
||||
}
|
||||
}
|
||||
if !state.common.ei_seats.is_empty() {
|
||||
let seat = state.common.shell.read().seats.last_active().clone();
|
||||
if let Some(keyboard) = seat.get_keyboard() {
|
||||
state.broadcast_ei_keyboard_modifiers(&keyboard);
|
||||
}
|
||||
}
|
||||
state.common.config.cosmic_conf.xkb_config = value;
|
||||
}
|
||||
"keyboard_config" => {
|
||||
|
|
@ -947,6 +987,10 @@ fn config_changed(config: cosmic_config::Config, keys: Vec<String>, state: &mut
|
|||
}
|
||||
}
|
||||
}
|
||||
"cursor_shake_to_find" => {
|
||||
let new = get_config::<bool>(&config, "cursor_shake_to_find");
|
||||
state.common.config.cosmic_conf.cursor_shake_to_find = new;
|
||||
}
|
||||
"cursor_hide_timeout" => {
|
||||
let new = get_config::<Option<u32>>(&config, "cursor_hide_timeout");
|
||||
if new != state.common.config.cosmic_conf.cursor_hide_timeout {
|
||||
|
|
@ -972,6 +1016,13 @@ fn config_changed(config: cosmic_config::Config, keys: Vec<String>, state: &mut
|
|||
state.common.config.cosmic_conf.activation_policy = new;
|
||||
}
|
||||
}
|
||||
"decoration_preference" => {
|
||||
let new = get_config::<DecorationPreference>(&config, "decoration_preference");
|
||||
if new != state.common.config.cosmic_conf.decoration_preference {
|
||||
state.common.config.cosmic_conf.decoration_preference = new;
|
||||
state.update_decorations();
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -150,9 +150,10 @@ impl A11yKeyboardMonitorState {
|
|||
}
|
||||
|
||||
pub fn key_event(&self, modifiers: &ModifiersState, keysym: &KeysymHandle, state: KeyState) {
|
||||
let has_key_grab = self.has_key_grab(modifiers, keysym.modified_sym());
|
||||
let clients = self.clients.lock().unwrap();
|
||||
for (unique_name, client) in clients.0.iter() {
|
||||
if !client.watched && !self.has_key_grab(modifiers, keysym.modified_sym()) {
|
||||
if !client.watched && !has_key_grab {
|
||||
continue;
|
||||
}
|
||||
|
||||
|
|
|
|||
80
src/dbus/ei.rs
Normal file
80
src/dbus/ei.rs
Normal file
|
|
@ -0,0 +1,80 @@
|
|||
use std::{
|
||||
os::unix::net::UnixStream,
|
||||
sync::{Arc, Mutex},
|
||||
};
|
||||
|
||||
use smithay::reexports::calloop;
|
||||
use zbus::names::{UniqueName, WellKnownName};
|
||||
|
||||
use super::name_owners::NameOwners;
|
||||
|
||||
static ALLOWED_NAMES: &[WellKnownName] = &[
|
||||
WellKnownName::from_static_str_unchecked("org.freedesktop.impl.portal.desktop.cosmic"),
|
||||
WellKnownName::from_static_str_unchecked("com.system76.CosmicOSK"),
|
||||
];
|
||||
|
||||
/// Channel for handing the EI socketpair (and requested device types)
|
||||
/// It's `None` until the EI sender side has been set up
|
||||
type EiSender = Arc<Mutex<Option<calloop::channel::Sender<crate::libei::EiRequest>>>>;
|
||||
|
||||
struct Ei {
|
||||
ei_sender: EiSender,
|
||||
name_owners: NameOwners,
|
||||
}
|
||||
|
||||
impl Ei {
|
||||
async fn check_sender_allowed(&self, sender: &UniqueName<'_>) -> zbus::fdo::Result<()> {
|
||||
if self.name_owners.check_owner(sender, ALLOWED_NAMES).await {
|
||||
Ok(())
|
||||
} else {
|
||||
Err(zbus::fdo::Error::AccessDenied("Access denied".to_string()))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[zbus::interface(name = "com.system76.CosmicComp.Ei")]
|
||||
impl Ei {
|
||||
/// Create a new EI sender context
|
||||
async fn get_sender_socket(
|
||||
&self,
|
||||
device_types: u32,
|
||||
#[zbus(header)] header: zbus::message::Header<'_>,
|
||||
) -> zbus::fdo::Result<zbus::zvariant::OwnedFd> {
|
||||
if let Some(sender) = header.sender() {
|
||||
self.check_sender_allowed(sender).await?;
|
||||
}
|
||||
|
||||
let (comp_stream, client_stream) = UnixStream::pair().map_err(|err| {
|
||||
zbus::fdo::Error::Failed(format!("Failed to create socket pair: {err}"))
|
||||
})?;
|
||||
|
||||
{
|
||||
let guard = self.ei_sender.lock().unwrap();
|
||||
let sender = guard
|
||||
.as_ref()
|
||||
.ok_or_else(|| zbus::fdo::Error::Failed("EI sender not available".to_string()))?;
|
||||
sender.send((comp_stream, device_types)).map_err(|err| {
|
||||
zbus::fdo::Error::Failed(format!("Failed to hand off EI socket: {err}"))
|
||||
})?;
|
||||
}
|
||||
|
||||
Ok(std::os::fd::OwnedFd::from(client_stream).into())
|
||||
}
|
||||
}
|
||||
|
||||
/// Register the `com.system76.CosmicComp.Ei` interface on the shared session connection.
|
||||
pub async fn init(
|
||||
conn: &zbus::Connection,
|
||||
name_owners: &NameOwners,
|
||||
ei_sender: EiSender,
|
||||
) -> zbus::Result<()> {
|
||||
let ei = Ei {
|
||||
ei_sender,
|
||||
name_owners: name_owners.clone(),
|
||||
};
|
||||
conn.object_server()
|
||||
.at("/com/system76/CosmicComp/Ei", ei)
|
||||
.await?;
|
||||
conn.request_name("com.system76.CosmicComp").await?;
|
||||
Ok(())
|
||||
}
|
||||
|
|
@ -9,11 +9,13 @@ use std::{
|
|||
cell::{RefCell, RefMut},
|
||||
collections::HashMap,
|
||||
rc::Rc,
|
||||
sync::{Arc, Mutex},
|
||||
};
|
||||
use tracing::{error, warn};
|
||||
|
||||
pub mod a11y_keyboard_monitor;
|
||||
use a11y_keyboard_monitor::A11yKeyboardMonitorState;
|
||||
pub mod ei;
|
||||
#[cfg(feature = "logind")]
|
||||
pub mod logind;
|
||||
mod name_owners;
|
||||
|
|
@ -29,6 +31,7 @@ struct DBusStateInner {
|
|||
session_conn: zbus::Result<zbus::Connection>,
|
||||
system_conn: zbus::Result<zbus::Connection>,
|
||||
a11y_keyboard_monitor: RefCell<Option<a11y_keyboard_monitor::A11yKeyboardMonitorState>>,
|
||||
ei_sender: Arc<Mutex<Option<calloop::channel::Sender<crate::libei::EiRequest>>>>,
|
||||
}
|
||||
|
||||
impl DBusState {
|
||||
|
|
@ -42,6 +45,7 @@ impl DBusState {
|
|||
session_conn,
|
||||
system_conn,
|
||||
a11y_keyboard_monitor: RefCell::new(None),
|
||||
ei_sender: Arc::new(Mutex::new(None)),
|
||||
}));
|
||||
evlh.insert_source(source, |_, _, _| {}).unwrap();
|
||||
let state_clone = state.clone();
|
||||
|
|
@ -65,6 +69,10 @@ impl DBusState {
|
|||
RefMut::filter_map(self.0.a11y_keyboard_monitor.borrow_mut(), |x| x.as_mut()).ok()
|
||||
}
|
||||
|
||||
pub fn set_ei_sender(&self, sender: calloop::channel::Sender<crate::libei::EiRequest>) {
|
||||
*self.0.ei_sender.lock().unwrap() = Some(sender);
|
||||
}
|
||||
|
||||
// TODO Lazy async init when we don't have anything blocking main thread
|
||||
async fn session_conn(&self) -> zbus::Result<&zbus::Connection> {
|
||||
self.0.session_conn.as_ref().map_err(|err| err.clone())
|
||||
|
|
@ -85,6 +93,7 @@ async fn init_session(state: &DBusState) -> zbus::Result<()> {
|
|||
let a11y_keyboard_monitor_state =
|
||||
A11yKeyboardMonitorState::new(conn, &name_owners, &state.0.executor).await?;
|
||||
*state.0.a11y_keyboard_monitor.borrow_mut() = Some(a11y_keyboard_monitor_state);
|
||||
ei::init(conn, &name_owners, state.0.ei_sender.clone()).await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@
|
|||
|
||||
use crate::{
|
||||
config::{Action, PrivateAction},
|
||||
input::InputBackendId,
|
||||
shell::{
|
||||
FocusResult, InvalidWorkspaceIndex, MoveResult, SeatExt, Trigger, WorkspaceDelta,
|
||||
focus::{FocusTarget, target::KeyboardFocusTarget},
|
||||
|
|
@ -17,6 +18,7 @@ use cosmic_config::ConfigSet;
|
|||
use cosmic_settings_config::shortcuts;
|
||||
use cosmic_settings_config::shortcuts::action::{Direction, FocusDirection};
|
||||
use smithay::{
|
||||
backend::input::InputTime,
|
||||
input::{Seat, pointer::MotionEvent},
|
||||
utils::{Point, Serial},
|
||||
};
|
||||
|
|
@ -28,6 +30,8 @@ use std::{os::unix::process::CommandExt, thread};
|
|||
|
||||
use super::gestures;
|
||||
|
||||
const MAX_ZOOM: f64 = 256.0;
|
||||
|
||||
fn propagate_by_default(action: &shortcuts::Action) -> bool {
|
||||
matches!(
|
||||
action,
|
||||
|
|
@ -39,9 +43,10 @@ impl State {
|
|||
pub fn handle_action(
|
||||
&mut self,
|
||||
action: Action,
|
||||
backend_id: &InputBackendId,
|
||||
seat: &Seat<State>,
|
||||
serial: Serial,
|
||||
time: u32,
|
||||
time: InputTime,
|
||||
pattern: shortcuts::Binding,
|
||||
direction: Option<Direction>,
|
||||
) {
|
||||
|
|
@ -69,7 +74,7 @@ impl State {
|
|||
Action::Shortcut(action) => {
|
||||
let propagate = propagate_by_default(&action);
|
||||
self.handle_shortcut_action(
|
||||
action, seat, serial, time, pattern, direction, propagate,
|
||||
action, backend_id, seat, serial, time, pattern, direction, propagate,
|
||||
)
|
||||
}
|
||||
Action::Private(PrivateAction::Escape) => {
|
||||
|
|
@ -143,9 +148,10 @@ impl State {
|
|||
pub fn handle_shortcut_action(
|
||||
&mut self,
|
||||
action: shortcuts::Action,
|
||||
backend_id: &InputBackendId,
|
||||
seat: &Seat<State>,
|
||||
serial: Serial,
|
||||
time: u32,
|
||||
time: InputTime,
|
||||
pattern: shortcuts::Binding,
|
||||
direction: Option<Direction>,
|
||||
propagate: bool,
|
||||
|
|
@ -232,6 +238,7 @@ impl State {
|
|||
{
|
||||
self.handle_shortcut_action(
|
||||
Action::SwitchOutput(inferred),
|
||||
backend_id,
|
||||
seat,
|
||||
serial,
|
||||
time,
|
||||
|
|
@ -271,6 +278,7 @@ impl State {
|
|||
{
|
||||
self.handle_shortcut_action(
|
||||
Action::SwitchOutput(inferred),
|
||||
backend_id,
|
||||
seat,
|
||||
serial,
|
||||
time,
|
||||
|
|
@ -392,6 +400,7 @@ impl State {
|
|||
} else {
|
||||
Action::SendToOutput(inferred)
|
||||
},
|
||||
backend_id,
|
||||
seat,
|
||||
serial,
|
||||
time,
|
||||
|
|
@ -417,6 +426,7 @@ impl State {
|
|||
} else {
|
||||
Action::SendToWorkspace(1)
|
||||
},
|
||||
backend_id,
|
||||
seat,
|
||||
serial,
|
||||
time,
|
||||
|
|
@ -483,6 +493,7 @@ impl State {
|
|||
} else {
|
||||
Action::SendToOutput(inferred)
|
||||
},
|
||||
backend_id,
|
||||
seat,
|
||||
serial,
|
||||
time,
|
||||
|
|
@ -508,6 +519,7 @@ impl State {
|
|||
} else {
|
||||
Action::SendToLastWorkspace
|
||||
},
|
||||
backend_id,
|
||||
seat,
|
||||
serial,
|
||||
time,
|
||||
|
|
@ -532,7 +544,9 @@ impl State {
|
|||
if propagate
|
||||
&& let Some((serial, prev_output, prev_idx)) =
|
||||
shell.previous_workspace_idx.take()
|
||||
&& seat.last_modifier_change().is_some_and(|s| s == serial)
|
||||
&& seat
|
||||
.last_modifier_change_for(backend_id)
|
||||
.is_some_and(|s| s == serial)
|
||||
&& prev_output == current_output
|
||||
{
|
||||
let _ = shell.activate(
|
||||
|
|
@ -705,6 +719,7 @@ impl State {
|
|||
if res.is_ok() {
|
||||
self.handle_shortcut_action(
|
||||
Action::SwitchOutput(direction),
|
||||
backend_id,
|
||||
seat,
|
||||
serial,
|
||||
time,
|
||||
|
|
@ -741,7 +756,8 @@ impl State {
|
|||
};
|
||||
|
||||
if let Some(direction) = dir {
|
||||
if let Some(last_mod_serial) = seat.last_modifier_change() {
|
||||
if let Some(last_mod_serial) = seat.last_modifier_change_for(backend_id)
|
||||
{
|
||||
let mut shell = self.common.shell.write();
|
||||
if !shell
|
||||
.previous_workspace_idx
|
||||
|
|
@ -776,6 +792,7 @@ impl State {
|
|||
|
||||
self.handle_shortcut_action(
|
||||
action,
|
||||
backend_id,
|
||||
seat,
|
||||
serial,
|
||||
time,
|
||||
|
|
@ -800,7 +817,7 @@ impl State {
|
|||
.move_current_element(direction, seat);
|
||||
match res {
|
||||
MoveResult::MoveFurther(_move_further) => {
|
||||
if let Some(last_mod_serial) = seat.last_modifier_change() {
|
||||
if let Some(last_mod_serial) = seat.last_modifier_change_for(backend_id) {
|
||||
let mut shell = self.common.shell.write();
|
||||
if !shell
|
||||
.previous_workspace_idx
|
||||
|
|
@ -834,6 +851,7 @@ impl State {
|
|||
|
||||
self.handle_shortcut_action(
|
||||
action,
|
||||
backend_id,
|
||||
seat,
|
||||
serial,
|
||||
time,
|
||||
|
|
@ -1101,7 +1119,14 @@ impl State {
|
|||
}
|
||||
|
||||
if zoom_seat == *seat {
|
||||
let new_level = (current_level + change).max(1.0);
|
||||
let factor = 1.0 + change.abs();
|
||||
let new_level = if change < 0. {
|
||||
current_level / factor
|
||||
} else {
|
||||
current_level * factor
|
||||
}
|
||||
.clamp(1.0, MAX_ZOOM);
|
||||
let new_level = if new_level < 1.01 { 1.0 } else { new_level };
|
||||
shell.trigger_zoom(
|
||||
seat,
|
||||
Some(&output),
|
||||
|
|
|
|||
1245
src/input/mod.rs
1245
src/input/mod.rs
File diff suppressed because it is too large
Load diff
256
src/input/tablet_emu.rs
Normal file
256
src/input/tablet_emu.rs
Normal file
|
|
@ -0,0 +1,256 @@
|
|||
// SPDX-License-Identifier: GPL-3.0-only
|
||||
|
||||
//! Pointer Emulating TabletTool grab.
|
||||
|
||||
use crate::{shell::focus::target::PointerFocusTarget, state::State};
|
||||
use smithay::{
|
||||
backend::input::{ButtonState, InputTime, MouseButton},
|
||||
input::{
|
||||
Seat, pointer,
|
||||
tablet::{
|
||||
self,
|
||||
tool::{
|
||||
AxisFrame, ButtonEvent, DownEvent, GrabStartData, ProximityInEvent,
|
||||
ProximityOutEvent, TabletToolGrab, TabletToolInnerHandle, UpEvent,
|
||||
},
|
||||
},
|
||||
},
|
||||
utils::{Logical, Point, SERIAL_COUNTER},
|
||||
};
|
||||
|
||||
// from https://gitlab.freedesktop.org/libinput/libinput/-/blob/main/include/linux/linux/input-event-codes.h
|
||||
const BTN_STYLUS: u32 = 0x14b;
|
||||
const BTN_STYLUS_2: u32 = 0x14c;
|
||||
const BTN_LEFT: u32 = 0x110;
|
||||
const BTN_RIGHT: u32 = 0x111;
|
||||
const BTN_MIDDLE: u32 = 0x112;
|
||||
const BTN_FORWARD: u32 = 0x115;
|
||||
const BTN_BACK: u32 = 0x116;
|
||||
|
||||
pub struct PointerEmulationGrab {
|
||||
start_data: GrabStartData<State>,
|
||||
seat: Seat<State>,
|
||||
|
||||
above_source: bool,
|
||||
tip_down: bool,
|
||||
button_down: Vec<u32>,
|
||||
}
|
||||
|
||||
impl PointerEmulationGrab {
|
||||
pub fn new(start_data: GrabStartData<State>, seat: Seat<State>) -> Self {
|
||||
Self {
|
||||
start_data,
|
||||
seat,
|
||||
|
||||
above_source: true,
|
||||
tip_down: false,
|
||||
button_down: Vec::new(),
|
||||
}
|
||||
}
|
||||
|
||||
fn button_to_mouse(&self, button: u32) -> Option<MouseButton> {
|
||||
// FIXME: This should be configurable.
|
||||
if button == BTN_STYLUS {
|
||||
Some(MouseButton::Right)
|
||||
} else if button == BTN_STYLUS_2 {
|
||||
Some(MouseButton::Middle)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
fn pointer_button(&self, data: &mut State, button: MouseButton, state: ButtonState) {
|
||||
if let Some(pointer) = self.seat.get_pointer() {
|
||||
let button = match button {
|
||||
MouseButton::Left => BTN_LEFT,
|
||||
MouseButton::Right => BTN_RIGHT,
|
||||
MouseButton::Middle => BTN_MIDDLE,
|
||||
MouseButton::Back => BTN_BACK,
|
||||
MouseButton::Forward => BTN_FORWARD,
|
||||
_ => return,
|
||||
};
|
||||
|
||||
pointer.button(
|
||||
data,
|
||||
&pointer::ButtonEvent {
|
||||
serial: SERIAL_COUNTER.next_serial(),
|
||||
button,
|
||||
state,
|
||||
time: InputTime::now(),
|
||||
},
|
||||
);
|
||||
|
||||
pointer.frame(data);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
type Type = tablet::tool::MotionEvent;
|
||||
|
||||
impl TabletToolGrab<State> for PointerEmulationGrab {
|
||||
fn proximity_in(
|
||||
&mut self,
|
||||
data: &mut State,
|
||||
handle: &mut TabletToolInnerHandle<'_, State>,
|
||||
focus: Option<(PointerFocusTarget, Point<f64, Logical>)>,
|
||||
event: &ProximityInEvent,
|
||||
) {
|
||||
handle.proximity_in(data, focus.clone(), event);
|
||||
|
||||
if !self.above_source && !self.tip_down && self.button_down.is_empty() {
|
||||
handle.unset_grab(
|
||||
self,
|
||||
data,
|
||||
SERIAL_COUNTER.next_serial(),
|
||||
InputTime::now(),
|
||||
true,
|
||||
);
|
||||
} else if let Some(pointer) = self.seat.get_pointer() {
|
||||
pointer.motion(
|
||||
data,
|
||||
focus,
|
||||
&pointer::MotionEvent {
|
||||
location: event.location,
|
||||
serial: SERIAL_COUNTER.next_serial(),
|
||||
time: InputTime::now(),
|
||||
},
|
||||
);
|
||||
pointer.frame(data);
|
||||
}
|
||||
}
|
||||
|
||||
fn proximity_out(
|
||||
&mut self,
|
||||
data: &mut State,
|
||||
handle: &mut TabletToolInnerHandle<'_, State>,
|
||||
event: &ProximityOutEvent,
|
||||
) {
|
||||
handle.proximity_out(data, event);
|
||||
|
||||
handle.unset_grab(self, data, event.serial, event.time, true);
|
||||
}
|
||||
|
||||
fn down(
|
||||
&mut self,
|
||||
data: &mut State,
|
||||
handle: &mut TabletToolInnerHandle<'_, State>,
|
||||
event: &DownEvent,
|
||||
) {
|
||||
handle.down(data, event);
|
||||
self.tip_down = true;
|
||||
|
||||
self.pointer_button(data, MouseButton::Left, ButtonState::Pressed);
|
||||
}
|
||||
|
||||
fn up(
|
||||
&mut self,
|
||||
data: &mut State,
|
||||
handle: &mut TabletToolInnerHandle<'_, State>,
|
||||
event: &UpEvent,
|
||||
) {
|
||||
handle.up(data, event);
|
||||
|
||||
self.pointer_button(data, MouseButton::Left, ButtonState::Released);
|
||||
self.tip_down = false;
|
||||
|
||||
if !self.above_source && !self.tip_down && self.button_down.is_empty() {
|
||||
handle.unset_grab(self, data, event.serial, event.time, true);
|
||||
}
|
||||
}
|
||||
|
||||
fn motion(
|
||||
&mut self,
|
||||
data: &mut State,
|
||||
handle: &mut TabletToolInnerHandle<'_, State>,
|
||||
focus: Option<(PointerFocusTarget, Point<f64, Logical>)>,
|
||||
event: &Type,
|
||||
) {
|
||||
self.above_source = focus.as_ref().map(|(target, _)| target)
|
||||
== self.start_data.focus.as_ref().map(|(target, _)| target);
|
||||
handle.motion(data, self.start_data.focus.clone(), event);
|
||||
|
||||
if !self.above_source && !self.tip_down && self.button_down.is_empty() {
|
||||
handle.unset_grab(
|
||||
self,
|
||||
data,
|
||||
SERIAL_COUNTER.next_serial(),
|
||||
InputTime::now(),
|
||||
true,
|
||||
);
|
||||
} else if let Some(pointer) = self.seat.get_pointer() {
|
||||
pointer.motion(
|
||||
data,
|
||||
focus,
|
||||
&pointer::MotionEvent {
|
||||
location: event.location,
|
||||
serial: SERIAL_COUNTER.next_serial(),
|
||||
time: InputTime::now(),
|
||||
},
|
||||
);
|
||||
pointer.frame(data);
|
||||
}
|
||||
}
|
||||
|
||||
fn button(
|
||||
&mut self,
|
||||
data: &mut State,
|
||||
handle: &mut TabletToolInnerHandle<'_, State>,
|
||||
event: &ButtonEvent,
|
||||
) {
|
||||
handle.button(data, event);
|
||||
|
||||
self.button_down.retain(|b| b != &event.button);
|
||||
if matches!(event.state, ButtonState::Pressed) {
|
||||
self.button_down.push(event.button);
|
||||
}
|
||||
|
||||
if let Some(button) = self.button_to_mouse(event.button) {
|
||||
self.pointer_button(data, button, event.state);
|
||||
}
|
||||
|
||||
if !self.above_source && !self.tip_down && self.button_down.is_empty() {
|
||||
handle.unset_grab(
|
||||
self,
|
||||
data,
|
||||
SERIAL_COUNTER.next_serial(),
|
||||
InputTime::now(),
|
||||
true,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
fn axis(
|
||||
&mut self,
|
||||
data: &mut State,
|
||||
handle: &mut TabletToolInnerHandle<'_, State>,
|
||||
frame: AxisFrame,
|
||||
) {
|
||||
handle.axis(data, frame);
|
||||
}
|
||||
|
||||
fn frame(
|
||||
&mut self,
|
||||
data: &mut State,
|
||||
handle: &mut TabletToolInnerHandle<'_, State>,
|
||||
time: InputTime,
|
||||
) {
|
||||
handle.frame(data, time);
|
||||
}
|
||||
|
||||
fn unset(&mut self, data: &mut State) {
|
||||
if self.tip_down {
|
||||
self.pointer_button(data, MouseButton::Left, ButtonState::Released);
|
||||
}
|
||||
|
||||
let buttons = std::mem::take(&mut self.button_down);
|
||||
for button in buttons.into_iter() {
|
||||
if let Some(button) = self.button_to_mouse(button) {
|
||||
self.pointer_button(data, button, ButtonState::Released);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn start_data(&self) -> &GrabStartData<State> {
|
||||
&self.start_data
|
||||
}
|
||||
}
|
||||
43
src/lib.rs
43
src/lib.rs
|
|
@ -16,7 +16,7 @@ use smithay::{
|
|||
};
|
||||
|
||||
use anyhow::{Context, Result};
|
||||
use state::{LastRefresh, State};
|
||||
use state::{BackendData, LastRefresh, State};
|
||||
use std::{
|
||||
env,
|
||||
ffi::OsString,
|
||||
|
|
@ -43,6 +43,7 @@ pub mod dbus;
|
|||
pub mod debug;
|
||||
pub mod hooks;
|
||||
pub mod input;
|
||||
pub mod libei;
|
||||
mod logger;
|
||||
pub mod session;
|
||||
pub mod shell;
|
||||
|
|
@ -176,6 +177,10 @@ pub fn run(hooks: crate::hooks::Hooks) -> Result<(), Box<dyn Error>> {
|
|||
with_xwayland,
|
||||
kiosk_command,
|
||||
);
|
||||
// Set up the libei sender side before the backend spawns Xwayland.
|
||||
let ei_sender = libei::setup_ei(&event_loop.handle());
|
||||
state.common.dbus_state.set_ei_sender(ei_sender);
|
||||
|
||||
// init backend
|
||||
backend::init_backend_auto(&display, &mut event_loop, &mut state)?;
|
||||
|
||||
|
|
@ -222,19 +227,17 @@ pub fn run(hooks: crate::hooks::Hooks) -> Result<(), Box<dyn Error>> {
|
|||
// Kiosk child exited with status
|
||||
Ok(Some(exit_status)) => {
|
||||
info!("Command exited with status {:?}", exit_status);
|
||||
match exit_status.code() {
|
||||
// Exiting with the same status as the kiosk child
|
||||
Some(code) => process::exit(code),
|
||||
// The kiosk child exited with signal, exiting with error
|
||||
None => process::exit(1),
|
||||
}
|
||||
// Stop cleanly so surface threads are joined before exit() (signal -> 1).
|
||||
state.common.kiosk_exit_code = Some(exit_status.code().unwrap_or(1));
|
||||
state.common.should_stop = true;
|
||||
}
|
||||
// Command still running
|
||||
Ok(None) => {}
|
||||
// Kiosk child disappeared, exiting with error
|
||||
Err(err) => {
|
||||
warn!(?err, "Failed to wait for command");
|
||||
process::exit(1);
|
||||
state.common.kiosk_exit_code = Some(1);
|
||||
state.common.should_stop = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -245,10 +248,32 @@ pub fn run(hooks: crate::hooks::Hooks) -> Result<(), Box<dyn Error>> {
|
|||
let _ = child.kill();
|
||||
}
|
||||
|
||||
let kiosk_exit_code = state.common.kiosk_exit_code;
|
||||
|
||||
// Join surface threads before exit() so no thread is mid-eglCreateSync when
|
||||
// Mesa's atexit handlers run and corrupt the heap (issue #2375). Safe here
|
||||
// because the event loop has stopped; an unconditional join in Surface::Drop
|
||||
// would instead deadlock against apply_config_for_outputs.
|
||||
if let BackendData::Kms(kms) = &mut state.backend {
|
||||
// Release master first so the surface drop path skips its blocking commit.
|
||||
for device in kms.drm_devices.values_mut() {
|
||||
device.drm.pause();
|
||||
}
|
||||
for device in kms.drm_devices.values_mut() {
|
||||
for (_, surface) in device.inner.surfaces.drain() {
|
||||
surface.drop_and_join();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// drop eventloop & state before logger
|
||||
std::mem::drop(event_loop);
|
||||
std::mem::drop(state);
|
||||
|
||||
if let Some(code) = kiosk_exit_code {
|
||||
process::exit(code);
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
|
|
@ -264,7 +289,7 @@ Project home page: https://github.com/pop-os/cosmic-comp
|
|||
Options:
|
||||
-h, --help Show this message
|
||||
--no-xwayland Run without Xwayland
|
||||
-v, --version Show the version of cosmic-comp"#
|
||||
-V, --version Show the version of cosmic-comp"#
|
||||
);
|
||||
}
|
||||
|
||||
|
|
|
|||
230
src/libei.rs
Normal file
230
src/libei.rs
Normal file
|
|
@ -0,0 +1,230 @@
|
|||
use std::os::unix::net::UnixStream;
|
||||
|
||||
use reis::eis;
|
||||
use smithay::reexports::reis;
|
||||
|
||||
use smithay::backend::libei::{EiInput, EiInputEvent, EiRegion};
|
||||
use smithay::input::keyboard::Keysym;
|
||||
use smithay::reexports::calloop;
|
||||
use smithay::wayland::input_method::InputMethodSeat;
|
||||
use smithay::wayland::text_input::TextInputSeat;
|
||||
|
||||
use crate::config::xkb_config_to_wl;
|
||||
use crate::input::InputBackendId;
|
||||
use crate::state::{BackendData, State};
|
||||
use crate::utils::{geometry::RectGlobalExt, prelude::OutputExt};
|
||||
|
||||
// Requested device types for an EI connection, mirroring the XDG RemoteDesktop portal `DeviceType` bitmask
|
||||
const DEVICE_TYPE_KEYBOARD: u32 = 1;
|
||||
const DEVICE_TYPE_POINTER: u32 = 2;
|
||||
const DEVICE_TYPE_TOUCHSCREEN: u32 = 4;
|
||||
|
||||
// Name of the EI absolute-pointer device. Shared so the connect path and the
|
||||
// re-advertise-on-output-change path recreate the same device.
|
||||
const ABSOLUTE_POINTER_NAME: &str = "virtual absolute pointer";
|
||||
|
||||
pub type EiRequest = (UnixStream, u32);
|
||||
|
||||
/// Build the regions advertised on the EI absolute devices (absolute pointer and touch) for the
|
||||
/// output layout: one region per output, each at its **global logical** offset with
|
||||
/// its logical size and scale.
|
||||
pub fn absolute_regions(state: &State) -> Vec<EiRegion> {
|
||||
let shell = state.common.shell.read();
|
||||
shell
|
||||
.outputs()
|
||||
.map(|output| {
|
||||
let scale = output.current_scale().fractional_scale();
|
||||
EiRegion {
|
||||
// Keep the signed logical rect
|
||||
// the u32 clamp happens at the `ei_device.region` in smithay.
|
||||
rect: output.geometry().as_logical(),
|
||||
scale: scale as f32,
|
||||
// Tie the region to its output so a client can correlate it with the
|
||||
// matching screencast stream (the portal advertises the same name).
|
||||
mapping_id: Some(output.name()),
|
||||
}
|
||||
})
|
||||
.collect()
|
||||
}
|
||||
|
||||
/// Re-advertise the coordinate regions on every active EI seat's absolute devices.
|
||||
pub fn refresh_absolute_pointer_regions(state: &State) {
|
||||
if state.common.ei_seats.is_empty() {
|
||||
return;
|
||||
}
|
||||
let regions = absolute_regions(state);
|
||||
for seat in state.common.ei_seats.values() {
|
||||
seat.update_regions(®ions);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn setup_ei(
|
||||
handle: &calloop::LoopHandle<'static, State>,
|
||||
) -> calloop::channel::Sender<EiRequest> {
|
||||
let (sender, channel) = calloop::channel::channel::<EiRequest>();
|
||||
let handle_clone = handle.clone();
|
||||
handle
|
||||
.insert_source(channel, move |event, _, _| {
|
||||
let calloop::channel::Event::Msg((stream, device_types)) = event else {
|
||||
return;
|
||||
};
|
||||
let context = match eis::Context::new(stream) {
|
||||
Ok(context) => context,
|
||||
Err(err) => {
|
||||
tracing::error!("Failed to create EI context: {}", err);
|
||||
return;
|
||||
}
|
||||
};
|
||||
let source = EiInput::new(context);
|
||||
if let Err(err) =
|
||||
handle_clone.insert_source(source, move |event, connection, data| match event {
|
||||
EiInputEvent::Connected => {
|
||||
let conn = connection.eis_connection().clone();
|
||||
let seat = connection.add_seat("default");
|
||||
let wants_keyboard = device_types & DEVICE_TYPE_KEYBOARD != 0;
|
||||
if wants_keyboard {
|
||||
let conf = data.common.config.xkb_config();
|
||||
// The ei_keyboard device is given the compositor keymap; its key
|
||||
// events feed the shared seat like any other keyboard.
|
||||
let _ = seat.add_keyboard("virtual keyboard", xkb_config_to_wl(&conf));
|
||||
// The text device lets clients inject keysyms/utf8 directly, delivered
|
||||
seat.add_text("virtual text");
|
||||
}
|
||||
if device_types & DEVICE_TYPE_POINTER != 0 {
|
||||
seat.add_pointer("virtual pointer");
|
||||
let regions = absolute_regions(data);
|
||||
seat.add_pointer_absolute(ABSOLUTE_POINTER_NAME, ®ions);
|
||||
}
|
||||
if device_types & DEVICE_TYPE_TOUCHSCREEN != 0 {
|
||||
let regions = absolute_regions(data);
|
||||
seat.add_touch("virtual touch", ®ions);
|
||||
}
|
||||
// Kb-capable connections get a shared-seat source so their `ei_keyboard`
|
||||
// key events feed the seat keyboard tracking.
|
||||
if wants_keyboard {
|
||||
data.common.ei_keyboard_source.insert(
|
||||
conn.clone(),
|
||||
smithay::input::keyboard::KeyboardSource::new_auxiliary(),
|
||||
);
|
||||
}
|
||||
// Track the seat for every connection
|
||||
data.common.ei_seats.insert(conn, seat);
|
||||
data.update_ei_input_method();
|
||||
}
|
||||
EiInputEvent::Disconnected => {
|
||||
let conn = connection.eis_connection().clone();
|
||||
let backend_id = InputBackendId::Ei(conn.clone());
|
||||
// Release any keys/modifiers and pointer buttons this remote still holds
|
||||
data.release_ei_keyboard(&conn);
|
||||
data.release_ei_pointer(&conn);
|
||||
data.clear_input_source_state(&backend_id);
|
||||
data.common.ei_seats.remove(&conn);
|
||||
data.common.ei_keyboard_source.remove(&conn);
|
||||
data.common.ei_pointer_buttons.remove(&conn);
|
||||
data.update_ei_input_method();
|
||||
// Notify the remaining libei clients of the now-cleared modifier state
|
||||
let seat = data.common.shell.read().seats.last_active().clone();
|
||||
if let Some(keyboard) = seat.get_keyboard() {
|
||||
data.broadcast_ei_keyboard_modifiers(&keyboard);
|
||||
}
|
||||
}
|
||||
EiInputEvent::Event(event) => {
|
||||
use smithay::backend::input::{InputEvent, KeyboardKeyEvent};
|
||||
match event {
|
||||
InputEvent::Keyboard { event } => {
|
||||
data.inject_ei_key(
|
||||
connection.eis_connection(),
|
||||
event.key_code(),
|
||||
event.state(),
|
||||
);
|
||||
}
|
||||
other => {
|
||||
let backend_id =
|
||||
InputBackendId::Ei(connection.eis_connection().clone());
|
||||
data.process_input_event(other, backend_id);
|
||||
if matches!(data.backend, BackendData::Kms(_)) {
|
||||
for output in data.common.shell.read().outputs() {
|
||||
data.backend.kms().schedule_render(output);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
EiInputEvent::TextKeysym { keysym, state } => {
|
||||
data.inject_ei_text_keysym(connection.eis_connection(), keysym, state);
|
||||
}
|
||||
EiInputEvent::TextUtf8 { text } => {
|
||||
data.inject_ei_text(&text);
|
||||
}
|
||||
})
|
||||
{
|
||||
tracing::error!("Failed to insert EI input source: {}", err);
|
||||
}
|
||||
})
|
||||
.expect("Failed to insert EI channel source into the event loop");
|
||||
|
||||
sender
|
||||
}
|
||||
|
||||
impl State {
|
||||
/// Act as the input method for text injection while any text-capable EI connection is
|
||||
/// active, so `ei_text` UTF-8 can be committed into the focused app even without a real
|
||||
/// IME, but only when none is bound (a real IME always wins)
|
||||
pub(crate) fn update_ei_input_method(&mut self) {
|
||||
let active = !self.common.ei_keyboard_source.is_empty();
|
||||
let seats = self
|
||||
.common
|
||||
.shell
|
||||
.read()
|
||||
.seats
|
||||
.iter()
|
||||
.cloned()
|
||||
.collect::<Vec<_>>();
|
||||
for seat in seats {
|
||||
let has_ime = seat.input_method().has_instance();
|
||||
let text_input = seat.text_input();
|
||||
if active {
|
||||
if !has_ime {
|
||||
text_input.set_compositor_input_method(true);
|
||||
}
|
||||
} else {
|
||||
text_input.set_compositor_input_method(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Inject UTF-8 text (from an EI `ei_text` device) into the focused client.
|
||||
pub fn inject_ei_text(&mut self, text: &str) {
|
||||
let seat = self.common.shell.read().seats.last_active().clone();
|
||||
// Only commit through text-input when we're the active input method (no real IME)
|
||||
if !seat.input_method().has_instance() {
|
||||
let text_input = seat.text_input();
|
||||
let mut injected = false;
|
||||
text_input.with_active_text_input(|ti, _surface| {
|
||||
ti.commit_string(Some(text.to_owned()));
|
||||
injected = true;
|
||||
});
|
||||
if injected {
|
||||
text_input.done(false);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// Bind the whole chunk to spare keycodes in one temporary keymap per batch (delivered
|
||||
// to just the focused client), so we change the keymap ~once per chunk instead of once
|
||||
// per character. Leaves the seat's own keyboard state untouched.
|
||||
let keysyms: Vec<Keysym> = text
|
||||
.chars()
|
||||
.map(Keysym::from_char)
|
||||
.filter(|keysym| keysym.raw() != 0)
|
||||
.collect();
|
||||
let Some(keyboard) = seat.get_keyboard() else {
|
||||
return;
|
||||
};
|
||||
// At most ~247 keysyms fit one spare keymap (keycodes 9..=255); leave margin.
|
||||
const BATCH: usize = 240;
|
||||
for batch in keysyms.chunks(BATCH) {
|
||||
keyboard.inject_text_keysyms(self, batch);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,15 +1,6 @@
|
|||
// SPDX-License-Identifier: GPL-3.0-only
|
||||
|
||||
fn main() {
|
||||
// libcosmic-yoda enables zbus' `tokio` feature, so zbus 5.14 expects an
|
||||
// ambient Tokio runtime via Handle::current(). cosmic-comp's loop is not
|
||||
// async, so hold a runtime guard for the lifetime of run().
|
||||
let runtime = tokio::runtime::Builder::new_multi_thread()
|
||||
.enable_all()
|
||||
.build()
|
||||
.expect("failed to build tokio runtime");
|
||||
let _guard = runtime.enter();
|
||||
|
||||
if let Err(err) = cosmic_comp::run(Default::default()) {
|
||||
tracing::error!("Error occured in main(): {}", err);
|
||||
std::process::exit(1);
|
||||
|
|
|
|||
|
|
@ -12,7 +12,7 @@ use id_tree::NodeId;
|
|||
use smithay::{
|
||||
backend::{
|
||||
drm::DrmNode,
|
||||
input::KeyState,
|
||||
input::{InputTime, KeyState},
|
||||
renderer::{
|
||||
element::{
|
||||
Element, Kind, RenderElement, UnderlyingStorage,
|
||||
|
|
@ -1012,7 +1012,7 @@ impl KeyboardTarget<State> for CosmicMapped {
|
|||
key: KeysymHandle<'_>,
|
||||
state: KeyState,
|
||||
serial: Serial,
|
||||
time: u32,
|
||||
time: InputTime,
|
||||
) {
|
||||
match &self.element {
|
||||
CosmicMappedInternal::Stack(s) => {
|
||||
|
|
|
|||
|
|
@ -11,7 +11,7 @@ use crate::{
|
|||
shell::{
|
||||
element::{CosmicMappedKey, CosmicMappedKeyInner},
|
||||
focus::target::PointerFocusTarget,
|
||||
grabs::{ReleaseMode, ResizeEdge},
|
||||
grabs::{GrabType, ReleaseMode, ResizeEdge},
|
||||
layout::tiling::NodeDesc,
|
||||
},
|
||||
state::State,
|
||||
|
|
@ -38,7 +38,7 @@ use shortcuts::action::{Direction, FocusDirection};
|
|||
use smithay::{
|
||||
backend::{
|
||||
drm::DrmNode,
|
||||
input::KeyState,
|
||||
input::{InputTime, KeyState, TabletToolDescriptor},
|
||||
renderer::{
|
||||
ImportAll, ImportMem, Renderer,
|
||||
element::{Element, Id as RendererId, Kind, RenderElement, UnderlyingStorage},
|
||||
|
|
@ -52,14 +52,23 @@ use smithay::{
|
|||
Seat,
|
||||
keyboard::{KeyboardTarget, KeysymHandle, ModifiersState},
|
||||
pointer::{
|
||||
AxisFrame, ButtonEvent, CursorImageStatus, GestureHoldBeginEvent, GestureHoldEndEvent,
|
||||
GesturePinchBeginEvent, GesturePinchEndEvent, GesturePinchUpdateEvent,
|
||||
GestureSwipeBeginEvent, GestureSwipeEndEvent, GestureSwipeUpdateEvent, MotionEvent,
|
||||
AxisFrame as PointerAxisFrame, ButtonEvent as PointerButtonEvent, CursorImageStatus,
|
||||
GestureHoldBeginEvent, GestureHoldEndEvent, GesturePinchBeginEvent,
|
||||
GesturePinchEndEvent, GesturePinchUpdateEvent, GestureSwipeBeginEvent,
|
||||
GestureSwipeEndEvent, GestureSwipeUpdateEvent, MotionEvent as PointerMotionEvent,
|
||||
PointerTarget, RelativeMotionEvent,
|
||||
},
|
||||
tablet::{
|
||||
Tablet, TabletSeatTrait,
|
||||
tool::{
|
||||
AxisFrame as ToolAxisFrame, ButtonEvent as ToolButtonEvent,
|
||||
DownEvent as ToolDownEvent, MotionEvent as ToolMotionEvent, TabletToolTarget,
|
||||
UpEvent as ToolUpEvent,
|
||||
},
|
||||
},
|
||||
touch::{
|
||||
DownEvent, FrameMarker, MotionEvent as TouchMotionEvent, OrientationEvent, ShapeEvent,
|
||||
TouchTarget, UpEvent,
|
||||
DownEvent as TouchDownEvent, FrameMarker, MotionEvent as TouchMotionEvent,
|
||||
OrientationEvent, ShapeEvent, TouchTarget, UpEvent as TouchUpEvent,
|
||||
},
|
||||
},
|
||||
output::Output,
|
||||
|
|
@ -112,6 +121,7 @@ pub struct CosmicStackInternal {
|
|||
previous_keyboard: AtomicUsize,
|
||||
pointer_entered: AtomicU8,
|
||||
touch_serial: AtomicU32,
|
||||
tablet_serial: AtomicU32,
|
||||
reenter: AtomicBool,
|
||||
potential_drag: Mutex<Option<usize>>,
|
||||
override_alive: AtomicBool,
|
||||
|
|
@ -171,6 +181,7 @@ impl CosmicStack {
|
|||
previous_keyboard: AtomicUsize::new(0),
|
||||
pointer_entered: AtomicU8::new(0),
|
||||
touch_serial: AtomicU32::new(0),
|
||||
tablet_serial: AtomicU32::new(0),
|
||||
reenter: AtomicBool::new(false),
|
||||
potential_drag: Mutex::new(None),
|
||||
override_alive: AtomicBool::new(true),
|
||||
|
|
@ -831,7 +842,7 @@ impl CosmicStack {
|
|||
)));
|
||||
};
|
||||
|
||||
let radii = radii.map(|[a, _, c, _]| [a, 0, c, 0]);
|
||||
let radii = radii.map(|[_, _, c, d]| [0, 0, c, d]);
|
||||
windows[active].push_render_elements(
|
||||
renderer,
|
||||
window_loc,
|
||||
|
|
@ -909,12 +920,19 @@ impl CosmicStack {
|
|||
false,
|
||||
);
|
||||
if let Some((grab, focus)) = res {
|
||||
if grab.is_touch_grab() {
|
||||
seat.get_touch().unwrap().set_grab(state, grab, serial);
|
||||
} else {
|
||||
seat.get_pointer()
|
||||
match grab.grab_type() {
|
||||
GrabType::Touch => {
|
||||
seat.get_touch().unwrap().set_grab(state, grab, serial)
|
||||
}
|
||||
GrabType::Pointer => seat
|
||||
.get_pointer()
|
||||
.unwrap()
|
||||
.set_grab(state, grab, serial, focus);
|
||||
.set_grab(state, grab, serial, focus),
|
||||
GrabType::TabletTool => seat
|
||||
.tablet_seat()
|
||||
.get_tool(grab.tool().unwrap())
|
||||
.unwrap()
|
||||
.set_grab(state, grab, InputTime::now(), serial, focus),
|
||||
}
|
||||
}
|
||||
});
|
||||
|
|
@ -997,17 +1015,17 @@ impl CosmicStack {
|
|||
.corner_radius(geometry_size)
|
||||
.unwrap_or([default_radius; 4]);
|
||||
|
||||
corners[0] = 0;
|
||||
corners[1] = 0;
|
||||
corners[3] = 0;
|
||||
|
||||
corners
|
||||
} else {
|
||||
let mut corners = active_window.corner_radius(geometry_size).unwrap_or(radii);
|
||||
|
||||
corners[0] = radii[0].max(corners[0]);
|
||||
corners[0] = radii[0];
|
||||
corners[1] = radii[1];
|
||||
corners[2] = radii[2].max(corners[2]);
|
||||
corners[3] = radii[3];
|
||||
corners[3] = radii[3].max(corners[3]);
|
||||
|
||||
corners
|
||||
}
|
||||
|
|
@ -1089,12 +1107,19 @@ impl Program for CosmicStackInternal {
|
|||
false,
|
||||
);
|
||||
if let Some((grab, focus)) = res {
|
||||
if grab.is_touch_grab() {
|
||||
seat.get_touch().unwrap().set_grab(state, grab, serial);
|
||||
} else {
|
||||
seat.get_pointer()
|
||||
match grab.grab_type() {
|
||||
GrabType::Touch => {
|
||||
seat.get_touch().unwrap().set_grab(state, grab, serial)
|
||||
}
|
||||
GrabType::Pointer => seat
|
||||
.get_pointer()
|
||||
.unwrap()
|
||||
.set_grab(state, grab, serial, focus);
|
||||
.set_grab(state, grab, serial, focus),
|
||||
GrabType::TabletTool => seat
|
||||
.tablet_seat()
|
||||
.get_tool(grab.tool().unwrap())
|
||||
.unwrap()
|
||||
.set_grab(state, grab, InputTime::now(), serial, focus),
|
||||
}
|
||||
}
|
||||
});
|
||||
|
|
@ -1547,7 +1572,7 @@ impl KeyboardTarget<State> for CosmicStack {
|
|||
key: KeysymHandle<'_>,
|
||||
state: KeyState,
|
||||
serial: Serial,
|
||||
time: u32,
|
||||
time: InputTime,
|
||||
) {
|
||||
let active = self.keyboard_leave_if_previous(seat, data, serial);
|
||||
self.0.with_program(|p| {
|
||||
|
|
@ -1587,7 +1612,7 @@ impl KeyboardTarget<State> for CosmicStack {
|
|||
}
|
||||
|
||||
impl PointerTarget<State> for CosmicStack {
|
||||
fn enter(&self, seat: &Seat<State>, data: &mut State, event: &MotionEvent) {
|
||||
fn enter(&self, seat: &Seat<State>, data: &mut State, event: &PointerMotionEvent) {
|
||||
let mut event = event.clone();
|
||||
self.0.with_program(|p| {
|
||||
let active_window = &p.windows.lock().unwrap()[p.active.load(Ordering::SeqCst)];
|
||||
|
|
@ -1615,7 +1640,7 @@ impl PointerTarget<State> for CosmicStack {
|
|||
PointerTarget::enter(&self.0, seat, data, &event)
|
||||
}
|
||||
|
||||
fn motion(&self, seat: &Seat<State>, data: &mut State, event: &MotionEvent) {
|
||||
fn motion(&self, seat: &Seat<State>, data: &mut State, event: &PointerMotionEvent) {
|
||||
let mut event = event.clone();
|
||||
self.0.with_program(|p| {
|
||||
let active = p.active.load(Ordering::SeqCst);
|
||||
|
|
@ -1658,7 +1683,7 @@ impl PointerTarget<State> for CosmicStack {
|
|||
) {
|
||||
}
|
||||
|
||||
fn button(&self, seat: &Seat<State>, data: &mut State, event: &ButtonEvent) {
|
||||
fn button(&self, seat: &Seat<State>, data: &mut State, event: &PointerButtonEvent) {
|
||||
match self.0.with_program(|p| p.current_focus()) {
|
||||
Some(Focus::Header) => PointerTarget::button(&self.0, seat, data, event),
|
||||
Some(x) => {
|
||||
|
|
@ -1689,14 +1714,12 @@ impl PointerTarget<State> for CosmicStack {
|
|||
state.common.config.cosmic_conf.edge_snap_threshold,
|
||||
false,
|
||||
);
|
||||
if let Some((grab, focus)) = res {
|
||||
if grab.is_touch_grab() {
|
||||
seat.get_touch().unwrap().set_grab(state, grab, serial);
|
||||
} else {
|
||||
seat.get_pointer()
|
||||
.unwrap()
|
||||
.set_grab(state, grab, serial, focus);
|
||||
}
|
||||
if let Some((grab, focus)) = res
|
||||
&& let GrabType::Pointer = grab.grab_type()
|
||||
{
|
||||
seat.get_pointer()
|
||||
.unwrap()
|
||||
.set_grab(state, grab, serial, focus);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
|
@ -1704,7 +1727,7 @@ impl PointerTarget<State> for CosmicStack {
|
|||
}
|
||||
}
|
||||
|
||||
fn axis(&self, seat: &Seat<State>, data: &mut State, frame: AxisFrame) {
|
||||
fn axis(&self, seat: &Seat<State>, data: &mut State, frame: PointerAxisFrame) {
|
||||
if let Some(Focus::Header) = self.0.with_program(|p| p.current_focus()) {
|
||||
PointerTarget::axis(&self.0, seat, data, frame)
|
||||
}
|
||||
|
|
@ -1716,7 +1739,7 @@ impl PointerTarget<State> for CosmicStack {
|
|||
}
|
||||
}
|
||||
|
||||
fn leave(&self, seat: &Seat<State>, data: &mut State, serial: Serial, time: u32) {
|
||||
fn leave(&self, seat: &Seat<State>, data: &mut State, serial: Serial, time: InputTime) {
|
||||
self.0.with_program(|p| {
|
||||
let mut cursor_state = seat
|
||||
.user_data()
|
||||
|
|
@ -1752,14 +1775,12 @@ impl PointerTarget<State> for CosmicStack {
|
|||
&state.common.event_loop_handle,
|
||||
false,
|
||||
);
|
||||
if let Some((grab, focus)) = res {
|
||||
if grab.is_touch_grab() {
|
||||
seat.get_touch().unwrap().set_grab(state, grab, serial);
|
||||
} else {
|
||||
seat.get_pointer()
|
||||
.unwrap()
|
||||
.set_grab(state, grab, serial, focus);
|
||||
}
|
||||
if let Some((grab, focus)) = res
|
||||
&& let GrabType::Pointer = grab.grab_type()
|
||||
{
|
||||
seat.get_pointer()
|
||||
.unwrap()
|
||||
.set_grab(state, grab, serial, focus);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
|
@ -1832,7 +1853,7 @@ impl PointerTarget<State> for CosmicStack {
|
|||
}
|
||||
|
||||
impl TouchTarget<State> for CosmicStack {
|
||||
fn down(&self, seat: &Seat<State>, data: &mut State, event: &DownEvent) {
|
||||
fn down(&self, seat: &Seat<State>, data: &mut State, event: &TouchDownEvent) {
|
||||
let mut event = event.clone();
|
||||
let active_window_geo = self.0.with_program(|p| {
|
||||
p.windows.lock().unwrap()[p.active.load(Ordering::SeqCst)].geometry()
|
||||
|
|
@ -1843,7 +1864,7 @@ impl TouchTarget<State> for CosmicStack {
|
|||
TouchTarget::down(&self.0, seat, data, &event)
|
||||
}
|
||||
|
||||
fn up(&self, seat: &Seat<State>, data: &mut State, event: &UpEvent) {
|
||||
fn up(&self, seat: &Seat<State>, data: &mut State, event: &TouchUpEvent) {
|
||||
TouchTarget::up(&self.0, seat, data, event)
|
||||
}
|
||||
|
||||
|
|
@ -1891,6 +1912,185 @@ impl TouchTarget<State> for CosmicStack {
|
|||
}
|
||||
}
|
||||
|
||||
impl TabletToolTarget<State> for CosmicStack {
|
||||
fn proximity_in(
|
||||
&self,
|
||||
seat: &Seat<State>,
|
||||
data: &mut State,
|
||||
tool_descriptor: &TabletToolDescriptor,
|
||||
tablet: &Tablet,
|
||||
serial: Serial,
|
||||
) {
|
||||
TabletToolTarget::proximity_in(&self.0, seat, data, tool_descriptor, tablet, serial)
|
||||
}
|
||||
|
||||
fn proximity_out(
|
||||
&self,
|
||||
seat: &Seat<State>,
|
||||
data: &mut State,
|
||||
tool_descriptor: &TabletToolDescriptor,
|
||||
) {
|
||||
self.0.with_program(|p| {
|
||||
let mut cursor_state = seat
|
||||
.user_data()
|
||||
.get::<CursorState>()
|
||||
.unwrap()
|
||||
.lock()
|
||||
.unwrap();
|
||||
cursor_state.unset_shape();
|
||||
let _previous = p.swap_focus(None);
|
||||
});
|
||||
TabletToolTarget::proximity_out(&self.0, seat, data, tool_descriptor);
|
||||
}
|
||||
|
||||
fn down(
|
||||
&self,
|
||||
seat: &Seat<State>,
|
||||
data: &mut State,
|
||||
tool_descriptor: &TabletToolDescriptor,
|
||||
event: &ToolDownEvent,
|
||||
) {
|
||||
self.0.with_program(|p| {
|
||||
p.tablet_serial
|
||||
.store(event.serial.into(), Ordering::Release)
|
||||
});
|
||||
match self.0.with_program(|p| p.current_focus()) {
|
||||
Some(Focus::Header) => {
|
||||
TabletToolTarget::down(&self.0, seat, data, tool_descriptor, event)
|
||||
}
|
||||
Some(x) => {
|
||||
let serial = self
|
||||
.0
|
||||
.with_program(|p| p.tablet_serial.load(Ordering::Acquire))
|
||||
.into();
|
||||
let seat = seat.clone();
|
||||
let Some(surface) = self.0.with_program(|p| {
|
||||
let window = &p.windows.lock().unwrap()[p.active.load(Ordering::SeqCst)];
|
||||
window.wl_surface().map(Cow::into_owned)
|
||||
}) else {
|
||||
return;
|
||||
};
|
||||
self.0.loop_handle().insert_idle(move |state| {
|
||||
let res = state.common.shell.write().resize_request(
|
||||
&surface,
|
||||
&seat,
|
||||
serial,
|
||||
match x {
|
||||
Focus::ResizeTop => ResizeEdge::TOP,
|
||||
Focus::ResizeTopLeft => ResizeEdge::TOP_LEFT,
|
||||
Focus::ResizeTopRight => ResizeEdge::TOP_RIGHT,
|
||||
Focus::ResizeBottom => ResizeEdge::BOTTOM,
|
||||
Focus::ResizeBottomLeft => ResizeEdge::BOTTOM_LEFT,
|
||||
Focus::ResizeBottomRight => ResizeEdge::BOTTOM_RIGHT,
|
||||
Focus::ResizeLeft => ResizeEdge::LEFT,
|
||||
Focus::ResizeRight => ResizeEdge::RIGHT,
|
||||
Focus::Header => unreachable!(),
|
||||
},
|
||||
state.common.config.cosmic_conf.edge_snap_threshold,
|
||||
false,
|
||||
);
|
||||
if let Some((grab, focus)) = res
|
||||
&& let GrabType::TabletTool = grab.grab_type()
|
||||
{
|
||||
seat.tablet_seat()
|
||||
.get_tool(grab.tool().unwrap())
|
||||
.unwrap()
|
||||
.set_grab(state, grab, InputTime::now(), serial, focus);
|
||||
}
|
||||
});
|
||||
}
|
||||
None => {}
|
||||
}
|
||||
}
|
||||
|
||||
fn up(
|
||||
&self,
|
||||
seat: &Seat<State>,
|
||||
data: &mut State,
|
||||
tool_descriptor: &TabletToolDescriptor,
|
||||
event: &ToolUpEvent,
|
||||
) {
|
||||
TabletToolTarget::up(&self.0, seat, data, tool_descriptor, event)
|
||||
}
|
||||
|
||||
fn motion(
|
||||
&self,
|
||||
seat: &Seat<State>,
|
||||
data: &mut State,
|
||||
tool_descriptor: &TabletToolDescriptor,
|
||||
event: &ToolMotionEvent,
|
||||
) {
|
||||
let mut event = event.clone();
|
||||
self.0.with_program(|p| {
|
||||
let active = p.active.load(Ordering::SeqCst);
|
||||
let active_window = &p.windows.lock().unwrap()[active];
|
||||
let Some(next) = Focus::under(active_window, TAB_HEIGHT, event.location) else {
|
||||
return;
|
||||
};
|
||||
let _previous = p.swap_focus(Some(next));
|
||||
|
||||
let mut cursor_state = seat
|
||||
.user_data()
|
||||
.get::<CursorState>()
|
||||
.unwrap()
|
||||
.lock()
|
||||
.unwrap();
|
||||
cursor_state.set_shape(next.cursor_shape());
|
||||
seat.set_cursor_image_status(CursorImageStatus::default_named());
|
||||
});
|
||||
|
||||
let active_window_geo = self.0.with_program(|p| {
|
||||
p.windows.lock().unwrap()[p.active.load(Ordering::SeqCst)].geometry()
|
||||
});
|
||||
event.location -= active_window_geo.loc.to_f64();
|
||||
|
||||
TabletToolTarget::motion(&self.0, seat, data, tool_descriptor, &event);
|
||||
if event.location.y < 0.0
|
||||
|| event.location.y > TAB_HEIGHT as f64
|
||||
|| event.location.x < 64.0
|
||||
|| event.location.x > (active_window_geo.size.w as f64 - 64.0)
|
||||
{
|
||||
self.start_drag(
|
||||
data,
|
||||
seat,
|
||||
self.0
|
||||
.with_program(|p| p.tablet_serial.load(Ordering::Acquire))
|
||||
.into(),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
fn axis(
|
||||
&self,
|
||||
seat: &Seat<State>,
|
||||
data: &mut State,
|
||||
tool_descriptor: &TabletToolDescriptor,
|
||||
frame: ToolAxisFrame,
|
||||
) {
|
||||
TabletToolTarget::axis(&self.0, seat, data, tool_descriptor, frame)
|
||||
}
|
||||
|
||||
fn button(
|
||||
&self,
|
||||
seat: &Seat<State>,
|
||||
data: &mut State,
|
||||
tool_descriptor: &TabletToolDescriptor,
|
||||
event: &ToolButtonEvent,
|
||||
) {
|
||||
TabletToolTarget::button(&self.0, seat, data, tool_descriptor, event)
|
||||
}
|
||||
|
||||
fn frame(
|
||||
&self,
|
||||
seat: &Seat<State>,
|
||||
data: &mut State,
|
||||
tool_descriptor: &TabletToolDescriptor,
|
||||
time: InputTime,
|
||||
) {
|
||||
TabletToolTarget::frame(&self.0, seat, data, tool_descriptor, time)
|
||||
}
|
||||
}
|
||||
|
||||
pub enum CosmicStackRenderElement<R: Renderer + ImportAll + ImportMem> {
|
||||
Header(IcedRenderElement<R>),
|
||||
Shadow(PixelShaderElement),
|
||||
|
|
|
|||
|
|
@ -8,6 +8,7 @@ use cosmic::{
|
|||
layout::{Layout, Limits, Node},
|
||||
mouse, overlay, renderer,
|
||||
text::{Ellipsize, EllipsizeHeightLimit, Shaping, Wrapping},
|
||||
touch,
|
||||
widget::{Id, Widget, operation::Operation, tree::Tree},
|
||||
},
|
||||
widget::{self, container::draw_background, rule::FillMode, scrollable::AbsoluteOffset},
|
||||
|
|
@ -370,6 +371,7 @@ where
|
|||
if matches!(
|
||||
event,
|
||||
event::Event::Mouse(mouse::Event::ButtonPressed(mouse::Button::Left))
|
||||
| event::Event::Touch(touch::Event::FingerPressed { .. })
|
||||
) && let Some(message) = self.press_message.clone()
|
||||
{
|
||||
shell.publish(message);
|
||||
|
|
@ -388,6 +390,7 @@ where
|
|||
if matches!(
|
||||
event,
|
||||
event::Event::Mouse(mouse::Event::ButtonReleased(mouse::Button::Left))
|
||||
| event::Event::Touch(touch::Event::FingerLifted { .. })
|
||||
) {
|
||||
shell.publish(Message::activate(self.idx));
|
||||
shell.capture_event();
|
||||
|
|
|
|||
|
|
@ -31,7 +31,7 @@ use keyframe::{
|
|||
functions::{EaseInOutCubic, EaseOutCubic},
|
||||
};
|
||||
use std::{
|
||||
collections::{HashMap, HashSet, VecDeque},
|
||||
collections::{HashMap, VecDeque},
|
||||
time::{Duration, Instant},
|
||||
};
|
||||
|
||||
|
|
@ -757,12 +757,10 @@ where
|
|||
state.last_state = Some(current_state.clone());
|
||||
}
|
||||
let last_state = state.last_state.as_mut().unwrap();
|
||||
let unknown_keys = current_state
|
||||
.keys()
|
||||
.collect::<HashSet<_>>()
|
||||
.symmetric_difference(&last_state.keys().collect::<HashSet<_>>())
|
||||
.next()
|
||||
.is_some();
|
||||
let unknown_keys = current_state.len() != last_state.len()
|
||||
|| current_state
|
||||
.keys()
|
||||
.any(|key| !last_state.contains_key(key));
|
||||
|
||||
enum Difference {
|
||||
NewOrRemoved,
|
||||
|
|
|
|||
|
|
@ -21,6 +21,7 @@ use std::{
|
|||
use smithay::{
|
||||
backend::{
|
||||
drm::DrmNode,
|
||||
input::InputTime,
|
||||
renderer::{
|
||||
ImportAll, Renderer, buffer_has_alpha,
|
||||
element::{Kind, RenderElementStates, surface::KindEvaluation},
|
||||
|
|
@ -838,10 +839,7 @@ impl CosmicSurface {
|
|||
self.0
|
||||
.send_dmabuf_feedback(output, primary_scan_out_output, |_, data| {
|
||||
if is_fullscreen {
|
||||
feedback
|
||||
.primary_scanout_feedback
|
||||
.as_ref()
|
||||
.unwrap_or(&feedback.render_feedback)
|
||||
&feedback.primary_scanout_feedback
|
||||
} else if frame_time_filter_fn(data) == Kind::ScanoutCandidate {
|
||||
feedback
|
||||
.overlay_scanout_feedback
|
||||
|
|
@ -1087,7 +1085,7 @@ impl KeyboardTarget<State> for CosmicSurface {
|
|||
key: KeysymHandle<'_>,
|
||||
state: smithay::backend::input::KeyState,
|
||||
serial: smithay::utils::Serial,
|
||||
time: u32,
|
||||
time: InputTime,
|
||||
) {
|
||||
match self.0.underlying_surface() {
|
||||
WindowSurface::Wayland(toplevel) => {
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@ use crate::{
|
|||
shell::{
|
||||
element::{CosmicMappedKey, CosmicMappedKeyInner},
|
||||
focus::target::PointerFocusTarget,
|
||||
grabs::{ReleaseMode, ResizeEdge},
|
||||
grabs::{GrabType, ReleaseMode, ResizeEdge},
|
||||
},
|
||||
state::State,
|
||||
utils::{
|
||||
|
|
@ -21,7 +21,7 @@ use cosmic_comp_config::AppearanceConfig;
|
|||
use smithay::{
|
||||
backend::{
|
||||
drm::DrmNode,
|
||||
input::KeyState,
|
||||
input::{InputTime, KeyState, TabletToolDescriptor},
|
||||
renderer::{
|
||||
ImportAll, ImportMem, Renderer,
|
||||
element::{Element, Id as RendererId, Kind, RenderElement, UnderlyingStorage},
|
||||
|
|
@ -35,14 +35,23 @@ use smithay::{
|
|||
Seat,
|
||||
keyboard::{KeyboardTarget, KeysymHandle, ModifiersState},
|
||||
pointer::{
|
||||
AxisFrame, ButtonEvent, CursorIcon, CursorImageStatus, GestureHoldBeginEvent,
|
||||
GestureHoldEndEvent, GesturePinchBeginEvent, GesturePinchEndEvent,
|
||||
GesturePinchUpdateEvent, GestureSwipeBeginEvent, GestureSwipeEndEvent,
|
||||
GestureSwipeUpdateEvent, MotionEvent, PointerTarget, RelativeMotionEvent,
|
||||
AxisFrame as PointerAxisFrame, ButtonEvent as PointerButtonEvent, CursorIcon,
|
||||
CursorImageStatus, GestureHoldBeginEvent, GestureHoldEndEvent, GesturePinchBeginEvent,
|
||||
GesturePinchEndEvent, GesturePinchUpdateEvent, GestureSwipeBeginEvent,
|
||||
GestureSwipeEndEvent, GestureSwipeUpdateEvent, MotionEvent as PointerMotionEvent,
|
||||
PointerTarget, RelativeMotionEvent,
|
||||
},
|
||||
tablet::{
|
||||
Tablet, TabletSeatTrait,
|
||||
tool::{
|
||||
AxisFrame as ToolAxisFrame, ButtonEvent as ToolButtonEvent,
|
||||
DownEvent as ToolDownEvent, MotionEvent as ToolMotionEvent, TabletToolTarget,
|
||||
UpEvent as ToolUpEvent,
|
||||
},
|
||||
},
|
||||
touch::{
|
||||
DownEvent, FrameMarker, MotionEvent as TouchMotionEvent, OrientationEvent, ShapeEvent,
|
||||
TouchTarget, UpEvent,
|
||||
DownEvent as TouchDownEvent, FrameMarker, MotionEvent as TouchMotionEvent,
|
||||
OrientationEvent, ShapeEvent, TouchTarget, UpEvent as TouchUpEvent,
|
||||
},
|
||||
},
|
||||
output::Output,
|
||||
|
|
@ -429,12 +438,12 @@ impl CosmicWindow {
|
|||
.map(|x| (x * scale as f32).round() as u8);
|
||||
if has_ssd && !clip {
|
||||
// bottom corners
|
||||
radii[0] = 0;
|
||||
radii[2] = 0;
|
||||
radii[3] = 0;
|
||||
if is_tiled {
|
||||
// top corners
|
||||
radii[0] = 0;
|
||||
radii[1] = 0;
|
||||
radii[3] = 0;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -501,12 +510,12 @@ impl CosmicWindow {
|
|||
&& !is_maximized;
|
||||
if has_ssd && !clip {
|
||||
// bottom corners
|
||||
radii[0] = 0;
|
||||
radii[2] = 0;
|
||||
radii[3] = 0;
|
||||
if is_tiled {
|
||||
// top corners
|
||||
radii[0] = 0;
|
||||
radii[1] = 0;
|
||||
radii[3] = 0;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -551,8 +560,8 @@ impl CosmicWindow {
|
|||
self.0.with_program(|p| {
|
||||
let mut radii = radii;
|
||||
if has_ssd {
|
||||
radii[0] = 0;
|
||||
radii[1] = 0;
|
||||
radii[3] = 0;
|
||||
}
|
||||
let theme = p.theme.lock().unwrap();
|
||||
let frosted = if theme.cosmic().frosted_windows {
|
||||
|
|
@ -576,8 +585,8 @@ impl CosmicWindow {
|
|||
});
|
||||
|
||||
if has_ssd {
|
||||
radii[0] = 0;
|
||||
radii[2] = 0;
|
||||
radii[3] = 0;
|
||||
let ssd_loc = location
|
||||
+ self
|
||||
.0
|
||||
|
|
@ -682,26 +691,26 @@ impl CosmicWindow {
|
|||
(has_ssd, true) => {
|
||||
let mut corners = p.window.corner_radius(geometry_size).unwrap_or(radii);
|
||||
|
||||
corners[0] = radii[0].max(corners[0]);
|
||||
corners[0] = if has_ssd {
|
||||
radii[0]
|
||||
} else {
|
||||
radii[0].max(corners[0])
|
||||
};
|
||||
corners[1] = if has_ssd {
|
||||
radii[1]
|
||||
} else {
|
||||
radii[1].max(corners[1])
|
||||
};
|
||||
corners[2] = radii[2].max(corners[2]);
|
||||
corners[3] = if has_ssd {
|
||||
radii[3]
|
||||
} else {
|
||||
radii[3].max(corners[3])
|
||||
};
|
||||
corners[3] = radii[3].max(corners[3]);
|
||||
|
||||
corners
|
||||
}
|
||||
(true, false) => p
|
||||
.window
|
||||
.corner_radius(geometry_size)
|
||||
.map(|[a, _, c, _]| [a, radii[1], c, radii[3]])
|
||||
.unwrap_or([default_radius, radii[1], default_radius, radii[3]]),
|
||||
.map(|[_, _, c, d]| [radii[0], radii[1], c, d])
|
||||
.unwrap_or([radii[0], radii[1], default_radius, default_radius]),
|
||||
(false, false) => p
|
||||
.window
|
||||
.corner_radius(geometry_size)
|
||||
|
|
@ -746,12 +755,19 @@ impl Program for CosmicWindowInternal {
|
|||
false,
|
||||
);
|
||||
if let Some((grab, focus)) = res {
|
||||
if grab.is_touch_grab() {
|
||||
seat.get_touch().unwrap().set_grab(state, grab, serial);
|
||||
} else {
|
||||
seat.get_pointer()
|
||||
match grab.grab_type() {
|
||||
GrabType::Touch => {
|
||||
seat.get_touch().unwrap().set_grab(state, grab, serial)
|
||||
}
|
||||
GrabType::Pointer => seat
|
||||
.get_pointer()
|
||||
.unwrap()
|
||||
.set_grab(state, grab, serial, focus);
|
||||
.set_grab(state, grab, serial, focus),
|
||||
GrabType::TabletTool => seat
|
||||
.tablet_seat()
|
||||
.get_tool(grab.tool().unwrap())
|
||||
.unwrap()
|
||||
.set_grab(state, grab, InputTime::now(), serial, focus),
|
||||
}
|
||||
}
|
||||
});
|
||||
|
|
@ -981,7 +997,7 @@ impl KeyboardTarget<State> for CosmicWindow {
|
|||
key: KeysymHandle<'_>,
|
||||
state: KeyState,
|
||||
serial: Serial,
|
||||
time: u32,
|
||||
time: InputTime,
|
||||
) {
|
||||
self.0
|
||||
.with_program(|p| KeyboardTarget::key(&p.window, seat, data, key, state, serial, time))
|
||||
|
|
@ -999,7 +1015,7 @@ impl KeyboardTarget<State> for CosmicWindow {
|
|||
}
|
||||
|
||||
impl PointerTarget<State> for CosmicWindow {
|
||||
fn enter(&self, seat: &Seat<State>, data: &mut State, event: &MotionEvent) {
|
||||
fn enter(&self, seat: &Seat<State>, data: &mut State, event: &PointerMotionEvent) {
|
||||
let mut event = event.clone();
|
||||
self.0.with_program(|p| {
|
||||
let has_ssd = p.has_ssd(false);
|
||||
|
|
@ -1012,8 +1028,7 @@ impl PointerTarget<State> for CosmicWindow {
|
|||
return;
|
||||
};
|
||||
|
||||
let old_focus = p.swap_focus(Some(next));
|
||||
assert_eq!(old_focus, None);
|
||||
let _ = p.swap_focus(Some(next));
|
||||
|
||||
let cursor_state = seat.user_data().get::<CursorState>().unwrap();
|
||||
cursor_state.lock().unwrap().set_shape(next.cursor_shape());
|
||||
|
|
@ -1025,7 +1040,7 @@ impl PointerTarget<State> for CosmicWindow {
|
|||
PointerTarget::enter(&self.0, seat, data, &event)
|
||||
}
|
||||
|
||||
fn motion(&self, seat: &Seat<State>, data: &mut State, event: &MotionEvent) {
|
||||
fn motion(&self, seat: &Seat<State>, data: &mut State, event: &PointerMotionEvent) {
|
||||
let mut event = event.clone();
|
||||
self.0.with_program(|p| {
|
||||
let has_ssd = p.has_ssd(false);
|
||||
|
|
@ -1057,7 +1072,7 @@ impl PointerTarget<State> for CosmicWindow {
|
|||
) {
|
||||
}
|
||||
|
||||
fn button(&self, seat: &Seat<State>, data: &mut State, event: &ButtonEvent) {
|
||||
fn button(&self, seat: &Seat<State>, data: &mut State, event: &PointerButtonEvent) {
|
||||
match self.0.with_program(|p| p.current_focus()) {
|
||||
Some(Focus::Header) => PointerTarget::button(&self.0, seat, data, event),
|
||||
Some(x) => {
|
||||
|
|
@ -1093,14 +1108,12 @@ impl PointerTarget<State> for CosmicWindow {
|
|||
false,
|
||||
);
|
||||
|
||||
if let Some((grab, focus)) = res {
|
||||
if grab.is_touch_grab() {
|
||||
seat.get_touch().unwrap().set_grab(state, grab, serial);
|
||||
} else {
|
||||
seat.get_pointer()
|
||||
.unwrap()
|
||||
.set_grab(state, grab, serial, focus);
|
||||
}
|
||||
if let Some((grab, focus)) = res
|
||||
&& let GrabType::Pointer = grab.grab_type()
|
||||
{
|
||||
seat.get_pointer()
|
||||
.unwrap()
|
||||
.set_grab(state, grab, serial, focus)
|
||||
}
|
||||
});
|
||||
}
|
||||
|
|
@ -1108,7 +1121,7 @@ impl PointerTarget<State> for CosmicWindow {
|
|||
}
|
||||
}
|
||||
|
||||
fn axis(&self, seat: &Seat<State>, data: &mut State, frame: AxisFrame) {
|
||||
fn axis(&self, seat: &Seat<State>, data: &mut State, frame: PointerAxisFrame) {
|
||||
if let Some(Focus::Header) = self.0.with_program(|p| p.current_focus()) {
|
||||
PointerTarget::axis(&self.0, seat, data, frame)
|
||||
}
|
||||
|
|
@ -1120,7 +1133,7 @@ impl PointerTarget<State> for CosmicWindow {
|
|||
}
|
||||
}
|
||||
|
||||
fn leave(&self, seat: &Seat<State>, data: &mut State, serial: Serial, time: u32) {
|
||||
fn leave(&self, seat: &Seat<State>, data: &mut State, serial: Serial, time: InputTime) {
|
||||
self.0.with_program(|p| {
|
||||
let cursor_state = seat.user_data().get::<CursorState>().unwrap();
|
||||
cursor_state.lock().unwrap().unset_shape();
|
||||
|
|
@ -1195,7 +1208,7 @@ impl PointerTarget<State> for CosmicWindow {
|
|||
}
|
||||
|
||||
impl TouchTarget<State> for CosmicWindow {
|
||||
fn down(&self, seat: &Seat<State>, data: &mut State, event: &DownEvent) {
|
||||
fn down(&self, seat: &Seat<State>, data: &mut State, event: &TouchDownEvent) {
|
||||
let mut event = event.clone();
|
||||
self.0.with_program(|p| {
|
||||
event.location -= p.window.geometry().loc.to_f64();
|
||||
|
|
@ -1203,7 +1216,7 @@ impl TouchTarget<State> for CosmicWindow {
|
|||
TouchTarget::down(&self.0, seat, data, &event)
|
||||
}
|
||||
|
||||
fn up(&self, seat: &Seat<State>, data: &mut State, event: &UpEvent) {
|
||||
fn up(&self, seat: &Seat<State>, data: &mut State, event: &TouchUpEvent) {
|
||||
TouchTarget::up(&self.0, seat, data, event)
|
||||
}
|
||||
|
||||
|
|
@ -1234,6 +1247,155 @@ impl TouchTarget<State> for CosmicWindow {
|
|||
}
|
||||
}
|
||||
|
||||
impl TabletToolTarget<State> for CosmicWindow {
|
||||
fn proximity_in(
|
||||
&self,
|
||||
seat: &Seat<State>,
|
||||
data: &mut State,
|
||||
tool_descriptor: &TabletToolDescriptor,
|
||||
tablet: &Tablet,
|
||||
serial: Serial,
|
||||
) {
|
||||
TabletToolTarget::proximity_in(&self.0, seat, data, tool_descriptor, tablet, serial)
|
||||
}
|
||||
|
||||
fn proximity_out(
|
||||
&self,
|
||||
seat: &Seat<State>,
|
||||
data: &mut State,
|
||||
tool_descriptor: &TabletToolDescriptor,
|
||||
) {
|
||||
self.0.with_program(|p| {
|
||||
let cursor_state = seat.user_data().get::<CursorState>().unwrap();
|
||||
cursor_state.lock().unwrap().unset_shape();
|
||||
let _previous = p.swap_focus(None);
|
||||
});
|
||||
TabletToolTarget::proximity_out(&self.0, seat, data, tool_descriptor)
|
||||
}
|
||||
|
||||
fn down(
|
||||
&self,
|
||||
seat: &Seat<State>,
|
||||
data: &mut State,
|
||||
tool_descriptor: &TabletToolDescriptor,
|
||||
event: &ToolDownEvent,
|
||||
) {
|
||||
match self.0.with_program(|p| p.current_focus()) {
|
||||
Some(Focus::Header) => {
|
||||
TabletToolTarget::down(&self.0, seat, data, tool_descriptor, event)
|
||||
}
|
||||
Some(x) => {
|
||||
let serial = event.serial;
|
||||
let seat = seat.clone();
|
||||
let Some(surface) = self.wl_surface().map(Cow::into_owned) else {
|
||||
return;
|
||||
};
|
||||
|
||||
self.0.loop_handle().insert_idle(move |state| {
|
||||
let res = state.common.shell.write().resize_request(
|
||||
&surface,
|
||||
&seat,
|
||||
serial,
|
||||
match x {
|
||||
Focus::ResizeTop => ResizeEdge::TOP,
|
||||
Focus::ResizeTopLeft => ResizeEdge::TOP_LEFT,
|
||||
Focus::ResizeTopRight => ResizeEdge::TOP_RIGHT,
|
||||
Focus::ResizeBottom => ResizeEdge::BOTTOM,
|
||||
Focus::ResizeBottomLeft => ResizeEdge::BOTTOM_LEFT,
|
||||
Focus::ResizeBottomRight => ResizeEdge::BOTTOM_RIGHT,
|
||||
Focus::ResizeLeft => ResizeEdge::LEFT,
|
||||
Focus::ResizeRight => ResizeEdge::RIGHT,
|
||||
Focus::Header => unreachable!(),
|
||||
},
|
||||
state.common.config.cosmic_conf.edge_snap_threshold,
|
||||
false,
|
||||
);
|
||||
|
||||
if let Some((grab, focus)) = res
|
||||
&& let GrabType::TabletTool = grab.grab_type()
|
||||
{
|
||||
seat.tablet_seat()
|
||||
.get_tool(grab.tool().unwrap())
|
||||
.unwrap()
|
||||
.set_grab(state, grab, InputTime::now(), serial, focus)
|
||||
}
|
||||
});
|
||||
}
|
||||
None => {}
|
||||
}
|
||||
}
|
||||
|
||||
fn up(
|
||||
&self,
|
||||
seat: &Seat<State>,
|
||||
data: &mut State,
|
||||
tool_descriptor: &TabletToolDescriptor,
|
||||
event: &ToolUpEvent,
|
||||
) {
|
||||
TabletToolTarget::up(&self.0, seat, data, tool_descriptor, event)
|
||||
}
|
||||
|
||||
fn motion(
|
||||
&self,
|
||||
seat: &Seat<State>,
|
||||
data: &mut State,
|
||||
tool_descriptor: &TabletToolDescriptor,
|
||||
event: &ToolMotionEvent,
|
||||
) {
|
||||
let mut event = event.clone();
|
||||
self.0.with_program(|p| {
|
||||
let has_ssd = p.has_ssd(false);
|
||||
if has_ssd || p.has_tiled_state() {
|
||||
let Some(next) = Focus::under(
|
||||
&p.window,
|
||||
if has_ssd { SSD_HEIGHT } else { 0 },
|
||||
event.location,
|
||||
) else {
|
||||
return;
|
||||
};
|
||||
let _previous = p.swap_focus(Some(next));
|
||||
|
||||
let cursor_state = seat.user_data().get::<CursorState>().unwrap();
|
||||
cursor_state.lock().unwrap().set_shape(next.cursor_shape());
|
||||
seat.set_cursor_image_status(CursorImageStatus::default_named());
|
||||
}
|
||||
});
|
||||
|
||||
event.location -= self.0.with_program(|p| p.window.geometry().loc.to_f64());
|
||||
TabletToolTarget::motion(&self.0, seat, data, tool_descriptor, &event)
|
||||
}
|
||||
|
||||
fn axis(
|
||||
&self,
|
||||
seat: &Seat<State>,
|
||||
data: &mut State,
|
||||
tool_descriptor: &TabletToolDescriptor,
|
||||
frame: ToolAxisFrame,
|
||||
) {
|
||||
TabletToolTarget::axis(&self.0, seat, data, tool_descriptor, frame)
|
||||
}
|
||||
|
||||
fn button(
|
||||
&self,
|
||||
seat: &Seat<State>,
|
||||
data: &mut State,
|
||||
tool_descriptor: &TabletToolDescriptor,
|
||||
event: &ToolButtonEvent,
|
||||
) {
|
||||
TabletToolTarget::button(&self.0, seat, data, tool_descriptor, event)
|
||||
}
|
||||
|
||||
fn frame(
|
||||
&self,
|
||||
seat: &Seat<State>,
|
||||
data: &mut State,
|
||||
tool_descriptor: &TabletToolDescriptor,
|
||||
time: InputTime,
|
||||
) {
|
||||
TabletToolTarget::frame(&self.0, seat, data, tool_descriptor, time)
|
||||
}
|
||||
}
|
||||
|
||||
impl WaylandFocus for CosmicWindow {
|
||||
fn wl_surface(&self) -> Option<Cow<'_, WlSurface>> {
|
||||
self.0.with_program(|p| {
|
||||
|
|
|
|||
|
|
@ -2,11 +2,18 @@ use crate::{
|
|||
shell::{CosmicSurface, MinimizedWindow, Shell, Trigger, element::CosmicMapped},
|
||||
state::{Common, State},
|
||||
utils::prelude::*,
|
||||
wayland::handlers::{xdg_shell::PopupGrabData, xwayland_keyboard_grab::XWaylandGrabSeatData},
|
||||
wayland::{
|
||||
handlers::{xdg_shell::PopupGrabData, xwayland_keyboard_grab::XWaylandGrabSeatData},
|
||||
protocols::session_lock_layer::layer_show_on_lock,
|
||||
},
|
||||
};
|
||||
use indexmap::IndexSet;
|
||||
use smithay::{
|
||||
desktop::{PopupUngrabStrategy, layer_map_for_output},
|
||||
backend::input::InputTime,
|
||||
desktop::{
|
||||
LayerSurface, PopupUngrabStrategy, WindowSurfaceType, find_popup_root_surface,
|
||||
layer_map_for_output,
|
||||
},
|
||||
input::{Seat, pointer::MotionEvent},
|
||||
output::Output,
|
||||
reexports::wayland_server::{Resource, protocol::wl_surface::WlSurface},
|
||||
|
|
@ -212,6 +219,29 @@ impl Shell {
|
|||
state.common.shell.write().update_active();
|
||||
}
|
||||
|
||||
// We suppress Element(X) to Fullscreen(X) transition to avoid
|
||||
// loss of focus by the X window when having a transition to fullscreen
|
||||
// but in the case of X11 unmap/map the leave/enter needs to happen for the X11
|
||||
// internal state to be focused on the window
|
||||
pub fn set_focus_on_x11_map(
|
||||
state: &mut State,
|
||||
target: &KeyboardFocusTarget,
|
||||
seat: &Seat<State>,
|
||||
update_cursor: bool,
|
||||
) {
|
||||
let need_reset = seat
|
||||
.get_keyboard()
|
||||
.and_then(|keyboard| keyboard.current_focus())
|
||||
.and_then(|current| current.x11_surface())
|
||||
.is_some_and(|current| Some(current) == target.x11_surface());
|
||||
|
||||
if need_reset {
|
||||
update_focus_state(seat, None, state, None, false);
|
||||
}
|
||||
|
||||
Shell::set_focus(state, Some(target), seat, None, update_cursor);
|
||||
}
|
||||
|
||||
pub fn append_focus_stack(&mut self, target: impl Into<FocusTarget>, seat: &Seat<State>) {
|
||||
let target = target.into();
|
||||
if target.is_minimized() {
|
||||
|
|
@ -396,7 +426,7 @@ fn update_focus_state(
|
|||
&MotionEvent {
|
||||
location: new_pos.as_logical(),
|
||||
serial: SERIAL_COUNTER.next_serial(),
|
||||
time: 0,
|
||||
time: InputTime::now(),
|
||||
},
|
||||
);
|
||||
}
|
||||
|
|
@ -603,19 +633,42 @@ fn focus_target_is_valid(
|
|||
output: &Output,
|
||||
target: KeyboardFocusTarget,
|
||||
) -> bool {
|
||||
// If a session lock is active, only lock surfaces can be focused
|
||||
// If a session lock is active, only lock surfaces and lock layers can be focused
|
||||
if shell.session_lock.is_some() {
|
||||
if let KeyboardFocusTarget::LayerSurface(layer) = &target {
|
||||
return layer_show_on_lock(layer.wl_surface());
|
||||
} else if let KeyboardFocusTarget::Popup(popup) = &target
|
||||
&& let Ok(root) = find_popup_root_surface(popup)
|
||||
{
|
||||
return layer_show_on_lock(&root);
|
||||
}
|
||||
return matches!(target, KeyboardFocusTarget::LockSurface(_));
|
||||
}
|
||||
|
||||
// If an exclusive layer shell surface exists (on any output), only exclusive
|
||||
// shell surfaces can have focus, on the highest layer with exclusive surfaces.
|
||||
// Popups are judged by their root surface, so an exclusive surface can
|
||||
// still open grabbing popups (menus, dropdowns, context menus).
|
||||
if let Some(layer) = exclusive_layer_surface_layer(shell) {
|
||||
return if let KeyboardFocusTarget::LayerSurface(layer_surface) = target {
|
||||
let is_exclusive_on_layer = |layer_surface: &LayerSurface| {
|
||||
let data = layer_surface.cached_state();
|
||||
(data.keyboard_interactivity, data.layer) == (KeyboardInteractivity::Exclusive, layer)
|
||||
} else {
|
||||
false
|
||||
};
|
||||
return match target {
|
||||
KeyboardFocusTarget::LayerSurface(layer_surface) => {
|
||||
is_exclusive_on_layer(&layer_surface)
|
||||
}
|
||||
KeyboardFocusTarget::Popup(popup) => find_popup_root_surface(&popup)
|
||||
.ok()
|
||||
.and_then(|root| {
|
||||
shell.outputs().find_map(|o| {
|
||||
layer_map_for_output(o)
|
||||
.layer_for_surface(&root, WindowSurfaceType::ALL)
|
||||
.map(&is_exclusive_on_layer)
|
||||
})
|
||||
})
|
||||
.unwrap_or(false),
|
||||
_ => false,
|
||||
};
|
||||
}
|
||||
|
||||
|
|
@ -745,7 +798,7 @@ fn update_pointer_focus(state: &mut State, seat: &Seat<State>) {
|
|||
&MotionEvent {
|
||||
location: pointer.current_location(),
|
||||
serial: SERIAL_COUNTER.next_serial(),
|
||||
time: state.common.clock.now().as_millis(),
|
||||
time: InputTime::now(),
|
||||
},
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -22,7 +22,7 @@ use crate::{
|
|||
prelude::OutputExt,
|
||||
quirks::{WORKSPACE_OVERVIEW_NAMESPACE, workspace_overview_is_open},
|
||||
},
|
||||
wayland::protocols::workspace::WorkspaceHandle,
|
||||
wayland::protocols::{session_lock_layer::layer_show_on_lock, workspace::WorkspaceHandle},
|
||||
};
|
||||
|
||||
pub enum Stage<'a> {
|
||||
|
|
@ -77,6 +77,8 @@ fn render_input_order_internal<R: 'static>(
|
|||
element_filter: ElementFilter,
|
||||
mut callback: impl FnMut(Stage) -> ControlFlow<Result<R, OutputNoMode>, ()>,
|
||||
) -> ControlFlow<Result<R, OutputNoMode>, ()> {
|
||||
// NOTE: Keep in sync with other surface iteration functions
|
||||
|
||||
if shell
|
||||
.zoom_state
|
||||
.as_ref()
|
||||
|
|
@ -87,7 +89,92 @@ fn render_input_order_internal<R: 'static>(
|
|||
|
||||
// Session Lock
|
||||
if let Some(session_lock) = &shell.session_lock {
|
||||
return callback(Stage::SessionLock(session_lock.surfaces.get(output)));
|
||||
for (layer, popup, location) in layer_popups(output, Layer::Overlay, element_filter) {
|
||||
if !layer_show_on_lock(layer.wl_surface()) {
|
||||
continue;
|
||||
}
|
||||
callback(Stage::LayerPopup {
|
||||
layer,
|
||||
popup: &popup,
|
||||
location,
|
||||
workspace_idx: current.1,
|
||||
})?;
|
||||
}
|
||||
for (layer, location) in layer_surfaces(output, Layer::Overlay, element_filter) {
|
||||
if !layer_show_on_lock(layer.wl_surface()) {
|
||||
continue;
|
||||
}
|
||||
callback(Stage::LayerSurface {
|
||||
layer,
|
||||
location,
|
||||
workspace_idx: current.1,
|
||||
})?;
|
||||
}
|
||||
for (layer, popup, location) in layer_popups(output, Layer::Top, element_filter) {
|
||||
if !layer_show_on_lock(layer.wl_surface()) {
|
||||
continue;
|
||||
}
|
||||
callback(Stage::LayerPopup {
|
||||
layer,
|
||||
popup: &popup,
|
||||
location,
|
||||
workspace_idx: current.1,
|
||||
})?;
|
||||
}
|
||||
for (layer, location) in layer_surfaces(output, Layer::Top, element_filter) {
|
||||
if !layer_show_on_lock(layer.wl_surface()) {
|
||||
continue;
|
||||
}
|
||||
callback(Stage::LayerSurface {
|
||||
layer,
|
||||
location,
|
||||
workspace_idx: current.1,
|
||||
})?;
|
||||
}
|
||||
callback(Stage::SessionLock(session_lock.surfaces.get(output)))?;
|
||||
for (layer, popup, location) in layer_popups(output, Layer::Bottom, element_filter) {
|
||||
if !layer_show_on_lock(layer.wl_surface()) {
|
||||
continue;
|
||||
}
|
||||
callback(Stage::LayerPopup {
|
||||
layer,
|
||||
popup: &popup,
|
||||
location,
|
||||
workspace_idx: current.1,
|
||||
})?;
|
||||
}
|
||||
for (layer, popup, location) in layer_popups(output, Layer::Background, element_filter) {
|
||||
if !layer_show_on_lock(layer.wl_surface()) {
|
||||
continue;
|
||||
}
|
||||
callback(Stage::LayerPopup {
|
||||
layer,
|
||||
popup: &popup,
|
||||
location,
|
||||
workspace_idx: current.1,
|
||||
})?;
|
||||
}
|
||||
for (layer, location) in layer_surfaces(output, Layer::Bottom, element_filter) {
|
||||
if !layer_show_on_lock(layer.wl_surface()) {
|
||||
continue;
|
||||
}
|
||||
callback(Stage::LayerSurface {
|
||||
layer,
|
||||
location,
|
||||
workspace_idx: current.1,
|
||||
})?;
|
||||
}
|
||||
for (layer, location) in layer_surfaces(output, Layer::Background, element_filter) {
|
||||
if !layer_show_on_lock(layer.wl_surface()) {
|
||||
continue;
|
||||
}
|
||||
callback(Stage::LayerSurface {
|
||||
layer,
|
||||
location,
|
||||
workspace_idx: current.1,
|
||||
})?;
|
||||
}
|
||||
return ControlFlow::Continue(());
|
||||
}
|
||||
|
||||
// Overlay-level layer shell
|
||||
|
|
|
|||
|
|
@ -16,21 +16,30 @@ use crate::{
|
|||
};
|
||||
use id_tree::NodeId;
|
||||
use smithay::{
|
||||
backend::input::KeyState,
|
||||
backend::input::{InputTime, KeyState, TabletToolDescriptor},
|
||||
desktop::{LayerSurface, PopupKind, WindowSurface, WindowSurfaceType, space::SpaceElement},
|
||||
input::{
|
||||
Seat,
|
||||
dnd::{DndFocus, OfferData, Source},
|
||||
keyboard::{KeyboardTarget, KeysymHandle, ModifiersState},
|
||||
pointer::{
|
||||
AxisFrame, ButtonEvent, GestureHoldBeginEvent, GestureHoldEndEvent,
|
||||
GesturePinchBeginEvent, GesturePinchEndEvent, GesturePinchUpdateEvent,
|
||||
GestureSwipeBeginEvent, GestureSwipeEndEvent, GestureSwipeUpdateEvent,
|
||||
MotionEvent as PointerMotionEvent, PointerTarget, RelativeMotionEvent,
|
||||
AxisFrame as PointerAxisFrame, ButtonEvent as PointerButtonEvent,
|
||||
GestureHoldBeginEvent, GestureHoldEndEvent, GesturePinchBeginEvent,
|
||||
GesturePinchEndEvent, GesturePinchUpdateEvent, GestureSwipeBeginEvent,
|
||||
GestureSwipeEndEvent, GestureSwipeUpdateEvent, MotionEvent as PointerMotionEvent,
|
||||
PointerTarget, RelativeMotionEvent,
|
||||
},
|
||||
tablet::{
|
||||
Tablet,
|
||||
tool::{
|
||||
AxisFrame as ToolAxisFrame, ButtonEvent as ToolButtonEvent,
|
||||
DownEvent as ToolDownEvent, MotionEvent as ToolMotionEvent, TabletToolHandle,
|
||||
TabletToolTarget, UpEvent as ToolUpEvent,
|
||||
},
|
||||
},
|
||||
touch::{
|
||||
DownEvent, FrameMarker, MotionEvent as TouchMotionEvent, OrientationEvent, ShapeEvent,
|
||||
TouchTarget, UpEvent,
|
||||
DownEvent as TouchDownEvent, FrameMarker, MotionEvent as TouchMotionEvent,
|
||||
OrientationEvent, ShapeEvent, TouchTarget, UpEvent as TouchUpEvent,
|
||||
},
|
||||
},
|
||||
reexports::wayland_server::{
|
||||
|
|
@ -164,6 +173,17 @@ impl PointerFocusTarget {
|
|||
}
|
||||
}
|
||||
|
||||
fn inner_tablet_tool_target(&self) -> &dyn TabletToolTarget<State> {
|
||||
match self {
|
||||
PointerFocusTarget::WlSurface { surface, .. } => surface,
|
||||
PointerFocusTarget::X11Surface { surface, .. } => surface,
|
||||
PointerFocusTarget::StackUI(u) => u,
|
||||
PointerFocusTarget::WindowUI(u) => u,
|
||||
PointerFocusTarget::ResizeFork(f) => f,
|
||||
PointerFocusTarget::ZoomUI(e) => e,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn under_surface<P: Into<Point<f64, Logical>>>(
|
||||
surface: &CosmicSurface,
|
||||
point: P,
|
||||
|
|
@ -272,9 +292,10 @@ impl PointerFocusTarget {
|
|||
None
|
||||
};
|
||||
|
||||
let cursor_hotspot = if let Some(CursorGeometry { hotspot, .. }) =
|
||||
seat.cursor_geometry((0.0, 0.0), Duration::from_millis(event.time as u64).into())
|
||||
{
|
||||
let cursor_hotspot = if let Some(CursorGeometry { hotspot, .. }) = seat.cursor_geometry(
|
||||
(0.0, 0.0),
|
||||
Duration::from_millis(event.time.millis() as u64).into(),
|
||||
) {
|
||||
hotspot
|
||||
} else {
|
||||
Point::from((0, 0))
|
||||
|
|
@ -285,6 +306,16 @@ impl PointerFocusTarget {
|
|||
session.set_cursor_hotspot(cursor_hotspot);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn supports_tool(&self, tool_handle: &TabletToolHandle<State>) -> bool {
|
||||
match self {
|
||||
Self::WlSurface { surface, .. } if surface.client().is_some() => tool_handle
|
||||
.client_tools(&surface.client().unwrap())
|
||||
.next()
|
||||
.is_some(),
|
||||
_ => true,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl KeyboardFocusTarget {
|
||||
|
|
@ -326,7 +357,7 @@ impl KeyboardFocusTarget {
|
|||
}
|
||||
}
|
||||
|
||||
fn x11_surface(&self) -> Option<X11Surface> {
|
||||
pub fn x11_surface(&self) -> Option<X11Surface> {
|
||||
match self {
|
||||
KeyboardFocusTarget::Element(mapped) => mapped.active_window().x11_surface().cloned(),
|
||||
KeyboardFocusTarget::Fullscreen(surface) => surface.x11_surface().cloned(),
|
||||
|
|
@ -408,16 +439,16 @@ impl PointerTarget<State> for PointerFocusTarget {
|
|||
self.inner_pointer_target()
|
||||
.relative_motion(seat, data, event);
|
||||
}
|
||||
fn button(&self, seat: &Seat<State>, data: &mut State, event: &ButtonEvent) {
|
||||
fn button(&self, seat: &Seat<State>, data: &mut State, event: &PointerButtonEvent) {
|
||||
self.inner_pointer_target().button(seat, data, event);
|
||||
}
|
||||
fn axis(&self, seat: &Seat<State>, data: &mut State, frame: AxisFrame) {
|
||||
fn axis(&self, seat: &Seat<State>, data: &mut State, frame: PointerAxisFrame) {
|
||||
self.inner_pointer_target().axis(seat, data, frame);
|
||||
}
|
||||
fn frame(&self, seat: &Seat<State>, data: &mut State) {
|
||||
self.inner_pointer_target().frame(seat, data);
|
||||
}
|
||||
fn leave(&self, seat: &Seat<State>, data: &mut State, serial: Serial, time: u32) {
|
||||
fn leave(&self, seat: &Seat<State>, data: &mut State, serial: Serial, time: InputTime) {
|
||||
let toplevel = self.toplevel(&data.common.shell.read());
|
||||
if let Some(element) = toplevel {
|
||||
for session in element.cursor_sessions() {
|
||||
|
|
@ -497,12 +528,102 @@ impl PointerTarget<State> for PointerFocusTarget {
|
|||
}
|
||||
}
|
||||
|
||||
impl TabletToolTarget<State> for PointerFocusTarget {
|
||||
fn proximity_in(
|
||||
&self,
|
||||
seat: &Seat<State>,
|
||||
data: &mut State,
|
||||
tool_descriptor: &TabletToolDescriptor,
|
||||
tablet: &Tablet,
|
||||
serial: Serial,
|
||||
) {
|
||||
self.inner_tablet_tool_target()
|
||||
.proximity_in(seat, data, tool_descriptor, tablet, serial);
|
||||
}
|
||||
|
||||
fn proximity_out(
|
||||
&self,
|
||||
seat: &Seat<State>,
|
||||
data: &mut State,
|
||||
tool_descriptor: &TabletToolDescriptor,
|
||||
) {
|
||||
self.inner_tablet_tool_target()
|
||||
.proximity_out(seat, data, tool_descriptor);
|
||||
}
|
||||
|
||||
fn down(
|
||||
&self,
|
||||
seat: &Seat<State>,
|
||||
data: &mut State,
|
||||
tool_descriptor: &TabletToolDescriptor,
|
||||
event: &ToolDownEvent,
|
||||
) {
|
||||
self.inner_tablet_tool_target()
|
||||
.down(seat, data, tool_descriptor, event);
|
||||
}
|
||||
|
||||
fn up(
|
||||
&self,
|
||||
seat: &Seat<State>,
|
||||
data: &mut State,
|
||||
tool_descriptor: &TabletToolDescriptor,
|
||||
event: &ToolUpEvent,
|
||||
) {
|
||||
self.inner_tablet_tool_target()
|
||||
.up(seat, data, tool_descriptor, event);
|
||||
}
|
||||
|
||||
fn motion(
|
||||
&self,
|
||||
seat: &Seat<State>,
|
||||
data: &mut State,
|
||||
tool_descriptor: &TabletToolDescriptor,
|
||||
event: &ToolMotionEvent,
|
||||
) {
|
||||
self.inner_tablet_tool_target()
|
||||
.motion(seat, data, tool_descriptor, event);
|
||||
}
|
||||
|
||||
fn axis(
|
||||
&self,
|
||||
seat: &Seat<State>,
|
||||
data: &mut State,
|
||||
tool_descriptor: &TabletToolDescriptor,
|
||||
frame: ToolAxisFrame,
|
||||
) {
|
||||
self.inner_tablet_tool_target()
|
||||
.axis(seat, data, tool_descriptor, frame);
|
||||
}
|
||||
|
||||
fn button(
|
||||
&self,
|
||||
seat: &Seat<State>,
|
||||
data: &mut State,
|
||||
tool_descriptor: &TabletToolDescriptor,
|
||||
event: &ToolButtonEvent,
|
||||
) {
|
||||
self.inner_tablet_tool_target()
|
||||
.button(seat, data, tool_descriptor, event);
|
||||
}
|
||||
|
||||
fn frame(
|
||||
&self,
|
||||
seat: &Seat<State>,
|
||||
data: &mut State,
|
||||
tool_descriptor: &TabletToolDescriptor,
|
||||
time: InputTime,
|
||||
) {
|
||||
self.inner_tablet_tool_target()
|
||||
.frame(seat, data, tool_descriptor, time);
|
||||
}
|
||||
}
|
||||
|
||||
impl TouchTarget<State> for PointerFocusTarget {
|
||||
fn down(&self, seat: &Seat<State>, data: &mut State, event: &DownEvent) {
|
||||
fn down(&self, seat: &Seat<State>, data: &mut State, event: &TouchDownEvent) {
|
||||
self.inner_touch_target().down(seat, data, event);
|
||||
}
|
||||
|
||||
fn up(&self, seat: &Seat<State>, data: &mut State, event: &UpEvent) {
|
||||
fn up(&self, seat: &Seat<State>, data: &mut State, event: &TouchUpEvent) {
|
||||
self.inner_touch_target().up(seat, data, event);
|
||||
}
|
||||
|
||||
|
|
@ -593,7 +714,7 @@ impl DndFocus<State> for PointerFocusTarget {
|
|||
offer: Option<&mut CosmicOfferData<S>>,
|
||||
seat: &Seat<State>,
|
||||
location: Point<f64, Logical>,
|
||||
time: u32,
|
||||
time: InputTime,
|
||||
) {
|
||||
match self {
|
||||
PointerFocusTarget::WlSurface { surface, .. } => {
|
||||
|
|
@ -695,7 +816,7 @@ impl KeyboardTarget<State> for KeyboardFocusTarget {
|
|||
key: KeysymHandle<'_>,
|
||||
state: KeyState,
|
||||
serial: Serial,
|
||||
time: u32,
|
||||
time: InputTime,
|
||||
) {
|
||||
if let Some(inner) = self.inner_keyboard_target() {
|
||||
inner.key(seat, data, key, state, serial, time);
|
||||
|
|
|
|||
|
|
@ -1,22 +1,34 @@
|
|||
use smithay::{
|
||||
backend::input::{InputTime, TabletToolDescriptor},
|
||||
input::{
|
||||
Seat, SeatHandler,
|
||||
pointer::{
|
||||
AxisFrame, ButtonEvent, Focus, GestureHoldBeginEvent, GestureHoldEndEvent,
|
||||
GesturePinchBeginEvent, GesturePinchEndEvent, GesturePinchUpdateEvent,
|
||||
GestureSwipeBeginEvent, GestureSwipeEndEvent, GestureSwipeUpdateEvent,
|
||||
GrabStartData as PointerGrabStartData, MotionEvent as PointerMotionEvent, PointerGrab,
|
||||
PointerInnerHandle, RelativeMotionEvent,
|
||||
AxisFrame as PointerAxisFrame, ButtonEvent as PointerButtonEvent, Focus,
|
||||
GestureHoldBeginEvent, GestureHoldEndEvent, GesturePinchBeginEvent,
|
||||
GesturePinchEndEvent, GesturePinchUpdateEvent, GestureSwipeBeginEvent,
|
||||
GestureSwipeEndEvent, GestureSwipeUpdateEvent, GrabStartData as PointerGrabStartData,
|
||||
MotionEvent as PointerMotionEvent, PointerGrab, PointerInnerHandle,
|
||||
RelativeMotionEvent,
|
||||
},
|
||||
tablet::{
|
||||
TabletSeatHandler, TabletSeatTrait,
|
||||
tool::{
|
||||
AxisFrame as TabletAxisFrame, ButtonEvent as TabletButtonEvent,
|
||||
DownEvent as TabletDownEvent, GrabStartData as TabletGrabStartData,
|
||||
MotionEvent as TabletMotionEvent, ProximityInEvent, ProximityOutEvent,
|
||||
TabletToolGrab, TabletToolInnerHandle, UpEvent as TabletUpEvent,
|
||||
},
|
||||
},
|
||||
touch::{
|
||||
DownEvent, GrabStartData as TouchGrabStartData, MotionEvent as TouchMotionEvent,
|
||||
OrientationEvent, ShapeEvent, TouchGrab, TouchInnerHandle, UpEvent,
|
||||
DownEvent as TouchDownEvent, GrabStartData as TouchGrabStartData,
|
||||
MotionEvent as TouchMotionEvent, OrientationEvent, ShapeEvent, TouchGrab,
|
||||
TouchInnerHandle, UpEvent as TouchUpEvent,
|
||||
},
|
||||
},
|
||||
utils::{Logical, Point, SERIAL_COUNTER, Serial},
|
||||
};
|
||||
|
||||
use crate::state::State;
|
||||
use crate::{shell::grabs::GrabType, state::State};
|
||||
|
||||
use super::GrabStartData;
|
||||
|
||||
|
|
@ -44,10 +56,15 @@ impl<G> DelayGrab<G> {
|
|||
}
|
||||
}
|
||||
|
||||
pub fn is_touch_grab(&self) -> bool {
|
||||
match self.start_data {
|
||||
GrabStartData::Touch(_) => true,
|
||||
GrabStartData::Pointer(_) => false,
|
||||
pub fn grab_type(&self) -> GrabType {
|
||||
self.start_data.type_()
|
||||
}
|
||||
|
||||
pub fn tool(&self) -> Option<&TabletToolDescriptor> {
|
||||
if let GrabStartData::TabletTool { tool, .. } = &self.start_data {
|
||||
Some(tool)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -92,7 +109,7 @@ impl<G: PointerGrab<State>> PointerGrab<State> for DelayGrab<G> {
|
|||
&mut self,
|
||||
data: &mut State,
|
||||
handle: &mut PointerInnerHandle<'_, State>,
|
||||
event: &ButtonEvent,
|
||||
event: &PointerButtonEvent,
|
||||
) {
|
||||
handle.button(data, event);
|
||||
if handle.current_pressed().is_empty() {
|
||||
|
|
@ -104,7 +121,7 @@ impl<G: PointerGrab<State>> PointerGrab<State> for DelayGrab<G> {
|
|||
&mut self,
|
||||
data: &mut State,
|
||||
handle: &mut PointerInnerHandle<'_, State>,
|
||||
details: AxisFrame,
|
||||
details: PointerAxisFrame,
|
||||
) {
|
||||
handle.axis(data, details);
|
||||
}
|
||||
|
|
@ -201,12 +218,17 @@ impl<G: TouchGrab<State>> TouchGrab<State> for DelayGrab<G> {
|
|||
data: &mut State,
|
||||
handle: &mut TouchInnerHandle<'_, State>,
|
||||
focus: Option<(<State as SeatHandler>::TouchFocus, Point<f64, Logical>)>,
|
||||
event: &DownEvent,
|
||||
event: &TouchDownEvent,
|
||||
) {
|
||||
handle.down(data, focus, event);
|
||||
}
|
||||
|
||||
fn up(&mut self, data: &mut State, handle: &mut TouchInnerHandle<'_, State>, event: &UpEvent) {
|
||||
fn up(
|
||||
&mut self,
|
||||
data: &mut State,
|
||||
handle: &mut TouchInnerHandle<'_, State>,
|
||||
event: &TouchUpEvent,
|
||||
) {
|
||||
handle.up(data, event);
|
||||
|
||||
if event.slot == TouchGrab::start_data(self).slot {
|
||||
|
|
@ -273,3 +295,110 @@ impl<G: TouchGrab<State>> TouchGrab<State> for DelayGrab<G> {
|
|||
|
||||
fn unset(&mut self, _data: &mut State) {}
|
||||
}
|
||||
|
||||
impl<G: TabletToolGrab<State>> TabletToolGrab<State> for DelayGrab<G> {
|
||||
fn start_data(&self) -> &TabletGrabStartData<State> {
|
||||
match &self.start_data {
|
||||
GrabStartData::TabletTool { data, .. } => data,
|
||||
_ => unreachable!(),
|
||||
}
|
||||
}
|
||||
|
||||
fn proximity_out(
|
||||
&mut self,
|
||||
data: &mut State,
|
||||
handle: &mut TabletToolInnerHandle<'_, State>,
|
||||
event: &ProximityOutEvent,
|
||||
) {
|
||||
handle.proximity_out(data, event);
|
||||
}
|
||||
|
||||
fn motion(
|
||||
&mut self,
|
||||
data: &mut State,
|
||||
handle: &mut TabletToolInnerHandle<'_, State>,
|
||||
focus: Option<(<State as TabletSeatHandler>::ToolFocus, Point<f64, Logical>)>,
|
||||
event: &TabletMotionEvent,
|
||||
) {
|
||||
handle.motion(data, focus, event);
|
||||
|
||||
let distance = self.start_data.distance(event.location);
|
||||
if distance >= 1.
|
||||
&& let Some(factory) = self.grab_factory.take()
|
||||
{
|
||||
let seat = self.seat.clone();
|
||||
let serial = self.serial.unwrap_or_else(|| SERIAL_COUNTER.next_serial());
|
||||
let time = InputTime::now();
|
||||
let Some(tool) = self.tool().cloned() else {
|
||||
handle.unset_grab(self, data, serial, time, true);
|
||||
return;
|
||||
};
|
||||
data.common.event_loop_handle.insert_idle(move |data| {
|
||||
if let Some((grab, focus)) = factory(data) {
|
||||
seat.tablet_seat()
|
||||
.get_tool(&tool)
|
||||
.unwrap()
|
||||
.set_grab(data, grab, time, serial, focus);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
fn down(
|
||||
&mut self,
|
||||
data: &mut State,
|
||||
handle: &mut TabletToolInnerHandle<'_, State>,
|
||||
event: &TabletDownEvent,
|
||||
) {
|
||||
handle.down(data, event)
|
||||
}
|
||||
|
||||
fn up(
|
||||
&mut self,
|
||||
data: &mut State,
|
||||
handle: &mut TabletToolInnerHandle<'_, State>,
|
||||
event: &TabletUpEvent,
|
||||
) {
|
||||
handle.up(data, event);
|
||||
handle.unset_grab(self, data, event.serial, event.time, false);
|
||||
}
|
||||
|
||||
fn button(
|
||||
&mut self,
|
||||
data: &mut State,
|
||||
handle: &mut TabletToolInnerHandle<'_, State>,
|
||||
event: &TabletButtonEvent,
|
||||
) {
|
||||
handle.button(data, event);
|
||||
}
|
||||
|
||||
fn axis(
|
||||
&mut self,
|
||||
data: &mut State,
|
||||
handle: &mut TabletToolInnerHandle<'_, State>,
|
||||
frame: TabletAxisFrame,
|
||||
) {
|
||||
handle.axis(data, frame)
|
||||
}
|
||||
|
||||
fn frame(
|
||||
&mut self,
|
||||
data: &mut State,
|
||||
handle: &mut TabletToolInnerHandle<'_, State>,
|
||||
time: InputTime,
|
||||
) {
|
||||
handle.frame(data, time)
|
||||
}
|
||||
|
||||
fn unset(&mut self, _data: &mut State) {}
|
||||
|
||||
fn proximity_in(
|
||||
&mut self,
|
||||
data: &mut State,
|
||||
handle: &mut TabletToolInnerHandle<'_, State>,
|
||||
focus: Option<(<State as TabletSeatHandler>::ToolFocus, Point<f64, Logical>)>,
|
||||
event: &ProximityInEvent,
|
||||
) {
|
||||
handle.proximity_in(data, focus, event);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,7 +1,10 @@
|
|||
use cosmic_settings_config::shortcuts::Action;
|
||||
use smithay::{
|
||||
input::pointer::MotionEvent, reexports::wayland_server::protocol::wl_surface::WlSurface,
|
||||
utils::SERIAL_COUNTER, wayland::seat::WaylandFocus,
|
||||
backend::input::InputTime,
|
||||
input::{pointer::MotionEvent, tablet::TabletSeatTrait},
|
||||
reexports::wayland_server::protocol::wl_surface::WlSurface,
|
||||
utils::SERIAL_COUNTER,
|
||||
wayland::seat::WaylandFocus,
|
||||
};
|
||||
|
||||
use crate::{
|
||||
|
|
@ -10,7 +13,7 @@ use crate::{
|
|||
shell::{
|
||||
CosmicSurface, PointGlobalExt, Shell,
|
||||
element::{CosmicMapped, CosmicWindow},
|
||||
grabs::ReleaseMode,
|
||||
grabs::{GrabType, ReleaseMode},
|
||||
},
|
||||
state::State,
|
||||
utils::{prelude::SeatExt, screenshot::screenshot_window},
|
||||
|
|
@ -352,19 +355,20 @@ pub fn window_items(
|
|||
|
||||
std::mem::drop(shell);
|
||||
if let Some((grab, focus)) = res {
|
||||
if grab.is_touch_grab() {
|
||||
seat.get_touch().unwrap().set_grab(
|
||||
state,
|
||||
grab,
|
||||
SERIAL_COUNTER.next_serial(),
|
||||
)
|
||||
} else {
|
||||
seat.get_pointer().unwrap().set_grab(
|
||||
state,
|
||||
grab,
|
||||
SERIAL_COUNTER.next_serial(),
|
||||
focus,
|
||||
);
|
||||
let serial = SERIAL_COUNTER.next_serial();
|
||||
match grab.grab_type() {
|
||||
GrabType::Touch => {
|
||||
seat.get_touch().unwrap().set_grab(state, grab, serial)
|
||||
}
|
||||
GrabType::Pointer => seat
|
||||
.get_pointer()
|
||||
.unwrap()
|
||||
.set_grab(state, grab, serial, focus),
|
||||
GrabType::TabletTool => seat
|
||||
.tablet_seat()
|
||||
.get_tool(grab.tool().unwrap())
|
||||
.unwrap()
|
||||
.set_grab(state, grab, InputTime::now(), serial, focus),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -388,21 +392,29 @@ pub fn window_items(
|
|||
std::mem::drop(shell);
|
||||
if let Some(((target, loc), (grab, focus))) = res {
|
||||
let serial = SERIAL_COUNTER.next_serial();
|
||||
if grab.is_touch_grab() {
|
||||
seat.get_touch().unwrap().set_grab(state, grab, serial);
|
||||
} else {
|
||||
let pointer = seat.get_pointer().unwrap();
|
||||
pointer.motion(
|
||||
state,
|
||||
target,
|
||||
&MotionEvent {
|
||||
location: loc.as_logical().to_f64(),
|
||||
serial,
|
||||
time: state.common.clock.now().as_millis(),
|
||||
},
|
||||
);
|
||||
pointer.frame(state);
|
||||
pointer.set_grab(state, grab, serial, focus);
|
||||
match grab.grab_type() {
|
||||
GrabType::Touch => {
|
||||
seat.get_touch().unwrap().set_grab(state, grab, serial)
|
||||
}
|
||||
GrabType::TabletTool => seat
|
||||
.tablet_seat()
|
||||
.get_tool(grab.tool().unwrap())
|
||||
.unwrap()
|
||||
.set_grab(state, grab, InputTime::now(), serial, focus),
|
||||
GrabType::Pointer => {
|
||||
let pointer = seat.get_pointer().unwrap();
|
||||
pointer.motion(
|
||||
state,
|
||||
target,
|
||||
&MotionEvent {
|
||||
location: loc.as_logical().to_f64(),
|
||||
serial,
|
||||
time: InputTime::now(),
|
||||
},
|
||||
);
|
||||
pointer.frame(state);
|
||||
pointer.set_grab(state, grab, serial, focus);
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
|
@ -423,21 +435,30 @@ pub fn window_items(
|
|||
std::mem::drop(shell);
|
||||
if let Some(((target, loc), (grab, focus))) = res {
|
||||
let serial = SERIAL_COUNTER.next_serial();
|
||||
if grab.is_touch_grab() {
|
||||
seat.get_touch().unwrap().set_grab(state, grab, serial);
|
||||
} else {
|
||||
let pointer = seat.get_pointer().unwrap();
|
||||
pointer.motion(
|
||||
state,
|
||||
target,
|
||||
&MotionEvent {
|
||||
location: loc.as_logical().to_f64(),
|
||||
serial,
|
||||
time: state.common.clock.now().as_millis(),
|
||||
},
|
||||
);
|
||||
pointer.frame(state);
|
||||
pointer.set_grab(state, grab, serial, focus);
|
||||
match grab.grab_type() {
|
||||
GrabType::Touch => {
|
||||
seat.get_touch().unwrap().set_grab(state, grab, serial)
|
||||
}
|
||||
GrabType::Pointer => {
|
||||
let pointer = seat.get_pointer().unwrap();
|
||||
pointer.motion(
|
||||
state,
|
||||
target,
|
||||
&MotionEvent {
|
||||
location: loc.as_logical().to_f64(),
|
||||
serial,
|
||||
time: InputTime::now(),
|
||||
},
|
||||
);
|
||||
pointer.frame(state);
|
||||
pointer.set_grab(state, grab, serial, focus);
|
||||
}
|
||||
GrabType::TabletTool => {
|
||||
seat.tablet_seat()
|
||||
.get_tool(grab.tool().unwrap())
|
||||
.unwrap()
|
||||
.set_grab(state, grab, InputTime::now(), serial, focus);
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
|
@ -458,21 +479,30 @@ pub fn window_items(
|
|||
std::mem::drop(shell);
|
||||
if let Some(((target, loc), (grab, focus))) = res {
|
||||
let serial = SERIAL_COUNTER.next_serial();
|
||||
if grab.is_touch_grab() {
|
||||
seat.get_touch().unwrap().set_grab(state, grab, serial);
|
||||
} else {
|
||||
let pointer = seat.get_pointer().unwrap();
|
||||
pointer.motion(
|
||||
state,
|
||||
target,
|
||||
&MotionEvent {
|
||||
location: loc.as_logical().to_f64(),
|
||||
serial,
|
||||
time: state.common.clock.now().as_millis(),
|
||||
},
|
||||
);
|
||||
pointer.frame(state);
|
||||
pointer.set_grab(state, grab, serial, focus);
|
||||
match grab.grab_type() {
|
||||
GrabType::Touch => {
|
||||
seat.get_touch().unwrap().set_grab(state, grab, serial)
|
||||
}
|
||||
GrabType::Pointer => {
|
||||
let pointer = seat.get_pointer().unwrap();
|
||||
pointer.motion(
|
||||
state,
|
||||
target,
|
||||
&MotionEvent {
|
||||
location: loc.as_logical().to_f64(),
|
||||
serial,
|
||||
time: InputTime::now(),
|
||||
},
|
||||
);
|
||||
pointer.frame(state);
|
||||
pointer.set_grab(state, grab, serial, focus);
|
||||
}
|
||||
GrabType::TabletTool => {
|
||||
seat.tablet_seat()
|
||||
.get_tool(grab.tool().unwrap())
|
||||
.unwrap()
|
||||
.set_grab(state, grab, InputTime::now(), serial, focus);
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
|
@ -493,21 +523,29 @@ pub fn window_items(
|
|||
std::mem::drop(shell);
|
||||
if let Some(((target, loc), (grab, focus))) = res {
|
||||
let serial = SERIAL_COUNTER.next_serial();
|
||||
if grab.is_touch_grab() {
|
||||
seat.get_touch().unwrap().set_grab(state, grab, serial);
|
||||
} else {
|
||||
let pointer = seat.get_pointer().unwrap();
|
||||
pointer.motion(
|
||||
state,
|
||||
target,
|
||||
&MotionEvent {
|
||||
location: loc.as_logical().to_f64(),
|
||||
serial,
|
||||
time: state.common.clock.now().as_millis(),
|
||||
},
|
||||
);
|
||||
pointer.frame(state);
|
||||
pointer.set_grab(state, grab, serial, focus);
|
||||
match grab.grab_type() {
|
||||
GrabType::Touch => {
|
||||
seat.get_touch().unwrap().set_grab(state, grab, serial)
|
||||
}
|
||||
GrabType::Pointer => {
|
||||
let pointer = seat.get_pointer().unwrap();
|
||||
pointer.motion(
|
||||
state,
|
||||
target,
|
||||
&MotionEvent {
|
||||
location: loc.as_logical().to_f64(),
|
||||
serial,
|
||||
time: InputTime::now(),
|
||||
},
|
||||
);
|
||||
pointer.frame(state);
|
||||
pointer.set_grab(state, grab, serial, focus);
|
||||
}
|
||||
GrabType::TabletTool => seat
|
||||
.tablet_seat()
|
||||
.get_tool(grab.tool().unwrap())
|
||||
.unwrap()
|
||||
.set_grab(state, grab, InputTime::now(), serial, focus),
|
||||
}
|
||||
}
|
||||
});
|
||||
|
|
@ -627,19 +665,20 @@ pub fn fullscreen_items(window: &CosmicSurface, config: &Config) -> impl Iterato
|
|||
|
||||
std::mem::drop(shell);
|
||||
if let Some((grab, focus)) = res {
|
||||
if grab.is_touch_grab() {
|
||||
seat.get_touch().unwrap().set_grab(
|
||||
state,
|
||||
grab,
|
||||
SERIAL_COUNTER.next_serial(),
|
||||
)
|
||||
} else {
|
||||
seat.get_pointer().unwrap().set_grab(
|
||||
state,
|
||||
grab,
|
||||
SERIAL_COUNTER.next_serial(),
|
||||
focus,
|
||||
);
|
||||
let serial = SERIAL_COUNTER.next_serial();
|
||||
match grab.grab_type() {
|
||||
GrabType::Touch => {
|
||||
seat.get_touch().unwrap().set_grab(state, grab, serial)
|
||||
}
|
||||
GrabType::Pointer => seat
|
||||
.get_pointer()
|
||||
.unwrap()
|
||||
.set_grab(state, grab, serial, focus),
|
||||
GrabType::TabletTool => seat
|
||||
.tablet_seat()
|
||||
.get_tool(grab.tool().unwrap())
|
||||
.unwrap()
|
||||
.set_grab(state, grab, InputTime::now(), serial, focus),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -19,22 +19,33 @@ use cosmic::{
|
|||
};
|
||||
use smithay::{
|
||||
backend::{
|
||||
input::{ButtonState, TouchSlot},
|
||||
input::{ButtonState, InputTime, TabletToolDescriptor, TouchSlot},
|
||||
renderer::ImportMem,
|
||||
},
|
||||
desktop::space::SpaceElement,
|
||||
input::{
|
||||
Seat,
|
||||
pointer::{
|
||||
AxisFrame, ButtonEvent, GestureHoldBeginEvent, GestureHoldEndEvent,
|
||||
GesturePinchBeginEvent, GesturePinchEndEvent, GesturePinchUpdateEvent,
|
||||
GestureSwipeBeginEvent, GestureSwipeEndEvent, GestureSwipeUpdateEvent,
|
||||
GrabStartData as PointerGrabStartData, MotionEvent as PointerMotionEvent, PointerGrab,
|
||||
PointerInnerHandle, PointerTarget, RelativeMotionEvent,
|
||||
AxisFrame as PointerAxisFrame, ButtonEvent as PointerButtonEvent,
|
||||
GestureHoldBeginEvent, GestureHoldEndEvent, GesturePinchBeginEvent,
|
||||
GesturePinchEndEvent, GesturePinchUpdateEvent, GestureSwipeBeginEvent,
|
||||
GestureSwipeEndEvent, GestureSwipeUpdateEvent, GrabStartData as PointerGrabStartData,
|
||||
MotionEvent as PointerMotionEvent, PointerGrab, PointerInnerHandle, PointerTarget,
|
||||
RelativeMotionEvent,
|
||||
},
|
||||
tablet::{
|
||||
TabletSeatHandler,
|
||||
tool::{
|
||||
AxisFrame as TabletAxisFrame, ButtonEvent as TabletButtonEvent,
|
||||
DownEvent as TabletDownEvent, GrabStartData as TabletGrabStartData,
|
||||
MotionEvent as TabletMotionEvent, ProximityInEvent, ProximityOutEvent,
|
||||
TabletToolGrab, TabletToolInnerHandle, TabletToolTarget, UpEvent as TabletUpEvent,
|
||||
},
|
||||
},
|
||||
touch::{
|
||||
DownEvent, GrabStartData as TouchGrabStartData, MotionEvent as TouchMotionEvent,
|
||||
TouchGrab, TouchInnerHandle, TouchTarget, UpEvent,
|
||||
DownEvent as TouchDownEvent, GrabStartData as TouchGrabStartData,
|
||||
MotionEvent as TouchMotionEvent, TouchGrab, TouchInnerHandle, TouchTarget,
|
||||
UpEvent as TouchUpEvent,
|
||||
},
|
||||
},
|
||||
output::Output,
|
||||
|
|
@ -43,7 +54,7 @@ use smithay::{
|
|||
|
||||
use crate::{
|
||||
backend::render::element::AsGlowRenderer,
|
||||
shell::{SeatExt, focus::target::PointerFocusTarget},
|
||||
shell::{SeatExt, focus::target::PointerFocusTarget, grabs::GrabType},
|
||||
state::State,
|
||||
utils::{
|
||||
iced::{IcedElement, IcedRenderElement, Program},
|
||||
|
|
@ -350,6 +361,7 @@ impl Program for ContextMenu {
|
|||
position,
|
||||
pointer_entered: false,
|
||||
touch_entered: None,
|
||||
tablet_entered: None,
|
||||
})
|
||||
}
|
||||
});
|
||||
|
|
@ -509,6 +521,7 @@ pub struct Element {
|
|||
position: Point<i32, Global>,
|
||||
pointer_entered: bool,
|
||||
touch_entered: Option<TouchSlot>,
|
||||
tablet_entered: Option<TabletToolDescriptor>,
|
||||
}
|
||||
|
||||
pub struct MenuGrab {
|
||||
|
|
@ -517,6 +530,7 @@ pub struct MenuGrab {
|
|||
seat: Seat<State>,
|
||||
screen_space_relative: Option<Output>,
|
||||
scale: Arc<Mutex<f64>>,
|
||||
last_tablet_idx: Option<usize>,
|
||||
}
|
||||
|
||||
impl PointerGrab<State> for MenuGrab {
|
||||
|
|
@ -599,7 +613,7 @@ impl PointerGrab<State> for MenuGrab {
|
|||
&mut self,
|
||||
state: &mut State,
|
||||
handle: &mut PointerInnerHandle<'_, State>,
|
||||
event: &ButtonEvent,
|
||||
event: &PointerButtonEvent,
|
||||
) {
|
||||
let any_entered = self
|
||||
.elements
|
||||
|
|
@ -633,7 +647,7 @@ impl PointerGrab<State> for MenuGrab {
|
|||
&mut self,
|
||||
state: &mut State,
|
||||
handle: &mut PointerInnerHandle<'_, State>,
|
||||
details: AxisFrame,
|
||||
details: PointerAxisFrame,
|
||||
) {
|
||||
handle.axis(state, details);
|
||||
}
|
||||
|
|
@ -730,7 +744,7 @@ impl TouchGrab<State> for MenuGrab {
|
|||
data: &mut State,
|
||||
handle: &mut TouchInnerHandle<'_, State>,
|
||||
_focus: Option<(PointerFocusTarget, Point<f64, Logical>)>,
|
||||
event: &DownEvent,
|
||||
event: &TouchDownEvent,
|
||||
) {
|
||||
{
|
||||
let mut guard = self.elements.lock().unwrap();
|
||||
|
|
@ -758,7 +772,7 @@ impl TouchGrab<State> for MenuGrab {
|
|||
}) {
|
||||
let element = &mut elements[i];
|
||||
|
||||
let new_event = DownEvent {
|
||||
let new_event = TouchDownEvent {
|
||||
slot: event.slot,
|
||||
location: event_location - element.position.as_logical().to_f64(),
|
||||
serial: event.serial,
|
||||
|
|
@ -773,7 +787,12 @@ impl TouchGrab<State> for MenuGrab {
|
|||
handle.down(data, None, event);
|
||||
}
|
||||
|
||||
fn up(&mut self, data: &mut State, handle: &mut TouchInnerHandle<'_, State>, event: &UpEvent) {
|
||||
fn up(
|
||||
&mut self,
|
||||
data: &mut State,
|
||||
handle: &mut TouchInnerHandle<'_, State>,
|
||||
event: &TouchUpEvent,
|
||||
) {
|
||||
{
|
||||
let elements = self.elements.lock().unwrap();
|
||||
for element in elements.iter().filter(|elem| {
|
||||
|
|
@ -849,6 +868,248 @@ impl TouchGrab<State> for MenuGrab {
|
|||
fn unset(&mut self, _data: &mut State) {}
|
||||
}
|
||||
|
||||
impl TabletToolGrab<State> for MenuGrab {
|
||||
fn start_data(&self) -> &TabletGrabStartData<State> {
|
||||
match &self.start_data {
|
||||
GrabStartData::TabletTool { data, .. } => data,
|
||||
_ => unreachable!(),
|
||||
}
|
||||
}
|
||||
|
||||
fn proximity_in(
|
||||
&mut self,
|
||||
data: &mut State,
|
||||
handle: &mut TabletToolInnerHandle<'_, State>,
|
||||
_focus: Option<(<State as TabletSeatHandler>::ToolFocus, Point<f64, Logical>)>,
|
||||
event: &ProximityInEvent,
|
||||
) {
|
||||
{
|
||||
let location = if let Some(output) = self.screen_space_relative.as_ref() {
|
||||
if data.common.shell.read().zoom_state().is_some() {
|
||||
event
|
||||
.location
|
||||
.as_global()
|
||||
.to_zoomed(output)
|
||||
.to_global(output)
|
||||
.as_logical()
|
||||
} else {
|
||||
event.location
|
||||
}
|
||||
} else {
|
||||
event.location
|
||||
};
|
||||
|
||||
let mut elements = self.elements.lock().unwrap();
|
||||
if let Some(i) = elements.iter().position(|elem| {
|
||||
let mut bbox = elem.iced.bbox();
|
||||
bbox.loc = elem.position.as_logical();
|
||||
|
||||
bbox.contains(location.to_i32_floor())
|
||||
}) {
|
||||
let element = &mut elements[i];
|
||||
|
||||
let new_event = TabletMotionEvent {
|
||||
location: location - element.position.as_logical().to_f64(),
|
||||
serial: event.serial,
|
||||
time: event.time,
|
||||
};
|
||||
|
||||
TabletToolTarget::proximity_in(
|
||||
&element.iced,
|
||||
&self.seat,
|
||||
data,
|
||||
handle.descriptor(),
|
||||
&handle.current_tablet(),
|
||||
event.serial,
|
||||
);
|
||||
element.tablet_entered = Some(handle.descriptor().clone());
|
||||
TabletToolTarget::motion(
|
||||
&element.iced,
|
||||
&self.seat,
|
||||
data,
|
||||
handle.descriptor(),
|
||||
&new_event,
|
||||
);
|
||||
self.last_tablet_idx = Some(i);
|
||||
}
|
||||
}
|
||||
handle.proximity_in(data, None, event);
|
||||
}
|
||||
|
||||
fn proximity_out(
|
||||
&mut self,
|
||||
data: &mut State,
|
||||
handle: &mut TabletToolInnerHandle<'_, State>,
|
||||
event: &ProximityOutEvent,
|
||||
) {
|
||||
{
|
||||
let mut elements = self.elements.lock().unwrap();
|
||||
for element in elements.iter_mut().filter(|elem| {
|
||||
elem.tablet_entered
|
||||
.as_ref()
|
||||
.is_some_and(|tool| tool == handle.descriptor())
|
||||
}) {
|
||||
TabletToolTarget::proximity_out(
|
||||
&element.iced,
|
||||
&self.seat,
|
||||
data,
|
||||
handle.descriptor(),
|
||||
);
|
||||
element.tablet_entered.take();
|
||||
}
|
||||
self.last_tablet_idx.take();
|
||||
}
|
||||
handle.unset_grab(self, data, event.serial, event.time, true);
|
||||
}
|
||||
|
||||
fn motion(
|
||||
&mut self,
|
||||
data: &mut State,
|
||||
handle: &mut TabletToolInnerHandle<'_, State>,
|
||||
_focus: Option<(<State as TabletSeatHandler>::ToolFocus, Point<f64, Logical>)>,
|
||||
event: &TabletMotionEvent,
|
||||
) {
|
||||
{
|
||||
let location = if let Some(output) = self.screen_space_relative.as_ref() {
|
||||
if data.common.shell.read().zoom_state().is_some() {
|
||||
event
|
||||
.location
|
||||
.as_global()
|
||||
.to_zoomed(output)
|
||||
.to_global(output)
|
||||
.as_logical()
|
||||
} else {
|
||||
event.location
|
||||
}
|
||||
} else {
|
||||
event.location
|
||||
};
|
||||
|
||||
let mut elements = self.elements.lock().unwrap();
|
||||
if let Some(i) = elements.iter().position(|elem| {
|
||||
let mut bbox = elem.iced.bbox();
|
||||
bbox.loc = elem.position.as_logical();
|
||||
|
||||
bbox.contains(location.to_i32_floor())
|
||||
}) {
|
||||
let element = &mut elements[i];
|
||||
|
||||
let new_event = TabletMotionEvent {
|
||||
location: location - element.position.as_logical().to_f64(),
|
||||
serial: event.serial,
|
||||
time: event.time,
|
||||
};
|
||||
|
||||
if element.tablet_entered.is_none() {
|
||||
TabletToolTarget::proximity_in(
|
||||
&element.iced,
|
||||
&self.seat,
|
||||
data,
|
||||
handle.descriptor(),
|
||||
&handle.current_tablet(),
|
||||
event.serial,
|
||||
);
|
||||
element.tablet_entered = Some(handle.descriptor().clone());
|
||||
}
|
||||
TabletToolTarget::motion(
|
||||
&element.iced,
|
||||
&self.seat,
|
||||
data,
|
||||
handle.descriptor(),
|
||||
&new_event,
|
||||
);
|
||||
self.last_tablet_idx = Some(i);
|
||||
} else {
|
||||
elements
|
||||
.iter_mut()
|
||||
.filter(|element| element.tablet_entered.is_some())
|
||||
.skip(1)
|
||||
.for_each(|element| {
|
||||
TabletToolTarget::proximity_out(
|
||||
&element.iced,
|
||||
&self.seat,
|
||||
data,
|
||||
handle.descriptor(),
|
||||
);
|
||||
});
|
||||
self.last_tablet_idx.take();
|
||||
}
|
||||
}
|
||||
handle.motion(data, None, event);
|
||||
}
|
||||
|
||||
fn down(
|
||||
&mut self,
|
||||
data: &mut State,
|
||||
handle: &mut TabletToolInnerHandle<'_, State>,
|
||||
event: &TabletDownEvent,
|
||||
) {
|
||||
let mut guard = self.elements.lock().unwrap();
|
||||
let elements = &mut *guard;
|
||||
|
||||
if let Some(elem) = self.last_tablet_idx.and_then(|i| elements.get_mut(i))
|
||||
&& elem
|
||||
.tablet_entered
|
||||
.as_ref()
|
||||
.is_some_and(|desc| desc == handle.descriptor())
|
||||
{
|
||||
TabletToolTarget::down(&elem.iced, &self.seat, data, handle.descriptor(), event);
|
||||
} else {
|
||||
handle.down(data, event);
|
||||
}
|
||||
}
|
||||
|
||||
fn up(
|
||||
&mut self,
|
||||
data: &mut State,
|
||||
handle: &mut TabletToolInnerHandle<'_, State>,
|
||||
event: &TabletUpEvent,
|
||||
) {
|
||||
let mut guard = self.elements.lock().unwrap();
|
||||
let elements = &mut *guard;
|
||||
|
||||
if let Some(elem) = self.last_tablet_idx.and_then(|i| elements.get_mut(i))
|
||||
&& elem
|
||||
.tablet_entered
|
||||
.as_ref()
|
||||
.is_some_and(|desc| desc == handle.descriptor())
|
||||
{
|
||||
TabletToolTarget::up(&elem.iced, &self.seat, data, handle.descriptor(), event);
|
||||
} else {
|
||||
handle.up(data, event);
|
||||
}
|
||||
}
|
||||
|
||||
fn button(
|
||||
&mut self,
|
||||
data: &mut State,
|
||||
handle: &mut TabletToolInnerHandle<'_, State>,
|
||||
event: &TabletButtonEvent,
|
||||
) {
|
||||
handle.button(data, event);
|
||||
}
|
||||
|
||||
fn axis(
|
||||
&mut self,
|
||||
data: &mut State,
|
||||
handle: &mut TabletToolInnerHandle<'_, State>,
|
||||
frame: TabletAxisFrame,
|
||||
) {
|
||||
handle.axis(data, frame);
|
||||
}
|
||||
|
||||
fn frame(
|
||||
&mut self,
|
||||
data: &mut State,
|
||||
handle: &mut TabletToolInnerHandle<'_, State>,
|
||||
time: InputTime,
|
||||
) {
|
||||
handle.frame(data, time);
|
||||
}
|
||||
|
||||
fn unset(&mut self, _data: &mut State) {}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
pub struct MenuAlignment {
|
||||
pub x: AxisAlignment,
|
||||
|
|
@ -1052,6 +1313,7 @@ impl MenuGrab {
|
|||
position,
|
||||
pointer_entered: false,
|
||||
touch_entered: None,
|
||||
tablet_entered: None,
|
||||
}]));
|
||||
|
||||
let scale = Arc::new(Mutex::new(screen_space_relative.unwrap_or(1.)));
|
||||
|
|
@ -1076,6 +1338,7 @@ impl MenuGrab {
|
|||
seat: seat.clone(),
|
||||
screen_space_relative,
|
||||
scale,
|
||||
last_tablet_idx: None,
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -1086,10 +1349,15 @@ impl MenuGrab {
|
|||
}
|
||||
}
|
||||
|
||||
pub fn is_touch_grab(&self) -> bool {
|
||||
match self.start_data {
|
||||
GrabStartData::Touch(_) => true,
|
||||
GrabStartData::Pointer(_) => false,
|
||||
pub fn grab_type(&self) -> GrabType {
|
||||
self.start_data.type_()
|
||||
}
|
||||
|
||||
pub fn tool(&self) -> Option<&TabletToolDescriptor> {
|
||||
if let GrabStartData::TabletTool { tool, .. } = &self.start_data {
|
||||
Some(tool)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,18 +1,26 @@
|
|||
use calloop::LoopHandle;
|
||||
use cosmic_settings_config::shortcuts;
|
||||
use smithay::{
|
||||
backend::input::{InputTime, TabletToolDescriptor},
|
||||
input::{
|
||||
Seat,
|
||||
pointer::{
|
||||
AxisFrame, ButtonEvent, GestureHoldBeginEvent, GestureHoldEndEvent,
|
||||
GesturePinchBeginEvent, GesturePinchEndEvent, GesturePinchUpdateEvent,
|
||||
GestureSwipeBeginEvent, GestureSwipeEndEvent, GestureSwipeUpdateEvent,
|
||||
GrabStartData as PointerGrabStartData, MotionEvent, PointerGrab, PointerInnerHandle,
|
||||
RelativeMotionEvent,
|
||||
AxisFrame as PointerAxisFrame, ButtonEvent as PointerButtonEvent,
|
||||
GestureHoldBeginEvent, GestureHoldEndEvent, GesturePinchBeginEvent,
|
||||
GesturePinchEndEvent, GesturePinchUpdateEvent, GestureSwipeBeginEvent,
|
||||
GestureSwipeEndEvent, GestureSwipeUpdateEvent, GrabStartData as PointerGrabStartData,
|
||||
MotionEvent, PointerGrab, PointerInnerHandle, RelativeMotionEvent,
|
||||
},
|
||||
tablet::tool::{
|
||||
AxisFrame as TabletAxisFrame, ButtonEvent as TabletButtonEvent,
|
||||
DownEvent as TabletDownEvent, GrabStartData as TabletGrabStartData,
|
||||
MotionEvent as TabletMotionEvent, ProximityOutEvent, TabletToolGrab,
|
||||
TabletToolInnerHandle, UpEvent as TabletUpEvent,
|
||||
},
|
||||
touch::{
|
||||
DownEvent, GrabStartData as TouchGrabStartData, MotionEvent as TouchMotionEvent,
|
||||
OrientationEvent, ShapeEvent, TouchGrab, TouchInnerHandle, UpEvent,
|
||||
DownEvent as TouchDownEvent, GrabStartData as TouchGrabStartData,
|
||||
MotionEvent as TouchMotionEvent, OrientationEvent, ShapeEvent, TouchGrab,
|
||||
TouchInnerHandle, UpEvent as TouchUpEvent,
|
||||
},
|
||||
},
|
||||
output::Output,
|
||||
|
|
@ -39,6 +47,17 @@ use super::{
|
|||
pub enum GrabStartData {
|
||||
Touch(TouchGrabStartData<State>),
|
||||
Pointer(PointerGrabStartData<State>),
|
||||
TabletTool {
|
||||
tool: TabletToolDescriptor,
|
||||
data: TabletGrabStartData<State>,
|
||||
},
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub enum GrabType {
|
||||
Touch,
|
||||
Pointer,
|
||||
TabletTool,
|
||||
}
|
||||
|
||||
impl GrabStartData {
|
||||
|
|
@ -46,6 +65,7 @@ impl GrabStartData {
|
|||
match self {
|
||||
Self::Touch(touch) => touch.focus.as_ref(),
|
||||
Self::Pointer(pointer) => pointer.focus.as_ref(),
|
||||
Self::TabletTool { data, .. } => data.focus.as_ref(),
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -53,6 +73,7 @@ impl GrabStartData {
|
|||
match self {
|
||||
Self::Touch(touch) => touch.focus = focus,
|
||||
Self::Pointer(pointer) => pointer.focus = focus,
|
||||
Self::TabletTool { data, .. } => data.focus = focus,
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -60,6 +81,7 @@ impl GrabStartData {
|
|||
match self {
|
||||
Self::Touch(touch) => touch.location,
|
||||
Self::Pointer(pointer) => pointer.location,
|
||||
Self::TabletTool { data, .. } => data.location,
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -67,6 +89,7 @@ impl GrabStartData {
|
|||
match self {
|
||||
Self::Touch(touch) => touch.location = location,
|
||||
Self::Pointer(pointer) => pointer.location = location,
|
||||
Self::TabletTool { data, .. } => data.location = location,
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -76,6 +99,14 @@ impl GrabStartData {
|
|||
|
||||
((new.x - old.x).powi(2) + (new.y - old.y).powi(2)).sqrt()
|
||||
}
|
||||
|
||||
pub fn type_(&self) -> GrabType {
|
||||
match self {
|
||||
Self::Pointer(_) => GrabType::Pointer,
|
||||
Self::Touch(_) => GrabType::Touch,
|
||||
Self::TabletTool { .. } => GrabType::TabletTool,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
|
||||
|
|
@ -203,10 +234,17 @@ impl From<ResizeForkGrab> for ResizeGrab {
|
|||
}
|
||||
|
||||
impl ResizeGrab {
|
||||
pub fn is_touch_grab(&self) -> bool {
|
||||
pub fn grab_type(&self) -> GrabType {
|
||||
match self {
|
||||
ResizeGrab::Floating(grab) => grab.is_touch_grab(),
|
||||
ResizeGrab::Tiling(grab) => grab.is_touch_grab(),
|
||||
ResizeGrab::Floating(grab) => grab.grab_type(),
|
||||
ResizeGrab::Tiling(grab) => grab.grab_type(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn tool(&self) -> Option<&TabletToolDescriptor> {
|
||||
match self {
|
||||
ResizeGrab::Floating(grab) => grab.tool(),
|
||||
ResizeGrab::Tiling(grab) => grab.tool(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -242,11 +280,11 @@ impl PointerGrab<State> for ResizeGrab {
|
|||
&mut self,
|
||||
data: &mut State,
|
||||
handle: &mut PointerInnerHandle<'_, State>,
|
||||
event: &ButtonEvent,
|
||||
event: &PointerButtonEvent,
|
||||
) {
|
||||
match self {
|
||||
ResizeGrab::Floating(grab) => grab.button(data, handle, event),
|
||||
ResizeGrab::Tiling(grab) => grab.button(data, handle, event),
|
||||
ResizeGrab::Floating(grab) => PointerGrab::button(grab, data, handle, event),
|
||||
ResizeGrab::Tiling(grab) => PointerGrab::button(grab, data, handle, event),
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -254,11 +292,11 @@ impl PointerGrab<State> for ResizeGrab {
|
|||
&mut self,
|
||||
data: &mut State,
|
||||
handle: &mut PointerInnerHandle<'_, State>,
|
||||
details: AxisFrame,
|
||||
details: PointerAxisFrame,
|
||||
) {
|
||||
match self {
|
||||
ResizeGrab::Floating(grab) => grab.axis(data, handle, details),
|
||||
ResizeGrab::Tiling(grab) => grab.axis(data, handle, details),
|
||||
ResizeGrab::Floating(grab) => PointerGrab::axis(grab, data, handle, details),
|
||||
ResizeGrab::Tiling(grab) => PointerGrab::axis(grab, data, handle, details),
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -386,7 +424,7 @@ impl TouchGrab<State> for ResizeGrab {
|
|||
data: &mut State,
|
||||
handle: &mut TouchInnerHandle<'_, State>,
|
||||
focus: Option<(PointerFocusTarget, Point<f64, Logical>)>,
|
||||
event: &DownEvent,
|
||||
event: &TouchDownEvent,
|
||||
) {
|
||||
match self {
|
||||
ResizeGrab::Floating(grab) => TouchGrab::down(grab, data, handle, focus, event),
|
||||
|
|
@ -394,7 +432,12 @@ impl TouchGrab<State> for ResizeGrab {
|
|||
}
|
||||
}
|
||||
|
||||
fn up(&mut self, data: &mut State, handle: &mut TouchInnerHandle<'_, State>, event: &UpEvent) {
|
||||
fn up(
|
||||
&mut self,
|
||||
data: &mut State,
|
||||
handle: &mut TouchInnerHandle<'_, State>,
|
||||
event: &TouchUpEvent,
|
||||
) {
|
||||
match self {
|
||||
ResizeGrab::Floating(grab) => TouchGrab::up(grab, data, handle, event),
|
||||
ResizeGrab::Tiling(grab) => TouchGrab::up(grab, data, handle, event),
|
||||
|
|
@ -467,6 +510,107 @@ impl TouchGrab<State> for ResizeGrab {
|
|||
}
|
||||
}
|
||||
|
||||
impl TabletToolGrab<State> for ResizeGrab {
|
||||
fn down(
|
||||
&mut self,
|
||||
data: &mut State,
|
||||
handle: &mut TabletToolInnerHandle<'_, State>,
|
||||
event: &TabletDownEvent,
|
||||
) {
|
||||
match self {
|
||||
ResizeGrab::Floating(grab) => TabletToolGrab::down(grab, data, handle, event),
|
||||
ResizeGrab::Tiling(grab) => TabletToolGrab::down(grab, data, handle, event),
|
||||
}
|
||||
}
|
||||
|
||||
fn up(
|
||||
&mut self,
|
||||
data: &mut State,
|
||||
handle: &mut TabletToolInnerHandle<'_, State>,
|
||||
event: &TabletUpEvent,
|
||||
) {
|
||||
match self {
|
||||
ResizeGrab::Floating(grab) => TabletToolGrab::up(grab, data, handle, event),
|
||||
ResizeGrab::Tiling(grab) => TabletToolGrab::up(grab, data, handle, event),
|
||||
}
|
||||
}
|
||||
|
||||
fn motion(
|
||||
&mut self,
|
||||
data: &mut State,
|
||||
handle: &mut TabletToolInnerHandle<'_, State>,
|
||||
focus: Option<(PointerFocusTarget, Point<f64, Logical>)>,
|
||||
event: &TabletMotionEvent,
|
||||
) {
|
||||
match self {
|
||||
ResizeGrab::Floating(grab) => TabletToolGrab::motion(grab, data, handle, focus, event),
|
||||
ResizeGrab::Tiling(grab) => TabletToolGrab::motion(grab, data, handle, focus, event),
|
||||
}
|
||||
}
|
||||
|
||||
fn frame(
|
||||
&mut self,
|
||||
data: &mut State,
|
||||
handle: &mut TabletToolInnerHandle<'_, State>,
|
||||
serial: InputTime,
|
||||
) {
|
||||
match self {
|
||||
ResizeGrab::Floating(grab) => TabletToolGrab::frame(grab, data, handle, serial),
|
||||
ResizeGrab::Tiling(grab) => TabletToolGrab::frame(grab, data, handle, serial),
|
||||
}
|
||||
}
|
||||
|
||||
fn button(
|
||||
&mut self,
|
||||
data: &mut State,
|
||||
handle: &mut TabletToolInnerHandle<'_, State>,
|
||||
event: &TabletButtonEvent,
|
||||
) {
|
||||
match self {
|
||||
ResizeGrab::Floating(grab) => TabletToolGrab::button(grab, data, handle, event),
|
||||
ResizeGrab::Tiling(grab) => TabletToolGrab::button(grab, data, handle, event),
|
||||
}
|
||||
}
|
||||
|
||||
fn axis(
|
||||
&mut self,
|
||||
data: &mut State,
|
||||
handle: &mut TabletToolInnerHandle<'_, State>,
|
||||
details: TabletAxisFrame,
|
||||
) {
|
||||
match self {
|
||||
ResizeGrab::Floating(grab) => TabletToolGrab::axis(grab, data, handle, details),
|
||||
ResizeGrab::Tiling(grab) => TabletToolGrab::axis(grab, data, handle, details),
|
||||
}
|
||||
}
|
||||
|
||||
fn proximity_out(
|
||||
&mut self,
|
||||
data: &mut State,
|
||||
handle: &mut TabletToolInnerHandle<'_, State>,
|
||||
event: &ProximityOutEvent,
|
||||
) {
|
||||
match self {
|
||||
ResizeGrab::Floating(grab) => TabletToolGrab::proximity_out(grab, data, handle, event),
|
||||
ResizeGrab::Tiling(grab) => TabletToolGrab::proximity_out(grab, data, handle, event),
|
||||
}
|
||||
}
|
||||
|
||||
fn start_data(&self) -> &TabletGrabStartData<State> {
|
||||
match self {
|
||||
ResizeGrab::Floating(grab) => TabletToolGrab::start_data(grab),
|
||||
ResizeGrab::Tiling(grab) => TabletToolGrab::start_data(grab),
|
||||
}
|
||||
}
|
||||
|
||||
fn unset(&mut self, data: &mut State) {
|
||||
match self {
|
||||
ResizeGrab::Floating(grab) => TabletToolGrab::unset(grab, data),
|
||||
ResizeGrab::Tiling(grab) => TabletToolGrab::unset(grab, data),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub enum MoveGrab {
|
||||
Move(moving::MoveGrab),
|
||||
Delayed(delay::DelayGrab<MoveGrab>),
|
||||
|
|
@ -536,10 +680,17 @@ impl MoveGrab {
|
|||
}
|
||||
}
|
||||
|
||||
pub fn is_touch_grab(&self) -> bool {
|
||||
pub fn grab_type(&self) -> GrabType {
|
||||
match self {
|
||||
MoveGrab::Move(m) => m.is_touch_grab(),
|
||||
MoveGrab::Delayed(d) => d.is_touch_grab(),
|
||||
MoveGrab::Move(m) => m.grab_type(),
|
||||
MoveGrab::Delayed(d) => d.grab_type(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn tool(&self) -> Option<&TabletToolDescriptor> {
|
||||
match self {
|
||||
MoveGrab::Move(m) => m.tool(),
|
||||
MoveGrab::Delayed(d) => d.tool(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -574,11 +725,11 @@ impl PointerGrab<State> for MoveGrab {
|
|||
&mut self,
|
||||
data: &mut State,
|
||||
handle: &mut PointerInnerHandle<'_, State>,
|
||||
event: &ButtonEvent,
|
||||
event: &PointerButtonEvent,
|
||||
) {
|
||||
match self {
|
||||
MoveGrab::Move(grab) => grab.button(data, handle, event),
|
||||
MoveGrab::Delayed(grab) => grab.button(data, handle, event),
|
||||
MoveGrab::Move(grab) => PointerGrab::button(grab, data, handle, event),
|
||||
MoveGrab::Delayed(grab) => PointerGrab::button(grab, data, handle, event),
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -586,11 +737,11 @@ impl PointerGrab<State> for MoveGrab {
|
|||
&mut self,
|
||||
data: &mut State,
|
||||
handle: &mut PointerInnerHandle<'_, State>,
|
||||
details: AxisFrame,
|
||||
details: PointerAxisFrame,
|
||||
) {
|
||||
match self {
|
||||
MoveGrab::Move(grab) => grab.axis(data, handle, details),
|
||||
MoveGrab::Delayed(grab) => grab.axis(data, handle, details),
|
||||
MoveGrab::Move(grab) => PointerGrab::axis(grab, data, handle, details),
|
||||
MoveGrab::Delayed(grab) => PointerGrab::axis(grab, data, handle, details),
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -718,7 +869,7 @@ impl TouchGrab<State> for MoveGrab {
|
|||
data: &mut State,
|
||||
handle: &mut TouchInnerHandle<'_, State>,
|
||||
focus: Option<(PointerFocusTarget, Point<f64, Logical>)>,
|
||||
event: &DownEvent,
|
||||
event: &TouchDownEvent,
|
||||
) {
|
||||
match self {
|
||||
MoveGrab::Move(grab) => TouchGrab::down(grab, data, handle, focus, event),
|
||||
|
|
@ -726,7 +877,12 @@ impl TouchGrab<State> for MoveGrab {
|
|||
}
|
||||
}
|
||||
|
||||
fn up(&mut self, data: &mut State, handle: &mut TouchInnerHandle<'_, State>, event: &UpEvent) {
|
||||
fn up(
|
||||
&mut self,
|
||||
data: &mut State,
|
||||
handle: &mut TouchInnerHandle<'_, State>,
|
||||
event: &TouchUpEvent,
|
||||
) {
|
||||
match self {
|
||||
MoveGrab::Move(grab) => TouchGrab::up(grab, data, handle, event),
|
||||
MoveGrab::Delayed(grab) => TouchGrab::up(grab, data, handle, event),
|
||||
|
|
@ -798,3 +954,104 @@ impl TouchGrab<State> for MoveGrab {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl TabletToolGrab<State> for MoveGrab {
|
||||
fn down(
|
||||
&mut self,
|
||||
data: &mut State,
|
||||
handle: &mut TabletToolInnerHandle<'_, State>,
|
||||
event: &TabletDownEvent,
|
||||
) {
|
||||
match self {
|
||||
MoveGrab::Move(grab) => TabletToolGrab::down(grab, data, handle, event),
|
||||
MoveGrab::Delayed(grab) => TabletToolGrab::down(grab, data, handle, event),
|
||||
}
|
||||
}
|
||||
|
||||
fn up(
|
||||
&mut self,
|
||||
data: &mut State,
|
||||
handle: &mut TabletToolInnerHandle<'_, State>,
|
||||
event: &TabletUpEvent,
|
||||
) {
|
||||
match self {
|
||||
MoveGrab::Move(grab) => TabletToolGrab::up(grab, data, handle, event),
|
||||
MoveGrab::Delayed(grab) => TabletToolGrab::up(grab, data, handle, event),
|
||||
}
|
||||
}
|
||||
|
||||
fn motion(
|
||||
&mut self,
|
||||
data: &mut State,
|
||||
handle: &mut TabletToolInnerHandle<'_, State>,
|
||||
focus: Option<(PointerFocusTarget, Point<f64, Logical>)>,
|
||||
event: &TabletMotionEvent,
|
||||
) {
|
||||
match self {
|
||||
MoveGrab::Move(grab) => TabletToolGrab::motion(grab, data, handle, focus, event),
|
||||
MoveGrab::Delayed(grab) => TabletToolGrab::motion(grab, data, handle, focus, event),
|
||||
}
|
||||
}
|
||||
|
||||
fn frame(
|
||||
&mut self,
|
||||
data: &mut State,
|
||||
handle: &mut TabletToolInnerHandle<'_, State>,
|
||||
serial: InputTime,
|
||||
) {
|
||||
match self {
|
||||
MoveGrab::Move(grab) => TabletToolGrab::frame(grab, data, handle, serial),
|
||||
MoveGrab::Delayed(grab) => TabletToolGrab::frame(grab, data, handle, serial),
|
||||
}
|
||||
}
|
||||
|
||||
fn button(
|
||||
&mut self,
|
||||
data: &mut State,
|
||||
handle: &mut TabletToolInnerHandle<'_, State>,
|
||||
event: &TabletButtonEvent,
|
||||
) {
|
||||
match self {
|
||||
MoveGrab::Move(grab) => TabletToolGrab::button(grab, data, handle, event),
|
||||
MoveGrab::Delayed(grab) => TabletToolGrab::button(grab, data, handle, event),
|
||||
}
|
||||
}
|
||||
|
||||
fn axis(
|
||||
&mut self,
|
||||
data: &mut State,
|
||||
handle: &mut TabletToolInnerHandle<'_, State>,
|
||||
details: TabletAxisFrame,
|
||||
) {
|
||||
match self {
|
||||
MoveGrab::Move(grab) => TabletToolGrab::axis(grab, data, handle, details),
|
||||
MoveGrab::Delayed(grab) => TabletToolGrab::axis(grab, data, handle, details),
|
||||
}
|
||||
}
|
||||
|
||||
fn proximity_out(
|
||||
&mut self,
|
||||
data: &mut State,
|
||||
handle: &mut TabletToolInnerHandle<'_, State>,
|
||||
event: &ProximityOutEvent,
|
||||
) {
|
||||
match self {
|
||||
MoveGrab::Move(grab) => TabletToolGrab::proximity_out(grab, data, handle, event),
|
||||
MoveGrab::Delayed(grab) => TabletToolGrab::proximity_out(grab, data, handle, event),
|
||||
}
|
||||
}
|
||||
|
||||
fn start_data(&self) -> &TabletGrabStartData<State> {
|
||||
match self {
|
||||
MoveGrab::Move(grab) => TabletToolGrab::start_data(grab),
|
||||
MoveGrab::Delayed(grab) => TabletToolGrab::start_data(grab),
|
||||
}
|
||||
}
|
||||
|
||||
fn unset(&mut self, data: &mut State) {
|
||||
match self {
|
||||
MoveGrab::Move(grab) => TabletToolGrab::unset(grab, data),
|
||||
MoveGrab::Delayed(grab) => TabletToolGrab::unset(grab, data),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -8,6 +8,7 @@ use crate::{
|
|||
CosmicMapped, CosmicSurface, Direction, ManagedLayer,
|
||||
element::{CosmicMappedRenderElement, stack_hover::StackHover},
|
||||
focus::target::{KeyboardFocusTarget, PointerFocusTarget},
|
||||
grabs::GrabType,
|
||||
layout::floating::TiledCorners,
|
||||
},
|
||||
utils::prelude::*,
|
||||
|
|
@ -20,7 +21,7 @@ use smallvec::SmallVec;
|
|||
use smithay::{
|
||||
backend::{
|
||||
drm::DrmNode,
|
||||
input::ButtonState,
|
||||
input::{ButtonState, InputTime, TabletToolDescriptor},
|
||||
renderer::{
|
||||
ImportAll, ImportMem,
|
||||
element::{RenderElement, utils::RescaleRenderElement},
|
||||
|
|
@ -30,13 +31,27 @@ use smithay::{
|
|||
input::{
|
||||
Seat,
|
||||
pointer::{
|
||||
AxisFrame, ButtonEvent, CursorIcon, GestureHoldBeginEvent, GestureHoldEndEvent,
|
||||
GesturePinchBeginEvent, GesturePinchEndEvent, GesturePinchUpdateEvent,
|
||||
GestureSwipeBeginEvent, GestureSwipeEndEvent, GestureSwipeUpdateEvent,
|
||||
GrabStartData as PointerGrabStartData, MotionEvent, PointerGrab, PointerInnerHandle,
|
||||
AxisFrame as PointerAxisFrame, ButtonEvent as PointerButtonEvent, CursorIcon,
|
||||
GestureHoldBeginEvent, GestureHoldEndEvent, GesturePinchBeginEvent,
|
||||
GesturePinchEndEvent, GesturePinchUpdateEvent, GestureSwipeBeginEvent,
|
||||
GestureSwipeEndEvent, GestureSwipeUpdateEvent, GrabStartData as PointerGrabStartData,
|
||||
MotionEvent as PointerMotionEvent, PointerGrab, PointerInnerHandle,
|
||||
RelativeMotionEvent,
|
||||
},
|
||||
touch::{self, GrabStartData as TouchGrabStartData, TouchGrab, TouchInnerHandle},
|
||||
tablet::{
|
||||
TabletSeatHandler,
|
||||
tool::{
|
||||
AxisFrame as TabletAxisFrame, ButtonEvent as TabletButtonEvent,
|
||||
DownEvent as TabletDownEvent, GrabStartData as TabletGrabStartData,
|
||||
MotionEvent as TabletMotionEvent, ProximityInEvent, ProximityOutEvent,
|
||||
TabletToolGrab, TabletToolInnerHandle, UpEvent as TabletUpEvent,
|
||||
},
|
||||
},
|
||||
touch::{
|
||||
DownEvent as TouchDownEvent, GrabStartData as TouchGrabStartData,
|
||||
MotionEvent as TouchMotionEvent, OrientationEvent, ShapeEvent, TouchGrab,
|
||||
TouchInnerHandle, UpEvent as TouchUpEvent,
|
||||
},
|
||||
},
|
||||
output::Output,
|
||||
utils::{IsAlive, Logical, Point, Rectangle, SERIAL_COUNTER, Scale},
|
||||
|
|
@ -503,7 +518,7 @@ impl PointerGrab<State> for MoveGrab {
|
|||
state: &mut State,
|
||||
handle: &mut PointerInnerHandle<'_, State>,
|
||||
_focus: Option<(PointerFocusTarget, Point<f64, Logical>)>,
|
||||
event: &MotionEvent,
|
||||
event: &PointerMotionEvent,
|
||||
) {
|
||||
self.update_location(state, event.location);
|
||||
|
||||
|
|
@ -529,7 +544,7 @@ impl PointerGrab<State> for MoveGrab {
|
|||
&mut self,
|
||||
state: &mut State,
|
||||
handle: &mut PointerInnerHandle<'_, State>,
|
||||
event: &ButtonEvent,
|
||||
event: &PointerButtonEvent,
|
||||
) {
|
||||
handle.button(state, event);
|
||||
match self.release {
|
||||
|
|
@ -550,7 +565,7 @@ impl PointerGrab<State> for MoveGrab {
|
|||
&mut self,
|
||||
state: &mut State,
|
||||
handle: &mut PointerInnerHandle<'_, State>,
|
||||
details: AxisFrame,
|
||||
details: PointerAxisFrame,
|
||||
) {
|
||||
handle.axis(state, details);
|
||||
}
|
||||
|
|
@ -647,7 +662,7 @@ impl TouchGrab<State> for MoveGrab {
|
|||
data: &mut State,
|
||||
handle: &mut TouchInnerHandle<'_, State>,
|
||||
_focus: Option<(PointerFocusTarget, Point<f64, Logical>)>,
|
||||
event: &touch::DownEvent,
|
||||
event: &TouchDownEvent,
|
||||
) {
|
||||
handle.down(data, None, event)
|
||||
}
|
||||
|
|
@ -656,7 +671,7 @@ impl TouchGrab<State> for MoveGrab {
|
|||
&mut self,
|
||||
data: &mut State,
|
||||
handle: &mut TouchInnerHandle<'_, State>,
|
||||
event: &touch::UpEvent,
|
||||
event: &TouchUpEvent,
|
||||
) {
|
||||
if event.slot == <Self as TouchGrab<State>>::start_data(self).slot {
|
||||
handle.unset_grab(self, data);
|
||||
|
|
@ -670,7 +685,7 @@ impl TouchGrab<State> for MoveGrab {
|
|||
data: &mut State,
|
||||
handle: &mut TouchInnerHandle<'_, State>,
|
||||
_focus: Option<(PointerFocusTarget, Point<f64, Logical>)>,
|
||||
event: &touch::MotionEvent,
|
||||
event: &TouchMotionEvent,
|
||||
) {
|
||||
if event.slot == <Self as TouchGrab<State>>::start_data(self).slot {
|
||||
self.update_location(data, event.location);
|
||||
|
|
@ -691,7 +706,7 @@ impl TouchGrab<State> for MoveGrab {
|
|||
&mut self,
|
||||
data: &mut State,
|
||||
handle: &mut TouchInnerHandle<'_, State>,
|
||||
event: &touch::ShapeEvent,
|
||||
event: &ShapeEvent,
|
||||
) {
|
||||
handle.shape(data, event)
|
||||
}
|
||||
|
|
@ -700,7 +715,7 @@ impl TouchGrab<State> for MoveGrab {
|
|||
&mut self,
|
||||
data: &mut State,
|
||||
handle: &mut TouchInnerHandle<'_, State>,
|
||||
event: &touch::OrientationEvent,
|
||||
event: &OrientationEvent,
|
||||
) {
|
||||
handle.orientation(data, event)
|
||||
}
|
||||
|
|
@ -715,6 +730,98 @@ impl TouchGrab<State> for MoveGrab {
|
|||
fn unset(&mut self, _data: &mut State) {}
|
||||
}
|
||||
|
||||
impl TabletToolGrab<State> for MoveGrab {
|
||||
fn start_data(&self) -> &TabletGrabStartData<State> {
|
||||
match &self.start_data {
|
||||
GrabStartData::TabletTool { data, .. } => data,
|
||||
_ => unreachable!(),
|
||||
}
|
||||
}
|
||||
|
||||
fn proximity_out(
|
||||
&mut self,
|
||||
data: &mut State,
|
||||
handle: &mut TabletToolInnerHandle<'_, State>,
|
||||
event: &ProximityOutEvent,
|
||||
) {
|
||||
handle.proximity_out(data, event);
|
||||
handle.unset_grab(self, data, event.serial, event.time, false);
|
||||
}
|
||||
|
||||
fn motion(
|
||||
&mut self,
|
||||
data: &mut State,
|
||||
handle: &mut TabletToolInnerHandle<'_, State>,
|
||||
_focus: Option<(<State as TabletSeatHandler>::ToolFocus, Point<f64, Logical>)>,
|
||||
event: &TabletMotionEvent,
|
||||
) {
|
||||
handle.motion(data, None, event);
|
||||
|
||||
self.update_location(data, event.location);
|
||||
if !self.window.alive() {
|
||||
handle.unset_grab(self, data, event.serial, event.time, true);
|
||||
}
|
||||
}
|
||||
|
||||
fn down(
|
||||
&mut self,
|
||||
data: &mut State,
|
||||
handle: &mut TabletToolInnerHandle<'_, State>,
|
||||
event: &TabletDownEvent,
|
||||
) {
|
||||
handle.down(data, event)
|
||||
}
|
||||
|
||||
fn up(
|
||||
&mut self,
|
||||
data: &mut State,
|
||||
handle: &mut TabletToolInnerHandle<'_, State>,
|
||||
event: &TabletUpEvent,
|
||||
) {
|
||||
handle.up(data, event);
|
||||
handle.unset_grab(self, data, event.serial, event.time, false);
|
||||
}
|
||||
|
||||
fn button(
|
||||
&mut self,
|
||||
data: &mut State,
|
||||
handle: &mut TabletToolInnerHandle<'_, State>,
|
||||
event: &TabletButtonEvent,
|
||||
) {
|
||||
handle.button(data, event)
|
||||
}
|
||||
|
||||
fn axis(
|
||||
&mut self,
|
||||
data: &mut State,
|
||||
handle: &mut TabletToolInnerHandle<'_, State>,
|
||||
frame: TabletAxisFrame,
|
||||
) {
|
||||
handle.axis(data, frame)
|
||||
}
|
||||
|
||||
fn frame(
|
||||
&mut self,
|
||||
data: &mut State,
|
||||
handle: &mut TabletToolInnerHandle<'_, State>,
|
||||
time: InputTime,
|
||||
) {
|
||||
handle.frame(data, time)
|
||||
}
|
||||
|
||||
fn proximity_in(
|
||||
&mut self,
|
||||
data: &mut State,
|
||||
handle: &mut TabletToolInnerHandle<'_, State>,
|
||||
focus: Option<(<State as TabletSeatHandler>::ToolFocus, Point<f64, Logical>)>,
|
||||
event: &ProximityInEvent,
|
||||
) {
|
||||
handle.proximity_in(data, focus, event);
|
||||
}
|
||||
|
||||
fn unset(&mut self, _data: &mut State) {}
|
||||
}
|
||||
|
||||
impl MoveGrab {
|
||||
pub fn new(
|
||||
start_data: GrabStartData,
|
||||
|
|
@ -778,10 +885,15 @@ impl MoveGrab {
|
|||
self.previous == ManagedLayer::Tiling
|
||||
}
|
||||
|
||||
pub fn is_touch_grab(&self) -> bool {
|
||||
match self.start_data {
|
||||
GrabStartData::Touch(_) => true,
|
||||
GrabStartData::Pointer(_) => false,
|
||||
pub fn grab_type(&self) -> GrabType {
|
||||
self.start_data.type_()
|
||||
}
|
||||
|
||||
pub fn tool(&self) -> Option<&TabletToolDescriptor> {
|
||||
if let GrabStartData::TabletTool { tool, .. } = &self.start_data {
|
||||
Some(tool)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -793,7 +905,7 @@ impl Drop for MoveGrab {
|
|||
let seat = self.seat.clone();
|
||||
// false-positive: `Output`s hash is based on it's inner ptr
|
||||
#[allow(clippy::mutable_key_type)]
|
||||
let window_outputs = self.window_outputs.drain().collect::<HashSet<_>>();
|
||||
let window_outputs = std::mem::take(&mut self.window_outputs);
|
||||
let previous = self.previous;
|
||||
let window = self.window.clone();
|
||||
let is_touch_grab = matches!(self.start_data, GrabStartData::Touch(_));
|
||||
|
|
@ -953,10 +1065,10 @@ impl Drop for MoveGrab {
|
|||
position.as_logical().to_f64() - window.geometry().loc.to_f64()
|
||||
+ offset,
|
||||
)),
|
||||
&MotionEvent {
|
||||
&PointerMotionEvent {
|
||||
location: pointer.current_location(),
|
||||
serial,
|
||||
time: state.common.clock.now().as_millis(),
|
||||
time: InputTime::now(),
|
||||
},
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -7,25 +7,36 @@ use crate::{
|
|||
shell::{
|
||||
element::CosmicMapped,
|
||||
focus::target::PointerFocusTarget,
|
||||
grabs::{GrabStartData, ReleaseMode, ResizeEdge},
|
||||
grabs::{GrabStartData, GrabType, ReleaseMode, ResizeEdge},
|
||||
},
|
||||
utils::prelude::*,
|
||||
};
|
||||
use smithay::{
|
||||
backend::input::ButtonState,
|
||||
backend::input::{ButtonState, InputTime, TabletToolDescriptor},
|
||||
desktop::{WindowSurface, space::SpaceElement},
|
||||
input::{
|
||||
Seat,
|
||||
pointer::{
|
||||
AxisFrame, ButtonEvent, CursorIcon, GestureHoldBeginEvent, GestureHoldEndEvent,
|
||||
GesturePinchBeginEvent, GesturePinchEndEvent, GesturePinchUpdateEvent,
|
||||
GestureSwipeBeginEvent, GestureSwipeEndEvent, GestureSwipeUpdateEvent,
|
||||
GrabStartData as PointerGrabStartData, MotionEvent, PointerGrab, PointerInnerHandle,
|
||||
AxisFrame as PointerAxisFrame, ButtonEvent as PointerButtonEvent, CursorIcon,
|
||||
GestureHoldBeginEvent, GestureHoldEndEvent, GesturePinchBeginEvent,
|
||||
GesturePinchEndEvent, GesturePinchUpdateEvent, GestureSwipeBeginEvent,
|
||||
GestureSwipeEndEvent, GestureSwipeUpdateEvent, GrabStartData as PointerGrabStartData,
|
||||
MotionEvent as PointerMotionEvent, PointerGrab, PointerInnerHandle,
|
||||
RelativeMotionEvent,
|
||||
},
|
||||
tablet::{
|
||||
TabletSeatHandler,
|
||||
tool::{
|
||||
AxisFrame as TabletAxisFrame, ButtonEvent as TabletButtonEvent,
|
||||
DownEvent as TabletDownEvent, GrabStartData as TabletGrabStartData,
|
||||
MotionEvent as TabletMotionEvent, ProximityInEvent, ProximityOutEvent,
|
||||
TabletToolGrab, TabletToolInnerHandle, UpEvent as TabletUpEvent,
|
||||
},
|
||||
},
|
||||
touch::{
|
||||
DownEvent, GrabStartData as TouchGrabStartData, MotionEvent as TouchMotionEvent,
|
||||
OrientationEvent, ShapeEvent, TouchGrab, TouchInnerHandle, UpEvent,
|
||||
DownEvent as TouchDownEvent, GrabStartData as TouchGrabStartData,
|
||||
MotionEvent as TouchMotionEvent, OrientationEvent, ShapeEvent, TouchGrab,
|
||||
TouchInnerHandle, UpEvent as TouchUpEvent,
|
||||
},
|
||||
},
|
||||
output::Output,
|
||||
|
|
@ -170,10 +181,19 @@ impl ResizeSurfaceGrab {
|
|||
false
|
||||
}
|
||||
|
||||
pub fn is_touch_grab(&self) -> bool {
|
||||
pub fn grab_type(&self) -> GrabType {
|
||||
match self.start_data {
|
||||
GrabStartData::Touch(_) => true,
|
||||
GrabStartData::Pointer(_) => false,
|
||||
GrabStartData::Pointer(_) => GrabType::Pointer,
|
||||
GrabStartData::Touch(_) => GrabType::Touch,
|
||||
GrabStartData::TabletTool { .. } => GrabType::TabletTool,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn tool(&self) -> Option<&TabletToolDescriptor> {
|
||||
if let GrabStartData::TabletTool { tool, .. } = &self.start_data {
|
||||
Some(tool)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -184,7 +204,7 @@ impl PointerGrab<State> for ResizeSurfaceGrab {
|
|||
data: &mut State,
|
||||
handle: &mut PointerInnerHandle<'_, State>,
|
||||
_focus: Option<(PointerFocusTarget, Point<f64, Logical>)>,
|
||||
event: &MotionEvent,
|
||||
event: &PointerMotionEvent,
|
||||
) {
|
||||
// While the grab is active, no client has pointer focus
|
||||
handle.motion(data, None, event);
|
||||
|
|
@ -209,7 +229,7 @@ impl PointerGrab<State> for ResizeSurfaceGrab {
|
|||
&mut self,
|
||||
data: &mut State,
|
||||
handle: &mut PointerInnerHandle<'_, State>,
|
||||
event: &ButtonEvent,
|
||||
event: &PointerButtonEvent,
|
||||
) {
|
||||
handle.button(data, event);
|
||||
match self.release {
|
||||
|
|
@ -230,7 +250,7 @@ impl PointerGrab<State> for ResizeSurfaceGrab {
|
|||
&mut self,
|
||||
data: &mut State,
|
||||
handle: &mut PointerInnerHandle<'_, State>,
|
||||
details: AxisFrame,
|
||||
details: PointerAxisFrame,
|
||||
) {
|
||||
handle.axis(data, details)
|
||||
}
|
||||
|
|
@ -329,12 +349,17 @@ impl TouchGrab<State> for ResizeSurfaceGrab {
|
|||
data: &mut State,
|
||||
handle: &mut TouchInnerHandle<'_, State>,
|
||||
_focus: Option<(PointerFocusTarget, Point<f64, Logical>)>,
|
||||
event: &DownEvent,
|
||||
event: &TouchDownEvent,
|
||||
) {
|
||||
handle.down(data, None, event)
|
||||
}
|
||||
|
||||
fn up(&mut self, data: &mut State, handle: &mut TouchInnerHandle<'_, State>, event: &UpEvent) {
|
||||
fn up(
|
||||
&mut self,
|
||||
data: &mut State,
|
||||
handle: &mut TouchInnerHandle<'_, State>,
|
||||
event: &TouchUpEvent,
|
||||
) {
|
||||
if event.slot == <Self as TouchGrab<State>>::start_data(self).slot {
|
||||
handle.unset_grab(self, data);
|
||||
}
|
||||
|
|
@ -396,6 +421,99 @@ impl TouchGrab<State> for ResizeSurfaceGrab {
|
|||
}
|
||||
}
|
||||
|
||||
impl TabletToolGrab<State> for ResizeSurfaceGrab {
|
||||
fn start_data(&self) -> &TabletGrabStartData<State> {
|
||||
match &self.start_data {
|
||||
GrabStartData::TabletTool { data, .. } => data,
|
||||
_ => unreachable!(),
|
||||
}
|
||||
}
|
||||
|
||||
fn proximity_out(
|
||||
&mut self,
|
||||
data: &mut State,
|
||||
handle: &mut TabletToolInnerHandle<'_, State>,
|
||||
event: &ProximityOutEvent,
|
||||
) {
|
||||
handle.unset_grab(self, data, event.serial, event.time, true);
|
||||
handle.proximity_out(data, event);
|
||||
}
|
||||
|
||||
fn motion(
|
||||
&mut self,
|
||||
data: &mut State,
|
||||
handle: &mut TabletToolInnerHandle<'_, State>,
|
||||
_focus: Option<(<State as TabletSeatHandler>::ToolFocus, Point<f64, Logical>)>,
|
||||
event: &TabletMotionEvent,
|
||||
) {
|
||||
if self.update_location(event.location.as_global()) {
|
||||
handle.unset_grab(self, data, event.serial, event.time, true);
|
||||
}
|
||||
|
||||
handle.motion(data, None, event);
|
||||
}
|
||||
|
||||
fn down(
|
||||
&mut self,
|
||||
data: &mut State,
|
||||
handle: &mut TabletToolInnerHandle<'_, State>,
|
||||
event: &TabletDownEvent,
|
||||
) {
|
||||
handle.down(data, event)
|
||||
}
|
||||
|
||||
fn up(
|
||||
&mut self,
|
||||
data: &mut State,
|
||||
handle: &mut TabletToolInnerHandle<'_, State>,
|
||||
event: &TabletUpEvent,
|
||||
) {
|
||||
handle.unset_grab(self, data, event.serial, event.time, true);
|
||||
handle.up(data, event);
|
||||
}
|
||||
|
||||
fn button(
|
||||
&mut self,
|
||||
data: &mut State,
|
||||
handle: &mut TabletToolInnerHandle<'_, State>,
|
||||
event: &TabletButtonEvent,
|
||||
) {
|
||||
handle.button(data, event)
|
||||
}
|
||||
|
||||
fn axis(
|
||||
&mut self,
|
||||
data: &mut State,
|
||||
handle: &mut TabletToolInnerHandle<'_, State>,
|
||||
frame: TabletAxisFrame,
|
||||
) {
|
||||
handle.axis(data, frame)
|
||||
}
|
||||
|
||||
fn frame(
|
||||
&mut self,
|
||||
data: &mut State,
|
||||
handle: &mut TabletToolInnerHandle<'_, State>,
|
||||
time: InputTime,
|
||||
) {
|
||||
handle.frame(data, time)
|
||||
}
|
||||
|
||||
fn unset(&mut self, _data: &mut State) {
|
||||
self.ungrab()
|
||||
}
|
||||
|
||||
fn proximity_in(
|
||||
&mut self,
|
||||
data: &mut State,
|
||||
handle: &mut TabletToolInnerHandle<'_, State>,
|
||||
_focus: Option<(<State as TabletSeatHandler>::ToolFocus, Point<f64, Logical>)>,
|
||||
event: &ProximityInEvent,
|
||||
) {
|
||||
handle.proximity_in(data, None, event);
|
||||
}
|
||||
}
|
||||
|
||||
pub struct ResizeGrabMarker(AtomicBool);
|
||||
|
||||
impl ResizeGrabMarker {
|
||||
|
|
|
|||
|
|
@ -4,31 +4,42 @@ use crate::{
|
|||
backend::render::cursor::CursorState,
|
||||
shell::{
|
||||
focus::target::PointerFocusTarget,
|
||||
grabs::{GrabStartData, ReleaseMode},
|
||||
grabs::{GrabStartData, GrabType, ReleaseMode},
|
||||
layout::Orientation,
|
||||
},
|
||||
utils::prelude::*,
|
||||
};
|
||||
use id_tree::{NodeId, Tree};
|
||||
use smithay::{
|
||||
backend::input::ButtonState,
|
||||
backend::input::{ButtonState, InputTime, TabletToolDescriptor},
|
||||
input::{
|
||||
Seat,
|
||||
pointer::{
|
||||
AxisFrame, ButtonEvent, CursorIcon, Focus, GestureHoldBeginEvent, GestureHoldEndEvent,
|
||||
GesturePinchBeginEvent, GesturePinchEndEvent, GesturePinchUpdateEvent,
|
||||
GestureSwipeBeginEvent, GestureSwipeEndEvent, GestureSwipeUpdateEvent,
|
||||
GrabStartData as PointerGrabStartData, MotionEvent, PointerGrab, PointerInnerHandle,
|
||||
PointerTarget, RelativeMotionEvent,
|
||||
AxisFrame as PointerAxisFrame, ButtonEvent as PointerButtonEvent, CursorIcon, Focus,
|
||||
GestureHoldBeginEvent, GestureHoldEndEvent, GesturePinchBeginEvent,
|
||||
GesturePinchEndEvent, GesturePinchUpdateEvent, GestureSwipeBeginEvent,
|
||||
GestureSwipeEndEvent, GestureSwipeUpdateEvent, GrabStartData as PointerGrabStartData,
|
||||
MotionEvent as PointerMotionEvent, PointerGrab, PointerInnerHandle, PointerTarget,
|
||||
RelativeMotionEvent,
|
||||
},
|
||||
tablet::{
|
||||
Tablet, TabletSeatHandler, TabletSeatTrait,
|
||||
tool::{
|
||||
AxisFrame as TabletAxisFrame, ButtonEvent as TabletButtonEvent,
|
||||
DownEvent as TabletDownEvent, GrabStartData as TabletToolGrabStartData,
|
||||
GrabTrigger as TabletToolGrabTrigger, MotionEvent as TabletMotionEvent,
|
||||
ProximityInEvent, ProximityOutEvent, TabletToolGrab, TabletToolInnerHandle,
|
||||
TabletToolTarget, UpEvent as TabletUpEvent,
|
||||
},
|
||||
},
|
||||
touch::{
|
||||
DownEvent, FrameMarker, GrabStartData as TouchGrabStartData,
|
||||
DownEvent as TouchDownEvent, FrameMarker, GrabStartData as TouchGrabStartData,
|
||||
MotionEvent as TouchMotionEvent, OrientationEvent, ShapeEvent, TouchGrab,
|
||||
TouchInnerHandle, TouchTarget, UpEvent,
|
||||
TouchInnerHandle, TouchTarget, UpEvent as TouchUpEvent,
|
||||
},
|
||||
},
|
||||
output::WeakOutput,
|
||||
utils::{IsAlive, Logical, Point},
|
||||
utils::{IsAlive, Logical, Point, Serial},
|
||||
};
|
||||
|
||||
use super::super::{Data, TilingLayout};
|
||||
|
|
@ -48,7 +59,7 @@ impl IsAlive for ResizeForkTarget {
|
|||
}
|
||||
|
||||
impl PointerTarget<State> for ResizeForkTarget {
|
||||
fn enter(&self, seat: &Seat<State>, _data: &mut State, _event: &MotionEvent) {
|
||||
fn enter(&self, seat: &Seat<State>, _data: &mut State, _event: &PointerMotionEvent) {
|
||||
let user_data = seat.user_data();
|
||||
let cursor_state = user_data.get::<CursorState>().unwrap();
|
||||
cursor_state
|
||||
|
|
@ -60,19 +71,13 @@ impl PointerTarget<State> for ResizeForkTarget {
|
|||
});
|
||||
}
|
||||
|
||||
fn leave(
|
||||
&self,
|
||||
seat: &Seat<State>,
|
||||
_data: &mut State,
|
||||
_serial: smithay::utils::Serial,
|
||||
_time: u32,
|
||||
) {
|
||||
fn leave(&self, seat: &Seat<State>, _data: &mut State, _serial: Serial, _time: InputTime) {
|
||||
let user_data = seat.user_data();
|
||||
let cursor_state = user_data.get::<CursorState>().unwrap();
|
||||
cursor_state.lock().unwrap().unset_shape();
|
||||
}
|
||||
|
||||
fn button(&self, seat: &Seat<State>, data: &mut State, event: &ButtonEvent) {
|
||||
fn button(&self, seat: &Seat<State>, data: &mut State, event: &PointerButtonEvent) {
|
||||
if event.button == 0x110 && event.state == ButtonState::Pressed {
|
||||
let seat = seat.clone();
|
||||
let node = self.node.clone();
|
||||
|
|
@ -106,7 +111,7 @@ impl PointerTarget<State> for ResizeForkTarget {
|
|||
}
|
||||
}
|
||||
|
||||
fn motion(&self, _seat: &Seat<State>, _data: &mut State, _event: &MotionEvent) {}
|
||||
fn motion(&self, _seat: &Seat<State>, _data: &mut State, _event: &PointerMotionEvent) {}
|
||||
fn relative_motion(
|
||||
&self,
|
||||
_seat: &Seat<State>,
|
||||
|
|
@ -114,7 +119,7 @@ impl PointerTarget<State> for ResizeForkTarget {
|
|||
_event: &RelativeMotionEvent,
|
||||
) {
|
||||
}
|
||||
fn axis(&self, _seat: &Seat<State>, _data: &mut State, _frame: AxisFrame) {}
|
||||
fn axis(&self, _seat: &Seat<State>, _data: &mut State, _frame: PointerAxisFrame) {}
|
||||
fn frame(&self, _seat: &Seat<State>, _data: &mut State) {}
|
||||
fn gesture_swipe_begin(&self, _: &Seat<State>, _: &mut State, _: &GestureSwipeBeginEvent) {}
|
||||
fn gesture_swipe_update(&self, _: &Seat<State>, _: &mut State, _: &GestureSwipeUpdateEvent) {}
|
||||
|
|
@ -127,7 +132,7 @@ impl PointerTarget<State> for ResizeForkTarget {
|
|||
}
|
||||
|
||||
impl TouchTarget<State> for ResizeForkTarget {
|
||||
fn down(&self, seat: &Seat<State>, data: &mut State, event: &DownEvent) {
|
||||
fn down(&self, seat: &Seat<State>, data: &mut State, event: &TouchDownEvent) {
|
||||
let seat = seat.clone();
|
||||
let node = self.node.clone();
|
||||
let output = self.output.clone();
|
||||
|
|
@ -158,7 +163,7 @@ impl TouchTarget<State> for ResizeForkTarget {
|
|||
});
|
||||
}
|
||||
|
||||
fn up(&self, _seat: &Seat<State>, _data: &mut State, _event: &UpEvent) {}
|
||||
fn up(&self, _seat: &Seat<State>, _data: &mut State, _event: &TouchUpEvent) {}
|
||||
fn motion(&self, _seat: &Seat<State>, _data: &mut State, _event: &TouchMotionEvent) {}
|
||||
fn frame(&self, _seat: &Seat<State>, _data: &mut State, _frame: FrameMarker) {}
|
||||
fn cancel(&self, _seat: &Seat<State>, _data: &mut State, _frame: FrameMarker) {}
|
||||
|
|
@ -169,6 +174,121 @@ impl TouchTarget<State> for ResizeForkTarget {
|
|||
}
|
||||
}
|
||||
|
||||
impl TabletToolTarget<State> for ResizeForkTarget {
|
||||
fn down(
|
||||
&self,
|
||||
seat: &Seat<State>,
|
||||
data: &mut State,
|
||||
tool_descriptor: &TabletToolDescriptor,
|
||||
event: &TabletDownEvent,
|
||||
) {
|
||||
let seat = seat.clone();
|
||||
let node = self.node.clone();
|
||||
let output = self.output.clone();
|
||||
let left_up_idx = self.left_up_idx;
|
||||
let orientation = self.orientation;
|
||||
let serial = event.serial;
|
||||
let time = event.time;
|
||||
let tool = tool_descriptor.clone();
|
||||
data.common.event_loop_handle.insert_idle(move |state| {
|
||||
let pointer = seat.get_pointer().unwrap();
|
||||
let location = pointer.current_location();
|
||||
let tablet = seat.tablet_seat().get_tool(&tool).unwrap();
|
||||
tablet.set_grab(
|
||||
state,
|
||||
ResizeForkGrab::new(
|
||||
GrabStartData::TabletTool {
|
||||
tool,
|
||||
data: TabletToolGrabStartData {
|
||||
focus: None,
|
||||
location,
|
||||
trigger: TabletToolGrabTrigger::Tip,
|
||||
},
|
||||
},
|
||||
location.as_global(),
|
||||
node,
|
||||
left_up_idx,
|
||||
orientation,
|
||||
output,
|
||||
ReleaseMode::NoMouseButtons,
|
||||
),
|
||||
time,
|
||||
serial,
|
||||
Focus::Keep,
|
||||
)
|
||||
});
|
||||
}
|
||||
fn motion(
|
||||
&self,
|
||||
_seat: &Seat<State>,
|
||||
_data: &mut State,
|
||||
_tool_descriptor: &TabletToolDescriptor,
|
||||
_event: &TabletMotionEvent,
|
||||
) {
|
||||
}
|
||||
fn proximity_out(
|
||||
&self,
|
||||
seat: &Seat<State>,
|
||||
_data: &mut State,
|
||||
_tool_descriptor: &TabletToolDescriptor,
|
||||
) {
|
||||
let user_data = seat.user_data();
|
||||
let cursor_state = user_data.get::<CursorState>().unwrap();
|
||||
cursor_state.lock().unwrap().unset_shape();
|
||||
}
|
||||
|
||||
fn proximity_in(
|
||||
&self,
|
||||
seat: &Seat<State>,
|
||||
_data: &mut State,
|
||||
_tool_descriptor: &TabletToolDescriptor,
|
||||
_tablet: &Tablet,
|
||||
_serial: Serial,
|
||||
) {
|
||||
let user_data = seat.user_data();
|
||||
let cursor_state = user_data.get::<CursorState>().unwrap();
|
||||
cursor_state
|
||||
.lock()
|
||||
.unwrap()
|
||||
.set_shape(match self.orientation {
|
||||
Orientation::Horizontal => CursorIcon::RowResize,
|
||||
Orientation::Vertical => CursorIcon::ColResize,
|
||||
});
|
||||
}
|
||||
fn up(
|
||||
&self,
|
||||
_seat: &Seat<State>,
|
||||
_data: &mut State,
|
||||
_tool_descriptor: &TabletToolDescriptor,
|
||||
_event: &TabletUpEvent,
|
||||
) {
|
||||
}
|
||||
fn axis(
|
||||
&self,
|
||||
_seat: &Seat<State>,
|
||||
_data: &mut State,
|
||||
_tool_descriptor: &TabletToolDescriptor,
|
||||
_frame: TabletAxisFrame,
|
||||
) {
|
||||
}
|
||||
fn button(
|
||||
&self,
|
||||
_seat: &Seat<State>,
|
||||
_data: &mut State,
|
||||
_tool_descriptor: &TabletToolDescriptor,
|
||||
_event: &TabletButtonEvent,
|
||||
) {
|
||||
}
|
||||
fn frame(
|
||||
&self,
|
||||
_seat: &Seat<State>,
|
||||
_data: &mut State,
|
||||
_tool_descriptor: &TabletToolDescriptor,
|
||||
_time: InputTime,
|
||||
) {
|
||||
}
|
||||
}
|
||||
|
||||
pub struct ResizeForkGrab {
|
||||
start_data: GrabStartData,
|
||||
last_loc: Point<f64, Global>,
|
||||
|
|
@ -338,10 +458,19 @@ impl ResizeForkGrab {
|
|||
false
|
||||
}
|
||||
|
||||
pub fn is_touch_grab(&self) -> bool {
|
||||
pub fn grab_type(&self) -> GrabType {
|
||||
match self.start_data {
|
||||
GrabStartData::Touch(_) => true,
|
||||
GrabStartData::Pointer(_) => false,
|
||||
GrabStartData::Pointer(_) => GrabType::Pointer,
|
||||
GrabStartData::Touch(_) => GrabType::Touch,
|
||||
GrabStartData::TabletTool { .. } => GrabType::TabletTool,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn tool(&self) -> Option<&TabletToolDescriptor> {
|
||||
if let GrabStartData::TabletTool { tool, .. } = &self.start_data {
|
||||
Some(tool)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -352,7 +481,7 @@ impl PointerGrab<State> for ResizeForkGrab {
|
|||
data: &mut State,
|
||||
handle: &mut PointerInnerHandle<'_, State>,
|
||||
_focus: Option<(PointerFocusTarget, Point<f64, Logical>)>,
|
||||
event: &MotionEvent,
|
||||
event: &PointerMotionEvent,
|
||||
) {
|
||||
// While the grab is active, no client has pointer focus
|
||||
handle.motion(data, None, event);
|
||||
|
|
@ -377,7 +506,7 @@ impl PointerGrab<State> for ResizeForkGrab {
|
|||
&mut self,
|
||||
data: &mut State,
|
||||
handle: &mut PointerInnerHandle<'_, State>,
|
||||
event: &ButtonEvent,
|
||||
event: &PointerButtonEvent,
|
||||
) {
|
||||
handle.button(data, event);
|
||||
match self.release {
|
||||
|
|
@ -398,7 +527,7 @@ impl PointerGrab<State> for ResizeForkGrab {
|
|||
&mut self,
|
||||
data: &mut State,
|
||||
handle: &mut PointerInnerHandle<'_, State>,
|
||||
details: AxisFrame,
|
||||
details: PointerAxisFrame,
|
||||
) {
|
||||
handle.axis(data, details)
|
||||
}
|
||||
|
|
@ -497,12 +626,17 @@ impl TouchGrab<State> for ResizeForkGrab {
|
|||
data: &mut State,
|
||||
handle: &mut TouchInnerHandle<'_, State>,
|
||||
_focus: Option<(PointerFocusTarget, Point<f64, Logical>)>,
|
||||
event: &DownEvent,
|
||||
event: &TouchDownEvent,
|
||||
) {
|
||||
handle.down(data, None, event)
|
||||
}
|
||||
|
||||
fn up(&mut self, data: &mut State, handle: &mut TouchInnerHandle<'_, State>, event: &UpEvent) {
|
||||
fn up(
|
||||
&mut self,
|
||||
data: &mut State,
|
||||
handle: &mut TouchInnerHandle<'_, State>,
|
||||
event: &TouchUpEvent,
|
||||
) {
|
||||
if event.slot == <Self as TouchGrab<State>>::start_data(self).slot {
|
||||
handle.unset_grab(self, data);
|
||||
}
|
||||
|
|
@ -563,3 +697,96 @@ impl TouchGrab<State> for ResizeForkGrab {
|
|||
self.update_location(data, self.last_loc.as_logical(), true);
|
||||
}
|
||||
}
|
||||
|
||||
impl TabletToolGrab<State> for ResizeForkGrab {
|
||||
fn start_data(&self) -> &TabletToolGrabStartData<State> {
|
||||
match &self.start_data {
|
||||
GrabStartData::TabletTool { data, .. } => data,
|
||||
_ => unreachable!(),
|
||||
}
|
||||
}
|
||||
|
||||
fn proximity_out(
|
||||
&mut self,
|
||||
data: &mut State,
|
||||
handle: &mut TabletToolInnerHandle<'_, State>,
|
||||
event: &ProximityOutEvent,
|
||||
) {
|
||||
handle.proximity_out(data, event);
|
||||
handle.unset_grab(self, data, event.serial, event.time, true);
|
||||
}
|
||||
|
||||
fn motion(
|
||||
&mut self,
|
||||
data: &mut State,
|
||||
handle: &mut TabletToolInnerHandle<'_, State>,
|
||||
_focus: Option<(<State as TabletSeatHandler>::ToolFocus, Point<f64, Logical>)>,
|
||||
event: &TabletMotionEvent,
|
||||
) {
|
||||
handle.motion(data, None, event);
|
||||
|
||||
if self.update_location(data, event.location, false) {
|
||||
handle.unset_grab(self, data, event.serial, event.time, true);
|
||||
}
|
||||
}
|
||||
|
||||
fn down(
|
||||
&mut self,
|
||||
data: &mut State,
|
||||
handle: &mut TabletToolInnerHandle<'_, State>,
|
||||
event: &TabletDownEvent,
|
||||
) {
|
||||
handle.down(data, event);
|
||||
}
|
||||
|
||||
fn up(
|
||||
&mut self,
|
||||
data: &mut State,
|
||||
handle: &mut TabletToolInnerHandle<'_, State>,
|
||||
event: &TabletUpEvent,
|
||||
) {
|
||||
handle.up(data, event);
|
||||
handle.unset_grab(self, data, event.serial, event.time, true);
|
||||
}
|
||||
|
||||
fn button(
|
||||
&mut self,
|
||||
data: &mut State,
|
||||
handle: &mut TabletToolInnerHandle<'_, State>,
|
||||
event: &TabletButtonEvent,
|
||||
) {
|
||||
handle.button(data, event)
|
||||
}
|
||||
|
||||
fn axis(
|
||||
&mut self,
|
||||
data: &mut State,
|
||||
handle: &mut TabletToolInnerHandle<'_, State>,
|
||||
frame: TabletAxisFrame,
|
||||
) {
|
||||
handle.axis(data, frame)
|
||||
}
|
||||
|
||||
fn frame(
|
||||
&mut self,
|
||||
data: &mut State,
|
||||
handle: &mut TabletToolInnerHandle<'_, State>,
|
||||
time: InputTime,
|
||||
) {
|
||||
handle.frame(data, time)
|
||||
}
|
||||
|
||||
fn unset(&mut self, data: &mut State) {
|
||||
self.update_location(data, self.last_loc.as_logical(), true);
|
||||
}
|
||||
|
||||
fn proximity_in(
|
||||
&mut self,
|
||||
data: &mut State,
|
||||
handle: &mut TabletToolInnerHandle<'_, State>,
|
||||
focus: Option<(<State as TabletSeatHandler>::ToolFocus, Point<f64, Logical>)>,
|
||||
event: &ProximityInEvent,
|
||||
) {
|
||||
handle.proximity_in(data, focus, event);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
use cosmic_settings_config::shortcuts;
|
||||
use smithay::{
|
||||
backend::input::{KeyState, Keycode},
|
||||
backend::input::{InputTime, KeyState, Keycode},
|
||||
input::{
|
||||
Seat, SeatHandler,
|
||||
keyboard::{
|
||||
|
|
@ -13,6 +13,7 @@ use smithay::{
|
|||
|
||||
use crate::{
|
||||
config::key_bindings::cosmic_modifiers_from_smithay,
|
||||
input::InputBackendId,
|
||||
shell::{Trigger, layout::tiling::NodeDesc},
|
||||
state::State,
|
||||
};
|
||||
|
|
@ -37,7 +38,7 @@ impl KeyboardGrab<State> for SwapWindowGrab {
|
|||
state: KeyState,
|
||||
modifiers: Option<ModifiersState>,
|
||||
serial: Serial,
|
||||
time: u32,
|
||||
time: InputTime,
|
||||
) {
|
||||
if !matches!(&data.common.shell.read().overview_mode.active_trigger(), Some(Trigger::KeyboardSwap(_, d)) if d == &self.desc)
|
||||
{
|
||||
|
|
@ -73,6 +74,7 @@ impl KeyboardGrab<State> for SwapWindowGrab {
|
|||
|
||||
data.handle_shortcut_action(
|
||||
shortcuts::Action::Focus(direction),
|
||||
&InputBackendId::Normal,
|
||||
&self.seat,
|
||||
serial,
|
||||
time,
|
||||
|
|
|
|||
|
|
@ -3381,7 +3381,7 @@ impl TilingLayout {
|
|||
let Some(root) = tree.root_node_id() else {
|
||||
if matches!(
|
||||
overview.active_trigger(),
|
||||
Some(Trigger::Pointer(_) | Trigger::Touch(_))
|
||||
Some(Trigger::Pointer(_) | Trigger::Touch(_) | Trigger::Tool(_, _))
|
||||
) && location_f64.is_some()
|
||||
{
|
||||
let mut tree = tree.copy_clone();
|
||||
|
|
@ -3414,7 +3414,7 @@ impl TilingLayout {
|
|||
|
||||
if matches!(
|
||||
overview.active_trigger(),
|
||||
Some(Trigger::Pointer(_) | Trigger::Touch(_))
|
||||
Some(Trigger::Pointer(_) | Trigger::Touch(_) | Trigger::Tool(_, _))
|
||||
) {
|
||||
let non_exclusive_zone = layer_map_for_output(&self.output)
|
||||
.non_exclusive_zone()
|
||||
|
|
@ -4062,8 +4062,11 @@ impl TilingLayout {
|
|||
let draw_groups = overview.0.alpha();
|
||||
|
||||
let is_overview = !matches!(overview.0, OverviewMode::None);
|
||||
let is_mouse_tiling = (matches!(overview.0.trigger(), Some(Trigger::Pointer(_))))
|
||||
.then(|| self.last_overview_hover.as_ref().map(|(_, zone)| zone));
|
||||
let is_mouse_tiling = (matches!(
|
||||
overview.0.trigger(),
|
||||
Some(Trigger::Pointer(_) | Trigger::Tool(_, _))
|
||||
))
|
||||
.then(|| self.last_overview_hover.as_ref().map(|(_, zone)| zone));
|
||||
let swap_desc = if let Some(Trigger::KeyboardSwap(_, desc)) = overview.0.trigger() {
|
||||
Some(desc.clone())
|
||||
} else {
|
||||
|
|
@ -4216,8 +4219,11 @@ impl TilingLayout {
|
|||
};
|
||||
let draw_groups = overview.0.alpha();
|
||||
|
||||
let is_mouse_tiling = (matches!(overview.0.trigger(), Some(Trigger::Pointer(_))))
|
||||
.then(|| self.last_overview_hover.as_ref().map(|(_, zone)| zone));
|
||||
let is_mouse_tiling = (matches!(
|
||||
overview.0.trigger(),
|
||||
Some(Trigger::Pointer(_) | Trigger::Tool(_, _))
|
||||
))
|
||||
.then(|| self.last_overview_hover.as_ref().map(|(_, zone)| zone));
|
||||
let swap_desc = if let Some(Trigger::KeyboardSwap(_, desc)) = overview.0.trigger() {
|
||||
Some(desc.clone())
|
||||
} else {
|
||||
|
|
|
|||
112
src/shell/mod.rs
112
src/shell/mod.rs
|
|
@ -19,7 +19,10 @@ use crate::{
|
|||
utils,
|
||||
wayland::{
|
||||
handlers::data_device::{self, get_dnd_icon},
|
||||
protocols::workspace::{State as WState, WorkspaceCapabilities},
|
||||
protocols::{
|
||||
session_lock_layer::layer_show_on_lock,
|
||||
workspace::{State as WState, WorkspaceCapabilities},
|
||||
},
|
||||
},
|
||||
};
|
||||
use cosmic_comp_config::{
|
||||
|
|
@ -32,7 +35,10 @@ use cosmic_settings_config::shortcuts::action::{Direction, FocusDirection, Resiz
|
|||
use cosmic_settings_config::{shortcuts, window_rules::ApplicationException};
|
||||
use keyframe::{ease, functions::EaseInOutCubic};
|
||||
use smithay::{
|
||||
backend::{input::TouchSlot, renderer::element::RenderElementStates},
|
||||
backend::{
|
||||
input::{TabletToolDescriptor, TouchSlot},
|
||||
renderer::element::RenderElementStates,
|
||||
},
|
||||
desktop::{
|
||||
LayerSurface, PopupKind, WindowSurface, WindowSurfaceType, layer_map_for_output,
|
||||
space::SpaceElement,
|
||||
|
|
@ -46,6 +52,7 @@ use smithay::{
|
|||
pointer::{
|
||||
CursorImageStatus, CursorImageSurfaceData, Focus, GrabStartData as PointerGrabStartData,
|
||||
},
|
||||
tablet::{TabletSeatTrait, tool::GrabTrigger as TabletGrabTrigger},
|
||||
},
|
||||
output::{Output, WeakOutput},
|
||||
reexports::{
|
||||
|
|
@ -130,6 +137,7 @@ pub enum Trigger {
|
|||
KeyboardMove(shortcuts::Modifiers),
|
||||
Pointer(u32),
|
||||
Touch(TouchSlot),
|
||||
Tool(TabletToolDescriptor, TabletGrabTrigger),
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
|
|
@ -254,6 +262,7 @@ pub struct PendingWindow {
|
|||
pub surface: CosmicSurface,
|
||||
pub seat: Seat<State>,
|
||||
pub fullscreen: Option<Output>,
|
||||
pub minimized: bool,
|
||||
pub maximized: bool,
|
||||
pub sticky: bool,
|
||||
}
|
||||
|
|
@ -1614,6 +1623,29 @@ impl Common {
|
|||
a11y_keyboard_monitor.refresh();
|
||||
}
|
||||
self.image_copy_capture_state.cleanup();
|
||||
self.cleanup_cursor_images();
|
||||
}
|
||||
|
||||
/// Release the enlarged cursor frames a finished shake or zoom left behind.
|
||||
fn cleanup_cursor_images(&mut self) {
|
||||
let shell = self.shell.read();
|
||||
let zoomed = shell.zoom_state.as_ref().is_some_and(|zoom_state| {
|
||||
shell
|
||||
.outputs()
|
||||
.any(|output| zoom_state.animating_level(output) > 1.0)
|
||||
});
|
||||
let now = Instant::now();
|
||||
for seat in shell.seats.iter() {
|
||||
if let Some(cursor_state) = seat
|
||||
.user_data()
|
||||
.get::<crate::backend::render::cursor::CursorState>()
|
||||
{
|
||||
cursor_state
|
||||
.lock()
|
||||
.unwrap()
|
||||
.drop_magnified_frames(now, zoomed);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn refresh_idle_inhibit(&mut self) {
|
||||
|
|
@ -1731,7 +1763,7 @@ impl Shell {
|
|||
if let Some(set) = self.workspaces.sets.get_mut(output) {
|
||||
if matches!(
|
||||
self.overview_mode.active_trigger(),
|
||||
Some(Trigger::Pointer(_) | Trigger::Touch(_))
|
||||
Some(Trigger::Pointer(_) | Trigger::Touch(_) | Trigger::Tool(_, _))
|
||||
) {
|
||||
set.workspaces[set.active].tiling_layer.cleanup_drag();
|
||||
}
|
||||
|
|
@ -1782,7 +1814,7 @@ impl Shell {
|
|||
if let Some(set) = self.workspaces.sets.get_mut(output) {
|
||||
if matches!(
|
||||
self.overview_mode.active_trigger(),
|
||||
Some(Trigger::Pointer(_) | Trigger::Touch(_))
|
||||
Some(Trigger::Pointer(_) | Trigger::Touch(_) | Trigger::Tool(_, _))
|
||||
) {
|
||||
set.workspaces[set.active].tiling_layer.cleanup_drag();
|
||||
}
|
||||
|
|
@ -2017,12 +2049,27 @@ impl Shell {
|
|||
}
|
||||
|
||||
pub fn visible_output_for_surface(&self, surface: &WlSurface) -> Option<&Output> {
|
||||
// NOTE: Keep in sync with surface iteration in `render_input_order_internal`
|
||||
|
||||
if let Some(session_lock) = &self.session_lock {
|
||||
return session_lock
|
||||
if let Some((output, _)) = session_lock
|
||||
.surfaces
|
||||
.iter()
|
||||
.find(|(_, v)| v.wl_surface() == surface)
|
||||
.map(|(k, _)| k);
|
||||
{
|
||||
return Some(output);
|
||||
}
|
||||
for o in self.outputs() {
|
||||
let map = layer_map_for_output(o);
|
||||
if let Some(layer_surface) = map.layer_for_surface(surface, WindowSurfaceType::ALL)
|
||||
{
|
||||
if layer_show_on_lock(layer_surface.wl_surface()) {
|
||||
return Some(o);
|
||||
} else {
|
||||
return None;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
self.outputs()
|
||||
|
|
@ -2286,6 +2333,11 @@ impl Shell {
|
|||
.is_some_and(|state| state.lock().unwrap().is_animating())
|
||||
})
|
||||
})
|
||||
|| self.seats.iter().any(|seat| {
|
||||
seat.user_data()
|
||||
.get::<crate::backend::render::cursor::CursorState>()
|
||||
.is_some_and(|state| state.lock().unwrap().is_magnifying())
|
||||
})
|
||||
}
|
||||
|
||||
pub fn update_animations(&mut self) -> HashMap<ClientId, Client> {
|
||||
|
|
@ -2828,6 +2880,7 @@ impl Shell {
|
|||
surface: window,
|
||||
seat,
|
||||
fullscreen: output,
|
||||
minimized: should_be_minimized,
|
||||
maximized: should_be_maximized,
|
||||
sticky: mut should_be_sticky,
|
||||
} = self.pending_windows.remove(pos);
|
||||
|
|
@ -2911,6 +2964,7 @@ impl Shell {
|
|||
if let Some(FocusTarget::Window(focused)) = maybe_focused
|
||||
&& let Some(stack) = focused.stack_ref()
|
||||
&& !is_dialog
|
||||
&& !should_be_minimized
|
||||
&& !should_be_maximized
|
||||
&& !(workspace.is_tiled(&focused.active_window()) && floating_exception)
|
||||
{
|
||||
|
|
@ -2960,8 +3014,13 @@ impl Shell {
|
|||
self.maximize_request(&mapped, &seat, false, loop_handle);
|
||||
}
|
||||
|
||||
let new_target = if (workspace_output == seat.active_output()
|
||||
&& active_handle == workspace_handle)
|
||||
if should_be_minimized {
|
||||
self.minimize_request(&window);
|
||||
}
|
||||
|
||||
let new_target = if should_be_minimized {
|
||||
None
|
||||
} else if (workspace_output == seat.active_output() && active_handle == workspace_handle)
|
||||
|| should_be_sticky
|
||||
{
|
||||
// TODO: enforce focus stealing prevention by also checking the same rules as for the else case.
|
||||
|
|
@ -3085,6 +3144,7 @@ impl Shell {
|
|||
surface,
|
||||
seat: seat.clone(),
|
||||
fullscreen: None,
|
||||
minimized: false,
|
||||
maximized: false,
|
||||
sticky: false,
|
||||
});
|
||||
|
|
@ -3803,6 +3863,7 @@ impl Shell {
|
|||
let trigger = match &start_data {
|
||||
GrabStartData::Pointer(start_data) => Trigger::Pointer(start_data.button),
|
||||
GrabStartData::Touch(start_data) => Trigger::Touch(start_data.slot),
|
||||
GrabStartData::TabletTool { tool, data } => Trigger::Tool(tool.clone(), data.trigger),
|
||||
};
|
||||
let active_hint = if config.cosmic_conf.active_hint {
|
||||
self.theme.cosmic().active_hint as u8
|
||||
|
|
@ -5024,6 +5085,20 @@ impl Shell {
|
|||
let mut output_presentation_feedback = OutputPresentationFeedback::new(output);
|
||||
|
||||
if let Some(active) = self.active_space(output) {
|
||||
for fs in active.get_fullscreen_surfaces() {
|
||||
fs.surface.take_presentation_feedback(
|
||||
&mut output_presentation_feedback,
|
||||
surface_primary_scanout_output,
|
||||
|surface, _| {
|
||||
surface_presentation_feedback_flags_from_states(
|
||||
surface,
|
||||
None,
|
||||
render_element_states,
|
||||
)
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
active.mapped().for_each(|mapped| {
|
||||
mapped.active_window().take_presentation_feedback(
|
||||
&mut output_presentation_feedback,
|
||||
|
|
@ -5107,10 +5182,19 @@ pub fn check_grab_preconditions(
|
|||
|
||||
let pointer = seat.get_pointer().unwrap();
|
||||
let touch = seat.get_touch().unwrap();
|
||||
let tablet = seat.tablet_seat();
|
||||
let tools = tablet.get_tools();
|
||||
|
||||
let start_data =
|
||||
if serial.is_some_and(|serial| touch.has_grab(serial)) {
|
||||
GrabStartData::Touch(touch.grab_start_data().unwrap())
|
||||
} else if let Some((desc, tool)) =
|
||||
serial.and_then(|serial| tools.iter().find(|(_, tool)| tool.has_grab(serial)))
|
||||
{
|
||||
GrabStartData::TabletTool {
|
||||
tool: desc.clone(),
|
||||
data: tool.grab_start_data().unwrap(),
|
||||
}
|
||||
} else {
|
||||
GrabStartData::Pointer(pointer.grab_start_data().unwrap_or_else(|| {
|
||||
PointerGrabStartData {
|
||||
|
|
@ -5124,8 +5208,16 @@ pub fn check_grab_preconditions(
|
|||
if let Some(surface) = client_initiated {
|
||||
// Check that this surface has a click or touch down grab.
|
||||
if !match serial {
|
||||
Some(serial) => pointer.has_grab(serial) || touch.has_grab(serial),
|
||||
None => pointer.is_grabbed() | touch.is_grabbed(),
|
||||
Some(serial) => {
|
||||
pointer.has_grab(serial)
|
||||
|| touch.has_grab(serial)
|
||||
|| tools.values().any(|tool| tool.has_grab(serial))
|
||||
}
|
||||
None => {
|
||||
pointer.is_grabbed()
|
||||
|| touch.is_grabbed()
|
||||
|| tools.values().any(|tool| tool.is_grabbed())
|
||||
}
|
||||
} {
|
||||
return None;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@ use std::{any::Any, cell::RefCell, collections::HashMap, sync::Mutex};
|
|||
use crate::{
|
||||
backend::render::cursor::CursorState,
|
||||
config::{Config, xkb_config_to_wl},
|
||||
input::{ModifiersShortcutQueue, SupressedButtons, SupressedKeys},
|
||||
input::{InputBackendId, ModifiersShortcutQueue, SupressedButtons, SupressedKeys},
|
||||
state::State,
|
||||
};
|
||||
use smithay::{
|
||||
|
|
@ -82,12 +82,25 @@ impl Seats {
|
|||
self.last_active = Some(seat.clone());
|
||||
}
|
||||
|
||||
pub fn for_device<D: Device>(&self, device: &D) -> Option<&Seat<State>> {
|
||||
self.iter().find(|seat| {
|
||||
let userdata = seat.user_data();
|
||||
let devices = userdata.get::<Devices>().unwrap();
|
||||
devices.has_device(device)
|
||||
})
|
||||
pub fn for_device<D: Device>(
|
||||
&self,
|
||||
device: &D,
|
||||
backend_id: &InputBackendId,
|
||||
) -> Option<&Seat<State>> {
|
||||
self.iter()
|
||||
.find(|seat| {
|
||||
let userdata = seat.user_data();
|
||||
let devices = userdata.get::<Devices>().unwrap();
|
||||
devices.has_device(device, backend_id)
|
||||
})
|
||||
.or_else(|| {
|
||||
// EI devices can be transiently unregistered while the compositor recreates
|
||||
// the absolute-pointer device (e.g. on a scale/geometry change), which would
|
||||
// otherwise drop all pointer/touch input until the client re-binds it. EI is
|
||||
// single-seat, so fall back to the active seat here, matching the EI keyboard
|
||||
// path, which always targets the active seat.
|
||||
matches!(backend_id, InputBackendId::Ei(_)).then(|| self.last_active())
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -96,6 +109,7 @@ impl Devices {
|
|||
&self,
|
||||
device: &D,
|
||||
led_state: LedState,
|
||||
backend_id: &InputBackendId,
|
||||
) -> Vec<DeviceCapability> {
|
||||
let id = device.id();
|
||||
let mut map = self.capabilities.borrow_mut();
|
||||
|
|
@ -113,7 +127,7 @@ impl Devices {
|
|||
.cloned()
|
||||
.filter(|c| map.values().flatten().all(|has| *c != *has))
|
||||
.collect::<Vec<_>>();
|
||||
map.insert(id, caps);
|
||||
map.insert((backend_id.clone(), id), caps);
|
||||
|
||||
if device.has_capability(DeviceCapability::Keyboard)
|
||||
&& let Some(device) = <dyn Any>::downcast_ref::<InputDevice>(device)
|
||||
|
|
@ -126,11 +140,18 @@ impl Devices {
|
|||
new_caps
|
||||
}
|
||||
|
||||
pub fn has_device<D: Device>(&self, device: &D) -> bool {
|
||||
self.capabilities.borrow().contains_key(&device.id())
|
||||
/// Whether the given backend's device with this id is registered on the seat.
|
||||
pub fn has_device<D: Device>(&self, device: &D, backend_id: &InputBackendId) -> bool {
|
||||
self.capabilities
|
||||
.borrow()
|
||||
.contains_key(&(backend_id.clone(), device.id()))
|
||||
}
|
||||
|
||||
pub fn remove_device<D: Device>(&self, device: &D) -> Vec<DeviceCapability> {
|
||||
pub fn remove_device<D: Device>(
|
||||
&self,
|
||||
device: &D,
|
||||
backend_id: &InputBackendId,
|
||||
) -> Vec<DeviceCapability> {
|
||||
let id = device.id();
|
||||
|
||||
let mut keyboards = self.keyboards.borrow_mut();
|
||||
|
|
@ -139,7 +160,7 @@ impl Devices {
|
|||
}
|
||||
|
||||
let mut map = self.capabilities.borrow_mut();
|
||||
map.remove(&id)
|
||||
map.remove(&(backend_id.clone(), id))
|
||||
.unwrap_or_default()
|
||||
.into_iter()
|
||||
.filter(|c| map.values().flatten().all(|has| *c != *has))
|
||||
|
|
@ -155,7 +176,8 @@ impl Devices {
|
|||
|
||||
#[derive(Default)]
|
||||
pub struct Devices {
|
||||
capabilities: RefCell<HashMap<String, Vec<DeviceCapability>>>,
|
||||
// Keyed by `(backend, device_id)`
|
||||
capabilities: RefCell<HashMap<(InputBackendId, String), Vec<DeviceCapability>>>,
|
||||
// Used for updating keyboard leds on kms backend
|
||||
keyboards: RefCell<Vec<InputDevice>>,
|
||||
}
|
||||
|
|
@ -185,7 +207,7 @@ struct FocusedOutput(pub Mutex<Option<Output>>);
|
|||
pub struct PointerConstraintHint(pub Mutex<Option<(WlSurface, Point<f64, Logical>)>>);
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct LastModifierChange(pub Mutex<Option<Serial>>);
|
||||
pub struct LastModifierChange(pub Mutex<(HashMap<InputBackendId, Serial>, Option<Serial>)>);
|
||||
|
||||
pub fn create_seat(
|
||||
dh: &DisplayHandle,
|
||||
|
|
@ -265,6 +287,9 @@ pub trait SeatExt {
|
|||
fn supressed_buttons(&self) -> &SupressedButtons;
|
||||
fn modifiers_shortcut_queue(&self) -> &ModifiersShortcutQueue;
|
||||
fn last_modifier_change(&self) -> Option<Serial>;
|
||||
fn last_modifier_change_for(&self, backend_id: &InputBackendId) -> Option<Serial>;
|
||||
fn set_last_modifier_change(&self, backend_id: &InputBackendId, serial: Serial);
|
||||
fn clear_last_modifier_change(&self, backend_id: &InputBackendId);
|
||||
fn pointer_constraint_hint(&self) -> Option<(WlSurface, Point<f64, Logical>)>;
|
||||
fn set_pointer_constraint_hint(&self, hint: Option<(WlSurface, Point<f64, Logical>)>);
|
||||
|
||||
|
|
@ -343,13 +368,48 @@ impl SeatExt for Seat<State> {
|
|||
}
|
||||
|
||||
fn last_modifier_change(&self) -> Option<Serial> {
|
||||
*self
|
||||
.user_data()
|
||||
self.user_data()
|
||||
.get::<LastModifierChange>()
|
||||
.unwrap()
|
||||
.0
|
||||
.lock()
|
||||
.unwrap()
|
||||
.1
|
||||
}
|
||||
|
||||
fn last_modifier_change_for(&self, backend_id: &InputBackendId) -> Option<Serial> {
|
||||
self.user_data()
|
||||
.get::<LastModifierChange>()
|
||||
.unwrap()
|
||||
.0
|
||||
.lock()
|
||||
.unwrap()
|
||||
.0
|
||||
.get(backend_id)
|
||||
.copied()
|
||||
}
|
||||
|
||||
fn set_last_modifier_change(&self, backend_id: &InputBackendId, serial: Serial) {
|
||||
let mut guard = self
|
||||
.user_data()
|
||||
.get::<LastModifierChange>()
|
||||
.unwrap()
|
||||
.0
|
||||
.lock()
|
||||
.unwrap();
|
||||
guard.0.insert(backend_id.clone(), serial);
|
||||
guard.1 = Some(serial);
|
||||
}
|
||||
|
||||
fn clear_last_modifier_change(&self, backend_id: &InputBackendId) {
|
||||
self.user_data()
|
||||
.get::<LastModifierChange>()
|
||||
.unwrap()
|
||||
.0
|
||||
.lock()
|
||||
.unwrap()
|
||||
.0
|
||||
.remove(backend_id);
|
||||
}
|
||||
|
||||
fn pointer_constraint_hint(&self) -> Option<(WlSurface, Point<f64, Logical>)> {
|
||||
|
|
|
|||
|
|
@ -7,23 +7,35 @@ use cosmic::{
|
|||
theme,
|
||||
widget::{self, icon::Named},
|
||||
};
|
||||
use cosmic_comp_config::ZoomMovement;
|
||||
use cosmic_comp_config::{ZoomConfig, ZoomMovement};
|
||||
use cosmic_config::ConfigSet;
|
||||
use keyframe::{ease, functions::Linear};
|
||||
use smithay::{
|
||||
backend::renderer::ImportMem,
|
||||
backend::{
|
||||
input::{InputTime, TabletToolDescriptor},
|
||||
renderer::ImportMem,
|
||||
},
|
||||
desktop::space::SpaceElement,
|
||||
input::{
|
||||
Seat,
|
||||
pointer::{
|
||||
AxisFrame, ButtonEvent, Focus, GestureHoldBeginEvent, GestureHoldEndEvent,
|
||||
GesturePinchBeginEvent, GesturePinchEndEvent, GesturePinchUpdateEvent,
|
||||
GestureSwipeBeginEvent, GestureSwipeEndEvent, GestureSwipeUpdateEvent,
|
||||
MotionEvent as PointerMotionEvent, PointerTarget, RelativeMotionEvent,
|
||||
AxisFrame as PointerAxisFrame, ButtonEvent as PointerButtonEvent, Focus,
|
||||
GestureHoldBeginEvent, GestureHoldEndEvent, GesturePinchBeginEvent,
|
||||
GesturePinchEndEvent, GesturePinchUpdateEvent, GestureSwipeBeginEvent,
|
||||
GestureSwipeEndEvent, GestureSwipeUpdateEvent, MotionEvent as PointerMotionEvent,
|
||||
PointerTarget, RelativeMotionEvent,
|
||||
},
|
||||
tablet::{
|
||||
Tablet, TabletSeatTrait,
|
||||
tool::{
|
||||
AxisFrame as TabletAxisFrame, ButtonEvent as TabletButtonEvent,
|
||||
DownEvent as TabletDownEvent, MotionEvent as TabletMotionEvent, TabletToolTarget,
|
||||
UpEvent as TabletUpEvent,
|
||||
},
|
||||
},
|
||||
touch::{
|
||||
DownEvent, FrameMarker, MotionEvent as TouchMotionEvent, OrientationEvent, ShapeEvent,
|
||||
TouchTarget, UpEvent,
|
||||
DownEvent as TouchDownEvent, FrameMarker, MotionEvent as TouchMotionEvent,
|
||||
OrientationEvent, ShapeEvent, TouchTarget, UpEvent as TouchUpEvent,
|
||||
},
|
||||
},
|
||||
output::Output,
|
||||
|
|
@ -33,6 +45,7 @@ use tracing::error;
|
|||
|
||||
use crate::{
|
||||
backend::render::element::AsGlowRenderer,
|
||||
shell::grabs::GrabType,
|
||||
state::State,
|
||||
utils::{
|
||||
iced::{IcedElement, IcedRenderElement, Program},
|
||||
|
|
@ -447,7 +460,7 @@ pub enum MenuMessage {
|
|||
|
||||
impl ZoomProgram {
|
||||
pub fn new(level: f64, movement: ZoomMovement, increment: u32) -> Self {
|
||||
let mut increments = vec![25, 50, 100, 150, 200];
|
||||
let mut increments = ZoomConfig::ZOOM_INCREMENT_PRESETS.to_vec();
|
||||
if !increments.contains(&increment) {
|
||||
increments.push(increment);
|
||||
}
|
||||
|
|
@ -699,15 +712,27 @@ impl Program for ZoomProgram {
|
|||
);
|
||||
|
||||
std::mem::drop(shell);
|
||||
if grab.is_touch_grab() {
|
||||
seat.get_touch().unwrap().set_grab(state, grab, serial);
|
||||
} else {
|
||||
seat.get_pointer().unwrap().set_grab(
|
||||
match grab.grab_type() {
|
||||
GrabType::Touch => {
|
||||
seat.get_touch().unwrap().set_grab(state, grab, serial)
|
||||
}
|
||||
GrabType::Pointer => seat.get_pointer().unwrap().set_grab(
|
||||
state,
|
||||
grab,
|
||||
serial,
|
||||
Focus::Clear,
|
||||
);
|
||||
),
|
||||
GrabType::TabletTool => seat
|
||||
.tablet_seat()
|
||||
.get_tool(grab.tool().unwrap())
|
||||
.unwrap()
|
||||
.set_grab(
|
||||
state,
|
||||
grab,
|
||||
InputTime::now(),
|
||||
serial,
|
||||
Focus::Clear,
|
||||
),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -786,15 +811,27 @@ impl Program for ZoomProgram {
|
|||
);
|
||||
|
||||
std::mem::drop(shell);
|
||||
if grab.is_touch_grab() {
|
||||
seat.get_touch().unwrap().set_grab(state, grab, serial);
|
||||
} else {
|
||||
seat.get_pointer().unwrap().set_grab(
|
||||
match grab.grab_type() {
|
||||
GrabType::Touch => {
|
||||
seat.get_touch().unwrap().set_grab(state, grab, serial)
|
||||
}
|
||||
GrabType::Pointer => seat.get_pointer().unwrap().set_grab(
|
||||
state,
|
||||
grab,
|
||||
serial,
|
||||
Focus::Clear,
|
||||
);
|
||||
),
|
||||
GrabType::TabletTool => seat
|
||||
.tablet_seat()
|
||||
.get_tool(grab.tool().unwrap())
|
||||
.unwrap()
|
||||
.set_grab(
|
||||
state,
|
||||
grab,
|
||||
InputTime::now(),
|
||||
serial,
|
||||
Focus::Clear,
|
||||
),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -829,7 +866,7 @@ impl Program for ZoomProgram {
|
|||
if let Some(pos) = self.increments.iter().position(|val| *val == increment) {
|
||||
self.increment_idx = pos;
|
||||
} else {
|
||||
let mut increments = vec![25, 50, 100, 150, 200];
|
||||
let mut increments = ZoomConfig::ZOOM_INCREMENT_PRESETS.to_vec();
|
||||
if !increments.contains(&increment) {
|
||||
increments.push(increment);
|
||||
}
|
||||
|
|
@ -884,14 +921,14 @@ impl PointerTarget<State> for ZoomFocusTarget {
|
|||
}
|
||||
}
|
||||
|
||||
fn button(&self, seat: &Seat<State>, data: &mut State, event: &ButtonEvent) {
|
||||
fn button(&self, seat: &Seat<State>, data: &mut State, event: &PointerButtonEvent) {
|
||||
match self {
|
||||
ZoomFocusTarget::Main(elem) => PointerTarget::button(elem, seat, data, event),
|
||||
ZoomFocusTarget::Menu(elem) => PointerTarget::button(elem, seat, data, event),
|
||||
}
|
||||
}
|
||||
|
||||
fn axis(&self, seat: &Seat<State>, data: &mut State, frame: AxisFrame) {
|
||||
fn axis(&self, seat: &Seat<State>, data: &mut State, frame: PointerAxisFrame) {
|
||||
match self {
|
||||
ZoomFocusTarget::Main(elem) => PointerTarget::axis(elem, seat, data, frame),
|
||||
ZoomFocusTarget::Menu(elem) => PointerTarget::axis(elem, seat, data, frame),
|
||||
|
|
@ -1024,7 +1061,7 @@ impl PointerTarget<State> for ZoomFocusTarget {
|
|||
}
|
||||
}
|
||||
|
||||
fn leave(&self, seat: &Seat<State>, data: &mut State, serial: Serial, time: u32) {
|
||||
fn leave(&self, seat: &Seat<State>, data: &mut State, serial: Serial, time: InputTime) {
|
||||
match self {
|
||||
ZoomFocusTarget::Main(elem) => PointerTarget::leave(elem, seat, data, serial, time),
|
||||
ZoomFocusTarget::Menu(elem) => PointerTarget::leave(elem, seat, data, serial, time),
|
||||
|
|
@ -1033,14 +1070,14 @@ impl PointerTarget<State> for ZoomFocusTarget {
|
|||
}
|
||||
|
||||
impl TouchTarget<State> for ZoomFocusTarget {
|
||||
fn down(&self, seat: &Seat<State>, data: &mut State, event: &DownEvent) {
|
||||
fn down(&self, seat: &Seat<State>, data: &mut State, event: &TouchDownEvent) {
|
||||
match self {
|
||||
ZoomFocusTarget::Main(elem) => TouchTarget::down(elem, seat, data, event),
|
||||
ZoomFocusTarget::Menu(elem) => TouchTarget::down(elem, seat, data, event),
|
||||
}
|
||||
}
|
||||
|
||||
fn up(&self, seat: &Seat<State>, data: &mut State, event: &UpEvent) {
|
||||
fn up(&self, seat: &Seat<State>, data: &mut State, event: &TouchUpEvent) {
|
||||
match self {
|
||||
ZoomFocusTarget::Main(elem) => TouchTarget::up(elem, seat, data, event),
|
||||
ZoomFocusTarget::Menu(elem) => TouchTarget::up(elem, seat, data, event),
|
||||
|
|
@ -1090,6 +1127,144 @@ impl TouchTarget<State> for ZoomFocusTarget {
|
|||
}
|
||||
}
|
||||
|
||||
impl TabletToolTarget<State> for ZoomFocusTarget {
|
||||
fn proximity_in(
|
||||
&self,
|
||||
seat: &Seat<State>,
|
||||
data: &mut State,
|
||||
tool_descriptor: &TabletToolDescriptor,
|
||||
tablet: &Tablet,
|
||||
serial: Serial,
|
||||
) {
|
||||
match self {
|
||||
ZoomFocusTarget::Main(elem) => {
|
||||
TabletToolTarget::proximity_in(elem, seat, data, tool_descriptor, tablet, serial)
|
||||
}
|
||||
ZoomFocusTarget::Menu(elem) => {
|
||||
TabletToolTarget::proximity_in(elem, seat, data, tool_descriptor, tablet, serial)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn proximity_out(
|
||||
&self,
|
||||
seat: &Seat<State>,
|
||||
data: &mut State,
|
||||
tool_descriptor: &TabletToolDescriptor,
|
||||
) {
|
||||
match self {
|
||||
ZoomFocusTarget::Main(elem) => {
|
||||
TabletToolTarget::proximity_out(elem, seat, data, tool_descriptor)
|
||||
}
|
||||
ZoomFocusTarget::Menu(elem) => {
|
||||
TabletToolTarget::proximity_out(elem, seat, data, tool_descriptor)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn down(
|
||||
&self,
|
||||
seat: &Seat<State>,
|
||||
data: &mut State,
|
||||
tool_descriptor: &TabletToolDescriptor,
|
||||
event: &TabletDownEvent,
|
||||
) {
|
||||
match self {
|
||||
ZoomFocusTarget::Main(elem) => {
|
||||
TabletToolTarget::down(elem, seat, data, tool_descriptor, event)
|
||||
}
|
||||
ZoomFocusTarget::Menu(elem) => {
|
||||
TabletToolTarget::down(elem, seat, data, tool_descriptor, event)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn up(
|
||||
&self,
|
||||
seat: &Seat<State>,
|
||||
data: &mut State,
|
||||
tool_descriptor: &TabletToolDescriptor,
|
||||
event: &TabletUpEvent,
|
||||
) {
|
||||
match self {
|
||||
ZoomFocusTarget::Main(elem) => {
|
||||
TabletToolTarget::up(elem, seat, data, tool_descriptor, event)
|
||||
}
|
||||
ZoomFocusTarget::Menu(elem) => {
|
||||
TabletToolTarget::up(elem, seat, data, tool_descriptor, event)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn motion(
|
||||
&self,
|
||||
seat: &Seat<State>,
|
||||
data: &mut State,
|
||||
tool_descriptor: &TabletToolDescriptor,
|
||||
event: &TabletMotionEvent,
|
||||
) {
|
||||
match self {
|
||||
ZoomFocusTarget::Main(elem) => {
|
||||
TabletToolTarget::motion(elem, seat, data, tool_descriptor, event)
|
||||
}
|
||||
ZoomFocusTarget::Menu(elem) => {
|
||||
TabletToolTarget::motion(elem, seat, data, tool_descriptor, event)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn axis(
|
||||
&self,
|
||||
seat: &Seat<State>,
|
||||
data: &mut State,
|
||||
tool_descriptor: &TabletToolDescriptor,
|
||||
frame: TabletAxisFrame,
|
||||
) {
|
||||
match self {
|
||||
ZoomFocusTarget::Main(elem) => {
|
||||
TabletToolTarget::axis(elem, seat, data, tool_descriptor, frame)
|
||||
}
|
||||
ZoomFocusTarget::Menu(elem) => {
|
||||
TabletToolTarget::axis(elem, seat, data, tool_descriptor, frame)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn button(
|
||||
&self,
|
||||
seat: &Seat<State>,
|
||||
data: &mut State,
|
||||
tool_descriptor: &TabletToolDescriptor,
|
||||
event: &TabletButtonEvent,
|
||||
) {
|
||||
match self {
|
||||
ZoomFocusTarget::Main(elem) => {
|
||||
TabletToolTarget::button(elem, seat, data, tool_descriptor, event)
|
||||
}
|
||||
ZoomFocusTarget::Menu(elem) => {
|
||||
TabletToolTarget::button(elem, seat, data, tool_descriptor, event)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn frame(
|
||||
&self,
|
||||
seat: &Seat<State>,
|
||||
data: &mut State,
|
||||
tool_descriptor: &TabletToolDescriptor,
|
||||
time: InputTime,
|
||||
) {
|
||||
match self {
|
||||
ZoomFocusTarget::Main(elem) => {
|
||||
TabletToolTarget::frame(elem, seat, data, tool_descriptor, time)
|
||||
}
|
||||
ZoomFocusTarget::Menu(elem) => {
|
||||
TabletToolTarget::frame(elem, seat, data, tool_descriptor, time)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl IsAlive for ZoomFocusTarget {
|
||||
fn alive(&self) -> bool {
|
||||
match self {
|
||||
|
|
|
|||
96
src/state.rs
96
src/state.rs
|
|
@ -23,6 +23,7 @@ use crate::{
|
|||
output_configuration::OutputConfigurationState,
|
||||
output_power::OutputPowerState,
|
||||
overlap_notify::OverlapNotifyState,
|
||||
session_lock_layer::SessionLockLayerState,
|
||||
toplevel_info::ToplevelInfoState,
|
||||
toplevel_management::{ManagementCapabilities, ToplevelManagementState},
|
||||
workspace::{WorkspaceState, WorkspaceUpdateGuard},
|
||||
|
|
@ -244,9 +245,32 @@ pub struct Common {
|
|||
pub clock: Clock<Monotonic>,
|
||||
pub startup_done: Arc<AtomicBool>,
|
||||
pub should_stop: bool,
|
||||
pub kiosk_exit_code: Option<i32>,
|
||||
|
||||
pub gesture_state: Option<GestureState>,
|
||||
|
||||
/// Active libei sender seats, keyed by their `eis` connection. Tracked so their virtual
|
||||
/// keyboards can be re-created when the keyboard configuration changes at runtime.
|
||||
pub ei_seats: std::collections::HashMap<
|
||||
smithay::reexports::reis::eis::Connection,
|
||||
smithay::backend::libei::EiInputSeat,
|
||||
>,
|
||||
|
||||
/// The shared-seat [`KeyboardSource`] assigned to each libei connection, so its
|
||||
/// `ei_keyboard` key events feed the seat keyboard with independent per-source hold
|
||||
/// tracking (and can be released together on disconnect). Keyed by connection.
|
||||
pub ei_keyboard_source: std::collections::HashMap<
|
||||
smithay::reexports::reis::eis::Connection,
|
||||
smithay::input::keyboard::KeyboardSource,
|
||||
>,
|
||||
|
||||
/// Pointer buttons currently held by each libei connection, so they can be released when the
|
||||
/// connection drops
|
||||
pub ei_pointer_buttons: std::collections::HashMap<
|
||||
smithay::reexports::reis::eis::Connection,
|
||||
std::collections::HashSet<u32>,
|
||||
>,
|
||||
|
||||
pub kiosk_child: Option<Child>,
|
||||
pub theme: cosmic::Theme,
|
||||
|
||||
|
|
@ -275,7 +299,7 @@ pub struct Common {
|
|||
pub idle_inhibiting_surfaces: HashSet<WlSurface>,
|
||||
pub shm_state: ShmState,
|
||||
pub cursor_shape_manager_state: CursorShapeManagerState,
|
||||
pub wl_drm_state: WlDrmState<Option<DrmNode>>,
|
||||
pub wl_drm_state: Option<WlDrmState<Option<DrmNode>>>,
|
||||
pub viewporter_state: ViewporterState,
|
||||
pub kde_decoration_state: KdeDecorationState,
|
||||
pub xdg_decoration_state: XdgDecorationState,
|
||||
|
|
@ -297,6 +321,7 @@ pub struct Common {
|
|||
pub xwayland_state: Option<XWaylandState>,
|
||||
pub xwayland_shell_state: XWaylandShellState,
|
||||
pub pointer_focus_state: Option<PointerFocusState>,
|
||||
pub session_lock_layer_state: SessionLockLayerState,
|
||||
|
||||
#[cfg(feature = "logind")]
|
||||
pub inhibit_lid_fd: Option<OwnedFd>,
|
||||
|
|
@ -325,7 +350,7 @@ pub enum LockedBackend<'a> {
|
|||
pub struct SurfaceDmabufFeedback {
|
||||
pub render_feedback: DmabufFeedback,
|
||||
pub overlay_scanout_feedback: Option<DmabufFeedback>,
|
||||
pub primary_scanout_feedback: Option<DmabufFeedback>,
|
||||
pub primary_scanout_feedback: DmabufFeedback,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
|
|
@ -642,7 +667,7 @@ impl State {
|
|||
.unwrap();
|
||||
|
||||
let clock = Clock::new();
|
||||
let config = Config::load(&handle);
|
||||
let config = Config::load(&handle, kiosk_command.is_some());
|
||||
let compositor_state = CompositorState::new::<Self>(dh);
|
||||
let corner_radius_state = CornerRadiusState::new::<Self>(dh);
|
||||
let data_device_state = DataDeviceState::new::<Self>(dh);
|
||||
|
|
@ -670,7 +695,7 @@ impl State {
|
|||
let cursor_shape_manager_state = CursorShapeManagerState::new::<State>(dh);
|
||||
let seat_state = SeatState::<Self>::new();
|
||||
let viewporter_state = ViewporterState::new::<Self>(dh);
|
||||
let wl_drm_state = WlDrmState::<Option<DrmNode>>::default();
|
||||
let wl_drm_state = None;
|
||||
let kde_decoration_state = KdeDecorationState::new::<Self>(dh, Mode::Client);
|
||||
let xdg_decoration_state = XdgDecorationState::new::<Self>(dh);
|
||||
let session_lock_manager_state =
|
||||
|
|
@ -740,6 +765,9 @@ impl State {
|
|||
|
||||
let dbus_state = DBusState::init(&handle);
|
||||
|
||||
let session_lock_layer_state =
|
||||
SessionLockLayerState::new::<State, _>(dh, client_not_sandboxed);
|
||||
|
||||
State {
|
||||
common: Common {
|
||||
config,
|
||||
|
|
@ -754,7 +782,11 @@ impl State {
|
|||
clock,
|
||||
startup_done: Arc::new(AtomicBool::new(false)),
|
||||
should_stop: false,
|
||||
kiosk_exit_code: None,
|
||||
gesture_state: None,
|
||||
ei_seats: std::collections::HashMap::new(),
|
||||
ei_keyboard_source: std::collections::HashMap::new(),
|
||||
ei_pointer_buttons: std::collections::HashMap::new(),
|
||||
|
||||
kiosk_child: None,
|
||||
theme: cosmic::theme::system_preference(),
|
||||
|
|
@ -803,6 +835,7 @@ impl State {
|
|||
pointer_focus_state: None,
|
||||
dbus_state,
|
||||
keyboard_layout_state,
|
||||
session_lock_layer_state,
|
||||
|
||||
#[cfg(feature = "logind")]
|
||||
inhibit_lid_fd: None,
|
||||
|
|
@ -941,6 +974,8 @@ impl Common {
|
|||
output: &Output,
|
||||
render_element_states: &RenderElementStates,
|
||||
) {
|
||||
// NOTE: Keep in sync with surface iteration in `render_input_order_internal`
|
||||
|
||||
let shell = self.shell.read();
|
||||
let processor = |namespace: Option<usize>| {
|
||||
move |surface: &WlSurface, states: &SurfaceData| {
|
||||
|
|
@ -954,8 +989,11 @@ impl Common {
|
|||
);
|
||||
if let Some(output) = primary_scanout_output {
|
||||
with_fractional_scale(states, |fraction_scale| {
|
||||
fraction_scale
|
||||
.set_preferred_scale(output.current_scale().fractional_scale());
|
||||
// The 1.0 clamp is a workaround for Chromium
|
||||
// TODO: remove if Chromium ever gets fixed
|
||||
fraction_scale.set_preferred_scale(
|
||||
output.current_scale().fractional_scale().max(1.0),
|
||||
);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
@ -1044,6 +1082,8 @@ impl Common {
|
|||
render_element_states: &RenderElementStates,
|
||||
mut dmabuf_feedback: impl FnMut(DrmNode) -> Option<SurfaceDmabufFeedback>,
|
||||
) {
|
||||
// NOTE: Keep in sync with surface iteration in `render_input_order_internal`
|
||||
|
||||
let shell = self.shell.read();
|
||||
|
||||
if let Some(session_lock) = shell.session_lock.as_ref()
|
||||
|
|
@ -1061,10 +1101,7 @@ impl Common {
|
|||
surface,
|
||||
render_element_states,
|
||||
&feedback.render_feedback,
|
||||
feedback
|
||||
.primary_scanout_feedback
|
||||
.as_ref()
|
||||
.unwrap_or(&feedback.render_feedback),
|
||||
&feedback.primary_scanout_feedback,
|
||||
)
|
||||
},
|
||||
)
|
||||
|
|
@ -1260,6 +1297,8 @@ impl Common {
|
|||
|
||||
#[profiling::function]
|
||||
pub fn send_frames(&self, output: &Output, sequence: Option<usize>) {
|
||||
// NOTE: Keep in sync with surface iteration in `render_input_order_internal`
|
||||
|
||||
let time = self.clock.now();
|
||||
let should_send = |surface: &WlSurface, states: &SurfaceData| {
|
||||
// Do the standard primary scanout output check. For pointer surfaces it deduplicates
|
||||
|
|
@ -1301,8 +1340,12 @@ impl Common {
|
|||
const THROTTLE: Option<Duration> = Some(Duration::from_millis(995));
|
||||
const SCREENCOPY_THROTTLE: Option<Duration> = Some(Duration::from_nanos(16_666_666));
|
||||
|
||||
fn throttle(session_holder: &impl SessionHolder) -> Option<Duration> {
|
||||
if session_holder.sessions().is_empty() && session_holder.cursor_sessions().is_empty() {
|
||||
fn throttle(session_holder: &impl SessionHolder, is_xwayland: bool) -> Option<Duration> {
|
||||
if is_xwayland {
|
||||
Some(Duration::ZERO)
|
||||
} else if session_holder.sessions().is_empty()
|
||||
&& session_holder.cursor_sessions().is_empty()
|
||||
{
|
||||
THROTTLE
|
||||
} else {
|
||||
SCREENCOPY_THROTTLE
|
||||
|
|
@ -1338,7 +1381,8 @@ impl Common {
|
|||
&& let Some(grab_state) = move_grab.lock().unwrap().as_ref()
|
||||
{
|
||||
for (window, _) in grab_state.element().windows() {
|
||||
window.send_frame(output, time, throttle(&window), should_send);
|
||||
let throttle = throttle(&window, window.x11_surface().is_some());
|
||||
window.send_frame(output, time, throttle, should_send);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -1362,25 +1406,28 @@ impl Common {
|
|||
.mapped()
|
||||
.for_each(|mapped| {
|
||||
for (window, _) in mapped.windows() {
|
||||
window.send_frame(output, time, throttle(&window), should_send);
|
||||
let throttle = throttle(&window, window.x11_surface().is_some());
|
||||
window.send_frame(output, time, throttle, should_send);
|
||||
}
|
||||
});
|
||||
|
||||
if let Some(active) = shell.active_space(output) {
|
||||
if let Some(fs) = active.get_fullscreen(shell.seats.last_active()) {
|
||||
fs.surface
|
||||
.send_frame(output, time, throttle(&fs.surface), should_send);
|
||||
let throttle = throttle(&fs.surface, fs.surface.x11_surface().is_some());
|
||||
fs.surface.send_frame(output, time, throttle, should_send);
|
||||
}
|
||||
active.mapped().for_each(|mapped| {
|
||||
for (window, _) in mapped.windows() {
|
||||
window.send_frame(output, time, throttle(&window), should_send);
|
||||
let throttle = throttle(&window, window.x11_surface().is_some());
|
||||
window.send_frame(output, time, throttle, should_send);
|
||||
}
|
||||
});
|
||||
|
||||
// other (throttled) windows
|
||||
active.minimized_windows.iter().for_each(|m| {
|
||||
for window in m.windows() {
|
||||
window.send_frame(output, time, throttle(&window), |_, _| None);
|
||||
let throttle = throttle(&window, window.x11_surface().is_some());
|
||||
window.send_frame(output, time, throttle, |_, _| None);
|
||||
}
|
||||
});
|
||||
|
||||
|
|
@ -1390,18 +1437,25 @@ impl Common {
|
|||
.filter(|w| w.handle != active.handle)
|
||||
{
|
||||
if let Some(fs) = space.get_fullscreen(shell.seats.last_active()) {
|
||||
let throttle = min(throttle(space), throttle(&fs.surface));
|
||||
let throttle = min(
|
||||
throttle(space, false),
|
||||
throttle(&fs.surface, fs.surface.x11_surface().is_some()),
|
||||
);
|
||||
fs.surface.send_frame(output, time, throttle, |_, _| None);
|
||||
}
|
||||
space.mapped().for_each(|mapped| {
|
||||
for (window, _) in mapped.windows() {
|
||||
let throttle = min(throttle(space), throttle(&window));
|
||||
let throttle = min(
|
||||
throttle(space, false),
|
||||
throttle(&window, window.x11_surface().is_some()),
|
||||
);
|
||||
window.send_frame(output, time, throttle, |_, _| None);
|
||||
}
|
||||
});
|
||||
space.minimized_windows.iter().for_each(|m| {
|
||||
for window in m.windows() {
|
||||
window.send_frame(output, time, throttle(&window), |_, _| None);
|
||||
let throttle = throttle(&window, window.x11_surface().is_some());
|
||||
window.send_frame(output, time, throttle, |_, _| None);
|
||||
}
|
||||
})
|
||||
}
|
||||
|
|
|
|||
28
src/utils/global.rs
Normal file
28
src/utils/global.rs
Normal file
|
|
@ -0,0 +1,28 @@
|
|||
use std::time::Duration;
|
||||
|
||||
use calloop::{
|
||||
LoopHandle,
|
||||
timer::{TimeoutAction, Timer},
|
||||
};
|
||||
use smithay::reexports::wayland_server::DisplayHandle;
|
||||
use wayland_backend::server::GlobalId;
|
||||
|
||||
pub fn remove_global_with_timer<D: 'static>(
|
||||
dh: &DisplayHandle,
|
||||
event_loop_handle: &LoopHandle<D>,
|
||||
id: GlobalId,
|
||||
) {
|
||||
dh.disable_global::<D>(id.clone());
|
||||
let source = Timer::from_duration(Duration::from_secs(5));
|
||||
let dh = dh.clone();
|
||||
let res = event_loop_handle.insert_source(source, move |_, _, _state| {
|
||||
dh.remove_global::<D>(id.clone());
|
||||
TimeoutAction::Drop
|
||||
});
|
||||
if let Err(err) = res {
|
||||
tracing::error!(
|
||||
"failed to insert timer source to destroy output global: {}",
|
||||
err
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
@ -31,7 +31,7 @@ use ordered_float::OrderedFloat;
|
|||
use smithay::{
|
||||
backend::{
|
||||
allocator::Fourcc,
|
||||
input::{ButtonState, KeyState},
|
||||
input::{ButtonState, InputTime, KeyState, TabletToolDescriptor},
|
||||
renderer::{
|
||||
ImportMem,
|
||||
element::{
|
||||
|
|
@ -45,14 +45,23 @@ use smithay::{
|
|||
Seat,
|
||||
keyboard::{KeyboardTarget, KeysymHandle, ModifiersState},
|
||||
pointer::{
|
||||
AxisFrame, ButtonEvent, GestureHoldBeginEvent, GestureHoldEndEvent,
|
||||
GesturePinchBeginEvent, GesturePinchEndEvent, GesturePinchUpdateEvent,
|
||||
GestureSwipeBeginEvent, GestureSwipeEndEvent, GestureSwipeUpdateEvent, MotionEvent,
|
||||
AxisFrame as PointerAxisFrame, ButtonEvent as PointerButtonEvent,
|
||||
GestureHoldBeginEvent, GestureHoldEndEvent, GesturePinchBeginEvent,
|
||||
GesturePinchEndEvent, GesturePinchUpdateEvent, GestureSwipeBeginEvent,
|
||||
GestureSwipeEndEvent, GestureSwipeUpdateEvent, MotionEvent as PointerMotionEvent,
|
||||
PointerTarget, RelativeMotionEvent,
|
||||
},
|
||||
tablet::{
|
||||
Tablet,
|
||||
tool::{
|
||||
AxisFrame as ToolAxisFrame, ButtonEvent as ToolButtonEvent,
|
||||
DownEvent as ToolDownEvent, MotionEvent as ToolMotionEvent, TabletToolTarget,
|
||||
UpEvent as ToolUpEvent,
|
||||
},
|
||||
},
|
||||
touch::{
|
||||
DownEvent, FrameMarker, MotionEvent as TouchMotionEvent, OrientationEvent, ShapeEvent,
|
||||
TouchTarget, UpEvent,
|
||||
DownEvent as TouchDownEvent, FrameMarker, MotionEvent as TouchMotionEvent,
|
||||
OrientationEvent, ShapeEvent, TouchTarget, UpEvent as TouchUpEvent,
|
||||
},
|
||||
},
|
||||
output::Output,
|
||||
|
|
@ -189,6 +198,7 @@ pub(crate) struct IcedElementInternal<P: Program + Send + 'static> {
|
|||
touch_map: HashMap<Finger, IcedPoint>,
|
||||
last_touch_frame: Option<FrameMarker>,
|
||||
last_touch_serial: Option<Serial>,
|
||||
last_tablet_serial: Option<Serial>,
|
||||
|
||||
// iced
|
||||
theme: Theme,
|
||||
|
|
@ -240,6 +250,7 @@ impl<P: Program + Send + Clone + 'static> Clone for IcedElementInternal<P> {
|
|||
touch_map: self.touch_map.clone(),
|
||||
last_touch_frame: None,
|
||||
last_touch_serial: None,
|
||||
last_tablet_serial: None,
|
||||
theme: self.theme.clone(),
|
||||
renderer,
|
||||
state,
|
||||
|
|
@ -267,6 +278,7 @@ impl<P: Program + Send + 'static> fmt::Debug for IcedElementInternal<P> {
|
|||
.field("touch_map", &self.touch_map)
|
||||
.field("last_touch_frame", &self.last_touch_frame)
|
||||
.field("last_touch_serial", &self.last_touch_serial)
|
||||
.field("last_tablet_serial", &self.last_tablet_serial)
|
||||
.field("theme", &"...")
|
||||
.field("renderer", &"...")
|
||||
.field("state", &"...")
|
||||
|
|
@ -326,6 +338,7 @@ impl<P: Program + Send + 'static> IcedElement<P> {
|
|||
touch_map: HashMap::new(),
|
||||
last_touch_frame: None,
|
||||
last_touch_serial: None,
|
||||
last_tablet_serial: None,
|
||||
theme,
|
||||
renderer,
|
||||
state,
|
||||
|
|
@ -481,12 +494,143 @@ impl<P: Program + Send + 'static> IcedElementInternal<P> {
|
|||
}
|
||||
}
|
||||
|
||||
impl<P: Program + Send + 'static> TabletToolTarget<crate::state::State> for IcedElement<P> {
|
||||
fn proximity_in(
|
||||
&self,
|
||||
seat: &Seat<crate::state::State>,
|
||||
_data: &mut crate::state::State,
|
||||
_tool_descriptor: &TabletToolDescriptor,
|
||||
_tablet: &Tablet,
|
||||
serial: Serial,
|
||||
) {
|
||||
let mut internal = self.0.lock().unwrap();
|
||||
internal
|
||||
.state
|
||||
.queue_event(Event::Mouse(MouseEvent::CursorEntered));
|
||||
internal.last_tablet_serial = Some(serial);
|
||||
*internal.last_seat.lock().unwrap() = Some((seat.clone(), serial));
|
||||
internal.update(false);
|
||||
}
|
||||
|
||||
fn proximity_out(
|
||||
&self,
|
||||
_seat: &Seat<crate::state::State>,
|
||||
_data: &mut crate::state::State,
|
||||
_tool_descriptor: &TabletToolDescriptor,
|
||||
) {
|
||||
let mut internal = self.0.lock().unwrap();
|
||||
internal
|
||||
.state
|
||||
.queue_event(Event::Mouse(MouseEvent::CursorLeft));
|
||||
internal.update(false);
|
||||
}
|
||||
|
||||
fn down(
|
||||
&self,
|
||||
seat: &Seat<crate::state::State>,
|
||||
_data: &mut crate::state::State,
|
||||
_tool_descriptor: &TabletToolDescriptor,
|
||||
event: &ToolDownEvent,
|
||||
) {
|
||||
let mut internal = self.0.lock().unwrap();
|
||||
let id = Finger(0);
|
||||
let Some(event_location) = internal.cursor_pos else {
|
||||
return;
|
||||
};
|
||||
let position = IcedPoint::new(event_location.x as f32, event_location.y as f32);
|
||||
internal
|
||||
.state
|
||||
.queue_event(Event::Touch(TouchEvent::FingerPressed { id, position }));
|
||||
internal.last_tablet_serial = Some(event.serial);
|
||||
*internal.last_seat.lock().unwrap() = Some((seat.clone(), event.serial));
|
||||
internal.update(false);
|
||||
}
|
||||
|
||||
fn up(
|
||||
&self,
|
||||
seat: &Seat<crate::state::State>,
|
||||
_data: &mut crate::state::State,
|
||||
_tool_descriptor: &TabletToolDescriptor,
|
||||
_event: &ToolUpEvent,
|
||||
) {
|
||||
let mut internal = self.0.lock().unwrap();
|
||||
let id = Finger(0);
|
||||
if let Some(event_location) = internal.cursor_pos {
|
||||
*internal.last_seat.lock().unwrap() =
|
||||
Some((seat.clone(), internal.last_tablet_serial.unwrap()));
|
||||
let position = IcedPoint::new(event_location.x as f32, event_location.y as f32);
|
||||
internal
|
||||
.state
|
||||
.queue_event(Event::Touch(TouchEvent::FingerLifted { id, position }));
|
||||
internal.update(false);
|
||||
}
|
||||
}
|
||||
|
||||
fn motion(
|
||||
&self,
|
||||
seat: &Seat<crate::state::State>,
|
||||
_data: &mut crate::state::State,
|
||||
_tool_descriptor: &TabletToolDescriptor,
|
||||
event: &ToolMotionEvent,
|
||||
) {
|
||||
let mut internal = self.0.lock().unwrap();
|
||||
let event_location = event.location.downscale(internal.additional_scale);
|
||||
let position = IcedPoint::new(event_location.x as f32, event_location.y as f32);
|
||||
internal
|
||||
.state
|
||||
.queue_event(Event::Mouse(MouseEvent::CursorMoved { position }));
|
||||
internal.cursor_pos = Some(event_location);
|
||||
*internal.last_seat.lock().unwrap() =
|
||||
Some((seat.clone(), internal.last_tablet_serial.unwrap()));
|
||||
internal.update(false);
|
||||
}
|
||||
|
||||
fn axis(
|
||||
&self,
|
||||
_seat: &Seat<crate::state::State>,
|
||||
_data: &mut crate::state::State,
|
||||
_tool_descriptor: &TabletToolDescriptor,
|
||||
_frame: ToolAxisFrame,
|
||||
) {
|
||||
}
|
||||
|
||||
fn button(
|
||||
&self,
|
||||
seat: &Seat<crate::state::State>,
|
||||
_data: &mut crate::state::State,
|
||||
_tool_descriptor: &TabletToolDescriptor,
|
||||
event: &ToolButtonEvent,
|
||||
) {
|
||||
let mut internal = self.0.lock().unwrap();
|
||||
let button = match event.button {
|
||||
0x14b => MouseButton::Right,
|
||||
0x14c => MouseButton::Middle,
|
||||
_ => return,
|
||||
};
|
||||
internal.state.queue_event(Event::Mouse(match event.state {
|
||||
ButtonState::Pressed => MouseEvent::ButtonPressed(button),
|
||||
ButtonState::Released => MouseEvent::ButtonReleased(button),
|
||||
}));
|
||||
*internal.last_seat.lock().unwrap() = Some((seat.clone(), event.serial));
|
||||
internal.update(false);
|
||||
}
|
||||
|
||||
fn frame(
|
||||
&self,
|
||||
_seat: &Seat<crate::state::State>,
|
||||
_data: &mut crate::state::State,
|
||||
_tool_descriptor: &TabletToolDescriptor,
|
||||
_time: InputTime,
|
||||
) {
|
||||
}
|
||||
}
|
||||
|
||||
impl<P: Program + Send + 'static> PointerTarget<crate::state::State> for IcedElement<P> {
|
||||
fn enter(
|
||||
&self,
|
||||
seat: &Seat<crate::state::State>,
|
||||
_data: &mut crate::state::State,
|
||||
event: &MotionEvent,
|
||||
event: &PointerMotionEvent,
|
||||
) {
|
||||
let mut internal = self.0.lock().unwrap();
|
||||
internal
|
||||
|
|
@ -507,7 +651,7 @@ impl<P: Program + Send + 'static> PointerTarget<crate::state::State> for IcedEle
|
|||
&self,
|
||||
seat: &Seat<crate::state::State>,
|
||||
_data: &mut crate::state::State,
|
||||
event: &MotionEvent,
|
||||
event: &PointerMotionEvent,
|
||||
) {
|
||||
let mut internal = self.0.lock().unwrap();
|
||||
let event_location = event.location.downscale(internal.additional_scale);
|
||||
|
|
@ -532,7 +676,7 @@ impl<P: Program + Send + 'static> PointerTarget<crate::state::State> for IcedEle
|
|||
&self,
|
||||
seat: &Seat<crate::state::State>,
|
||||
_data: &mut crate::state::State,
|
||||
event: &ButtonEvent,
|
||||
event: &PointerButtonEvent,
|
||||
) {
|
||||
let mut internal = self.0.lock().unwrap();
|
||||
let button = match event.button {
|
||||
|
|
@ -553,7 +697,7 @@ impl<P: Program + Send + 'static> PointerTarget<crate::state::State> for IcedEle
|
|||
&self,
|
||||
_seat: &Seat<crate::state::State>,
|
||||
_data: &mut crate::state::State,
|
||||
frame: AxisFrame,
|
||||
frame: PointerAxisFrame,
|
||||
) {
|
||||
let mut internal = self.0.lock().unwrap();
|
||||
internal
|
||||
|
|
@ -581,7 +725,7 @@ impl<P: Program + Send + 'static> PointerTarget<crate::state::State> for IcedEle
|
|||
_seat: &Seat<crate::state::State>,
|
||||
_data: &mut crate::state::State,
|
||||
_serial: Serial,
|
||||
_time: u32,
|
||||
_time: InputTime,
|
||||
) {
|
||||
let mut internal = self.0.lock().unwrap();
|
||||
internal
|
||||
|
|
@ -653,7 +797,7 @@ impl<P: Program + Send + 'static> TouchTarget<crate::state::State> for IcedEleme
|
|||
&self,
|
||||
seat: &Seat<crate::state::State>,
|
||||
_data: &mut crate::state::State,
|
||||
event: &DownEvent,
|
||||
event: &TouchDownEvent,
|
||||
) {
|
||||
let mut internal = self.0.lock().unwrap();
|
||||
let id = Finger(i32::from(event.slot) as u64);
|
||||
|
|
@ -673,7 +817,7 @@ impl<P: Program + Send + 'static> TouchTarget<crate::state::State> for IcedEleme
|
|||
&self,
|
||||
seat: &Seat<crate::state::State>,
|
||||
_data: &mut crate::state::State,
|
||||
event: &UpEvent,
|
||||
event: &TouchUpEvent,
|
||||
) {
|
||||
let mut internal = self.0.lock().unwrap();
|
||||
let id = Finger(i32::from(event.slot) as u64);
|
||||
|
|
@ -784,7 +928,7 @@ impl<P: Program + Send + 'static> KeyboardTarget<crate::state::State> for IcedEl
|
|||
_key: KeysymHandle<'_>,
|
||||
_state: KeyState,
|
||||
_serial: Serial,
|
||||
_time: u32,
|
||||
_time: InputTime,
|
||||
) {
|
||||
// TODO convert keys
|
||||
}
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@ pub mod env;
|
|||
mod ids;
|
||||
pub(crate) use self::ids::id_gen;
|
||||
pub mod geometry;
|
||||
pub mod global;
|
||||
pub mod iced;
|
||||
pub mod prelude;
|
||||
pub mod quirks;
|
||||
|
|
|
|||
|
|
@ -3,9 +3,12 @@
|
|||
use crate::{shell::grabs::SeatMoveGrabState, state::ClientState, utils::prelude::*};
|
||||
use calloop::Interest;
|
||||
use smithay::{
|
||||
backend::renderer::{
|
||||
element::{Kind, surface::KindEvaluation},
|
||||
utils::{on_commit_buffer_handler, with_renderer_surface_state},
|
||||
backend::{
|
||||
input::InputTime,
|
||||
renderer::{
|
||||
element::{Kind, surface::KindEvaluation},
|
||||
utils::{on_commit_buffer_handler, with_renderer_surface_state},
|
||||
},
|
||||
},
|
||||
desktop::{LayerSurface, PopupKind, WindowSurfaceType, layer_map_for_output},
|
||||
reexports::wayland_server::{Client, Resource, protocol::wl_surface::WlSurface},
|
||||
|
|
@ -325,9 +328,11 @@ impl CompositorHandler for State {
|
|||
}
|
||||
} else {
|
||||
std::mem::drop(shell);
|
||||
seat.get_pointer()
|
||||
.unwrap()
|
||||
.unset_grab(self, SERIAL_COUNTER.next_serial(), 0);
|
||||
seat.get_pointer().unwrap().unset_grab(
|
||||
self,
|
||||
SERIAL_COUNTER.next_serial(),
|
||||
InputTime::now(),
|
||||
);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -45,18 +45,11 @@ pub fn surface_corners(states: &SurfaceData, size: Size<i32, Logical>) -> Option
|
|||
])
|
||||
}
|
||||
|
||||
pub fn surface_padding(states: &SurfaceData, size: Size<i32, Logical>) -> Option<[i32; 4]> {
|
||||
pub fn surface_padding(states: &SurfaceData) -> Option<[i32; 4]> {
|
||||
let mut guard = states.cached_state.get::<CacheablePadding>();
|
||||
|
||||
let padding = guard.current().0?;
|
||||
|
||||
// guard against padding being too large
|
||||
Some([
|
||||
padding.top.min(size.h / 2),
|
||||
padding.right.min(size.w / 2),
|
||||
padding.bottom.min(size.h / 2),
|
||||
padding.left.min(size.w / 2),
|
||||
])
|
||||
Some([padding.top, padding.right, padding.bottom, padding.left])
|
||||
}
|
||||
|
||||
pub fn pad_rect(
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
use std::{cell::RefCell, sync::Mutex};
|
||||
|
||||
use cosmic_comp_config::DecorationPreference;
|
||||
use smithay::{
|
||||
desktop::Window,
|
||||
reexports::{
|
||||
|
|
@ -20,7 +21,7 @@ use smithay::{
|
|||
};
|
||||
use wayland_backend::protocol::WEnum;
|
||||
|
||||
use crate::state::State;
|
||||
use crate::{shell::CosmicMapped, state::State};
|
||||
|
||||
pub struct PreferredDecorationMode(RefCell<Option<XdgMode>>);
|
||||
|
||||
|
|
@ -29,7 +30,7 @@ impl PreferredDecorationMode {
|
|||
window
|
||||
.user_data()
|
||||
.get::<PreferredDecorationMode>()
|
||||
.is_none()
|
||||
.is_none_or(|mode| mode.0.borrow().is_none())
|
||||
}
|
||||
|
||||
pub fn mode(window: &Window) -> Option<XdgMode> {
|
||||
|
|
@ -53,6 +54,72 @@ impl PreferredDecorationMode {
|
|||
}
|
||||
}
|
||||
|
||||
impl State {
|
||||
pub fn default_decoration(&self) -> DecorationPreference {
|
||||
self.common.config.cosmic_conf.decoration_preference
|
||||
}
|
||||
|
||||
pub fn update_decorations(&self) {
|
||||
let shell = self.common.shell.read();
|
||||
|
||||
let update = |mapped: &CosmicMapped| {
|
||||
let mode = if mapped.is_stack() {
|
||||
XdgMode::ServerSide
|
||||
} else {
|
||||
XdgMode::from_preference(self.default_decoration())
|
||||
};
|
||||
for (window, _) in mapped.windows() {
|
||||
if PreferredDecorationMode::is_unset(&window.0)
|
||||
&& let Some(toplevel) = window.0.toplevel()
|
||||
&& toplevel.with_committed_state(|state| {
|
||||
state.is_some_and(|state| state.decoration_mode.is_some())
|
||||
})
|
||||
{
|
||||
toplevel.with_pending_state(|state| {
|
||||
state.decoration_mode = Some(mode);
|
||||
});
|
||||
toplevel.send_configure();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
for set in shell.workspaces.sets.values() {
|
||||
set.sticky_layer.mapped().for_each(update);
|
||||
}
|
||||
|
||||
for space in shell.workspaces.spaces() {
|
||||
space.mapped().for_each(update);
|
||||
space
|
||||
.minimized_windows
|
||||
.iter()
|
||||
.filter_map(|m| m.mapped())
|
||||
.for_each(update);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
trait FromDecorationPreference {
|
||||
fn from_preference(preference: DecorationPreference) -> Self;
|
||||
}
|
||||
|
||||
impl FromDecorationPreference for XdgMode {
|
||||
fn from_preference(preference: DecorationPreference) -> Self {
|
||||
match preference {
|
||||
DecorationPreference::ClientSide => XdgMode::ClientSide,
|
||||
DecorationPreference::ServerSide => XdgMode::ServerSide,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl FromDecorationPreference for KdeMode {
|
||||
fn from_preference(preference: DecorationPreference) -> Self {
|
||||
match preference {
|
||||
DecorationPreference::ClientSide => KdeMode::Client,
|
||||
DecorationPreference::ServerSide => KdeMode::Server,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub type KdeDecorationData = Mutex<KdeDecorationSurfaceState>;
|
||||
#[derive(Debug, Default)]
|
||||
pub struct KdeDecorationSurfaceState {
|
||||
|
|
@ -67,7 +134,7 @@ impl XdgDecorationHandler for State {
|
|||
let mode = if mapped.is_stack() {
|
||||
XdgMode::ServerSide
|
||||
} else {
|
||||
XdgMode::ClientSide
|
||||
XdgMode::from_preference(self.default_decoration())
|
||||
};
|
||||
|
||||
if let Some((window, _)) = mapped
|
||||
|
|
@ -80,6 +147,10 @@ impl XdgDecorationHandler for State {
|
|||
});
|
||||
toplevel.send_configure();
|
||||
}
|
||||
} else {
|
||||
toplevel.with_pending_state(|state| {
|
||||
state.decoration_mode = Some(XdgMode::from_preference(self.default_decoration()))
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -99,6 +170,13 @@ impl XdgDecorationHandler for State {
|
|||
}
|
||||
} else {
|
||||
toplevel.with_pending_state(|state| state.decoration_mode = Some(mode));
|
||||
if let Some(pending) = shell
|
||||
.pending_windows
|
||||
.iter()
|
||||
.find(|pending| pending.surface.0.toplevel().is_some_and(|t| t == &toplevel))
|
||||
{
|
||||
PreferredDecorationMode::update(&pending.surface.0, Some(mode));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -110,11 +188,28 @@ impl XdgDecorationHandler for State {
|
|||
.find(|(window, _)| window.wl_surface().as_deref() == Some(toplevel.wl_surface()))
|
||||
&& let Some(toplevel) = window.0.toplevel()
|
||||
{
|
||||
let mode = if mapped.is_stack() {
|
||||
XdgMode::ServerSide
|
||||
} else {
|
||||
XdgMode::from_preference(self.default_decoration())
|
||||
};
|
||||
|
||||
PreferredDecorationMode::update(&window.0, None);
|
||||
toplevel.with_pending_state(|state| {
|
||||
state.decoration_mode = None;
|
||||
state.decoration_mode = Some(mode);
|
||||
});
|
||||
toplevel.send_configure();
|
||||
} else {
|
||||
toplevel.with_pending_state(|state| {
|
||||
state.decoration_mode = Some(XdgMode::from_preference(self.default_decoration()))
|
||||
});
|
||||
if let Some(pending) = shell
|
||||
.pending_windows
|
||||
.iter()
|
||||
.find(|pending| pending.surface.0.toplevel().is_some_and(|t| t == &toplevel))
|
||||
{
|
||||
PreferredDecorationMode::update(&pending.surface.0, None);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -129,10 +224,10 @@ impl KdeDecorationHandler for State {
|
|||
if mapped.is_stack() {
|
||||
KdeMode::Server
|
||||
} else {
|
||||
KdeMode::Client
|
||||
KdeMode::from_preference(self.default_decoration())
|
||||
}
|
||||
} else {
|
||||
KdeMode::Client
|
||||
KdeMode::from_preference(self.default_decoration())
|
||||
};
|
||||
|
||||
with_states(surface, |states| {
|
||||
|
|
|
|||
|
|
@ -40,7 +40,7 @@ impl DmabufHandler for State {
|
|||
fn new_surface_feedback(
|
||||
&mut self,
|
||||
surface: &WlSurface,
|
||||
global: &DmabufGlobal,
|
||||
_global: &DmabufGlobal,
|
||||
) -> Option<DmabufFeedback> {
|
||||
let BackendData::Kms(kms) = &self.backend else {
|
||||
return None;
|
||||
|
|
@ -57,18 +57,7 @@ impl DmabufHandler for State {
|
|||
f.ended_at.is_none() && f.surface.has_surface(surface, WindowSurfaceType::all())
|
||||
});
|
||||
|
||||
let node = kms
|
||||
.drm_devices
|
||||
.values()
|
||||
.find(|device| {
|
||||
device
|
||||
.socket
|
||||
.as_ref()
|
||||
.map(|s| &s.dmabuf_global == global)
|
||||
.unwrap_or(false)
|
||||
})?
|
||||
.inner
|
||||
.render_node;
|
||||
let node = kms.primary_node.read().unwrap().unwrap();
|
||||
let kms_surface = kms
|
||||
.drm_devices
|
||||
.values()
|
||||
|
|
@ -77,9 +66,7 @@ impl DmabufHandler for State {
|
|||
|
||||
Some(with_states(surface, |data| {
|
||||
if is_fullscreen {
|
||||
feedback
|
||||
.primary_scanout_feedback
|
||||
.unwrap_or(feedback.render_feedback)
|
||||
feedback.primary_scanout_feedback
|
||||
} else if frame_time_filter_fn(data) == Kind::ScanoutCandidate {
|
||||
feedback
|
||||
.overlay_scanout_feedback
|
||||
|
|
|
|||
|
|
@ -49,7 +49,10 @@ impl FractionalScaleHandler for State {
|
|||
|
||||
with_states(&surface, |states| {
|
||||
with_fractional_scale(states, |fractional_scale| {
|
||||
fractional_scale.set_preferred_scale(output.current_scale().fractional_scale());
|
||||
// The 1.0 clamp is a workaround for Chromium
|
||||
// TODO: remove if Chromium ever gets fixed
|
||||
fractional_scale
|
||||
.set_preferred_scale(output.current_scale().fractional_scale().max(1.0));
|
||||
});
|
||||
});
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,5 +1,7 @@
|
|||
// SPDX-License-Identifier: GPL-3.0-only
|
||||
|
||||
use smithay::input::keyboard::KeyboardHandle;
|
||||
|
||||
use crate::state::State;
|
||||
use crate::wayland::protocols::keyboard_layout::{KeyboardLayoutHandler, KeyboardLayoutState};
|
||||
|
||||
|
|
@ -7,4 +9,8 @@ impl KeyboardLayoutHandler for State {
|
|||
fn keyboard_layout_state(&mut self) -> &mut KeyboardLayoutState {
|
||||
&mut self.common.keyboard_layout_state
|
||||
}
|
||||
|
||||
fn group_changed(&mut self, keyboard: &KeyboardHandle<Self>) {
|
||||
self.broadcast_ei_keyboard_modifiers(keyboard);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -230,6 +230,16 @@ impl State {
|
|||
state.common.output_configuration_state.update();
|
||||
});
|
||||
|
||||
// Output scale or geometry may have changed. EI absolute-pointer regions
|
||||
// are immutable per device, so any connected EI client (e.g. an RDP server)
|
||||
// keeps mapping with the old scale until it reconnects. Recreate the
|
||||
// device with the updated region so the mapping tracks the change live.
|
||||
// (drop the backend lock first: refresh borrows `self` immutably.)
|
||||
drop(backend);
|
||||
if !test_only {
|
||||
crate::libei::refresh_absolute_pointer_regions(self);
|
||||
}
|
||||
|
||||
true
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -5,7 +5,10 @@ use smithay::{
|
|||
input::pointer::PointerHandle,
|
||||
reexports::wayland_server::protocol::wl_surface::WlSurface,
|
||||
utils::{Logical, Point},
|
||||
wayland::{pointer_constraints::PointerConstraintsHandler, seat::WaylandFocus},
|
||||
wayland::{
|
||||
pointer_constraints::{ConstraintRemove, PointerConstraintsHandler},
|
||||
seat::WaylandFocus,
|
||||
},
|
||||
};
|
||||
|
||||
pub use smithay::wayland::pointer_constraints::{PointerConstraintRef, with_pointer_constraint};
|
||||
|
|
@ -76,23 +79,37 @@ impl PointerConstraintsHandler for State {
|
|||
}
|
||||
}
|
||||
|
||||
fn remove_constraint(&mut self, surface: &WlSurface, pointer: &PointerHandle<Self>) {
|
||||
if with_pointer_constraint(surface, pointer, |constraint| constraint.is_none()) {
|
||||
let seat = self
|
||||
.common
|
||||
.shell
|
||||
.read()
|
||||
.seats
|
||||
.iter()
|
||||
.find(|s| s.get_pointer().as_ref() == Some(pointer))
|
||||
.cloned();
|
||||
fn remove_constraint(
|
||||
&mut self,
|
||||
surface: &WlSurface,
|
||||
pointer: &PointerHandle<Self>,
|
||||
constraint_remove: ConstraintRemove,
|
||||
) {
|
||||
match constraint_remove {
|
||||
ConstraintRemove::PointerLeave(_) => {
|
||||
// If the constraint was broken by the pointer forcibly leaving the surface, then it doesn't
|
||||
// make much sense to warp it.
|
||||
}
|
||||
ConstraintRemove::Destroyed(constraint) => {
|
||||
let Some(seat) = self
|
||||
.common
|
||||
.shell
|
||||
.read()
|
||||
.seats
|
||||
.iter()
|
||||
.find(|s| s.get_pointer().as_ref() == Some(pointer))
|
||||
.cloned()
|
||||
else {
|
||||
return;
|
||||
};
|
||||
let Some((hint_surface, hint_location)) = seat.pointer_constraint_hint() else {
|
||||
return;
|
||||
};
|
||||
|
||||
if let Some(seat) = seat
|
||||
&& let Some((hint_surface, hint_location)) = seat.pointer_constraint_hint()
|
||||
&& hint_surface == *surface
|
||||
{
|
||||
self.apply_cursor_hint(surface, pointer, hint_location);
|
||||
seat.set_pointer_constraint_hint(None);
|
||||
if hint_surface == *surface {
|
||||
self.apply_cursor_hint(surface, pointer, hint_location, Some(&constraint));
|
||||
seat.set_pointer_constraint_hint(None);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -38,7 +38,7 @@ impl PointerWarpHandler for State {
|
|||
drop(shell);
|
||||
|
||||
if let Some(pointer_handle) = pointer_handle {
|
||||
self.apply_cursor_hint(&surface, &pointer_handle, pos);
|
||||
self.apply_cursor_hint(&surface, &pointer_handle, pos, None);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -46,6 +46,11 @@ impl SessionLockHandler for State {
|
|||
let mut shell = self.common.shell.write();
|
||||
shell.session_lock = None;
|
||||
|
||||
let seats = shell.seats.iter().cloned().collect::<Vec<_>>();
|
||||
for seat in &seats {
|
||||
self.common.idle_notifier_state.notify_activity(seat);
|
||||
}
|
||||
|
||||
for output in shell.outputs() {
|
||||
self.backend.schedule_render(output);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,12 +1,14 @@
|
|||
// SPDX-License-Identifier: GPL-3.0-only
|
||||
|
||||
use crate::state::State;
|
||||
use crate::{shell::focus::target::PointerFocusTarget, state::State};
|
||||
use smithay::{
|
||||
backend::input::TabletToolDescriptor, input::pointer::CursorImageStatus,
|
||||
wayland::tablet_manager::TabletSeatHandler,
|
||||
input::tablet::TabletSeatHandler,
|
||||
};
|
||||
|
||||
impl TabletSeatHandler for State {
|
||||
type ToolFocus = PointerFocusTarget;
|
||||
|
||||
fn tablet_tool_image(&mut self, _tool: &TabletToolDescriptor, _image: CursorImageStatus) {
|
||||
// TODO display cursor for each tablet tool
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
// SPDX-License-Identifier: GPL-3.0-only
|
||||
|
||||
use smithay::{
|
||||
backend::input::InputTime,
|
||||
desktop::{WindowSurfaceType, layer_map_for_output},
|
||||
input::{Seat, pointer::MotionEvent},
|
||||
output::Output,
|
||||
|
|
@ -124,7 +125,7 @@ impl ToplevelManagementHandler for State {
|
|||
&MotionEvent {
|
||||
location: new_pos.to_f64().as_logical(),
|
||||
serial,
|
||||
time: self.common.clock.now().as_millis(),
|
||||
time: InputTime::now(),
|
||||
},
|
||||
);
|
||||
ptr.frame(self);
|
||||
|
|
|
|||
|
|
@ -243,7 +243,7 @@ impl State {
|
|||
{
|
||||
for mapped in current_workspace
|
||||
.mapped()
|
||||
.filter(|m| m.maximized_state.lock().unwrap().is_some())
|
||||
.filter(|m| m.maximized_state.lock().unwrap().is_some() && *m != &element)
|
||||
.cloned()
|
||||
.collect::<Vec<_>>()
|
||||
.into_iter()
|
||||
|
|
|
|||
|
|
@ -1,11 +1,15 @@
|
|||
// SPDX-License-Identifier: GPL-3.0-only
|
||||
|
||||
use crate::{
|
||||
shell::{CosmicSurface, PendingWindow, focus::target::KeyboardFocusTarget, grabs::ReleaseMode},
|
||||
shell::{
|
||||
CosmicSurface, PendingWindow,
|
||||
focus::target::KeyboardFocusTarget,
|
||||
grabs::{GrabType, ReleaseMode},
|
||||
},
|
||||
utils::prelude::*,
|
||||
};
|
||||
use smithay::desktop::layer_map_for_output;
|
||||
use smithay::{
|
||||
backend::input::InputTime,
|
||||
desktop::{
|
||||
PopupGrab, PopupKeyboardGrab, PopupKind, PopupPointerGrab, PopupUngrabStrategy,
|
||||
WindowSurfaceType, find_popup_root_surface,
|
||||
|
|
@ -26,6 +30,7 @@ use smithay::{
|
|||
},
|
||||
},
|
||||
};
|
||||
use smithay::{desktop::layer_map_for_output, input::tablet::TabletSeatTrait};
|
||||
use std::cell::Cell;
|
||||
use tracing::warn;
|
||||
|
||||
|
|
@ -50,6 +55,7 @@ impl XdgShellHandler for State {
|
|||
surface,
|
||||
seat,
|
||||
fullscreen: None,
|
||||
minimized: false,
|
||||
maximized: false,
|
||||
sticky: false,
|
||||
})
|
||||
|
|
@ -189,12 +195,17 @@ impl XdgShellHandler for State {
|
|||
true,
|
||||
) {
|
||||
std::mem::drop(shell);
|
||||
if grab.is_touch_grab() {
|
||||
seat.get_touch().unwrap().set_grab(self, grab, serial);
|
||||
} else {
|
||||
seat.get_pointer()
|
||||
match grab.grab_type() {
|
||||
GrabType::Touch => seat.get_touch().unwrap().set_grab(self, grab, serial),
|
||||
GrabType::Pointer => seat
|
||||
.get_pointer()
|
||||
.unwrap()
|
||||
.set_grab(self, grab, serial, focus)
|
||||
.set_grab(self, grab, serial, focus),
|
||||
GrabType::TabletTool => seat
|
||||
.tablet_seat()
|
||||
.get_tool(grab.tool().unwrap())
|
||||
.unwrap()
|
||||
.set_grab(self, grab, InputTime::now(), serial, focus),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -217,12 +228,17 @@ impl XdgShellHandler for State {
|
|||
true,
|
||||
) {
|
||||
std::mem::drop(shell);
|
||||
if grab.is_touch_grab() {
|
||||
seat.get_touch().unwrap().set_grab(self, grab, serial)
|
||||
} else {
|
||||
seat.get_pointer()
|
||||
match grab.grab_type() {
|
||||
GrabType::Touch => seat.get_touch().unwrap().set_grab(self, grab, serial),
|
||||
GrabType::Pointer => seat
|
||||
.get_pointer()
|
||||
.unwrap()
|
||||
.set_grab(self, grab, serial, focus)
|
||||
.set_grab(self, grab, serial, focus),
|
||||
GrabType::TabletTool => seat
|
||||
.tablet_seat()
|
||||
.get_tool(grab.tool().unwrap())
|
||||
.unwrap()
|
||||
.set_grab(self, grab, InputTime::now(), serial, focus),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -333,17 +349,15 @@ impl XdgShellHandler for State {
|
|||
if let Some(ref grab) = grab {
|
||||
if grab.has_ended() {
|
||||
should_ungrab = true;
|
||||
} else if let Some(target) = grab.current_grab() {
|
||||
if let Some(wl_surface) = target.wl_surface() {
|
||||
if wl_surface.as_ref() == surface.wl_surface()
|
||||
|| smithay::desktop::PopupManager::popups_for_surface(
|
||||
surface.wl_surface(),
|
||||
)
|
||||
.any(|(p, _)| p.wl_surface() == wl_surface.as_ref())
|
||||
{
|
||||
should_ungrab = true;
|
||||
}
|
||||
}
|
||||
} else if let Some(target) = grab.current_grab()
|
||||
&& let Some(wl_surface) = target.wl_surface()
|
||||
&& (wl_surface.as_ref() == surface.wl_surface()
|
||||
|| smithay::desktop::PopupManager::popups_for_surface(
|
||||
surface.wl_surface(),
|
||||
)
|
||||
.any(|(p, _)| p.wl_surface() == wl_surface.as_ref()))
|
||||
{
|
||||
should_ungrab = true;
|
||||
}
|
||||
}
|
||||
if should_ungrab {
|
||||
|
|
|
|||
|
|
@ -5,17 +5,19 @@ use cosmic_protocols::corner_radius::v1::server::cosmic_corner_radius_toplevel_v
|
|||
use cosmic_protocols::corner_radius::v1::server::{
|
||||
cosmic_corner_radius_manager_v1, cosmic_corner_radius_toplevel_v1,
|
||||
};
|
||||
use smithay::desktop::utils::bbox_from_surface_tree;
|
||||
use smithay::backend::renderer::buffer_dimensions;
|
||||
use smithay::backend::renderer::utils::RendererSurfaceStateUserData;
|
||||
use smithay::reexports::wayland_protocols::xdg::shell::server::xdg_popup::XdgPopup;
|
||||
use smithay::reexports::wayland_protocols_wlr::layer_shell::v1::server::zwlr_layer_surface_v1::ZwlrLayerSurfaceV1;
|
||||
use smithay::reexports::wayland_server::New;
|
||||
use smithay::reexports::wayland_server::protocol::wl_surface::WlSurface;
|
||||
use smithay::utils::{HookId, Logical, Point, Rectangle};
|
||||
use smithay::wayland::compositor::Cacheable;
|
||||
use smithay::utils::{HookId, Logical, Rectangle};
|
||||
use smithay::wayland::compositor::add_pre_commit_hook;
|
||||
use smithay::wayland::compositor::with_states;
|
||||
use smithay::wayland::compositor::{BufferAssignment, Cacheable, SurfaceAttributes};
|
||||
use smithay::wayland::shell::wlr_layer::WlrLayerShellHandler;
|
||||
use smithay::wayland::shell::xdg::{SurfaceCachedState, XdgShellSurfaceUserData};
|
||||
use smithay::wayland::viewporter::ViewportCachedState;
|
||||
use smithay::{
|
||||
reexports::{
|
||||
wayland_protocols::xdg::shell::server::xdg_toplevel::XdgToplevel,
|
||||
|
|
@ -190,7 +192,7 @@ where
|
|||
let radius_exists = with_states(surface.wl_surface(), |surface_data| {
|
||||
let hook_id = surface_data
|
||||
.data_map
|
||||
.get_or_insert_threadsafe(|| ToplevelHookId::new(None));
|
||||
.get_or_insert_threadsafe(|| LayerHookId::new(None));
|
||||
let guard = hook_id.lock().unwrap();
|
||||
guard.as_ref().map(|(_, t)| t.upgrade().is_ok())
|
||||
});
|
||||
|
|
@ -655,7 +657,6 @@ fn pad_rect(
|
|||
}
|
||||
|
||||
fn layer_radius_hook<D: 'static>(_state: &mut D, _dh: &DisplayHandle, surface: &WlSurface) {
|
||||
let bbox = bbox_from_surface_tree(surface, Point::default());
|
||||
with_states(surface, |surface_data| {
|
||||
let corners = *surface_data
|
||||
.cached_state
|
||||
|
|
@ -665,8 +666,40 @@ fn layer_radius_hook<D: 'static>(_state: &mut D, _dh: &DisplayHandle, surface: &
|
|||
.cached_state
|
||||
.get::<CacheablePadding>()
|
||||
.pending();
|
||||
|
||||
// The wl_surface size after this commit is:
|
||||
// - viewport destination if set
|
||||
// - else the logical size of the buffer that will be current attached now
|
||||
// - else existing
|
||||
let size = surface_data
|
||||
.cached_state
|
||||
.get::<ViewportCachedState>()
|
||||
.pending()
|
||||
.size()
|
||||
.or_else(|| {
|
||||
let mut guard = surface_data.cached_state.get::<SurfaceAttributes>();
|
||||
let attrs = guard.pending();
|
||||
match &attrs.buffer {
|
||||
Some(BufferAssignment::NewBuffer(buffer)) => buffer_dimensions(buffer)
|
||||
.map(|d| d.to_logical(attrs.buffer_scale, attrs.buffer_transform.into())),
|
||||
_ => None,
|
||||
}
|
||||
})
|
||||
.or_else(|| {
|
||||
surface_data
|
||||
.data_map
|
||||
.get::<RendererSurfaceStateUserData>()
|
||||
.and_then(|state| state.lock().unwrap().surface_size())
|
||||
});
|
||||
let Some(size) = size else {
|
||||
return;
|
||||
};
|
||||
|
||||
let empty = Padding::default();
|
||||
let Some(padded_box) = pad_rect(bbox, padding.0.as_ref().unwrap_or(&empty)) else {
|
||||
let Some(padded_box) = pad_rect(
|
||||
Rectangle::from_size(size),
|
||||
padding.0.as_ref().unwrap_or(&empty),
|
||||
) else {
|
||||
if let Some(hook) = surface_data.data_map.get::<LayerHookId>() {
|
||||
let hook_ref = hook.lock().unwrap();
|
||||
if let Some((_, obj)) = hook_ref.as_ref()
|
||||
|
|
|
|||
|
|
@ -38,7 +38,11 @@ use smithay::{
|
|||
};
|
||||
use tracing::trace;
|
||||
|
||||
use std::{convert::TryFrom, path::PathBuf, sync::Arc};
|
||||
use std::{
|
||||
convert::TryFrom,
|
||||
path::PathBuf,
|
||||
sync::{Arc, Mutex},
|
||||
};
|
||||
|
||||
pub enum ImportError {
|
||||
Failed,
|
||||
|
|
@ -52,19 +56,28 @@ pub trait DrmHandler<R: 'static> {
|
|||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Default)]
|
||||
pub struct WlDrmState<R>(std::marker::PhantomData<R>);
|
||||
#[derive(Debug)]
|
||||
pub struct WlDrmState<R> {
|
||||
device: Arc<Mutex<DrmGlobalDeviceData>>,
|
||||
global: GlobalId,
|
||||
_marker: std::marker::PhantomData<R>,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct DrmGlobalDeviceData {
|
||||
formats: Vec<Fourcc>,
|
||||
path: PathBuf,
|
||||
}
|
||||
|
||||
/// Data associated with a drm global.
|
||||
pub struct DrmGlobalData {
|
||||
filter: Box<dyn for<'a> Fn(&'a Client) -> bool + Send + Sync>,
|
||||
formats: Arc<Vec<Fourcc>>,
|
||||
device_path: PathBuf,
|
||||
dmabuf_global: DmabufGlobal,
|
||||
device: Arc<Mutex<DrmGlobalDeviceData>>,
|
||||
}
|
||||
|
||||
pub struct DrmInstanceData {
|
||||
formats: Arc<Vec<Fourcc>>,
|
||||
device: Arc<Mutex<DrmGlobalDeviceData>>,
|
||||
dmabuf_global: DmabufGlobal,
|
||||
}
|
||||
|
||||
|
|
@ -86,16 +99,24 @@ where
|
|||
data_init: &mut DataInit<'_, D>,
|
||||
) {
|
||||
let data = DrmInstanceData {
|
||||
formats: global_data.formats.clone(),
|
||||
device: global_data.device.clone(),
|
||||
dmabuf_global: global_data.dmabuf_global,
|
||||
};
|
||||
let drm_instance = data_init.init(resource, data);
|
||||
|
||||
drm_instance.device(global_data.device_path.to_string_lossy().into_owned());
|
||||
drm_instance.device(
|
||||
global_data
|
||||
.device
|
||||
.lock()
|
||||
.unwrap()
|
||||
.path
|
||||
.to_string_lossy()
|
||||
.into_owned(),
|
||||
);
|
||||
if drm_instance.version() >= 2 {
|
||||
drm_instance.capabilities(wl_drm::Capability::Prime as u32);
|
||||
}
|
||||
for format in global_data.formats.iter() {
|
||||
for format in global_data.device.lock().unwrap().formats.iter() {
|
||||
if let Ok(converted) = wl_drm::Format::try_from(*format as u32) {
|
||||
drm_instance.format(converted as u32);
|
||||
}
|
||||
|
|
@ -148,7 +169,7 @@ where
|
|||
} => {
|
||||
let format = match Fourcc::try_from(format) {
|
||||
Ok(format) => {
|
||||
if !data.formats.contains(&format) {
|
||||
if !data.device.lock().unwrap().formats.contains(&format) {
|
||||
drm.post_error(
|
||||
wl_drm::Error::InvalidFormat,
|
||||
String::from("Format not advertised by wl_drm"),
|
||||
|
|
@ -220,13 +241,12 @@ where
|
|||
}
|
||||
|
||||
impl<R: 'static> WlDrmState<R> {
|
||||
pub fn create_global<D>(
|
||||
&mut self,
|
||||
pub fn new<D>(
|
||||
display: &DisplayHandle,
|
||||
device_path: PathBuf,
|
||||
formats: FormatSet,
|
||||
dmabuf_global: &DmabufGlobal,
|
||||
) -> GlobalId
|
||||
) -> Self
|
||||
where
|
||||
D: GlobalDispatch<wl_drm::WlDrm, DrmGlobalData>
|
||||
+ Dispatch<wl_drm::WlDrm, DrmInstanceData>
|
||||
|
|
@ -234,19 +254,16 @@ impl<R: 'static> WlDrmState<R> {
|
|||
+ DmabufHandler
|
||||
+ 'static,
|
||||
{
|
||||
self.create_global_with_filter::<D, _>(display, device_path, formats, dmabuf_global, |_| {
|
||||
true
|
||||
})
|
||||
Self::new_with_filter::<D, _>(display, device_path, formats, dmabuf_global, |_| true)
|
||||
}
|
||||
|
||||
pub fn create_global_with_filter<D, F>(
|
||||
&mut self,
|
||||
pub fn new_with_filter<D, F>(
|
||||
display: &DisplayHandle,
|
||||
device_path: PathBuf,
|
||||
formats: FormatSet,
|
||||
dmabuf_global: &DmabufGlobal,
|
||||
client_filter: F,
|
||||
) -> GlobalId
|
||||
) -> Self
|
||||
where
|
||||
D: GlobalDispatch<wl_drm::WlDrm, DrmGlobalData>
|
||||
+ Dispatch<wl_drm::WlDrm, DrmInstanceData>
|
||||
|
|
@ -255,21 +272,45 @@ impl<R: 'static> WlDrmState<R> {
|
|||
+ 'static,
|
||||
F: for<'a> Fn(&'a Client) -> bool + Send + Sync + 'static,
|
||||
{
|
||||
let formats = Arc::new(
|
||||
formats
|
||||
let device = Arc::new(Mutex::new(DrmGlobalDeviceData {
|
||||
formats: formats
|
||||
.into_iter()
|
||||
.filter(|f| f.modifier == Modifier::Invalid)
|
||||
.map(|f| f.code)
|
||||
.collect(),
|
||||
);
|
||||
path: device_path,
|
||||
}));
|
||||
|
||||
let data = DrmGlobalData {
|
||||
filter: Box::new(client_filter),
|
||||
formats,
|
||||
device_path,
|
||||
device: device.clone(),
|
||||
dmabuf_global: *dmabuf_global,
|
||||
};
|
||||
|
||||
display.create_global::<D, wl_drm::WlDrm, _>(2, data)
|
||||
let global = display.create_global::<D, wl_drm::WlDrm, _>(2, data);
|
||||
|
||||
WlDrmState {
|
||||
device,
|
||||
global,
|
||||
_marker: Default::default(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn update_device(&mut self, device_path: PathBuf, formats: FormatSet) {
|
||||
let new_device = DrmGlobalDeviceData {
|
||||
formats: formats
|
||||
.into_iter()
|
||||
.filter(|f| f.modifier == Modifier::Invalid)
|
||||
.map(|f| f.code)
|
||||
.collect(),
|
||||
path: device_path,
|
||||
};
|
||||
|
||||
*self.device.lock().unwrap() = new_device;
|
||||
}
|
||||
|
||||
pub fn global(&self) -> &GlobalId {
|
||||
&self.global
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -17,8 +17,10 @@ use smithay::{
|
|||
use std::mem;
|
||||
use wayland_backend::server::{ClientId, GlobalId};
|
||||
|
||||
pub trait KeyboardLayoutHandler {
|
||||
pub trait KeyboardLayoutHandler: SeatHandler {
|
||||
fn keyboard_layout_state(&mut self) -> &mut KeyboardLayoutState;
|
||||
/// Group has been changed through the protocol
|
||||
fn group_changed(&mut self, keyboard: &KeyboardHandle<Self>);
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
|
|
@ -57,6 +59,7 @@ impl KeyboardLayoutState {
|
|||
let active_layout = handle.with_xkb_state(state, |context| {
|
||||
context.xkb().lock().unwrap().active_layout()
|
||||
});
|
||||
state.group_changed(handle);
|
||||
if *last_layout != Some(active_layout) {
|
||||
keyboard_layout.group(active_layout.0);
|
||||
*last_layout = Some(active_layout);
|
||||
|
|
|
|||
|
|
@ -8,6 +8,7 @@ pub mod keyboard_layout;
|
|||
pub mod output_configuration;
|
||||
pub mod output_power;
|
||||
pub mod overlap_notify;
|
||||
pub mod session_lock_layer;
|
||||
pub mod toplevel_info;
|
||||
pub mod toplevel_management;
|
||||
pub mod workspace;
|
||||
|
|
|
|||
|
|
@ -1,9 +1,6 @@
|
|||
// SPDX-License-Identifier: GPL-3.0-only
|
||||
|
||||
use calloop::{
|
||||
LoopHandle,
|
||||
timer::{TimeoutAction, Timer},
|
||||
};
|
||||
use calloop::LoopHandle;
|
||||
use cosmic_comp_config::output::comp::AdaptiveSync;
|
||||
use cosmic_protocols::output_management::v1::server::{
|
||||
zcosmic_output_configuration_head_v1::ZcosmicOutputConfigurationHeadV1,
|
||||
|
|
@ -30,7 +27,7 @@ use smithay::{
|
|||
utils::{Logical, Physical, Point, Size, Transform},
|
||||
wayland::output::WlOutputData,
|
||||
};
|
||||
use std::{convert::TryFrom, sync::Mutex, time::Duration};
|
||||
use std::{convert::TryFrom, sync::Mutex};
|
||||
|
||||
mod handlers;
|
||||
|
||||
|
|
@ -519,26 +516,6 @@ where
|
|||
}
|
||||
}
|
||||
|
||||
fn remove_global_with_timer<D: 'static>(
|
||||
dh: &DisplayHandle,
|
||||
event_loop_handle: &LoopHandle<D>,
|
||||
id: GlobalId,
|
||||
) {
|
||||
dh.disable_global::<D>(id.clone());
|
||||
let source = Timer::from_duration(Duration::from_secs(5));
|
||||
let dh = dh.clone();
|
||||
let res = event_loop_handle.insert_source(source, move |_, _, _state| {
|
||||
dh.remove_global::<D>(id.clone());
|
||||
TimeoutAction::Drop
|
||||
});
|
||||
if let Err(err) = res {
|
||||
tracing::error!(
|
||||
"failed to insert timer source to destroy output global: {}",
|
||||
err
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
macro_rules! delegate_output_configuration {
|
||||
($(@<$( $lt:tt $( : $clt:tt $(+ $dlt:tt )* )? ),+>)? $ty: ty) => {
|
||||
smithay::reexports::wayland_server::delegate_global_dispatch!($(@< $( $lt $( : $clt $(+ $dlt )* )? ),+ >)? $ty: [
|
||||
|
|
@ -578,4 +555,4 @@ macro_rules! delegate_output_configuration {
|
|||
}
|
||||
pub(crate) use delegate_output_configuration;
|
||||
|
||||
use crate::utils::prelude::OutputExt;
|
||||
use crate::utils::{global::remove_global_with_timer, prelude::OutputExt};
|
||||
|
|
|
|||
136
src/wayland/protocols/session_lock_layer.rs
Normal file
136
src/wayland/protocols/session_lock_layer.rs
Normal file
|
|
@ -0,0 +1,136 @@
|
|||
use cosmic_protocols::session_lock_layer::v1::server::cosmic_session_lock_layer_manager_v1;
|
||||
use smithay::{
|
||||
reexports::wayland_server::{
|
||||
Client, DataInit, Dispatch, DisplayHandle, GlobalDispatch, New, backend::GlobalId,
|
||||
protocol::wl_surface,
|
||||
},
|
||||
wayland::{
|
||||
Dispatch2, GlobalDispatch2,
|
||||
compositor::{Cacheable, with_states},
|
||||
shell::wlr_layer::WlrLayerShellHandler,
|
||||
},
|
||||
};
|
||||
|
||||
struct SessionLockLayerData;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct SessionLockLayerState {
|
||||
global: GlobalId,
|
||||
}
|
||||
|
||||
impl SessionLockLayerState {
|
||||
pub fn new<D, F>(dh: &DisplayHandle, client_filter: F) -> Self
|
||||
where
|
||||
F: for<'a> Fn(&'a Client) -> bool + Clone + Send + Sync + 'static,
|
||||
D: GlobalDispatch<
|
||||
cosmic_session_lock_layer_manager_v1::CosmicSessionLockLayerManagerV1,
|
||||
SessionLockLayerGlobalData,
|
||||
> + 'static,
|
||||
{
|
||||
let global = dh
|
||||
.create_global::<D, cosmic_session_lock_layer_manager_v1::CosmicSessionLockLayerManagerV1, _>(
|
||||
1,
|
||||
SessionLockLayerGlobalData {
|
||||
filter: Box::new(client_filter.clone()),
|
||||
},
|
||||
);
|
||||
Self { global }
|
||||
}
|
||||
|
||||
pub fn global_id(&self) -> GlobalId {
|
||||
self.global.clone()
|
||||
}
|
||||
}
|
||||
|
||||
#[doc(hidden)]
|
||||
pub struct SessionLockLayerGlobalData {
|
||||
filter: Box<dyn for<'a> Fn(&'a Client) -> bool + Send + Sync>,
|
||||
}
|
||||
|
||||
impl<D> GlobalDispatch2<cosmic_session_lock_layer_manager_v1::CosmicSessionLockLayerManagerV1, D>
|
||||
for SessionLockLayerGlobalData
|
||||
where
|
||||
D: Dispatch<
|
||||
cosmic_session_lock_layer_manager_v1::CosmicSessionLockLayerManagerV1,
|
||||
SessionLockLayerData,
|
||||
>,
|
||||
{
|
||||
fn bind(
|
||||
&self,
|
||||
_state: &mut D,
|
||||
_handle: &DisplayHandle,
|
||||
_client: &Client,
|
||||
resource: New<cosmic_session_lock_layer_manager_v1::CosmicSessionLockLayerManagerV1>,
|
||||
data_init: &mut DataInit<'_, D>,
|
||||
) {
|
||||
data_init.init(resource, SessionLockLayerData);
|
||||
}
|
||||
|
||||
fn can_view(&self, client: &Client) -> bool {
|
||||
(self.filter)(client)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Default)]
|
||||
struct SessionLockLayerSurfaceData {
|
||||
show_on_lock: bool,
|
||||
}
|
||||
|
||||
impl Cacheable for SessionLockLayerSurfaceData {
|
||||
fn commit(&mut self, _dh: &DisplayHandle) -> Self {
|
||||
*self
|
||||
}
|
||||
|
||||
fn merge_into(self, into: &mut Self, _dh: &DisplayHandle) {
|
||||
*into = self;
|
||||
}
|
||||
}
|
||||
|
||||
pub fn layer_show_on_lock(wl_surface: &wl_surface::WlSurface) -> bool {
|
||||
with_states(wl_surface, |states| {
|
||||
if states.cached_state.has::<SessionLockLayerSurfaceData>() {
|
||||
let mut state = states.cached_state.get::<SessionLockLayerSurfaceData>();
|
||||
state.current().show_on_lock
|
||||
} else {
|
||||
false
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
impl<D> Dispatch2<cosmic_session_lock_layer_manager_v1::CosmicSessionLockLayerManagerV1, D>
|
||||
for SessionLockLayerData
|
||||
where
|
||||
D: WlrLayerShellHandler,
|
||||
{
|
||||
fn request(
|
||||
&self,
|
||||
state: &mut D,
|
||||
_: &Client,
|
||||
_: &cosmic_session_lock_layer_manager_v1::CosmicSessionLockLayerManagerV1,
|
||||
request: cosmic_session_lock_layer_manager_v1::Request,
|
||||
_: &DisplayHandle,
|
||||
_: &mut DataInit<'_, D>,
|
||||
) {
|
||||
let (layer, value) = match request {
|
||||
cosmic_session_lock_layer_manager_v1::Request::SetShowOnLock { layer } => (layer, true),
|
||||
cosmic_session_lock_layer_manager_v1::Request::UnsetShowOnLock { layer } => {
|
||||
(layer, false)
|
||||
}
|
||||
cosmic_session_lock_layer_manager_v1::Request::Destroy => {
|
||||
return;
|
||||
}
|
||||
_ => unreachable!(),
|
||||
};
|
||||
|
||||
if let Some(layer) = state
|
||||
.shell_state()
|
||||
.layer_surfaces()
|
||||
.find(|surface| surface.shell_surface() == &layer)
|
||||
{
|
||||
with_states(layer.wl_surface(), |states| {
|
||||
let mut state = states.cached_state.get::<SessionLockLayerSurfaceData>();
|
||||
state.pending().show_on_lock = value;
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -418,6 +418,12 @@ where
|
|||
handle.closed();
|
||||
}
|
||||
}
|
||||
// Remove the foreign handle too, so new subscribers cannot
|
||||
// discover a toplevel whose window has already been destroyed.
|
||||
if let Some(handle) = state.foreign_handle.take() {
|
||||
self.foreign_toplevel_list.remove_toplevel(&handle);
|
||||
}
|
||||
*state = Default::default();
|
||||
dirty = true;
|
||||
false
|
||||
}
|
||||
|
|
|
|||
|
|
@ -9,7 +9,9 @@ use std::{
|
|||
use crate::{
|
||||
backend::render::cursor::{Cursor, load_cursor_env, load_cursor_theme},
|
||||
shell::{
|
||||
CosmicSurface, PendingWindow, Shell, focus::target::KeyboardFocusTarget, grabs::ReleaseMode,
|
||||
CosmicSurface, PendingWindow, Shell,
|
||||
focus::target::KeyboardFocusTarget,
|
||||
grabs::{GrabType, ReleaseMode},
|
||||
},
|
||||
state::State,
|
||||
utils::prelude::*,
|
||||
|
|
@ -20,7 +22,7 @@ use smithay::{
|
|||
backend::{
|
||||
allocator::Fourcc,
|
||||
drm::DrmNode,
|
||||
input::{ButtonState, KeyState, Keycode},
|
||||
input::{ButtonState, InputTime, KeyState, Keycode},
|
||||
renderer::{
|
||||
Bind, Frame, Offscreen, Renderer,
|
||||
element::{
|
||||
|
|
@ -32,7 +34,7 @@ use smithay::{
|
|||
},
|
||||
},
|
||||
desktop::space::SpaceElement,
|
||||
input::{keyboard::ModifiersState, pointer::CursorIcon},
|
||||
input::{keyboard::ModifiersState, pointer::CursorIcon, tablet::TabletSeatTrait},
|
||||
reexports::{wayland_server::Client, x11rb::protocol::xproto::Window as X11Window},
|
||||
utils::{
|
||||
Buffer as BufferCoords, Logical, Point, Rectangle, SERIAL_COUNTER, Serial, Size, Transform,
|
||||
|
|
@ -115,7 +117,7 @@ impl State {
|
|||
&self.common.display_handle,
|
||||
None,
|
||||
std::iter::empty::<(OsString, OsString)>(),
|
||||
std::iter::empty::<OsString>(),
|
||||
["-enable-ei-portal"],
|
||||
true,
|
||||
Stdio::null(),
|
||||
Stdio::null(),
|
||||
|
|
@ -394,8 +396,9 @@ impl Common {
|
|||
sym: Keysym,
|
||||
code: Keycode,
|
||||
state: KeyState,
|
||||
modifiers: ModifiersState,
|
||||
serial: Serial,
|
||||
time: u32,
|
||||
time: InputTime,
|
||||
) {
|
||||
let config = self.config.cosmic_conf.xwayland_eavesdropping.keyboard;
|
||||
if config == EavesdroppingKeyboardMode::None {
|
||||
|
|
@ -418,7 +421,6 @@ impl Common {
|
|||
.last_active()
|
||||
.get_keyboard()
|
||||
.unwrap();
|
||||
let modifiers = keyboard.modifier_state();
|
||||
let is_modifier = sym.is_modifier_key();
|
||||
|
||||
let xstate = self.xwayland_state.as_mut().unwrap();
|
||||
|
|
@ -458,7 +460,7 @@ impl Common {
|
|||
|
||||
tracing::trace!("Forwaring key {} {:?} to xwayland", code.raw() - 8, state);
|
||||
for wl_keyboard in keyboard.client_keyboards(&xstate.client) {
|
||||
wl_keyboard.key(serial.into(), time, code.raw() - 8, state.into());
|
||||
wl_keyboard.key(serial.into(), time.millis(), code.raw() - 8, state.into());
|
||||
if xstate.last_modifier_state != Some(modifiers) {
|
||||
xstate.last_modifier_state = Some(modifiers);
|
||||
wl_keyboard.modifiers(
|
||||
|
|
@ -478,7 +480,7 @@ impl Common {
|
|||
button: u32,
|
||||
state: ButtonState,
|
||||
serial: Serial,
|
||||
time: u32,
|
||||
time: InputTime,
|
||||
) {
|
||||
if !self.config.cosmic_conf.xwayland_eavesdropping.pointer {
|
||||
return;
|
||||
|
|
@ -518,7 +520,7 @@ impl Common {
|
|||
|
||||
tracing::trace!("Forwaring ptr button {} {:?} to Xwayland", button, state);
|
||||
for wl_pointer in pointer.client_pointers(&xstate.client) {
|
||||
wl_pointer.button(serial.into(), time, button, state.into());
|
||||
wl_pointer.button(serial.into(), time.millis(), button, state.into());
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -808,6 +810,7 @@ impl XwmHandler for State {
|
|||
);
|
||||
}
|
||||
let fullscreen = window.is_fullscreen().then(|| seat.active_output());
|
||||
let minimized = window.is_hidden();
|
||||
let maximized = window.is_maximized();
|
||||
if let Some(pending) = shell
|
||||
.pending_windows
|
||||
|
|
@ -816,6 +819,7 @@ impl XwmHandler for State {
|
|||
{
|
||||
pending.seat = seat;
|
||||
pending.fullscreen = fullscreen;
|
||||
pending.minimized = minimized;
|
||||
pending.maximized = maximized;
|
||||
} else {
|
||||
let surface = CosmicSurface::from(window);
|
||||
|
|
@ -823,6 +827,7 @@ impl XwmHandler for State {
|
|||
surface,
|
||||
seat,
|
||||
fullscreen,
|
||||
minimized,
|
||||
maximized,
|
||||
sticky: false,
|
||||
})
|
||||
|
|
@ -858,7 +863,7 @@ impl XwmHandler for State {
|
|||
if let Some(target) = res {
|
||||
let seat = shell.seats.last_active().clone();
|
||||
std::mem::drop(shell);
|
||||
Shell::set_focus(self, Some(&target), &seat, None, false);
|
||||
Shell::set_focus_on_x11_map(self, &target, &seat, false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1042,17 +1047,29 @@ impl XwmHandler for State {
|
|||
true,
|
||||
) {
|
||||
std::mem::drop(shell);
|
||||
if grab.is_touch_grab() {
|
||||
seat.get_touch()
|
||||
.unwrap()
|
||||
.set_grab(self, grab, SERIAL_COUNTER.next_serial())
|
||||
} else {
|
||||
seat.get_pointer().unwrap().set_grab(
|
||||
match grab.grab_type() {
|
||||
GrabType::Touch => {
|
||||
seat.get_touch()
|
||||
.unwrap()
|
||||
.set_grab(self, grab, SERIAL_COUNTER.next_serial())
|
||||
}
|
||||
GrabType::Pointer => seat.get_pointer().unwrap().set_grab(
|
||||
self,
|
||||
grab,
|
||||
SERIAL_COUNTER.next_serial(),
|
||||
focus,
|
||||
)
|
||||
),
|
||||
GrabType::TabletTool => seat
|
||||
.tablet_seat()
|
||||
.get_tool(grab.tool().unwrap())
|
||||
.unwrap()
|
||||
.set_grab(
|
||||
self,
|
||||
grab,
|
||||
InputTime::now(),
|
||||
SERIAL_COUNTER.next_serial(),
|
||||
focus,
|
||||
),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1073,17 +1090,29 @@ impl XwmHandler for State {
|
|||
true,
|
||||
) {
|
||||
std::mem::drop(shell);
|
||||
if grab.is_touch_grab() {
|
||||
seat.get_touch()
|
||||
.unwrap()
|
||||
.set_grab(self, grab, SERIAL_COUNTER.next_serial())
|
||||
} else {
|
||||
seat.get_pointer().unwrap().set_grab(
|
||||
match grab.grab_type() {
|
||||
GrabType::Touch => {
|
||||
seat.get_touch()
|
||||
.unwrap()
|
||||
.set_grab(self, grab, SERIAL_COUNTER.next_serial())
|
||||
}
|
||||
GrabType::Pointer => seat.get_pointer().unwrap().set_grab(
|
||||
self,
|
||||
grab,
|
||||
SERIAL_COUNTER.next_serial(),
|
||||
focus,
|
||||
)
|
||||
),
|
||||
GrabType::TabletTool => seat
|
||||
.tablet_seat()
|
||||
.get_tool(grab.tool().unwrap())
|
||||
.unwrap()
|
||||
.set_grab(
|
||||
self,
|
||||
grab,
|
||||
InputTime::now(),
|
||||
SERIAL_COUNTER.next_serial(),
|
||||
focus,
|
||||
),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue