Add a mock backend for testing (including on other compositors)

Should help determine which issues are cosmic-comp bugs.
This commit is contained in:
Ian Douglas Scott 2024-04-23 13:44:59 -07:00
parent 170e102275
commit c75a48535a
13 changed files with 397 additions and 184 deletions

View file

@ -39,6 +39,7 @@ wgpu = ["libcosmic/wgpu"]
# Debugging features
force-shm-screencopy = []
no-subsurfaces = ["force-shm-screencopy"]
mock-backend = []
[profile.dev]
# Not usable at opt-level 0, at least with software renderer

216
src/backend/mock.rs Normal file
View file

@ -0,0 +1,216 @@
// Copyright 2024 System76 <info@system76.com>
// SPDX-License-Identifier: GPL-3.0-only
use cosmic::{
cctk::{
cosmic_protocols::{
toplevel_info::v1::client::zcosmic_toplevel_handle_v1,
workspace::v1::client::zcosmic_workspace_handle_v1,
},
wayland_client::{
protocol::{wl_output, wl_shm},
Connection, WEnum,
},
},
iced::{
self,
futures::{executor::block_on, FutureExt, SinkExt},
},
iced_sctk::subsurface_widget::{Shmbuf, SubsurfaceBuffer},
};
use futures_channel::mpsc;
use std::{
collections::HashSet,
fs,
io::Write,
sync::{
atomic::{AtomicUsize, Ordering},
Arc,
},
thread,
};
use super::{CaptureImage, Cmd, Event};
use crate::utils;
#[derive(Eq, PartialEq, Clone, Debug, Hash)]
struct MockObjectId(usize);
fn create_solid_capture_image(r: u8, g: u8, b: u8) -> CaptureImage {
let mut file = fs::File::from(utils::create_memfile().unwrap());
for i in 0..512 * 512 {
file.write(&[r, g, b, 255]).unwrap();
}
CaptureImage {
width: 512,
height: 512,
wl_buffer: SubsurfaceBuffer::new(Arc::new(
Shmbuf {
fd: file.into(),
offset: 0,
width: 512,
height: 512,
stride: 512 * 4,
format: wl_shm::Format::Abgr8888,
}
.into(),
))
.0,
}
}
impl MockObjectId {
fn new() -> Self {
static NEXT_MOCK_ID: AtomicUsize = AtomicUsize::new(0);
Self(NEXT_MOCK_ID.fetch_add(1, Ordering::SeqCst))
}
}
#[derive(Eq, PartialEq, Clone, Debug, Hash)]
pub struct ZcosmicWorkspaceHandleV1(MockObjectId);
#[derive(Eq, PartialEq, Clone, Debug, Hash)]
pub struct ZcosmicToplevelHandleV1(MockObjectId);
#[derive(Clone, Debug, Default)]
pub struct CaptureFilter {
pub workspaces_on_outputs: Vec<wl_output::WlOutput>,
pub toplevels_on_workspaces: Vec<ZcosmicWorkspaceHandleV1>,
}
#[derive(Clone, Debug, Default)]
pub struct ToplevelInfo {
pub title: String,
pub app_id: String,
pub state: HashSet<zcosmic_toplevel_handle_v1::State>,
pub output: HashSet<wl_output::WlOutput>,
pub workspace: HashSet<ZcosmicWorkspaceHandleV1>,
}
#[derive(Clone, Debug)]
pub struct Workspace {
pub handle: ZcosmicWorkspaceHandleV1,
pub name: String,
// pub coordinates: Vec<u32>,
pub state: Vec<WEnum<zcosmic_workspace_handle_v1::State>>,
// pub capabilities: Vec<WEnum<zcosmic_workspace_handle_v1::ZcosmicWorkspaceCapabilitiesV1>>,
// pub tiling: Option<WEnum<zcosmic_workspace_handle_v1::TilingState>>,
}
pub fn subscription(conn: Connection) -> iced::Subscription<Event> {
iced::subscription::run_with_id("wayland-mock-sub", async { start(conn) }.flatten_stream())
}
struct AppData {
sender: mpsc::Sender<Event>,
outputs: Vec<wl_output::WlOutput>,
workspaces: Vec<(HashSet<wl_output::WlOutput>, Workspace)>,
}
impl AppData {
fn send_event(&mut self, event: Event) {
let _ = block_on(self.sender.send(event));
}
fn add_output(&mut self, output: &wl_output::WlOutput) {
// Add four workspaces for each output
let mut new_workspaces = Vec::new();
for i in 0..=4 {
let workspace_handle = ZcosmicWorkspaceHandleV1(MockObjectId::new());
let workspace = Workspace {
handle: workspace_handle.clone(),
name: format!("Workspace {i}"),
state: if i == 0 {
vec![WEnum::Value(zcosmic_workspace_handle_v1::State::Active)]
} else {
Vec::new()
},
};
// Add three toplevels for each workspace
for j in 0..=3 {
let toplevel_handle = ZcosmicToplevelHandleV1(MockObjectId::new());
let toplevel_info = ToplevelInfo {
title: format!("App {}", j),
app_id: "com.example.app".to_string(),
state: if i == 0 {
HashSet::from([zcosmic_toplevel_handle_v1::State::Activated])
} else {
HashSet::new()
},
output: HashSet::from([output.clone()]),
workspace: HashSet::from([workspace_handle.clone()]),
};
self.send_event(Event::NewToplevel(toplevel_handle.clone(), toplevel_info));
self.send_event(Event::ToplevelCapture(
toplevel_handle,
create_solid_capture_image(255, 0, 0),
));
}
self.workspaces
.push((HashSet::from([output.clone()]), workspace));
new_workspaces.push(workspace_handle);
}
self.send_event(Event::Workspaces(self.workspaces.clone()));
for workspace_handle in new_workspaces {
self.send_event(Event::WorkspaceCapture(
workspace_handle,
output.clone(),
create_solid_capture_image(0, 255, 0),
));
}
self.outputs.push(output.clone());
}
fn handle_cmd(&mut self, cmd: Cmd) {
match cmd {
Cmd::CaptureFilter(filter) => {
for output in &filter.workspaces_on_outputs {
if !self.outputs.contains(output) {
self.add_output(output);
}
}
}
Cmd::ActivateToplevel(toplevel_handle) => {
println!("Activate {:?}", toplevel_handle);
}
Cmd::CloseToplevel(toplevel_handle) => {
println!("Close {:?}", toplevel_handle);
}
Cmd::MoveToplevelToWorkspace(toplevel_handle, workspace_handle, output) => {}
Cmd::ActivateWorkspace(workspace_handle) => {
println!("Activate {:?}", workspace_handle);
}
}
}
}
fn start(conn: Connection) -> mpsc::Receiver<Event> {
let (sender, receiver) = mpsc::channel(20);
thread::spawn(move || {
let mut event_loop = calloop::EventLoop::try_new().unwrap();
let (cmd_sender, cmd_channel) = calloop::channel::channel();
event_loop
.handle()
.insert_source(cmd_channel, |cmd, (), app_data: &mut AppData| match cmd {
calloop::channel::Event::Msg(cmd) => app_data.handle_cmd(cmd),
calloop::channel::Event::Closed => {}
})
.unwrap();
let mut app_data = AppData {
sender,
outputs: Vec::new(),
workspaces: Vec::new(),
};
app_data.send_event(Event::CmdSender(cmd_sender));
loop {
event_loop.dispatch(None, &mut app_data).unwrap();
}
});
receiver
}
// TODO WorkspaceCapture, ToplevelCapture, NewToplevel, Workspaces

