diff --git a/Cargo.lock b/Cargo.lock index bd6e0988..486c7d6f 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -123,6 +123,19 @@ version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" +[[package]] +name = "chrono" +version = "0.4.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "670ad68c9088c2a963aaa298cb369688cf3f9465ce5e2d4ca10e6e0098a1ce73" +dependencies = [ + "libc", + "num-integer", + "num-traits", + "time", + "winapi", +] + [[package]] name = "clap" version = "3.1.6" @@ -167,6 +180,7 @@ dependencies = [ name = "default-config" version = "0.1.0" dependencies = [ + "chrono", "jay-config", "log", "rand", @@ -387,6 +401,16 @@ dependencies = [ "syn", ] +[[package]] +name = "num-integer" +version = "0.1.44" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d2cc698a63b549a70bc047073d2949cce27cd1c7b0a4a862d08a8031bc2801db" +dependencies = [ + "autocfg", + "num-traits", +] + [[package]] name = "num-traits" version = "0.2.14" @@ -663,6 +687,17 @@ dependencies = [ "syn", ] +[[package]] +name = "time" +version = "0.1.44" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6db9e6914ab8b1ae1c260a4ae7a49b6c5611b40328a735b21862567685e73255" +dependencies = [ + "libc", + "wasi", + "winapi", +] + [[package]] name = "uapi" version = "0.2.7" diff --git a/default-config/Cargo.toml b/default-config/Cargo.toml index 35f0633e..77fb8f22 100644 --- a/default-config/Cargo.toml +++ b/default-config/Cargo.toml @@ -10,3 +10,4 @@ crate-type = ["lib", "cdylib"] jay-config = { path = "../jay-config" } log = "0.4.14" rand = "0.8.5" +chrono = "0.4.19" diff --git a/default-config/src/lib.rs b/default-config/src/lib.rs index 315979be..f1feba53 100644 --- a/default-config/src/lib.rs +++ b/default-config/src/lib.rs @@ -1,25 +1,31 @@ -use jay_config::{ - config, - drm::{get_connector, on_connector_connected, on_new_connector}, - embedded::grab_input_device, - get_workspace, - input::{ - capability::{CAP_KEYBOARD, CAP_POINTER}, - create_seat, input_devices, on_new_input_device, InputDevice, Seat, - }, - keyboard::{ - mods::{Modifiers, ALT, CTRL, SHIFT}, - syms::{ - SYM_Super_L, SYM_b, SYM_c, SYM_d, SYM_f, SYM_h, SYM_j, SYM_k, SYM_l, SYM_m, SYM_p, - SYM_q, SYM_t, SYM_v, SYM_y, SYM_F1, SYM_F10, SYM_F11, SYM_F12, SYM_F13, SYM_F14, - SYM_F15, SYM_F16, SYM_F17, SYM_F18, SYM_F19, SYM_F2, SYM_F20, SYM_F21, SYM_F22, - SYM_F23, SYM_F24, SYM_F25, SYM_F3, SYM_F4, SYM_F5, SYM_F6, SYM_F7, SYM_F8, SYM_F9, +use { + chrono::{format::StrftimeItems, Local, Timelike}, + jay_config::{ + config, + drm::{get_connector, on_connector_connected, on_new_connector}, + embedded::grab_input_device, + get_timer, get_workspace, + input::{ + capability::{CAP_KEYBOARD, CAP_POINTER}, + create_seat, input_devices, on_new_input_device, InputDevice, Seat, }, + keyboard::{ + mods::{Modifiers, ALT, CTRL, SHIFT}, + syms::{ + SYM_Super_L, SYM_b, SYM_c, SYM_d, SYM_f, SYM_h, SYM_j, SYM_k, SYM_l, SYM_m, SYM_p, + SYM_q, SYM_t, SYM_v, SYM_y, SYM_F1, SYM_F10, SYM_F11, SYM_F12, SYM_F13, SYM_F14, + SYM_F15, SYM_F16, SYM_F17, SYM_F18, SYM_F19, SYM_F2, SYM_F20, SYM_F21, SYM_F22, + SYM_F23, SYM_F24, SYM_F25, SYM_F3, SYM_F4, SYM_F5, SYM_F6, SYM_F7, SYM_F8, SYM_F9, + }, + }, + quit, + status::set_status, + switch_to_vt, + Axis::{Horizontal, Vertical}, + Command, + Direction::{Down, Left, Right, Up}, }, - quit, switch_to_vt, - Axis::{Horizontal, Vertical}, - Command, - Direction::{Down, Left, Right, Up}, + std::time::Duration, }; const MOD: Modifiers = ALT; @@ -117,6 +123,28 @@ pub fn configure() { on_new_connector(move |_| handle_connectors_changed()); on_connector_connected(move |_| handle_connectors_changed()); handle_connectors_changed(); + + { + let time_format: Vec<_> = StrftimeItems::new("%Y-%m-%d %H:%M:%S").collect(); + let update_status = move || { + let status = format!( + "{}", + Local::now().format_with_items(time_format.iter()), + ); + set_status(&status); + }; + update_status(); + let initial = { + let now = Local::now(); + 5000 - (now.second() * 1000 + now.timestamp_subsec_millis()) % 5000 + }; + let timer = get_timer("status_timer"); + timer.program( + Duration::from_millis(initial as u64), + Some(Duration::from_secs(5)), + ); + timer.on_tick(update_status); + } } config!(configure); diff --git a/jay-config/src/_private/client.rs b/jay-config/src/_private/client.rs index 476465ce..2959a9d6 100644 --- a/jay-config/src/_private/client.rs +++ b/jay-config/src/_private/client.rs @@ -14,7 +14,7 @@ use { input::{acceleration::AccelProfile, capability::Capability, InputDevice, Seat}, keyboard::keymap::Keymap, theme::Color, - Axis, Command, Direction, LogLevel, ModifiedKeySym, Workspace, + Axis, Command, Direction, LogLevel, ModifiedKeySym, Timer, Workspace, }, std::{ cell::{Cell, RefCell}, @@ -23,6 +23,7 @@ use { ptr, rc::Rc, slice, + time::Duration, }, }; @@ -32,6 +33,7 @@ pub(crate) struct Client { srv_unref: unsafe extern "C" fn(data: *const u8), srv_handler: unsafe extern "C" fn(data: *const u8, msg: *const u8, size: usize), key_handlers: RefCell>>, + timer_handlers: RefCell>>, response: RefCell>, on_new_seat: RefCell>>, on_new_input_device: RefCell>>, @@ -113,6 +115,7 @@ pub unsafe extern "C" fn init( srv_unref, srv_handler, key_handlers: Default::default(), + timer_handlers: Default::default(), response: Default::default(), on_new_seat: Default::default(), on_new_input_device: Default::default(), @@ -222,6 +225,33 @@ impl Client { mono } + pub fn get_timer(&self, name: &str) -> Timer { + let res = self.with_response(|| self.send(&ClientMessage::GetTimer { name })); + get_response!(res, Timer(0), GetTimer, timer); + timer + } + + pub fn remove_timer(&self, timer: Timer) { + self.send(&ClientMessage::RemoveTimer { timer }); + } + + pub fn program_timer( + &self, + timer: Timer, + initial: Option, + periodic: Option, + ) { + self.send(&ClientMessage::ProgramTimer { + timer, + initial, + periodic, + }); + } + + pub fn on_timer_tick(&self, timer: Timer, f: F) { + self.timer_handlers.borrow_mut().insert(timer, Rc::new(f)); + } + pub fn get_workspace(&self, name: &str) -> Workspace { let res = self.with_response(|| self.send(&ClientMessage::GetWorkspace { name })); get_response!(res, Workspace(0), GetWorkspace, workspace); @@ -288,6 +318,10 @@ impl Client { self.send(&ClientMessage::SetMono { seat, mono }); } + pub fn set_status(&self, status: &str) { + self.send(&ClientMessage::SetStatus { status }); + } + pub fn set_split(&self, seat: Seat, axis: Axis) { self.send(&ClientMessage::SetSplit { seat, axis }); } @@ -507,6 +541,12 @@ impl Client { } } ServerMessage::DelConnector { .. } => {} + ServerMessage::TimerExpired { timer } => { + let handler = self.timer_handlers.borrow_mut().get(&timer).cloned(); + if let Some(handler) = handler { + handler(); + } + } } } diff --git a/jay-config/src/_private/ipc.rs b/jay-config/src/_private/ipc.rs index 94676499..fcf9fb26 100644 --- a/jay-config/src/_private/ipc.rs +++ b/jay-config/src/_private/ipc.rs @@ -4,9 +4,10 @@ use { input::{acceleration::AccelProfile, capability::Capability, InputDevice, Seat}, keyboard::{keymap::Keymap, mods::Modifiers, syms::KeySym}, theme::Color, - Axis, Direction, LogLevel, Workspace, + Axis, Direction, LogLevel, Timer, Workspace, }, bincode::{BorrowDecode, Decode, Encode}, + std::time::Duration, }; #[derive(Encode, BorrowDecode, Debug)] @@ -38,6 +39,9 @@ pub enum ServerMessage { mods: Modifiers, sym: KeySym, }, + TimerExpired { + timer: Timer, + }, } #[derive(Encode, BorrowDecode, Debug)] @@ -77,6 +81,9 @@ pub enum ClientMessage<'a> { GetSplit { seat: Seat, }, + SetStatus { + status: &'a str, + }, SetSplit { seat: Seat, axis: Axis, @@ -203,6 +210,17 @@ pub enum ClientMessage<'a> { seat: Seat, workspace: Workspace, }, + GetTimer { + name: &'a str, + }, + RemoveTimer { + timer: Timer, + }, + ProgramTimer { + timer: Timer, + initial: Option, + periodic: Option, + }, } #[derive(Encode, Decode, Debug)] @@ -242,6 +260,9 @@ pub enum Response { GetDeviceName { name: String, }, + GetTimer { + timer: Timer, + }, GetWorkspace { workspace: Workspace, }, diff --git a/jay-config/src/lib.rs b/jay-config/src/lib.rs index 7403ac5f..7df35cfd 100644 --- a/jay-config/src/lib.rs +++ b/jay-config/src/lib.rs @@ -1,7 +1,7 @@ use { crate::keyboard::{keymap::Keymap, ModifiedKeySym}, bincode::{Decode, Encode}, - std::collections::HashMap, + std::{collections::HashMap, time::Duration}, }; #[macro_use] @@ -12,6 +12,7 @@ pub mod drm; pub mod embedded; pub mod input; pub mod keyboard; +pub mod status; pub mod theme; #[derive(Encode, Decode, Copy, Clone, Debug)] @@ -91,3 +92,28 @@ pub struct Workspace(pub u64); pub fn get_workspace(name: &str) -> Workspace { get!(Workspace(0)).get_workspace(name) } + +#[derive(Encode, Decode, Copy, Clone, Debug, Hash, Eq, PartialEq)] +pub struct Timer(pub u64); + +pub fn get_timer(name: &str) -> Timer { + get!(Timer(0)).get_timer(name) +} + +impl Timer { + pub fn program(self, initial: Duration, periodic: Option) { + get!().program_timer(self, Some(initial), periodic); + } + + pub fn cancel(self) { + get!().program_timer(self, None, None); + } + + pub fn remove(self) { + get!().remove_timer(self); + } + + pub fn on_tick(self, f: F) { + get!().on_timer_tick(self, f); + } +} diff --git a/jay-config/src/status.rs b/jay-config/src/status.rs new file mode 100644 index 00000000..05d4e254 --- /dev/null +++ b/jay-config/src/status.rs @@ -0,0 +1,3 @@ +pub fn set_status(status: &str) { + get!().set_status(status); +} diff --git a/src/async_engine.rs b/src/async_engine.rs index 2a7b47bd..dbe90ffc 100644 --- a/src/async_engine.rs +++ b/src/async_engine.rs @@ -3,11 +3,12 @@ pub use { fd::{AsyncFd, FdStatus}, task::SpawnedFuture, timeout::Timeout, + timer::Timer, }; use { crate::{ event_loop::{EventLoop, EventLoopError}, - utils::{copyhashmap::CopyHashMap, numcell::NumCell}, + utils::{copyhashmap::CopyHashMap, numcell::NumCell, oserror::OsError}, wheel::{Wheel, WheelError}, }, fd::AsyncFdData, @@ -19,15 +20,21 @@ use { }, thiserror::Error, timeout::TimeoutData, - uapi::OwnedFd, + uapi::{c, OwnedFd}, }; #[derive(Debug, Error)] pub enum AsyncError { - #[error("The timer wheel returned an error: {0}")] + #[error("The timer wheel returned an error")] WheelError(#[from] WheelError), - #[error("The event loop caused an error: {0}")] + #[error("The event loop caused an error")] EventLoopError(#[from] EventLoopError), + #[error("Could not read from a timer")] + TimerReadError(#[source] OsError), + #[error("Could not set a timer")] + SetTimer(#[source] OsError), + #[error("Could not create a timer")] + CreateTimer(#[source] OsError), } #[derive(Copy, Clone, Eq, PartialEq)] @@ -71,6 +78,10 @@ impl AsyncEngine { }) } + pub fn timer(self: &Rc, clock_id: c::c_int) -> Result { + Timer::new(self, clock_id) + } + pub fn spawn + 'static>(&self, f: F) -> SpawnedFuture { self.queue.spawn(Phase::EventHandling, f) } @@ -146,6 +157,59 @@ mod yield_ { } } +mod timer { + use { + crate::async_engine::{AsyncEngine, AsyncError, AsyncFd}, + std::{rc::Rc, time::Duration}, + uapi::c, + }; + + #[derive(Clone)] + pub struct Timer { + fd: AsyncFd, + } + + impl Timer { + pub(super) fn new(eng: &Rc, clock_id: c::c_int) -> Result { + let fd = match uapi::timerfd_create(clock_id, c::TFD_CLOEXEC | c::TFD_NONBLOCK) { + Ok(fd) => fd, + Err(e) => return Err(AsyncError::CreateTimer(e.into())), + }; + let afd = eng.fd(&Rc::new(fd))?; + Ok(Self { fd: afd }) + } + + pub async fn expired(&self) -> Result { + self.fd.readable().await?; + let mut buf = 0u64; + if let Err(e) = uapi::read(self.fd.raw(), &mut buf) { + return Err(AsyncError::TimerReadError(e.into())); + } + Ok(buf) + } + + pub fn program( + &self, + initial: Option, + periodic: Option, + ) -> Result<(), AsyncError> { + let mut timerspec: c::itimerspec = uapi::pod_zeroed(); + if let Some(init) = initial { + timerspec.it_value.tv_sec = init.as_secs() as _; + timerspec.it_value.tv_nsec = init.subsec_nanos() as _; + if let Some(per) = periodic { + timerspec.it_interval.tv_sec = per.as_secs() as _; + timerspec.it_interval.tv_nsec = per.subsec_nanos() as _; + } + } + if let Err(e) = uapi::timerfd_settime(self.fd.raw(), 0, &timerspec) { + return Err(AsyncError::SetTimer(e.into())); + } + Ok(()) + } + } +} + mod timeout { use { crate::wheel::{Wheel, WheelDispatcher, WheelId}, diff --git a/src/backends/metal/input.rs b/src/backends/metal/input.rs index a822b1eb..082a5c47 100644 --- a/src/backends/metal/input.rs +++ b/src/backends/metal/input.rs @@ -3,6 +3,7 @@ use { async_engine::FdStatus, backend::{AxisSource, InputEvent, KeyState, ScrollAxis}, backends::metal::MetalBackend, + ifs::wl_seat::PX_PER_SCROLL, libinput::{ consts::{ LIBINPUT_BUTTON_STATE_PRESSED, LIBINPUT_KEY_STATE_PRESSED, @@ -14,7 +15,6 @@ use { }, std::rc::Rc, }; -use crate::ifs::wl_seat::PX_PER_SCROLL; macro_rules! unpack { ($slf:expr, $ev:expr) => {{ diff --git a/src/backends/x.rs b/src/backends/x.rs index 3318882d..f391665b 100644 --- a/src/backends/x.rs +++ b/src/backends/x.rs @@ -8,6 +8,7 @@ use { }, fixed::Fixed, format::XRGB8888, + ifs::wl_seat::PX_PER_SCROLL, render::{Framebuffer, RenderContext, RenderError}, state::State, utils::{ @@ -54,7 +55,6 @@ use { }, thiserror::Error, }; -use crate::ifs::wl_seat::PX_PER_SCROLL; #[derive(Debug, Error)] pub enum XBackendError { diff --git a/src/compositor.rs b/src/compositor.rs index d62f25a0..8b197569 100644 --- a/src/compositor.rs +++ b/src/compositor.rs @@ -119,6 +119,7 @@ fn main_(forker: Rc, logger: Arc, _args: &RunArgs) -> Resul logger, connectors: Default::default(), outputs: Default::default(), + status: Default::default(), }); { let dummy_output = Rc::new(OutputNode { @@ -150,6 +151,7 @@ fn main_(forker: Rc, logger: Arc, _args: &RunArgs) -> Resul render_data: Default::default(), state: state.clone(), is_dummy: true, + status: Default::default(), }); let dummy_workspace = Rc::new(WorkspaceNode { id: state.node_ids.next(), diff --git a/src/config.rs b/src/config.rs index 4c63c91c..e036a506 100644 --- a/src/config.rs +++ b/src/config.rs @@ -19,7 +19,7 @@ use { keyboard::ModifiedKeySym, }, libloading::Library, - std::{cell::Cell, ptr, rc::Rc}, + std::{cell::Cell, mem, ptr, rc::Rc}, thiserror::Error, }; @@ -84,7 +84,7 @@ impl ConfigProxy { impl Drop for ConfigProxy { fn drop(&mut self) { unsafe { - self.handler.dropped.set(true); + self.handler.do_drop(); (self.handler.unref)(self.handler.client_data.get()); } } @@ -120,6 +120,9 @@ impl ConfigProxy { workspace_ids: NumCell::new(1), workspaces_by_name: Default::default(), workspaces_by_id: Default::default(), + timer_ids: NumCell::new(1), + timers_by_name: Default::default(), + timers_by_id: Default::default(), }); let init_msg = bincode::encode_to_vec(&InitMessage::V1(V1InitMessage {}), bincode_ops()).unwrap(); @@ -172,6 +175,8 @@ unsafe extern "C" fn handle_msg(data: *const u8, msg: *const u8, size: usize) { if server.dropped.get() { return; } + let rc = Rc::from_raw(server); let msg = std::slice::from_raw_parts(msg, size); - server.handle_request(msg); + rc.handle_request(msg); + mem::forget(rc); } diff --git a/src/config/handler.rs b/src/config/handler.rs index e7003386..99b239e3 100644 --- a/src/config/handler.rs +++ b/src/config/handler.rs @@ -1,7 +1,9 @@ use { crate::{ - backend, - backend::{ConnectorId, InputDeviceAccelProfile, InputDeviceCapability, InputDeviceId}, + async_engine::{AsyncError, SpawnedFuture, Timer}, + backend::{ + self, ConnectorId, InputDeviceAccelProfile, InputDeviceCapability, InputDeviceId, + }, compositor::MAX_EXTENTS, ifs::wl_seat::{SeatId, WlSeatGlobal}, state::{ConnectorData, DeviceHandlerData, OutputData, State}, @@ -32,8 +34,9 @@ use { }, libloading::Library, log::Level, - std::{cell::Cell, rc::Rc}, + std::{cell::Cell, rc::Rc, time::Duration}, thiserror::Error, + uapi::c, }; pub(super) struct ConfigProxyHandler { @@ -47,12 +50,31 @@ pub(super) struct ConfigProxyHandler { pub next_id: NumCell, pub keymaps: CopyHashMap>, pub bufs: Stack>, + pub workspace_ids: NumCell, pub workspaces_by_name: CopyHashMap, u64>, pub workspaces_by_id: CopyHashMap>, + + pub timer_ids: NumCell, + pub timers_by_name: CopyHashMap, Rc>, + pub timers_by_id: CopyHashMap>, +} + +pub(super) struct TimerData { + timer: Timer, + id: u64, + name: Rc, + _handler: SpawnedFuture<()>, } impl ConfigProxyHandler { + pub fn do_drop(&self) { + self.dropped.set(true); + + self.timers_by_name.clear(); + self.timers_by_id.clear(); + } + pub fn send(&self, msg: &ServerMessage) { let mut buf = self.bufs.pop().unwrap_or_default(); buf.clear(); @@ -132,6 +154,79 @@ impl ConfigProxyHandler { Ok(()) } + fn handle_set_status(&self, status: &str) { + self.state.set_status(status); + } + + fn get_timer(&self, timer: jay_config::Timer) -> Result, CphError> { + match self.timers_by_id.get(&timer.0) { + Some(t) => Ok(t), + _ => Err(CphError::TimerDoesNotExist(timer)), + } + } + + fn handle_remove_timer(&self, timer: jay_config::Timer) -> Result<(), CphError> { + let timer = self.get_timer(timer)?; + self.timers_by_id.remove(&timer.id); + self.timers_by_name.remove(&timer.name); + Ok(()) + } + + fn handle_program_timer( + &self, + timer: jay_config::Timer, + initial: Option, + periodic: Option, + ) -> Result<(), CphError> { + let timer = self.get_timer(timer)?; + timer.timer.program(initial, periodic)?; + Ok(()) + } + + fn handle_get_timer(self: &Rc, name: &str) -> Result<(), CphError> { + let name = Rc::new(name.to_owned()); + if let Some(t) = self.timers_by_name.get(&name) { + self.respond(Response::GetTimer { + timer: jay_config::Timer(t.id), + }); + return Ok(()); + } + let id = self.timer_ids.fetch_add(1); + let timer = self.state.eng.timer(c::CLOCK_BOOTTIME)?; + let handler = { + let timer = timer.clone(); + let slf = self.clone(); + self.state.eng.spawn(async move { + loop { + match timer.expired().await { + Ok(_) => slf.send(&ServerMessage::TimerExpired { + timer: jay_config::Timer(id), + }), + Err(e) => { + log::error!("Could not wait for timer expiration: {}", ErrorFmt(e)); + if let Some(timer) = slf.timers_by_id.remove(&id) { + slf.timers_by_name.remove(&timer.name); + } + return; + } + } + } + }) + }; + let td = Rc::new(TimerData { + timer, + id, + name: name.clone(), + _handler: handler, + }); + self.timers_by_name.set(name.clone(), td.clone()); + self.timers_by_id.set(id, td.clone()); + self.respond(Response::GetTimer { + timer: jay_config::Timer(id), + }); + Ok(()) + } + fn handle_close(&self, seat: Seat) -> Result<(), CphError> { let seat = self.get_seat(seat)?; seat.close(); @@ -361,6 +456,7 @@ impl ConfigProxyHandler { return Err(CphError::InvalidConnectorPosition(x, y)); } let old_pos = connector.node.global.pos.get(); + connector.node.set_position(x, y); let seats = self.state.globals.seats.lock(); for seat in seats.values() { if seat.get_output().id == connector.node.id { @@ -371,7 +467,6 @@ impl ConfigProxyHandler { ); } } - connector.node.set_position(x, y); Ok(()) } @@ -642,13 +737,13 @@ impl ConfigProxyHandler { self.state.theme.underline_color.set(color.into()); } - pub fn handle_request(&self, msg: &[u8]) { + pub fn handle_request(self: &Rc, msg: &[u8]) { if let Err(e) = self.handle_request_(msg) { log::error!("Could not handle client request: {}", ErrorFmt(e)); } } - fn handle_request_(&self, msg: &[u8]) -> Result<(), CphError> { + fn handle_request_(self: &Rc, msg: &[u8]) -> Result<(), CphError> { let (request, _) = match bincode::decode_from_slice::(msg, bincode_ops()) { Ok(msg) => msg, @@ -770,6 +865,18 @@ impl ConfigProxyHandler { .handle_connector_set_position(connector, x, y) .wrn("connector_set_position")?, ClientMessage::Close { seat } => self.handle_close(seat).wrn("close")?, + ClientMessage::SetStatus { status } => self.handle_set_status(status), + ClientMessage::GetTimer { name } => self.handle_get_timer(name).wrn("get_timer")?, + ClientMessage::RemoveTimer { timer } => { + self.handle_remove_timer(timer).wrn("remove_timer")? + } + ClientMessage::ProgramTimer { + timer, + initial, + periodic, + } => self + .handle_program_timer(timer, initial, periodic) + .wrn("program_timer")?, } Ok(()) } @@ -801,6 +908,8 @@ enum CphError { DeviceDoesNotExist(InputDevice), #[error("Connector {0:?} does not exist")] ConnectorDoesNotExist(Connector), + #[error("Timer {0:?} does not exist")] + TimerDoesNotExist(jay_config::Timer), #[error("Connector {0:?} does not exist or is not connected")] OutputDoesNotExist(Connector), #[error("{0}x{1} is not a valid connector position")] @@ -817,6 +926,8 @@ enum CphError { ParsingFailed(#[source] DecodeError), #[error("Could not process a `{0}` request")] FailedRequest(&'static str, #[source] Box), + #[error(transparent)] + AsyncError(#[from] AsyncError), } trait WithRequestName { diff --git a/src/ifs/wl_seat.rs b/src/ifs/wl_seat.rs index 6edda577..ff0f2045 100644 --- a/src/ifs/wl_seat.rs +++ b/src/ifs/wl_seat.rs @@ -217,6 +217,16 @@ impl WlSeatGlobal { pub fn set_position(&self, x: i32, y: i32) { self.pos.set((Fixed::from_int(x), Fixed::from_int(y))); self.trigger_tree_changed(); + 'set_output: { + let outputs = self.state.outputs.lock(); + for output in outputs.values() { + if output.node.global.pos.get().contains(x, y) { + self.output.set(output.node.clone()); + break 'set_output; + } + } + self.output.set(self.state.dummy_output.get().unwrap()); + } } pub fn position(&self) -> (Fixed, Fixed) { diff --git a/src/ifs/wl_seat/event_handling.rs b/src/ifs/wl_seat/event_handling.rs index 119a7508..893e60a2 100644 --- a/src/ifs/wl_seat/event_handling.rs +++ b/src/ifs/wl_seat/event_handling.rs @@ -21,7 +21,7 @@ use { wl_surface::{xdg_surface::xdg_popup::XdgPopup, WlSurface}, }, object::ObjectId, - tree::{FloatNode, Node, OutputNode, ToplevelNode}, + tree::{FloatNode, Node, ToplevelNode}, utils::{clonecell::CloneCell, smallmap::SmallMap}, wire::WlDataOfferId, xkbcommon::{ModifierState, XKB_KEY_DOWN, XKB_KEY_UP}, @@ -172,6 +172,7 @@ impl WlSeatGlobal { Some(o) => o, _ => return, }; + self.output.set(output.node.clone()); let pos = output.node.global.pos.get(); x += Fixed::from_int(pos.x1()); y += Fixed::from_int(pos.y1()); @@ -191,6 +192,7 @@ impl WlSeatGlobal { let outputs = self.state.outputs.lock(); for output in outputs.values() { if output.node.global.pos.get().contains(x_int, y_int) { + self.output.set(output.node.clone()); break 'warp; } } @@ -517,10 +519,6 @@ impl WlSeatGlobal { // self.focus_xdg_surface(&n.xdg); } - pub fn enter_output(self: &Rc, output: &Rc) { - self.output.set(output.clone()); - } - pub fn enter_surface(&self, n: &WlSurface, x: Fixed, y: Fixed) { let serial = self.serial.fetch_add(1); self.surface_pointer_event(0, n, |p| p.send_enter(serial, n.id, x, y)); diff --git a/src/ifs/wl_surface/xdg_surface/xdg_popup.rs b/src/ifs/wl_surface/xdg_surface/xdg_popup.rs index 1607abfc..d990076d 100644 --- a/src/ifs/wl_surface/xdg_surface/xdg_popup.rs +++ b/src/ifs/wl_surface/xdg_surface/xdg_popup.rs @@ -293,6 +293,7 @@ impl Node for XdgPopup { } fn set_visible(&self, visible: bool) { + log::info!("set visible = {}", visible); self.xdg.set_visible(visible); self.xdg.seat_state.set_visible(self, visible); } @@ -362,11 +363,18 @@ impl XdgSurfaceExt for XdgPopup { *wl = Some(ws.stacked.add_last(self.clone())); *dl = Some(state.root.stacked.add_last(self.clone())); state.tree_changed(); + self.set_visible( + self.parent + .get() + .map(|p| p.surface.visible.get()) + .unwrap_or(false), + ); } } else { if wl.take().is_some() { drop(wl); drop(dl); + self.set_visible(false); self.destroy_node(true); self.send_popup_done(); } diff --git a/src/ifs/wl_surface/xwindow.rs b/src/ifs/wl_surface/xwindow.rs index 2070cf04..cc9ca605 100644 --- a/src/ifs/wl_surface/xwindow.rs +++ b/src/ifs/wl_surface/xwindow.rs @@ -280,16 +280,7 @@ impl Xwindow { self.data.state.tree_changed(); } Change::Map if self.data.info.wants_floating.get() => { - let ws = match self.data.state.root.outputs.lock().values().cloned().next() { - Some(output) => output.ensure_workspace(), - _ => self - .data - .state - .dummy_output - .get() - .unwrap() - .ensure_workspace(), - }; + let ws = self.data.state.float_map_ws(); let ext = self.data.info.extents.get(); self.data .state diff --git a/src/pango.rs b/src/pango.rs index b64e66fd..a4e97dd6 100644 --- a/src/pango.rs +++ b/src/pango.rs @@ -72,6 +72,7 @@ extern "C" { desc: *const PangoFontDescription_, ); fn pango_layout_set_text(layout: *mut PangoLayout_, text: *const c::c_char, length: c::c_int); + fn pango_layout_set_markup(layout: *mut PangoLayout_, text: *const c::c_char, length: c::c_int); fn pango_layout_get_pixel_size( layout: *mut PangoLayout_, width: *mut c::c_int, @@ -295,6 +296,12 @@ impl PangoLayout { } } + pub fn set_markup(&self, text: &str) { + unsafe { + pango_layout_set_markup(self.l, text.as_ptr() as _, text.len() as _); + } + } + pub fn pixel_size(&self) -> (i32, i32) { unsafe { let mut w = 0; diff --git a/src/render/renderer/renderer.rs b/src/render/renderer/renderer.rs index 24d7a41f..f6ce2b88 100644 --- a/src/render/renderer/renderer.rs +++ b/src/render/renderer/renderer.rs @@ -65,6 +65,9 @@ impl Renderer<'_> { for title in &rd.titles { self.render_texture(&title.tex, x + title.x, y + title.y, ARGB8888); } + if let Some(status) = &rd.status { + self.render_texture(&status.tex, x + status.x, y + status.y, ARGB8888); + } } if let Some(ws) = output.workspace.get() { self.render_workspace(&ws, x, y + th); diff --git a/src/state.rs b/src/state.rs index 996a3870..52913679 100644 --- a/src/state.rs +++ b/src/state.rs @@ -75,6 +75,7 @@ pub struct State { pub logger: Arc, pub connectors: CopyHashMap>, pub outputs: CopyHashMap>, + pub status: CloneCell>, } pub struct InputDeviceData { @@ -265,4 +266,26 @@ impl State { seat.workspace_changed(&output); } } + + pub fn float_map_ws(&self) -> Rc { + if let Some(seat) = self.seat_queue.last() { + let output = seat.get_output(); + if !output.is_dummy { + return output.ensure_workspace(); + } + } + if let Some(output) = self.root.outputs.lock().values().cloned().next() { + return output.ensure_workspace(); + } + self.dummy_output.get().unwrap().ensure_workspace() + } + + pub fn set_status(&self, status: &str) { + let status = Rc::new(status.to_owned()); + self.status.set(status.clone()); + let outputs = self.root.outputs.lock(); + for output in outputs.values() { + output.set_status(&status); + } + } } diff --git a/src/tasks/connector.rs b/src/tasks/connector.rs index 0cb5cb10..b4767af7 100644 --- a/src/tasks/connector.rs +++ b/src/tasks/connector.rs @@ -97,9 +97,11 @@ impl ConnectorHandler { active_workspace: Rect::new_empty(0, 0), inactive_workspaces: Default::default(), titles: Default::default(), + status: None, }), state: self.state.clone(), is_dummy: false, + status: self.state.status.clone(), }); let mode = info.initial_mode; let output_data = Rc::new(OutputData { diff --git a/src/text.rs b/src/text.rs index 9e29dd1d..2551633e 100644 --- a/src/text.rs +++ b/src/text.rs @@ -68,9 +68,13 @@ fn create_data(font: &str, width: i32, height: i32) -> Result { }) } -pub fn measure(font: &str, text: &str) -> Result { +pub fn measure(font: &str, text: &str, markup: bool) -> Result { let data = create_data(font, 1, 1)?; - data.layout.set_text(text); + if markup { + data.layout.set_markup(text); + } else { + data.layout.set_text(text); + } Ok(data.layout.inc_pixel_rect()) } @@ -82,7 +86,7 @@ pub fn render( text: &str, color: Color, ) -> Result, TextError> { - render2(ctx, 1, width, height, 1, font, text, color, true) + render2(ctx, 1, width, height, 1, font, text, color, true, false) } pub fn render2( @@ -95,6 +99,7 @@ pub fn render2( text: &str, color: Color, ellipsize: bool, + markup: bool, ) -> Result, TextError> { let data = create_data(font, width, height)?; if ellipsize { @@ -102,7 +107,11 @@ pub fn render2( .set_width((width - 2 * padding).max(0) * PANGO_SCALE); data.layout.set_ellipsize(PANGO_ELLIPSIZE_END); } - data.layout.set_text(text); + if markup { + data.layout.set_markup(text); + } else { + data.layout.set_text(text); + } let font_height = data.layout.pixel_size().1; data.cctx.set_operator(CAIRO_OPERATOR_SOURCE); data.cctx @@ -127,8 +136,9 @@ pub fn render_fitting( font: &str, text: &str, color: Color, + markup: bool, ) -> Result, TextError> { - let rect = measure(font, text)?; + let rect = measure(font, text, markup)?; render2( ctx, rect.x1().neg(), @@ -139,5 +149,6 @@ pub fn render_fitting( text, color, false, + markup, ) } diff --git a/src/tree/container.rs b/src/tree/container.rs index a6cc463c..f46af40b 100644 --- a/src/tree/container.rs +++ b/src/tree/container.rs @@ -3,7 +3,10 @@ use { backend::KeyState, cursor::KnownCursor, fixed::Fixed, - ifs::wl_seat::{NodeSeatState, SeatId, WlSeatGlobal, BTN_LEFT}, + ifs::wl_seat::{ + wl_pointer::{PendingScroll, VERTICAL_SCROLL}, + NodeSeatState, SeatId, WlSeatGlobal, BTN_LEFT, PX_PER_SCROLL, + }, rect::Rect, render::{Renderer, Texture}, state::State, @@ -32,8 +35,6 @@ use { rc::Rc, }, }; -use crate::ifs::wl_seat::PX_PER_SCROLL; -use crate::ifs::wl_seat::wl_pointer::{PendingScroll, VERTICAL_SCROLL}; #[allow(dead_code)] #[derive(Copy, Clone, Debug, Eq, PartialEq)] @@ -290,6 +291,7 @@ impl ContainerNode { }; new.clone().set_workspace(&self.workspace.get()); new.clone().set_parent(self.clone()); + new.set_visible(self.visible.get()); let num_children = self.num_children.fetch_add(1) + 1; self.update_content_size(); let new_child_factor = 1.0 / num_children as f64; @@ -1134,24 +1136,36 @@ impl Node for ContainerNode { Some(c) => c, None => return, }; + let (have_mc, was_mc) = match self.mono_child.get() { + None => (false, false), + Some(mc) => (true, mc.node.id() == old.id()), + }; node.focus_history.set(None); let link = node.append(ContainerChild { node: new.clone(), active: Cell::new(false), body: Cell::new(node.body.get()), - content: Cell::new(node.content.get()), + content: Default::default(), factor: Cell::new(node.factor.get()), title: Default::default(), title_rect: Cell::new(node.title_rect.get()), focus_history: Cell::new(None), }); - let body = link.body.get(); drop(node); + let mut body = None; + if was_mc { + self.mono_child.set(Some(link.to_ref())); + body = Some(self.mono_body.get()); + } else if !have_mc { + body = Some(link.body.get()); + }; self.child_nodes.borrow_mut().insert(new.id(), link); new.clone().set_parent(self.clone()); new.clone().set_workspace(&self.workspace.get()); - let body = body.move_(self.abs_x1.get(), self.abs_y1.get()); - new.clone().change_extents(&body); + if let Some(body) = body { + let body = body.move_(self.abs_x1.get(), self.abs_y1.get()); + new.clone().change_extents(&body); + } } fn remove_child(self: Rc, child: &dyn Node) { diff --git a/src/tree/output.rs b/src/tree/output.rs index 58e77e9d..d9564de6 100644 --- a/src/tree/output.rs +++ b/src/tree/output.rs @@ -2,7 +2,6 @@ use { crate::{ backend::Mode, cursor::KnownCursor, - fixed::Fixed, ifs::{ wl_output::WlOutputGlobal, wl_seat::{NodeSeatState, WlSeatGlobal}, @@ -36,6 +35,7 @@ pub struct OutputNode { pub render_data: RefCell, pub state: Rc, pub is_dummy: bool, + pub status: CloneCell>, } impl OutputNode { @@ -43,6 +43,7 @@ impl OutputNode { let mut rd = self.render_data.borrow_mut(); rd.titles.clear(); rd.inactive_workspaces.clear(); + rd.status = None; let mut pos = 0; let font = self.state.theme.font.borrow_mut(); let th = self.state.theme.title_height.get(); @@ -54,13 +55,14 @@ impl OutputNode { if th == 0 || ws.name.is_empty() { break 'create_texture; } - let title = match text::render_fitting(&ctx, th, &font, &ws.name, Color::GREY) { - Ok(t) => t, - Err(e) => { - log::error!("Could not render title {}: {}", ws.name, ErrorFmt(e)); - break 'create_texture; - } - }; + let title = + match text::render_fitting(&ctx, th, &font, &ws.name, Color::GREY, false) { + Ok(t) => t, + Err(e) => { + log::error!("Could not render title {}: {}", ws.name, ErrorFmt(e)); + break 'create_texture; + } + }; let mut x = pos + 1; if title.width() + 2 > title_width { title_width = title.width() + 2; @@ -82,6 +84,29 @@ impl OutputNode { } pos += title_width; } + 'set_status: { + let ctx = match self.state.render_ctx.get() { + Some(ctx) => ctx, + _ => break 'set_status, + }; + let status = self.status.get(); + if status.is_empty() { + break 'set_status; + } + let title = match text::render_fitting(&ctx, th, &font, &status, Color::GREY, true) { + Ok(t) => t, + Err(e) => { + log::error!("Could not render status {}: {}", status, ErrorFmt(e)); + break 'set_status; + } + }; + let pos = self.global.pos.get().width() - title.width() - 1; + rd.status = Some(OutputTitle { + x: pos, + y: 0, + tex: title, + }); + } } pub fn ensure_workspace(self: &Rc) -> Rc { @@ -106,7 +131,7 @@ impl OutputNode { seat_state: Default::default(), name: name.clone(), output_link: Default::default(), - visible: Cell::new(false), + visible: Cell::new(true), }); self.state.workspaces.set(name, workspace.clone()); workspace @@ -122,9 +147,9 @@ impl OutputNode { if old.id == ws.id { return; } - old.visible.set(false); + old.set_visible(false); } - ws.visible.set(true); + ws.set_visible(true); ws.clone().change_extents(&self.workspace_rect()); } @@ -161,6 +186,7 @@ impl OutputNode { fn change_extents_(&self, rect: &Rect) { self.global.pos.set(*rect); + self.update_render_data(); if let Some(c) = self.workspace.get() { c.change_extents(&self.workspace_rect()); } @@ -194,6 +220,11 @@ impl OutputNode { } FindTreeResult::Other } + + pub fn set_status(&self, status: &Rc) { + self.status.set(status.clone()); + self.update_render_data(); + } } pub struct OutputTitle { @@ -207,6 +238,7 @@ pub struct OutputRenderData { pub active_workspace: Rect, pub inactive_workspaces: Vec, pub titles: Vec, + pub status: Option, } impl Debug for OutputNode { @@ -283,10 +315,6 @@ impl Node for OutputNode { unimplemented!(); } - fn pointer_enter(self: Rc, seat: &Rc, _x: Fixed, _y: Fixed) { - seat.enter_output(&self) - } - fn pointer_focus(&self, seat: &Rc) { seat.set_known_cursor(KnownCursor::Default); } diff --git a/src/tree/workspace.rs b/src/tree/workspace.rs index fcd7b9d1..24f7c503 100644 --- a/src/tree/workspace.rs +++ b/src/tree/workspace.rs @@ -35,6 +35,7 @@ impl WorkspaceNode { let pos = self.position.get(); container.clone().change_extents(&pos); container.clone().set_workspace(self); + container.set_visible(self.visible.get()); self.container.set(Some(container.clone())); } } diff --git a/todo.md b/todo.md index 4075279f..45acb43e 100644 --- a/todo.md +++ b/todo.md @@ -1,11 +1,9 @@ # todo - Container moving (mouse) -- Workspaces - presentation time - viewporter - session lock -- xwayland # done @@ -21,3 +19,5 @@ - Config - Highlighting active - Toplevel splitting +- Workspaces +- xwayland