Add a mock backend for testing (including on other compositors)
Should help determine which issues are cosmic-comp bugs.
This commit is contained in:
parent
170e102275
commit
c75a48535a
13 changed files with 397 additions and 184 deletions
|
|
@ -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
216
src/backend/mock.rs
Normal 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
75
src/backend/mod.rs
Normal 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),
|
||||
}
|
||||
|
|
@ -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(
|
||||
|
|
@ -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>>,
|
||||
|
|
@ -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,
|
||||
99
src/main.rs
99
src/main.rs
|
|
@ -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
51
src/utils.rs
Normal 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()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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),
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue