Skip to content

Commit 1a73bd9

Browse files
committed
WIP: LED interface
1 parent 8425f13 commit 1a73bd9

File tree

3 files changed

+237
-5
lines changed

3 files changed

+237
-5
lines changed

src/dbus/interface/led.rs

Lines changed: 195 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,195 @@
1+
use std::{error::Error, future::Future};
2+
3+
use zbus::{fdo, message::Header, Connection};
4+
use zbus_macros::interface;
5+
6+
use crate::dbus::polkit::check_polkit;
7+
8+
use super::Unregisterable;
9+
10+
/// [LedEmitter] is any device that can implement changing LED colors
11+
pub trait LedEmitter {
12+
fn name(&self) -> impl Future<Output = Result<String, Box<dyn Error>>> + Send;
13+
fn get_colors_available(
14+
&self,
15+
) -> impl Future<Output = Result<Vec<String>, Box<dyn Error>>> + Send;
16+
fn set_enabled(
17+
&mut self,
18+
enabled: bool,
19+
) -> impl Future<Output = Result<(), Box<dyn Error>>> + Send;
20+
fn rumble(&mut self, value: f64) -> impl Future<Output = Result<(), Box<dyn Error>>> + Send;
21+
fn stop(&mut self) -> impl Future<Output = Result<(), Box<dyn Error>>> + Send;
22+
}
23+
24+
/// DBus interface for a particular LED zone
25+
pub struct LedInterface<T>
26+
where
27+
T: LedEmitter + Send + Sync,
28+
{
29+
device: T,
30+
name: String,
31+
colors: Vec<String>,
32+
}
33+
34+
impl<T> LedInterface<T>
35+
where
36+
T: LedEmitter + Send + Sync,
37+
{
38+
/// Create a new dbus interface for the given LED zone
39+
pub fn new(device: T, name: String, colors: Vec<String>) -> Self {
40+
Self {
41+
device,
42+
name,
43+
colors,
44+
}
45+
}
46+
}
47+
48+
#[interface(
49+
name = "org.shadowblip.Output.LED",
50+
proxy(default_service = "org.shadowblip.InputPlumber")
51+
)]
52+
impl<T> LedInterface<T>
53+
where
54+
T: LedEmitter + Send + Sync + 'static,
55+
{
56+
/// Name of the LED zone
57+
#[zbus(property)]
58+
async fn name(&self) -> fdo::Result<String> {
59+
Ok(self.name.clone())
60+
}
61+
62+
/// Colors available for the LED zone
63+
#[zbus(property)]
64+
async fn colors(&self) -> fdo::Result<Vec<String>> {
65+
Ok(self.colors.clone())
66+
}
67+
}
68+
69+
impl<T> Unregisterable for LedInterface<T> where T: LedEmitter + Send + Sync {}
70+
71+
/// DBus interface for controlling a single LED color
72+
pub struct LedColorInterface {
73+
color: String,
74+
value: f64,
75+
brightness: f64,
76+
}
77+
78+
impl LedColorInterface {
79+
/// Create a new dbus interface for the given color
80+
pub fn new(color: &str) -> Self {
81+
Self {
82+
color: color.to_string(),
83+
value: 0.0,
84+
brightness: 0.0,
85+
}
86+
}
87+
88+
/// Update the color value
89+
pub fn update_value(conn: &Connection, path: &str, value: f64) {
90+
let conn = conn.clone();
91+
let path = path.to_string();
92+
tokio::task::spawn(async move {
93+
// Get the object instance at the given path so we can send DBus signal
94+
// updates
95+
let iface_ref = match conn
96+
.object_server()
97+
.interface::<_, Self>(path.clone())
98+
.await
99+
{
100+
Ok(iface) => iface,
101+
Err(e) => {
102+
log::error!("Failed to get DBus interface {path}: {e}");
103+
return;
104+
}
105+
};
106+
107+
let mut iface = iface_ref.get_mut().await;
108+
iface.value = value;
109+
let result = iface.value_changed(iface_ref.signal_emitter()).await;
110+
if let Err(e) = result {
111+
log::error!("Failed to signal property changed: {e}");
112+
}
113+
});
114+
}
115+
116+
/// Update the color brightness
117+
pub fn update_brightness(conn: &Connection, path: &str, value: f64) {
118+
let conn = conn.clone();
119+
let path = path.to_string();
120+
tokio::task::spawn(async move {
121+
// Get the object instance at the given path so we can send DBus signal
122+
// updates
123+
let iface_ref = match conn
124+
.object_server()
125+
.interface::<_, Self>(path.clone())
126+
.await
127+
{
128+
Ok(iface) => iface,
129+
Err(e) => {
130+
log::error!("Failed to get DBus interface {path}: {e}");
131+
return;
132+
}
133+
};
134+
135+
let mut iface = iface_ref.get_mut().await;
136+
iface.brightness = value;
137+
let result = iface.brightness_changed(iface_ref.signal_emitter()).await;
138+
if let Err(e) = result {
139+
log::error!("Failed to signal property changed: {e}");
140+
}
141+
});
142+
}
143+
}
144+
145+
#[interface(
146+
name = "org.shadowblip.Output.LED.Color",
147+
proxy(default_service = "org.shadowblip.InputPlumber")
148+
)]
149+
impl LedColorInterface {
150+
/// Name of the LED color
151+
#[zbus(property)]
152+
async fn color(&self) -> fdo::Result<String> {
153+
Ok(self.color.clone())
154+
}
155+
156+
/// Intensity value of the LED color. A value of 1.0 is maximum intensity, and 0.0 is minimum
157+
/// intensity.
158+
#[zbus(property)]
159+
async fn value(&self) -> fdo::Result<f64> {
160+
Ok(self.value)
161+
}
162+
163+
#[zbus(property)]
164+
async fn set_value(
165+
&mut self,
166+
value: f64,
167+
#[zbus(connection)] conn: &Connection,
168+
#[zbus(header)] hdr: Option<Header<'_>>,
169+
) -> fdo::Result<()> {
170+
check_polkit(conn, hdr, "org.shadowblip.Output.LED.Color.Value").await?;
171+
self.value = value.clamp(0.0, 1.0);
172+
Ok(())
173+
}
174+
175+
/// Brightness value of the LED color. A value of 1.0 is maximum brightness, and 0.0 is minimum
176+
/// brightness.
177+
#[zbus(property)]
178+
async fn brightness(&self) -> fdo::Result<f64> {
179+
Ok(0.0)
180+
}
181+
182+
#[zbus(property)]
183+
async fn set_brightness(
184+
&mut self,
185+
value: f64,
186+
#[zbus(connection)] conn: &Connection,
187+
#[zbus(header)] hdr: Option<Header<'_>>,
188+
) -> fdo::Result<()> {
189+
check_polkit(conn, hdr, "org.shadowblip.Output.LED.Color.Brightness").await?;
190+
self.brightness = value.clamp(0.0, 1.0);
191+
Ok(())
192+
}
193+
}
194+
195+
impl Unregisterable for LedColorInterface {}

