diff --git a/src/backends/metal/video.rs b/src/backends/metal/video.rs index 90a8426e..0207fce4 100644 --- a/src/backends/metal/video.rs +++ b/src/backends/metal/video.rs @@ -836,6 +836,7 @@ impl MetalConnector { render_hw_cursor, output.has_fullscreen(), output.global.persistent.transform.get(), + Some(&self.state.damage_visualizer), ); let try_direct_scanout = try_direct_scanout && self.direct_scanout_enabled() diff --git a/src/cli.rs b/src/cli.rs index ba221c76..7d9366a8 100644 --- a/src/cli.rs +++ b/src/cli.rs @@ -1,3 +1,6 @@ +mod color; +mod damage_tracking; +mod duration; mod generate; mod idle; mod input; @@ -12,7 +15,7 @@ mod unlock; use { crate::{ - cli::{input::InputArgs, randr::RandrArgs}, + cli::{damage_tracking::DamageTrackingArgs, input::InputArgs, randr::RandrArgs}, compositor::start_compositor, portal, }, @@ -65,6 +68,9 @@ pub enum Cmd { Randr(RandrArgs), /// Inspect/modify input settings. Input(InputArgs), + /// Modify damage tracking settings. (Only for debugging.) + #[clap(hide = true)] + DamageTracking(DamageTrackingArgs), #[cfg(feature = "it")] RunTests, } @@ -241,6 +247,7 @@ pub fn main() { Cmd::Portal => portal::run_freestanding(cli.global), Cmd::Randr(a) => randr::main(cli.global, a), Cmd::Input(a) => input::main(cli.global, a), + Cmd::DamageTracking(a) => damage_tracking::main(cli.global, a), #[cfg(feature = "it")] Cmd::RunTests => crate::it::run_tests(), } diff --git a/src/cli/color.rs b/src/cli/color.rs new file mode 100644 index 00000000..24136ecc --- /dev/null +++ b/src/cli/color.rs @@ -0,0 +1,36 @@ +use { + crate::{theme::Color, utils::errorfmt::ErrorFmt}, + std::ops::Range, +}; + +pub fn parse_color(string: &str) -> Color { + let hex = match string.strip_prefix("#") { + Some(s) => s, + _ => fatal!("Color must start with #"), + }; + let d = |range: Range| match u8::from_str_radix(&hex[range.clone()], 16) { + Ok(n) => n, + Err(e) => { + fatal!( + "Could not parse color component {}: {}", + &hex[range], + ErrorFmt(e) + ) + } + }; + let s = |range: Range| { + let v = d(range); + (v << 4) | v + }; + let (r, g, b, a) = match hex.len() { + 3 => (s(0..1), s(1..2), s(2..3), u8::MAX), + 4 => (s(0..1), s(1..2), s(2..3), s(3..4)), + 6 => (d(0..2), d(2..4), d(4..6), u8::MAX), + 8 => (d(0..2), d(2..4), d(4..6), d(6..8)), + _ => fatal!( + "Unexpected length of color string (should be 3, 4, 6, or 8): {}", + hex.len() + ), + }; + jay_config::theme::Color::new_straight(r, g, b, a).into() +} diff --git a/src/cli/damage_tracking.rs b/src/cli/damage_tracking.rs new file mode 100644 index 00000000..438e80ff --- /dev/null +++ b/src/cli/damage_tracking.rs @@ -0,0 +1,106 @@ +use { + crate::{ + cli::{color::parse_color, duration::parse_duration, GlobalArgs}, + tools::tool_client::{with_tool_client, ToolClient}, + wire::jay_damage_tracking::{SetVisualizerColor, SetVisualizerDecay, SetVisualizerEnabled}, + }, + clap::{Args, Subcommand}, + std::rc::Rc, +}; + +#[derive(Args, Debug)] +pub struct DamageTrackingArgs { + #[clap(subcommand)] + pub command: DamageTrackingCmd, +} + +#[derive(Subcommand, Debug)] +pub enum DamageTrackingCmd { + /// Visualize damage. + Show, + /// Hide damage. + Hide, + /// Set the color used for damage visualization. + SetColor(ColorArgs), + /// Set the amount of time damage is shown. + SetDecay(DecayArgs), +} + +#[derive(Args, Debug)] +pub struct ColorArgs { + /// The color to visualize damage. + /// + /// Should be specified in one of the following formats: + /// + /// * `#rgb` + /// * `#rgba` + /// * `#rrggbb` + /// * `#rrggbbaa` + pub color: String, +} + +#[derive(Args, Debug)] +pub struct DecayArgs { + /// The interval of inactivity after which to disable the screens. + /// + /// Minutes, seconds, and milliseconds can be specified in any of the following formats: + /// + /// * 1m + /// * 1m5s + /// * 1m 5s + /// * 1min 5sec + /// * 1 minute 5 seconds. + pub duration: Vec, +} + +pub fn main(global: GlobalArgs, damage_tracking_args: DamageTrackingArgs) { + with_tool_client(global.log_level.into(), |tc| async move { + let damage_tracking = Rc::new(DamageTracking { tc: tc.clone() }); + damage_tracking.run(damage_tracking_args).await; + }); +} + +struct DamageTracking { + tc: Rc, +} + +impl DamageTracking { + async fn run(&self, args: DamageTrackingArgs) { + let tc = &self.tc; + let Some(dt) = tc.jay_damage_tracking().await else { + fatal!("Compositor does not support damage tracking"); + }; + match args.command { + DamageTrackingCmd::Show => { + tc.send(SetVisualizerEnabled { + self_id: dt, + enabled: 1, + }); + } + DamageTrackingCmd::Hide => { + tc.send(SetVisualizerEnabled { + self_id: dt, + enabled: 0, + }); + } + DamageTrackingCmd::SetColor(c) => { + let color = parse_color(&c.color); + tc.send(SetVisualizerColor { + self_id: dt, + r: color.r, + g: color.g, + b: color.b, + a: color.a, + }); + } + DamageTrackingCmd::SetDecay(c) => { + let duration = parse_duration(&c.duration); + tc.send(SetVisualizerDecay { + self_id: dt, + millis: duration.as_millis() as _, + }); + } + } + tc.round_trip().await; + } +} diff --git a/src/cli/duration.rs b/src/cli/duration.rs new file mode 100644 index 00000000..823d40ba --- /dev/null +++ b/src/cli/duration.rs @@ -0,0 +1,102 @@ +use { + crate::utils::errorfmt::ErrorFmt, + std::{collections::VecDeque, str::FromStr, time::Duration}, +}; + +#[derive(Debug)] +enum Component { + Number(u64), + Minutes(String), + Seconds(String), + Milliseconds(String), +} + +pub fn parse_duration(args: &[String]) -> Duration { + let comp = parse_components(args); + let mut minutes = None; + let mut seconds = None; + let mut milliseconds = None; + let mut pending_num = None; + for comp in comp { + match comp { + Component::Number(_) if pending_num.is_some() => { + fatal!("missing number unit after {}", pending_num.unwrap()) + } + Component::Number(n) => pending_num = Some(n), + + Component::Minutes(n) if pending_num.is_none() => { + fatal!("`{}` must be preceded by a number", n) + } + Component::Minutes(_) if minutes.is_some() => { + fatal!("minutes specified multiple times") + } + Component::Minutes(_) => minutes = pending_num.take(), + + Component::Seconds(n) if pending_num.is_none() => { + fatal!("`{}` must be preceded by a number", n) + } + Component::Seconds(_) if seconds.is_some() => { + fatal!("seconds specified multiple times") + } + Component::Seconds(_) => seconds = pending_num.take(), + Component::Milliseconds(n) if pending_num.is_none() => { + fatal!("`{}` must be preceded by a number", n) + } + Component::Milliseconds(_) if milliseconds.is_some() => { + fatal!("milliseconds specified multiple times") + } + Component::Milliseconds(_) => milliseconds = pending_num.take(), + } + } + if pending_num.is_some() { + fatal!("missing number unit after {}", pending_num.unwrap()); + } + if minutes.is_none() && seconds.is_none() && milliseconds.is_none() { + fatal!("duration must be specified"); + } + let mut ms = minutes.unwrap_or(0) as u128 * 60 * 1000 + + seconds.unwrap_or(0) as u128 * 1000 + + milliseconds.unwrap_or(0) as u128; + if ms > u64::MAX as u128 { + ms = u64::MAX as u128; + } + Duration::from_millis(ms as u64) +} + +fn parse_components(args: &[String]) -> Vec { + let mut args = VecDeque::from_iter(args.iter().map(|s| s.to_ascii_lowercase())); + let mut res = vec![]; + while let Some(arg) = args.pop_front() { + if arg.is_empty() { + continue; + } + let mut arg = &arg[..]; + if is_num(arg.as_bytes()[0]) { + if let Some(pos) = arg.as_bytes().iter().position(|&a| !is_num(a)) { + args.push_front(arg[pos..].to_string()); + arg = &arg[..pos]; + } + match u64::from_str(arg) { + Ok(n) => res.push(Component::Number(n)), + Err(e) => fatal!("Could not parse `{}` as a number: {}", arg, ErrorFmt(e)), + } + } else { + if let Some(pos) = arg.as_bytes().iter().position(|&a| is_num(a)) { + args.push_front(arg[pos..].to_string()); + arg = &arg[..pos]; + } + let comp = match arg { + "minutes" | "minute" | "min" | "m" => Component::Minutes(arg.to_string()), + "seconds" | "second" | "sec" | "s" => Component::Seconds(arg.to_string()), + "milliseconds" | "millisecond" | "ms" => Component::Milliseconds(arg.to_string()), + _ => fatal!("Could not parse `{}`", arg), + }; + res.push(comp); + } + } + res +} + +fn is_num(b: u8) -> bool { + matches!(b, b'0'..=b'9') +} diff --git a/src/cli/idle.rs b/src/cli/idle.rs index cbd388e9..c5782f7a 100644 --- a/src/cli/idle.rs +++ b/src/cli/idle.rs @@ -1,11 +1,11 @@ use { crate::{ - cli::{GlobalArgs, IdleArgs, IdleCmd, IdleSetArgs}, + cli::{duration::parse_duration, GlobalArgs, IdleArgs, IdleCmd, IdleSetArgs}, tools::tool_client::{with_tool_client, Handle, ToolClient}, - utils::{errorfmt::ErrorFmt, stack::Stack}, + utils::stack::Stack, wire::{jay_compositor, jay_idle, JayIdleId, WlSurfaceId}, }, - std::{cell::Cell, collections::VecDeque, rc::Rc, str::FromStr}, + std::{cell::Cell, rc::Rc}, }; pub fn main(global: GlobalArgs, args: IdleArgs) { @@ -97,41 +97,7 @@ impl Idle { if args.interval.len() == 1 && args.interval[0] == "disabled" { interval = 0; } else { - let comp = parse_components(&args.interval); - let mut minutes = None; - let mut seconds = None; - let mut pending_num = None; - for comp in comp { - match comp { - Component::Number(_) if pending_num.is_some() => { - fatal!("missing number unit after {}", pending_num.unwrap()) - } - Component::Number(n) => pending_num = Some(n), - - Component::Minutes(n) if pending_num.is_none() => { - fatal!("`{}` must be preceded by a number", n) - } - Component::Minutes(_) if minutes.is_some() => { - fatal!("minutes specified multiple times") - } - Component::Minutes(_) => minutes = pending_num.take(), - - Component::Seconds(n) if pending_num.is_none() => { - fatal!("`{}` must be preceded by a number", n) - } - Component::Seconds(_) if seconds.is_some() => { - fatal!("seconds specified multiple times") - } - Component::Seconds(_) => seconds = pending_num.take(), - } - } - if pending_num.is_some() { - fatal!("missing number unit after {}", pending_num.unwrap()); - } - if minutes.is_none() && seconds.is_none() { - fatal!("minutes and/or numbers must be specified"); - } - interval = minutes.unwrap_or(0) * 60 + seconds.unwrap_or(0); + interval = parse_duration(&args.interval).as_secs() as u64; } tc.send(jay_idle::SetInterval { self_id: idle, @@ -140,47 +106,3 @@ impl Idle { tc.round_trip().await; } } - -#[derive(Debug)] -enum Component { - Number(u64), - Minutes(String), - Seconds(String), -} - -fn parse_components(args: &[String]) -> Vec { - let mut args = VecDeque::from_iter(args.iter().map(|s| s.to_ascii_lowercase())); - let mut res = vec![]; - while let Some(arg) = args.pop_front() { - if arg.is_empty() { - continue; - } - let mut arg = &arg[..]; - if is_num(arg.as_bytes()[0]) { - if let Some(pos) = arg.as_bytes().iter().position(|&a| !is_num(a)) { - args.push_front(arg[pos..].to_string()); - arg = &arg[..pos]; - } - match u64::from_str(arg) { - Ok(n) => res.push(Component::Number(n)), - Err(e) => fatal!("Could not parse `{}` as a number: {}", arg, ErrorFmt(e)), - } - } else { - if let Some(pos) = arg.as_bytes().iter().position(|&a| is_num(a)) { - args.push_front(arg[pos..].to_string()); - arg = &arg[..pos]; - } - let comp = match arg { - "minutes" | "minute" | "min" | "m" => Component::Minutes(arg.to_string()), - "seconds" | "second" | "sec" | "s" => Component::Seconds(arg.to_string()), - _ => fatal!("Could not parse `{}`", arg), - }; - res.push(comp); - } - } - res -} - -fn is_num(b: u8) -> bool { - matches!(b, b'0'..=b'9') -} diff --git a/src/compositor.rs b/src/compositor.rs index f8c427aa..bdb4925d 100644 --- a/src/compositor.rs +++ b/src/compositor.rs @@ -13,6 +13,7 @@ use { client::{ClientId, Clients}, clientmem::{self, ClientMemError}, config::ConfigProxy, + damage::visualize_damage, dbus::Dbus, forker, globals::Globals, @@ -244,6 +245,7 @@ fn start_compositor2( tablet_ids: Default::default(), tablet_tool_ids: Default::default(), tablet_pad_ids: Default::default(), + damage_visualizer: Default::default(), }); state.tracker.register(ClientId::from_raw(0)); create_dummy_output(&state); @@ -343,6 +345,7 @@ fn start_global_event_handlers( eng.spawn2(Phase::PostLayout, input_popup_positioning(state.clone())), eng.spawn2(Phase::Present, perform_toplevel_screencasts(state.clone())), eng.spawn2(Phase::PostLayout, perform_screencast_realloc(state.clone())), + eng.spawn2(Phase::PostLayout, visualize_damage(state.clone())), ] } diff --git a/src/damage.rs b/src/damage.rs new file mode 100644 index 00000000..ed38865e --- /dev/null +++ b/src/damage.rs @@ -0,0 +1,158 @@ +use { + crate::{ + rect::{Rect, Region}, + renderer::renderer_base::RendererBase, + state::State, + theme::Color, + utils::{asyncevent::AsyncEvent, errorfmt::ErrorFmt, timer::TimerFd}, + }, + isnt::std_1::primitive::IsntSliceExt, + std::{ + cell::{Cell, RefCell}, + collections::VecDeque, + rc::Rc, + time::{Duration, Instant}, + }, + uapi::c::CLOCK_MONOTONIC, +}; + +pub async fn visualize_damage(state: Rc) { + let timer = match TimerFd::new(CLOCK_MONOTONIC) { + Ok(t) => t, + Err(e) => { + log::error!("Could not create timer fd: {}", ErrorFmt(e)); + return; + } + }; + loop { + state.damage_visualizer.entry_added.triggered().await; + let duration = Duration::from_millis(50); + let res = timer.program(Some(duration), Some(duration)); + if let Err(e) = res { + log::error!("Could not program timer: {}", ErrorFmt(e)); + return; + } + loop { + let res = timer.expired(&state.ring).await; + if let Err(e) = res { + log::error!("Could not wait for timer to expire: {}", ErrorFmt(e)); + return; + } + if state.damage_visualizer.entries.borrow_mut().is_empty() { + break; + } + damage_all(&state); + } + let res = timer.program(None, None); + if let Err(e) = res { + log::error!("Could not disable timer: {}", ErrorFmt(e)); + return; + } + } +} + +fn damage_all(state: &State) { + for connector in state.connectors.lock().values() { + if connector.connected.get() { + connector.connector.damage(); + } + } +} + +pub struct DamageVisualizer { + entries: RefCell>, + entry_added: AsyncEvent, + enabled: Cell, + decay: Cell, + color: Cell, +} + +impl Default for DamageVisualizer { + fn default() -> Self { + Self { + entries: Default::default(), + entry_added: Default::default(), + enabled: Default::default(), + decay: Cell::new(Duration::from_secs(2)), + color: Cell::new(Color::from_rgba_straight(255, 0, 0, 128)), + } + } +} + +const MAX_RECTS: usize = 100_000; + +struct Damage { + time: Instant, + rect: Rect, +} + +impl DamageVisualizer { + #[allow(dead_code)] + pub fn add(&self, rect: Rect) { + if !self.enabled.get() { + return; + } + let entries = &mut *self.entries.borrow_mut(); + if entries.is_empty() { + self.entry_added.trigger(); + } + entries.push_back(Damage { + time: Instant::now(), + rect, + }); + if entries.len() > MAX_RECTS { + entries.pop_front(); + } + } + + pub fn set_enabled(&self, state: &State, enabled: bool) { + self.enabled.set(enabled); + if !enabled { + self.entries.borrow_mut().clear(); + damage_all(state); + } + } + + pub fn set_decay(&self, decay: Duration) { + let millis = decay.as_millis(); + if millis == 0 || millis > u64::MAX as u128 { + return; + } + self.decay.set(decay); + } + + pub fn set_color(&self, color: Color) { + self.color.set(color); + } + + pub fn render(&self, cursor_rect: &Rect, renderer: &mut RendererBase<'_>) { + if !self.enabled.get() { + return; + } + let now = Instant::now(); + let entries = &mut *self.entries.borrow_mut(); + let decay = self.decay.get(); + while let Some(first) = entries.front() { + if now - first.time >= decay { + entries.pop_front(); + } else { + break; + } + } + let base_color = self.color.get(); + let mut used = Region::empty(); + let dx = -cursor_rect.x1(); + let dy = -cursor_rect.y1(); + let decay_millis = decay.as_millis() as u64 as f32; + for entry in entries.iter().rev() { + let region = Region::new(entry.rect); + let region = region.subtract(&used); + if region.is_not_empty() { + let age = (now - entry.time).as_millis() as u64 as f32 / decay_millis; + let color = base_color * (1.0 - age); + renderer.fill_boxes2(region.rects(), &color, dx, dy); + used = used.union(®ion); + } + } + } +} diff --git a/src/gfx_api.rs b/src/gfx_api.rs index a36c9a4e..da293ed1 100644 --- a/src/gfx_api.rs +++ b/src/gfx_api.rs @@ -1,6 +1,7 @@ use { crate::{ cursor::Cursor, + damage::DamageVisualizer, fixed::Fixed, format::Format, rect::Rect, @@ -329,6 +330,7 @@ impl dyn GfxFramebuffer { render_hardware_cursor: bool, black_background: bool, transform: Transform, + visualizer: Option<&DamageVisualizer>, ) -> GfxRenderPass { let mut ops = self.take_render_ops(); let mut renderer = Renderer { @@ -380,6 +382,11 @@ impl dyn GfxFramebuffer { } } } + if let Some(visualizer) = visualizer { + if let Some(cursor_rect) = cursor_rect { + visualizer.render(&cursor_rect, &mut renderer.base); + } + } let c = match black_background { true => Color::SOLID_BLACK, false => state.theme.colors.background.get(), @@ -438,6 +445,7 @@ impl dyn GfxFramebuffer { render_hardware_cursor, black_background, transform, + None, ); self.perform_render_pass(pass) } diff --git a/src/globals.rs b/src/globals.rs index d893a06c..73a92f1f 100644 --- a/src/globals.rs +++ b/src/globals.rs @@ -12,6 +12,7 @@ use { zwp_primary_selection_device_manager_v1::ZwpPrimarySelectionDeviceManagerV1Global, }, jay_compositor::JayCompositorGlobal, + jay_damage_tracking::JayDamageTrackingGlobal, org_kde_kwin_server_decoration_manager::OrgKdeKwinServerDecorationManagerGlobal, wl_compositor::WlCompositorGlobal, wl_output::WlOutputGlobal, @@ -195,6 +196,7 @@ impl Globals { add_singleton!(ExtTransientSeatManagerV1Global); add_singleton!(ZwpPointerGesturesV1Global); add_singleton!(ZwpTabletManagerV2Global); + add_singleton!(JayDamageTrackingGlobal); } pub fn add_backend_singletons(&self, backend: &Rc) { diff --git a/src/ifs.rs b/src/ifs.rs index 9efdf7b0..851dd9fa 100644 --- a/src/ifs.rs +++ b/src/ifs.rs @@ -6,6 +6,7 @@ pub mod ext_session_lock_manager_v1; pub mod ext_session_lock_v1; pub mod ipc; pub mod jay_compositor; +pub mod jay_damage_tracking; pub mod jay_idle; pub mod jay_input; pub mod jay_log_file; diff --git a/src/ifs/jay_damage_tracking.rs b/src/ifs/jay_damage_tracking.rs new file mode 100644 index 00000000..10015f34 --- /dev/null +++ b/src/ifs/jay_damage_tracking.rs @@ -0,0 +1,135 @@ +use { + crate::{ + client::{Client, ClientCaps, ClientError, CAP_JAY_COMPOSITOR}, + globals::{Global, GlobalName}, + leaks::Tracker, + object::{Object, Version}, + theme::Color, + wire::{ + jay_damage_tracking::{ + Destroy, JayDamageTrackingRequestHandler, SetVisualizerColor, SetVisualizerDecay, + SetVisualizerEnabled, + }, + JayCompositorId, + }, + }, + std::{rc::Rc, time::Duration}, + thiserror::Error, +}; + +pub struct JayDamageTrackingGlobal { + name: GlobalName, +} + +impl JayDamageTrackingGlobal { + pub fn new(name: GlobalName) -> Self { + Self { name } + } + + fn bind_( + self: Rc, + id: JayCompositorId, + client: &Rc, + version: Version, + ) -> Result<(), JayDamageTrackingError> { + let obj = Rc::new(JayDamageTracking { + id, + client: client.clone(), + tracker: Default::default(), + version, + }); + track!(client, obj); + client.add_client_obj(&obj)?; + Ok(()) + } +} + +global_base!( + JayDamageTrackingGlobal, + JayDamageTracking, + JayDamageTrackingError +); + +impl Global for JayDamageTrackingGlobal { + fn singleton(&self) -> bool { + true + } + + fn version(&self) -> u32 { + 1 + } + + fn required_caps(&self) -> ClientCaps { + CAP_JAY_COMPOSITOR + } +} + +simple_add_global!(JayDamageTrackingGlobal); + +pub struct JayDamageTracking { + id: JayCompositorId, + client: Rc, + tracker: Tracker, + version: Version, +} + +impl JayDamageTrackingRequestHandler for JayDamageTracking { + type Error = JayDamageTrackingError; + + fn destroy(&self, _req: Destroy, _slf: &Rc) -> Result<(), Self::Error> { + self.client.remove_obj(self)?; + Ok(()) + } + + fn set_visualizer_enabled( + &self, + req: SetVisualizerEnabled, + _slf: &Rc, + ) -> Result<(), Self::Error> { + let state = &self.client.state; + state.damage_visualizer.set_enabled(state, req.enabled != 0); + Ok(()) + } + + fn set_visualizer_color( + &self, + req: SetVisualizerColor, + _slf: &Rc, + ) -> Result<(), Self::Error> { + self.client.state.damage_visualizer.set_color(Color { + r: req.r, + g: req.g, + b: req.b, + a: req.a, + }); + Ok(()) + } + + fn set_visualizer_decay( + &self, + req: SetVisualizerDecay, + _slf: &Rc, + ) -> Result<(), Self::Error> { + self.client + .state + .damage_visualizer + .set_decay(Duration::from_millis(req.millis)); + Ok(()) + } +} + +object_base! { + self = JayDamageTracking; + version = self.version; +} + +impl Object for JayDamageTracking {} + +simple_add_obj!(JayDamageTracking); + +#[derive(Debug, Error)] +pub enum JayDamageTrackingError { + #[error(transparent)] + ClientError(Box), +} +efrom!(JayDamageTrackingError, ClientError); diff --git a/src/main.rs b/src/main.rs index d7cc40b5..264305db 100644 --- a/src/main.rs +++ b/src/main.rs @@ -54,6 +54,7 @@ mod compositor; mod config; mod cursor; mod cursor_user; +mod damage; mod dbus; mod drm_feedback; mod edid; diff --git a/src/rect/region.rs b/src/rect/region.rs index 0ef3dc28..cf9e8c86 100644 --- a/src/rect/region.rs +++ b/src/rect/region.rs @@ -78,6 +78,10 @@ impl Region { self.extents } + pub fn rects(&self) -> &[Rect] { + unsafe { mem::transmute::<&[RectRaw], &[Rect]>(&self.rects[..]) } + } + pub fn contains(&self, x: i32, y: i32) -> bool { if !self.extents.contains(x, y) { return false; diff --git a/src/state.rs b/src/state.rs index 07af9b1b..448548a9 100644 --- a/src/state.rs +++ b/src/state.rs @@ -14,6 +14,7 @@ use { config::ConfigProxy, cursor::{Cursor, ServerCursors}, cursor_user::{CursorUserGroup, CursorUserGroupId, CursorUserGroupIds, CursorUserIds}, + damage::DamageVisualizer, dbus::Dbus, drm_feedback::{DrmFeedback, DrmFeedbackIds}, fixed::Fixed, @@ -198,6 +199,7 @@ pub struct State { pub tablet_ids: TabletIds, pub tablet_tool_ids: TabletToolIds, pub tablet_pad_ids: TabletPadIds, + pub damage_visualizer: DamageVisualizer, } // impl Drop for State { diff --git a/src/tools/tool_client.rs b/src/tools/tool_client.rs index 43c58b5b..0abd322b 100644 --- a/src/tools/tool_client.rs +++ b/src/tools/tool_client.rs @@ -23,8 +23,8 @@ use { }, wheel::{Wheel, WheelError}, wire::{ - wl_callback, wl_display, wl_registry, JayCompositor, JayCompositorId, WlCallbackId, - WlRegistryId, + wl_callback, wl_display, wl_registry, JayCompositor, JayCompositorId, + JayDamageTracking, JayDamageTrackingId, WlCallbackId, WlRegistryId, }, }, ahash::AHashMap, @@ -92,6 +92,7 @@ pub struct ToolClient { outgoing: Cell>>, singletons: CloneCell>>, jay_compositor: Cell>, + jay_damage_tracking: Cell>>, } pub fn with_tool_client(level: Level, f: F) @@ -188,6 +189,7 @@ impl ToolClient { outgoing: Default::default(), singletons: Default::default(), jay_compositor: Default::default(), + jay_damage_tracking: Default::default(), }); wl_display::Error::handle(&slf, WL_DISPLAY_ID, (), |_, val| { fatal!("The compositor returned a fatal error: {}", val.message); @@ -285,6 +287,7 @@ impl ToolClient { #[derive(Default)] struct S { jay_compositor: Cell>, + jay_damage_tracking: Cell>, } let s = Rc::new(S::default()); let registry: WlRegistryId = self.id(); @@ -295,6 +298,8 @@ impl ToolClient { wl_registry::Global::handle(self, registry, s.clone(), |s, g| { if g.interface == JayCompositor.name() { s.jay_compositor.set(Some(g.name)); + } else if g.interface == JayDamageTracking.name() { + s.jay_damage_tracking.set(Some(g.name)); } }); self.round_trip().await; @@ -309,6 +314,7 @@ impl ToolClient { let res = Rc::new(Singletons { registry, jay_compositor: get!(jay_compositor, JayCompositor), + jay_damage_tracking: s.jay_damage_tracking.get(), }); self.singletons.set(Some(res.clone())); res @@ -330,11 +336,33 @@ impl ToolClient { self.jay_compositor.set(Some(id)); id } + + pub async fn jay_damage_tracking(self: &Rc) -> Option { + if let Some(id) = self.jay_damage_tracking.get() { + return id; + } + let s = self.singletons().await; + let Some(name) = s.jay_damage_tracking else { + self.jay_damage_tracking.set(Some(None)); + return None; + }; + let id: JayDamageTrackingId = self.id(); + self.send(wl_registry::Bind { + self_id: s.registry, + name, + interface: JayDamageTracking.name(), + version: 1, + id: id.into(), + }); + self.jay_damage_tracking.set(Some(Some(id))); + Some(id) + } } pub struct Singletons { registry: WlRegistryId, pub jay_compositor: u32, + pub jay_damage_tracking: Option, } pub const NONE_FUTURE: Option> = None; diff --git a/toml-config/src/config/parsers/color.rs b/toml-config/src/config/parsers/color.rs index a4418b0c..25073b20 100644 --- a/toml-config/src/config/parsers/color.rs +++ b/toml-config/src/config/parsers/color.rs @@ -49,7 +49,7 @@ impl Parser for ColorParser { 3 => (s(0..1)?, s(1..2)?, s(2..3)?, u8::MAX), 4 => (s(0..1)?, s(1..2)?, s(2..3)?, s(3..4)?), 6 => (d(0..2)?, d(2..4)?, d(4..6)?, u8::MAX), - 8 => (d(0..2)?, d(2..4)?, d(4..6)?, d(4..8)?), + 8 => (d(0..2)?, d(2..4)?, d(4..6)?, d(6..8)?), _ => return Err(ColorParserError::Length.spanned(span)), }; Ok(Color::new_straight(r, g, b, a)) diff --git a/wire/jay_damage_tracking.txt b/wire/jay_damage_tracking.txt new file mode 100644 index 00000000..96b6d6da --- /dev/null +++ b/wire/jay_damage_tracking.txt @@ -0,0 +1,18 @@ +request destroy { + +} + +request set_visualizer_enabled { + enabled: u32, +} + +request set_visualizer_color { + r: pod(f32), + g: pod(f32), + b: pod(f32), + a: pod(f32), +} + +request set_visualizer_decay { + millis: pod(u64), +}