Merge upstream/master (COSMIC 1.6) into local/yoda-main
32 commits amont, aucun conflit de code : seul le lockfile divergeait, il est regenere. Les 9 commits yoda sont preserves, dont le patch des onglets de piles suivant WindowControlsPosition. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
commit
f60c573f4d
39 changed files with 1945 additions and 657 deletions
425
Cargo.lock
generated
425
Cargo.lock
generated
File diff suppressed because it is too large
Load diff
|
|
@ -102,6 +102,7 @@ features = [
|
|||
"backend_drm",
|
||||
"backend_gbm",
|
||||
"backend_egl",
|
||||
"backend_libei",
|
||||
"backend_libinput",
|
||||
"backend_session_libseat",
|
||||
"backend_udev",
|
||||
|
|
@ -177,4 +178,4 @@ dnd = { path = "../window_clipboard/dnd" }
|
|||
mime = { path = "../window_clipboard/mime" }
|
||||
|
||||
[patch.crates-io]
|
||||
smithay = { git = "https://github.com/smithay/smithay.git", rev = "1ed69cb" }
|
||||
smithay = { git = "https://github.com/smithay/smithay.git", rev = "5fb12b8" }
|
||||
|
|
|
|||
|
|
@ -0,0 +1,28 @@
|
|||
a11y-zoom-move-continuously = Visningen bevæger sig kontinuerligt med markøren
|
||||
a11y-zoom-move-onedge = Vis bevægelser når markør rammer kant
|
||||
a11y-zoom-move-centered = Vis bevægelser, for at holde markør centreret
|
||||
a11y-zoom-settings = Forstørrelsesglas-indstillinger...
|
||||
grow-window = Gro
|
||||
shrink-window = Skrump
|
||||
swap-windows = Byt Vinduer
|
||||
stack-windows = Stabel Vinduer
|
||||
window-menu-tiled = Gør vindue flydende
|
||||
window-menu-screenshot = Tag skærmbillede
|
||||
window-menu-move = Flyt
|
||||
window-menu-resize = Skift størrelse
|
||||
window-menu-move-prev-workspace = Flyt til forrige arbejdsområde
|
||||
window-menu-move-next-workspace = Flyt til næste arbejdsområde
|
||||
window-menu-stack = Opret vinduestak
|
||||
window-menu-unstack-all = Udtag vinduer af stak
|
||||
window-menu-unstack = Udtag vindue af stak
|
||||
window-menu-sticky = Fastgjort vindue
|
||||
window-menu-close = Luk
|
||||
window-menu-close-all = Luk alle vinduer
|
||||
window-menu-resize-edge-top = Top
|
||||
window-menu-resize-edge-left = Venstre
|
||||
window-menu-resize-edge-right = Højre
|
||||
window-menu-resize-edge-bottom = Bund
|
||||
window-menu-minimize = Minimér
|
||||
window-menu-maximize = Maksimér
|
||||
window-menu-fullscreen = Fuld skærm
|
||||
unknown-keybinding = <uangiven>
|
||||
|
|
@ -0,0 +1,28 @@
|
|||
a11y-zoom-move-continuously = View moves continuously with pointer
|
||||
a11y-zoom-move-onedge = View moves when pointer reaches edge
|
||||
a11y-zoom-move-centered = View moves to keep pointer centred
|
||||
a11y-zoom-settings = Magnifier settings...
|
||||
grow-window = Grow
|
||||
shrink-window = Shrink
|
||||
swap-windows = Swap Windows
|
||||
stack-windows = Stack Windows
|
||||
unknown-keybinding = <unset>
|
||||
window-menu-minimize = Minimise
|
||||
window-menu-maximize = Maximise
|
||||
window-menu-fullscreen = Fullscreen
|
||||
window-menu-tiled = Float window
|
||||
window-menu-screenshot = Take screenshot
|
||||
window-menu-move = Move
|
||||
window-menu-resize = Resize
|
||||
window-menu-move-prev-workspace = Move to previous workspace
|
||||
window-menu-move-next-workspace = Move to next workspace
|
||||
window-menu-stack = Create window stack
|
||||
window-menu-unstack-all = Unstack windows
|
||||
window-menu-unstack = Unstack window
|
||||
window-menu-sticky = Sticky window
|
||||
window-menu-close = Close
|
||||
window-menu-close-all = Close all windows
|
||||
window-menu-resize-edge-top = Top
|
||||
window-menu-resize-edge-left = Left
|
||||
window-menu-resize-edge-right = Right
|
||||
window-menu-resize-edge-bottom = Bottom
|
||||
28
resources/i18n/gl/cosmic_comp.ftl
Normal file
28
resources/i18n/gl/cosmic_comp.ftl
Normal file
|
|
@ -0,0 +1,28 @@
|
|||
a11y-zoom-move-continuously = A vista móvese continuamente co punteiro
|
||||
a11y-zoom-move-onedge = A vista móvese cando o punteiro chega ao bordo
|
||||
a11y-zoom-move-centered = Mover a vista para manter o cursor centrado
|
||||
a11y-zoom-settings = Configuración da lupa...
|
||||
grow-window = Aumentar
|
||||
shrink-window = Encoller
|
||||
swap-windows = Cambiar xanelas
|
||||
stack-windows = Apilar xanelas
|
||||
unknown-keybinding = <unset>
|
||||
window-menu-minimize = Minimizar
|
||||
window-menu-maximize = Maximizar
|
||||
window-menu-fullscreen = Pantalla Completa
|
||||
window-menu-tiled = Xanela flotante
|
||||
window-menu-screenshot = Capturar pantalla
|
||||
window-menu-move = Mover
|
||||
window-menu-resize = Redimensionar
|
||||
window-menu-move-prev-workspace = Mover ao espazo de traballo anterior
|
||||
window-menu-move-next-workspace = Mover ao seguinte espazo de traballo
|
||||
window-menu-stack = Crear pila de xanelas
|
||||
window-menu-unstack-all = Desapilar xanelas
|
||||
window-menu-unstack = Desapilar xanela
|
||||
window-menu-sticky = Xanela persistente
|
||||
window-menu-close = Pechar
|
||||
window-menu-close-all = Pechar todas as xanelas
|
||||
window-menu-resize-edge-top = Arriba
|
||||
window-menu-resize-edge-left = Esquerda
|
||||
window-menu-resize-edge-right = Dereita
|
||||
window-menu-resize-edge-bottom = Fondo
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
grow-window = Ingrandisci
|
||||
grow-window = Crescere
|
||||
shrink-window = Riduci
|
||||
swap-windows = Scambia le finestre
|
||||
stack-windows = Sovrapponi le finestre
|
||||
|
|
@ -6,7 +6,7 @@ unknown-keybinding = <unset>
|
|||
window-menu-minimize = Minimizza
|
||||
window-menu-maximize = Massimizza
|
||||
window-menu-tiled = Finestra flottante
|
||||
window-menu-screenshot = Cattura una schermata
|
||||
window-menu-screenshot = Scatta uno screenshot
|
||||
window-menu-move = Sposta
|
||||
window-menu-resize = Ridimensiona
|
||||
window-menu-move-prev-workspace = Sposta all'area di lavoro precedente
|
||||
|
|
@ -22,7 +22,7 @@ window-menu-resize-edge-left = Sinistra
|
|||
window-menu-resize-edge-right = Destra
|
||||
window-menu-resize-edge-bottom = In basso
|
||||
window-menu-fullscreen = Schermo intero
|
||||
a11y-zoom-settings = Opzioni lente d'ingrandimento...
|
||||
a11y-zoom-settings = Impostazioni lente d'ingrandimento...
|
||||
a11y-zoom-move-continuously = Segue sempre il cursorse
|
||||
a11y-zoom-move-centered = Mantiene il cursore centrato
|
||||
a11y-zoom-move-onedge = Segue il cursore ai bordi
|
||||
|
|
|
|||
0
resources/i18n/sat/cosmic_comp.ftl
Normal file
0
resources/i18n/sat/cosmic_comp.ftl
Normal file
|
|
@ -59,7 +59,7 @@ use std::{
|
|||
time::Duration,
|
||||
};
|
||||
|
||||
use super::{drm_helpers, socket::Socket, surface::Surface};
|
||||
use super::{drm_helpers, surface::Surface};
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct EGLInternals {
|
||||
|
|
@ -98,14 +98,12 @@ pub struct Device {
|
|||
|
||||
pub texture_formats: FormatSet,
|
||||
event_token: Option<RegistrationToken>,
|
||||
pub socket: Option<Socket>,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
struct ReusableDevice {
|
||||
leasing_global: Option<DrmLeaseState>,
|
||||
active_clients: HashSet<ClientId>,
|
||||
socket: Option<Socket>,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
|
|
@ -593,7 +591,7 @@ impl State {
|
|||
}
|
||||
}
|
||||
|
||||
pub fn device_removed(&mut self, dev: dev_t, dh: &DisplayHandle) -> Result<()> {
|
||||
pub fn device_removed(&mut self, dev: dev_t, _dh: &DisplayHandle) -> Result<()> {
|
||||
let backend = self.backend.kms();
|
||||
// we can't use DrmNode::from_node_id, because that assumes the node is still on sysfs
|
||||
let drm_node = backend
|
||||
|
|
@ -616,13 +614,6 @@ impl State {
|
|||
if let Some(token) = device.event_token.take() {
|
||||
self.common.event_loop_handle.remove(token);
|
||||
}
|
||||
if let Some(socket) = device.socket.take() {
|
||||
self.common.event_loop_handle.remove(socket.token);
|
||||
self.common
|
||||
.dmabuf_state
|
||||
.destroy_global::<State>(dh, socket.dmabuf_global);
|
||||
dh.remove_global::<State>(socket.drm_global);
|
||||
}
|
||||
backend.api.as_mut().remove_node(&device.inner.render_node);
|
||||
backend
|
||||
.primary_node
|
||||
|
|
@ -755,23 +746,7 @@ impl Device {
|
|||
let ReusableDevice {
|
||||
leasing_global,
|
||||
active_clients,
|
||||
socket,
|
||||
} = reuse.unwrap_or_else(|| {
|
||||
let socket = match (!is_software)
|
||||
.then(|| common.create_socket(dh, render_node, texture_formats.clone()))
|
||||
.transpose()
|
||||
{
|
||||
Ok(socket) => socket,
|
||||
Err(err) => {
|
||||
warn!(
|
||||
?err,
|
||||
"Failed to initialize hardware-acceleration for clients on {}.",
|
||||
render_node,
|
||||
);
|
||||
None
|
||||
}
|
||||
};
|
||||
|
||||
let leasing_global = match (!is_software)
|
||||
.then(|| DrmLeaseState::new::<State>(dh, &dev_node))
|
||||
.transpose()
|
||||
|
|
@ -790,7 +765,6 @@ impl Device {
|
|||
ReusableDevice {
|
||||
leasing_global,
|
||||
active_clients: HashSet::new(),
|
||||
socket,
|
||||
}
|
||||
});
|
||||
|
||||
|
|
@ -831,7 +805,6 @@ impl Device {
|
|||
|
||||
texture_formats,
|
||||
event_token: Some(token),
|
||||
socket,
|
||||
})
|
||||
}
|
||||
|
||||
|
|
@ -892,7 +865,6 @@ impl Device {
|
|||
let device = ReusableDevice {
|
||||
leasing_global: self.inner.leasing_global,
|
||||
active_clients: self.inner.active_clients,
|
||||
socket: self.socket,
|
||||
};
|
||||
|
||||
let state = OldDeviceState {
|
||||
|
|
|
|||
|
|
@ -75,6 +75,7 @@ pub fn display_configuration(
|
|||
// And then cleanup
|
||||
if device.is_atomic() {
|
||||
let mut req = AtomicModeReq::new();
|
||||
let mut has_changes = false;
|
||||
let plane_handles = device.plane_handles()?;
|
||||
|
||||
// We cannot just shortcut and use the legacy api for all cleanups because of this.
|
||||
|
|
@ -95,10 +96,15 @@ pub fn display_configuration(
|
|||
let fb_id = get_prop(device, plane, "FB_ID")?;
|
||||
req.add_property(plane, crtc_id, property::Value::CRTC(None));
|
||||
req.add_property(plane, fb_id, property::Value::Framebuffer(None));
|
||||
has_changes = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
device.atomic_commit(AtomicCommitFlags::ALLOW_MODESET, req)?;
|
||||
// Skip an empty commit: a no-op modeset that also fails with EPERM
|
||||
// without DRM master (e.g. a render-only secondary GPU).
|
||||
if has_changes {
|
||||
device.atomic_commit(AtomicCommitFlags::ALLOW_MODESET, req)?;
|
||||
}
|
||||
} else {
|
||||
for crtc in res_handles.crtcs() {
|
||||
#[allow(deprecated)]
|
||||
|
|
|
|||
|
|
@ -4,12 +4,15 @@ use crate::{
|
|||
config::{CompOutputConfig, ScreenFilter},
|
||||
shell::Shell,
|
||||
state::BackendData,
|
||||
utils::{env::dev_var, prelude::*},
|
||||
wayland::protocols::output_power::OutputPowerState,
|
||||
utils::{env::dev_var, global::remove_global_with_timer, prelude::*},
|
||||
wayland::protocols::{drm::WlDrmState, output_power::OutputPowerState},
|
||||
};
|
||||
|
||||
use anyhow::{Context, Result};
|
||||
use calloop::LoopSignal;
|
||||
use anyhow::{self, Context, Result};
|
||||
use calloop::{
|
||||
LoopSignal,
|
||||
timer::{TimeoutAction, Timer},
|
||||
};
|
||||
use cosmic_comp_config::output::comp::{AdaptiveSync, OutputState};
|
||||
use indexmap::IndexMap;
|
||||
use render::gles::GbmGlowBackend;
|
||||
|
|
@ -32,11 +35,12 @@ 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::DmabufGlobal,
|
||||
dmabuf::{DmabufFeedbackBuilder, DmabufGlobal},
|
||||
drm_syncobj::{DrmSyncobjState, supports_syncobj_eventfd},
|
||||
relative_pointer::RelativePointerManagerState,
|
||||
},
|
||||
|
|
@ -48,12 +52,12 @@ use std::{
|
|||
collections::{HashMap, HashSet},
|
||||
path::Path,
|
||||
sync::{Arc, RwLock, atomic::AtomicBool},
|
||||
time::Duration,
|
||||
};
|
||||
|
||||
mod device;
|
||||
mod drm_helpers;
|
||||
pub mod render;
|
||||
mod socket;
|
||||
mod surface;
|
||||
use device::*;
|
||||
pub(crate) use surface::Surface;
|
||||
|
|
@ -74,6 +78,7 @@ pub struct KmsState {
|
|||
libinput: Libinput,
|
||||
|
||||
pub syncobj_state: Option<DrmSyncobjState>,
|
||||
pub dmabuf_global: Option<DmabufGlobal>,
|
||||
}
|
||||
|
||||
pub struct KmsGuard<'a> {
|
||||
|
|
@ -131,6 +136,7 @@ pub fn init_backend(
|
|||
libinput: libinput_context,
|
||||
|
||||
syncobj_state: None,
|
||||
dmabuf_global: None,
|
||||
});
|
||||
|
||||
// manually add already present gpus
|
||||
|
|
@ -142,9 +148,10 @@ pub fn init_backend(
|
|||
}
|
||||
}
|
||||
|
||||
if let Err(err) = state.backend.kms().select_primary_gpu(dh) {
|
||||
if let Err(err) = state.select_primary_gpu(dh) {
|
||||
warn!("Failed to determine primary gpu: {}", err);
|
||||
}
|
||||
state.update_default_feedback();
|
||||
|
||||
if let Err(err) = state.refresh_output_config() {
|
||||
info!(
|
||||
|
|
@ -204,7 +211,7 @@ fn init_libinput(
|
|||
state.backend.kms().input_devices.remove(&*device.name());
|
||||
}
|
||||
|
||||
state.process_input_event(event);
|
||||
state.process_input_event(event, crate::input::InputBackendId::Normal);
|
||||
|
||||
for output in state.common.shell.read().outputs() {
|
||||
state.backend.kms().schedule_render(output);
|
||||
|
|
@ -317,11 +324,13 @@ fn init_udev(
|
|||
|
||||
{
|
||||
let backend = state.backend.kms();
|
||||
if matches!(event, UdevEvent::Added { .. } | UdevEvent::Removed { .. })
|
||||
&& backend.primary_node.read().unwrap().is_none()
|
||||
&& let Err(err) = state.backend.kms().select_primary_gpu(&dh)
|
||||
{
|
||||
warn!("Failed to determine a new primary gpu: {}", err);
|
||||
if matches!(event, UdevEvent::Added { .. } | UdevEvent::Removed { .. }) {
|
||||
if backend.primary_node.read().unwrap().is_none()
|
||||
&& let Err(err) = state.select_primary_gpu(&dh)
|
||||
{
|
||||
warn!("Failed to determine a new primary gpu: {}", err);
|
||||
}
|
||||
state.update_default_feedback();
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -465,50 +474,118 @@ impl State {
|
|||
device.drm.pause();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl KmsState {
|
||||
fn select_primary_gpu(&mut self, dh: &DisplayHandle) -> Result<()> {
|
||||
// We don't have to check the allow/blocklist here,
|
||||
// as any disallowed devices won't be in `self.drm_devices`.
|
||||
|
||||
let mut primary_node = self.primary_node.write().unwrap();
|
||||
let kms = self.backend.kms();
|
||||
let mut primary_node = kms.primary_node.write().unwrap();
|
||||
let _ = primary_node.take(); // if we error don't leave an old node in place
|
||||
*primary_node = determine_primary_gpu(&self.drm_devices, self.session.seat())?;
|
||||
*primary_node = determine_primary_gpu(&kms.drm_devices, kms.session.seat())?;
|
||||
|
||||
if let Some(node) = *primary_node {
|
||||
info!("Using {} as primary gpu for rendering.", node);
|
||||
self.software_renderer.take();
|
||||
} else if self.software_renderer.is_none() {
|
||||
kms.software_renderer.take();
|
||||
|
||||
// setup minimal feedback. We will update it in `update_default_feedback`
|
||||
let primary_formats = kms
|
||||
.drm_devices
|
||||
.values()
|
||||
.find(|dev| dev.inner.render_node == node)
|
||||
.unwrap()
|
||||
.texture_formats
|
||||
.clone();
|
||||
let feedback = DmabufFeedbackBuilder::new(node.dev_id(), primary_formats.clone())
|
||||
.build()
|
||||
.unwrap();
|
||||
|
||||
if let Some(global) = kms.dmabuf_global.as_ref() {
|
||||
self.common
|
||||
.dmabuf_state
|
||||
.set_default_feedback(global, &feedback);
|
||||
} else {
|
||||
let dmabuf_global = self
|
||||
.common
|
||||
.dmabuf_state
|
||||
.create_global_with_default_feedback::<State>(dh, &feedback);
|
||||
kms.dmabuf_global = Some(dmabuf_global);
|
||||
};
|
||||
|
||||
let device_path = node
|
||||
.dev_path_with_type(NodeType::Render)
|
||||
.or_else(|| node.dev_path())
|
||||
.ok_or(anyhow::anyhow!(
|
||||
"Could not determine path for gpu node: {}",
|
||||
node
|
||||
))?;
|
||||
|
||||
if let Some(drm) = self.common.wl_drm_state.as_mut() {
|
||||
drm.update_device(device_path, primary_formats);
|
||||
} else {
|
||||
self.common.wl_drm_state = Some(WlDrmState::new::<State>(
|
||||
dh,
|
||||
device_path,
|
||||
primary_formats,
|
||||
kms.dmabuf_global.as_ref().unwrap(),
|
||||
));
|
||||
}
|
||||
} else if kms.software_renderer.is_none() {
|
||||
info!("Failed to find a suitable gpu, using software renderingr");
|
||||
self.software_renderer = match software_renderer() {
|
||||
kms.software_renderer = match software_renderer() {
|
||||
Ok(renderer) => Some(renderer),
|
||||
Err(err) => {
|
||||
error!(?err, "Failed to initialize software EGL renderer.");
|
||||
None
|
||||
}
|
||||
};
|
||||
|
||||
if let Some(drm) = self.common.wl_drm_state.take() {
|
||||
remove_global_with_timer(dh, &self.common.event_loop_handle, drm.global().clone());
|
||||
}
|
||||
if let Some(global) = kms.dmabuf_global.take() {
|
||||
self.common
|
||||
.dmabuf_state
|
||||
.disable_global::<State>(dh, &global);
|
||||
let source = Timer::from_duration(Duration::from_secs(5));
|
||||
let res =
|
||||
self.common
|
||||
.event_loop_handle
|
||||
.insert_source(source, move |_, _, state| {
|
||||
state
|
||||
.common
|
||||
.dmabuf_state
|
||||
.destroy_global::<State>(&state.common.display_handle, global);
|
||||
TimeoutAction::Drop
|
||||
});
|
||||
if let Err(err) = res {
|
||||
tracing::error!(
|
||||
"failed to insert timer source to destroy output global: {}",
|
||||
err
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if !crate::utils::env::bool_var("COSMIC_DISABLE_SYNCOBJ").unwrap_or(false) {
|
||||
if let Some(primary_node) = primary_node
|
||||
.as_ref()
|
||||
.and_then(|node| node.node_with_type(NodeType::Primary).and_then(|x| x.ok()))
|
||||
&& let Some(device) = self.drm_devices.get(&primary_node)
|
||||
&& let Some(device) = kms.drm_devices.get(&primary_node)
|
||||
{
|
||||
let import_device = device.drm.device().device_fd().clone();
|
||||
if supports_syncobj_eventfd(&import_device) {
|
||||
if let Some(state) = self.syncobj_state.as_mut() {
|
||||
if let Some(state) = kms.syncobj_state.as_mut() {
|
||||
state.update_device(import_device);
|
||||
} else {
|
||||
let syncobj_state = DrmSyncobjState::new::<State>(dh, import_device);
|
||||
self.syncobj_state = Some(syncobj_state);
|
||||
kms.syncobj_state = Some(syncobj_state);
|
||||
}
|
||||
return Ok(());
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(old_state) = self.syncobj_state.take() {
|
||||
if let Some(old_state) = kms.syncobj_state.take() {
|
||||
dh.remove_global::<State>(old_state.into_global());
|
||||
}
|
||||
}
|
||||
|
|
@ -516,6 +593,45 @@ impl KmsState {
|
|||
Ok(())
|
||||
}
|
||||
|
||||
fn update_default_feedback(&mut self) {
|
||||
let kms = self.backend.kms();
|
||||
let primary_node = kms.primary_node.read().unwrap();
|
||||
if let Some(primary_node) = *primary_node {
|
||||
let primary_formats = kms
|
||||
.drm_devices
|
||||
.values()
|
||||
.find(|dev| dev.inner.render_node == primary_node)
|
||||
.unwrap()
|
||||
.texture_formats
|
||||
.clone();
|
||||
|
||||
let mut feedback =
|
||||
DmabufFeedbackBuilder::new(primary_node.dev_id(), primary_formats.clone());
|
||||
for dev in kms
|
||||
.drm_devices
|
||||
.values()
|
||||
.filter(|dev| dev.inner.render_node != primary_node)
|
||||
{
|
||||
feedback = feedback.add_preference_tranche(
|
||||
dev.inner.render_node.dev_id(),
|
||||
TrancheFlags::Sampling,
|
||||
dev.texture_formats.iter().cloned(),
|
||||
6..=6,
|
||||
);
|
||||
}
|
||||
|
||||
let default_feedback = feedback.build().unwrap();
|
||||
self.common.dmabuf_state.set_default_feedback(
|
||||
kms.dmabuf_global
|
||||
.as_ref()
|
||||
.expect("Primary node but no dmabuf global?"),
|
||||
&default_feedback,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl KmsState {
|
||||
pub fn switch_vt(&mut self, num: i32) -> Result<(), anyhow::Error> {
|
||||
self.session.change_vt(num).map_err(Into::into)
|
||||
}
|
||||
|
|
@ -523,29 +639,25 @@ impl KmsState {
|
|||
pub fn dmabuf_imported(
|
||||
&mut self,
|
||||
client: Option<Client>,
|
||||
global: &DmabufGlobal,
|
||||
_global: &DmabufGlobal,
|
||||
dmabuf: Dmabuf,
|
||||
) -> Result<DrmNode> {
|
||||
let device_node = dmabuf
|
||||
.node()
|
||||
.unwrap_or_else(|| self.primary_node.read().unwrap().unwrap());
|
||||
let mut device = self
|
||||
.drm_devices
|
||||
.values_mut()
|
||||
.find(|device| {
|
||||
device
|
||||
.socket
|
||||
.as_ref()
|
||||
.map(|s| &s.dmabuf_global == global)
|
||||
.unwrap_or(false)
|
||||
})
|
||||
.context("Couldn't find gpu for dmabuf global")?;
|
||||
.find(|dev| dev.inner.render_node == device_node)
|
||||
.ok_or(anyhow::anyhow!(
|
||||
"Unable to find device for node: {}",
|
||||
device_node
|
||||
))?;
|
||||
|
||||
// If device advertised to client doesn't support format/modifier, select
|
||||
// first device that does. This is needed for image-copy from
|
||||
// output/toplevel on a different node.
|
||||
//
|
||||
// TODO: After
|
||||
// https://gitlab.freedesktop.org/wayland/wayland-protocols/-/merge_requests/268,
|
||||
// only try the device specified explicitly by the client, if set.
|
||||
if !device.texture_formats.contains(&dmabuf.format()) {
|
||||
if dmabuf.node().is_none() && !device.texture_formats.contains(&dmabuf.format()) {
|
||||
device = self
|
||||
.drm_devices
|
||||
.values_mut()
|
||||
|
|
|
|||
|
|
@ -1006,12 +1006,16 @@ impl SurfaceThreadState {
|
|||
&self.shell.read(),
|
||||
);
|
||||
|
||||
// Acquiring a renderer can fail transiently when the underlying DRM
|
||||
// device is lost (e.g. after a GPU reset).
|
||||
let mut renderer = if render_node != self.target_node {
|
||||
self.api
|
||||
.renderer(&render_node, &self.target_node, compositor.format())
|
||||
.unwrap()
|
||||
.map_err(|err| anyhow::format_err!("Failed to create renderer: {:?}", err))?
|
||||
} else {
|
||||
self.api.single_renderer(&self.target_node).unwrap()
|
||||
self.api
|
||||
.single_renderer(&self.target_node)
|
||||
.map_err(|err| anyhow::format_err!("Failed to create renderer: {:?}", err))?
|
||||
};
|
||||
|
||||
self.timings.start_render(&self.clock);
|
||||
|
|
@ -1267,7 +1271,10 @@ impl SurfaceThreadState {
|
|||
})
|
||||
.context("Failed to draw to offscreen render target")?;
|
||||
|
||||
renderer = self.api.single_renderer(&self.target_node).unwrap();
|
||||
renderer = self
|
||||
.api
|
||||
.single_renderer(&self.target_node)
|
||||
.map_err(|err| anyhow::format_err!("Failed to create renderer: {:?}", err))?;
|
||||
|
||||
elements = postprocess_elements(
|
||||
&mut renderer,
|
||||
|
|
@ -1522,7 +1529,7 @@ fn get_surface_dmabuf_feedback(
|
|||
render_node: DrmNode,
|
||||
target_node: DrmNode,
|
||||
render_formats: FormatSet,
|
||||
_target_formats: FormatSet,
|
||||
target_formats: FormatSet,
|
||||
primary_plane_formats: FormatSet,
|
||||
overlay_plane_formats: Option<FormatSet>,
|
||||
) -> SurfaceDmabufFeedback {
|
||||
|
|
@ -1540,60 +1547,40 @@ fn get_surface_dmabuf_feedback(
|
|||
.cloned()
|
||||
.collect::<FormatSet>()
|
||||
});
|
||||
let builder = DmabufFeedbackBuilder::new(render_node.dev_id(), render_formats);
|
||||
|
||||
/*
|
||||
// Sadly no implementation would pick this up as a preferred render tranche,
|
||||
// where the combined formats would increase our chances of doing a dmabuf copy.
|
||||
// .. So we should probably not advertise this on the off-chance it actually triggers bugs.
|
||||
//
|
||||
let mut builder = DmabufFeedbackBuilder::new(render_node.dev_id(), render_formats.clone());
|
||||
|
||||
let combined_formats = render_formats.intersection(&target_formats).cloned().collect::<FormatSet>();
|
||||
if target_node != render_node.dev_id() && !combined_formats.is_empty() {
|
||||
builder = builder.add_preference_tranche(
|
||||
render_node.dev_id(),
|
||||
None,
|
||||
combined_formats,
|
||||
);
|
||||
};
|
||||
|
||||
// We also can't advertise scan out tranches for the actual display device,
|
||||
// as e.g. the nvidia driver might then send us dmabufs, that makes e.g. the iris hangs on import...
|
||||
if target_node != render_node.dev_id() && !combined_formats.is_empty() {
|
||||
if target_node != render_node {
|
||||
builder = builder.add_preference_tranche(
|
||||
target_node.dev_id(),
|
||||
Some(zwp_linux_dmabuf_feedback_v1::TrancheFlags::Scanout),
|
||||
combined_formats,
|
||||
zwp_linux_dmabuf_feedback_v1::TrancheFlags::Sampling,
|
||||
target_formats,
|
||||
6..=6,
|
||||
);
|
||||
};
|
||||
|
||||
// So no fun combinations, we gotta wait for dmabuf-v6
|
||||
*/
|
||||
|
||||
let render_feedback = builder.clone().build().unwrap();
|
||||
let primary_scanout_feedback = (target_node == render_node).then(|| {
|
||||
|
||||
let primary_scanout_feedback = builder
|
||||
.clone()
|
||||
.add_preference_tranche(
|
||||
target_node.dev_id(),
|
||||
zwp_linux_dmabuf_feedback_v1::TrancheFlags::Scanout,
|
||||
primary_plane_formats,
|
||||
4..=6,
|
||||
)
|
||||
.build()
|
||||
.unwrap();
|
||||
let overlay_scanout_feedback = overlay_plane_formats.map(|formats| {
|
||||
builder
|
||||
.clone()
|
||||
.add_preference_tranche(
|
||||
render_node.dev_id(),
|
||||
Some(zwp_linux_dmabuf_feedback_v1::TrancheFlags::Scanout),
|
||||
primary_plane_formats,
|
||||
target_node.dev_id(),
|
||||
zwp_linux_dmabuf_feedback_v1::TrancheFlags::Scanout,
|
||||
formats,
|
||||
4..=6,
|
||||
)
|
||||
.build()
|
||||
.unwrap()
|
||||
});
|
||||
let overlay_scanout_feedback = overlay_plane_formats
|
||||
.filter(|_| target_node == render_node)
|
||||
.map(|formats| {
|
||||
builder
|
||||
.add_preference_tranche(
|
||||
render_node.dev_id(),
|
||||
Some(zwp_linux_dmabuf_feedback_v1::TrancheFlags::Scanout),
|
||||
formats,
|
||||
)
|
||||
.build()
|
||||
.unwrap()
|
||||
});
|
||||
|
||||
SurfaceDmabufFeedback {
|
||||
render_feedback,
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@ use crate::{
|
|||
shell::{Devices, SeatExt},
|
||||
state::{BackendData, Common},
|
||||
utils::prelude::*,
|
||||
wayland::protocols::drm::WlDrmState,
|
||||
};
|
||||
use anyhow::{Context, Result, anyhow};
|
||||
use cosmic_comp_config::output::comp::{OutputConfig, TransformDef};
|
||||
|
|
@ -273,7 +274,7 @@ fn init_egl_client_side(
|
|||
.create_global_with_default_feedback::<State>(dh, &feedback);
|
||||
|
||||
let render_node = render_node.unwrap().unwrap();
|
||||
let _drm_global_id = state.common.wl_drm_state.create_global::<State>(
|
||||
state.common.wl_drm_state = Some(WlDrmState::new::<State>(
|
||||
dh,
|
||||
render_node
|
||||
.dev_path_with_type(NodeType::Render)
|
||||
|
|
@ -284,7 +285,7 @@ fn init_egl_client_side(
|
|||
))?,
|
||||
dmabuf_formats,
|
||||
&dmabuf_global,
|
||||
);
|
||||
));
|
||||
|
||||
info!("EGL hardware-acceleration enabled.");
|
||||
}
|
||||
|
|
@ -309,7 +310,9 @@ impl State {
|
|||
WinitEvent::Focus(true) => {
|
||||
for seat in self.common.shell.read().seats.iter() {
|
||||
let devices = seat.user_data().get::<Devices>().unwrap();
|
||||
if devices.has_device(&WinitVirtualDevice) {
|
||||
if devices
|
||||
.has_device(&WinitVirtualDevice, &crate::input::InputBackendId::Normal)
|
||||
{
|
||||
seat.set_active_output(&self.backend.winit().output);
|
||||
break;
|
||||
}
|
||||
|
|
@ -339,7 +342,9 @@ impl State {
|
|||
render_ping.ping();
|
||||
}
|
||||
WinitEvent::Redraw => render_ping.ping(),
|
||||
WinitEvent::Input(event) => self.process_input_event(event),
|
||||
WinitEvent::Input(event) => {
|
||||
self.process_input_event(event, crate::input::InputBackendId::Normal)
|
||||
}
|
||||
WinitEvent::CloseRequested => {
|
||||
self.common.should_stop = true;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@ use crate::{
|
|||
shell::{Devices, SeatExt},
|
||||
state::{BackendData, Common},
|
||||
utils::prelude::*,
|
||||
wayland::protocols::drm::WlDrmState,
|
||||
};
|
||||
use anyhow::{Context, Result, anyhow};
|
||||
use cosmic_comp_config::output::comp::OutputConfig;
|
||||
|
|
@ -497,7 +498,7 @@ where
|
|||
.common
|
||||
.dmabuf_state
|
||||
.create_global_with_default_feedback::<State>(dh, &default_feedback);
|
||||
let _drm_global_id = state.common.wl_drm_state.create_global::<State>(
|
||||
state.common.wl_drm_state = Some(WlDrmState::new::<State>(
|
||||
dh,
|
||||
render_node
|
||||
.dev_path_with_type(NodeType::Render)
|
||||
|
|
@ -508,7 +509,7 @@ where
|
|||
))?,
|
||||
renderer.dmabuf_formats(),
|
||||
&dmabuf_global,
|
||||
);
|
||||
));
|
||||
|
||||
info!("EGL hardware-acceleration enabled.");
|
||||
|
||||
|
|
@ -533,14 +534,14 @@ impl State {
|
|||
let device = event.device();
|
||||
for seat in self.common.shell.read().seats.iter() {
|
||||
let devices = seat.user_data().get::<Devices>().unwrap();
|
||||
if devices.has_device(&device) {
|
||||
if devices.has_device(&device, &crate::input::InputBackendId::Normal) {
|
||||
seat.set_active_output(&output);
|
||||
break;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
self.process_input_event(event);
|
||||
self.process_input_event(event, crate::input::InputBackendId::Normal);
|
||||
// TODO actually figure out the output
|
||||
for output in self.common.shell.read().outputs() {
|
||||
self.backend.x11().schedule_render(output);
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
// SPDX-License-Identifier: GPL-3.0-only
|
||||
|
||||
use crate::{
|
||||
input::InputBackendId,
|
||||
shell::Shell,
|
||||
state::{BackendData, State},
|
||||
utils::prelude::OutputExt,
|
||||
|
|
@ -820,6 +821,27 @@ 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" => {
|
||||
|
|
|
|||
|
|
@ -150,9 +150,10 @@ impl A11yKeyboardMonitorState {
|
|||
}
|
||||
|
||||
pub fn key_event(&self, modifiers: &ModifiersState, keysym: &KeysymHandle, state: KeyState) {
|
||||
let has_key_grab = self.has_key_grab(modifiers, keysym.modified_sym());
|
||||
let clients = self.clients.lock().unwrap();
|
||||
for (unique_name, client) in clients.0.iter() {
|
||||
if !client.watched && !self.has_key_grab(modifiers, keysym.modified_sym()) {
|
||||
if !client.watched && !has_key_grab {
|
||||
continue;
|
||||
}
|
||||
|
||||
|
|
|
|||
80
src/dbus/ei.rs
Normal file
80
src/dbus/ei.rs
Normal file
|
|
@ -0,0 +1,80 @@
|
|||
use std::{
|
||||
os::unix::net::UnixStream,
|
||||
sync::{Arc, Mutex},
|
||||
};
|
||||
|
||||
use smithay::reexports::calloop;
|
||||
use zbus::names::{UniqueName, WellKnownName};
|
||||
|
||||
use super::name_owners::NameOwners;
|
||||
|
||||
static ALLOWED_NAMES: &[WellKnownName] = &[
|
||||
WellKnownName::from_static_str_unchecked("org.freedesktop.impl.portal.desktop.cosmic"),
|
||||
WellKnownName::from_static_str_unchecked("com.system76.CosmicOSK"),
|
||||
];
|
||||
|
||||
/// Channel for handing the EI socketpair (and requested device types)
|
||||
/// It's `None` until the EI sender side has been set up
|
||||
type EiSender = Arc<Mutex<Option<calloop::channel::Sender<crate::libei::EiRequest>>>>;
|
||||
|
||||
struct Ei {
|
||||
ei_sender: EiSender,
|
||||
name_owners: NameOwners,
|
||||
}
|
||||
|
||||
impl Ei {
|
||||
async fn check_sender_allowed(&self, sender: &UniqueName<'_>) -> zbus::fdo::Result<()> {
|
||||
if self.name_owners.check_owner(sender, ALLOWED_NAMES).await {
|
||||
Ok(())
|
||||
} else {
|
||||
Err(zbus::fdo::Error::AccessDenied("Access denied".to_string()))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[zbus::interface(name = "com.system76.CosmicComp.Ei")]
|
||||
impl Ei {
|
||||
/// Create a new EI sender context
|
||||
async fn get_sender_socket(
|
||||
&self,
|
||||
device_types: u32,
|
||||
#[zbus(header)] header: zbus::message::Header<'_>,
|
||||
) -> zbus::fdo::Result<zbus::zvariant::OwnedFd> {
|
||||
if let Some(sender) = header.sender() {
|
||||
self.check_sender_allowed(sender).await?;
|
||||
}
|
||||
|
||||
let (comp_stream, client_stream) = UnixStream::pair().map_err(|err| {
|
||||
zbus::fdo::Error::Failed(format!("Failed to create socket pair: {err}"))
|
||||
})?;
|
||||
|
||||
{
|
||||
let guard = self.ei_sender.lock().unwrap();
|
||||
let sender = guard
|
||||
.as_ref()
|
||||
.ok_or_else(|| zbus::fdo::Error::Failed("EI sender not available".to_string()))?;
|
||||
sender.send((comp_stream, device_types)).map_err(|err| {
|
||||
zbus::fdo::Error::Failed(format!("Failed to hand off EI socket: {err}"))
|
||||
})?;
|
||||
}
|
||||
|
||||
Ok(std::os::fd::OwnedFd::from(client_stream).into())
|
||||
}
|
||||
}
|
||||
|
||||
/// Register the `com.system76.CosmicComp.Ei` interface on the shared session connection.
|
||||
pub async fn init(
|
||||
conn: &zbus::Connection,
|
||||
name_owners: &NameOwners,
|
||||
ei_sender: EiSender,
|
||||
) -> zbus::Result<()> {
|
||||
let ei = Ei {
|
||||
ei_sender,
|
||||
name_owners: name_owners.clone(),
|
||||
};
|
||||
conn.object_server()
|
||||
.at("/com/system76/CosmicComp/Ei", ei)
|
||||
.await?;
|
||||
conn.request_name("com.system76.CosmicComp").await?;
|
||||
Ok(())
|
||||
}
|
||||
|
|
@ -9,11 +9,13 @@ use std::{
|
|||
cell::{RefCell, RefMut},
|
||||
collections::HashMap,
|
||||
rc::Rc,
|
||||
sync::{Arc, Mutex},
|
||||
};
|
||||
use tracing::{error, warn};
|
||||
|
||||
pub mod a11y_keyboard_monitor;
|
||||
use a11y_keyboard_monitor::A11yKeyboardMonitorState;
|
||||
pub mod ei;
|
||||
#[cfg(feature = "logind")]
|
||||
pub mod logind;
|
||||
mod name_owners;
|
||||
|
|
@ -29,6 +31,7 @@ struct DBusStateInner {
|
|||
session_conn: zbus::Result<zbus::Connection>,
|
||||
system_conn: zbus::Result<zbus::Connection>,
|
||||
a11y_keyboard_monitor: RefCell<Option<a11y_keyboard_monitor::A11yKeyboardMonitorState>>,
|
||||
ei_sender: Arc<Mutex<Option<calloop::channel::Sender<crate::libei::EiRequest>>>>,
|
||||
}
|
||||
|
||||
impl DBusState {
|
||||
|
|
@ -42,6 +45,7 @@ impl DBusState {
|
|||
session_conn,
|
||||
system_conn,
|
||||
a11y_keyboard_monitor: RefCell::new(None),
|
||||
ei_sender: Arc::new(Mutex::new(None)),
|
||||
}));
|
||||
evlh.insert_source(source, |_, _, _| {}).unwrap();
|
||||
let state_clone = state.clone();
|
||||
|
|
@ -65,6 +69,10 @@ impl DBusState {
|
|||
RefMut::filter_map(self.0.a11y_keyboard_monitor.borrow_mut(), |x| x.as_mut()).ok()
|
||||
}
|
||||
|
||||
pub fn set_ei_sender(&self, sender: calloop::channel::Sender<crate::libei::EiRequest>) {
|
||||
*self.0.ei_sender.lock().unwrap() = Some(sender);
|
||||
}
|
||||
|
||||
// TODO Lazy async init when we don't have anything blocking main thread
|
||||
async fn session_conn(&self) -> zbus::Result<&zbus::Connection> {
|
||||
self.0.session_conn.as_ref().map_err(|err| err.clone())
|
||||
|
|
@ -85,6 +93,7 @@ async fn init_session(state: &DBusState) -> zbus::Result<()> {
|
|||
let a11y_keyboard_monitor_state =
|
||||
A11yKeyboardMonitorState::new(conn, &name_owners, &state.0.executor).await?;
|
||||
*state.0.a11y_keyboard_monitor.borrow_mut() = Some(a11y_keyboard_monitor_state);
|
||||
ei::init(conn, &name_owners, state.0.ei_sender.clone()).await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@
|
|||
|
||||
use crate::{
|
||||
config::{Action, PrivateAction},
|
||||
input::InputBackendId,
|
||||
shell::{
|
||||
FocusResult, InvalidWorkspaceIndex, MoveResult, SeatExt, Trigger, WorkspaceDelta,
|
||||
focus::{FocusTarget, target::KeyboardFocusTarget},
|
||||
|
|
@ -39,6 +40,7 @@ impl State {
|
|||
pub fn handle_action(
|
||||
&mut self,
|
||||
action: Action,
|
||||
backend_id: &InputBackendId,
|
||||
seat: &Seat<State>,
|
||||
serial: Serial,
|
||||
time: u32,
|
||||
|
|
@ -69,7 +71,7 @@ impl State {
|
|||
Action::Shortcut(action) => {
|
||||
let propagate = propagate_by_default(&action);
|
||||
self.handle_shortcut_action(
|
||||
action, seat, serial, time, pattern, direction, propagate,
|
||||
action, backend_id, seat, serial, time, pattern, direction, propagate,
|
||||
)
|
||||
}
|
||||
Action::Private(PrivateAction::Escape) => {
|
||||
|
|
@ -143,6 +145,7 @@ impl State {
|
|||
pub fn handle_shortcut_action(
|
||||
&mut self,
|
||||
action: shortcuts::Action,
|
||||
backend_id: &InputBackendId,
|
||||
seat: &Seat<State>,
|
||||
serial: Serial,
|
||||
time: u32,
|
||||
|
|
@ -232,6 +235,7 @@ impl State {
|
|||
{
|
||||
self.handle_shortcut_action(
|
||||
Action::SwitchOutput(inferred),
|
||||
backend_id,
|
||||
seat,
|
||||
serial,
|
||||
time,
|
||||
|
|
@ -271,6 +275,7 @@ impl State {
|
|||
{
|
||||
self.handle_shortcut_action(
|
||||
Action::SwitchOutput(inferred),
|
||||
backend_id,
|
||||
seat,
|
||||
serial,
|
||||
time,
|
||||
|
|
@ -392,6 +397,7 @@ impl State {
|
|||
} else {
|
||||
Action::SendToOutput(inferred)
|
||||
},
|
||||
backend_id,
|
||||
seat,
|
||||
serial,
|
||||
time,
|
||||
|
|
@ -417,6 +423,7 @@ impl State {
|
|||
} else {
|
||||
Action::SendToWorkspace(1)
|
||||
},
|
||||
backend_id,
|
||||
seat,
|
||||
serial,
|
||||
time,
|
||||
|
|
@ -483,6 +490,7 @@ impl State {
|
|||
} else {
|
||||
Action::SendToOutput(inferred)
|
||||
},
|
||||
backend_id,
|
||||
seat,
|
||||
serial,
|
||||
time,
|
||||
|
|
@ -508,6 +516,7 @@ impl State {
|
|||
} else {
|
||||
Action::SendToLastWorkspace
|
||||
},
|
||||
backend_id,
|
||||
seat,
|
||||
serial,
|
||||
time,
|
||||
|
|
@ -532,7 +541,9 @@ impl State {
|
|||
if propagate
|
||||
&& let Some((serial, prev_output, prev_idx)) =
|
||||
shell.previous_workspace_idx.take()
|
||||
&& seat.last_modifier_change().is_some_and(|s| s == serial)
|
||||
&& seat
|
||||
.last_modifier_change_for(backend_id)
|
||||
.is_some_and(|s| s == serial)
|
||||
&& prev_output == current_output
|
||||
{
|
||||
let _ = shell.activate(
|
||||
|
|
@ -705,6 +716,7 @@ impl State {
|
|||
if res.is_ok() {
|
||||
self.handle_shortcut_action(
|
||||
Action::SwitchOutput(direction),
|
||||
backend_id,
|
||||
seat,
|
||||
serial,
|
||||
time,
|
||||
|
|
@ -741,7 +753,8 @@ impl State {
|
|||
};
|
||||
|
||||
if let Some(direction) = dir {
|
||||
if let Some(last_mod_serial) = seat.last_modifier_change() {
|
||||
if let Some(last_mod_serial) = seat.last_modifier_change_for(backend_id)
|
||||
{
|
||||
let mut shell = self.common.shell.write();
|
||||
if !shell
|
||||
.previous_workspace_idx
|
||||
|
|
@ -776,6 +789,7 @@ impl State {
|
|||
|
||||
self.handle_shortcut_action(
|
||||
action,
|
||||
backend_id,
|
||||
seat,
|
||||
serial,
|
||||
time,
|
||||
|
|
@ -800,7 +814,7 @@ impl State {
|
|||
.move_current_element(direction, seat);
|
||||
match res {
|
||||
MoveResult::MoveFurther(_move_further) => {
|
||||
if let Some(last_mod_serial) = seat.last_modifier_change() {
|
||||
if let Some(last_mod_serial) = seat.last_modifier_change_for(backend_id) {
|
||||
let mut shell = self.common.shell.write();
|
||||
if !shell
|
||||
.previous_workspace_idx
|
||||
|
|
@ -834,6 +848,7 @@ impl State {
|
|||
|
||||
self.handle_shortcut_action(
|
||||
action,
|
||||
backend_id,
|
||||
seat,
|
||||
serial,
|
||||
time,
|
||||
|
|
|
|||
953
src/input/mod.rs
953
src/input/mod.rs
File diff suppressed because it is too large
Load diff
41
src/lib.rs
41
src/lib.rs
|
|
@ -16,7 +16,7 @@ use smithay::{
|
|||
};
|
||||
|
||||
use anyhow::{Context, Result};
|
||||
use state::{LastRefresh, State};
|
||||
use state::{BackendData, LastRefresh, State};
|
||||
use std::{
|
||||
env,
|
||||
ffi::OsString,
|
||||
|
|
@ -43,6 +43,7 @@ pub mod dbus;
|
|||
pub mod debug;
|
||||
pub mod hooks;
|
||||
pub mod input;
|
||||
pub mod libei;
|
||||
mod logger;
|
||||
pub mod session;
|
||||
pub mod shell;
|
||||
|
|
@ -176,6 +177,10 @@ pub fn run(hooks: crate::hooks::Hooks) -> Result<(), Box<dyn Error>> {
|
|||
with_xwayland,
|
||||
kiosk_command,
|
||||
);
|
||||
// Set up the libei sender side before the backend spawns Xwayland.
|
||||
let ei_sender = libei::setup_ei(&event_loop.handle());
|
||||
state.common.dbus_state.set_ei_sender(ei_sender);
|
||||
|
||||
// init backend
|
||||
backend::init_backend_auto(&display, &mut event_loop, &mut state)?;
|
||||
|
||||
|
|
@ -222,19 +227,17 @@ pub fn run(hooks: crate::hooks::Hooks) -> Result<(), Box<dyn Error>> {
|
|||
// Kiosk child exited with status
|
||||
Ok(Some(exit_status)) => {
|
||||
info!("Command exited with status {:?}", exit_status);
|
||||
match exit_status.code() {
|
||||
// Exiting with the same status as the kiosk child
|
||||
Some(code) => process::exit(code),
|
||||
// The kiosk child exited with signal, exiting with error
|
||||
None => process::exit(1),
|
||||
}
|
||||
// Stop cleanly so surface threads are joined before exit() (signal -> 1).
|
||||
state.common.kiosk_exit_code = Some(exit_status.code().unwrap_or(1));
|
||||
state.common.should_stop = true;
|
||||
}
|
||||
// Command still running
|
||||
Ok(None) => {}
|
||||
// Kiosk child disappeared, exiting with error
|
||||
Err(err) => {
|
||||
warn!(?err, "Failed to wait for command");
|
||||
process::exit(1);
|
||||
state.common.kiosk_exit_code = Some(1);
|
||||
state.common.should_stop = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -245,10 +248,32 @@ pub fn run(hooks: crate::hooks::Hooks) -> Result<(), Box<dyn Error>> {
|
|||
let _ = child.kill();
|
||||
}
|
||||
|
||||
let kiosk_exit_code = state.common.kiosk_exit_code;
|
||||
|
||||
// Join surface threads before exit() so no thread is mid-eglCreateSync when
|
||||
// Mesa's atexit handlers run and corrupt the heap (issue #2375). Safe here
|
||||
// because the event loop has stopped; an unconditional join in Surface::Drop
|
||||
// would instead deadlock against apply_config_for_outputs.
|
||||
if let BackendData::Kms(kms) = &mut state.backend {
|
||||
// Release master first so the surface drop path skips its blocking commit.
|
||||
for device in kms.drm_devices.values_mut() {
|
||||
device.drm.pause();
|
||||
}
|
||||
for device in kms.drm_devices.values_mut() {
|
||||
for (_, surface) in device.inner.surfaces.drain() {
|
||||
surface.drop_and_join();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// drop eventloop & state before logger
|
||||
std::mem::drop(event_loop);
|
||||
std::mem::drop(state);
|
||||
|
||||
if let Some(code) = kiosk_exit_code {
|
||||
process::exit(code);
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
|
|
|
|||
228
src/libei.rs
Normal file
228
src/libei.rs
Normal file
|
|
@ -0,0 +1,228 @@
|
|||
use std::os::unix::net::UnixStream;
|
||||
|
||||
use reis::eis;
|
||||
use smithay::reexports::reis;
|
||||
|
||||
use smithay::backend::libei::{EiInput, EiInputEvent, EiRegion};
|
||||
use smithay::input::keyboard::Keysym;
|
||||
use smithay::reexports::calloop;
|
||||
use smithay::wayland::input_method::InputMethodSeat;
|
||||
use smithay::wayland::text_input::TextInputSeat;
|
||||
|
||||
use crate::config::xkb_config_to_wl;
|
||||
use crate::input::InputBackendId;
|
||||
use crate::state::{BackendData, State};
|
||||
use crate::utils::{geometry::RectGlobalExt, prelude::OutputExt};
|
||||
|
||||
// Requested device types for an EI connection, mirroring the XDG RemoteDesktop portal `DeviceType` bitmask
|
||||
const DEVICE_TYPE_KEYBOARD: u32 = 1;
|
||||
const DEVICE_TYPE_POINTER: u32 = 2;
|
||||
const DEVICE_TYPE_TOUCHSCREEN: u32 = 4;
|
||||
|
||||
// Name of the EI absolute-pointer device. Shared so the connect path and the
|
||||
// re-advertise-on-output-change path recreate the same device.
|
||||
const ABSOLUTE_POINTER_NAME: &str = "virtual absolute pointer";
|
||||
|
||||
pub type EiRequest = (UnixStream, u32);
|
||||
|
||||
/// Build the regions advertised on the EI absolute devices (absolute pointer and touch) for the
|
||||
/// output layout: one region per output, each at its **global logical** offset with
|
||||
/// its logical size and scale.
|
||||
pub fn absolute_regions(state: &State) -> Vec<EiRegion> {
|
||||
let shell = state.common.shell.read();
|
||||
shell
|
||||
.outputs()
|
||||
.map(|output| {
|
||||
let scale = output.current_scale().fractional_scale();
|
||||
EiRegion {
|
||||
// Keep the signed logical rect
|
||||
// the u32 clamp happens at the `ei_device.region` in smithay.
|
||||
rect: output.geometry().as_logical(),
|
||||
scale: scale as f32,
|
||||
// Tie the region to its output so a client can correlate it with the
|
||||
// matching screencast stream (the portal advertises the same name).
|
||||
mapping_id: Some(output.name()),
|
||||
}
|
||||
})
|
||||
.collect()
|
||||
}
|
||||
|
||||
/// Re-advertise the coordinate regions on every active EI seat's absolute devices.
|
||||
pub fn refresh_absolute_pointer_regions(state: &State) {
|
||||
if state.common.ei_seats.is_empty() {
|
||||
return;
|
||||
}
|
||||
let regions = absolute_regions(state);
|
||||
for seat in state.common.ei_seats.values() {
|
||||
seat.update_regions(®ions);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn setup_ei(
|
||||
handle: &calloop::LoopHandle<'static, State>,
|
||||
) -> calloop::channel::Sender<EiRequest> {
|
||||
let (sender, channel) = calloop::channel::channel::<EiRequest>();
|
||||
let handle_clone = handle.clone();
|
||||
handle
|
||||
.insert_source(channel, move |event, _, _| {
|
||||
let calloop::channel::Event::Msg((stream, device_types)) = event else {
|
||||
return;
|
||||
};
|
||||
let context = match eis::Context::new(stream) {
|
||||
Ok(context) => context,
|
||||
Err(err) => {
|
||||
tracing::error!("Failed to create EI context: {}", err);
|
||||
return;
|
||||
}
|
||||
};
|
||||
let source = EiInput::new(context);
|
||||
if let Err(err) =
|
||||
handle_clone.insert_source(source, move |event, connection, data| match event {
|
||||
EiInputEvent::Connected => {
|
||||
let conn = connection.eis_connection().clone();
|
||||
let seat = connection.add_seat("default");
|
||||
let wants_keyboard = device_types & DEVICE_TYPE_KEYBOARD != 0;
|
||||
if wants_keyboard {
|
||||
let conf = data.common.config.xkb_config();
|
||||
// The ei_keyboard device is given the compositor keymap; its key
|
||||
// events feed the shared seat like any other keyboard.
|
||||
let _ = seat.add_keyboard("virtual keyboard", xkb_config_to_wl(&conf));
|
||||
// The text device lets clients inject keysyms/utf8 directly, delivered
|
||||
seat.add_text("virtual text");
|
||||
}
|
||||
if device_types & DEVICE_TYPE_POINTER != 0 {
|
||||
seat.add_pointer("virtual pointer");
|
||||
let regions = absolute_regions(data);
|
||||
seat.add_pointer_absolute(ABSOLUTE_POINTER_NAME, ®ions);
|
||||
}
|
||||
if device_types & DEVICE_TYPE_TOUCHSCREEN != 0 {
|
||||
let regions = absolute_regions(data);
|
||||
seat.add_touch("virtual touch", ®ions);
|
||||
}
|
||||
// Kb-capable connections get a shared-seat source so their `ei_keyboard`
|
||||
// key events feed the seat keyboard tracking.
|
||||
if wants_keyboard {
|
||||
data.common.ei_keyboard_source.insert(
|
||||
conn.clone(),
|
||||
smithay::input::keyboard::KeyboardSource::new_auxiliary(),
|
||||
);
|
||||
}
|
||||
// Track the seat for every connection
|
||||
data.common.ei_seats.insert(conn, seat);
|
||||
data.update_ei_input_method();
|
||||
}
|
||||
EiInputEvent::Disconnected => {
|
||||
let conn = connection.eis_connection().clone();
|
||||
let backend_id = InputBackendId::Ei(conn.clone());
|
||||
// Release any keys/modifiers and pointer buttons this remote still holds
|
||||
data.release_ei_keyboard(&conn);
|
||||
data.release_ei_pointer(&conn);
|
||||
data.clear_input_source_state(&backend_id);
|
||||
data.common.ei_seats.remove(&conn);
|
||||
data.common.ei_keyboard_source.remove(&conn);
|
||||
data.common.ei_pointer_buttons.remove(&conn);
|
||||
data.update_ei_input_method();
|
||||
// Notify the remaining libei clients of the now-cleared modifier state
|
||||
let seat = data.common.shell.read().seats.last_active().clone();
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -838,10 +838,7 @@ impl CosmicSurface {
|
|||
self.0
|
||||
.send_dmabuf_feedback(output, primary_scan_out_output, |_, data| {
|
||||
if is_fullscreen {
|
||||
feedback
|
||||
.primary_scanout_feedback
|
||||
.as_ref()
|
||||
.unwrap_or(&feedback.render_feedback)
|
||||
&feedback.primary_scanout_feedback
|
||||
} else if frame_time_filter_fn(data) == Kind::ScanoutCandidate {
|
||||
feedback
|
||||
.overlay_scanout_feedback
|
||||
|
|
|
|||
|
|
@ -212,6 +212,29 @@ impl Shell {
|
|||
state.common.shell.write().update_active();
|
||||
}
|
||||
|
||||
// We suppress Element(X) to Fullscreen(X) transition to avoid
|
||||
// loss of focus by the X window when having a transition to fullscreen
|
||||
// but in the case of X11 unmap/map the leave/enter needs to happen for the X11
|
||||
// internal state to be focused on the window
|
||||
pub fn set_focus_on_x11_map(
|
||||
state: &mut State,
|
||||
target: &KeyboardFocusTarget,
|
||||
seat: &Seat<State>,
|
||||
update_cursor: bool,
|
||||
) {
|
||||
let need_reset = seat
|
||||
.get_keyboard()
|
||||
.and_then(|keyboard| keyboard.current_focus())
|
||||
.and_then(|current| current.x11_surface())
|
||||
.is_some_and(|current| Some(current) == target.x11_surface());
|
||||
|
||||
if need_reset {
|
||||
update_focus_state(seat, None, state, None, false);
|
||||
}
|
||||
|
||||
Shell::set_focus(state, Some(target), seat, None, update_cursor);
|
||||
}
|
||||
|
||||
pub fn append_focus_stack(&mut self, target: impl Into<FocusTarget>, seat: &Seat<State>) {
|
||||
let target = target.into();
|
||||
if target.is_minimized() {
|
||||
|
|
|
|||
|
|
@ -326,7 +326,7 @@ impl KeyboardFocusTarget {
|
|||
}
|
||||
}
|
||||
|
||||
fn x11_surface(&self) -> Option<X11Surface> {
|
||||
pub fn x11_surface(&self) -> Option<X11Surface> {
|
||||
match self {
|
||||
KeyboardFocusTarget::Element(mapped) => mapped.active_window().x11_surface().cloned(),
|
||||
KeyboardFocusTarget::Fullscreen(surface) => surface.x11_surface().cloned(),
|
||||
|
|
|
|||
|
|
@ -13,6 +13,7 @@ use smithay::{
|
|||
|
||||
use crate::{
|
||||
config::key_bindings::cosmic_modifiers_from_smithay,
|
||||
input::InputBackendId,
|
||||
shell::{Trigger, layout::tiling::NodeDesc},
|
||||
state::State,
|
||||
};
|
||||
|
|
@ -73,6 +74,7 @@ impl KeyboardGrab<State> for SwapWindowGrab {
|
|||
|
||||
data.handle_shortcut_action(
|
||||
shortcuts::Action::Focus(direction),
|
||||
&InputBackendId::Normal,
|
||||
&self.seat,
|
||||
serial,
|
||||
time,
|
||||
|
|
|
|||
|
|
@ -5024,6 +5024,20 @@ impl Shell {
|
|||
let mut output_presentation_feedback = OutputPresentationFeedback::new(output);
|
||||
|
||||
if let Some(active) = self.active_space(output) {
|
||||
for fs in active.get_fullscreen_surfaces() {
|
||||
fs.surface.take_presentation_feedback(
|
||||
&mut output_presentation_feedback,
|
||||
surface_primary_scanout_output,
|
||||
|surface, _| {
|
||||
surface_presentation_feedback_flags_from_states(
|
||||
surface,
|
||||
None,
|
||||
render_element_states,
|
||||
)
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
active.mapped().for_each(|mapped| {
|
||||
mapped.active_window().take_presentation_feedback(
|
||||
&mut output_presentation_feedback,
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@ use std::{any::Any, cell::RefCell, collections::HashMap, sync::Mutex};
|
|||
use crate::{
|
||||
backend::render::cursor::CursorState,
|
||||
config::{Config, xkb_config_to_wl},
|
||||
input::{ModifiersShortcutQueue, SupressedButtons, SupressedKeys},
|
||||
input::{InputBackendId, ModifiersShortcutQueue, SupressedButtons, SupressedKeys},
|
||||
state::State,
|
||||
};
|
||||
use smithay::{
|
||||
|
|
@ -82,12 +82,25 @@ impl Seats {
|
|||
self.last_active = Some(seat.clone());
|
||||
}
|
||||
|
||||
pub fn for_device<D: Device>(&self, device: &D) -> Option<&Seat<State>> {
|
||||
self.iter().find(|seat| {
|
||||
let userdata = seat.user_data();
|
||||
let devices = userdata.get::<Devices>().unwrap();
|
||||
devices.has_device(device)
|
||||
})
|
||||
pub fn for_device<D: Device>(
|
||||
&self,
|
||||
device: &D,
|
||||
backend_id: &InputBackendId,
|
||||
) -> Option<&Seat<State>> {
|
||||
self.iter()
|
||||
.find(|seat| {
|
||||
let userdata = seat.user_data();
|
||||
let devices = userdata.get::<Devices>().unwrap();
|
||||
devices.has_device(device, backend_id)
|
||||
})
|
||||
.or_else(|| {
|
||||
// EI devices can be transiently unregistered while the compositor recreates
|
||||
// the absolute-pointer device (e.g. on a scale/geometry change), which would
|
||||
// otherwise drop all pointer/touch input until the client re-binds it. EI is
|
||||
// single-seat, so fall back to the active seat here, matching the EI keyboard
|
||||
// path, which always targets the active seat.
|
||||
matches!(backend_id, InputBackendId::Ei(_)).then(|| self.last_active())
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -96,6 +109,7 @@ impl Devices {
|
|||
&self,
|
||||
device: &D,
|
||||
led_state: LedState,
|
||||
backend_id: &InputBackendId,
|
||||
) -> Vec<DeviceCapability> {
|
||||
let id = device.id();
|
||||
let mut map = self.capabilities.borrow_mut();
|
||||
|
|
@ -113,7 +127,7 @@ impl Devices {
|
|||
.cloned()
|
||||
.filter(|c| map.values().flatten().all(|has| *c != *has))
|
||||
.collect::<Vec<_>>();
|
||||
map.insert(id, caps);
|
||||
map.insert((backend_id.clone(), id), caps);
|
||||
|
||||
if device.has_capability(DeviceCapability::Keyboard)
|
||||
&& let Some(device) = <dyn Any>::downcast_ref::<InputDevice>(device)
|
||||
|
|
@ -126,11 +140,18 @@ impl Devices {
|
|||
new_caps
|
||||
}
|
||||
|
||||
pub fn has_device<D: Device>(&self, device: &D) -> bool {
|
||||
self.capabilities.borrow().contains_key(&device.id())
|
||||
/// Whether the given backend's device with this id is registered on the seat.
|
||||
pub fn has_device<D: Device>(&self, device: &D, backend_id: &InputBackendId) -> bool {
|
||||
self.capabilities
|
||||
.borrow()
|
||||
.contains_key(&(backend_id.clone(), device.id()))
|
||||
}
|
||||
|
||||
pub fn remove_device<D: Device>(&self, device: &D) -> Vec<DeviceCapability> {
|
||||
pub fn remove_device<D: Device>(
|
||||
&self,
|
||||
device: &D,
|
||||
backend_id: &InputBackendId,
|
||||
) -> Vec<DeviceCapability> {
|
||||
let id = device.id();
|
||||
|
||||
let mut keyboards = self.keyboards.borrow_mut();
|
||||
|
|
@ -139,7 +160,7 @@ impl Devices {
|
|||
}
|
||||
|
||||
let mut map = self.capabilities.borrow_mut();
|
||||
map.remove(&id)
|
||||
map.remove(&(backend_id.clone(), id))
|
||||
.unwrap_or_default()
|
||||
.into_iter()
|
||||
.filter(|c| map.values().flatten().all(|has| *c != *has))
|
||||
|
|
@ -155,7 +176,8 @@ impl Devices {
|
|||
|
||||
#[derive(Default)]
|
||||
pub struct Devices {
|
||||
capabilities: RefCell<HashMap<String, Vec<DeviceCapability>>>,
|
||||
// Keyed by `(backend, device_id)`
|
||||
capabilities: RefCell<HashMap<(InputBackendId, String), Vec<DeviceCapability>>>,
|
||||
// Used for updating keyboard leds on kms backend
|
||||
keyboards: RefCell<Vec<InputDevice>>,
|
||||
}
|
||||
|
|
@ -185,7 +207,7 @@ struct FocusedOutput(pub Mutex<Option<Output>>);
|
|||
pub struct PointerConstraintHint(pub Mutex<Option<(WlSurface, Point<f64, Logical>)>>);
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct LastModifierChange(pub Mutex<Option<Serial>>);
|
||||
pub struct LastModifierChange(pub Mutex<(HashMap<InputBackendId, Serial>, Option<Serial>)>);
|
||||
|
||||
pub fn create_seat(
|
||||
dh: &DisplayHandle,
|
||||
|
|
@ -265,6 +287,9 @@ pub trait SeatExt {
|
|||
fn supressed_buttons(&self) -> &SupressedButtons;
|
||||
fn modifiers_shortcut_queue(&self) -> &ModifiersShortcutQueue;
|
||||
fn last_modifier_change(&self) -> Option<Serial>;
|
||||
fn last_modifier_change_for(&self, backend_id: &InputBackendId) -> Option<Serial>;
|
||||
fn set_last_modifier_change(&self, backend_id: &InputBackendId, serial: Serial);
|
||||
fn clear_last_modifier_change(&self, backend_id: &InputBackendId);
|
||||
fn pointer_constraint_hint(&self) -> Option<(WlSurface, Point<f64, Logical>)>;
|
||||
fn set_pointer_constraint_hint(&self, hint: Option<(WlSurface, Point<f64, Logical>)>);
|
||||
|
||||
|
|
@ -343,13 +368,48 @@ impl SeatExt for Seat<State> {
|
|||
}
|
||||
|
||||
fn last_modifier_change(&self) -> Option<Serial> {
|
||||
*self
|
||||
.user_data()
|
||||
self.user_data()
|
||||
.get::<LastModifierChange>()
|
||||
.unwrap()
|
||||
.0
|
||||
.lock()
|
||||
.unwrap()
|
||||
.1
|
||||
}
|
||||
|
||||
fn last_modifier_change_for(&self, backend_id: &InputBackendId) -> Option<Serial> {
|
||||
self.user_data()
|
||||
.get::<LastModifierChange>()
|
||||
.unwrap()
|
||||
.0
|
||||
.lock()
|
||||
.unwrap()
|
||||
.0
|
||||
.get(backend_id)
|
||||
.copied()
|
||||
}
|
||||
|
||||
fn set_last_modifier_change(&self, backend_id: &InputBackendId, serial: Serial) {
|
||||
let mut guard = self
|
||||
.user_data()
|
||||
.get::<LastModifierChange>()
|
||||
.unwrap()
|
||||
.0
|
||||
.lock()
|
||||
.unwrap();
|
||||
guard.0.insert(backend_id.clone(), serial);
|
||||
guard.1 = Some(serial);
|
||||
}
|
||||
|
||||
fn clear_last_modifier_change(&self, backend_id: &InputBackendId) {
|
||||
self.user_data()
|
||||
.get::<LastModifierChange>()
|
||||
.unwrap()
|
||||
.0
|
||||
.lock()
|
||||
.unwrap()
|
||||
.0
|
||||
.remove(backend_id);
|
||||
}
|
||||
|
||||
fn pointer_constraint_hint(&self) -> Option<(WlSurface, Point<f64, Logical>)> {
|
||||
|
|
|
|||
45
src/state.rs
45
src/state.rs
|
|
@ -244,9 +244,32 @@ pub struct Common {
|
|||
pub clock: Clock<Monotonic>,
|
||||
pub startup_done: Arc<AtomicBool>,
|
||||
pub should_stop: bool,
|
||||
pub kiosk_exit_code: Option<i32>,
|
||||
|
||||
pub gesture_state: Option<GestureState>,
|
||||
|
||||
/// Active libei sender seats, keyed by their `eis` connection. Tracked so their virtual
|
||||
/// keyboards can be re-created when the keyboard configuration changes at runtime.
|
||||
pub ei_seats: std::collections::HashMap<
|
||||
smithay::reexports::reis::eis::Connection,
|
||||
smithay::backend::libei::EiInputSeat,
|
||||
>,
|
||||
|
||||
/// The shared-seat [`KeyboardSource`] assigned to each libei connection, so its
|
||||
/// `ei_keyboard` key events feed the seat keyboard with independent per-source hold
|
||||
/// tracking (and can be released together on disconnect). Keyed by connection.
|
||||
pub ei_keyboard_source: std::collections::HashMap<
|
||||
smithay::reexports::reis::eis::Connection,
|
||||
smithay::input::keyboard::KeyboardSource,
|
||||
>,
|
||||
|
||||
/// Pointer buttons currently held by each libei connection, so they can be released when the
|
||||
/// connection drops
|
||||
pub ei_pointer_buttons: std::collections::HashMap<
|
||||
smithay::reexports::reis::eis::Connection,
|
||||
std::collections::HashSet<u32>,
|
||||
>,
|
||||
|
||||
pub kiosk_child: Option<Child>,
|
||||
pub theme: cosmic::Theme,
|
||||
|
||||
|
|
@ -275,7 +298,7 @@ pub struct Common {
|
|||
pub idle_inhibiting_surfaces: HashSet<WlSurface>,
|
||||
pub shm_state: ShmState,
|
||||
pub cursor_shape_manager_state: CursorShapeManagerState,
|
||||
pub wl_drm_state: WlDrmState<Option<DrmNode>>,
|
||||
pub wl_drm_state: Option<WlDrmState<Option<DrmNode>>>,
|
||||
pub viewporter_state: ViewporterState,
|
||||
pub kde_decoration_state: KdeDecorationState,
|
||||
pub xdg_decoration_state: XdgDecorationState,
|
||||
|
|
@ -325,7 +348,7 @@ pub enum LockedBackend<'a> {
|
|||
pub struct SurfaceDmabufFeedback {
|
||||
pub render_feedback: DmabufFeedback,
|
||||
pub overlay_scanout_feedback: Option<DmabufFeedback>,
|
||||
pub primary_scanout_feedback: Option<DmabufFeedback>,
|
||||
pub primary_scanout_feedback: DmabufFeedback,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
|
|
@ -670,7 +693,7 @@ impl State {
|
|||
let cursor_shape_manager_state = CursorShapeManagerState::new::<State>(dh);
|
||||
let seat_state = SeatState::<Self>::new();
|
||||
let viewporter_state = ViewporterState::new::<Self>(dh);
|
||||
let wl_drm_state = WlDrmState::<Option<DrmNode>>::default();
|
||||
let wl_drm_state = None;
|
||||
let kde_decoration_state = KdeDecorationState::new::<Self>(dh, Mode::Client);
|
||||
let xdg_decoration_state = XdgDecorationState::new::<Self>(dh);
|
||||
let session_lock_manager_state =
|
||||
|
|
@ -754,7 +777,11 @@ impl State {
|
|||
clock,
|
||||
startup_done: Arc::new(AtomicBool::new(false)),
|
||||
should_stop: false,
|
||||
kiosk_exit_code: None,
|
||||
gesture_state: None,
|
||||
ei_seats: std::collections::HashMap::new(),
|
||||
ei_keyboard_source: std::collections::HashMap::new(),
|
||||
ei_pointer_buttons: std::collections::HashMap::new(),
|
||||
|
||||
kiosk_child: None,
|
||||
theme: cosmic::theme::system_preference(),
|
||||
|
|
@ -954,8 +981,11 @@ impl Common {
|
|||
);
|
||||
if let Some(output) = primary_scanout_output {
|
||||
with_fractional_scale(states, |fraction_scale| {
|
||||
fraction_scale
|
||||
.set_preferred_scale(output.current_scale().fractional_scale());
|
||||
// The 1.0 clamp is a workaround for Chromium
|
||||
// TODO: remove if Chromium ever gets fixed
|
||||
fraction_scale.set_preferred_scale(
|
||||
output.current_scale().fractional_scale().max(1.0),
|
||||
);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
@ -1061,10 +1091,7 @@ impl Common {
|
|||
surface,
|
||||
render_element_states,
|
||||
&feedback.render_feedback,
|
||||
feedback
|
||||
.primary_scanout_feedback
|
||||
.as_ref()
|
||||
.unwrap_or(&feedback.render_feedback),
|
||||
&feedback.primary_scanout_feedback,
|
||||
)
|
||||
},
|
||||
)
|
||||
|
|
|
|||
28
src/utils/global.rs
Normal file
28
src/utils/global.rs
Normal file
|
|
@ -0,0 +1,28 @@
|
|||
use std::time::Duration;
|
||||
|
||||
use calloop::{
|
||||
LoopHandle,
|
||||
timer::{TimeoutAction, Timer},
|
||||
};
|
||||
use smithay::reexports::wayland_server::DisplayHandle;
|
||||
use wayland_backend::server::GlobalId;
|
||||
|
||||
pub fn remove_global_with_timer<D: 'static>(
|
||||
dh: &DisplayHandle,
|
||||
event_loop_handle: &LoopHandle<D>,
|
||||
id: GlobalId,
|
||||
) {
|
||||
dh.disable_global::<D>(id.clone());
|
||||
let source = Timer::from_duration(Duration::from_secs(5));
|
||||
let dh = dh.clone();
|
||||
let res = event_loop_handle.insert_source(source, move |_, _, _state| {
|
||||
dh.remove_global::<D>(id.clone());
|
||||
TimeoutAction::Drop
|
||||
});
|
||||
if let Err(err) = res {
|
||||
tracing::error!(
|
||||
"failed to insert timer source to destroy output global: {}",
|
||||
err
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
@ -4,6 +4,7 @@ pub mod env;
|
|||
mod ids;
|
||||
pub(crate) use self::ids::id_gen;
|
||||
pub mod geometry;
|
||||
pub mod global;
|
||||
pub mod iced;
|
||||
pub mod prelude;
|
||||
pub mod quirks;
|
||||
|
|
|
|||
|
|
@ -40,7 +40,7 @@ impl DmabufHandler for State {
|
|||
fn new_surface_feedback(
|
||||
&mut self,
|
||||
surface: &WlSurface,
|
||||
global: &DmabufGlobal,
|
||||
_global: &DmabufGlobal,
|
||||
) -> Option<DmabufFeedback> {
|
||||
let BackendData::Kms(kms) = &self.backend else {
|
||||
return None;
|
||||
|
|
@ -57,18 +57,7 @@ impl DmabufHandler for State {
|
|||
f.ended_at.is_none() && f.surface.has_surface(surface, WindowSurfaceType::all())
|
||||
});
|
||||
|
||||
let node = kms
|
||||
.drm_devices
|
||||
.values()
|
||||
.find(|device| {
|
||||
device
|
||||
.socket
|
||||
.as_ref()
|
||||
.map(|s| &s.dmabuf_global == global)
|
||||
.unwrap_or(false)
|
||||
})?
|
||||
.inner
|
||||
.render_node;
|
||||
let node = kms.primary_node.read().unwrap().unwrap();
|
||||
let kms_surface = kms
|
||||
.drm_devices
|
||||
.values()
|
||||
|
|
@ -77,9 +66,7 @@ impl DmabufHandler for State {
|
|||
|
||||
Some(with_states(surface, |data| {
|
||||
if is_fullscreen {
|
||||
feedback
|
||||
.primary_scanout_feedback
|
||||
.unwrap_or(feedback.render_feedback)
|
||||
feedback.primary_scanout_feedback
|
||||
} else if frame_time_filter_fn(data) == Kind::ScanoutCandidate {
|
||||
feedback
|
||||
.overlay_scanout_feedback
|
||||
|
|
|
|||
|
|
@ -49,7 +49,10 @@ impl FractionalScaleHandler for State {
|
|||
|
||||
with_states(&surface, |states| {
|
||||
with_fractional_scale(states, |fractional_scale| {
|
||||
fractional_scale.set_preferred_scale(output.current_scale().fractional_scale());
|
||||
// The 1.0 clamp is a workaround for Chromium
|
||||
// TODO: remove if Chromium ever gets fixed
|
||||
fractional_scale
|
||||
.set_preferred_scale(output.current_scale().fractional_scale().max(1.0));
|
||||
});
|
||||
});
|
||||
}
|
||||
|
|
|
|||
|
|
@ -230,6 +230,16 @@ impl State {
|
|||
state.common.output_configuration_state.update();
|
||||
});
|
||||
|
||||
// Output scale or geometry may have changed. EI absolute-pointer regions
|
||||
// are immutable per device, so any connected EI client (e.g. an RDP server)
|
||||
// keeps mapping with the old scale until it reconnects. Recreate the
|
||||
// device with the updated region so the mapping tracks the change live.
|
||||
// (drop the backend lock first: refresh borrows `self` immutably.)
|
||||
drop(backend);
|
||||
if !test_only {
|
||||
crate::libei::refresh_absolute_pointer_regions(self);
|
||||
}
|
||||
|
||||
true
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,10 +3,12 @@
|
|||
use crate::state::State;
|
||||
use smithay::{
|
||||
backend::input::TabletToolDescriptor, input::pointer::CursorImageStatus,
|
||||
wayland::tablet_manager::TabletSeatHandler,
|
||||
input::tablet::TabletSeatHandler, reexports::wayland_server::protocol::wl_surface,
|
||||
};
|
||||
|
||||
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
|
||||
}
|
||||
|
|
|
|||
|
|
@ -333,17 +333,15 @@ impl XdgShellHandler for State {
|
|||
if let Some(ref grab) = grab {
|
||||
if grab.has_ended() {
|
||||
should_ungrab = true;
|
||||
} else if let Some(target) = grab.current_grab() {
|
||||
if let Some(wl_surface) = target.wl_surface() {
|
||||
if wl_surface.as_ref() == surface.wl_surface()
|
||||
|| smithay::desktop::PopupManager::popups_for_surface(
|
||||
surface.wl_surface(),
|
||||
)
|
||||
.any(|(p, _)| p.wl_surface() == wl_surface.as_ref())
|
||||
{
|
||||
should_ungrab = true;
|
||||
}
|
||||
}
|
||||
} else if let Some(target) = grab.current_grab()
|
||||
&& let Some(wl_surface) = target.wl_surface()
|
||||
&& (wl_surface.as_ref() == surface.wl_surface()
|
||||
|| smithay::desktop::PopupManager::popups_for_surface(
|
||||
surface.wl_surface(),
|
||||
)
|
||||
.any(|(p, _)| p.wl_surface() == wl_surface.as_ref()))
|
||||
{
|
||||
should_ungrab = true;
|
||||
}
|
||||
}
|
||||
if should_ungrab {
|
||||
|
|
|
|||
|
|
@ -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(|| ToplevelHookId::new(None));
|
||||
.get_or_insert_threadsafe(|| LayerHookId::new(None));
|
||||
let guard = hook_id.lock().unwrap();
|
||||
guard.as_ref().map(|(_, t)| t.upgrade().is_ok())
|
||||
});
|
||||
|
|
|
|||
|
|
@ -38,7 +38,11 @@ use smithay::{
|
|||
};
|
||||
use tracing::trace;
|
||||
|
||||
use std::{convert::TryFrom, path::PathBuf, sync::Arc};
|
||||
use std::{
|
||||
convert::TryFrom,
|
||||
path::PathBuf,
|
||||
sync::{Arc, Mutex},
|
||||
};
|
||||
|
||||
pub enum ImportError {
|
||||
Failed,
|
||||
|
|
@ -52,19 +56,28 @@ pub trait DrmHandler<R: 'static> {
|
|||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Default)]
|
||||
pub struct WlDrmState<R>(std::marker::PhantomData<R>);
|
||||
#[derive(Debug)]
|
||||
pub struct WlDrmState<R> {
|
||||
device: Arc<Mutex<DrmGlobalDeviceData>>,
|
||||
global: GlobalId,
|
||||
_marker: std::marker::PhantomData<R>,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct DrmGlobalDeviceData {
|
||||
formats: Vec<Fourcc>,
|
||||
path: PathBuf,
|
||||
}
|
||||
|
||||
/// Data associated with a drm global.
|
||||
pub struct DrmGlobalData {
|
||||
filter: Box<dyn for<'a> Fn(&'a Client) -> bool + Send + Sync>,
|
||||
formats: Arc<Vec<Fourcc>>,
|
||||
device_path: PathBuf,
|
||||
dmabuf_global: DmabufGlobal,
|
||||
device: Arc<Mutex<DrmGlobalDeviceData>>,
|
||||
}
|
||||
|
||||
pub struct DrmInstanceData {
|
||||
formats: Arc<Vec<Fourcc>>,
|
||||
device: Arc<Mutex<DrmGlobalDeviceData>>,
|
||||
dmabuf_global: DmabufGlobal,
|
||||
}
|
||||
|
||||
|
|
@ -86,16 +99,24 @@ where
|
|||
data_init: &mut DataInit<'_, D>,
|
||||
) {
|
||||
let data = DrmInstanceData {
|
||||
formats: global_data.formats.clone(),
|
||||
device: global_data.device.clone(),
|
||||
dmabuf_global: global_data.dmabuf_global,
|
||||
};
|
||||
let drm_instance = data_init.init(resource, data);
|
||||
|
||||
drm_instance.device(global_data.device_path.to_string_lossy().into_owned());
|
||||
drm_instance.device(
|
||||
global_data
|
||||
.device
|
||||
.lock()
|
||||
.unwrap()
|
||||
.path
|
||||
.to_string_lossy()
|
||||
.into_owned(),
|
||||
);
|
||||
if drm_instance.version() >= 2 {
|
||||
drm_instance.capabilities(wl_drm::Capability::Prime as u32);
|
||||
}
|
||||
for format in global_data.formats.iter() {
|
||||
for format in global_data.device.lock().unwrap().formats.iter() {
|
||||
if let Ok(converted) = wl_drm::Format::try_from(*format as u32) {
|
||||
drm_instance.format(converted as u32);
|
||||
}
|
||||
|
|
@ -148,7 +169,7 @@ where
|
|||
} => {
|
||||
let format = match Fourcc::try_from(format) {
|
||||
Ok(format) => {
|
||||
if !data.formats.contains(&format) {
|
||||
if !data.device.lock().unwrap().formats.contains(&format) {
|
||||
drm.post_error(
|
||||
wl_drm::Error::InvalidFormat,
|
||||
String::from("Format not advertised by wl_drm"),
|
||||
|
|
@ -220,13 +241,12 @@ where
|
|||
}
|
||||
|
||||
impl<R: 'static> WlDrmState<R> {
|
||||
pub fn create_global<D>(
|
||||
&mut self,
|
||||
pub fn new<D>(
|
||||
display: &DisplayHandle,
|
||||
device_path: PathBuf,
|
||||
formats: FormatSet,
|
||||
dmabuf_global: &DmabufGlobal,
|
||||
) -> GlobalId
|
||||
) -> Self
|
||||
where
|
||||
D: GlobalDispatch<wl_drm::WlDrm, DrmGlobalData>
|
||||
+ Dispatch<wl_drm::WlDrm, DrmInstanceData>
|
||||
|
|
@ -234,19 +254,16 @@ impl<R: 'static> WlDrmState<R> {
|
|||
+ DmabufHandler
|
||||
+ 'static,
|
||||
{
|
||||
self.create_global_with_filter::<D, _>(display, device_path, formats, dmabuf_global, |_| {
|
||||
true
|
||||
})
|
||||
Self::new_with_filter::<D, _>(display, device_path, formats, dmabuf_global, |_| true)
|
||||
}
|
||||
|
||||
pub fn create_global_with_filter<D, F>(
|
||||
&mut self,
|
||||
pub fn new_with_filter<D, F>(
|
||||
display: &DisplayHandle,
|
||||
device_path: PathBuf,
|
||||
formats: FormatSet,
|
||||
dmabuf_global: &DmabufGlobal,
|
||||
client_filter: F,
|
||||
) -> GlobalId
|
||||
) -> Self
|
||||
where
|
||||
D: GlobalDispatch<wl_drm::WlDrm, DrmGlobalData>
|
||||
+ Dispatch<wl_drm::WlDrm, DrmInstanceData>
|
||||
|
|
@ -255,21 +272,45 @@ impl<R: 'static> WlDrmState<R> {
|
|||
+ 'static,
|
||||
F: for<'a> Fn(&'a Client) -> bool + Send + Sync + 'static,
|
||||
{
|
||||
let formats = Arc::new(
|
||||
formats
|
||||
let device = Arc::new(Mutex::new(DrmGlobalDeviceData {
|
||||
formats: formats
|
||||
.into_iter()
|
||||
.filter(|f| f.modifier == Modifier::Invalid)
|
||||
.map(|f| f.code)
|
||||
.collect(),
|
||||
);
|
||||
path: device_path,
|
||||
}));
|
||||
|
||||
let data = DrmGlobalData {
|
||||
filter: Box::new(client_filter),
|
||||
formats,
|
||||
device_path,
|
||||
device: device.clone(),
|
||||
dmabuf_global: *dmabuf_global,
|
||||
};
|
||||
|
||||
display.create_global::<D, wl_drm::WlDrm, _>(2, data)
|
||||
let global = display.create_global::<D, wl_drm::WlDrm, _>(2, data);
|
||||
|
||||
WlDrmState {
|
||||
device,
|
||||
global,
|
||||
_marker: Default::default(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn update_device(&mut self, device_path: PathBuf, formats: FormatSet) {
|
||||
let new_device = DrmGlobalDeviceData {
|
||||
formats: formats
|
||||
.into_iter()
|
||||
.filter(|f| f.modifier == Modifier::Invalid)
|
||||
.map(|f| f.code)
|
||||
.collect(),
|
||||
path: device_path,
|
||||
};
|
||||
|
||||
*self.device.lock().unwrap() = new_device;
|
||||
}
|
||||
|
||||
pub fn global(&self) -> &GlobalId {
|
||||
&self.global
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,9 +1,6 @@
|
|||
// SPDX-License-Identifier: GPL-3.0-only
|
||||
|
||||
use calloop::{
|
||||
LoopHandle,
|
||||
timer::{TimeoutAction, Timer},
|
||||
};
|
||||
use calloop::LoopHandle;
|
||||
use cosmic_comp_config::output::comp::AdaptiveSync;
|
||||
use cosmic_protocols::output_management::v1::server::{
|
||||
zcosmic_output_configuration_head_v1::ZcosmicOutputConfigurationHeadV1,
|
||||
|
|
@ -30,7 +27,7 @@ use smithay::{
|
|||
utils::{Logical, Physical, Point, Size, Transform},
|
||||
wayland::output::WlOutputData,
|
||||
};
|
||||
use std::{convert::TryFrom, sync::Mutex, time::Duration};
|
||||
use std::{convert::TryFrom, sync::Mutex};
|
||||
|
||||
mod handlers;
|
||||
|
||||
|
|
@ -519,26 +516,6 @@ where
|
|||
}
|
||||
}
|
||||
|
||||
fn remove_global_with_timer<D: 'static>(
|
||||
dh: &DisplayHandle,
|
||||
event_loop_handle: &LoopHandle<D>,
|
||||
id: GlobalId,
|
||||
) {
|
||||
dh.disable_global::<D>(id.clone());
|
||||
let source = Timer::from_duration(Duration::from_secs(5));
|
||||
let dh = dh.clone();
|
||||
let res = event_loop_handle.insert_source(source, move |_, _, _state| {
|
||||
dh.remove_global::<D>(id.clone());
|
||||
TimeoutAction::Drop
|
||||
});
|
||||
if let Err(err) = res {
|
||||
tracing::error!(
|
||||
"failed to insert timer source to destroy output global: {}",
|
||||
err
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
macro_rules! delegate_output_configuration {
|
||||
($(@<$( $lt:tt $( : $clt:tt $(+ $dlt:tt )* )? ),+>)? $ty: ty) => {
|
||||
smithay::reexports::wayland_server::delegate_global_dispatch!($(@< $( $lt $( : $clt $(+ $dlt )* )? ),+ >)? $ty: [
|
||||
|
|
@ -578,4 +555,4 @@ macro_rules! delegate_output_configuration {
|
|||
}
|
||||
pub(crate) use delegate_output_configuration;
|
||||
|
||||
use crate::utils::prelude::OutputExt;
|
||||
use crate::utils::{global::remove_global_with_timer, prelude::OutputExt};
|
||||
|
|
|
|||
|
|
@ -115,7 +115,7 @@ impl State {
|
|||
&self.common.display_handle,
|
||||
None,
|
||||
std::iter::empty::<(OsString, OsString)>(),
|
||||
std::iter::empty::<OsString>(),
|
||||
["-enable-ei-portal"],
|
||||
true,
|
||||
Stdio::null(),
|
||||
Stdio::null(),
|
||||
|
|
@ -394,6 +394,7 @@ impl Common {
|
|||
sym: Keysym,
|
||||
code: Keycode,
|
||||
state: KeyState,
|
||||
modifiers: ModifiersState,
|
||||
serial: Serial,
|
||||
time: u32,
|
||||
) {
|
||||
|
|
@ -418,7 +419,6 @@ impl Common {
|
|||
.last_active()
|
||||
.get_keyboard()
|
||||
.unwrap();
|
||||
let modifiers = keyboard.modifier_state();
|
||||
let is_modifier = sym.is_modifier_key();
|
||||
|
||||
let xstate = self.xwayland_state.as_mut().unwrap();
|
||||
|
|
@ -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(self, Some(&target), &seat, None, false);
|
||||
Shell::set_focus_on_x11_map(self, &target, &seat, false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue