1. Artikel
  2. Forum
  3. Mitglieder
    1. Letzte Aktivitäten
    2. Mitgliedersuche
  4. Team / Über uns
  5. Partner
  • Anmelden oder registrieren
  • Suche
Python
  • Alles
  • Python
  • Artikel
  • Seiten
  • Forum
  • Erweiterte Suche
  1. PixelCodeWerk
  2. Artikel
  3. Python

Activity Tracker Pro

  • Kaneplarium
  • 10. Juni 2026 um 21:27
  • 0 Kommentare
  • 29 Mal gelesen
  • Neu

Dieses Skript zeichnet auf, welches Programm (und welches Fenster/welche Datei) wie lange auf welchem Rechner von welchem Benutzer geöffnet war. Es funktioniert plattformübergreifend
unter Windows, macOS und Linux. Die Log-Dateien werden täglich getrennt (z.B. "activity_log_2026-06-08.csv") im Ordner "Documents/ActivityTrackerLog" bzw. "Dokumente/ActivityTrackerLog" gespeichert.

Features:
- Dateierkennung: Erkennt automatisch geöffnete Dateinamen aus dem Fenstertitel und loggt diese separat.
- Täglicher Wechsel: Automatische Erstellung einer neuen Logdatei um Mitternacht.
- Standby-/Ruhezustand-Erkennung: Erkennt Standby-Phasen und loggt diese als System-Event.
- Shutdown/Neustart-Erkennung: Fängt Systembeendigungen und Neustarts ab und schreibt entsprechende Einträge.
- Ausfallsicher: Setzt Logs nahtlos fort und hängt Daten an bereits existierende Tagesdateien an.

Voraussetzungen/Abhängigkeiten:
- Windows: Keine (verwendet das integrierte ctypes-Modul).
- macOS: Keine (verwendet das integrierte osascript/AppleScript).
- Linux (X11): Erfordert 'xprop' (meist vorinstalliert, z.B. über apt-get install x11-utils).

Verwendung:
python3 activity_tracker.py


Python
#!/usr/bin/env python3
# -*- coding: utf-8 -*-

import os
import sys
import time
import csv
import socket
import getpass
import datetime
import subprocess
import re
import signal

# Falls Windows aktiv ist, ctypes für direkten Win32-API-Zugriff laden
if sys.platform == "win32":
   import ctypes
   from ctypes import wintypes


def get_active_window_windows():
   """Gibt das aktive Programm und den Fenstertitel unter Windows zurück."""
   try:
       hwnd = ctypes.windll.user32.GetForegroundWindow()
       if not hwnd:
           return "Sperrbildschirm / Idle", "Kein aktives Fenster"
       
       # Fenstertitel abfragen
       length = ctypes.windll.user32.GetWindowTextLengthW(hwnd)
       title = ""
       if length > 0:
           buf = ctypes.create_unicode_buffer(length + 1)
           ctypes.windll.user32.GetWindowTextW(hwnd, buf, length + 1)
           title = buf.value
           
       # Prozess-ID abfragen
       pid = ctypes.c_ulong()
       ctypes.windll.user32.GetWindowThreadProcessId(hwnd, ctypes.byref(pid))
       
       # Programmname (.exe) abfragen
       h_process = ctypes.windll.kernel32.OpenProcess(0x1000, False, pid)
       app_name = "Unbekannt"
       if h_process:
           try:
               buf_size = ctypes.c_ulong(260)
               buf_path = ctypes.create_unicode_buffer(buf_size.value)
               if ctypes.windll.kernel32.QueryFullProcessImageNameW(h_process, 0, buf_path, ctypes.byref(buf_size)):
                   app_name = os.path.basename(buf_path.value)
           finally:
               ctypes.windll.kernel32.CloseHandle(h_process)
               
       return app_name, title
   except Exception as e:
       return "Windows System", f"Fehler beim Ermitteln des Fensters: {str(e)}"


