├── .gitignore ├── Cargo.lock ├── Cargo.toml ├── LICENSE-MIT ├── README.md ├── default ├── aliases ├── example.conf └── input_BARNAME.zsh ├── files ├── bspwm │ └── focus ├── bswpm_subscribe.zsh ├── eDP-1.conf ├── images │ └── screenshot01.png ├── input_DP-5.zsh ├── input_HDMI-0.zsh ├── input_eDP-1.zsh └── launch_bar.sh └── src ├── bar.rs ├── config.rs ├── input.rs ├── lib.rs ├── main.rs └── optional ├── kill_me.rs └── mod.rs /.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | tags 3 | files/bspwm 4 | -------------------------------------------------------------------------------- /Cargo.lock: -------------------------------------------------------------------------------- 1 | # This file is automatically @generated by Cargo. 2 | # It is not intended for manual editing. 3 | version = 3 4 | 5 | [[package]] 6 | name = "ansi_term" 7 | version = "0.11.0" 8 | source = "registry+https://github.com/rust-lang/crates.io-index" 9 | checksum = "ee49baf6cb617b853aa8d93bf420db2383fab46d314482ca2803b40d5fde979b" 10 | dependencies = [ 11 | "winapi", 12 | ] 13 | 14 | [[package]] 15 | name = "anyhow" 16 | version = "1.0.42" 17 | source = "registry+https://github.com/rust-lang/crates.io-index" 18 | checksum = "595d3cfa7a60d4555cb5067b99f07142a08ea778de5cf993f7b75c7d8fabc486" 19 | 20 | [[package]] 21 | name = "atty" 22 | version = "0.2.14" 23 | source = "registry+https://github.com/rust-lang/crates.io-index" 24 | checksum = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8" 25 | dependencies = [ 26 | "hermit-abi", 27 | "libc", 28 | "winapi", 29 | ] 30 | 31 | [[package]] 32 | name = "bitflags" 33 | version = "1.2.1" 34 | source = "registry+https://github.com/rust-lang/crates.io-index" 35 | checksum = "cf1de2fe8c75bc145a2f577add951f8134889b4795d47466a54a5c846d691693" 36 | 37 | [[package]] 38 | name = "cfg-if" 39 | version = "1.0.0" 40 | source = "registry+https://github.com/rust-lang/crates.io-index" 41 | checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" 42 | 43 | [[package]] 44 | name = "clap" 45 | version = "2.33.3" 46 | source = "registry+https://github.com/rust-lang/crates.io-index" 47 | checksum = "37e58ac78573c40708d45522f0d80fa2f01cc4f9b4e2bf749807255454312002" 48 | dependencies = [ 49 | "ansi_term", 50 | "atty", 51 | "bitflags", 52 | "strsim", 53 | "textwrap", 54 | "unicode-width", 55 | "vec_map", 56 | ] 57 | 58 | [[package]] 59 | name = "dirs" 60 | version = "3.0.2" 61 | source = "registry+https://github.com/rust-lang/crates.io-index" 62 | checksum = "30baa043103c9d0c2a57cf537cc2f35623889dc0d405e6c3cccfadbc81c71309" 63 | dependencies = [ 64 | "dirs-sys", 65 | ] 66 | 67 | [[package]] 68 | name = "dirs-sys" 69 | version = "0.3.6" 70 | source = "registry+https://github.com/rust-lang/crates.io-index" 71 | checksum = "03d86534ed367a67548dc68113a0f5db55432fdfbb6e6f9d77704397d95d5780" 72 | dependencies = [ 73 | "libc", 74 | "redox_users", 75 | "winapi", 76 | ] 77 | 78 | [[package]] 79 | name = "getrandom" 80 | version = "0.2.3" 81 | source = "registry+https://github.com/rust-lang/crates.io-index" 82 | checksum = "7fcd999463524c52659517fe2cea98493cfe485d10565e7b0fb07dbba7ad2753" 83 | dependencies = [ 84 | "cfg-if", 85 | "libc", 86 | "wasi", 87 | ] 88 | 89 | [[package]] 90 | name = "hermit-abi" 91 | version = "0.1.19" 92 | source = "registry+https://github.com/rust-lang/crates.io-index" 93 | checksum = "62b467343b94ba476dcb2500d242dadbb39557df889310ac77c5d99100aaac33" 94 | dependencies = [ 95 | "libc", 96 | ] 97 | 98 | [[package]] 99 | name = "lazy_static" 100 | version = "1.4.0" 101 | source = "registry+https://github.com/rust-lang/crates.io-index" 102 | checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" 103 | 104 | [[package]] 105 | name = "libc" 106 | version = "0.2.98" 107 | source = "registry+https://github.com/rust-lang/crates.io-index" 108 | checksum = "320cfe77175da3a483efed4bc0adc1968ca050b098ce4f2f1c13a56626128790" 109 | 110 | [[package]] 111 | name = "maybe-uninit" 112 | version = "2.0.0" 113 | source = "registry+https://github.com/rust-lang/crates.io-index" 114 | checksum = "60302e4db3a61da70c0cb7991976248362f30319e88850c487b9b95bbf059e00" 115 | 116 | [[package]] 117 | name = "pkg-config" 118 | version = "0.3.19" 119 | source = "registry+https://github.com/rust-lang/crates.io-index" 120 | checksum = "3831453b3449ceb48b6d9c7ad7c96d5ea673e9b470a1dc578c2ce6521230884c" 121 | 122 | [[package]] 123 | name = "proc-macro2" 124 | version = "1.0.28" 125 | source = "registry+https://github.com/rust-lang/crates.io-index" 126 | checksum = "5c7ed8b8c7b886ea3ed7dde405212185f423ab44682667c8c6dd14aa1d9f6612" 127 | dependencies = [ 128 | "unicode-xid", 129 | ] 130 | 131 | [[package]] 132 | name = "quote" 133 | version = "1.0.9" 134 | source = "registry+https://github.com/rust-lang/crates.io-index" 135 | checksum = "c3d0b9745dc2debf507c8422de05d7226cc1f0644216dfdfead988f9b1ab32a7" 136 | dependencies = [ 137 | "proc-macro2", 138 | ] 139 | 140 | [[package]] 141 | name = "redox_syscall" 142 | version = "0.2.10" 143 | source = "registry+https://github.com/rust-lang/crates.io-index" 144 | checksum = "8383f39639269cde97d255a32bdb68c047337295414940c68bdd30c2e13203ff" 145 | dependencies = [ 146 | "bitflags", 147 | ] 148 | 149 | [[package]] 150 | name = "redox_users" 151 | version = "0.4.0" 152 | source = "registry+https://github.com/rust-lang/crates.io-index" 153 | checksum = "528532f3d801c87aec9def2add9ca802fe569e44a544afe633765267840abe64" 154 | dependencies = [ 155 | "getrandom", 156 | "redox_syscall", 157 | ] 158 | 159 | [[package]] 160 | name = "signal-hook" 161 | version = "0.1.17" 162 | source = "registry+https://github.com/rust-lang/crates.io-index" 163 | checksum = "7e31d442c16f047a671b5a71e2161d6e68814012b7f5379d269ebd915fac2729" 164 | dependencies = [ 165 | "libc", 166 | "signal-hook-registry", 167 | ] 168 | 169 | [[package]] 170 | name = "signal-hook-registry" 171 | version = "1.4.0" 172 | source = "registry+https://github.com/rust-lang/crates.io-index" 173 | checksum = "e51e73328dc4ac0c7ccbda3a494dfa03df1de2f46018127f60c693f2648455b0" 174 | dependencies = [ 175 | "libc", 176 | ] 177 | 178 | [[package]] 179 | name = "strsim" 180 | version = "0.8.0" 181 | source = "registry+https://github.com/rust-lang/crates.io-index" 182 | checksum = "8ea5119cdb4c55b55d432abb513a0429384878c15dde60cc77b1c99de1a95a6a" 183 | 184 | [[package]] 185 | name = "syn" 186 | version = "1.0.74" 187 | source = "registry+https://github.com/rust-lang/crates.io-index" 188 | checksum = "1873d832550d4588c3dbc20f01361ab00bfe741048f71e3fecf145a7cc18b29c" 189 | dependencies = [ 190 | "proc-macro2", 191 | "quote", 192 | "unicode-xid", 193 | ] 194 | 195 | [[package]] 196 | name = "textwrap" 197 | version = "0.11.0" 198 | source = "registry+https://github.com/rust-lang/crates.io-index" 199 | checksum = "d326610f408c7a4eb6f51c37c330e496b08506c9457c9d34287ecc38809fb060" 200 | dependencies = [ 201 | "unicode-width", 202 | ] 203 | 204 | [[package]] 205 | name = "thiserror" 206 | version = "1.0.26" 207 | source = "registry+https://github.com/rust-lang/crates.io-index" 208 | checksum = "93119e4feac1cbe6c798c34d3a53ea0026b0b1de6a120deef895137c0529bfe2" 209 | dependencies = [ 210 | "thiserror-impl", 211 | ] 212 | 213 | [[package]] 214 | name = "thiserror-impl" 215 | version = "1.0.26" 216 | source = "registry+https://github.com/rust-lang/crates.io-index" 217 | checksum = "060d69a0afe7796bf42e9e2ff91f5ee691fb15c53d38b4b62a9a53eb23164745" 218 | dependencies = [ 219 | "proc-macro2", 220 | "quote", 221 | "syn", 222 | ] 223 | 224 | [[package]] 225 | name = "unibar" 226 | version = "0.1.3" 227 | dependencies = [ 228 | "anyhow", 229 | "clap", 230 | "dirs", 231 | "libc", 232 | "signal-hook", 233 | "thiserror", 234 | "x11-dl", 235 | ] 236 | 237 | [[package]] 238 | name = "unicode-width" 239 | version = "0.1.8" 240 | source = "registry+https://github.com/rust-lang/crates.io-index" 241 | checksum = "9337591893a19b88d8d87f2cec1e73fad5cdfd10e5a6f349f498ad6ea2ffb1e3" 242 | 243 | [[package]] 244 | name = "unicode-xid" 245 | version = "0.2.2" 246 | source = "registry+https://github.com/rust-lang/crates.io-index" 247 | checksum = "8ccb82d61f80a663efe1f787a51b16b5a51e3314d6ac365b08639f52387b33f3" 248 | 249 | [[package]] 250 | name = "vec_map" 251 | version = "0.8.2" 252 | source = "registry+https://github.com/rust-lang/crates.io-index" 253 | checksum = "f1bddf1187be692e79c5ffeab891132dfb0f236ed36a43c7ed39f1165ee20191" 254 | 255 | [[package]] 256 | name = "wasi" 257 | version = "0.10.2+wasi-snapshot-preview1" 258 | source = "registry+https://github.com/rust-lang/crates.io-index" 259 | checksum = "fd6fbd9a79829dd1ad0cc20627bf1ed606756a7f77edff7b66b7064f9cb327c6" 260 | 261 | [[package]] 262 | name = "winapi" 263 | version = "0.3.9" 264 | source = "registry+https://github.com/rust-lang/crates.io-index" 265 | checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" 266 | dependencies = [ 267 | "winapi-i686-pc-windows-gnu", 268 | "winapi-x86_64-pc-windows-gnu", 269 | ] 270 | 271 | [[package]] 272 | name = "winapi-i686-pc-windows-gnu" 273 | version = "0.4.0" 274 | source = "registry+https://github.com/rust-lang/crates.io-index" 275 | checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" 276 | 277 | [[package]] 278 | name = "winapi-x86_64-pc-windows-gnu" 279 | version = "0.4.0" 280 | source = "registry+https://github.com/rust-lang/crates.io-index" 281 | checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" 282 | 283 | [[package]] 284 | name = "x11-dl" 285 | version = "2.18.5" 286 | source = "registry+https://github.com/rust-lang/crates.io-index" 287 | checksum = "2bf981e3a5b3301209754218f962052d4d9ee97e478f4d26d4a6eced34c1fef8" 288 | dependencies = [ 289 | "lazy_static", 290 | "libc", 291 | "maybe-uninit", 292 | "pkg-config", 293 | ] 294 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "unibar" 3 | version = "0.1.3" 4 | authors = ["Curtis Jones "] 5 | description = "Simple minimal status bar, written with Xlib in rust." 6 | readme = "README.md" 7 | homepage = "https://github.com/ikubetoomuzik/unibar.git" 8 | repository = "https://github.com/ikubetoomuzik/unibar.git" 9 | license = "MIT" 10 | edition = "2018" 11 | exclude = [ 12 | "files/*", 13 | ] 14 | 15 | [dependencies] 16 | x11-dl = "2.18.5" 17 | libc = "0.2.74" 18 | dirs = "3.0.1" 19 | clap = "2.33.3" 20 | signal-hook = "0.1.16" 21 | anyhow = "1.0.42" 22 | thiserror = "1.0.26" 23 | -------------------------------------------------------------------------------- /LICENSE-MIT: -------------------------------------------------------------------------------- 1 | Copyright 2020 Curtis Jones 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 4 | 5 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 6 | 7 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 8 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Unibar 2 | 3 | Simple Xorg display bar written for speed and ease of use. 4 | 5 | ![Crates.io](https://img.shields.io/crates/v/unibar?color=%238be9fd) 6 | ![Crates.io](https://img.shields.io/crates/d/unibar?color=8be9fd) 7 | ![GitHub release (latest by date)](https://img.shields.io/github/v/release/ikubetoomuzik/unibar?color=%23ff79c6) 8 | ![GitHub commits since latest release (by date)](https://img.shields.io/github/commits-since/ikubetoomuzik/unibar/latest?color=ff79c6) 9 | 10 | ## Issues in progress: 11 | * [ ] Add additional comentary to the bar.rs file for the Xlib stuff. 12 | * [ ] Potentially start a new library on top of the Xlib C bindings to make this easier for others. 13 | * [x] Add error handling and some basic error messages to the application. 14 | 15 | ## CLI Interface 16 | 17 | ### Required Arg 18 | * **[[NAME]]** ---> *Used to find config file, also used to create unique WMNAME.* 19 | 20 | ### Flags 21 | * **-H, --help** ---> *Display help info* 22 | * **-V, --version** ---> *Display version info* 23 | * **-C, --noconfig** ---> _Do not try to load a conifg file, only use cli options._ 24 | 25 | ### Options 26 | * **-c, --config ** ---> *Specify custom config file to use.* 27 | 28 | * **-p, --position ** ---> *Choose bar position, options are* __TOP__ *or* __BOTTOM__*.* 29 | * **-m, --monitor ** ---> *Monitor to use: can either be the Xrandr monitor name, or a number. If value is a number it is used to index the Xinerama displays. Valid index starts at 0.* 30 | 31 | * **-h, --height ** ---> *Choose bar height in pixels.* 32 | * **-u, --underline ** ---> *Choose underline highlight height in pixels.* 33 | 34 | * **-b, --background ** ---> *Choose default bg colour in '#XXXXXX' hex format.* 35 | * **-y, --fonty ** ---> *Choose font offset from top of bar in pixels.* 36 | * **-f, --fonts ...** ---> *Comma seperated list of FcConfig font name strings. Ex. 'FontName:size=XX:antialias=true/false'* 37 | 38 | * **-F, --ftcolours ...** ---> *Comma seperated list of font colours in '#XXXXXX' hex format.* 39 | * **-B, --bgcolours ...** ---> *Comma seperated list of background highlight colours in '#XXXXXX' hex format.* 40 | * **-U, --ulcolours ...** ---> *Comma seperated list of underline highlight colours in '#XXXXXX' hex format.* 41 | 42 | ## Configuration 43 | The bar looks for the config file at: 44 | * **$XDGCONFIGDIR**/unibar/**[[NAME]]**.conf 45 | **OR** 46 | * **~/.config**/unibar/**[[NAME]]**.conf 47 | 48 | ## Defaults 49 | Any configuration options set with command line arguements override options set in the config file. 50 | The default config file provided lays out the default options for configuration and how to override them. 51 | 52 | ## Usage 53 | The bar is only used to display text provided to it on *stdin*. 54 | Input is read and displayed everytime a new line is output. 55 | So make sure to use *echo -n* or some other print method that does not end in newline until you are ready to refresh the bar. 56 | Text written to the bar can also include formatting blocks with the following format. 57 | 58 | ### Formatting 59 | All formatting blocks are enclosed in *curly braces* **{}**. 60 | All closing blocks are enclosed in *curly braces* and start with the *slash* **{/}**. 61 | 62 | * {*f*__i__} {/*f*} => all characters within the blocks will be printed with the *font face* at index **i**. 63 | * {*F*__i__} {/*F*} => all characters within the blocks will be printed with the *font colour* at index **i**. 64 | * {*B*__i__} {/*B*} => all characters within the blocks will have a background highlight behind them with the *btcolour* at index **i**. 65 | * {*H*__i__} {/*H*} => all characters within the blocks will have an underline highlight behind them with the *htcolour* at index **i**. 66 | 67 | ### Splitting Input 68 | There is only one special block the is not in curly braces. 69 | The *splitting block* is **<|>** and seperates between the left, right, and center displays. 70 | 71 | * *0 splitting blocks* => the whole string will be considered **left-adjusted**. 72 | * *1 splitting block* => the part of the string before the block will be **left-adjusted** and everything else will be **right-adjusted**. 73 | * *2 or more splitting blocks* => the part of the string before the first block will be **left-adjusted** the part between the first and second will be **center-adjusted** and everything between the second and third will be **right-adjusted**. Any other *splitting blocks* and their strings will be ignored. 74 | 75 | ## Example 76 | The bar running on my system by default, set up using the scripts in the files repo. 77 | 78 | ![Screenshot](https://github.com/ikubetoomuzik/unibar/blob/master/files/images/screenshot01.png) 79 | 80 | ## Installation 81 | The project has been uploaded to crates.io and can be downloaded with: 82 | ```sh 83 | cargo install unibar 84 | ``` 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | ## Author 94 | 95 | By: **Curtis Jones** <*mail@curtisjones.ca*> 96 | -------------------------------------------------------------------------------- /default/aliases: -------------------------------------------------------------------------------- 1 | # Helpful Aliases 2 | # Created: September 15, 2020 3 | alias kl='touch ~/.config/unibar/stop' 4 | alias lb='files/launch_bar.sh DP-5 & files/launch_bar.sh HDMI-0 &' 5 | 6 | -------------------------------------------------------------------------------- /default/example.conf: -------------------------------------------------------------------------------- 1 | # Default Unibar conf file. 2 | # Made on September 20, 2020. 3 | 4 | # Monitor is by default empty and if left empty will use the width of whole XDisplay. 5 | # monitor = 6 | 7 | # Position defaults to top, but if you want you can set to bottom. 8 | # position = top 9 | 10 | # Height default. 11 | # height = 32 12 | 13 | # Underline height default. 14 | # underline_height = 4 15 | 16 | # Font y-offset default. 17 | # The offset is measured from top of bar to bottom of font in pixels. 18 | # font_y = 20 19 | 20 | # Background of the whole bar is set once at the beginning. 21 | # Can't be changed during runtime. 22 | # default_background = #000000 23 | 24 | # Default font is set below. 25 | # There can be multiple fonts set in config. 26 | # The first font set that is not default will remove default and at yours at index 0. 27 | # After all other fonts listed are added to list (up to 10). 28 | # font = mono:size=12 29 | 30 | 31 | # Default font, background & underline highlight colours are set below. 32 | # There can be multiple colours set in config. 33 | # The first colour set that is not default will remove default and at yours at index 0. 34 | # After all other colours listed are added to list (up to 10). 35 | # ft_colour = #FFFFFF 36 | # background_colour = #0000FF 37 | # highlight_colour = #FF0000 38 | -------------------------------------------------------------------------------- /default/input_BARNAME.zsh: -------------------------------------------------------------------------------- 1 | #! /bin/zsh 2 | 3 | DEFAULT_DIR=$(dirname $(realpath $0)); 4 | SPACING=" "; 5 | 6 | # start the bspwm watching script here. 7 | if ! pgrep -x bspwm_report_fo > /dev/null; then 8 | $DEFAULT_DIR/bspwm/bspwm_report_format.sh & 9 | fi 10 | if ! pgrep -x bspwm_node_focu > /dev/null; then 11 | $DEFAULT_DIR/bspwm/bspwm_node_focus_format.sh & 12 | fi 13 | 14 | # If the exit file already exists then clear it. 15 | STOP_FILE="$HOME/.config/unibar/stop" 16 | if [ -f "$STOP_FILE" ]; then 17 | rm "$STOP_FILE"; 18 | fi 19 | 20 | # Sleep to start because the bar needs time to start up. 21 | sleep 1; 22 | 23 | # Set the count to 0 to start. 24 | count=0; 25 | 26 | while [ 1 -gt 0 ]; do 27 | # Skip defaults to false. 28 | skip=1 29 | 30 | ###################################### 31 | # Get current focused window. 32 | new_focus_win=$(< "$DEFAULT_DIR/bspwm/focus"); 33 | if [ "$focus_win" != "$new_focus_win" ]; then 34 | focus_win=$new_focus_win; 35 | skip=0; 36 | fi 37 | new_focus_win_name=$(xdotool getwindowname "$focus_win"); 38 | if [ "$focus_win_name" != "$new_focus_win_name" ]; then 39 | focus_win_name=$new_focus_win_name; 40 | focus_win_display="{f1}{/f}{F1} $focus_win_name{/F}" 41 | skip=0; 42 | fi 43 | 44 | ###################################### 45 | 46 | if ! [ -z "$1" ]; then 47 | cd $DEFAULT_DIR/bspwm; 48 | # Get desktops 49 | new_dktps=$(< "$DEFAULT_DIR/bspwm/$1"); 50 | if [ "$dktps" != "$new_dktps" ]; then 51 | # If they are different than we set the new value. 52 | dktps=$new_dktps; 53 | # Also need to inform the later loop to reprint string. 54 | skip=0; 55 | fi 56 | # Go back; 57 | cd -; 58 | fi 59 | 60 | # only every second. 61 | ########################################################## 62 | if [ $(($count % 10)) -eq 0 ]; then 63 | ######################################################## 64 | # Time bit; 65 | # Grab the current time. 66 | new_date=$(date +%H:%M); 67 | # Do the comparison. 68 | if [ "$date" != "$new_date" ]; then 69 | # If it is different than we set the new value. 70 | date=$new_date; 71 | date_display="{F1} $date{/F}"; 72 | # Also need to inform the later loop to reprint string. 73 | skip=0; 74 | fi 75 | ######################################################## 76 | ######################################################## 77 | # Network bit. 78 | new_ip=$(ip a | grep -e ".*inet .*eno1$" | tr "/" " " | awk '{ printf "%s",$2 }'); 79 | if [ "$ip" != "$new_ip" ]; then 80 | ip=$new_ip; 81 | if [ "$ip" = "" ]; then 82 | ip_display="{B0}{F1}{/F} disconnected.{/B}" 83 | else 84 | ip_display="{F1} $ip{/F} "; 85 | fi 86 | skip=0; 87 | fi 88 | ######################################################## 89 | fi 90 | ########################################################## 91 | 92 | ###################################### 93 | # Volume bit. 94 | # Get current volume. 95 | new_vol=$(pamixer --get-volume); 96 | new_mute=$(pamixer --get-mute); 97 | if [ "$vol" != "$new_vol" ] || [ "$mute" != "$new_mute" ]; then 98 | # Set the new vol to the vol variable. 99 | vol=$new_vol; 100 | # Set the new mute to the mute variable. 101 | mute=$new_mute; 102 | if [ "$mute" = "true" ]; then 103 | vol_display="{B0} {F1}{/F}muted {/B}"; 104 | else 105 | if [ $vol -gt 66 ]; then 106 | vol_icon=""; 107 | elif [ $vol -gt 33 ]; then 108 | vol_icon=""; 109 | else 110 | vol_icon=""; 111 | fi 112 | 113 | if [ $vol -lt 10 ]; then 114 | vol_space=" "; 115 | else 116 | vol_space=""; 117 | fi 118 | 119 | vol_display="{F1} Vol{F0}$vol_icon{F1} $vol_space$vol% {/F}"; 120 | fi 121 | skip=0; 122 | fi 123 | ###################################### 124 | 125 | ###################################### 126 | # Check the root file system usage. 127 | # Every 60 seconds. 128 | if [ $(($count % 600)) -eq 0 ]; then 129 | # Grab the current percentage of root partition. 130 | new_root_percent=$(df --output=pcent,target | grep "/$" | awk '{print $1}'); 131 | if [ "$root_percent" != "$new_root_percent" ]; then 132 | # If it is different than we set the new value. 133 | root_percent=$new_root_percent; 134 | root_percent_display="{F2}/{F1}: $root_percent{/F}" 135 | # Also need to inform the later loop to reprint string. 136 | skip=0; 137 | fi 138 | fi 139 | ###################################### 140 | 141 | # If skip is set to false then we re-print the string. 142 | if [ $skip -eq 0 ]; then 143 | # Clear the string and set to just empty brackets 144 | # so we don't lose leading spaces. 145 | string="{}" 146 | 147 | # Set the left aligned section. 148 | string+="$dktps"; 149 | # Add the section seperator. 150 | string+="<|>" 151 | if [ "$1" != "HDMI-0" ]; then 152 | # Set the center aligned section. 153 | string+="$focus_win_display"; 154 | # Add the section seperator. 155 | string+="<|>" 156 | # Set the right aligned section. 157 | string+="$root_percent_display$SPACING$vol_display$SPACING$ip_display$SPACING$date_display"; 158 | fi 159 | # Add the section seperator. 160 | string+="<|>" 161 | # Add the right side. 162 | string+="$SPACING$date_display"; 163 | 164 | # Print the string. 165 | echo "$string"; 166 | fi 167 | 168 | # Check for the exit file and if it's there we exit. 169 | if [ -f "$STOP_FILE" ]; then 170 | # This string makes unibar quit. 171 | echo "QUIT NOW"; 172 | exit; 173 | fi 174 | 175 | # Increment our conter variable. 176 | count=$(($count + 1)); 177 | # Just to make sure we don't get ridiculous. 178 | if [ $count -gt 1000 ]; then 179 | count=1 180 | fi 181 | 182 | # Sleep so we don't eat the processor. 183 | sleep 0.1; 184 | done 185 | -------------------------------------------------------------------------------- /files/bspwm/focus: -------------------------------------------------------------------------------- 1 | 0x01C00002 2 | -------------------------------------------------------------------------------- /files/bswpm_subscribe.zsh: -------------------------------------------------------------------------------- 1 | #! /bin/zsh 2 | # Trying to replace awk script. 3 | 4 | bspc subscribe report node_focus | 5 | while read line 6 | do 7 | setopt shwordsplit 8 | if [ "${line:0:10}" = "node_focus" ]; then 9 | printf "${line:33}" > "bspwm/focus" 10 | else 11 | line="${line:1}" 12 | line="${line//:/ }" 13 | for tmp in $line 14 | do 15 | unsetopt shwordsplit 16 | ch="${tmp:0:1}" 17 | dktp="${tmp:1}" 18 | case "$ch" in 19 | M|m) 20 | if [ -z "$count" ]; then 21 | count=1; 22 | fi 23 | array[$count]="new_desktop" 24 | count=$(($count + 1)); 25 | array[$count]=$dktp; 26 | count=$(($count + 1)); 27 | ;; 28 | O|F|U) 29 | array[$count]=" {B0}{H0}{F1} $dktp {/BHF}"; 30 | count=$(($count + 1)); 31 | ;; 32 | o|u) 33 | array[$count]=" {F1} $dktp {/F}"; 34 | count=$(($count + 1)); 35 | ;; 36 | f) 37 | array[$count]=" $dktp "; 38 | count=$(($count + 1)); 39 | ;; 40 | esac 41 | done 42 | next_is_mon=0 43 | for i in $array 44 | do 45 | if [ "$i" = "new_desktop" ]; then 46 | if ! [ -z "$filename" ] && ! [ -z "$printstr" ]; then 47 | printf "$printstr" > "bspwm/$filename" 48 | printstr="" 49 | fi 50 | next_is_mon=1 51 | elif [ $next_is_mon -eq 1 ]; then 52 | filename="$i" 53 | next_is_mon=0 54 | elif [ -z "$printstr" ]; then 55 | printstr="$i" 56 | else 57 | printstr+="$i" 58 | fi 59 | done 60 | printf "$printstr" > "bspwm/$filename" 61 | fi 62 | done 63 | 64 | -------------------------------------------------------------------------------- /files/eDP-1.conf: -------------------------------------------------------------------------------- 1 | # this is a comment. 2 | 3 | monitor = eDP-1 4 | default_background = #282A36 5 | underline_height = 3 6 | font = Anonymous Pro:size=12:antialias=true 7 | font = Siji:size=16:antialias=true 8 | ft_colour = #FF79C6 9 | ft_colour = #8BE9FD 10 | ft_colour = #0a81f5 11 | ft_colour = #4d4d4d 12 | 13 | # Background colours. 14 | background_colour = #000000 15 | 16 | # Highlight colours. 17 | highlight_colour = #8BE9FD 18 | highlight_colour = #5AF78E 19 | highlight_colour = #F4F99D 20 | highlight_colour = #DB2B39 21 | 22 | # submodules 23 | kill_me_cmd = kill 24 | -------------------------------------------------------------------------------- /files/images/screenshot01.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ikubetoomuzik/unibar/7a06926f11b53770faebd662e26c965678f4206d/files/images/screenshot01.png -------------------------------------------------------------------------------- /files/input_DP-5.zsh: -------------------------------------------------------------------------------- 1 | #! /bin/zsh 2 | 3 | DEFAULT_DIR=$(dirname $(realpath $0)); 4 | SPACING=" "; 5 | 6 | # Args: 7 | # $1 => drive name to search for. 8 | # $2 => display to use for drive ('/' for root, or '~' for $HOME) 9 | # $3 => old val for storage. 10 | fs_storage() { 11 | # forces a query of all drives to set up the next queries. 12 | # Grab the current percentage of root partition. 13 | new_percent=$(df --output=pcent,target | grep "$1\$" | awk '{printf $1}'); 14 | if [ "$3" != "$new_percent" ]; then 15 | # If it is different than we set the new value. 16 | if ! [ -z "$new_percent" ]; then 17 | echo -n "{F2} $2{F1}: $new_percent{/F}" 18 | else 19 | echo -n "{F1}{F2} $2{/F}: disconnected" 20 | fi 21 | fi 22 | } 23 | 24 | # start the bspwm watching script here. 25 | if ! pgrep -x bspwm_report_fo > /dev/null; then 26 | $DEFAULT_DIR/bspwm_subscribe.zsh & 27 | fi 28 | 29 | # If the exit file already exists then clear it. 30 | STOP_FILE="$HOME/.config/unibar/stop" 31 | if [ -f "$STOP_FILE" ]; then 32 | rm "$STOP_FILE"; 33 | fi 34 | 35 | # Sleep to start because the bar needs time to start up. 36 | sleep 1; 37 | 38 | # Set the count to 0 to start. 39 | count=-10; 40 | 41 | while [ 1 -gt 0 ]; do 42 | # Skip defaults to false. 43 | skip=1 44 | 45 | ########################################################## 46 | # Get current focused window. 47 | new_focus_win=$(< "$DEFAULT_DIR/bspwm/focus"); 48 | if [ "$focus_win" != "$new_focus_win" ]; then 49 | focus_win=$new_focus_win; 50 | skip=0; 51 | fi 52 | new_focus_win_name=$(xdotool getwindowname "$focus_win" 2> /dev/null); 53 | if [ "$focus_win_name" != "$new_focus_win_name" ]; then 54 | focus_win_name=$new_focus_win_name; 55 | if [ ${#focus_win_name} -gt 90 ]; then 56 | win_name="$focus_win_name[1,87]..."; 57 | else 58 | win_name="$focus_win_name"; 59 | fi 60 | focus_win_display="{f1}{/f}{F1} $win_name{/F}" 61 | skip=0; 62 | fi 63 | ########################################################## 64 | 65 | ########################################################## 66 | # Get desktops 67 | new_dktps=$(< "$DEFAULT_DIR/bspwm/DP-5"); 68 | if [ "$dktps" != "$new_dktps" ]; then 69 | # If they are different than we set the new value. 70 | dktps=$new_dktps; 71 | # Also need to inform the later loop to reprint string. 72 | skip=0; 73 | fi 74 | ########################################################## 75 | 76 | # only every second. 77 | ########################################################## 78 | if [ $(($count % 10)) -eq 0 ] || [ $count -lt 0 ]; then 79 | ######################################################## 80 | # Time bit; 81 | # Grab the current time. 82 | new_time=$(date +%H:%M); 83 | # Do the comparison. 84 | if [ "$cur_time" != "$new_time" ]; then 85 | # If it is different than we set the new value. 86 | cur_time=$new_time; 87 | time_display="{F1} $cur_time{/F}"; 88 | # Also need to inform the later loop to reprint string. 89 | skip=0; 90 | fi 91 | ######################################################## 92 | ######################################################## 93 | # Network bit. 94 | ip a | grep -e ".*inet .*eno1$" | tr "/" " " | read x new_ip x; 95 | if [ "$ip" != "$new_ip" ]; then 96 | ip=$new_ip; 97 | if [ "$ip" = "" ]; then 98 | ip_display="{B0}{F1}{/F} disconnected.{/B}" 99 | else 100 | ip_display="{F1} $ip{/F}"; 101 | fi 102 | skip=0; 103 | fi 104 | ######################################################## 105 | fi 106 | ########################################################## 107 | 108 | ###################################### 109 | # Volume bit. 110 | # Get current volume. 111 | new_vol=$(pamixer --get-volume); 112 | new_mute=$(pamixer --get-mute); 113 | if [ "$vol" != "$new_vol" ] || [ "$mute" != "$new_mute" ]; then 114 | # Set the new vol to the vol variable. 115 | vol=$new_vol; 116 | # Set the new mute to the mute variable. 117 | mute=$new_mute; 118 | if [ "$mute" = "true" ]; then 119 | vol_display="{F3}muted{/F}"; 120 | else 121 | if [ $vol -ge 70 ]; then 122 | vol_icon=""; 123 | elif [ $vol -ge 25 ]; then 124 | vol_icon=""; 125 | elif [ $vol -eq 0 ]; then 126 | vol_icon=""; 127 | else 128 | vol_icon=""; 129 | fi 130 | if [ $vol -lt 10 ]; then 131 | vol_space=" "; 132 | else 133 | vol_space=""; 134 | fi 135 | vol_display="{F0}$vol_icon{F1} $vol_space$vol%{/F}"; 136 | fi 137 | skip=0; 138 | fi 139 | ###################################### 140 | 141 | ###################################### 142 | # Check the root file system usage. 143 | # Every 60 seconds. 144 | if [ $(($count % 60)) -eq 0 ] || [ $count -lt 0 ]; then 145 | # forces a query of all drives to set up the next queries. 146 | df -a > /dev/null 147 | # Grab the current percentage of root partition. 148 | new_root_percent=$(fs_storage / / $root_percent); 149 | if [ "$root_percent" != "$new_root_percent" ] && ! [ -z "$new_root_percent" ]; then 150 | # If it is different than we set the new value. 151 | root_percent=$new_root_percent; 152 | # Also need to inform the later loop to reprint string. 153 | skip=0; 154 | fi 155 | ###################################### 156 | new_media_percent=$(fs_storage "/media" "~/media" "$media_percent"); 157 | if [ "$media_percent" != "$new_media_percent" ] && ! [ -z "$new_media_percent" ]; then 158 | # If it is different than we set the new value. 159 | media_percent=$new_media_percent; 160 | # Also need to inform the later loop to reprint string. 161 | skip=0; 162 | fi 163 | ###################################### 164 | fi 165 | ###################################### 166 | 167 | # If skip is set to false then we re-print the string. 168 | if [ $skip -eq 0 ] || [ $count -lt 0 ] || [ $(($count % 30)) -eq 0 ]; then 169 | # Clear the string and set to just empty brackets 170 | # so we don't lose leading spaces. 171 | string="{}" 172 | 173 | # Set the left aligned section. 174 | string+="$dktps"; 175 | # Add the section seperator. 176 | string+="<|>" 177 | # Set the center aligned section. 178 | string+="$focus_win_display"; 179 | # Add the section seperator. 180 | string+="<|>" 181 | # Set the right aligned section. 182 | string+=" $root_percent$SPACING$media_percent$SPACING$vol_display$SPACING$ip_display$SPACING$time_display"; 183 | 184 | # Print the string. 185 | echo "$string"; 186 | fi 187 | 188 | # Check for the exit file and if it's there we exit. 189 | if [ -f "$STOP_FILE" ]; then 190 | # This string makes unibar quit. 191 | echo "QUIT NOW"; 192 | exit; 193 | fi 194 | 195 | # Increment our conter variable. 196 | count=$(($count + 1)); 197 | # Just to make sure we don't get ridiculous. 198 | if [ $count -gt 1000 ]; then 199 | count=1 200 | fi 201 | 202 | # Sleep so we don't eat the processor. 203 | sleep 0.1; 204 | done 205 | -------------------------------------------------------------------------------- /files/input_HDMI-0.zsh: -------------------------------------------------------------------------------- 1 | #! /bin/zsh 2 | 3 | DEFAULT_DIR=$(dirname $(realpath $0)); 4 | SPACING=" "; 5 | 6 | # start the bspwm watching script here. 7 | if ! pgrep -x bspwm_report_fo > /dev/null; then 8 | $DEFAULT_DIR/bspwm_subscribe.zsh & 9 | fi 10 | 11 | # If the exit file already exists then clear it. 12 | STOP_FILE="$HOME/.config/unibar/stop" 13 | if [ -f "$STOP_FILE" ]; then 14 | rm "$STOP_FILE"; 15 | fi 16 | 17 | # Sleep to start because the bar needs time to start up. 18 | sleep 1; 19 | 20 | # Set the count to 0 to start. 21 | count=0; 22 | 23 | while [ 1 -gt 0 ]; do 24 | # Skip defaults to false. 25 | skip=1 26 | 27 | ########################################################## 28 | # Get desktops 29 | new_dktps=$(< "$DEFAULT_DIR/bspwm/HDMI-0"); 30 | if [ "$dktps" != "$new_dktps" ]; then 31 | # If they are different than we set the new value. 32 | dktps=$new_dktps; 33 | # Also need to inform the later loop to reprint string. 34 | skip=0; 35 | fi 36 | ########################################################## 37 | 38 | # only every second. 39 | ########################################################## 40 | if [ $(($count % 10)) -eq 0 ]; then 41 | ######################################################## 42 | # Time bit; 43 | # Grab the current time. 44 | new_time=$(date +%H:%M); 45 | # Do the comparison. 46 | if [ "$cur_time" != "$new_time" ]; then 47 | # If it is different than we set the new value. 48 | cur_time=$new_time; 49 | time_display="{F1} $cur_time{/F}"; 50 | # Also need to inform the later loop to reprint string. 51 | skip=0; 52 | fi 53 | ######################################################## 54 | fi 55 | ########################################################## 56 | 57 | # If skip is set to false then we re-print the string. 58 | # OR every three seconds to make sure that if the screen sleeps the bar comes back. 59 | if [ $skip -eq 0 ] || [ $count -lt 0 ] || [ $(($count % 30)) -eq 0 ]; then 60 | # Clear the string and set to just empty brackets 61 | # so we don't lose leading spaces. 62 | string="{}" 63 | 64 | # Set the left aligned section. 65 | string+="$dktps"; 66 | # Add the section seperator. 67 | string+="<|>" 68 | # Add the section seperator. 69 | string+="<|>" 70 | # Add the right side. 71 | string+="$SPACING$time_display"; 72 | 73 | # Print the string. 74 | echo "$string"; 75 | fi 76 | 77 | # Check for the exit file and if it's there we exit. 78 | if [ -f "$STOP_FILE" ]; then 79 | # This string makes unibar quit. 80 | echo "QUIT NOW"; 81 | exit; 82 | fi 83 | 84 | # Increment our conter variable. 85 | count=$(($count + 1)); 86 | # Just to make sure we don't get ridiculous. 87 | if [ $count -gt 1000 ]; then 88 | count=1 89 | fi 90 | 91 | # Sleep so we don't eat the processor. 92 | sleep 0.1; 93 | done 94 | -------------------------------------------------------------------------------- /files/input_eDP-1.zsh: -------------------------------------------------------------------------------- 1 | #! /bin/zsh 2 | 3 | BRIGHT_DIR="/sys/class/backlight/intel_backlight"; 4 | DEFAULT_DIR="$HOME/.config/unibar"; 5 | SPACING=" "; 6 | 7 | 8 | # Args: 9 | # $1 => drive name to search for. 10 | # $2 => display to use for drive ('/' for root, or '~' for $HOME) 11 | # $3 => old val for storage. 12 | fs_storage() { 13 | # forces a query of all drives to set up the next queries. 14 | # Grab the current percentage of root partition. 15 | new_percent=$(df --output=pcent,target | grep "$1\$" | awk '{printf $1}'); 16 | if [ "$3" != "$new_percent" ]; then 17 | # If it is different than we set the new value. 18 | if ! [ -z "$new_percent" ]; then 19 | echo -n "{F2} $2{F1}: $new_percent{/F}" 20 | else 21 | echo -n "{F1}{F2} $2{/F}: disconnected" 22 | fi 23 | fi 24 | } 25 | 26 | # Args: 27 | # $1 => Network device name. 28 | net_info() { 29 | case "${1:0:1}" in 30 | e) 31 | ip a | grep -e ".*inet .*$1$" | tr "/" " " | read _ new_info _; 32 | connected_sym="" 33 | disconnected_sym="" 34 | ;; 35 | w) 36 | iw dev "$1" info | grep "ssid " | read _ new_info; 37 | connected_sym="" 38 | disconnected_sym="" 39 | ;; 40 | esac 41 | 42 | if [ -z "$new_info" ]; then 43 | printf "{F1}$disconnected_sym{/F} disconnected." 44 | else 45 | printf "$connected_sym{F1} $new_info{/F}"; 46 | fi 47 | } 48 | 49 | # Args none. 50 | brightness() { 51 | max=$(($(< "$BRIGHT_DIR/max_brightness") - 100)); 52 | cur=$(($(< "$BRIGHT_DIR/brightness") - 100)); 53 | perc=$((100 * $cur / $max)); 54 | if [ $perc -ge 50 ]; then 55 | spc=""; 56 | sym=""; 57 | elif [ $perc -lt 10 ]; then 58 | spc=" "; 59 | sym=""; 60 | else 61 | spc=""; 62 | sym=""; 63 | fi 64 | 65 | printf "$sym{F1} $spc$perc%%{/F}"; 66 | } 67 | 68 | # Args none. 69 | battery() { 70 | acpi | read _ _ chrge bat_per _; 71 | chrge="${chrge//,}" 72 | bat_per="${${bat_per//,}//\%}" 73 | bat_syms=("" "" ""); 74 | 75 | case "$chrge" in 76 | Charging) 77 | new_count=$(($1 + 1)); 78 | bat_ul=1; 79 | ;; 80 | Discharging) 81 | new_count=$(($1 - 1)); 82 | if [ $bat_per -le 15 ]; then 83 | bat_ul=3; 84 | else 85 | bat_ul=2; 86 | fi 87 | ;; 88 | esac 89 | 90 | bat_sym="${bat_syms[$1]}" 91 | 92 | if [ $bat_per -gt 97 ]; then 93 | bat_per=100; 94 | bat_sym=""; 95 | fi 96 | 97 | printf "$new_count{H$bat_ul}$bat_sym{F1} $bat_per%%{/HF}"; 98 | } 99 | 100 | # start the bspwm watching script here. 101 | if ! pgrep -x bspwm_subscribe > /dev/null; then 102 | $DEFAULT_DIR/bspwm_subscribe.zsh & 103 | fi 104 | 105 | # If the exit file already exists then clear it. 106 | STOP_FILE="$HOME/.config/unibar/stop" 107 | if [ -f "$STOP_FILE" ]; then 108 | rm "$STOP_FILE"; 109 | fi 110 | 111 | # Sleep to start because the bar needs time to start up. 112 | sleep 1; 113 | 114 | # Set the count to 0 to start. 115 | count=-10; 116 | 117 | # before we start the loop, ask unibar to kill us. 118 | echo "PLEASE KILL:$$"; 119 | 120 | while [ 1 -gt 0 ]; do 121 | # Skip defaults to false. 122 | skip=1 123 | 124 | ########################################################## 125 | # Get current focused window. 126 | new_focus_win=$(< "$DEFAULT_DIR/bspwm/focus"); 127 | if [ "$focus_win" != "$new_focus_win" ]; then 128 | focus_win=$new_focus_win; 129 | skip=0; 130 | fi 131 | new_focus_win_name=$(xdotool getwindowname "$focus_win" 2> /dev/null); 132 | if [ "$focus_win_name" != "$new_focus_win_name" ]; then 133 | focus_win_name=$new_focus_win_name; 134 | if [ ${#focus_win_name} -gt 42 ]; then 135 | win_name="$focus_win_name[1,39]..."; 136 | else 137 | win_name="$focus_win_name"; 138 | fi 139 | focus_win_display="{f1}{/f}{F1} $win_name{/F}" 140 | skip=0; 141 | fi 142 | ########################################################## 143 | 144 | ########################################################## 145 | # Get desktops 146 | new_dktps=$(< "$DEFAULT_DIR/bspwm/eDP-1"); 147 | if [ "$dktps" != "$new_dktps" ]; then 148 | # If they are different than we set the new value. 149 | dktps=$new_dktps; 150 | # Also need to inform the later loop to reprint string. 151 | skip=0; 152 | fi 153 | ########################################################## 154 | 155 | # Every half second. 156 | if [ $(($count % 8)) -eq 0 ]; then 157 | ######################################################## 158 | # Battery bit 159 | if [ -z "$bat_count" ] || [ $bat_count -gt 3 ]; then 160 | bat_count=1; 161 | elif [ $bat_count -lt 1 ]; then 162 | bat_count=3; 163 | fi 164 | return_val=$(battery $bat_count); 165 | bat_count=${return_val:0:1}; 166 | new_bat_display=${return_val:1}; 167 | if [ "$bat_display" != "$new_bat_display" ]; then 168 | bat_display=$new_bat_display; 169 | skip=0; 170 | fi 171 | ######################################################## 172 | fi 173 | # only every second. 174 | ########################################################## 175 | if [ $(($count % 10)) -eq 0 ] || [ $count -lt 0 ]; then 176 | ######################################################## 177 | # Time bit; 178 | # Grab the current time. 179 | new_time=$(date +%H:%M); 180 | # Do the comparison. 181 | if [ "$cur_time" != "$new_time" ]; then 182 | # If it is different than we set the new value. 183 | cur_time=$new_time; 184 | time_display="{F1} $cur_time{/F}"; 185 | # Also need to inform the later loop to reprint string. 186 | skip=0; 187 | fi 188 | ######################################################## 189 | ######################################################## 190 | # Network bit. 191 | new_ip_display=$(net_info "wlp58s0"); 192 | if [ "$ip_display" != "$new_ip_display" ]; then 193 | ip_display=$new_ip_display; 194 | skip=0; 195 | fi 196 | ######################################################## 197 | fi 198 | ########################################################## 199 | 200 | ###################################### 201 | # Brightness bit. 202 | new_bright_display=$(brightness); 203 | if [ "$bright_display" != "$new_bright_display" ]; then 204 | bright_display=$new_bright_display; 205 | skip=0; 206 | fi 207 | ###################################### 208 | 209 | ###################################### 210 | # Volume bit. 211 | # Get current volume. 212 | new_vol=$(pamixer --get-volume); 213 | new_mute=$(pamixer --get-mute); 214 | if [ "$vol" != "$new_vol" ] || [ "$mute" != "$new_mute" ]; then 215 | # Set the new vol to the vol variable. 216 | vol=$new_vol; 217 | # Set the new mute to the mute variable. 218 | mute=$new_mute; 219 | if [ "$mute" = "true" ]; then 220 | vol_display="{F3}muted{/F}"; 221 | else 222 | if [ $vol -ge 70 ]; then 223 | vol_icon=""; 224 | elif [ $vol -ge 25 ]; then 225 | vol_icon=""; 226 | elif [ $vol -eq 0 ]; then 227 | vol_icon=""; 228 | else 229 | vol_icon=""; 230 | fi 231 | if [ $vol -lt 10 ]; then 232 | vol_space=" "; 233 | else 234 | vol_space=""; 235 | fi 236 | vol_display="{F0}$vol_icon{F1} $vol_space$vol%{/F}"; 237 | fi 238 | skip=0; 239 | fi 240 | ###################################### 241 | 242 | ###################################### 243 | # Check the root file system usage. 244 | # Every 60 seconds. 245 | if [ $(($count % 60)) -eq 0 ] || [ $count -lt 0 ]; then 246 | # forces a query of all drives to set up the next queries. 247 | df -a > /dev/null 248 | # Grab the current percentage of root partition. 249 | new_root_percent=$(fs_storage / / $root_percent); 250 | if [ "$root_percent" != "$new_root_percent" ] && ! [ -z "$new_root_percent" ]; then 251 | # If it is different than we set the new value. 252 | root_percent=$new_root_percent; 253 | # Also need to inform the later loop to reprint string. 254 | skip=0; 255 | fi 256 | ###################################### 257 | new_home_percent=$(fs_storage "/home" "/home" "$home_percent"); 258 | if [ "$home_percent" != "$new_home_percent" ] && ! [ -z "$new_home_percent" ]; then 259 | # If it is different than we set the new value. 260 | home_percent=$new_home_percent; 261 | # Also need to inform the later loop to reprint string. 262 | skip=0; 263 | fi 264 | ###################################### 265 | fi 266 | ###################################### 267 | 268 | # If skip is set to false then we re-print the string. 269 | if [ $skip -eq 0 ] || [ $count -lt 0 ] || [ $(($count % 30)) -eq 0 ]; then 270 | # Clear the string and set to just empty brackets 271 | # so we don't lose leading spaces. 272 | string="{}" 273 | 274 | # Set the left aligned section. 275 | string+="$dktps"; 276 | # Add the section seperator. 277 | string+="<|>" 278 | # Set the center aligned section. 279 | string+="{}"; 280 | # Add the section seperator. 281 | string+="<|>" 282 | # Set the right aligned section. 283 | string+="$focus_win_display$SPACING$root_percent$SPACING$home_percent$SPACING$bright_display$SPACING$vol_display$SPACING$ip_display$SPACING$bat_display$SPACING$time_display"; 284 | 285 | # Print the string. 286 | echo "$string"; 287 | fi 288 | 289 | # Check for the exit file and if it's there we exit. 290 | if [ -f "$STOP_FILE" ]; then 291 | # This string makes unibar quit. 292 | echo "QUIT NOW"; 293 | exit; 294 | fi 295 | 296 | # Increment our conter variable. 297 | count=$(($count + 1)); 298 | # Just to make sure we don't get ridiculous. 299 | if [ $count -gt 1000 ]; then 300 | count=1 301 | fi 302 | 303 | # Sleep so we don't eat the processor. 304 | sleep 0.1; 305 | done 306 | -------------------------------------------------------------------------------- /files/launch_bar.sh: -------------------------------------------------------------------------------- 1 | #! /bin/sh 2 | 3 | pathname=$(dirname $0); 4 | 5 | cargo run -- -c $pathname/$1.conf "$@" < <($pathname/input_$1.zsh) 6 | -------------------------------------------------------------------------------- /src/bar.rs: -------------------------------------------------------------------------------- 1 | // Trying to actually organize this a bit. 2 | // By: Curtis Jones 3 | // Started on August 23, 2020 4 | 5 | use super::{ 6 | config::Config, 7 | input::{ColourPalette, Input}, 8 | optional::kill_me::KillMeModule, 9 | }; 10 | use anyhow::Result; 11 | use signal_hook::iterator::Signals; 12 | use std::{ 13 | collections::HashMap, ffi::CString, io, mem::MaybeUninit, process, ptr, sync::mpsc, thread, 14 | time, 15 | }; 16 | use thiserror::Error; 17 | use x11_dl::{xft, xinerama, xlib, xrandr}; 18 | 19 | /// The function we dump into a seperate thread to wait for any input. 20 | /// Put in a seperate funtion to make some of the methods cleaner. 21 | /// 22 | /// # Arguments 23 | /// * stdin: -> lock on the standard input for the projram. 24 | /// * send: -> sender part of an across thread message pipe. 25 | fn input_loop(stdin: io::Stdin, send: mpsc::Sender) { 26 | loop { 27 | let mut tmp = String::new(); 28 | stdin.read_line(&mut tmp).expect("wont fail."); 29 | if !tmp.is_empty() { 30 | send.send(tmp.trim().to_owned()) 31 | .expect("If this fails then the bar is already gone."); 32 | } 33 | } 34 | } 35 | 36 | #[derive(Debug, Error)] 37 | enum Error { 38 | #[error("Failed to open a connection to the default XDisplay")] 39 | DisplayOpenError, 40 | } 41 | 42 | /// Main struct of the whole program. 43 | pub struct Bar { 44 | name: String, 45 | xlib: xlib::Xlib, 46 | xft: xft::Xft, 47 | display: *mut xlib::Display, 48 | screen: i32, 49 | top: bool, 50 | monitor: String, 51 | x: i32, 52 | y: i32, 53 | width: i32, 54 | height: i32, 55 | back_colour: u64, 56 | cmap: xlib::Colormap, 57 | visual: *mut xlib::Visual, 58 | root: u64, 59 | window_id: u64, 60 | event: MaybeUninit, 61 | draw: *mut xft::XftDraw, 62 | font_map: HashMap, 63 | fonts: Vec<*mut xft::XftFont>, 64 | font_y: i32, 65 | palette: ColourPalette, 66 | underline_height: i32, 67 | left_string: Input, 68 | center_string: Input, 69 | right_string: Input, 70 | kill_me: Option, 71 | } 72 | 73 | impl Bar { 74 | /// Basic function to generate and empty bar object. Main focus is starting the essential 75 | /// library connections. 76 | /// 77 | /// Output: 78 | /// An unitialized Bar object, still need to load a config before it is useful. 79 | pub fn new() -> Result { 80 | // big ugly unsafe block here. 81 | unsafe { 82 | let xlib = xlib::Xlib::open()?; 83 | let xft = xft::Xft::open()?; 84 | let display = (xlib.XOpenDisplay)(ptr::null()); 85 | if display.is_null() { 86 | return Err(Error::DisplayOpenError.into()); 87 | } 88 | let screen = (xlib.XDefaultScreen)(display); 89 | let root = (xlib.XRootWindow)(display, screen); 90 | let visual = (xlib.XDefaultVisual)(display, screen); 91 | let cmap = (xlib.XDefaultColormap)(display, screen); 92 | 93 | Ok(Self { 94 | name: String::new(), 95 | xlib, 96 | xft, 97 | display, 98 | screen, 99 | top: true, 100 | monitor: String::new(), 101 | x: 0, 102 | y: 0, 103 | width: 0, 104 | height: 0, 105 | back_colour: 0, 106 | cmap, 107 | visual, 108 | root, 109 | window_id: 0, 110 | event: MaybeUninit::uninit(), 111 | draw: ptr::null_mut(), 112 | font_map: HashMap::new(), 113 | fonts: Vec::new(), 114 | font_y: 0, 115 | palette: ColourPalette::empty(), 116 | underline_height: 0, 117 | left_string: Input::empty(), 118 | center_string: Input::empty(), 119 | right_string: Input::empty(), 120 | kill_me: None, 121 | }) 122 | } 123 | } 124 | 125 | /// Main initial load for the program. Parsing the config object defined previously and 126 | /// creating a usable bar. 127 | /// 128 | /// Arguments: 129 | /// * Config object made using the gen_config function. 130 | /// 131 | /// Output: 132 | /// None, method alters the bar object itself, loading real values into the placeholders 133 | /// generated in Bar::new(). 134 | pub fn load_config(&mut self, conf: Config) -> Result<()> { 135 | // As per tradition, name first! 136 | self.name = conf.name; 137 | // We are setting x to 0 for now but we check for other monitors later. 138 | self.x = 0; 139 | // Bar height is configurable. 140 | self.height = conf.height; 141 | // Duh.. 142 | self.top = conf.top; 143 | // load kill_me module settings 144 | self.kill_me = conf.kill_me_cmd.map(KillMeModule::new); 145 | // Now we do all the yucky C library stuff in a big unsafe block. 146 | unsafe { 147 | // Width for now is the full XDisplay width. 148 | self.width = (self.xlib.XDisplayWidth)(self.display, self.screen); 149 | // If its the top then 0, otherwise subtract bar height from monitor height. 150 | self.y = if conf.top { 151 | 0 152 | } else { 153 | (self.xlib.XDisplayHeight)(self.display, self.screen) - conf.height 154 | }; 155 | 156 | // Setting the monitor using Xinerama or Xrandr depending on value provided. 157 | // Integer means Xinerama and any non-integer will be used to lookup in Xrandr. 158 | self.monitor = conf.monitor; 159 | if self.monitor.is_empty() { 160 | eprintln!("No monitor provided, using full XDisplay!"); 161 | } else if let Ok(mon) = self.monitor.parse::() { 162 | // If the monitor provided is a valid usize number. Then we are using Xinerama to 163 | // detect monitors. 164 | 165 | // xinerama stuff 166 | // grab the monitor number set in the conf. 167 | match xinerama::Xlib::open() { 168 | Ok(xin) => { 169 | // Grab another copy of the XDisplay. Because the Xinerama methods change the pointer 170 | // and causes the close to seg fault. 171 | let dpy = (self.xlib.XOpenDisplay)(ptr::null()); 172 | // Even if we have connected to the library that doesn't necessarily mean that Xinerama 173 | // is active. So we make another check here. 174 | // Old school c bool where 0 is false and anything else is true. 175 | if let 0 = (xin.XineramaIsActive)(dpy) { 176 | eprintln!( 177 | "Xinerama is not currently active -- using full XDisplay width." 178 | ); 179 | } else { 180 | // Temp var because the query strings funtion needs a pointer to a i32. 181 | let mut num_scr = 0; 182 | // Gets a dumb mutable pointer to an array of ScreenInfo objects for each screen. 183 | let scrns = (xin.XineramaQueryScreens)(dpy, &mut num_scr); 184 | // Using pointer arithmetic and the num_scr variable from the previous function we 185 | // fold the range into a Vec of ScreenInfo objects. 186 | let scrns = (0..num_scr as usize).fold(Vec::new(), |mut acc, i| { 187 | acc.push(*scrns.add(i)); 188 | acc 189 | }); 190 | // If the monitor set is not available, use first screen. 191 | let scrn = if mon >= num_scr as usize { 192 | eprintln!( 193 | "Monitor index: {} is too large! Using first screen.", 194 | mon 195 | ); 196 | scrns[0] 197 | } else { 198 | scrns[mon] 199 | }; 200 | self.x = scrn.x_org as i32; 201 | self.y = if self.top { 202 | scrn.y_org as i32 203 | } else { 204 | (scrn.y_org + scrn.height) as i32 - self.height 205 | }; 206 | self.width = scrn.width as i32; 207 | } 208 | // Close out the temp display we opened. 209 | (self.xlib.XCloseDisplay)(dpy); 210 | } 211 | Err(e) => eprintln!( 212 | "Could not connect to Xinerama lib -- using full XDisplay width.\n{}", 213 | e 214 | ), 215 | } 216 | } else if let Ok(xrr) = xrandr::Xrandr::open() { 217 | // xrandr stuff 218 | // again we load a seperate pointer to the display, because otherwise we get 219 | // segfaults. those are hard enough to understand when the language intends that as 220 | // an error, but rust has a real hard time explaining so we just eat this and try 221 | // again. 222 | let dpy = (self.xlib.XOpenDisplay)(ptr::null()); 223 | let resources = (xrr.XRRGetScreenResources)(dpy, self.root); 224 | // doesn't matter what we set here, the GetMonitors function overrides with the 225 | // real val before we read. 226 | let mut num_mon: i32 = 0; 227 | // Now we query the library for a list on monitors and it helpfully (kill me now) 228 | // returns a pointer to the first monitor and a total count in the num_mon var. 229 | let mons = (xrr.XRRGetMonitors)(dpy, self.root, xlib::True, &mut num_mon); 230 | // translating between weird c structs and pretty rust ones. 231 | // we create a range iterator as large as the number of monitors and use pointer 232 | // arithmetic to collect those into a Rust Vec. 233 | // plus a quick type alias to make the code signatures easier to read. 234 | type MonitorInfoList = Vec<(String, i32, i32, i32, i32)>; 235 | let mons = (0..num_mon as usize).try_fold( 236 | Vec::new(), 237 | |mut acc, i| -> Result { 238 | let m = *mons.add(i); 239 | // The way xrandr organizes information probably makes sense if you wrote the 240 | // library. or maybe if you can find docs because they either dont exist or 241 | // suck. Basically every Xrandr Monitor has outputs. Unless you have multiple cords 242 | // from pc to monitor you only have one output. 243 | let mut tmp = (0..m.noutput as usize).try_fold( 244 | Vec::new(), 245 | |mut ac, j| -> Result { 246 | let output = *m.outputs.add(j); 247 | let info = *(xrr.XRRGetOutputInfo)(dpy, resources, output); 248 | // Inside the output object we have another object called CRTC. 249 | let crtc = *(xrr.XRRGetCrtcInfo)(dpy, resources, info.crtc); 250 | // This library returns strings just like arrays, you get a pointer to the 251 | // first char and a count. So we do the same iteration trick to collect 252 | // into a string. 253 | let name = 254 | (0..info.nameLen as usize).fold(Vec::new(), |mut acc, k| { 255 | acc.push(*info.name.add(k) as u8); 256 | acc 257 | }); 258 | // Inside the CRTC is information that an actual human or basic ass application 259 | // like this may need. So we grab what we need there and push a tuple 260 | // containing the info into the vec, instead of the full monitor object, to 261 | // avoid all this abstraction craziness later. 262 | ac.push(( 263 | String::from_utf8(name)?, 264 | crtc.x, 265 | crtc.y, 266 | crtc.width as i32, 267 | crtc.height as i32, 268 | )); 269 | Ok(ac) 270 | }, 271 | )?; 272 | // Append the tmp vec of usable monitor info to the result and finally move 273 | // onto the next Monitor. 274 | acc.append(&mut tmp); 275 | Ok(acc) 276 | }, 277 | )?; 278 | match mons.iter().find(|m| m.0 == self.monitor) { 279 | Some(m) => { 280 | self.x = m.1; 281 | self.y = if self.top { m.2 } else { m.4 - self.height }; 282 | self.width = m.3; 283 | } 284 | None => eprintln!( 285 | "Xrandr monitor -> {} <- not found, using full XDisplay!", 286 | self.monitor 287 | ), 288 | } 289 | (self.xlib.XCloseDisplay)(dpy); 290 | } else { 291 | eprintln!("XRandr not available, using full XDisplay!"); 292 | } 293 | 294 | if let Some(width) = conf.width { 295 | self.width = width; 296 | } 297 | self.underline_height = conf.ul_height; 298 | self.fonts = conf.fonts.iter().try_fold( 299 | Vec::new(), 300 | |mut acc, fs| -> Result> { 301 | acc.push(self.get_font(fs)?); 302 | Ok(acc) 303 | }, 304 | )?; 305 | self.font_y = conf.font_y; 306 | self.back_colour = self.get_xlib_color(&conf.back_color)?; 307 | type XftColorList = Vec; 308 | self.palette.font = 309 | conf.ft_clrs 310 | .iter() 311 | .try_fold(Vec::new(), |mut acc, s| -> Result { 312 | acc.push(self.get_xft_colour(s)?); 313 | Ok(acc) 314 | })?; 315 | self.palette.background = 316 | conf.bg_clrs 317 | .iter() 318 | .try_fold(Vec::new(), |mut acc, s| -> Result { 319 | acc.push(self.get_xft_colour(s)?); 320 | Ok(acc) 321 | })?; 322 | self.palette.underline = 323 | conf.ul_clrs 324 | .iter() 325 | .try_fold(Vec::new(), |mut acc, s| -> Result { 326 | acc.push(self.get_xft_colour(s)?); 327 | Ok(acc) 328 | })?; 329 | } 330 | Ok(()) 331 | } 332 | 333 | pub fn init(&mut self) -> Result<()> { 334 | unsafe { 335 | // Manually set the attributes here so we can get more fine grain control. 336 | let mut attributes: MaybeUninit = MaybeUninit::uninit(); 337 | let atts = attributes.as_mut_ptr(); 338 | (*atts).background_pixel = self.back_colour; 339 | (*atts).colormap = self.cmap; 340 | (*atts).override_redirect = xlib::False; 341 | (*atts).event_mask = 342 | xlib::ExposureMask | xlib::ButtonPressMask | xlib::VisibilityChangeMask; 343 | let mut attributes = attributes.assume_init(); 344 | 345 | // Use the attributes we created to make a window. 346 | self.window_id = (self.xlib.XCreateWindow)( 347 | self.display, // Display to use. 348 | self.root, // Parent window. 349 | self.x, // X position (from top-left. 350 | self.y, // Y position (from top-left. 351 | self.width as u32, // Length of the bar in x direction. 352 | self.height as u32, // Height of the bar in y direction. 353 | 0, // Border-width. 354 | xlib::CopyFromParent, // Window depth. 355 | xlib::InputOutput as u32, // Window class. 356 | self.visual, // Visual type to use. 357 | xlib::CWBackPixel | xlib::CWColormap | xlib::CWOverrideRedirect | xlib::CWEventMask, // Mask for which attributes are set. 358 | &mut attributes, // Pointer to the attributes to use. 359 | ); 360 | self.draw = 361 | (self.xft.XftDrawCreate)(self.display, self.window_id, self.visual, self.cmap); 362 | 363 | self.set_atoms()?; 364 | 365 | // Map it up. 366 | (self.xlib.XMapWindow)(self.display, self.window_id); 367 | } 368 | Ok(()) 369 | } 370 | 371 | pub fn event_loop(&mut self) -> Result<()> { 372 | // Input thread. Has to be seperate to not block xlib events. 373 | let (tx, rx) = mpsc::channel(); 374 | // If we don't call the stdin function in this thread and pass it then sometimes we lose 375 | // the connection and the threads both panic. 376 | thread::spawn(move || input_loop(io::stdin(), tx)); 377 | 378 | // Signals that are incoming. 379 | let signals = Signals::new(&[ 380 | signal_hook::SIGTERM, 381 | signal_hook::SIGINT, 382 | signal_hook::SIGQUIT, 383 | signal_hook::SIGHUP, 384 | ])?; 385 | 386 | loop { 387 | // Check signals. 388 | // All of the signals basically tell the program to shutdown, so we just get ahead and 389 | // make sure that we clean up properly. 390 | if signals.pending().count() > 0 { 391 | self.close(1); 392 | } 393 | 394 | // Check the input thread. 395 | if let Ok(string) = rx.try_recv() { 396 | // Small kill marker for when I can't click. 397 | if string == "QUIT NOW" { 398 | break; 399 | } 400 | 401 | // messy way to check if kill me option is enabled 402 | if string.starts_with("PLEASE KILL:") { 403 | if let Some(kill_me) = self.kill_me.as_mut() { 404 | if let Some(id_str) = string.split(':').nth(1) { 405 | if let Ok(id) = id_str.parse::() { 406 | kill_me.push(id); 407 | } 408 | } 409 | } 410 | continue; 411 | } 412 | 413 | let split: Vec = string.split("<|>").map(|s| s.to_owned()).collect(); 414 | unsafe { 415 | match split.len() { 416 | // If there are no seperators then we assign the whole string to the left 417 | // bar section. 418 | 1 => { 419 | self.left_string.parse_string( 420 | &self.xft, 421 | self.display, 422 | &self.fonts, 423 | &mut self.font_map, 424 | &self.palette, 425 | &split[0], 426 | )?; 427 | self.center_string.clear(); 428 | self.right_string.clear(); 429 | } 430 | // If there is only one seperator we assign the first bit to the left and 431 | // the second to the right. 432 | 2 => { 433 | self.left_string.parse_string( 434 | &self.xft, 435 | self.display, 436 | &self.fonts, 437 | &mut self.font_map, 438 | &self.palette, 439 | &split[0], 440 | )?; 441 | self.center_string.clear(); 442 | self.right_string.parse_string( 443 | &self.xft, 444 | self.display, 445 | &self.fonts, 446 | &mut self.font_map, 447 | &self.palette, 448 | &split[1], 449 | )?; 450 | } 451 | // If there are two or more seperators then we are only gonna use the first 452 | // three, assign the first to left, second to center, and third to right. 453 | _ => { 454 | self.left_string.parse_string( 455 | &self.xft, 456 | self.display, 457 | &self.fonts, 458 | &mut self.font_map, 459 | &self.palette, 460 | &split[0], 461 | )?; 462 | self.center_string.parse_string( 463 | &self.xft, 464 | self.display, 465 | &self.fonts, 466 | &mut self.font_map, 467 | &self.palette, 468 | &split[1], 469 | )?; 470 | self.right_string.parse_string( 471 | &self.xft, 472 | self.display, 473 | &self.fonts, 474 | &mut self.font_map, 475 | &self.palette, 476 | &split[2], 477 | )?; 478 | } 479 | } 480 | self.draw_display(); 481 | } 482 | } 483 | 484 | unsafe { 485 | // Check events. 486 | if self.poll_events() { 487 | #[allow(clippy::single_match)] 488 | match self.event.assume_init_ref().get_type() { 489 | // if the bar is show on the screen we draw content. 490 | xlib::Expose => self.draw_display(), 491 | // ignore all other events 492 | _ => (), 493 | } 494 | } 495 | } 496 | 497 | thread::sleep(time::Duration::from_millis(100)); 498 | } 499 | Ok(()) 500 | } 501 | 502 | unsafe fn clear_display(&self) { 503 | (self.xlib.XClearWindow)(self.display, self.window_id); 504 | } 505 | 506 | unsafe fn draw_display(&self) { 507 | // clear display before we redraw 508 | self.clear_display(); 509 | // left string. 510 | self.left_string.draw( 511 | &self.xft, 512 | self.display, 513 | self.draw, 514 | &self.palette, 515 | &self.fonts, 516 | 0, 517 | self.font_y, 518 | self.height as u32, 519 | self.underline_height as u32, 520 | ); 521 | 522 | // center string. 523 | self.center_string.draw( 524 | &self.xft, 525 | self.display, 526 | self.draw, 527 | &self.palette, 528 | &self.fonts, 529 | (self.width - self.center_string.len(&self.xft, self.display, &self.fonts) as i32) / 2, 530 | self.font_y, 531 | self.height as u32, 532 | self.underline_height as u32, 533 | ); 534 | 535 | // right string. 536 | self.right_string.draw( 537 | &self.xft, 538 | self.display, 539 | self.draw, 540 | &self.palette, 541 | &self.fonts, 542 | self.width - self.right_string.len(&self.xft, self.display, &self.fonts) as i32, 543 | self.font_y, 544 | self.height as u32, 545 | self.underline_height as u32, 546 | ); 547 | } 548 | 549 | pub fn close(&mut self, code: i32) -> ! { 550 | println!("\nShutting down..."); 551 | unsafe { 552 | self.palette 553 | .destroy(&self.xft, self.display, self.cmap, self.visual); 554 | (self.xft.XftDrawDestroy)(self.draw); 555 | self.fonts 556 | .iter() 557 | .for_each(|&f| (self.xft.XftFontClose)(self.display, f)); 558 | (self.xlib.XFreeColormap)(self.display, self.cmap); 559 | (self.xlib.XDestroyWindow)(self.display, self.window_id); 560 | (self.xlib.XCloseDisplay)(self.display); 561 | } 562 | if let Some(km) = self.kill_me.as_mut() { 563 | km.kill_all() 564 | } 565 | process::exit(code); 566 | } 567 | 568 | unsafe fn get_atom(&self, name: &str) -> Result { 569 | let name = CString::new(name)?; 570 | Ok((self.xlib.XInternAtom)( 571 | self.display, 572 | name.as_ptr() as *const i8, 573 | xlib::False, 574 | )) 575 | } 576 | 577 | unsafe fn get_font(&self, name: &str) -> Result<*mut xft::XftFont> { 578 | let name = CString::new(name)?; 579 | let tmp = (self.xft.XftFontOpenName)(self.display, self.screen, name.as_ptr() as *const i8); 580 | if tmp.is_null() { 581 | panic!("Font {} not found!!", name.to_str()?) 582 | } else { 583 | Ok(tmp) 584 | } 585 | } 586 | 587 | unsafe fn get_xft_colour(&self, name: &str) -> Result { 588 | let name = CString::new(name)?; 589 | 590 | let mut tmp: MaybeUninit = MaybeUninit::uninit(); 591 | (self.xft.XftColorAllocName)( 592 | self.display, 593 | self.visual, 594 | self.cmap, 595 | name.as_ptr() as *const i8, 596 | tmp.as_mut_ptr(), 597 | ); 598 | let tmp = tmp.assume_init(); 599 | Ok(tmp) 600 | } 601 | 602 | unsafe fn get_xlib_color(&self, name: &str) -> Result { 603 | let name = CString::new(name)?; 604 | let mut temp: MaybeUninit = MaybeUninit::uninit(); 605 | (self.xlib.XParseColor)(self.display, self.cmap, name.as_ptr(), temp.as_mut_ptr()); 606 | (self.xlib.XAllocColor)(self.display, self.cmap, temp.as_mut_ptr()); 607 | let temp = temp.assume_init(); 608 | Ok(temp.pixel) 609 | } 610 | 611 | unsafe fn poll_events(&mut self) -> bool { 612 | (self.xlib.XCheckWindowEvent)( 613 | self.display, 614 | self.window_id, 615 | xlib::ButtonPressMask | xlib::ExposureMask, 616 | self.event.as_mut_ptr(), 617 | ) == 1 618 | } 619 | 620 | unsafe fn set_atoms(&mut self) -> Result<()> { 621 | // Set the WM_NAME. 622 | let name = format!("Unibar_{}", self.name); 623 | let title = CString::new(name)?; 624 | (self.xlib.XStoreName)(self.display, self.window_id, title.as_ptr() as *mut i8); 625 | // Set WM_CLASS 626 | let class: *mut xlib::XClassHint = (self.xlib.XAllocClassHint)(); 627 | let cl_names = [CString::new("unibar")?, CString::new("Unibar")?]; 628 | (*class).res_name = cl_names[0].as_ptr() as *mut i8; 629 | (*class).res_class = cl_names[1].as_ptr() as *mut i8; 630 | (self.xlib.XSetClassHint)(self.display, self.window_id, class); 631 | // Set WM_CLIENT_MACHINE 632 | let hn_size = libc::sysconf(libc::_SC_HOST_NAME_MAX) as libc::size_t; 633 | let hn_buffer: *mut i8 = vec![0i8; hn_size].as_mut_ptr(); 634 | libc::gethostname(hn_buffer, hn_size); 635 | let mut hn_list = [hn_buffer]; 636 | let mut hn_text_prop: std::mem::MaybeUninit = MaybeUninit::uninit(); 637 | (self.xlib.XStringListToTextProperty)(hn_list.as_mut_ptr(), 1, hn_text_prop.as_mut_ptr()); 638 | let mut hn_text_prop = hn_text_prop.assume_init(); 639 | (self.xlib.XSetWMClientMachine)(self.display, self.window_id, &mut hn_text_prop); 640 | // Set _NET_WM_PID 641 | let pid = [process::id()].as_ptr(); 642 | let wm_pid_atom = self.get_atom("_NET_WM_PID")?; 643 | (self.xlib.XChangeProperty)( 644 | self.display, 645 | self.window_id, 646 | wm_pid_atom, 647 | xlib::XA_CARDINAL, 648 | 32, 649 | xlib::PropModeReplace, 650 | pid as *const u8, 651 | 1, 652 | ); 653 | 654 | // Set _NET_WM_DESKTOP 655 | let dk_num = [0xFFFFFFFFu64].as_ptr(); 656 | let wm_dktp_atom = self.get_atom("_NET_WM_DESKTOP")?; 657 | (self.xlib.XChangeProperty)( 658 | self.display, 659 | self.window_id, 660 | wm_dktp_atom, 661 | xlib::XA_CARDINAL, 662 | 32, 663 | xlib::PropModeReplace, 664 | dk_num as *const u8, 665 | 1, 666 | ); 667 | 668 | // Change _NET_WM_STATE 669 | let wm_state_atom = self.get_atom("_NET_WM_STATE")?; 670 | let state_atoms = [ 671 | self.get_atom("_NET_WM_STATE_STICKY")?, 672 | self.get_atom("_NET_WM_STATE_ABOVE")?, 673 | ]; 674 | (self.xlib.XChangeProperty)( 675 | self.display, 676 | self.window_id, 677 | wm_state_atom, 678 | xlib::XA_ATOM, 679 | 32, 680 | xlib::PropModeAppend, 681 | state_atoms.as_ptr() as *const u8, 682 | 2, 683 | ); 684 | 685 | // Set the _NET_WM_STRUT[_PARTIAL] 686 | // TOP = 2 -> height, 8 -> start x, 9 -> end x 687 | // BOTTOM = 3 -> height, 10 -> start x, 11 -> end x 688 | let mut strut: [i64; 12] = [0; 12]; 689 | if self.top { 690 | strut[2] = self.height as i64; 691 | strut[8] = self.x as i64; 692 | strut[9] = (self.x + self.width - 1) as i64; 693 | } else { 694 | strut[3] = self.height as i64; 695 | strut[10] = self.x as i64; 696 | strut[11] = (self.x + self.width - 1) as i64; 697 | } 698 | let strut_atoms = [ 699 | self.get_atom("_NET_WM_STRUT_PARTIAL")?, 700 | self.get_atom("_NET_WM_STRUT")?, 701 | ]; 702 | (self.xlib.XChangeProperty)( 703 | self.display, 704 | self.window_id, 705 | strut_atoms[0], 706 | xlib::XA_CARDINAL, 707 | 32, 708 | xlib::PropModeReplace, 709 | strut.as_ptr() as *const u8, 710 | 12, 711 | ); 712 | (self.xlib.XChangeProperty)( 713 | self.display, 714 | self.window_id, 715 | strut_atoms[1], 716 | xlib::XA_CARDINAL, 717 | 32, 718 | xlib::PropModeReplace, 719 | strut.as_ptr() as *const u8, 720 | 4, 721 | ); 722 | 723 | // Set the _NET_WM_WINDOW_TYPE atom 724 | let win_type_atom = self.get_atom("_NET_WM_WINDOW_TYPE")?; 725 | let dock_atom = [self.get_atom("_NET_WM_WINDOW_TYPE_DOCK")?]; 726 | (self.xlib.XChangeProperty)( 727 | self.display, 728 | self.window_id, 729 | win_type_atom, 730 | xlib::XA_ATOM, 731 | 32, 732 | xlib::PropModeReplace, 733 | dock_atom.as_ptr() as *const u8, 734 | 1, 735 | ); 736 | Ok(()) 737 | } 738 | } 739 | -------------------------------------------------------------------------------- /src/config.rs: -------------------------------------------------------------------------------- 1 | // Struct to load config from file and cli args. 2 | // By: Curtis Jones 3 | // Started on: September 07, 2020 4 | 5 | // gonna start by implementing the loading from file bits. 6 | 7 | use anyhow::Result; 8 | use clap::clap_app; 9 | use dirs::config_dir; 10 | use std::fs::read_to_string; 11 | use std::path::PathBuf; 12 | 13 | #[derive(Debug, thiserror::Error)] 14 | #[error("[{0}] is not a valid option!")] 15 | pub struct Error(String); 16 | 17 | #[derive(Debug)] 18 | pub struct Config { 19 | pub name: String, // name of the bar 20 | pub top: bool, // top or bottom 21 | pub monitor: String, // xinerama montior list index for monitor 22 | pub height: i32, // width or height of bar depending on pos. 23 | pub width: Option, // width or height of bar depending on pos. 24 | pub ul_height: i32, // width or height of bar depending on pos. 25 | pub fonts: Vec, // Vec of strings listing the fonts in FcLookup form. 26 | pub font_y: i32, // pixel offset from the top of bar to bottom font. 27 | pub back_color: String, // String of the hex color. 28 | pub ft_clrs: Vec, // String of the hex color. 29 | pub bg_clrs: Vec, // String of the hex color. 30 | pub ul_clrs: Vec, // String of the hex color. 31 | pub kill_me_cmd: Option, // Command to run on each pid for kill_me module. 32 | } 33 | 34 | impl Config { 35 | pub fn default() -> Config { 36 | Config { 37 | name: String::new(), 38 | top: true, 39 | monitor: String::new(), 40 | height: 32, 41 | width: None, 42 | ul_height: 4, 43 | fonts: vec![String::from("mono:size=12")], 44 | font_y: 20, 45 | back_color: String::from("#000000"), 46 | ft_clrs: vec![String::from("#FFFFFF")], 47 | bg_clrs: vec![String::from("#0000FF")], 48 | ul_clrs: vec![String::from("#FF0000")], 49 | kill_me_cmd: None, 50 | } 51 | } 52 | 53 | /// Making a full Config by parsing the CLI arguments, parsing the config file, and mashing 54 | /// them together to create whatever. 55 | /// 56 | /// # Output 57 | /// Main Config to be used for the Bar. 58 | pub fn from_args() -> Result { 59 | // Create an App object for parsing CLI args. Thankfully the library makes the code pretty 60 | // readable and there is no runtime penalty. 61 | let matches = clap_app!(Unibar => 62 | (version: env!("CARGO_PKG_VERSION")) 63 | (author: "Curtis Jones ") 64 | (about: "Simple Xorg display bar!") 65 | (@arg NO_CONFIG: -C --noconfig "Tells Unibar to skip loading a config file.") 66 | (@arg CONFIG: -c --config +takes_value "Sets a custom config file") 67 | (@arg NAME: * +takes_value "Sets name and is required") 68 | (@arg POSITION: -p --position +takes_value "overrides config file position option") 69 | (@arg MONITOR: -m --monitor +takes_value "sets the monitor number to use. starts at 1") 70 | (@arg DEF_BACKGROUND: -b --background +takes_value "overrides config file default background") 71 | (@arg HEIGHT: -h --height +takes_value "overrides config file bar height option") 72 | (@arg WIDTH: -w --width +takes_value "overrides config file bar width option") 73 | (@arg UNDERLINE: -u --underline +takes_value "overrides config file underline height option") 74 | (@arg FONT_Y: -y --fonty +takes_value "overrides config file font y offset option") 75 | (@arg FONTS: -f --fonts ... +takes_value "overrides config file font options") 76 | (@arg FT_COLOURS: -F --ftcolours ... +takes_value "overrides config file font colours") 77 | (@arg BG_COLOURS: -B --bgcolours ... +takes_value "overrides config file background highlight colours") 78 | (@arg UL_COLOURS: -U --ulcolours ... +takes_value "overrides config file underline highlight colours") 79 | (@arg KILL_ME_CMD: -k --killme +takes_value "Enabled kill_me module and set the command to use.") 80 | ) 81 | .help_short("H") // We are using the lowercase h to set height. 82 | .setting(clap::AppSettings::ColoredHelp) // Make it look pretty. 83 | .get_matches(); // We actually only take the matches because we don't need clap for anything else. 84 | // Get the name first. It's required. 85 | let name = matches 86 | .value_of("NAME") 87 | .expect("Clap verifies this arguement is present before we get to this point."); 88 | // Decide what the default config file will be. 89 | let default_conf = match config_dir() { 90 | // We look in XDG_CONFIG_DIR or $HOME/.config for a unibar folder with unibar.conf 91 | // avaiable. 92 | Some(mut d) => { 93 | let config = format!("unibar/{}.conf", name); 94 | d.push(config); 95 | d 96 | } 97 | // If neither of those dirs are a thing, then we just set an empty string. 98 | None => PathBuf::new(), 99 | }; 100 | // If a explicit config file was set in the CLI args then we use that instead of our 101 | // default. 102 | let conf_opt = match matches.value_of("CONFIG") { 103 | Some(conf) => PathBuf::from(conf), 104 | None => default_conf, 105 | }; 106 | // Whatever we chose in the previous step we now try to load that config file. 107 | // IF we are loading a config file then we use the value generated from bar name, if not we use 108 | // the default Config. 109 | let mut tmp = if matches.is_present("NO_CONFIG") { 110 | Config::default() 111 | } else { 112 | Config::from_file(conf_opt)? 113 | }; 114 | // Set the name first as we got it earlier. 115 | tmp.change_option("NAME", name)?; 116 | // Now we alter the loaded Config object with the CLI args. 117 | // First we check all of the options that only take one val. 118 | for opt in &[ 119 | "MONITOR", 120 | "POSITION", 121 | "DEF_BACKGROUND", 122 | "HEIGHT", 123 | "WIDTH", 124 | "UNDERLINE", 125 | "FONT_Y", 126 | "KILL_ME_CMD", 127 | ] { 128 | if let Some(s) = matches.value_of(opt) { 129 | tmp.change_option(opt, s)?; 130 | } 131 | } 132 | // Next we check all of the options that take multiple vals. 133 | for opt in &["FONTS", "FT_COLOURS", "BG_COLOURS", "UL_COLOURS"] { 134 | if let Some(strs) = matches.values_of(opt) { 135 | tmp.replace_opt(opt, strs.map(|s| s.to_string()).collect())?; 136 | } 137 | } 138 | // Return the final Config to be used. 139 | Ok(tmp) 140 | } 141 | 142 | pub fn from_file(file: PathBuf) -> Result { 143 | let mut tmp = Config::default(); 144 | 145 | // Read the config file to a string. 146 | let conf_file = read_to_string(&file)?; 147 | 148 | for (i, line) in (1..).zip(conf_file.lines()) { 149 | // line to allow comments 150 | if line.starts_with('#') || line.is_empty() { 151 | continue; 152 | } 153 | let mut line = line.splitn(2, '='); 154 | let opt = match line.next() { 155 | Some(o) => o, 156 | None => { 157 | eprintln!("Invalid config on line {}.", i); 158 | continue; 159 | } 160 | }; 161 | let val = match line.next() { 162 | Some(v) => v, 163 | None => { 164 | eprintln!("Invalid config on line {}.", i); 165 | continue; 166 | } 167 | }; 168 | tmp.change_option(opt, val)?; 169 | } 170 | 171 | // Clear out the defaults if anything else was set. 172 | if tmp.fonts.len() > 1 { 173 | tmp.fonts.remove(0); 174 | } 175 | if tmp.ft_clrs.len() > 1 { 176 | tmp.ft_clrs.remove(0); 177 | } 178 | if tmp.bg_clrs.len() > 1 { 179 | tmp.bg_clrs.remove(0); 180 | } 181 | if tmp.ul_clrs.len() > 1 { 182 | tmp.ul_clrs.remove(0); 183 | } 184 | 185 | // Return our temp variable. 186 | Ok(tmp) 187 | } 188 | 189 | pub fn change_option(&mut self, opt: &str, val: &str) -> std::result::Result<(), Error> { 190 | // Doing a lot of direct comparisons so we gotta trim and set the values to lowercase. 191 | // Also grabbing just string slices because it makes the rest of the code look pretty. 192 | let opt = &opt.trim().to_lowercase()[..]; 193 | let val = val.trim().to_string(); 194 | 195 | // Can't get around a big ass match statement in a situation like this. 196 | // For args that take specific vals we check to see if the val given fits within the 197 | // constraints but otherwise we just push it into the Config. 198 | match opt { 199 | // skip name... 200 | "name" => self.name = val, 201 | "position" => match &val.to_lowercase()[..] { 202 | "top" => self.top = true, 203 | "bottom" => self.top = false, 204 | _ => eprintln!("Invaild position option!"), 205 | }, 206 | "monitor" => self.monitor = val, 207 | "width" => { 208 | if let Ok(s) = val.parse::() { 209 | self.width = Some(s); 210 | } else { 211 | eprintln!("Invaild size option! Needs to be a digit representable by a 32-bit integer."); 212 | } 213 | } 214 | "height" => { 215 | if let Ok(s) = val.parse::() { 216 | self.height = s; 217 | } else { 218 | eprintln!("Invaild size option! Needs to be a digit representable by a 32-bit integer."); 219 | } 220 | } 221 | "underline_height" => { 222 | if let Ok(s) = val.parse::() { 223 | self.ul_height = s; 224 | } else { 225 | eprintln!("Invaild highlight_size option! Needs to be a digit representable by a 32-bit integer."); 226 | } 227 | } 228 | "font" => self.fonts.push(val), 229 | "font_y" => { 230 | if let Ok(y) = val.parse::() { 231 | self.font_y = y; 232 | } else { 233 | eprintln!("Invaild font_y option! Needs to be a digit representable by a 32-bit integer."); 234 | } 235 | } 236 | "default_background" => self.back_color = val, 237 | "ft_colour" => self.ft_clrs.push(val), 238 | "background_colour" => self.bg_clrs.push(val), 239 | "highlight_colour" => self.ul_clrs.push(val), 240 | "kill_me_cmd" => self.kill_me_cmd = Some(val), 241 | _ => return Err(Error(opt.into())), 242 | } 243 | Ok(()) 244 | } 245 | 246 | pub fn replace_opt(&mut self, opt: &str, vals: Vec) -> std::result::Result<(), Error> { 247 | let opt = &opt.trim().to_lowercase()[..]; 248 | match opt { 249 | "fonts" => self.fonts = vals, 250 | "ft_colours" => self.ft_clrs = vals, 251 | "bg_colours" => self.bg_clrs = vals, 252 | "ul_colours" => self.ul_clrs = vals, 253 | _ => return Err(Error(opt.into())), 254 | } 255 | Ok(()) 256 | } 257 | } 258 | -------------------------------------------------------------------------------- /src/input.rs: -------------------------------------------------------------------------------- 1 | // File to define the valid string type we are using for display. 2 | // By: Curtis Jones 3 | // Started on: September 07, 2020 4 | // 5 | 6 | use anyhow::Result; 7 | use std::{ 8 | collections::{hash_map::Entry, HashMap}, 9 | mem::MaybeUninit, 10 | }; 11 | use x11_dl::{xft, xlib, xrender::XGlyphInfo}; 12 | 13 | /// Utility funtion so get the index of the first font that has a glyph for the provided char. 14 | /// 15 | /// # Arguments 16 | /// * xft: -> reference to the xft lib. 17 | /// * dpy: -> pointer to the XDisplay object. 18 | /// * fonts: -> list of fonts available to the bar. 19 | /// * chr: -> character we are checking. 20 | /// 21 | /// # Output 22 | /// Index of the default font in the Bar.fonts field as a usize. 23 | unsafe fn default_font_idx( 24 | xft: &xft::Xft, 25 | dpy: *mut xlib::Display, 26 | fonts: &[*mut xft::XftFont], 27 | chr: char, 28 | ) -> usize { 29 | fonts 30 | .iter() 31 | // Get the position of the first font with a glyph for chr. 32 | .position(|&f| (xft.XftCharExists)(dpy, f, chr as u32) > 0) 33 | // If none are found then we default to 0. 34 | .unwrap_or(0) 35 | } 36 | 37 | /// Utility funtion so get the pixel width of a chunk of characters in a given font. 38 | /// 39 | /// # Arguments 40 | /// * xft: -> reference to the xft lib. 41 | /// * dpy: -> pointer to the XDisplay object. 42 | /// * font -> pointer to the font we are checking the width of the string in. 43 | /// * string -> refernce to the string we want the width of. 44 | /// 45 | /// # Output 46 | /// Returns a c_uint representing the pixel with of the arg. 47 | unsafe fn string_pixel_width( 48 | xft: &xft::Xft, 49 | dpy: *mut xlib::Display, 50 | font: *mut xft::XftFont, 51 | string: &str, 52 | ) -> u32 { 53 | // Rust gets mad if you don't initialize a variable before providing it as a function arg so we 54 | // lie to the rust compiler. 55 | let mut extents: MaybeUninit = MaybeUninit::uninit(); 56 | // let mut extents: XGlyphInfo = init!(); 57 | 58 | // Getting just so much info about the glyphs to be printed for the string arg when using the 59 | // font provided. 60 | (xft.XftTextExtentsUtf8)( 61 | dpy, 62 | font, 63 | string.as_bytes().as_ptr() as *mut u8, 64 | string.as_bytes().len() as i32, 65 | // &mut extents, 66 | extents.as_mut_ptr(), 67 | ); 68 | 69 | let extents = extents.assume_init(); 70 | 71 | // All that nice info and we just need the width. 72 | extents.width as u32 73 | } 74 | 75 | /// Private struct to contain colour information for the status bar. 76 | /// Simpler than storing seperate fields as individual Vecs. 77 | pub struct ColourPalette { 78 | /// Colours for the background highlight. 79 | pub background: Vec, 80 | /// Colours for the underline highlight. 81 | pub underline: Vec, 82 | /// Colours for the fonts. 83 | pub font: Vec, 84 | } 85 | 86 | impl ColourPalette { 87 | /// Simple helper function to get an empty ColourPalette object. 88 | /// 89 | /// # Output 90 | /// ColourPalette than can be added to later. 91 | pub fn empty() -> ColourPalette { 92 | ColourPalette { 93 | background: Vec::new(), 94 | underline: Vec::new(), 95 | font: Vec::new(), 96 | } 97 | } 98 | 99 | /// Simple helper function to free all of the colours contained in seperate vecs. 100 | /// 101 | /// # Arguments 102 | /// * xft: -> Reference to the Xft library for XftColorFree. 103 | /// * dpy: -> Pointer to the Display that the bar is using. 104 | /// * cmap: -> Pointer to the Colormap for the active Display. 105 | /// * visual: -> Pointer to the Visual for the active Display. 106 | pub unsafe fn destroy( 107 | &mut self, 108 | xft: &xft::Xft, 109 | dpy: *mut xlib::Display, 110 | cmap: xlib::Colormap, 111 | visual: *mut xlib::Visual, 112 | ) { 113 | // Do the background colours first. 114 | self.background 115 | .drain(..) 116 | .for_each(|mut col| (xft.XftColorFree)(dpy, visual, cmap, &mut col)); 117 | // Next the underline highlight colours. 118 | self.underline 119 | .drain(..) 120 | .for_each(|mut col| (xft.XftColorFree)(dpy, visual, cmap, &mut col)); 121 | // Finally we free our font colours. 122 | self.font 123 | .drain(..) 124 | .for_each(|mut col| (xft.XftColorFree)(dpy, visual, cmap, &mut col)); 125 | } 126 | } 127 | 128 | #[derive(Clone, Copy, Debug)] 129 | /// Private struct to use instead of a tuple of usize vals. Just helps to make things more explicit 130 | /// during parsing of inputs. Used for font colours and faces, as well as background and underline 131 | /// highlights. 132 | struct DisplayTemp { 133 | /// Index into the vector of available fonts or colours. 134 | idx: usize, 135 | /// Start character within the parsed input string. 136 | start: usize, 137 | /// End character within the parsed input string. 138 | end: usize, 139 | } 140 | 141 | impl DisplayTemp { 142 | /// Some pathological need I have for constructors is being filled here I guess. 143 | /// 144 | /// # Arguments 145 | /// * idx: -> see struct def. 146 | /// * start: -> see struct def. 147 | /// * end: -> see struct def. 148 | /// 149 | /// # Output 150 | /// DisplayTemp that has the values provided in the arguements. 151 | fn from(idx: usize, start: usize, end: usize) -> DisplayTemp { 152 | DisplayTemp { idx, start, end } 153 | } 154 | 155 | /// The parsing function when reading in font faces doesn't check for the default font that 156 | /// each char belongs to. So this function sorts through and checks for only the sections that 157 | /// were not previously explicitly set. 158 | /// 159 | /// # Arguments 160 | /// def: -> reference to a HashMap that is acting like a lookup table for the default face 161 | /// index for each char. 162 | /// expl: -> reference to the explicitly set font face values from the parse. With usize::MAX 163 | /// set for the sections to be filled in. 164 | /// text: -> the actual printed text for the Input to be made. 165 | /// 166 | /// # Output 167 | /// A vec of DisplayTemp representing the font_faces for the Input. 168 | fn default_font_faces( 169 | def: &HashMap, 170 | expl: &[DisplayTemp], 171 | text: &str, 172 | ) -> Result> { 173 | expl.iter() 174 | .try_fold(Vec::new(), |mut acc, dt| -> Result> { 175 | // usize::MAX is our key value to mean use default here. 176 | if dt.idx == usize::MAX { 177 | // Get the parts of the string within the DisplayTemp start and end. 178 | let chunk: String = text.chars().take(dt.end).skip(dt.start).collect(); 179 | 180 | // Temp value to use while counting. 181 | let mut tmp = DisplayTemp::from(0, dt.start, dt.start); 182 | 183 | // Generating the Vec of DisplayTemp values by folding over the chars. 184 | let mut res = chunk.chars().fold(Vec::new(), |mut ac, ch| { 185 | // Get a copy of the default index for the char 186 | let ch_idx = *def.get(&ch).expect("cant fail."); 187 | 188 | // If the default index is different from the tmp val index then we push our 189 | // tmp and start the new count. 190 | if ch_idx != tmp.idx { 191 | // We only do the push if we actually counted something. Otherwise just 192 | // start over by changing the font index. 193 | if tmp.start != tmp.end { 194 | ac.push(tmp); 195 | tmp.start = tmp.end; 196 | tmp.end = tmp.start + 1; 197 | } 198 | tmp.idx = ch_idx; 199 | } else { 200 | // If the default is the same as the idx for our tmp val then we just increment 201 | // the end value. 202 | tmp.end += 1; 203 | } 204 | 205 | // Gotta return something every loop. 206 | ac 207 | }); 208 | 209 | // Once the loop is done we push the tmp value on the end if it counted and 210 | // characters. 211 | if tmp.start != tmp.end { 212 | res.push(tmp); 213 | } 214 | 215 | // We use append here because it is possible to have generated multiple 216 | // DisplayTemps when using the default indexes. 217 | acc.append(&mut res); 218 | } else { 219 | // If we are not using the defaults then we just push the value. 220 | acc.push(*dt); 221 | } 222 | 223 | // Once we pushed the old DisplayTemp or generated default ones we return our acc Vec. 224 | Ok(acc) 225 | }) 226 | } 227 | } 228 | 229 | #[derive(Debug)] 230 | /// Private struct to contain display info for the underline and background highlight objects. 231 | /// No reason to have different structs as they would just end up repeating code. 232 | struct RectDisplayInfo { 233 | /// Index into the ColourPalette.{background or underline} vectors of colours for this section. 234 | idx: usize, 235 | /// Pixel x-value to start. 236 | start: usize, 237 | /// Pixel x-value to end. 238 | end: usize, 239 | } 240 | 241 | impl RectDisplayInfo { 242 | /// Function to generate a list of background or underline highlight instructions from the text 243 | /// and the text_display instructions. 244 | /// 245 | /// # Arguments 246 | /// * xft: -> Reference to the xft library for the string_pixel_width function. 247 | /// * dpy: -> Pointer to the xlib Display for the string_pixel_width function. 248 | /// * fonts: -> List of fonts available to use, for the string_pixel_width function. 249 | /// * rect_display_types: -> List of DisplayTemps for the backgrounds/highlights we are 250 | /// converting. 251 | /// * text_display: -> List of reference FontDisplayInfo objects used to get the string chunks. 252 | /// * text: -> Actual chars that will be displayed. 253 | /// 254 | /// # Output 255 | /// Returns the converted list of RectDisplayInfo objects for the highlights or backgrounds. 256 | unsafe fn gen_list( 257 | xft: &xft::Xft, 258 | dpy: *mut xlib::Display, 259 | fonts: &[*mut xft::XftFont], 260 | rect_display_types: &[DisplayTemp], 261 | text_display: &[FontDisplayInfo], 262 | text: &str, 263 | ) -> Vec { 264 | // Only need one loop, going over the chars and converting from char indexes to pixel vals. 265 | rect_display_types 266 | .iter() 267 | .fold(Vec::new(), |mut acc, rect_oj| { 268 | // Generate the start pixel val. 269 | let mut start = 0; 270 | for font_display in text_display.iter() { 271 | if font_display.start == rect_oj.start { 272 | // If the starts are equal we are done. 273 | break; 274 | } else if font_display.end > rect_oj.start { 275 | // If the end is larger than our start we can get the width up to our start 276 | // and we are done. 277 | let chunk: String = text 278 | .chars() 279 | .skip(font_display.start) 280 | .take(rect_oj.start - font_display.start) 281 | .collect(); 282 | start += string_pixel_width(xft, dpy, fonts[font_display.face_idx], &chunk); 283 | break; 284 | } else { 285 | // Other wise just add in the pixel withd of the whole font display. 286 | let chunk: String = text 287 | .chars() 288 | .take(font_display.end) 289 | .skip(font_display.start) 290 | .collect(); 291 | start += string_pixel_width(xft, dpy, fonts[font_display.face_idx], &chunk); 292 | } 293 | } 294 | // Generate the end pixel val. 295 | let mut end = 0; 296 | for font_display in text_display.iter() { 297 | if font_display.start == rect_oj.end { 298 | // If the start matches our end we grab one char width and we are done. 299 | let chunk: String = text.chars().skip(font_display.start).take(1).collect(); 300 | end += string_pixel_width(xft, dpy, fonts[font_display.face_idx], &chunk); 301 | break; 302 | } else if font_display.end <= rect_oj.end { 303 | // If the ends match or the objects is less than ours we get the width of 304 | // the whole font object in pixels. 305 | let chunk: String = text 306 | .chars() 307 | .take(font_display.end) 308 | .skip(font_display.start) 309 | .collect(); 310 | end += string_pixel_width(xft, dpy, fonts[font_display.face_idx], &chunk); 311 | } else { 312 | // Otherwise we just get the width up to the end value our our object and 313 | // convert it to pixels and finish up. 314 | let chunk: String = text 315 | .chars() 316 | .skip(font_display.start) 317 | .take(rect_oj.end - font_display.start) 318 | .collect(); 319 | end += string_pixel_width(xft, dpy, fonts[font_display.face_idx], &chunk); 320 | break; 321 | } 322 | } 323 | // Once we have our start and end vals moved to pixels we can make the 324 | // RectDisplayInfo object with the idx from the current ref val. 325 | acc.push(RectDisplayInfo { 326 | idx: rect_oj.idx, 327 | start: start as usize, 328 | end: end as usize, 329 | }); 330 | acc 331 | }) 332 | } 333 | } 334 | 335 | #[derive(Debug)] 336 | /// Private struct used to hold the data for drawing the text of a Input to the display. 337 | struct FontDisplayInfo { 338 | /// Index in the faces field of a Bar struct to be used. 339 | face_idx: usize, 340 | /// Index in the palette.font field of a Bar struct to be used. 341 | col_idx: usize, 342 | /// First char to include in formatting. 343 | start: usize, 344 | /// Include upto but not this value. 345 | end: usize, 346 | } 347 | 348 | impl FontDisplayInfo { 349 | /// General helper function to transition from our basic tuples into info we can use for 350 | /// font display. 351 | /// 352 | /// # Arguments 353 | /// * col_objs: -> The instructions for the different font colors to use on different sections 354 | /// of the text. 355 | /// * face_objs: -> The instructions for different font faces to use on different sections of 356 | /// the text. 357 | /// 358 | /// # Output 359 | /// List of instructions for both font colour and font face to use. Seperated into the minimum 360 | /// different sets of instructions to use. 361 | fn generate_list(col_objs: &[DisplayTemp], face_objs: &[DisplayTemp]) -> Vec { 362 | let mut strt_idx = 0; 363 | // Loop through the color objects and for each one generate the mixed FontDisplayInfo 364 | // object until the start of the face object is larger than the start of the colour object. 365 | col_objs.iter().fold(Vec::new(), |mut acc, cl_oj| { 366 | // Temp var is a vector because sometimes we create multiple objects. 367 | let mut tmp: Vec = Vec::new(); 368 | // We skip until the end of the previous iteration to try and save compute. 369 | for (i, fc_oj) in face_objs.iter().enumerate().skip(strt_idx) { 370 | let start = if fc_oj.start <= cl_oj.start { 371 | cl_oj.start 372 | } else { 373 | fc_oj.start 374 | }; 375 | // The end is either the end of the face val if we need more loops or the end of 376 | // the colour val if we are gonna be done. 377 | let end = if fc_oj.end >= cl_oj.end { 378 | cl_oj.end 379 | } else { 380 | fc_oj.end 381 | }; 382 | // If start and end are the same then there is no point in pushing. 383 | if start != end { 384 | tmp.push(FontDisplayInfo { 385 | face_idx: fc_oj.idx, 386 | col_idx: cl_oj.idx, 387 | start, 388 | end, 389 | }); 390 | } 391 | // If we are at the end of the color object then we can break the face object loop 392 | // and set the start val for next iteration. 393 | if fc_oj.end >= cl_oj.end { 394 | strt_idx = i; 395 | break; 396 | } 397 | } 398 | // Appending is just push for another vec of similar objects. 399 | acc.append(&mut tmp); 400 | acc 401 | }) 402 | } 403 | } 404 | 405 | /// Small private enum to help when parsing the inital input. 406 | enum IndexType { 407 | BackgroundColour, 408 | HighlightColour, 409 | FontColour, 410 | FontFace, 411 | } 412 | 413 | #[derive(Debug)] 414 | /// Main struct to hold display info for text on the bar. 415 | /// Has references needed to display the text, backgrounds, and underlines. 416 | pub struct Input { 417 | // The actual text to be drawn. 418 | text: String, 419 | // Reference for which chars to display in which font or colour. 420 | text_display: Vec, 421 | // Reference for background highlights to draw with pixel val start and ends. 422 | backgrounds: Vec, 423 | // Reference for underline highlights to draw with pixel val start and ends. 424 | underlines: Vec, 425 | } 426 | 427 | impl Input { 428 | /// Helper to clear 429 | pub fn clear(&mut self) { 430 | self.text.clear(); 431 | self.text_display.clear(); 432 | self.backgrounds.clear(); 433 | self.underlines.clear(); 434 | } 435 | /// Small helper function to generate an emply Input. 436 | /// 437 | /// # Output 438 | /// Empty Input object to use as placeholder. 439 | pub fn empty() -> Input { 440 | Input { 441 | text: String::new(), 442 | text_display: Vec::new(), 443 | backgrounds: Vec::new(), 444 | underlines: Vec::new(), 445 | } 446 | } 447 | 448 | /// Draw the valid string onto the XftDraw object using the different struct fields. Starting 449 | /// with the background rectangles and then finishing with the text. 450 | /// 451 | /// # Arguments 452 | /// * xft: -> Reference to the xft library and it's functions. 453 | /// * dpy: -> Pointer to the XDisplay we are drawing to. 454 | /// * draw: -> Pointer to the XftDraw object we are drawing to. 455 | /// * colours: -> Reference to the ColourPalette object holding the colours available. 456 | /// * fonts: -> Reference to the list of fonts available. 457 | /// * start_x: -> X-value to start drawing at. 458 | /// * font_y: -> Y-value to draw the text at. 459 | /// * height: -> Height of the bar. 460 | /// * hlt_hgt: -> Height of the underline highlights. 461 | /// 462 | #[allow(clippy::too_many_arguments)] 463 | pub unsafe fn draw( 464 | &self, 465 | xft: &xft::Xft, 466 | dpy: *mut xlib::Display, 467 | draw: *mut xft::XftDraw, 468 | colours: &ColourPalette, 469 | fonts: &[*mut xft::XftFont], 470 | start_x: i32, 471 | font_y: i32, 472 | height: u32, 473 | hlt_hgt: u32, 474 | ) { 475 | // Displaying the backgrounds first. 476 | self.backgrounds.iter().for_each(|b| { 477 | (xft.XftDrawRect)( 478 | draw, 479 | &colours.background[b.idx], 480 | start_x + b.start as i32, 481 | 0, 482 | (b.end - b.start) as u32, 483 | height, 484 | ); 485 | }); 486 | 487 | // Display the highlights next. 488 | self.underlines.iter().for_each(|h| { 489 | (xft.XftDrawRect)( 490 | draw, 491 | &colours.underline[h.idx], 492 | start_x + h.start as i32, 493 | (height - hlt_hgt) as i32, 494 | (h.end - h.start) as u32, 495 | hlt_hgt, 496 | ); 497 | }); 498 | 499 | // Do the font bits last. 500 | self.text_display.iter().fold(0, |acc, td| { 501 | let chunk: String = self.text.chars().take(td.end).skip(td.start).collect(); 502 | (xft.XftDrawStringUtf8)( 503 | draw, 504 | &colours.font[td.col_idx], 505 | fonts[td.face_idx], 506 | start_x + acc, 507 | font_y, 508 | chunk.as_bytes().as_ptr() as *const u8, 509 | chunk.as_bytes().len() as i32, 510 | ); 511 | acc + string_pixel_width(xft, dpy, fonts[td.face_idx], &chunk) as i32 512 | }); 513 | } 514 | 515 | /// Small helper function to get the pixel length of a Input object. 516 | /// 517 | /// # Arguments 518 | /// * xft: -> reference to the link to the Xft library. 519 | /// * dpy: -> pointer to the XDisplay object we are displaying to. 520 | /// * fonts: -> list of pointers to our XftFont objects available to use. 521 | /// 522 | /// # Output 523 | /// c_uint representing the pixel length of the self Input. 524 | pub unsafe fn len( 525 | &self, 526 | xft: &xft::Xft, 527 | dpy: *mut xlib::Display, 528 | fonts: &[*mut xft::XftFont], 529 | ) -> u32 { 530 | self.text_display.iter().fold(0, |acc, fd| { 531 | let chunk: String = self.text.chars().take(fd.end).skip(fd.start).collect(); 532 | acc + string_pixel_width(xft, dpy, fonts[fd.face_idx], &chunk) 533 | }) 534 | } 535 | 536 | /// Function to parse a string and develop a Input. 537 | /// Tries to do most of it's work in one loop over the input. 538 | /// 539 | /// # Arguments 540 | /// * xft: -> reference to the link to the Xft library. 541 | /// * dpy: -> pointer to the XDisplay object we are displaying to. 542 | /// * fonts: -> list of pointers to our XftFont objects available to use. 543 | /// * colours: -> reference to the ColourPalette available to use. 544 | /// * input: -> the string we are reading from to develop a Input. 545 | /// 546 | /// # Output 547 | /// Input made based on the input String object. 548 | pub fn parse_string( 549 | &mut self, 550 | xft: &xft::Xft, 551 | dpy: *mut xlib::Display, 552 | fonts: &[*mut xft::XftFont], 553 | def_font_map: &mut HashMap, 554 | colours: &ColourPalette, 555 | input: &str, 556 | ) -> Result<()> { 557 | // clear self to start. 558 | self.clear(); 559 | 560 | // Loop vars. 561 | let mut in_format_block = false; 562 | let mut next_is_index = false; 563 | let mut closing_block = false; 564 | let mut index_type = IndexType::FontColour; 565 | 566 | // Result vars. 567 | let mut text = String::new(); 568 | let mut background_vec: Vec = Vec::new(); 569 | let mut underline_vec: Vec = Vec::new(); 570 | let mut font_colour_vec: Vec = Vec::new(); 571 | let mut font_face_vec: Vec = Vec::new(); 572 | 573 | // Temp vars. 574 | let mut count: usize = 0; 575 | let mut bckgrnd_tmp: DisplayTemp = DisplayTemp::from(usize::MAX, 0, 0); 576 | let mut underln_tmp: DisplayTemp = DisplayTemp::from(usize::MAX, 0, 0); 577 | let mut fcol_tmp: DisplayTemp = DisplayTemp::from(0, 0, 0); 578 | let mut fface_tmp: DisplayTemp = DisplayTemp::from(usize::MAX, 0, 0); 579 | 580 | // Big ass loop to proces the input. 581 | for ch in input.chars() { 582 | if in_format_block { 583 | if closing_block { 584 | match ch { 585 | // B is the marker for the background highlight. 586 | 'B' => { 587 | bckgrnd_tmp.end = count; 588 | background_vec.push(bckgrnd_tmp); 589 | bckgrnd_tmp = DisplayTemp::from(usize::MAX, count, 0); 590 | } 591 | // H is the marker for the underline highlight. 592 | 'H' => { 593 | underln_tmp.end = count; 594 | underline_vec.push(underln_tmp); 595 | underln_tmp = DisplayTemp::from(usize::MAX, count, 0); 596 | } 597 | // F is the marker for the font colour. 598 | 'F' => { 599 | fcol_tmp.end = count; 600 | font_colour_vec.push(fcol_tmp); 601 | fcol_tmp = DisplayTemp::from(0, count, 0); 602 | } 603 | // F is the marker for the font face. 604 | 'f' => { 605 | fface_tmp.end = count; 606 | font_face_vec.push(fface_tmp); 607 | fface_tmp = DisplayTemp::from(usize::MAX, count, 0); 608 | } 609 | // End the block if we hit a close bracket. 610 | '}' => { 611 | in_format_block = false; 612 | closing_block = false; 613 | } 614 | _ => (), 615 | } 616 | // When we hit any of the markers the next char will be an index val so we 617 | // start to process it. 618 | } else if next_is_index { 619 | // Converting to a base 10 digit creates a nice limit of 10 fonts, 620 | // font-colours, background-colours, and highlight-colours. 621 | if let Some(d) = ch.to_digit(10) { 622 | // All four index types are basically the same, check to make sure the 623 | // index is valid & if it is we push our current count onto the vec and 624 | // start a new tmp count. 625 | match index_type { 626 | IndexType::BackgroundColour => { 627 | if d > (colours.background.len() - 1) as u32 { 628 | eprintln!("Invalid background colour index -- TOO LARGE."); 629 | } else { 630 | bckgrnd_tmp.end = count; 631 | background_vec.push(bckgrnd_tmp); 632 | bckgrnd_tmp = DisplayTemp::from(d as usize, count, 0); 633 | } 634 | } 635 | IndexType::HighlightColour => { 636 | if d > (colours.underline.len() - 1) as u32 { 637 | eprintln!("Invalid underline colour index -- TOO LARGE."); 638 | } else { 639 | underln_tmp.end = count; 640 | underline_vec.push(underln_tmp); 641 | underln_tmp = DisplayTemp::from(d as usize, count, 0); 642 | } 643 | } 644 | IndexType::FontColour => { 645 | if d > (colours.font.len() - 1) as u32 { 646 | eprintln!("Invalid font colour index -- TOO LARGE."); 647 | } else { 648 | fcol_tmp.end = count; 649 | font_colour_vec.push(fcol_tmp); 650 | fcol_tmp = DisplayTemp::from(d as usize, count, 0); 651 | } 652 | } 653 | IndexType::FontFace => { 654 | if d > (fonts.len() - 1) as u32 { 655 | eprintln!("Invalid font face index -- TOO LARGE."); 656 | } else { 657 | fface_tmp.end = count; 658 | font_face_vec.push(fface_tmp); 659 | fface_tmp = DisplayTemp::from(d as usize, count, 0); 660 | } 661 | } 662 | } 663 | next_is_index = false; 664 | } 665 | } else { 666 | // If we are in a format block and have no other info we sort through and 667 | // determine if we are in a closing or opening block. And what kind of format 668 | // specifically. 669 | match ch { 670 | '/' => closing_block = true, 671 | 'B' => { 672 | next_is_index = true; 673 | index_type = IndexType::BackgroundColour; 674 | } 675 | 'H' => { 676 | next_is_index = true; 677 | index_type = IndexType::HighlightColour; 678 | } 679 | 'F' => { 680 | next_is_index = true; 681 | index_type = IndexType::FontColour; 682 | } 683 | 'f' => { 684 | next_is_index = true; 685 | index_type = IndexType::FontFace; 686 | } 687 | '}' => in_format_block = false, 688 | _ => (), 689 | } 690 | } 691 | } else { 692 | // If we are not in a format block either it's a valid char or the beginning of a 693 | // new format block. We also take the chance to get the default valid font for the 694 | // char. 695 | match ch { 696 | '{' => in_format_block = true, 697 | _ => { 698 | count += 1; 699 | text.push(ch); 700 | if let Entry::Vacant(v) = def_font_map.entry(ch) { 701 | v.insert(unsafe { default_font_idx(xft, dpy, fonts, ch) }); 702 | } 703 | } 704 | } 705 | } 706 | } 707 | 708 | // Set the end of the tmp var to the end count to finish off the tmp vars. 709 | bckgrnd_tmp.end = count; 710 | underln_tmp.end = count; 711 | fcol_tmp.end = count; 712 | fface_tmp.end = count; 713 | 714 | // Push the last val onto all of our count vecs. 715 | if bckgrnd_tmp.end != bckgrnd_tmp.start { 716 | background_vec.push(bckgrnd_tmp); 717 | } 718 | if underln_tmp.end != underln_tmp.start { 719 | underline_vec.push(underln_tmp); 720 | } 721 | if fcol_tmp.end != fcol_tmp.start { 722 | font_colour_vec.push(fcol_tmp); 723 | } 724 | if fface_tmp.end != fface_tmp.start { 725 | font_face_vec.push(fface_tmp); 726 | } 727 | 728 | // usize::MAX is our default value we need to get rid of it from background vec. 729 | let background_vec: Vec = background_vec 730 | .iter() 731 | .filter_map(|&bk_oj| { 732 | if bk_oj.idx != usize::MAX { 733 | Some(bk_oj) 734 | } else { 735 | None 736 | } 737 | }) 738 | .collect(); 739 | 740 | // usize::MAX is our default value we need to get rid of it from highlight vec. 741 | let underline_vec: Vec = underline_vec 742 | .iter() 743 | .filter_map(|&ul_oj| { 744 | if ul_oj.idx != usize::MAX { 745 | Some(ul_oj) 746 | } else { 747 | None 748 | } 749 | }) 750 | .collect(); 751 | 752 | // Fill in the default font faces. 753 | let merg_fcs = DisplayTemp::default_font_faces(def_font_map, &font_face_vec, &text)?; 754 | 755 | // Gen the final FontDisplayInfo objects. 756 | let text_display = FontDisplayInfo::generate_list(&font_colour_vec, &merg_fcs); 757 | 758 | // Gen the final underline RectDisplayInfo objects. 759 | let underlines = unsafe { 760 | RectDisplayInfo::gen_list(xft, dpy, fonts, &underline_vec, &text_display, &text) 761 | }; 762 | 763 | // Gen the final background RectDisplayInfo objects. 764 | let backgrounds = unsafe { 765 | RectDisplayInfo::gen_list(xft, dpy, fonts, &background_vec, &text_display, &text) 766 | }; 767 | 768 | // Return our valid string using the objects we generated previously. 769 | self.text = text; 770 | self.text_display = text_display; 771 | self.underlines = underlines; 772 | self.backgrounds = backgrounds; 773 | Ok(()) 774 | } 775 | } 776 | -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | #![allow( 2 | clippy::missing_safety_doc, 3 | clippy::len_without_is_empty, 4 | clippy::not_unsafe_ptr_arg_deref 5 | )] 6 | // this file is basically just to weave together the seperate mod files. 7 | // also the pub use statements bring objects over to our main file. 8 | // By: Curtis Jones 9 | // Started on: September 07, 2020 10 | // 11 | 12 | /// Main meat of the program, where all the direct access to Xlib lives. 13 | pub mod bar; 14 | 15 | /// Parsing the config file and adjusting based on command line args provided. 16 | pub mod config; 17 | 18 | /// Turning basic random characters into Input struct that the Bar struct can display to the 19 | /// screen. 20 | pub mod input; 21 | 22 | /// Module containing optional additions to the bar. 23 | pub mod optional; 24 | 25 | /// To be used by the binary crate. 26 | pub use bar::Bar; 27 | pub use config::Config; 28 | -------------------------------------------------------------------------------- /src/main.rs: -------------------------------------------------------------------------------- 1 | // Super simple status bar written in rust with direct Xlib. 2 | // By: Curtis Jones 3 | // Started on Ausust 06, 2020 4 | 5 | use anyhow::Result; 6 | use unibar::{Bar, Config}; 7 | 8 | fn main() -> Result<()> { 9 | // Generate configuration from a file and any command line args. 10 | let conf = Config::from_args()?; 11 | 12 | // Generate a new empty bar object. 13 | let mut bar = Bar::new()?; 14 | 15 | // Apply the configuration loaded previously. 16 | bar.load_config(conf)?; 17 | 18 | // Initialize the window and set any specific Atoms needed to get the bar displayed correctly. 19 | bar.init()?; 20 | 21 | // Here is where the real work is done: 22 | // -> checking for any exit signals that may have come in from the os. 23 | // -> parsing any input on stdin to generate new text to display. 24 | // -> deal with any XEvents like clicks or messages. 25 | bar.event_loop()?; 26 | 27 | // Because we are using C libraries alot of the objects we load need to be freed manually, so 28 | // we do that here before exiting with the code provided as arg. 29 | bar.close(0); 30 | } 31 | -------------------------------------------------------------------------------- /src/optional/kill_me.rs: -------------------------------------------------------------------------------- 1 | //! Module to kill a process with a list of ids 2 | //! 3 | //! 4 | 5 | use std::process::Command; 6 | 7 | pub struct KillMeModule { 8 | command: String, 9 | args: Vec, 10 | list: Vec, 11 | } 12 | 13 | impl KillMeModule { 14 | pub fn new(cmd: String) -> Self { 15 | let mut command = String::new(); 16 | let mut args = Vec::new(); 17 | for (i, s) in cmd 18 | .split_whitespace() 19 | .enumerate() 20 | .map(|(i, s)| (i, s.to_owned())) 21 | { 22 | if i == 0 { 23 | command = s; 24 | } else { 25 | args.push(s); 26 | } 27 | } 28 | Self { 29 | command, 30 | args, 31 | list: Vec::new(), 32 | } 33 | } 34 | 35 | pub fn kill_all(&mut self) { 36 | for id in self.list.drain(..) { 37 | match Command::new(&self.command) 38 | .args(&self.args) 39 | .arg(id.to_string()) 40 | .output() 41 | { 42 | Ok(out) if out.status.success() => eprintln!("Killed process: {}", id), 43 | Ok(_) => eprintln!("Failed to kill process: {}", id), 44 | Err(e) => eprintln!("Error trying to kill process (id: {}): {}", id, e), 45 | } 46 | } 47 | } 48 | 49 | pub fn push(&mut self, id: u32) { 50 | if !self.list.contains(&id) { 51 | self.list.push(id); 52 | } 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /src/optional/mod.rs: -------------------------------------------------------------------------------- 1 | //! Optional extra modules that are disabled by default in the bar. 2 | //! 3 | //! # Implemented 4 | //! 5 | //! * Kill Me => Provide unibar process-ids that it will run a command against on close. The 6 | //! default is just the kill command. 7 | //! 8 | //! # Planned 9 | //! 10 | //! 11 | 12 | pub mod kill_me; 13 | --------------------------------------------------------------------------------