├── .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 | 
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 |
47 | );
48 | };
49 |
50 | export default null;
51 |
--------------------------------------------------------------------------------