def get_active_window_mac():
   """Gibt das aktive Programm und den Fenstertitel unter macOS zurück."""
   try:
       script = """
       global frontApp, windowTitle
       set windowTitle to ""
       tell application "System Events"
           set frontApp to name of first application process whose frontmost is true
           try
               tell process frontApp
                   set windowTitle to name of window 1
               end tell
           on error
               set windowTitle to ""
           end try
       end tell
       return frontApp & "|||" & windowTitle
       """
       proc = subprocess.Popen(
           ['osascript', '-e', script],
           stdout=subprocess.PIPE,
           stderr=subprocess.PIPE,
           text=True
       )
       stdout, stderr = proc.communicate(timeout=2)
       if proc.returncode == 0:
           parts = stdout.strip().split("|||")
           app_name = parts[0] if len(parts) > 0 else "Unbekannt"
           window_title = parts[1] if len(parts) > 1 else ""
           if not window_title:
               window_title = "Kein aktiver Fenstertitel"
           return app_name, window_title
       else:
           return "macOS System", "Hintergrund oder gesperrt"
   except subprocess.TimeoutExpired:
       return "macOS System", "Timeout beim Abfragen des Fensters"
   except FileNotFoundError:
       return "Fehler", "osascript nicht gefunden (nicht auf macOS?)"
   except Exception as e:
       return "macOS System", f"Fehler: {str(e)}"


def get_active_window_linux():
   """Gibt das aktive Programm und den Fenstertitel unter Linux (X11) zurück."""
   try:
       root_proc = subprocess.Popen(
           ['xprop', '-root', '-len', '14', '_NET_ACTIVE_WINDOW'],
           stdout=subprocess.PIPE,
           stderr=subprocess.PIPE,
           text=True
       )
       stdout, _ = root_proc.communicate(timeout=2)
       if root_proc.returncode != 0:
           return "Linux System", "Kein aktives X11-Fenster gefunden (evtl. Wayland?)"
           
       match = re.search(r"window id # (0x[0-9a-fA-F]+)", stdout)
       if not match:
           return "Idle / Desktop", "Kein aktives Fenster"
           
       win_id = match.group(1)
       
       class_proc = subprocess.Popen(
           ['xprop', '-id', win_id, 'WM_CLASS', 'WM_NAME'],
           stdout=subprocess.PIPE,
           stderr=subprocess.PIPE,
           text=True
       )
       stdout, _ = class_proc.communicate(timeout=2)
       
       app_name = "Unbekannt"
       window_title = "Unbekannt"
       
       class_match = re.search(r'WM_CLASS\(STRING\) = "(.*?)"', stdout)
       if class_match:
           app_name = class_match.group(1)
           
       name_match = re.search(r'WM_NAME.*? = "(.*?)"', stdout)
       if name_match:
           window_title = name_match.group(1)
           
       return app_name, window_title
   except FileNotFoundError:
       return "Fehler", "Bitte installiere 'x11-utils' für xprop"
   except subprocess.TimeoutExpired:
       return "Linux System", "Timeout beim Abfragen des Fensters"
   except Exception as e:
       return "Linux System", f"Fehler: {str(e)}"


def get_active_window():
   """Wählt je nach Betriebssystem die richtige Methode aus."""
   platform = sys.platform
   if platform == "win32":
       return get_active_window_windows()
   elif platform == "darwin":
       return get_active_window_mac()
   elif platform.startswith("linux"):
       return get_active_window_linux()
   else:
       return "Unbekanntes OS", "Nicht unterstützt"


