diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 37bde70..44d743d 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -30,7 +30,13 @@ jobs: - name: Check formatting run: cargo fmt --check - - name: Clippy + - name: Clippy (base features) + uses: actions-rs/clippy-check@v1 + with: + token: ${{ secrets.GITHUB_TOKEN }} + args: --no-default-features --features config+json + + - name: Clippy (all features) uses: actions-rs/clippy-check@v1 with: token: ${{ secrets.GITHUB_TOKEN }} diff --git a/Cargo.lock b/Cargo.lock index a84404d..409b402 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1346,6 +1346,7 @@ name = "ironbar" version = "0.9.0" dependencies = [ "async_once", + "cfg-if", "chrono", "color-eyre", "dirs", diff --git a/Cargo.toml b/Cargo.toml index a28cf91..c1c7b96 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -5,11 +5,47 @@ edition = "2021" license = "MIT" description = "Customisable GTK Layer Shell wlroots/sway bar" +[features] +default = [ + "http", + "config+all", + "clock", + "music+all", + "sys_info", + "tray", + "workspaces+all" +] + +http = ["dep:reqwest"] + +"config+all" = ["config+json", "config+yaml", "config+toml", "config+corn"] +"config+json" = ["serde_json"] +"config+yaml" = ["serde_yaml"] +"config+toml" = ["toml"] +"config+corn" = ["libcorn"] + +clock = ["chrono"] + +music = ["regex"] +"music+all" = ["music", "music+mpris", "music+mpd"] +"music+mpris" = ["music", "mpris"] +"music+mpd" = ["music", "mpd_client"] + +sys_info = ["sysinfo", "regex"] + +tray = ["stray"] + +workspaces = ["futures-util"] +"workspaces+all" = ["workspaces", "workspaces+sway", "workspaces+hyprland"] +"workspaces+sway" = ["workspaces", "swayipc-async"] +"workspaces+hyprland" = ["workspaces", "hyprland"] + [dependencies] +# core gtk = "0.16.0" gtk-layer-shell = "0.5.0" glib = "0.16.2" -tokio = { version = "1.21.2", features = ["macros", "rt-multi-thread", "time", "process"] } +tokio = { version = "1.21.2", features = ["macros", "rt-multi-thread", "time", "process", "sync", "io-util", "net"] } tracing = "0.1.37" tracing-subscriber = { version = "0.3.16", features = ["env-filter"] } tracing-error = "0.2.0" @@ -17,26 +53,43 @@ tracing-appender = "0.2.2" strip-ansi-escapes = "0.1.1" color-eyre = "0.6.2" serde = { version = "1.0.141", features = ["derive"] } -serde_json = "1.0.82" -serde_yaml = "0.9.4" -toml = "0.7.0" -libcorn = "0.6.1" -lazy_static = "1.4.0" -async_once = "0.2.6" indexmap = "1.9.1" -futures-util = "0.3.21" -chrono = "0.4.19" -reqwest = {version = "0.11.14" } -regex = { version = "1.6.0", default-features = false, features = ["std"] } -stray = { version = "0.1.3" } dirs = "4.0.0" walkdir = "2.3.2" notify = { version = "5.0.0", default-features = false } -mpd_client = "1.0.0" -mpris = "2.0.0" -swayipc-async = { version = "2.0.1" } -hyprland = "0.3.0" -sysinfo = "0.27.0" wayland-client = "0.29.5" wayland-protocols = { version = "0.29.5", features = ["unstable_protocols", "client"] } -smithay-client-toolkit = { version = "0.16.0", default-features = false, features = ["calloop"] } \ No newline at end of file +smithay-client-toolkit = { version = "0.16.0", default-features = false, features = ["calloop"] } +lazy_static = "1.4.0" +async_once = "0.2.6" +cfg-if = "1.0.0" + +# http +reqwest = { version = "0.11.14", optional = true } + +# config +serde_json = { version = "1.0.82", optional = true } +serde_yaml = { version = "0.9.4", optional = true } +toml = { version = "0.7.0", optional = true } +libcorn = { version = "0.6.1", optional = true } + +# clock +chrono = { version = "0.4.19", optional = true } + +# music +mpd_client = { version = "1.0.0", optional = true } +mpris = { version = "2.0.0", optional = true } + +# sys_info +sysinfo = { version = "0.27.0", optional = true } + +# tray +stray = { version = "0.1.3", optional = true } + +# workspaces +swayipc-async = { version = "2.0.1", optional = true } +hyprland = { version = "0.3.0", optional = true } +futures-util = { version = "0.3.21", optional = true } + +# shared +regex = { version = "1.6.0", default-features = false, features = ["std"], optional = true } # music, sys_info \ No newline at end of file diff --git a/README.md b/README.md index 7c40aae..dd708fc 100644 --- a/README.md +++ b/README.md @@ -29,9 +29,11 @@ yay -S ironbar-git ### Nix Flake +A flake is included with the repo which can be used with home-manager. + #### Example -Here is an example nix flake that uses ironbar, this is just a -proof of concept, please adapt it to your config + +Here is an example nix flake that uses Ironbar. ```nix { @@ -67,8 +69,9 @@ proof of concept, please adapt it to your config ``` #### Binary Caching -There is also a cachix cache at `https://app.cachix.org/cache/jakestanger` -incase you don't want to compile ironbar! + +There is a Cachix cache available at `https://app.cachix.org/cache/jakestanger` +in case you don't want to compile Ironbar. ### Source @@ -80,6 +83,9 @@ cargo build --release install target/release/ironbar ~/.local/bin/ironbar ``` +By default, all features are enabled. +See [here](https://github.com/JakeStanger/ironbar/wiki/compiling) for controlling which features are included. + [repo](https://github.com/jakestanger/ironbar) ## Running diff --git a/docs/Compiling.md b/docs/Compiling.md new file mode 100644 index 0000000..804f957 --- /dev/null +++ b/docs/Compiling.md @@ -0,0 +1,51 @@ +You can compile Ironbar from source using `cargo`. +Just clone the repo and build: + +```sh +git clone https://github.com/jakestanger/ironbar.git +cd ironbar +cargo build --release +# change path to wherever you want to install +install target/release/ironbar ~/.local/bin/ironbar +``` + +## Features + +By default, all features are enabled for convenience. This can result in a significant compile time. +If you know you are not going to need all the features, you can compile with only the features you need. + +As of `v0.10.0`, compiling with no features is about 33% faster. +On a 3800X, it takes about 60 seconds for no features and 90 seconds for all. +This difference is expected to increase as the bar develops. + +Features containing a `+` can be stacked, for example `config+json` and `config+yaml` could both be enabled. + +To build using only specific features, disable default features and pass a comma separated list to `cargo build`: + +```shell +cargo build --release --no-default-features \ + --features http,config+json,clock +``` + +> ⚠ Make sure you enable at least one `config` feature otherwise you will not be able to start the bar! + +| Feature | Description | +|---------------------|-----------------------------------------------------------------------------------| +| **Core** | | +| http | Enables HTTP features. Currently this includes the ability to load remote images. | +| config+all | Enables support for all configuration languages. | +| config+json | Enables configuration support for JSON. | +| config+yaml | Enables configuration support for YAML. | +| config+toml | Enables configuration support for TOML. | +| config+corn | Enables configuration support for [Corn](https://github.com/jakestanger.corn). | +| **Modules** | | +| clock | Enables the `clock` module. | +| music+all | Enables the `music` module with support for all player types. | +| music+mpris | Enables the `music` module with MPRIS support. | +| music+mpd | Enables the `music` module with MPD support | +| sys_info | Enables the `sys_info` module. | +| tray | Enables the `tray` module. | +| workspaces+all | Enables the `workspaces` module with support for all compositors. | +| workspaces+sway | Enables the `workspaces` module with support for Sway. | +| workspaces+hyprland | Enables the `workspaces` module with support for Hyprland. | + diff --git a/src/bar.rs b/src/bar.rs index 333f925..a5294f1 100644 --- a/src/bar.rs +++ b/src/bar.rs @@ -195,15 +195,20 @@ fn add_modules(content: >k::Box, modules: Vec, info: &ModuleInfo for (id, config) in modules.into_iter().enumerate() { match config { + #[cfg(feature = "clock")] ModuleConfig::Clock(mut module) => add_module!(module, id), - ModuleConfig::Script(mut module) => add_module!(module, id), - ModuleConfig::SysInfo(mut module) => add_module!(module, id), - ModuleConfig::Focused(mut module) => add_module!(module, id), - ModuleConfig::Workspaces(mut module) => add_module!(module, id), - ModuleConfig::Tray(mut module) => add_module!(module, id), - ModuleConfig::Music(mut module) => add_module!(module, id), - ModuleConfig::Launcher(mut module) => add_module!(module, id), ModuleConfig::Custom(mut module) => add_module!(module, id), + ModuleConfig::Focused(mut module) => add_module!(module, id), + ModuleConfig::Launcher(mut module) => add_module!(module, id), + #[cfg(feature = "music")] + ModuleConfig::Music(mut module) => add_module!(module, id), + ModuleConfig::Script(mut module) => add_module!(module, id), + #[cfg(feature = "sys_info")] + ModuleConfig::SysInfo(mut module) => add_module!(module, id), + #[cfg(feature = "tray")] + ModuleConfig::Tray(mut module) => add_module!(module, id), + #[cfg(feature = "workspaces")] + ModuleConfig::Workspaces(mut module) => add_module!(module, id), } } diff --git a/src/clients/compositor/mod.rs b/src/clients/compositor/mod.rs index 1764617..953f683 100644 --- a/src/clients/compositor/mod.rs +++ b/src/clients/compositor/mod.rs @@ -1,13 +1,18 @@ +use cfg_if::cfg_if; use color_eyre::{Help, Report, Result}; use std::fmt::{Display, Formatter}; use tokio::sync::broadcast; use tracing::debug; +#[cfg(feature = "workspaces+hyprland")] pub mod hyprland; +#[cfg(feature = "workspaces+sway")] pub mod sway; pub enum Compositor { + #[cfg(feature = "workspaces+sway")] Sway, + #[cfg(feature = "workspaces+hyprland")] Hyprland, Unsupported, } @@ -18,7 +23,9 @@ impl Display for Compositor { f, "{}", match self { + #[cfg(feature = "workspaces+sway")] Self::Sway => "Sway", + #[cfg(feature = "workspaces+hyprland")] Self::Hyprland => "Hyprland", Self::Unsupported => "Unsupported", } @@ -31,9 +38,15 @@ impl Compositor { /// This is done by checking system env vars. fn get_current() -> Self { if std::env::var("SWAYSOCK").is_ok() { - Self::Sway + cfg_if! { + if #[cfg(feature = "workspaces+sway")] { Self::Sway } + else { tracing::error!("Not compiled with Sway support"); Self::Unsupported } + } } else if std::env::var("HYPRLAND_INSTANCE_SIGNATURE").is_ok() { - Self::Hyprland + cfg_if! { + if #[cfg(feature = "workspaces+hyprland")] { Self::Hyprland} + else { tracing::error!("Not compiled with Hyprland support"); Self::Unsupported } + } } else { Self::Unsupported } @@ -44,7 +57,9 @@ impl Compositor { let current = Self::get_current(); debug!("Getting workspace client for: {current}"); match current { + #[cfg(feature = "workspaces+sway")] Self::Sway => Ok(sway::get_sub_client()), + #[cfg(feature = "workspaces+hyprland")] Self::Hyprland => Ok(hyprland::get_client()), Self::Unsupported => Err(Report::msg("Unsupported compositor") .note("Currently workspaces are only supported by Sway and Hyprland")), diff --git a/src/clients/mod.rs b/src/clients/mod.rs index 544ed60..31d72ba 100644 --- a/src/clients/mod.rs +++ b/src/clients/mod.rs @@ -1,4 +1,7 @@ +#[cfg(feature = "workspaces")] pub mod compositor; +#[cfg(feature = "music")] pub mod music; +#[cfg(feature = "tray")] pub mod system_tray; pub mod wayland; diff --git a/src/clients/music/mod.rs b/src/clients/music/mod.rs index 15d56cf..b71777b 100644 --- a/src/clients/music/mod.rs +++ b/src/clients/music/mod.rs @@ -4,7 +4,9 @@ use std::sync::Arc; use std::time::Duration; use tokio::sync::broadcast; +#[cfg(feature = "music+mpd")] pub mod mpd; +#[cfg(feature = "music+mpris")] pub mod mpris; #[derive(Clone, Debug)] diff --git a/src/config/impl.rs b/src/config/impl.rs index 5018865..cfd4045 100644 --- a/src/config/impl.rs +++ b/src/config/impl.rs @@ -133,11 +133,16 @@ impl Config { .unwrap_or_default(); match extension { + #[cfg(feature = "config+json")] "json" => serde_json::from_str(str).wrap_err("Invalid JSON config"), + #[cfg(feature = "config+toml")] "toml" => toml::from_str(str).wrap_err("Invalid TOML config"), + #[cfg(feature = "config+yaml")] "yaml" | "yml" => serde_yaml::from_str(str).wrap_err("Invalid YAML config"), + #[cfg(feature = "config+corn")] "corn" => libcorn::from_str(str).wrap_err("Invalid Corn config"), - _ => unreachable!(), + _ => Err(Report::msg(format!("Unsupported config type: {extension}")) + .note("You may need to recompile with support if available")), } } } diff --git a/src/config/mod.rs b/src/config/mod.rs index f29a2ff..fdec0fc 100644 --- a/src/config/mod.rs +++ b/src/config/mod.rs @@ -1,14 +1,19 @@ mod r#impl; mod truncate; +#[cfg(feature = "clock")] use crate::modules::clock::ClockModule; use crate::modules::custom::CustomModule; use crate::modules::focused::FocusedModule; use crate::modules::launcher::LauncherModule; +#[cfg(feature = "music")] use crate::modules::music::MusicModule; use crate::modules::script::ScriptModule; +#[cfg(feature = "sys_info")] use crate::modules::sysinfo::SysInfoModule; +#[cfg(feature = "tray")] use crate::modules::tray::TrayModule; +#[cfg(feature = "workspaces")] use crate::modules::workspaces::WorkspacesModule; use crate::script::ScriptInput; use serde::Deserialize; @@ -32,15 +37,20 @@ pub struct CommonConfig { #[derive(Debug, Deserialize, Clone)] #[serde(tag = "type", rename_all = "snake_case")] pub enum ModuleConfig { + #[cfg(feature = "clock")] Clock(ClockModule), - Music(MusicModule), - Tray(TrayModule), - Workspaces(WorkspacesModule), - SysInfo(SysInfoModule), - Launcher(LauncherModule), - Script(ScriptModule), - Focused(FocusedModule), Custom(CustomModule), + Focused(FocusedModule), + Launcher(LauncherModule), + #[cfg(feature = "music")] + Music(MusicModule), + Script(ScriptModule), + #[cfg(feature = "sys_info")] + SysInfo(SysInfoModule), + #[cfg(feature = "tray")] + Tray(TrayModule), + #[cfg(feature = "workspaces")] + Workspaces(WorkspacesModule), } #[derive(Debug, Clone)] diff --git a/src/image/gtk.rs b/src/image/gtk.rs index 49d3b6f..5155ef0 100644 --- a/src/image/gtk.rs +++ b/src/image/gtk.rs @@ -3,6 +3,7 @@ use gtk::prelude::*; use gtk::{Button, IconTheme, Image, Label, Orientation}; use tracing::error; +#[cfg(any(feature = "music", feature = "workspaces"))] pub fn new_icon_button(input: &str, icon_theme: &IconTheme, size: i32) -> Button { let button = Button::new(); @@ -27,6 +28,7 @@ pub fn new_icon_button(input: &str, icon_theme: &IconTheme, size: i32) -> Button button } +#[cfg(feature = "music")] pub fn new_icon_label(input: &str, icon_theme: &IconTheme, size: i32) -> gtk::Box { let container = gtk::Box::new(Orientation::Horizontal, 0); diff --git a/src/image/mod.rs b/src/image/mod.rs index 5e46764..c7e41aa 100644 --- a/src/image/mod.rs +++ b/src/image/mod.rs @@ -1,5 +1,7 @@ +#[cfg(any(feature = "music", feature = "workspaces"))] mod gtk; mod provider; +#[cfg(any(feature = "music", feature = "workspaces"))] pub use self::gtk::*; pub use provider::ImageProvider; diff --git a/src/image/provider.rs b/src/image/provider.rs index 5aa6c13..dac843e 100644 --- a/src/image/provider.rs +++ b/src/image/provider.rs @@ -1,22 +1,30 @@ use crate::desktop_file::get_desktop_icon_name; -use crate::send; -use color_eyre::{Report, Result}; -use glib::Bytes; +use cfg_if::cfg_if; +use color_eyre::{Help, Report, Result}; use gtk::gdk_pixbuf::Pixbuf; -use gtk::gio::{Cancellable, MemoryInputStream}; use gtk::prelude::*; use gtk::{IconLookupFlags, IconTheme}; -use reqwest::Url; use std::path::{Path, PathBuf}; -use tokio::spawn; -use tracing::error; + +cfg_if!( + if #[cfg(feature = "http")] { + use crate::send; + use gtk::gio::{Cancellable, MemoryInputStream}; + use tokio::spawn; + use tracing::error; + } +); #[derive(Debug)] enum ImageLocation<'a> { - Icon { name: String, theme: &'a IconTheme }, + Icon { + name: String, + theme: &'a IconTheme, + }, Local(PathBuf), Steam(String), - Remote(Url), + #[cfg(feature = "http")] + Remote(reqwest::Url), } pub struct ImageProvider<'a> { @@ -38,6 +46,7 @@ impl<'a> ImageProvider<'a> { /// Returns true if the input starts with a prefix /// that is supported by the parser /// (ie the parser would not fallback to checking the input). + #[cfg(any(feature = "music", feature = "workspaces"))] pub fn is_definitely_image_input(input: &str) -> bool { input.starts_with("icon:") || input.starts_with("file://") @@ -58,6 +67,7 @@ impl<'a> ImageProvider<'a> { Some(input_type) if input_type == "file" => Ok(ImageLocation::Local(PathBuf::from( input_name[2..].to_string(), ))), + #[cfg(feature = "http")] Some(input_type) if input_type == "http" || input_type == "https" => { Ok(ImageLocation::Remote(input.parse()?)) } @@ -73,7 +83,8 @@ impl<'a> ImageProvider<'a> { theme, }) } - Some(input_type) => Err(Report::msg(format!("Unsupported image type: {input_type}"))), + Some(input_type) => Err(Report::msg(format!("Unsupported image type: {input_type}")) + .note("You may need to recompile with support if available")), None if PathBuf::from(input_name).exists() => { Ok(ImageLocation::Local(PathBuf::from(input_name))) } @@ -88,6 +99,7 @@ impl<'a> ImageProvider<'a> { /// and load it into the provided `GTK::Image` widget. pub fn load_into_image(&self, image: gtk::Image) -> Result<()> { // handle remote locations async to avoid blocking UI thread while downloading + #[cfg(feature = "http")] if let ImageLocation::Remote(url) = &self.location { let url = url.clone(); let (tx, rx) = glib::MainContext::channel(glib::PRIORITY_DEFAULT); @@ -120,16 +132,26 @@ impl<'a> ImageProvider<'a> { }); } } else { - let pixbuf = match &self.location { - ImageLocation::Icon { name, theme } => self.get_from_icon(name, theme), - ImageLocation::Local(path) => self.get_from_file(path), - ImageLocation::Steam(steam_id) => self.get_from_steam_id(steam_id), - ImageLocation::Remote(_) => unreachable!(), // handled above - }?; - - image.set_pixbuf(Some(&pixbuf)); + self.load_into_image_sync(image)?; }; + #[cfg(not(feature = "http"))] + self.load_into_image_sync(image)?; + + Ok(()) + } + + fn load_into_image_sync(&self, image: gtk::Image) -> Result<()> { + let pixbuf = match &self.location { + ImageLocation::Icon { name, theme } => self.get_from_icon(name, theme), + ImageLocation::Local(path) => self.get_from_file(path), + ImageLocation::Steam(steam_id) => self.get_from_steam_id(steam_id), + #[cfg(feature = "http")] + _ => unreachable!(), // handled above + }?; + + image.set_pixbuf(Some(&pixbuf)); + Ok(()) } @@ -169,8 +191,9 @@ impl<'a> ImageProvider<'a> { } /// Attempts to get `Bytes` from an HTTP resource asynchronously. - async fn get_bytes_from_http(url: Url) -> Result { + #[cfg(feature = "http")] + async fn get_bytes_from_http(url: reqwest::Url) -> Result { let bytes = reqwest::get(url).await?.bytes().await?; - Ok(Bytes::from_owned(bytes)) + Ok(glib::Bytes::from_owned(bytes)) } } diff --git a/src/modules/launcher/mod.rs b/src/modules/launcher/mod.rs index f70a43e..cbb61b1 100644 --- a/src/modules/launcher/mod.rs +++ b/src/modules/launcher/mod.rs @@ -294,8 +294,6 @@ impl Module for LauncherModule { } } } - - Ok::<(), swayipc_async::Error>(()) }); Ok(()) diff --git a/src/modules/mod.rs b/src/modules/mod.rs index 9e70845..785890d 100644 --- a/src/modules/mod.rs +++ b/src/modules/mod.rs @@ -4,14 +4,19 @@ /// /// Clicking the widget opens a popup containing the current time /// with second-level precision and a calendar. +#[cfg(feature = "clock")] pub mod clock; pub mod custom; pub mod focused; pub mod launcher; +#[cfg(feature = "music")] pub mod music; pub mod script; +#[cfg(feature = "sys_info")] pub mod sysinfo; +#[cfg(feature = "tray")] pub mod tray; +#[cfg(feature = "workspaces")] pub mod workspaces; use crate::config::BarPosition;