├── .github └── FUNDING.yml ├── .gitignore ├── LICENSE ├── README.md ├── bar.jsx ├── config.json ├── lib ├── App.jsx ├── Battery.jsx ├── Cpu.jsx ├── DateTime.jsx ├── Desktop.jsx ├── Dnd.jsx ├── Error.jsx ├── Wifi.jsx ├── parse.jsx └── styles.jsx ├── scripts ├── spaces.sh ├── status.sh └── windows.sh ├── spaces.jsx ├── ss.png ├── status.jsx ├── widget.json └── windows.jsx /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | # These are supported funding model platforms 2 | 3 | github: # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2] 4 | patreon: # kkga 5 | open_collective: # Replace with a single Open Collective username 6 | ko_fi: # Replace with a single Ko-fi username 7 | tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel 8 | community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry 9 | liberapay: # Replace with a single Liberapay username 10 | issuehunt: # Replace with a single IssueHunt username 11 | otechie: # Replace with a single Otechie username 12 | custom: # Replace with up to 4 custom sponsorship URLs e.g., ['link1', 'link2'] 13 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | 8 | # Runtime data 9 | pids 10 | *.pid 11 | *.seed 12 | *.pid.lock 13 | 14 | # Directory for instrumented libs generated by jscoverage/JSCover 15 | lib-cov 16 | 17 | # Coverage directory used by tools like istanbul 18 | coverage 19 | 20 | # nyc test coverage 21 | .nyc_output 22 | 23 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) 24 | .grunt 25 | 26 | # Bower dependency directory (https://bower.io/) 27 | bower_components 28 | 29 | # node-waf configuration 30 | .lock-wscript 31 | 32 | # Compiled binary addons (https://nodejs.org/api/addons.html) 33 | build/Release 34 | 35 | # Dependency directories 36 | node_modules/ 37 | jspm_packages/ 38 | 39 | # TypeScript v1 declaration files 40 | typings/ 41 | 42 | # Optional npm cache directory 43 | .npm 44 | 45 | # Optional eslint cache 46 | .eslintcache 47 | 48 | # Optional REPL history 49 | .node_repl_history 50 | 51 | # Output of 'npm pack' 52 | *.tgz 53 | 54 | # Yarn Integrity file 55 | .yarn-integrity 56 | 57 | # dotenv environment variables file 58 | .env 59 | 60 | # next.js build output 61 | .next 62 | 63 | # OS Files 64 | .DS_Store 65 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 Gadzhi Kharkharov 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # nibar 2 | 3 | Simple [Übersicht](https://github.com/felixhageloh/uebersicht) widget status bar with [yabai](https://github.com/koekeishiya/yabai) support. 4 | 5 | Originally forked from https://github.com/ajdnik/powerbar. I made it work with yabai and tweaked the aesthetics to match my preference. 6 | 7 | ![img](./ss.png) 8 | 9 | ## Installation 10 | 11 | Clone this repo to your Übersicht widgets directory. 12 | 13 | ```bash 14 | # assuming your widgets are in the default Übersicht location 15 | $ git clone https://github.com/kkga/nibar $HOME/Library/Application\ Support/Übersicht/widgets/nibar 16 | ``` 17 | 18 | ## Dependencies 19 | 20 | - [SF Symbols](https://developer.apple.com/sf-symbols/) (optional) — used for symbols in the statusbar widget 21 | 22 | ## Usage 23 | 24 | ### Refreshing yabai workspaces widget 25 | 26 | The workspaces widget is not refreshing automatically (to preserve battery). Add these lines at the end of your `yabairc` to utilize [yabai's signals](https://github.com/koekeishiya/yabai/wiki/Commands#automation-with-rules-and-signals) for auto-updating the widget whenever a workspace is changed: 27 | 28 | ```sh 29 | yabai -m signal --add event=space_changed \ 30 | action="osascript -e 'tell application id \"tracesOf.Uebersicht\" to refresh widget id \"nibar-spaces-jsx\"'" 31 | 32 | # if using multple displays, add an additional rule for "display_changed" event 33 | yabai -m signal --add event=display_changed \ 34 | action="osascript -e 'tell application id \"tracesOf.Uebersicht\" to refresh widget id \"nibar-spaces-jsx\"'" 35 | ``` 36 | 37 | ```sh 38 | # add these rules to auto-update the "windows" widget 39 | yabai -m signal --add event=window_focused \ 40 | action="osascript -e 'tell application id \"tracesOf.Uebersicht\" to refresh widget id \"nibar-windows-jsx\"'" 41 | yabai -m signal --add event=window_destroyed \ 42 | action="osascript -e 'tell application id \"tracesOf.Uebersicht\" to refresh widget id \"nibar-windows-jsx\"'" 43 | yabai -m signal --add event=window_created \ 44 | action="osascript -e 'tell application id \"tracesOf.Uebersicht\" to refresh widget id \"nibar-windows-jsx\"'" 45 | yabai -m signal --add event=space_changed \ 46 | action="osascript -e 'tell application id \"tracesOf.Uebersicht\" to refresh widget id \"nibar-windows-jsx\"'" 47 | ``` 48 | -------------------------------------------------------------------------------- /bar.jsx: -------------------------------------------------------------------------------- 1 | import styles from "./lib/styles.jsx"; 2 | 3 | const style = { 4 | background: styles.colors.bg, 5 | cursor: "default", 6 | userSelect: "none", 7 | zIndex: "-1", 8 | width: "100%", 9 | height: "20px", 10 | position: "fixed", 11 | overflow: "hidden", 12 | top: "0px", 13 | right: "0px", 14 | left: "0px" 15 | }; 16 | 17 | export const refreshFrequency = 1000000; 18 | 19 | export const render = ({ output }) => { 20 | return
; 21 | }; 22 | 23 | export default null; 24 | -------------------------------------------------------------------------------- /config.json: -------------------------------------------------------------------------------- 1 | { 2 | "windows_offset_left": "280px", 3 | "windows_label_max_width": "30ch" 4 | } 5 | -------------------------------------------------------------------------------- /lib/App.jsx: -------------------------------------------------------------------------------- 1 | import styles from "./styles.jsx"; 2 | import run from "uebersicht"; 3 | import config from '../config.json' 4 | 5 | const containerStyle = { 6 | display: "grid", 7 | gridAutoFlow: "column", 8 | gridGap: "3px" 9 | }; 10 | 11 | const desktopStyle = { 12 | maxWidth: config.windows_max_win, 13 | overflow: "hidden" 14 | 15 | }; 16 | 17 | 18 | const renderWindow = (app, focused, visible) => { 19 | let contentStyle = JSON.parse(JSON.stringify(desktopStyle)); 20 | if (focused == 1) { 21 | contentStyle.color = styles.colors.fg; 22 | contentStyle.fontWeight = "bold"; 23 | } else if (visible == 1) { 24 | contentStyle.color = styles.colors.fg; 25 | } 26 | return ( 27 |
28 | {focused ? "[" :   } 29 | {app} 30 | {focused ? "]" :   } 31 |
32 | ); 33 | }; 34 | 35 | 36 | const render = ({ output }) => { 37 | if (typeof output === "undefined") return null; 38 | 39 | const windows = []; 40 | 41 | output.forEach(function(window) { 42 | windows.push(renderWindow(window.app, window.focused, window.visible)); 43 | }); 44 | return ( 45 |
46 | {windows} 47 |
48 | ); 49 | }; 50 | 51 | export default render; 52 | -------------------------------------------------------------------------------- /lib/Battery.jsx: -------------------------------------------------------------------------------- 1 | import styles from "./styles.jsx"; 2 | 3 | const render = ({ output }) => { 4 | let charging = output.charging; 5 | let percentage = output.percentage; 6 | let remaining = output.remaining; 7 | return ( 8 |
9 |
16 | {charging ? "􀋦" : null} {percentage}% 17 |
18 |
19 | ); 20 | }; 21 | 22 | export default render; 23 | -------------------------------------------------------------------------------- /lib/Cpu.jsx: -------------------------------------------------------------------------------- 1 | import styles from "./styles.jsx"; 2 | 3 | const render = ({ output }) => { 4 | if (typeof output === "undefined") return null; 5 | return ( 6 |
3 ? { color: styles.colors.red } : null}> 7 | 􀍽 {output.loadAverage} 8 |
9 | ); 10 | }; 11 | 12 | export default render; 13 | -------------------------------------------------------------------------------- /lib/DateTime.jsx: -------------------------------------------------------------------------------- 1 | const render = ({ output }) => { 2 | if (typeof output === "undefined") return null; 3 | return ( 4 |
5 | {output.date} 6 |   7 | {output.time} 8 |
9 | ); 10 | }; 11 | 12 | export default render; 13 | -------------------------------------------------------------------------------- /lib/Desktop.jsx: -------------------------------------------------------------------------------- 1 | import styles from "./styles.jsx"; 2 | import run from "uebersicht"; 3 | 4 | const containerStyle = { 5 | display: "grid", 6 | gridAutoFlow: "column", 7 | gridGap: "8px" 8 | }; 9 | 10 | const desktopStyle = { 11 | width: "3ch", 12 | }; 13 | 14 | 15 | const renderSpace = (index, focused, visible, windows) => { 16 | let contentStyle = JSON.parse(JSON.stringify(desktopStyle)); 17 | let hasWindows = windows.length > 0; 18 | if (focused == 1) { 19 | contentStyle.color = styles.colors.fg; 20 | contentStyle.fontWeight = "bold"; 21 | } else if (visible == 1) { 22 | contentStyle.color = styles.colors.fg; 23 | } 24 | return ( 25 |
26 | {focused ? "[" :   } 27 | {index} 28 | {!focused && hasWindows ? "°" : null } 29 | {focused ? "]" :   } 30 |
31 | ); 32 | }; 33 | 34 | const render = ({ output }) => { 35 | if (typeof output === "undefined") return null; 36 | 37 | const spaces = []; 38 | 39 | output.forEach(function(space) { 40 | spaces.push(renderSpace(space.index, space.focused, space.visible, space.windows)); 41 | }); 42 | 43 | return ( 44 |
45 | {spaces} 46 |
47 | ); 48 | }; 49 | 50 | export default render; 51 | -------------------------------------------------------------------------------- /lib/Dnd.jsx: -------------------------------------------------------------------------------- 1 | import styles from "./styles.jsx"; 2 | 3 | const style = { 4 | color: styles.colors.red 5 | } 6 | 7 | const render = ({ output }) => { 8 | if (output === 0) return null; 9 | return
􀆺
; 10 | }; 11 | 12 | export default render; 13 | -------------------------------------------------------------------------------- /lib/Error.jsx: -------------------------------------------------------------------------------- 1 | import styles from "./styles.jsx" 2 | 3 | const style = { 4 | color: styles.colors.red 5 | }; 6 | 7 | const render = ({ msg, side }) => { 8 | if (typeof msg === "undefined") return null; 9 | return
{msg}
; 10 | }; 11 | 12 | export default render; 13 | -------------------------------------------------------------------------------- /lib/Wifi.jsx: -------------------------------------------------------------------------------- 1 | const render = ({ output }) => { 2 | if (typeof output === "undefined") return null; 3 | const status = output.status; 4 | const ssid = output.ssid; 5 | if (status === "inactive") return
􀙈
; 6 | return
􀙇 {output.ssid}
; 7 | }; 8 | 9 | export default render; 10 | -------------------------------------------------------------------------------- /lib/parse.jsx: -------------------------------------------------------------------------------- 1 | const parse = data => { 2 | try { 3 | return JSON.parse(data); 4 | } catch (e) { 5 | return undefined; 6 | } 7 | }; 8 | 9 | export default parse; 10 | -------------------------------------------------------------------------------- /lib/styles.jsx: -------------------------------------------------------------------------------- 1 | export default { 2 | colors: { 3 | fg: "rgba(255,255,255,0.8)", 4 | dim: "rgba(255,255,255,0.6)", 5 | bg: "#1c1c1c", 6 | red: "#ff8700", 7 | accent: "#5fafaf" 8 | }, 9 | fontSize: "11px", 10 | lineHeight: "20px", 11 | fontFamily: "'Iosevka Custom', 'SF Mono', monospace" 12 | } 13 | -------------------------------------------------------------------------------- /scripts/spaces.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | PATH=/usr/local/bin/:$PATH 4 | 5 | # Check if yabai exists 6 | if ! [ -x "$(command -v yabai)" ]; then 7 | echo "{\"error\":\"yabai binary not found\"}" 8 | exit 1 9 | fi 10 | 11 | SPACES=$(yabai -m query --spaces) 12 | DISPLAYS=$(yabai -m query --displays) 13 | 14 | echo $(cat <<-EOF 15 | { 16 | "spaces": $SPACES, 17 | "displays": $DISPLAYS 18 | } 19 | EOF 20 | ) 21 | -------------------------------------------------------------------------------- /scripts/status.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | export LC_TIME="en_US.UTF-8" 4 | TIME=$(date +"%H:%M") 5 | DATE=$(date +"%a %d/%m") 6 | 7 | BATTERY_PERCENTAGE=$(pmset -g batt | egrep '([0-9]+\%).*' -o --colour=auto | cut -f1 -d'%') 8 | BATTERY_STATUS=$(pmset -g batt | grep "'.*'" | sed "s/'//g" | cut -c 18-19) 9 | BATTERY_REMAINING=$(pmset -g batt | egrep -o '([0-9]+%).*' | cut -d\ -f3) 10 | 11 | BATTERY_CHARGING="" 12 | if [ "$BATTERY_STATUS" == "Ba" ]; then 13 | BATTERY_CHARGING="false" 14 | elif [ "$BATTERY_STATUS" == "AC" ]; then 15 | BATTERY_CHARGING="true" 16 | fi 17 | 18 | LOAD_AVERAGE=$(sysctl -n vm.loadavg | awk '{print $2}') 19 | 20 | WIFI_STATUS=$(ifconfig en0 | grep status | cut -c 10-) 21 | WIFI_SSID=$(networksetup -getairportnetwork en0 | cut -c 24-) 22 | 23 | DND=$(defaults read com.apple.controlcenter "NSStatusItem Visible FocusModes") 24 | 25 | echo $(cat <<-EOF 26 | { 27 | "datetime": { 28 | "time": "$TIME", 29 | "date": "$DATE" 30 | }, 31 | "battery": { 32 | "percentage": $BATTERY_PERCENTAGE, 33 | "charging": $BATTERY_CHARGING, 34 | "remaining": "$BATTERY_REMAINING" 35 | }, 36 | "cpu": { 37 | "loadAverage": $LOAD_AVERAGE 38 | }, 39 | "wifi": { 40 | "status": "$WIFI_STATUS", 41 | "ssid": "$WIFI_SSID" 42 | }, 43 | "dnd": $DND 44 | } 45 | EOF 46 | ) 47 | -------------------------------------------------------------------------------- /scripts/windows.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | PATH=/usr/local/bin/:$PATH 4 | 5 | # Check if yabai exists 6 | if ! [ -x "$(command -v yabai)" ]; then 7 | echo "{\"error\":\"yabai binary not found\"}" 8 | exit 1 9 | fi 10 | 11 | WINDOWS=$(yabai -m query --windows --space mouse) 12 | 13 | echo $(cat <<-EOF 14 | { 15 | "windows": $WINDOWS 16 | } 17 | EOF 18 | ) 19 | -------------------------------------------------------------------------------- /spaces.jsx: -------------------------------------------------------------------------------- 1 | import Desktop from "./lib/Desktop.jsx"; 2 | import Error from "./lib/Error.jsx"; 3 | import parse from "./lib/parse.jsx"; 4 | import styles from "./lib/styles.jsx"; 5 | 6 | const style = { 7 | padding: "0 8px", 8 | display: "grid", 9 | gridAutoFlow: "column", 10 | gridGap: "16px", 11 | position: "fixed", 12 | overflow: "hidden", 13 | left: "0px", 14 | top: "0px", 15 | fontFamily: styles.fontFamily, 16 | lineHeight: styles.lineHeight, 17 | fontSize: styles.fontSize, 18 | color: styles.colors.dim, 19 | fontWeight: styles.fontWeight 20 | }; 21 | 22 | export const refreshFrequency = false; 23 | export const command = "./nibar/scripts/spaces.sh"; 24 | 25 | export const render = ({ output }, ...args) => { 26 | const data = parse(output); 27 | if (typeof data === "undefined") { 28 | return ( 29 |
30 | 31 |
32 | ); 33 | } 34 | if (typeof data.error !== "undefined") { 35 | return ( 36 |
37 | 38 |
39 | ); 40 | } 41 | const displayId = Number(window.location.pathname.replace(/\D+/g, '')); 42 | const display = data.displays.find(d => d.id === displayId); 43 | return ( 44 |
45 | s.display === display.index)} /> 46 |
47 | ); 48 | }; 49 | 50 | export default null; 51 | -------------------------------------------------------------------------------- /ss.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kkga/nibar/df0b730aba3205d82f657fddef76a9a09fc3c64d/ss.png -------------------------------------------------------------------------------- /status.jsx: -------------------------------------------------------------------------------- 1 | import DateTime from "./lib/DateTime.jsx"; 2 | import Battery from "./lib/Battery.jsx"; 3 | import Cpu from "./lib/Cpu.jsx"; 4 | import Wifi from "./lib/Wifi.jsx"; 5 | import Dnd from "./lib/Dnd.jsx"; 6 | import Error from "./lib/Error.jsx"; 7 | import parse from "./lib/parse.jsx"; 8 | import styles from "./lib/styles.jsx"; 9 | 10 | const style = { 11 | display: "grid", 12 | padding: "0 12px", 13 | gridAutoFlow: "column", 14 | gridGap: "20px", 15 | position: "fixed", 16 | overflow: "hidden", 17 | right: "0px", 18 | top: "0px", 19 | color: styles.colors.dim, 20 | fontFamily: styles.fontFamily, 21 | fontSize: styles.fontSize, 22 | lineHeight: styles.lineHeight, 23 | fontWeight: styles.fontWeight 24 | }; 25 | 26 | export const refreshFrequency = 10000; 27 | 28 | export const command = "./nibar/scripts/status.sh"; 29 | 30 | export const render = ({ output }) => { 31 | const data = parse(output); 32 | if (typeof data === "undefined") { 33 | return ( 34 |
35 | 36 |
37 | ); 38 | } 39 | return ( 40 |
41 | 42 | 43 | 44 | 45 | 46 |
47 | ); 48 | }; 49 | 50 | export default null; 51 | -------------------------------------------------------------------------------- /widget.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "nibar", 3 | "description": "Displays a bar at the top of the screen with system stats.", 4 | "author": "Gadzhi Kharkharov", 5 | "email": "me@kkga.me" 6 | } 7 | -------------------------------------------------------------------------------- /windows.jsx: -------------------------------------------------------------------------------- 1 | import App from "./lib/App.jsx"; 2 | import Error from "./lib/Error.jsx"; 3 | import parse from "./lib/parse.jsx"; 4 | import styles from "./lib/styles.jsx"; 5 | import config from "./config.json" 6 | 7 | 8 | const style = { 9 | padding: "0 8px", 10 | display: "grid", 11 | gridAutoFlow: "column", 12 | gridGap: "16px", 13 | position: "fixed", 14 | overflow: "hidden", 15 | left: config.windows_offset_left, 16 | top: "0px", 17 | fontFamily: styles.fontFamily, 18 | lineHeight: styles.lineHeight, 19 | fontSize: styles.fontSize, 20 | color: styles.colors.dim, 21 | fontWeight: styles.fontWeight 22 | }; 23 | 24 | export const refreshFrequency = false; 25 | export const command = "./nibar/scripts/windows.sh"; 26 | 27 | export const render = ({ output }) => { 28 | const data = parse(output); 29 | if (typeof data === "undefined") { 30 | return ( 31 |
32 | 33 |
34 | ); 35 | } 36 | if (typeof data.error !== "undefined") { 37 | return ( 38 |
39 | 40 |
41 | ); 42 | } 43 | return ( 44 |
45 | 46 |
47 | ); 48 | }; 49 | 50 | export default null; 51 | --------------------------------------------------------------------------------