├── .gitignore ├── .gitmodules ├── Cargo.toml ├── LICENSE ├── README.md └── src ├── config.rs ├── layout.rs ├── main.rs └── modules ├── battery.rs ├── date.rs ├── mod.rs ├── module.rs └── workspace.rs /.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | **/*.rs.bk 3 | run/* 4 | Cargo.lock 5 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "gtk-layer-shell-rs"] 2 | path = gtk-layer-shell-rs 3 | url = https://github.com/subgraph/gtk-layer-shell-rs.git 4 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "rustbar" 3 | version = "0.1.0" 4 | authors = ["zeroeightsix"] 5 | edition = "2018" 6 | 7 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 8 | 9 | [dependencies] 10 | gtk-layer-shell-rs = { path = "gtk-layer-shell-rs/" } 11 | tokio = { version = "0.2.21", features = ["macros", "rt-threaded", "time", "sync", "blocking"] } 12 | futures = "0.3.5" 13 | chrono = "0.4.11" 14 | serde = { version = "1.0.111", features = ["derive"] } 15 | serde_json = "1.0.55" 16 | json5 = "0.2.7" 17 | ksway = "0.1.0" 18 | battery = "0.7.5" 19 | 20 | [dependencies.gtk] 21 | git = "https://github.com/gtk-rs/gtk" 22 | 23 | [dependencies.gio] 24 | git = "https://github.com/gtk-rs/gio" 25 | 26 | [dependencies.glib] 27 | git = "https://github.com/gtk-rs/glib" 28 | 29 | [dependencies.gdk] 30 | git = "https://github.com/gtk-rs/gdk" 31 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 Ridan Vandenbergh 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # rustbar 2 | 3 | Abandoned, try this instead: 4 | 5 | https://github.com/JakeStanger/ironbar 6 | 7 | ## Compile & run 8 | 9 | Assuming you have `git`, `cargo` and [gtk-layer-shell](https://github.com/wmww/gtk-layer-shell) installed: 10 | ``` 11 | git clone https://github.com/zeroeightysix/rustbar 12 | cargo build --release 13 | target/build/rustbar 14 | ``` 15 | (`cargo run` in development) 16 | -------------------------------------------------------------------------------- /src/config.rs: -------------------------------------------------------------------------------- 1 | use serde::{ 2 | Deserialize, 3 | Serialize, 4 | }; 5 | 6 | use crate::layout::Group; 7 | 8 | #[derive(Deserialize, Serialize)] 9 | pub struct Config { 10 | #[serde(default)] 11 | pub anchors: Anchors, 12 | #[serde(default)] 13 | pub margins: Margins, 14 | #[serde(default)] 15 | pub layout: Group, 16 | } 17 | 18 | #[derive(Deserialize, Serialize)] 19 | pub struct Anchors { 20 | #[serde(default = "default_true")] 21 | pub top: bool, 22 | #[serde(default = "default_false")] 23 | pub bottom: bool, 24 | #[serde(default = "default_true")] 25 | pub left: bool, 26 | #[serde(default = "default_true")] 27 | pub right: bool, 28 | } 29 | 30 | impl Default for Anchors { 31 | fn default() -> Self { 32 | Anchors { 33 | top: true, 34 | bottom: false, 35 | left: true, 36 | right: true, 37 | } 38 | } 39 | } 40 | 41 | #[derive(Deserialize, Serialize)] 42 | pub struct Margins { 43 | #[serde(default = "default_zero")] 44 | pub top: i32, 45 | #[serde(default = "default_zero")] 46 | pub bottom: i32, 47 | #[serde(default = "default_zero")] 48 | pub left: i32, 49 | #[serde(default = "default_zero")] 50 | pub right: i32, 51 | } 52 | 53 | impl Default for Margins { 54 | fn default() -> Self { 55 | Margins { 56 | top: 0, 57 | bottom: 0, 58 | left: 0, 59 | right: 0, 60 | } 61 | } 62 | } 63 | 64 | // I don't get why serde doesn't support literal defaults yet. Am I missing something? 65 | fn default_true() -> bool { true } 66 | 67 | fn default_false() -> bool { false } 68 | 69 | fn default_zero() -> i32 { 0 } -------------------------------------------------------------------------------- /src/layout.rs: -------------------------------------------------------------------------------- 1 | use std::collections::HashMap; 2 | 3 | use gtk::{ 4 | Orientation, 5 | prelude::*, 6 | }; 7 | use serde::{Deserialize, Serialize}; 8 | use serde_json::Value; 9 | 10 | use crate::{ 11 | layout::Group::{Modules, Positions}, 12 | modules::{ 13 | date::DateModule, 14 | module::Module, 15 | workspace::WorkspaceModule, 16 | battery::BatteryModule 17 | }, 18 | }; 19 | 20 | macro_rules! add_module { 21 | ( 22 | $nm:expr, 23 | $cb:expr, 24 | $js:expr, 25 | $( 26 | $name:expr => $m:ident 27 | );* 28 | ) => { 29 | $( 30 | if $nm == $name { 31 | let w = $m::from_value($js).into_widget(); 32 | w.set_widget_name($name); 33 | $cb.add(&w); 34 | } 35 | )* 36 | } 37 | } 38 | 39 | #[derive(Deserialize, Serialize, Eq, PartialEq, Hash, Debug)] 40 | #[serde(rename_all = "lowercase")] 41 | pub enum Position { 42 | Left, 43 | Centre, 44 | Right, 45 | } 46 | 47 | #[derive(Deserialize, Serialize)] 48 | #[serde(untagged)] 49 | pub enum Group { 50 | Positions(Box>), 51 | // We never really keep the module struct around, so let's not try to do it here either 52 | Modules(Vec), 53 | } 54 | 55 | impl Default for Group { 56 | fn default() -> Self { 57 | Modules(Vec::default()) 58 | } 59 | } 60 | 61 | impl Group { 62 | pub fn initialise_handlers(&self, content: >k::Box) { 63 | match self { 64 | Modules(modules) => { 65 | for m in modules { 66 | if let Some(name) = m["name"].as_str() { 67 | // We use a macro here because the module is of varying type. 68 | add_module!(name, content, m, 69 | "date" => DateModule; 70 | "workspaces" => WorkspaceModule; 71 | "battery" => BatteryModule 72 | ); 73 | } 74 | }; 75 | } 76 | Positions(map) => { 77 | for (p, g) in map.iter() { 78 | let new = gtk::Box::new(Orientation::Horizontal, 0); 79 | match p { 80 | Position::Left => content.add(&new), 81 | Position::Centre => content.set_center_widget(Some(&new)), 82 | Position::Right => content.pack_end(&new, false, false, 0) 83 | }; 84 | g.initialise_handlers(&new) 85 | } 86 | } 87 | } 88 | } 89 | } -------------------------------------------------------------------------------- /src/main.rs: -------------------------------------------------------------------------------- 1 | #![feature(box_syntax)] 2 | extern crate gtk_layer_shell_rs as gtk_layer_shell; 3 | 4 | use std::{ 5 | env::args, 6 | vec::Vec, 7 | }; 8 | use std::path::Path; 9 | 10 | use futures::executor::block_on; 11 | use gio::prelude::*; 12 | use gtk::{ApplicationWindow, Orientation, prelude::*, WidgetExt, CssProvider, StyleContext}; 13 | use serde_json::json; 14 | use tokio::task::block_in_place; 15 | 16 | use crate::config::Config; 17 | 18 | mod modules; 19 | mod config; 20 | mod layout; 21 | 22 | #[tokio::main] 23 | async fn main() -> Result<(), std::io::Error> { 24 | let cfg_path = Path::new("config.json5"); 25 | let cfg = if !cfg_path.exists() { 26 | serde_json::from_value(json!({}))? 27 | } else { 28 | json5::from_str::(std::fs::read_to_string(cfg_path).unwrap().as_str()).unwrap() 29 | }; 30 | 31 | let application = gtk::Application::new(Some("me.zeroeightsix.rustbar"), Default::default()) 32 | .expect("Initialisation failed"); 33 | 34 | application.connect_activate(move |app| { 35 | block_on(activate(app, &cfg)); 36 | }); 37 | 38 | application.run(&args().collect::>()); 39 | 40 | Ok(()) 41 | } 42 | 43 | async fn activate(application: >k::Application, cfg: &Config) { 44 | let style_path = Path::new("style.css"); 45 | if style_path.exists() { 46 | let provider = CssProvider::new(); 47 | provider.load_from_file(&gio::File::new_for_path(style_path)).expect("Couldn't load custom style"); 48 | StyleContext::add_provider_for_screen(&gdk::Screen::get_default().expect("Couldn't get default GDK screen"), &provider, 800); 49 | println!("Applied custom style sheet!"); 50 | } 51 | 52 | let window = gtk::ApplicationWindow::new(application); 53 | window.connect_delete_event(|_, _| { 54 | gtk::main_quit(); 55 | Inhibit(false) 56 | }); 57 | 58 | init_layer_shell(&window, cfg); 59 | 60 | let content = gtk::Box::new(Orientation::Horizontal, 0); 61 | window.add(&content); 62 | 63 | cfg.layout.initialise_handlers(&content); 64 | 65 | block_in_place(|| { window.show_all() }); 66 | } 67 | 68 | /// Initialises the window as a top-level layer shell window. Layer-shell is the protocol 69 | /// used for things like docks, notification windows, bars(!), etc. 70 | fn init_layer_shell(window: &ApplicationWindow, cfg: &Config) { 71 | gtk_layer_shell::init_for_window(window); 72 | gtk_layer_shell::set_layer(window, gtk_layer_shell::Layer::Top); 73 | gtk_layer_shell::auto_exclusive_zone_enable(window); 74 | 75 | gtk_layer_shell::set_margin(window, gtk_layer_shell::Edge::Top, cfg.margins.top); 76 | gtk_layer_shell::set_margin(window, gtk_layer_shell::Edge::Bottom, cfg.margins.bottom); 77 | gtk_layer_shell::set_margin(window, gtk_layer_shell::Edge::Left, cfg.margins.left); 78 | gtk_layer_shell::set_margin(window, gtk_layer_shell::Edge::Right, cfg.margins.right); 79 | 80 | gtk_layer_shell::set_anchor(window, gtk_layer_shell::Edge::Top, cfg.anchors.top); 81 | gtk_layer_shell::set_anchor(window, gtk_layer_shell::Edge::Bottom, cfg.anchors.bottom); 82 | gtk_layer_shell::set_anchor(window, gtk_layer_shell::Edge::Left, cfg.anchors.left); 83 | gtk_layer_shell::set_anchor(window, gtk_layer_shell::Edge::Right, cfg.anchors.right); 84 | } 85 | -------------------------------------------------------------------------------- /src/modules/battery.rs: -------------------------------------------------------------------------------- 1 | use futures::executor::block_on; 2 | use glib::{ 3 | bitflags::_core::time::Duration, 4 | Continue, 5 | }; 6 | use gtk::{Label, LabelExt}; 7 | use serde::Deserialize; 8 | use tokio::{ 9 | task::spawn_blocking, 10 | time::delay_for, 11 | }; 12 | 13 | use crate::modules::module::Module; 14 | use crate::modules::battery::BatteryFormat::Percentage; 15 | 16 | #[derive(Deserialize)] 17 | pub struct BatteryModule { 18 | #[serde(default)] 19 | format: BatteryFormat 20 | } 21 | 22 | #[derive(Deserialize)] 23 | #[serde(rename_all = "lowercase", tag = "type", content = "precision")] 24 | enum BatteryFormat { 25 | Percentage(usize), 26 | Floating(usize) 27 | } 28 | 29 | impl Default for BatteryFormat { 30 | fn default() -> Self { 31 | Percentage(0) 32 | } 33 | } 34 | 35 | impl BatteryFormat { 36 | fn format(&self, input: f32) -> String { 37 | match self { 38 | BatteryFormat::Percentage(p) => { 39 | format!("{:.*}%", p, input * 100.) 40 | }, 41 | BatteryFormat::Floating(p) => { 42 | format!("{:.*}", p, input) 43 | } 44 | } 45 | } 46 | } 47 | 48 | impl Module