src/dbus/interface/mod.rs

Lines changed: 36 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ use zbus::{names::InterfaceName, object_server::Interface, zvariant::ObjectPath,
55

66
pub mod composite_device;
77
pub mod force_feedback;
8+
pub mod led;
89
pub mod manager;
910
pub mod performance;
1011
pub mod source;
@@ -19,13 +20,14 @@ pub enum InterfaceError {
1920
/// Manages dbus interface registration and deregistration. When the interface
2021
/// manager goes out of scope, all registered interfaces are automatically
2122
/// unregistered.
22-
pub struct DBusInterfaceManager {
23+
pub struct DBusInterfaceManager<'path> {
2324
dbus: Connection,
2425
dbus_path: String,
2526
dbus_ifaces: HashMap<InterfaceName<'static>, Box<UnregisterFn>>,
27+
children: HashMap<ObjectPath<'path>, DBusInterfaceManager<'path>>,
2628
}
2729

28-
impl DBusInterfaceManager {
30+
impl<'path> DBusInterfaceManager<'path> {
2931
/// Creates a new dbus interface manager to manage one or more dbus interfaces
3032
/// for the given dbus path.
3133
///
@@ -71,6 +73,7 @@ impl DBusInterfaceManager {
7173
dbus: conn,
7274
dbus_path: dbus_path.to_string(),
7375
dbus_ifaces: Default::default(),
76+
children: Default::default(),
7477
})
7578
}
7679

@@ -135,6 +138,35 @@ impl DBusInterfaceManager {
135138
});
136139
}
137140

141+
/// Register and start the given dbus interface as a child object. The given
142+
/// path should be relative to the parent path.
143+
pub fn register_child<P, I>(&mut self, path: P, iface: I) -> Result<(), InterfaceError>
144+
where
145+
P: TryInto<ObjectPath<'path>>,
146+
I: Interface + Unregisterable,
147+
{
148+
let parent_path = self.path();
149+
let child_path: ObjectPath<'path> = match path.try_into() {
150+
Ok(path) => path,
151+
Err(_) => {
152+
return Err(InterfaceError::PathError);
153+
}
154+
};
155+
156+
let path = format!("{parent_path}{}", child_path.to_string());
157+
let path: ObjectPath<'path> = match path.try_into() {
158+
Ok(path) => path,
159+
Err(_) => {
160+
return Err(InterfaceError::PathError);
161+
}
162+
};
163+
let mut child_manager = Self::new(self.connection().clone(), path.clone())?;
164+
child_manager.register(iface);
165+
self.children.insert(path, child_manager);
166+
167+
Ok(())
168+
}
169+
138170
/// Returns true if the interface with the given name has been registered.
139171
pub fn has_interface(&self, iface_name: &InterfaceName<'static>) -> bool {
140172
self.dbus_ifaces.contains_key(iface_name)
@@ -190,7 +222,7 @@ impl DBusInterfaceManager {
190222
}
191223
}
192224

193-
impl Debug for DBusInterfaceManager {
225+
impl Debug for DBusInterfaceManager<'_> {
194226
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
195227
let mut dbus_ifaces: HashMap<String, &str> = HashMap::with_capacity(self.dbus_ifaces.len());
196228
for (key, _value) in self.dbus_ifaces.iter() {
@@ -204,7 +236,7 @@ impl Debug for DBusInterfaceManager {
204236
}
205237
}
206238

207-
impl Drop for DBusInterfaceManager {
239+
impl Drop for DBusInterfaceManager<'_> {
208240
/// Unregister all dbus interfaces when this goes out of scope
209241
fn drop(&mut self) {
210242
self.unregister_all();

src/input/composite_device/mod.rs

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@ use crate::{
3232
value::{InputValue, TranslationError},
3333
Event,
3434
},
35-
output_capability::OutputCapability,
35+
output_capability::{OutputCapability, LED},
3636
output_event::UinputOutputEvent,
3737
source::{
3838
evdev::EventDevice, hidraw::HidRawDevice, iio::IioDevice, led::LedDevice,
@@ -1800,6 +1800,11 @@ impl CompositeDevice {
18001800
let iface = ForceFeedbackInterface::new(self.client());
18011801
self.dbus.register(iface);
18021802
}
1803+
1804+
// Determine if the LED dbus interface(s) should be created
1805+
let supports_leds = self
1806+
.output_capabilities
1807+
.contains(&OutputCapability::LED(LED::Color));
18031808
}
18041809

18051810
// Check if this device should be blocked from sending events to target devices.

0 commit comments

Comments
 (0)