I have created a script that monitors the time each application is running on my main window in Ubuntu OS and sends the data to a PostgreSQL db. The script is working fine but I need to manually start the script and need to keep its terminal open, and if my display is suspended or I have not closed my system completely, the script keeps on recording the duration of application on window. After some searching I realized using systemd
services I can ensure the script starts when my system starts and stops if the display is in sleep mode or suspended.
This is my script:
import subprocessimport psycopg2import time def get_friendly_name(class_name): # Mapping class names to user-friendly names mapping = {"Code": "Visual Studio Code","notion-snap": "Notion", } return mapping.get(class_name, class_name)def get_app_name(window_id): result = subprocess.run(['xprop', '-id', window_id], stdout=subprocess.PIPE, stderr=subprocess.DEVNULL, text=True) xprop_output = result.stdout app_name= None app_class = None for line in xprop_output.split('\n'): if 'WM_NAME(STRING)' in line: app_name = line.split('"')[1] if 'WM_CLASS(STRING)' in line: app_class = line.split('"')[3] # Fallback to class name if WM_NAME is not found if not app_name and app_class: app_name = get_friendly_name(app_class) # Display the name of the application if app_name: return(app_name)host = #hostdbname = 'postgres'user = #userpassword = #passwordport = #portdef connect(): conn = None try: `conn = psycopg2.connect(host=host, dbname=dbname, user=user, password=password, port=port)` return conn except (Exception, psycopg2.DatabaseError) as e: print(e) return Nonedef postAppData(app_name, duration): conn = connect() if conn is None: return try: cur = conn.cursor() cur.execute(""" SELECT * FROM screen_time WHERE app_name = %s AND DATE(timestamp) = CURRENT_DATE;""", (app_name,)) row = cur.fetchone() if row: cur.execute(""" UPDATE screen_time SET duration = duration + %s WHERE id = %s;""", (duration, row[0])) else: cur.execute(""" INSERT INTO screen_time (app_name, duration) VALUES(%s, %s) RETURNING id;""", (app_name, duration)) conn.commit() cur.close() except(Exception, psycopg2.DatabaseError) as error: print(error) finally: if conn is not None: conn.close()def format_time(duration): if duration < 60: unit = "seconds" elif duration < 3600: duration /= 60 # Convert to minutes unit = "minutes" else: duration /= 3600 # Convert to hours unit = "hours" formatted_time = f"{duration:.2f}" return f"{formatted_time} {unit}"prev_window = Nonestart_time = time.time()while True: try: `result = subprocess.run(['xdotool', 'getactivewindow'], stdout=subprocess.PIPE, stderr=subprocess.DEVNULL, text=True)` window_id = result.stdout.strip() current_window = get_app_name(window_id) if current_window != prev_window: end_time = time.time() duration = end_time - start_time if prev_window is not None: postAppData(prev_window, duration) # print(f"Window: {prev_window}, Duration: {format_time(duration)}") prev_window = current_window start_time = end_time except Exception as e: print(f"An error occurred: {e}")
Here are the few services I tried writing:
[Unit]Description=My test serviceAfter=multi-user.target[Service]Type=simpleRestart=alwaysExecStart=/usr/bin/python3 /path/to/script.py[Install]WantedBy=multi-user.target
Paths above are accurate and absolute path from the root.
But when I checked status of this service, it showed it failed:process: (code=exited, status=2)
systemd[1]: ScreenTimeMonitor.service: Scheduled restart job, restart counter is at 5.
systemd[1]: Stopped My test service.
systemd[1]: ScreenTimeMonitor.service: Start request repeated too quickly.
systemd[1]: ScreenTimeMonitor.service: Failed with result 'exit-code'.
systemd[1]: Failed to start My test service.
I was expecting the script to run normally as service and log data into my db.