fix blocking code, change configuration, other small changes

This commit is contained in:
Fl1tzi 2023-12-08 22:51:01 +01:00
parent ff8599ec6b
commit bf5d1fdf3a
No known key found for this signature in database
GPG key ID: 06B333727810C686
6 changed files with 137 additions and 44 deletions

View file

@ -8,7 +8,6 @@ license = "MIT"
hidapi = "2.2.0" hidapi = "2.2.0"
deck-driver = { git = "https://codeberg.org/Fl1tzi/deck-driver.git", branch = "main", features = ["async"] } deck-driver = { git = "https://codeberg.org/Fl1tzi/deck-driver.git", branch = "main", features = ["async"] }
tokio = { version = "1", features = ["full"] } tokio = { version = "1", features = ["full"] }
log = "0.4"
dirs = "4.0.0" dirs = "4.0.0"
serde = { version = "1.0", features = ["derive", "rc"] } serde = { version = "1.0", features = ["derive", "rc"] }
serde_json = "1.0" serde_json = "1.0"
@ -19,6 +18,5 @@ imageproc = "0.23.0"
rusttype = "0.9.3" rusttype = "0.9.3"
tracing = "0.1" tracing = "0.1"
tracing-subscriber = { version = "0.3", features = ["env-filter"] } tracing-subscriber = { version = "0.3", features = ["env-filter"] }
lazy_static = "1.4.0"
font-loader = "0.11.0" font-loader = "0.11.0"
clru = "0.6.1" clru = "0.6.1"

41
example/config.json Normal file
View file

@ -0,0 +1,41 @@
{
"global": {},
"devices": [
{
"serial": "*",
"buttons": [
{
"index": 1,
"module": "counter",
"options": {
"font_size": "20",
"title": "Test\ntest",
"increment": "500"
}
},
{
"index": 0,
"module": "space",
"options": {
"name": "folder1"
}
}
]
}
],
"spaces": {
"folder1": [
{
"index": 1,
"module": "space",
"options": {
"name": "home"
}
},
{
"index": 2,
"module": "counter"
}
]
}
}

View file

@ -15,8 +15,10 @@ use std::{
use tracing::debug; use tracing::debug;
/// The name of the folder which holds the config /// The name of the folder which holds the config
pub const CONFIG_ENVIRONMENT_VARIABLE: &'static str = "MICRODECK_CONFIG";
pub const CONFIG_FOLDER_NAME: &'static str = "microdeck"; pub const CONFIG_FOLDER_NAME: &'static str = "microdeck";
pub const CONFIG_FILE: &'static str = "config.json"; pub const CONFIG_FILE: &'static str = "config.json";
pub const DEFAULT_DEVICE_REFRESH_CYCLE: u64 = 3;
/// Combination of buttons acting as a folder which a device can switch to /// Combination of buttons acting as a folder which a device can switch to
pub type Space = Vec<Arc<Button>>; pub type Space = Vec<Arc<Button>>;
@ -28,7 +30,7 @@ pub type Space = Vec<Arc<Button>>;
/// CONFIGURATION /// CONFIGURATION
#[derive(Deserialize, Debug)] #[derive(Deserialize, Debug)]
pub struct Config { pub struct Config {
pub global: Option<GlobalConfig>, pub global: GlobalConfig,
pub devices: Vec<DeviceConfig>, pub devices: Vec<DeviceConfig>,
pub spaces: Arc<HashMap<String, Space>>, pub spaces: Arc<HashMap<String, Space>>,
} }
@ -37,6 +39,13 @@ pub struct Config {
#[derive(Deserialize, Debug)] #[derive(Deserialize, Debug)]
pub struct GlobalConfig { pub struct GlobalConfig {
pub font_family: Option<String>, pub font_family: Option<String>,
/// Seconds when new devices get detected
#[serde(default = "default_device_refresh_cycle")]
pub device_list_refresh_cycle: u64,
}
fn default_device_refresh_cycle() -> u64 {
DEFAULT_DEVICE_REFRESH_CYCLE
} }
/// configuration of a single device with its default page /// configuration of a single device with its default page
@ -169,6 +178,7 @@ impl Button {
} }
/// Just retrieve the raw string from a key without trying to parse the value /// Just retrieve the raw string from a key without trying to parse the value
#[allow(dead_code)]
pub fn raw_module(&self, key: &String) -> Option<&String> { pub fn raw_module(&self, key: &String) -> Option<&String> {
self.options.get(key) self.options.get(key)
} }
@ -180,7 +190,7 @@ impl Button {
#[tracing::instrument] #[tracing::instrument]
pub fn load_config() -> Result<Config, ConfigError> { pub fn load_config() -> Result<Config, ConfigError> {
let config_file: PathBuf = match env::var_os("MICRODECK_CONFIG") { let config_file: PathBuf = match env::var_os(CONFIG_ENVIRONMENT_VARIABLE) {
Some(path) => { Some(path) => {
debug!("Using env variable: {:?}", path); debug!("Using env variable: {:?}", path);
PathBuf::from(path) PathBuf::from(path)

View file

@ -59,6 +59,7 @@ pub struct Device {
config: DeviceConfig, config: DeviceConfig,
spaces: Arc<HashMap<String, Space>>, spaces: Arc<HashMap<String, Space>>,
selected_space: Option<String>, selected_space: Option<String>,
is_dead_handle: Arc<std::sync::Mutex<bool>>,
serial: String, serial: String,
image_cache: Arc<Mutex<ImageCache>>, image_cache: Arc<Mutex<ImageCache>>,
} }
@ -68,6 +69,7 @@ impl Device {
serial: String, serial: String,
kind: Kind, kind: Kind,
device_conf: DeviceConfig, device_conf: DeviceConfig,
is_dead_handle: Arc<std::sync::Mutex<bool>>,
spaces: Arc<HashMap<String, Space>>, spaces: Arc<HashMap<String, Space>>,
hid: &HidApi, hid: &HidApi,
) -> Result<Device, DeviceError> { ) -> Result<Device, DeviceError> {
@ -100,6 +102,7 @@ impl Device {
config: device_conf, config: device_conf,
spaces, spaces,
selected_space: None, selected_space: None,
is_dead_handle,
serial, serial,
image_cache: Arc::new(Mutex::new(CLruCache::new( image_cache: Arc::new(Mutex::new(CLruCache::new(
NonZeroUsize::new(button_count.into()).unwrap(), NonZeroUsize::new(button_count.into()).unwrap(),
@ -171,17 +174,25 @@ impl Device {
self.serial.clone() self.serial.clone()
} }
/// shutdown the runtime and therefore kill all the modules /// Shutdown the runtime and therefore kill all the modules and
/// note death in field [self.is_dead_handle].
fn drop(&mut self) { fn drop(&mut self) {
if let Some(handle) = self.modules_runtime.take() { if let Some(handle) = self.modules_runtime.take() {
handle.shutdown_background(); handle.shutdown_background();
} }
self.modules = HashMap::new(); self.modules = HashMap::new();
*self
.is_dead_handle
.lock()
.expect("Unable to lock Mutex to signal drop of device") = true;
} }
/// if this device holds any modules /// shutdown the runtime and therefore kill all the modules.
pub fn has_modules(&self) -> bool { fn shutdown_modules(&mut self) {
!self.modules.is_empty() if let Some(handle) = self.modules_runtime.take() {
handle.shutdown_background();
}
self.modules = HashMap::new();
} }
/// listener for button press changes on the device /// listener for button press changes on the device
@ -217,7 +228,7 @@ impl Device {
} else { } else {
self.selected_space = Some(name) self.selected_space = Some(name)
} }
self.drop(); self.shutdown_modules();
for key in 0..self.device.kind().key_count() { for key in 0..self.device.kind().key_count() {
self.device.clear_button_image(key).await.unwrap(); self.device.clear_button_image(key).await.unwrap();
} }

View file

@ -7,10 +7,11 @@ use rusttype::Font;
use std::{ use std::{
collections::HashMap, collections::HashMap,
process::exit, process::exit,
sync::{Arc, OnceLock}, sync::{Arc, Mutex, OnceLock},
thread,
time::Duration, time::Duration,
}; };
use tracing::{debug, error, info, warn}; use tracing::{debug, error, info, trace, warn};
use tracing_subscriber::{ use tracing_subscriber::{
self, prelude::__tracing_subscriber_SubscriberExt, util::SubscriberInitExt, EnvFilter, self, prelude::__tracing_subscriber_SubscriberExt, util::SubscriberInitExt, EnvFilter,
}; };
@ -65,21 +66,14 @@ fn main() {
}; };
// load font // load font
// TODO: make this prettier let font = match config.global.font_family {
let font = match config.global { Some(ref f) => font_loader::system_fonts::get(
Some(ref g) => { &mut FontPropertyBuilder::new().family(f.as_str()).build(),
if let Some(family) = &g.font_family { )
font_loader::system_fonts::get( .unwrap_or_else(|| {
&mut FontPropertyBuilder::new().family(family.as_str()).build(), warn!("Unable to load custom font");
) load_system_font()
.unwrap_or_else(|| { }),
warn!("Unable to load custom font");
load_system_font()
})
} else {
load_system_font()
}
}
None => load_system_font(), None => load_system_font(),
}; };
@ -104,37 +98,71 @@ fn load_system_font() -> (Vec<u8>, i32) {
.expect("Unable to load system monospace font. Please specify a custom font in the config.") .expect("Unable to load system monospace font. Please specify a custom font in the config.")
} }
pub async fn start(config: Config, mut hid: HidApi) { struct DeviceHandle<T> {
let mut devices: HashMap<String, Device> = HashMap::new(); handle: tokio::task::JoinHandle<T>,
is_dead: Arc<Mutex<bool>>,
}
impl<T> DeviceHandle<T> {
fn new(handle: tokio::task::JoinHandle<T>, is_dead: Arc<Mutex<bool>>) -> Self {
DeviceHandle { handle, is_dead }
}
fn is_dead(&self) -> bool {
match self.is_dead.lock() {
Ok(guard) => *guard,
Err(_) => false,
}
}
}
async fn start(config: Config, mut hid: HidApi) {
let mut devices: HashMap<String, DeviceHandle<_>> = HashMap::new();
// devices which are not configured anyways // devices which are not configured anyways
let mut ignore_devices: Vec<String> = Vec::new(); let mut ignore_devices: Vec<String> = Vec::new();
let refresh_cycle = Duration::from_secs(config.global.device_list_refresh_cycle);
loop { loop {
// refresh device list // refresh device list
trace!("Refreshing device list");
if let Err(e) = streamdeck::refresh_device_list(&mut hid) { if let Err(e) = streamdeck::refresh_device_list(&mut hid) {
warn!("Cannot fetch new devices: {}", e); warn!("Cannot fetch new devices: {}", e);
} else { } else {
for hw_device in streamdeck::list_devices(&hid) { for hw_device in streamdeck::list_devices(&hid) {
// if the device is not ignored and device is not already started // if the device is not already started or the device is
if !ignore_devices.contains(&hw_device.1) && devices.get(&hw_device.1).is_none() { // dropped
debug!("New device detected: {}", &hw_device.1); if let Some(d) = devices.get(&hw_device.1) {
// match regex for device serial if d.is_dead() {
trace!("Removing dead device {}", &hw_device.1);
d.handle.abort();
devices.remove(&hw_device.1);
} else {
// ignore this device
continue;
}
}
if !ignore_devices.contains(&hw_device.1) {
// TODO: match regex for device serial
if let Some(device_config) = config if let Some(device_config) = config
.devices .devices
.iter() .iter()
.find(|d| d.serial == hw_device.1 || d.serial == "*") .find(|d| d.serial == hw_device.1 || d.serial == "*")
{ {
// start the device and its listener // start the device and its functions
let is_dead = Arc::new(Mutex::new(false));
if let Some(device) = start_device( if let Some(device) = start_device(
hw_device, hw_device,
&hid, &hid,
device_config.clone(), device_config.clone(),
config.spaces.clone(), config.spaces.clone(),
is_dead.clone(),
) )
.await .await
{ {
devices.insert(device.serial(), device); let serial = device.serial();
let handle = tokio::spawn(init_device_functions(device));
devices.insert(serial, DeviceHandle::new(handle, is_dead));
} }
} else { } else {
info!("The device {} is not configured.", hw_device.1); info!("The device {} is not configured.", hw_device.1);
@ -143,24 +171,29 @@ pub async fn start(config: Config, mut hid: HidApi) {
} }
} }
} }
tokio::time::sleep(refresh_cycle).await;
tokio::time::sleep(Duration::from_secs(2)).await;
} }
} }
/// Start a device by initially creating the [Device] and then starting all modules and listeners for that device /// Create [Device] modules and init listener
async fn init_device_functions(device: Device) {
let mut device = device;
device.init_modules().await;
device.key_listener().await;
}
/// Start a device by initially creating the [Device]
#[tracing::instrument(name = "device", skip_all, fields(serial = device.1))] #[tracing::instrument(name = "device", skip_all, fields(serial = device.1))]
pub async fn start_device( async fn start_device(
device: (streamdeck::info::Kind, String), device: (streamdeck::info::Kind, String),
hid: &HidApi, hid: &HidApi,
device_config: DeviceConfig, device_config: DeviceConfig,
spaces: Arc<HashMap<String, Space>>, spaces: Arc<HashMap<String, Space>>,
is_dead: Arc<Mutex<bool>>,
) -> Option<Device> { ) -> Option<Device> {
match Device::new(device.1, device.0, device_config, spaces, &hid).await { match Device::new(device.1, device.0, device_config, is_dead, spaces, &hid).await {
Ok(mut device) => { Ok(device) => {
info!("Connected"); info!("Connected");
device.init_modules().await;
device.key_listener().await;
Some(device) Some(device)
} }
Err(e) => { Err(e) => {

View file

@ -19,7 +19,7 @@ pub struct Counter {
title: String, title: String,
title_size: f32, title_size: f32,
number_size: f32, number_size: f32,
increment: u32 increment: u32,
} }
#[async_trait] #[async_trait]
@ -37,7 +37,7 @@ impl Module for Counter {
title, title,
title_size, title_size,
number_size, number_size,
increment increment,
})) }))
} }