✨ 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:
+155
-35
@@ -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}
|
||||
|
||||
Reference in New Issue
Block a user