Compare commits

..

No commits in common. "f60c573f4d9c7b52a4118fa2b5ad5601c6cf8fa2" and "b45d12edef7d4b37c12f1c0cdee2845482d448d1" have entirely different histories.

39 changed files with 656 additions and 1944 deletions

425
Cargo.lock generated

File diff suppressed because it is too large Load diff

View file

@ -102,7 +102,6 @@ features = [
"backend_drm",
"backend_gbm",
"backend_egl",
"backend_libei",
"backend_libinput",
"backend_session_libseat",
"backend_udev",
@ -178,4 +177,4 @@ dnd = { path = "../window_clipboard/dnd" }
mime = { path = "../window_clipboard/mime" }
[patch.crates-io]
smithay = { git = "https://github.com/smithay/smithay.git", rev = "5fb12b8" }
smithay = { git = "https://github.com/smithay/smithay.git", rev = "1ed69cb" }

View file

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

View file

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

View file

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

View file

@ -1,4 +1,4 @@
grow-window = Crescere
grow-window = Ingrandisci
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 = Scatta uno screenshot
window-menu-screenshot = Cattura una schermata
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 = Impostazioni lente d'ingrandimento...
a11y-zoom-settings = Opzioni lente d'ingrandimento...
a11y-zoom-move-continuously = Segue sempre il cursorse
a11y-zoom-move-centered = Mantiene il cursore centrato
a11y-zoom-move-onedge = Segue il cursore ai bordi

View file

@ -59,7 +59,7 @@ use std::{
time::Duration,
};
use super::{drm_helpers, surface::Surface};
use super::{drm_helpers, socket::Socket, surface::Surface};
#[derive(Debug)]
pub struct EGLInternals {
@ -98,12 +98,14 @@ 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)]
@ -591,7 +593,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
@ -614,6 +616,13 @@ 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
@ -746,7 +755,23 @@ 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()
@ -765,6 +790,7 @@ impl Device {
ReusableDevice {
leasing_global,
active_clients: HashSet::new(),
socket,
}
});
@ -805,6 +831,7 @@ impl Device {
texture_formats,
event_token: Some(token),
socket,
})
}
@ -865,6 +892,7 @@ impl Device {
let device = ReusableDevice {
leasing_global: self.inner.leasing_global,
active_clients: self.inner.active_clients,
socket: self.socket,
};
let state = OldDeviceState {

View file

@ -75,7 +75,6 @@ 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.
@ -96,15 +95,10 @@ 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;
}
}
}
// 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)?;
}
device.atomic_commit(AtomicCommitFlags::ALLOW_MODESET, req)?;
} else {
for crtc in res_handles.crtcs() {
#[allow(deprecated)]

View file

@ -4,15 +4,12 @@ use crate::{
config::{CompOutputConfig, ScreenFilter},
shell::Shell,
state::BackendData,
utils::{env::dev_var, global::remove_global_with_timer, prelude::*},
wayland::protocols::{drm::WlDrmState, output_power::OutputPowerState},
utils::{env::dev_var, prelude::*},
wayland::protocols::output_power::OutputPowerState,
};
use anyhow::{self, Context, Result};
use calloop::{
LoopSignal,
timer::{TimeoutAction, Timer},
};
use anyhow::{Context, Result};
use calloop::LoopSignal;
use cosmic_comp_config::output::comp::{AdaptiveSync, OutputState};
use indexmap::IndexMap;
use render::gles::GbmGlowBackend;
@ -35,12 +32,11 @@ use smithay::{
control::{Device as _, connector::Interface, 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::{DmabufFeedbackBuilder, DmabufGlobal},
dmabuf::DmabufGlobal,
drm_syncobj::{DrmSyncobjState, supports_syncobj_eventfd},
relative_pointer::RelativePointerManagerState,
},
@ -52,12 +48,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;
@ -78,7 +74,6 @@ pub struct KmsState {
libinput: Libinput,
pub syncobj_state: Option<DrmSyncobjState>,
pub dmabuf_global: Option<DmabufGlobal>,
}
pub struct KmsGuard<'a> {
@ -136,7 +131,6 @@ pub fn init_backend(
libinput: libinput_context,
syncobj_state: None,
dmabuf_global: None,
});
// manually add already present gpus
@ -148,10 +142,9 @@ pub fn init_backend(
}
}
if let Err(err) = state.select_primary_gpu(dh) {
if let Err(err) = state.backend.kms().select_primary_gpu(dh) {
warn!("Failed to determine primary gpu: {}", err);
}
state.update_default_feedback();
if let Err(err) = state.refresh_output_config() {
info!(
@ -211,7 +204,7 @@ fn init_libinput(
state.backend.kms().input_devices.remove(&*device.name());
}
state.process_input_event(event, crate::input::InputBackendId::Normal);
state.process_input_event(event);
for output in state.common.shell.read().outputs() {
state.backend.kms().schedule_render(output);
@ -324,13 +317,11 @@ fn init_udev(
{
let backend = state.backend.kms();
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();
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);
}
}
@ -474,118 +465,50 @@ 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 kms = self.backend.kms();
let mut primary_node = kms.primary_node.write().unwrap();
let mut primary_node = self.primary_node.write().unwrap();
let _ = primary_node.take(); // if we error don't leave an old node in place
*primary_node = determine_primary_gpu(&kms.drm_devices, kms.session.seat())?;
*primary_node = determine_primary_gpu(&self.drm_devices, self.session.seat())?;
if let Some(node) = *primary_node {
info!("Using {} as primary gpu for rendering.", node);
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() {
self.software_renderer.take();
} else if self.software_renderer.is_none() {
info!("Failed to find a suitable gpu, using software renderingr");
kms.software_renderer = match software_renderer() {
self.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) = kms.drm_devices.get(&primary_node)
&& let Some(device) = self.drm_devices.get(&primary_node)
{
let import_device = device.drm.device().device_fd().clone();
if supports_syncobj_eventfd(&import_device) {
if let Some(state) = kms.syncobj_state.as_mut() {
if let Some(state) = self.syncobj_state.as_mut() {
state.update_device(import_device);
} else {
let syncobj_state = DrmSyncobjState::new::<State>(dh, import_device);
kms.syncobj_state = Some(syncobj_state);
self.syncobj_state = Some(syncobj_state);
}
return Ok(());
}
}
if let Some(old_state) = kms.syncobj_state.take() {
if let Some(old_state) = self.syncobj_state.take() {
dh.remove_global::<State>(old_state.into_global());
}
}
@ -593,45 +516,6 @@ impl State {
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)
}
@ -639,25 +523,29 @@ 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(|dev| dev.inner.render_node == device_node)
.ok_or(anyhow::anyhow!(
"Unable to find device for node: {}",
device_node
))?;
.find(|device| {
device
.socket
.as_ref()
.map(|s| &s.dmabuf_global == global)
.unwrap_or(false)
})
.context("Couldn't find gpu for dmabuf global")?;
// 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.
if dmabuf.node().is_none() && !device.texture_formats.contains(&dmabuf.format()) {
//
// 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()) {
device = self
.drm_devices
.values_mut()

View file

@ -1006,16 +1006,12 @@ 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())
.map_err(|err| anyhow::format_err!("Failed to create renderer: {:?}", err))?
.unwrap()
} else {
self.api
.single_renderer(&self.target_node)
.map_err(|err| anyhow::format_err!("Failed to create renderer: {:?}", err))?
self.api.single_renderer(&self.target_node).unwrap()
};
self.timings.start_render(&self.clock);
@ -1271,10 +1267,7 @@ impl SurfaceThreadState {
})
.context("Failed to draw to offscreen render target")?;
renderer = self
.api
.single_renderer(&self.target_node)
.map_err(|err| anyhow::format_err!("Failed to create renderer: {:?}", err))?;
renderer = self.api.single_renderer(&self.target_node).unwrap();
elements = postprocess_elements(
&mut renderer,
@ -1529,7 +1522,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 {
@ -1547,40 +1540,60 @@ fn get_surface_dmabuf_feedback(
.cloned()
.collect::<FormatSet>()
});
let builder = DmabufFeedbackBuilder::new(render_node.dev_id(), render_formats);
let mut builder = DmabufFeedbackBuilder::new(render_node.dev_id(), render_formats.clone());
/*
// 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.
//
if target_node != render_node {
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(
target_node.dev_id(),
zwp_linux_dmabuf_feedback_v1::TrancheFlags::Sampling,
target_formats,
6..=6,
render_node.dev_id(),
None,
combined_formats,
);
};
let render_feedback = builder.clone().build().unwrap();
let primary_scanout_feedback = builder
.clone()
.add_preference_tranche(
// 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() {
builder = builder.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| {
Some(zwp_linux_dmabuf_feedback_v1::TrancheFlags::Scanout),
combined_formats,
);
};
// 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(|| {
builder
.clone()
.add_preference_tranche(
target_node.dev_id(),
zwp_linux_dmabuf_feedback_v1::TrancheFlags::Scanout,
formats,
4..=6,
render_node.dev_id(),
Some(zwp_linux_dmabuf_feedback_v1::TrancheFlags::Scanout),
primary_plane_formats,
)
.build()
.unwrap()
});
let overlay_scanout_feedback = overlay_plane_formats
.filter(|_| target_node == render_node)
.map(|formats| {
builder
.add_preference_tranche(
render_node.dev_id(),
Some(zwp_linux_dmabuf_feedback_v1::TrancheFlags::Scanout),
formats,
)
.build()
.unwrap()
});
SurfaceDmabufFeedback {
render_feedback,

View file

@ -6,7 +6,6 @@ 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};
@ -274,7 +273,7 @@ fn init_egl_client_side(
.create_global_with_default_feedback::<State>(dh, &feedback);
let render_node = render_node.unwrap().unwrap();
state.common.wl_drm_state = Some(WlDrmState::new::<State>(
let _drm_global_id = state.common.wl_drm_state.create_global::<State>(
dh,
render_node
.dev_path_with_type(NodeType::Render)
@ -285,7 +284,7 @@ fn init_egl_client_side(
))?,
dmabuf_formats,
&dmabuf_global,
));
);
info!("EGL hardware-acceleration enabled.");
}
@ -310,9 +309,7 @@ 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, &crate::input::InputBackendId::Normal)
{
if devices.has_device(&WinitVirtualDevice) {
seat.set_active_output(&self.backend.winit().output);
break;
}
@ -342,9 +339,7 @@ impl State {
render_ping.ping();
}
WinitEvent::Redraw => render_ping.ping(),
WinitEvent::Input(event) => {
self.process_input_event(event, crate::input::InputBackendId::Normal)
}
WinitEvent::Input(event) => self.process_input_event(event),
WinitEvent::CloseRequested => {
self.common.should_stop = true;
}

View file

@ -6,7 +6,6 @@ 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;
@ -498,7 +497,7 @@ where
.common
.dmabuf_state
.create_global_with_default_feedback::<State>(dh, &default_feedback);
state.common.wl_drm_state = Some(WlDrmState::new::<State>(
let _drm_global_id = state.common.wl_drm_state.create_global::<State>(
dh,
render_node
.dev_path_with_type(NodeType::Render)
@ -509,7 +508,7 @@ where
))?,
renderer.dmabuf_formats(),
&dmabuf_global,
));
);
info!("EGL hardware-acceleration enabled.");
@ -534,14 +533,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, &crate::input::InputBackendId::Normal) {
if devices.has_device(&device) {
seat.set_active_output(&output);
break;
}
}
};
self.process_input_event(event, crate::input::InputBackendId::Normal);
self.process_input_event(event);
// TODO actually figure out the output
for output in self.common.shell.read().outputs() {
self.backend.x11().schedule_render(output);

View file

@ -1,7 +1,6 @@
// SPDX-License-Identifier: GPL-3.0-only
use crate::{
input::InputBackendId,
shell::Shell,
state::{BackendData, State},
utils::prelude::OutputExt,
@ -821,27 +820,6 @@ 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();
state.broadcast_ei_keyboard_modifiers(&seat);
}
state.common.config.cosmic_conf.xkb_config = value;
}
"keyboard_config" => {

View file

@ -150,10 +150,9 @@ 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 && !has_key_grab {
if !client.watched && !self.has_key_grab(modifiers, keysym.modified_sym()) {
continue;
}

View file

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

View file

@ -9,13 +9,11 @@ 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;
@ -31,7 +29,6 @@ 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 {
@ -45,7 +42,6 @@ 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();
@ -69,10 +65,6 @@ 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())
@ -93,7 +85,6 @@ async fn init_session(state: &DBusState) -> zbus::Result<()> {
let a11y_keyboard_monitor_state =
A11yKeyboardMonitorState::new(conn, &name_owners, &state.0.executor).await?;
*state.0.a11y_keyboard_monitor.borrow_mut() = Some(a11y_keyboard_monitor_state);
ei::init(conn, &name_owners, state.0.ei_sender.clone()).await?;
Ok(())
}

View file

@ -2,7 +2,6 @@
use crate::{
config::{Action, PrivateAction},
input::InputBackendId,
shell::{
FocusResult, InvalidWorkspaceIndex, MoveResult, SeatExt, Trigger, WorkspaceDelta,
focus::{FocusTarget, target::KeyboardFocusTarget},
@ -40,7 +39,6 @@ impl State {
pub fn handle_action(
&mut self,
action: Action,
backend_id: &InputBackendId,
seat: &Seat<State>,
serial: Serial,
time: u32,
@ -71,7 +69,7 @@ impl State {
Action::Shortcut(action) => {
let propagate = propagate_by_default(&action);
self.handle_shortcut_action(
action, backend_id, seat, serial, time, pattern, direction, propagate,
action, seat, serial, time, pattern, direction, propagate,
)
}
Action::Private(PrivateAction::Escape) => {
@ -145,7 +143,6 @@ impl State {
pub fn handle_shortcut_action(
&mut self,
action: shortcuts::Action,
backend_id: &InputBackendId,
seat: &Seat<State>,
serial: Serial,
time: u32,
@ -235,7 +232,6 @@ impl State {
{
self.handle_shortcut_action(
Action::SwitchOutput(inferred),
backend_id,
seat,
serial,
time,
@ -275,7 +271,6 @@ impl State {
{
self.handle_shortcut_action(
Action::SwitchOutput(inferred),
backend_id,
seat,
serial,
time,
@ -397,7 +392,6 @@ impl State {
} else {
Action::SendToOutput(inferred)
},
backend_id,
seat,
serial,
time,
@ -423,7 +417,6 @@ impl State {
} else {
Action::SendToWorkspace(1)
},
backend_id,
seat,
serial,
time,
@ -490,7 +483,6 @@ impl State {
} else {
Action::SendToOutput(inferred)
},
backend_id,
seat,
serial,
time,
@ -516,7 +508,6 @@ impl State {
} else {
Action::SendToLastWorkspace
},
backend_id,
seat,
serial,
time,
@ -541,9 +532,7 @@ impl State {
if propagate
&& let Some((serial, prev_output, prev_idx)) =
shell.previous_workspace_idx.take()
&& seat
.last_modifier_change_for(backend_id)
.is_some_and(|s| s == serial)
&& seat.last_modifier_change().is_some_and(|s| s == serial)
&& prev_output == current_output
{
let _ = shell.activate(
@ -716,7 +705,6 @@ impl State {
if res.is_ok() {
self.handle_shortcut_action(
Action::SwitchOutput(direction),
backend_id,
seat,
serial,
time,
@ -753,8 +741,7 @@ impl State {
};
if let Some(direction) = dir {
if let Some(last_mod_serial) = seat.last_modifier_change_for(backend_id)
{
if let Some(last_mod_serial) = seat.last_modifier_change() {
let mut shell = self.common.shell.write();
if !shell
.previous_workspace_idx
@ -789,7 +776,6 @@ impl State {
self.handle_shortcut_action(
action,
backend_id,
seat,
serial,
time,
@ -814,7 +800,7 @@ impl State {
.move_current_element(direction, seat);
match res {
MoveResult::MoveFurther(_move_further) => {
if let Some(last_mod_serial) = seat.last_modifier_change_for(backend_id) {
if let Some(last_mod_serial) = seat.last_modifier_change() {
let mut shell = self.common.shell.write();
if !shell
.previous_workspace_idx
@ -848,7 +834,6 @@ impl State {
self.handle_shortcut_action(
action,
backend_id,
seat,
serial,
time,

File diff suppressed because it is too large Load diff

View file

@ -16,7 +16,7 @@ use smithay::{
};
use anyhow::{Context, Result};
use state::{BackendData, LastRefresh, State};
use state::{LastRefresh, State};
use std::{
env,
ffi::OsString,
@ -43,7 +43,6 @@ pub mod dbus;
pub mod debug;
pub mod hooks;
pub mod input;
pub mod libei;
mod logger;
pub mod session;
pub mod shell;
@ -177,10 +176,6 @@ 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)?;
@ -227,17 +222,19 @@ 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);
// 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;
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),
}
}
// Command still running
Ok(None) => {}
// Kiosk child disappeared, exiting with error
Err(err) => {
warn!(?err, "Failed to wait for command");
state.common.kiosk_exit_code = Some(1);
state.common.should_stop = true;
process::exit(1);
}
}
}
@ -248,32 +245,10 @@ 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(())
}

View file

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

View file

@ -838,7 +838,10 @@ impl CosmicSurface {
self.0
.send_dmabuf_feedback(output, primary_scan_out_output, |_, data| {
if is_fullscreen {
&feedback.primary_scanout_feedback
feedback
.primary_scanout_feedback
.as_ref()
.unwrap_or(&feedback.render_feedback)
} else if frame_time_filter_fn(data) == Kind::ScanoutCandidate {
feedback
.overlay_scanout_feedback

View file

@ -212,29 +212,6 @@ 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() {

View file

@ -326,7 +326,7 @@ impl KeyboardFocusTarget {
}
}
pub fn x11_surface(&self) -> Option<X11Surface> {
fn x11_surface(&self) -> Option<X11Surface> {
match self {
KeyboardFocusTarget::Element(mapped) => mapped.active_window().x11_surface().cloned(),
KeyboardFocusTarget::Fullscreen(surface) => surface.x11_surface().cloned(),

View file

@ -13,7 +13,6 @@ use smithay::{
use crate::{
config::key_bindings::cosmic_modifiers_from_smithay,
input::InputBackendId,
shell::{Trigger, layout::tiling::NodeDesc},
state::State,
};
@ -74,7 +73,6 @@ impl KeyboardGrab<State> for SwapWindowGrab {
data.handle_shortcut_action(
shortcuts::Action::Focus(direction),
&InputBackendId::Normal,
&self.seat,
serial,
time,

View file

@ -5024,20 +5024,6 @@ 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,

View file

@ -5,7 +5,7 @@ use std::{any::Any, cell::RefCell, collections::HashMap, sync::Mutex};
use crate::{
backend::render::cursor::CursorState,
config::{Config, xkb_config_to_wl},
input::{InputBackendId, ModifiersShortcutQueue, SupressedButtons, SupressedKeys},
input::{ModifiersShortcutQueue, SupressedButtons, SupressedKeys},
state::State,
};
use smithay::{
@ -82,25 +82,12 @@ impl Seats {
self.last_active = Some(seat.clone());
}
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())
})
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)
})
}
}
@ -109,7 +96,6 @@ impl Devices {
&self,
device: &D,
led_state: LedState,
backend_id: &InputBackendId,
) -> Vec<DeviceCapability> {
let id = device.id();
let mut map = self.capabilities.borrow_mut();
@ -127,7 +113,7 @@ impl Devices {
.cloned()
.filter(|c| map.values().flatten().all(|has| *c != *has))
.collect::<Vec<_>>();
map.insert((backend_id.clone(), id), caps);
map.insert(id, caps);
if device.has_capability(DeviceCapability::Keyboard)
&& let Some(device) = <dyn Any>::downcast_ref::<InputDevice>(device)
@ -140,18 +126,11 @@ impl Devices {
new_caps
}
/// 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 has_device<D: Device>(&self, device: &D) -> bool {
self.capabilities.borrow().contains_key(&device.id())
}
pub fn remove_device<D: Device>(
&self,
device: &D,
backend_id: &InputBackendId,
) -> Vec<DeviceCapability> {
pub fn remove_device<D: Device>(&self, device: &D) -> Vec<DeviceCapability> {
let id = device.id();
let mut keyboards = self.keyboards.borrow_mut();
@ -160,7 +139,7 @@ impl Devices {
}
let mut map = self.capabilities.borrow_mut();
map.remove(&(backend_id.clone(), id))
map.remove(&id)
.unwrap_or_default()
.into_iter()
.filter(|c| map.values().flatten().all(|has| *c != *has))
@ -176,8 +155,7 @@ impl Devices {
#[derive(Default)]
pub struct Devices {
// Keyed by `(backend, device_id)`
capabilities: RefCell<HashMap<(InputBackendId, String), Vec<DeviceCapability>>>,
capabilities: RefCell<HashMap<String, Vec<DeviceCapability>>>,
// Used for updating keyboard leds on kms backend
keyboards: RefCell<Vec<InputDevice>>,
}
@ -207,7 +185,7 @@ struct FocusedOutput(pub Mutex<Option<Output>>);
pub struct PointerConstraintHint(pub Mutex<Option<(WlSurface, Point<f64, Logical>)>>);
#[derive(Default)]
pub struct LastModifierChange(pub Mutex<(HashMap<InputBackendId, Serial>, Option<Serial>)>);
pub struct LastModifierChange(pub Mutex<Option<Serial>>);
pub fn create_seat(
dh: &DisplayHandle,
@ -287,9 +265,6 @@ 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>)>);
@ -368,48 +343,13 @@ impl SeatExt for Seat<State> {
}
fn last_modifier_change(&self) -> Option<Serial> {
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
*self
.user_data()
.get::<LastModifierChange>()
.unwrap()
.0
.lock()
.unwrap();
guard.0.insert(backend_id.clone(), serial);
guard.1 = Some(serial);
}
fn clear_last_modifier_change(&self, backend_id: &InputBackendId) {
self.user_data()
.get::<LastModifierChange>()
.unwrap()
.0
.lock()
.unwrap()
.0
.remove(backend_id);
}
fn pointer_constraint_hint(&self) -> Option<(WlSurface, Point<f64, Logical>)> {

View file

@ -244,32 +244,9 @@ 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,
@ -298,7 +275,7 @@ pub struct Common {
pub idle_inhibiting_surfaces: HashSet<WlSurface>,
pub shm_state: ShmState,
pub cursor_shape_manager_state: CursorShapeManagerState,
pub wl_drm_state: Option<WlDrmState<Option<DrmNode>>>,
pub wl_drm_state: WlDrmState<Option<DrmNode>>,
pub viewporter_state: ViewporterState,
pub kde_decoration_state: KdeDecorationState,
pub xdg_decoration_state: XdgDecorationState,
@ -348,7 +325,7 @@ pub enum LockedBackend<'a> {
pub struct SurfaceDmabufFeedback {
pub render_feedback: DmabufFeedback,
pub overlay_scanout_feedback: Option<DmabufFeedback>,
pub primary_scanout_feedback: DmabufFeedback,
pub primary_scanout_feedback: Option<DmabufFeedback>,
}
#[derive(Debug)]
@ -693,7 +670,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 = None;
let wl_drm_state = WlDrmState::<Option<DrmNode>>::default();
let kde_decoration_state = KdeDecorationState::new::<Self>(dh, Mode::Client);
let xdg_decoration_state = XdgDecorationState::new::<Self>(dh);
let session_lock_manager_state =
@ -777,11 +754,7 @@ 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(),
@ -981,11 +954,8 @@ impl Common {
);
if let Some(output) = primary_scanout_output {
with_fractional_scale(states, |fraction_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),
);
fraction_scale
.set_preferred_scale(output.current_scale().fractional_scale());
});
}
}
@ -1091,7 +1061,10 @@ impl Common {
surface,
render_element_states,
&feedback.render_feedback,
&feedback.primary_scanout_feedback,
feedback
.primary_scanout_feedback
.as_ref()
.unwrap_or(&feedback.render_feedback),
)
},
)

View file

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

View file

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

View file

@ -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,7 +57,18 @@ impl DmabufHandler for State {
f.ended_at.is_none() && f.surface.has_surface(surface, WindowSurfaceType::all())
});
let node = kms.primary_node.read().unwrap().unwrap();
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 kms_surface = kms
.drm_devices
.values()
@ -66,7 +77,9 @@ impl DmabufHandler for State {
Some(with_states(surface, |data| {
if is_fullscreen {
feedback.primary_scanout_feedback
feedback
.primary_scanout_feedback
.unwrap_or(feedback.render_feedback)
} else if frame_time_filter_fn(data) == Kind::ScanoutCandidate {
feedback
.overlay_scanout_feedback

View file

@ -49,10 +49,7 @@ impl FractionalScaleHandler for State {
with_states(&surface, |states| {
with_fractional_scale(states, |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));
fractional_scale.set_preferred_scale(output.current_scale().fractional_scale());
});
});
}

View file

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

View file

@ -3,12 +3,10 @@
use crate::state::State;
use smithay::{
backend::input::TabletToolDescriptor, input::pointer::CursorImageStatus,
input::tablet::TabletSeatHandler, reexports::wayland_server::protocol::wl_surface,
wayland::tablet_manager::TabletSeatHandler,
};
impl TabletSeatHandler for State {
type ToolFocus = wl_surface::WlSurface;
fn tablet_tool_image(&mut self, _tool: &TabletToolDescriptor, _image: CursorImageStatus) {
// TODO display cursor for each tablet tool
}

View file

@ -333,15 +333,17 @@ 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()
&& 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;
} else if let Some(target) = grab.current_grab() {
if let Some(wl_surface) = target.wl_surface() {
if wl_surface.as_ref() == surface.wl_surface()
|| smithay::desktop::PopupManager::popups_for_surface(
surface.wl_surface(),
)
.any(|(p, _)| p.wl_surface() == wl_surface.as_ref())
{
should_ungrab = true;
}
}
}
}
if should_ungrab {

View file

@ -190,7 +190,7 @@ where
let radius_exists = with_states(surface.wl_surface(), |surface_data| {
let hook_id = surface_data
.data_map
.get_or_insert_threadsafe(|| LayerHookId::new(None));
.get_or_insert_threadsafe(|| ToplevelHookId::new(None));
let guard = hook_id.lock().unwrap();
guard.as_ref().map(|(_, t)| t.upgrade().is_ok())
});

View file

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

View file

@ -1,6 +1,9 @@
// SPDX-License-Identifier: GPL-3.0-only
use calloop::LoopHandle;
use calloop::{
LoopHandle,
timer::{TimeoutAction, Timer},
};
use cosmic_comp_config::output::comp::AdaptiveSync;
use cosmic_protocols::output_management::v1::server::{
zcosmic_output_configuration_head_v1::ZcosmicOutputConfigurationHeadV1,
@ -27,7 +30,7 @@ use smithay::{
utils::{Logical, Physical, Point, Size, Transform},
wayland::output::WlOutputData,
};
use std::{convert::TryFrom, sync::Mutex};
use std::{convert::TryFrom, sync::Mutex, time::Duration};
mod handlers;
@ -516,6 +519,26 @@ 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: [
@ -555,4 +578,4 @@ macro_rules! delegate_output_configuration {
}
pub(crate) use delegate_output_configuration;
use crate::utils::{global::remove_global_with_timer, prelude::OutputExt};
use crate::utils::prelude::OutputExt;

View file

@ -115,7 +115,7 @@ impl State {
&self.common.display_handle,
None,
std::iter::empty::<(OsString, OsString)>(),
["-enable-ei-portal"],
std::iter::empty::<OsString>(),
true,
Stdio::null(),
Stdio::null(),
@ -394,7 +394,6 @@ impl Common {
sym: Keysym,
code: Keycode,
state: KeyState,
modifiers: ModifiersState,
serial: Serial,
time: u32,
) {
@ -419,6 +418,7 @@ 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();
@ -858,7 +858,7 @@ impl XwmHandler for State {
if let Some(target) = res {
let seat = shell.seats.last_active().clone();
std::mem::drop(shell);
Shell::set_focus_on_x11_map(self, &target, &seat, false);
Shell::set_focus(self, Some(&target), &seat, None, false);
}
}
}