render: add a damage visualizer
This commit is contained in:
parent
3f4a677d0c
commit
76a3c50560
18 changed files with 625 additions and 90 deletions
|
|
@ -835,6 +835,7 @@ impl MetalConnector {
|
||||||
render_hw_cursor,
|
render_hw_cursor,
|
||||||
output.has_fullscreen(),
|
output.has_fullscreen(),
|
||||||
output.global.persistent.transform.get(),
|
output.global.persistent.transform.get(),
|
||||||
|
Some(&self.state.damage_visualizer),
|
||||||
);
|
);
|
||||||
let try_direct_scanout = try_direct_scanout
|
let try_direct_scanout = try_direct_scanout
|
||||||
&& self.direct_scanout_enabled()
|
&& self.direct_scanout_enabled()
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,6 @@
|
||||||
|
mod color;
|
||||||
|
mod damage_tracking;
|
||||||
|
mod duration;
|
||||||
mod generate;
|
mod generate;
|
||||||
mod idle;
|
mod idle;
|
||||||
mod input;
|
mod input;
|
||||||
|
|
@ -12,7 +15,7 @@ mod unlock;
|
||||||
|
|
||||||
use {
|
use {
|
||||||
crate::{
|
crate::{
|
||||||
cli::{input::InputArgs, randr::RandrArgs},
|
cli::{damage_tracking::DamageTrackingArgs, input::InputArgs, randr::RandrArgs},
|
||||||
compositor::start_compositor,
|
compositor::start_compositor,
|
||||||
portal,
|
portal,
|
||||||
},
|
},
|
||||||
|
|
@ -65,6 +68,9 @@ pub enum Cmd {
|
||||||
Randr(RandrArgs),
|
Randr(RandrArgs),
|
||||||
/// Inspect/modify input settings.
|
/// Inspect/modify input settings.
|
||||||
Input(InputArgs),
|
Input(InputArgs),
|
||||||
|
/// Modify damage tracking settings. (Only for debugging.)
|
||||||
|
#[clap(hide = true)]
|
||||||
|
DamageTracking(DamageTrackingArgs),
|
||||||
#[cfg(feature = "it")]
|
#[cfg(feature = "it")]
|
||||||
RunTests,
|
RunTests,
|
||||||
}
|
}
|
||||||
|
|
@ -241,6 +247,7 @@ pub fn main() {
|
||||||
Cmd::Portal => portal::run_freestanding(cli.global),
|
Cmd::Portal => portal::run_freestanding(cli.global),
|
||||||
Cmd::Randr(a) => randr::main(cli.global, a),
|
Cmd::Randr(a) => randr::main(cli.global, a),
|
||||||
Cmd::Input(a) => input::main(cli.global, a),
|
Cmd::Input(a) => input::main(cli.global, a),
|
||||||
|
Cmd::DamageTracking(a) => damage_tracking::main(cli.global, a),
|
||||||
#[cfg(feature = "it")]
|
#[cfg(feature = "it")]
|
||||||
Cmd::RunTests => crate::it::run_tests(),
|
Cmd::RunTests => crate::it::run_tests(),
|
||||||
}
|
}
|
||||||
|
|
|
||||||
36
src/cli/color.rs
Normal file
36
src/cli/color.rs
Normal file
|
|
@ -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
src/cli/damage_tracking.rs
Normal file
106
src/cli/damage_tracking.rs
Normal file
|
|
@ -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
src/cli/duration.rs
Normal file
102
src/cli/duration.rs
Normal file
|
|
@ -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')
|
||||||
|
}
|
||||||
|
|
@ -1,11 +1,11 @@
|
||||||
use {
|
use {
|
||||||
crate::{
|
crate::{
|
||||||
cli::{GlobalArgs, IdleArgs, IdleCmd, IdleSetArgs},
|
cli::{duration::parse_duration, GlobalArgs, IdleArgs, IdleCmd, IdleSetArgs},
|
||||||
tools::tool_client::{with_tool_client, Handle, ToolClient},
|
tools::tool_client::{with_tool_client, Handle, ToolClient},
|
||||||
utils::{errorfmt::ErrorFmt, stack::Stack},
|
utils::stack::Stack,
|
||||||
wire::{jay_compositor, jay_idle, JayIdleId, WlSurfaceId},
|
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) {
|
pub fn main(global: GlobalArgs, args: IdleArgs) {
|
||||||
|
|
@ -93,46 +93,11 @@ impl Idle {
|
||||||
|
|
||||||
async fn set(self, idle: JayIdleId, args: IdleSetArgs) {
|
async fn set(self, idle: JayIdleId, args: IdleSetArgs) {
|
||||||
let tc = &self.tc;
|
let tc = &self.tc;
|
||||||
let interval;
|
let interval = if args.interval.len() == 1 && args.interval[0] == "disabled" {
|
||||||
if args.interval.len() == 1 && args.interval[0] == "disabled" {
|
0
|
||||||
interval = 0;
|
|
||||||
} else {
|
} else {
|
||||||
let comp = parse_components(&args.interval);
|
parse_duration(&args.interval).as_secs() as u64
|
||||||
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);
|
|
||||||
}
|
|
||||||
tc.send(jay_idle::SetInterval {
|
tc.send(jay_idle::SetInterval {
|
||||||
self_id: idle,
|
self_id: idle,
|
||||||
interval,
|
interval,
|
||||||
|
|
@ -140,47 +105,3 @@ impl Idle {
|
||||||
tc.round_trip().await;
|
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')
|
|
||||||
}
|
|
||||||
|
|
|
||||||
|
|
@ -13,6 +13,7 @@ use {
|
||||||
client::{ClientId, Clients},
|
client::{ClientId, Clients},
|
||||||
clientmem::{self, ClientMemError},
|
clientmem::{self, ClientMemError},
|
||||||
config::ConfigProxy,
|
config::ConfigProxy,
|
||||||
|
damage::{visualize_damage, DamageVisualizer},
|
||||||
dbus::Dbus,
|
dbus::Dbus,
|
||||||
forker,
|
forker,
|
||||||
globals::Globals,
|
globals::Globals,
|
||||||
|
|
@ -244,6 +245,7 @@ fn start_compositor2(
|
||||||
tablet_ids: Default::default(),
|
tablet_ids: Default::default(),
|
||||||
tablet_tool_ids: Default::default(),
|
tablet_tool_ids: Default::default(),
|
||||||
tablet_pad_ids: Default::default(),
|
tablet_pad_ids: Default::default(),
|
||||||
|
damage_visualizer: DamageVisualizer::new(&engine),
|
||||||
});
|
});
|
||||||
state.tracker.register(ClientId::from_raw(0));
|
state.tracker.register(ClientId::from_raw(0));
|
||||||
create_dummy_output(&state);
|
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::PostLayout, input_popup_positioning(state.clone())),
|
||||||
eng.spawn2(Phase::Present, perform_toplevel_screencasts(state.clone())),
|
eng.spawn2(Phase::Present, perform_toplevel_screencasts(state.clone())),
|
||||||
eng.spawn2(Phase::PostLayout, perform_screencast_realloc(state.clone())),
|
eng.spawn2(Phase::PostLayout, perform_screencast_realloc(state.clone())),
|
||||||
|
eng.spawn2(Phase::PostLayout, visualize_damage(state.clone())),
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
160
src/damage.rs
Normal file
160
src/damage.rs
Normal file
|
|
@ -0,0 +1,160 @@
|
||||||
|
use {
|
||||||
|
crate::{
|
||||||
|
async_engine::AsyncEngine,
|
||||||
|
rect::{Rect, Region},
|
||||||
|
renderer::renderer_base::RendererBase,
|
||||||
|
state::State,
|
||||||
|
theme::Color,
|
||||||
|
time::Time,
|
||||||
|
utils::{asyncevent::AsyncEvent, errorfmt::ErrorFmt, timer::TimerFd},
|
||||||
|
},
|
||||||
|
isnt::std_1::primitive::IsntSliceExt,
|
||||||
|
std::{
|
||||||
|
cell::{Cell, RefCell},
|
||||||
|
collections::VecDeque,
|
||||||
|
rc::Rc,
|
||||||
|
time::Duration,
|
||||||
|
},
|
||||||
|
uapi::c::CLOCK_MONOTONIC,
|
||||||
|
};
|
||||||
|
|
||||||
|
pub async fn visualize_damage(state: Rc<State>) {
|
||||||
|
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 {
|
||||||
|
eng: Rc<AsyncEngine>,
|
||||||
|
entries: RefCell<VecDeque<Damage>>,
|
||||||
|
entry_added: AsyncEvent,
|
||||||
|
enabled: Cell<bool>,
|
||||||
|
decay: Cell<Duration>,
|
||||||
|
color: Cell<Color>,
|
||||||
|
}
|
||||||
|
|
||||||
|
const MAX_RECTS: usize = 100_000;
|
||||||
|
|
||||||
|
struct Damage {
|
||||||
|
time: Time,
|
||||||
|
rect: Rect,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl DamageVisualizer {
|
||||||
|
pub fn new(eng: &Rc<AsyncEngine>) -> Self {
|
||||||
|
Self {
|
||||||
|
eng: eng.clone(),
|
||||||
|
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)),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[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: self.eng.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 = self.eng.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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1,6 +1,7 @@
|
||||||
use {
|
use {
|
||||||
crate::{
|
crate::{
|
||||||
cursor::Cursor,
|
cursor::Cursor,
|
||||||
|
damage::DamageVisualizer,
|
||||||
fixed::Fixed,
|
fixed::Fixed,
|
||||||
format::Format,
|
format::Format,
|
||||||
rect::Rect,
|
rect::Rect,
|
||||||
|
|
@ -359,6 +360,7 @@ impl dyn GfxFramebuffer {
|
||||||
render_hardware_cursor: bool,
|
render_hardware_cursor: bool,
|
||||||
black_background: bool,
|
black_background: bool,
|
||||||
transform: Transform,
|
transform: Transform,
|
||||||
|
visualizer: Option<&DamageVisualizer>,
|
||||||
) -> GfxRenderPass {
|
) -> GfxRenderPass {
|
||||||
let mut ops = self.take_render_ops();
|
let mut ops = self.take_render_ops();
|
||||||
let mut renderer = Renderer {
|
let mut renderer = Renderer {
|
||||||
|
|
@ -410,6 +412,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 {
|
let c = match black_background {
|
||||||
true => Color::SOLID_BLACK,
|
true => Color::SOLID_BLACK,
|
||||||
false => state.theme.colors.background.get(),
|
false => state.theme.colors.background.get(),
|
||||||
|
|
@ -468,6 +475,7 @@ impl dyn GfxFramebuffer {
|
||||||
render_hardware_cursor,
|
render_hardware_cursor,
|
||||||
black_background,
|
black_background,
|
||||||
transform,
|
transform,
|
||||||
|
None,
|
||||||
);
|
);
|
||||||
self.perform_render_pass(pass)
|
self.perform_render_pass(pass)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -12,6 +12,7 @@ use {
|
||||||
zwp_primary_selection_device_manager_v1::ZwpPrimarySelectionDeviceManagerV1Global,
|
zwp_primary_selection_device_manager_v1::ZwpPrimarySelectionDeviceManagerV1Global,
|
||||||
},
|
},
|
||||||
jay_compositor::JayCompositorGlobal,
|
jay_compositor::JayCompositorGlobal,
|
||||||
|
jay_damage_tracking::JayDamageTrackingGlobal,
|
||||||
org_kde_kwin_server_decoration_manager::OrgKdeKwinServerDecorationManagerGlobal,
|
org_kde_kwin_server_decoration_manager::OrgKdeKwinServerDecorationManagerGlobal,
|
||||||
wl_compositor::WlCompositorGlobal,
|
wl_compositor::WlCompositorGlobal,
|
||||||
wl_output::WlOutputGlobal,
|
wl_output::WlOutputGlobal,
|
||||||
|
|
@ -195,6 +196,7 @@ impl Globals {
|
||||||
add_singleton!(ExtTransientSeatManagerV1Global);
|
add_singleton!(ExtTransientSeatManagerV1Global);
|
||||||
add_singleton!(ZwpPointerGesturesV1Global);
|
add_singleton!(ZwpPointerGesturesV1Global);
|
||||||
add_singleton!(ZwpTabletManagerV2Global);
|
add_singleton!(ZwpTabletManagerV2Global);
|
||||||
|
add_singleton!(JayDamageTrackingGlobal);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn add_backend_singletons(&self, backend: &Rc<dyn Backend>) {
|
pub fn add_backend_singletons(&self, backend: &Rc<dyn Backend>) {
|
||||||
|
|
|
||||||
|
|
@ -6,6 +6,7 @@ pub mod ext_session_lock_manager_v1;
|
||||||
pub mod ext_session_lock_v1;
|
pub mod ext_session_lock_v1;
|
||||||
pub mod ipc;
|
pub mod ipc;
|
||||||
pub mod jay_compositor;
|
pub mod jay_compositor;
|
||||||
|
pub mod jay_damage_tracking;
|
||||||
pub mod jay_idle;
|
pub mod jay_idle;
|
||||||
pub mod jay_input;
|
pub mod jay_input;
|
||||||
pub mod jay_log_file;
|
pub mod jay_log_file;
|
||||||
|
|
|
||||||
135
src/ifs/jay_damage_tracking.rs
Normal file
135
src/ifs/jay_damage_tracking.rs
Normal file
|
|
@ -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<Self>,
|
||||||
|
id: JayCompositorId,
|
||||||
|
client: &Rc<Client>,
|
||||||
|
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<Client>,
|
||||||
|
tracker: Tracker<Self>,
|
||||||
|
version: Version,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl JayDamageTrackingRequestHandler for JayDamageTracking {
|
||||||
|
type Error = JayDamageTrackingError;
|
||||||
|
|
||||||
|
fn destroy(&self, _req: Destroy, _slf: &Rc<Self>) -> Result<(), Self::Error> {
|
||||||
|
self.client.remove_obj(self)?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn set_visualizer_enabled(
|
||||||
|
&self,
|
||||||
|
req: SetVisualizerEnabled,
|
||||||
|
_slf: &Rc<Self>,
|
||||||
|
) -> 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<Self>,
|
||||||
|
) -> 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<Self>,
|
||||||
|
) -> 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<ClientError>),
|
||||||
|
}
|
||||||
|
efrom!(JayDamageTrackingError, ClientError);
|
||||||
|
|
@ -54,6 +54,7 @@ mod compositor;
|
||||||
mod config;
|
mod config;
|
||||||
mod cursor;
|
mod cursor;
|
||||||
mod cursor_user;
|
mod cursor_user;
|
||||||
|
mod damage;
|
||||||
mod dbus;
|
mod dbus;
|
||||||
mod drm_feedback;
|
mod drm_feedback;
|
||||||
mod edid;
|
mod edid;
|
||||||
|
|
|
||||||
|
|
@ -78,6 +78,10 @@ impl Region {
|
||||||
self.extents
|
self.extents
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn rects(&self) -> &[Rect] {
|
||||||
|
unsafe { mem::transmute::<&[RectRaw], &[Rect]>(&self.rects[..]) }
|
||||||
|
}
|
||||||
|
|
||||||
pub fn contains(&self, x: i32, y: i32) -> bool {
|
pub fn contains(&self, x: i32, y: i32) -> bool {
|
||||||
if !self.extents.contains(x, y) {
|
if !self.extents.contains(x, y) {
|
||||||
return false;
|
return false;
|
||||||
|
|
|
||||||
|
|
@ -14,6 +14,7 @@ use {
|
||||||
config::ConfigProxy,
|
config::ConfigProxy,
|
||||||
cursor::{Cursor, ServerCursors},
|
cursor::{Cursor, ServerCursors},
|
||||||
cursor_user::{CursorUserGroup, CursorUserGroupId, CursorUserGroupIds, CursorUserIds},
|
cursor_user::{CursorUserGroup, CursorUserGroupId, CursorUserGroupIds, CursorUserIds},
|
||||||
|
damage::DamageVisualizer,
|
||||||
dbus::Dbus,
|
dbus::Dbus,
|
||||||
drm_feedback::{DrmFeedback, DrmFeedbackIds},
|
drm_feedback::{DrmFeedback, DrmFeedbackIds},
|
||||||
fixed::Fixed,
|
fixed::Fixed,
|
||||||
|
|
@ -199,6 +200,7 @@ pub struct State {
|
||||||
pub tablet_ids: TabletIds,
|
pub tablet_ids: TabletIds,
|
||||||
pub tablet_tool_ids: TabletToolIds,
|
pub tablet_tool_ids: TabletToolIds,
|
||||||
pub tablet_pad_ids: TabletPadIds,
|
pub tablet_pad_ids: TabletPadIds,
|
||||||
|
pub damage_visualizer: DamageVisualizer,
|
||||||
}
|
}
|
||||||
|
|
||||||
// impl Drop for State {
|
// impl Drop for State {
|
||||||
|
|
|
||||||
|
|
@ -23,8 +23,8 @@ use {
|
||||||
},
|
},
|
||||||
wheel::{Wheel, WheelError},
|
wheel::{Wheel, WheelError},
|
||||||
wire::{
|
wire::{
|
||||||
wl_callback, wl_display, wl_registry, JayCompositor, JayCompositorId, WlCallbackId,
|
wl_callback, wl_display, wl_registry, JayCompositor, JayCompositorId,
|
||||||
WlRegistryId,
|
JayDamageTracking, JayDamageTrackingId, WlCallbackId, WlRegistryId,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
ahash::AHashMap,
|
ahash::AHashMap,
|
||||||
|
|
@ -92,6 +92,7 @@ pub struct ToolClient {
|
||||||
outgoing: Cell<Option<SpawnedFuture<()>>>,
|
outgoing: Cell<Option<SpawnedFuture<()>>>,
|
||||||
singletons: CloneCell<Option<Rc<Singletons>>>,
|
singletons: CloneCell<Option<Rc<Singletons>>>,
|
||||||
jay_compositor: Cell<Option<JayCompositorId>>,
|
jay_compositor: Cell<Option<JayCompositorId>>,
|
||||||
|
jay_damage_tracking: Cell<Option<Option<JayDamageTrackingId>>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn with_tool_client<T, F>(level: Level, f: F)
|
pub fn with_tool_client<T, F>(level: Level, f: F)
|
||||||
|
|
@ -188,6 +189,7 @@ impl ToolClient {
|
||||||
outgoing: Default::default(),
|
outgoing: Default::default(),
|
||||||
singletons: Default::default(),
|
singletons: Default::default(),
|
||||||
jay_compositor: Default::default(),
|
jay_compositor: Default::default(),
|
||||||
|
jay_damage_tracking: Default::default(),
|
||||||
});
|
});
|
||||||
wl_display::Error::handle(&slf, WL_DISPLAY_ID, (), |_, val| {
|
wl_display::Error::handle(&slf, WL_DISPLAY_ID, (), |_, val| {
|
||||||
fatal!("The compositor returned a fatal error: {}", val.message);
|
fatal!("The compositor returned a fatal error: {}", val.message);
|
||||||
|
|
@ -285,6 +287,7 @@ impl ToolClient {
|
||||||
#[derive(Default)]
|
#[derive(Default)]
|
||||||
struct S {
|
struct S {
|
||||||
jay_compositor: Cell<Option<u32>>,
|
jay_compositor: Cell<Option<u32>>,
|
||||||
|
jay_damage_tracking: Cell<Option<u32>>,
|
||||||
}
|
}
|
||||||
let s = Rc::new(S::default());
|
let s = Rc::new(S::default());
|
||||||
let registry: WlRegistryId = self.id();
|
let registry: WlRegistryId = self.id();
|
||||||
|
|
@ -295,6 +298,8 @@ impl ToolClient {
|
||||||
wl_registry::Global::handle(self, registry, s.clone(), |s, g| {
|
wl_registry::Global::handle(self, registry, s.clone(), |s, g| {
|
||||||
if g.interface == JayCompositor.name() {
|
if g.interface == JayCompositor.name() {
|
||||||
s.jay_compositor.set(Some(g.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;
|
self.round_trip().await;
|
||||||
|
|
@ -309,6 +314,7 @@ impl ToolClient {
|
||||||
let res = Rc::new(Singletons {
|
let res = Rc::new(Singletons {
|
||||||
registry,
|
registry,
|
||||||
jay_compositor: get!(jay_compositor, JayCompositor),
|
jay_compositor: get!(jay_compositor, JayCompositor),
|
||||||
|
jay_damage_tracking: s.jay_damage_tracking.get(),
|
||||||
});
|
});
|
||||||
self.singletons.set(Some(res.clone()));
|
self.singletons.set(Some(res.clone()));
|
||||||
res
|
res
|
||||||
|
|
@ -330,11 +336,33 @@ impl ToolClient {
|
||||||
self.jay_compositor.set(Some(id));
|
self.jay_compositor.set(Some(id));
|
||||||
id
|
id
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub async fn jay_damage_tracking(self: &Rc<Self>) -> Option<JayDamageTrackingId> {
|
||||||
|
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 {
|
pub struct Singletons {
|
||||||
registry: WlRegistryId,
|
registry: WlRegistryId,
|
||||||
pub jay_compositor: u32,
|
pub jay_compositor: u32,
|
||||||
|
pub jay_damage_tracking: Option<u32>,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub const NONE_FUTURE: Option<Pending<()>> = None;
|
pub const NONE_FUTURE: Option<Pending<()>> = None;
|
||||||
|
|
|
||||||
|
|
@ -49,7 +49,7 @@ impl Parser for ColorParser {
|
||||||
3 => (s(0..1)?, s(1..2)?, s(2..3)?, u8::MAX),
|
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)?),
|
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),
|
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)),
|
_ => return Err(ColorParserError::Length.spanned(span)),
|
||||||
};
|
};
|
||||||
Ok(Color::new_straight(r, g, b, a))
|
Ok(Color::new_straight(r, g, b, a))
|
||||||
|
|
|
||||||
18
wire/jay_damage_tracking.txt
Normal file
18
wire/jay_damage_tracking.txt
Normal file
|
|
@ -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),
|
||||||
|
}
|
||||||
Loading…
Add table
Add a link
Reference in a new issue