diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 4742bfb..0b86f29 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -1,4 +1,4 @@ -name: Build, Test, Lint and Package +name: Build, Test, Lint & Package on: workflow_dispatch: @@ -50,6 +50,7 @@ jobs: - name: Restore cached cargo dependencies id: deps-cache + if: ${{ !inputs.release }} uses: actions/cache/restore@v5 with: path: | diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index ea2d5db..e46120e 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -1,22 +1,28 @@ name: CI on: - pull_request_target: push: jobs: build: + name: Build uses: ./.github/workflows/build.yml - comment: - name: Bot comment + pr: + name: PR needs: build runs-on: ubuntu-latest - if: github.event_name == 'pull_request_target' + if: github.head_ref env: MESSAGE_FILE: ./message.md steps: - - name: Create message + - name: Check if in PR + uses: insurgent-lab/is-in-pr-action@v0.2.0 + id: is-pr + + - name: Create PR comment + id: create-comment + if: ${{ steps.is-pr.outputs.result == 'true' }} run: | function outcome_emoji() { if [ "$1" == "success" ]; then @@ -32,12 +38,14 @@ jobs: echo "$1" >> ${{ env.MESSAGE_FILE }} } - write "## :arrow_right: Commit ${{ github.event.pull_request.head.sha }}" + write "## :arrow_right: Commit ${{ github.sha }}" write "## :hammer_and_wrench: Building result: $(outcome_emoji ${{ needs.build.outputs.build }})" - write "### :package: Artifacts: $(outcome_emoji ${{ needs.build.outputs.artifact-url }})" - write "1. [Download the tarball](${{ needs.build.outputs.artifact-url }})" - write "2. \`unzip linux-enable-ir-emitter*.tar.gz.zip\`" - write "3. [Execute the install instructions](https://github.com/EmixamPP/linux-enable-ir-emitter?tab=readme-ov-file#installation)" + if [ "${{ needs.build.outputs.build }}" == "success" ]; then + write "### :package: Artifacts:" + write "1. [Download the tarball](${{ needs.build.outputs.artifact-url }})" + write "2. \`unzip linux-enable-ir-emitter*.tar.gz.zip\`" + write "3. [Execute the install instructions](https://github.com/EmixamPP/linux-enable-ir-emitter?tab=readme-ov-file#installation)" + fi write "" write "## :test_tube: Tests results: $(outcome_emoji ${{ needs.build.outputs.tests }})" write "## :stethoscope: Clippy results: $(outcome_emoji ${{ needs.build.outputs.clippy }})" @@ -45,7 +53,9 @@ jobs: write "## :books: Doc results: $(outcome_emoji ${{ needs.build.outputs.doc }})" write "## :scissors: Shear results: $(outcome_emoji ${{ needs.build.outputs.shear }})" - - uses: thollander/actions-comment-pull-request@v3 + - name: Comment on PR + uses: thollander/actions-comment-pull-request@v3 + if: ${{ steps.create-comment.outcome == 'success' }} with: comment-tag: bot_comment mode: recreate diff --git a/CHANGELOG.md b/CHANGELOG.md index 20c0fcf..21c1f70 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,6 +11,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - New TUI interface for improved user experience and understanding. - `tweak` command is now accessible via the `configure` command. - Better balance between configuration search time and exhaustiveness. +- Full rootless capabilities. ### Added - More debug commands to help users to report issues: `--config`, `--log`, `--grey-devices`. diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 3f23de5..0500a04 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -3,7 +3,7 @@ Thank you for considering contributing to our project! We appreciate your intere ## Packaging Guidelines When building a package for distribution, please ensure the following: -1. You modified the configuration and log file paths in [.cargo/config.toml](.cargo/config.toml) to match the packaging standards of the target distribution. +1. You can modify the configuration and log file paths in [.cargo/config.toml](.cargo/config.toml) to match the packaging standards of the target distribution. Note that the defaults allow rootless utilization. 2. The only external *compile time* dependency needed are * `gcc` * `libclang` diff --git a/Cargo.lock b/Cargo.lock index ea4f2d1..a472c3e 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -257,9 +257,9 @@ checksum = "f4ad8f11f288f48ca24471bbd51ac257aaeaaa07adae295591266b792902ae64" [[package]] name = "bumpalo" -version = "3.19.0" +version = "3.19.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "46c5e41b57b8bba42a04676d81cb89e9ee8e859a1a66f80a5a72e1cb76b34d43" +checksum = "5dd9dc738b7a8311c7ade152424974d8115f2cdad61e8dab8dac9f2362298510" [[package]] name = "bytemuck" @@ -1089,7 +1089,7 @@ dependencies = [ [[package]] name = "linux-enable-ir-emitter" -version = "7.0.0-beta1" +version = "7.0.0-beta2" dependencies = [ "ansi-to-tui", "ansipix", diff --git a/Cargo.toml b/Cargo.toml index 2753ef9..0b34324 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "linux-enable-ir-emitter" -version = "7.0.0-beta1" +version = "7.0.0-beta2" edition = "2024" authors = ["Maxime Dirksen (EmixamPP)"] license = "MIT" diff --git a/README.md b/README.md index ea1093b..f14af2c 100644 --- a/README.md +++ b/README.md @@ -11,14 +11,14 @@ Provides support for infrared cameras that are not directly enabled out-of-the b > If you plan to package this software for a Linux distribution, or to contribute code, please read [CONTRIBUTING.md](CONTRIBUTING.md). ## Installation -Download the latest [linux-enable-ir-emitter-x.x.x-release-x86-64.tar.gz](https://github.com/EmixamPP/linux-enable-ir-emitter/releases). Then execute: +Download the latest asset [linux-enable-ir-emitter-x.x.x-release-x86-64.tar.gz](https://github.com/EmixamPP/linux-enable-ir-emitter/releases). Then execute: > [!NOTE] > Please try the 7.0.0-beta! Furthermore, this README has been updated for this version. ``` tar -C $HOME/.local/bin --no-same-owner -m -vxzf linux-enable-ir-emitter*.tar.gz ``` -If not already done, add `$HOME/.local/bin` to your PATH, e.g.: +If not already done, add `$HOME/.local/bin` to your `$PATH`, e.g.: ``` echo 'export PATH=$HOME/.local/bin:$PATH' >> $HOME/.bashrc && source $HOME/.bashrc ``` @@ -31,16 +31,20 @@ The installation consists of 3 files: ### Integration with Howdy In all files returned by `grep -rl howdy /etc/pam.d`, add the following line before the one mentioning "howdy", replacing `` with your actual username: ``` -auth optional pam_exec.so /home//.local/bin/linux-enable-ir-emitter run +auth optional pam_exec.so /home//.local/bin/linux-enable-ir-emitter run --config /home//.config/linux-enable-ir-emitter.toml ``` -The path to the binary may vary depending on your installation method. You can determine the correct absolute path by running `which linux-enable-ir-emitter` and use that path instead. +> [!TIP] +> The installation paths may vary depending on your installation method. You can determine the correct binary absolute paths by running `which linux-enable-ir-emitter` and use that path instead. For the configuration path, it will be written when you can execute `linux-enable-ir-emitter --config`. ### Integration with other program You will need to execute the `linux-enable-ir-emitter run` command before the program that uses the infrared camera. Alternatively, if you can and/or want to integrate better with the program that uses the camera, you can pass an opened file descriptor for the camera to the command: `linux-enable-ir-emitter run --device --fd `. +> [!Important] +> You will need to pass the config path as argument to `linux-enable-ir-emitter run --config ` **when executed as root** if `linux-enable-ir-emitter configure` was executed as a normal user. + ## How do I enable my infrared emitter? 0. For a better experience, use a large terminal window. 1. Execute the command: `linux-enable-ir-emitter configure` diff --git a/src/configuration.rs b/src/configuration.rs index 3c8d9fc..84319d6 100644 --- a/src/configuration.rs +++ b/src/configuration.rs @@ -1,7 +1,7 @@ use crate::video::uvc::XuControl; use std::collections::{BTreeMap as Map, BTreeSet as Set}; -use std::path::Path; +use std::path::{Path, PathBuf}; use anyhow::{Context, Result, anyhow}; use derive_more::AsRef; @@ -13,23 +13,23 @@ static _CONFIG: &str = env!("CONFIG"); #[cfg(test)] pub const _CONFIG: &str = "target/tmp/linux-enable-ir-emitter.toml"; -fn config_file_path() -> Result { +fn config_file_path() -> Result { Ok(shellexpand::env(_CONFIG) .context("failed to expend shell variable in log file path")? - .to_string()) + .to_string() + .into()) } pub fn print_config() -> Result<()> { let config_file = config_file_path()?; let content_str = std::fs::read_to_string(&config_file).context("failed to read configuration file")?; - print!("# {}\n\n{}", config_file, content_str); + print!("# {}\n\n{}", config_file.display(), content_str); Ok(()) } mod path { use super::*; - use std::path::PathBuf; #[derive(Default, Debug, PartialEq, Eq, PartialOrd, Ord, Clone, AsRef)] pub struct V4LPath(PathBuf); @@ -118,18 +118,25 @@ impl Configurations { let config_str = toml::to_string(&self.devices).context("failed to serialize configuration")?; let config_file = config_file_path()?; - log::debug!("Saving configuration at {}:\n{}", config_file, config_str); + log::debug!( + "Saving configuration at {}:\n{}", + config_file.display(), + config_str + ); std::fs::write(config_file, config_str).context("failed to write configuration file") } - /// Load an existing configurations or create an empty one. + /// Load an existing configurations at the default location or create an empty one. pub fn load() -> Result { - Ok(match std::fs::read_to_string(config_file_path()?) { - Ok(config_str) => Self { - devices: toml::from_str(&config_str) - .context("failed to parse configuration file")?, - }, - Err(_) => Self::default(), + Ok(Self::load_from(&config_file_path()?).unwrap_or_default()) + } + + /// Load an existing configurations at a specified path. + pub fn load_from(config: &Path) -> Result { + log::info!("Loading configuration from {}.", config.display()); + let config_str = std::fs::read_to_string(config)?; + Ok(Self { + devices: toml::from_str(&config_str).context("failed to parse configuration file")?, }) } @@ -140,9 +147,8 @@ impl Configurations { /// Initializes an existing configuration if it does not exist. fn initialize_config_dir() -> Result<()> { let config_file = config_file_path()?; - log::debug!("Configuration located at: {}", config_file); - let config_dir = Path::new(&config_file).parent(); - if let Some(config_dir) = config_dir { + log::debug!("Configuration located at: {}", config_file.display()); + if let Some(config_dir) = config_file.parent() { std::fs::create_dir_all(config_dir).context("failed to create configuration directory") } else { Ok(()) diff --git a/src/main.rs b/src/main.rs index 9df3988..cf355f0 100644 --- a/src/main.rs +++ b/src/main.rs @@ -29,9 +29,9 @@ async fn main() -> Result<()> { log::debug!("Version {}.", env!("CARGO_PKG_VERSION")); configure::configure().await } - Some(Commands::Run { device, fd }) => { + Some(Commands::Run { device, fd, config }) => { logger::init_term()?; - run::run(device.as_deref(), fd) + run::run(device.as_deref(), fd, config.as_deref()) } None => Ok(()), } @@ -76,5 +76,12 @@ enum Commands { requires = "device" )] fd: Option, + #[arg( + short, + long, + help = "Specify the configuration file to use. Default: use the default configuration path.", + default_value = None, + )] + config: Option, }, } diff --git a/src/run.rs b/src/run.rs index a5be436..a7ecd4e 100644 --- a/src/run.rs +++ b/src/run.rs @@ -10,12 +10,17 @@ use std::path::Path; /// /// Does not return an error if no configuration exists for the device. /// -pub fn run(device: Option<&Path>, fd: Option) -> Result<()> { +pub fn run(device: Option<&Path>, fd: Option, config: Option<&Path>) -> Result<()> { if fd.is_some() && device.is_none() { bail!("if a file descriptor is provided, a device path must also be provided"); } - let config = Configurations::load()?; + let config = if let Some(config) = config { + Configurations::load_from(config)? + } else { + Configurations::load()? + }; + for (path, conf) in config.devices() { if let Some(d) = device && d != path.as_ref()