diff --git a/jay-config/src/_private/client.rs b/jay-config/src/_private/client.rs index 8daf7d0d..22fd8561 100644 --- a/jay-config/src/_private/client.rs +++ b/jay-config/src/_private/client.rs @@ -506,6 +506,14 @@ impl Client { self.send(&ClientMessage::SetEnv { key, val }); } + pub fn set_log_level(&self, level: LogLevel) { + self.send(&ClientMessage::SetLogLevel { level }) + } + + pub fn unset_env(&self, key: &str) { + self.send(&ClientMessage::UnsetEnv { key }); + } + pub fn set_status(&self, status: &str) { self.send(&ClientMessage::SetStatus { status }); } @@ -580,6 +588,12 @@ impl Client { self.send(&ClientMessage::DisableDefaultSeat); } + pub fn connector_get_position(&self, connector: Connector) -> (i32, i32) { + let res = self.send_with_response(&ClientMessage::ConnectorGetPosition { connector }); + get_response!(res, (0, 0), ConnectorGetPosition { x, y }); + (x, y) + } + pub fn connector_set_position(&self, connector: Connector, x: i32, y: i32) { self.send(&ClientMessage::ConnectorSetPosition { connector, x, y }); } @@ -595,9 +609,49 @@ impl Client { }); } - pub fn device_connectors(&self, device: DrmDevice) -> Vec { - let res = self.send_with_response(&ClientMessage::GetDeviceConnectors { device }); - get_response!(res, vec![], GetDeviceConnectors { connectors }); + pub fn connector_get_name(&self, connector: Connector) -> String { + let res = self.send_with_response(&ClientMessage::GetConnectorName { connector }); + get_response!(res, String::new(), GetConnectorName { name }); + name + } + + pub fn connector_get_model(&self, connector: Connector) -> String { + let res = self.send_with_response(&ClientMessage::GetConnectorModel { connector }); + get_response!(res, String::new(), GetConnectorModel { model }); + model + } + + pub fn connector_get_manufacturer(&self, connector: Connector) -> String { + let res = self.send_with_response(&ClientMessage::GetConnectorManufacturer { connector }); + get_response!( + res, + String::new(), + GetConnectorManufacturer { manufacturer } + ); + manufacturer + } + + pub fn connector_get_serial_number(&self, connector: Connector) -> String { + let res = self.send_with_response(&ClientMessage::GetConnectorSerialNumber { connector }); + get_response!( + res, + String::new(), + GetConnectorSerialNumber { serial_number } + ); + serial_number + } + + pub fn connectors(&self, device: Option) -> Vec { + if let Some(device) = device { + let res = self.send_with_response(&ClientMessage::GetDeviceConnectors { device }); + get_response!(res, vec![], GetConnectors { connectors }); + return connectors; + } + let res = self.send_with_response(&ClientMessage::GetConnectors { + device, + connected_only: false, + }); + get_response!(res, vec![], GetConnectors { connectors }); connectors } @@ -607,6 +661,12 @@ impl Client { syspath } + pub fn drm_device_devnode(&self, device: DrmDevice) -> String { + let res = self.send_with_response(&ClientMessage::GetDrmDeviceDevnode { device }); + get_response!(res, String::new(), GetDrmDeviceDevnode { devnode }); + devnode + } + pub fn drm_device_vendor(&self, device: DrmDevice) -> String { let res = self.send_with_response(&ClientMessage::GetDrmDeviceVendor { device }); get_response!(res, String::new(), GetDrmDeviceVendor { vendor }); @@ -727,6 +787,22 @@ impl Client { self.on_devices_enumerated.set(Some(Box::new(f))); } + pub fn config_dir(&self) -> String { + let res = self.send_with_response(&ClientMessage::GetConfigDir); + get_response!(res, String::new(), GetConfigDir { dir }); + dir + } + + pub fn workspaces(&self) -> Vec { + let res = self.send_with_response(&ClientMessage::GetWorkspaces); + get_response!(res, vec![], GetWorkspaces { workspaces }); + workspaces + } + + pub fn set_idle(&self, timeout: Duration) { + self.send(&ClientMessage::SetIdle { timeout }) + } + pub fn set_seat(&self, device: InputDevice, seat: Seat) { self.send(&ClientMessage::SetSeat { device, seat }) } @@ -776,6 +852,18 @@ impl Client { name } + pub fn input_device_syspath(&self, device: InputDevice) -> String { + let res = self.send_with_response(&ClientMessage::GetInputDeviceSyspath { device }); + get_response!(res, String::new(), GetInputDeviceSyspath { syspath }); + syspath + } + + pub fn input_device_devnode(&self, device: InputDevice) -> String { + let res = self.send_with_response(&ClientMessage::GetInputDeviceDevnode { device }); + get_response!(res, String::new(), GetInputDeviceDevnode { devnode }); + devnode + } + pub fn has_capability(&self, device: InputDevice, cap: Capability) -> bool { let res = self.send_with_response(&ClientMessage::HasCapability { device, cap }); get_response!(res, false, HasCapability { has }); diff --git a/jay-config/src/_private/ipc.rs b/jay-config/src/_private/ipc.rs index b6d485c3..377bd3bc 100644 --- a/jay-config/src/_private/ipc.rs +++ b/jay-config/src/_private/ipc.rs @@ -385,6 +385,45 @@ pub enum ClientMessage<'a> { DestroyKeymap { keymap: Keymap, }, + GetConnectorName { + connector: Connector, + }, + GetConnectorModel { + connector: Connector, + }, + GetConnectorManufacturer { + connector: Connector, + }, + GetConnectorSerialNumber { + connector: Connector, + }, + GetConnectors { + device: Option, + connected_only: bool, + }, + ConnectorGetPosition { + connector: Connector, + }, + GetConfigDir, + GetWorkspaces, + UnsetEnv { + key: &'a str, + }, + SetLogLevel { + level: LogLevel, + }, + GetDrmDeviceDevnode { + device: DrmDevice, + }, + GetInputDeviceSyspath { + device: InputDevice, + }, + GetInputDeviceDevnode { + device: InputDevice, + }, + SetIdle { + timeout: Duration, + }, } #[derive(Serialize, Deserialize, Debug)] @@ -444,7 +483,7 @@ pub enum Response { GetFullscreen { fullscreen: bool, }, - GetDeviceConnectors { + GetConnectors { connectors: Vec, }, GetDrmDeviceSyspath { @@ -493,6 +532,37 @@ pub enum Response { AddPollable { id: Result, }, + GetConnectorName { + name: String, + }, + GetConnectorModel { + model: String, + }, + GetConnectorManufacturer { + manufacturer: String, + }, + GetConnectorSerialNumber { + serial_number: String, + }, + ConnectorGetPosition { + x: i32, + y: i32, + }, + GetConfigDir { + dir: String, + }, + GetWorkspaces { + workspaces: Vec, + }, + GetDrmDeviceDevnode { + devnode: String, + }, + GetInputDeviceSyspath { + syspath: String, + }, + GetInputDeviceDevnode { + devnode: String, + }, } #[derive(Serialize, Deserialize, Debug)] diff --git a/jay-config/src/exec.rs b/jay-config/src/exec.rs index f1c2e2ee..4c858900 100644 --- a/jay-config/src/exec.rs +++ b/jay-config/src/exec.rs @@ -9,6 +9,13 @@ pub fn set_env(key: &str, val: &str) { get!().set_env(key, val); } +/// Unsets an environment variable. +/// +/// This does not affect the compositor itself but only programs spawned by the compositor. +pub fn unset_env(key: &str) { + get!().unset_env(key); +} + /// A command to be spawned. pub struct Command { pub(crate) prog: String, diff --git a/jay-config/src/input.rs b/jay-config/src/input.rs index d3c5091f..58666aee 100644 --- a/jay-config/src/input.rs +++ b/jay-config/src/input.rs @@ -113,6 +113,20 @@ impl InputDevice { pub fn set_natural_scrolling_enabled(self, enabled: bool) { get!().set_input_natural_scrolling_enabled(self, enabled); } + + /// Returns the syspath of this device. + /// + /// E.g. `/sys/devices/pci0000:00/0000:00:08.1/0000:14:00.4/usb5/5-1/5-1.1/5-1.1.3/5-1.1.3:1.0`. + pub fn syspath(self) -> String { + get!(String::new()).input_device_syspath(self) + } + + /// Returns the devnode of this device. + /// + /// E.g. `/dev/input/event7`. + pub fn devnode(self) -> String { + get!(String::new()).input_device_devnode(self) + } } /// A seat. diff --git a/jay-config/src/lib.rs b/jay-config/src/lib.rs index 5630744a..5881821c 100644 --- a/jay-config/src/lib.rs +++ b/jay-config/src/lib.rs @@ -42,7 +42,10 @@ use { crate::keyboard::ModifiedKeySym, serde::{Deserialize, Serialize}, - std::fmt::{Debug, Display, Formatter}, + std::{ + fmt::{Debug, Display, Formatter}, + time::Duration, + }, }; #[macro_use] @@ -195,3 +198,20 @@ pub fn on_idle(f: F) { pub fn on_devices_enumerated(f: F) { get!().on_devices_enumerated(f) } + +/// Returns the Jay config directory. +pub fn config_dir() -> String { + get!().config_dir() +} + +/// Returns all visible workspaces. +pub fn workspaces() -> Vec { + get!().workspaces() +} + +/// Configures the idle timeout. +/// +/// `None` disables the timeout. +pub fn set_idle(timeout: Option) { + get!().set_idle(timeout.unwrap_or_default()) +} diff --git a/jay-config/src/logging.rs b/jay-config/src/logging.rs index 4acdcfeb..1f37aa46 100644 --- a/jay-config/src/logging.rs +++ b/jay-config/src/logging.rs @@ -14,3 +14,8 @@ pub enum LogLevel { Debug, Trace, } + +/// Sets the log level of the compositor. +pub fn set_log_level(level: LogLevel) { + get!().set_log_level(level); +} diff --git a/jay-config/src/theme.rs b/jay-config/src/theme.rs index d953344f..3cbc2331 100644 --- a/jay-config/src/theme.rs +++ b/jay-config/src/theme.rs @@ -15,7 +15,7 @@ use serde::{Deserialize, Serialize}; /// /// When using hexadecimal notation, `#RRGGBBAA`, the RGB values are usually straight. // values are stored premultiplied -#[derive(Serialize, Deserialize, Debug)] +#[derive(Serialize, Deserialize, Debug, Copy, Clone)] pub struct Color { r: f32, g: f32, @@ -32,13 +32,13 @@ fn to_u8(c: f32) -> u8 { } fn validate_f32(f: f32) -> bool { - f.is_normal() && f >= 0.0 && f <= 1.0 + f >= 0.0 && f <= 1.0 } fn validate_f32_all(f: [f32; 4]) -> bool { if !f.into_iter().all(validate_f32) { log::warn!( - "f32 values {:?} are not in the valid color range. Using solid black instead", + "f32 values {:?} are not in the valid color range. Using solid black instead xyz", f ); return false; @@ -72,7 +72,7 @@ impl Color { /// Creates a new color from premultiplied `f32` RGBA values. pub fn new_f32_premultiplied(r: f32, g: f32, b: f32, a: f32) -> Self { - if validate_f32_all([r, g, b, a]) { + if !validate_f32_all([r, g, b, a]) { Self::BLACK } else if r > a || g > a || b > a { log::warn!("f32 values {:?} are not valid valid for a premultiplied color. Using solid black instead.", [r, g, b, a]); @@ -84,7 +84,7 @@ impl Color { /// Creates a new color from straight `f32` RGBA values. pub fn new_f32_straight(r: f32, g: f32, b: f32, a: f32) -> Self { - if validate_f32_all([r, g, b, a]) { + if !validate_f32_all([r, g, b, a]) { Self::BLACK } else { Self { diff --git a/jay-config/src/video.rs b/jay-config/src/video.rs index c01fa2bf..acbd2601 100644 --- a/jay-config/src/video.rs +++ b/jay-config/src/video.rs @@ -178,6 +178,14 @@ impl Connector { self.mode().refresh_millihz } + /// Retrieves the position of the output in the global compositor space. + pub fn position(self) -> (i32, i32) { + if !self.connected() { + return (0, 0); + } + get!().connector_get_position(self) + } + /// Sets the position of the connector in the global compositor space. /// /// `x` and `y` must be non-negative and must not exceed a currently unspecified limit. @@ -212,6 +220,34 @@ impl Connector { } get!().connector_set_transform(self, transform); } + + pub fn name(self) -> String { + if !self.exists() { + return String::new(); + } + get!(String::new()).connector_get_name(self) + } + + pub fn model(self) -> String { + if !self.exists() { + return String::new(); + } + get!(String::new()).connector_get_model(self) + } + + pub fn manufacturer(self) -> String { + if !self.exists() { + return String::new(); + } + get!(String::new()).connector_get_manufacturer(self) + } + + pub fn serial_number(self) -> String { + if !self.exists() { + return String::new(); + } + get!(String::new()).connector_get_serial_number(self) + } } /// Returns all available DRM devices. @@ -247,6 +283,10 @@ pub fn on_graphics_initialized(f: F) { get!().on_graphics_initialized(f) } +pub fn connectors() -> Vec { + get!().connectors(None) +} + /// Returns the connector with the given id. /// /// The linux kernel identifies connectors by a (type, idx) tuple, e.g., `DP-0`. @@ -381,7 +421,14 @@ pub struct DrmDevice(pub u64); impl DrmDevice { /// Returns the connectors of this device. pub fn connectors(self) -> Vec { - get!().device_connectors(self) + get!().connectors(Some(self)) + } + + /// Returns the devnode of this device. + /// + /// E.g. `/dev/dri/card0`. + pub fn devnode(self) -> String { + get!().drm_device_devnode(self) } /// Returns the syspath of this device. diff --git a/src/config/handler.rs b/src/config/handler.rs index e1eccb22..fe76c274 100644 --- a/src/config/handler.rs +++ b/src/config/handler.rs @@ -183,13 +183,26 @@ impl ConfigProxyHandler { res } - fn handle_get_drm_device_connectors(&self, dev: DrmDevice) -> Result<(), CphError> { - let dev = self.get_drm_device(dev)?; - let mut connectors = vec![]; - for c in dev.connectors.lock().values() { - connectors.push(Connector(c.connector.id().raw() as _)); + fn handle_get_connectors( + &self, + dev: Option, + connected_only: bool, + ) -> Result<(), CphError> { + let datas: Vec<_>; + if let Some(dev) = dev { + let dev = self.get_drm_device(dev)?; + datas = dev.connectors.lock().values().cloned().collect(); + } else { + datas = self.state.connectors.lock().values().cloned().collect(); } - self.respond(Response::GetDeviceConnectors { connectors }); + let connectors = datas + .iter() + .flat_map(|d| match (connected_only, d.connected.get()) { + (false, _) | (true, true) => Some(Connector(d.connector.id().raw() as _)), + _ => None, + }) + .collect(); + self.respond(Response::GetConnectors { connectors }); Ok(()) } @@ -200,6 +213,13 @@ impl ConfigProxyHandler { Ok(()) } + fn handle_get_drm_device_devnode(&self, dev: DrmDevice) -> Result<(), CphError> { + let dev = self.get_drm_device(dev)?; + let devnode = dev.devnode.clone().unwrap_or_default(); + self.respond(Response::GetDrmDeviceDevnode { devnode }); + Ok(()) + } + fn handle_get_drm_device_vendor(&self, dev: DrmDevice) -> Result<(), CphError> { let dev = self.get_drm_device(dev)?; let vendor = dev.vendor.clone().unwrap_or_default(); @@ -304,6 +324,35 @@ impl ConfigProxyHandler { } } + fn handle_unset_env(&self, key: &str) { + if let Some(f) = self.state.forker.get() { + f.unsetenv(key.as_bytes()); + } + } + + fn handle_get_config_dir(&self) { + let dir = self.state.config_dir.clone().unwrap_or_default(); + self.respond(Response::GetConfigDir { dir }); + } + + fn handle_get_workspaces(&self) { + let mut workspaces = vec![]; + for ws in self.state.workspaces.lock().values() { + let id = match self.workspaces_by_name.get(&ws.name) { + None => { + let id = self.workspace_ids.fetch_add(1); + let name = Rc::new(ws.name.clone()); + self.workspaces_by_name.set(name.clone(), id); + self.workspaces_by_id.set(id, name); + id + } + Some(id) => id, + }; + workspaces.push(Workspace(id)); + } + self.respond(Response::GetWorkspaces { workspaces }); + } + fn handle_program_timer( &self, timer: JayTimer, @@ -688,6 +737,26 @@ impl ConfigProxyHandler { Ok(()) } + fn handle_get_input_device_syspath(&self, device: InputDevice) -> Result<(), CphError> { + let dev = self.get_device_handler_data(device)?; + self.respond(Response::GetInputDeviceSyspath { + syspath: dev.syspath.clone().unwrap_or_default(), + }); + Ok(()) + } + + fn handle_get_input_device_devnode(&self, device: InputDevice) -> Result<(), CphError> { + let dev = self.get_device_handler_data(device)?; + self.respond(Response::GetInputDeviceDevnode { + devnode: dev.devnode.clone().unwrap_or_default(), + }); + Ok(()) + } + + fn handle_set_idle(&self, timeout: Duration) { + self.state.idle.set_timeout(timeout); + } + fn handle_connector_connected(&self, connector: Connector) -> Result<(), CphError> { let connector = self.get_connector(connector)?; self.respond(Response::ConnectorConnected { @@ -747,6 +816,38 @@ impl ConfigProxyHandler { Ok(()) } + fn handle_connector_name(&self, connector: Connector) -> Result<(), CphError> { + let connector = self.get_connector(connector)?; + self.respond(Response::GetConnectorName { + name: connector.name.clone(), + }); + Ok(()) + } + + fn handle_connector_model(&self, connector: Connector) -> Result<(), CphError> { + let connector = self.get_output(connector)?; + self.respond(Response::GetConnectorModel { + model: connector.monitor_info.product.clone(), + }); + Ok(()) + } + + fn handle_connector_manufacturer(&self, connector: Connector) -> Result<(), CphError> { + let connector = self.get_output(connector)?; + self.respond(Response::GetConnectorManufacturer { + manufacturer: connector.monitor_info.manufacturer.clone(), + }); + Ok(()) + } + + fn handle_connector_serial_number(&self, connector: Connector) -> Result<(), CphError> { + let connector = self.get_output(connector)?; + self.respond(Response::GetConnectorSerialNumber { + serial_number: connector.monitor_info.serial_number.clone(), + }); + Ok(()) + } + fn handle_set_cursor_size(&self, seat: Seat, size: i32) -> Result<(), CphError> { let seat = self.get_seat(seat)?; if size < 0 { @@ -848,6 +949,13 @@ impl ConfigProxyHandler { Ok(()) } + fn handle_connector_get_position(&self, connector: Connector) -> Result<(), CphError> { + let connector = self.get_output(connector)?; + let (x, y) = connector.node.global.pos.get().position(); + self.respond(Response::ConnectorGetPosition { x, y }); + Ok(()) + } + fn handle_connector_set_enabled( &self, connector: Connector, @@ -1011,6 +1119,19 @@ impl ConfigProxyHandler { Ok(()) } + fn handle_set_log_level(&self, level: LogLevel) { + let level = match level { + LogLevel::Error => Level::Error, + LogLevel::Warn => Level::Warn, + LogLevel::Info => Level::Info, + LogLevel::Debug => Level::Debug, + LogLevel::Trace => Level::Trace, + }; + if let Some(logger) = &self.state.logger { + logger.set_level(level); + } + } + fn handle_grab(&self, kb: InputDevice, grab: bool) -> Result<(), CphError> { let kb = self.get_kb(kb)?; kb.grab(grab); @@ -1403,7 +1524,7 @@ impl ConfigProxyHandler { } ClientMessage::Reload => self.handle_reload(), ClientMessage::GetDeviceConnectors { device } => self - .handle_get_drm_device_connectors(device) + .handle_get_connectors(Some(device), false) .wrn("get_device_connectors")?, ClientMessage::GetDrmDeviceSyspath { device } => self .handle_get_drm_device_syspath(device) @@ -1520,6 +1641,41 @@ impl ConfigProxyHandler { } => self.handle_run(prog, args, env, fds).wrn("run")?, ClientMessage::DisableDefaultSeat => self.state.create_default_seat.set(false), ClientMessage::DestroyKeymap { keymap } => self.handle_destroy_keymap(keymap), + ClientMessage::GetConnectorName { connector } => self + .handle_connector_name(connector) + .wrn("connector_name")?, + ClientMessage::GetConnectorModel { connector } => self + .handle_connector_model(connector) + .wrn("connector_model")?, + ClientMessage::GetConnectorManufacturer { connector } => self + .handle_connector_manufacturer(connector) + .wrn("connector_manufacturer")?, + ClientMessage::GetConnectorSerialNumber { connector } => self + .handle_connector_serial_number(connector) + .wrn("connector_serial_number")?, + ClientMessage::GetConnectors { + device, + connected_only, + } => self + .handle_get_connectors(device, connected_only) + .wrn("get_connectors")?, + ClientMessage::ConnectorGetPosition { connector } => self + .handle_connector_get_position(connector) + .wrn("connector_get_position")?, + ClientMessage::GetConfigDir => self.handle_get_config_dir(), + ClientMessage::GetWorkspaces => self.handle_get_workspaces(), + ClientMessage::UnsetEnv { key } => self.handle_unset_env(key), + ClientMessage::SetLogLevel { level } => self.handle_set_log_level(level), + ClientMessage::GetDrmDeviceDevnode { device } => self + .handle_get_drm_device_devnode(device) + .wrn("get_drm_device_devnode")?, + ClientMessage::GetInputDeviceSyspath { device } => self + .handle_get_input_device_syspath(device) + .wrn("get_input_device_syspath")?, + ClientMessage::GetInputDeviceDevnode { device } => self + .handle_get_input_device_devnode(device) + .wrn("get_input_device_devnode")?, + ClientMessage::SetIdle { timeout } => self.handle_set_idle(timeout), } Ok(()) } diff --git a/src/ifs/jay_input.rs b/src/ifs/jay_input.rs index 560b301a..765a25ef 100644 --- a/src/ifs/jay_input.rs +++ b/src/ifs/jay_input.rs @@ -204,8 +204,8 @@ impl JayInput { .map(|s| s.seat_name()) .unwrap_or_default(), id: data.id.raw(), - syspath: data.syspath.as_deref().unwrap_or_default(), - devnode: data.devnode.as_deref().unwrap_or_default(), + syspath: data.data.syspath.as_deref().unwrap_or_default(), + devnode: data.data.devnode.as_deref().unwrap_or_default(), name: dev.name().as_str(), capabilities: &caps, accel_available: accel_profile.is_some() as _, diff --git a/src/state.rs b/src/state.rs index 31837998..66942fe2 100644 --- a/src/state.rs +++ b/src/state.rs @@ -216,14 +216,14 @@ pub struct InputDeviceData { pub id: InputDeviceId, pub data: Rc, pub async_event: Rc, - pub syspath: Option, - pub devnode: Option, } pub struct DeviceHandlerData { pub seat: CloneCell>>, pub px_per_scroll_wheel: Cell, pub device: Rc, + pub syspath: Option, + pub devnode: Option, } pub struct ConnectorData { diff --git a/src/tasks/input_device.rs b/src/tasks/input_device.rs index 7b57f15d..b2c84210 100644 --- a/src/tasks/input_device.rs +++ b/src/tasks/input_device.rs @@ -11,10 +11,16 @@ use { }; pub fn handle(state: &Rc, dev: Rc) { + let props = match dev.dev_t() { + None => UdevProps::default(), + Some(dev_t) => udev_props(dev_t, 3), + }; let data = Rc::new(DeviceHandlerData { seat: Default::default(), px_per_scroll_wheel: Cell::new(PX_PER_SCROLL), device: dev.clone(), + syspath: props.syspath, + devnode: props.devnode, }); let ae = Rc::new(AsyncEvent::default()); let oh = DeviceHandler { @@ -24,10 +30,6 @@ pub fn handle(state: &Rc, dev: Rc) { ae: ae.clone(), }; let handler = state.eng.spawn(oh.handle()); - let props = match dev.dev_t() { - None => UdevProps::default(), - Some(dev_t) => udev_props(dev_t, 3), - }; state.input_device_handlers.borrow_mut().insert( dev.id(), InputDeviceData { @@ -35,8 +37,6 @@ pub fn handle(state: &Rc, dev: Rc) { id: dev.id(), data, async_event: ae, - syspath: props.syspath, - devnode: props.devnode, }, ); }