From a3e9f21fc5b63ad0c26842c68f9c7616157f06b8 Mon Sep 17 00:00:00 2001 From: Julian Orth Date: Tue, 5 Apr 2022 18:28:42 +0200 Subject: [PATCH] autocommit 2022-04-05 18:28:42 CEST --- Cargo.toml | 5 +- README.md | 23 ++++-- jay-config/src/_private/client.rs | 19 +++++ jay-config/src/_private/ipc.rs | 13 +++ jay-config/src/drm.rs | 4 + jay-config/src/lib.rs | 1 + src/backend.rs | 36 +++++++-- src/backends/dummy.rs | 14 +++- src/backends/metal/video.rs | 115 ++++++++++++++++++++++----- src/backends/x.rs | 41 +++++++--- src/compositor.rs | 39 +++++---- src/config.rs | 27 ++++++- src/drm/drm.rs | 20 ++++- src/ifs/wl_output.rs | 42 +++++++--- src/ifs/wl_region.rs | 2 +- src/ifs/wl_seat/event_handling.rs | 19 +++-- src/ifs/zwlr_layer_shell_v1.rs | 10 ++- src/logger.rs | 26 +++++- src/main.rs | 1 - src/rect.rs | 3 +- src/rect/region.rs | 30 ++++++- src/state.rs | 19 ++--- src/tasks.rs | 2 +- src/tasks/backend.rs | 17 +--- src/tasks/connector.rs | 128 ++++++++++++++++++++++++++++++ src/tasks/output.rs | 98 ----------------------- src/tree.rs | 4 +- src/tree/output.rs | 31 ++++++++ src/utils/linkedlist.rs | 4 + 29 files changed, 568 insertions(+), 225 deletions(-) create mode 100644 jay-config/src/drm.rs create mode 100644 src/tasks/connector.rs delete mode 100644 src/tasks/output.rs diff --git a/Cargo.toml b/Cargo.toml index 28edbcf5..447fe264 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -37,8 +37,7 @@ clap = { version = "3.1.6", features = ["derive", "wrap_help"] } clap_complete = "3.1.1" humantime = "2.1.0" dirs = "4.0.0" - -backtrace = { version = "0.3.64", optional = true } +backtrace = "0.3.64" [build-dependencies] repc = "0.1.1" @@ -49,4 +48,4 @@ bstr = { version = "0.2.17", default-features = false, features = ["std"] } #opt-level = 3 [features] -rc_tracking = ["backtrace"] +rc_tracking = [] diff --git a/README.md b/README.md index 0b6dfb48..3694d9e8 100644 --- a/README.md +++ b/README.md @@ -9,12 +9,19 @@ Do not expect any kind of structured commit history. ## Dependencies -While Jay is written almost completely in rust, it has some native dependencies: +While Jay is written almost completely in rust, it depends on the following libraries: -* **pixman-1.so**: For damage tracking. -* **input.so**: For processing input events. -* **EGL.so**, **GLESv2.so**: For OpenGL rendering. -* **gbm.so**: For graphics buffer allocation. -* **xkbcommon.so**: For keymap handling. -* **udev.so**: For device enumeration and hotplug support. -* **cairo.so**, **pangocairo-1.0.so**, **gobject-2.0.so**, **pango-1.0.so**: For text rendering. +* **libinput.so**: For processing input events. +* **libEGL.so**, **libGLESv2.so**: For OpenGL rendering. +* **libgbm.so**: For graphics buffer allocation. +* **libxkbcommon.so**: For keymap handling. +* **libudev.so**: For device enumeration and hotplug support. +* **libcairo.so**, **libpangocairo-1.0.so**, **libgobject-2.0.so**, **libpango-1.0.so**: For text rendering. + +Furthermore, Jay depends on the following runtime services: + +* **An up-to-date linux kernel** +* **XWayland**: For XWayland support. +* **Pipewire**: For screen-recording. +* **A running X server**: For the X backend. (Only required if you want to run Jay as an X client.) +* **Logind**: For the metal backend. (Only required if you want to run Jay from a TTY.) diff --git a/jay-config/src/_private/client.rs b/jay-config/src/_private/client.rs index ab1048ac..610a0e51 100644 --- a/jay-config/src/_private/client.rs +++ b/jay-config/src/_private/client.rs @@ -2,6 +2,7 @@ use crate::_private::ipc::{ClientMessage, InitMessage, Response, ServerMessage}; use crate::_private::{bincode_ops, logging, Config, ConfigEntry, ConfigEntryGen, VERSION}; +use crate::drm::Connector; use crate::input::acceleration::AccelProfile; use crate::input::capability::Capability; use crate::input::InputDevice; @@ -24,6 +25,8 @@ pub(crate) struct Client { response: RefCell>, on_new_seat: RefCell>>, on_new_input_device: RefCell>>, + on_connector_connected: RefCell>>, + on_new_connector: RefCell>>, bufs: RefCell>>, } @@ -103,6 +106,8 @@ pub unsafe extern "C" fn init( response: Default::default(), on_new_seat: Default::default(), on_new_input_device: Default::default(), + on_connector_connected: Default::default(), + on_new_connector: Default::default(), bufs: Default::default(), }); let init = slice::from_raw_parts(init, size); @@ -471,6 +476,20 @@ impl Client { } } ServerMessage::DelInputDevice { .. } => {} + ServerMessage::ConnectorConnect { device } => { + let handler = self.on_connector_connected.borrow_mut().clone(); + if let Some(handler) = handler { + handler(device); + } + } + ServerMessage::ConnectorDisconnect { .. } => {} + ServerMessage::NewConnector { device } => { + let handler = self.on_new_connector.borrow_mut().clone(); + if let Some(handler) = handler { + handler(device); + } + } + ServerMessage::DelConnector { .. } => {} } } diff --git a/jay-config/src/_private/ipc.rs b/jay-config/src/_private/ipc.rs index 90a53148..a1dce1c9 100644 --- a/jay-config/src/_private/ipc.rs +++ b/jay-config/src/_private/ipc.rs @@ -1,3 +1,4 @@ +use crate::drm::Connector; use crate::input::acceleration::AccelProfile; use crate::input::capability::Capability; use crate::input::InputDevice; @@ -14,6 +15,18 @@ pub enum ServerMessage { Response { response: Response, }, + ConnectorConnect { + device: Connector, + }, + ConnectorDisconnect { + device: Connector, + }, + NewConnector { + device: Connector, + }, + DelConnector { + device: Connector, + }, NewInputDevice { device: InputDevice, }, diff --git a/jay-config/src/drm.rs b/jay-config/src/drm.rs new file mode 100644 index 00000000..6e955e07 --- /dev/null +++ b/jay-config/src/drm.rs @@ -0,0 +1,4 @@ +use bincode::{Decode, Encode}; + +#[derive(Encode, Decode, Copy, Clone, Debug, Hash, Eq, PartialEq)] +pub struct Connector(pub u64); diff --git a/jay-config/src/lib.rs b/jay-config/src/lib.rs index aceb1d7f..4a811715 100644 --- a/jay-config/src/lib.rs +++ b/jay-config/src/lib.rs @@ -8,6 +8,7 @@ use std::collections::HashMap; mod macros; #[doc(hidden)] pub mod _private; +pub mod drm; pub mod embedded; pub mod input; pub mod keyboard; diff --git a/src/backend.rs b/src/backend.rs index aa99f406..02006e9b 100644 --- a/src/backend.rs +++ b/src/backend.rs @@ -1,8 +1,9 @@ +use crate::drm::drm::ConnectorType; use crate::fixed::Fixed; -use std::fmt::Debug; +use std::fmt::{Debug, Display, Formatter}; use std::rc::Rc; -linear_ids!(OutputIds, OutputId); +linear_ids!(ConnectorIds, ConnectorId); linear_ids!(InputDeviceIds, InputDeviceId); pub trait Backend { @@ -13,17 +14,42 @@ pub trait Backend { pub struct Mode { pub width: i32, pub height: i32, - pub refresh_rate: u32, + pub refresh_rate_millihz: u32, +} + +#[derive(Clone, Debug)] +pub struct MonitorInfo { + pub modes: Vec, + pub manufacturer: String, + pub product: String, + pub serial_number: String, + pub initial_mode: Mode, + pub width_mm: i32, + pub height_mm: i32, +} + +pub struct ConnectorKernelId { + pub ty: ConnectorType, + pub id: u32, +} + +impl Display for ConnectorKernelId { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + write!(f, "{}-{}", self.ty, self.id) + } } pub trait Connector { - fn id(&self) -> OutputId; + fn id(&self) -> ConnectorId; + fn kernel_id(&self) -> ConnectorKernelId; fn event(&self) -> Option; fn on_change(&self, cb: Rc); } #[derive(Debug)] pub enum ConnectorEvent { + Connected(MonitorInfo), + Disconnected, Removed, ModeChanged(Mode), } @@ -79,7 +105,7 @@ pub enum ScrollAxis { #[derive(Debug)] pub enum InputEvent { Key(u32, KeyState), - OutputPosition(OutputId, Fixed, Fixed), + ConnectorPosition(ConnectorId, Fixed, Fixed), #[allow(dead_code)] Motion(Fixed, Fixed), Button(u32, KeyState), diff --git a/src/backends/dummy.rs b/src/backends/dummy.rs index b21dace8..a673d471 100644 --- a/src/backends/dummy.rs +++ b/src/backends/dummy.rs @@ -1,4 +1,5 @@ -use crate::backend::{Backend, Connector, ConnectorEvent, OutputId}; +use crate::backend::{Backend, Connector, ConnectorEvent, ConnectorId, ConnectorKernelId}; +use crate::drm::drm::ConnectorType; use std::rc::Rc; pub struct DummyBackend {} @@ -10,14 +11,21 @@ impl Backend for DummyBackend { } pub struct DummyOutput { - pub id: OutputId, + pub id: ConnectorId, } impl Connector for DummyOutput { - fn id(&self) -> OutputId { + fn id(&self) -> ConnectorId { self.id } + fn kernel_id(&self) -> ConnectorKernelId { + ConnectorKernelId { + ty: ConnectorType::Unknown(0), + id: 0, + } + } + fn event(&self) -> Option { None } diff --git a/src/backends/metal/video.rs b/src/backends/metal/video.rs index 454f5740..3d687de6 100644 --- a/src/backends/metal/video.rs +++ b/src/backends/metal/video.rs @@ -1,5 +1,5 @@ use crate::async_engine::{AsyncFd, SpawnedFuture}; -use crate::backend::{BackendEvent, Connector, ConnectorEvent, Mode, OutputId}; +use crate::backend::{BackendEvent, Connector, ConnectorEvent, ConnectorId, ConnectorKernelId, MonitorInfo}; use crate::backends::metal::{DrmId, MetalBackend, MetalError}; use crate::drm::drm::{ drm_mode_modeinfo, Change, ConnectorStatus, ConnectorType, DrmBlob, DrmConnector, DrmCrtc, @@ -25,6 +25,7 @@ use std::ffi::CString; use std::fmt::{Debug, Formatter}; use std::rc::Rc; use uapi::c; +use crate::edid::Descriptor; pub struct PendingDrmDevice { pub id: DrmId, @@ -72,12 +73,16 @@ pub struct MetalConnector { pub id: DrmConnector, pub master: Rc, - pub output_id: OutputId, + pub connector_id: ConnectorId, pub crtcs: AHashMap>, pub modes: Vec, pub mode: CloneCell>>, + pub monitor_manufacturer: String, + pub monitor_name: String, + pub monitor_serial_number: String, + pub events: SyncQueue, pub buffers: CloneCell>>, @@ -114,8 +119,15 @@ impl Debug for OnChange { } impl Connector for MetalConnector { - fn id(&self) -> OutputId { - self.output_id + fn id(&self) -> ConnectorId { + self.connector_id + } + + fn kernel_id(&self) -> ConnectorKernelId { + ConnectorKernelId { + ty: self.connector_type, + id: self.connector_type_id, + } } fn event(&self) -> Option { @@ -212,28 +224,73 @@ fn create_connector( } } let props = collect_properties(&dev.master, connector)?; - let mode = info.modes.first().cloned().map(Rc::new); - let events = SyncQueue::default(); - if let Some(mode) = &mode { - events.push(ConnectorEvent::ModeChanged(Mode { - width: mode.hdisplay as _, - height: mode.vdisplay as _, - refresh_rate: mode.refresh_rate(), - })); + let connection = ConnectorStatus::from(info.connection); + let connector_type = ConnectorType::from(info.connector_type); + let mut name = String::new(); + let mut manufacturer = String::new(); + let mut serial_number = String::new(); + 'fetch_edid: { + if connection != ConnectorStatus::Connected { + break 'fetch_edid; + } + let edid = match props.get("EDID") { + Ok(e) => e, + _ => { + log::warn!("Connector {}-{} is connected but has no EDID blob", connector_type, info.connector_type_id); + break 'fetch_edid; + } + }; + let blob = match dev.master.getblob_vec::(DrmBlob(edid.value.get() as _)) { + Ok(b) => b, + Err(e) => { + log::error!("Could not fetch edid property of connector {}-{}: {}", connector_type, info.connector_type_id, ErrorFmt(e)); + break 'fetch_edid; + } + }; + let edid = match crate::edid::parse(&blob) { + Ok(e) => e, + Err(e) => { + log::error!("Could not parse edid property of connector {}-{}: {}", connector_type, info.connector_type_id, ErrorFmt(e)); + break 'fetch_edid; + } + }; + manufacturer = edid.base_block.id_manufacturer_name.to_string(); + for descriptor in &edid.base_block.descriptors { + if let Some(d) = descriptor { + match d { + Descriptor::DisplayProductSerialNumber(s) => { + serial_number = s.to_string(); + } + Descriptor::DisplayProductName(s) => { + name = s.to_string(); + } + _ => { }, + } + } + } + if name.is_empty() { + log::warn!("The display attached to connector {}-{} does not have a product name descriptor", connector_type, info.connector_type_id); + } + if serial_number.is_empty() { + serial_number = edid.base_block.id_serial_number.to_string(); + } } Ok(MetalConnector { id: connector, master: dev.master.clone(), - output_id: state.output_ids.next(), + connector_id: state.connector_ids.next(), crtcs, - mode: CloneCell::new(mode), - events, + mode: CloneCell::new(info.modes.first().cloned().map(Rc::new)), + monitor_manufacturer: manufacturer, + monitor_name: name, + monitor_serial_number: serial_number, + events: Default::default(), modes: info.modes, buffers: Default::default(), next_buffer: Default::default(), - connector_type: info.connector_type.into(), + connector_type, connector_type_id: info.connector_type_id, - connection: info.connection.into(), + connection, mm_width: info.mm_width, mm_height: info.mm_height, subpixel: info.subpixel, @@ -488,7 +545,27 @@ impl MetalBackend { self.state .backend_events .push(BackendEvent::NewConnector(connector.clone())); - if connector.primary_plane.get().is_some() { + if connector.connection == ConnectorStatus::Connected { + if connector.primary_plane.get().is_none() { + log::error!("Connector {}-{} is connected but does not have a primary plane", connector.connector_type, connector.connector_type_id); + continue; + } + let mut prev_mode = None; + let mut modes = vec!(); + for mode in connector.modes.iter().map(|m| m.to_backend()) { + if prev_mode.replace(mode) != Some(mode) { + modes.push(mode); + } + } + connector.events.push(ConnectorEvent::Connected(MonitorInfo { + modes, + manufacturer: connector.monitor_manufacturer.clone(), + product: connector.monitor_name.clone(), + serial_number: connector.monitor_serial_number.clone(), + initial_mode: connector.mode.get().unwrap().to_backend(), + width_mm: connector.mm_width as _, + height_mm: connector.mm_height as _, + })); self.start_connector(connector); } } @@ -901,7 +978,7 @@ impl MetalBackend { _ => return, }; let buffer = &buffers[connector.next_buffer.fetch_add(1) % buffers.len()]; - if let Some(node) = self.state.root.outputs.get(&connector.output_id) { + if let Some(node) = self.state.root.outputs.get(&connector.connector_id) { buffer .egl .render(&*node, &self.state, Some(node.global.pos.get())); diff --git a/src/backends/x.rs b/src/backends/x.rs index ef07ae05..1104a9a4 100644 --- a/src/backends/x.rs +++ b/src/backends/x.rs @@ -1,9 +1,6 @@ use crate::async_engine::{Phase, SpawnedFuture}; -use crate::backend::{ - Backend, BackendEvent, Connector, ConnectorEvent, InputDevice, InputDeviceAccelProfile, - InputDeviceCapability, InputDeviceId, InputEvent, KeyState, Mode, OutputId, ScrollAxis, -}; -use crate::drm::drm::{Drm, DrmError}; +use crate::backend::{Backend, BackendEvent, Connector, ConnectorEvent, ConnectorId, ConnectorKernelId, InputDevice, InputDeviceAccelProfile, InputDeviceCapability, InputDeviceId, InputEvent, KeyState, Mode, MonitorInfo, ScrollAxis}; +use crate::drm::drm::{ConnectorType, Drm, DrmError}; use crate::drm::gbm::{GbmDevice, GbmError, GBM_BO_USE_RENDERING}; use crate::drm::{ModifiedFormat, INVALID_MODIFIER}; use crate::fixed::Fixed; @@ -394,7 +391,7 @@ impl XBackendData { }; let images = self.create_images(window_id, WIDTH, HEIGHT).await?; let output = Rc::new(XOutput { - id: self.state.output_ids.next(), + id: self.state.connector_ids.next(), _backend: self.clone(), window: window_id, events: Default::default(), @@ -481,6 +478,20 @@ impl XBackendData { self.state .backend_events .push(BackendEvent::NewConnector(output.clone())); + output.events.push(ConnectorEvent::Connected(MonitorInfo { + modes: vec![], + manufacturer: "X.Org Foundation".to_string(), + product: format!("X-Window-{}", window_id), + serial_number: window_id.to_string(), + initial_mode: Mode { + width: WIDTH, + height: HEIGHT, + refresh_rate_millihz: 60_000, // TODO + }, + width_mm: WIDTH, + height_mm: HEIGHT, + })); + output.changed(); self.present(&output).await; Ok(()) } @@ -759,7 +770,7 @@ impl XBackendData { self.outputs.get(&event.event), self.mouse_seats.get(&event.deviceid), ) { - seat.mouse_event(InputEvent::OutputPosition( + seat.mouse_event(InputEvent::ConnectorPosition( win.id, Fixed::from_1616(event.event_x), Fixed::from_1616(event.event_y), @@ -777,7 +788,7 @@ impl XBackendData { (Some(a), Some(b)) => (a, b), _ => return Ok(()), }; - seat.mouse_event(InputEvent::OutputPosition( + seat.mouse_event(InputEvent::ConnectorPosition( win.id, Fixed::from_1616(event.event_x), Fixed::from_1616(event.event_y), @@ -792,6 +803,7 @@ impl XBackendData { Some(o) => o, _ => return Ok(()), }; + output.events.push(ConnectorEvent::Disconnected); output.events.push(ConnectorEvent::Removed); output.changed(); Ok(()) @@ -820,7 +832,7 @@ impl XBackendData { output.events.push(ConnectorEvent::ModeChanged(Mode { width, height, - refresh_rate: 60, // TODO + refresh_rate_millihz: 60, // TODO })); output.changed(); } @@ -829,7 +841,7 @@ impl XBackendData { } struct XOutput { - id: OutputId, + id: ConnectorId, _backend: Rc, window: u32, events: SyncQueue, @@ -859,10 +871,17 @@ impl XOutput { } impl Connector for XOutput { - fn id(&self) -> OutputId { + fn id(&self) -> ConnectorId { self.id } + fn kernel_id(&self) -> ConnectorKernelId { + ConnectorKernelId { + ty: ConnectorType::EmbeddedWindow, + id: self.id.raw(), + } + } + fn event(&self) -> Option { self.events.pop() } diff --git a/src/compositor.rs b/src/compositor.rs index ad3812e5..059095c2 100644 --- a/src/compositor.rs +++ b/src/compositor.rs @@ -7,14 +7,13 @@ use crate::clientmem::ClientMemError; use crate::config::ConfigProxy; use crate::dbus::Dbus; use crate::event_loop::{EventLoop, EventLoopError}; -use crate::forker::ForkerError; use crate::globals::Globals; use crate::ifs::wl_output::WlOutputGlobal; use crate::ifs::wl_surface::NoneSurfaceExt; use crate::logger::Logger; use crate::render::RenderError; use crate::sighand::SighandError; -use crate::state::State; +use crate::state::{ConnectorData, State}; use crate::tree::{ container_layout, container_render_data, float_layout, float_titles, DisplayNode, NodeIds, OutputNode, WorkspaceNode, @@ -22,12 +21,11 @@ use crate::tree::{ use crate::utils::clonecell::CloneCell; use crate::utils::errorfmt::ErrorFmt; use crate::utils::fdcloser::FdCloser; -use crate::utils::numcell::NumCell; use crate::utils::queue::AsyncQueue; use crate::utils::run_toplevel::RunToplevel; use crate::wheel::{Wheel, WheelError}; use crate::xkbcommon::XkbContext; -use crate::{clientmem, forker, leaks, render, sighand, tasks, xwayland}; +use crate::{backend, clientmem, forker, leaks, render, sighand, tasks, xwayland}; use forker::ForkerProxy; use std::cell::Cell; use std::ops::Deref; @@ -36,8 +34,12 @@ use std::sync::Arc; use thiserror::Error; pub fn start_compositor(global: GlobalArgs, args: RunArgs) { + let forker = match ForkerProxy::create() { + Ok(f) => Rc::new(f), + Err(e) => fatal!("Could not create a forker process: {}", ErrorFmt(e)), + }; let logger = Logger::install_compositor(global.log_level.into()); - if let Err(e) = main_(logger.clone(), &args) { + if let Err(e) = main_(forker, logger.clone(), &args) { let e = ErrorFmt(e); log::error!("A fatal error occurred: {}", e); eprintln!("A fatal error occurred: {}", e); @@ -62,12 +64,9 @@ enum MainError { AsyncError(#[from] AsyncError), #[error("The render backend caused an error")] RenderError(#[from] RenderError), - #[error("The ol' forker caused an error")] - ForkerError(#[from] ForkerError), } -fn main_(logger: Arc, _args: &RunArgs) -> Result<(), MainError> { - let forker = Rc::new(ForkerProxy::create()?); +fn main_(forker: Rc, logger: Arc, _args: &RunArgs) -> Result<(), MainError> { leaks::init(); render::init()?; clientmem::init()?; @@ -90,17 +89,14 @@ fn main_(logger: Arc, _args: &RunArgs) -> Result<(), MainError> { cursors: Default::default(), wheel, clients: Clients::new(), - next_name: NumCell::new(1), globals: Globals::new(), - output_ids: Default::default(), + connector_ids: Default::default(), root: Rc::new(DisplayNode::new(node_ids.next())), workspaces: Default::default(), dummy_output: Default::default(), node_ids, backend_events: AsyncQueue::new(), - output_handlers: Default::default(), seat_ids: Default::default(), - outputs: Default::default(), seat_queue: Default::default(), slow_clients: AsyncQueue::new(), none_surface_ext: Rc::new(NoneSurfaceExt), @@ -116,16 +112,29 @@ fn main_(logger: Arc, _args: &RunArgs) -> Result<(), MainError> { dbus: Dbus::new(&engine, &run_toplevel), fdcloser: FdCloser::new(), logger, + connectors: Default::default(), }); { let dummy_output = Rc::new(OutputNode { id: state.node_ids.next(), global: Rc::new(WlOutputGlobal::new( state.globals.name(), - Rc::new(DummyOutput { - id: state.output_ids.next(), + &Rc::new(ConnectorData { + connector: Rc::new(DummyOutput { id: state.connector_ids.next() }), + monitor_info: Default::default(), + handler: Cell::new(None), + node: Default::default() }), 0, + &backend::Mode { + width: 0, + height: 0, + refresh_rate_millihz: 0, + }, + "none", + "none", + 0, + 0, )), workspaces: Default::default(), workspace: Default::default(), diff --git a/src/config.rs b/src/config.rs index 305c245c..014e8555 100644 --- a/src/config.rs +++ b/src/config.rs @@ -1,6 +1,6 @@ mod handler; -use crate::backend::InputDeviceId; +use crate::backend::{ConnectorId, InputDeviceId}; use crate::config::handler::ConfigProxyHandler; use crate::ifs::wl_seat::SeatId; use crate::state::State; @@ -8,6 +8,7 @@ use crate::utils::numcell::NumCell; use crate::utils::ptr_ext::PtrExt; use jay_config::_private::ipc::{InitMessage, ServerMessage, V1InitMessage}; use jay_config::_private::{bincode_ops, ConfigEntry, VERSION}; +use jay_config::drm::Connector; use jay_config::input::InputDevice; use jay_config::keyboard::ModifiedKeySym; use jay_config::Seat; @@ -38,6 +39,30 @@ impl ConfigProxy { }); } + pub fn new_connector(&self, connector: ConnectorId) { + self.handler.send(&ServerMessage::NewConnector { + device: Connector(connector.raw() as _), + }); + } + + pub fn del_connector(&self, connector: ConnectorId) { + self.handler.send(&ServerMessage::DelConnector { + device: Connector(connector.raw() as _), + }); + } + + pub fn connector_connected(&self, connector: ConnectorId) { + self.handler.send(&ServerMessage::ConnectorConnect { + device: Connector(connector.raw() as _), + }); + } + + pub fn connector_disconnected(&self, connector: ConnectorId) { + self.handler.send(&ServerMessage::ConnectorDisconnect { + device: Connector(connector.raw() as _), + }); + } + pub fn new_input_device(&self, dev: InputDeviceId) { self.handler.send(&ServerMessage::NewInputDevice { device: InputDevice(dev.raw() as _), diff --git a/src/drm/drm.rs b/src/drm/drm.rs index b231204f..c23db579 100644 --- a/src/drm/drm.rs +++ b/src/drm/drm.rs @@ -33,6 +33,7 @@ pub use sys::{ drm_mode_modeinfo, DRM_CLIENT_CAP_ATOMIC, DRM_MODE_ATOMIC_ALLOW_MODESET, DRM_MODE_ATOMIC_NONBLOCK, DRM_MODE_PAGE_FLIP_EVENT, }; +use crate::backend; #[derive(Debug, Error)] pub enum DrmError { @@ -597,13 +598,21 @@ impl DrmModeInfo { } } - pub fn refresh_rate(&self) -> u32 { - let clock_mhz = self.clock as u64 * 1_000_000; + pub fn to_backend(&self) -> backend::Mode { + backend::Mode { + width: self.hdisplay as _, + height: self.vdisplay as _, + refresh_rate_millihz: self.refresh_rate_millihz(), + } + } + + pub fn refresh_rate_millihz(&self) -> u32 { + let clock_millihz = self.clock as u64 * 1_000_000; let htotal = self.htotal as u64; let vtotal = self.vtotal as u64; - (((clock_mhz / htotal) + (vtotal / 2)) / vtotal) as u32 + (((clock_millihz / htotal) + (vtotal / 2)) / vtotal) as u32 // simplifies to - // clock_mhz / (htotal * vtotal) + 1/2 + // clock_millihz / (htotal * vtotal) + 1/2 // why round up (+1/2) instead of down? } } @@ -725,6 +734,7 @@ pub enum ConnectorType { WRITEBACK, SPI, USB, + EmbeddedWindow, } impl Display for ConnectorType { @@ -751,6 +761,7 @@ impl Display for ConnectorType { Self::WRITEBACK => "Writeback", Self::SPI => "SPI", Self::USB => "USB", + Self::EmbeddedWindow => "EmbeddedWindow", }; f.write_str(s) } @@ -808,6 +819,7 @@ impl Into for ConnectorType { Self::WRITEBACK => sys::DRM_MODE_CONNECTOR_WRITEBACK, Self::SPI => sys::DRM_MODE_CONNECTOR_SPI, Self::USB => sys::DRM_MODE_CONNECTOR_USB, + Self::EmbeddedWindow => sys::DRM_MODE_CONNECTOR_Unknown, } } } diff --git a/src/ifs/wl_output.rs b/src/ifs/wl_output.rs index b365af1f..eeb1ea1b 100644 --- a/src/ifs/wl_output.rs +++ b/src/ifs/wl_output.rs @@ -1,11 +1,11 @@ use crate::backend; -use crate::backend::Connector; use crate::client::{Client, ClientError, ClientId}; use crate::globals::{Global, GlobalName}; use crate::ifs::zxdg_output_v1::ZxdgOutputV1; use crate::leaks::Tracker; use crate::object::Object; use crate::rect::Rect; +use crate::state::ConnectorData; use crate::tree::OutputNode; use crate::utils::buffd::MsgParser; use crate::utils::buffd::MsgParserError; @@ -53,21 +53,38 @@ const MODE_PREFERRED: u32 = 2; pub struct WlOutputGlobal { name: GlobalName, - pub connector: Rc, + pub connector: Rc, pub pos: Cell, + pub manufacturer: String, + pub display: String, pub mode: Cell, pub node: CloneCell>>, + pub width_mm: i32, + pub height_mm: i32, pub bindings: RefCell>>>, } impl WlOutputGlobal { - pub fn new(name: GlobalName, connector: Rc, x1: i32) -> Self { + pub fn new( + name: GlobalName, + connector: &Rc, + x1: i32, + mode: &backend::Mode, + manufacturer: &str, + product: &str, + width_mm: i32, + height_mm: i32, + ) -> Self { Self { name, connector: connector.clone(), - pos: Cell::new(Rect::new_empty(x1, 0)), - mode: Default::default(), + pos: Cell::new(Rect::new_sized(x1, 0, mode.width, mode.height).unwrap()), + manufacturer: manufacturer.to_string(), + display: product.to_string(), + mode: Cell::new(*mode), node: Default::default(), + width_mm, + height_mm, bindings: Default::default(), } } @@ -163,24 +180,25 @@ impl WlOutput { self_id: self.id, x: pos.x1(), y: pos.y1(), - physical_width: pos.width(), - physical_height: pos.height(), + physical_width: self.global.width_mm, + physical_height: self.global.height_mm, subpixel: SP_UNKNOWN, - make: "jay", - model: "jay", + make: &self.global.manufacturer, + model: &self.global.display, transform: TF_NORMAL, }; self.client.event(event); } fn send_mode(&self) { + let mode = self.global.mode.get(); let pos = self.global.pos.get(); let event = Mode { self_id: self.id, flags: MODE_CURRENT, - width: pos.width(), - height: pos.height(), - refresh: 60_000_000, + width: mode.width, + height: mode.height, + refresh: mode.refresh_rate_millihz as _, }; self.client.event(event); } diff --git a/src/ifs/wl_region.rs b/src/ifs/wl_region.rs index 80206685..d6badcad 100644 --- a/src/ifs/wl_region.rs +++ b/src/ifs/wl_region.rs @@ -1,6 +1,7 @@ use crate::client::{Client, ClientError}; use crate::leaks::Tracker; use crate::object::Object; +use crate::rect::{Rect, Region, RegionBuilder}; use crate::utils::buffd::MsgParser; use crate::utils::buffd::MsgParserError; use crate::wire::wl_region::*; @@ -8,7 +9,6 @@ use crate::wire::WlRegionId; use std::cell::RefCell; use std::rc::Rc; use thiserror::Error; -use crate::rect::{Rect, Region, RegionBuilder}; pub struct WlRegion { id: WlRegionId, diff --git a/src/ifs/wl_seat/event_handling.rs b/src/ifs/wl_seat/event_handling.rs index 175ae156..d7dd4184 100644 --- a/src/ifs/wl_seat/event_handling.rs +++ b/src/ifs/wl_seat/event_handling.rs @@ -1,4 +1,4 @@ -use crate::backend::{InputEvent, KeyState, OutputId, ScrollAxis}; +use crate::backend::{ConnectorId, InputEvent, KeyState, ScrollAxis}; use crate::client::{Client, ClientId}; use crate::fixed::Fixed; use crate::ifs::ipc; @@ -113,19 +113,28 @@ impl WlSeatGlobal { pub fn event(self: &Rc, event: InputEvent) { match event { InputEvent::Key(k, s) => self.key_event(k, s), - InputEvent::OutputPosition(o, x, y) => self.output_position_event(o, x, y), + InputEvent::ConnectorPosition(o, x, y) => self.connector_position_event(o, x, y), InputEvent::Motion(dx, dy) => self.motion_event(dx, dy), InputEvent::Button(b, s) => self.pointer_owner.button(self, b, s), InputEvent::Scroll(d, a) => self.pointer_owner.scroll(self, d, a), } } - fn output_position_event(self: &Rc, output: OutputId, mut x: Fixed, mut y: Fixed) { - let output = match self.state.outputs.get(&output) { + fn connector_position_event( + self: &Rc, + connector: ConnectorId, + mut x: Fixed, + mut y: Fixed, + ) { + let output = match self.state.connectors.get(&connector) { Some(o) => o, _ => return, }; - let pos = output.position(); + let node = match output.node.get() { + Some(n) => n, + _ => return, + }; + let pos = node.global.pos.get(); x += Fixed::from_int(pos.x1()); y += Fixed::from_int(pos.y1()); self.set_new_position(x, y); diff --git a/src/ifs/zwlr_layer_shell_v1.rs b/src/ifs/zwlr_layer_shell_v1.rs index 7db0e3bc..cf5efb36 100644 --- a/src/ifs/zwlr_layer_shell_v1.rs +++ b/src/ifs/zwlr_layer_shell_v1.rs @@ -75,11 +75,13 @@ impl ZwlrLayerShellV1 { break 'get_output output; } } - let outputs = self.client.state.outputs.lock(); - match outputs.values().next() { - Some(ou) => ou.node.get().unwrap(), - _ => return Err(GetLayerSurfaceError::NoOutputs), + let outputs = self.client.state.connectors.lock(); + for output in outputs.values() { + if let Some(node) = output.node.get() { + break 'get_output node; + } } + return Err(GetLayerSurfaceError::NoOutputs); } }; log::info!("output = {:?}", output.global.pos.get()); diff --git a/src/logger.rs b/src/logger.rs index beb3c5ae..4830e3a8 100644 --- a/src/logger.rs +++ b/src/logger.rs @@ -1,6 +1,7 @@ use crate::utils::errorfmt::ErrorFmt; use crate::utils::oserror::OsError; use crate::utils::ptr_ext::MutPtrExt; +use backtrace::Backtrace; use bstr::{BStr, BString, ByteSlice}; use log::{Level, Log, Metadata, Record}; use std::cell::UnsafeCell; @@ -29,8 +30,7 @@ impl Logger { Ok(fd) => fd, Err(e) => { let e = OsError::from(e); - eprintln!("Error: Could not dup stderr: {}", ErrorFmt(e)); - std::process::exit(1); + fatal!("Error: Could not dup stderr: {}", ErrorFmt(e)); } }; Self::install(level, b"", file) @@ -55,13 +55,31 @@ impl Logger { Err(Errno(c::EEXIST)) => {} Err(e) => { let e: OsError = e.into(); - eprintln!("Error: Could not create log file: {}", ErrorFmt(e)); - std::process::exit(1); + fatal!("Error: Could not create log file: {}", ErrorFmt(e)); } } } unreachable!(); }; + std::panic::set_hook(Box::new(|p| { + if let Some(loc) = p.location() { + log::error!( + "Panic at {} line {} column {}", + loc.file(), + loc.line(), + loc.column() + ); + } else { + log::error!("Panic at unknown location"); + } + if let Some(msg) = p.payload().downcast_ref::<&str>() { + log::error!("Message: {}", msg); + } + if let Some(msg) = p.payload().downcast_ref::() { + log::error!("Message: {}", msg); + } + log::error!("Backtrace:\n{:?}", Backtrace::new()); + })); Self::install(level, path.as_bytes(), file) } diff --git a/src/main.rs b/src/main.rs index 23cf9940..fbb87e08 100644 --- a/src/main.rs +++ b/src/main.rs @@ -2,7 +2,6 @@ c_variadic, thread_local, label_break_value, - try_blocks, generic_associated_types, extern_types )] diff --git a/src/rect.rs b/src/rect.rs index 0e344be2..2e179c41 100644 --- a/src/rect.rs +++ b/src/rect.rs @@ -17,7 +17,7 @@ pub struct Rect { type Container = SmallVec<[Rect; 1]>; -#[derive(Default, Clone)] +#[derive(Clone)] pub struct Region { rects: Container, extents: Rect, @@ -75,6 +75,7 @@ impl Rect { Some(Self { x1, y1, x2, y2 }) } + #[allow(dead_code)] fn new_unchecked(x1: i32, y1: i32, x2: i32, y2: i32) -> Self { Self { x1, y1, x2, y2 } } diff --git a/src/rect/region.rs b/src/rect/region.rs index 11298dc4..0c8837fd 100644 --- a/src/rect/region.rs +++ b/src/rect/region.rs @@ -1,12 +1,21 @@ use crate::rect::{Container, Rect, Region}; use crate::utils::windows::WindowsExt; +use once_cell::unsync::Lazy; use smallvec::SmallVec; use std::cmp::Ordering; -use std::collections::{BinaryHeap}; +use std::collections::BinaryHeap; use std::mem; use std::ops::Deref; use std::rc::Rc; +#[thread_local] +static EMPTY: Lazy> = Lazy::new(|| { + Rc::new(Region { + rects: Default::default(), + extents: Default::default(), + }) +}); + impl Region { pub fn new(rect: Rect) -> Rc { let mut rects = SmallVec::new(); @@ -17,9 +26,13 @@ impl Region { }) } + pub fn empty() -> Rc { + EMPTY.clone() + } + pub fn from_rects(rects: &[Rect]) -> Rc { if rects.is_empty() { - return Rc::new(Self::default()); + return Self::empty(); } if rects.len() == 1 { return Self::new(rects[0]); @@ -531,13 +544,22 @@ impl Default for BuilderOp { } } -#[derive(Default)] pub struct RegionBuilder { base: Rc, op: BuilderOp, pending: Vec, } +impl Default for RegionBuilder { + fn default() -> Self { + Self { + base: Region::empty(), + op: Default::default(), + pending: Default::default(), + } + } +} + impl RegionBuilder { pub fn add(&mut self, rect: Rect) { self.set_op(BuilderOp::Add); @@ -556,7 +578,7 @@ impl RegionBuilder { pub fn clear(&mut self) { self.pending.clear(); - self.base = Rc::new(Region::default()); + self.base = Region::empty(); } fn set_op(&mut self, op: BuilderOp) { diff --git a/src/state.rs b/src/state.rs index 2f3c29c1..8a91a5cf 100644 --- a/src/state.rs +++ b/src/state.rs @@ -1,7 +1,5 @@ use crate::async_engine::{AsyncEngine, SpawnedFuture}; -use crate::backend::{ - Backend, BackendEvent, InputDevice, InputDeviceId, InputDeviceIds, OutputId, OutputIds, -}; +use crate::backend::{Backend, BackendEvent, Connector, ConnectorId, ConnectorIds, InputDevice, InputDeviceId, InputDeviceIds, MonitorInfo}; use crate::client::{Client, Clients}; use crate::config::ConfigProxy; use crate::cursor::ServerCursors; @@ -9,7 +7,6 @@ use crate::dbus::Dbus; use crate::event_loop::EventLoop; use crate::forker::ForkerProxy; use crate::globals::{Globals, GlobalsError, WaylandGlobal}; -use crate::ifs::wl_output::WlOutputGlobal; use crate::ifs::wl_seat::{SeatIds, WlSeatGlobal}; use crate::ifs::wl_surface::NoneSurfaceExt; use crate::logger::Logger; @@ -25,7 +22,6 @@ use crate::utils::copyhashmap::CopyHashMap; use crate::utils::errorfmt::ErrorFmt; use crate::utils::fdcloser::FdCloser; use crate::utils::linkedlist::LinkedList; -use crate::utils::numcell::NumCell; use crate::utils::queue::AsyncQueue; use crate::wheel::Wheel; use crate::xkbcommon::{XkbContext, XkbKeymap}; @@ -45,9 +41,8 @@ pub struct State { pub cursors: CloneCell>>, pub wheel: Rc, pub clients: Clients, - pub next_name: NumCell, pub globals: Globals, - pub output_ids: OutputIds, + pub connector_ids: ConnectorIds, pub seat_ids: SeatIds, pub input_device_ids: InputDeviceIds, pub node_ids: NodeIds, @@ -55,9 +50,7 @@ pub struct State { pub workspaces: CopyHashMap>, pub dummy_output: CloneCell>>, pub backend_events: AsyncQueue, - pub output_handlers: RefCell>>, pub input_device_handlers: RefCell>, - pub outputs: CopyHashMap>, pub seat_queue: LinkedList>, pub slow_clients: AsyncQueue>, pub none_surface_ext: Rc, @@ -71,6 +64,7 @@ pub struct State { pub dbus: Dbus, pub fdcloser: Arc, pub logger: Arc, + pub connectors: CopyHashMap>, } pub struct InputDeviceData { @@ -84,6 +78,13 @@ pub struct DeviceHandlerData { pub device: Rc, } +pub struct ConnectorData { + pub connector: Rc, + pub monitor_info: CloneCell>>, + pub handler: Cell>>, + pub node: CloneCell>>, +} + impl State { pub fn set_render_ctx(&self, ctx: &Rc) { let cursors = match ServerCursors::load(ctx) { diff --git a/src/tasks.rs b/src/tasks.rs index d9e1e0a2..354f56e7 100644 --- a/src/tasks.rs +++ b/src/tasks.rs @@ -1,6 +1,6 @@ mod backend; +mod connector; mod input_device; -mod output; mod slow_clients; mod start_backend; diff --git a/src/tasks/backend.rs b/src/tasks/backend.rs index e3fc3ce8..c7ce0943 100644 --- a/src/tasks/backend.rs +++ b/src/tasks/backend.rs @@ -1,7 +1,6 @@ -use crate::backend::{BackendEvent, Connector}; +use crate::backend::{BackendEvent}; use crate::state::State; -use crate::tasks::input_device; -use crate::tasks::output::OutputHandler; +use crate::tasks::{connector, input_device}; use std::rc::Rc; pub struct BackendEventHandler { @@ -18,18 +17,8 @@ impl BackendEventHandler { fn handle_event(&mut self, event: BackendEvent) { match event { - BackendEvent::NewConnector(output) => self.handle_new_output(output), + BackendEvent::NewConnector(connector) => connector::handle(&self.state, &connector), BackendEvent::NewInputDevice(s) => input_device::handle(&self.state, s), } } - - fn handle_new_output(&mut self, output: Rc) { - let id = output.id(); - let oh = OutputHandler { - state: self.state.clone(), - output, - }; - let future = self.state.eng.spawn(oh.handle()); - self.state.output_handlers.borrow_mut().insert(id, future); - } } diff --git a/src/tasks/connector.rs b/src/tasks/connector.rs new file mode 100644 index 00000000..82ae4c9e --- /dev/null +++ b/src/tasks/connector.rs @@ -0,0 +1,128 @@ +use crate::backend::{Connector, ConnectorEvent, ConnectorId, MonitorInfo}; +use crate::ifs::wl_output::WlOutputGlobal; +use crate::rect::Rect; +use crate::state::{ConnectorData, State}; +use crate::tree::{OutputNode, OutputRenderData}; +use crate::utils::asyncevent::AsyncEvent; +use crate::utils::clonecell::CloneCell; +use std::cell::RefCell; +use std::rc::Rc; + +pub fn handle(state: &Rc, connector: &Rc) { + let id = connector.id(); + let data = Rc::new(ConnectorData { + connector: connector.clone(), + monitor_info: Default::default(), + handler: Default::default(), + node: Default::default(), + }); + let oh = ConnectorHandler { + id, + state: state.clone(), + data: data.clone(), + }; + let future = state.eng.spawn(oh.handle()); + data.handler.set(Some(future)); + state.connectors.set(id, data); +} + +struct ConnectorHandler { + id: ConnectorId, + state: Rc, + data: Rc, +} + +impl ConnectorHandler { + async fn handle(self) { + let ae = Rc::new(AsyncEvent::default()); + { + let ae = ae.clone(); + self.data.connector.on_change(Rc::new(move || ae.trigger())); + } + if let Some(config) = self.state.config.get() { + config.new_connector(self.id); + } + 'outer: loop { + while let Some(event) = self.data.connector.event() { + match event { + ConnectorEvent::Removed => break 'outer, + ConnectorEvent::Connected(mi) => self.handle_connected(&ae, mi).await, + _ => unreachable!(), + } + } + ae.triggered().await; + } + if let Some(config) = self.state.config.get() { + config.del_connector(self.id); + } + self.data.handler.set(None); + self.state.connectors.remove(&self.id); + } + + async fn handle_connected(&self, ae: &Rc, info: MonitorInfo) { + log::info!("Connector {} connected: {:#?}", self.data.connector.kernel_id(), info); + self.data.monitor_info.set(Some(Rc::new(info.clone()))); + let name = self.state.globals.name(); + let x1 = self + .state + .root + .outputs + .lock() + .values() + .map(|o| o.global.pos.get().x2()) + .max() + .unwrap_or(0); + let global = Rc::new(WlOutputGlobal::new( + name, + &self.data, + x1, + &info.initial_mode, + &info.manufacturer, + &info.product, + info.width_mm, + info.height_mm, + )); + let on = Rc::new(OutputNode { + id: self.state.node_ids.next(), + workspaces: Default::default(), + workspace: CloneCell::new(None), + seat_state: Default::default(), + global: global.clone(), + layers: Default::default(), + render_data: RefCell::new(OutputRenderData { + active_workspace: Rect::new_empty(0, 0), + inactive_workspaces: Default::default(), + titles: Default::default(), + }), + state: self.state.clone(), + is_dummy: false, + }); + self.data.node.set(Some(on.clone())); + global.node.set(Some(on.clone())); + if let Some(config) = self.state.config.get() { + config.connector_connected(self.id); + } + on.ensure_workspace(); + self.state.root.outputs.set(self.id, on.clone()); + self.state.add_global(&global); + 'outer: loop { + while let Some(event) = self.data.connector.event() { + match event { + ConnectorEvent::Disconnected => break 'outer, + ConnectorEvent::ModeChanged(mode) => { + on.update_mode(mode); + } + _ => unreachable!(), + } + } + ae.triggered().await; + } + if let Some(config) = self.state.config.get() { + config.connector_disconnected(self.id); + } + self.data.node.take(); + global.node.set(None); + let _ = self.state.remove_global(&*global); + self.state.root.outputs.remove(&self.id); + } +} diff --git a/src/tasks/output.rs b/src/tasks/output.rs deleted file mode 100644 index 46fa0f4f..00000000 --- a/src/tasks/output.rs +++ /dev/null @@ -1,98 +0,0 @@ -use crate::backend::{Connector, ConnectorEvent}; -use crate::ifs::wl_output::WlOutputGlobal; -use crate::rect::Rect; -use crate::state::State; -use crate::tree::{OutputNode, OutputRenderData, WorkspaceNode}; -use crate::utils::asyncevent::AsyncEvent; -use crate::utils::clonecell::CloneCell; -use std::cell::RefCell; -use std::rc::Rc; - -pub struct OutputHandler { - pub state: Rc, - pub output: Rc, -} - -impl OutputHandler { - pub async fn handle(self) { - let ae = Rc::new(AsyncEvent::default()); - { - let ae = ae.clone(); - self.output.on_change(Rc::new(move || ae.trigger())); - } - let name = self.state.globals.name(); - let x1 = self - .state - .root - .outputs - .lock() - .values() - .map(|o| o.global.pos.get().x2()) - .max() - .unwrap_or(0); - let global = Rc::new(WlOutputGlobal::new(name, self.output.clone(), x1)); - let on = Rc::new(OutputNode { - id: self.state.node_ids.next(), - workspaces: Default::default(), - workspace: CloneCell::new(None), - seat_state: Default::default(), - global: global.clone(), - layers: Default::default(), - render_data: RefCell::new(OutputRenderData { - active_workspace: Rect::new_empty(0, 0), - inactive_workspaces: Default::default(), - titles: Default::default(), - }), - state: self.state.clone(), - is_dummy: false, - }); - global.node.set(Some(on.clone())); - let name = 'name: { - for i in 1.. { - let name = i.to_string(); - if !self.state.workspaces.contains(&name) { - break 'name name; - } - } - unreachable!(); - }; - let workspace = Rc::new(WorkspaceNode { - id: self.state.node_ids.next(), - output: CloneCell::new(on.clone()), - position: Default::default(), - container: Default::default(), - stacked: Default::default(), - seat_state: Default::default(), - name: name.clone(), - output_link: Default::default(), - }); - self.state.workspaces.set(name, workspace.clone()); - workspace - .output_link - .set(Some(on.workspaces.add_last(workspace.clone()))); - on.show_workspace(&workspace); - on.update_render_data(); - self.state.root.outputs.set(self.output.id(), on.clone()); - self.state.add_global(&global); - self.state.outputs.set(self.output.id(), global.clone()); - 'outer: loop { - while let Some(event) = self.output.event() { - match event { - ConnectorEvent::Removed => break 'outer, - ConnectorEvent::ModeChanged(mode) => { - on.update_mode(mode); - } - } - } - ae.triggered().await; - } - global.node.set(None); - self.state.outputs.remove(&self.output.id()); - let _ = self.state.remove_global(&*global); - self.state - .output_handlers - .borrow_mut() - .remove(&self.output.id()); - self.state.root.outputs.remove(&self.output.id()); - } -} diff --git a/src/tree.rs b/src/tree.rs index 47296f5f..5e5fe776 100644 --- a/src/tree.rs +++ b/src/tree.rs @@ -1,4 +1,4 @@ -use crate::backend::{KeyState, OutputId, ScrollAxis}; +use crate::backend::{ConnectorId, KeyState, ScrollAxis}; use crate::client::{Client, ClientId}; use crate::cursor::KnownCursor; use crate::fixed::Fixed; @@ -356,7 +356,7 @@ tree_id!(ToplevelNodeId); pub struct DisplayNode { pub id: NodeId, - pub outputs: CopyHashMap>, + pub outputs: CopyHashMap>, pub stacked: LinkedList>, pub xstacked: LinkedList>, pub seat_state: NodeSeatState, diff --git a/src/tree/output.rs b/src/tree/output.rs index 41a8951c..35f8c4ed 100644 --- a/src/tree/output.rs +++ b/src/tree/output.rs @@ -78,6 +78,37 @@ impl OutputNode { } } + pub fn ensure_workspace(self: &Rc) { + if !self.workspaces.is_empty() { + return; + } + let name = 'name: { + for i in 1.. { + let name = i.to_string(); + if !self.state.workspaces.contains(&name) { + break 'name name; + } + } + unreachable!(); + }; + let workspace = Rc::new(WorkspaceNode { + id: self.state.node_ids.next(), + output: CloneCell::new(self.clone()), + position: Default::default(), + container: Default::default(), + stacked: Default::default(), + seat_state: Default::default(), + name: name.clone(), + output_link: Default::default(), + }); + self.state.workspaces.set(name, workspace.clone()); + workspace + .output_link + .set(Some(self.workspaces.add_last(workspace.clone()))); + self.show_workspace(&workspace); + self.update_render_data(); + } + pub fn show_workspace(&self, ws: &Rc) { self.workspace.set(Some(ws.clone())); ws.clone().change_extents(&self.workspace_rect()); diff --git a/src/utils/linkedlist.rs b/src/utils/linkedlist.rs index 64398ec3..e895fd02 100644 --- a/src/utils/linkedlist.rs +++ b/src/utils/linkedlist.rs @@ -52,6 +52,10 @@ impl LinkedList { } } + pub fn is_empty(&self) -> bool { + self.last().is_none() + } + pub fn last(&self) -> Option> { unsafe { self.endpoint(self.root.data.as_ref().prev.get()) } }