Sedentary reminder GUI using Python
This is a Python code sample to implement a sedentary reminder GUI on your Windows based personal computers. This tool uses Tkinter for GUI and win32 library to calculate total session times based on windows events like login, logoff, lock, unlock, etc.
This tool has mainly 2 modules, named as scheduler.py and reminder.py. scheduler.py is built using pywin32 library and helps in tracking session lock and unlock time. Using this, we can calculate total login time for the day, and also current session active time. reminder.py is a TKinter GUI which displays messages.
Both these modules are scheduled to run in background in our windows machines.
In this example, we are going to configure following reminders.
Hourly reminder to drink water.
If the session continuously active for more than an hour, a reminder popup to have a walk and rest your eyes.
Also, a message within the popup, that mentions total login time for the day.
You can build on this to apply additional logic as per your need. Note that this tool is made for the Windows OS using pywin32 library. We have similar libraries for other operating systems which can be used to develop same tool for other OS as well.
-
Install Python, Tkinter and pywin32
Download and install the latest version of python from Python Website.
Keep your favorite Python editor ready. This can be a basic notepad application available by default for your OS or you can download and install any free editor like VS Code.
Set up virtual environment also to avoid any unexpected errors.
Open command prompt and navigate to the folder where you are going to place the code and activate virtual environment.
Install TKinter using command pip install tk
Please refer Tkinter Quick Reference if needed.Install pywin32 using command pip install pywin32
To check if libraries installed successfully, type command pip freeze
Should give an output like:pywin32==302 tk==0.1.0
-
Scheduler
scheduler.py creates a hidden window which runs in background and calls function trackSessionChanges for each event. In this function, we are only tracking session change event group with id 689 and events lock and unlock with ids 7 and 8.
Above events calls a function writeTimeToJson, which records the session start time and total active seconds for the day in a json file. On session lock, total time gets updated. On unlock, session start time is recorded.scheduler.py and reminder.py uses data stored in data.json file. Make sure to add this file in the same folder and place a square bracket [] inside before running for the first time.
Copiedimport win32api as api import win32con as con import win32gui as gui import win32ts as ts import datetime import time import json import os windowid = 0 totSecs = 0 jsnfile = os.path.dirname(__file__) + '\data.json' sessStartTime = datetime.datetime.now() itm = [] def trackSessionChanges(wid, evgrp, evid, sessid): global sessStartTime, totSecs if evgrp == 689:#Id for session change group if(evid == 8):#Session unlock writeTimeToJson(datetime.datetime.now(), 'unlock') elif(evid == 7):#Session lock writeTimeToJson(datetime.datetime.now(), 'lock') def writeTimeToJson(tm, type): itm = [] tmStr = str(tm.year) + '-' + str(tm.month) + '-' + str(tm.day) + ' ' + str(tm.hour) + ':' + str(tm.minute) + ':' + str(tm.second) with open(jsnfile) as i: itm = json.load(i) if(type == 'unlock'): if(len(itm) == 0): itm.append({'sessionStartTime': tmStr, 'totSecs': 0, 'sessActive': 'Y'}) else: jsonTme = datetime.datetime.strptime(itm[0]['sessionStartTime'], '%Y-%m-%d %H:%M:%S') if(tm.day > jsonTme.day): #Old session started yesterday. So reset seconds itm = [] itm.append({'sessionStartTime': tmStr, 'totSecs': 0, 'sessActive': 'Y'}) else: secs = itm[0]['totSecs'] itm = [] itm.append({'sessionStartTime': tmStr, 'totSecs': secs, 'sessActive': 'Y'}) elif(type == 'lock'): if(len(itm) == 0): itm.append({'sessionStartTime': tmStr, 'totSecs': 0, 'sessActive': 'N'}) else: jsonTme = datetime.datetime.strptime(itm[0]['sessionStartTime'], '%Y-%m-%d %H:%M:%S') secs = int(itm[0]['totSecs']) + (tm - jsonTme).seconds tm = itm[0]['sessionStartTime'] itm = [] itm.append({'sessionStartTime': tm, 'totSecs': secs, 'sessActive': 'N'}) with open(jsnfile, 'w') as cw: json.dump(itm, cw) cname = "HiddenWindow" hndle = api.GetModuleHandle(None) wc = gui.WNDCLASS() wc.hInstance = hndle wc.lpszClassName = cname wc.lpfnWndProc = trackSessionChanges cl = gui.RegisterClass(wc) w = gui.CreateWindow(cl, cname, 0, 0, 0, con.CW_USEDEFAULT, con.CW_USEDEFAULT, 0, 0, hndle, None) gui.UpdateWindow(w) ts.WTSRegisterSessionNotification(w, ts.NOTIFY_FOR_ALL_SESSIONS) if __name__ == '__main__': gui.PumpMessages()
-
Reminder
Create reminder.py in the same folder and add below code. This creates a Tkinter window to display messages. getMessage() function in this module has the logic to check for the time to display the popup message. If session is active and checks satisfies, a message as in below screenshot is displayed. You can modify this logic as per your need. Update sessMessageInterval value to change the message frequency. Here it is set to 3600 seconds (1 Hour).
Copiedfrom tkinter import * import tkinter as tk import datetime import time import json import os jsnfile = os.path.dirname(__file__) + '\data.json' def getMessage(): msg = '' sessMessageInterval = 3600 #1 hour now = datetime.datetime.now() with open(jsnfile) as i: itm = json.load(i) sstTm = datetime.datetime.strptime(itm[0]['sessionStartTime'], '%Y-%m-%d %H:%M:%S') secs = int(itm[0]['totSecs']) + (now - sstTm).seconds if(itm[0]['sessActive'] == 'Y'): if ((now - sstTm).seconds > sessMessageInterval) and (((now - sstTm).seconds % sessMessageInterval) <= 600): #10 min msg = ('Current session has been active\nfor more than an hour.\n' + 'Please have a walk.\nRelax your eyes and body.') sessLastMsgTime = now openWindow(msg, secs) elif(time.localtime().tm_min < 10): #This will run only once an hour msg = ('Take a break for drinks') openWindow(msg, secs) def openWindow(msg, secs): root = tk.Tk() root.title("Reminder") root.geometry("350x300") txtS = tk.StringVar() txtM = tk.StringVar() #print(secs) hr = str(int(secs // 3600)) min = str(int((secs % 3600) // 60)) def close(): root.destroy() return lblS= Label(root, textvariable=txtS) lblS.configure(font=('Calibri Bold', 15), fg='Black') txtS.set("Todays' Total Login Time: " + hr + " hr: " + min + " min.") lblS.pack(fill='both', expand=True) lblM= Label(root, textvariable=txtM) txtM.set(msg) lblM.configure(font=('Calibri Bold', 15), fg='Blue') lblM.pack(fill='both', expand=True) btnClose = tk.Button(root, text ="Close", command = close) btnClose.configure(font=('Calibri Bold', 15), fg='Red') btnClose.pack(side=BOTTOM) root.mainloop() if __name__ == '__main__': getMessage()
-
Batch Files
When we test our modules, make sure both these modules are running at the same time. So open 2 cmd windows and run both modules using command, python reminder.py or py reminder.py
After testing we have to schedule these 2 modules using windows scheduler to run in background. For this, create 2 batch files, scheduler.bat and reminder.bat and add the below code in these batch files. Make sure to provide the right path of the python and code here. C:\Users\UserName\AppData\Local\Programs\Python\Python310\pythonw.exe "CodePath\scheduler.py"; exit 0 C:\Users\UserName\AppData\Local\Programs\Python\Python310\pythonw.exe "CodePath\reminder.py"Use command where.exe python to get the python path to use in batch file.
We are using pythonw.exe to make sure that we are not getting the command window when the modules runs in background. -
Using Windows Scheduler to schedule your modules
Last step is to schedule these 2 batch files in Windows scheduler.
scheduler module tracks the session locks and unlocks. So this should be running in the background always. So schedule this to start at the user logon
We will schedule the reminder module to run every 10 minutes. getMessage() function is called each time to check for the time to display the message. If we are changing the frequency of reminder module call, make sure to update minutes in code as well in line numbers 20 and 25.