diff --git a/Cargo.lock b/Cargo.lock index 672b9e86..592eb7ee 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -42,6 +42,16 @@ dependencies = [ "cfg-if", ] +[[package]] +name = "dbus" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "add8dd36d6d34a084220eb9fe216d3e230d52b37c31702e1ffda4fb2d4ef950e" +dependencies = [ + "libc", + "libdbus-sys", +] + [[package]] name = "filetime" version = "0.2.9" @@ -80,6 +90,15 @@ version = "0.2.68" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dea0c0405123bba743ee3f91f49b1c7cfb684eef0da0a50110f758ccf24cdff0" +[[package]] +name = "libdbus-sys" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc12a3bc971424edbbf7edaf6e5740483444db63aa8e23d3751ff12a30f306f0" +dependencies = [ + "pkg-config", +] + [[package]] name = "libflate" version = "0.1.27" @@ -144,8 +163,9 @@ checksum = "cabe4fa914dec5870285fa7f71f602645da47c486e68486d2b4ceb4a343e90ac" [[package]] name = "rog-core" -version = "0.1.0" +version = "0.2.0" dependencies = [ + "dbus", "gumdrop", "rusb", ] diff --git a/Cargo.toml b/Cargo.toml index 65f25a8c..b2151c7a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -7,3 +7,4 @@ edition = "2018" [dependencies] rusb = "0.5" gumdrop = "0.8" +dbus = "0.7.1" diff --git a/Makefile b/Makefile new file mode 100644 index 00000000..3504bb51 --- /dev/null +++ b/Makefile @@ -0,0 +1,59 @@ +prefix ?= /usr +sysconfdir ?= /etc +exec_prefix = $(prefix) +bindir = $(exec_prefix)/bin +libdir = $(exec_prefix)/lib +includedir = $(prefix)/include +datarootdir = $(prefix)/share +datadir = $(datarootdir) + +SRC = Cargo.toml Cargo.lock Makefile $(shell find src -type f -wholename '*src/*.rs') + +.PHONY: all clean distclean install uninstall update + +BIN=rog-core + +DEBUG ?= 0 +ifeq ($(DEBUG),0) + ARGS += "--release" + TARGET = release +endif + +VENDORED ?= 0 +ifeq ($(VENDORED),1) + ARGS += "--frozen" +endif + +all: target/release/$(BIN) + +clean: + cargo clean + +distclean: + rm -rf .cargo vendor vendor.tar.xz + +install: all + install -D -m 04755 "target/release/$(BIN)" "$(DESTDIR)$(bindir)/$(BIN)" + install -D -m 0644 "data/$(BIN).conf" "$(DESTDIR)$(sysconfdir)/dbus-1/system.d/$(BIN).conf" + install -D -m 0644 "data/$(BIN).service" "$(DESTDIR)$(libdir)/systemd/system/$(BIN).service" + +uninstall: + rm -f "$(DESTDIR)$(bindir)/$(BIN)" + rm -f "$(DESTDIR)$(sysconfdir)/dbus-1/system.d/$(BIN).conf" + rm -f "$(DESTDIR)$(libdir)/systemd/system/$(BIN).service" + +update: + cargo update + +vendor: + mkdir -p .cargo + cargo vendor | head -n -1 > .cargo/config + echo 'directory = "vendor"' >> .cargo/config + tar pcfJ vendor.tar.xz vendor + rm -rf vendor + +target/release/$(BIN): $(SRC) +ifeq ($(VENDORED),1) + tar pxf vendor.tar.xz +endif + cargo build $(ARGS) diff --git a/data/rog-core.conf b/data/rog-core.conf new file mode 100644 index 00000000..f6c4a1f9 --- /dev/null +++ b/data/rog-core.conf @@ -0,0 +1,18 @@ + + + + + + + + + + + + + + + + diff --git a/data/rog-core.service b/data/rog-core.service new file mode 100644 index 00000000..b5183b96 --- /dev/null +++ b/data/rog-core.service @@ -0,0 +1,9 @@ +[Unit] +Description=ROG Core Daemon + +[Service] +ExecStart=/usr/bin/rog-core -d +Restart=on-failure + +[Install] +WantedBy=multi-user.target \ No newline at end of file diff --git a/src/aura.rs b/src/aura.rs index 9b50e045..d8bca0d8 100644 --- a/src/aura.rs +++ b/src/aura.rs @@ -1,45 +1,11 @@ use crate::core::LED_MSG_LEN; +use crate::error::AuraError; use gumdrop::Options; -use std::error::Error; -use std::fmt; -use std::fmt::{Debug, Display}; +use std::fmt::Debug; use std::str::FromStr; -#[derive(PartialEq)] -pub enum AuraError { - ParseColour, - ParseSpeed, - ParseDirection, - ParseBrightness, -} - -impl Debug for AuraError { - #[inline] - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - Display::fmt(self.description(), f) - } -} - -impl Display for AuraError { - #[inline] - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - Display::fmt(self.description(), f) - } -} - -impl Error for AuraError { - fn description(&self) -> &str { - match self { - AuraError::ParseColour => "could not parse colour", - AuraError::ParseSpeed => "could not parse speed", - AuraError::ParseDirection => "could not parse direction", - AuraError::ParseBrightness => "could not parse brightness", - } - } -} - #[derive(Debug, PartialEq)] -pub struct Colour(u8, u8, u8); +pub(crate) struct Colour(u8, u8, u8); impl Default for Colour { fn default() -> Self { Colour(255, 0, 0) @@ -60,7 +26,7 @@ impl FromStr for Colour { } #[derive(Debug, PartialEq)] -pub enum Speed { +pub(crate) enum Speed { Low = 0xe1, Med = 0xeb, High = 0xf5, @@ -88,7 +54,7 @@ impl FromStr for Speed { /// /// Enum corresponds to the required integer value #[derive(Debug, PartialEq)] -pub enum Direction { +pub(crate) enum Direction { Right, Left, Up, @@ -115,7 +81,7 @@ impl FromStr for Direction { } #[derive(Debug, PartialEq, Options)] -pub struct Breathe { +pub(crate) struct Breathe { #[options(help = "print help message")] help: bool, #[options(no_long, help = "set the first colour, must be hex string e.g, ff00ff")] @@ -130,7 +96,7 @@ pub struct Breathe { } #[derive(Debug, PartialEq, Options)] -pub struct SingleSpeed { +pub(crate) struct SingleSpeed { #[options(help = "print help message")] help: bool, #[options(no_long, help = "set the speed: low, med, high")] @@ -138,7 +104,7 @@ pub struct SingleSpeed { } #[derive(Debug, PartialEq, Options)] -pub struct SingleColour { +pub(crate) struct SingleColour { #[options(help = "print help message")] help: bool, #[options(no_long, help = "set the colour, must be hex string e.g, ff00ff")] @@ -146,7 +112,7 @@ pub struct SingleColour { } #[derive(Debug, PartialEq, Options)] -pub struct SingleSpeedDirection { +pub(crate) struct SingleSpeedDirection { #[options(help = "print help message")] help: bool, #[options(no_long, help = "set the direction: up, down, left, right")] @@ -156,7 +122,7 @@ pub struct SingleSpeedDirection { } #[derive(Debug, PartialEq, Options)] -pub struct SingleColourSpeed { +pub(crate) struct SingleColourSpeed { #[options(help = "print help message")] help: bool, #[options(no_long, help = "set the colour, must be hex string e.g, ff00ff")] @@ -169,7 +135,7 @@ pub struct SingleColourSpeed { /// /// Enum corresponds to the required integer value #[derive(Debug, Options)] -pub enum SetAuraBuiltin { +pub(crate) enum SetAuraBuiltin { #[options(help = "set a single static colour")] Stable(SingleColour), #[options(help = "pulse between one or two colours")] @@ -247,7 +213,7 @@ impl Default for SetAuraBuiltin { /// - 0x03 = downwards /// /// Bytes 10, 11, 12 are Red, Green, Blue for second colour if mode supports it -pub struct ModeMessage(pub [u8; LED_MSG_LEN]); +pub(crate) struct ModeMessage(pub [u8; LED_MSG_LEN]); impl From for ModeMessage { fn from(mode: SetAuraBuiltin) -> Self { diff --git a/src/core.rs b/src/core.rs index aa6aa0b0..59912f39 100644 --- a/src/core.rs +++ b/src/core.rs @@ -1,5 +1,7 @@ -use crate::aura::AuraError; -use crate::aura::ModeMessage; +use crate::error::AuraError; +use crate::{DBUS_IFACE, DBUS_NAME, DBUS_PATH}; +use dbus::Error as DbusError; +use dbus::{ffidisp::Connection, Message}; use gumdrop::Options; use rusb::{DeviceHandle, Error}; use std::str::FromStr; @@ -17,7 +19,7 @@ static LED_APPLY: [u8; 17] = [0x5d, 0xb4, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, static LED_SET: [u8; 17] = [0x5d, 0xb5, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]; #[derive(Debug, Options)] -pub struct LedBrightness { +pub(crate) struct LedBrightness { pub level: u8, } impl FromStr for LedBrightness { @@ -47,7 +49,7 @@ impl FromStr for LedBrightness { /// - `LED_INIT4` /// - `LED_INIT2` /// - `LED_INIT4` -pub struct RogCore { +pub(crate) struct RogCore { handle: DeviceHandle, initialised: bool, led_interface_num: u8, @@ -96,7 +98,7 @@ impl RogCore { Err(Error::NoDevice) } - fn aura_write_messages(&mut self, messages: &[[u8; LED_MSG_LEN]]) -> Result<(), Error> { + fn aura_write_messages(&mut self, messages: &[&[u8]]) -> Result<(), Error> { self.handle.claim_interface(self.led_interface_num)?; // Declared as a zoomy so that it is hidden let write = |message: &[u8]| { @@ -114,7 +116,7 @@ impl RogCore { for message in messages { println!("{:x?}", &message); - write(message)?; + write(*message)?; write(&LED_SET)?; } // Changes won't persist unless apply is set @@ -124,19 +126,31 @@ impl RogCore { Ok(()) } - pub fn aura_set_brightness(&mut self, brightness: u8) -> Result<(), Error> { + pub fn aura_brightness_bytes(brightness: u8) -> Result<[u8; 17], Error> { let mut bright = [0u8; LED_MSG_LEN]; bright[0] = 0x5a; bright[1] = 0xba; bright[2] = 0xc5; bright[3] = 0xc4; bright[4] = brightness; - let messages = [bright]; - self.aura_write_messages(&messages) + Ok(bright) } - pub fn aura_set_mode(&mut self, mode: ModeMessage) -> Result<(), Error> { - let messages = [mode.0]; + pub fn aura_set_mode(&mut self, mode: &[u8]) -> Result<(), Error> { + let messages = [mode]; self.aura_write_messages(&messages) } } + +pub(super) fn dbus_led_builtin_write(bytes: &[u8]) -> Result<(), Box> { + let bus = Connection::new_system()?; + //let proxy = bus.with_proxy(DBUS_IFACE, "/", Duration::from_millis(5000)); + let msg = Message::new_method_call(DBUS_NAME, DBUS_PATH, DBUS_IFACE, "ledmessage")? + .append1(bytes.to_vec()); + let r = bus.send_with_reply_and_block(msg, 5000)?; + if let Some(reply) = r.get1::<&str>() { + println!("Daemon sez: {}", reply); + return Ok(()); + } + Err(Box::new(DbusError::new_custom("name", "message"))) +} diff --git a/src/daemon.rs b/src/daemon.rs new file mode 100644 index 00000000..7120d29b --- /dev/null +++ b/src/daemon.rs @@ -0,0 +1,55 @@ +use crate::{core::RogCore, DBUS_IFACE, DBUS_PATH}; +use dbus::{ + blocking::Connection, + tree::{Factory, MethodErr}, +}; +use std::error::Error; +use std::time::Duration; + +pub fn start_daemon() -> Result<(), Box> { + let mut c = Connection::new_system()?; + c.request_name(DBUS_IFACE, false, true, false)?; + + // The choice of factory tells us what type of tree we want, + // and if we want any extra data inside. We pick the simplest variant. + let f = Factory::new_fn::<()>(); + + // We create a tree with one object path inside and make that path introspectable. + let tree = f.tree(()).add( + f.object_path(DBUS_PATH, ()).introspectable().add( + // We add an interface to the object path... + f.interface(DBUS_IFACE, ()) + // ...and a method inside the interface + .add_m( + f.method("ledmessage", (), move |m| { + // Reads the args passed to the method + // Reads the args passed to the method + let bytes: Vec = m.msg.read1()?; + let s = format!("Shoving {:x?} in to keyboard!", bytes); + + if let Ok(mut core) = RogCore::new() { + match core.aura_set_mode(&bytes[..]) { + Ok(_) => { + let mret = m.msg.method_return().append1(s); + Ok(vec![mret]) + } + Err(err) => Err(MethodErr::failed(&err)), + } + } else { + Err(MethodErr::failed("Failed to start RogCore")) + } + }) + // Input? + .outarg::<&str, _>("reply") + .inarg::, _>("bytearray"), + ), + ), + ); + + // We add the tree to the connection so that incoming method calls will be handled. + tree.start_receive(&c); + + loop { + c.process(Duration::from_secs(1))?; + } +} diff --git a/src/error.rs b/src/error.rs new file mode 100644 index 00000000..b49d61ef --- /dev/null +++ b/src/error.rs @@ -0,0 +1,36 @@ +use std::error::Error; +use std::fmt; +use std::fmt::{Debug, Display}; + +#[derive(PartialEq)] +pub(crate) enum AuraError { + ParseColour, + ParseSpeed, + ParseDirection, + ParseBrightness, +} + +impl Debug for AuraError { + #[inline] + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + Display::fmt(self.description(), f) + } +} + +impl Display for AuraError { + #[inline] + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + Display::fmt(self.description(), f) + } +} + +impl Error for AuraError { + fn description(&self) -> &str { + match self { + AuraError::ParseColour => "could not parse colour", + AuraError::ParseSpeed => "could not parse speed", + AuraError::ParseDirection => "could not parse direction", + AuraError::ParseBrightness => "could not parse brightness", + } + } +} diff --git a/src/main.rs b/src/main.rs index cf28b9d5..f1f06577 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,15 +1,26 @@ // TODO: use /sys/class/dmi/id/board_name to detect model mod aura; mod core; +mod daemon; +mod error; use crate::aura::*; -use crate::core::{LedBrightness, RogCore}; +use crate::core::{dbus_led_builtin_write, LedBrightness, RogCore}; +use crate::daemon::*; use gumdrop::Options; +pub static DBUS_NAME: &'static str = "org.rogcore.Daemon"; +pub static DBUS_PATH: &'static str = "/org/rogcore/Daemon"; +pub static DBUS_IFACE: &'static str = "org.rogcore.Daemon"; + #[derive(Debug, Options)] struct CLIStart { #[options(help = "print help message")] help: bool, + #[options(help = "start daemon")] + daemon: bool, + #[options(help = "client mode")] + client: bool, #[options(help = "")] bright: Option, #[options(command)] @@ -32,12 +43,20 @@ struct LedModeCommand { fn main() -> Result<(), Box> { let parsed = CLIStart::parse_args_default_or_exit(); + if parsed.daemon { + start_daemon()?; + } + match parsed.command { Some(Command::LedMode(okk)) => match okk.command { Some(command) => { - let mut core = RogCore::new()?; let mode = ModeMessage::from(command); - core.aura_set_mode(mode)?; + if parsed.client { + dbus_led_builtin_write(&mode.0)?; + } else { + let mut core = RogCore::new()?; + core.aura_set_mode(&mode.0)?; + } } _ => {} }, @@ -45,8 +64,13 @@ fn main() -> Result<(), Box> { } match parsed.bright { Some(brightness) => { - let mut core = RogCore::new()?; - core.aura_set_brightness(brightness.level as u8)?; + let bytes = RogCore::aura_brightness_bytes(brightness.level as u8)?; + if parsed.client { + dbus_led_builtin_write(&bytes)?; + } else { + let mut core = RogCore::new()?; + core.aura_set_mode(&bytes)?; + } } _ => {} }