Skip to content

Commit

Permalink
render: add a damage visualizer
Browse files Browse the repository at this point in the history
  • Loading branch information
mahkoh committed Jul 10, 2024
1 parent e37cee3 commit 83586fb
Show file tree
Hide file tree
Showing 18 changed files with 620 additions and 86 deletions.
1 change: 1 addition & 0 deletions src/backends/metal/video.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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()
Expand Down
9 changes: 8 additions & 1 deletion src/cli.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
mod color;
mod damage_tracking;
mod duration;
mod generate;
mod idle;
mod input;
Expand All @@ -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,
},
Expand Down Expand Up @@ -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,
}
Expand Down Expand Up @@ -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(),
}
Expand Down
36 changes: 36 additions & 0 deletions src/cli/color.rs
Original file line number Diff line number Diff line change
@@ -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<usize>| 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<usize>| {
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()
}
106 changes: 106 additions & 0 deletions src/cli/damage_tracking.rs
Original file line number Diff line number Diff line change
@@ -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<String>,
}

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<ToolClient>,
}

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;
}
}
102 changes: 102 additions & 0 deletions src/cli/duration.rs
Original file line number Diff line number Diff line change
@@ -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<Component> {
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')
}
86 changes: 4 additions & 82 deletions src/cli/idle.rs
Original file line number Diff line number Diff line change
@@ -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) {
Expand Down Expand Up @@ -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,
Expand All @@ -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<Component> {
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')
}
Loading

0 comments on commit 83586fb

Please sign in to comment.