def extract_filename(title):
   """Extrahiert einen eventuellen Dateinamen aus dem Fenstertitel."""
   if not title:
       return ""
       
   # Überprüfen, ob es sich wahrscheinlich um eine Webseite handelt (URLs ausschließen)
   is_web = any(web in title.lower() for web in ["http:", "https:", "www.", ".com", ".org", ".net", "google", "firefox", "safari", "chrome"])
   
   # 1. Pfade (absolut oder relativ) nach Dateinamen durchsuchen
   path_match = re.search(r'([a-zA-Z]:\\[\\\w\-\._\s]+|/[\w\-\._\s/]+)', title)
   if path_match and not is_web:
       potential_path = path_match.group(1)
       basename = os.path.basename(potential_path)
       if "." in basename and len(basename.split(".")[-1]) <= 5:
           ext = basename.split(".")[-1]
           if len(ext) >= 2 and not ext.isdigit():
               return basename

   # 2. Regulärer Dateiname mit Endung (z.B. Bericht.docx, main.py, test.xlsx)
   match = re.search(r'\b([\w\-äöüÄÖÜß\s]+\.[a-zA-Z0-9]{2,5})\b', title)
   if match:
       filename = match.group(1).strip()
       ext = os.path.splitext(filename.lower())[1]
       exclusions = {'.com', '.de', '.org', '.net', '.info', '.gov', '.edu', '.co'}
       if ext in exclusions:
           return ""
       if len(ext) >= 3:
           return filename
           
   return ""


def get_daily_log_filepath(log_dir, date_obj):
   """Erstellt den Dateipfad für den heutigen Tag und schreibt ggf. den Header."""
   date_str = date_obj.strftime("%Y-%m-%d")
   log_file = os.path.join(log_dir, f"activity_log_{date_str}.csv")
   
   if not os.path.exists(log_file):
       try:
           with open(log_file, mode="a", newline="", encoding="utf-8") as f:
               writer = csv.writer(f)
               writer.writerow([
                   "Start_Time", 
                   "End_Time", 
                   "Duration_Seconds", 
                   "Hostname", 
                   "Username", 
                   "Program", 
                   "Window_Title",
                   "Active_File"
               ])
       except Exception as e:
           sys.stderr.write(f"Fehler beim Erstellen der täglichen Log-Datei: {e}\n")
   return log_file


def log_activity(log_dir, start_time, end_time, duration, program, title):
   """Schreibt einen Aktivitätseintrag in die tägliche CSV-Datei."""
   log_file = get_daily_log_filepath(log_dir, start_time.date())
   hostname = socket.gethostname()
   username = getpass.getuser()
   
   # Datei-Name aus dem Titel extrahieren
   active_file = extract_filename(title)
   
   # Zeiten formatieren
   start_str = start_time.strftime("%Y-%m-%d %H:%M:%S")
   end_str = end_time.strftime("%Y-%m-%d %H:%M:%S")
   
   # Dauer runden
   duration_rounded = round(duration, 1)
   
   try:
       with open(log_file, mode="a", newline="", encoding="utf-8") as f:
           writer = csv.writer(f)
           writer.writerow([
               start_str,
               end_str,
               duration_rounded,
               hostname,
               username,
               program,
               title,
               active_file
           ])
       # Ausgabe auf der Konsole
       file_msg = f" | Datei: '{active_file}'" if active_file else ""
       print(f"[{start_str} -> {end_str}] {duration_rounded:5.1f}s | {hostname} | {username} | {program} | {title}{file_msg}")
   except Exception as e:
       sys.stderr.write(f"Fehler beim Schreiben in die Log-Datei: {e}\n")


