Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions docs/features.md
Original file line number Diff line number Diff line change
Expand Up @@ -148,6 +148,7 @@ Jay supports the following wayland protocols:
| ext_output_image_capture_source_manager_v1 | 1 | |
| ext_session_lock_manager_v1 | 1 | Yes |
| ext_transient_seat_manager_v1 | 1[^ts_rejected] | Yes |
| ext_workspace_manager_v1 | 1 | Yes |
| jay_tray_v1 | 1 | |
| org_kde_kwin_server_decoration_manager | 1 | |
| wl_compositor | 6 | |
Expand Down
1 change: 1 addition & 0 deletions release-notes.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
- Add an idle grace period. During the grace period, the screen goes black but is neither
disabled nor locked. This is similar to how android handles going idle. The default is
5 seconds.
- Implement ext-workspace-v1.

# 1.7.0 (2024-10-25)

Expand Down
1 change: 1 addition & 0 deletions src/client.rs
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,7 @@ bitflags! {
CAP_SEAT_MANAGER = 1 << 8,
CAP_DRM_LEASE = 1 << 9,
CAP_INPUT_METHOD = 1 << 10,
CAP_WORKSPACE = 1 << 11,
}

pub const CAPS_DEFAULT: ClientCaps = ClientCaps(CAP_LAYER_SHELL.0 | CAP_DRM_LEASE.0);
Expand Down
11 changes: 8 additions & 3 deletions src/client/objects.rs
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ use {
xdg_surface::{xdg_popup::XdgPopup, xdg_toplevel::XdgToplevel, XdgSurface},
WlSurface,
},
workspace_manager::ext_workspace_group_handle_v1::ExtWorkspaceGroupHandleV1,
wp_drm_lease_connector_v1::WpDrmLeaseConnectorV1,
wp_linux_drm_syncobj_timeline_v1::WpLinuxDrmSyncobjTimelineV1,
xdg_positioner::XdgPositioner,
Expand All @@ -39,9 +40,9 @@ use {
},
wire::{
ExtDataControlSourceV1Id, ExtForeignToplevelHandleV1Id, ExtImageCaptureSourceV1Id,
ExtImageCopyCaptureSessionV1Id, JayOutputId, JayScreencastId, JayToplevelId,
JayWorkspaceId, WlBufferId, WlDataSourceId, WlOutputId, WlPointerId, WlRegionId,
WlRegistryId, WlSeatId, WlSurfaceId, WpDrmLeaseConnectorV1Id,
ExtImageCopyCaptureSessionV1Id, ExtWorkspaceGroupHandleV1Id, JayOutputId,
JayScreencastId, JayToplevelId, JayWorkspaceId, WlBufferId, WlDataSourceId, WlOutputId,
WlPointerId, WlRegionId, WlRegistryId, WlSeatId, WlSurfaceId, WpDrmLeaseConnectorV1Id,
WpLinuxDrmSyncobjTimelineV1Id, XdgPopupId, XdgPositionerId, XdgSurfaceId,
XdgToplevelId, XdgWmBaseId, ZwlrDataControlSourceV1Id, ZwpPrimarySelectionSourceV1Id,
ZwpTabletToolV2Id,
Expand Down Expand Up @@ -82,6 +83,8 @@ pub struct Objects {
pub ext_copy_sessions:
CopyHashMap<ExtImageCopyCaptureSessionV1Id, Rc<ExtImageCopyCaptureSessionV1>>,
pub ext_data_sources: CopyHashMap<ExtDataControlSourceV1Id, Rc<ExtDataControlSourceV1>>,
pub ext_workspace_groups:
CopyHashMap<ExtWorkspaceGroupHandleV1Id, Rc<ExtWorkspaceGroupHandleV1>>,
ids: RefCell<Vec<usize>>,
}

Expand Down Expand Up @@ -119,6 +122,7 @@ impl Objects {
foreign_toplevel_handles: Default::default(),
ext_copy_sessions: Default::default(),
ext_data_sources: Default::default(),
ext_workspace_groups: Default::default(),
ids: RefCell::new(vec![]),
}
}
Expand Down Expand Up @@ -160,6 +164,7 @@ impl Objects {
self.foreign_toplevel_handles.clear();
self.ext_copy_sessions.clear();
self.ext_data_sources.clear();
self.ext_workspace_groups.clear();
}

pub fn id<T>(&self, client_data: &Client) -> Result<T, ClientError>
Expand Down
9 changes: 9 additions & 0 deletions src/compositor.rs
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ use {
jay_screencast::{perform_screencast_realloc, perform_toplevel_screencasts},
wl_output::{OutputId, PersistentOutputState, WlOutputGlobal},
wl_surface::{zwp_input_popup_surface_v2::input_popup_positioning, NoneSurfaceExt},
workspace_manager::workspace_manager_done,
},
io_uring::{IoUring, IoUringError},
leaks,
Expand Down Expand Up @@ -278,6 +279,7 @@ fn start_compositor2(
const_40hz_latch: Default::default(),
tray_item_ids: Default::default(),
data_control_device_ids: Default::default(),
workspace_managers: Default::default(),
});
state.tracker.register(ClientId::from_raw(0));
create_dummy_output(&state);
Expand Down Expand Up @@ -446,6 +448,10 @@ fn start_global_event_handlers(
Phase::Present,
handle_const_40hz_latch(state.clone()),
),
eng.spawn(
"workspace manager done",
workspace_manager_done(state.clone()),
),
]
}

Expand Down Expand Up @@ -593,6 +599,7 @@ fn create_dummy_output(state: &Rc<State>) {
before_latch_event: Default::default(),
tray_start_rel: Default::default(),
tray_items: Default::default(),
ext_workspace_groups: Default::default(),
});
let dummy_workspace = Rc::new(WorkspaceNode {
id: state.node_ids.next(),
Expand All @@ -615,6 +622,8 @@ fn create_dummy_output(state: &Rc<State>) {
title_texture: Default::default(),
attention_requests: Default::default(),
render_highlight: Default::default(),
ext_workspaces: Default::default(),
opt: Default::default(),
});
*dummy_workspace.output_link.borrow_mut() =
Some(dummy_output.workspaces.add_last(dummy_workspace.clone()));
Expand Down
2 changes: 2 additions & 0 deletions src/globals.rs
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ use {
wl_shm::WlShmGlobal,
wl_subcompositor::WlSubcompositorGlobal,
wl_surface::xwayland_shell_v1::XwaylandShellV1Global,
workspace_manager::ext_workspace_manager_v1::ExtWorkspaceManagerV1Global,
wp_alpha_modifier_v1::WpAlphaModifierV1Global,
wp_commit_timing_manager_v1::WpCommitTimingManagerV1Global,
wp_content_type_manager_v1::WpContentTypeManagerV1Global,
Expand Down Expand Up @@ -213,6 +214,7 @@ impl Globals {
add_singleton!(WpCommitTimingManagerV1Global);
add_singleton!(ExtDataControlManagerV1Global);
add_singleton!(WlFixesGlobal);
add_singleton!(ExtWorkspaceManagerV1Global);
}

pub fn add_backend_singletons(&self, backend: &Rc<dyn Backend>) {
Expand Down
1 change: 1 addition & 0 deletions src/ifs.rs
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@ pub mod wl_shm;
pub mod wl_shm_pool;
pub mod wl_subcompositor;
pub mod wl_surface;
pub mod workspace_manager;
pub mod wp_alpha_modifier_v1;
pub mod wp_commit_timing_manager_v1;
pub mod wp_content_type_manager_v1;
Expand Down
7 changes: 6 additions & 1 deletion src/ifs/wl_output.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ use {
state::{ConnectorData, State},
tree::{calculate_logical_size, OutputNode, TearingMode, VrrMode},
utils::{
cell_ext::CellExt, clonecell::CloneCell, copyhashmap::CopyHashMap,
cell_ext::CellExt, clonecell::CloneCell, copyhashmap::CopyHashMap, rc_eq::rc_eq,
transform_ext::TransformExt,
},
wire::{wl_output::*, WlOutputId, ZxdgOutputV1Id},
Expand Down Expand Up @@ -238,6 +238,11 @@ impl WlOutputGlobal {
if obj.version >= SEND_DONE_SINCE {
obj.send_done();
}
for group in client.objects.ext_workspace_groups.lock().values() {
if rc_eq(&group.output, &self.opt) {
group.handle_new_output(&obj);
}
}
Ok(())
}

Expand Down
64 changes: 64 additions & 0 deletions src/ifs/workspace_manager.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
use {
crate::{
client::Client,
ifs::workspace_manager::{
ext_workspace_group_handle_v1::ExtWorkspaceGroupHandleV1,
ext_workspace_manager_v1::{
ExtWorkspaceManagerV1, WorkspaceManagerId, WorkspaceManagerIds,
},
},
state::State,
tree::{OutputNode, WorkspaceNode},
utils::{copyhashmap::CopyHashMap, opt::Opt, queue::AsyncQueue},
},
std::rc::Rc,
};

pub mod ext_workspace_group_handle_v1;
pub mod ext_workspace_handle_v1;
pub mod ext_workspace_manager_v1;

#[derive(Default)]
pub struct WorkspaceManagerState {
queue: AsyncQueue<Rc<Opt<ExtWorkspaceManagerV1>>>,
dangling_group: Rc<Opt<ExtWorkspaceGroupHandleV1>>,
ids: WorkspaceManagerIds,
managers: CopyHashMap<WorkspaceManagerId, Rc<ExtWorkspaceManagerV1>>,
}

impl WorkspaceManagerState {
pub fn clear(&self) {
self.managers.clear();
self.queue.clear();
}

pub fn announce_output(&self, on: &OutputNode) {
for manager in self.managers.lock().values() {
manager.announce_output(on);
}
}

pub fn announce_workspace(&self, output: &OutputNode, ws: &WorkspaceNode) {
for manager in self.managers.lock().values() {
manager.announce_workspace(output, ws);
}
}
}

pub async fn workspace_manager_done(state: Rc<State>) {
loop {
let manager = state.workspace_managers.queue.pop().await;
if let Some(manager) = manager.get() {
manager.send_done();
}
}
}

fn group_or_dangling(
client: &Client,
group: Option<&ExtWorkspaceGroupHandleV1>,
) -> Rc<Opt<ExtWorkspaceGroupHandleV1>> {
group
.map(|g| g.opt.clone())
.unwrap_or_else(|| client.state.workspace_managers.dangling_group.clone())
}
164 changes: 164 additions & 0 deletions src/ifs/workspace_manager/ext_workspace_group_handle_v1.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,164 @@
use {
crate::{
client::{Client, ClientError},
ifs::{
wl_output::{OutputGlobalOpt, WlOutput},
workspace_manager::{
ext_workspace_handle_v1::ExtWorkspaceHandleV1,
ext_workspace_manager_v1::{
ExtWorkspaceManagerV1, WorkspaceChange, WorkspaceManagerId,
},
},
},
leaks::Tracker,
object::{Object, Version},
utils::opt::Opt,
wire::{ext_workspace_group_handle_v1::*, ExtWorkspaceGroupHandleV1Id},
},
std::rc::Rc,
thiserror::Error,
};

pub struct ExtWorkspaceGroupHandleV1 {
pub(super) id: ExtWorkspaceGroupHandleV1Id,
pub(super) client: Rc<Client>,
pub(super) tracker: Tracker<Self>,
pub(super) version: Version,
pub output: Rc<OutputGlobalOpt>,
pub(super) manager_id: WorkspaceManagerId,
pub(super) manager: Rc<Opt<ExtWorkspaceManagerV1>>,
pub(super) opt: Rc<Opt<ExtWorkspaceGroupHandleV1>>,
}

const CAP_CREATE_WORKSPACE: u32 = 1;

impl ExtWorkspaceGroupHandleV1 {
fn detach(&self) {
self.opt.set(None);
if let Some(node) = self.output.node() {
node.ext_workspace_groups.remove(&self.manager_id);
}
}

pub(super) fn send_capabilities(&self) {
let capabilities = CAP_CREATE_WORKSPACE;
self.client.event(Capabilities {
self_id: self.id,
capabilities,
});
}

pub(super) fn send_output_enter(&self, output: &WlOutput) {
self.client.event(OutputEnter {
self_id: self.id,
output: output.id,
});
}

#[expect(dead_code)]
fn send_output_leave(&self, output: &WlOutput) {
self.client.event(OutputLeave {
self_id: self.id,
output: output.id,
});
}

pub(super) fn send_workspace_enter(&self, workspace: &ExtWorkspaceHandleV1) {
self.client.event(WorkspaceEnter {
self_id: self.id,
workspace: workspace.id,
});
}

pub(super) fn send_workspace_leave(&self, workspace: &ExtWorkspaceHandleV1) {
self.client.event(WorkspaceLeave {
self_id: self.id,
workspace: workspace.id,
});
}

fn send_removed(&self) {
self.client.event(Removed { self_id: self.id });
}

pub fn handle_destroyed(&self) {
self.detach();
if let Some(manager) = self.manager.get() {
self.send_removed();
manager.schedule_done();
}
}

pub fn handle_new_output(&self, output: &WlOutput) {
if let Some(manager) = self.manager.get() {
self.send_output_enter(output);
manager.schedule_done();
}
}
}

object_base! {
self = ExtWorkspaceGroupHandleV1;
version = self.version;
}

impl Object for ExtWorkspaceGroupHandleV1 {
fn break_loops(&self) {
self.detach();
}
}

dedicated_add_obj!(
ExtWorkspaceGroupHandleV1,
ExtWorkspaceGroupHandleV1Id,
ext_workspace_groups
);

impl ExtWorkspaceGroupHandleV1RequestHandler for ExtWorkspaceGroupHandleV1 {
type Error = ExtWorkspaceGroupHandleV1Error;

fn create_workspace(
&self,
req: CreateWorkspace<'_>,
_slf: &Rc<Self>,
) -> Result<(), Self::Error> {
if self.opt.is_none() {
return Ok(());
}
let Some(manager) = self.manager.get() else {
return Ok(());
};
manager.pending.push(WorkspaceChange::CreateWorkspace(
req.workspace.to_string(),
self.output.clone(),
));
Ok(())
}

fn destroy(&self, _req: Destroy, _slf: &Rc<Self>) -> Result<(), Self::Error> {
if let Some(manager) = self.manager.get() {
if let Some(node) = self.output.node() {
let mut sent_any = false;
for ws in node.workspaces.iter() {
if let Some(ws) = ws.ext_workspaces.get(&self.manager_id) {
self.send_workspace_leave(&ws);
sent_any = true;
}
}
if sent_any {
manager.schedule_done();
}
}
}
self.detach();
self.client.remove_obj(self)?;
Ok(())
}
}

#[derive(Debug, Error)]
pub enum ExtWorkspaceGroupHandleV1Error {
#[error(transparent)]
ClientError(Box<ClientError>),
}
efrom!(ExtWorkspaceGroupHandleV1Error, ClientError);
Loading
Loading