mirror of
https://github.com/niri-wm/niri.git
synced 2026-06-21 02:01:55 +07:00
Implement ext-workspace
This commit is contained in:
+42
-2
@@ -76,8 +76,10 @@ use smithay::{
|
||||
};
|
||||
|
||||
pub use crate::handlers::xdg_shell::KdeDecorationsModeState;
|
||||
use crate::layout::workspace::WorkspaceId;
|
||||
use crate::layout::ActivateWindow;
|
||||
use crate::niri::{DndIcon, NewClient, State};
|
||||
use crate::protocols::ext_workspace::{self, ExtWorkspaceHandler, ExtWorkspaceManagerState};
|
||||
use crate::protocols::foreign_toplevel::{
|
||||
self, ForeignToplevelHandler, ForeignToplevelManagerState,
|
||||
};
|
||||
@@ -92,8 +94,9 @@ use crate::protocols::virtual_pointer::{
|
||||
};
|
||||
use crate::utils::{output_size, send_scale_transform, with_toplevel_role};
|
||||
use crate::{
|
||||
delegate_foreign_toplevel, delegate_gamma_control, delegate_mutter_x11_interop,
|
||||
delegate_output_management, delegate_screencopy, delegate_virtual_pointer,
|
||||
delegate_ext_workspace, delegate_foreign_toplevel, delegate_gamma_control,
|
||||
delegate_mutter_x11_interop, delegate_output_management, delegate_screencopy,
|
||||
delegate_virtual_pointer,
|
||||
};
|
||||
|
||||
pub const XDG_ACTIVATION_TOKEN_TIMEOUT: Duration = Duration::from_secs(10);
|
||||
@@ -414,6 +417,7 @@ delegate_ext_data_control!(State);
|
||||
impl OutputHandler for State {
|
||||
fn output_bound(&mut self, output: Output, wl_output: WlOutput) {
|
||||
foreign_toplevel::on_output_bound(self, &output, &wl_output);
|
||||
ext_workspace::on_output_bound(self, &output, &wl_output);
|
||||
}
|
||||
}
|
||||
delegate_output!(State);
|
||||
@@ -574,6 +578,42 @@ impl ForeignToplevelHandler for State {
|
||||
}
|
||||
delegate_foreign_toplevel!(State);
|
||||
|
||||
impl ExtWorkspaceHandler for State {
|
||||
fn ext_workspace_manager_state(&mut self) -> &mut ExtWorkspaceManagerState {
|
||||
&mut self.niri.ext_workspace_state
|
||||
}
|
||||
|
||||
fn activate_workspace(&mut self, id: WorkspaceId) {
|
||||
let reference = niri_config::WorkspaceReference::Id(id.get());
|
||||
if let Some((mut output, index)) = self.niri.find_output_and_workspace_index(reference) {
|
||||
if let Some(active) = self.niri.layout.active_output() {
|
||||
if output.as_ref() == Some(active) {
|
||||
output = None;
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(output) = output {
|
||||
self.niri.layout.focus_output(&output);
|
||||
}
|
||||
self.niri.layout.switch_workspace(index);
|
||||
// No mouse warp: assuming the layer-shell bar workspaces use-case.
|
||||
|
||||
// FIXME: granular
|
||||
self.niri.queue_redraw_all();
|
||||
}
|
||||
}
|
||||
|
||||
fn assign_workspace(&mut self, ws_id: WorkspaceId, output: Output) {
|
||||
let reference = niri_config::WorkspaceReference::Id(ws_id.get());
|
||||
if let Some((old_output, old_idx)) = self.niri.find_output_and_workspace_index(reference) {
|
||||
self.niri
|
||||
.layout
|
||||
.move_workspace_to_output_by_id(old_idx, old_output, output);
|
||||
}
|
||||
}
|
||||
}
|
||||
delegate_ext_workspace!(State);
|
||||
|
||||
impl ScreencopyHandler for State {
|
||||
fn frame(&mut self, manager: &ZwlrScreencopyManagerV1, screencopy: Screencopy) {
|
||||
// If with_damage then push it onto the queue for redraw of the output,
|
||||
|
||||
@@ -136,6 +136,7 @@ use crate::layout::tile::TileRenderElement;
|
||||
use crate::layout::workspace::{Workspace, WorkspaceId};
|
||||
use crate::layout::{HitType, Layout, LayoutElement as _, MonitorRenderElement};
|
||||
use crate::niri_render_elements;
|
||||
use crate::protocols::ext_workspace::{self, ExtWorkspaceManagerState};
|
||||
use crate::protocols::foreign_toplevel::{self, ForeignToplevelManagerState};
|
||||
use crate::protocols::gamma_control::GammaControlManagerState;
|
||||
use crate::protocols::mutter_x11_interop::MutterX11InteropManagerState;
|
||||
@@ -259,6 +260,7 @@ pub struct Niri {
|
||||
pub layer_shell_state: WlrLayerShellState,
|
||||
pub session_lock_state: SessionLockManagerState,
|
||||
pub foreign_toplevel_state: ForeignToplevelManagerState,
|
||||
pub ext_workspace_state: ExtWorkspaceManagerState,
|
||||
pub screencopy_state: ScreencopyManagerState,
|
||||
pub output_management_state: OutputManagementManagerState,
|
||||
pub viewporter_state: ViewporterState,
|
||||
@@ -719,6 +721,7 @@ impl State {
|
||||
self.niri.refresh_idle_inhibit();
|
||||
self.refresh_pointer_contents();
|
||||
foreign_toplevel::refresh(self);
|
||||
ext_workspace::refresh(self);
|
||||
|
||||
#[cfg(feature = "xdp-gnome-screencast")]
|
||||
self.niri.refresh_mapped_cast_outputs();
|
||||
@@ -2325,6 +2328,8 @@ impl Niri {
|
||||
VirtualPointerManagerState::new::<State, _>(&display_handle, client_is_unrestricted);
|
||||
let foreign_toplevel_state =
|
||||
ForeignToplevelManagerState::new::<State, _>(&display_handle, client_is_unrestricted);
|
||||
let ext_workspace_state =
|
||||
ExtWorkspaceManagerState::new::<State, _>(&display_handle, client_is_unrestricted);
|
||||
let mut output_management_state =
|
||||
OutputManagementManagerState::new::<State, _>(&display_handle, client_is_unrestricted);
|
||||
output_management_state.on_config_changed(config_.outputs.clone());
|
||||
@@ -2519,6 +2524,7 @@ impl Niri {
|
||||
layer_shell_state,
|
||||
session_lock_state,
|
||||
foreign_toplevel_state,
|
||||
ext_workspace_state,
|
||||
output_management_state,
|
||||
screencopy_state,
|
||||
viewporter_state,
|
||||
|
||||
@@ -0,0 +1,715 @@
|
||||
//! ext-workspace protocol implementation.
|
||||
//!
|
||||
//! This is how we map the protocol concepts to the niri concepts:
|
||||
//!
|
||||
//! - Workspace groups are outputs.
|
||||
//! - Workspace coordinates: X = 0, Y = workspace index. They need to be two-dimensional because 1D
|
||||
//! coordinates are defined to be a plain list without a geometric interpretation, while we do
|
||||
//! order workspaces in a vertical line.
|
||||
//! - Workspace id: name for named workspaces, unset for unnamed. Because ids in this protocol are
|
||||
//! expected to be stable across sessions.
|
||||
//! - Workspace name: name for named workspaces, index for unnamed.
|
||||
|
||||
use std::collections::hash_map::Entry;
|
||||
use std::collections::HashMap;
|
||||
use std::mem;
|
||||
|
||||
use arrayvec::ArrayVec;
|
||||
use ext_workspace_group_handle_v1::ExtWorkspaceGroupHandleV1;
|
||||
use ext_workspace_handle_v1::ExtWorkspaceHandleV1;
|
||||
use ext_workspace_manager_v1::ExtWorkspaceManagerV1;
|
||||
use smithay::output::{Output, WeakOutput};
|
||||
use smithay::reexports::wayland_protocols::ext::workspace::v1::server::{
|
||||
ext_workspace_group_handle_v1, ext_workspace_handle_v1, ext_workspace_manager_v1,
|
||||
};
|
||||
use smithay::reexports::wayland_server::protocol::wl_output::WlOutput;
|
||||
use smithay::reexports::wayland_server::{
|
||||
Client, DataInit, Dispatch, DisplayHandle, GlobalDispatch, New, Resource,
|
||||
};
|
||||
use wayland_backend::server::ClientId;
|
||||
|
||||
use crate::layout::monitor::Monitor;
|
||||
use crate::layout::workspace::{Workspace, WorkspaceId};
|
||||
use crate::niri::State;
|
||||
use crate::window::Mapped;
|
||||
|
||||
const VERSION: u32 = 1;
|
||||
|
||||
pub trait ExtWorkspaceHandler {
|
||||
fn ext_workspace_manager_state(&mut self) -> &mut ExtWorkspaceManagerState;
|
||||
fn activate_workspace(&mut self, id: WorkspaceId);
|
||||
fn assign_workspace(&mut self, ws_id: WorkspaceId, output: Output);
|
||||
}
|
||||
|
||||
enum Action {
|
||||
Assign(WorkspaceId, WeakOutput),
|
||||
Activate(WorkspaceId),
|
||||
}
|
||||
|
||||
impl Action {
|
||||
fn order(&self) -> u8 {
|
||||
// First assign everything (move across outputs), then activate.
|
||||
match self {
|
||||
Action::Assign(_, _) => 0,
|
||||
Action::Activate(_) => 1,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub struct ExtWorkspaceManagerState {
|
||||
display: DisplayHandle,
|
||||
instances: HashMap<ExtWorkspaceManagerV1, Vec<Action>>,
|
||||
workspace_groups: HashMap<Output, ExtWorkspaceGroupData>,
|
||||
workspaces: HashMap<WorkspaceId, ExtWorkspaceData>,
|
||||
}
|
||||
|
||||
struct ExtWorkspaceGroupData {
|
||||
instances: Vec<ExtWorkspaceGroupHandleV1>,
|
||||
}
|
||||
|
||||
struct ExtWorkspaceData {
|
||||
// id cannot change once set.
|
||||
id: Option<String>,
|
||||
name: String,
|
||||
coordinates: ArrayVec<u32, 2>,
|
||||
state: ext_workspace_handle_v1::State,
|
||||
instances: Vec<ExtWorkspaceHandleV1>,
|
||||
output: Option<Output>,
|
||||
}
|
||||
|
||||
pub struct ExtWorkspaceGlobalData {
|
||||
filter: Box<dyn for<'c> Fn(&'c Client) -> bool + Send + Sync>,
|
||||
}
|
||||
|
||||
pub fn refresh(state: &mut State) {
|
||||
let _span = tracy_client::span!("ext_workspace::refresh");
|
||||
|
||||
let protocol_state = &mut state.niri.ext_workspace_state;
|
||||
|
||||
let mut changed = false;
|
||||
|
||||
// Remove workspaces that no longer exist (sending workspace_leave to workspace groups).
|
||||
let mut seen_workspaces = HashMap::new();
|
||||
for (mon, _, ws) in state.niri.layout.workspaces() {
|
||||
let output = mon.map(|mon| mon.output());
|
||||
seen_workspaces.insert(ws.id(), output);
|
||||
}
|
||||
|
||||
protocol_state.workspaces.retain(|id, workspace| {
|
||||
if seen_workspaces.contains_key(id) {
|
||||
return true;
|
||||
}
|
||||
|
||||
remove_workspace_instances(&protocol_state.workspace_groups, workspace);
|
||||
changed = true;
|
||||
false
|
||||
});
|
||||
|
||||
// Remove workspace groups for outputs that no longer exist.
|
||||
protocol_state.workspace_groups.retain(|output, data| {
|
||||
if state.niri.sorted_outputs.contains(output) {
|
||||
return true;
|
||||
}
|
||||
|
||||
for group in &data.instances {
|
||||
// Send workspace_leave for all workspaces in this group with matching manager.
|
||||
let manager: &ExtWorkspaceManagerV1 = group.data().unwrap();
|
||||
for ws in protocol_state.workspaces.values() {
|
||||
if ws.output.as_ref() == Some(output) {
|
||||
for workspace in &ws.instances {
|
||||
if workspace.data() == Some(manager) {
|
||||
group.workspace_leave(workspace);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
group.removed();
|
||||
}
|
||||
|
||||
changed = true;
|
||||
false
|
||||
});
|
||||
|
||||
// Update existing workspaces and create new ones.
|
||||
for (mon, ws_idx, ws) in state.niri.layout.workspaces() {
|
||||
changed |= refresh_workspace(protocol_state, mon, ws_idx, ws);
|
||||
}
|
||||
|
||||
// Update workspace groups and create new ones, sending workspace_enter events as needed.
|
||||
for output in &state.niri.sorted_outputs {
|
||||
changed |= refresh_workspace_group(protocol_state, output);
|
||||
}
|
||||
|
||||
if changed {
|
||||
for manager in protocol_state.instances.keys() {
|
||||
manager.done();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn on_output_bound(state: &mut State, output: &Output, wl_output: &WlOutput) {
|
||||
let Some(client) = wl_output.client() else {
|
||||
return;
|
||||
};
|
||||
|
||||
let mut sent = false;
|
||||
|
||||
let protocol_state = &mut state.niri.ext_workspace_state;
|
||||
if let Some(data) = protocol_state.workspace_groups.get_mut(output) {
|
||||
for group in &mut data.instances {
|
||||
if group.client().as_ref() != Some(&client) {
|
||||
continue;
|
||||
}
|
||||
|
||||
group.output_enter(wl_output);
|
||||
sent = true;
|
||||
}
|
||||
}
|
||||
|
||||
if !sent {
|
||||
return;
|
||||
}
|
||||
|
||||
for manager in protocol_state.instances.keys() {
|
||||
if manager.client().as_ref() == Some(&client) {
|
||||
manager.done();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn refresh_workspace_group(protocol_state: &mut ExtWorkspaceManagerState, output: &Output) -> bool {
|
||||
if protocol_state.workspace_groups.contains_key(output) {
|
||||
// Existing workspace group. Nothing can actually change since our workspace groups are tied
|
||||
// to an output.
|
||||
return false;
|
||||
}
|
||||
|
||||
// New workspace group, start tracking it.
|
||||
let mut data = ExtWorkspaceGroupData {
|
||||
instances: Vec::new(),
|
||||
};
|
||||
|
||||
// Create workspace group handle for each manager instance.
|
||||
for manager in protocol_state.instances.keys() {
|
||||
if let Some(client) = manager.client() {
|
||||
data.add_instance::<State>(&protocol_state.display, &client, manager, output);
|
||||
}
|
||||
}
|
||||
|
||||
// Send workspace_enter for all existing workspaces on this output.
|
||||
for group in &data.instances {
|
||||
let manager: &ExtWorkspaceManagerV1 = group.data().unwrap();
|
||||
for (_, ws) in protocol_state.workspaces.iter() {
|
||||
if ws.output.as_ref() != Some(output) {
|
||||
continue;
|
||||
}
|
||||
for workspace in &ws.instances {
|
||||
if workspace.data() == Some(manager) {
|
||||
group.workspace_enter(workspace);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protocol_state.workspace_groups.insert(output.clone(), data);
|
||||
true
|
||||
}
|
||||
|
||||
fn send_workspace_enter_leave(
|
||||
workspace_groups: &HashMap<Output, ExtWorkspaceGroupData>,
|
||||
data: &ExtWorkspaceData,
|
||||
enter: bool,
|
||||
) {
|
||||
if let Some(output) = &data.output {
|
||||
if let Some(group_data) = workspace_groups.get(output) {
|
||||
for group in &group_data.instances {
|
||||
let manager: &ExtWorkspaceManagerV1 = group.data().unwrap();
|
||||
for workspace in &data.instances {
|
||||
if workspace.data() == Some(manager) {
|
||||
if enter {
|
||||
group.workspace_enter(workspace);
|
||||
} else {
|
||||
group.workspace_leave(workspace);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn remove_workspace_instances(
|
||||
workspace_groups: &HashMap<Output, ExtWorkspaceGroupData>,
|
||||
data: &ExtWorkspaceData,
|
||||
) {
|
||||
send_workspace_enter_leave(workspace_groups, data, false);
|
||||
|
||||
for workspace in &data.instances {
|
||||
workspace.removed();
|
||||
}
|
||||
}
|
||||
|
||||
fn build_name(ws: &Workspace<Mapped>, ws_idx: usize) -> String {
|
||||
ws.name().cloned().unwrap_or_else(|| {
|
||||
// Add 1 since this is a human-readable name, and our action indexing is 1-based.
|
||||
(ws_idx + 1).to_string()
|
||||
})
|
||||
}
|
||||
|
||||
fn refresh_workspace(
|
||||
protocol_state: &mut ExtWorkspaceManagerState,
|
||||
mon: Option<&Monitor<Mapped>>,
|
||||
ws_idx: usize,
|
||||
ws: &Workspace<Mapped>,
|
||||
) -> bool {
|
||||
let mut state = ext_workspace_handle_v1::State::empty();
|
||||
if mon.is_some_and(|mon| mon.active_workspace_idx() == ws_idx) {
|
||||
state |= ext_workspace_handle_v1::State::Active;
|
||||
}
|
||||
if ws.is_urgent() {
|
||||
state |= ext_workspace_handle_v1::State::Urgent;
|
||||
}
|
||||
|
||||
let output = mon.map(|mon| mon.output());
|
||||
|
||||
match protocol_state.workspaces.entry(ws.id()) {
|
||||
Entry::Occupied(entry) => {
|
||||
// Existing workspace, check if anything changed.
|
||||
let data = entry.into_mut();
|
||||
|
||||
let mut id_set = false;
|
||||
let mut recreate = false;
|
||||
let id = ws.name();
|
||||
if data.id.as_ref() != id {
|
||||
if data.id.is_some() {
|
||||
recreate = true;
|
||||
} else {
|
||||
id_set = true;
|
||||
}
|
||||
data.id = id.cloned();
|
||||
}
|
||||
|
||||
let mut coordinates_changed = false;
|
||||
if data.coordinates[1] != ws_idx as u32 {
|
||||
data.coordinates[1] = ws_idx as u32;
|
||||
coordinates_changed = true;
|
||||
}
|
||||
|
||||
let mut state_changed = false;
|
||||
if data.state != state {
|
||||
data.state = state;
|
||||
state_changed = true;
|
||||
}
|
||||
|
||||
// Recreate means name got changed or unset (meaning data.name is back to ws_idx).
|
||||
let check = recreate
|
||||
|| if data.id.is_some() {
|
||||
// True means workspace got named, going from ws_idx to name.
|
||||
id_set
|
||||
} else {
|
||||
// The workspace is unnamed, check if ws_idx changed.
|
||||
coordinates_changed
|
||||
};
|
||||
let mut name_changed = false;
|
||||
if check {
|
||||
let new_name = build_name(ws, ws_idx);
|
||||
// This will likely be true, except if the workspace got named its index.
|
||||
if data.name != new_name {
|
||||
data.name = new_name;
|
||||
name_changed = true;
|
||||
}
|
||||
}
|
||||
|
||||
let mut output_changed = false;
|
||||
if data.output.as_ref() != output {
|
||||
send_workspace_enter_leave(&protocol_state.workspace_groups, data, false);
|
||||
data.output = output.cloned();
|
||||
output_changed = true;
|
||||
}
|
||||
|
||||
if recreate {
|
||||
remove_workspace_instances(&protocol_state.workspace_groups, data);
|
||||
data.instances.clear();
|
||||
|
||||
for manager in protocol_state.instances.keys() {
|
||||
if let Some(client) = manager.client() {
|
||||
data.add_instance::<State>(&protocol_state.display, &client, manager);
|
||||
}
|
||||
}
|
||||
|
||||
send_workspace_enter_leave(&protocol_state.workspace_groups, data, true);
|
||||
return true;
|
||||
}
|
||||
|
||||
if output_changed {
|
||||
// Send workspace_enter to the new output's group. If the group doesn't exist yet
|
||||
// (new groups are created after refreshing workspaces), then workspace_enter() will
|
||||
// be sent when the group is created.
|
||||
send_workspace_enter_leave(&protocol_state.workspace_groups, data, true);
|
||||
}
|
||||
|
||||
let something_changed = id_set || name_changed || coordinates_changed || state_changed;
|
||||
if something_changed {
|
||||
for instance in &data.instances {
|
||||
if id_set {
|
||||
instance.id(data.id.clone().unwrap());
|
||||
}
|
||||
if name_changed {
|
||||
instance.name(data.name.clone());
|
||||
}
|
||||
if coordinates_changed {
|
||||
instance.coordinates(
|
||||
data.coordinates
|
||||
.iter()
|
||||
.flat_map(|x| x.to_ne_bytes())
|
||||
.collect(),
|
||||
);
|
||||
}
|
||||
if state_changed {
|
||||
instance.state(data.state);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
output_changed || something_changed
|
||||
}
|
||||
Entry::Vacant(entry) => {
|
||||
// New workspace, start tracking it.
|
||||
let mut data = ExtWorkspaceData {
|
||||
id: ws.name().cloned(),
|
||||
name: build_name(ws, ws_idx),
|
||||
coordinates: ArrayVec::from([0, ws_idx as u32]),
|
||||
state,
|
||||
instances: Vec::new(),
|
||||
output: output.cloned(),
|
||||
};
|
||||
|
||||
for manager in protocol_state.instances.keys() {
|
||||
if let Some(client) = manager.client() {
|
||||
data.add_instance::<State>(&protocol_state.display, &client, manager);
|
||||
}
|
||||
}
|
||||
|
||||
send_workspace_enter_leave(&protocol_state.workspace_groups, &data, true);
|
||||
entry.insert(data);
|
||||
true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl ExtWorkspaceGroupData {
|
||||
fn add_instance<D>(
|
||||
&mut self,
|
||||
handle: &DisplayHandle,
|
||||
client: &Client,
|
||||
manager: &ExtWorkspaceManagerV1,
|
||||
output: &Output,
|
||||
) -> &ExtWorkspaceGroupHandleV1
|
||||
where
|
||||
D: Dispatch<ExtWorkspaceGroupHandleV1, ExtWorkspaceManagerV1>,
|
||||
D: 'static,
|
||||
{
|
||||
let group = client
|
||||
.create_resource::<ExtWorkspaceGroupHandleV1, _, D>(
|
||||
handle,
|
||||
manager.version(),
|
||||
manager.clone(),
|
||||
)
|
||||
.unwrap();
|
||||
manager.workspace_group(&group);
|
||||
|
||||
group.capabilities(ext_workspace_group_handle_v1::GroupCapabilities::empty());
|
||||
|
||||
for wl_output in output.client_outputs(client) {
|
||||
group.output_enter(&wl_output);
|
||||
}
|
||||
|
||||
self.instances.push(group);
|
||||
self.instances.last().unwrap()
|
||||
}
|
||||
}
|
||||
|
||||
impl ExtWorkspaceData {
|
||||
fn add_instance<D>(
|
||||
&mut self,
|
||||
handle: &DisplayHandle,
|
||||
client: &Client,
|
||||
manager: &ExtWorkspaceManagerV1,
|
||||
) -> &ExtWorkspaceHandleV1
|
||||
where
|
||||
D: Dispatch<ExtWorkspaceHandleV1, ExtWorkspaceManagerV1>,
|
||||
D: 'static,
|
||||
{
|
||||
let workspace = client
|
||||
.create_resource::<ExtWorkspaceHandleV1, _, D>(
|
||||
handle,
|
||||
manager.version(),
|
||||
manager.clone(),
|
||||
)
|
||||
.unwrap();
|
||||
manager.workspace(&workspace);
|
||||
|
||||
if let Some(id) = self.id.clone() {
|
||||
workspace.id(id);
|
||||
}
|
||||
|
||||
workspace.name(self.name.clone());
|
||||
workspace.coordinates(
|
||||
self.coordinates
|
||||
.iter()
|
||||
.flat_map(|x| x.to_ne_bytes())
|
||||
.collect(),
|
||||
);
|
||||
workspace.state(self.state);
|
||||
workspace.capabilities(
|
||||
ext_workspace_handle_v1::WorkspaceCapabilities::Activate
|
||||
| ext_workspace_handle_v1::WorkspaceCapabilities::Assign,
|
||||
);
|
||||
|
||||
self.instances.push(workspace);
|
||||
self.instances.last().unwrap()
|
||||
}
|
||||
}
|
||||
|
||||
impl ExtWorkspaceManagerState {
|
||||
pub fn new<D, F>(display: &DisplayHandle, filter: F) -> Self
|
||||
where
|
||||
D: GlobalDispatch<ExtWorkspaceManagerV1, ExtWorkspaceGlobalData>,
|
||||
D: Dispatch<ExtWorkspaceManagerV1, ()>,
|
||||
D: 'static,
|
||||
F: for<'c> Fn(&'c Client) -> bool + Send + Sync + 'static,
|
||||
{
|
||||
let global_data = ExtWorkspaceGlobalData {
|
||||
filter: Box::new(filter),
|
||||
};
|
||||
display.create_global::<D, ExtWorkspaceManagerV1, _>(VERSION, global_data);
|
||||
Self {
|
||||
display: display.clone(),
|
||||
instances: HashMap::new(),
|
||||
workspace_groups: HashMap::new(),
|
||||
workspaces: HashMap::new(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<D> GlobalDispatch<ExtWorkspaceManagerV1, ExtWorkspaceGlobalData, D>
|
||||
for ExtWorkspaceManagerState
|
||||
where
|
||||
D: GlobalDispatch<ExtWorkspaceManagerV1, ExtWorkspaceGlobalData>,
|
||||
D: Dispatch<ExtWorkspaceManagerV1, ()>,
|
||||
D: Dispatch<ExtWorkspaceHandleV1, ExtWorkspaceManagerV1>,
|
||||
D: ExtWorkspaceHandler,
|
||||
{
|
||||
fn bind(
|
||||
state: &mut D,
|
||||
handle: &DisplayHandle,
|
||||
client: &Client,
|
||||
resource: New<ExtWorkspaceManagerV1>,
|
||||
_global_data: &ExtWorkspaceGlobalData,
|
||||
data_init: &mut DataInit<'_, D>,
|
||||
) {
|
||||
let manager = data_init.init(resource, ());
|
||||
|
||||
let state = state.ext_workspace_manager_state();
|
||||
|
||||
// Send existing workspaces to the new client.
|
||||
let mut new_workspaces: HashMap<_, Vec<_>> = HashMap::new();
|
||||
for data in state.workspaces.values_mut() {
|
||||
let output = data.output.clone();
|
||||
let workspace = data.add_instance::<State>(handle, client, &manager);
|
||||
|
||||
if let Some(output) = output {
|
||||
new_workspaces.entry(output).or_default().push(workspace);
|
||||
}
|
||||
}
|
||||
|
||||
// Create workspace groups for all outputs.
|
||||
for (output, group_data) in &mut state.workspace_groups {
|
||||
let group = group_data.add_instance::<State>(handle, client, &manager, output);
|
||||
|
||||
for workspace in new_workspaces.get(output).into_iter().flatten() {
|
||||
group.workspace_enter(workspace);
|
||||
}
|
||||
}
|
||||
|
||||
manager.done();
|
||||
state.instances.insert(manager, Vec::new());
|
||||
}
|
||||
|
||||
fn can_view(client: Client, global_data: &ExtWorkspaceGlobalData) -> bool {
|
||||
(global_data.filter)(&client)
|
||||
}
|
||||
}
|
||||
|
||||
impl<D> Dispatch<ExtWorkspaceManagerV1, (), D> for ExtWorkspaceManagerState
|
||||
where
|
||||
D: Dispatch<ExtWorkspaceManagerV1, ()>,
|
||||
D: ExtWorkspaceHandler,
|
||||
{
|
||||
fn request(
|
||||
state: &mut D,
|
||||
_client: &Client,
|
||||
resource: &ExtWorkspaceManagerV1,
|
||||
request: <ExtWorkspaceManagerV1 as Resource>::Request,
|
||||
_data: &(),
|
||||
_dhandle: &DisplayHandle,
|
||||
_data_init: &mut DataInit<'_, D>,
|
||||
) {
|
||||
match request {
|
||||
ext_workspace_manager_v1::Request::Commit => {
|
||||
let protocol_state = state.ext_workspace_manager_state();
|
||||
let actions = protocol_state.instances.get_mut(resource).unwrap();
|
||||
let mut actions = mem::take(actions);
|
||||
|
||||
actions.sort_by_key(Action::order);
|
||||
|
||||
for action in actions {
|
||||
match action {
|
||||
Action::Assign(ws_id, output) => {
|
||||
if let Some(output) = output.upgrade() {
|
||||
state.assign_workspace(ws_id, output);
|
||||
}
|
||||
}
|
||||
Action::Activate(id) => state.activate_workspace(id),
|
||||
}
|
||||
}
|
||||
}
|
||||
ext_workspace_manager_v1::Request::Stop => {
|
||||
resource.finished();
|
||||
|
||||
let state = state.ext_workspace_manager_state();
|
||||
state.instances.retain(|x, _| x != resource);
|
||||
|
||||
for data in state.workspace_groups.values_mut() {
|
||||
data.instances
|
||||
.retain(|instance| instance.data() != Some(resource));
|
||||
}
|
||||
|
||||
for data in state.workspaces.values_mut() {
|
||||
data.instances
|
||||
.retain(|instance| instance.data() != Some(resource));
|
||||
}
|
||||
}
|
||||
_ => unreachable!(),
|
||||
}
|
||||
}
|
||||
|
||||
fn destroyed(state: &mut D, _client: ClientId, resource: &ExtWorkspaceManagerV1, _data: &()) {
|
||||
let state = state.ext_workspace_manager_state();
|
||||
state.instances.retain(|x, _| x != resource);
|
||||
}
|
||||
}
|
||||
|
||||
impl<D> Dispatch<ExtWorkspaceHandleV1, ExtWorkspaceManagerV1, D> for ExtWorkspaceManagerState
|
||||
where
|
||||
D: Dispatch<ExtWorkspaceHandleV1, ExtWorkspaceManagerV1>,
|
||||
D: ExtWorkspaceHandler,
|
||||
{
|
||||
fn request(
|
||||
state: &mut D,
|
||||
_client: &Client,
|
||||
resource: &ExtWorkspaceHandleV1,
|
||||
request: <ExtWorkspaceHandleV1 as Resource>::Request,
|
||||
data: &ExtWorkspaceManagerV1,
|
||||
_dhandle: &DisplayHandle,
|
||||
_data_init: &mut DataInit<'_, D>,
|
||||
) {
|
||||
let protocol_state = state.ext_workspace_manager_state();
|
||||
|
||||
let Some((workspace, _)) = protocol_state
|
||||
.workspaces
|
||||
.iter()
|
||||
.find(|(_, data)| data.instances.contains(resource))
|
||||
else {
|
||||
return;
|
||||
};
|
||||
let workspace = *workspace;
|
||||
|
||||
match request {
|
||||
ext_workspace_handle_v1::Request::Activate => {
|
||||
let actions = protocol_state.instances.get_mut(data).unwrap();
|
||||
actions.push(Action::Activate(workspace));
|
||||
}
|
||||
ext_workspace_handle_v1::Request::Deactivate => (),
|
||||
ext_workspace_handle_v1::Request::Assign { workspace_group } => {
|
||||
if let Some(output) = protocol_state
|
||||
.workspace_groups
|
||||
.iter()
|
||||
.find(|(_, data)| data.instances.contains(&workspace_group))
|
||||
.map(|(output, _)| output.clone())
|
||||
{
|
||||
let actions = protocol_state.instances.get_mut(data).unwrap();
|
||||
actions.push(Action::Assign(workspace, output.downgrade()));
|
||||
}
|
||||
}
|
||||
ext_workspace_handle_v1::Request::Remove => (),
|
||||
ext_workspace_handle_v1::Request::Destroy => (),
|
||||
_ => unreachable!(),
|
||||
}
|
||||
}
|
||||
|
||||
fn destroyed(
|
||||
state: &mut D,
|
||||
_client: ClientId,
|
||||
resource: &ExtWorkspaceHandleV1,
|
||||
_data: &ExtWorkspaceManagerV1,
|
||||
) {
|
||||
let state = state.ext_workspace_manager_state();
|
||||
for data in state.workspaces.values_mut() {
|
||||
data.instances.retain(|instance| instance != resource);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<D> Dispatch<ExtWorkspaceGroupHandleV1, ExtWorkspaceManagerV1, D> for ExtWorkspaceManagerState
|
||||
where
|
||||
D: Dispatch<ExtWorkspaceGroupHandleV1, ExtWorkspaceManagerV1>,
|
||||
D: ExtWorkspaceHandler,
|
||||
{
|
||||
fn request(
|
||||
_state: &mut D,
|
||||
_client: &Client,
|
||||
_resource: &ExtWorkspaceGroupHandleV1,
|
||||
request: <ExtWorkspaceGroupHandleV1 as Resource>::Request,
|
||||
_data: &ExtWorkspaceManagerV1,
|
||||
_dhandle: &DisplayHandle,
|
||||
_data_init: &mut DataInit<'_, D>,
|
||||
) {
|
||||
match request {
|
||||
ext_workspace_group_handle_v1::Request::CreateWorkspace { .. } => (),
|
||||
ext_workspace_group_handle_v1::Request::Destroy => (),
|
||||
_ => unreachable!(),
|
||||
}
|
||||
}
|
||||
|
||||
fn destroyed(
|
||||
state: &mut D,
|
||||
_client: ClientId,
|
||||
resource: &ExtWorkspaceGroupHandleV1,
|
||||
_data: &ExtWorkspaceManagerV1,
|
||||
) {
|
||||
let state = state.ext_workspace_manager_state();
|
||||
for data in state.workspace_groups.values_mut() {
|
||||
data.instances.retain(|instance| instance != resource);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[macro_export]
|
||||
macro_rules! delegate_ext_workspace {
|
||||
($(@<$( $lt:tt $( : $clt:tt $(+ $dlt:tt )* )? ),+>)? $ty: ty) => {
|
||||
smithay::reexports::wayland_server::delegate_global_dispatch!($(@< $( $lt $( : $clt $(+ $dlt )* )? ),+ >)? $ty: [
|
||||
smithay::reexports::wayland_protocols::ext::workspace::v1::server::ext_workspace_manager_v1::ExtWorkspaceManagerV1: $crate::protocols::ext_workspace::ExtWorkspaceGlobalData
|
||||
] => $crate::protocols::ext_workspace::ExtWorkspaceManagerState);
|
||||
smithay::reexports::wayland_server::delegate_dispatch!($(@< $( $lt $( : $clt $(+ $dlt )* )? ),+ >)? $ty: [
|
||||
smithay::reexports::wayland_protocols::ext::workspace::v1::server::ext_workspace_manager_v1::ExtWorkspaceManagerV1: ()
|
||||
] => $crate::protocols::ext_workspace::ExtWorkspaceManagerState);
|
||||
smithay::reexports::wayland_server::delegate_dispatch!($(@< $( $lt $( : $clt $(+ $dlt )* )? ),+ >)? $ty: [
|
||||
smithay::reexports::wayland_protocols::ext::workspace::v1::server::ext_workspace_handle_v1::ExtWorkspaceHandleV1: smithay::reexports::wayland_protocols::ext::workspace::v1::server::ext_workspace_manager_v1::ExtWorkspaceManagerV1
|
||||
] => $crate::protocols::ext_workspace::ExtWorkspaceManagerState);
|
||||
smithay::reexports::wayland_server::delegate_dispatch!($(@< $( $lt $( : $clt $(+ $dlt )* )? ),+ >)? $ty: [
|
||||
smithay::reexports::wayland_protocols::ext::workspace::v1::server::ext_workspace_group_handle_v1::ExtWorkspaceGroupHandleV1: smithay::reexports::wayland_protocols::ext::workspace::v1::server::ext_workspace_manager_v1::ExtWorkspaceManagerV1
|
||||
] => $crate::protocols::ext_workspace::ExtWorkspaceManagerState);
|
||||
};
|
||||
}
|
||||
@@ -1,3 +1,4 @@
|
||||
pub mod ext_workspace;
|
||||
pub mod foreign_toplevel;
|
||||
pub mod gamma_control;
|
||||
pub mod mutter_x11_interop;
|
||||
|
||||
Reference in New Issue
Block a user