Article
TimeScope, a Simple Desktop Activity Tracker
On this page 11
TimeScope
This is a small project I started on a whim a few months ago. It has recently reached a mostly finished state, so I am documenting it briefly here.
Repository: repo-address
Abstract
TimeScope is a small Windows desktop application that records application usage.
Its main dependencies are:
- Process and window collection:
pywin32,psutil - Data storage:
sqlite - Data visualization:
matplotlib - Text classification:
fasttext,jieba - GUI, background operation, and system tray:
customtkinter,pystray,PIL
1. Analysis and Data Processing
1) Data Collection
In simple terms, a timer periodically uses pywin32 to retrieve information about the currently focused window, including:
- Process name
- Window title
The program stores that information in a database together with the current time.
The database has two tables:
| Processes | window_sessions |
|---|---|
| name | process_id |
| executable_path (currently unused; assists debugging) | window_title |
| first_seen (currently unused; assists debugging) | start_time |
| last_seen (currently unused; assists debugging) | end_time |
The main loop compares the focused window with current_session and updates the record whenever they differ.
During an update, if the current window’s process name matches current_process_id, the process ID is used to create a new session.
If process.name has changed, the program creates both a new process record and its corresponding session.
loop():
process_name, window_title = self.tracker.get_foreground_info()
if process_name:
# Advance the internal clock whenever real activity occurs
tm.update_internal_clock()
# Current session
current_session = self.db.get_current_session_info()
should_record = False
# Check whether the record needs updating
if current_session is None:
should_record = True
else:
session_process, session_title, _, _ = current_session
if process_name != session_process or window_title != session_title:
should_record = True
# Update the record
if should_record:
if self.db.record_window_switch(process_name, window_title):
ts = datetime.now().strftime("%H:%M:%S")
self.update_log(f"[{ts}] Switched to: {process_name} - {window_title}\n")
# Timer interval
time.sleep(1)
2) Statistics
To produce statistics, the program iterates through window_sessions and plots the results with matplotlib.
3) Problems Encountered
The main problem was that the code could not reliably detect when the screen turned off and stop recording. Consequently, when the current application session ended, included the time during which the screen was off.
My first idea was to detect display-off and wake events. The win32 libraries seemed to provide relevant functionality, but unfortunately it did not work in practice; the mechanism was not responsive enough.
I therefore introduced an internal clock managed by time_manager, which continually updates its own state. When the gap between this clock and the actual system time exceeds the configured max_gap_seconds, the program repairs the record: it ends the previous process session using the internal time and then updates the clock.
2. Classifier / Machine Learning
Once the statistics were available, I wanted to design a classifier that could total the time spent on each type of activity.
The original idea combined a few rules with a small trained BERT model. Some applications belong to a single activity category, while web pages and video players require finer classification based on their window titles.
Because BERT turned out to be far too large, I trained several versions using TF-IDF and fastText instead.
1).TF-IDF
Term Frequency–Inverse Document Frequency is the product of term frequency (TF) and inverse document frequency (IDF).
Its core idea is that a word is an important feature when it appears frequently in the current passage but is uncommon across the full corpus.
- Term frequency:
- Inverse document frequency:
- Weight:
Applying this algorithm to all terms in a sentence produces a high-dimensional vector.
Example:
Vocabulary: ["code","visual","studio","python","game"]
Text vector: [0.8, 0.3, 0.3, 0, 0]
The training target is a linear multiclass classifier. Each category has a corresponding weight and bias .
Feeding in a vector produces a score for each category:
The category with the highest score is selected.
Disadvantages:
- It cannot handle unseen words effectively
- The feature dimensions are not stable
2).FastText
Simplified workflow
- Tokenization: split text into words (Chinese text can be segmented first).
- Subword decomposition: split each word into character n-grams, such as “coding” → “co”, “cod”, “odi”, …
- Word-vector construction: a word vector is the sum of all its n-gram vectors.
- Sentence-vector construction: a text vector is the average or weighted sum of all word vectors.
- Linear classification: use a linear layer with softmax to output category probabilities.
3. To Be Continued
I am also considering adding a sedentary-time reminder.
A basic reminder would be easy enough: simply package a system-notification library.
But could it be smarter? Sometimes the user may not actually be at the computer. Every type of application has a plausible duration of active use: people normally switch programs after some time instead of leaving the same focused window unchanged indefinitely. That behavior might provide evidence for a more intelligent reminder.