feat(tab-bar)!: implements new functionality on tab-bar

Allows the tab-bar to show system battery level, date/time, WakaTime stats for the day, and the currently playing track on most music players.

BREAKING CHANGE: A compatible music player and the playerctl executable are required for the music feature. The WakaTime CLI app and a wakatime (or equivalent) api key are required for the wakatime stats. Checks are implemented to prevent crashing but the functionality will be unavailable without the prerequisites being met.
This commit is contained in:
2024-08-02 09:09:07 -04:00
parent 4615a38402
commit fb30056f20
2 changed files with 157 additions and 37 deletions
+155 -35
View File
@@ -1,11 +1,17 @@
# pyright: reportMissingImports=false
# pyright: reportUnknownVariableType=false
# pyright: reportUnknownParameterType=false
# pyright: reportUnknownArgumentType=false
# pyright: reportUnknownMemberType=false
import datetime
import json
import time
import subprocess
from collections import defaultdict
import psutil
import os
from kitty.boss import get_boss
from kitty.fast_data_types import Screen, add_timer, get_options
from kitty.rgb import to_color
from kitty.fast_data_types import Screen, add_timer
from kitty.tab_bar import (
DrawData,
ExtraData,
@@ -18,6 +24,45 @@ from kitty.tab_bar import (
timer_id = None
UNPLUGGED_ICONS = {
10: "",
20: "",
30: "",
40: "",
50: "",
60: "",
70: "",
80: "",
90: "",
100: "",
}
PLUGGED_ICONS = {
1: "",
}
class WakaTime:
def __init__(self):
self.cache = None
self.cache_time = 0
self.cache_duration = 60 # Cache duration in seconds (1 minute)
self.cli_path = os.path.expanduser("~/.wakatime/wakatime-cli")
def get_today_time(self):
current_time = time.time()
if self.cache is None or (current_time - self.cache_time) > self.cache_duration:
# Run the command
result = subprocess.run(
[self.cli_path, "--today"], stdout=subprocess.PIPE, text=True
)
self.cache = result.stdout.strip()
self.cache_time = current_time
return self.cache
waka_time = WakaTime()
def draw_tab(
draw_data: DrawData,
screen: Screen,
@@ -35,63 +80,138 @@ def draw_tab(
draw_tab_with_powerline(
draw_data, screen, tab, before, max_title_length, index, is_last, extra_data
)
if is_last: draw_right_status(draw_data, screen)
if is_last:
draw_right_status(draw_data, screen)
return screen.cursor.x
def draw_right_status(draw_data: DrawData, screen: Screen) -> None:
# The tabs may have left some formats enabled. Disable them now.
draw_attributed_string(Formatter.reset, screen)
tab_bg = as_rgb(int(draw_data.inactive_bg))
tab_fg = as_rgb(int(draw_data.inactive_fg))
default_bg = as_rgb(int(draw_data.default_bg))
cells = create_cells()
# Drop cells that wont fit
while True:
if not cells:
return
padding = screen.columns - screen.cursor.x - sum(len(" ".join([c.get("icon", ""), c["text"]])) + 2 for c in cells)
padding = screen.columns - screen.cursor.x - sum(len(c) + 3 for c in cells)
if padding >= 0:
break
cells = cells[1:]
if padding: screen.draw(" " * padding)
if padding:
screen.draw(" " * padding)
for c in cells:
screen.cursor.bg = default_bg
icon = c.get("icon")
if icon:
fg = to_color(c.get("color")) if c.get("color") else tab_fg
screen.cursor.fg = as_rgb(int(fg))
screen.draw(f" {icon}")
tab_bg = as_rgb(int(draw_data.inactive_bg))
tab_fg = as_rgb(int(draw_data.inactive_fg))
default_bg = as_rgb(int(draw_data.default_bg))
for cell in cells:
# Draw the separator
if cell == cells[0]:
screen.cursor.fg = tab_bg
screen.draw("")
else:
screen.cursor.fg = default_bg
screen.cursor.bg = tab_bg
screen.draw("")
screen.cursor.fg = tab_fg
text = c["text"]
screen.draw(f" {text} ")
screen.cursor.bg = tab_bg
screen.draw(f" {cell} ")
def create_cells():
cells = [
get_todo(),
get_date(),
get_time()
def create_cells() -> list[str]:
return [
get_current_artist_title(),
format_cell(get_wakatime_today()),
format_cell(get_date()),
format_cell(get_time()),
get_battery_level()["icon"],
]
return [c for c in cells if c is not None]
def format_cell(data: dict[str, str]) -> str:
return f"{data['icon']} {data['text']}"
def _redraw_tab_bar(timer_id: int):
for tm in get_boss().all_tab_managers:
tm.mark_tab_bar_dirty()
def get_time():
now = datetime.datetime.now().strftime("%H:%M")
return { "icon": "", "color": "#669bbc", "text": now }
return {"icon": "", "color": "#669bbc", "text": now}
def get_date():
today = datetime.date.today()
if today.weekday() < 5:
return { "icon": "󰃵 ", "color": "#2a9d8f", "text": today.strftime("%b %e") }
return {"icon": "󰃵 ", "color": "#2a9d8f", "text": today.strftime("%e %b")}
else:
return { "icon": "󰧓 ", "color": "#f2e8cf", "text": today.strftime("%b %e") }
return {"icon": "󰧓 ", "color": "#f2e8cf", "text": today.strftime("%e %b")}
def get_todo():
return None
def _redraw_tab_bar(timer_id):
for tm in get_boss().all_tab_managers:
tm.mark_tab_bar_dirty()
def get_battery_level():
battery = psutil.sensors_battery()
if battery is None:
return {"icon": "", "text": "Battery info not available"}
battery_percentage = round(battery.percent)
charging = battery.power_plugged
icon: str = ""
if charging:
icon = PLUGGED_ICONS[1]
else:
for level in sorted(UNPLUGGED_ICONS.keys(), reverse=True):
if battery_percentage >= level:
icon = UNPLUGGED_ICONS[level]
break
return {"icon": icon, "text": f"{battery_percentage}%"}
def get_current_artist_title():
try:
# Run playerctl to get the artist, ignoring Firefox and KDE Connect
artist = (
subprocess.check_output(
[
"playerctl",
"-i",
"firefox,kdeconnect,plasma-browser-integration",
"metadata",
"artist",
]
)
.decode("utf-8")
.strip()
)
# Run playerctl to get the title, ignoring Firefox and KDE Connect
title = (
subprocess.check_output(
[
"playerctl",
"-i",
"firefox,kdeconnect,plasma-browser-integration",
"metadata",
"title",
]
)
.decode("utf-8")
.strip()
)
if title == "":
return "󰟎 "
if artist != "":
return f"󰋋 {artist} - {title}"
else:
return f"󰋋 {title}"
except subprocess.CalledProcessError:
return "󰟎 "
except FileNotFoundError:
return "playerctl is not installed"
def get_wakatime_today():
time_spent = waka_time.get_today_time()
return {"icon": "󱦺 ", "text": time_spent}
+2 -2
View File
@@ -1,5 +1,5 @@
tab_bar_align left
tab_bar_style powerline
tab_bar_align center
tab_bar_style custom
tab_powerline_style angled
tab_bar_edge top
tab_bar_min_tabs 1