From 72ad533e74a49bb7d8aa507fc68670b53e886923 Mon Sep 17 00:00:00 2001 From: Ian Douglas Scott Date: Wed, 2 Sep 2020 14:37:34 -0700 Subject: [PATCH 1/2] improv: Use dbus::blocking::* instead of dbus::ffidisp::* The `ffidisp` module is described as "the legacy design used up to 0.6.x. It is not recommended for new development." This also uses `SyncConnection` in the server, rather than `Connection`, so it can be shared between threads. --- src/client.rs | 9 +++++---- src/daemon/mod.rs | 37 +++++++++++++++++++------------------ 2 files changed, 24 insertions(+), 22 deletions(-) diff --git a/src/client.rs b/src/client.rs index fdf29315..3eea29e1 100644 --- a/src/client.rs +++ b/src/client.rs @@ -1,11 +1,12 @@ use crate::{err_str, Power, DBUS_IFACE, DBUS_NAME, DBUS_PATH}; use clap::ArgMatches; -use dbus::{arg::Append, ffidisp::Connection, Message}; +use dbus::{arg::Append, blocking::{BlockingSender, Connection}, Message}; use pstate::PState; use std::io; +use std::time::Duration; use sysfs_class::{Backlight, Brightness, Leds, SysClass}; -static TIMEOUT: i32 = 60 * 1000; +static TIMEOUT: u64 = 60 * 1000; pub struct PowerClient { bus: Connection, @@ -29,7 +30,7 @@ impl PowerClient { let r = self .bus - .send_with_reply_and_block(m, TIMEOUT) + .send_with_reply_and_block(m, Duration::from_millis(TIMEOUT)) .map_err(|why| format!("daemon returned an error message: {}", err_str(why)))?; Ok(r) @@ -62,7 +63,7 @@ impl Power for PowerClient { fn get_profile(&mut self) -> Result { let m = Message::new_method_call(DBUS_NAME, DBUS_PATH, DBUS_IFACE, "GetProfile")?; - let r = self.bus.send_with_reply_and_block(m, TIMEOUT).map_err(err_str)?; + let r = self.bus.send_with_reply_and_block(m, Duration::from_millis(TIMEOUT)).map_err(err_str)?; r.get1().ok_or_else(|| "return value not found".to_string()) } diff --git a/src/daemon/mod.rs b/src/daemon/mod.rs index a9a654d3..02f4202d 100644 --- a/src/daemon/mod.rs +++ b/src/daemon/mod.rs @@ -1,15 +1,16 @@ use dbus::{ - ffidisp::{Connection, NameFlag}, + blocking::SyncConnection, + channel::Sender, tree::{Factory, MethodErr, Signal}, }; use std::{ - cell::RefCell, fs, - rc::Rc, sync::{ atomic::{AtomicBool, Ordering}, Arc, + Mutex, }, + time::Duration, thread, }; @@ -59,14 +60,14 @@ struct PowerDaemon { graphics: Graphics, power_profile: String, profile_errors: Vec, - dbus_connection: Arc, + dbus_connection: Arc, power_switch_signal: Arc>, } impl PowerDaemon { fn new( power_switch_signal: Arc>, - dbus_connection: Arc, + dbus_connection: Arc, ) -> Result { let graphics = Graphics::new().map_err(err_str)?; Ok(PowerDaemon { @@ -162,16 +163,16 @@ pub fn daemon() -> Result<(), String> { PCI_RUNTIME_PM.store(pci_runtime_pm, Ordering::SeqCst); info!("Connecting to dbus system bus"); - let c = Arc::new(Connection::new_system().map_err(err_str)?); + let c = Arc::new(SyncConnection::new_system().map_err(err_str)?); - let f = Factory::new_fn::<()>(); + let f = Factory::new_sync::<()>(); let hotplug_signal = Arc::new(f.signal("HotPlugDetect", ()).sarg::("port")); let power_switch_signal = Arc::new(f.signal("PowerProfileSwitch", ()).sarg::<&str, _>("profile")); let daemon = PowerDaemon::new(power_switch_signal.clone(), c.clone())?; let nvidia_exists = !daemon.graphics.nvidia.is_empty(); - let daemon = Rc::new(RefCell::new(daemon)); + let daemon = Arc::new(Mutex::new(daemon)); info!("Disabling NMI Watchdog (for kernel debugging only)"); NmiWatchdog::default().set(b"0"); @@ -184,7 +185,7 @@ pub fn daemon() -> Result<(), String> { }; info!("Setting automatic graphics power"); - match daemon.borrow_mut().auto_graphics_power() { + match daemon.lock().unwrap().auto_graphics_power() { Ok(()) => (), Err(err) => { warn!("Failed to set automatic graphics power: {}", err); @@ -193,7 +194,7 @@ pub fn daemon() -> Result<(), String> { { info!("Initializing with the balanced profile"); - let mut daemon = daemon.borrow_mut(); + let mut daemon = daemon.lock().unwrap(); if let Err(why) = daemon.balanced() { warn!("Failed to set initial profile: {}", why); } @@ -202,7 +203,7 @@ pub fn daemon() -> Result<(), String> { } info!("Registering dbus name {}", DBUS_NAME); - c.register_name(DBUS_NAME, NameFlag::ReplaceExisting as u32).map_err(err_str)?; + c.request_name(DBUS_NAME, false, true, false).map_err(err_str)?; // Defines whether the value returned by the method should be appended. macro_rules! append { @@ -219,12 +220,12 @@ pub fn daemon() -> Result<(), String> { (true, $name:expr, $daemon:ident, $m:ident, $method:tt) => {{ let value = $m.msg.read1()?; info!("DBUS Received {}({}) method", $name, value); - $daemon.borrow_mut().$method(value) + $daemon.lock().unwrap().$method(value) }}; (false, $name:expr, $daemon:ident, $m:ident, $method:tt) => {{ info!("DBUS Received {} method", $name); - $daemon.borrow_mut().$method() + $daemon.lock().unwrap().$method() }}; } @@ -276,11 +277,9 @@ pub fn daemon() -> Result<(), String> { .add_s(hotplug_signal.clone()) .add_s(power_switch_signal.clone()), ), - ); - - tree.set_registered(&c, true).map_err(err_str)?; + ).add(f.object_path("/", ()).introspectable()); - c.add_handler(tree); + tree.start_receive_sync(c.as_ref()); // Spawn hid backlight daemon let _hid_backlight = thread::spawn(|| hid_backlight::daemon()); @@ -303,7 +302,9 @@ pub fn daemon() -> Result<(), String> { info!("Handling dbus requests"); while CONTINUE.load(Ordering::SeqCst) { - c.incoming(1000).next(); + if let Err(err) = c.process(Duration::from_millis(1000)) { + error!("{}", err); + } fan_daemon.step(); From 52784a1cafebd94398f6989c2a10b7cfa95dd10c Mon Sep 17 00:00:00 2001 From: Ian Douglas Scott Date: Thu, 3 Sep 2020 12:45:10 -0700 Subject: [PATCH 2/2] feat: Expose keyboard brightness/color through a dbus API This defines a new dbus interface `com.system76.PowerDaemon.Keyboard`. This wraps the sysfs API without requiring root access. This is handled in a seperate thread that monitors brightness and color changes with inotify. The API might later be extended to support things like setting keymaps. --- src/daemon/mod.rs | 21 +++- src/keyboard.rs | 280 ++++++++++++++++++++++++++++++++++++++++++++++ src/lib.rs | 2 + 3 files changed, 299 insertions(+), 4 deletions(-) create mode 100644 src/keyboard.rs diff --git a/src/daemon/mod.rs b/src/daemon/mod.rs index 02f4202d..984b38d1 100644 --- a/src/daemon/mod.rs +++ b/src/daemon/mod.rs @@ -1,6 +1,7 @@ use dbus::{ blocking::SyncConnection, - channel::Sender, + channel::{MatchingReceiver, Sender}, + message::MatchRule, tree::{Factory, MethodErr, Signal}, }; use std::{ @@ -251,7 +252,7 @@ pub fn daemon() -> Result<(), String> { info!("Adding dbus path {} with interface {}", DBUS_PATH, DBUS_IFACE); let tree = f.tree(()).add( - f.object_path(DBUS_PATH, ()).introspectable().add( + f.object_path(DBUS_PATH, ()).introspectable().object_manager().add( f.interface(DBUS_IFACE, ()) .add_m(method!(performance, "Performance", false, false)) .add_m(method!(balanced, "Balanced", false, false)) @@ -278,8 +279,17 @@ pub fn daemon() -> Result<(), String> { .add_s(power_switch_signal.clone()), ), ).add(f.object_path("/", ()).introspectable()); - - tree.start_receive_sync(c.as_ref()); + let tree = Arc::new(Mutex::new(tree)); + + // Equivalent to tree.start_receive_sync, but taking a + // Arc>> instead of consuming the tree. + let tree_clone = tree.clone(); + c.start_receive(MatchRule::new_method_call(), Box::new(move |msg, c| { + if let Some(replies) = tree_clone.lock().unwrap().handle(&msg) { + for r in replies { let _ = c.send(r); } + } + true + })); // Spawn hid backlight daemon let _hid_backlight = thread::spawn(|| hid_backlight::daemon()); @@ -290,6 +300,9 @@ pub fn daemon() -> Result<(), String> { let mux_res = unsafe { DisplayPortMux::new() }; + let c_clone = c.clone(); + thread::spawn(move || crate::keyboard::daemon(c_clone, tree)); + let hpd = || -> [bool; 4] { if let Ok(ref hpd) = hpd_res { unsafe { hpd.detect() } diff --git a/src/keyboard.rs b/src/keyboard.rs new file mode 100644 index 00000000..298700e2 --- /dev/null +++ b/src/keyboard.rs @@ -0,0 +1,280 @@ +use dbus::arg::RefArg; +use dbus::blocking::SyncConnection; +use dbus::channel::Channel; +use dbus::message::SignalArgs; +use dbus::strings::Path as DbusPath; +use dbus::tree::{self, Access, MTSync, MethodErr}; +use inotify::{Inotify, WatchDescriptor, WatchMask}; +use std::collections::HashMap; +use std::fs; +use std::path::{Path, PathBuf}; +use std::sync::{Arc, Mutex}; + +use crate::{DBUS_KEYBOARD_IFACE, DBUS_PATH}; + +type Factory = dbus::tree::Factory, ()>; +type Interface = dbus::tree::Interface, ()>; +type Property = dbus::tree::Property, ()>; +type Tree = dbus::tree::Tree, ()>; + +/// Get maximum brightness from a sysfs directory path +fn get_max_brightness(path: &Path) -> Result { + let mut path = PathBuf::from(path); + path.push("max_brightness"); + let brightness = fs::read_to_string(&path).map_err(|e| MethodErr::failed(&e))?; + i32::from_str_radix(brightness.trim_end(), 10).map_err(|e| MethodErr::failed(&e)) +} + +/// Get brightness from a sysfs directory path +fn get_brightness(path: &Path) -> Result { + let mut path = PathBuf::from(path); + path.push("brightness"); + let brightness = fs::read_to_string(&path).map_err(|e| MethodErr::failed(&e))?; + i32::from_str_radix(brightness.trim_end(), 10).map_err(|e| MethodErr::failed(&e)) +} + +/// Sets brightness with a sysfs directory path +fn set_brightness(path: &Path, brightness: i32) -> Result<(), MethodErr> { + let mut path = PathBuf::from(path); + path.push("brightness"); + fs::write(&path, format!("{}\n", brightness)).map_err(|e| MethodErr::failed(&e)) +} + +/// Gets color from a sysfs directory path +/// Returns "" if it does not support color +fn get_color(path: &Path) -> Result { + let mut path = PathBuf::from(path); + path.push("color"); + if !path.exists() { + path.pop(); + path.push("color_left"); + if !path.exists() { + return Ok("".to_string()); + } + } + let color = fs::read_to_string(&path).map_err(|e| MethodErr::failed(&e))?; + Ok(color.trim_end().to_string()) +} + +/// Sets color with a sysfs directory path +fn set_color(path: &Path, color: &str) -> Result<(), MethodErr> { + let entries = fs::read_dir(path).map_err(|e| MethodErr::failed(&e))?; + for i in entries { + let i = i.map_err(|e| MethodErr::failed(&e))?; + if let Some(filename) = i.file_name().to_str() { + if filename.starts_with("color") { + fs::write(i.path(), color).map_err(|e| MethodErr::failed(&e))?; + } + } + } + Ok(()) +} + +struct Keyboard { + brightness_prop: Arc, + color_prop: Arc, + interface: Arc, + dbus_path: DbusPath<'static>, + path: PathBuf, +} + +impl Keyboard { + fn new(f: &Factory, path: &Path, dbus_path: DbusPath<'static>) -> Self { + let path = path.to_owned(); + let path0 = path.clone(); + let path1 = path.clone(); + let path2 = path.clone(); + let path3 = path.clone(); + let path4 = path.clone(); + let max_brightness_prop = + Arc::new(f.property::("max-brightness", ()).auto_emit_on_set(false).on_get( + move |iter, _| { + iter.append(get_max_brightness(&path0)?); + Ok(()) + }, + )); + let brightness_prop = Arc::new( + f.property::("brightness", ()) + .auto_emit_on_set(false) + .access(Access::ReadWrite) + .on_get(move |iter, _| { + iter.append(get_brightness(&path1)?); + Ok(()) + }) + .on_set(move |iter, _| set_brightness(&path2, iter.read()?)), + ); + let color_prop = Arc::new( + f.property::<&str, _>("color", ()) + .auto_emit_on_set(false) + .access(Access::ReadWrite) + .on_get(move |iter, _| { + iter.append(get_color(&path3)?); + Ok(()) + }) + .on_set(move |iter, _| set_color(&path4, iter.read()?)), + ); + let name_prop = Arc::new(f.property::<&str, _>("name", ()).on_get(|iter, _| { + // TODO: Update for Launch keyboard + iter.append("Built-in Keyboard"); + Ok(()) + })); + let interface = Arc::new( + f.interface(DBUS_KEYBOARD_IFACE, ()) + .add_p(max_brightness_prop.clone()) + .add_p(brightness_prop.clone()) + .add_p(color_prop.clone()) + .add_p(name_prop.clone()), + ); + Self { brightness_prop, color_prop, interface, dbus_path, path } + } + + fn notify_prop(&self, c: &Channel, p: &Property, value: T) { + let mut v = Vec::new(); + p.add_propertieschanged(&mut v, &DBUS_KEYBOARD_IFACE.into(), || Box::new(value)); + for i in v { + let _ = c.send(i.to_emit_message(&self.dbus_path)); + } + } + + fn notify_color(&self, c: &Channel) { + match get_color(&self.path) { + Ok(value) => self.notify_prop(c, &self.color_prop, value), + Err(err) => error!("{:?}", err), + } + } + + fn notify_brightness(&self, c: &Channel) { + match get_brightness(&self.path) { + Ok(value) => self.notify_prop(c, &self.brightness_prop, value), + Err(err) => error!("{:?}", err), + } + } +} + +struct Daemon { + c: Arc, + tree: Arc>, + keyboards: HashMap, Keyboard>, + number: u64, + inotify: Inotify, + watches: HashMap, &'static str)>, +} + +impl Daemon { + fn new(c: Arc, tree: Arc>) -> Self { + let keyboards = HashMap::new(); + let inotify = Inotify::init().unwrap(); + let number = 0; + let watches = HashMap::new(); + + Self { c, tree, keyboards, number, inotify, watches } + } + + fn add_inotify_watches(&mut self, path: &Path, dbus_path: &DbusPath<'static>) { + let mut brightness_path = path.to_owned(); + brightness_path.push("brightness"); + match self.inotify.add_watch(&brightness_path, WatchMask::MODIFY) { + Ok(wd) => { + self.watches.insert(wd, (dbus_path.clone(), "brightness")); + } + Err(err) => error!("{}", err), + } + + let mut color_path = path.to_owned(); + color_path.push("color"); + if !color_path.exists() { + color_path.pop(); + color_path.push("color_left"); + }; + if color_path.exists() { + match self.inotify.add_watch(&color_path, WatchMask::MODIFY) { + Ok(wd) => { + self.watches.insert(wd, (dbus_path.clone(), "color")); + } + Err(err) => error!("{}", err), + } + } + + let mut brightness_hw_path = path.to_owned(); + brightness_hw_path.push("brightness_hw_changed"); + match self.inotify.add_watch(&brightness_hw_path, WatchMask::MODIFY) { + Ok(wd) => { + self.watches.insert(wd, (dbus_path.clone(), "")); + } + Err(err) => error!("{}", err), + } + } + + fn load(&mut self) { + let f = tree::Factory::new_sync::<()>(); + + let entries = match fs::read_dir("/sys/class/leds") { + Ok(entries) => entries, + Err(err) => { + error!("{}", err); + return; + } + }; + + for i in entries { + let i = match i { + Ok(i) => i, + Err(err) => { + error!("{}", err); + continue; + } + }; + + if let Some(filename) = i.file_name().to_str() { + if filename.ends_with(":kbd_backlight") { + let path = i.path(); + + let dbus_path = + DbusPath::from(format!("{}/keyboard{}", DBUS_PATH, self.number)); + self.number += 1; + + self.add_inotify_watches(&path, &dbus_path); + + let keyboard = Keyboard::new(&f, &path, dbus_path.clone()); + let interface = keyboard.interface.clone(); + self.keyboards.insert(dbus_path.clone(), keyboard); + + info!("Adding dbus path {} with interface {}", dbus_path, DBUS_KEYBOARD_IFACE); + let mut tree = self.tree.lock().unwrap(); + tree.insert(f.object_path(dbus_path, ()).introspectable().add(interface)); + } + } + } + } + + fn run(&mut self) { + // TODO: Watch for added/removed devices + + self.load(); + + let mut buffer = [0; 1024]; + + loop { + for event in self.inotify.read_events_blocking(&mut buffer).unwrap() { + trace!("{:?}", event); + + if let Some((dbus_path, property)) = self.watches.get(&event.wd) { + if let Some(keyboard) = self.keyboards.get(dbus_path) { + if property == &"brightness" { + keyboard.notify_brightness(self.c.channel()); + } else if property == &"color" { + keyboard.notify_color(self.c.channel()); + } else if property == &"" { + keyboard.notify_brightness(self.c.channel()); + keyboard.notify_color(self.c.channel()); + } + } + } + } + } + } +} + +pub fn daemon(c: Arc, tree: Arc>) { + Daemon::new(c, tree).run(); +} \ No newline at end of file diff --git a/src/lib.rs b/src/lib.rs index e1cf97a2..742da211 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -13,6 +13,7 @@ pub mod graphics; pub mod hid_backlight; pub mod hotplug; pub mod kernel_parameters; +pub mod keyboard; pub mod logging; pub mod modprobe; pub mod module; @@ -29,6 +30,7 @@ include!(concat!(env!("OUT_DIR"), "/version.rs")); pub static DBUS_NAME: &'static str = "com.system76.PowerDaemon"; pub static DBUS_PATH: &'static str = "/com/system76/PowerDaemon"; pub static DBUS_IFACE: &'static str = "com.system76.PowerDaemon"; +pub static DBUS_KEYBOARD_IFACE: &'static str = "com.system76.PowerDaemon.Keyboard"; pub trait Power { fn performance(&mut self) -> Result<(), String>;