diff --git a/Cargo.lock b/Cargo.lock index 57b6e88..794b5ce 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -213,6 +213,12 @@ dependencies = [ "rustc-demangle", ] +[[package]] +name = "base64" +version = "0.21.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a4a4ddaa51a5bc52a6948f74c06d20aaaddb71924eab79b8c97a8c556e942d6a" + [[package]] name = "bitflags" version = "1.3.2" @@ -366,6 +372,16 @@ dependencies = [ "crossbeam-utils", ] +[[package]] +name = "core-foundation" +version = "0.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "194a7a9e6de53fa55116934067c844d9d749312f75c6f6d0980e8c252f8c2146" +dependencies = [ + "core-foundation-sys", + "libc", +] + [[package]] name = "core-foundation-sys" version = "0.8.3" @@ -603,6 +619,15 @@ version = "1.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "90e5c1c8368803113bf0c9584fc495a58b86dc8a29edbf8fe877d21d9507e797" +[[package]] +name = "encoding_rs" +version = "0.8.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9852635589dc9f9ea1b6fe9f05b50ef208c85c834a562f0c6abb1c475736ec2b" +dependencies = [ + "cfg-if", +] + [[package]] name = "enum-kinds" version = "0.5.1" @@ -688,6 +713,30 @@ version = "1.0.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" +[[package]] +name = "foreign-types" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f6f339eb8adc052cd2ca78910fda869aefa38d22d5cb648e6485e4d3fc06f3b1" +dependencies = [ + "foreign-types-shared", +] + +[[package]] +name = "foreign-types-shared" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "00b0228411908ca8685dba7fc2cdd70ec9990a6e753e89b6ac91a84c40fbaf4b" + +[[package]] +name = "form_urlencoded" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a9c384f161156f5260c24a097c56119f9be8c798586aecc13afbcbe7b7e26bf8" +dependencies = [ + "percent-encoding", +] + [[package]] name = "from_variants" version = "1.0.0" @@ -1054,6 +1103,25 @@ dependencies = [ "syn 1.0.105", ] +[[package]] +name = "h2" +version = "0.3.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5f9f29bc9dda355256b2916cf526ab02ce0aeaaaf2bad60d65ef3f12f11dd0f4" +dependencies = [ + "bytes", + "fnv", + "futures-core", + "futures-sink", + "futures-util", + "http", + "indexmap", + "slab", + "tokio", + "tokio-util", + "tracing", +] + [[package]] name = "hashbrown" version = "0.12.3" @@ -1090,6 +1158,77 @@ version = "0.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" +[[package]] +name = "http" +version = "0.2.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "75f43d41e26995c17e71ee126451dd3941010b0514a81a9d11f3b341debc2399" +dependencies = [ + "bytes", + "fnv", + "itoa", +] + +[[package]] +name = "http-body" +version = "0.4.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d5f38f16d184e36f2408a55281cd658ecbd3ca05cce6d6510a176eca393e26d1" +dependencies = [ + "bytes", + "http", + "pin-project-lite", +] + +[[package]] +name = "httparse" +version = "1.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d897f394bad6a705d5f4104762e116a75639e470d80901eed05a860a95cb1904" + +[[package]] +name = "httpdate" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c4a1e36c821dbe04574f602848a19f742f4fb3c98d40449f11bcad18d6b17421" + +[[package]] +name = "hyper" +version = "0.14.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "034711faac9d2166cb1baf1a2fb0b60b1f277f8492fd72176c17f3515e1abd3c" +dependencies = [ + "bytes", + "futures-channel", + "futures-core", + "futures-util", + "h2", + "http", + "http-body", + "httparse", + "httpdate", + "itoa", + "pin-project-lite", + "socket2", + "tokio", + "tower-service", + "tracing", + "want", +] + +[[package]] +name = "hyper-tls" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d6183ddfa99b85da61a140bea0efc93fdf56ceaa041b37d553518030827f9905" +dependencies = [ + "bytes", + "hyper", + "native-tls", + "tokio", + "tokio-native-tls", +] + [[package]] name = "hyprland" version = "0.3.0" @@ -1141,6 +1280,16 @@ version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39" +[[package]] +name = "idna" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e14ddfc70884202db2244c223200c204c2bda1bc6e0998d11b5e024d657209e6" +dependencies = [ + "unicode-bidi", + "unicode-normalization", +] + [[package]] name = "indenter" version = "0.3.3" @@ -1186,6 +1335,12 @@ dependencies = [ "cfg-if", ] +[[package]] +name = "ipnet" +version = "2.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "30e22bd8629359895450b59ea7a776c850561b96a3b1d31321c1949d9e6c9146" + [[package]] name = "ironbar" version = "0.9.0" @@ -1206,6 +1361,7 @@ dependencies = [ "mpris", "notify", "regex", + "reqwest", "serde", "serde_json", "serde_yaml", @@ -1374,6 +1530,12 @@ dependencies = [ "autocfg", ] +[[package]] +name = "mime" +version = "0.3.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2a60c7ce501c71e03a9c9c0d35b861413ae925bd979cc7a4e30d060069aaac8d" + [[package]] name = "minimal-lexical" version = "0.2.1" @@ -1439,6 +1601,24 @@ dependencies = [ "thiserror", ] +[[package]] +name = "native-tls" +version = "0.2.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "07226173c32f2926027b63cce4bcd8076c3552846cbe7925f3aaffeac0a3b92e" +dependencies = [ + "lazy_static", + "libc", + "log", + "openssl", + "openssl-probe", + "openssl-sys", + "schannel", + "security-framework", + "security-framework-sys", + "tempfile", +] + [[package]] name = "nix" version = "0.23.2" @@ -1575,6 +1755,51 @@ version = "1.16.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "86f0b0d4bf799edbc74508c1e8bf170ff5f41238e5f8225603ca7caaae2b7860" +[[package]] +name = "openssl" +version = "0.10.45" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b102428fd03bc5edf97f62620f7298614c45cedf287c271e7ed450bbaf83f2e1" +dependencies = [ + "bitflags", + "cfg-if", + "foreign-types", + "libc", + "once_cell", + "openssl-macros", + "openssl-sys", +] + +[[package]] +name = "openssl-macros" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b501e44f11665960c7e7fcf062c7d96a14ade4aa98116c004b2e37b5be7d736c" +dependencies = [ + "proc-macro2", + "quote 1.0.21", + "syn 1.0.105", +] + +[[package]] +name = "openssl-probe" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ff011a302c396a5197692431fc1948019154afc178baf7d8e37367442a4601cf" + +[[package]] +name = "openssl-sys" +version = "0.9.80" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "23bbbf7854cd45b83958ebe919f0e8e516793727652e27fda10a8384cfc790b7" +dependencies = [ + "autocfg", + "cc", + "libc", + "pkg-config", + "vcpkg", +] + [[package]] name = "ordered-stream" version = "0.0.1" @@ -1658,6 +1883,12 @@ version = "1.0.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d01a5bd0424d00070b0098dd17ebca6f961a959dead1dbcbbbc1d1cd8d3deeba" +[[package]] +name = "percent-encoding" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "478c572c3d73181ff3c2539045f6eb99e5491218eae919370993b890cdbdd98e" + [[package]] name = "pest" version = "2.5.1" @@ -1906,6 +2137,43 @@ dependencies = [ "winapi", ] +[[package]] +name = "reqwest" +version = "0.11.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "21eed90ec8570952d53b772ecf8f206aa1ec9a3d76b2521c56c42973f2d91ee9" +dependencies = [ + "base64", + "bytes", + "encoding_rs", + "futures-core", + "futures-util", + "h2", + "http", + "http-body", + "hyper", + "hyper-tls", + "ipnet", + "js-sys", + "log", + "mime", + "native-tls", + "once_cell", + "percent-encoding", + "pin-project-lite", + "serde", + "serde_json", + "serde_urlencoded", + "tokio", + "tokio-native-tls", + "tower-service", + "url", + "wasm-bindgen", + "wasm-bindgen-futures", + "web-sys", + "winreg", +] + [[package]] name = "rustc-demangle" version = "0.1.21" @@ -1942,6 +2210,15 @@ dependencies = [ "winapi-util", ] +[[package]] +name = "schannel" +version = "0.1.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "713cfb06c7059f3588fb8044c0fad1d09e3c01d225e25b9220dbfdcf16dbb1b3" +dependencies = [ + "windows-sys", +] + [[package]] name = "scopeguard" version = "1.1.0" @@ -1954,6 +2231,29 @@ version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9c8132065adcfd6e02db789d9285a0deb2f3fcb04002865ab67d5fb103533898" +[[package]] +name = "security-framework" +version = "2.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7c4437699b6d34972de58652c68b98cb5b53a4199ab126db8e20ec8ded29a721" +dependencies = [ + "bitflags", + "core-foundation", + "core-foundation-sys", + "libc", + "security-framework-sys", +] + +[[package]] +name = "security-framework-sys" +version = "2.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "31c9bb296072e961fcbd8853511dd39c2d8be2deb1e17c6860b1d30732b323b4" +dependencies = [ + "core-foundation-sys", + "libc", +] + [[package]] name = "semver" version = "0.11.0" @@ -2023,6 +2323,18 @@ dependencies = [ "serde", ] +[[package]] +name = "serde_urlencoded" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3491c14715ca2294c4d6a88f15e84739788c1d030eed8c110436aafdaa2f3fd" +dependencies = [ + "form_urlencoded", + "itoa", + "ryu", + "serde", +] + [[package]] name = "serde_yaml" version = "0.9.14" @@ -2365,6 +2677,21 @@ dependencies = [ "time-core", ] +[[package]] +name = "tinyvec" +version = "1.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "87cc5ceb3875bb20c2890005a4e226a4651264a5c75edb2421b52861a0a0cb50" +dependencies = [ + "tinyvec_macros", +] + +[[package]] +name = "tinyvec_macros" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cda74da7e1a664f795bb1f8a87ec406fb89a02522cf6e50620d016add6dbbf5c" + [[package]] name = "tokio" version = "1.23.0" @@ -2396,6 +2723,16 @@ dependencies = [ "syn 1.0.105", ] +[[package]] +name = "tokio-native-tls" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f7d995660bd2b7f8c1568414c1126076c13fbb725c40112dc0120b78eb9b717b" +dependencies = [ + "native-tls", + "tokio", +] + [[package]] name = "tokio-stream" version = "0.1.11" @@ -2407,6 +2744,20 @@ dependencies = [ "tokio", ] +[[package]] +name = "tokio-util" +version = "0.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0bb2e075f03b3d66d8d8785356224ba688d2906a371015e225beeb65ca92c740" +dependencies = [ + "bytes", + "futures-core", + "futures-sink", + "pin-project-lite", + "tokio", + "tracing", +] + [[package]] name = "toml" version = "0.5.9" @@ -2450,6 +2801,12 @@ dependencies = [ "toml_datetime", ] +[[package]] +name = "tower-service" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6bc1c9ce2b5135ac7f93c72918fc37feb872bdc6a5533a8b85eb4b86bfdae52" + [[package]] name = "tracing" version = "0.1.37" @@ -2533,6 +2890,12 @@ dependencies = [ "tracing-log", ] +[[package]] +name = "try-lock" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3528ecfd12c466c6f163363caf2d02a71161dd5e1cc6ae7b34207ea2d42d81ed" + [[package]] name = "typenum" version = "1.16.0" @@ -2555,12 +2918,27 @@ dependencies = [ "winapi", ] +[[package]] +name = "unicode-bidi" +version = "0.3.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d54675592c1dbefd78cbd98db9bacd89886e1ca50692a0692baefffdeb92dd58" + [[package]] name = "unicode-ident" version = "1.0.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6ceab39d59e4c9499d4e5a8ee0e2735b891bb7308ac83dfb4e80cad195c9f6f3" +[[package]] +name = "unicode-normalization" +version = "0.1.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c5713f0fc4b5db668a2ac63cdb7bb4469d8c9fed047b1d0292cc7b0ce2ba921" +dependencies = [ + "tinyvec", +] + [[package]] name = "unicode-segmentation" version = "1.10.0" @@ -2585,6 +2963,17 @@ version = "0.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c1e5fa573d8ac5f1a856f8d7be41d390ee973daf97c806b2c1a465e4e1406e68" +[[package]] +name = "url" +version = "2.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0d68c799ae75762b8c3fe375feb6600ef5602c883c5d21eb51c09f22b83c4643" +dependencies = [ + "form_urlencoded", + "idna", + "percent-encoding", +] + [[package]] name = "utf8parse" version = "0.2.0" @@ -2597,6 +2986,12 @@ version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "830b7e5d4d90034032940e4ace0d9a9a057e7a45cd94e6c007832e39edb82f6d" +[[package]] +name = "vcpkg" +version = "0.2.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426" + [[package]] name = "vec_map" version = "0.8.2" @@ -2653,6 +3048,16 @@ dependencies = [ "winapi-util", ] +[[package]] +name = "want" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1ce8a968cb1cd110d136ff8b819a556d6fb6d919363c61534f6860c7eb172ba0" +dependencies = [ + "log", + "try-lock", +] + [[package]] name = "wasi" version = "0.10.0+wasi-snapshot-preview1" @@ -2690,6 +3095,18 @@ dependencies = [ "wasm-bindgen-shared", ] +[[package]] +name = "wasm-bindgen-futures" +version = "0.4.33" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "23639446165ca5a5de86ae1d8896b737ae80319560fbaa4c2887b7da6e7ebd7d" +dependencies = [ + "cfg-if", + "js-sys", + "wasm-bindgen", + "web-sys", +] + [[package]] name = "wasm-bindgen-macro" version = "0.2.83" @@ -2789,6 +3206,16 @@ dependencies = [ "pkg-config", ] +[[package]] +name = "web-sys" +version = "0.3.60" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bcda906d8be16e728fd5adc5b729afad4e444e106ab28cd1c7256e54fa61510f" +dependencies = [ + "js-sys", + "wasm-bindgen", +] + [[package]] name = "wepoll-ffi" version = "0.1.2" @@ -2886,6 +3313,15 @@ version = "0.42.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f40009d85759725a34da6d89a94e63d7bdc50a862acf0dbc7c8e488f1edcb6f5" +[[package]] +name = "winreg" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "80d0f4e272c85def139476380b12f9ac60926689dd2e01d4923222f40580869d" +dependencies = [ + "winapi", +] + [[package]] name = "xcursor" version = "0.3.4" diff --git a/Cargo.toml b/Cargo.toml index 167aaaf..6d8d25a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -26,6 +26,7 @@ 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.2" } dirs = "4.0.0" diff --git a/docs/Configuration guide.md b/docs/Configuration guide.md index 3ff70b0..d63bb67 100644 --- a/docs/Configuration guide.md +++ b/docs/Configuration guide.md @@ -272,6 +272,7 @@ The following table lists each of the top-level bar config options: | `position` | `top` or `bottom` or `left` or `right` | `bottom` | The bar's position on screen. | | `anchor_to_edges` | `boolean` | `false` | Whether to anchor the bar to the edges of the screen. Setting to false centres the bar. | | `height` | `integer` | `42` | The bar's height in pixels. | +| `icon_theme` | `string` | `null` | Name of the GTK icon theme to use. Leave blank to use default. | | `start` | `Module[]` | `[]` | Array of left or top modules. | | `center` | `Module[]` | `[]` | Array of center modules. | | `end` | `Module[]` | `[]` | Array of right or bottom modules. | diff --git a/docs/Images.md b/docs/Images.md new file mode 100644 index 0000000..9522d6f --- /dev/null +++ b/docs/Images.md @@ -0,0 +1,15 @@ +Ironbar is capable of loading images from multiple sources. +In any situation where an option takes text or an icon, +you can use a string in any of the following formats, and it will automatically be detected as an image: + +| Source | Example | +|-------------------------------|---------------------------------| +| GTK icon theme | `icon:firefox` | +| Local file | `file:///path/to/file.jpg` | +| Remote file (over HTTP/HTTPS) | `https://example.com/image.jpg` | + +Remote images are loaded asynchronously to avoid blocking the UI thread. +Be aware this can cause elements to change size upon load if the image is large enough. + +Note that mixing text and images is not supported. +Your best option here is to use Nerd Font icons instead. \ No newline at end of file diff --git a/docs/_Sidebar.md b/docs/_Sidebar.md index 32641df..a493976 100644 --- a/docs/_Sidebar.md +++ b/docs/_Sidebar.md @@ -2,6 +2,7 @@ - [Configuration guide](configuration-guide) - [Scripts](scripts) + - [Images](images) - [Styling guide](styling-guide) # Examples diff --git a/docs/modules/Custom.md b/docs/modules/Custom.md index 9b1b28d..1b91d82 100644 --- a/docs/modules/Custom.md +++ b/docs/modules/Custom.md @@ -18,15 +18,17 @@ It is well worth looking at the examples. ### `Widget` -| Name | Type | Default | Description | -|---------------|------------------------------|--------------|---------------------------------------------------------------------------| -| `widget_type` | `box` or `label` or `button` | `null` | Type of GTK widget to create. | -| `name` | `string` | `null` | Widget name. | -| `class` | `string` | `null` | Widget class name. | -| `label` | `string` | `null` | [`label` and `button`] Widget text label. Pango markup supported. | -| `on_click` | `string` | `null` | [`button`] Command to execute. More on this [below](#commands). | -| `orientation` | `horizontal` or `vertical` | `horizontal` | [`box`] Whether child widgets should be horizontally or vertically added. | -| `widgets` | `Widget[]` | `[]` | [`box`] List of widgets to add to this box. | +| Name | Type | Default | Description | +|---------------|-----------------------------------------|--------------|---------------------------------------------------------------------------| +| `widget_type` | `box` or `label` or `button` or `image` | `null` | Type of GTK widget to create. | +| `name` | `string` | `null` | Widget name. | +| `class` | `string` | `null` | Widget class name. | +| `label` | `string` | `null` | [`label` and `button`] Widget text label. Pango markup supported. | +| `on_click` | `string` | `null` | [`button`] Command to execute. More on this [below](#commands). | +| `src` | `image` | `null` | [`image`] Image source. See [here](images) for information on images. | +| `size` | `integer` | `null` | [`image`] Width/height of the image. Aspect ratio is preserved. | +| `orientation` | `horizontal` or `vertical` | `horizontal` | [`box`] Whether child widgets should be horizontally or vertically added. | +| `widgets` | `Widget[]` | `[]` | [`box`] List of widgets to add to this box. | ### Labels @@ -56,6 +58,8 @@ The following bar commands are supported: - `popup:open` - `popup:close` +--- + XML is arguably better-suited and easier to read for this sort of markup, but currently is not supported. Nonetheless, it may be worth comparing the examples to the below equivalent diff --git a/docs/modules/Focused.md b/docs/modules/Focused.md index d581c77..1397121 100644 --- a/docs/modules/Focused.md +++ b/docs/modules/Focused.md @@ -12,7 +12,6 @@ Displays the title and/or icon of the currently focused window. | `show_icon` | `boolean` | `true` | Whether to show the app's icon | | `show_title` | `boolean` | `true` | Whether to show the app's title | | `icon_size` | `integer` | `32` | Size of icon in pixels | -| `icon_theme` | `string` | `null` | GTK icon theme to use | | `truncate` or `truncate.mode` | `start` or `middle` or `end` | `null` | The location of the ellipses and where to truncate text from. Leave null to avoid truncating. Use the long-hand version if specifying a length. | | `truncate.length` | `integer` | `null` | The maximum number of characters before truncating. Leave blank to let GTK automatically handle. | diff --git a/docs/modules/Launcher.md b/docs/modules/Launcher.md index 3ca9936..58784f0 100644 --- a/docs/modules/Launcher.md +++ b/docs/modules/Launcher.md @@ -14,7 +14,6 @@ Optionally displays a launchable set of favourites. | `favorites` | `string[]` | `[]` | List of app IDs (or classes) to always show at the start of the launcher | | `show_names` | `boolean` | `false` | Whether to show app names on the button label. Names will still show on tooltips when set to false. | | `show_icons` | `boolean` | `true` | Whether to show app icons on the button. | -| `icon_theme` | `string` | `null` | GTK icon theme to use. |
JSON diff --git a/docs/modules/Music.md b/docs/modules/Music.md index f9bfa7d..71fe7c7 100644 --- a/docs/modules/Music.md +++ b/docs/modules/Music.md @@ -17,11 +17,18 @@ in MPRIS mode, the widget will listen to all players and automatically detect/di | `format` | `string` | `{icon} {title} / {artist}` | Format string for the widget. More info below. | | `truncate` or `truncate.mode` | `start` or `middle` or `end` | `null` | The location of the ellipses and where to truncate text from. Leave null to avoid truncating. Use the long-hand version if specifying a length. | | `truncate.length` | `integer` | `null` | The maximum number of characters before truncating. Leave blank to let GTK automatically handle. | -| `icons.play` | `string` | `` | Icon to show when playing. | -| `icons.pause` | `string` | `` | Icon to show when paused. | -| `icons.volume` | `string` | `墳` | Icon to show under popup volume slider. | -| `host` | `string` | `localhost:6600` | [MPD Only] TCP or Unix socket for the MPD server. | -| `music_dir` | `string` | `$HOME/Music` | [MPD Only] Path to MPD server's music directory on disc. Required for album art. | +| `icons.play` | `string/image` | `` | Icon to show when playing. | +| `icons.pause` | `string/image` | `` | Icon to show when paused. | +| `icons.prev` | `string/image` | `玲` | Icon to show on previous button. | +| `icons.next` | `string/image` | `怜` | Icon to show on next button. | +| `icons.volume` | `string/image` | `墳` | Icon to show under popup volume slider. | +| `icons.track` | `string/image` | `` | Icon to show next to track title. | +| `icons.album` | `string/image` | `` | Icon to show next to album name. | +| `icons.artist` | `string/image` | `ﴁ` | Icon to show next to artist name. | +| `host` | `string/image` | `localhost:6600` | [MPD Only] TCP or Unix socket for the MPD server. | +| `music_dir` | `string/image` | `$HOME/Music` | [MPD Only] Path to MPD server's music directory on disc. Required for album art. | + +See [here](images) for information on images.
JSON @@ -122,24 +129,25 @@ and will be replaced with values from the currently playing track: ## Styling -| Selector | Description | -|------------------------------------------|------------------------------------------| -| `#music` | Tray widget button | -| `#popup-music` | Popup box | -| `#popup-music #album-art` | Album art image inside popup box | -| `#popup-music #title` | Track title container inside popup box | -| `#popup-music #title .icon` | Track title icon label inside popup box | -| `#popup-music #title .label` | Track title label inside popup box | -| `#popup-music #album` | Track album container inside popup box | -| `#popup-music #album .icon` | Track album icon label inside popup box | -| `#popup-music #album .label` | Track album label inside popup box | -| `#popup-music #artist` | Track artist container inside popup box | -| `#popup-music #artist .icon` | Track artist icon label inside popup box | -| `#popup-music #artist .label` | Track artist label inside popup box | -| `#popup-music #controls` | Controls container inside popup box | -| `#popup-music #controls #btn-prev` | Previous button inside popup box | -| `#popup-music #controls #btn-play-pause` | Play/pause button inside popup box | -| `#popup-music #controls #btn-next` | Next button inside popup box | -| `#popup-music #volume` | Volume container inside popup box | -| `#popup-music #volume #slider` | Volume slider popup box | -| `#popup-music #volume .icon` | Volume icon label inside popup box | \ No newline at end of file +| Selector | Description | +|-------------------------------------|------------------------------------------| +| `#music` | Tray widget button | +| `#popup-music` | Popup box | +| `#popup-music #album-art` | Album art image inside popup box | +| `#popup-music #title` | Track title container inside popup box | +| `#popup-music #title .icon` | Track title icon label inside popup box | +| `#popup-music #title .label` | Track title label inside popup box | +| `#popup-music #album` | Track album container inside popup box | +| `#popup-music #album .icon` | Track album icon label inside popup box | +| `#popup-music #album .label` | Track album label inside popup box | +| `#popup-music #artist` | Track artist container inside popup box | +| `#popup-music #artist .icon` | Track artist icon label inside popup box | +| `#popup-music #artist .label` | Track artist label inside popup box | +| `#popup-music #controls` | Controls container inside popup box | +| `#popup-music #controls #btn-prev` | Previous button inside popup box | +| `#popup-music #controls #btn-play` | Play button inside popup box | +| `#popup-music #controls #btn-pause` | Pause button inside popup box | +| `#popup-music #controls #btn-next` | Next button inside popup box | +| `#popup-music #volume` | Volume container inside popup box | +| `#popup-music #volume #slider` | Volume slider popup box | +| `#popup-music #volume .icon` | Volume icon label inside popup box | \ No newline at end of file diff --git a/docs/modules/Workspaces.md b/docs/modules/Workspaces.md index b58295f..0caac3a 100644 --- a/docs/modules/Workspaces.md +++ b/docs/modules/Workspaces.md @@ -8,11 +8,11 @@ Shows all current workspaces. Clicking a workspace changes focus to it. > Type: `workspaces` -| Name | Type | Default | Description | -|----------------|---------------------------|----------------|----------------------------------------------------------------------------------------------------------------------| -| `name_map` | `Map` | `{}` | A map of actual workspace names to their display labels. Workspaces use their actual name if not present in the map. | -| `all_monitors` | `boolean` | `false` | Whether to display workspaces from all monitors. When `false`, only shows workspaces on the current monitor. | -| `sort` | `added` or `alphanumeric` | `alphanumeric` | The method used for sorting workspaces. `added` always appends to the end, `alphanumeric` sorts by number/name. | +| Name | Type | Default | Description | +|----------------|-----------------------------|----------------|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------| +| `name_map` | `Map` | `{}` | A map of actual workspace names to their display labels/images. Workspaces use their actual name if not present in the map. See [here](images) for information on images. | +| `all_monitors` | `boolean` | `false` | Whether to display workspaces from all monitors. When `false`, only shows workspaces on the current monitor. | +| `sort` | `added` or `alphanumeric` | `alphanumeric` | The method used for sorting workspaces. `added` always appends to the end, `alphanumeric` sorts by number/name. |
JSON @@ -72,15 +72,15 @@ end: ```corn { - end = [ - { - type = "workspaces", - name_map.1 = "" - name_map.2 = "" - name_map.3 = "" - all_monitors = false - } - ] + end = [ + { + type = "workspaces", + name_map.1 = "" + name_map.2 = "" + name_map.3 = "" + all_monitors = false + } + ] } ``` diff --git a/flake.lock b/flake.lock index 32692a5..0ef735a 100644 --- a/flake.lock +++ b/flake.lock @@ -17,11 +17,11 @@ }, "nixpkgs": { "locked": { - "lastModified": 1672350804, - "narHash": "sha256-jo6zkiCabUBn3ObuKXHGqqORUMH27gYDIFFfLq5P4wg=", + "lastModified": 1674641431, + "narHash": "sha256-qfo19qVZBP4qn5M5gXc/h1MDgAtPA5VxJm9s8RUAkVk=", "owner": "nixos", "repo": "nixpkgs", - "rev": "677ed08a50931e38382dbef01cba08a8f7eac8f6", + "rev": "9b97ad7b4330aacda9b2343396eb3df8a853b4fc", "type": "github" }, "original": { @@ -45,11 +45,11 @@ ] }, "locked": { - "lastModified": 1672453260, - "narHash": "sha256-ruR2xo30Vn7kY2hAgg2Z2xrCvNePxck6mgR5a8u+zow=", + "lastModified": 1674959389, + "narHash": "sha256-N0kc+CE9Vy7Mj8Z/cLHx59gL+ZnkmylYH0BQEWz3TZU=", "owner": "oxalica", "repo": "rust-overlay", - "rev": "176b6fd3dd3d7cea8d22ab1131364a050228d94c", + "rev": "edd082ca16aa055d5504bea39da36b3ee68e4f1d", "type": "github" }, "original": { diff --git a/flake.nix b/flake.nix index 5a6f565..4e129d4 100644 --- a/flake.nix +++ b/flake.nix @@ -50,7 +50,7 @@ cargoDeps = rustPlatform.importCargoLock {lockFile = ./Cargo.lock;}; cargoLock.lockFile = ./Cargo.lock; nativeBuildInputs = with prev; [pkg-config]; - buildInputs = with prev; [gtk3 gdk-pixbuf gtk-layer-shell libxkbcommon]; + buildInputs = with prev; [gtk3 gdk-pixbuf gtk-layer-shell libxkbcommon openssl]; }; }; packages = genSystems ( @@ -74,6 +74,7 @@ gtk3 gtk-layer-shell pkg-config + openssl ]; RUST_SRC_PATH = "${rust}/lib/rustlib/src/rust/library"; diff --git a/src/bar.rs b/src/bar.rs index c5db9a6..333f925 100644 --- a/src/bar.rs +++ b/src/bar.rs @@ -8,7 +8,7 @@ use crate::{await_sync, read_lock, send, write_lock, Config}; use color_eyre::Result; use gtk::gdk::{EventMask, Monitor, ScrollDirection}; use gtk::prelude::*; -use gtk::{Application, ApplicationWindow, EventBox, Orientation, Widget}; +use gtk::{Application, ApplicationWindow, EventBox, IconTheme, Orientation, Widget}; use std::sync::{Arc, RwLock}; use tokio::spawn; use tokio::sync::mpsc; @@ -140,6 +140,11 @@ fn load_modules( monitor: &Monitor, output_name: &str, ) -> Result<()> { + let icon_theme = IconTheme::new(); + if let Some(ref theme) = config.icon_theme { + icon_theme.set_custom_theme(Some(theme)); + } + macro_rules! info { ($location:expr) => { ModuleInfo { @@ -148,6 +153,7 @@ fn load_modules( monitor, output_name, location: $location, + icon_theme: &icon_theme, } }; } diff --git a/src/clients/music/mod.rs b/src/clients/music/mod.rs index 554dd7c..15d56cf 100644 --- a/src/clients/music/mod.rs +++ b/src/clients/music/mod.rs @@ -22,7 +22,7 @@ pub struct Track { pub disc: Option, pub genre: Option, pub track: Option, - pub cover_path: Option, + pub cover_path: Option, } #[derive(Clone, Debug)] diff --git a/src/clients/music/mpd.rs b/src/clients/music/mpd.rs index 3dfd48d..6079b12 100644 --- a/src/clients/music/mpd.rs +++ b/src/clients/music/mpd.rs @@ -121,12 +121,16 @@ impl MpdClient { fn convert_song(song: &Song, music_dir: &Path) -> Track { let (track, disc) = song.number(); - let cover_path = music_dir.join( - song.file_path() - .parent() - .expect("Song path should not be root") - .join("cover.jpg"), - ); + let cover_path = music_dir + .join( + song.file_path() + .parent() + .expect("Song path should not be root") + .join("cover.jpg"), + ) + .into_os_string() + .into_string() + .ok(); Track { title: song.title().map(std::string::ToString::to_string), @@ -136,7 +140,7 @@ impl MpdClient { genre: try_get_first_tag(song, &Tag::Genre).map(std::string::ToString::to_string), disc: Some(disc), track: Some(track), - cover_path: Some(cover_path), + cover_path, } } } diff --git a/src/clients/music/mpris.rs b/src/clients/music/mpris.rs index 3d418cf..6f56d21 100644 --- a/src/clients/music/mpris.rs +++ b/src/clients/music/mpris.rs @@ -5,7 +5,6 @@ use color_eyre::Result; use lazy_static::lazy_static; use mpris::{DBusError, Event, Metadata, PlaybackStatus, Player, PlayerFinder}; use std::collections::HashSet; -use std::path::PathBuf; use std::sync::{Arc, Mutex}; use std::thread::sleep; use std::time::Duration; @@ -260,10 +259,7 @@ impl From for Track { .and_then(mpris::MetadataValue::as_str_array) .and_then(|arr| arr.first().map(|val| (*val).to_string())), track: value.track_number().map(|track| track as u64), - cover_path: value - .art_url() - .map(|path| path.replace("file://", "")) - .map(PathBuf::from), + cover_path: value.art_url().map(|s| s.to_string()), } } } diff --git a/src/config/mod.rs b/src/config/mod.rs index 51f1d21..f29a2ff 100644 --- a/src/config/mod.rs +++ b/src/config/mod.rs @@ -73,6 +73,9 @@ pub struct Config { #[serde(default = "default_bar_height")] pub height: i32, + /// GTK icon theme to use. + pub icon_theme: Option, + pub start: Option>, pub center: Option>, pub end: Option>, diff --git a/src/icon.rs b/src/desktop_file.rs similarity index 51% rename from src/icon.rs rename to src/desktop_file.rs index b2c6fd2..a2c9758 100644 --- a/src/icon.rs +++ b/src/desktop_file.rs @@ -1,6 +1,3 @@ -use gtk::gdk_pixbuf::Pixbuf; -use gtk::prelude::*; -use gtk::{IconLookupFlags, IconTheme}; use std::collections::HashMap; use std::fs::File; use std::io; @@ -67,7 +64,7 @@ fn parse_desktop_file(path: PathBuf) -> io::Result> { } /// Attempts to get the icon name from the app's `.desktop` file. -fn get_desktop_icon_name(app_id: &str) -> Option { +pub fn get_desktop_icon_name(app_id: &str) -> Option { find_desktop_file(app_id).and_then(|file| { let map = parse_desktop_file(file); map.map_or(None, |map| { @@ -75,65 +72,3 @@ fn get_desktop_icon_name(app_id: &str) -> Option { }) }) } - -enum IconLocation { - Theme(String), - File(PathBuf), -} - -/// Attempts to get the location of an icon. -/// -/// Handles icons that are part of a GTK theme, icons specified as path -/// and icons for steam games. -fn get_icon_location(theme: &IconTheme, app_id: &str, size: i32) -> Option { - let has_icon = theme - .lookup_icon(app_id, size, IconLookupFlags::empty()) - .is_some(); - - if has_icon { - return Some(IconLocation::Theme(app_id.to_string())); - } - - let is_steam_game = app_id.starts_with("steam_app_"); - if is_steam_game { - let steam_id: String = app_id.chars().skip("steam_app_".len()).collect(); - - return match dirs::data_dir() { - Some(dir) => { - let path = dir.join(format!( - "icons/hicolor/32x32/apps/steam_icon_{steam_id}.png" - )); - - return Some(IconLocation::File(path)); - } - None => None, - }; - } - - let icon_name = get_desktop_icon_name(app_id); - if let Some(icon_name) = icon_name { - let is_path = PathBuf::from(&icon_name).exists(); - - return if is_path { - Some(IconLocation::File(PathBuf::from(icon_name))) - } else { - return Some(IconLocation::Theme(icon_name)); - }; - } - - None -} - -/// Gets the icon associated with an app. -pub fn get_icon(theme: &IconTheme, app_id: &str, size: i32) -> Option { - let icon_location = get_icon_location(theme, app_id, size); - - match icon_location { - Some(IconLocation::Theme(icon_name)) => { - let icon = theme.load_icon(&icon_name, size, IconLookupFlags::FORCE_SIZE); - icon.map_or(None, |icon| icon) - } - Some(IconLocation::File(path)) => Pixbuf::from_file_at_scale(path, size, size, true).ok(), - None => None, - } -} diff --git a/src/image/gtk.rs b/src/image/gtk.rs new file mode 100644 index 0000000..49d3b6f --- /dev/null +++ b/src/image/gtk.rs @@ -0,0 +1,48 @@ +use super::ImageProvider; +use gtk::prelude::*; +use gtk::{Button, IconTheme, Image, Label, Orientation}; +use tracing::error; + +pub fn new_icon_button(input: &str, icon_theme: &IconTheme, size: i32) -> Button { + let button = Button::new(); + + if ImageProvider::is_definitely_image_input(input) { + let image = Image::new(); + match ImageProvider::parse(input, icon_theme, size) + .and_then(|provider| provider.load_into_image(image.clone())) + { + Ok(_) => { + button.set_image(Some(&image)); + button.set_always_show_image(true); + } + Err(err) => { + error!("{err:?}"); + button.set_label(input); + } + } + } else { + button.set_label(input); + } + + button +} + +pub fn new_icon_label(input: &str, icon_theme: &IconTheme, size: i32) -> gtk::Box { + let container = gtk::Box::new(Orientation::Horizontal, 0); + + if ImageProvider::is_definitely_image_input(input) { + let image = Image::new(); + container.add(&image); + + if let Err(err) = ImageProvider::parse(input, icon_theme, size) + .and_then(|provider| provider.load_into_image(image)) + { + error!("{err:?}"); + } + } else { + let label = Label::new(Some(input)); + container.add(&label); + } + + container +} diff --git a/src/image/mod.rs b/src/image/mod.rs new file mode 100644 index 0000000..5e46764 --- /dev/null +++ b/src/image/mod.rs @@ -0,0 +1,5 @@ +mod gtk; +mod provider; + +pub use self::gtk::*; +pub use provider::ImageProvider; diff --git a/src/image/provider.rs b/src/image/provider.rs new file mode 100644 index 0000000..5aa6c13 --- /dev/null +++ b/src/image/provider.rs @@ -0,0 +1,176 @@ +use crate::desktop_file::get_desktop_icon_name; +use crate::send; +use color_eyre::{Report, Result}; +use glib::Bytes; +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; + +#[derive(Debug)] +enum ImageLocation<'a> { + Icon { name: String, theme: &'a IconTheme }, + Local(PathBuf), + Steam(String), + Remote(Url), +} + +pub struct ImageProvider<'a> { + location: ImageLocation<'a>, + size: i32, +} + +impl<'a> ImageProvider<'a> { + /// Attempts to parse the image input to find its location. + /// Errors if no valid location type can be found. + /// + /// Note this checks that icons exist in theme, or files exist on disk + /// but no other check is performed. + pub fn parse(input: &str, theme: &'a IconTheme, size: i32) -> Result { + let location = Self::get_location(input, theme, size)?; + Ok(Self { location, size }) + } + + /// 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). + pub fn is_definitely_image_input(input: &str) -> bool { + input.starts_with("icon:") + || input.starts_with("file://") + || input.starts_with("http://") + || input.starts_with("https://") + } + + fn get_location(input: &str, theme: &'a IconTheme, size: i32) -> Result> { + let (input_type, input_name) = input + .split_once(':') + .map_or((None, input), |(t, n)| (Some(t), n)); + + match input_type { + Some(input_type) if input_type == "icon" => Ok(ImageLocation::Icon { + name: input_name.to_string(), + theme, + }), + Some(input_type) if input_type == "file" => Ok(ImageLocation::Local(PathBuf::from( + input_name[2..].to_string(), + ))), + Some(input_type) if input_type == "http" || input_type == "https" => { + Ok(ImageLocation::Remote(input.parse()?)) + } + None if input.starts_with("steam_app_") => Ok(ImageLocation::Steam( + input_name.chars().skip("steam_app_".len()).collect(), + )), + None if theme + .lookup_icon(input, size, IconLookupFlags::empty()) + .is_some() => + { + Ok(ImageLocation::Icon { + name: input_name.to_string(), + theme, + }) + } + Some(input_type) => Err(Report::msg(format!("Unsupported image type: {input_type}"))), + None if PathBuf::from(input_name).exists() => { + Ok(ImageLocation::Local(PathBuf::from(input_name))) + } + None => get_desktop_icon_name(input_name).map_or_else( + || Err(Report::msg("Unknown image type")), + |input| Self::get_location(&input, theme, size), + ), + } + } + + /// Attempts to fetch the image from the location + /// 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 + if let ImageLocation::Remote(url) = &self.location { + let url = url.clone(); + let (tx, rx) = glib::MainContext::channel(glib::PRIORITY_DEFAULT); + + spawn(async move { + let bytes = Self::get_bytes_from_http(url).await; + if let Ok(bytes) = bytes { + send!(tx, bytes); + } + }); + + { + let size = self.size; + rx.attach(None, move |bytes| { + let stream = MemoryInputStream::from_bytes(&bytes); + let pixbuf = Pixbuf::from_stream_at_scale( + &stream, + size, + size, + true, + Some(&Cancellable::new()), + ); + + match pixbuf { + Ok(pixbuf) => image.set_pixbuf(Some(&pixbuf)), + Err(err) => error!("{err:?}"), + } + + Continue(false) + }); + } + } 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)); + }; + + Ok(()) + } + + /// Attempts to get a `Pixbuf` from the GTK icon theme. + fn get_from_icon(&self, name: &str, theme: &IconTheme) -> Result { + let pixbuf = match theme.lookup_icon(name, self.size, IconLookupFlags::empty()) { + Some(_) => theme.load_icon(name, self.size, IconLookupFlags::FORCE_SIZE), + None => Ok(None), + }?; + + pixbuf.map_or_else( + || Err(Report::msg("Icon theme does not contain icon '{name}'")), + Ok, + ) + } + + /// Attempts to get a `Pixbuf` from a local file. + fn get_from_file(&self, path: &Path) -> Result { + let pixbuf = Pixbuf::from_file_at_scale(path, self.size, self.size, true)?; + Ok(pixbuf) + } + + /// Attempts to get a `Pixbuf` from a local file, + /// using the Steam game ID to look it up. + fn get_from_steam_id(&self, steam_id: &str) -> Result { + // TODO: Can we load this from icon theme with app id `steam_icon_{}`? + let path = dirs::data_dir().map_or_else( + || Err(Report::msg("Missing XDG data dir")), + |dir| { + Ok(dir.join(format!( + "icons/hicolor/32x32/apps/steam_icon_{steam_id}.png" + ))) + }, + )?; + + self.get_from_file(&path) + } + + /// Attempts to get `Bytes` from an HTTP resource asynchronously. + async fn get_bytes_from_http(url: Url) -> Result { + let bytes = reqwest::get(url).await?.bytes().await?; + Ok(Bytes::from_owned(bytes)) + } +} diff --git a/src/main.rs b/src/main.rs index c560ea1..dff8254 100644 --- a/src/main.rs +++ b/src/main.rs @@ -2,9 +2,10 @@ mod bar; mod bridge_channel; mod clients; mod config; +mod desktop_file; mod dynamic_string; mod error; -mod icon; +mod image; mod logging; mod macros; mod modules; diff --git a/src/modules/clock.rs b/src/modules/clock.rs index 2156ef9..7dac728 100644 --- a/src/modules/clock.rs +++ b/src/modules/clock.rs @@ -82,7 +82,7 @@ impl Module