From c2b3b2351ccfed0900b581ac931d6bafe2dc6c1f Mon Sep 17 00:00:00 2001 From: Sasha Finkelstein Date: Mon, 11 Dec 2023 17:35:21 +0100 Subject: [PATCH 01/24] Do not fail if config is not present in /etc --- src/config.rs | 127 ++++++++++++++++++++++++++++++++++++++++++++++++++ src/main.rs | 107 +++++------------------------------------- 2 files changed, 139 insertions(+), 95 deletions(-) create mode 100644 src/config.rs diff --git a/src/config.rs b/src/config.rs new file mode 100644 index 0000000..fc6c86e --- /dev/null +++ b/src/config.rs @@ -0,0 +1,127 @@ +use std::fs::read_to_string; +use anyhow::Error; +use cairo::FontFace; +use crate::FunctionLayer; +use crate::fonts::{FontConfig, Pattern}; +use freetype::Library as FtLibrary; +use input_linux::Key; +use nix::errno::Errno; +use nix::poll::{PollFd, PollFlags}; +use nix::sys::inotify::{AddWatchFlags, InitFlags, Inotify, WatchDescriptor}; +use serde::Deserialize; + +const USER_CFG_PATH: &'static str = "/etc/tiny-dfr/config.toml"; + +pub struct Config { + pub show_button_outlines: bool, + pub enable_pixel_shift: bool, + pub font_face: FontFace, +} + +#[derive(Deserialize)] +#[serde(rename_all = "PascalCase")] +struct ConfigProxy { + media_layer_default: Option, + show_button_outlines: Option, + enable_pixel_shift: Option, + font_template: Option, + primary_layer_keys: Option>, + media_layer_keys: Option> +} + +#[derive(Deserialize)] +#[serde(rename_all = "PascalCase")] +pub struct ButtonConfig { + #[serde(alias = "Svg")] + pub icon: Option, + pub text: Option, + pub action: Key +} + +fn load_font(name: &str) -> FontFace { + let fontconfig = FontConfig::new(); + let mut pattern = Pattern::new(name); + fontconfig.perform_substitutions(&mut pattern); + let pat_match = fontconfig.match_pattern(&pattern); + let file_name = pat_match.get_file_name(); + let file_idx = pat_match.get_font_index(); + let ft_library = FtLibrary::init().unwrap(); + let face = ft_library.new_face(file_name, file_idx).unwrap(); + FontFace::create_from_ft(&face).unwrap() +} + +fn load_config() -> (Config, [FunctionLayer; 2]) { + let mut base = toml::from_str::(&read_to_string("/usr/share/tiny-dfr/config.toml").unwrap()).unwrap(); + let user = read_to_string(USER_CFG_PATH).map_err::(|e| e.into()) + .and_then(|r| Ok(toml::from_str::(&r)?)); + if let Ok(user) = user { + base.media_layer_default = user.media_layer_default.or(base.media_layer_default); + base.show_button_outlines = user.show_button_outlines.or(base.show_button_outlines); + base.enable_pixel_shift = user.enable_pixel_shift.or(base.enable_pixel_shift); + base.font_template = user.font_template.or(base.font_template); + base.media_layer_keys = user.media_layer_keys.or(base.media_layer_keys); + base.primary_layer_keys = user.primary_layer_keys.or(base.primary_layer_keys); + }; + let media_layer = FunctionLayer::with_config(base.media_layer_keys.unwrap()); + let fkey_layer = FunctionLayer::with_config(base.primary_layer_keys.unwrap()); + let layers = if base.media_layer_default.unwrap(){ [media_layer, fkey_layer] } else { [fkey_layer, media_layer] }; + let cfg = Config { + show_button_outlines: base.show_button_outlines.unwrap(), + enable_pixel_shift: base.enable_pixel_shift.unwrap(), + font_face: load_font(&base.font_template.unwrap()), + }; + (cfg, layers) +} + +pub struct ConfigManager { + inotify_fd: Inotify, + watch_desc: Option +} + +fn arm_inotify(inotify_fd: &Inotify) -> Option { + let flags = AddWatchFlags::IN_MOVED_TO | AddWatchFlags::IN_CLOSE | AddWatchFlags::IN_ONESHOT; + match inotify_fd.add_watch(USER_CFG_PATH, flags) { + Ok(wd) => Some(wd), + Err(Errno::ENOENT) => None, + e => Some(e.unwrap()) + } +} + +impl ConfigManager { + pub fn new() -> ConfigManager { + let inotify_fd = Inotify::init(InitFlags::IN_NONBLOCK).unwrap(); + let watch_desc = arm_inotify(&inotify_fd); + ConfigManager { + inotify_fd, watch_desc + } + } + pub fn load_config(&self) -> (Config, [FunctionLayer; 2]) { + load_config() + } + pub fn update_config(&mut self, cfg: &mut Config, layers: &mut [FunctionLayer; 2]) -> bool { + if self.watch_desc.is_none() { + self.watch_desc = arm_inotify(&self.inotify_fd); + return false; + } + let evts = match self.inotify_fd.read_events() { + Ok(e) => e, + Err(Errno::EAGAIN) => Vec::new(), + r => r.unwrap(), + }; + let mut ret = false; + for evt in evts { + if evt.wd != self.watch_desc.unwrap() { + continue + } + let parts = load_config(); + *cfg = parts.0; + *layers = parts.1; + ret = true; + self.watch_desc = arm_inotify(&self.inotify_fd); + } + ret + } + pub fn pollfd(&self) -> PollFd { + PollFd::new(&self.inotify_fd, PollFlags::POLLIN) + } +} \ No newline at end of file diff --git a/src/main.rs b/src/main.rs index 3929e2e..6c0b089 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,5 +1,5 @@ use std::{ - fs::{File, OpenOptions, read_to_string}, + fs::{File, OpenOptions}, os::{ fd::{AsRawFd, AsFd}, unix::{io::OwnedFd, fs::OpenOptionsExt} @@ -9,10 +9,10 @@ use std::{ cmp::min, panic::{self, AssertUnwindSafe} }; -use cairo::{ImageSurface, Format, Context, Surface, Rectangle, FontFace, Antialias}; +use cairo::{ImageSurface, Format, Context, Surface, Rectangle, Antialias}; use rsvg::{Loader, CairoRenderer, SvgHandle}; use drm::control::ClipRect; -use anyhow::{Error, Result}; +use anyhow::Result; use input::{ Libinput, LibinputInterface, Device as InputDevice, event::{ @@ -26,25 +26,21 @@ use input_linux::{uinput::UInputHandle, EventKind, Key, SynchronizeKind}; use input_linux_sys::{uinput_setup, input_id, timeval, input_event}; use nix::{ poll::{poll, PollFd, PollFlags}, - sys::{ - signal::{Signal, SigSet}, - inotify::{AddWatchFlags, InitFlags, Inotify, WatchDescriptor} - }, - errno::Errno + sys::signal::{Signal, SigSet}, }; use privdrop::PrivDrop; -use serde::Deserialize; -use freetype::Library as FtLibrary; mod backlight; mod display; mod pixel_shift; mod fonts; +mod config; use backlight::BacklightManager; use display::DrmBackend; use pixel_shift::{PixelShiftManager, PIXEL_SHIFT_WIDTH_PX}; -use fonts::{FontConfig, Pattern}; +use config::{ButtonConfig, Config}; +use crate::config::ConfigManager; const DFR_WIDTH: i32 = 2008; const DFR_HEIGHT: i32 = 60; @@ -54,33 +50,6 @@ const BUTTON_COLOR_INACTIVE: f64 = 0.200; const BUTTON_COLOR_ACTIVE: f64 = 0.400; const ICON_SIZE: i32 = 48; const TIMEOUT_MS: i32 = 10 * 1000; -const USER_CFG_PATH: &'static str = "/etc/tiny-dfr/config.toml"; - -#[derive(Deserialize)] -#[serde(rename_all = "PascalCase")] -struct ConfigProxy { - media_layer_default: Option, - show_button_outlines: Option, - enable_pixel_shift: Option, - font_template: Option, - primary_layer_keys: Option>, - media_layer_keys: Option> -} - -#[derive(Deserialize)] -#[serde(rename_all = "PascalCase")] -struct ButtonConfig { - #[serde(alias = "Svg")] - icon: Option, - text: Option, - action: Key -} - -struct Config { - show_button_outlines: bool, - enable_pixel_shift: bool, - font_face: FontFace, -} enum ButtonImage { Text(String), @@ -184,7 +153,7 @@ impl Button { } #[derive(Default)] -struct FunctionLayer { +pub struct FunctionLayer { buttons: Vec