mirror of
https://codeberg.org/Fl1tzi/microdeck.git
synced 2024-05-16 01:45:49 +00:00
fix blocking code, change configuration, other small changes
This commit is contained in:
parent
ff8599ec6b
commit
bf5d1fdf3a
|
@ -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
41
example/config.json
Normal 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"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
|
@ -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)
|
||||||
|
|
|
@ -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();
|
||||||
}
|
}
|
||||||
|
|
99
src/main.rs
99
src/main.rs
|
@ -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) => {
|
||||||
|
|
|
@ -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,
|
||||||
}))
|
}))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue