#!/usr/bin/env python3 import json import os import re import subprocess import tkinter as tk from tkinter import font as tkfont CACHE_FILE = 'stats_cache.json' REFRESH_MS = 3000 # Re-read cache every 3 seconds BG_COLOR = "#232323" WINDOW_CHECK_MS = 80 MINECRAFT_PATTERNS = ('minecraft', 'lunar client', 'badlion', 'fabric', 'forge') # ── formatting helpers (mirrors main.py, no imports needed) ────────────────── def fmt_money(value): if not value or value in ('N/A', '❌'): return value or 'N/A' try: num = int(value) if num >= 1_000_000_000: return f"{num / 1_000_000_000:.2f} B" elif num >= 1_000_000: return f"{num / 1_000_000:.2f} M" elif num >= 1_000: return f"{num / 1_000:.2f} K" return str(num) except (ValueError, TypeError): return str(value) def _run_cmd(command): try: result = subprocess.run( command, stdout=subprocess.PIPE, stderr=subprocess.DEVNULL, text=True, timeout=0.10, check=False, ) if result.returncode != 0: return None return result.stdout.strip() except Exception: return None def get_active_window_info(): """Return active window id and title via xdotool, or (None, None).""" win_id = _run_cmd(['xdotool', 'getactivewindow']) if not win_id: return None, None title = _run_cmd(['xdotool', 'getwindowname', win_id]) or '' return win_id, title def is_minecraft_title(title): title_l = (title or '').lower() return any(pattern in title_l for pattern in MINECRAFT_PATTERNS) def get_window_geometry(win_id): """Return dict with x,y,width,height for a window id, or None.""" out = _run_cmd(['xwininfo', '-id', str(win_id)]) if not out: return None def find_int(pattern): match = re.search(pattern, out) return int(match.group(1)) if match else None x = find_int(r'Absolute upper-left X:\s+(-?\d+)') y = find_int(r'Absolute upper-left Y:\s+(-?\d+)') width = find_int(r'Width:\s+(\d+)') height = find_int(r'Height:\s+(\d+)') if None in (x, y, width, height): return None return {'x': x, 'y': y, 'width': width, 'height': height} # ── overlay ────────────────────────────────────────────────────────────────── class Overlay(tk.Tk): def __init__(self): super().__init__() # Window chrome self.overrideredirect(True) # no title bar self.attributes('-topmost', True) self.configure(bg=BG_COLOR) # Fonts self.name_font = tkfont.Font(family='Consolas', size=9) self.money_font = tkfont.Font(family='Consolas', size=10, weight='bold') # Player rows container self.rows_frame = tk.Frame(self, bg=BG_COLOR) self.rows_frame.pack(fill='both', padx=6, pady=4) self.total_label = tk.Label( self, text='Total: 0', bg=BG_COLOR, fg='#f0c040', font=self.money_font, anchor='w' ) self.total_label.pack(fill='x', padx=6, pady=(0, 4)) # Initial position (top-right corner area) self.update_idletasks() self.geometry('+0+100') # Player label widgets: {username: (name_lbl, money_lbl)} self._labels: dict[str, tuple[tk.Label, tk.Label]] = {} self._visible_for_minecraft = True self._sync_window_visibility() self._refresh() # ── data ──────────────────────────────────────────────────────────────── def _load_cache(self): if not os.path.exists(CACHE_FILE): return None try: with open(CACHE_FILE, 'r') as f: return json.load(f) except Exception: return None def _sync_window_visibility(self): """Show overlay only while Minecraft window is focused and anchor to it.""" win_id, title = get_active_window_info() if win_id and is_minecraft_title(title): geo = get_window_geometry(win_id) if geo: # Top-left corner of Minecraft with a small inset. self.update_idletasks() x = geo['x'] y = geo['y'] + 90 self.geometry(f'+{max(0, x)}+{max(0, y)}') if not self._visible_for_minecraft: self.deiconify() self._visible_for_minecraft = True else: if self._visible_for_minecraft: self.withdraw() self._visible_for_minecraft = False self.after(WINDOW_CHECK_MS, self._sync_window_visibility) def _refresh(self): data = self._load_cache() if data is None: self.total_label.configure(text='Total: N/A') self.after(REFRESH_MS, self._refresh) return # Create or update a row per player players = list(data.keys()) total_money = 0 for player in players: stats = data.get(player) or {} raw = stats.get('money', 'N/A') money = fmt_money(raw) if raw else 'N/A' color = '#2ecc71' if stats else '#e74c3c' if stats: try: total_money += int(raw) except (ValueError, TypeError): pass if player not in self._labels: row = tk.Frame(self.rows_frame, bg=BG_COLOR) row.pack(fill='x', pady=1) name_lbl = tk.Label(row, text=player, bg=BG_COLOR, fg='#aaaaaa', font=self.name_font, width=14, anchor='w') money_lbl = tk.Label(row, text='', bg=BG_COLOR, fg=color, font=self.money_font, anchor='e') name_lbl.pack(side='left') money_lbl.pack(side='right') self._labels[player] = (name_lbl, money_lbl) else: name_lbl, money_lbl = self._labels[player] money_lbl.configure(fg=color) self._labels[player][1].configure(text=money) # Remove rows for players no longer in cache for player in list(self._labels.keys()): if player not in players: self._labels[player][0].master.destroy() del self._labels[player] self.total_label.configure(text=f'Total: {fmt_money(total_money)}') self.after(REFRESH_MS, self._refresh) if __name__ == '__main__': app = Overlay() app.mainloop()