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_drm",
"backend_gbm", "backend_gbm",
"backend_egl", "backend_egl",
"backend_libei",
"backend_libinput", "backend_libinput",
"backend_session_libseat", "backend_session_libseat",
"backend_udev", "backend_udev",
@ -178,4 +177,4 @@ dnd = { path = "../window_clipboard/dnd" }
mime = { path = "../window_clipboard/mime" } mime = { path = "../window_clipboard/mime" }
[patch.crates-io] [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 shrink-window = Riduci
swap-windows = Scambia le finestre swap-windows = Scambia le finestre
stack-windows = Sovrapponi le finestre stack-windows = Sovrapponi le finestre
@ -6,7 +6,7 @@ unknown-keybinding = <unset>
window-menu-minimize = Minimizza window-menu-minimize = Minimizza
window-menu-maximize = Massimizza window-menu-maximize = Massimizza
window-menu-tiled = Finestra flottante window-menu-tiled = Finestra flottante
window-menu-screenshot = Scatta uno screenshot window-menu-screenshot = Cattura una schermata
window-menu-move = Sposta window-menu-move = Sposta
window-menu-resize = Ridimensiona window-menu-resize = Ridimensiona
window-menu-move-prev-workspace = Sposta all'area di lavoro precedente 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-right = Destra
window-menu-resize-edge-bottom = In basso window-menu-resize-edge-bottom = In basso
window-menu-fullscreen = Schermo intero 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-continuously = Segue sempre il cursorse
a11y-zoom-move-centered = Mantiene il cursore centrato a11y-zoom-move-centered = Mantiene il cursore centrato
a11y-zoom-move-onedge = Segue il cursore ai bordi a11y-zoom-move-onedge = Segue il cursore ai bordi

View file

@ -59,7 +59,7 @@ use std::{
time::Duration, time::Duration,
}; };
use super::{drm_helpers, surface::Surface}; use super::{drm_helpers, socket::Socket, surface::Surface};
#[derive(Debug)] #[derive(Debug)]
pub struct EGLInternals { pub struct EGLInternals {
@ -98,12 +98,14 @@ pub struct Device {
pub texture_formats: FormatSet, pub texture_formats: FormatSet,
event_token: Option<RegistrationToken>, event_token: Option<RegistrationToken>,
pub socket: Option<Socket>,
} }
#[derive(Debug)] #[derive(Debug)]
struct ReusableDevice { struct ReusableDevice {
leasing_global: Option<DrmLeaseState>, leasing_global: Option<DrmLeaseState>,
active_clients: HashSet<ClientId>, active_clients: HashSet<ClientId>,
socket: Option<Socket>,
} }
#[derive(Debug)] #[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(); let backend = self.backend.kms();
// we can't use DrmNode::from_node_id, because that assumes the node is still on sysfs // we can't use DrmNode::from_node_id, because that assumes the node is still on sysfs
let drm_node = backend let drm_node = backend
@ -614,6 +616,13 @@ impl State {
if let Some(token) = device.event_token.take() { if let Some(token) = device.event_token.take() {
self.common.event_loop_handle.remove(token); 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.api.as_mut().remove_node(&device.inner.render_node);
backend backend
.primary_node .primary_node
@ -746,7 +755,23 @@ impl Device {
let ReusableDevice { let ReusableDevice {
leasing_global, leasing_global,
active_clients, active_clients,
socket,
} = reuse.unwrap_or_else(|| { } = 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) let leasing_global = match (!is_software)
.then(|| DrmLeaseState::new::<State>(dh, &dev_node)) .then(|| DrmLeaseState::new::<State>(dh, &dev_node))
.transpose() .transpose()
@ -765,6 +790,7 @@ impl Device {
ReusableDevice { ReusableDevice {
leasing_global, leasing_global,
active_clients: HashSet::new(), active_clients: HashSet::new(),
socket,
} }
}); });
@ -805,6 +831,7 @@ impl Device {
texture_formats, texture_formats,
event_token: Some(token), event_token: Some(token),
socket,
}) })
} }
@ -865,6 +892,7 @@ impl Device {
let device = ReusableDevice { let device = ReusableDevice {
leasing_global: self.inner.leasing_global, leasing_global: self.inner.leasing_global,
active_clients: self.inner.active_clients, active_clients: self.inner.active_clients,
socket: self.socket,
}; };
let state = OldDeviceState { let state = OldDeviceState {

View file

@ -75,7 +75,6 @@ pub fn display_configuration(
// And then cleanup // And then cleanup
if device.is_atomic() { if device.is_atomic() {
let mut req = AtomicModeReq::new(); let mut req = AtomicModeReq::new();
let mut has_changes = false;
let plane_handles = device.plane_handles()?; let plane_handles = device.plane_handles()?;
// We cannot just shortcut and use the legacy api for all cleanups because of this. // 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")?; let fb_id = get_prop(device, plane, "FB_ID")?;
req.add_property(plane, crtc_id, property::Value::CRTC(None)); req.add_property(plane, crtc_id, property::Value::CRTC(None));
req.add_property(plane, fb_id, property::Value::Framebuffer(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 device.atomic_commit(AtomicCommitFlags::ALLOW_MODESET, req)?;
// without DRM master (e.g. a render-only secondary GPU).
if has_changes {
device.atomic_commit(AtomicCommitFlags::ALLOW_MODESET, req)?;
}
} else { } else {
for crtc in res_handles.crtcs() { for crtc in res_handles.crtcs() {
#[allow(deprecated)] #[allow(deprecated)]

View file

@ -4,15 +4,12 @@ use crate::{
config::{CompOutputConfig, ScreenFilter}, config::{CompOutputConfig, ScreenFilter},
shell::Shell, shell::Shell,
state::BackendData, state::BackendData,
utils::{env::dev_var, global::remove_global_with_timer, prelude::*}, utils::{env::dev_var, prelude::*},
wayland::protocols::{drm::WlDrmState, output_power::OutputPowerState}, wayland::protocols::output_power::OutputPowerState,
}; };
use anyhow::{self, Context, Result}; use anyhow::{Context, Result};
use calloop::{ use calloop::LoopSignal;
LoopSignal,
timer::{TimeoutAction, Timer},
};
use cosmic_comp_config::output::comp::{AdaptiveSync, OutputState}; use cosmic_comp_config::output::comp::{AdaptiveSync, OutputState};
use indexmap::IndexMap; use indexmap::IndexMap;
use render::gles::GbmGlowBackend; use render::gles::GbmGlowBackend;
@ -35,12 +32,11 @@ use smithay::{
control::{Device as _, connector::Interface, crtc}, control::{Device as _, connector::Interface, crtc},
}, },
input::{self, Libinput}, input::{self, Libinput},
wayland_protocols::wp::linux_dmabuf::zv1::server::zwp_linux_dmabuf_feedback_v1::TrancheFlags,
wayland_server::{Client, DisplayHandle}, wayland_server::{Client, DisplayHandle},
}, },
utils::{Clock, DevPath, Monotonic, Size}, utils::{Clock, DevPath, Monotonic, Size},
wayland::{ wayland::{
dmabuf::{DmabufFeedbackBuilder, DmabufGlobal}, dmabuf::DmabufGlobal,
drm_syncobj::{DrmSyncobjState, supports_syncobj_eventfd}, drm_syncobj::{DrmSyncobjState, supports_syncobj_eventfd},
relative_pointer::RelativePointerManagerState, relative_pointer::RelativePointerManagerState,
}, },
@ -52,12 +48,12 @@ use std::{
collections::{HashMap, HashSet}, collections::{HashMap, HashSet},
path::Path, path::Path,
sync::{Arc, RwLock, atomic::AtomicBool}, sync::{Arc, RwLock, atomic::AtomicBool},
time::Duration,
}; };
mod device; mod device;
mod drm_helpers; mod drm_helpers;
pub mod render; pub mod render;
mod socket;
mod surface; mod surface;
use device::*; use device::*;
pub(crate) use surface::Surface; pub(crate) use surface::Surface;
@ -78,7 +74,6 @@ pub struct KmsState {
libinput: Libinput, libinput: Libinput,
pub syncobj_state: Option<DrmSyncobjState>, pub syncobj_state: Option<DrmSyncobjState>,
pub dmabuf_global: Option<DmabufGlobal>,
} }
pub struct KmsGuard<'a> { pub struct KmsGuard<'a> {
@ -136,7 +131,6 @@ pub fn init_backend(
libinput: libinput_context, libinput: libinput_context,
syncobj_state: None, syncobj_state: None,
dmabuf_global: None,
}); });
// manually add already present gpus // 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); warn!("Failed to determine primary gpu: {}", err);
} }
state.update_default_feedback();
if let Err(err) = state.refresh_output_config() { if let Err(err) = state.refresh_output_config() {
info!( info!(
@ -211,7 +204,7 @@ fn init_libinput(
state.backend.kms().input_devices.remove(&*device.name()); 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() { for output in state.common.shell.read().outputs() {
state.backend.kms().schedule_render(output); state.backend.kms().schedule_render(output);
@ -324,13 +317,11 @@ fn init_udev(
{ {
let backend = state.backend.kms(); let backend = state.backend.kms();
if matches!(event, UdevEvent::Added { .. } | UdevEvent::Removed { .. }) { if matches!(event, UdevEvent::Added { .. } | UdevEvent::Removed { .. })
if backend.primary_node.read().unwrap().is_none() && backend.primary_node.read().unwrap().is_none()
&& let Err(err) = state.select_primary_gpu(&dh) && let Err(err) = state.backend.kms().select_primary_gpu(&dh)
{ {
warn!("Failed to determine a new primary gpu: {}", err); warn!("Failed to determine a new primary gpu: {}", err);
}
state.update_default_feedback();
} }
} }
@ -474,118 +465,50 @@ impl State {
device.drm.pause(); device.drm.pause();
} }
} }
}
impl KmsState {
fn select_primary_gpu(&mut self, dh: &DisplayHandle) -> Result<()> { fn select_primary_gpu(&mut self, dh: &DisplayHandle) -> Result<()> {
// We don't have to check the allow/blocklist here, // We don't have to check the allow/blocklist here,
// as any disallowed devices won't be in `self.drm_devices`. // as any disallowed devices won't be in `self.drm_devices`.
let kms = self.backend.kms(); let mut primary_node = self.primary_node.write().unwrap();
let mut primary_node = kms.primary_node.write().unwrap();
let _ = primary_node.take(); // if we error don't leave an old node in place 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 { if let Some(node) = *primary_node {
info!("Using {} as primary gpu for rendering.", node); info!("Using {} as primary gpu for rendering.", node);
kms.software_renderer.take(); self.software_renderer.take();
} else if self.software_renderer.is_none() {
// 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"); 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), Ok(renderer) => Some(renderer),
Err(err) => { Err(err) => {
error!(?err, "Failed to initialize software EGL renderer."); error!(?err, "Failed to initialize software EGL renderer.");
None 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 !crate::utils::env::bool_var("COSMIC_DISABLE_SYNCOBJ").unwrap_or(false) {
if let Some(primary_node) = primary_node if let Some(primary_node) = primary_node
.as_ref() .as_ref()
.and_then(|node| node.node_with_type(NodeType::Primary).and_then(|x| x.ok())) .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(); let import_device = device.drm.device().device_fd().clone();
if supports_syncobj_eventfd(&import_device) { 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); state.update_device(import_device);
} else { } else {
let syncobj_state = DrmSyncobjState::new::<State>(dh, import_device); let syncobj_state = DrmSyncobjState::new::<State>(dh, import_device);
kms.syncobj_state = Some(syncobj_state); self.syncobj_state = Some(syncobj_state);
} }
return Ok(()); 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()); dh.remove_global::<State>(old_state.into_global());
} }
} }
@ -593,45 +516,6 @@ impl State {
Ok(()) 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> { pub fn switch_vt(&mut self, num: i32) -> Result<(), anyhow::Error> {
self.session.change_vt(num).map_err(Into::into) self.session.change_vt(num).map_err(Into::into)
} }
@ -639,25 +523,29 @@ impl KmsState {
pub fn dmabuf_imported( pub fn dmabuf_imported(
&mut self, &mut self,
client: Option<Client>, client: Option<Client>,
_global: &DmabufGlobal, global: &DmabufGlobal,
dmabuf: Dmabuf, dmabuf: Dmabuf,
) -> Result<DrmNode> { ) -> Result<DrmNode> {
let device_node = dmabuf
.node()
.unwrap_or_else(|| self.primary_node.read().unwrap().unwrap());
let mut device = self let mut device = self
.drm_devices .drm_devices
.values_mut() .values_mut()
.find(|dev| dev.inner.render_node == device_node) .find(|device| {
.ok_or(anyhow::anyhow!( device
"Unable to find device for node: {}", .socket
device_node .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 // If device advertised to client doesn't support format/modifier, select
// first device that does. This is needed for image-copy from // first device that does. This is needed for image-copy from
// output/toplevel on a different node. // 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 device = self
.drm_devices .drm_devices
.values_mut() .values_mut()

View file

@ -1006,16 +1006,12 @@ impl SurfaceThreadState {
&self.shell.read(), &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 { let mut renderer = if render_node != self.target_node {
self.api self.api
.renderer(&render_node, &self.target_node, compositor.format()) .renderer(&render_node, &self.target_node, compositor.format())
.map_err(|err| anyhow::format_err!("Failed to create renderer: {:?}", err))? .unwrap()
} else { } else {
self.api self.api.single_renderer(&self.target_node).unwrap()
.single_renderer(&self.target_node)
.map_err(|err| anyhow::format_err!("Failed to create renderer: {:?}", err))?
}; };
self.timings.start_render(&self.clock); self.timings.start_render(&self.clock);
@ -1271,10 +1267,7 @@ impl SurfaceThreadState {
}) })
.context("Failed to draw to offscreen render target")?; .context("Failed to draw to offscreen render target")?;
renderer = self renderer = self.api.single_renderer(&self.target_node).unwrap();
.api
.single_renderer(&self.target_node)
.map_err(|err| anyhow::format_err!("Failed to create renderer: {:?}", err))?;
elements = postprocess_elements( elements = postprocess_elements(
&mut renderer, &mut renderer,
@ -1529,7 +1522,7 @@ fn get_surface_dmabuf_feedback(
render_node: DrmNode, render_node: DrmNode,
target_node: DrmNode, target_node: DrmNode,
render_formats: FormatSet, render_formats: FormatSet,
target_formats: FormatSet, _target_formats: FormatSet,
primary_plane_formats: FormatSet, primary_plane_formats: FormatSet,
overlay_plane_formats: Option<FormatSet>, overlay_plane_formats: Option<FormatSet>,
) -> SurfaceDmabufFeedback { ) -> SurfaceDmabufFeedback {
@ -1547,40 +1540,60 @@ fn get_surface_dmabuf_feedback(
.cloned() .cloned()
.collect::<FormatSet>() .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( builder = builder.add_preference_tranche(
target_node.dev_id(), render_node.dev_id(),
zwp_linux_dmabuf_feedback_v1::TrancheFlags::Sampling, None,
target_formats, combined_formats,
6..=6,
); );
}; };
let render_feedback = builder.clone().build().unwrap();
let primary_scanout_feedback = builder // We also can't advertise scan out tranches for the actual display device,
.clone() // as e.g. the nvidia driver might then send us dmabufs, that makes e.g. the iris hangs on import...
.add_preference_tranche( if target_node != render_node.dev_id() && !combined_formats.is_empty() {
builder = builder.add_preference_tranche(
target_node.dev_id(), target_node.dev_id(),
zwp_linux_dmabuf_feedback_v1::TrancheFlags::Scanout, Some(zwp_linux_dmabuf_feedback_v1::TrancheFlags::Scanout),
primary_plane_formats, combined_formats,
4..=6, );
) };
.build()
.unwrap(); // So no fun combinations, we gotta wait for dmabuf-v6
let overlay_scanout_feedback = overlay_plane_formats.map(|formats| { */
let render_feedback = builder.clone().build().unwrap();
let primary_scanout_feedback = (target_node == render_node).then(|| {
builder builder
.clone()
.add_preference_tranche( .add_preference_tranche(
target_node.dev_id(), render_node.dev_id(),
zwp_linux_dmabuf_feedback_v1::TrancheFlags::Scanout, Some(zwp_linux_dmabuf_feedback_v1::TrancheFlags::Scanout),
formats, primary_plane_formats,
4..=6,
) )
.build() .build()
.unwrap() .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 { SurfaceDmabufFeedback {
render_feedback, render_feedback,

View file

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

View file

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

View file

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

View file

@ -150,10 +150,9 @@ impl A11yKeyboardMonitorState {
} }
pub fn key_event(&self, modifiers: &ModifiersState, keysym: &KeysymHandle, state: KeyState) { 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(); let clients = self.clients.lock().unwrap();
for (unique_name, client) in clients.0.iter() { 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; 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}, cell::{RefCell, RefMut},
collections::HashMap, collections::HashMap,
rc::Rc, rc::Rc,
sync::{Arc, Mutex},
}; };
use tracing::{error, warn}; use tracing::{error, warn};
pub mod a11y_keyboard_monitor; pub mod a11y_keyboard_monitor;
use a11y_keyboard_monitor::A11yKeyboardMonitorState; use a11y_keyboard_monitor::A11yKeyboardMonitorState;
pub mod ei;
#[cfg(feature = "logind")] #[cfg(feature = "logind")]
pub mod logind; pub mod logind;
mod name_owners; mod name_owners;
@ -31,7 +29,6 @@ struct DBusStateInner {
session_conn: zbus::Result<zbus::Connection>, session_conn: zbus::Result<zbus::Connection>,
system_conn: zbus::Result<zbus::Connection>, system_conn: zbus::Result<zbus::Connection>,
a11y_keyboard_monitor: RefCell<Option<a11y_keyboard_monitor::A11yKeyboardMonitorState>>, a11y_keyboard_monitor: RefCell<Option<a11y_keyboard_monitor::A11yKeyboardMonitorState>>,
ei_sender: Arc<Mutex<Option<calloop::channel::Sender<crate::libei::EiRequest>>>>,
} }
impl DBusState { impl DBusState {
@ -45,7 +42,6 @@ impl DBusState {
session_conn, session_conn,
system_conn, system_conn,
a11y_keyboard_monitor: RefCell::new(None), a11y_keyboard_monitor: RefCell::new(None),
ei_sender: Arc::new(Mutex::new(None)),
})); }));
evlh.insert_source(source, |_, _, _| {}).unwrap(); evlh.insert_source(source, |_, _, _| {}).unwrap();
let state_clone = state.clone(); 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() 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 // TODO Lazy async init when we don't have anything blocking main thread
async fn session_conn(&self) -> zbus::Result<&zbus::Connection> { async fn session_conn(&self) -> zbus::Result<&zbus::Connection> {
self.0.session_conn.as_ref().map_err(|err| err.clone()) 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 = let a11y_keyboard_monitor_state =
A11yKeyboardMonitorState::new(conn, &name_owners, &state.0.executor).await?; A11yKeyboardMonitorState::new(conn, &name_owners, &state.0.executor).await?;
*state.0.a11y_keyboard_monitor.borrow_mut() = Some(a11y_keyboard_monitor_state); *state.0.a11y_keyboard_monitor.borrow_mut() = Some(a11y_keyboard_monitor_state);
ei::init(conn, &name_owners, state.0.ei_sender.clone()).await?;
Ok(()) Ok(())
} }

View file

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

File diff suppressed because it is too large Load diff

View file

@ -16,7 +16,7 @@ use smithay::{
}; };
use anyhow::{Context, Result}; use anyhow::{Context, Result};
use state::{BackendData, LastRefresh, State}; use state::{LastRefresh, State};
use std::{ use std::{
env, env,
ffi::OsString, ffi::OsString,
@ -43,7 +43,6 @@ pub mod dbus;
pub mod debug; pub mod debug;
pub mod hooks; pub mod hooks;
pub mod input; pub mod input;
pub mod libei;
mod logger; mod logger;
pub mod session; pub mod session;
pub mod shell; pub mod shell;
@ -177,10 +176,6 @@ pub fn run(hooks: crate::hooks::Hooks) -> Result<(), Box<dyn Error>> {
with_xwayland, with_xwayland,
kiosk_command, 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 // init backend
backend::init_backend_auto(&display, &mut event_loop, &mut state)?; 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 // Kiosk child exited with status
Ok(Some(exit_status)) => { Ok(Some(exit_status)) => {
info!("Command exited with status {:?}", exit_status); info!("Command exited with status {:?}", exit_status);
// Stop cleanly so surface threads are joined before exit() (signal -> 1). match exit_status.code() {
state.common.kiosk_exit_code = Some(exit_status.code().unwrap_or(1)); // Exiting with the same status as the kiosk child
state.common.should_stop = true; Some(code) => process::exit(code),
// The kiosk child exited with signal, exiting with error
None => process::exit(1),
}
} }
// Command still running // Command still running
Ok(None) => {} Ok(None) => {}
// Kiosk child disappeared, exiting with error // Kiosk child disappeared, exiting with error
Err(err) => { Err(err) => {
warn!(?err, "Failed to wait for command"); warn!(?err, "Failed to wait for command");
state.common.kiosk_exit_code = Some(1); process::exit(1);
state.common.should_stop = true;
} }
} }
} }
@ -248,32 +245,10 @@ pub fn run(hooks: crate::hooks::Hooks) -> Result<(), Box<dyn Error>> {
let _ = child.kill(); 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 // drop eventloop & state before logger
std::mem::drop(event_loop); std::mem::drop(event_loop);
std::mem::drop(state); std::mem::drop(state);
if let Some(code) = kiosk_exit_code {
process::exit(code);
}
Ok(()) 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 self.0
.send_dmabuf_feedback(output, primary_scan_out_output, |_, data| { .send_dmabuf_feedback(output, primary_scan_out_output, |_, data| {
if is_fullscreen { 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 { } else if frame_time_filter_fn(data) == Kind::ScanoutCandidate {
feedback feedback
.overlay_scanout_feedback .overlay_scanout_feedback

View file

@ -212,29 +212,6 @@ impl Shell {
state.common.shell.write().update_active(); 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>) { pub fn append_focus_stack(&mut self, target: impl Into<FocusTarget>, seat: &Seat<State>) {
let target = target.into(); let target = target.into();
if target.is_minimized() { 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 { match self {
KeyboardFocusTarget::Element(mapped) => mapped.active_window().x11_surface().cloned(), KeyboardFocusTarget::Element(mapped) => mapped.active_window().x11_surface().cloned(),
KeyboardFocusTarget::Fullscreen(surface) => surface.x11_surface().cloned(), KeyboardFocusTarget::Fullscreen(surface) => surface.x11_surface().cloned(),

View file

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

View file

@ -5024,20 +5024,6 @@ impl Shell {
let mut output_presentation_feedback = OutputPresentationFeedback::new(output); let mut output_presentation_feedback = OutputPresentationFeedback::new(output);
if let Some(active) = self.active_space(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| { active.mapped().for_each(|mapped| {
mapped.active_window().take_presentation_feedback( mapped.active_window().take_presentation_feedback(
&mut output_presentation_feedback, &mut output_presentation_feedback,

View file

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

View file

@ -244,32 +244,9 @@ pub struct Common {
pub clock: Clock<Monotonic>, pub clock: Clock<Monotonic>,
pub startup_done: Arc<AtomicBool>, pub startup_done: Arc<AtomicBool>,
pub should_stop: bool, pub should_stop: bool,
pub kiosk_exit_code: Option<i32>,
pub gesture_state: Option<GestureState>, 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 kiosk_child: Option<Child>,
pub theme: cosmic::Theme, pub theme: cosmic::Theme,
@ -298,7 +275,7 @@ pub struct Common {
pub idle_inhibiting_surfaces: HashSet<WlSurface>, pub idle_inhibiting_surfaces: HashSet<WlSurface>,
pub shm_state: ShmState, pub shm_state: ShmState,
pub cursor_shape_manager_state: CursorShapeManagerState, 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 viewporter_state: ViewporterState,
pub kde_decoration_state: KdeDecorationState, pub kde_decoration_state: KdeDecorationState,
pub xdg_decoration_state: XdgDecorationState, pub xdg_decoration_state: XdgDecorationState,
@ -348,7 +325,7 @@ pub enum LockedBackend<'a> {
pub struct SurfaceDmabufFeedback { pub struct SurfaceDmabufFeedback {
pub render_feedback: DmabufFeedback, pub render_feedback: DmabufFeedback,
pub overlay_scanout_feedback: Option<DmabufFeedback>, pub overlay_scanout_feedback: Option<DmabufFeedback>,
pub primary_scanout_feedback: DmabufFeedback, pub primary_scanout_feedback: Option<DmabufFeedback>,
} }
#[derive(Debug)] #[derive(Debug)]
@ -693,7 +670,7 @@ impl State {
let cursor_shape_manager_state = CursorShapeManagerState::new::<State>(dh); let cursor_shape_manager_state = CursorShapeManagerState::new::<State>(dh);
let seat_state = SeatState::<Self>::new(); let seat_state = SeatState::<Self>::new();
let viewporter_state = ViewporterState::new::<Self>(dh); 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 kde_decoration_state = KdeDecorationState::new::<Self>(dh, Mode::Client);
let xdg_decoration_state = XdgDecorationState::new::<Self>(dh); let xdg_decoration_state = XdgDecorationState::new::<Self>(dh);
let session_lock_manager_state = let session_lock_manager_state =
@ -777,11 +754,7 @@ impl State {
clock, clock,
startup_done: Arc::new(AtomicBool::new(false)), startup_done: Arc::new(AtomicBool::new(false)),
should_stop: false, should_stop: false,
kiosk_exit_code: None,
gesture_state: 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, kiosk_child: None,
theme: cosmic::theme::system_preference(), theme: cosmic::theme::system_preference(),
@ -981,11 +954,8 @@ impl Common {
); );
if let Some(output) = primary_scanout_output { if let Some(output) = primary_scanout_output {
with_fractional_scale(states, |fraction_scale| { with_fractional_scale(states, |fraction_scale| {
// The 1.0 clamp is a workaround for Chromium fraction_scale
// TODO: remove if Chromium ever gets fixed .set_preferred_scale(output.current_scale().fractional_scale());
fraction_scale.set_preferred_scale(
output.current_scale().fractional_scale().max(1.0),
);
}); });
} }
} }
@ -1091,7 +1061,10 @@ impl Common {
surface, surface,
render_element_states, render_element_states,
&feedback.render_feedback, &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; mod ids;
pub(crate) use self::ids::id_gen; pub(crate) use self::ids::id_gen;
pub mod geometry; pub mod geometry;
pub mod global;
pub mod iced; pub mod iced;
pub mod prelude; pub mod prelude;
pub mod quirks; pub mod quirks;

View file

@ -40,7 +40,7 @@ impl DmabufHandler for State {
fn new_surface_feedback( fn new_surface_feedback(
&mut self, &mut self,
surface: &WlSurface, surface: &WlSurface,
_global: &DmabufGlobal, global: &DmabufGlobal,
) -> Option<DmabufFeedback> { ) -> Option<DmabufFeedback> {
let BackendData::Kms(kms) = &self.backend else { let BackendData::Kms(kms) = &self.backend else {
return None; return None;
@ -57,7 +57,18 @@ impl DmabufHandler for State {
f.ended_at.is_none() && f.surface.has_surface(surface, WindowSurfaceType::all()) 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 let kms_surface = kms
.drm_devices .drm_devices
.values() .values()
@ -66,7 +77,9 @@ impl DmabufHandler for State {
Some(with_states(surface, |data| { Some(with_states(surface, |data| {
if is_fullscreen { 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 { } else if frame_time_filter_fn(data) == Kind::ScanoutCandidate {
feedback feedback
.overlay_scanout_feedback .overlay_scanout_feedback

View file

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

View file

@ -230,16 +230,6 @@ impl State {
state.common.output_configuration_state.update(); 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 true
} }
} }

View file

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

View file

@ -333,15 +333,17 @@ impl XdgShellHandler for State {
if let Some(ref grab) = grab { if let Some(ref grab) = grab {
if grab.has_ended() { if grab.has_ended() {
should_ungrab = true; should_ungrab = true;
} else if let Some(target) = grab.current_grab() } else if let Some(target) = grab.current_grab() {
&& let Some(wl_surface) = target.wl_surface() if let Some(wl_surface) = target.wl_surface() {
&& (wl_surface.as_ref() == surface.wl_surface() if wl_surface.as_ref() == surface.wl_surface()
|| smithay::desktop::PopupManager::popups_for_surface( || smithay::desktop::PopupManager::popups_for_surface(
surface.wl_surface(), surface.wl_surface(),
) )
.any(|(p, _)| p.wl_surface() == wl_surface.as_ref())) .any(|(p, _)| p.wl_surface() == wl_surface.as_ref())
{ {
should_ungrab = true; should_ungrab = true;
}
}
} }
} }
if should_ungrab { if should_ungrab {

View file

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

View file

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

View file

@ -1,6 +1,9 @@
// SPDX-License-Identifier: GPL-3.0-only // 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_comp_config::output::comp::AdaptiveSync;
use cosmic_protocols::output_management::v1::server::{ use cosmic_protocols::output_management::v1::server::{
zcosmic_output_configuration_head_v1::ZcosmicOutputConfigurationHeadV1, zcosmic_output_configuration_head_v1::ZcosmicOutputConfigurationHeadV1,
@ -27,7 +30,7 @@ use smithay::{
utils::{Logical, Physical, Point, Size, Transform}, utils::{Logical, Physical, Point, Size, Transform},
wayland::output::WlOutputData, wayland::output::WlOutputData,
}; };
use std::{convert::TryFrom, sync::Mutex}; use std::{convert::TryFrom, sync::Mutex, time::Duration};
mod handlers; 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 { macro_rules! delegate_output_configuration {
($(@<$( $lt:tt $( : $clt:tt $(+ $dlt:tt )* )? ),+>)? $ty: ty) => { ($(@<$( $lt:tt $( : $clt:tt $(+ $dlt:tt )* )? ),+>)? $ty: ty) => {
smithay::reexports::wayland_server::delegate_global_dispatch!($(@< $( $lt $( : $clt $(+ $dlt )* )? ),+ >)? $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; 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, &self.common.display_handle,
None, None,
std::iter::empty::<(OsString, OsString)>(), std::iter::empty::<(OsString, OsString)>(),
["-enable-ei-portal"], std::iter::empty::<OsString>(),
true, true,
Stdio::null(), Stdio::null(),
Stdio::null(), Stdio::null(),
@ -394,7 +394,6 @@ impl Common {
sym: Keysym, sym: Keysym,
code: Keycode, code: Keycode,
state: KeyState, state: KeyState,
modifiers: ModifiersState,
serial: Serial, serial: Serial,
time: u32, time: u32,
) { ) {
@ -419,6 +418,7 @@ impl Common {
.last_active() .last_active()
.get_keyboard() .get_keyboard()
.unwrap(); .unwrap();
let modifiers = keyboard.modifier_state();
let is_modifier = sym.is_modifier_key(); let is_modifier = sym.is_modifier_key();
let xstate = self.xwayland_state.as_mut().unwrap(); let xstate = self.xwayland_state.as_mut().unwrap();
@ -858,7 +858,7 @@ impl XwmHandler for State {
if let Some(target) = res { if let Some(target) = res {
let seat = shell.seats.last_active().clone(); let seat = shell.seats.last_active().clone();
std::mem::drop(shell); std::mem::drop(shell);
Shell::set_focus_on_x11_map(self, &target, &seat, false); Shell::set_focus(self, Some(&target), &seat, None, false);
} }
} }
} }