import psutil
import json
import time
import os
import threading
from datetime import datetime, date
from pathlib import Path
from collections import defaultdict
# ============================================================
# CONFIGURATION
# ============================================================
DATA_FILE = "screen_time_data.json"
POLL_INTERVAL = 3 # seconds between process checks
DAILY_LIMIT = {} # optional per-app daily limits in minutes
# ============================================================
# LOAD & SAVE DATA
# ============================================================
def load_data():
if Path(DATA_FILE).exists():
try:
with open(DATA_FILE, "r") as f:
return json.load(f)
except:
return {}
return {}
def save_data(data):
with open(DATA_FILE, "w") as f:
json.dump(data, f, indent=2)
# ============================================================
# GET ACTIVE PROCESSES
# ============================================================
def get_running_apps():
"""Return a set of currently running process names (lowercased)."""
apps = set()
for proc in psutil.process_iter(["name", "status"]):
try:
if proc.info["status"] == psutil.STATUS_RUNNING:
name = proc.info["name"].lower().replace(".exe", "")
apps.add(name)
except (psutil.NoSuchProcess, psutil.AccessDenied):
pass
return apps
def get_all_processes():
"""Return all running process names (including sleeping/idle)."""
apps = set()
for proc in psutil.process_iter(["name"]):
try:
name = proc.info["name"].lower().replace(".exe", "")
apps.add(name)
except (psutil.NoSuchProcess, psutil.AccessDenied):
pass
return apps
# ============================================================
# SCREEN TIME TRACKER CLASS
# ============================================================
class ScreenTimeTracker:
def __init__(self):
self.data = load_data()
self.tracking = {} # app -> start_time (if currently active)
self.watch_list = set() # apps to specifically watch
self.running = False
self.today = str(date.today())
self.limits = {} # app -> minutes limit
self.alerted = set() # apps already alerted today
# Ensure today's entry exists
if self.today not in self.data:
self.data[self.today] = {}
# ----------------------------------------------------------
# Add / Remove from Watch List
# ----------------------------------------------------------
def add_to_watchlist(self, app_name):
name = app_name.lower().replace(".exe", "")
self.watch_list.add(name)
print(f"\n Added to watchlist: {name}")
def remove_from_watchlist(self, app_name):
name = app_name.lower().replace(".exe", "")
self.watch_list.discard(name)
print(f"\n Removed from watchlist: {name}")
def set_limit(self, app_name, minutes):
name = app_name.lower().replace(".exe", "")
self.limits[name] = minutes
print(f"\n Limit set: {name} → {minutes} min/day")
# ----------------------------------------------------------
# Core Tracking Loop
# ----------------------------------------------------------
def track(self):
print("\n Tracker running in background...")
self.running = True
while self.running:
now = datetime.now()
today_str = str(date.today())
# New day reset
if today_str != self.today:
self.today = today_str
self.alerted = set()
if self.today not in self.data:
self.data[self.today] = {}
# Get active apps
if self.watch_list:
active_now = get_all_processes() & self.watch_list
else:
active_now = get_running_apps()
# Apps that just started
for app in active_now:
if app not in self.tracking:
self.tracking[app] = now
# Apps that just stopped
stopped = set(self.tracking.keys()) - active_now
for app in stopped:
elapsed = (now - self.tracking[app]).total_seconds()
self._add_time(app, elapsed)
del self.tracking[app]
# Check limits
for app in active_now:
total_mins = self._get_today_minutes(app)
if app in self.limits and total_mins >= self.limits[app]:
if app not in self.alerted:
print(f"\n ⚠ LIMIT REACHED: [{app}] — {total_mins:.1f} min used (limit: {self.limits[app]} min)")
print(" > ", end="", flush=True)
self.alerted.add(app)
save_data(self.data)
time.sleep(POLL_INTERVAL)
# Final flush — save any still-running apps
now = datetime.now()
for app, start in self.tracking.items():
elapsed = (now - start).total_seconds()
self._add_time(app, elapsed)
save_data(self.data)
def stop(self):
self.running = False
# ----------------------------------------------------------
# Time Helpers
# ----------------------------------------------------------
def _add_time(self, app, seconds):
today = self.today
if today not in self.data:
self.data[today] = {}
self.data[today][app] = self.data[today].get(app, 0) + seconds
def _get_today_minutes(self, app):
return self.data.get(self.today, {}).get(app, 0) / 60
# ----------------------------------------------------------
# Reports
# ----------------------------------------------------------
def report_today(self):
today_data = self.data.get(self.today, {})
# Add currently running session time (not yet saved)
now = datetime.now()
live = {}
for app, start in self.tracking.items():
live[app] = (now - start).total_seconds()
merged = dict(today_data)
for app, secs in live.items():
merged[app] = merged.get(app, 0) + secs
if not merged:
print("\n No screen time data for today yet.")
return
sorted_apps = sorted(merged.items(), key=lambda x: x[1], reverse=True)
print("\n" + "="*55)
print(f" SCREEN TIME REPORT — {self.today}")
print("="*55)
print(f"\n {'APP':<25} {'TIME':>12} {'LIMIT':>10}")
print(" " + "-"*50)
total_secs = 0
for app, secs in sorted_apps:
h, m, s = _hms(secs)
time_str = f"{h}h {m:02d}m {s:02d}s" if h else f"{m}m {s:02d}s"
limit_str = f"{self.limits[app]} min" if app in self.limits else "—"
bar = _bar(secs, max(v for _, v in sorted_apps))
print(f" {app:<25} {time_str:>12} {limit_str:>10} {bar}")
total_secs += secs
print(" " + "-"*50)
h, m, s = _hms(total_secs)
print(f" {'TOTAL':<25} {h}h {m:02d}m {s:02d}s")
print("="*55)
def report_week(self):
print("\n" + "="*55)
print(" WEEKLY SCREEN TIME SUMMARY")
print("="*55)
app_totals = defaultdict(float)
day_totals = {}
for day_str, apps in sorted(self.data.items())[-7:]:
day_total = sum(apps.values())
day_totals[day_str] = day_total
for app, secs in apps.items():
app_totals[app] += secs
print("\n Daily totals:")
for day, secs in day_totals.items():
h, m, _ = _hms(secs)
bar = _bar(secs, max(day_totals.values()) if day_totals else 1)
print(f" {day} {h}h {m:02d}m {bar}")
print("\n Top apps this week:")
top = sorted(app_totals.items(), key=lambda x: x[1], reverse=True)[:10]
for app, secs in top:
h, m, _ = _hms(secs)
print(f" • {app:<25} {h}h {m:02d}m")
print("="*55)
def list_watchlist(self):
print("\n 👁 Current watchlist:")
if self.watch_list:
for app in sorted(self.watch_list):
limit = f" (limit: {self.limits[app]} min)" if app in self.limits else ""
print(f" • {app}{limit}")
else:
print(" (empty — tracking ALL running processes)")
def show_running_now(self):
apps = get_all_processes()
print(f"\n 🖥 Currently running processes ({len(apps)}):")
for i, app in enumerate(sorted(apps), 1):
active = "" if app in self.tracking else " "
print(f" {active} {app}")
if i % 30 == 0:
cont = input(" ... show more? (y/n): ").strip().lower()
if cont != "y":
break
# ============================================================
# HELPERS
# ============================================================
def _hms(seconds):
seconds = int(seconds)
h = seconds // 3600
m = (seconds % 3600) // 60
s = seconds % 60
return h, m, s
def _bar(value, max_value, width=10):
if max_value == 0:
return ""
filled = int((value / max_value) * width)
return "█" * filled + "░" * (width - filled)
# ============================================================
# MAIN MENU
# ============================================================
def print_menu():
print("\n" + "-"*45)
print(" SCREEN TIME TRACKER")
print("-"*45)
print(" 1. Start tracking (background)")
print(" 2. Today's report")
print(" 3. Weekly summary")
print(" 4. Manage watchlist")
print(" 5. Set app time limit")
print(" 6. Show all running processes")
print(" 0. Stop & Exit")
print("-"*45)
def manage_watchlist(tracker):
print("\n Watchlist options:")
print(" 1. Add app to watchlist")
print(" 2. Remove app from watchlist")
print(" 3. View current watchlist")
sub = input(" Choice: ").strip()
if sub == "1":
app = input(" App name (e.g. chrome, code, vlc): ").strip()
tracker.add_to_watchlist(app)
elif sub == "2":
app = input(" App name to remove: ").strip()
tracker.remove_from_watchlist(app)
elif sub == "3":
tracker.list_watchlist()
def main():
print("\n" + "="*55)
print(" SCREEN TIME TRACKER")
print("="*55)
print(f"\n Data file : {DATA_FILE}")
print(f" Poll interval: every {POLL_INTERVAL} seconds")
print("\n Tip: Add specific apps to watchlist for focused tracking.")
print(" Or track ALL processes without a watchlist.")
tracker = ScreenTimeTracker()
tracker_thread = None
while True:
print_menu()
choice = input(" > ").strip()
if choice == "1":
if tracker_thread and tracker_thread.is_alive():
print("\n Tracker is already running.")
else:
tracker_thread = threading.Thread(target=tracker.track, daemon=True)
tracker_thread.start()
elif choice == "2":
tracker.report_today()
elif choice == "3":
tracker.report_week()
elif choice == "4":
manage_watchlist(tracker)
elif choice == "5":
app = input("\n App name: ").strip()
mins = int(input(" Daily limit (minutes): ").strip())
tracker.set_limit(app, mins)
elif choice == "6":
tracker.show_running_now()
elif choice == "0":
print("\n Stopping tracker...")
tracker.stop()
if tracker_thread:
tracker_thread.join(timeout=5)
print(" Data saved.")
print(" Goodbye!\n")
break
else:
print(" Invalid choice.")
# ============================================================
# RUN
# ============================================================
if __name__ == "__main__":
main()