Screen Time Tracker

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()

No comments: