use super::Huntsman;
use crate::{
capabilities::{
AvailableCapabilities, BreathEffect, Brightness, CapabilityBase, CustomEffect, GameMode,
KeyboardLayout, MacroEffect, MacroMode, MatrixDimensions, NoEffect, ReactiveEffect,
RippleEffect, SpectrumEffect, StarlightEffect, StaticEffect, WaveEffect,
},
devices::Device,
Error,
};
use dbus_async::DBus;
use dbus_message_parser::{Message, Value};
use futures::future::BoxFuture;
use std::{collections::HashMap, sync::Arc};
use xmltree::Element;
pub(crate) const DEVICE_INTERFACE: &str = "razer.device.misc";
const INTROSPECT_INTERFACE: &str = "org.freedesktop.DBus.Introspectable";
#[derive(Debug, Clone)]
pub struct GenericDevice {
pub(crate) dbus: DBus,
pub(crate) serial: String,
pub(crate) path: Arc<str>,
pub(crate) available: AvailableCapabilities,
}
pub trait GenericMethods {
fn get_generic_device(&self) -> &GenericDevice;
fn serial(&self) -> &str {
&self.get_generic_device().serial
}
fn capabilities(&self) -> AvailableCapabilities {
self.get_generic_device().available
}
fn name(&self) -> BoxFuture<'_, Result<String, Error>> {
let device = self.get_generic_device();
Box::pin(async move {
let (_, body) = device
.dbus
.call(device.message(DEVICE_INTERFACE, "getDeviceName"))
.await?
.split();
match body.into_iter().next() {
Some(Value::String(name)) => Ok(name),
_ => Err(Error::InvalidResponse),
}
})
}
fn firmware_version(&self) -> BoxFuture<'_, Result<String, Error>> {
let device = self.get_generic_device();
Box::pin(async move {
let (_, body) = device
.dbus
.call(device.message(DEVICE_INTERFACE, "getFirmware"))
.await?
.split();
match body.into_iter().next() {
Some(Value::String(version)) => Ok(version),
_ => Err(Error::InvalidResponse),
}
})
}
fn driver_version(&self) -> BoxFuture<'_, Result<String, Error>> {
let device = self.get_generic_device();
Box::pin(async move {
let (_, body) = device
.dbus
.call(device.message(DEVICE_INTERFACE, "getDriverVersion"))
.await?
.split();
match body.into_iter().next() {
Some(Value::String(version)) => Ok(version),
_ => Err(Error::InvalidResponse),
}
})
}
fn has_dedicated_macro_keys(&self) -> BoxFuture<'_, Result<bool, Error>> {
let device = self.get_generic_device();
Box::pin(async move {
let response = device
.dbus
.call(device.message(DEVICE_INTERFACE, "hasDedicatedMacroKeys"))
.await?;
match response.get_body() {
[Value::Boolean(has_keys)] => Ok(*has_keys),
_ => Err(Error::InvalidResponse),
}
})
}
fn razer_urls(&self) -> BoxFuture<'_, Result<HashMap<String, String>, Error>> {
let device = self.get_generic_device();
Box::pin(async move {
let response = device
.dbus
.call(device.message(DEVICE_INTERFACE, "getRazerUrls"))
.await?;
match response.get_body() {
[Value::String(json)] => serde_json::from_str(json).or(Err(Error::InvalidResponse)),
_ => Err(Error::InvalidResponse),
}
})
}
}
impl GenericDevice {
pub(crate) async fn new(dbus: DBus, serial: String) -> Result<Self, Error> {
let path = format!("/org/razer/device/{}", &serial);
let available_capabilities = get_available_capabilities(&dbus, &path).await?;
Ok(Self {
dbus,
serial,
path: path.into(),
available: available_capabilities,
})
}
pub(crate) fn message(&self, interface: &'static str, method: &'static str) -> Message {
Message::method_call(crate::DESTINATION, &self.path, interface, method)
}
pub(crate) fn capability_base(&self) -> CapabilityBase {
CapabilityBase::new(self.dbus.clone(), Arc::clone(&self.path))
}
pub(crate) async fn into_device(self) -> Result<Device, Error> {
Ok(match self.name().await?.as_str() {
Huntsman::NAME => Device::Huntsman(Huntsman(self)),
_ => Device::Other(self),
})
}
}
async fn get_available_capabilities(
dbus: &DBus,
path: &str,
) -> Result<AvailableCapabilities, Error> {
let message =
Message::method_call(crate::DESTINATION, path, INTROSPECT_INTERFACE, "Introspect");
let response = dbus.call(message).await?;
let xml = match response.get_body() {
[Value::String(xml)] => xml,
_ => return Err(Error::InvalidResponse),
};
let mut node = Element::parse(xml.as_bytes()).or(Err(Error::InvalidResponse))?;
let mut capabilities = AvailableCapabilities::default();
while let Some(mut interface) = node.take_child("interface") {
let interface_name = interface
.attributes
.get("name")
.ok_or(Error::InvalidResponse)?
.to_owned();
if interface_name == INTROSPECT_INTERFACE {
continue;
}
while let Some(method) = interface.take_child("method") {
let method_name = method
.attributes
.get("name")
.ok_or(Error::InvalidResponse)?;
match (interface_name.as_str(), method_name.as_str()) {
(BreathEffect::INTERFACE, BreathEffect::METHOD_SINGLE) => {
capabilities.breath_effect.single = true
}
(BreathEffect::INTERFACE, BreathEffect::METHOD_DUAL) => {
capabilities.breath_effect.dual = true
}
(BreathEffect::INTERFACE, BreathEffect::METHOD_TRIPLE) => {
capabilities.breath_effect.triple = true
}
(BreathEffect::INTERFACE, BreathEffect::METHOD_RANDOM) => {
capabilities.breath_effect.random = true
}
(Brightness::INTERFACE, Brightness::GET_METHOD) => capabilities.brightness = true,
(CustomEffect::INTERFACE, CustomEffect::SET_CUSTOM_METHOD) => {
capabilities.custom_effect.set_effect = true
}
(CustomEffect::INTERFACE, CustomEffect::SET_ROW_METHOD) => {
capabilities.custom_effect.set_key_row = true
}
(GameMode::INTERFACE, GameMode::GET_METHOD) => capabilities.game_mode = true,
(KeyboardLayout::INTERFACE, KeyboardLayout::METHOD) => {
capabilities.keyboard_layout = true
}
(MacroEffect::INTERFACE, MacroEffect::GET_METHOD) => {
capabilities.macro_effect = true
}
(MacroMode::INTERFACE, MacroMode::GET_METHOD) => capabilities.macro_mode = true,
(MatrixDimensions::INTERFACE, MatrixDimensions::GET_METHOD) => {
capabilities.matrix_dimensions = true
}
(NoEffect::INTERFACE, NoEffect::METHOD) => capabilities.no_effect = true,
(ReactiveEffect::INTERFACE, ReactiveEffect::METHOD) => {
capabilities.reactive_effect = true
}
(RippleEffect::INTERFACE, RippleEffect::METHOD) => {
capabilities.ripple_effect.single = true
}
(RippleEffect::INTERFACE, RippleEffect::METHOD_RANDOM) => {
capabilities.ripple_effect.random = true
}
(SpectrumEffect::INTERFACE, SpectrumEffect::METHOD) => {
capabilities.spectrum_effect = true
}
(StarlightEffect::INTERFACE, StarlightEffect::METHOD_SINGLE) => {
capabilities.starlight_effect.single = true
}
(StarlightEffect::INTERFACE, StarlightEffect::METHOD_DUAL) => {
capabilities.starlight_effect.dual = true
}
(StarlightEffect::INTERFACE, StarlightEffect::METHOD_RANDOM) => {
capabilities.starlight_effect.random = true
}
(StaticEffect::INTERFACE, StaticEffect::METHOD) => {
capabilities.static_effect = true
}
(WaveEffect::INTERFACE, WaveEffect::METHOD) => capabilities.wave_effect = true,
_ => (),
}
}
}
Ok(capabilities)
}
impl GenericMethods for GenericDevice {
fn get_generic_device(&self) -> &GenericDevice {
self
}
}