Update dependencies, allow changing the screen brightness dynamically

This commit is contained in:
Moritz Ruth 2024-06-17 13:25:17 +02:00
parent e2f4aac438
commit 95f34add08
Signed by: moritzruth
GPG key ID: C9BBAB79405EE56D
22 changed files with 1021 additions and 844 deletions

1586
Cargo.lock generated

File diff suppressed because it is too large Load diff

View file

@ -4,36 +4,36 @@ version = "0.1.0"
edition = "2021"
[dependencies]
bytes = "1.5.0"
clap = { version = "4.4.12", features = ["derive"] }
color-eyre = "0.6.2"
cosmic-text = "0.10.0"
derive_more = "0.99.17"
parse-display = "0.9.0"
loupedeck_serial = { path = "./crates/loupedeck_serial" }
deckster_shared = { path = "./crates/deckster_shared" }
bytes = "1.6.0"
clap = { version = "4.5.7", features = ["derive"] }
color-eyre = "0.6.3"
cosmic-text = "0.11.2"
derive_more = { version = "1.0.0-beta.6", features = ["deref", "from", "into"] }
parse-display = "0.9.1"
encode_unicode = "1.0.0"
enum-map = "3.0.0-beta.2"
enum-ordinalize = "4.3.0"
env_logger = "0.11.0"
env_logger = "0.11.3"
humantime-serde = "1.1.1"
log = "0.4.20"
loupedeck_serial = { path = "./crates/loupedeck_serial" }
deckster_shared = { path = "./crates/deckster_shared" }
regex = "1.10.2"
resvg = "0.37.0"
log = "0.4.21"
regex = "1.10.5"
resvg = "0.42.0"
rgb = "0.8.37"
serde = { version = "1.0.193", features = ["derive", "rc"] }
serde_json = "1.0.113"
serde = { version = "1.0.203", features = ["derive", "rc"] }
serde_json = "1.0.117"
serde_regex = "1.1.0"
serde_with = "3.4.0"
thiserror = "1.0.52"
tiny-skia = "0.11.3"
tokio = { version = "1.35.1", features = ["macros", "parking_lot", "rt", "rt-multi-thread", "sync", "process", "io-util"] }
toml = "0.8.8"
walkdir = "2.4.0"
serde_with = "3.8.1"
thiserror = "1.0.61"
tiny-skia = "0.11.4"
tokio = { version = "1.38.0", features = ["macros", "parking_lot", "rt", "rt-multi-thread", "sync", "process", "io-util"] }
toml = "0.8.14"
walkdir = "2.5.0"
once_cell = "1.19.0"
is_executable = "1.0.1"
rumqttc = "0.23.0"
itertools = "0.12.1"
rumqttc = "0.24.0"
itertools = "0.13.0"
flume = "0.11.0"
nanoid = "0.4.0"

View file

