diff --git a/scripts/waybar/wakatime.py b/scripts/waybar/wakatime.py
new file mode 100755
index 0000000..4bbcaf1
--- /dev/null
+++ b/scripts/waybar/wakatime.py
@@ -0,0 +1,60 @@
+#! /usr/local/bin/python3
+
+# Retrieve status bar data from WakaTime API and present in Waybar widget
+
+import requests
+import os
+import json
+import textwrap
+
+WAKATIME_API_KEY = os.getenv("WAKATIME_API_KEY")
+WAKATIME_ENDPOINT = "https://wakatime.com/api/v1/users/current/status_bar/today"
+
+
+def get_data(url):
+ response = requests.get(url)
+ if response.status_code == 200:
+ return response.json()
+ else:
+ raise Exception(
+ f"Failed to fetch data from API. Status code: {response.status_code}"
+ )
+
+
+def generate_tooltip(time, languages, projects):
+ return textwrap.dedent(
+ f"""\
+ Time coding: {time}
+ Languages: {languages}
+ Projects: {projects}"""
+ )
+
+
+def format_metric(metrics):
+ return ", ".join(
+ [f'{metric["name"]} ({metric["percent"]}%)' for metric in metrics[:3]]
+ )
+
+
+def main():
+ output = {}
+ try:
+ data = get_data(WAKATIME_ENDPOINT + "?api_key=" + WAKATIME_API_KEY)
+ digital_time = data["data"]["grand_total"]["digital"]
+ human_time = data["data"]["grand_total"]["text"]
+ langs = data["data"]["languages"]
+ projects = data["data"]["projects"]
+ tooltip = generate_tooltip(
+ human_time, format_metric(langs), format_metric(projects)
+ )
+ output["text"] = digital_time
+ output["tooltip"] = tooltip
+
+ except Exception as e:
+ output["text"] = "Error"
+
+ print(json.dumps(output))
+
+
+if __name__ == "__main__":
+ main()
diff --git a/waybar/config b/waybar/config
index dd5472a..a12c9f5 100644
--- a/waybar/config
+++ b/waybar/config
@@ -1,171 +1,195 @@
{
- "layer": "top", // Waybar at top layer
- "height": 30, // Waybar height (to be removed for auto height)
- "spacing": 4, // Gaps between modules (4px)
- // Choose the order of the modules
- "modules-left": ["custom/os", "hyprland/workspaces"],
- "modules-right": ["custom/spotify", "network", "bluetooth", "cpu",
- "memory", "disk", "temperature", "keyboard-state", "battery", "clock" ],
- "keyboard-state": {
- "numlock": true,
- "capslock": true,
- "format": "{name} {icon}",
- "format-icons": {
- "locked": "",
- "unlocked": ""
- }
+ "layer": "top",
+ "height": 30,
+ "spacing": 4,
+ "modules-left": [
+ "hyprland/workspaces"
+
+ ],
+ "modules-right": [
+ "custom/spotify",
+ "network",
+ "bluetooth",
+ "cpu",
+ "memory",
+ "disk",
+ "temperature",
+ "backlight",
+ "custom/wakatime",
+ "battery",
+ "clock"
+ ],
+ "keyboard-state": {
+ "numlock": true,
+ "capslock": true,
+ "format": "{name} {icon}",
+ "format-icons": {
+ "locked": "",
+ "unlocked": ""
+ }
+ },
+ "mpd": {
+ "format": "{stateIcon} {consumeIcon}{randomIcon}{repeatIcon}{singleIcon}{artist} - {album} - {title} ({elapsedTime:%M:%S}/{totalTime:%M:%S}) ⸨{songPosition}|{queueLength}⸩ {volume}% ",
+ "format-disconnected": "Disconnected ",
+ "format-stopped": "{consumeIcon}{randomIcon}{repeatIcon}{singleIcon}Stopped ",
+ "unknown-tag": "N/A",
+ "interval": 2,
+ "consume-icons": {
+ "on": " "
},
- "mpd": {
- "format": "{stateIcon} {consumeIcon}{randomIcon}{repeatIcon}{singleIcon}{artist} - {album} - {title} ({elapsedTime:%M:%S}/{totalTime:%M:%S}) ⸨{songPosition}|{queueLength}⸩ {volume}% ",
- "format-disconnected": "Disconnected ",
- "format-stopped": "{consumeIcon}{randomIcon}{repeatIcon}{singleIcon}Stopped ",
- "unknown-tag": "N/A",
- "interval": 2,
- "consume-icons": {
- "on": " "
- },
- "random-icons": {
- "off": " ",
- "on": " "
- },
- "repeat-icons": {
- "on": " "
- },
- "single-icons": {
- "on": "1 "
- },
- "state-icons": {
- "paused": "",
- "playing": ""
- },
- "tooltip-format": "MPD (connected)",
- "tooltip-format-disconnected": "MPD (disconnected)"
+ "random-icons": {
+ "off": " ",
+ "on": " "
},
- "idle_inhibitor": {
- "format": "{icon}",
- "format-icons": {
- "activated": "",
- "deactivated": ""
- }
+ "repeat-icons": {
+ "on": " "
},
- "tray": {
- // "icon-size": 21,
- "spacing": 10
+ "single-icons": {
+ "on": "1 "
},
- "clock": {
- // "timezone": "America/New_York",
- "format": "{: %H:%M %d/%m/%Y}",
- "tooltip-format": "{:%Y %B}\n{calendar}",
- "format-alt": "{:%Y-%m-%d}"
+ "state-icons": {
+ "paused": "",
+ "playing": ""
},
- "cpu": {
- "format": " {usage}%",
- "tooltip": false
+ "tooltip-format": "MPD (connected)",
+ "tooltip-format-disconnected": "MPD (disconnected)"
+ },
+ "idle_inhibitor": {
+ "format": "{icon}",
+ "format-icons": {
+ "activated": "",
+ "deactivated": ""
+ }
+ },
+ "tray": {
+ "spacing": 10
+ },
+ "clock": {
+ "format": "{: %H:%M %d/%m/%Y}",
+ "tooltip-format": "{:%Y %B}\n{calendar}",
+ "format-alt": "{:%Y-%m-%d}"
+ },
+ "cpu": {
+ "format": " {usage}%",
+ "tooltip": false
+ },
+ "memory": {
+ "format": " {}%"
+ },
+ "temperature": {
+ "critical-threshold": 80,
+ "format": "{icon} {temperatureC}°C",
+ "format-icons": [
+ "",
+ "",
+ ""
+ ]
+ },
+ "backlight": {
+ "device": "acpi_video1",
+ "format": "{percent}% {icon}",
+ "format-icons": [
+ "",
+ "",
+ "",
+ "",
+ "",
+ "",
+ "",
+ "",
+ ""
+ ]
+ },
+ "battery": {
+ "states": {
+ "warning": 30,
+ "critical": 15
},
- "memory": {
- "format": " {}%"
+ "format": "{icon} {capacity}%",
+ "format-charging": " {capacity}%",
+ "format-plugged": " {capacity}%",
+ "format-alt": "{time} {icon}",
+ "format-icons": [
+ "",
+ "",
+ "",
+ "",
+ ""
+ ]
+ },
+ "battery#bat2": {
+ "bat": "BAT2"
+ },
+ "network": {
+ "format-wifi": " {essid} ({signalStrength}%)",
+ "format-ethernet": "{ipaddr}/{cidr} ",
+ "tooltip-format": "{ifname} via {gwaddr} ",
+ "format-linked": "{ifname} (No IP) ",
+ "format-disconnected": "⚠ Disconnected",
+ "format-alt": "{ifname}: {ipaddr}/{cidr}"
+ },
+ "pulseaudio": {
+ "format": "{icon} {volume}%",
+ "format-bluetooth": "{icon} {volume}% {format_source}",
+ "format-bluetooth-muted": " {icon} {format_source}",
+ "format-muted": " {format_source}",
+ "format-source": " {volume}%",
+ "format-source-muted": "",
+ "format-icons": {
+ "headphone": "",
+ "hands-free": "",
+ "phone": "",
+ "portable": "",
+ "car": "",
+ "default": [
+ "",
+ "",
+ ""
+ ]
},
- "temperature": {
- // "thermal-zone": 2,
- // "hwmon-path": "/sys/class/hwmon/hwmon2/temp1_input",
- "critical-threshold": 80,
- // "format-critical": "{temperatureC}°C {icon}",
- "format": "{icon} {temperatureC}°C",
- "format-icons": ["", "", ""]
- },
- "backlight": {
- "device": "acpi_video1",
- "format": "{percent}% {icon}",
- "format-icons": ["", "", "", "", "", "", "", "", ""]
- },
- "battery": {
- "states": {
- // "good": 95,
- "warning": 30,
- "critical": 15
- },
- "format": "{icon} {capacity}%",
- "format-charging": " {capacity}%",
- "format-plugged": " {capacity}%",
- "format-alt": "{time} {icon}",
- // "format-good": "", // An empty format will hide the module
- // "format-full": "",
- "format-icons": ["", "", "", "", ""]
- },
- "battery#bat2": {
- "bat": "BAT2"
- },
- "network": {
- // "interface": "wlp2*", // (Optional) To force the use of this interface
- "format-wifi": " {essid} ({signalStrength}%)",
- "format-ethernet": "{ipaddr}/{cidr} ",
- "tooltip-format": "{ifname} via {gwaddr} ",
- "format-linked": "{ifname} (No IP) ",
- "format-disconnected": "⚠ Disconnected",
- "format-alt": "{ifname}: {ipaddr}/{cidr}"
- },
- "pulseaudio": {
- // "scroll-step": 1, // %, can be a float
- "format": "{icon} {volume}%",
- "format-bluetooth": "{icon} {volume}% {format_source}",
- "format-bluetooth-muted": " {icon} {format_source}",
- "format-muted": " {format_source}",
- "format-source": " {volume}%",
- "format-source-muted": "",
- "format-icons": {
- "headphone": "",
- "hands-free": "",
- "headset": "",
- "phone": "",
- "portable": "",
- "car": "",
- "default": ["", "", ""]
- },
- "on-click": "pavucontrol"
- },
- "custom/media": {
- "format": "{icon} {}",
- "return-type": "json",
- "max-length": 40,
- "format-icons": {
- "spotify": "",
- "default": "🎜"
- },
- "escape": true,
- "exec": "$HOME/.config/waybar/mediaplayer.py 2> /dev/null" // Script in resources folder
- // "exec": "$HOME/.config/waybar/mediaplayer.py --player spotify 2> /dev/null" // Filter player based on name
- },
- "custom/os": {
- "format": " archbish",
- },
-
-"disk": {
+ "on-click": "pavucontrol"
+ },
+ "disk": {
"interval": 30,
"format": " {percentage_used}%",
- "path": "/home",
-},
-
-"custom/spotify": {
+ "path": "/home"
+ },
+ "bluetooth": {
+ "controller": "bluetoothctl",
+ "format": " {status}",
+ "format-connected": " {device_alias}",
+ "format-connected-battery": " {device_alias} {device_battery_percentage}% ",
+ "tooltip-format": "{controller_alias}\t{controller_address}\n\n{num_connections} connected",
+ "tooltip-format-connected": "{controller_alias}\t{controller_address}\n\n{num_connections} connected\n\n{device_enumerate}",
+ "tooltip-format-enumerate-connected": "{device_alias}\t{device_address}",
+ "tooltip-format-enumerate-connected-battery": "{device_alias}\t{device_address}\t{device_battery_percentage}%"
+ },
+ "custom/media": {
+ "format": "{icon} {}",
+ "return-type": "json",
+ "max-length": 40,
+ "format-icons": {
+ "spotify": "",
+ "default": "🎜"
+ },
+ "escape": true,
+ "exec": "$HOME/.config/waybar/mediaplayer.py 2> /dev/null"
+ },
+ "custom/os": {
+ "format": " archbish"
+ },
+ "custom/spotify": {
"exec": "/usr/bin/python3 $HOME/.config/waybar/resources/custom_modules/mediaplayer.py --player spotify",
"format": "{} ",
"return-type": "json",
"on-click": "playerctl play-pause",
"on-scroll-up": "playerctl next",
"on-scroll-down": "playerctl previous"
-},
-
-"bluetooth": {
- "controller": "bluetoothctl", // specify the alias of the controller if there are more than 1 on the system
- "format": " {status}",
- "format-connected": " {device_alias}",
- "format-connected-battery": " {device_alias} {device_battery_percentage}% ",
- // "format-device-preference": [ "device1", "device2" ], // preference list deciding the displayed device
- "tooltip-format": "{controller_alias}\t{controller_address}\n\n{num_connections} connected",
- "tooltip-format-connected": "{controller_alias}\t{controller_address}\n\n{num_connections} connected\n\n{device_enumerate}",
- "tooltip-format-enumerate-connected": "{device_alias}\t{device_address}",
- "tooltip-format-enumerate-connected-battery": "{device_alias}\t{device_address}\t{device_battery_percentage}%"
+ },
+ "custom/wakatime": {
+ "exec": "source $HOME/dotfiles/.env && python3 $HOME/.config/waybar/resources/custom_modules/wakatime.py",
+ "format": " {}",
+ "return-type": "json",
+ "interval": 600
+ }
}
-
-
-}
-
diff --git a/waybar/resources/custom_modules/wakatime.py b/waybar/resources/custom_modules/wakatime.py
new file mode 100755
index 0000000..deb305b
--- /dev/null
+++ b/waybar/resources/custom_modules/wakatime.py
@@ -0,0 +1,57 @@
+#! /usr/local/bin/python3
+import requests
+import os
+import json
+import textwrap
+
+WAKATIME_API_KEY = os.getenv("WAKATIME_API_KEY")
+WAKATIME_ENDPOINT = "https://wakatime.com/api/v1/users/current/status_bar/today"
+
+
+def get_data(url):
+ response = requests.get(url)
+ if response.status_code == 200:
+ return response.json()
+ else:
+ raise Exception(
+ f"Failed to fetch data from API. Status code: {response.status_code}"
+ )
+
+
+def generate_tooltip(time, languages, projects):
+ return textwrap.dedent(
+ f"""\
+ Time coding: {time}
+ Languages: {languages}
+ Projects: {projects}"""
+ )
+
+
+def format_metric(metrics):
+ return ", ".join(
+ [f'{metric["name"]} ({metric["percent"]}%)' for metric in metrics[:3]]
+ )
+
+
+def main():
+ output = {}
+ try:
+ data = get_data(WAKATIME_ENDPOINT + "?api_key=" + WAKATIME_API_KEY)
+ digital_time = data["data"]["grand_total"]["digital"]
+ human_time = data["data"]["grand_total"]["text"]
+ langs = data["data"]["languages"]
+ projects = data["data"]["projects"]
+ tooltip = generate_tooltip(
+ human_time, format_metric(langs), format_metric(projects)
+ )
+ output["text"] = digital_time
+ output["tooltip"] = tooltip
+
+ except Exception as e:
+ output["text"] = "Error"
+
+ print(json.dumps(output))
+
+
+if __name__ == "__main__":
+ main()