From 0122b9c321c5a5ec881eb937c3c5f7482b4b455f Mon Sep 17 00:00:00 2001 From: thomasabishop Date: Fri, 9 Feb 2024 16:04:58 +0000 Subject: [PATCH] waybar: add wakatime custom module --- scripts/waybar/wakatime.py | 60 ++++ waybar/config | 332 +++++++++++--------- waybar/resources/custom_modules/wakatime.py | 57 ++++ 3 files changed, 295 insertions(+), 154 deletions(-) create mode 100755 scripts/waybar/wakatime.py create mode 100755 waybar/resources/custom_modules/wakatime.py 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()