75
src/backend/mod.rs Normal file
View file

@ -0,0 +1,75 @@
// Copyright 2023 System76 <info@system76.com>
// SPDX-License-Identifier: GPL-3.0-only
//! The backend of getting workspace/toplevel information and previews, and
//! sending commands to change them.
//!
//! There are two backends: one that uses cosmic-comp protocols, and a mock
//! backend for testing without any special protocols.
use cosmic::{
cctk::wayland_client::protocol::wl_output, iced_sctk::subsurface_widget::SubsurfaceBuffer,
};
use std::collections::HashSet;
// Wayland backend using cosmic-comp specific protocols
#[cfg(not(feature = "mock-backend"))]
mod wayland;
#[cfg(not(feature = "mock-backend"))]
pub use cosmic::cctk::{
cosmic_protocols::{
toplevel_info::v1::client::zcosmic_toplevel_handle_v1::ZcosmicToplevelHandleV1,
workspace::v1::client::zcosmic_workspace_handle_v1::ZcosmicWorkspaceHandleV1,
},
toplevel_info::ToplevelInfo,
workspace::Workspace,
};
#[cfg(not(feature = "mock-backend"))]
pub use wayland::subscription;
// Mock backend
#[cfg(feature = "mock-backend")]
mod mock;
#[cfg(feature = "mock-backend")]
pub use mock::{
subscription, ToplevelInfo, Workspace, ZcosmicToplevelHandleV1, ZcosmicWorkspaceHandleV1,
};
#[derive(Clone, Debug, Default)]
pub struct CaptureFilter {
pub workspaces_on_outputs: Vec<wl_output::WlOutput>,
pub toplevels_on_workspaces: Vec<ZcosmicWorkspaceHandleV1>,
}
#[derive(Clone, Debug)]
pub struct CaptureImage {
pub width: u32,
pub height: u32,
pub wl_buffer: SubsurfaceBuffer,
#[cfg(feature = "no-subsurfaces")]
pub image: cosmic::widget::image::Handle,
}
#[derive(Clone, Debug)]
pub enum Event {
CmdSender(calloop::channel::Sender<Cmd>),
Workspaces(Vec<(HashSet<wl_output::WlOutput>, Workspace)>),
WorkspaceCapture(ZcosmicWorkspaceHandleV1, wl_output::WlOutput, CaptureImage),
NewToplevel(ZcosmicToplevelHandleV1, ToplevelInfo),
UpdateToplevel(ZcosmicToplevelHandleV1, ToplevelInfo),
CloseToplevel(ZcosmicToplevelHandleV1),
ToplevelCapture(ZcosmicToplevelHandleV1, CaptureImage),
}
#[derive(Debug)]
pub enum Cmd {
CaptureFilter(CaptureFilter),
ActivateToplevel(ZcosmicToplevelHandleV1),
CloseToplevel(ZcosmicToplevelHandleV1),
MoveToplevelToWorkspace(
ZcosmicToplevelHandleV1,
ZcosmicWorkspaceHandleV1,
wl_output::WlOutput,
),
ActivateWorkspace(ZcosmicWorkspaceHandleV1),
}

