Compare commits
11 commits
61149d95d0
...
934db82775
| Author | SHA1 | Date | |
|---|---|---|---|
| 934db82775 | |||
|
|
28258e5a5f | ||
|
|
6ebe2a1f04 | ||
|
|
451b8319d6 | ||
|
|
200074e580 | ||
|
|
8450e89de4 | ||
|
|
c776eeac03 | ||
|
|
657f0aeddf | ||
|
|
61e677e39f | ||
|
|
23b198f30b | ||
|
|
92fcceba54 |
13 changed files with 1786 additions and 361 deletions
1317
Cargo.lock
generated
1317
Cargo.lock
generated
File diff suppressed because it is too large
Load diff
51
Cargo.toml
51
Cargo.toml
|
|
@ -17,18 +17,18 @@ cosmic-comp-config = { path = "cosmic-comp-config", features = [
|
|||
"libdisplay-info",
|
||||
"output",
|
||||
] }
|
||||
cosmic-config = { git = "https://github.com/pop-os/libcosmic", features = [
|
||||
cosmic-config = { path = "../libcosmic/cosmic-config", features = [
|
||||
"calloop",
|
||||
"macro",
|
||||
] }
|
||||
cosmic-protocols = { git = "https://github.com/pop-os/cosmic-protocols", rev = "160b086", default-features = false, features = [
|
||||
cosmic-protocols = { path = "../cosmic-protocols", default-features = false, features = [
|
||||
"server",
|
||||
] }
|
||||
cosmic-settings-config = { git = "https://github.com/pop-os/cosmic-settings-daemon" }
|
||||
cosmic-settings-daemon-config = { git = "https://github.com/pop-os/cosmic-settings-daemon", features = [
|
||||
cosmic-settings-config = { path = "../cosmic-settings-daemon/config" }
|
||||
cosmic-settings-daemon-config = { path = "../cosmic-settings-daemon/cosmic-settings-daemon-config", features = [
|
||||
"greeter",
|
||||
] }
|
||||
cosmic-text = { git = "https://github.com/pop-os/cosmic-text.git", features = [
|
||||
cosmic-text = { git = "https://forge.aditua.com/leyoda/cosmic-text.git", branch = "local/pr-503", features = [
|
||||
"shape-run-cache",
|
||||
] }
|
||||
libdisplay-info = "0.3.0"
|
||||
|
|
@ -39,10 +39,14 @@ i18n-embed = { version = "0.16", features = [
|
|||
"desktop-requester",
|
||||
] }
|
||||
i18n-embed-fl = "0.10"
|
||||
iced_tiny_skia = { git = "https://github.com/pop-os/libcosmic" }
|
||||
iced_tiny_skia = { path = "../libcosmic/iced/tiny_skia" }
|
||||
indexmap = "2.13"
|
||||
keyframe = "1.1.1"
|
||||
libcosmic = { git = "https://github.com/pop-os/libcosmic", default-features = false }
|
||||
cosmic = { package = "libcosmic-yoda", path = "../libcosmic", default-features = false, features = [
|
||||
"tokio",
|
||||
"wayland",
|
||||
"multi-window",
|
||||
] }
|
||||
libsystemd = { version = "0.7", optional = true }
|
||||
log-panics = { version = "2", features = ["with-backtrace"] }
|
||||
ordered-float = "5.1"
|
||||
|
|
@ -138,9 +142,36 @@ inherits = "release"
|
|||
[profile.release]
|
||||
lto = "fat"
|
||||
|
||||
[patch."https://github.com/pop-os/cosmic-protocols"]
|
||||
cosmic-protocols = { git = "https://github.com/pop-os//cosmic-protocols", branch = "main" }
|
||||
cosmic-client-toolkit = { git = "https://github.com/pop-os//cosmic-protocols", branch = "main" }
|
||||
[patch.'https://github.com/pop-os/libcosmic']
|
||||
cosmic-config = { path = "../libcosmic/cosmic-config" }
|
||||
cosmic-theme = { path = "../libcosmic/cosmic-theme" }
|
||||
iced = { path = "../libcosmic/iced" }
|
||||
iced_accessibility = { path = "../libcosmic/iced/accessibility" }
|
||||
iced_core = { path = "../libcosmic/iced/core" }
|
||||
iced_futures = { path = "../libcosmic/iced/futures" }
|
||||
iced_graphics = { path = "../libcosmic/iced/graphics" }
|
||||
iced_renderer = { path = "../libcosmic/iced/renderer" }
|
||||
iced_runtime = { path = "../libcosmic/iced/runtime" }
|
||||
iced_tiny_skia = { path = "../libcosmic/iced/tiny_skia" }
|
||||
iced_wgpu = { path = "../libcosmic/iced/wgpu" }
|
||||
iced_widget = { path = "../libcosmic/iced/widget" }
|
||||
iced_winit = { path = "../libcosmic/iced/winit" }
|
||||
|
||||
[patch.'https://github.com/pop-os/cosmic-protocols']
|
||||
cosmic-protocols = { path = "../cosmic-protocols" }
|
||||
cosmic-client-toolkit = { path = "../cosmic-protocols/client-toolkit" }
|
||||
|
||||
[patch.'https://github.com/pop-os/cosmic-settings-daemon']
|
||||
cosmic-settings-config = { path = "../cosmic-settings-daemon/config" }
|
||||
cosmic-settings-daemon-config = { path = "../cosmic-settings-daemon/cosmic-settings-daemon-config" }
|
||||
|
||||
[patch.'https://github.com/pop-os/cosmic-text.git']
|
||||
cosmic-text = { git = "https://forge.aditua.com/leyoda/cosmic-text.git", branch = "local/pr-503" }
|
||||
|
||||
[patch.'https://forge.aditua.com/leyoda/window_clipboard.git']
|
||||
window_clipboard = { path = "../window_clipboard" }
|
||||
dnd = { path = "../window_clipboard/dnd" }
|
||||
mime = { path = "../window_clipboard/mime" }
|
||||
|
||||
[patch.crates-io]
|
||||
smithay = { git = "https://github.com/smithay/smithay.git", rev = "774f2ab" }
|
||||
|
|
|
|||
|
|
@ -4,8 +4,8 @@ version = "1.0.0"
|
|||
edition = "2024"
|
||||
|
||||
[dependencies]
|
||||
cosmic-config = { git = "https://github.com/pop-os/libcosmic/" }
|
||||
cosmic-randr-shell = { git = "https://github.com/pop-os/cosmic-randr/", optional = true }
|
||||
cosmic-config = { path = "../../libcosmic/cosmic-config" }
|
||||
cosmic-randr-shell = { path = "../../cosmic-randr/shell", optional = true }
|
||||
input = "0.10.0"
|
||||
libdisplay-info = { version = "0.3.0", optional = true }
|
||||
serde = { version = "1", features = ["derive"] }
|
||||
|
|
|
|||
|
|
@ -99,6 +99,8 @@ pub struct CosmicCompConfig {
|
|||
pub edge_snap_threshold: u32,
|
||||
pub accessibility_zoom: ZoomConfig,
|
||||
pub appearance_settings: AppearanceConfig,
|
||||
/// Hide the cursor after this many seconds of pointer inactivity (None disables)
|
||||
pub cursor_hide_timeout: Option<u32>,
|
||||
}
|
||||
|
||||
impl Default for CosmicCompConfig {
|
||||
|
|
@ -135,6 +137,7 @@ impl Default for CosmicCompConfig {
|
|||
edge_snap_threshold: 0,
|
||||
accessibility_zoom: ZoomConfig::default(),
|
||||
appearance_settings: AppearanceConfig::default(),
|
||||
cursor_hide_timeout: None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -28,7 +28,7 @@ use smithay::{
|
|||
},
|
||||
egl::{EGLContext, EGLDevice, EGLDisplay, context::ContextPriority},
|
||||
renderer::glow::GlowRenderer,
|
||||
session::Session,
|
||||
session::{Session, libseat::LibSeatSession},
|
||||
},
|
||||
desktop::utils::OutputPresentationFeedback,
|
||||
output::{Mode as OutputMode, Output, PhysicalProperties, Scale, Subpixel},
|
||||
|
|
@ -40,7 +40,10 @@ use smithay::{
|
|||
wayland_server::DisplayHandle,
|
||||
},
|
||||
utils::{Clock, DevPath, DeviceFd, Monotonic, Point, Transform},
|
||||
wayland::drm_lease::{DrmLease, DrmLeaseState},
|
||||
wayland::{
|
||||
drm_lease::{DrmLease, DrmLeaseState},
|
||||
drm_syncobj::supports_syncobj_eventfd,
|
||||
},
|
||||
};
|
||||
use tracing::{error, info, warn};
|
||||
use wayland_backend::server::ClientId;
|
||||
|
|
@ -99,6 +102,20 @@ pub struct Device {
|
|||
pub socket: Option<Socket>,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
struct ReusableDevice {
|
||||
leasing_global: Option<DrmLeaseState>,
|
||||
active_clients: HashSet<ClientId>,
|
||||
socket: Option<Socket>,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
struct OldDeviceState {
|
||||
fd: DeviceFd,
|
||||
outputs: HashMap<connector::Handle, Output>,
|
||||
leased_connectors: HashSet<connector::Handle>,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct LockedDevice<'a> {
|
||||
pub inner: &'a mut InnerDevice,
|
||||
|
|
@ -222,138 +239,14 @@ impl State {
|
|||
}
|
||||
}
|
||||
|
||||
let fd = DrmDeviceFd::new(DeviceFd::from(
|
||||
self.backend
|
||||
.kms()
|
||||
.session
|
||||
.open(
|
||||
path,
|
||||
OFlags::RDWR | OFlags::CLOEXEC | OFlags::NOCTTY | OFlags::NONBLOCK,
|
||||
)
|
||||
.with_context(|| {
|
||||
format!(
|
||||
"Failed to optain file descriptor for drm device: {}",
|
||||
path.display()
|
||||
)
|
||||
})?,
|
||||
));
|
||||
let (drm, notifier) = DrmDevice::new(fd.clone(), false)
|
||||
.with_context(|| format!("Failed to initialize drm device for: {}", path.display()))?;
|
||||
let drm_node = DrmNode::from_dev_id(dev)?;
|
||||
let supports_atomic = drm.is_atomic();
|
||||
|
||||
let gbm = GbmDevice::new(fd)
|
||||
.with_context(|| format!("Failed to initialize GBM device for {}", path.display()))?;
|
||||
let (render_node, render_formats, texture_formats, is_software) = {
|
||||
let egl = init_egl(&gbm)?;
|
||||
|
||||
let render_node = egl
|
||||
.device
|
||||
.try_get_render_node()
|
||||
.ok()
|
||||
.and_then(std::convert::identity)
|
||||
.unwrap_or(drm_node);
|
||||
let render_formats = egl.context.dmabuf_render_formats().clone();
|
||||
let texture_formats = egl.context.dmabuf_texture_formats().clone();
|
||||
|
||||
(
|
||||
render_node,
|
||||
render_formats,
|
||||
texture_formats,
|
||||
egl.device.is_software(),
|
||||
)
|
||||
};
|
||||
|
||||
let token = self
|
||||
.common
|
||||
.event_loop_handle
|
||||
.insert_source(
|
||||
notifier,
|
||||
move |event, metadata, state: &mut State| match event {
|
||||
DrmEvent::VBlank(crtc) => {
|
||||
if let Some(device) = state.backend.kms().drm_devices.get_mut(&drm_node)
|
||||
&& let Some(surface) = device.inner.surfaces.get_mut(&crtc)
|
||||
{
|
||||
surface.on_vblank(metadata.take());
|
||||
}
|
||||
}
|
||||
DrmEvent::Error(err) => {
|
||||
warn!(?err, "Failed to read events of device {:?}.", dev);
|
||||
}
|
||||
},
|
||||
)
|
||||
.with_context(|| format!("Failed to add drm device to event loop: {}", dev))?;
|
||||
|
||||
let socket = match (!is_software)
|
||||
.then(|| self.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, &drm_node))
|
||||
.transpose()
|
||||
{
|
||||
Ok(global) => global,
|
||||
Err(err) => {
|
||||
// TODO: replace with inspect_err, once stable
|
||||
warn!(
|
||||
?err,
|
||||
"Failed to initialize drm lease global for: {}", drm_node
|
||||
);
|
||||
None
|
||||
}
|
||||
};
|
||||
|
||||
let drm = GbmDrmOutputManager::new(
|
||||
drm,
|
||||
GbmAllocator::new(
|
||||
gbm.clone(),
|
||||
GbmBufferFlags::RENDERING | GbmBufferFlags::SCANOUT,
|
||||
),
|
||||
GbmFramebufferExporter::new(gbm.clone(), render_node.into()),
|
||||
Some(gbm.clone()),
|
||||
[
|
||||
Fourcc::Abgr2101010,
|
||||
Fourcc::Argb2101010,
|
||||
Fourcc::Abgr8888,
|
||||
Fourcc::Argb8888,
|
||||
],
|
||||
render_formats,
|
||||
);
|
||||
|
||||
let mut device = Device {
|
||||
drm,
|
||||
inner: InnerDevice {
|
||||
dev_node: drm_node,
|
||||
render_node,
|
||||
is_software,
|
||||
egl: None,
|
||||
|
||||
outputs: HashMap::new(),
|
||||
surfaces: HashMap::new(),
|
||||
gbm,
|
||||
|
||||
leased_connectors: Vec::new(),
|
||||
leasing_global,
|
||||
active_leases: Vec::new(),
|
||||
active_clients: HashSet::new(),
|
||||
},
|
||||
|
||||
supports_atomic,
|
||||
texture_formats,
|
||||
event_token: Some(token),
|
||||
socket,
|
||||
};
|
||||
|
||||
let mut device = Device::new(
|
||||
dev,
|
||||
path,
|
||||
&mut self.backend.kms().session,
|
||||
&mut self.common,
|
||||
dh,
|
||||
None,
|
||||
)?;
|
||||
let connectors = device.enumerate_surfaces()?.added; // There are no removed outputs on newly added devices
|
||||
let mut wl_outputs = Vec::new();
|
||||
let mut w = self.common.shell.read().global_space().size.w as u32;
|
||||
|
|
@ -387,7 +280,7 @@ impl State {
|
|||
// TODO atomic commit all surfaces together and drop surfaces, if it fails due to bandwidth
|
||||
|
||||
let kms = self.backend.kms();
|
||||
kms.drm_devices.insert(drm_node, device);
|
||||
kms.drm_devices.insert(device.inner.dev_node, device);
|
||||
}
|
||||
|
||||
self.common
|
||||
|
|
@ -489,6 +382,218 @@ impl State {
|
|||
Ok(outputs_added)
|
||||
}
|
||||
|
||||
pub fn reopen_device(
|
||||
&mut self,
|
||||
dev: dev_t,
|
||||
path: &Path,
|
||||
dh: &DisplayHandle,
|
||||
) -> Result<Vec<Output>> {
|
||||
let backend = self.backend.kms();
|
||||
let drm_node = backend
|
||||
.drm_devices
|
||||
.values()
|
||||
.find_map(|device| {
|
||||
(device.inner.dev_node.dev_id() == dev).then_some(device.inner.dev_node)
|
||||
})
|
||||
.with_context(|| format!("Couldn't find drm node for {}", dev))?;
|
||||
|
||||
if let Some(mut device) = backend.drm_devices.shift_remove(&drm_node) {
|
||||
let is_primary = backend
|
||||
.primary_node
|
||||
.read()
|
||||
.unwrap()
|
||||
.is_some_and(|node| node == device.inner.render_node);
|
||||
|
||||
// all drm resources become inert
|
||||
if let Some(token) = device.event_token.take() {
|
||||
self.common.event_loop_handle.remove(token);
|
||||
}
|
||||
for (_, surface) in device.inner.surfaces.drain() {
|
||||
surface.drop_and_join();
|
||||
}
|
||||
if let Some(leasing_global) = device.inner.leasing_global.as_mut() {
|
||||
for (connector, _) in &device.inner.leased_connectors {
|
||||
leasing_global.withdraw_connector(*connector);
|
||||
}
|
||||
device.inner.active_leases.clear();
|
||||
}
|
||||
|
||||
// These contain a reference to the file descriptor
|
||||
backend.api.as_mut().remove_node(&device.inner.render_node);
|
||||
// trigger enumeration
|
||||
let _ = backend.api.devices();
|
||||
|
||||
for surface in backend
|
||||
.drm_devices
|
||||
.values_mut()
|
||||
.flat_map(|device| device.inner.surfaces.values_mut())
|
||||
{
|
||||
surface.remove_node(device.inner.render_node);
|
||||
}
|
||||
let syncobj_guard =
|
||||
if is_primary && let Some(syncobj_state) = backend.syncobj_state.as_mut() {
|
||||
Some(syncobj_state.close_device())
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
let (reusable, mut old_state) = device.reuse();
|
||||
|
||||
match TryInto::<OwnedFd>::try_into(old_state.fd) {
|
||||
Ok(fd) => {
|
||||
if let Err(err) = backend.session.close(fd) {
|
||||
warn!("Failed to close drm device fd: {}", err);
|
||||
}
|
||||
}
|
||||
Err(_) => {
|
||||
warn!(?drm_node, "Unable to close drm device fd cleanly.");
|
||||
}
|
||||
};
|
||||
|
||||
let mut new_device = Device::new(
|
||||
dev,
|
||||
path,
|
||||
&mut backend.session,
|
||||
&mut self.common,
|
||||
dh,
|
||||
Some(reusable),
|
||||
)?;
|
||||
|
||||
if let Some(guard) = syncobj_guard {
|
||||
let import_device = new_device.drm.device().device_fd().clone();
|
||||
if supports_syncobj_eventfd(&import_device) {
|
||||
guard.update_device(import_device);
|
||||
} else {
|
||||
drop(guard);
|
||||
dh.remove_global::<State>(backend.syncobj_state.take().unwrap().into_global());
|
||||
}
|
||||
}
|
||||
|
||||
let connectors = new_device.enumerate_surfaces()?.added;
|
||||
let mut outputs_added = Vec::new();
|
||||
let mut w = self.common.shell.read().global_space().size.w as u32;
|
||||
|
||||
{
|
||||
for (conn, maybe_crtc) in connectors {
|
||||
if let Some(output) = old_state.outputs.remove(&conn) {
|
||||
let has_surface = if old_state.leased_connectors.contains(&conn) {
|
||||
if let Some(crtc) = maybe_crtc {
|
||||
new_device.inner.leased_connectors.push((conn, crtc));
|
||||
info!(
|
||||
"Connector {} is non-desktop, setting up for leasing",
|
||||
output.name()
|
||||
);
|
||||
if let Some(lease_state) = new_device.inner.leasing_global.as_mut()
|
||||
{
|
||||
let physical = output.physical_properties();
|
||||
lease_state.add_connector::<State>(
|
||||
conn,
|
||||
output.name(),
|
||||
format!("{} {}", physical.make, physical.model),
|
||||
);
|
||||
}
|
||||
} else {
|
||||
warn!(
|
||||
"Connector {} is non-desktop, but we don't have a free crtc: not leasing",
|
||||
output.name()
|
||||
);
|
||||
}
|
||||
false
|
||||
} else {
|
||||
let loc = output.current_location();
|
||||
populate_modes(
|
||||
new_device.drm.device_mut(),
|
||||
&output,
|
||||
conn,
|
||||
true,
|
||||
(loc.x as u32, loc.y as u32),
|
||||
)
|
||||
.with_context(|| "Failed to enumerate connector modes")?;
|
||||
|
||||
if let Some(crtc) = maybe_crtc {
|
||||
match Surface::new(
|
||||
&output,
|
||||
crtc,
|
||||
conn,
|
||||
backend.primary_node.clone(),
|
||||
new_device.inner.dev_node,
|
||||
new_device.inner.render_node,
|
||||
&self.common.event_loop_handle,
|
||||
self.common.config.dynamic_conf.screen_filter().clone(),
|
||||
self.common.shell.clone(),
|
||||
self.common.startup_done.clone(),
|
||||
) {
|
||||
Ok(data) => {
|
||||
new_device.inner.surfaces.insert(crtc, data);
|
||||
true
|
||||
}
|
||||
Err(err) => {
|
||||
error!(?crtc, "Failed to initialize surface: {}", err);
|
||||
false
|
||||
}
|
||||
}
|
||||
} else {
|
||||
false
|
||||
}
|
||||
};
|
||||
|
||||
if !has_surface {
|
||||
output
|
||||
.user_data()
|
||||
.get::<RefCell<OutputConfig>>()
|
||||
.unwrap()
|
||||
.borrow_mut()
|
||||
.enabled = OutputState::Disabled;
|
||||
}
|
||||
new_device.inner.outputs.insert(conn, output);
|
||||
} else {
|
||||
match new_device.inner.connector_added(
|
||||
new_device.drm.device_mut(),
|
||||
backend.primary_node.clone(),
|
||||
conn,
|
||||
maybe_crtc,
|
||||
(w, 0),
|
||||
&self.common.event_loop_handle,
|
||||
self.common.config.dynamic_conf.screen_filter().clone(),
|
||||
self.common.shell.clone(),
|
||||
self.common.startup_done.clone(),
|
||||
) {
|
||||
Ok((output, should_expose)) => {
|
||||
if should_expose {
|
||||
w += output.geometry().size.w as u32;
|
||||
outputs_added.push(output.clone());
|
||||
}
|
||||
|
||||
new_device.inner.outputs.insert(conn, output);
|
||||
}
|
||||
Err(err) => {
|
||||
warn!(?err, "Failed to initialize output, skipping");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
self.common
|
||||
.output_configuration_state
|
||||
.remove_heads(old_state.outputs.values());
|
||||
self.common
|
||||
.output_configuration_state
|
||||
.add_heads(outputs_added.iter());
|
||||
|
||||
for output in old_state.outputs.values() {
|
||||
self.common.remove_output(output);
|
||||
}
|
||||
|
||||
backend.drm_devices.insert(drm_node, new_device);
|
||||
backend.refresh_used_devices()?;
|
||||
|
||||
Ok(outputs_added)
|
||||
} else {
|
||||
Ok(Vec::new())
|
||||
}
|
||||
}
|
||||
|
||||
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
|
||||
|
|
@ -581,6 +686,158 @@ pub struct OutputChanges {
|
|||
}
|
||||
|
||||
impl Device {
|
||||
fn new(
|
||||
dev: dev_t,
|
||||
path: impl AsRef<Path>,
|
||||
session: &mut LibSeatSession,
|
||||
common: &mut Common,
|
||||
dh: &DisplayHandle,
|
||||
reuse: Option<ReusableDevice>,
|
||||
) -> Result<Self> {
|
||||
let path = path.as_ref();
|
||||
let fd = DrmDeviceFd::new(DeviceFd::from(
|
||||
session
|
||||
.open(
|
||||
path,
|
||||
OFlags::RDWR | OFlags::CLOEXEC | OFlags::NOCTTY | OFlags::NONBLOCK,
|
||||
)
|
||||
.with_context(|| {
|
||||
format!(
|
||||
"Failed to optain file descriptor for drm device: {}",
|
||||
path.display()
|
||||
)
|
||||
})?,
|
||||
));
|
||||
let (drm, notifier) = DrmDevice::new(fd.clone(), false)
|
||||
.with_context(|| format!("Failed to initialize drm device for: {}", path.display()))?;
|
||||
let dev_node = DrmNode::from_dev_id(dev)?;
|
||||
let supports_atomic = drm.is_atomic();
|
||||
|
||||
let gbm = GbmDevice::new(fd)
|
||||
.with_context(|| format!("Failed to initialize GBM device for {}", path.display()))?;
|
||||
let (render_node, render_formats, texture_formats, is_software) = {
|
||||
let egl = init_egl(&gbm)?;
|
||||
|
||||
let render_node = egl
|
||||
.device
|
||||
.try_get_render_node()
|
||||
.ok()
|
||||
.and_then(std::convert::identity)
|
||||
.unwrap_or(dev_node);
|
||||
let render_formats = egl.context.dmabuf_render_formats().clone();
|
||||
let texture_formats = egl.context.dmabuf_texture_formats().clone();
|
||||
|
||||
(
|
||||
render_node,
|
||||
render_formats,
|
||||
texture_formats,
|
||||
egl.device.is_software(),
|
||||
)
|
||||
};
|
||||
|
||||
let token = common
|
||||
.event_loop_handle
|
||||
.insert_source(
|
||||
notifier,
|
||||
move |event, metadata, state: &mut State| match event {
|
||||
DrmEvent::VBlank(crtc) => {
|
||||
if let Some(device) = state.backend.kms().drm_devices.get_mut(&dev_node)
|
||||
&& let Some(surface) = device.inner.surfaces.get_mut(&crtc)
|
||||
{
|
||||
surface.on_vblank(metadata.take());
|
||||
}
|
||||
}
|
||||
DrmEvent::Error(err) => {
|
||||
warn!(?err, "Failed to read events of device {:?}.", dev);
|
||||
}
|
||||
},
|
||||
)
|
||||
.with_context(|| format!("Failed to add drm device to event loop: {}", dev))?;
|
||||
|
||||
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()
|
||||
{
|
||||
Ok(global) => global,
|
||||
Err(err) => {
|
||||
// TODO: replace with inspect_err, once stable
|
||||
warn!(
|
||||
?err,
|
||||
"Failed to initialize drm lease global for: {}", dev_node
|
||||
);
|
||||
None
|
||||
}
|
||||
};
|
||||
|
||||
ReusableDevice {
|
||||
leasing_global,
|
||||
active_clients: HashSet::new(),
|
||||
socket,
|
||||
}
|
||||
});
|
||||
|
||||
let drm = GbmDrmOutputManager::new(
|
||||
drm,
|
||||
GbmAllocator::new(
|
||||
gbm.clone(),
|
||||
GbmBufferFlags::RENDERING | GbmBufferFlags::SCANOUT,
|
||||
),
|
||||
GbmFramebufferExporter::new(gbm.clone(), render_node.into()),
|
||||
Some(gbm.clone()),
|
||||
[
|
||||
Fourcc::Abgr2101010,
|
||||
Fourcc::Argb2101010,
|
||||
Fourcc::Abgr8888,
|
||||
Fourcc::Argb8888,
|
||||
],
|
||||
render_formats,
|
||||
);
|
||||
|
||||
Ok(Device {
|
||||
drm,
|
||||
inner: InnerDevice {
|
||||
dev_node,
|
||||
render_node,
|
||||
is_software,
|
||||
egl: None,
|
||||
|
||||
outputs: HashMap::new(),
|
||||
surfaces: HashMap::new(),
|
||||
gbm,
|
||||
|
||||
leased_connectors: Vec::new(),
|
||||
leasing_global,
|
||||
active_leases: Vec::new(),
|
||||
active_clients,
|
||||
},
|
||||
|
||||
supports_atomic,
|
||||
texture_formats,
|
||||
event_token: Some(token),
|
||||
socket,
|
||||
})
|
||||
}
|
||||
|
||||
pub fn enumerate_surfaces(&mut self) -> Result<OutputChanges> {
|
||||
// enumerate our outputs
|
||||
let config =
|
||||
|
|
@ -634,6 +891,27 @@ impl Device {
|
|||
drm: self.drm.lock(),
|
||||
}
|
||||
}
|
||||
|
||||
fn reuse(self) -> (ReusableDevice, OldDeviceState) {
|
||||
let device = ReusableDevice {
|
||||
leasing_global: self.inner.leasing_global,
|
||||
active_clients: self.inner.active_clients,
|
||||
socket: self.socket,
|
||||
};
|
||||
|
||||
let state = OldDeviceState {
|
||||
fd: self.drm.device().device_fd().device_fd(),
|
||||
outputs: self.inner.outputs,
|
||||
leased_connectors: self
|
||||
.inner
|
||||
.leased_connectors
|
||||
.iter()
|
||||
.map(|(conn, _)| *conn)
|
||||
.collect(),
|
||||
};
|
||||
|
||||
(device, state)
|
||||
}
|
||||
}
|
||||
|
||||
impl LockedDevice<'_> {
|
||||
|
|
|
|||
|
|
@ -394,15 +394,37 @@ impl State {
|
|||
continue;
|
||||
}
|
||||
};
|
||||
let dh = state.common.display_handle.clone();
|
||||
|
||||
if state.backend.kms().drm_devices.contains_key(&drm_node) {
|
||||
match state.device_changed(dev) {
|
||||
Ok(outputs) => added.extend(outputs),
|
||||
Err(err) => {
|
||||
error!(?err, "Failed to update drm device {}.", path.display(),)
|
||||
error!(
|
||||
?err,
|
||||
"Failed to update drm device {}. Re-opening",
|
||||
path.display(),
|
||||
);
|
||||
match state.reopen_device(dev, path, &dh) {
|
||||
Ok(outputs) => added.extend(outputs),
|
||||
Err(err) => {
|
||||
error!(
|
||||
?err,
|
||||
"Failed to re-open drm device {}. Device lost",
|
||||
path.display(),
|
||||
);
|
||||
if let Err(err) = state.device_removed(dev, &dh) {
|
||||
error!(
|
||||
?err,
|
||||
"Failed to close drm device {}.",
|
||||
path.display(),
|
||||
);
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
let dh = state.common.display_handle.clone();
|
||||
match state.device_added(dev, path, &dh) {
|
||||
Ok(outputs) => added.extend(outputs),
|
||||
Err(err) => error!(?err, "Failed to add drm device {}.", path.display(),),
|
||||
|
|
|
|||
|
|
@ -18,7 +18,7 @@ use smithay::{
|
|||
use std::sync::Arc;
|
||||
use tracing::{info, warn};
|
||||
|
||||
use crate::state::{ClientState, State, advertised_node_for_client};
|
||||
use crate::state::{ClientState, Common, State, advertised_node_for_client};
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct Socket {
|
||||
|
|
@ -27,7 +27,7 @@ pub struct Socket {
|
|||
pub dmabuf_global: DmabufGlobal,
|
||||
}
|
||||
|
||||
impl State {
|
||||
impl Common {
|
||||
pub(super) fn create_socket(
|
||||
&mut self,
|
||||
dh: &DisplayHandle,
|
||||
|
|
@ -36,7 +36,7 @@ impl State {
|
|||
) -> Result<Socket> {
|
||||
let socket_name = format!(
|
||||
"{}-{}",
|
||||
&self.common.socket.to_string_lossy(),
|
||||
&self.socket.to_string_lossy(),
|
||||
render_node
|
||||
.dev_path()
|
||||
.unwrap()
|
||||
|
|
@ -53,33 +53,28 @@ impl State {
|
|||
.with_context(|| "Failed to create drm format shared memory table")?;
|
||||
|
||||
let dmabuf_global = self
|
||||
.common
|
||||
.dmabuf_state
|
||||
.create_global_with_filter_and_default_feedback::<State, _>(dh, &feedback, filter);
|
||||
|
||||
let drm_global_id = self
|
||||
.common
|
||||
.wl_drm_state
|
||||
.create_global_with_filter::<State, _>(
|
||||
dh,
|
||||
render_node
|
||||
.dev_path_with_type(NodeType::Render)
|
||||
.or_else(|| render_node.dev_path())
|
||||
.ok_or(anyhow!(
|
||||
"Could not determine path for gpu node: {}",
|
||||
render_node
|
||||
))?,
|
||||
formats,
|
||||
&dmabuf_global,
|
||||
filter,
|
||||
);
|
||||
let drm_global_id = self.wl_drm_state.create_global_with_filter::<State, _>(
|
||||
dh,
|
||||
render_node
|
||||
.dev_path_with_type(NodeType::Render)
|
||||
.or_else(|| render_node.dev_path())
|
||||
.ok_or(anyhow!(
|
||||
"Could not determine path for gpu node: {}",
|
||||
render_node
|
||||
))?,
|
||||
formats,
|
||||
&dmabuf_global,
|
||||
filter,
|
||||
);
|
||||
|
||||
// add a special socket for the gpu
|
||||
let listener = ListeningSocketSource::with_name(&socket_name)
|
||||
.with_context(|| format!("Failed to bind socket to {}", socket_name))?;
|
||||
let socket_name_clone = socket_name.clone();
|
||||
let token = self
|
||||
.common
|
||||
.event_loop_handle
|
||||
.insert_source(listener, move |client_stream, _, state: &mut State| {
|
||||
if let Err(err) = state.common.display_handle.insert_client(
|
||||
|
|
|
|||
|
|
@ -464,7 +464,7 @@ impl Surface {
|
|||
|
||||
pub fn drop_and_join(mut self) {
|
||||
let thread = self.thread.take();
|
||||
let _ = self;
|
||||
std::mem::drop(self);
|
||||
if let Some(thread) = thread {
|
||||
let name = thread.thread().name().unwrap().to_string();
|
||||
let _ = thread.join();
|
||||
|
|
|
|||
|
|
@ -17,14 +17,25 @@ use smithay::{
|
|||
Seat,
|
||||
pointer::{CursorIcon, CursorImageAttributes, CursorImageStatus},
|
||||
},
|
||||
reexports::wayland_server::protocol::wl_surface,
|
||||
reexports::{
|
||||
calloop::{
|
||||
RegistrationToken,
|
||||
timer::{TimeoutAction, Timer},
|
||||
},
|
||||
wayland_server::protocol::wl_surface,
|
||||
},
|
||||
render_elements,
|
||||
utils::{
|
||||
Buffer as BufferCoords, Logical, Monotonic, Physical, Point, Scale, Size, Time, Transform,
|
||||
},
|
||||
wayland::compositor::{get_role, with_states},
|
||||
};
|
||||
use std::{collections::HashMap, io::Read, sync::Mutex};
|
||||
use std::{
|
||||
collections::HashMap,
|
||||
io::Read,
|
||||
sync::Mutex,
|
||||
time::{Duration, Instant},
|
||||
};
|
||||
use tracing::warn;
|
||||
use xcursor::{
|
||||
CursorTheme,
|
||||
|
|
@ -196,6 +207,10 @@ pub struct CursorStateInner {
|
|||
cursors: HashMap<CursorIcon, Cursor>,
|
||||
current_image: Option<Image>,
|
||||
image_cache: Vec<(Image, MemoryRenderBuffer)>,
|
||||
|
||||
hidden: bool,
|
||||
idle_timer: Option<RegistrationToken>,
|
||||
last_armed: Option<Instant>,
|
||||
}
|
||||
|
||||
impl CursorStateInner {
|
||||
|
|
@ -246,6 +261,10 @@ impl Default for CursorStateInner {
|
|||
cursors: HashMap::new(),
|
||||
current_image: None,
|
||||
image_cache: Vec::new(),
|
||||
|
||||
hidden: false,
|
||||
idle_timer: None,
|
||||
last_armed: None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -271,6 +290,10 @@ where
|
|||
let mut state_ref = seat_userdata.get::<CursorState>().unwrap().lock().unwrap();
|
||||
let state = &mut *state_ref;
|
||||
|
||||
if state.hidden {
|
||||
return Vec::new();
|
||||
}
|
||||
|
||||
let named_cursor = state.current_cursor.or(match cursor_status {
|
||||
CursorImageStatus::Named(named_cursor) => Some(named_cursor),
|
||||
_ => None,
|
||||
|
|
@ -335,3 +358,71 @@ where
|
|||
Vec::new()
|
||||
}
|
||||
}
|
||||
|
||||
const ACTIVITY_THROTTLE: Duration = Duration::from_millis(100);
|
||||
|
||||
/// Reveal the cursor and (re)arm the idle-hide timer; returns true if it was previously hidden
|
||||
pub fn notify_cursor_activity(state: &State, seat: &Seat<State>) -> bool {
|
||||
let timeout = state.common.config.cosmic_conf.cursor_hide_timeout;
|
||||
let loop_handle = &state.common.event_loop_handle;
|
||||
let cursor_state = seat.user_data().get::<CursorState>().unwrap();
|
||||
let now = Instant::now();
|
||||
|
||||
let (was_hidden, old_token) = {
|
||||
let mut inner = cursor_state.lock().unwrap();
|
||||
let was_hidden = inner.hidden;
|
||||
inner.hidden = false;
|
||||
|
||||
let throttled = timeout.is_some()
|
||||
&& !was_hidden
|
||||
&& inner.idle_timer.is_some()
|
||||
&& inner
|
||||
.last_armed
|
||||
.is_some_and(|t| now.duration_since(t) < ACTIVITY_THROTTLE);
|
||||
if throttled {
|
||||
return was_hidden;
|
||||
}
|
||||
|
||||
let old_token = inner.idle_timer.take();
|
||||
inner.last_armed = None;
|
||||
(was_hidden, old_token)
|
||||
};
|
||||
|
||||
if let Some(token) = old_token {
|
||||
loop_handle.remove(token);
|
||||
}
|
||||
|
||||
if let Some(secs) = timeout {
|
||||
let timer = Timer::from_duration(Duration::from_secs(secs as u64));
|
||||
let seat = seat.clone();
|
||||
if let Ok(token) = loop_handle.insert_source(timer, move |_, _, state| {
|
||||
hide_cursor(state, &seat);
|
||||
TimeoutAction::Drop
|
||||
}) {
|
||||
let mut inner = cursor_state.lock().unwrap();
|
||||
inner.idle_timer = Some(token);
|
||||
inner.last_armed = Some(now);
|
||||
}
|
||||
}
|
||||
|
||||
was_hidden
|
||||
}
|
||||
|
||||
fn hide_cursor(state: &mut State, seat: &Seat<State>) {
|
||||
if let Some(ptr) = seat.get_pointer()
|
||||
&& ptr.is_grabbed()
|
||||
{
|
||||
return;
|
||||
}
|
||||
let cursor_state = seat.user_data().get::<CursorState>().unwrap();
|
||||
{
|
||||
let mut inner = cursor_state.lock().unwrap();
|
||||
inner.hidden = true;
|
||||
inner.idle_timer = None;
|
||||
inner.last_armed = None;
|
||||
}
|
||||
let outputs: Vec<_> = state.common.shell.read().outputs().cloned().collect();
|
||||
for output in outputs {
|
||||
state.backend.schedule_render(&output);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -947,6 +947,25 @@ fn config_changed(config: cosmic_config::Config, keys: Vec<String>, state: &mut
|
|||
}
|
||||
}
|
||||
}
|
||||
"cursor_hide_timeout" => {
|
||||
let new = get_config::<Option<u32>>(&config, "cursor_hide_timeout");
|
||||
if new != state.common.config.cosmic_conf.cursor_hide_timeout {
|
||||
state.common.config.cosmic_conf.cursor_hide_timeout = new;
|
||||
let seats: Vec<_> = state.common.shell.read().seats.iter().cloned().collect();
|
||||
let mut needs_render = false;
|
||||
for seat in seats {
|
||||
needs_render |=
|
||||
crate::backend::render::cursor::notify_cursor_activity(state, &seat);
|
||||
}
|
||||
if needs_render {
|
||||
let outputs: Vec<_> =
|
||||
state.common.shell.read().outputs().cloned().collect();
|
||||
for output in outputs {
|
||||
state.backend.schedule_render(&output);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
// SPDX-License-Identifier: GPL-3.0-only
|
||||
|
||||
use crate::{
|
||||
backend::render::ElementFilter,
|
||||
backend::render::{ElementFilter, cursor::notify_cursor_activity},
|
||||
config::{
|
||||
Action, Config, PrivateAction,
|
||||
key_bindings::{
|
||||
|
|
@ -37,11 +37,12 @@ use cosmic_settings_config::shortcuts;
|
|||
use cosmic_settings_config::shortcuts::action::{Direction, ResizeDirection};
|
||||
use smithay::{
|
||||
backend::input::{
|
||||
AbsolutePositionEvent, Axis, AxisSource, Device, DeviceCapability, GestureBeginEvent,
|
||||
GestureEndEvent, GesturePinchUpdateEvent as _, GestureSwipeUpdateEvent as _, InputBackend,
|
||||
InputEvent, KeyState, KeyboardKeyEvent, PointerAxisEvent, ProximityState, Switch,
|
||||
SwitchState, SwitchToggleEvent, TabletToolButtonEvent, TabletToolEvent,
|
||||
TabletToolProximityEvent, TabletToolTipEvent, TabletToolTipState, TouchEvent,
|
||||
AbsolutePositionEvent, Axis, AxisRelativeDirection, AxisSource, Device, DeviceCapability,
|
||||
GestureBeginEvent, GestureEndEvent, GesturePinchUpdateEvent as _,
|
||||
GestureSwipeUpdateEvent as _, InputBackend, InputEvent, KeyState, KeyboardKeyEvent,
|
||||
PointerAxisEvent, ProximityState, Switch, SwitchState, SwitchToggleEvent,
|
||||
TabletToolButtonEvent, TabletToolEvent, TabletToolProximityEvent, TabletToolTipEvent,
|
||||
TabletToolTipState, TouchEvent,
|
||||
},
|
||||
desktop::{PopupKeyboardGrab, WindowSurfaceType, utils::under_from_surface_tree},
|
||||
input::{
|
||||
|
|
@ -308,6 +309,7 @@ impl State {
|
|||
let shell = self.common.shell.write();
|
||||
if let Some(seat) = shell.seats.for_device(&event.device()).cloned() {
|
||||
self.common.idle_notifier_state.notify_activity(&seat);
|
||||
notify_cursor_activity(self, &seat);
|
||||
let current_output = seat.active_output();
|
||||
|
||||
let mut position = seat.get_pointer().unwrap().current_location().as_global();
|
||||
|
|
@ -621,6 +623,7 @@ impl State {
|
|||
.cloned();
|
||||
if let Some(seat) = maybe_seat {
|
||||
self.common.idle_notifier_state.notify_activity(&seat);
|
||||
notify_cursor_activity(self, &seat);
|
||||
let output = seat.active_output();
|
||||
let geometry = output.geometry();
|
||||
let position = geometry.loc.to_f64()
|
||||
|
|
@ -687,6 +690,7 @@ impl State {
|
|||
return;
|
||||
};
|
||||
self.common.idle_notifier_state.notify_activity(&seat);
|
||||
notify_cursor_activity(self, &seat);
|
||||
|
||||
let current_focus = seat.get_keyboard().unwrap().current_focus();
|
||||
let shortcuts_inhibited = current_focus.as_ref().is_some_and(|f| {
|
||||
|
|
@ -898,6 +902,7 @@ impl State {
|
|||
.cloned();
|
||||
if let Some(seat) = maybe_seat {
|
||||
self.common.idle_notifier_state.notify_activity(&seat);
|
||||
notify_cursor_activity(self, &seat);
|
||||
|
||||
if seat.get_keyboard().unwrap().modifier_state().logo
|
||||
&& self
|
||||
|
|
@ -914,6 +919,12 @@ impl State {
|
|||
.or_else(|| event.amount(Axis::Vertical))
|
||||
.map(|val| val * scroll_factor)
|
||||
{
|
||||
if event.relative_direction(Axis::Vertical)
|
||||
== AxisRelativeDirection::Inverted
|
||||
{
|
||||
percentage *= -1.;
|
||||
}
|
||||
|
||||
if event.source() == AxisSource::Wheel {
|
||||
percentage *= 5.;
|
||||
}
|
||||
|
|
@ -1360,6 +1371,7 @@ impl State {
|
|||
let shell = self.common.shell.write();
|
||||
if let Some(seat) = shell.seats.for_device(&event.device()).cloned() {
|
||||
self.common.idle_notifier_state.notify_activity(&seat);
|
||||
notify_cursor_activity(self, &seat);
|
||||
let Some(output) =
|
||||
mapped_output_for_device(&self.common.config, &shell, &event.device())
|
||||
.cloned()
|
||||
|
|
@ -1425,6 +1437,7 @@ impl State {
|
|||
let shell = self.common.shell.write();
|
||||
if let Some(seat) = shell.seats.for_device(&event.device()).cloned() {
|
||||
self.common.idle_notifier_state.notify_activity(&seat);
|
||||
notify_cursor_activity(self, &seat);
|
||||
let Some(output) =
|
||||
mapped_output_for_device(&self.common.config, &shell, &event.device())
|
||||
.cloned()
|
||||
|
|
@ -1486,6 +1499,7 @@ impl State {
|
|||
.cloned();
|
||||
if let Some(seat) = maybe_seat {
|
||||
self.common.idle_notifier_state.notify_activity(&seat);
|
||||
notify_cursor_activity(self, &seat);
|
||||
if let Some(tool) = seat.tablet_seat().get_tool(&event.tool()) {
|
||||
match event.tip_state() {
|
||||
TabletToolTipState::Down => {
|
||||
|
|
@ -1508,6 +1522,7 @@ impl State {
|
|||
.cloned();
|
||||
if let Some(seat) = maybe_seat {
|
||||
self.common.idle_notifier_state.notify_activity(&seat);
|
||||
notify_cursor_activity(self, &seat);
|
||||
if let Some(tool) = seat.tablet_seat().get_tool(&event.tool()) {
|
||||
tool.button(
|
||||
event.button(),
|
||||
|
|
|
|||
|
|
@ -9,7 +9,7 @@ use cosmic::{
|
|||
};
|
||||
use cosmic_comp_config::ZoomMovement;
|
||||
use cosmic_config::ConfigSet;
|
||||
use keyframe::{ease, functions::EaseInOutCubic};
|
||||
use keyframe::{ease, functions::Linear};
|
||||
use smithay::{
|
||||
backend::renderer::{ImportMem, Renderer, element::AsRenderElements},
|
||||
desktop::space::SpaceElement,
|
||||
|
|
@ -135,7 +135,7 @@ impl OutputZoomState {
|
|||
let percentage =
|
||||
duration_since.as_millis() as f32 / ANIMATION_DURATION.as_millis() as f32;
|
||||
ease(
|
||||
EaseInOutCubic,
|
||||
Linear,
|
||||
EasePoint(*old_point),
|
||||
EasePoint(self.focal_point),
|
||||
percentage,
|
||||
|
|
@ -159,7 +159,7 @@ impl OutputZoomState {
|
|||
let percentage = Instant::now().duration_since(*start).as_millis() as f32
|
||||
/ ANIMATION_DURATION.as_millis() as f32;
|
||||
|
||||
ease(EaseInOutCubic, *old_level, self.level, percentage)
|
||||
ease(Linear, *old_level, self.level, percentage)
|
||||
} else {
|
||||
self.level
|
||||
}
|
||||
|
|
|
|||
|
|
@ -809,12 +809,14 @@ impl XwmHandler for State {
|
|||
);
|
||||
}
|
||||
if !shell.pending_windows.iter().any(|w| w.surface == window) {
|
||||
let fullscreen = window.is_fullscreen().then(|| seat.active_output());
|
||||
let maximized = window.is_maximized();
|
||||
let surface = CosmicSurface::from(window);
|
||||
shell.pending_windows.push(PendingWindow {
|
||||
surface,
|
||||
seat,
|
||||
fullscreen: None,
|
||||
maximized: false,
|
||||
fullscreen,
|
||||
maximized,
|
||||
sticky: false,
|
||||
})
|
||||
}
|
||||
|
|
@ -914,7 +916,11 @@ impl XwmHandler for State {
|
|||
// We only allow floating X11 windows to resize themselves. Nothing else
|
||||
let shell = self.common.shell.read();
|
||||
|
||||
// TODO: Fullscreen
|
||||
if window.is_fullscreen() {
|
||||
let _ = window.configure(None);
|
||||
return;
|
||||
}
|
||||
|
||||
if let Some(mapped) = shell
|
||||
.element_for_surface(&window)
|
||||
.filter(|mapped| !mapped.is_minimized())
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue