ext-workspace: implement v1
This commit is contained in:
parent
2b76083d6e
commit
a4e197d92a
22 changed files with 948 additions and 4 deletions
|
|
@ -148,6 +148,7 @@ Jay supports the following wayland protocols:
|
||||||
| ext_output_image_capture_source_manager_v1 | 1 | |
|
| ext_output_image_capture_source_manager_v1 | 1 | |
|
||||||
| ext_session_lock_manager_v1 | 1 | Yes |
|
| ext_session_lock_manager_v1 | 1 | Yes |
|
||||||
| ext_transient_seat_manager_v1 | 1[^ts_rejected] | Yes |
|
| ext_transient_seat_manager_v1 | 1[^ts_rejected] | Yes |
|
||||||
|
| ext_workspace_manager_v1 | 1 | Yes |
|
||||||
| jay_tray_v1 | 1 | |
|
| jay_tray_v1 | 1 | |
|
||||||
| org_kde_kwin_server_decoration_manager | 1 | |
|
| org_kde_kwin_server_decoration_manager | 1 | |
|
||||||
| wl_compositor | 6 | |
|
| wl_compositor | 6 | |
|
||||||
|
|
|
||||||
|
|
@ -8,6 +8,7 @@
|
||||||
- Add an idle grace period. During the grace period, the screen goes black but is neither
|
- 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
|
disabled nor locked. This is similar to how android handles going idle. The default is
|
||||||
5 seconds.
|
5 seconds.
|
||||||
|
- Implement ext-workspace-v1.
|
||||||
|
|
||||||
# 1.7.0 (2024-10-25)
|
# 1.7.0 (2024-10-25)
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -56,6 +56,7 @@ bitflags! {
|
||||||
CAP_SEAT_MANAGER = 1 << 8,
|
CAP_SEAT_MANAGER = 1 << 8,
|
||||||
CAP_DRM_LEASE = 1 << 9,
|
CAP_DRM_LEASE = 1 << 9,
|
||||||
CAP_INPUT_METHOD = 1 << 10,
|
CAP_INPUT_METHOD = 1 << 10,
|
||||||
|
CAP_WORKSPACE = 1 << 11,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub const CAPS_DEFAULT: ClientCaps = ClientCaps(CAP_LAYER_SHELL.0 | CAP_DRM_LEASE.0);
|
pub const CAPS_DEFAULT: ClientCaps = ClientCaps(CAP_LAYER_SHELL.0 | CAP_DRM_LEASE.0);
|
||||||
|
|
|
||||||
|
|
@ -27,6 +27,7 @@ use {
|
||||||
xdg_surface::{xdg_popup::XdgPopup, xdg_toplevel::XdgToplevel, XdgSurface},
|
xdg_surface::{xdg_popup::XdgPopup, xdg_toplevel::XdgToplevel, XdgSurface},
|
||||||
WlSurface,
|
WlSurface,
|
||||||
},
|
},
|
||||||
|
workspace_manager::ext_workspace_group_handle_v1::ExtWorkspaceGroupHandleV1,
|
||||||
wp_drm_lease_connector_v1::WpDrmLeaseConnectorV1,
|
wp_drm_lease_connector_v1::WpDrmLeaseConnectorV1,
|
||||||
wp_linux_drm_syncobj_timeline_v1::WpLinuxDrmSyncobjTimelineV1,
|
wp_linux_drm_syncobj_timeline_v1::WpLinuxDrmSyncobjTimelineV1,
|
||||||
xdg_positioner::XdgPositioner,
|
xdg_positioner::XdgPositioner,
|
||||||
|
|
@ -39,9 +40,9 @@ use {
|
||||||
},
|
},
|
||||||
wire::{
|
wire::{
|
||||||
ExtDataControlSourceV1Id, ExtForeignToplevelHandleV1Id, ExtImageCaptureSourceV1Id,
|
ExtDataControlSourceV1Id, ExtForeignToplevelHandleV1Id, ExtImageCaptureSourceV1Id,
|
||||||
ExtImageCopyCaptureSessionV1Id, JayOutputId, JayScreencastId, JayToplevelId,
|
ExtImageCopyCaptureSessionV1Id, ExtWorkspaceGroupHandleV1Id, JayOutputId,
|
||||||
JayWorkspaceId, WlBufferId, WlDataSourceId, WlOutputId, WlPointerId, WlRegionId,
|
JayScreencastId, JayToplevelId, JayWorkspaceId, WlBufferId, WlDataSourceId, WlOutputId,
|
||||||
WlRegistryId, WlSeatId, WlSurfaceId, WpDrmLeaseConnectorV1Id,
|
WlPointerId, WlRegionId, WlRegistryId, WlSeatId, WlSurfaceId, WpDrmLeaseConnectorV1Id,
|
||||||
WpLinuxDrmSyncobjTimelineV1Id, XdgPopupId, XdgPositionerId, XdgSurfaceId,
|
WpLinuxDrmSyncobjTimelineV1Id, XdgPopupId, XdgPositionerId, XdgSurfaceId,
|
||||||
XdgToplevelId, XdgWmBaseId, ZwlrDataControlSourceV1Id, ZwpPrimarySelectionSourceV1Id,
|
XdgToplevelId, XdgWmBaseId, ZwlrDataControlSourceV1Id, ZwpPrimarySelectionSourceV1Id,
|
||||||
ZwpTabletToolV2Id,
|
ZwpTabletToolV2Id,
|
||||||
|
|
@ -82,6 +83,8 @@ pub struct Objects {
|
||||||
pub ext_copy_sessions:
|
pub ext_copy_sessions:
|
||||||
CopyHashMap<ExtImageCopyCaptureSessionV1Id, Rc<ExtImageCopyCaptureSessionV1>>,
|
CopyHashMap<ExtImageCopyCaptureSessionV1Id, Rc<ExtImageCopyCaptureSessionV1>>,
|
||||||
pub ext_data_sources: CopyHashMap<ExtDataControlSourceV1Id, Rc<ExtDataControlSourceV1>>,
|
pub ext_data_sources: CopyHashMap<ExtDataControlSourceV1Id, Rc<ExtDataControlSourceV1>>,
|
||||||
|
pub ext_workspace_groups:
|
||||||
|
CopyHashMap<ExtWorkspaceGroupHandleV1Id, Rc<ExtWorkspaceGroupHandleV1>>,
|
||||||
ids: RefCell<Vec<usize>>,
|
ids: RefCell<Vec<usize>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -119,6 +122,7 @@ impl Objects {
|
||||||
foreign_toplevel_handles: Default::default(),
|
foreign_toplevel_handles: Default::default(),
|
||||||
ext_copy_sessions: Default::default(),
|
ext_copy_sessions: Default::default(),
|
||||||
ext_data_sources: Default::default(),
|
ext_data_sources: Default::default(),
|
||||||
|
ext_workspace_groups: Default::default(),
|
||||||
ids: RefCell::new(vec![]),
|
ids: RefCell::new(vec![]),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -160,6 +164,7 @@ impl Objects {
|
||||||
self.foreign_toplevel_handles.clear();
|
self.foreign_toplevel_handles.clear();
|
||||||
self.ext_copy_sessions.clear();
|
self.ext_copy_sessions.clear();
|
||||||
self.ext_data_sources.clear();
|
self.ext_data_sources.clear();
|
||||||
|
self.ext_workspace_groups.clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn id<T>(&self, client_data: &Client) -> Result<T, ClientError>
|
pub fn id<T>(&self, client_data: &Client) -> Result<T, ClientError>
|
||||||
|
|
|
||||||
|
|
@ -23,6 +23,7 @@ use {
|
||||||
jay_screencast::{perform_screencast_realloc, perform_toplevel_screencasts},
|
jay_screencast::{perform_screencast_realloc, perform_toplevel_screencasts},
|
||||||
wl_output::{OutputId, PersistentOutputState, WlOutputGlobal},
|
wl_output::{OutputId, PersistentOutputState, WlOutputGlobal},
|
||||||
wl_surface::{zwp_input_popup_surface_v2::input_popup_positioning, NoneSurfaceExt},
|
wl_surface::{zwp_input_popup_surface_v2::input_popup_positioning, NoneSurfaceExt},
|
||||||
|
workspace_manager::workspace_manager_done,
|
||||||
},
|
},
|
||||||
io_uring::{IoUring, IoUringError},
|
io_uring::{IoUring, IoUringError},
|
||||||
leaks,
|
leaks,
|
||||||
|
|
@ -278,6 +279,7 @@ fn start_compositor2(
|
||||||
const_40hz_latch: Default::default(),
|
const_40hz_latch: Default::default(),
|
||||||
tray_item_ids: Default::default(),
|
tray_item_ids: Default::default(),
|
||||||
data_control_device_ids: Default::default(),
|
data_control_device_ids: Default::default(),
|
||||||
|
workspace_managers: Default::default(),
|
||||||
});
|
});
|
||||||
state.tracker.register(ClientId::from_raw(0));
|
state.tracker.register(ClientId::from_raw(0));
|
||||||
create_dummy_output(&state);
|
create_dummy_output(&state);
|
||||||
|
|
@ -446,6 +448,10 @@ fn start_global_event_handlers(
|
||||||
Phase::Present,
|
Phase::Present,
|
||||||
handle_const_40hz_latch(state.clone()),
|
handle_const_40hz_latch(state.clone()),
|
||||||
),
|
),
|
||||||
|
eng.spawn(
|
||||||
|
"workspace manager done",
|
||||||
|
workspace_manager_done(state.clone()),
|
||||||
|
),
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -593,6 +599,7 @@ fn create_dummy_output(state: &Rc<State>) {
|
||||||
before_latch_event: Default::default(),
|
before_latch_event: Default::default(),
|
||||||
tray_start_rel: Default::default(),
|
tray_start_rel: Default::default(),
|
||||||
tray_items: Default::default(),
|
tray_items: Default::default(),
|
||||||
|
ext_workspace_groups: Default::default(),
|
||||||
});
|
});
|
||||||
let dummy_workspace = Rc::new(WorkspaceNode {
|
let dummy_workspace = Rc::new(WorkspaceNode {
|
||||||
id: state.node_ids.next(),
|
id: state.node_ids.next(),
|
||||||
|
|
@ -615,6 +622,8 @@ fn create_dummy_output(state: &Rc<State>) {
|
||||||
title_texture: Default::default(),
|
title_texture: Default::default(),
|
||||||
attention_requests: Default::default(),
|
attention_requests: Default::default(),
|
||||||
render_highlight: Default::default(),
|
render_highlight: Default::default(),
|
||||||
|
ext_workspaces: Default::default(),
|
||||||
|
opt: Default::default(),
|
||||||
});
|
});
|
||||||
*dummy_workspace.output_link.borrow_mut() =
|
*dummy_workspace.output_link.borrow_mut() =
|
||||||
Some(dummy_output.workspaces.add_last(dummy_workspace.clone()));
|
Some(dummy_output.workspaces.add_last(dummy_workspace.clone()));
|
||||||
|
|
|
||||||
|
|
@ -40,6 +40,7 @@ use {
|
||||||
wl_shm::WlShmGlobal,
|
wl_shm::WlShmGlobal,
|
||||||
wl_subcompositor::WlSubcompositorGlobal,
|
wl_subcompositor::WlSubcompositorGlobal,
|
||||||
wl_surface::xwayland_shell_v1::XwaylandShellV1Global,
|
wl_surface::xwayland_shell_v1::XwaylandShellV1Global,
|
||||||
|
workspace_manager::ext_workspace_manager_v1::ExtWorkspaceManagerV1Global,
|
||||||
wp_alpha_modifier_v1::WpAlphaModifierV1Global,
|
wp_alpha_modifier_v1::WpAlphaModifierV1Global,
|
||||||
wp_commit_timing_manager_v1::WpCommitTimingManagerV1Global,
|
wp_commit_timing_manager_v1::WpCommitTimingManagerV1Global,
|
||||||
wp_content_type_manager_v1::WpContentTypeManagerV1Global,
|
wp_content_type_manager_v1::WpContentTypeManagerV1Global,
|
||||||
|
|
@ -213,6 +214,7 @@ impl Globals {
|
||||||
add_singleton!(WpCommitTimingManagerV1Global);
|
add_singleton!(WpCommitTimingManagerV1Global);
|
||||||
add_singleton!(ExtDataControlManagerV1Global);
|
add_singleton!(ExtDataControlManagerV1Global);
|
||||||
add_singleton!(WlFixesGlobal);
|
add_singleton!(WlFixesGlobal);
|
||||||
|
add_singleton!(ExtWorkspaceManagerV1Global);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn add_backend_singletons(&self, backend: &Rc<dyn Backend>) {
|
pub fn add_backend_singletons(&self, backend: &Rc<dyn Backend>) {
|
||||||
|
|
|
||||||
|
|
@ -46,6 +46,7 @@ pub mod wl_shm;
|
||||||
pub mod wl_shm_pool;
|
pub mod wl_shm_pool;
|
||||||
pub mod wl_subcompositor;
|
pub mod wl_subcompositor;
|
||||||
pub mod wl_surface;
|
pub mod wl_surface;
|
||||||
|
pub mod workspace_manager;
|
||||||
pub mod wp_alpha_modifier_v1;
|
pub mod wp_alpha_modifier_v1;
|
||||||
pub mod wp_commit_timing_manager_v1;
|
pub mod wp_commit_timing_manager_v1;
|
||||||
pub mod wp_content_type_manager_v1;
|
pub mod wp_content_type_manager_v1;
|
||||||
|
|
|
||||||
|
|
@ -13,7 +13,7 @@ use {
|
||||||
state::{ConnectorData, State},
|
state::{ConnectorData, State},
|
||||||
tree::{calculate_logical_size, OutputNode, TearingMode, VrrMode},
|
tree::{calculate_logical_size, OutputNode, TearingMode, VrrMode},
|
||||||
utils::{
|
utils::{
|
||||||
cell_ext::CellExt, clonecell::CloneCell, copyhashmap::CopyHashMap,
|
cell_ext::CellExt, clonecell::CloneCell, copyhashmap::CopyHashMap, rc_eq::rc_eq,
|
||||||
transform_ext::TransformExt,
|
transform_ext::TransformExt,
|
||||||
},
|
},
|
||||||
wire::{wl_output::*, WlOutputId, ZxdgOutputV1Id},
|
wire::{wl_output::*, WlOutputId, ZxdgOutputV1Id},
|
||||||
|
|
@ -238,6 +238,11 @@ impl WlOutputGlobal {
|
||||||
if obj.version >= SEND_DONE_SINCE {
|
if obj.version >= SEND_DONE_SINCE {
|
||||||
obj.send_done();
|
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(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
64
src/ifs/workspace_manager.rs
Normal file
64
src/ifs/workspace_manager.rs
Normal file
|
|
@ -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
src/ifs/workspace_manager/ext_workspace_group_handle_v1.rs
Normal file
164
src/ifs/workspace_manager/ext_workspace_group_handle_v1.rs
Normal file
|
|
@ -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);
|
||||||
215
src/ifs/workspace_manager/ext_workspace_handle_v1.rs
Normal file
215
src/ifs/workspace_manager/ext_workspace_handle_v1.rs
Normal file
|
|
@ -0,0 +1,215 @@
|
||||||
|
use {
|
||||||
|
crate::{
|
||||||
|
client::{Client, ClientError},
|
||||||
|
ifs::workspace_manager::{
|
||||||
|
ext_workspace_group_handle_v1::ExtWorkspaceGroupHandleV1,
|
||||||
|
ext_workspace_manager_v1::{
|
||||||
|
ExtWorkspaceManagerV1, WorkspaceChange, WorkspaceManagerId,
|
||||||
|
},
|
||||||
|
group_or_dangling,
|
||||||
|
},
|
||||||
|
leaks::Tracker,
|
||||||
|
object::{Object, Version},
|
||||||
|
tree::{OutputNode, WorkspaceNode},
|
||||||
|
utils::{clonecell::CloneCell, opt::Opt},
|
||||||
|
wire::{ext_workspace_handle_v1::*, ExtWorkspaceHandleV1Id},
|
||||||
|
},
|
||||||
|
std::{cell::Cell, rc::Rc},
|
||||||
|
thiserror::Error,
|
||||||
|
};
|
||||||
|
|
||||||
|
const STATE_ACTIVE: u32 = 1;
|
||||||
|
const STATE_URGENT: u32 = 2;
|
||||||
|
#[expect(dead_code)]
|
||||||
|
const STATE_HIDDEN: u32 = 4;
|
||||||
|
|
||||||
|
const CAP_ACTIVATE: u32 = 1;
|
||||||
|
#[expect(dead_code)]
|
||||||
|
const CAP_DEACTIVATE: u32 = 2;
|
||||||
|
#[expect(dead_code)]
|
||||||
|
const CAP_REMOVE: u32 = 4;
|
||||||
|
const CAP_ASSIGN: u32 = 8;
|
||||||
|
|
||||||
|
pub struct ExtWorkspaceHandleV1 {
|
||||||
|
pub(super) id: ExtWorkspaceHandleV1Id,
|
||||||
|
pub(super) client: Rc<Client>,
|
||||||
|
pub(super) tracker: Tracker<Self>,
|
||||||
|
pub version: Version,
|
||||||
|
pub(super) group: CloneCell<Rc<Opt<ExtWorkspaceGroupHandleV1>>>,
|
||||||
|
pub(super) workspace: Rc<Opt<WorkspaceNode>>,
|
||||||
|
pub(super) manager_id: WorkspaceManagerId,
|
||||||
|
pub(super) manager: Rc<Opt<ExtWorkspaceManagerV1>>,
|
||||||
|
pub(super) destroyed: Cell<bool>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ExtWorkspaceHandleV1 {
|
||||||
|
fn detach(&self) {
|
||||||
|
if let Some(ws) = self.workspace.get() {
|
||||||
|
ws.ext_workspaces.remove(&self.manager_id);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(super) fn send_id(&self, id: &str) {
|
||||||
|
self.client.event(Id {
|
||||||
|
self_id: self.id,
|
||||||
|
id,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(super) fn send_name(&self, name: &str) {
|
||||||
|
self.client.event(Name {
|
||||||
|
self_id: self.id,
|
||||||
|
name,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
#[expect(dead_code)]
|
||||||
|
fn send_coordinates(&self, coordinates: &[u32]) {
|
||||||
|
self.client.event(Coordinates {
|
||||||
|
self_id: self.id,
|
||||||
|
coordinates,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(super) fn send_current_state(&self) {
|
||||||
|
let Some(ws) = self.workspace.get() else {
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
let mut state = 0;
|
||||||
|
let output = ws.output.get();
|
||||||
|
if let Some(active) = output.workspace.get() {
|
||||||
|
if active.id == ws.id {
|
||||||
|
state |= STATE_ACTIVE;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if ws.attention_requests.active() {
|
||||||
|
state |= STATE_URGENT;
|
||||||
|
}
|
||||||
|
self.send_state(state);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn send_state(&self, state: u32) {
|
||||||
|
self.client.event(State {
|
||||||
|
self_id: self.id,
|
||||||
|
state,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(super) fn send_capabilities(&self) {
|
||||||
|
let capabilities = CAP_ACTIVATE | CAP_ASSIGN;
|
||||||
|
self.client.event(Capabilities {
|
||||||
|
self_id: self.id,
|
||||||
|
capabilities,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
fn send_removed(&self) {
|
||||||
|
self.client.event(Removed { self_id: self.id });
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn handle_destroyed(&self) {
|
||||||
|
self.destroyed.set(true);
|
||||||
|
if let Some(manager) = self.manager.get() {
|
||||||
|
if let Some(group) = self.group.get().get() {
|
||||||
|
group.send_workspace_leave(self);
|
||||||
|
}
|
||||||
|
self.group
|
||||||
|
.set(self.client.state.workspace_managers.dangling_group.clone());
|
||||||
|
self.send_state(0);
|
||||||
|
self.send_removed();
|
||||||
|
manager.schedule_done();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn handle_new_output(&self, output: &OutputNode) {
|
||||||
|
let new = output.ext_workspace_groups.get(&self.manager_id);
|
||||||
|
let new = group_or_dangling(&self.client, new.as_deref());
|
||||||
|
let old = self.group.set(new.clone());
|
||||||
|
if let Some(manager) = self.manager.get() {
|
||||||
|
if let Some(old) = old.get() {
|
||||||
|
old.send_workspace_leave(self);
|
||||||
|
}
|
||||||
|
if let Some(new) = new.get() {
|
||||||
|
new.send_workspace_enter(self);
|
||||||
|
}
|
||||||
|
manager.schedule_done();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn handle_visibility_changed(&self) {
|
||||||
|
if let Some(manager) = self.manager.get() {
|
||||||
|
self.send_current_state();
|
||||||
|
manager.schedule_done();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn handle_urgent_changed(&self) {
|
||||||
|
self.handle_visibility_changed();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
object_base! {
|
||||||
|
self = ExtWorkspaceHandleV1;
|
||||||
|
version = self.version;
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Object for ExtWorkspaceHandleV1 {
|
||||||
|
fn break_loops(&self) {
|
||||||
|
self.detach();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
simple_add_obj!(ExtWorkspaceHandleV1);
|
||||||
|
|
||||||
|
impl ExtWorkspaceHandleV1RequestHandler for ExtWorkspaceHandleV1 {
|
||||||
|
type Error = ExtWorkspaceHandleV1Error;
|
||||||
|
|
||||||
|
fn destroy(&self, _req: Destroy, _slf: &Rc<Self>) -> Result<(), Self::Error> {
|
||||||
|
self.detach();
|
||||||
|
self.client.remove_obj(self)?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn activate(&self, _req: Activate, _slf: &Rc<Self>) -> Result<(), Self::Error> {
|
||||||
|
if self.destroyed.get() {
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
|
let Some(manager) = self.manager.get() else {
|
||||||
|
return Ok(());
|
||||||
|
};
|
||||||
|
manager
|
||||||
|
.pending
|
||||||
|
.push(WorkspaceChange::ActivateWorkspace(self.workspace.clone()));
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn deactivate(&self, _req: Deactivate, _slf: &Rc<Self>) -> Result<(), Self::Error> {
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn assign(&self, req: Assign, _slf: &Rc<Self>) -> Result<(), Self::Error> {
|
||||||
|
if self.destroyed.get() {
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
|
let group = self.client.lookup(req.workspace_group)?;
|
||||||
|
let Some(manager) = self.manager.get() else {
|
||||||
|
return Ok(());
|
||||||
|
};
|
||||||
|
manager.pending.push(WorkspaceChange::AssignWorkspace(
|
||||||
|
self.workspace.clone(),
|
||||||
|
group.output.clone(),
|
||||||
|
));
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn remove(&self, _req: Remove, _slf: &Rc<Self>) -> Result<(), Self::Error> {
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Error)]
|
||||||
|
pub enum ExtWorkspaceHandleV1Error {
|
||||||
|
#[error(transparent)]
|
||||||
|
ClientError(Box<ClientError>),
|
||||||
|
}
|
||||||
|
efrom!(ExtWorkspaceHandleV1Error, ClientError);
|
||||||
306
src/ifs/workspace_manager/ext_workspace_manager_v1.rs
Normal file
306
src/ifs/workspace_manager/ext_workspace_manager_v1.rs
Normal file
|
|
@ -0,0 +1,306 @@
|
||||||
|
use {
|
||||||
|
crate::{
|
||||||
|
client::{Client, ClientCaps, ClientError, CAP_WORKSPACE},
|
||||||
|
globals::{Global, GlobalName},
|
||||||
|
ifs::{
|
||||||
|
wl_output::OutputGlobalOpt,
|
||||||
|
workspace_manager::{
|
||||||
|
ext_workspace_group_handle_v1::ExtWorkspaceGroupHandleV1,
|
||||||
|
ext_workspace_handle_v1::ExtWorkspaceHandleV1, group_or_dangling,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
leaks::Tracker,
|
||||||
|
object::{Object, Version},
|
||||||
|
tree::{move_ws_to_output, OutputNode, WorkspaceNode, WsMoveConfig},
|
||||||
|
utils::{clonecell::CloneCell, opt::Opt, syncqueue::SyncQueue},
|
||||||
|
wire::{ext_workspace_manager_v1::*, ExtWorkspaceManagerV1Id},
|
||||||
|
},
|
||||||
|
std::{cell::Cell, rc::Rc},
|
||||||
|
thiserror::Error,
|
||||||
|
};
|
||||||
|
|
||||||
|
linear_ids!(WorkspaceManagerIds, WorkspaceManagerId, u64);
|
||||||
|
|
||||||
|
pub struct ExtWorkspaceManagerV1Global {
|
||||||
|
pub name: GlobalName,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct ExtWorkspaceManagerV1 {
|
||||||
|
id: ExtWorkspaceManagerV1Id,
|
||||||
|
pub(super) manager_id: WorkspaceManagerId,
|
||||||
|
client: Rc<Client>,
|
||||||
|
tracker: Tracker<Self>,
|
||||||
|
version: Version,
|
||||||
|
pub(super) pending: SyncQueue<WorkspaceChange>,
|
||||||
|
opt: Rc<Opt<ExtWorkspaceManagerV1>>,
|
||||||
|
done_scheduled: Cell<bool>,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(super) enum WorkspaceChange {
|
||||||
|
CreateWorkspace(String, Rc<OutputGlobalOpt>),
|
||||||
|
ActivateWorkspace(Rc<Opt<WorkspaceNode>>),
|
||||||
|
AssignWorkspace(Rc<Opt<WorkspaceNode>>, Rc<OutputGlobalOpt>),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ExtWorkspaceManagerV1Global {
|
||||||
|
pub fn new(name: GlobalName) -> Self {
|
||||||
|
Self { name }
|
||||||
|
}
|
||||||
|
|
||||||
|
fn bind_(
|
||||||
|
self: Rc<Self>,
|
||||||
|
id: ExtWorkspaceManagerV1Id,
|
||||||
|
client: &Rc<Client>,
|
||||||
|
version: Version,
|
||||||
|
) -> Result<(), ExtWorkspaceManagerV1Error> {
|
||||||
|
let obj = Rc::new(ExtWorkspaceManagerV1 {
|
||||||
|
id,
|
||||||
|
manager_id: client.state.workspace_managers.ids.next(),
|
||||||
|
client: client.clone(),
|
||||||
|
tracker: Default::default(),
|
||||||
|
version,
|
||||||
|
pending: Default::default(),
|
||||||
|
opt: Default::default(),
|
||||||
|
done_scheduled: Cell::new(false),
|
||||||
|
});
|
||||||
|
track!(client, obj);
|
||||||
|
client.add_client_obj(&obj)?;
|
||||||
|
obj.opt.set(Some(obj.clone()));
|
||||||
|
client
|
||||||
|
.state
|
||||||
|
.workspace_managers
|
||||||
|
.managers
|
||||||
|
.set(obj.manager_id, obj.clone());
|
||||||
|
let dummy_output = client.state.dummy_output.get().unwrap();
|
||||||
|
for ws in dummy_output.workspaces.iter() {
|
||||||
|
if !ws.is_dummy {
|
||||||
|
obj.announce_workspace(&dummy_output, &ws);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for output in client.state.root.outputs.lock().values() {
|
||||||
|
obj.announce_output(output);
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ExtWorkspaceManagerV1 {
|
||||||
|
pub(super) fn announce_output(&self, node: &OutputNode) {
|
||||||
|
let id = match self.client.new_id() {
|
||||||
|
Ok(id) => id,
|
||||||
|
Err(e) => {
|
||||||
|
self.client.error(e);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
let group = Rc::new(ExtWorkspaceGroupHandleV1 {
|
||||||
|
id,
|
||||||
|
client: self.client.clone(),
|
||||||
|
tracker: Default::default(),
|
||||||
|
version: self.version,
|
||||||
|
output: node.global.opt.clone(),
|
||||||
|
manager_id: self.manager_id,
|
||||||
|
manager: self.opt.clone(),
|
||||||
|
opt: Default::default(),
|
||||||
|
});
|
||||||
|
track!(self.client, group);
|
||||||
|
self.client.add_server_obj(&group);
|
||||||
|
group.opt.set(Some(group.clone()));
|
||||||
|
node.ext_workspace_groups
|
||||||
|
.set(self.manager_id, group.clone());
|
||||||
|
self.send_workspace_group(&group);
|
||||||
|
group.send_capabilities();
|
||||||
|
if let Some(bindings) = node.global.bindings.borrow().get(&self.client.id) {
|
||||||
|
for wl_output in bindings.values() {
|
||||||
|
group.send_output_enter(wl_output);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for ws in node.workspaces.iter() {
|
||||||
|
if let Some(ws) = ws.ext_workspaces.get(&self.manager_id) {
|
||||||
|
ws.handle_new_output(node);
|
||||||
|
} else {
|
||||||
|
self.announce_workspace(node, &ws);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
self.schedule_done();
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(super) fn announce_workspace(&self, output: &OutputNode, workspace: &WorkspaceNode) {
|
||||||
|
let id = match self.client.new_id() {
|
||||||
|
Ok(id) => id,
|
||||||
|
Err(e) => {
|
||||||
|
self.client.error(e);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
let group = output.ext_workspace_groups.get(&self.manager_id);
|
||||||
|
let ws = Rc::new(ExtWorkspaceHandleV1 {
|
||||||
|
id,
|
||||||
|
client: self.client.clone(),
|
||||||
|
tracker: Default::default(),
|
||||||
|
version: self.version,
|
||||||
|
group: CloneCell::new(group_or_dangling(&self.client, group.as_deref())),
|
||||||
|
workspace: workspace.opt.clone(),
|
||||||
|
manager_id: self.manager_id,
|
||||||
|
manager: self.opt.clone(),
|
||||||
|
destroyed: Cell::new(false),
|
||||||
|
});
|
||||||
|
track!(self.client, ws);
|
||||||
|
self.client.add_server_obj(&ws);
|
||||||
|
workspace.ext_workspaces.set(self.manager_id, ws.clone());
|
||||||
|
self.send_workspace(&ws);
|
||||||
|
ws.send_capabilities();
|
||||||
|
ws.send_id(&workspace.name);
|
||||||
|
ws.send_name(&workspace.name);
|
||||||
|
ws.send_current_state();
|
||||||
|
if let Some(group) = group {
|
||||||
|
group.send_workspace_enter(&ws);
|
||||||
|
}
|
||||||
|
self.schedule_done();
|
||||||
|
}
|
||||||
|
|
||||||
|
fn send_workspace_group(&self, workspace_group: &ExtWorkspaceGroupHandleV1) {
|
||||||
|
self.client.event(WorkspaceGroup {
|
||||||
|
self_id: self.id,
|
||||||
|
workspace_group: workspace_group.id,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
fn send_workspace(&self, workspace: &ExtWorkspaceHandleV1) {
|
||||||
|
self.client.event(Workspace {
|
||||||
|
self_id: self.id,
|
||||||
|
workspace: workspace.id,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(super) fn send_done(&self) {
|
||||||
|
self.done_scheduled.set(false);
|
||||||
|
self.client.event(Done { self_id: self.id });
|
||||||
|
}
|
||||||
|
|
||||||
|
fn send_finished(&self) {
|
||||||
|
self.client.event(Finished { self_id: self.id });
|
||||||
|
}
|
||||||
|
|
||||||
|
fn detach(&self) {
|
||||||
|
self.opt.set(None);
|
||||||
|
self.pending.clear();
|
||||||
|
self.client
|
||||||
|
.state
|
||||||
|
.workspace_managers
|
||||||
|
.managers
|
||||||
|
.remove(&self.manager_id);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(super) fn schedule_done(&self) {
|
||||||
|
if self.done_scheduled.replace(true) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
self.client
|
||||||
|
.state
|
||||||
|
.workspace_managers
|
||||||
|
.queue
|
||||||
|
.push(self.opt.clone());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
global_base!(
|
||||||
|
ExtWorkspaceManagerV1Global,
|
||||||
|
ExtWorkspaceManagerV1,
|
||||||
|
ExtWorkspaceManagerV1Error
|
||||||
|
);
|
||||||
|
|
||||||
|
impl Global for ExtWorkspaceManagerV1Global {
|
||||||
|
fn singleton(&self) -> bool {
|
||||||
|
true
|
||||||
|
}
|
||||||
|
|
||||||
|
fn version(&self) -> u32 {
|
||||||
|
1
|
||||||
|
}
|
||||||
|
|
||||||
|
fn required_caps(&self) -> ClientCaps {
|
||||||
|
CAP_WORKSPACE
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
simple_add_global!(ExtWorkspaceManagerV1Global);
|
||||||
|
|
||||||
|
object_base! {
|
||||||
|
self = ExtWorkspaceManagerV1;
|
||||||
|
version = self.version;
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Object for ExtWorkspaceManagerV1 {
|
||||||
|
fn break_loops(&self) {
|
||||||
|
self.detach();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
simple_add_obj!(ExtWorkspaceManagerV1);
|
||||||
|
|
||||||
|
impl ExtWorkspaceManagerV1RequestHandler for ExtWorkspaceManagerV1 {
|
||||||
|
type Error = ExtWorkspaceManagerV1Error;
|
||||||
|
|
||||||
|
fn commit(&self, _req: Commit, _slf: &Rc<Self>) -> Result<(), Self::Error> {
|
||||||
|
while let Some(change) = self.pending.pop() {
|
||||||
|
match change {
|
||||||
|
WorkspaceChange::ActivateWorkspace(w) => {
|
||||||
|
let Some(ws) = w.get() else {
|
||||||
|
continue;
|
||||||
|
};
|
||||||
|
let output = ws.output.get();
|
||||||
|
output.show_workspace(&ws);
|
||||||
|
ws.flush_jay_workspaces();
|
||||||
|
output.schedule_update_render_data();
|
||||||
|
self.client.state.tree_changed();
|
||||||
|
}
|
||||||
|
WorkspaceChange::AssignWorkspace(w, o) => {
|
||||||
|
let Some(ws) = w.get() else {
|
||||||
|
continue;
|
||||||
|
};
|
||||||
|
let Some(o) = o.node() else {
|
||||||
|
continue;
|
||||||
|
};
|
||||||
|
let link = match &*ws.output_link.borrow() {
|
||||||
|
None => continue,
|
||||||
|
Some(l) => l.to_ref(),
|
||||||
|
};
|
||||||
|
let config = WsMoveConfig {
|
||||||
|
make_visible_always: false,
|
||||||
|
make_visible_if_empty: true,
|
||||||
|
source_is_destroyed: false,
|
||||||
|
before: None,
|
||||||
|
};
|
||||||
|
move_ws_to_output(&link, &o, config);
|
||||||
|
ws.desired_output.set(o.global.output_id.clone());
|
||||||
|
self.client.state.tree_changed();
|
||||||
|
}
|
||||||
|
WorkspaceChange::CreateWorkspace(name, output) => {
|
||||||
|
if self.client.state.workspaces.contains(&name) {
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
|
let Some(output) = output.node() else {
|
||||||
|
return Ok(());
|
||||||
|
};
|
||||||
|
output.create_workspace(&name);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn stop(&self, _req: Stop, _slf: &Rc<Self>) -> Result<(), Self::Error> {
|
||||||
|
self.detach();
|
||||||
|
self.send_finished();
|
||||||
|
self.client.remove_obj(self)?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Error)]
|
||||||
|
pub enum ExtWorkspaceManagerV1Error {
|
||||||
|
#[error(transparent)]
|
||||||
|
ClientError(Box<ClientError>),
|
||||||
|
}
|
||||||
|
efrom!(ExtWorkspaceManagerV1Error, ClientError);
|
||||||
|
|
@ -57,6 +57,7 @@ use {
|
||||||
zwp_input_popup_surface_v2::ZwpInputPopupSurfaceV2,
|
zwp_input_popup_surface_v2::ZwpInputPopupSurfaceV2,
|
||||||
NoneSurfaceExt,
|
NoneSurfaceExt,
|
||||||
},
|
},
|
||||||
|
workspace_manager::WorkspaceManagerState,
|
||||||
wp_drm_lease_connector_v1::WpDrmLeaseConnectorV1,
|
wp_drm_lease_connector_v1::WpDrmLeaseConnectorV1,
|
||||||
wp_drm_lease_device_v1::WpDrmLeaseDeviceV1Global,
|
wp_drm_lease_device_v1::WpDrmLeaseDeviceV1Global,
|
||||||
wp_linux_drm_syncobj_manager_v1::WpLinuxDrmSyncobjManagerV1Global,
|
wp_linux_drm_syncobj_manager_v1::WpLinuxDrmSyncobjManagerV1Global,
|
||||||
|
|
@ -229,6 +230,7 @@ pub struct State {
|
||||||
pub const_40hz_latch: EventSource<dyn LatchListener>,
|
pub const_40hz_latch: EventSource<dyn LatchListener>,
|
||||||
pub tray_item_ids: TrayItemIds,
|
pub tray_item_ids: TrayItemIds,
|
||||||
pub data_control_device_ids: DataControlDeviceIds,
|
pub data_control_device_ids: DataControlDeviceIds,
|
||||||
|
pub workspace_managers: WorkspaceManagerState,
|
||||||
}
|
}
|
||||||
|
|
||||||
// impl Drop for State {
|
// impl Drop for State {
|
||||||
|
|
@ -917,6 +919,7 @@ impl State {
|
||||||
self.ei_clients.clear();
|
self.ei_clients.clear();
|
||||||
self.slow_ei_clients.clear();
|
self.slow_ei_clients.clear();
|
||||||
self.toplevels.clear();
|
self.toplevels.clear();
|
||||||
|
self.workspace_managers.clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn damage_hardware_cursors(&self, render: bool) {
|
pub fn damage_hardware_cursors(&self, render: bool) {
|
||||||
|
|
|
||||||
|
|
@ -197,6 +197,7 @@ impl ConnectorHandler {
|
||||||
before_latch_event: Default::default(),
|
before_latch_event: Default::default(),
|
||||||
tray_start_rel: Default::default(),
|
tray_start_rel: Default::default(),
|
||||||
tray_items: Default::default(),
|
tray_items: Default::default(),
|
||||||
|
ext_workspace_groups: Default::default(),
|
||||||
});
|
});
|
||||||
on.update_visible();
|
on.update_visible();
|
||||||
on.update_rects();
|
on.update_rects();
|
||||||
|
|
@ -259,6 +260,7 @@ impl ConnectorHandler {
|
||||||
self.state.add_global(&tray);
|
self.state.add_global(&tray);
|
||||||
self.state.tree_changed();
|
self.state.tree_changed();
|
||||||
on.update_presentation_type();
|
on.update_presentation_type();
|
||||||
|
self.state.workspace_managers.announce_output(&on);
|
||||||
'outer: loop {
|
'outer: loop {
|
||||||
while let Some(event) = self.data.connector.event() {
|
while let Some(event) = self.data.connector.event() {
|
||||||
match event {
|
match event {
|
||||||
|
|
@ -331,6 +333,9 @@ impl ConnectorHandler {
|
||||||
};
|
};
|
||||||
move_ws_to_output(&ws, &target, config);
|
move_ws_to_output(&ws, &target, config);
|
||||||
}
|
}
|
||||||
|
for group in on.ext_workspace_groups.lock().drain_values() {
|
||||||
|
group.handle_destroyed();
|
||||||
|
}
|
||||||
for seat in self.state.globals.seats.lock().values() {
|
for seat in self.state.globals.seats.lock().values() {
|
||||||
seat.cursor_group().output_disconnected(&on, &target);
|
seat.cursor_group().output_disconnected(&on, &target);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -23,6 +23,10 @@ use {
|
||||||
zwlr_layer_surface_v1::{ExclusiveSize, ZwlrLayerSurfaceV1},
|
zwlr_layer_surface_v1::{ExclusiveSize, ZwlrLayerSurfaceV1},
|
||||||
SurfaceSendPreferredScaleVisitor, SurfaceSendPreferredTransformVisitor,
|
SurfaceSendPreferredScaleVisitor, SurfaceSendPreferredTransformVisitor,
|
||||||
},
|
},
|
||||||
|
workspace_manager::{
|
||||||
|
ext_workspace_group_handle_v1::ExtWorkspaceGroupHandleV1,
|
||||||
|
ext_workspace_manager_v1::WorkspaceManagerId,
|
||||||
|
},
|
||||||
wp_content_type_v1::ContentType,
|
wp_content_type_v1::ContentType,
|
||||||
zwlr_layer_shell_v1::{BACKGROUND, BOTTOM, OVERLAY, TOP},
|
zwlr_layer_shell_v1::{BACKGROUND, BOTTOM, OVERLAY, TOP},
|
||||||
zwlr_screencopy_frame_v1::ZwlrScreencopyFrameV1,
|
zwlr_screencopy_frame_v1::ZwlrScreencopyFrameV1,
|
||||||
|
|
@ -97,6 +101,7 @@ pub struct OutputNode {
|
||||||
pub before_latch_event: EventSource<dyn BeforeLatchListener>,
|
pub before_latch_event: EventSource<dyn BeforeLatchListener>,
|
||||||
pub tray_start_rel: Cell<i32>,
|
pub tray_start_rel: Cell<i32>,
|
||||||
pub tray_items: LinkedList<Rc<dyn DynTrayItem>>,
|
pub tray_items: LinkedList<Rc<dyn DynTrayItem>>,
|
||||||
|
pub ext_workspace_groups: CopyHashMap<WorkspaceManagerId, Rc<ExtWorkspaceGroupHandleV1>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Copy, Clone, Debug, PartialEq)]
|
#[derive(Copy, Clone, Debug, PartialEq)]
|
||||||
|
|
@ -414,6 +419,7 @@ impl OutputNode {
|
||||||
self.screencasts.clear();
|
self.screencasts.clear();
|
||||||
self.screencopies.clear();
|
self.screencopies.clear();
|
||||||
self.ext_copy_sessions.clear();
|
self.ext_copy_sessions.clear();
|
||||||
|
self.ext_workspace_groups.clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn on_spaces_changed(self: &Rc<Self>) {
|
pub fn on_spaces_changed(self: &Rc<Self>) {
|
||||||
|
|
@ -635,6 +641,9 @@ impl OutputNode {
|
||||||
jw.send_destroyed();
|
jw.send_destroyed();
|
||||||
jw.workspace.set(None);
|
jw.workspace.set(None);
|
||||||
}
|
}
|
||||||
|
for wh in old.ext_workspaces.lock().values() {
|
||||||
|
wh.handle_destroyed();
|
||||||
|
}
|
||||||
old.clear();
|
old.clear();
|
||||||
self.state.workspaces.remove(&old.name);
|
self.state.workspaces.remove(&old.name);
|
||||||
} else {
|
} else {
|
||||||
|
|
@ -678,7 +687,10 @@ impl OutputNode {
|
||||||
title_texture: Default::default(),
|
title_texture: Default::default(),
|
||||||
attention_requests: Default::default(),
|
attention_requests: Default::default(),
|
||||||
render_highlight: Default::default(),
|
render_highlight: Default::default(),
|
||||||
|
ext_workspaces: Default::default(),
|
||||||
|
opt: Default::default(),
|
||||||
});
|
});
|
||||||
|
ws.opt.set(Some(ws.clone()));
|
||||||
ws.update_has_captures();
|
ws.update_has_captures();
|
||||||
*ws.output_link.borrow_mut() = Some(self.workspaces.add_last(ws.clone()));
|
*ws.output_link.borrow_mut() = Some(self.workspaces.add_last(ws.clone()));
|
||||||
self.state.workspaces.set(name.to_string(), ws.clone());
|
self.state.workspaces.set(name.to_string(), ws.clone());
|
||||||
|
|
@ -694,6 +706,7 @@ impl OutputNode {
|
||||||
for (client, e) in clients_to_kill.values() {
|
for (client, e) in clients_to_kill.values() {
|
||||||
client.error(e);
|
client.error(e);
|
||||||
}
|
}
|
||||||
|
self.state.workspace_managers.announce_workspace(self, &ws);
|
||||||
self.schedule_update_render_data();
|
self.schedule_update_render_data();
|
||||||
ws
|
ws
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -10,6 +10,10 @@ use {
|
||||||
wl_surface::{
|
wl_surface::{
|
||||||
x_surface::xwindow::Xwindow, xdg_surface::xdg_toplevel::XdgToplevel, WlSurface,
|
x_surface::xwindow::Xwindow, xdg_surface::xdg_toplevel::XdgToplevel, WlSurface,
|
||||||
},
|
},
|
||||||
|
workspace_manager::{
|
||||||
|
ext_workspace_handle_v1::ExtWorkspaceHandleV1,
|
||||||
|
ext_workspace_manager_v1::WorkspaceManagerId,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
rect::Rect,
|
rect::Rect,
|
||||||
renderer::Renderer,
|
renderer::Renderer,
|
||||||
|
|
@ -25,6 +29,7 @@ use {
|
||||||
copyhashmap::CopyHashMap,
|
copyhashmap::CopyHashMap,
|
||||||
linkedlist::{LinkedList, LinkedNode, NodeRef},
|
linkedlist::{LinkedList, LinkedNode, NodeRef},
|
||||||
numcell::NumCell,
|
numcell::NumCell,
|
||||||
|
opt::Opt,
|
||||||
threshold_counter::ThresholdCounter,
|
threshold_counter::ThresholdCounter,
|
||||||
},
|
},
|
||||||
wire::JayWorkspaceId,
|
wire::JayWorkspaceId,
|
||||||
|
|
@ -60,6 +65,8 @@ pub struct WorkspaceNode {
|
||||||
pub title_texture: RefCell<Option<TextTexture>>,
|
pub title_texture: RefCell<Option<TextTexture>>,
|
||||||
pub attention_requests: ThresholdCounter,
|
pub attention_requests: ThresholdCounter,
|
||||||
pub render_highlight: NumCell<u32>,
|
pub render_highlight: NumCell<u32>,
|
||||||
|
pub ext_workspaces: CopyHashMap<WorkspaceManagerId, Rc<ExtWorkspaceHandleV1>>,
|
||||||
|
pub opt: Rc<Opt<WorkspaceNode>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl WorkspaceNode {
|
impl WorkspaceNode {
|
||||||
|
|
@ -68,6 +75,8 @@ impl WorkspaceNode {
|
||||||
*self.output_link.borrow_mut() = None;
|
*self.output_link.borrow_mut() = None;
|
||||||
self.fullscreen.set(None);
|
self.fullscreen.set(None);
|
||||||
self.jay_workspaces.clear();
|
self.jay_workspaces.clear();
|
||||||
|
self.ext_workspaces.clear();
|
||||||
|
self.opt.set(None);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn update_has_captures(&self) {
|
pub fn update_has_captures(&self) {
|
||||||
|
|
@ -95,6 +104,9 @@ impl WorkspaceNode {
|
||||||
|
|
||||||
pub fn set_output(&self, output: &Rc<OutputNode>) {
|
pub fn set_output(&self, output: &Rc<OutputNode>) {
|
||||||
self.output.set(output.clone());
|
self.output.set(output.clone());
|
||||||
|
for wh in self.ext_workspaces.lock().values() {
|
||||||
|
wh.handle_new_output(output);
|
||||||
|
}
|
||||||
for jw in self.jay_workspaces.lock().values() {
|
for jw in self.jay_workspaces.lock().values() {
|
||||||
jw.send_output(output);
|
jw.send_output(output);
|
||||||
}
|
}
|
||||||
|
|
@ -171,6 +183,9 @@ impl WorkspaceNode {
|
||||||
for jw in self.jay_workspaces.lock().values() {
|
for jw in self.jay_workspaces.lock().values() {
|
||||||
jw.send_visible(visible);
|
jw.send_visible(visible);
|
||||||
}
|
}
|
||||||
|
for wh in self.ext_workspaces.lock().values() {
|
||||||
|
wh.handle_visibility_changed();
|
||||||
|
}
|
||||||
for stacked in self.stacked.iter() {
|
for stacked in self.stacked.iter() {
|
||||||
stacked.stacked_prepare_set_visible();
|
stacked.stacked_prepare_set_visible();
|
||||||
}
|
}
|
||||||
|
|
@ -236,6 +251,9 @@ impl WorkspaceNode {
|
||||||
fn mod_attention_requested(&self, set: bool) {
|
fn mod_attention_requested(&self, set: bool) {
|
||||||
let crossed_threshold = self.attention_requests.adj(set);
|
let crossed_threshold = self.attention_requests.adj(set);
|
||||||
if crossed_threshold {
|
if crossed_threshold {
|
||||||
|
for wh in self.ext_workspaces.lock().values() {
|
||||||
|
wh.handle_urgent_changed();
|
||||||
|
}
|
||||||
self.output.get().schedule_update_render_data();
|
self.output.get().schedule_update_render_data();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -35,6 +35,7 @@ pub mod on_drop_event;
|
||||||
pub mod once;
|
pub mod once;
|
||||||
pub mod opaque;
|
pub mod opaque;
|
||||||
pub mod opaque_cell;
|
pub mod opaque_cell;
|
||||||
|
pub mod opt;
|
||||||
pub mod option_ext;
|
pub mod option_ext;
|
||||||
pub mod oserror;
|
pub mod oserror;
|
||||||
pub mod page_size;
|
pub mod page_size;
|
||||||
|
|
|
||||||
27
src/utils/opt.rs
Normal file
27
src/utils/opt.rs
Normal file
|
|
@ -0,0 +1,27 @@
|
||||||
|
use {crate::utils::clonecell::CloneCell, std::rc::Rc};
|
||||||
|
|
||||||
|
pub struct Opt<T> {
|
||||||
|
t: CloneCell<Option<Rc<T>>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T> Default for Opt<T> {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self {
|
||||||
|
t: Default::default(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T> Opt<T> {
|
||||||
|
pub fn set(&self, t: Option<Rc<T>>) {
|
||||||
|
self.t.set(t);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get(&self) -> Option<Rc<T>> {
|
||||||
|
self.t.get()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn is_none(&self) -> bool {
|
||||||
|
self.t.is_none()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -53,4 +53,10 @@ impl<T> SyncQueue<T> {
|
||||||
self.swap(&mut res);
|
self.swap(&mut res);
|
||||||
res
|
res
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn clear(&self) {
|
||||||
|
unsafe {
|
||||||
|
self.el.get().deref_mut().clear();
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
31
wire/ext_workspace_group_handle_v1.txt
Normal file
31
wire/ext_workspace_group_handle_v1.txt
Normal file
|
|
@ -0,0 +1,31 @@
|
||||||
|
event capabilities {
|
||||||
|
capabilities: u32,
|
||||||
|
}
|
||||||
|
|
||||||
|
event output_enter {
|
||||||
|
output: id(wl_output),
|
||||||
|
}
|
||||||
|
|
||||||
|
event output_leave {
|
||||||
|
output: id(wl_output),
|
||||||
|
}
|
||||||
|
|
||||||
|
event workspace_enter {
|
||||||
|
workspace: id(ext_workspace_handle_v1),
|
||||||
|
}
|
||||||
|
|
||||||
|
event workspace_leave {
|
||||||
|
workspace: id(ext_workspace_handle_v1),
|
||||||
|
}
|
||||||
|
|
||||||
|
event removed {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
request create_workspace {
|
||||||
|
workspace: str,
|
||||||
|
}
|
||||||
|
|
||||||
|
request destroy {
|
||||||
|
|
||||||
|
}
|
||||||
43
wire/ext_workspace_handle_v1.txt
Normal file
43
wire/ext_workspace_handle_v1.txt
Normal file
|
|
@ -0,0 +1,43 @@
|
||||||
|
event id {
|
||||||
|
id: str,
|
||||||
|
}
|
||||||
|
|
||||||
|
event name {
|
||||||
|
name: str,
|
||||||
|
}
|
||||||
|
|
||||||
|
event coordinates {
|
||||||
|
coordinates: array(u32),
|
||||||
|
}
|
||||||
|
|
||||||
|
event state {
|
||||||
|
state: u32,
|
||||||
|
}
|
||||||
|
|
||||||
|
event capabilities {
|
||||||
|
capabilities: u32,
|
||||||
|
}
|
||||||
|
|
||||||
|
event removed {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
request destroy {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
request activate {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
request deactivate {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
request assign {
|
||||||
|
workspace_group: id(ext_workspace_group_handle_v1),
|
||||||
|
}
|
||||||
|
|
||||||
|
request remove {
|
||||||
|
|
||||||
|
}
|
||||||
23
wire/ext_workspace_manager_v1.txt
Normal file
23
wire/ext_workspace_manager_v1.txt
Normal file
|
|
@ -0,0 +1,23 @@
|
||||||
|
event workspace_group {
|
||||||
|
workspace_group: id(ext_workspace_group_handle_v1),
|
||||||
|
}
|
||||||
|
|
||||||
|
event workspace {
|
||||||
|
workspace: id(ext_workspace_handle_v1),
|
||||||
|
}
|
||||||
|
|
||||||
|
request commit {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
event done {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
event finished {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
request stop {
|
||||||
|
|
||||||
|
}
|
||||||
Loading…
Add table
Add a link
Reference in a new issue