View file

@ -17,52 +17,7 @@ use std::{
use wayland_protocols::wp::linux_dmabuf::zv1::client::zwp_linux_buffer_params_v1;
use super::AppData;
#[cfg(target_os = "linux")]
fn create_memfd() -> rustix::io::Result<OwnedFd> {
let fd = rustix::io::retry_on_intr(|| {
rustix::fs::memfd_create(
"cosmic-workspaces-shm",
rustix::fs::MemfdFlags::CLOEXEC | rustix::fs::MemfdFlags::ALLOW_SEALING,
)
})?;
let _ = rustix::fs::fcntl_add_seals(
&fd,
rustix::fs::SealFlags::SHRINK | rustix::fs::SealFlags::SEAL,
);
Ok(fd)
}
fn create_memfile() -> rustix::io::Result<OwnedFd> {
#[cfg(target_os = "linux")]
if let Ok(fd) = create_memfd() {
return Ok(fd);
}
loop {
let flags = ShmOFlags::CREATE | ShmOFlags::EXCL | ShmOFlags::RDWR;
let time = SystemTime::now();
let name = format!(
"/cosmic-workspaces-shm-{}",
time.duration_since(UNIX_EPOCH).unwrap().subsec_nanos()
);
match rustix::shm::shm_open(&name, flags, 0600.into()) {
Ok(fd) => match rustix::shm::shm_unlink(&name) {
Ok(_) => return Ok(fd),
Err(errno) => {
return Err(errno.into());
}
},
#[allow(unreachable_patterns)]
Err(Errno::EXIST | Errno::EXIST) => {
continue;
}
Err(err) => return Err(err.into()),
}
}
}
use crate::utils;
pub struct Buffer {
pub backing: Arc<BufferSource>,
@ -75,7 +30,7 @@ pub struct Buffer {
impl AppData {
fn create_shm_buffer(&self, format: u32, (width, height): (u32, u32)) -> Buffer {
let fd = create_memfile().unwrap(); // XXX?
let fd = utils::create_memfile().unwrap(); // XXX?
rustix::fs::ftruncate(&fd, width as u64 * height as u64 * 4).unwrap();
let pool = self.shm_state.wl_shm().create_pool(

View file

@ -22,12 +22,6 @@ pub enum CaptureSource {
),
}
#[derive(Clone, Debug, Default)]
pub struct CaptureFilter {
pub workspaces_on_outputs: Vec<wl_output::WlOutput>,
pub toplevels_on_workspaces: Vec<zcosmic_workspace_handle_v1::ZcosmicWorkspaceHandleV1>,
}
pub struct Capture {
pub source: CaptureSource,
pub session: Mutex<Option<ScreencopySession>>,

View file

@ -3,10 +3,6 @@
use calloop_wayland_source::WaylandSource;
use cctk::{
cosmic_protocols::{
toplevel_info::v1::client::zcosmic_toplevel_handle_v1,
workspace::v1::client::zcosmic_workspace_handle_v1,
},
screencopy::ScreencopyState,
sctk::{
self,
@ -15,12 +11,10 @@ use cctk::{
seat::{SeatHandler, SeatState},
shm::{Shm, ShmHandler},
},
toplevel_info::{ToplevelInfo, ToplevelInfoState},
toplevel_info::ToplevelInfoState,
toplevel_management::ToplevelManagerState,
wayland_client::{
globals::registry_queue_init,
protocol::{wl_output, wl_seat},
Connection, Proxy, QueueHandle,
globals::registry_queue_init, protocol::wl_seat, Connection, Proxy, QueueHandle,
},
workspace::WorkspaceState,
};
@ -29,16 +23,8 @@ use cosmic::iced::{
self,
futures::{executor::block_on, FutureExt, SinkExt},
};
use cosmic::iced_sctk::subsurface_widget::SubsurfaceBuffer;
use futures_channel::mpsc;
use std::{
cell::RefCell,
collections::{HashMap, HashSet},
fs,
path::PathBuf,
sync::Arc,
thread,
};
use std::{cell::RefCell, collections::HashMap, fs, path::PathBuf, sync::Arc, thread};
mod buffer;
use buffer::Buffer;
@ -50,60 +36,12 @@ use screencopy::{ScreencopySession, SessionData};
mod toplevel;
mod workspace;
pub use capture::CaptureFilter;
// TODO define subscription for a particular output/workspace/toplevel (but we want to rate limit?)
#[derive(Clone, Debug)]
pub enum Event {
CmdSender(calloop::channel::Sender<Cmd>),
Workspaces(Vec<(HashSet<wl_output::WlOutput>, cctk::workspace::Workspace)>),
WorkspaceCapture(
zcosmic_workspace_handle_v1::ZcosmicWorkspaceHandleV1,
wl_output::WlOutput,
CaptureImage,
),
NewToplevel(
zcosmic_toplevel_handle_v1::ZcosmicToplevelHandleV1,
ToplevelInfo,
),
UpdateToplevel(
zcosmic_toplevel_handle_v1::ZcosmicToplevelHandleV1,
ToplevelInfo,
),
CloseToplevel(zcosmic_toplevel_handle_v1::ZcosmicToplevelHandleV1),
ToplevelCapture(
zcosmic_toplevel_handle_v1::ZcosmicToplevelHandleV1,
CaptureImage,
),
}
#[derive(Clone, Debug)]
pub struct CaptureImage {
pub width: u32,
pub height: u32,
pub wl_buffer: SubsurfaceBuffer,
#[cfg(feature = "no-subsurfaces")]
pub image: cosmic::widget::image::Handle,
}
use super::{CaptureFilter, CaptureImage, Cmd, Event};
pub fn subscription(conn: Connection) -> iced::Subscription<Event> {
iced::subscription::run_with_id("wayland-sub", async { start(conn) }.flatten_stream())
}
#[derive(Debug)]
pub enum Cmd {
CaptureFilter(CaptureFilter),
ActivateToplevel(zcosmic_toplevel_handle_v1::ZcosmicToplevelHandleV1),
CloseToplevel(zcosmic_toplevel_handle_v1::ZcosmicToplevelHandleV1),
MoveToplevelToWorkspace(
zcosmic_toplevel_handle_v1::ZcosmicToplevelHandleV1,
zcosmic_workspace_handle_v1::ZcosmicWorkspaceHandleV1,
wl_output::WlOutput,
),
ActivateWorkspace(zcosmic_workspace_handle_v1::ZcosmicWorkspaceHandleV1),
}
pub struct AppData {
qh: QueueHandle<Self>,
dmabuf_state: DmabufState,

View file

@ -4,14 +4,9 @@
#![allow(clippy::single_match)]
use cctk::{
cosmic_protocols::{
toplevel_info::v1::client::zcosmic_toplevel_handle_v1,
workspace::v1::client::zcosmic_workspace_handle_v1,
},
cosmic_protocols::workspace::v1::client::zcosmic_workspace_handle_v1,
sctk::shell::wlr_layer::{Anchor, KeyboardInteractivity, Layer},
toplevel_info::ToplevelInfo,
wayland_client::{
backend::ObjectId,
protocol::{wl_data_device_manager::DndAction, wl_output},
Connection, Proxy, WEnum,
},
@ -52,8 +47,10 @@ use std::{
mod desktop_info;
#[macro_use]
mod localize;
mod backend;
mod view;
mod wayland;
use backend::{ToplevelInfo, ZcosmicToplevelHandleV1, ZcosmicWorkspaceHandleV1};
mod utils;
mod widgets;
#[derive(Clone, Debug, Default, PartialEq, CosmicConfigEntry)]
@ -94,14 +91,13 @@ impl CosmicFlags for Args {
}
struct WlDndId {
id: ObjectId,
mime_type: &'static str,
}
impl DataFromMimeType for WlDndId {
fn from_mime_type(&self, mime_type: &str) -> Option<Vec<u8>> {
if mime_type == self.mime_type {
Some(self.id.protocol_id().to_string().into_bytes())
Some(Vec::new())
} else {
None
}
@ -111,16 +107,16 @@ impl DataFromMimeType for WlDndId {
#[derive(Clone, Debug)]
enum Msg {
WaylandEvent(WaylandEvent),
Wayland(wayland::Event),
Wayland(backend::Event),
Close,
ActivateWorkspace(zcosmic_workspace_handle_v1::ZcosmicWorkspaceHandleV1),
ActivateWorkspace(ZcosmicWorkspaceHandleV1),
#[allow(dead_code)]
CloseWorkspace(zcosmic_workspace_handle_v1::ZcosmicWorkspaceHandleV1),
ActivateToplevel(zcosmic_toplevel_handle_v1::ZcosmicToplevelHandleV1),
CloseToplevel(zcosmic_toplevel_handle_v1::ZcosmicToplevelHandleV1),
CloseWorkspace(ZcosmicWorkspaceHandleV1),
ActivateToplevel(ZcosmicToplevelHandleV1),
CloseToplevel(ZcosmicToplevelHandleV1),
StartDrag(Size, DragSurface),
DndWorkspaceEnter(
zcosmic_workspace_handle_v1::ZcosmicWorkspaceHandleV1,
ZcosmicWorkspaceHandleV1,
wl_output::WlOutput,
DndAction,
Vec<String>,
@ -139,17 +135,17 @@ enum Msg {
#[derive(Debug)]
struct Workspace {
name: String,
img_for_output: HashMap<wl_output::WlOutput, wayland::CaptureImage>,
handle: zcosmic_workspace_handle_v1::ZcosmicWorkspaceHandleV1,
img_for_output: HashMap<wl_output::WlOutput, backend::CaptureImage>,
handle: ZcosmicWorkspaceHandleV1,
outputs: HashSet<wl_output::WlOutput>,
is_active: bool,
}
#[derive(Debug)]
struct Toplevel {
handle: zcosmic_toplevel_handle_v1::ZcosmicToplevelHandleV1,
handle: ZcosmicToplevelHandleV1,
info: ToplevelInfo,
img: Option<wayland::CaptureImage>,
img: Option<backend::CaptureImage>,
icon: Option<PathBuf>,
}
@ -171,11 +167,11 @@ struct LayerSurface {
enum DragSurface {
#[allow(dead_code)]
Workspace {
handle: zcosmic_workspace_handle_v1::ZcosmicWorkspaceHandleV1,
handle: ZcosmicWorkspaceHandleV1,
output: wl_output::WlOutput,
},
Toplevel {
handle: zcosmic_toplevel_handle_v1::ZcosmicToplevelHandleV1,
handle: ZcosmicToplevelHandleV1,
output: wl_output::WlOutput,
},
}
@ -202,34 +198,28 @@ struct App {
toplevels: Vec<Toplevel>,
conn: Option<Connection>,
visible: bool,
wayland_cmd_sender: Option<calloop::channel::Sender<wayland::Cmd>>,
wayland_cmd_sender: Option<calloop::channel::Sender<backend::Cmd>>,
drag_surface: Option<(SurfaceId, DragSurface, Size)>,
conf: Conf,
core: cosmic::app::Core,
drop_target: Option<(
zcosmic_workspace_handle_v1::ZcosmicWorkspaceHandleV1,
wl_output::WlOutput,
)>,
drop_target: Option<(ZcosmicWorkspaceHandleV1, wl_output::WlOutput)>,
}
impl App {
fn workspace_for_handle(
&self,
handle: &zcosmic_workspace_handle_v1::ZcosmicWorkspaceHandleV1,
) -> Option<&Workspace> {
fn workspace_for_handle(&self, handle: &ZcosmicWorkspaceHandleV1) -> Option<&Workspace> {
self.workspaces.iter().find(|i| &i.handle == handle)
}
fn workspace_for_handle_mut(
&mut self,
handle: &zcosmic_workspace_handle_v1::ZcosmicWorkspaceHandleV1,
handle: &ZcosmicWorkspaceHandleV1,
) -> Option<&mut Workspace> {
self.workspaces.iter_mut().find(|i| &i.handle == handle)
}
fn toplevel_for_handle_mut(
&mut self,
handle: &zcosmic_toplevel_handle_v1::ZcosmicToplevelHandleV1,
handle: &ZcosmicToplevelHandleV1,
) -> Option<&mut Toplevel> {
self.toplevels.iter_mut().find(|i| &i.handle == handle)
}
@ -311,14 +301,14 @@ impl App {
)
}
fn send_wayland_cmd(&self, cmd: wayland::Cmd) {
fn send_wayland_cmd(&self, cmd: backend::Cmd) {
if let Some(sender) = self.wayland_cmd_sender.as_ref() {
sender.send(cmd).unwrap();
}
}
fn update_capture_filter(&self) {
let mut capture_filter = wayland::CaptureFilter::default();
let mut capture_filter = backend::CaptureFilter::default();
if self.visible {
capture_filter.workspaces_on_outputs =
self.outputs.iter().map(|x| x.handle.clone()).collect();
@ -329,7 +319,7 @@ impl App {
.map(|x| x.handle.clone())
.collect();
}
self.send_wayland_cmd(wayland::Cmd::CaptureFilter(capture_filter));
self.send_wayland_cmd(backend::Cmd::CaptureFilter(capture_filter));
}
}
@ -413,10 +403,10 @@ impl Application for App {
},
Msg::Wayland(evt) => {
match evt {
wayland::Event::CmdSender(sender) => {
backend::Event::CmdSender(sender) => {
self.wayland_cmd_sender = Some(sender);
}
wayland::Event::Workspaces(workspaces) => {
backend::Event::Workspaces(workspaces) => {
let old_workspaces = mem::take(&mut self.workspaces);
self.workspaces = Vec::new();
for (outputs, workspace) in workspaces {
@ -441,7 +431,7 @@ impl Application for App {
}
self.update_capture_filter();
}
wayland::Event::NewToplevel(handle, info) => {
backend::Event::NewToplevel(handle, info) => {
log::debug!("New toplevel: {info:?}");
self.toplevels.push(Toplevel {
icon: desktop_info::icon_for_app_id(info.app_id.clone()),
@ -450,7 +440,7 @@ impl Application for App {
img: None,
});
}
wayland::Event::UpdateToplevel(handle, info) => {
backend::Event::UpdateToplevel(handle, info) => {
if let Some(toplevel) =
self.toplevels.iter_mut().find(|x| x.handle == handle)
{
@ -458,17 +448,17 @@ impl Application for App {
toplevel.info = info;
}
}
wayland::Event::CloseToplevel(handle) => {
backend::Event::CloseToplevel(handle) => {
if let Some(idx) = self.toplevels.iter().position(|x| x.handle == handle) {
self.toplevels.remove(idx);
}
}
wayland::Event::WorkspaceCapture(handle, output_name, image) => {
backend::Event::WorkspaceCapture(handle, output_name, image) => {
if let Some(workspace) = self.workspace_for_handle_mut(&handle) {
workspace.img_for_output.insert(output_name, image);
}
}
wayland::Event::ToplevelCapture(handle, image) => {
backend::Event::ToplevelCapture(handle, image) => {
if let Some(toplevel) = self.toplevel_for_handle_mut(&handle) {
//println!("Got toplevel image!");
toplevel.img = Some(image);
@ -480,10 +470,10 @@ impl Application for App {
return self.hide();
}
Msg::ActivateWorkspace(workspace_handle) => {
self.send_wayland_cmd(wayland::Cmd::ActivateWorkspace(workspace_handle));
self.send_wayland_cmd(backend::Cmd::ActivateWorkspace(workspace_handle));
}
Msg::ActivateToplevel(toplevel_handle) => {
self.send_wayland_cmd(wayland::Cmd::ActivateToplevel(toplevel_handle));
self.send_wayland_cmd(backend::Cmd::ActivateToplevel(toplevel_handle));
return self.hide();
}
Msg::CloseWorkspace(_workspace_handle) => {
@ -502,16 +492,12 @@ impl Application for App {
}
Msg::CloseToplevel(toplevel_handle) => {
// TODO confirmation?
self.send_wayland_cmd(wayland::Cmd::CloseToplevel(toplevel_handle));
self.send_wayland_cmd(backend::Cmd::CloseToplevel(toplevel_handle));
}
Msg::StartDrag(size, drag_surface) => {
let (wl_id, output, mime_type) = match &drag_surface {
DragSurface::Workspace { handle, output } => {
(handle.clone().id(), output, &*WORKSPACE_MIME)
}
DragSurface::Toplevel { handle, output } => {
(handle.clone().id(), output, &*TOPLEVEL_MIME)
}
let (output, mime_type) = match &drag_surface {
DragSurface::Workspace { handle, output } => (output, &*WORKSPACE_MIME),
DragSurface::Toplevel { handle, output } => (output, &*TOPLEVEL_MIME),
};
let id = SurfaceId::unique();
if let Some((parent_id, _)) = self
@ -525,10 +511,7 @@ impl Application for App {
DndAction::Move,
*parent_id,
Some(DndIcon::Custom(id)),
Box::new(WlDndId {
id: wl_id,
mime_type,
}),
Box::new(WlDndId { mime_type }),
);
}
}
@ -559,7 +542,7 @@ impl Application for App {
.and_then(|s| u32::from_str(s).ok());
if let Some((_, DragSurface::Toplevel { handle, .. }, _)) = &self.drag_surface {
if let Some(drop_target) = &self.drop_target {
self.send_wayland_cmd(wayland::Cmd::MoveToplevelToWorkspace(
self.send_wayland_cmd(backend::Cmd::MoveToplevelToWorkspace(
handle.clone(),
drop_target.0.clone(),
drop_target.1.clone(),
@ -640,7 +623,7 @@ impl Application for App {
});
let mut subscriptions = vec![events, config_subscription, comp_config_subscription];
if let Some(conn) = self.conn.clone() {
subscriptions.push(wayland::subscription(conn).map(Msg::Wayland));
subscriptions.push(backend::subscription(conn).map(Msg::Wayland));
}
iced::Subscription::batch(subscriptions)
}

51
src/utils.rs Normal file
View file

@ -0,0 +1,51 @@
use rustix::{io::Errno, shm::ShmOFlags};
use std::{
os::fd::OwnedFd,
time::{SystemTime, UNIX_EPOCH},
};
#[cfg(target_os = "linux")]
fn create_memfd() -> rustix::io::Result<OwnedFd> {
let fd = rustix::io::retry_on_intr(|| {
rustix::fs::memfd_create(
"cosmic-workspaces-shm",
rustix::fs::MemfdFlags::CLOEXEC | rustix::fs::MemfdFlags::ALLOW_SEALING,
)
})?;
let _ = rustix::fs::fcntl_add_seals(
&fd,
rustix::fs::SealFlags::SHRINK | rustix::fs::SealFlags::SEAL,
);
Ok(fd)
}
pub fn create_memfile() -> rustix::io::Result<OwnedFd> {
#[cfg(target_os = "linux")]
if let Ok(fd) = create_memfd() {
return Ok(fd);
}
loop {
let flags = ShmOFlags::CREATE | ShmOFlags::EXCL | ShmOFlags::RDWR;
let time = SystemTime::now();
let name = format!(
"/cosmic-workspaces-shm-{}",
time.duration_since(UNIX_EPOCH).unwrap().subsec_nanos()
);
match rustix::shm::shm_open(&name, flags, 0600.into()) {
Ok(fd) => match rustix::shm::shm_unlink(&name) {
Ok(_) => return Ok(fd),
Err(errno) => {
return Err(errno.into());
}
},
#[allow(unreachable_patterns)]
Err(Errno::EXIST | Errno::EXIST) => {
continue;
}
Err(err) => return Err(err.into()),
}
}
}

View file

@ -1,8 +1,5 @@
use cctk::{
cosmic_protocols::{
toplevel_info::v1::client::zcosmic_toplevel_handle_v1,
workspace::v1::client::zcosmic_workspace_handle_v1,
},
cosmic_protocols::toplevel_info::v1::client::zcosmic_toplevel_handle_v1,
wayland_client::protocol::wl_output,
};
use cosmic::{
@ -19,7 +16,10 @@ use cosmic::{
};
use cosmic_comp_config::workspace::WorkspaceLayout;
use crate::{wayland::CaptureImage, App, DragSurface, LayerSurface, Msg, Toplevel, Workspace};
use crate::{
backend::{self, CaptureImage},
App, DragSurface, LayerSurface, Msg, Toplevel, Workspace,
};
pub(crate) fn layer_surface<'a>(
app: &'a App,
@ -148,7 +148,7 @@ fn workspaces_sidebar<'a>(
workspaces: impl Iterator<Item = &'a Workspace>,
output: &'a wl_output::WlOutput,
layout: WorkspaceLayout,
drop_target: Option<&zcosmic_workspace_handle_v1::ZcosmicWorkspaceHandleV1>,
drop_target: Option<&backend::ZcosmicWorkspaceHandleV1>,
) -> cosmic::Element<'a, Msg> {
let sidebar_entries = workspaces
.map(|w| workspace_sidebar_entry(w, output, drop_target == Some(&w.handle)))
@ -272,7 +272,7 @@ fn toplevel_previews<'a>(
toplevels: impl Iterator<Item = &'a Toplevel>,
output: &'a wl_output::WlOutput,
layout: WorkspaceLayout,
drag_toplevel: Option<&'a zcosmic_toplevel_handle_v1::ZcosmicToplevelHandleV1>,
drag_toplevel: Option<&'a backend::ZcosmicToplevelHandleV1>,
) -> cosmic::Element<'a, Msg> {
let (width, height) = match layout {
WorkspaceLayout::Vertical => (iced::Length::FillPortion(4), iced::Length::Fill),