Skip to content
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
![version](https://img.shields.io/badge/Version-1.2.0-white.svg)
![version](https://img.shields.io/badge/Version-1.3.0-white.svg)
![license](https://img.shields.io/badge/License-GPL%20v3-blue.svg)
![python](https://img.shields.io/badge/Python-3.12-green.svg)

Expand Down
2 changes: 1 addition & 1 deletion installers/macos_installer.sh
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
#!/bin/bash
# This script is used to create the installer for the macOS version of the application

create-dmg --volicon src/core/resources/icon.icns --volname AppUsageGUIsetup --window-pos 200 190 --window-size 800 400 --app-drop-link 600 185 --eula LICENSE.txt dist/AppUsageGUI_v1.2.0_MACOS_setup.dmg dist/AppUsageGUI.app
create-dmg --volicon src/core/resources/icon.icns --volname AppUsageGUIsetup --window-pos 200 190 --window-size 800 400 --app-drop-link 600 185 --eula LICENSE.txt dist/AppUsageGUI_v1.3.0_macOS_arm64_setup.dmg dist/AppUsageGUI.app
4 changes: 2 additions & 2 deletions installers/windows_installer.iss
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,11 @@
; SEE THE DOCUMENTATION FOR DETAILS ON CREATING INNO SETUP SCRIPT FILES!

#define MyAppName "AppUsageGUI"
#define MyAppVersion "1.2.0"
#define MyAppVersion "1.3.0"
#define MyAppPublisher "Adam Blair-Smith"
#define MyAppURL "https://github.com/Adam-Color/AppUsageGUI"
#define MyAppExeName "AppUsageGUI.exe"
#define MyInstallerName "AppUsageGUI_v1.2.0_WINDOWS_setup"
#define MyInstallerName "AppUsageGUI_v1.3.0_WINDOWS_setup"

[Setup]
; NOTE: The value of AppId uniquely identifies this application. Do not use the same AppId value in installers for other applications.
Expand Down
2 changes: 1 addition & 1 deletion src/_version.py
Original file line number Diff line number Diff line change
@@ -1 +1 @@
__version__ = "1.2.0"
__version__ = "1.3.0"
2 changes: 1 addition & 1 deletion src/core/logic/app_tracker.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ def __init__(self, parent, logic_controller):

def _start_tracking(self):
if self.update_thread is None:
self.update_thread = threading.Thread(target=self._monitor_processes)
self.update_thread = threading.Thread(target=self._monitor_processes, name="app_tracker")
self.update_thread.start()

def _fetch_app_names(self):
Expand Down
11 changes: 1 addition & 10 deletions src/core/logic/time_tracker.py
Original file line number Diff line number Diff line change
@@ -1,15 +1,6 @@
import time
import threading

def threaded(fn):
def wrapper(*args, **kwargs):
result = []
def run_and_capture():
result.append(fn(*args, **kwargs))
thread = threading.Thread(target=run_and_capture)
thread.start()
return thread, result
return wrapper
from core.utils.logic_utils import threaded

class TimeTracker:
def __init__(self, parent, logic_controller):
Expand Down
21 changes: 18 additions & 3 deletions src/core/logic/user_trackers.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ def __init__(self, parent, logic_controller):

self.pausing = False

self.update_thread = threading.Thread(target=self._update_mouse_position)
self.update_thread = threading.Thread(target=self._update_mouse_position, name="mouse_tracker")

def _update_mouse_position(self):
while not self.stop_event.is_set():
Expand All @@ -48,15 +48,15 @@ def _update_mouse_position(self):
if self.last_mouse_position == self.mouse_position:
self.logic_controller.time_tracker.pause()
self.pausing = True
elif self.logic_controller.time_tracker.get_is_paused():
elif self.pausing:
self.logic_controller.time_tracker.resume()
self.pausing = False


def start(self):
if self.enabled:
self.stop_event = threading.Event() # Reset the stop event to allow the thread to run again
self.update_thread = threading.Thread(target=self._update_mouse_position)
self.update_thread = threading.Thread(target=self._update_mouse_position, name="mouse_tracker")
print("Starting mouse tracker")
self.update_thread.start()

Expand Down Expand Up @@ -84,3 +84,18 @@ def is_pausing(self):

def is_enabled(self):
return self.enabled

class ResolveProjectTracker:
"""Tracks if the user is in a DaVinci Resolve project or not"""
def __init__(self, parent, logic_controller):
self.parent = parent
self.logic_controller = logic_controller
self.paused = False
self.project_name = None
self.project_open = False
self.stop_event = threading.Event() # Used to stop the thread gracefully
try:
self.enabled = read_file(config_file())["resolve_tracker_enabled"]
except FileNotFoundError or KeyError: #! KeyError for dev
self.enabled = False # Default value
self.update_thread = threading.Thread(target=self._update_project_status)
32 changes: 13 additions & 19 deletions src/core/screens/select_app_window.py
Original file line number Diff line number Diff line change
@@ -1,17 +1,8 @@
import tkinter as tk
from tkinter import messagebox
import time
import threading

def threaded(fn):
def wrapper(*args, **kwargs):
result = []
def run_and_capture():
result.append(fn(*args, **kwargs))
thread = threading.Thread(target=run_and_capture)
thread.start()
return thread, result
return wrapper
from core.utils.logic_utils import threaded

class SelectAppWindow(tk.Frame):
def __init__(self, parent, controller, logic_controller):
Expand Down Expand Up @@ -75,16 +66,19 @@ def select_app(self):
@threaded
def refresh_apps(self):
"""Fetch all app names and display them in the listbox."""
self.app_listbox.delete(0, tk.END)
time.sleep(1)
self.all_apps = self.app_tracker.get_app_names()
try:
self.app_listbox.delete(0, tk.END)
time.sleep(1)
self.all_apps = self.app_tracker.get_app_names()

if not self.all_apps:
messagebox.showerror("Error", "No applications found.")
else:
for app in self.all_apps:
self.app_listbox.insert(tk.END, app)
self.update_search()
if not self.all_apps:
messagebox.showerror("Error", "No applications found.")
else:
for app in self.all_apps:
self.app_listbox.insert(tk.END, app)
self.update_search()
except RuntimeError as e:
print("AppUsageGUI encountered an error it did not expect: ", e)

@threaded
def update_search(self, *args):
Expand Down
2 changes: 1 addition & 1 deletion src/core/screens/session_total_window.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ def __init__(self, parent, controller, logic_controller):
back_button.pack(pady=5, side='bottom')

# Start the total time thread
threading.Thread(target=self.total_time_thread, daemon=True).start()
threading.Thread(target=self.total_time_thread, daemon=True, name="total_session_time").start()

# Start the update queue
self.update_total_time()
Expand Down
44 changes: 36 additions & 8 deletions src/core/screens/tracker_window.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,16 +29,24 @@ def __init__(self, parent, controller, logic_controller):
self.time_label = tk.Label(self, text=self.track_time_disp)
self.time_label.pack(pady=10)

# TODO: pause and resume buttons need to be beutified

pause_button = tk.Button(self, text="Pause", command=self.logic_controller.time_tracker.pause)
# pause/resume button
self.pause_toggle_text = tk.StringVar()
self.pause_toggle_text.set("Pause")

pause_button = tk.Button(self,
textvariable=self.pause_toggle_text,
command=self.toggle_pause_tracker,
width=10)
pause_button.pack(pady=5)
resume_button = tk.Button(self, text="Resume", command=self.logic_controller.time_tracker.resume)
resume_button.pack(pady=5)

# stop button
self.stop_button_pressed = False
self.stop_button = tk.Button(self, text="Stop", command=self._stop, width=10)
self.stop_button.pack(pady=5)

self.update_queue = queue.Queue()

self.update_thread = threading.Thread(target=self.update_time_label)
self.update_thread = threading.Thread(target=self.update_time_label, name="update_time_label")
self.update_thread.daemon = True

self.periodic_update()
Expand All @@ -63,8 +71,9 @@ def update_time_label(self):
self.logic_controller.mouse_tracker.start()

# Stop tracking when the app closes
# includes exception for continuing tracking from a previous session.
if self.logic_controller.time_tracker.is_running() and self.app not in app_names and self.logic_controller.file_handler.get_continuing_tracker() is False:
# includes exception for continuing tracking from a previous session
# includes exception for when the user presses the stop button
if (self.logic_controller.time_tracker.is_running() and self.app not in app_names and self.logic_controller.file_handler.get_continuing_tracker() is False) or self.stop_button_pressed:
# handle situations where the time tracker is paused
self.logic_controller.time_tracker.resume()

Expand All @@ -85,6 +94,7 @@ def update_time_label(self):
#TODO: needs a more scalable implementation
if self.logic_controller.mouse_tracker.is_pausing():
self.page_label.config(text="Tracking paused, mouse is idle...")
self.toggle_pause_tracker(button=False)
else:
self.page_label.config(text=f"Tracking the selected app: {self.app}")

Expand All @@ -101,6 +111,24 @@ def update_time_label(self):
self.controller.frames["SessionTotalWindow"].stop_threads()
self.controller.show_frame("SaveWindow")

def toggle_pause_tracker(self, button=True):
"""Change the button text and pause/resume the tracker. Will not change the tracker if button is False."""
if self.pause_toggle_text.get() == "Pause":
self.pause_toggle_text.set("Resume")
else:
self.pause_toggle_text.set("Pause")

if button:
if self.logic_controller.time_tracker.get_is_paused():
self.logic_controller.time_tracker.resume()
else:
self.logic_controller.time_tracker.pause()

def _stop(self):
"""ask the user for confirmation to set the stop button pressed flag."""
confirm = tk.messagebox.askyesno("Confirm Stop Tracking", "Are you sure you want to stop tracking?\nProgress will be saved.")
if confirm:
self.stop_button_pressed = True

def periodic_update(self):
"""Update the GUI clock"""
Expand Down
11 changes: 11 additions & 0 deletions src/core/utils/logic_utils.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import threading

def threaded(fn):
def wrapper(*args, **kwargs):
result = []
def run_and_capture():
result.append(fn(*args, **kwargs))
thread = threading.Thread(target=run_and_capture, name=fn.__name__)
thread.start()
return thread, result
return wrapper