class ActivityTracker:
   def __init__(self):
       self.interval = 1.0
       self.log_dir = self.setup_log_directory()
       self.current_app = "System"
       self.current_title = "Initialisierung..."
       self.start_time = datetime.datetime.now()
       self.is_running = True

   def setup_log_directory(self):
       home = os.path.expanduser("~")
       possible_docs_dirs = [
           os.path.join(home, "Documents"),
           os.path.join(home, "Dokumente")
       ]
       
       docs_dir = possible_docs_dirs[0]
       for path in possible_docs_dirs:
           if os.path.exists(path) and os.path.isdir(path):
               docs_dir = path
               break
               
       log_dir = os.path.join(docs_dir, "ActivityTrackerLog")
       try:
           os.makedirs(log_dir, exist_ok=True)
       except Exception as e:
           log_dir = os.path.dirname(os.path.abspath(__file__))
           print(f"Hinweis: Zielordner konnte nicht erstellt werden. Speichere lokal. ({e})")
       return log_dir

   def log_system_event(self, event_type, description, event_time=None):
       """Schreibt ein wichtiges Systemereignis (Startup, Shutdown, Standby) in das Log."""
       if event_time is None:
           event_time = datetime.datetime.now()
       log_file = get_daily_log_filepath(self.log_dir, event_time.date())
       hostname = socket.gethostname()
       username = getpass.getuser()
       time_str = event_time.strftime("%Y-%m-%d %H:%M:%S")
       
       try:
           with open(log_file, mode="a", newline="", encoding="utf-8") as f:
               writer = csv.writer(f)
               writer.writerow([
                   time_str,
                   time_str,
                   0.0,
                   hostname,
                   username,
                   event_type,
                   description,
                   ""
               ])
           print(f"[{time_str}] >>> {event_type}: {description} <<<")
       except Exception as e:
           sys.stderr.write(f"Fehler beim Schreiben des Systemereignisses: {e}\n")

   def stop_and_log_final(self, event_type="SYSTEM_EVENT", description="HERUNTERGEFAHREN / RECHNER-NEUSTART"):
       """Beendet den Tracker sauber und schreibt den letzten Logeintrag."""
       if not self.is_running:
           return
       self.is_running = False
       end_time = datetime.datetime.now()
       duration = (end_time - self.start_time).total_seconds()
       
       if duration >= 0.5:
           log_activity(self.log_dir, self.start_time, end_time, duration, self.current_app, self.current_title)
           
       self.log_system_event(event_type, description, event_time=end_time)

   def run(self):
       # Startup event loggen
       self.log_system_event("SYSTEM_EVENT", "SYSTEM_STARTUP (Tracker gestartet / Rechner hochgefahren)")
       
       self.current_app, self.current_title = get_active_window()
       self.start_time = datetime.datetime.now()
       
       print("=" * 80)
       print(" AKTIVITÄTS-TRACKER PRO GESTARTET")
       print(f" Log-Ordner: {self.log_dir}")
       print(f" Intervall: {self.interval} Sekunde(n)")
       print(" Beenden mit: Strg + C")
       print("=" * 80)
       
       try:
           while self.is_running:
               loop_start = time.time()
               time.sleep(self.interval)
               actual_elapsed = time.time() - loop_start
               
               # 1. STANDBY- / RUHEZUSTAND-ERKENNUNG
               # Wenn der Schleifendurchlauf viel länger gedauert hat als das Intervall (z.B. > 5s bei 1s)
               if actual_elapsed > self.interval * 5:
                   # Loggen bis zum Standby-Start
                   end_time = self.start_time + datetime.timedelta(seconds=self.interval)
                   duration = self.interval
                   if duration >= 0.5:
                       log_activity(self.log_dir, self.start_time, end_time, duration, self.current_app, self.current_title)
                   
                   # Logge Standby Start und Standby Ende
                   self.log_system_event("SYSTEM_EVENT", "SYSTEM_STANDBY_START (Rechner ging in Ruhezustand)", event_time=end_time)
                   
                   wakeup_time = datetime.datetime.now()
                   self.log_system_event("SYSTEM_EVENT", "SYSTEM_STANDBY_END / WAKEUP (Rechner aufgewacht)", event_time=wakeup_time)
                   
                   # Zustand nach dem Wakeup neu setzen
                   self.start_time = wakeup_time
                   self.current_app, self.current_title = get_active_window()
                   continue
               
               # 2. TAGESWECHSEL-ERKENNUNG (Mitternachts-Übergang)
               current_date = datetime.date.today()
               if current_date != self.start_time.date():
                   # Wir schließen den alten Tag exakt um 23:59:59.999
                   end_of_day = datetime.datetime.combine(self.start_time.date(), datetime.time(23, 59, 59, 999999))
                   duration = (end_of_day - self.start_time).total_seconds()
                   if duration >= 0.5:
                       log_activity(self.log_dir, self.start_time, end_of_day, duration, self.current_app, self.current_title)
                   
                   # Logge den Tageswechsel als Event
                   self.log_system_event("SYSTEM_EVENT", "TAGESWECHSEL (Ende des Tages)", event_time=end_of_day)
                   
                   start_of_new_day = datetime.datetime.combine(current_date, datetime.time(0, 0, 0, 0))
                   self.log_system_event("SYSTEM_EVENT", "TAGESWECHSEL (Beginn des Tages)", event_time=start_of_new_day)
                   
                   # Neuer Zustand für den frischen Tag
                   self.start_time = start_of_new_day
                   self.current_app, self.current_title = get_active_window()
                   continue
               
               # 3. REGULÄRES FENSTER-TRACKING
               new_app, new_title = get_active_window()
               
               if (new_app != self.current_app) or (new_title != self.current_title):
                   end_time = datetime.datetime.now()
                   duration = (end_time - self.start_time).total_seconds()
                   
                   if duration >= 0.5:
                       log_activity(self.log_dir, self.start_time, end_time, duration, self.current_app, self.current_title)
                   
                   # Zustand aktualisieren
                   self.current_app, self.current_title = new_app, new_title
                   self.start_time = end_time
                   
       except KeyboardInterrupt:
           self.stop_and_log_final("SYSTEM_EVENT", "SYSTEM_SHUTDOWN_OR_TERMINATION (Manuell durch Strg+C beendet)")


