From 7bfb9ef50af0e0da6cb04c7011c8b860a430596a Mon Sep 17 00:00:00 2001 From: Sami Salonen Date: Sat, 25 Mar 2023 13:08:35 +0200 Subject: [PATCH] Mute special behaviour (#16) * Update description Signed-off-by: Sami Salonen * Configurable behaviour for commands Signed-off-by: Sami Salonen * Cleanup Signed-off-by: Sami Salonen * Disable code coverage Signed-off-by: Sami Salonen --------- Signed-off-by: Sami Salonen --- .github/workflows/cd.yml | 32 +++++------ .github/workflows/ci.yml | 55 ++++++++++--------- CHANGELOG.md | 5 ++ Cargo.lock | 115 ++++++++++++++++++++++++++++----------- Cargo.toml | 10 +++- Cross.toml | 14 ++--- src/bin.rs | 61 +++++++++++++++------ src/config.rs | 90 ++++++++++++++++++++++++++++++ 8 files changed, 278 insertions(+), 104 deletions(-) create mode 100644 src/config.rs diff --git a/.github/workflows/cd.yml b/.github/workflows/cd.yml index faf71bb..e580aca 100644 --- a/.github/workflows/cd.yml +++ b/.github/workflows/cd.yml @@ -94,19 +94,19 @@ jobs: env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - publish-cargo: - name: Publishing to Cargo - runs-on: ubuntu-22.04 - steps: - - uses: actions/checkout@master - - name: Install dependencies to avoid vendored libcec build in libcec-sys - run: sudo apt-get install libudev-dev libcec6 libcec-dev pkg-config libp8-platform-dev - - uses: actions-rs/toolchain@v1 - with: - toolchain: stable - profile: minimal - override: true - - uses: actions-rs/cargo@v1 - with: - command: publish - args: --token ${{ secrets.CARGO_API_KEY }} --allow-dirty + #publish-cargo: + # name: Publishing to Cargo + # runs-on: ubuntu-22.04 + # steps: + # - uses: actions/checkout@master + # - name: Install dependencies to avoid vendored libcec build in libcec-sys + # run: sudo apt-get install libudev-dev libcec6 libcec-dev pkg-config libp8-platform-dev + # - uses: actions-rs/toolchain@v1 + # with: + # toolchain: stable + # profile: minimal + # override: true + # - uses: actions-rs/cargo@v1 + # with: + # command: publish + # args: --token ${{ secrets.CARGO_API_KEY }} --allow-dirty diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 2798417..b07e8b7 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -84,30 +84,31 @@ jobs: command: clippy args: -- --target x86_64-unknown-linux-gnu -D warnings - coverage: - name: Code coverage - runs-on: ubuntu-22.04 - steps: - - name: Checkout repository - uses: actions/checkout@v2 - - name: Update apt - run: sudo apt-get update - - name: Install dependencies for build and coverage - run: sudo apt-get install libudev-dev libcec6 libcec-dev pkg-config libp8-platform-dev - - name: Install stable toolchain - uses: actions-rs/toolchain@v1 - with: - toolchain: stable - profile: minimal - override: true - - name: Run cargo-tarpaulin - uses: actions-rs/tarpaulin@v0.1.3 - with: - args: "--ignore-tests --out Lcov" - - name: Upload to Coveralls - # upload only if push - if: ${{ github.event_name == 'push' }} - uses: coverallsapp/github-action@master - with: - github-token: ${{ secrets.GITHUB_TOKEN }} - path-to-lcov: "./lcov.info" + # broken (and not useful) + # coverage: + # name: Code coverage + # runs-on: ubuntu-22.04 + # steps: + # - name: Checkout repository + # uses: actions/checkout@v2 + # - name: Update apt + # run: sudo apt-get update + # - name: Install dependencies for build and coverage + # run: sudo apt-get install libudev-dev libcec6 libcec-dev pkg-config libp8-platform-dev + # - name: Install stable toolchain + # uses: actions-rs/toolchain@v1 + # with: + # toolchain: stable + # profile: minimal + # override: true + # - name: Run cargo-tarpaulin + # uses: actions-rs/tarpaulin@v0.1.3 + # with: + # args: "--ignore-tests --out Lcov" + # - name: Upload to Coveralls + # # upload only if push + # if: ${{ github.event_name == 'push' }} + # uses: coverallsapp/github-action@master + # with: + # github-token: ${{ secrets.GITHUB_TOKEN }} + # path-to-lcov: "./lcov.info" diff --git a/CHANGELOG.md b/CHANGELOG.md index c2a53bd..85db48b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,11 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +## 3.1.0 + + +- CI: utilize Cross `pre-build` instead of pre-built docker images +- Support running custom commands on vol up, vol down, or mute button press. ## 3.0.3 diff --git a/Cargo.lock b/Cargo.lock index 7f1d59a..45a4678 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4,9 +4,9 @@ version = 3 [[package]] name = "aho-corasick" -version = "0.7.18" +version = "0.7.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e37cfd5e7657ada45f742d6e99ca5788580b5c529dc78faf11ece6dc702656f" +checksum = "cc936419f96fa211c1b9166887b38e5e40b19958e5b895be7c1f93adec7071ac" dependencies = [ "memchr", ] @@ -36,18 +36,21 @@ checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" [[package]] name = "cc" -version = "1.0.73" +version = "1.0.79" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2fff2a6927b3bb87f9595d67196a70493f627687a71d87a0d692242c33f58c11" +checksum = "50d30906286121d95be3d479533b458f87493b30a4b5f79a607db8f5d11aa91f" [[package]] name = "cec-alsa-sync" -version = "3.0.3" +version = "3.1.0" dependencies = [ "arrayvec", "cec-rs", "env_logger", "log", + "once_cell", + "serde", + "toml", ] [[package]] @@ -72,9 +75,9 @@ checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" [[package]] name = "cmake" -version = "0.1.48" +version = "0.1.49" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e8ad8cef104ac57b68b89df3208164d228503abbdce70f6880ffa3d970e7443a" +checksum = "db34956e100b30725f2eb215f90d4871051239535632f84fea3bc92722c66b7c" dependencies = [ "cc", ] @@ -109,7 +112,7 @@ dependencies = [ "proc-macro2", "quote", "strsim", - "syn", + "syn 1.0.109", ] [[package]] @@ -120,7 +123,7 @@ checksum = "29b5acf0dea37a7f66f7b25d2c5e93fd46f8f6968b1a5d7a3e02e97768afc95a" dependencies = [ "darling_core", "quote", - "syn", + "syn 1.0.109", ] [[package]] @@ -141,7 +144,7 @@ dependencies = [ "darling", "proc-macro2", "quote", - "syn", + "syn 1.0.109", ] [[package]] @@ -151,7 +154,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "58a94ace95092c5acb1e97a7e846b310cfbd499652f72297da7493f618a98d73" dependencies = [ "derive_builder_core", - "syn", + "syn 1.0.109", ] [[package]] @@ -163,14 +166,14 @@ dependencies = [ "proc-macro-error", "proc-macro2", "quote", - "syn", + "syn 1.0.109", ] [[package]] name = "env_logger" -version = "0.9.0" +version = "0.9.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0b2cf0344971ee6c64c31be0d530793fba457d322dfec2810c453d0ef228f9c3" +checksum = "a12e6657c4c97ebab115a42dcee77225f7f482cdd841cf7088c657a42e9e00e7" dependencies = [ "atty", "humantime", @@ -218,9 +221,9 @@ dependencies = [ [[package]] name = "libc" -version = "0.2.132" +version = "0.2.140" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8371e4e5341c3a96db127eb2465ac681ced4c433e01dd0e938adbef26ba93ba5" +checksum = "99227334921fae1a979cf0bfdfcc6b3e5ce376ef57e16fb6fb3ea2ed6095f80c" [[package]] name = "libcec-sys" @@ -258,11 +261,17 @@ dependencies = [ "autocfg", ] +[[package]] +name = "once_cell" +version = "1.17.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b7e5500299e16ebb147ae15a00a942af264cf3688f47923b8fc2cd5858f23ad3" + [[package]] name = "pkg-config" -version = "0.3.25" +version = "0.3.26" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1df8c4ec4b0627e53bdf214615ad287367e482558cf84b109250b37464dc03ae" +checksum = "6ac9a59f73473f1b8d852421e59e64809f025994837ef743615c6d0c5b305160" [[package]] name = "proc-macro-error" @@ -273,7 +282,7 @@ dependencies = [ "proc-macro-error-attr", "proc-macro2", "quote", - "syn", + "syn 1.0.109", "version_check", ] @@ -290,27 +299,27 @@ dependencies = [ [[package]] name = "proc-macro2" -version = "1.0.43" +version = "1.0.53" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0a2ca2c61bc9f3d74d2886294ab7b9853abd9c1ad903a3ac7815c58989bb7bab" +checksum = "ba466839c78239c09faf015484e5cc04860f88242cff4d03eb038f04b4699b73" dependencies = [ "unicode-ident", ] [[package]] name = "quote" -version = "1.0.21" +version = "1.0.26" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bbe448f377a7d6961e30f5955f9b8d106c3f5e449d493ee1b125c1d43c2b5179" +checksum = "4424af4bf778aae2051a77b60283332f386554255d722233d09fbfc7e30da2fc" dependencies = [ "proc-macro2", ] [[package]] name = "regex" -version = "1.6.0" +version = "1.7.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4c4eb3267174b8c6c2f654116623910a0fef09c4753f8dd83db29c48a0df988b" +checksum = "8b1f693b24f6ac912f4893ef08244d70b6067480d2f1a46e950c9691e6749d1d" dependencies = [ "aho-corasick", "memchr", @@ -319,9 +328,29 @@ dependencies = [ [[package]] name = "regex-syntax" -version = "0.6.27" +version = "0.6.29" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f162c6dd7b008981e4d40210aca20b4bd0f9b60ca9271061b07f78537722f2e1" + +[[package]] +name = "serde" +version = "1.0.158" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a3f87b73ce11b1619a3c6332f45341e0047173771e8b8b73f87bfeefb7b56244" +checksum = "771d4d9c4163ee138805e12c710dd365e4f44be8be0503cb1bb9eb989425d9c9" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_derive" +version = "1.0.158" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e801c1712f48475582b7696ac71e0ca34ebb30e09338425384269d9717c62cad" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.10", +] [[package]] name = "strsim" @@ -331,9 +360,20 @@ checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623" [[package]] name = "syn" -version = "1.0.99" +version = "1.0.109" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "58dbef6ec655055e20b86b15a8cc6d439cca19b667537ac6a1369572d151ab13" +checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "syn" +version = "2.0.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5aad1363ed6d37b84299588d62d3a7d95b5a5c2d9aad5c85609fda12afaa1f40" dependencies = [ "proc-macro2", "quote", @@ -342,18 +382,27 @@ dependencies = [ [[package]] name = "termcolor" -version = "1.1.3" +version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bab24d30b911b2376f3a13cc2cd443142f0c81dda04c118693e35b3835757755" +checksum = "be55cf8942feac5c765c2c993422806843c9a9a45d4d5c407ad6dd2ea95eb9b6" dependencies = [ "winapi-util", ] +[[package]] +name = "toml" +version = "0.5.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f4f7f0dd8d50a853a531c426359045b1998f04219d88799810762cd4ad314234" +dependencies = [ + "serde", +] + [[package]] name = "unicode-ident" -version = "1.0.3" +version = "1.0.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c4f5b37a154999a8f3f98cc23a628d850e154479cd94decf3414696e12e31aaf" +checksum = "e5464a87b239f13a63a501f2701565754bae92d243d4bb7eb12f6d57d2269bf4" [[package]] name = "version_check" diff --git a/Cargo.toml b/Cargo.toml index 78804f9..d3a64a2 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -4,11 +4,11 @@ path = 'src/bin.rs' [package] name = 'cec-alsa-sync' -version = "3.0.3" +version = "3.1.0" authors = ['Sami Salonen'] edition = '2018' license = 'GPL-2.0' -description = 'Utility to synchronize ALSA volume with commands from HDMI CEC' +description = 'Small command line application to command ALSA volume using CEC via Rasberry Pi HDMI port' documentation = 'https://docs.rs/cec-alsa-sync' readme = 'README.md' include = ['/README.md', '/LICENSE', '/Cargo.toml', '/Cargo.lock', '/src/*.rs'] @@ -16,12 +16,16 @@ keywords = ['libcec', 'cec', 'hdmi', 'alsa'] categories = ['command-line-utilities'] homepage = 'https://github.com/ssalonen/cec-alsa-sync' repository = 'https://github.com/ssalonen/cec-alsa-sync' +publish = false [dependencies] cec-rs = '6.0.0' +arrayvec = '0.7.1' env_logger = '0.9.0' log = '0.4.11' -arrayvec = '0.7.1' +once_cell = "1.17.1" +serde = { version = '1.0.118', features = ["derive"] } +toml = "0.5.8" [badges.travis-ci] repository = 'ssalonen/cec-alsa-sync' diff --git a/Cross.toml b/Cross.toml index c1e24ca..6ae61f5 100644 --- a/Cross.toml +++ b/Cross.toml @@ -1,9 +1,5 @@ - -[target.aarch64-unknown-linux-gnu] -image="docker.io/ssalonen/aarch64-unknown-linux-gnu-0.2.1" - -[target.arm-unknown-linux-gnueabi] -image="docker.io/ssalonen/arm-unknown-linux-gnueabi-0.2.1" - -[target.armv7-unknown-linux-gnueabihf] -image="docker.io/ssalonen/armv7-unknown-linux-gnueabihf-0.2.1" +[build] +default-target = "armv7-unknown-linux-gnueabihf" +pre-build = [ + "apt-get remove --allow-remove-essential -y libudev1 udev libudev-dev || :" +] diff --git a/src/bin.rs b/src/bin.rs index 25622b2..7064894 100644 --- a/src/bin.rs +++ b/src/bin.rs @@ -6,12 +6,20 @@ use cec_rs::{ CecCommand, CecConnection, CecConnectionCfgBuilder, CecDatapacket, CecDeviceType, CecDeviceTypeVec, CecKeypress, CecLogMessage, CecUserControlCode, }; + use std::convert::TryFrom; +use std::fs::File; +use std::io::Read; +use std::path::Path; use std::process::Command; use std::sync::mpsc::{channel, Sender}; use std::time::Duration; -use std::u8; + +mod config; +use config::CONFIG; + +use crate::config::{AppConfig, CreatesCommand}; struct VolumePercent(u8); @@ -65,18 +73,15 @@ fn on_key_press(keypress: CecKeypress) { keypress.keycode, keypress.duration ); - let vol_delta: Option<&str> = match keypress.keycode { - CecUserControlCode::VolumeUp => Some("1%+"), - CecUserControlCode::VolumeDown => Some("1%-"), + let app_config = CONFIG.get().expect("Config not available"); + let command: Option = match keypress.keycode { + CecUserControlCode::VolumeUp => Some(app_config.vol_up_command.new_command()), + CecUserControlCode::VolumeDown => Some(app_config.vol_down_command.new_command()), + CecUserControlCode::Mute => app_config.mute_command.as_ref().map(|c| c.new_command()), _ => None, }; - if let Some(vol_delta) = vol_delta { - Command::new("amixer") - .arg("set") - .arg("DSPVolume") - .arg(vol_delta) - .output() - .expect("Failed to call amixer"); + if let Some(mut command) = command { + command.output().expect("Failed to call amixer"); } } @@ -179,13 +184,16 @@ fn on_log_message(log_message: CecLogMessage) { } } -pub fn main() { +pub fn main() -> Result<(), &'static str> { env_logger::init(); + read_config()?; + let (sender, receiver) = channel(); + let app_config = CONFIG.get().expect("Config not available"); - let cfg = CecConnectionCfgBuilder::default() - .port("RPI".into()) - .device_name("Hifiberry".into()) + let connection_config = CecConnectionCfgBuilder::default() + .port(app_config.hdmi_port.clone()) + .device_name(app_config.device_name.clone()) .key_press_callback(Box::new(on_key_press)) .command_received_callback(Box::new(move |command| { on_command_received(sender.clone(), command) @@ -194,7 +202,7 @@ pub fn main() { .device_types(CecDeviceTypeVec::new(CecDeviceType::AudioSystem)) .build() .unwrap(); - let connection: CecConnection = cfg.open().unwrap(); + let connection: CecConnection = connection_config.open().unwrap(); trace!("Active source: {:?}", connection.get_active_source()); @@ -212,4 +220,25 @@ pub fn main() { break; } } + Ok(()) +} + +fn read_config() -> Result<(), &'static str> { + let args: Vec = std::env::args().collect(); + if args.len() != 2 { + // Load default config + let c: AppConfig = toml::from_str("").expect("error while reading config"); + CONFIG.set(c).expect("failed to set config"); + return Ok(()); + } + let config_path = &args[1]; + let config_file_path = Path::new(config_path); + let mut config_file = File::open(config_file_path).expect("Failed to open config"); + let mut contents = String::new(); + config_file + .read_to_string(&mut contents) + .expect("Failed to read config"); + let c: AppConfig = toml::from_str(&contents).expect("error while reading config"); + CONFIG.set(c).expect("failed to set config"); + Ok(()) } diff --git a/src/config.rs b/src/config.rs new file mode 100644 index 0000000..d8b429f --- /dev/null +++ b/src/config.rs @@ -0,0 +1,90 @@ +use std::process::Command; + +use once_cell::sync::OnceCell; +use serde::{de, Deserialize, Deserializer}; + +pub trait CreatesCommand { + fn new_command(&self) -> Command; +} + +#[derive(Debug)] +pub struct CommandTemplate(Vec); + +impl From> for CommandTemplate { + fn from(values: Vec<&str>) -> Self { + CommandTemplate(values.iter().cloned().map(String::from).collect()) + } +} + +impl CreatesCommand for CommandTemplate { + fn new_command(&self) -> Command { + let args = self.0.clone(); + let mut cmd = Command::new(args.get(0).expect("Missing program")); + cmd.args(&args[1..]); + cmd + } +} + +#[derive(Deserialize, Debug)] +pub struct AppConfig { + #[serde(default = "default_hdmi_port")] + pub hdmi_port: String, + #[serde(default = "default_device_name")] + pub device_name: String, + #[serde( + default = "default_volume_up", + deserialize_with = "command_template_from_args" + )] + pub vol_up_command: CommandTemplate, + #[serde( + default = "default_volume_down", + deserialize_with = "command_template_from_args" + )] + pub vol_down_command: CommandTemplate, + #[serde(default, deserialize_with = "command_template_option_from_args")] + pub mute_command: Option, +} + +fn command_template_from_args<'de, D>(deserializer: D) -> Result +where + D: Deserializer<'de>, +{ + let args: Vec = de::Deserialize::deserialize(deserializer)?; + if args.is_empty() { + return Err(de::Error::custom("app missing")); + } + Ok(CommandTemplate(args)) +} + +fn command_template_option_from_args<'de, D>( + deserializer: D, +) -> Result, D::Error> +where + D: Deserializer<'de>, +{ + let args: Option> = de::Deserialize::deserialize(deserializer)?; + + if let Some(ref args) = args { + if args.is_empty() { + return Ok(None); + } + } + Ok(args.map(CommandTemplate)) +} + +fn default_volume_up() -> CommandTemplate { + vec!["amixer", "set", "DSPVolume", "1%+"].into() +} + +fn default_volume_down() -> CommandTemplate { + vec!["amixer", "set", "DSPVolume", "1%-"].into() +} + +fn default_device_name() -> String { + "Hifiberry".to_owned() +} +fn default_hdmi_port() -> String { + "RPI".to_owned() +} + +pub static CONFIG: OnceCell = OnceCell::new();