@ -1,17 +1,20 @@
# Deckster
## To do
- Make the CLI of handlers more useful
- Make the `playerctl` handler independent of… playerctl. Use the [`mpris` crate](https://lib.rs/crates/mpris) directly instead.
- Implement scrolling
- Move loupedeck_serial and pa_volume_interface out of this repository.
- Publish libraries to crates.io
- Move handlers to their own repositories
- Update dependencies
- Make the CLI of handlers more useful
- Make the `playerctl` handler independent of… playerctl. Use the [`mpris` crate](https://lib.rs/crates/mpris) directly instead.
## Contributing
### Terminology
- `handler runner`: Node that is running handlers.
- `handler host`: A `handler runner` that is not the `coordinator`.
- `coordinator`: Node to which the Loupedeck device is physically connected. Always a `handler runner`.
- `coordinator`: Node to which the Loupedeck device is physically connected. Can be a `handler runner`.
### The different types of `unwrap`
- `expect("<reason>")`: The author thinks that unwrapping will never fail because of `<reason>`.
@ -19,7 +22,9 @@
- `unwrap_todo()`: The author has not yet thought about how to handle this value being `None` or `Err`.
They will replace this unwrapping with `expect("<reason>")`, `unwrap()`, or proper error handling later.
## Attribution
## Credits
[foxxyzs `loupedeck` library for JavaScript](https://github.com/foxxyz/loupedeck)
(licensed under the [MIT license](https://github.com/foxxyz/loupedeck/blob/e41e5d920130d9ef651e47173c68450b9c832b96/LICENSE))
was used as a reference for and inspired the design of `loupedeck_serial`.

View file

@ -5,7 +5,7 @@ edition = "2021"
[dependencies]
deckster_shared = { path = "../deckster_shared" }
thiserror = "1.0.56"
thiserror = "1.0.61"
im = "15.1.0"
serde = { version = "1.0.196", default-features = false }
serde_json = "1.0.113"
serde = { version = "1.0.203", default-features = false }
serde_json = "1.0.117"

View file

@ -4,11 +4,11 @@ version = "0.1.0"
edition = "2021"
[dependencies]
serde = { version = "1.0.195", features = ["derive", "rc"] }
serde_with = "3.4.0"
thiserror = "1.0.56"
derive_more = "0.99.17"
serde = { version = "1.0.203", features = ["derive", "rc"] }
serde_with = "3.8.1"
thiserror = "1.0.61"
derive_more = { version = "1.0.0-beta.6", features = ["from", "into", "deref"] }
rgb = "0.8.37"
enum-ordinalize = "4.3.0"
enum-map = "3.0.0-beta.2"
parse-display = "0.8.2"
parse-display = "0.9.1"

View file

@ -7,8 +7,8 @@ edition = "2021"
serialport = "4.3.0"
enum-ordinalize = "4.3.0"
enumset = "1.1.3"
bytes = "1.5.0"
thiserror = "1.0.52"
bytes = "1.6.0"
thiserror = "1.0.61"
rgb = "0.8.37"
flume = "0.11.0"
serde = { version = "1.0.195", features = ["derive"] }
serde = { version = "1.0.203", features = ["derive"] }

View file

@ -43,7 +43,7 @@ pub enum VibrationPattern {
pub(crate) enum LoupedeckCommand {
RequestSerialNumber,
RequestFirmwareVersion,
SetBrightness(f32),
SetBrightness(u8),
SetButtonColor {
button: LoupedeckButton,
color: RGB8,

View file

@ -103,7 +103,10 @@ impl LoupedeckDevice {
self.events_receiver.clone()
}
pub fn set_brightness(&self, value: f32) {
/// Sets the screen brightness.
///
/// `value` must be in 0..10. Higher values are clamped to 10.
pub fn set_brightness(&self, value: u8) {
self.commands_sender.send(LoupedeckCommand::SetBrightness(value)).unwrap();
}

View file

@ -263,10 +263,7 @@ pub(crate) fn write_messages_worker(mut port: Box<dyn SerialPort>, receiver: flu
let result = match command {
LoupedeckCommand::RequestSerialNumber => send(0x03, Bytes::new()),
LoupedeckCommand::RequestFirmwareVersion => send(0x07, Bytes::new()),
LoupedeckCommand::SetBrightness(value) => {
let raw_value = (value.clamp(0f32, 1f32) * 10.0) as u8;
send(0x09, Bytes::copy_from_slice(&[raw_value]))
}
LoupedeckCommand::SetBrightness(value) => send(0x09, Bytes::copy_from_slice(&[value])),
LoupedeckCommand::SetButtonColor { button, color } => send(0x02, Bytes::copy_from_slice(&[button.ordinal(), color.r, color.g, color.b])),
LoupedeckCommand::ReplaceFramebufferArea {
display_id,

View file

@ -6,7 +6,7 @@ edition = "2021"
[dependencies]
flume = "0.11.0"
im = "15.1.0"
tokio = { version = "1.35.1", default-features = false, features = ["sync"] }
tokio = { version = "1.38.0", default-features = false, features = ["sync"] }
libpulse-binding = "2.28.1"
log = "0.4.21"
arc-swap = "1.7.1"

View file

@ -1,5 +1,5 @@
use arc_swap::ArcSwap;
use std::sync::{Arc, RwLock};
use std::sync::Arc;
use std::thread;
use std::time::Instant;

View file

@ -5,9 +5,9 @@ edition = "2021"
[dependencies]
deckster_mode = { path = "../../crates/deckster_mode" }
clap = { version = "4.4.18", features = ["derive"] }
color-eyre = "0.6.2"
serde = { version = "1.0.196", features = ["derive"] }
env_logger = "0.11.1"
log = "0.4.20"
tokio = { version = "1.35.1", features = ["macros", "parking_lot", "rt", "process", "sync"] }
clap = { version = "4.5.7", features = ["derive"] }
color-eyre = "0.6.3"
serde = { version = "1.0.203", features = ["derive"] }
env_logger = "0.11.3"
log = "0.4.21"
tokio = { version = "1.38.0", features = ["macros", "parking_lot", "rt", "process", "sync"] }

View file

@ -5,17 +5,17 @@ edition = "2021"
[dependencies]
deckster_mode = { path = "../../crates/deckster_mode" }
clap = { version = "4.4.18", features = ["derive"] }
color-eyre = "0.6.2"
env_logger = "0.11.1"
log = "0.4.20"
tokio = { version = "1.35.1", features = ["macros", "parking_lot", "rt", "sync"] }
serde = { version = "1.0.196", features = ["derive"] }
serde_json = "1.0.114"
reqwest = { version = "0.11.24", default-features = false, features = ["rustls-tls-native-roots"] }
url = { version = "2.5.0", features = ["serde"] }
tokio-tungstenite = { version = "0.21.0", features = ["rustls-tls-native-roots"] }
tokio-stream = "0.1.14"
clap = { version = "4.5.7", features = ["derive"] }
color-eyre = "0.6.3"
env_logger = "0.11.3"
log = "0.4.21"
tokio = { version = "1.38.0", features = ["macros", "parking_lot", "rt", "sync"] }
serde = { version = "1.0.203", features = ["derive"] }
serde_json = "1.0.117"
reqwest = { version = "0.12.4", default-features = false, features = ["rustls-tls-native-roots"] }
url = { version = "2.5.1", features = ["serde"] }
tokio-tungstenite = { version = "0.23.1", features = ["rustls-tls-native-roots"] }
tokio-stream = "0.1.15"
futures-util = "0.3.30"
parse-display = "0.9.0"
serde_with = "3.6.1"
parse-display = "0.9.1"
serde_with = "3.8.1"

View file

@ -6,12 +6,12 @@ edition = "2021"
[dependencies]
deckster_mode = { path = "../../crates/deckster_mode" }
pa_volume_interface = { path = "../../crates/pa_volume_interface" }
clap = { version = "4.4.18", features = ["derive"] }
color-eyre = "0.6.2"
serde = { version = "1.0.196", features = ["derive"] }
clap = { version = "4.5.7", features = ["derive"] }
color-eyre = "0.6.3"
serde = { version = "1.0.203", features = ["derive"] }
serde_regex = "1.1.0"
regex = "1.10.3"
parse-display = "0.8.2"
env_logger = "0.11.1"
log = "0.4.20"
tokio = { version = "1.35.1", features = ["macros", "parking_lot", "rt-multi-thread", "sync"] }
regex = "1.10.5"
parse-display = "0.9.1"
env_logger = "0.11.3"
log = "0.4.21"
tokio = { version = "1.38.0", features = ["macros", "parking_lot", "rt-multi-thread", "sync"] }

View file

@ -5,10 +5,10 @@ edition = "2021"
[dependencies]
deckster_mode = { path = "../../crates/deckster_mode" }
clap = { version = "4.4.18", features = ["derive"] }
color-eyre = "0.6.2"
serde = { version = "1.0.196", features = ["derive"] }
env_logger = "0.11.1"
log = "0.4.20"
tokio = { version = "1.35.1", features = ["macros", "parking_lot", "rt-multi-thread", "sync"] }
clap = { version = "4.5.7", features = ["derive"] }
color-eyre = "0.6.3"
serde = { version = "1.0.203", features = ["derive"] }
env_logger = "0.11.3"
log = "0.4.21"
tokio = { version = "1.38.0", features = ["macros", "parking_lot", "rt-multi-thread", "sync"] }
once_cell = "1.19.0"

View file

@ -5,10 +5,10 @@ edition = "2021"
[dependencies]
deckster_mode = { path = "../../crates/deckster_mode" }
clap = { version = "4.4.18", features = ["derive"] }
color-eyre = "0.6.2"
env_logger = "0.11.1"
log = "0.4.20"
tokio = { version = "1.35.1", features = ["macros", "parking_lot", "rt", "sync", "time"] }
serde = { version = "1.0.196", features = ["derive"] }
clap = { version = "4.5.7", features = ["derive"] }
color-eyre = "0.6.3"
env_logger = "0.11.3"
log = "0.4.21"
tokio = { version = "1.38.0", features = ["macros", "parking_lot", "rt", "sync", "time"] }
serde = { version = "1.0.203", features = ["derive"] }
humantime-serde = "1.1.1"

View file

@ -190,7 +190,7 @@ pub mod labels {
let font_size = if text.utf8chars().count() == 1 { 40.0 } else { 11.0 };
self.buffer.set_metrics(&mut self.font_system, Metrics::new(font_size, font_size));
self.buffer.shape_until_scroll(&mut self.font_system);
self.buffer.shape_until_scroll(&mut self.font_system, false);
self.buffer.draw(
&mut self.font_system,

View file

@ -1,4 +1,5 @@
use std::cell::RefCell;
use std::ops::Add;
use std::path::Path;
use std::sync::Arc;
@ -10,7 +11,7 @@ use tokio::sync::broadcast;
use deckster_shared::handler_communication::{HandlerCommand, HandlerEvent, KeyEvent, KeyTouchEventKind, KnobEvent, VibrationPattern};
use deckster_shared::path::{KeyPath, KeyPosition, KnobPath, KnobPosition};
use deckster_shared::state::{Key, Knob};
use loupedeck_serial::characteristics::{LoupedeckDeviceKeyGridCharacteristics, LoupedeckDisplayRect, LoupedeckKnob};
use loupedeck_serial::characteristics::{LoupedeckButton, LoupedeckDeviceKeyGridCharacteristics, LoupedeckDisplayRect, LoupedeckKnob};
use loupedeck_serial::commands::VibrationPattern as LSVibrationPattern;
use loupedeck_serial::device::LoupedeckDevice;
use loupedeck_serial::events::{LoupedeckEvent, RotationDirection};
@ -23,6 +24,7 @@ use crate::model::coordinator_config::Config;
use crate::model::position::ButtonPosition;
#[derive(Debug, PartialEq, Clone)]
#[allow(clippy::enum_variant_names)]
pub enum CoordinatorCommand {
SetActivePages { key_page_id: String, knob_page_id: String },
SetRemoteHostIsActive { host_id: Box<str>, is_active: bool },
@ -120,6 +122,10 @@ fn handle_event(context: &mut IoWorkerContext, event: LoupedeckEvent) -> bool {
let position = ButtonPosition::of(&button);
let button_config = &context.config.buttons[position];
if button == LoupedeckButton::N0 {
context.state.is_0_button_held = true
}
context
.coordinator_commands_sender
.send(CoordinatorCommand::SetActivePages {
@ -128,6 +134,11 @@ fn handle_event(context: &mut IoWorkerContext, event: LoupedeckEvent) -> bool {
})
.unwrap()
}
LoupedeckEvent::ButtonUp { button } => {
if button == LoupedeckButton::N0 {
context.state.is_0_button_held = false
}
}
LoupedeckEvent::Touch { x, y, is_end, touch_id } => {
let characteristics = context.device.characteristics();
let display = characteristics.get_display_at_coordinates(x, y);
@ -183,20 +194,31 @@ fn handle_event(context: &mut IoWorkerContext, event: LoupedeckEvent) -> bool {
}
}
LoupedeckEvent::KnobRotate { knob, direction } => {
let position: KnobPosition = get_position_of_loupedeck_knob(knob);
if knob == LoupedeckKnob::LeftBottom && context.state.is_0_button_held {
context.state.screen_brightness = (context.state.screen_brightness as i8)
.add(match direction {
RotationDirection::Clockwise => 1,
RotationDirection::Counterclockwise => -1,
})
.clamp(0, 10) as u8;
send_knob_event(
KnobPath {
page_id: context.state.active_knob_page_id.clone(),
position,
},
KnobEvent::Rotate {
direction: match direction {
RotationDirection::Clockwise => deckster_shared::handler_communication::RotationDirection::Clockwise,
RotationDirection::Counterclockwise => deckster_shared::handler_communication::RotationDirection::Counterclockwise,
context.device.set_brightness(context.state.screen_brightness);
} else {
let position: KnobPosition = get_position_of_loupedeck_knob(knob);
send_knob_event(
KnobPath {
page_id: context.state.active_knob_page_id.clone(),
position,
},
},
)
KnobEvent::Rotate {
direction: match direction {
RotationDirection::Clockwise => deckster_shared::handler_communication::RotationDirection::Clockwise,
RotationDirection::Counterclockwise => deckster_shared::handler_communication::RotationDirection::Counterclockwise,
},
},
)
}
}
LoupedeckEvent::KnobDown { knob } => {
let position: KnobPosition = get_position_of_loupedeck_knob(knob);

View file

@ -117,7 +117,7 @@ pub async fn start(config_directory: &Path, config: Config) -> Result<()> {
let device = available_device.connect().wrap_err("Connecting to the device failed.")?;
log::info!("Connected.");
device.set_brightness(0.5);
device.set_brightness(config.initial.screen_brightness);
device.vibrate(VibrationPattern::RiseFall);
let io_worker_context = IoWorkerContext::create(config_directory, Arc::clone(&config), device, coordinator_commands_sender, events_sender);

View file

@ -16,6 +16,8 @@ pub struct State {
pub active_remote_handler_host_ids: HashSet<Box<str>>,
pub key_pages_by_id: HashMap<String, KeyPage>,
pub knob_pages_by_id: HashMap<String, KnobPage>,
pub is_0_button_held: bool,
pub screen_brightness: u8,
}
impl State {
@ -77,6 +79,8 @@ impl State {
},
key_pages_by_id,
knob_pages_by_id,
is_0_button_held: false,
screen_brightness: config.initial.screen_brightness,
}
}

View file

@ -5,9 +5,8 @@ use std::sync::Arc;
use color_eyre::eyre::{eyre, ContextCompat, WrapErr};
use color_eyre::Result;
use resvg::usvg::fontdb;
use resvg::usvg::tiny_skia_path::IntSize;
use resvg::usvg::{TextRendering, TreeParsing, TreeTextToPath};
use resvg::usvg::{fontdb, TextRendering};
use tiny_skia::{BlendMode, FilterQuality, Pixmap, PixmapPaint, Transform};
use deckster_shared::icon_descriptor::{IconDescriptor, IconDescriptorSource};
@ -23,7 +22,7 @@ pub struct IconManager {
config_directory: PathBuf,
icon_packs_by_id: Arc<HashMap<String, IconPack>>,
dpi: f32,
fonts_db: fontdb::Database,
fonts_db: Arc<fontdb::Database>,
}
#[derive(Debug)]
@ -41,11 +40,10 @@ impl IconManager {
config_directory,
icon_packs_by_id,
dpi,
fonts_db,
fonts_db: Arc::new(fonts_db),
}
}
// TODO: Add caching
fn load_icon(&self, descriptor: &IconDescriptor) -> Result<LoadedIcon> {
let icon_pack = if let IconDescriptorSource::IconPack { pack_id, .. } = &descriptor.source {
Some(
@ -113,7 +111,7 @@ impl IconManager {
fn read_image_and_get_scale(
config_directory: &Path,
dpi: f32,
fonts_db: &fontdb::Database,
fonts_db: &Arc<fontdb::Database>,
source: &IconDescriptorSource,
icon_pack: Option<&IconPack>,
preferred_scale: f32,
@ -148,34 +146,29 @@ fn read_image_and_get_scale(
})
}
fn read_image_from_svg(path: &Path, dpi: f32, font_db: &fontdb::Database, scale: f32) -> Result<Pixmap> {
fn read_image_from_svg(path: &Path, dpi: f32, font_db: &Arc<fontdb::Database>, scale: f32) -> Result<Pixmap> {
let raw_data = std::fs::read(path)?;
let tree = {
let mut tree = resvg::usvg::Tree::from_data(
&raw_data,
&resvg::usvg::Options {
dpi,
font_family: "Inter".to_owned(),
font_size: 11.0,
text_rendering: TextRendering::OptimizeLegibility,
..resvg::usvg::Options::default()
},
)?;
let tree = resvg::usvg::Tree::from_data(
&raw_data,
&resvg::usvg::Options {
dpi,
font_family: "Inter".to_owned(),
font_size: 11.0,
text_rendering: TextRendering::OptimizeLegibility,
fontdb: Arc::clone(font_db),
..resvg::usvg::Options::default()
},
)?;
tree.convert_text(font_db);
resvg::Tree::from_usvg(&tree)
};
let size = tree.size.to_int_size();
let size = tree.size().to_int_size();
let mut pixmap = Pixmap::new(
max(1, (size.width() as f32 * scale).ceil() as u32),
max(1, (size.height() as f32 * scale).ceil() as u32),
)
.expect("width and height can never be zero.");
tree.render(Transform::from_scale(scale, scale), &mut pixmap.as_mut());
resvg::render(&tree, Transform::from_scale(scale, scale), &mut pixmap.as_mut());
Ok(pixmap)
}

View file

@ -67,6 +67,7 @@ pub struct ButtonConfig {
pub struct InitialConfig {
pub key_page: String,
pub knob_page: String,
pub screen_brightness: u8,
}
#[derive(Debug, Eq, PartialEq, Hash, Clone, Serialize, Deserialize)]
@ -99,6 +100,10 @@ impl Config {
)));
}
if self.initial.screen_brightness > 10 {
return Err(eyre!("The maximum screen brightness is 10."));
}
for (position, button) in &self.buttons {
if let Some(key_page) = &button.key_page {
if !self.key_pages_by_id.contains_key(key_page) {