def main():
   tracker = ActivityTracker()
   
   # Registriere Signal-Handler für sauberes Beenden bei Shutdown/Reboots
   def make_signal_handler(sig_name):
       def signal_handler(signum, frame):
           tracker.stop_and_log_final("SYSTEM_EVENT", f"HERUNTERGEFAHREN / RECHNER-NEUSTART (Signal: {sig_name})")
           sys.exit(0)
       return signal_handler

   try:
       signal.signal(signal.SIGTERM, make_signal_handler("SIGTERM"))
   except ValueError:
       pass
   try:
       signal.signal(signal.SIGINT, make_signal_handler("SIGINT (Strg+C)"))
   except ValueError:
       pass
   if sys.platform != "win32":
       try:
           signal.signal(signal.SIGHUP, make_signal_handler("SIGHUP"))
       except ValueError:
           pass
           
   # Starte den Tracker Loop
   tracker.run()


if __name__ == "__main__":
   main()
Alles anzeigen
  • Activity Tracker Pro
  • Python

Kommentare

  • Smileys
  • :)
  • :(
  • ;)
  • :P
  • ^^
  • :D
  • ;(
  • X(
  • :*
  • :|
  • 8o
  • =O
  • <X
  • ||
  • :/
  • :S
  • X/
  • 8)
  • ?(
  • :huh:
  • :rolleyes:
  • :love:
  • 8|
  • :cursing:
  • :thumbdown:
  • :thumbup:
  • :sleeping:
  • :whistling:
  • :evil:
  • :saint:
  • <3
  • :!:
  • :?:
  • Smileys
  • :)
  • :(
  • ;)
  • :P
  • ^^
  • :D
  • ;(
  • X(
  • :*
  • :|
  • 8o
  • =O
  • <X
  • ||
  • :/
  • :S
  • X/
  • 8)
  • ?(
  • :huh:
  • :rolleyes:
  • :love:
  • 8|
  • :cursing:
  • :thumbdown:
  • :thumbup:
  • :sleeping:
  • :whistling:
  • :evil:
  • :saint:
  • <3
  • :!:
  • :?:

werbung

Partner-Community

Schaut Euch auch unsere Partner-Community an.
  1. Datenschutzerklärung
  2. Kontakt
  3. Impressum
Community-Software: WoltLab Suite™
Stil: Berryfox von Foxly