diff --git a/src/main.rs b/src/main.rs index 69840bf9..0f3451bb 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,12 +1,15 @@ // SPDX-License-Identifier: GPL-3.0-only -use smithay::reexports::{ - calloop::{generic::Generic, EventLoop, Interest, Mode, PostAction}, - wayland_server::Display, +use smithay::{ + reexports::{ + calloop::{generic::Generic, EventLoop, Interest, Mode, PostAction}, + wayland_server::Display, + }, + wayland::socket::ListeningSocketSource, }; use anyhow::{Context, Result}; -use std::{ffi::OsString, sync::atomic::Ordering}; +use std::{ffi::OsString, sync::{Arc, atomic::Ordering}}; pub mod backend; pub mod config; @@ -33,68 +36,84 @@ fn main() -> Result<()> { let (display, socket) = init_wayland_display(&mut event_loop)?; // init state let mut state = state::State::new( - display, + &display.handle(), socket, event_loop.handle(), event_loop.get_signal(), log, ); // init backend - backend::init_backend_auto(&mut event_loop, &mut state)?; + backend::init_backend_auto(&display.handle(), &mut event_loop, &mut state)?; // potentially tell systemd we are setup now systemd::ready(&state); + let mut data = state::Data { + display, + state, + }; // run the event loop - event_loop.run(None, &mut state, |state| { + event_loop.run(None, &mut data, |data| { // shall we shut down? - if state.common.shell.outputs().next().is_none() || state.common.should_stop { + if data.state.common.shell.outputs().next().is_none() || data.state.common.should_stop { slog_scope::info!("Shutting down"); - state.common.event_loop_signal.stop(); - state.common.event_loop_signal.wakeup(); + data.state.common.event_loop_signal.stop(); + data.state.common.event_loop_signal.wakeup(); return; } + // trigger routines + data.state.common.shell.refresh(&data.display.handle()); + data.state.common.refresh_focus(&data.display.handle()); + // do we need to trigger another render - if state.common.dirty_flag.swap(false, Ordering::SeqCst) { - for output in state.common.shell.outputs() { - state + if data.state.common.dirty_flag.swap(false, Ordering::SeqCst) { + for output in data.state.common.shell.outputs() { + data.state .backend - .schedule_render(&state.common.event_loop_handle, output) + .schedule_render(&data.state.common.event_loop_handle, output) } } // send out events - let display = state.common.display.clone(); - display.borrow_mut().flush_clients(state); - // trigger routines - state.common.shell.refresh(); - state.common.refresh_focus(); + let _ = data.display.flush_clients(); })?; - let _log = state.destroy(); + let _log = data.state.destroy(); // drop eventloop before logger std::mem::drop(event_loop); Ok(()) } -fn init_wayland_display(event_loop: &mut EventLoop) -> Result<(Display, OsString)> { - let mut display = Display::new(); - let socket_name = display.add_socket_auto()?; +fn init_wayland_display(event_loop: &mut EventLoop) -> Result<(Display, OsString)> { + let mut display = Display::new().unwrap(); + let source = ListeningSocketSource::new_auto(None).unwrap(); + let socket_name = source.socket_name().to_os_string(); slog_scope::info!("Listening on {:?}", socket_name); + + event_loop + .handle() + .insert_source(source, |client_stream, _, data| { + if let Err(err) = data + .display + .handle() + .insert_client(client_stream, Arc::new(data.state.new_client_state())) + { + slog_scope::warn!("Error adding wayland client: {}", err); + }; + }) + .with_context(|| "Failed to init the wayland socket source.")?; event_loop .handle() .insert_source( - Generic::new(display.get_poll_fd(), Interest::READ, Mode::Level), - move |_, _, state: &mut state::State| { - let display = state.common.display.clone(); - let mut display = display.borrow_mut(); - match display.dispatch(std::time::Duration::from_millis(0), state) { + Generic::new(display.backend().poll_fd(), Interest::READ, Mode::Level), + move |_, _, data: &mut state::Data| { + match data.display.dispatch_clients(&mut data.state) { Ok(_) => Ok(PostAction::Continue), Err(e) => { slog_scope::error!("I/O error on the Wayland display: {}", e); - state.common.should_stop = true; + data.state.common.should_stop = true; Err(e) } } diff --git a/src/state.rs b/src/state.rs index 4434bd69..a1bfedaa 100644 --- a/src/state.rs +++ b/src/state.rs @@ -4,38 +4,60 @@ use crate::{ backend::{kms::KmsState, winit::WinitState, x11::X11State}, config::{Config, OutputConfig}, logger::LogState, - shell::{init_shell, Shell}, + shell::Shell, + wayland::protocols::{ + drm::WlDrmState, + output_configuration::OutputConfigurationState, + workspace::WorkspaceClientState, + }, }; use smithay::{ + backend::drm::DrmNode, reexports::{ calloop::{LoopHandle, LoopSignal}, - wayland_server::{protocol::wl_surface::WlSurface, Display}, + wayland_server::{ + backend::{ClientData, ClientId, DisconnectReason}, + Display, DisplayHandle, + }, }, wayland::{ - data_device::{default_action_chooser, init_data_device, DataDeviceEvent}, + compositor::CompositorState, + data_device::DataDeviceState, + dmabuf::DmabufState, output::{ - wlr_configuration::{ - self, init_wlr_output_configuration, ConfigurationManager, ModeConfiguration, - }, - xdg::init_xdg_output_manager, + OutputManagerState, Mode as OutputMode, Output, Scale, }, - seat::Seat, - shell::xdg::ToplevelSurface, - shm::init_shm_global, + seat::{Seat, SeatState}, + shm::ShmState, + viewporter::ViewporterState, }, }; use std::{ cell::RefCell, ffi::OsString, - rc::Rc, sync::{atomic::AtomicBool, Arc}, time::Instant, }; #[cfg(feature = "debug")] use std::{collections::VecDeque, time::Duration}; +pub struct ClientState { + pub workspace_client_state: WorkspaceClientState, + pub drm_node: Option, + pub privileged: bool, +} +impl ClientData for ClientState { + fn initialized(&self, _client_id: ClientId) {} + fn disconnected(&self, _client_id: ClientId, _reason: DisconnectReason) {} +} + +pub struct Data { + pub display: Display, + pub state: State, +} + pub struct State { pub backend: BackendData, pub common: Common, @@ -44,18 +66,16 @@ pub struct State { pub struct Common { pub config: Config, - pub display: Rc>, pub socket: OsString, - pub event_loop_handle: LoopHandle<'static, State>, + pub event_loop_handle: LoopHandle<'static, Data>, pub event_loop_signal: LoopSignal, - pub output_conf: ConfigurationManager, + //pub output_conf: ConfigurationManager, pub shell: Shell, - pub pending_toplevels: Vec, pub dirty_flag: Arc, - pub seats: Vec, - pub last_active_seat: Seat, + pub seats: Vec>, + pub last_active_seat: Seat, pub start_time: Instant, pub should_stop: bool, @@ -63,6 +83,17 @@ pub struct Common { pub log: LogState, #[cfg(feature = "debug")] pub egui: Egui, + + // wayland state + pub compositor_state: CompositorState, + pub data_device_state: DataDeviceState, + pub dmabuf_state: DmabufState, + pub output_state: OutputManagerState, + pub output_configuration_state: OutputConfigurationState, + pub seat_state: SeatState, + pub shm_state: ShmState, + pub wl_drm_state: WlDrmState, + pub viewporter_state: ViewporterState, } #[cfg(feature = "debug")] @@ -118,7 +149,7 @@ impl BackendData { output: &Output, test_only: bool, shell: &mut Shell, - loop_handle: &LoopHandle<'_, State>, + loop_handle: &LoopHandle<'_, Data>, ) -> Result<(), anyhow::Error> { let result = match self { BackendData::Kms(ref mut state) => { @@ -156,7 +187,7 @@ impl BackendData { result } - pub fn schedule_render(&mut self, loop_handle: &LoopHandle<'_, State>, output: &Output) { + pub fn schedule_render(&mut self, loop_handle: &LoopHandle<'_, Data>, output: &Output) { match self { BackendData::Winit(_) => {} // We cannot do this on the winit backend. // Winit has a very strict render-loop and skipping frames breaks atleast the wayland winit-backend. @@ -172,159 +203,27 @@ impl BackendData { } } -struct DnDIcon { - surface: RefCell>, -} - -pub fn get_dnd_icon(seat: &Seat) -> Option { - let userdata = seat.user_data(); - userdata - .get::() - .and_then(|x| x.surface.borrow().clone()) -} - impl State { pub fn new( - mut display: Display, + dh: &DisplayHandle, socket: OsString, - handle: LoopHandle<'static, State>, + handle: LoopHandle<'static, Data>, signal: LoopSignal, log: LogState, ) -> State { let config = Config::load(); - init_shm_global(&mut display, vec![], None); - init_xdg_output_manager(&mut display, None); - let shell = init_shell(&config, &mut display); - let initial_seat = crate::input::add_seat(&mut display, "seat-0".into()); - init_data_device( - &mut display, - |dnd_event| match dnd_event { - DataDeviceEvent::DnDStarted { icon, seat, .. } => { - let user_data = seat.user_data(); - user_data.insert_if_missing(|| DnDIcon { - surface: RefCell::new(None), - }); - *user_data.get::().unwrap().surface.borrow_mut() = icon; - } - DataDeviceEvent::DnDDropped { seat } => { - seat.user_data() - .get::() - .unwrap() - .surface - .borrow_mut() - .take(); - } - _ => {} - }, - default_action_chooser, - None, - ); - let (output_conf, _) = init_wlr_output_configuration( - &mut display, - |_| true, - |conf, test_only, mut ddata| { - let state = ddata.get::().unwrap(); - if conf.iter().all(|(_, conf)| conf.is_none()) { - return false; // we don't allow the user to accidentally disable all their outputs - } + let compositor_state = CompositorState::new::(dh, None); + let data_device_state = DataDeviceState::new::(dh, None); + let dmabuf_state = DmabufState::new(); + let output_state = OutputManagerState::new_with_xdg_output::(dh); + let output_configuration_state = OutputConfigurationState::new(dh, |_| true); + let shm_state = ShmState::new::(dh, vec![], None); + let seat_state = SeatState::::new(); + let viewporter_state = ViewporterState::new::(dh, None); + let wl_drm_state = WlDrmState; - let mut backups = Vec::new(); - for (output, conf) in &conf { - { - let mut current_config = output - .user_data() - .get::>() - .unwrap() - .borrow_mut(); - backups.push((output, current_config.clone())); - - if let Some(conf) = conf { - match conf.mode { - Some(ModeConfiguration::Mode(mode)) => { - current_config.mode = - ((mode.size.w, mode.size.h), Some(mode.refresh as u32)); - } - Some(ModeConfiguration::Custom { size, refresh }) => { - current_config.mode = - ((size.w, size.h), refresh.map(|x| x as u32)); - } - _ => {} - } - if let Some(scale) = conf.scale { - current_config.scale = scale; - } - if let Some(transform) = conf.transform { - current_config.transform = transform; - } - if let Some(position) = conf.position { - current_config.position = position.into(); - } - current_config.enabled = true; - } else { - current_config.enabled = false; - } - } - - if let Err(err) = state.backend.apply_config_for_output( - output, - test_only, - &mut state.common.shell, - &state.common.event_loop_handle, - ) { - slog_scope::warn!( - "Failed to apply config to {}: {}. Resetting", - output.name(), - err - ); - for (output, backup) in backups { - { - let mut current_config = output - .user_data() - .get::>() - .unwrap() - .borrow_mut(); - *current_config = backup; - } - if !test_only { - if let Err(err) = state.backend.apply_config_for_output( - output, - false, - &mut state.common.shell, - &state.common.event_loop_handle, - ) { - slog_scope::error!( - "Failed to reset output config for {}: {}", - output.name(), - err - ); - } - } - } - return false; - } - } - - for output in conf.iter().filter(|(_, c)| c.is_some()).map(|(o, _)| o) { - wlr_configuration::enable_head(output); - } - for output in conf.iter().filter(|(_, c)| c.is_none()).map(|(o, _)| o) { - wlr_configuration::disable_head(output); - } - state - .common - .config - .write_outputs(state.common.output_conf.outputs()); - state.common.event_loop_handle.insert_idle(move |state| { - state - .common - .output_conf - .update(&mut *state.common.display.borrow_mut()); - }); - - true - }, - None, - ); + let shell = Shell::new(&config, dh); + let initial_seat = crate::input::add_seat(dh, "seat-0".into()); #[cfg(not(feature = "debug"))] let dirty_flag = Arc::new(AtomicBool::new(false)); @@ -334,14 +233,11 @@ impl State { State { common: Common { config, - display: Rc::new(RefCell::new(display)), socket, event_loop_handle: handle, event_loop_signal: signal, - output_conf, shell, - pending_toplevels: Vec::new(), dirty_flag, seats: vec![initial_seat.clone()], @@ -364,11 +260,51 @@ impl State { active: false, alpha: 1.0, }, + + compositor_state, + data_device_state, + dmabuf_state, + shm_state, + seat_state, + output_state, + output_configuration_state, + viewporter_state, + wl_drm_state, }, backend: BackendData::Unset, } } + pub fn new_client_state(&self) -> ClientState { + ClientState { + workspace_client_state: WorkspaceClientState::default(), + drm_node: match &self.backend { + BackendData::Kms(kms_state) => Some(kms_state.primary), + _ => None, + }, + privileged: false, + } + } + + pub fn new_client_state_with_node(&self, drm_node: DrmNode) -> ClientState { + ClientState { + workspace_client_state: WorkspaceClientState::default(), + drm_node: Some(drm_node), + privileged: false, + } + } + + pub fn new_privileged_client_state(&self) -> ClientState { + ClientState { + workspace_client_state: WorkspaceClientState::default(), + drm_node: match &self.backend { + BackendData::Kms(kms_state) => Some(kms_state.primary), + _ => None, + }, + privileged: true, + } + } + pub fn destroy(self) -> LogState { self.common.log } diff --git a/src/wayland/drm.rs b/src/wayland/drm.rs deleted file mode 100644 index 2124afcd..00000000 --- a/src/wayland/drm.rs +++ /dev/null @@ -1,115 +0,0 @@ -// SPDX-License-Identifier: GPL-3.0-only - -// Re-export only the actual code, and then only use this re-export -// The `generated` module below is just some boilerplate to properly isolate stuff -// and avoid exposing internal details. -// -// You can use all the types from my_protocol as if they went from `wayland_client::protocol`. -pub use generated::server::wl_drm; - -mod generated { - // The generated code tends to trigger a lot of warnings - // so we isolate it into a very permissive module - #![allow(dead_code, non_camel_case_types, unused_unsafe, unused_variables)] - #![allow(non_upper_case_globals, non_snake_case, unused_imports)] - - pub mod server { - use smithay::reexports::{wayland_commons, wayland_server}; - - // These imports are used by the generated code - pub(crate) use wayland_commons::map::{Object, ObjectMetadata}; - pub(crate) use wayland_commons::smallvec; - pub(crate) use wayland_commons::wire::{Argument, ArgumentType, Message, MessageDesc}; - pub(crate) use wayland_commons::{Interface, MessageGroup}; - pub(crate) use wayland_server::protocol::wl_buffer; - pub(crate) use wayland_server::sys; - pub(crate) use wayland_server::{AnonymousObject, Main, Resource, ResourceMap}; - include!(concat!(env!("OUT_DIR"), "/wl_drm.rs")); - } -} - -use smithay::{ - backend::allocator::{ - dmabuf::{Dmabuf, DmabufFlags}, - Format, Fourcc, Modifier, - }, - reexports::wayland_server::{Client, Display, Filter, Global, Main}, -}; - -use std::{convert::TryFrom, path::PathBuf}; - -pub fn init_wl_drm_global( - display: &mut Display, - device_path: PathBuf, - mut formats: Vec, - client_filter: F, -) -> Global -where - F: FnMut(Client) -> bool + 'static, -{ - formats.dedup_by(|f1, f2| f1.code == f2.code); - let global = Filter::new(move |(drm, version): (Main, u32), _, _| { - drm.quick_assign(move |drm, req, _| match req { - wl_drm::Request::Authenticate { .. } => drm.authenticated(), - wl_drm::Request::CreateBuffer { id, .. } => { - id.as_ref().post_error( - wl_drm::Error::InvalidName.to_raw(), - String::from("Flink handles are unsupported, use PRIME"), - ); - } - wl_drm::Request::CreatePlanarBuffer { id, .. } => { - id.as_ref().post_error( - wl_drm::Error::InvalidName.to_raw(), - String::from("Flink handles are unsupported, use PRIME"), - ); - } - wl_drm::Request::CreatePrimeBuffer { - id, - name, - width, - height, - format, - offset0, - stride0, - .. - } => { - let format = match Fourcc::try_from(format) { - Ok(format) => format, - Err(_) => { - id.as_ref().post_error( - wl_drm::Error::InvalidFormat.to_raw(), - String::from("Format not advertised by wl_drm"), - ); - return; - } - }; - - if width < 1 || height < 1 { - id.as_ref().post_error( - wl_drm::Error::InvalidFormat.to_raw(), - String::from("width or height not positive"), - ); - return; - } - - let mut dma = Dmabuf::builder((width, height), format, DmabufFlags::empty()); - dma.add_plane(name, 0, offset0 as u32, stride0 as u32, Modifier::Invalid); - id.as_ref() - .user_data() - .set_threadsafe(|| dma.build().unwrap()); - id.quick_assign(|_, _, _| {}); - slog_scope::trace!("Created a new validated dma wl_buffer via wl_drm."); - } - }); - drm.device(device_path.to_string_lossy().into_owned()); - if version >= 2 { - drm.capabilities(wl_drm::Capability::Prime.to_raw()); - } - for format in &formats { - if let Some(converted) = wl_drm::Format::from_raw(format.code as u32) { - drm.format(converted.to_raw()); - } - } - }); - display.create_global_with_filter(2, global, client_filter) -} diff --git a/src/wayland/handlers/buffer.rs b/src/wayland/handlers/buffer.rs new file mode 100644 index 00000000..842c5e57 --- /dev/null +++ b/src/wayland/handlers/buffer.rs @@ -0,0 +1,11 @@ +// SPDX-License-Identifier: GPL-3.0-only + +use smithay::{ + reexports::wayland_server::protocol::wl_buffer::WlBuffer, + wayland::buffer::BufferHandler, +}; +use crate::utils::prelude::*; + +impl BufferHandler for State { + fn buffer_destroyed(&mut self, _buffer: &WlBuffer) {} +} diff --git a/src/wayland/handlers/compositor.rs b/src/wayland/handlers/compositor.rs new file mode 100644 index 00000000..5f57fbb4 --- /dev/null +++ b/src/wayland/handlers/compositor.rs @@ -0,0 +1,185 @@ +// SPDX-License-Identifier: GPL-3.0-only + +use std::{ + sync::Mutex, +}; +use smithay::{ + backend::renderer::utils::{ + on_commit_buffer_handler, + }, + desktop::{ + LayerSurface, + PopupKind, + Kind, + WindowSurfaceType, + }, + reexports::wayland_server::{ + DisplayHandle, + protocol::wl_surface::WlSurface, + }, + wayland::{ + compositor::{ + CompositorHandler, + CompositorState, + with_states, + }, + shell::{ + xdg::{ + ToplevelSurface, + XdgToplevelSurfaceRoleAttributes, + XdgPopupSurfaceRoleAttributes, + }, + wlr_layer::LayerSurfaceAttributes, + }, + }, + delegate_compositor, +}; +use crate::{ + state::BackendData, + utils::prelude::*, +}; + +impl State { + fn early_import_surface(&mut self, dh: &DisplayHandle, surface: &WlSurface) { + let mut import_nodes = std::collections::HashSet::new(); + for output in self.common.shell.outputs_for_surface(&surface) { + if let BackendData::Kms(ref mut kms_state) = &mut self.backend { + if let Some(target) = kms_state.target_node_for_output(&output) { + if import_nodes.insert(target) { + kms_state.try_early_import(dh, surface, &output, target, &self.common.shell); + } + } + } + self + .backend + .schedule_render(&self.common.event_loop_handle, &output); + } + } + + fn toplevel_ensure_initial_configure(&mut self, toplevel: &ToplevelSurface) -> bool { + // send the initial configure if relevant + let initial_configure_sent = with_states(toplevel.wl_surface(), |states| { + states + .data_map + .get::>() + .unwrap() + .lock() + .unwrap() + .initial_configure_sent + }); + if !initial_configure_sent { + // TODO: query expected size from shell (without inserting and mapping) + toplevel.with_pending_state(|states| states.size = None); + toplevel.send_configure(); + } + initial_configure_sent + } + + fn xdg_popup_ensure_initial_configure(&mut self, popup: &PopupKind) { + let PopupKind::Xdg(ref popup) = popup; + let initial_configure_sent = with_states(popup.wl_surface(), |states| { + states + .data_map + .get::>() + .unwrap() + .lock() + .unwrap() + .initial_configure_sent + }); + if !initial_configure_sent { + // NOTE: This should never fail as the initial configure is always + // allowed. + popup.send_configure().expect("initial configure failed"); + } + } + + fn layer_surface_ensure_inital_configure(&mut self, surface: &LayerSurface) -> bool { + // send the initial configure if relevant + let initial_configure_sent = with_states(surface.wl_surface(), |states| { + states + .data_map + .get::>() + .unwrap() + .lock() + .unwrap() + .initial_configure_sent + }); + if !initial_configure_sent { + // TODO: we should be able to compute the initial dimensions + surface.layer_surface().send_configure(); + } + initial_configure_sent + } +} + +impl CompositorHandler for State { + fn compositor_state(&mut self) -> &mut CompositorState { + &mut self.common.compositor_state + } + + fn commit(&mut self, dh: &DisplayHandle, surface: &WlSurface) { + // initial configure + if let Some((window, seat)) = self.common.shell.pending_windows.iter().find(|(window, _)| { + window.toplevel().wl_surface() == surface + }).cloned() { + match window.toplevel() { + Kind::Xdg(toplevel) => { + if self.toplevel_ensure_initial_configure(&toplevel) { + let output = active_output(&seat, &self.common); + self.common.shell.map_window(&window, &output); + } else { + return; + } + } + } + } + + if let Some((layer_surface, _, _)) = self.common.shell.pending_layers.iter().find(|(layer_surface, _, _)| { + layer_surface.wl_surface() == surface + }).cloned() { + if self.layer_surface_ensure_inital_configure(&layer_surface) { + self.common.shell.map_layer(&layer_surface, dh); + } else { + return; + } + }; + + if let Some(popup) = self.common.shell.popups.find_popup(surface) { + self.xdg_popup_ensure_initial_configure(&popup); + } + + // If we would re-position the window inside the grab we would get a weird jittery animation. + // We only want to resize once the client has acknoledged & commited the new size, + // so we need to carefully track the state through different handlers. + if let Some((space, window)) = + self.common + .shell + .space_for_surface_mut(surface) + .and_then(|workspace| { + workspace + .space + .window_for_surface(surface, WindowSurfaceType::TOPLEVEL) + .cloned() + .map(|window| (&mut workspace.space, window)) + }) + { + let new_location = crate::shell::layout::floating::ResizeSurfaceGrab::apply_resize_state( + &window, + space.window_location(&window).unwrap(), + window.geometry().size, + ); + if let Some(location) = new_location { + space.map_window(&window, location, true); + } + } + + on_commit_buffer_handler(surface); + self.early_import_surface(dh, surface); + self.common.shell.popups.commit(surface); + for workspace in &self.common.shell.spaces { + workspace.space.commit(surface); + } + } +} + +delegate_compositor!(State); diff --git a/src/wayland/handlers/data_device.rs b/src/wayland/handlers/data_device.rs new file mode 100644 index 00000000..b6a4edfc --- /dev/null +++ b/src/wayland/handlers/data_device.rs @@ -0,0 +1,62 @@ +// SPDX-License-Identifier: GPL-3.0-only + +use std::cell::RefCell; +use smithay::{ + reexports::wayland_server::protocol::{ + wl_data_source::WlDataSource, + wl_surface::WlSurface, + }, + wayland::{ + data_device::{ + DataDeviceState, + DataDeviceHandler, + ClientDndGrabHandler, + ServerDndGrabHandler, + }, + seat::Seat, + }, + delegate_data_device, +}; +use crate::state::State; + +pub struct DnDIcon { + surface: RefCell>, +} + +pub fn get_dnd_icon(seat: &Seat) -> Option { + let userdata = seat.user_data(); + userdata + .get::() + .and_then(|x| x.surface.borrow().clone()) +} + +impl ClientDndGrabHandler for State { + fn started( + &mut self, + _source: Option, + icon: Option, + seat: Seat + ) { + let user_data = seat.user_data(); + user_data.insert_if_missing(|| DnDIcon { + surface: RefCell::new(None), + }); + *user_data.get::().unwrap().surface.borrow_mut() = icon; + } + fn dropped(&mut self, seat: Seat) { + seat.user_data() + .get::() + .unwrap() + .surface + .borrow_mut() + .take(); + } +} +impl ServerDndGrabHandler for State {} +impl DataDeviceHandler for State { + fn data_device_state(&self) -> &DataDeviceState { + &self.common.data_device_state + } +} + +delegate_data_device!(State); diff --git a/src/wayland/handlers/dmabuf.rs b/src/wayland/handlers/dmabuf.rs new file mode 100644 index 00000000..df6e8fce --- /dev/null +++ b/src/wayland/handlers/dmabuf.rs @@ -0,0 +1,38 @@ +// SPDX-License-Identifier: GPL-3.0-only + +use smithay::{ + backend::{ + allocator::dmabuf::Dmabuf, + renderer::ImportDma, + }, + reexports::wayland_server::DisplayHandle, + wayland::dmabuf::{DmabufGlobal, DmabufHandler, DmabufState, ImportError}, + delegate_dmabuf, +}; +use crate::state::{BackendData, State}; + +impl DmabufHandler for State { + fn dmabuf_state(&mut self) -> &mut DmabufState { + &mut self.common.dmabuf_state + } + + fn dmabuf_imported(&mut self, dh: &DisplayHandle, global: &DmabufGlobal, dmabuf: Dmabuf) -> Result<(), ImportError> { + match &mut self.backend { + BackendData::Kms(ref mut state) => state + .dmabuf_imported(dh, global, dmabuf) + .map_err(|_| ImportError::Failed), + BackendData::Winit(ref mut state) => state.backend + .renderer() + .import_dmabuf(&dmabuf, None) + .map(|_| ()) + .map_err(|_| ImportError::Failed), + BackendData::X11(ref mut state) => state.renderer + .import_dmabuf(&dmabuf, None) + .map(|_| ()) + .map_err(|_| ImportError::Failed), + _ => unreachable!("No backend set when importing dmabuf"), + } + } +} + +delegate_dmabuf!(State); diff --git a/src/wayland/handlers/layer_shell.rs b/src/wayland/handlers/layer_shell.rs new file mode 100644 index 00000000..7669aec3 --- /dev/null +++ b/src/wayland/handlers/layer_shell.rs @@ -0,0 +1,49 @@ +// SPDX-License-Identifier: GPL-3.0-only + +use smithay::{ + desktop::LayerSurface, + reexports::wayland_server::{ + DisplayHandle, + protocol::wl_output::WlOutput, + }, + wayland::{ + output::Output, + shell::wlr_layer::{ + WlrLayerShellHandler, + WlrLayerShellState, + LayerSurface as WlrLayerSurface, + Layer, + }, + }, + delegate_layer_shell, +}; +use crate::utils::prelude::*; + +impl WlrLayerShellHandler for State { + fn shell_state(&mut self) -> &mut WlrLayerShellState { + &mut self.common.shell.layer_shell_state + } + + fn new_layer_surface( + &mut self, + _dh: &DisplayHandle, + surface: WlrLayerSurface, + wl_output: Option, + _layer: Layer, + namespace: String + ) { + super::mark_dirty_on_drop(&self.common, surface.wl_surface()); + let seat = self.common.last_active_seat.clone(); + let output = wl_output + .as_ref() + .and_then(Output::from_resource) + .unwrap_or_else(|| active_output(&seat, &self.common)); + self.common.shell.pending_layers.push(( + LayerSurface::new(surface, namespace), + output, + seat, + )); + } +} + +delegate_layer_shell!(State); diff --git a/src/wayland/handlers/mod.rs b/src/wayland/handlers/mod.rs new file mode 100644 index 00000000..0569df3d --- /dev/null +++ b/src/wayland/handlers/mod.rs @@ -0,0 +1,39 @@ +// SPDX-License-Identifier: GPL-3.0-only + +pub mod buffer; +pub mod compositor; +pub mod data_device; +pub mod dmabuf; +pub mod layer_shell; +pub mod output; +pub mod output_configuration; +pub mod seat; +pub mod shm; +pub mod toplevel_info; +pub mod viewporter; +pub mod wl_drm; +pub mod workspace; +pub mod xdg_shell; + +use crate::state::Common; +use smithay::{ + reexports::wayland_server::protocol::wl_surface::WlSurface, + wayland::compositor::{add_destruction_hook, with_states}, +}; + +fn mark_dirty_on_drop(state: &Common, wl_surface: &WlSurface) { + use std::sync::{ + Arc, + atomic::{AtomicBool, Ordering}, + }; + + let dirty = state.dirty_flag.clone(); + struct DirtyFlag(Arc); + + with_states(wl_surface, |data| { + data.data_map.insert_if_missing(|| DirtyFlag(dirty)); + }); + add_destruction_hook(wl_surface, |data| if let Some(DirtyFlag(dirty)) = data.data_map.get::() { + dirty.store(true, Ordering::SeqCst); + }) +} \ No newline at end of file diff --git a/src/wayland/handlers/output.rs b/src/wayland/handlers/output.rs new file mode 100644 index 00000000..f34fa652 --- /dev/null +++ b/src/wayland/handlers/output.rs @@ -0,0 +1,6 @@ +// SPDX-License-Identifier: GPL-3.0-only + +use smithay::delegate_output; +use crate::state::State; + +delegate_output!(State); diff --git a/src/wayland/handlers/output_configuration.rs b/src/wayland/handlers/output_configuration.rs new file mode 100644 index 00000000..ef67f4c4 --- /dev/null +++ b/src/wayland/handlers/output_configuration.rs @@ -0,0 +1,141 @@ +// SPDX-License-Identifier: GPL-3.0-only + +use smithay::wayland::output::Output; + +use crate::{ + config::OutputConfig, + state::State, + wayland::protocols::output_configuration::{ + OutputConfigurationState, + OutputConfigurationHandler, + OutputConfiguration, + ModeConfiguration, + delegate_output_configuration, + }, +}; + +use std::cell::RefCell; + +impl OutputConfigurationHandler for State { + fn output_configuration_state(&mut self) -> &mut OutputConfigurationState { + &mut self.common.output_configuration_state + } + + fn test_configuration(&mut self, conf: Vec<(Output, OutputConfiguration)>) -> bool { + self.output_configuration(true, conf) + } + fn apply_configuration(&mut self, conf: Vec<(Output, OutputConfiguration)>) -> bool { + self.output_configuration(false, conf) + } +} + +impl State { + fn output_configuration(&mut self, test_only: bool, conf: Vec<(Output, OutputConfiguration)>) -> bool { + if conf.iter().all(|(_, conf)| matches!(conf, OutputConfiguration::Disabled)) { + return false; // we don't allow the user to accidentally disable all their outputs + } + + let mut backups = Vec::new(); + for (output, conf) in &conf { + { + let mut current_config = output + .user_data() + .get::>() + .unwrap() + .borrow_mut(); + backups.push((output, current_config.clone())); + + if let OutputConfiguration::Enabled { + mode, + scale, + transform, + position, + } = conf { + match mode { + Some(ModeConfiguration::Mode(mode)) => { + current_config.mode = + ((mode.size.w, mode.size.h), Some(mode.refresh as u32)); + } + Some(ModeConfiguration::Custom { size, refresh }) => { + current_config.mode = + ((size.w, size.h), refresh.map(|x| x as u32)); + } + _ => {} + } + if let Some(scale) = scale { + current_config.scale = *scale; + } + if let Some(transform) = transform { + current_config.transform = *transform; + } + if let Some(position) = position { + current_config.position = (*position).into(); + } + current_config.enabled = true; + } else { + current_config.enabled = false; + } + } + + if let Err(err) = self.backend.apply_config_for_output( + output, + test_only, + &mut self.common.shell, + &self.common.event_loop_handle, + ) { + slog_scope::warn!( + "Failed to apply config to {}: {}. Resetting", + output.name(), + err + ); + for (output, backup) in backups { + { + let mut current_config = output + .user_data() + .get::>() + .unwrap() + .borrow_mut(); + *current_config = backup; + } + if !test_only { + if let Err(err) = self.backend.apply_config_for_output( + output, + false, + &mut self.common.shell, + &self.common.event_loop_handle, + ) { + slog_scope::error!( + "Failed to reset output config for {}: {}", + output.name(), + err + ); + } + } + } + return false; + } + } + + for output in conf.iter().filter(|(_, c)| matches!(c, OutputConfiguration::Enabled { .. })).map(|(o, _)| o) { + self.common.output_configuration_state.enable_head(output); + } + for output in conf.iter().filter(|(_, c)| matches!(c, OutputConfiguration::Disabled)).map(|(o, _)| o) { + self.common.output_configuration_state.disable_head(output); + } + self + .common + .config + .write_outputs(self.common.output_configuration_state.outputs()); + self.common.event_loop_handle.insert_idle(move |data| { + data + .state + .common + .output_configuration_state + .update(); + }); + + true + } +} + +delegate_output_configuration!(State); diff --git a/src/wayland/handlers/seat.rs b/src/wayland/handlers/seat.rs new file mode 100644 index 00000000..076a9aee --- /dev/null +++ b/src/wayland/handlers/seat.rs @@ -0,0 +1,15 @@ +// SPDX-License-Identifier: GPL-3.0-only + +use smithay::{ + wayland::seat::{SeatHandler, SeatState}, + delegate_seat, +}; +use crate::state::State; + +impl SeatHandler for State { + fn seat_state(&mut self) -> &mut SeatState { + &mut self.common.seat_state + } +} + +delegate_seat!(State); diff --git a/src/wayland/handlers/shm.rs b/src/wayland/handlers/shm.rs new file mode 100644 index 00000000..f76cac83 --- /dev/null +++ b/src/wayland/handlers/shm.rs @@ -0,0 +1,15 @@ +// SPDX-License-Identifier: GPL-3.0-only + +use smithay::{ + wayland::shm::{ShmHandler, ShmState}, + delegate_shm, +}; +use crate::state::State; + +impl ShmHandler for State { + fn shm_state(&self) -> &ShmState { + &self.common.shm_state + } +} + +delegate_shm!(State); diff --git a/src/wayland/handlers/toplevel_info.rs b/src/wayland/handlers/toplevel_info.rs new file mode 100644 index 00000000..11d6fa15 --- /dev/null +++ b/src/wayland/handlers/toplevel_info.rs @@ -0,0 +1,21 @@ +// SPDX-License-Identifier: GPL-3.0-only + +use crate::{ + state::State, + wayland::protocols::toplevel_info::{ + ToplevelInfoHandler, + ToplevelInfoState, + delegate_toplevel_info, + }, +}; + +impl ToplevelInfoHandler for State { + fn toplevel_info_state(&self) -> &ToplevelInfoState { + &self.common.shell.toplevel_info_state + } + fn toplevel_info_state_mut(&mut self) -> &mut ToplevelInfoState { + &mut self.common.shell.toplevel_info_state + } +} + +delegate_toplevel_info!(State); diff --git a/src/wayland/handlers/viewporter.rs b/src/wayland/handlers/viewporter.rs new file mode 100644 index 00000000..4cdd6ace --- /dev/null +++ b/src/wayland/handlers/viewporter.rs @@ -0,0 +1,6 @@ +// SPDX-License-Identifier: GPL-3.0-only + +use smithay::delegate_viewporter; +use crate::state::State; + +delegate_viewporter!(State); diff --git a/src/wayland/handlers/wl_drm.rs b/src/wayland/handlers/wl_drm.rs new file mode 100644 index 00000000..9a5adc86 --- /dev/null +++ b/src/wayland/handlers/wl_drm.rs @@ -0,0 +1,8 @@ +// SPDX-License-Identifier: GPL-3.0-only + +use crate::{ + state::State, + wayland::protocols::drm::delegate_wl_drm, +}; + +delegate_wl_drm!(State); diff --git a/src/wayland/handlers/workspace.rs b/src/wayland/handlers/workspace.rs new file mode 100644 index 00000000..2c928f44 --- /dev/null +++ b/src/wayland/handlers/workspace.rs @@ -0,0 +1,48 @@ +// SPDX-License-Identifier: GPL-3.0-only + +use smithay::reexports::wayland_server::DisplayHandle; +use crate::{ + state::ClientState, + wayland::protocols::workspace::{ + Request, + WorkspaceHandler, + WorkspaceState, + WorkspaceClientHandler, + WorkspaceClientState, + delegate_workspace, + }, + utils::prelude::*, +}; + +impl WorkspaceClientHandler for ClientState { + fn workspace_state(&self) -> &WorkspaceClientState { + &self.workspace_client_state + } +} + +impl WorkspaceHandler for State { + type Client = ClientState; + fn workspace_state(&self) -> &WorkspaceState { + &self.common.shell.workspace_state + } + fn workspace_state_mut(&mut self) -> &mut WorkspaceState { + &mut self.common.shell.workspace_state + } + + fn commit_requests(&mut self, dh: &DisplayHandle, requests: Vec) { + for request in requests.into_iter() { + match request { + Request::Activate(handle) => { + if let Some(idx) = self.common.shell.spaces.iter().position(|w| w.handle == handle) { + let seat = &self.common.last_active_seat; + let output = active_output(seat, &self.common); + self.common.shell.activate(dh, seat, &output, idx); + } + }, + _ => {}, + } + } + } +} + +delegate_workspace!(State); diff --git a/src/wayland/handlers/xdg_shell.rs b/src/wayland/handlers/xdg_shell.rs new file mode 100644 index 00000000..9aa63869 --- /dev/null +++ b/src/wayland/handlers/xdg_shell.rs @@ -0,0 +1,298 @@ +// SPDX-License-Identifier: GPL-3.0-only + +use smithay::{ + desktop::{ + Kind, + PopupKind, + PopupUngrabStrategy, + PopupKeyboardGrab, + PopupPointerGrab, + PopupGrab, + Window, + WindowSurfaceType, + }, + reexports::{ + wayland_server::{ + DisplayHandle, + protocol::{ + wl_seat::WlSeat, + wl_surface::WlSurface, + wl_output::WlOutput, + }, + }, + wayland_protocols::xdg::shell::server::xdg_toplevel, + }, + wayland::{ + shell::xdg::{ + XdgShellHandler, + XdgShellState, + ToplevelSurface, + PopupSurface, + PositionerState, + Configure, + }, + seat::{ + Seat, + PointerGrabStartData, + }, + output::Output, + Serial, + }, + delegate_xdg_shell, +}; +use std::cell::Cell; +use crate::utils::prelude::*; + +pub type PopupGrabData = Cell>; + +impl XdgShellHandler for State { + fn xdg_shell_state(&mut self) -> &mut XdgShellState { + &mut self.common.shell.xdg_shell_state + } + + fn new_toplevel(&mut self, _dh: &DisplayHandle, surface: ToplevelSurface) { + super::mark_dirty_on_drop(&self.common, surface.wl_surface()); + + let seat = &self.common.last_active_seat; + let window = Window::new(Kind::Xdg(surface)); + self.common.shell.toplevel_info_state.new_toplevel(&window); + self.common.shell.pending_windows.push((window, seat.clone())); + // We will position the window after the first commit, when we know its size hints + } + + fn new_popup( + &mut self, + _dh: &DisplayHandle, + surface: PopupSurface, + _positioner: PositionerState, + ) { + super::mark_dirty_on_drop(&self.common, surface.wl_surface()); + + self.common + .shell + .popups + .track_popup(PopupKind::from(surface)) + .unwrap(); + } + + fn ack_configure( + &mut self, + _dh: &DisplayHandle, + surface: WlSurface, + configure: Configure + ) { + if let Configure::Toplevel(configure) = configure { + // If we would re-position the window inside the grab we would get a weird jittery animation. + // We only want to resize once the client has acknoledged & commited the new size, + // so we need to carefully track the state through different handlers. + if let Some(window) = self.common + .shell + .space_for_surface(&surface) + .and_then(|workspace| workspace.space.window_for_surface(&surface, WindowSurfaceType::TOPLEVEL)) + { + crate::shell::layout::floating::ResizeSurfaceGrab::ack_configure( + window, configure, + ) + } + } + } + + fn grab( + &mut self, + dh: &DisplayHandle, + surface: PopupSurface, + seat: WlSeat, + serial: Serial, + ) { + let seat = Seat::from_resource(&seat).unwrap(); + let ret = self.common.shell.popups.grab_popup(dh, surface.into(), &seat, serial); + + if let Ok(mut grab) = ret { + if let Some(keyboard) = seat.get_keyboard() { + if keyboard.is_grabbed() + && !(keyboard.has_grab(serial) + || keyboard.has_grab(grab.previous_serial().unwrap_or(serial))) + { + grab.ungrab(dh, PopupUngrabStrategy::All); + return; + } + self.common.set_focus(dh, grab.current_grab().as_ref(), &seat, Some(serial)); + keyboard.set_grab(PopupKeyboardGrab::new(&grab), serial); + } + + if let Some(pointer) = seat.get_pointer() { + if pointer.is_grabbed() + && !(pointer.has_grab(serial) + || pointer.has_grab( + grab.previous_serial().unwrap_or_else(|| grab.serial()), + )) + { + grab.ungrab(dh, PopupUngrabStrategy::All); + return; + } + pointer.set_grab(PopupPointerGrab::new(&grab), serial, 0); + } + + seat.user_data() + .insert_if_missing(|| PopupGrabData::new(None)); + seat.user_data() + .get::() + .unwrap() + .set(Some(grab)); + } + } + + fn reposition_request( + &mut self, + _dh: &DisplayHandle, + surface: PopupSurface, + positioner: PositionerState, + token: u32 + ) { + surface.with_pending_state(|state| { + // TODO: This is a simplification, a proper compositor would + // calculate the geometry of the popup here. + // For now we just use the default implementation here that does not take the + // window position and output constraints into account. + let geometry = positioner.get_geometry(); + state.geometry = geometry; + state.positioner = positioner; + }); + surface.send_repositioned(token); + } + + fn move_request( + &mut self, + _dh: &DisplayHandle, + surface: ToplevelSurface, + seat: WlSeat, + serial: Serial + ) { + let seat = Seat::from_resource(&seat).unwrap(); + if let Some(start_data) = + check_grab_preconditions(&seat, surface.wl_surface(), serial) + { + let workspace = self + .common + .shell + .space_for_surface_mut(surface.wl_surface()) + .unwrap(); + let window = workspace + .space + .window_for_surface(surface.wl_surface(), WindowSurfaceType::TOPLEVEL) + .unwrap() + .clone(); + + workspace.move_request(&window, &seat, serial, start_data); + } + } + + fn resize_request( + &mut self, + _dh: &DisplayHandle, + surface: ToplevelSurface, + seat: WlSeat, + serial: Serial, + edges: xdg_toplevel::ResizeEdge + ) { + let seat = Seat::from_resource(&seat).unwrap(); + if let Some(start_data) = + check_grab_preconditions(&seat, surface.wl_surface(), serial) + { + let workspace = self + .common + .shell + .space_for_surface_mut(surface.wl_surface()) + .unwrap(); + let window = workspace + .space + .window_for_surface(surface.wl_surface(), WindowSurfaceType::TOPLEVEL) + .unwrap() + .clone(); + + workspace.resize_request(&window, &seat, serial, start_data, edges); + } + } + + fn maximize_request(&mut self, _dh: &DisplayHandle, surface: ToplevelSurface) { + let surface = surface.wl_surface(); + let seat = &self.common.last_active_seat; + let output = active_output(seat, &self.common); + + if let Some(workspace) = self.common.shell.space_for_surface_mut(surface) { + let window = + workspace.space.window_for_surface(surface, WindowSurfaceType::TOPLEVEL).unwrap().clone(); + workspace.maximize_request(&window, &output) + } + } + + fn unmaximize_request(&mut self, _dh: &DisplayHandle, surface: ToplevelSurface) { + surface.with_pending_state(|state| { + state.states.unset(xdg_toplevel::State::Maximized); + state.size = None; + }); + surface.send_configure(); + } + + fn fullscreen_request(&mut self, _dh: &DisplayHandle, surface: ToplevelSurface, output: Option) { + let output = output + .as_ref() + .and_then(Output::from_resource) + .unwrap_or_else(|| { + let seat = &self.common.last_active_seat; + active_output(seat, &self.common) + }); + + let surface = surface.wl_surface(); + if let Some(workspace) = self.common.shell.space_for_surface_mut(surface) { + let window = + workspace.space.window_for_surface(surface, WindowSurfaceType::TOPLEVEL).unwrap().clone(); + workspace.fullscreen_request(&window, &output) + } + } + + fn unfullscreen_request(&mut self, _dh: &DisplayHandle, surface: ToplevelSurface) { + let surface = surface.wl_surface(); + if let Some(workspace) = self.common.shell.space_for_surface_mut(surface) { + let window = + workspace.space.window_for_surface(surface, WindowSurfaceType::TOPLEVEL).unwrap().clone(); + workspace.unfullscreen_request(&window) + } + } +} + +fn check_grab_preconditions( + seat: &Seat, + surface: &WlSurface, + serial: Serial, +) -> Option { + use smithay::reexports::wayland_server::Resource; + + // TODO: touch resize. + let pointer = seat.get_pointer().unwrap(); + + // Check that this surface has a click grab. + if !pointer.has_grab(serial) { + return None; + } + + let start_data = pointer.grab_start_data().unwrap(); + + // If the focus was for a different surface, ignore the request. + if start_data.focus.is_none() + || !start_data + .focus + .as_ref() + .unwrap() + .0 + .id() + .same_client_as(&surface.id()) + { + return None; + } + + Some(start_data) +} + + +delegate_xdg_shell!(State); diff --git a/src/wayland/mod.rs b/src/wayland/mod.rs index 68d0310e..9ce58adb 100644 --- a/src/wayland/mod.rs +++ b/src/wayland/mod.rs @@ -1,6 +1,4 @@ // SPDX-License-Identifier: GPL-3.0-only -mod drm; -pub use drm::*; -#[cfg(feature = "experimental")] -pub mod workspace; +pub mod protocols; +pub mod handlers; diff --git a/src/wayland/protocols/drm.rs b/src/wayland/protocols/drm.rs new file mode 100644 index 00000000..be23b4d3 --- /dev/null +++ b/src/wayland/protocols/drm.rs @@ -0,0 +1,269 @@ +// SPDX-License-Identifier: GPL-3.0-only + +// Re-export only the actual code, and then only use this re-export +// The `generated` module below is just some boilerplate to properly isolate stuff +// and avoid exposing internal details. +// +// You can use all the types from my_protocol as if they went from `wayland_client::protocol`. +pub use generated::wl_drm; + +mod generated { + use smithay::reexports::wayland_server::{self, protocol::*}; + + pub mod __interfaces { + use smithay::reexports::wayland_server::protocol::__interfaces::*; + use wayland_backend; + wayland_scanner::generate_interfaces!("resources/protocols/wayland-drm.xml"); + } + use self::__interfaces::*; + + wayland_scanner::generate_server_code!("resources/protocols/wayland-drm.xml"); +} + +use smithay::{ + backend::allocator::{ + dmabuf::{Dmabuf, DmabufFlags}, + Format, Fourcc, Modifier, + }, + reexports::wayland_server::{ + Client, DataInit, DisplayHandle, + DelegateGlobalDispatch, DelegateDispatch, + GlobalDispatch, Dispatch, Resource, + New, backend::GlobalId, + protocol::wl_buffer::WlBuffer, + }, + wayland::{ + buffer::BufferHandler, + dmabuf::{DmabufGlobal, DmabufHandler, ImportError}, + }, +}; + +use std::{ + convert::TryFrom, + path::PathBuf, + sync::Arc, +}; + +pub struct WlDrmState; + +/// Data associated with a drm global. +pub struct DrmGlobalData { + filter: Box Fn(&'a Client) -> bool + Send + Sync>, + formats: Arc>, + device_path: PathBuf, + _logger: slog::Logger, + dmabuf_global: DmabufGlobal, +} + +pub struct DrmInstanceData { + formats: Arc>, + dmabuf_global: DmabufGlobal, +} + +impl DelegateGlobalDispatch for WlDrmState +where + D: GlobalDispatch + + Dispatch + + BufferHandler + + DmabufHandler + + 'static, +{ + fn bind( + _state: &mut D, + _dh: &DisplayHandle, + _client: &Client, + resource: New, + global_data: &DrmGlobalData, + data_init: &mut DataInit<'_, D>, + ) { + let data = DrmInstanceData { + formats: global_data.formats.clone(), + dmabuf_global: global_data.dmabuf_global.clone(), + }; + let drm_instance = data_init.init(resource, data); + + drm_instance.device(global_data.device_path.to_string_lossy().into_owned()); + if drm_instance.version() >= 2 { + drm_instance.capabilities(wl_drm::Capability::Prime as u32); + } + for format in global_data.formats.iter() { + if let Ok(converted) = wl_drm::Format::try_from(*format as u32) { + drm_instance.format(converted as u32); + } + } + } + + fn can_view(client: Client, global_data: &DrmGlobalData) -> bool { + (global_data.filter)(&client) + } +} + +impl DelegateDispatch for WlDrmState +where + D: GlobalDispatch + + Dispatch + + Dispatch + + BufferHandler + + DmabufHandler + + 'static, +{ + fn request( + state: &mut D, + _client: &Client, + drm: &wl_drm::WlDrm, + request: wl_drm::Request, + data: &DrmInstanceData, + dh: &DisplayHandle, + data_init: &mut DataInit<'_, D>, + ) { + match request { + wl_drm::Request::Authenticate { .. } => drm.authenticated(), + wl_drm::Request::CreateBuffer { .. } => { + drm.post_error( + wl_drm::Error::InvalidName, + String::from("Flink handles are unsupported, use PRIME"), + ) + }, + wl_drm::Request::CreatePlanarBuffer { .. } => { + drm.post_error( + wl_drm::Error::InvalidName, + String::from("Flink handles are unsupported, use PRIME"), + ) + }, + wl_drm::Request::CreatePrimeBuffer { + id, + name, + width, + height, + format, + offset0, + stride0, + .. + } => { + let format = match Fourcc::try_from(format) { + Ok(format) => { + if !data.formats.contains(&format) { + drm.post_error( + wl_drm::Error::InvalidFormat, + String::from("Format not advertised by wl_drm"), + ); + return; + } + format + }, + Err(_) => { + drm.post_error( + wl_drm::Error::InvalidFormat, + String::from("Format unknown / not advertised by wl_drm"), + ); + return; + } + }; + + if width < 1 || height < 1 { + drm.post_error( + wl_drm::Error::InvalidFormat, + String::from("width or height not positive"), + ); + return; + } + + let mut dma = Dmabuf::builder((width, height), format, DmabufFlags::empty()); + dma.add_plane(name, 0, offset0 as u32, stride0 as u32, Modifier::Invalid); + match dma.build() { + Some(dmabuf) => match state.dmabuf_imported(dh, &data.dmabuf_global, dmabuf.clone()) { + Ok(_) => { + // import was successful + data_init.init(id, dmabuf); + slog_scope::trace!("Created a new validated dma wl_buffer via wl_drm."); + }, + + Err(ImportError::InvalidFormat) => { + drm.post_error( + wl_drm::Error::InvalidFormat, + "format and plane combination are not valid", + ); + } + + Err(ImportError::Failed) => { + // Buffer import failed. The protocol documentation heavily implies killing the + // client is the right thing to do here. + drm.post_error( + wl_drm::Error::InvalidName, + "buffer import failed", + ); + } + }, + None => { + // Buffer import failed. The protocol documentation heavily implies killing the + // client is the right thing to do here. + drm.post_error( + wl_drm::Error::InvalidName, + "dmabuf global was destroyed on server", + ); + } + } + } + } + } +} + +impl WlDrmState { + pub fn create_global( + &mut self, + display: &DisplayHandle, + device_path: PathBuf, + formats: Vec, + dmabuf_global: &DmabufGlobal, + ) -> GlobalId + where + D: GlobalDispatch + + Dispatch + + BufferHandler + + DmabufHandler + + 'static, + { + self.create_global_with_filter::(display, device_path, formats, dmabuf_global, |_| true) + } + + pub fn create_global_with_filter( + &mut self, + display: &DisplayHandle, + device_path: PathBuf, + formats: Vec, + dmabuf_global: &DmabufGlobal, + client_filter: F, + ) -> GlobalId + where + D: GlobalDispatch + + Dispatch + + BufferHandler + + DmabufHandler + + 'static, + F: for<'a> Fn(&'a Client) -> bool + Send + Sync + 'static, + { + let formats = Arc::new(formats.into_iter().filter(|f| f.modifier == Modifier::Invalid).map(|f| f.code).collect()); + let data = DrmGlobalData { + filter: Box::new(client_filter), + formats, + device_path, + dmabuf_global: dmabuf_global.clone(), + _logger: slog_scope::logger().new(slog::o!("cosmic_module" => "wayland_drm")), + }; + + display.create_global::(2, data) + } +} + +macro_rules! delegate_wl_drm { + ($(@<$( $lt:tt $( : $clt:tt $(+ $dlt:tt )* )? ),+>)? $ty: ty) => { + smithay::reexports::wayland_server::delegate_global_dispatch!($(@< $( $lt $( : $clt $(+ $dlt )* )? ),+ >)? $ty: [ + $crate::wayland::protocols::drm::wl_drm::WlDrm: $crate::wayland::protocols::drm::DrmGlobalData + ] => $crate::wayland::protocols::drm::WlDrmState); + smithay::reexports::wayland_server::delegate_dispatch!($(@< $( $lt $( : $clt $(+ $dlt )* )? ),+ >)? $ty: [ + $crate::wayland::protocols::drm::wl_drm::WlDrm: $crate::wayland::protocols::drm::DrmInstanceData + ] => $crate::wayland::protocols::drm::WlDrmState); + }; +} +pub(crate) use delegate_wl_drm; + \ No newline at end of file diff --git a/src/wayland/protocols/mod.rs b/src/wayland/protocols/mod.rs new file mode 100644 index 00000000..22046f65 --- /dev/null +++ b/src/wayland/protocols/mod.rs @@ -0,0 +1,6 @@ +// SPDX-License-Identifier: GPL-3.0-only + +pub mod drm; +pub mod output_configuration; +pub mod toplevel_info; +pub mod workspace; diff --git a/src/wayland/protocols/output_configuration.rs b/src/wayland/protocols/output_configuration.rs new file mode 100644 index 00000000..0ec7594f --- /dev/null +++ b/src/wayland/protocols/output_configuration.rs @@ -0,0 +1,745 @@ +// SPDX-License-Identifier: GPL-3.0-only + +use std::{ + convert::{TryFrom, TryInto}, + sync::{ + Arc, Mutex, + atomic::{AtomicBool, Ordering}, + }, +}; +use smithay::{ + reexports::{ + wayland_protocols_wlr::output_management::v1::server::{ + zwlr_output_configuration_head_v1::{self, ZwlrOutputConfigurationHeadV1}, + zwlr_output_configuration_v1::{self, ZwlrOutputConfigurationV1}, + zwlr_output_head_v1::{self, ZwlrOutputHeadV1}, + zwlr_output_manager_v1::{self, ZwlrOutputManagerV1}, + zwlr_output_mode_v1::{self, ZwlrOutputModeV1}, + }, + wayland_server::{ + Client, DisplayHandle, + GlobalDispatch, Dispatch, + DelegateGlobalDispatch, DelegateDispatch, + DataInit, New, Resource, + backend::{ClientId, GlobalId, ObjectId}, + protocol::wl_output::WlOutput, + }, + }, + wayland::output::{Output, Mode, OutputData}, + utils::{Logical, Physical, Point, Size, Transform}, +}; + +pub struct OutputConfigurationState { + outputs: Vec, + removed_outputs: Vec, + instances: Vec, + serial_counter: u32, + global: GlobalId, + dh: DisplayHandle, + _dispatch: std::marker::PhantomData, +} + +pub trait OutputConfigurationHandler: Sized { + fn output_configuration_state(&mut self) -> &mut OutputConfigurationState; + + fn test_configuration(&mut self, conf: Vec<(Output, OutputConfiguration)>) -> bool; + fn apply_configuration(&mut self, conf: Vec<(Output, OutputConfiguration)>) -> bool; +} + +pub struct OutputMngrGlobalData { + filter: Box Fn(&'a Client) -> bool + Send + Sync>, +} + +struct OutputMngrInstance { + obj: ZwlrOutputManagerV1, + active: Arc, + heads: Vec, +} + +struct OutputHeadInstance { + output: Output, + head: ZwlrOutputHeadV1, + modes: Vec, +} + +pub struct OutputMngrInstanceData { + active: Arc, +} + +#[derive(Debug, Default)] +pub struct PendingConfigurationInner { + serial: u32, + used: bool, + heads: Vec<(ZwlrOutputHeadV1, Option)>, +} +pub type PendingConfiguration = Mutex; + +#[derive(Debug, Clone)] +pub enum ModeConfiguration { + Mode(M), + Custom { + size: Size, + refresh: Option, + }, +} + +#[derive(Debug, Default, Clone)] +pub struct PendingOutputConfigurationInner { + mode: Option>, + position: Option>, + transform: Option, + scale: Option, +} +pub type PendingOutputConfiguration = Mutex; + +#[derive(Debug, Clone)] +pub enum OutputConfiguration { + Enabled { + mode: Option>, + position: Option>, + transform: Option, + scale: Option, + }, + Disabled, +} + +impl<'a> TryFrom<&'a mut PendingOutputConfigurationInner> for OutputConfiguration { + type Error = zwlr_output_configuration_head_v1::Error; + fn try_from(pending: &'a mut PendingOutputConfigurationInner) -> Result { + let mode = match pending.mode.clone() { + Some(ModeConfiguration::Mode(wlr_mode)) => Some(ModeConfiguration::Mode( + wlr_mode + .data::() + .cloned() + .ok_or_else(|| zwlr_output_configuration_head_v1::Error::InvalidMode)?, + )), + Some(ModeConfiguration::Custom { size, refresh }) => { + Some(ModeConfiguration::Custom { size, refresh }) + } + None => None, + }; + Ok(OutputConfiguration::Enabled { + mode, + position: pending.position, + transform: pending.transform, + scale: pending.scale, + }) + } +} + +struct OutputStateInner { + enabled: bool, + global: Option, +} +type OutputState = Mutex; + +impl DelegateGlobalDispatch for OutputConfigurationState +where + D: GlobalDispatch + + Dispatch + + Dispatch + + Dispatch + + Dispatch + + Dispatch + + OutputConfigurationHandler + + 'static +{ + fn bind( + state: &mut D, + dh: &DisplayHandle, + _client: &Client, + resource: New, + _global_data: &OutputMngrGlobalData, + data_init: &mut DataInit<'_, D>, + ) { + let active = Arc::new(AtomicBool::new(true)); + let data = OutputMngrInstanceData { + active: active.clone(), + }; + let mut instance = OutputMngrInstance { + obj: data_init.init(resource, data), + heads: Vec::new(), + active, + }; + + let mngr_state = state.output_configuration_state(); + for output in &mngr_state.outputs { + send_head_to_client::(dh, &mut instance, output); + } + instance.obj.done(mngr_state.serial_counter); + mngr_state.instances.push(instance); + } + + fn can_view(client: Client, global_data: &OutputMngrGlobalData) -> bool { + (global_data.filter)(&client) + } +} + +impl DelegateDispatch for OutputConfigurationState +where + D: GlobalDispatch + + Dispatch + + Dispatch + + Dispatch + + Dispatch + + Dispatch + + OutputConfigurationHandler + + 'static +{ + fn request( + state: &mut D, + _client: &Client, + _obj: &ZwlrOutputManagerV1, + request: zwlr_output_manager_v1::Request, + data: &OutputMngrInstanceData, + _dh: &DisplayHandle, + data_init: &mut DataInit<'_, D>, + ) { + match request { + zwlr_output_manager_v1::Request::CreateConfiguration { + id, + serial, + } => { + let conf = data_init.init(id, PendingConfiguration::new(PendingConfigurationInner { + serial, + used: false, + heads: Vec::new(), + })); + + let state = state.output_configuration_state(); + if serial != state.serial_counter { + conf.cancelled(); + } + }, + zwlr_output_manager_v1::Request::Stop => { + data.active.store(false, Ordering::SeqCst); + }, + _ => {}, + } + } +} + +impl DelegateDispatch for OutputConfigurationState +where + D: GlobalDispatch + + Dispatch + + Dispatch + + Dispatch + + Dispatch + + Dispatch + + OutputConfigurationHandler + + 'static +{ + fn request( + _state: &mut D, + _client: &Client, + _obj: &ZwlrOutputHeadV1, + request: zwlr_output_head_v1::Request, + _data: &Output, + _dh: &DisplayHandle, + _data_init: &mut DataInit<'_, D>, + ) { + match request { + _ => {} + } + } + + fn destroyed( + state: &mut D, + _client: ClientId, + resource: ObjectId, + _data: &Output, + ) { + for instance in &mut state.output_configuration_state().instances { + instance.heads.retain(|h| h.head.id() != resource); + } + } +} + +impl DelegateDispatch for OutputConfigurationState +where + D: GlobalDispatch + + Dispatch + + Dispatch + + Dispatch + + Dispatch + + Dispatch + + OutputConfigurationHandler + + 'static +{ + fn request( + _state: &mut D, + _client: &Client, + _obj: &ZwlrOutputModeV1, + request: zwlr_output_mode_v1::Request, + _data: &Mode, + _dh: &DisplayHandle, + _data_init: &mut DataInit<'_, D>, + ) { + match request { + _ => {} + } + } +} + +impl DelegateDispatch for OutputConfigurationState +where + D: GlobalDispatch + + Dispatch + + Dispatch + + Dispatch + + Dispatch + + Dispatch + + OutputConfigurationHandler + + 'static +{ + fn request( + state: &mut D, + _client: &Client, + obj: &ZwlrOutputConfigurationV1, + request: zwlr_output_configuration_v1::Request, + data: &PendingConfiguration, + _dh: &DisplayHandle, + data_init: &mut DataInit<'_, D>, + ) { + match request { + zwlr_output_configuration_v1::Request::EnableHead { id, head } => { + let mut pending = data.lock().unwrap(); + if pending.heads.iter().any(|(h, _)| *h == head) { + obj.post_error( + zwlr_output_configuration_v1::Error::AlreadyConfiguredHead, + format!("{:?} was already configured", head), + ); + return; + } + + let conf_head = data_init.init(id, PendingOutputConfiguration::default()); + pending.heads.push((head, Some(conf_head))); + }, + zwlr_output_configuration_v1::Request::DisableHead { head } => { + let mut pending = data.lock().unwrap(); + if pending.heads.iter().any(|(h, _)| *h == head) { + obj.post_error( + zwlr_output_configuration_v1::Error::AlreadyConfiguredHead, + format!("{:?} was already configured", head), + ); + return; + } + pending.heads.push((head, None)); + }, + x @ zwlr_output_configuration_v1::Request::Apply + | x @ zwlr_output_configuration_v1::Request::Test => { + let mut pending = data.lock().unwrap(); + + if pending.used { + return obj.post_error( + zwlr_output_configuration_v1::Error::AlreadyUsed, + "Configuration object was used already".to_string(), + ); + } + pending.used = true; + + let inner = state.output_configuration_state(); + if pending.serial != inner.serial_counter { + obj.cancelled(); + return; + } + + let final_conf = match pending + .heads + .iter_mut() + .map(|(head, conf)| { + let output = match { + inner.instances + .iter() + .find_map(|instance| instance.heads.iter().find(|h| h.head == *head)) + .map(|i| i.output.clone()) + } { + Some(o) => o, + None => { + return Err( + zwlr_output_configuration_head_v1::Error::InvalidMode, + ); + } + }; + + match conf { + Some(head) => (&mut *head + .data::() + .unwrap() + .lock() + .unwrap()) + .try_into() + .map(|c| (output, c)), + None => Ok((output, OutputConfiguration::Disabled)), + } + }) + .collect::, zwlr_output_configuration_head_v1::Error>>() + { + Ok(conf) => conf, + Err(code) => { + return obj.post_error(code, "Incomplete configuration".to_string()); + } + }; + + let result = if matches!(x, zwlr_output_configuration_v1::Request::Test) { + state.test_configuration(final_conf) + } else { + state.apply_configuration(final_conf) + }; + + if result { + obj.succeeded(); + } else { + obj.failed(); + } + }, + zwlr_output_configuration_v1::Request::Destroy => {}, + _ => {}, + } + } +} + +impl DelegateDispatch for OutputConfigurationState +where + D: GlobalDispatch + + Dispatch + + Dispatch + + Dispatch + + Dispatch + + Dispatch + + OutputConfigurationHandler + + 'static +{ + fn request( + _state: &mut D, + _client: &Client, + obj: &ZwlrOutputConfigurationHeadV1, + request: zwlr_output_configuration_head_v1::Request, + data: &PendingOutputConfiguration, + _dh: &DisplayHandle, + _data_init: &mut DataInit<'_, D>, + ) { + match request { + zwlr_output_configuration_head_v1::Request::SetMode { mode } => { + let mut pending = data.lock().unwrap(); + if pending.mode.is_some() { + obj.post_error( + zwlr_output_configuration_head_v1::Error::AlreadySet, + format!("{:?} already had a mode configured", obj), + ); + return; + } + pending.mode = Some(ModeConfiguration::Mode(mode)); + }, + zwlr_output_configuration_head_v1::Request::SetCustomMode { width, height, refresh } => { + let mut pending = data.lock().unwrap(); + if pending.mode.is_some() { + obj.post_error( + zwlr_output_configuration_head_v1::Error::AlreadySet, + format!("{:?} already had a mode configured", obj), + ); + return; + } + pending.mode = Some(ModeConfiguration::Custom { + size: Size::from((width, height)), + refresh: if refresh == 0 { None } else { Some(refresh) }, + }); + }, + zwlr_output_configuration_head_v1::Request::SetPosition { x, y } => { + let mut pending = data.lock().unwrap(); + if pending.position.is_some() { + obj.post_error( + zwlr_output_configuration_head_v1::Error::AlreadySet, + format!("{:?} already had a position configured", obj), + ); + return; + } + pending.position = Some(Point::from((x, y))); + }, + zwlr_output_configuration_head_v1::Request::SetScale { scale } => { + let mut pending = data.lock().unwrap(); + if pending.scale.is_some() { + obj.post_error( + zwlr_output_configuration_head_v1::Error::AlreadySet, + format!("{:?} already had a scale configured", obj), + ); + return; + } + pending.scale = Some(scale); + }, + zwlr_output_configuration_head_v1::Request::SetTransform { transform } => { + let mut pending = data.lock().unwrap(); + if pending.transform.is_some() { + obj.post_error( + zwlr_output_configuration_head_v1::Error::AlreadySet, + format!("{:?} already had a transform configured", obj), + ); + return; + } + pending.transform = Some(match transform.into_result() { + Ok(transform) => transform.into(), + Err(err) => { + obj.post_error( + zwlr_output_configuration_head_v1::Error::InvalidTransform, + format!("Invalid transform: {:?}", err), + ); + return; + }, + }); + }, + _ => {}, + } + } +} + +impl OutputConfigurationState +where + D: GlobalDispatch + + GlobalDispatch + + Dispatch + + Dispatch + + Dispatch + + Dispatch + + Dispatch + + OutputConfigurationHandler + + 'static, +{ + pub fn new( + dh: &DisplayHandle, + client_filter: F, + ) -> OutputConfigurationState + where + F: for<'a> Fn(&'a Client) -> bool + Send + Sync + 'static, + { + let global = dh.create_global::(2, OutputMngrGlobalData { + filter: Box::new(client_filter), + }); + + OutputConfigurationState { + outputs: Vec::new(), + removed_outputs: Vec::new(), + instances: Vec::new(), + serial_counter: 0, + global, + dh: dh.clone(), + _dispatch: std::marker::PhantomData, + } + } + + pub fn global_id(&self) -> GlobalId { + self.global.clone() + } + + pub fn add_heads<'a>(&mut self, outputs: impl Iterator) { + let new_outputs = outputs.filter(|o| !self.outputs.contains(o)).collect::>(); + + for output in new_outputs { + output.user_data().insert_if_missing(|| OutputState::new(OutputStateInner { + enabled: true, + global: None, + })); + self.outputs.push(output.clone()); + } + } + + pub fn remove_heads<'a>(&mut self, outputs: impl Iterator) { + for output in outputs { + if self.outputs.contains(output) { + self.removed_outputs.push(output.clone()); + if let Some(inner) = output.user_data().get::() { + let mut inner = inner.lock().unwrap(); + // if it gets re-added it should start with being enabled and no global + inner.enabled = true; + if let Some(global) = inner.global.take() { + self.dh.remove_global(global); + } + } + } + } + self.outputs.retain(|x| !self.removed_outputs.contains(x)); + } + + pub fn enable_head(&self, output: &Output) { + if let Some(inner) = output.user_data().get::() { + let mut inner = inner.lock().unwrap(); + inner.enabled = true; + } + } + + pub fn disable_head(&self, output: &Output) { + if let Some(inner) = output.user_data().get::() { + let mut inner = inner.lock().unwrap(); + inner.enabled = false; + } + } + + pub fn update(&mut self) { + self.instances.retain(|x| x.active.load(Ordering::SeqCst)); + self.serial_counter += 1; + + for output in std::mem::take(&mut self.removed_outputs).into_iter() { + for instance in &mut self.instances { + instance.heads.retain_mut(|head| if &head.output == &output { + for mode in &head.modes { + mode.finished(); + } + head.head.finished(); + false + } else { + true + }); + } + } + for output in &self.outputs { + { + let state = output.user_data().get::().unwrap(); + let mut inner = state.lock().unwrap(); + if inner.enabled && inner.global.is_none() { + inner.global = Some(output.create_global::(&self.dh)); + } + if !inner.enabled && inner.global.is_some() { + self.dh.remove_global(inner.global.take().unwrap()); + } + } + for manager in self.instances.iter_mut() { + send_head_to_client::(&self.dh, manager, output); + } + } + for manager in self.instances.iter() { + manager.obj.done(self.serial_counter); + } + } + + pub fn outputs(&self) -> impl Iterator { + self.outputs.clone().into_iter() + } +} + +fn send_head_to_client(dh: &DisplayHandle, mngr: &mut OutputMngrInstance, output: &Output) +where + D: GlobalDispatch + + Dispatch + + Dispatch + + Dispatch + + Dispatch + + OutputConfigurationHandler + + 'static +{ + let instance = match mngr.heads.iter_mut().find(|i| i.output == *output) { + Some(i) => i, + None => { + if let Ok(client) = dh.get_client(mngr.obj.id()) { + if let Ok(head) = client.create_resource::(dh, mngr.obj.version(), output.clone()) { + mngr.obj.head(&head); + let data = OutputHeadInstance { + head, + modes: Vec::new(), + output: output.clone(), + }; + mngr.heads.push(data); + mngr.heads.last_mut().unwrap() + } else { + return; + } + } else { + return; + } + } + }; + + instance.head.name(output.name()); + instance.head.description(output.description()); + let physical = output.physical_properties(); + if !(physical.size.w == 0 || physical.size.h == 0) { + instance.head.physical_size(physical.size.w, physical.size.h); + } + + let inner = output + .user_data() + .get::() + .unwrap() + .lock() + .unwrap(); + + let output_modes = output.modes(); + // remove old modes + instance.modes.retain_mut(|m| if !output_modes.contains(m.data::().unwrap()) { + m.finished(); + false + } else { + true + }); + // update other modes + for output_mode in output_modes.into_iter() { + if let Some(mode) = if let Some(wlr_mode) = instance.modes + .iter() + .find(|mode| *mode.data::().unwrap() == output_mode) + { + Some(wlr_mode) + } else if let Ok(client) = dh.get_client(instance.head.id()) { + // create the mode if it does not exist yet + if let Ok(mode) = client.create_resource::(dh, instance.head.version(), output_mode) { + instance.head.mode(&mode); + mode.size(output_mode.size.w, output_mode.size.h); + mode.refresh(output_mode.refresh); + if output.preferred_mode().map(|p| p == output_mode).unwrap_or(false) { + mode.preferred(); + } + instance.modes.push(mode); + instance.modes.last() + } else { + None + } + } else { + None + } { + if inner.enabled && output.current_mode().map(|c| c == output_mode).unwrap_or(false) { + instance.head.current_mode(&*mode); + } + } + } + + instance.head.enabled(if inner.enabled { 1 } else { 0 }); + if inner.enabled { + let point = output.current_location(); + instance.head.position(point.x, point.y); + instance.head.transform(output.current_transform()); + instance + .head + .scale(output.current_scale().fractional_scale()); + } + + if mngr.obj.version() >= zwlr_output_head_v1::EVT_MAKE_SINCE { + if physical.make != "Unknown" { + instance.head.make(physical.make.clone()); + } + if physical.model != "Unknown" { + instance.head.model(physical.model); + } + } +} + +macro_rules! delegate_output_configuration { + ($(@<$( $lt:tt $( : $clt:tt $(+ $dlt:tt )* )? ),+>)? $ty: ty) => { + smithay::reexports::wayland_server::delegate_global_dispatch!($(@< $( $lt $( : $clt $(+ $dlt )* )? ),+ >)? $ty: [ + smithay::reexports::wayland_protocols_wlr::output_management::v1::server::zwlr_output_manager_v1::ZwlrOutputManagerV1: $crate::wayland::protocols::output_configuration::OutputMngrGlobalData + ] => $crate::wayland::protocols::output_configuration::OutputConfigurationState); + smithay::reexports::wayland_server::delegate_dispatch!($(@< $( $lt $( : $clt $(+ $dlt )* )? ),+ >)? $ty: [ + smithay::reexports::wayland_protocols_wlr::output_management::v1::server::zwlr_output_manager_v1::ZwlrOutputManagerV1: $crate::wayland::protocols::output_configuration::OutputMngrInstanceData + ] => $crate::wayland::protocols::output_configuration::OutputConfigurationState); + smithay::reexports::wayland_server::delegate_dispatch!($(@< $( $lt $( : $clt $(+ $dlt )* )? ),+ >)? $ty: [ + smithay::reexports::wayland_protocols_wlr::output_management::v1::server::zwlr_output_head_v1::ZwlrOutputHeadV1: smithay::wayland::output::Output + ] => $crate::wayland::protocols::output_configuration::OutputConfigurationState); + smithay::reexports::wayland_server::delegate_dispatch!($(@< $( $lt $( : $clt $(+ $dlt )* )? ),+ >)? $ty: [ + smithay::reexports::wayland_protocols_wlr::output_management::v1::server::zwlr_output_mode_v1::ZwlrOutputModeV1: smithay::wayland::output::Mode + ] => $crate::wayland::protocols::output_configuration::OutputConfigurationState); + smithay::reexports::wayland_server::delegate_dispatch!($(@< $( $lt $( : $clt $(+ $dlt )* )? ),+ >)? $ty: [ + smithay::reexports::wayland_protocols_wlr::output_management::v1::server::zwlr_output_configuration_v1::ZwlrOutputConfigurationV1: $crate::wayland::protocols::output_configuration::PendingConfiguration + ] => $crate::wayland::protocols::output_configuration::OutputConfigurationState); + smithay::reexports::wayland_server::delegate_dispatch!($(@< $( $lt $( : $clt $(+ $dlt )* )? ),+ >)? $ty: [ + smithay::reexports::wayland_protocols_wlr::output_management::v1::server::zwlr_output_configuration_head_v1::ZwlrOutputConfigurationHeadV1: $crate::wayland::protocols::output_configuration::PendingOutputConfiguration + ] => $crate::wayland::protocols::output_configuration::OutputConfigurationState); + }; +} +pub(crate) use delegate_output_configuration; diff --git a/src/wayland/protocols/toplevel_info.rs b/src/wayland/protocols/toplevel_info.rs new file mode 100644 index 00000000..df657333 --- /dev/null +++ b/src/wayland/protocols/toplevel_info.rs @@ -0,0 +1,381 @@ +// SPDX-License-Identifier: GPL-3.0-only + +use std::sync::Mutex; + +use smithay::{ + desktop::Window, + reexports::{ + wayland_server::{ + Client, DataInit, + DisplayHandle, Resource, New, + GlobalDispatch, Dispatch, + DelegateGlobalDispatch, DelegateDispatch, + backend::{ClientId, GlobalId, ObjectId}, + }, + wayland_protocols::xdg::shell::server::xdg_toplevel, + }, + wayland::{ + compositor::with_states, + output::Output, + shell::xdg::XdgToplevelSurfaceRoleAttributes, + }, + utils::IsAlive, +}; + +use super::workspace::{WorkspaceHandler, WorkspaceHandle, WorkspaceState}; + +use cosmic_protocols::{ + toplevel_info::v1::server::{ + zcosmic_toplevel_info_v1::{self, ZcosmicToplevelInfoV1}, + zcosmic_toplevel_handle_v1::{self, ZcosmicToplevelHandleV1, State as States}, + }, +}; + +pub struct ToplevelInfoState { + dh: DisplayHandle, + toplevels: Vec, + instances: Vec, + global: GlobalId, + _dispatch_data: std::marker::PhantomData, +} + +pub trait ToplevelInfoHandler: WorkspaceHandler + Sized { + fn toplevel_info_state(&self) -> &ToplevelInfoState; + fn toplevel_info_state_mut(&mut self) -> &mut ToplevelInfoState; +} + +pub struct ToplevelInfoGlobalData { + filter: Box Fn(&'a Client) -> bool + Send + Sync>, +} + +#[derive(Default)] +struct ToplevelStateInner { + instances: Vec, + outputs: Vec, + workspaces: Vec, + minimized: bool, +} +type ToplevelState = Mutex; + +#[derive(Default)] +pub struct ToplevelHandleStateInner { + outputs: Vec, + workspaces: Vec, + title: String, + app_id: String, + states: Vec, +} +pub type ToplevelHandleState = Mutex; + +impl DelegateGlobalDispatch for ToplevelInfoState +where + D: GlobalDispatch + + Dispatch + + Dispatch + + ToplevelInfoHandler + + 'static +{ + fn bind( + state: &mut D, + dh: &DisplayHandle, + _client: &Client, + resource: New, + _global_data: &ToplevelInfoGlobalData, + data_init: &mut DataInit<'_, D>, + ) { + let instance = data_init.init(resource, ()); + for window in &state.toplevel_info_state().toplevels { + send_toplevel_to_client::(dh, Some(state.workspace_state()), &instance, window); + } + } + + fn can_view(client: Client, global_data: &ToplevelInfoGlobalData) -> bool { + (global_data.filter)(&client) + } +} + +impl DelegateDispatch for ToplevelInfoState +where + D: GlobalDispatch + + Dispatch + + Dispatch + + ToplevelInfoHandler + + 'static +{ + fn request( + state: &mut D, + _client: &Client, + obj: &ZcosmicToplevelInfoV1, + request: zcosmic_toplevel_info_v1::Request, + _data: &(), + _dh: &DisplayHandle, + _data_init: &mut DataInit<'_, D>, + ) { + match request { + zcosmic_toplevel_info_v1::Request::Stop => { + state.toplevel_info_state_mut().instances.retain(|i| i != obj); + }, + _ => {}, + } + } + + fn destroyed( + state: &mut D, + _client: ClientId, + resource: ObjectId, + _data: &(), + ) { + state.toplevel_info_state_mut().instances.retain(|i| i.id() != resource); + } +} + +impl DelegateDispatch for ToplevelInfoState +where + D: GlobalDispatch + + Dispatch + + Dispatch + + ToplevelInfoHandler + + 'static +{ + fn request( + _state: &mut D, + _client: &Client, + _obj: &ZcosmicToplevelHandleV1, + request: zcosmic_toplevel_handle_v1::Request, + _data: &ToplevelHandleState, + _dh: &DisplayHandle, + _data_init: &mut DataInit<'_, D>, + ) { + match request { + zcosmic_toplevel_handle_v1::Request::Destroy => {}, + _ => {}, + } + } + + fn destroyed( + state: &mut D, + _client: ClientId, + resource: ObjectId, + _data: &ToplevelHandleState, + ) { + for toplevel in &state.toplevel_info_state_mut().toplevels { + if let Some(state) = toplevel.user_data().get::() { + state.lock().unwrap().instances.retain(|i| i.id() != resource); + } + } + } +} + +impl ToplevelInfoState +where + D: GlobalDispatch + + Dispatch + + Dispatch + + ToplevelInfoHandler + + 'static, +{ + pub fn new( + dh: &DisplayHandle, + client_filter: F, + ) -> ToplevelInfoState + where + F: for<'a> Fn(&'a Client) -> bool + Send + Sync + 'static + { + let global = dh.create_global::(1, ToplevelInfoGlobalData { + filter: Box::new(client_filter), + }); + ToplevelInfoState { + dh: dh.clone(), + toplevels: Vec::new(), + instances: Vec::new(), + global, + _dispatch_data: std::marker::PhantomData, + } + } + + pub fn new_toplevel(&mut self, toplevel: &Window) { + toplevel.user_data().insert_if_missing(ToplevelState::default); + for instance in &self.instances { + send_toplevel_to_client::(&self.dh, None, instance, toplevel); + } + self.toplevels.push(toplevel.clone()); + } + + pub fn toplevel_enter_output(&mut self, toplevel: &Window, output: &Output) { + if let Some(state) = toplevel.user_data().get::() { + state.lock().unwrap().outputs.push(output.clone()); + } + } + + pub fn toplevel_leave_output(&mut self, toplevel: &Window, output: &Output) { + if let Some(state) = toplevel.user_data().get::() { + state.lock().unwrap().outputs.retain(|o| o != output); + } + } + + pub fn toplevel_enter_workspace(&mut self, toplevel: &Window, workspace: &WorkspaceHandle) { + if let Some(state) = toplevel.user_data().get::() { + state.lock().unwrap().workspaces.push(workspace.clone()); + } + } + + pub fn toplevel_leave_workspace(&mut self, toplevel: &Window, workspace: &WorkspaceHandle) { + if let Some(state) = toplevel.user_data().get::() { + state.lock().unwrap().workspaces.retain(|w| w != workspace); + } + } + + pub fn refresh(&mut self, workspace_state: Option<&WorkspaceState>) { + self.toplevels.retain(|window| { + if window.alive() { + for instance in &self.instances { + send_toplevel_to_client::(&self.dh, workspace_state, instance, window); + } + true + } else { + let state = window.user_data().get::().unwrap().lock().unwrap(); + for handle in &state.instances { + // don't send events to stopped instances + if self.instances.iter().any(|i| i.id().same_client_as(&handle.id())) { + handle.closed(); + } + } + false + } + }); + } + + pub fn global_id(&self) -> GlobalId { + self.global.clone() + } +} + +fn send_toplevel_to_client(dh: &DisplayHandle, workspace_state: Option<&WorkspaceState>, info: &ZcosmicToplevelInfoV1, window: &Window) +where + D: GlobalDispatch + + Dispatch + + Dispatch + + ToplevelInfoHandler + + 'static, +{ + let mut state = window.user_data().get::().unwrap().lock().unwrap(); + let instance = match state.instances.iter().find(|i| i.id().same_client_as(&info.id())) { + Some(i) => i, + None => { + if let Ok(client) = dh.get_client(info.id()) { + if let Ok(toplevel_handle) = client.create_resource::(dh, info.version(), ToplevelHandleState::default()) { + state.instances.push(toplevel_handle); + state.instances.last().unwrap() + } else { + return; + } + } else { + return; + } + } + }; + + let mut handle_state = instance.data::().unwrap().lock().unwrap(); + let mut changed = false; + with_states(window.toplevel().wl_surface(), |states| { + let attributes = states + .data_map + .get::>() + .unwrap() + .lock() + .unwrap(); + + if handle_state.title != attributes.title.as_deref().unwrap_or(&"") { + handle_state.title = attributes.title.clone().unwrap_or_else(String::new); + instance.title(handle_state.title.clone()); + changed = true; + } + if handle_state.app_id != attributes.app_id.as_deref().unwrap_or(&"") { + handle_state.title = attributes.app_id.clone().unwrap_or_else(String::new); + instance.app_id(handle_state.app_id.clone()); + changed = true; + } + + if (handle_state.states.contains(&States::Maximized) != attributes.current.states.contains(xdg_toplevel::State::Maximized)) + || (handle_state.states.contains(&States::Fullscreen) != attributes.current.states.contains(xdg_toplevel::State::Fullscreen)) + || (handle_state.states.contains(&States::Activated) != attributes.current.states.contains(xdg_toplevel::State::Activated)) + || (handle_state.states.contains(&States::Minimized) != state.minimized) { + let mut states = Vec::new(); + if attributes.current.states.contains(xdg_toplevel::State::Maximized) { + states.push(States::Maximized); + } + if attributes.current.states.contains(xdg_toplevel::State::Fullscreen) { + states.push(States::Fullscreen); + } + if attributes.current.states.contains(xdg_toplevel::State::Activated) { + states.push(States::Activated); + } + if attributes.current.states.contains(xdg_toplevel::State::Maximized) { + states.push(States::Maximized); + } + handle_state.states = states.clone(); + + let states: Vec = { + let ratio = std::mem::size_of::() / std::mem::size_of::(); + let ptr = states.as_mut_ptr() as *mut u8; + let len = states.len() * ratio; + let cap = states.capacity() * ratio; + std::mem::forget(states); + unsafe { Vec::from_raw_parts(ptr, len, cap) } + }; + instance.state(states); + changed = true; + } + }); + + if let Ok(client) = dh.get_client(instance.id()) { + for new_output in state.outputs.iter().filter(|o| !handle_state.outputs.contains(o)) { + new_output.with_client_outputs(dh, &client, |_dh, wl_output| { + instance.output_enter(wl_output); + }); + changed = true; + } + for old_output in handle_state.outputs.iter().filter(|o| !state.outputs.contains(o)) { + old_output.with_client_outputs(dh, &client, |_dh, wl_output| { + instance.output_leave(wl_output); + }); + changed = true; + } + handle_state.outputs = state.outputs.clone(); + } + + if let Some(workspace_state) = workspace_state { + for new_workspace in state.workspaces.iter().filter(|w| !handle_state.workspaces.contains(w)) { + if let Some(handle) = workspace_state.raw_workspace_handle(&new_workspace, &instance.id()) { + instance.workspace_enter(&handle); + changed = true; + } + } + for old_workspace in handle_state.workspaces.iter().filter(|w| !state.workspaces.contains(w)) { + if let Some(handle) = workspace_state.raw_workspace_handle(&old_workspace, &instance.id()) { + instance.workspace_leave(&handle); + changed = true; + } + } + handle_state.workspaces = state.workspaces.clone(); + } + + if changed { + instance.done(); + } +} + +macro_rules! delegate_toplevel_info { + ($(@<$( $lt:tt $( : $clt:tt $(+ $dlt:tt )* )? ),+>)? $ty: ty) => { + smithay::reexports::wayland_server::delegate_global_dispatch!($(@< $( $lt $( : $clt $(+ $dlt )* )? ),+ >)? $ty: [ + cosmic_protocols::toplevel_info::v1::server::zcosmic_toplevel_info_v1::ZcosmicToplevelInfoV1: $crate::wayland::protocols::toplevel_info::ToplevelInfoGlobalData + ] => $crate::wayland::protocols::toplevel_info::ToplevelInfoState); + smithay::reexports::wayland_server::delegate_dispatch!($(@< $( $lt $( : $clt $(+ $dlt )* )? ),+ >)? $ty: [ + cosmic_protocols::toplevel_info::v1::server::zcosmic_toplevel_info_v1::ZcosmicToplevelInfoV1: () + ] => $crate::wayland::protocols::toplevel_info::ToplevelInfoState); + smithay::reexports::wayland_server::delegate_dispatch!($(@< $( $lt $( : $clt $(+ $dlt )* )? ),+ >)? $ty: [ + cosmic_protocols::toplevel_info::v1::server::zcosmic_toplevel_handle_v1::ZcosmicToplevelHandleV1: $crate::wayland::protocols::toplevel_info::ToplevelHandleState + ] => $crate::wayland::protocols::toplevel_info::ToplevelInfoState); + }; +} +pub(crate) use delegate_toplevel_info; diff --git a/src/wayland/protocols/workspace.rs b/src/wayland/protocols/workspace.rs new file mode 100644 index 00000000..d281ed57 --- /dev/null +++ b/src/wayland/protocols/workspace.rs @@ -0,0 +1,807 @@ +// SPDX-License-Identifier: GPL-3.0-only + +use std::sync::Mutex; + +use smithay::{ + reexports::wayland_server::{ + Client, DataInit, + DisplayHandle, Resource, New, + GlobalDispatch, Dispatch, + DelegateGlobalDispatch, DelegateDispatch, + backend::{ClientId, ClientData, GlobalId, ObjectId}, + }, + wayland::output::Output, +}; + +use cosmic_protocols::workspace::v1::server::{ + zcosmic_workspace_manager_v1::{self, ZcosmicWorkspaceManagerV1}, + zcosmic_workspace_group_handle_v1::{self, ZcosmicWorkspaceGroupHandleV1}, + zcosmic_workspace_handle_v1::{self, ZcosmicWorkspaceHandleV1}, +}; + +pub use cosmic_protocols::workspace::v1::server::{ + zcosmic_workspace_group_handle_v1::ZcosmicWorkspaceGroupCapabilitiesV1 as GroupCapabilities, + zcosmic_workspace_handle_v1::ZcosmicWorkspaceCapabilitiesV1 as WorkspaceCapabilities, +}; + +pub struct WorkspaceState +where + D: GlobalDispatch + + Dispatch + + Dispatch + + Dispatch + + WorkspaceHandler + + 'static, + ::Client: ClientData + + WorkspaceClientHandler + + 'static, +{ + dh: DisplayHandle, + global: GlobalId, + instances: Vec, + groups: Vec, + _marker: std::marker::PhantomData, +} +pub struct WorkspaceUpdateGuard<'a, D>(&'a mut WorkspaceState) +where + D: GlobalDispatch + + Dispatch + + Dispatch + + Dispatch + + WorkspaceHandler + + 'static, + ::Client: ClientData + + WorkspaceClientHandler + + 'static +; + +crate::utils::id_gen!(next_group_id, GROUP_ID, GROUP_IDS); +crate::utils::id_gen!(next_workspace_id, WORKSPACE_ID, WORKSPACE_IDS); + +#[derive(Default)] +pub struct WorkspaceGroup { + id: usize, + instances: Vec, + workspaces: Vec, + + outputs: Vec, + capabilities: Vec, +} + +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] +pub struct WorkspaceGroupHandle { + id: usize, +} + +#[derive(Default)] +pub struct WorkspaceGroupDataInner { + outputs: Vec, + capabilities: Vec, +} +pub type WorkspaceGroupData = Mutex; + +#[derive(Default)] +pub struct Workspace { + id: usize, + instances: Vec, + + name: String, + capabilities: Vec, + coordinates: Vec, + states: Vec, +} + +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] +pub struct WorkspaceHandle { + id: usize, +} + +#[derive(Default)] +pub struct WorkspaceDataInner { + name: String, + capabilities: Vec, + coordinates: Vec, + states: Vec, +} +pub type WorkspaceData = Mutex; + +pub trait WorkspaceHandler +where + Self: GlobalDispatch + + Dispatch + + Dispatch + + Dispatch + + Sized + + 'static, +{ + type Client: ClientData + + WorkspaceClientHandler + + 'static; + + fn workspace_state(&self) -> &WorkspaceState; + fn workspace_state_mut(&mut self) -> &mut WorkspaceState; + fn commit_requests(&mut self, dh: &DisplayHandle, requests: Vec); +} + +pub struct WorkspaceGlobalData { + filter: Box Fn(&'a Client) -> bool + Send + Sync>, +} + +#[derive(Debug)] +pub enum Request { + Activate(WorkspaceHandle), + Deactivate(WorkspaceHandle), + Remove(WorkspaceHandle), + Create { + in_group: WorkspaceGroupHandle, + name: String, + }, +} + +#[derive(Debug, Default)] +pub struct WorkspaceClientStateInner { + requests: Vec, +} +pub type WorkspaceClientState = Mutex; + + +pub trait WorkspaceClientHandler { + fn workspace_state(&self) -> &WorkspaceClientState; +} + +impl DelegateGlobalDispatch for WorkspaceState +where + D: GlobalDispatch + + Dispatch + + Dispatch + + Dispatch + + WorkspaceHandler + + 'static, + ::Client: ClientData + + WorkspaceClientHandler + + 'static, +{ + fn bind( + state: &mut D, + dh: &DisplayHandle, + _client: &Client, + resource: New, + _global_data: &WorkspaceGlobalData, + data_init: &mut DataInit<'_, D>, + ) { + let state = state.workspace_state_mut(); + let instance = data_init.init(resource, ()); + for group in &mut state.groups { + send_group_to_client::(dh, &instance, group); + instance.done(); + } + state.instances.push(instance); + } + + fn can_view(client: Client, global_data: &WorkspaceGlobalData) -> bool { + (global_data.filter)(&client) + } +} + +impl DelegateDispatch for WorkspaceState +where + D: GlobalDispatch + + Dispatch + + Dispatch + + Dispatch + + WorkspaceHandler + + 'static, + ::Client: ClientData + + WorkspaceClientHandler + + 'static, +{ + fn request( + state: &mut D, + client: &Client, + obj: &ZcosmicWorkspaceManagerV1, + request: zcosmic_workspace_manager_v1::Request, + _data: &(), + dh: &DisplayHandle, + _data_init: &mut DataInit<'_, D>, + ) { + match request { + zcosmic_workspace_manager_v1::Request::Commit => { + if state.workspace_state().instances.contains(obj) { + let mut client_state = client.get_data::<::Client>().unwrap().workspace_state().lock().unwrap(); + state.commit_requests(dh, std::mem::take(&mut client_state.requests)); + } + }, + zcosmic_workspace_manager_v1::Request::Stop => { + state.workspace_state_mut().instances.retain(|i| i != obj); + // without an instance, the whole send_group_to_client machinery doesn't work + // so there is no way for the whole clients hierachy to get any new events + }, + _ => {}, + } + } + + fn destroyed( + state: &mut D, + _client: ClientId, + resource: ObjectId, + _data: &(), + ) { + state.workspace_state_mut().instances.retain(|i| i.id() != resource); + } +} + +impl DelegateDispatch for WorkspaceState +where + D: GlobalDispatch + + Dispatch + + Dispatch + + Dispatch + + WorkspaceHandler + + 'static, + ::Client: ClientData + + WorkspaceClientHandler + + 'static, +{ + fn request( + state: &mut D, + client: &Client, + obj: &ZcosmicWorkspaceGroupHandleV1, + request: zcosmic_workspace_group_handle_v1::Request, + _data: &WorkspaceGroupData, + _dh: &DisplayHandle, + _data_init: &mut DataInit<'_, D>, + ) { + match request { + zcosmic_workspace_group_handle_v1::Request::CreateWorkspace { workspace } => { + if let Some(id) = state.workspace_state().groups.iter().find(|g| g.instances.contains(obj)).map(|g| g.id) { + let mut state = client.get_data::<::Client>().unwrap().workspace_state().lock().unwrap(); + state.requests.push(Request::Create { + in_group: WorkspaceGroupHandle { id }, + name: workspace, + }); + } + }, + zcosmic_workspace_group_handle_v1::Request::Destroy => { + for group in &mut state.workspace_state_mut().groups { + group.instances.retain(|i| i != obj) + } + }, + _ => {}, + } + } + + fn destroyed( + state: &mut D, + _client: ClientId, + resource: ObjectId, + _data: &WorkspaceGroupData, + ) { + for group in &mut state.workspace_state_mut().groups { + group.instances.retain(|i| i.id() != resource) + } + } +} + +impl DelegateDispatch for WorkspaceState +where + D: GlobalDispatch + + Dispatch + + Dispatch + + Dispatch + + WorkspaceHandler + + 'static, + ::Client: ClientData + + WorkspaceClientHandler + + 'static, +{ + fn request( + state: &mut D, + client: &Client, + obj: &ZcosmicWorkspaceHandleV1, + request: zcosmic_workspace_handle_v1::Request, + _data: &WorkspaceData, + _dh: &DisplayHandle, + _data_init: &mut DataInit<'_, D>, + ) { + match request { + zcosmic_workspace_handle_v1::Request::Activate => { + if let Some(id) = state.workspace_state().groups.iter() + .find_map(|g| g.workspaces.iter().find(|w| w.instances.contains(obj))) + .map(|w| w.id) + { + let mut state = client.get_data::<::Client>().unwrap().workspace_state().lock().unwrap(); + state.requests.push(Request::Activate(WorkspaceHandle { id })); + } + } + zcosmic_workspace_handle_v1::Request::Deactivate => { + if let Some(id) = state.workspace_state().groups.iter() + .find_map(|g| g.workspaces.iter().find(|w| w.instances.contains(obj))) + .map(|w| w.id) + { + let mut state = client.get_data::<::Client>().unwrap().workspace_state().lock().unwrap(); + state.requests.push(Request::Deactivate(WorkspaceHandle { id })); + } + } + zcosmic_workspace_handle_v1::Request::Remove => { + if let Some(id) = state.workspace_state().groups.iter() + .find_map(|g| g.workspaces.iter().find(|w| w.instances.contains(obj))) + .map(|w| w.id) + { + let mut state = client.get_data::<::Client>().unwrap().workspace_state().lock().unwrap(); + state.requests.push(Request::Remove(WorkspaceHandle { id })); + } + } + zcosmic_workspace_handle_v1::Request::Destroy => { + for group in &mut state.workspace_state_mut().groups { + for workspace in &mut group.workspaces { + workspace.instances.retain(|i| i != obj) + } + } + } + _ => {}, + } + } + + fn destroyed( + state: &mut D, + _client: ClientId, + resource: ObjectId, + _data: &WorkspaceData, + ) { + for group in &mut state.workspace_state_mut().groups { + for workspace in &mut group.workspaces { + workspace.instances.retain(|i| i.id() != resource) + } + } + } +} + + +impl WorkspaceState +where + D: GlobalDispatch + + Dispatch + + Dispatch + + Dispatch + + WorkspaceHandler + + 'static, + ::Client: ClientData + + WorkspaceClientHandler + + 'static, +{ + pub fn new( + dh: &DisplayHandle, + client_filter: F, + ) -> WorkspaceState + where + F: for<'a> Fn(&'a Client) -> bool + Send + Sync + 'static, + { + let global = dh.create_global::(1, WorkspaceGlobalData { + filter: Box::new(client_filter), + }); + + WorkspaceState { + dh: dh.clone(), + global, + instances: Vec::new(), + groups: Vec::new(), + _marker: std::marker::PhantomData, + } + } + + pub fn workspace_belongs_to_group(&self, group: &WorkspaceGroupHandle, workspace: &WorkspaceHandle) -> bool { + if let Some(group) = self.groups.iter().find(|g| g.id == group.id) { + group.workspaces.iter().any(|w| w.id == workspace.id) + } else { + false + } + } + + pub fn group_capabilities(&self, group: &WorkspaceGroupHandle) -> Option> { + self.groups.iter().find(|g| g.id == group.id).map(|g| g.capabilities.iter()) + } + + pub fn group_outputs(&self, group: &WorkspaceGroupHandle) -> Option> { + self.groups.iter().find(|g| g.id == group.id).map(|g| g.outputs.iter()) + } + + pub fn workspace_capabilities(&self, workspace: &WorkspaceHandle) -> Option> { + self.groups.iter().find_map(|g| g.workspaces.iter().find(|w| w.id == workspace.id).map(|w| w.capabilities.iter())) + } + + pub fn workspace_name(&self, workspace: &WorkspaceHandle) -> Option<&str> { + self.groups.iter().find_map(|g| g.workspaces.iter().find(|w| w.id == workspace.id).map(|w| &*w.name)) + } + + pub fn workspace_coordinates(&self, workspace: &WorkspaceHandle) -> Option<&[u32]> { + self.groups.iter().find_map(|g| g.workspaces.iter().find(|w| w.id == workspace.id).map(|w| &*w.coordinates)) + } + + pub fn workspace_states(&self, workspace: &WorkspaceHandle) -> Option> { + self.groups.iter().find_map(|g| g.workspaces.iter().find(|w| w.id == workspace.id).map(|w| w.states.iter())) + } + + pub fn raw_group_handle(&self, group: &WorkspaceGroupHandle, client: &ObjectId) -> Option { + self.groups.iter() + .find(|g| g.id == group.id) + .and_then(|g| g.instances.iter().find(|i| i.id().same_client_as(client))) + .cloned() + } + pub fn raw_workspace_handle(&self, workspace: &WorkspaceHandle, client: &ObjectId) -> Option { + self.groups.iter() + .find_map(|g| g.workspaces.iter().find(|w| w.id == workspace.id)) + .and_then(|w| w.instances.iter().find(|i| i.id().same_client_as(client))) + .cloned() + } + + pub fn update<'a>(&'a mut self) -> WorkspaceUpdateGuard<'a, D> { + WorkspaceUpdateGuard(self) + } + + fn done(&mut self) { + let mut changed = false; + for instance in &self.instances { + for mut group in &mut self.groups { + if send_group_to_client::(&self.dh, instance, &mut group) { + changed = true; + } + } + } + if changed { + for instance in &self.instances { + instance.done(); + } + } + } + + pub fn global_id(&self) -> GlobalId { + self.global.clone() + } +} + +impl<'a, D> WorkspaceUpdateGuard<'a, D> +where + D: GlobalDispatch + + Dispatch + + Dispatch + + Dispatch + + WorkspaceHandler + + 'static, + ::Client: ClientData + + WorkspaceClientHandler + + 'static, +{ + pub fn create_workspace_group(&mut self) -> WorkspaceGroupHandle { + let id = next_group_id(); + let group = WorkspaceGroup { + id, + ..Default::default() + }; + self.0.groups.push(group); + WorkspaceGroupHandle { + id + } + } + + pub fn create_workspace(&mut self, group: &WorkspaceGroupHandle) -> Option { + if let Some(group) = self.0.groups.iter_mut().find(|g| g.id == group.id) { + let id = next_workspace_id(); + let workspace = Workspace { + id, + ..Default::default() + }; + group.workspaces.push(workspace); + Some(WorkspaceHandle { + id + }) + } else { + None + } + } + + pub fn remove_workspace_group(&mut self, group: WorkspaceGroupHandle) { + // "The compositor must remove all workspaces belonging to a workspace group before removing the workspace group." + for workspace in self.0.groups.iter() + .filter(|g| g.id == group.id) + .flat_map(|g| g.workspaces.iter().map(|w| WorkspaceHandle { id: w.id })) + .collect::>().into_iter() + { + self.remove_workspace(workspace); + } + + if let Some(group) = self.0.groups.iter().find(|g| g.id == group.id) { + for instance in &group.instances { + instance.remove() + } + } + self.0.groups.retain(|g| g.id != group.id); + GROUP_IDS.lock().unwrap().remove(&group.id); + } + + pub fn remove_workspace(&mut self, workspace: WorkspaceHandle) { + for group in &mut self.0.groups { + if let Some(workspace) = group.workspaces.iter().find(|w| w.id == workspace.id) { + for instance in &workspace.instances { + instance.remove(); + } + } + group.workspaces.retain(|w| w.id != workspace.id); + } + WORKSPACE_IDS.lock().unwrap().remove(&workspace.id); + } + + pub fn workspace_belongs_to_group(&self, group: &WorkspaceGroupHandle, workspace: &WorkspaceHandle) -> bool { + self.0.workspace_belongs_to_group(group, workspace) + } + + pub fn group_capabilities(&mut self, group: &WorkspaceGroupHandle) -> Option> { + self.0.group_capabilities(group) + } + + pub fn set_group_capabilities(&mut self, group: &WorkspaceGroupHandle, capabilities: impl Iterator) { + if let Some(group) = self.0.groups.iter_mut().find(|g| g.id == group.id) { + group.capabilities = capabilities.collect(); + } + } + + pub fn group_outputs(&self, group: &WorkspaceGroupHandle) -> Option> { + self.0.group_outputs(group) + } + + pub fn add_group_output(&mut self, group: &WorkspaceGroupHandle, output: &Output) { + if let Some(group) = self.0.groups.iter_mut().find(|g| g.id == group.id) { + group.outputs.push(output.clone()) + } + } + + pub fn remove_group_output(&mut self, group: &WorkspaceGroupHandle, output: &Output) { + if let Some(group) = self.0.groups.iter_mut().find(|g| g.id == group.id) { + group.outputs.retain(|o| o != output) + } + } + + pub fn workspace_capabilities(&self, workspace: &WorkspaceHandle) -> Option> { + self.0.workspace_capabilities(workspace) + } + + pub fn set_workspace_capabilities(&mut self, workspace: &WorkspaceHandle, capabilities: impl Iterator) { + if let Some(workspace) = self.0.groups.iter_mut() + .find_map(|g| g.workspaces.iter_mut().find(|w| w.id == workspace.id)) + { + workspace.capabilities = capabilities.collect(); + } + } + + pub fn workspace_name(&self, workspace: &WorkspaceHandle) -> Option<&str> { + self.0.workspace_name(workspace) + } + + pub fn set_workspace_name(&mut self, workspace: &WorkspaceHandle, name: impl Into) { + if let Some(workspace) = self.0.groups.iter_mut() + .find_map(|g| g.workspaces.iter_mut().find(|w| w.id == workspace.id)) + { + workspace.name = name.into(); + } + } + + pub fn workspace_coordinates(&self, workspace: &WorkspaceHandle) -> Option<&[u32]> { + self.0.workspace_coordinates(workspace) + } + + pub fn set_workspace_coordinates(&mut self, workspace: &WorkspaceHandle, coords: [Option; 3]) { + if let Some(workspace) = self.0.groups.iter_mut() + .find_map(|g| g.workspaces.iter_mut().find(|w| w.id == workspace.id)) + { + workspace.coordinates = coords.iter().flat_map(std::convert::identity).copied().collect(); + } + } + + pub fn workspace_states(&self, workspace: &WorkspaceHandle) -> Option> { + self.0.workspace_states(workspace) + } + + pub fn add_workspace_state(&mut self, workspace: &WorkspaceHandle, state: zcosmic_workspace_handle_v1::State) { + if let Some(workspace) = self.0.groups.iter_mut() + .find_map(|g| g.workspaces.iter_mut().find(|w| w.id == workspace.id)) + { + workspace.states.push(state); + } + } + + pub fn remove_workspace_state(&mut self, workspace: &WorkspaceHandle, state: zcosmic_workspace_handle_v1::State) { + if let Some(workspace) = self.0.groups.iter_mut() + .find_map(|g| g.workspaces.iter_mut().find(|w| w.id == workspace.id)) + { + workspace.states.retain(|s| *s != state); + } + } +} + +impl<'a, D> Drop for WorkspaceUpdateGuard<'a, D> +where + D: GlobalDispatch + + Dispatch + + Dispatch + + Dispatch + + WorkspaceHandler + + 'static, + ::Client: ClientData + + WorkspaceClientHandler + + 'static, +{ + fn drop(&mut self) { + self.0.done(); + } +} + +fn send_group_to_client(dh: &DisplayHandle, mngr: &ZcosmicWorkspaceManagerV1, group: &mut WorkspaceGroup) -> bool +where + D: GlobalDispatch + + Dispatch + + Dispatch + + Dispatch + + WorkspaceHandler + + 'static, + ::Client: ClientData + + WorkspaceClientHandler + + 'static, +{ + let instance = match group.instances.iter_mut().find(|i| i.id().same_client_as(&mngr.id())) { + Some(i) => i, + None => { + if let Ok(client) = dh.get_client(mngr.id()) { + if let Ok(handle) = client.create_resource::(dh, mngr.version(), WorkspaceGroupData::default()) { + mngr.workspace_group(&handle); + group.instances.push(handle); + group.instances.last_mut().unwrap() + } else { + return false; + } + } else { + return false; + } + } + }; + + let mut handle_state = instance.data::().unwrap().lock().unwrap(); + let mut changed = false; + if let Ok(client) = dh.get_client(instance.id()) { + for new_output in group.outputs.iter().filter(|o| !handle_state.outputs.contains(o)) { + new_output.with_client_outputs(dh, &client, |_dh, wl_output| { + instance.output_enter(wl_output); + }); + changed = true; + } + for old_output in handle_state.outputs.iter().filter(|o| group.outputs.contains(o)) { + old_output.with_client_outputs(dh, &client, |_dh, wl_output| { + instance.output_leave(wl_output); + }); + changed = true; + } + handle_state.outputs = group.outputs.clone(); + } + + if handle_state.capabilities != group.capabilities { + let caps: Vec = { + let mut caps = group.capabilities.clone(); + let ratio = std::mem::size_of::() / std::mem::size_of::(); + let ptr = caps.as_mut_ptr() as *mut u8; + let len = caps.len() * ratio; + let cap = caps.capacity() * ratio; + std::mem::forget(caps); + unsafe { Vec::from_raw_parts(ptr, len, cap) } + }; + instance.capabilities(caps); + handle_state.capabilities = group.capabilities.clone(); + changed = true; + } + + for workspace in &mut group.workspaces { + if send_workspace_to_client::(dh, instance, workspace) { + changed = true; + } + } + + changed +} + +fn send_workspace_to_client(dh: &DisplayHandle, group: &ZcosmicWorkspaceGroupHandleV1, workspace: &mut Workspace) -> bool +where + D: GlobalDispatch + + Dispatch + + Dispatch + + Dispatch + + WorkspaceHandler + + 'static, + ::Client: ClientData + + WorkspaceClientHandler + + 'static, +{ + let instance = match workspace.instances.iter_mut().find(|i| i.id().same_client_as(&group.id())) { + Some(i) => i, + None => { + if let Ok(client) = dh.get_client(group.id()) { + if let Ok(handle) = client.create_resource::(dh, group.version(), WorkspaceData::default()) { + group.workspace(&handle); + workspace.instances.push(handle); + workspace.instances.last_mut().unwrap() + } else { + return false; + } + } else { + return false; + } + } + }; + + let mut handle_state = instance.data::().unwrap().lock().unwrap(); + let mut changed = false; + + if handle_state.name != workspace.name { + instance.name(workspace.name.clone()); + handle_state.name = workspace.name.clone(); + changed = true; + } + if handle_state.coordinates != workspace.coordinates { + let coords: Vec = { + let mut coords = workspace.coordinates.clone(); + let ratio = std::mem::size_of::() / std::mem::size_of::(); + let ptr = coords.as_mut_ptr() as *mut u8; + let len = coords.len() * ratio; + let cap = coords.capacity() * ratio; + std::mem::forget(coords); + unsafe { Vec::from_raw_parts(ptr, len, cap) } + }; + instance.coordinates(coords); + handle_state.coordinates = workspace.coordinates.clone(); + changed = true; + } + if handle_state.capabilities != workspace.capabilities { + let caps: Vec = { + let mut caps = workspace.capabilities.clone(); + let ratio = std::mem::size_of::() / std::mem::size_of::(); + let ptr = caps.as_mut_ptr() as *mut u8; + let len = caps.len() * ratio; + let cap = caps.capacity() * ratio; + std::mem::forget(caps); + unsafe { Vec::from_raw_parts(ptr, len, cap) } + }; + instance.capabilities(caps); + handle_state.capabilities = workspace.capabilities.clone(); + changed = true; + } + if handle_state.states != workspace.states { + let states: Vec = { + let mut states = workspace.states.clone(); + let ratio = std::mem::size_of::() / std::mem::size_of::(); + let ptr = states.as_mut_ptr() as *mut u8; + let len = states.len() * ratio; + let cap = states.capacity() * ratio; + std::mem::forget(states); + unsafe { Vec::from_raw_parts(ptr, len, cap) } + }; + instance.state(states); + handle_state.states = workspace.states.clone(); + changed = true; + } + + changed +} + +macro_rules! delegate_workspace { + ($(@<$( $lt:tt $( : $clt:tt $(+ $dlt:tt )* )? ),+>)? $ty: ty) => { + smithay::reexports::wayland_server::delegate_global_dispatch!($(@< $( $lt $( : $clt $(+ $dlt )* )? ),+ >)? $ty: [ + cosmic_protocols::workspace::v1::server::zcosmic_workspace_manager_v1::ZcosmicWorkspaceManagerV1: $crate::wayland::protocols::workspace::WorkspaceGlobalData + ] => $crate::wayland::protocols::workspace::WorkspaceState); + smithay::reexports::wayland_server::delegate_dispatch!($(@< $( $lt $( : $clt $(+ $dlt )* )? ),+ >)? $ty: [ + cosmic_protocols::workspace::v1::server::zcosmic_workspace_manager_v1::ZcosmicWorkspaceManagerV1: () + ] => $crate::wayland::protocols::workspace::WorkspaceState); + smithay::reexports::wayland_server::delegate_dispatch!($(@< $( $lt $( : $clt $(+ $dlt )* )? ),+ >)? $ty: [ + cosmic_protocols::workspace::v1::server::zcosmic_workspace_group_handle_v1::ZcosmicWorkspaceGroupHandleV1: $crate::wayland::protocols::workspace::WorkspaceGroupData + ] => $crate::wayland::protocols::workspace::WorkspaceState); + smithay::reexports::wayland_server::delegate_dispatch!($(@< $( $lt $( : $clt $(+ $dlt )* )? ),+ >)? $ty: [ + cosmic_protocols::workspace::v1::server::zcosmic_workspace_handle_v1::ZcosmicWorkspaceHandleV1: $crate::wayland::protocols::workspace::WorkspaceData + ] => $crate::wayland::protocols::workspace::WorkspaceState); + }; +} +pub(crate) use delegate_workspace; diff --git a/src/wayland/workspace.rs b/src/wayland/workspace.rs deleted file mode 100644 index 69a88fe1..00000000 --- a/src/wayland/workspace.rs +++ /dev/null @@ -1,594 +0,0 @@ -// SPDX-License-Identifier: GPL-3.0-only - -// Re-export only the actual code, and then only use this re-export -// The `generated` module below is just some boilerplate to properly isolate stuff -// and avoid exposing internal details. -// -// You can use all the types from my_protocol as if they went from `wayland_client::protocol`. -pub use generated::server::{ - zext_workspace_group_handle_v1, zext_workspace_handle_v1, zext_workspace_manager_v1, -}; - -mod generated { - // The generated code tends to trigger a lot of warnings - // so we isolate it into a very permissive module - #![allow(dead_code, non_camel_case_types, unused_unsafe, unused_variables)] - #![allow(non_upper_case_globals, non_snake_case, unused_imports)] - - pub mod server { - use smithay::reexports::{wayland_commons, wayland_server}; - - // These imports are used by the generated code - pub(crate) use wayland_commons::map::{Object, ObjectMetadata}; - pub(crate) use wayland_commons::smallvec; - pub(crate) use wayland_commons::wire::{Argument, ArgumentType, Message, MessageDesc}; - pub(crate) use wayland_commons::{Interface, MessageGroup}; - pub(crate) use wayland_server::protocol::wl_output; - pub(crate) use wayland_server::sys; - pub(crate) use wayland_server::{AnonymousObject, Main, Resource, ResourceMap}; - include!(concat!(env!("OUT_DIR"), "/ext_workspace.rs")); - } -} - -pub use self::generated::server::zext_workspace_handle_v1::State; -use self::generated::server::{ - zext_workspace_group_handle_v1::ZextWorkspaceGroupHandleV1, - zext_workspace_handle_v1::ZextWorkspaceHandleV1, - zext_workspace_manager_v1::ZextWorkspaceManagerV1, -}; -use smithay::{ - reexports::wayland_server::{Client, DispatchData, Display, Filter, Global, Main}, - wayland::output::Output, -}; -use std::{ - cell::RefCell, - collections::HashSet, - fmt, - sync::{Arc, Mutex, Weak}, -}; - -#[derive(Debug, Clone)] -pub struct WorkspaceManager { - inner: Arc>, -} - -struct WorkspaceManagerInner { - instances: Vec>, - groups: Vec>>, - commit: Box< - dyn FnMut(&WorkspaceGroup, Vec<(&Workspace, Vec)>, DispatchData) - + 'static, - >, -} - -impl fmt::Debug for WorkspaceManagerInner { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - f.debug_struct("WorkspaceManagerInner") - .field("instances", &self.instances) - .field("groups", &self.groups) - .finish_non_exhaustive() - } -} - -pub fn init_ext_workspace( - display: &mut Display, - commit: C, - client_filter: F, -) -> (WorkspaceManager, Global) -where - C: FnMut(&WorkspaceGroup, Vec<(&Workspace, Vec)>, DispatchData) + 'static, - F: FnMut(Client) -> bool + 'static, -{ - let inner = Arc::new(Mutex::new(WorkspaceManagerInner { - instances: Vec::new(), - groups: Vec::new(), - commit: Box::new(commit), - })); - - let inner_clone = inner.clone(); - let filter = Filter::new( - move |(main, _version): (Main, u32), _, _| { - let inner = inner_clone.clone(); - main.quick_assign(move |main, request, mut ddata| match request { - zext_workspace_manager_v1::Request::Commit => { - if let Some(client) = main.as_ref().client() { - let mut inner_guard = inner.lock().unwrap(); - let inner = &mut *inner_guard; - for group in inner.groups.iter() { - let group_inner = group.lock().unwrap(); - let mut changes = group_inner - .workspaces - .iter() - .flat_map(|x| x.upgrade()) - .flat_map(|w| { - let workspace_inner = w.lock().unwrap(); - let operations = workspace_inner - .instances - .iter() - .find(|w_instance| { - w_instance - .as_ref() - .client() - .map(|c| c == client) - .unwrap_or(false) - }) - .and_then(|w_instance| { - w_instance - .as_ref() - .user_data() - .get::() - }) - .and_then(|user_data| { - let operations = - std::mem::take(&mut *user_data.borrow_mut()); - if !operations.is_empty() { - Some(operations) - } else { - None - } - }); - if let Some(ops) = operations { - std::mem::drop(workspace_inner); - Some((Workspace { inner: w }, ops)) - } else { - None - } - }) - .collect::)>>(); - - std::mem::drop(group_inner); - let group = WorkspaceGroup { - inner: group.clone(), - }; - let borrowed_changes = changes - .iter_mut() - .map(|(w, p)| (&*w, std::mem::take(p))) - .collect(); - (inner.commit)(&group, borrowed_changes, ddata.reborrow()); - } - } - } - zext_workspace_manager_v1::Request::Stop => { - let mut inner = inner.lock().unwrap(); - inner.instances.retain(|m| **m != *main); - } - }); - - let mut inner_guard = inner_clone.lock().unwrap(); - if let Some(client) = main.as_ref().client() { - for group in inner_guard.groups.iter() { - if let Some(new_instance) = - WorkspaceGroup::create_instance(&client, group.clone()) - { - main.workspace_group(&*new_instance); - - let mut group_inner_guard = group.lock().unwrap(); - for output in group_inner_guard.outputs.iter() { - output.with_client_outputs(client.clone(), |output| { - new_instance.output_enter(output) - }); - } - for workspace in group_inner_guard.workspaces.iter() { - if let Some(workspace) = workspace.upgrade() { - let workspace_clone = workspace.clone(); - if let Some(new_ws_instance) = - Workspace::create_instance(&client, workspace_clone) - { - new_instance.workspace(&*new_ws_instance); - let mut inner = workspace.lock().unwrap(); - inner.instances.push(new_ws_instance); - inner.send(&client); - } - } - } - group_inner_guard.instances.push(new_instance); - } - } - main.done(); - } - - inner_guard.instances.push(main); - }, - ); - let global = display.create_global_with_filter(1, filter, client_filter); - - (WorkspaceManager { inner }, global) -} - -impl WorkspaceManager { - pub fn new_group( - &self, - create_new_workspace: F, - outputs: impl Iterator, - ) -> WorkspaceGroup - where - F: Fn(&WorkspaceGroup, String, DispatchData) + 'static, - { - let mut inner = self.inner.lock().unwrap(); - let create_new_workspace = Arc::new(Box::new(create_new_workspace) - as Box); - let group_inner = Arc::new(Mutex::new(WorkspaceGroupInner { - instances: Vec::new(), - outputs: outputs.collect(), - workspaces: Vec::new(), - create_new_workspace, - })); - - for instance in inner.instances.iter() { - if let Some(client) = instance.as_ref().client() { - if let Some(group) = WorkspaceGroup::create_instance(&client, group_inner.clone()) { - instance.workspace_group(&*group); - let mut group_inner_guard = group_inner.lock().unwrap(); - for output in group_inner_guard.outputs.iter() { - output.with_client_outputs(client.clone(), |output| { - group.output_enter(output) - }); - } - group_inner_guard.instances.push(group); - } - } - instance.done(); - } - - inner.groups.push(group_inner.clone()); - WorkspaceGroup { inner: group_inner } - } - - pub fn update_outputs(&self, mut update: F) - where - F: FnMut(&WorkspaceGroup, &mut HashSet), - { - let inner = self.inner.lock().unwrap(); - for group in inner.groups.iter() { - let mut group_inner = group.lock().unwrap(); - let group = WorkspaceGroup { - inner: group.clone(), - }; - - let previous_outputs = group_inner.outputs.clone(); - update(&group, &mut group_inner.outputs); - - for output in previous_outputs.difference(&group_inner.outputs) { - for instance in group_inner.instances.iter() { - if let Some(client) = instance.as_ref().client() { - output.with_client_outputs(client.clone(), |output| { - instance.output_leave(output) - }); - } - } - } - for output in group_inner.outputs.difference(&previous_outputs) { - for instance in group_inner.instances.iter() { - if let Some(client) = instance.as_ref().client() { - output.with_client_outputs(client.clone(), |output| { - instance.output_enter(output) - }); - } - } - } - } - for instance in inner.instances.iter() { - instance.done(); - } - } - - pub fn remove_group(&self, group: &WorkspaceGroup) { - let mut inner = self.inner.lock().unwrap(); - // grr I want drain_filter - if let Some(pos) = inner - .groups - .iter() - .position(|x| Arc::ptr_eq(x, &group.inner)) - { - let group = inner.groups.remove(pos); - let inner = group.lock().unwrap(); - for instance in inner.instances.iter() { - instance.remove(); - } - } - } - - pub fn done(&self) { - for instance in self.inner.lock().unwrap().instances.iter() { - instance.done(); - } - } -} - -#[derive(Debug)] -pub struct WorkspaceGroup { - inner: Arc>, -} - -impl PartialEq for WorkspaceGroup { - fn eq(&self, other: &WorkspaceGroup) -> bool { - Arc::ptr_eq(&self.inner, &other.inner) - } -} - -struct WorkspaceGroupInner { - instances: Vec>, - outputs: HashSet, - workspaces: Vec>>, - create_new_workspace: Arc>, -} - -impl fmt::Debug for WorkspaceGroupInner { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - f.debug_struct("WorkspaceGroupInner") - .field("instances", &self.instances) - .field("outputs", &self.outputs) - .field("workspaces", &self.workspaces) - .finish_non_exhaustive() - } -} - -impl WorkspaceGroup { - pub fn create_workspace(&self, name: String) -> Workspace { - let mut inner = self.inner.lock().unwrap(); - let workspace_inner = Arc::new(Mutex::new(WorkspaceInner { - instances: Vec::new(), - name: name.clone(), - states: Vec::new(), - coordinates: Vec::new(), - })); - inner.workspaces.retain(|w| w.upgrade().is_some()); - inner.workspaces.push(Arc::downgrade(&workspace_inner)); - - for instance in inner.instances.iter() { - if let Some(client) = instance.as_ref().client() { - let workspace_inner_clone = workspace_inner.clone(); - if let Some(workspace) = Workspace::create_instance(&client, workspace_inner_clone) - { - instance.workspace(&*workspace); - let mut workspace_inner_guard = workspace_inner.lock().unwrap(); - workspace_inner_guard.instances.push(workspace); - workspace_inner_guard.send(&client); - } - } - } - - Workspace { - inner: workspace_inner, - } - } - - pub fn belongs(&self, workspace: &Workspace) -> bool { - self.inner - .lock() - .unwrap() - .workspaces - .iter() - .flat_map(|x| x.upgrade()) - .any(|w| Arc::ptr_eq(&w, &workspace.inner)) - } - - fn create_instance( - client: &Client, - group_inner: Arc>, - ) -> Option> { - if let Some(group) = client.create_resource::(1) { - let group_inner_clone = group_inner.clone(); - group.quick_assign(move |group, request, ddata| match request { - zext_workspace_group_handle_v1::Request::CreateWorkspace { workspace } => { - let callback = group_inner_clone - .lock() - .unwrap() - .create_new_workspace - .clone(); - let group = WorkspaceGroup { - inner: group_inner_clone.clone(), - }; - callback(&group, workspace, ddata); - } - zext_workspace_group_handle_v1::Request::Destroy => { - group_inner_clone - .lock() - .unwrap() - .instances - .retain(|g| *g != group); - } - }); - Some(group) - } else { - None - } - } -} - -#[derive(Debug)] -pub struct Workspace { - inner: Arc>, -} - -impl PartialEq for Workspace { - fn eq(&self, other: &Workspace) -> bool { - Arc::ptr_eq(&self.inner, &other.inner) - } -} - -#[derive(Debug)] -struct WorkspaceInner { - instances: Vec>, - name: String, - states: Vec, - coordinates: Vec, -} - -type WorkspaceUserdata = RefCell>; - -#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] -pub enum PendingOperation { - Activate, - Deactivate, - Remove, -} - -impl Workspace { - pub fn name(&self) -> String { - self.inner.lock().unwrap().name.clone() - } - - pub fn set_name(&self, name: String) { - let mut inner = self.inner.lock().unwrap(); - for instance in inner.instances.iter() { - instance.name(name.clone()); - } - inner.name = name; - } - - pub fn states(&self) -> Vec { - self.inner.lock().unwrap().states.clone() - } - - pub fn add_state(&self, state: State) { - let mut inner = self.inner.lock().unwrap(); - inner.states.push(state); - for instance in inner.instances.iter() { - let states = { - let mut states = inner.states.clone(); - let ptr = states.as_mut_ptr(); - let len = states.len(); - let cap = states.capacity(); - ::std::mem::forget(states); - unsafe { Vec::from_raw_parts(ptr as *mut u8, len * 4, cap * 4) } - }; - instance.state(states); - } - } - - pub fn remove_state(&self, state: State) { - let mut inner = self.inner.lock().unwrap(); - inner.states.retain(|s| *s != state); - for instance in inner.instances.iter() { - let states = { - let mut states = inner.states.clone(); - let ptr = states.as_mut_ptr(); - let len = states.len(); - let cap = states.capacity(); - ::std::mem::forget(states); - unsafe { Vec::from_raw_parts(ptr as *mut u8, len * 4, cap * 4) } - }; - instance.state(states); - } - } - - pub fn set_states(&self, states: impl Iterator) { - let mut inner = self.inner.lock().unwrap(); - inner.states = states.collect(); - for instance in inner.instances.iter() { - let states = { - let mut states = inner.states.clone(); - let ptr = states.as_mut_ptr(); - let len = states.len(); - let cap = states.capacity(); - ::std::mem::forget(states); - unsafe { Vec::from_raw_parts(ptr as *mut u8, len * 4, cap * 4) } - }; - instance.state(states); - } - } - - pub fn coordinates(&self) -> Vec { - self.inner.lock().unwrap().coordinates.clone() - } - - pub fn set_coordinates(&self, coordinates: impl Iterator) { - let mut inner = self.inner.lock().unwrap(); - inner.coordinates = coordinates.collect(); - for instances in inner.instances.iter() { - let coords = { - let mut coords = inner.coordinates.clone(); - let ptr = coords.as_mut_ptr(); - let len = coords.len(); - let cap = coords.capacity(); - ::std::mem::forget(coords); - unsafe { Vec::from_raw_parts(ptr as *mut u8, len * 4, cap * 4) } - }; - instances.coordinates(coords); - } - } - - pub fn remove(self) { - for instance in self.inner.lock().unwrap().instances.drain(..) { - instance.remove(); - } - } - - fn create_instance( - client: &Client, - workspace_inner_clone: Arc>, - ) -> Option> { - if let Some(workspace) = client.create_resource::(1) { - workspace.quick_assign(move |workspace, request, _| match request { - zext_workspace_handle_v1::Request::Activate => { - let user_data = workspace.as_ref().user_data(); - user_data.set(|| -> WorkspaceUserdata { RefCell::new(Vec::new()) }); - user_data - .get::() - .unwrap() - .borrow_mut() - .push(PendingOperation::Activate); - } - zext_workspace_handle_v1::Request::Deactivate => { - let user_data = workspace.as_ref().user_data(); - user_data.set(|| -> WorkspaceUserdata { RefCell::new(Vec::new()) }); - user_data - .get::() - .unwrap() - .borrow_mut() - .push(PendingOperation::Deactivate); - } - zext_workspace_handle_v1::Request::Remove => { - let user_data = workspace.as_ref().user_data(); - user_data.set(|| -> WorkspaceUserdata { RefCell::new(Vec::new()) }); - user_data - .get::() - .unwrap() - .borrow_mut() - .push(PendingOperation::Remove); - } - zext_workspace_handle_v1::Request::Destroy => { - workspace_inner_clone - .lock() - .unwrap() - .instances - .retain(|w| **w != *workspace); - } - }); - Some(workspace) - } else { - None - } - } -} - -impl WorkspaceInner { - fn send(&self, client: &Client) { - if let Some(instance) = self - .instances - .iter() - .find(|w| w.as_ref().client().map(|c| &c == client).unwrap_or(false)) - { - instance.name(self.name.clone()); - let states = { - let mut states = self.states.clone(); - let ptr = states.as_mut_ptr(); - let len = states.len(); - let cap = states.capacity(); - ::std::mem::forget(states); - unsafe { Vec::from_raw_parts(ptr as *mut u8, len * 4, cap * 4) } - }; - instance.state(states); - let coords = { - let mut coords = self.coordinates.clone(); - let ptr = coords.as_mut_ptr(); - let len = coords.len(); - let cap = coords.capacity(); - ::std::mem::forget(coords); - unsafe { Vec::from_raw_parts(ptr as *mut u8, len * 4, cap * 4) } - }; - instance.coordinates(coords); - } - } -}