Skip to content

Commit 223775a

Browse files
authored
Merge pull request #10 from Adam-Color/Develop
Merge for 1.2.0
2 parents 3abdcc3 + 59dc752 commit 223775a

20 files changed

+246
-139
lines changed

README.md

Lines changed: 11 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
1-
![version](https://img.shields.io/badge/Version-1.1.5-white.svg)
1+
![version](https://img.shields.io/badge/Version-1.2.0-white.svg)
22
![license](https://img.shields.io/badge/License-GPL%20v3-blue.svg)
3-
![python](https://img.shields.io/badge/Python-3.12.7-green.svg)
3+
![python](https://img.shields.io/badge/Python-3.12-green.svg)
44

55
# AppUsageGUI
66
### Application Runtime Tracker
@@ -22,8 +22,13 @@ Find them [here](https://github.com/Adam-Color/AppUsageGUI/releases)
2222

2323
## Contributing
2424

25-
Contributions are welcome and needed on the Develop branch! Here is a TODO list:
25+
### Contributions are welcome and needed! Here is a TODO list:
2626

27-
1. Add ways to analyze app usage data in the app
28-
2. Move to a single QT UI instead of multiple 'screens'
29-
3. Add support for tracking more than one executable per session
27+
* add integrations with professional applications
28+
* Optimize everything
29+
* Add support for tracking more than one executable per session
30+
31+
#### Version 2 ('v2-change-to-pyqt6' branch):
32+
33+
* Move to a single QT UI instead of multiple 'screens'
34+
* Add ways to analyze app usage data in the app

build.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@ def build_executable():
3232
f'--add-data "{icon_file}:." '
3333
f'--collect-submodules core '
3434
f'--collect-all psutil '
35-
f'--collect-all pyautogui '
35+
f'--collect-all pynput '
3636
f'--collect-all tkinter '
3737
f'--icon={icon_file} '
3838
f'--add-data "src/_version.py:." '
Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
11
#!/bin/bash
22
# This script is used to create the installer for the macOS version of the application
33

4-
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.1.5_MACOS_setup.dmg dist/AppUsageGUI.app
4+
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

installer/windows_installer.iss renamed to installers/windows_installer.iss

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,11 +2,11 @@
22
; SEE THE DOCUMENTATION FOR DETAILS ON CREATING INNO SETUP SCRIPT FILES!
33

44
#define MyAppName "AppUsageGUI"
5-
#define MyAppVersion "1.1.5"
5+
#define MyAppVersion "1.2.0"
66
#define MyAppPublisher "Adam Blair-Smith"
77
#define MyAppURL "https://github.com/Adam-Color/AppUsageGUI"
88
#define MyAppExeName "AppUsageGUI.exe"
9-
#define MyInstallerName "AppUsageGUI_v1.1.5_WINDOWS_setup"
9+
#define MyInstallerName "AppUsageGUI_v1.2.0_WINDOWS_setup"
1010

1111
[Setup]
1212
; NOTE: The value of AppId uniquely identifies this application. Do not use the same AppId value in installers for other applications.

pytest.ini

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
# pytest.ini
2+
[pytest]
3+
testpaths = tests
4+
pythonpath = src

requirements.txt

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
MouseInfo==0.1.3
22
psutil==5.9.8
3-
PyAutoGUI==0.9.54
3+
pynput==1.8.1
44
PyGetWindow==0.0.9
55
PyMsgBox==1.0.9
66
pyobjc-core==11.0; sys_platform == "darwin"
@@ -12,4 +12,4 @@ PyScreeze==1.0.1
1212
pytweening==1.2.0
1313
rubicon-objc==0.5.0
1414
pyinstaller
15-
requests
15+
requests==2.32.3

src/_version.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
__version__ = "1.1.5"
1+
__version__ = "1.2.0"

src/core/gui_root.py

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -75,8 +75,6 @@ def reset_frames(self):
7575
except Exception as e:
7676
print(f"Crash in reset_frames(): {e}")
7777

78-
79-
8078
def on_close(self):
8179
"""Handle cleanup and close the application."""
8280
# Stop the AppTracker thread

src/core/logic/app_tracker.py

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,23 @@
33
import os
44
import psutil
55

6+
excluded_apps = {
7+
'python', 'AppUsageGUI', 'Adobe Crash Processor', 'AdobeIPCBroker',
8+
'AdobeUpdateService', 'BMDStreamingServer', 'DesktopVideoUpdater',
9+
'gamingservices', 'gamingservicesnet', 'Registry', 'services',
10+
'System', 'system', 'System Idle Process', 'svchost', 'taskhostw',
11+
'taskhostex', 'wmi', 'WmiPrvSE', 'Windows Internal Database',
12+
'Windows Security Notification Icon', 'Windows Terminal',
13+
'wininit', 'winlogon', 'wlanext', 'WmiApSrv','dwm', 'explorer',
14+
'SearchIndexer', 'SearchProtocolHost', 'xrdd', 'conhost', 'csrss',
15+
'smss', 'lsass', 'win32k', 'SystemSettings', 'RuntimeBroker',
16+
'Taskmgr', 'ApplicationFrameHost', 'ShellExperienceHost',
17+
'SearchApp', 'ShellInfrastructureHost', 'amdfendrsr',
18+
'CrossDeviceService', 'dwmcore', 'fontdrvhost',
19+
'lghub_agent', 'lghub_updater', 'lghub_system_tray',
20+
'AggregatorHost', 'sntlkeyssrvr', 'sntlsrvnt'
21+
}
22+
623
class AppTracker:
724
def __init__(self, parent, logic_controller):
825
self.app_names = []
@@ -20,6 +37,10 @@ def _start_tracking(self):
2037
def _fetch_app_names(self):
2138
apps = []
2239
seen_names = set()
40+
41+
# add excluded apps to seen_names
42+
seen_names.update(excluded_apps)
43+
2344
for process in psutil.process_iter(['name']):
2445
try:
2546
app_name = process.info['name']
@@ -55,6 +76,7 @@ def set_selected_app(self, app):
5576
self.selected_app = app
5677

5778
def stop(self):
79+
print("Stopping App Tracker")
5880
self.stop_event.set()
5981
if self.update_thread is not None:
6082
try:
@@ -63,6 +85,7 @@ def stop(self):
6385
pass
6486

6587
def start(self):
88+
print("Starting App Tracker")
6689
self.stop_event = threading.Event()
6790
self._start_tracking()
6891

src/core/logic/file_handler.py

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,16 @@ def load_session_data(self, filename):
6161
self.corrupt_sessions.append((filename, "No hash file found"))
6262
self.data = None
6363

64+
def delete_session(self, filename):
65+
"""Delete data and hash files of a session"""
66+
file_path = os.path.join(self.directory, filename + '.dat')
67+
hash_path = os.path.join(self.directory, filename + '.hash')
68+
69+
if os.path.exists(file_path):
70+
os.remove(file_path)
71+
if os.path.exists(hash_path):
72+
os.remove(hash_path)
73+
6474
def get_data(self):
6575
return self.data
6676

src/core/logic/time_tracker.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,9 +40,11 @@ def clock(self):
4040
time.sleep(0.1)
4141

4242
def start(self):
43+
print("Starting time tracker")
4344
self.track = True
4445

4546
def stop(self):
47+
print("Stopping time tracker")
4648
self.track = False
4749

4850
def pause(self):

src/core/logic/user_trackers.py

Lines changed: 15 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,7 @@
44
"""
55

66
import threading
7-
import time
8-
import pyautogui
7+
import pynput
98

109
from core.utils.file_utils import read_file, config_file
1110

@@ -21,7 +20,7 @@ def __init__(self, parent, logic_controller):
2120
y = 0
2221
self.logic_controller = logic_controller
2322
self.mouse_position = x, y
24-
self.last_mouse_position = x , y
23+
self.last_mouse_position = x, y
2524
self.stop_event = threading.Event() # Used to stop the thread gracefully
2625
try:
2726
self.enabled = read_file(config_file())["mouse_tracker_enabled"]
@@ -36,29 +35,34 @@ def _update_mouse_position(self):
3635
while not self.stop_event.is_set():
3736
self.last_mouse_position = self.mouse_position
3837

39-
# time limit handling
40-
if not self.logic_controller.time_tracker.get_is_paused():
41-
time.sleep(self.idle_time_limit)
42-
else:
43-
time.sleep(1)
38+
wait_time = self.idle_time_limit if not self.logic_controller.time_tracker.get_is_paused() else 1
4439

45-
x, y = pyautogui.position()
40+
# Wait for the idle time or exit early if stop_event is set
41+
if self.stop_event.wait(timeout=wait_time):
42+
break
43+
44+
x, y = pynput.mouse.Controller().position
4645
self.mouse_position = x, y
4746

48-
# pause the timer
47+
# Pause the timer if mouse hasn’t moved
4948
if self.last_mouse_position == self.mouse_position:
5049
self.logic_controller.time_tracker.pause()
5150
self.pausing = True
5251
elif self.logic_controller.time_tracker.get_is_paused():
5352
self.logic_controller.time_tracker.resume()
5453
self.pausing = False
5554

55+
5656
def start(self):
57-
if self.enabled and not self.update_thread:
57+
if self.enabled:
58+
self.stop_event = threading.Event() # Reset the stop event to allow the thread to run again
59+
self.update_thread = threading.Thread(target=self._update_mouse_position)
60+
print("Starting mouse tracker")
5861
self.update_thread.start()
5962

6063
def stop(self):
6164
self.stop_event.set()
65+
print("Stopping mouse tracker")
6266
if self.update_thread is not None:
6367
try:
6468
self.update_thread.join()

src/core/screens/save_window.py

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import tkinter as tk
2-
import pickle
2+
3+
from core.utils.time_utils import format_time
34

45
class SaveWindow(tk.Frame):
56
def __init__(self, parent, controller, logic_controller):
@@ -23,9 +24,9 @@ def __init__(self, parent, controller, logic_controller):
2324
def save(self):
2425
if self.logic_controller.file_handler.get_continuing_session():
2526
session_time = self.logic_controller.time_tracker.get_total_time()
26-
#print("Session time: ", session_time) #! debug print
27+
print("Session time: ", format_time(round(session_time)))
2728
session_app_name = self.logic_controller.app_tracker.get_selected_app()
28-
#print("Session_app_name: ", session_app_name) #! debug print
29+
print("Session app name: ", session_app_name)
2930

3031
data = {'app_name': session_app_name, 'time_spent': session_time}
3132

src/core/screens/session_total_window.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -71,4 +71,4 @@ def stop_threads(self):
7171
except AttributeError:
7272
pass # If no updates were scheduled yet, ignore error
7373
except ValueError:
74-
pass # If already cancelled, ignore error
74+
pass # If already cancelled, ignore error

src/core/screens/sessions_window.py

Lines changed: 18 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -32,8 +32,12 @@ def __init__(self, parent, controller, logic_controller):
3232
self.load_sessions()
3333

3434
# button to make the selection
35-
select_button = tk.Button(self, text="Select", command=self.select_session)
36-
select_button.pack(pady=10)
35+
select_button = tk.Button(self, text="Continue from selected session", command=self.select_session)
36+
select_button.pack(pady=5)
37+
38+
# button to delete the session
39+
delete_button = tk.Button(self, text="Delete selected session", command=self.delete_session)
40+
delete_button.pack(pady=5)
3741

3842
back_button = tk.Button(self, text="Main Menu", command=lambda: (self.controller.reset_frames(), self.controller.show_frame("MainWindow")))
3943
back_button.pack(pady=5, side='bottom')
@@ -95,3 +99,15 @@ def select_session(self):
9599

96100
# show the TrackerWindow
97101
self.controller.show_frame('TrackerWindow')
102+
103+
def delete_session(self):
104+
if not self.get_session_text():
105+
tk.messagebox.showerror("Error", "No session selected")
106+
return 0
107+
else:
108+
selected_session_name = self.get_session_text().split(": ")[0]
109+
# ask for confirmation
110+
confirm = tk.messagebox.askyesno("Confirm Deletion", f"Are you sure you want to delete the session '{selected_session_name}'? \nThis action cannot be undone.")
111+
if confirm:
112+
self.logic_controller.file_handler.delete_session(selected_session_name)
113+
self.session_listbox.delete(self.session_listbox.curselection())

src/core/screens/tracker_window.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
"""Screen that displays the time tracked for a specific application. Needs optimazations"""
2+
13
import tkinter as tk
24
import threading
35
import queue
@@ -96,6 +98,7 @@ def update_time_label(self):
9698

9799
time.sleep(0.1)
98100
if not self.stop_event.is_set():
101+
self.controller.frames["SessionTotalWindow"].stop_threads()
99102
self.controller.show_frame("SaveWindow")
100103

101104

src/core/utils/file_utils.py

Lines changed: 10 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -26,11 +26,13 @@ def config_file():
2626
"""Returns the path to the config file"""
2727
return os.path.join(get_user_directory(), 'config.dat')
2828

29-
def sessions_exist():
30-
"""Check if sessions exist, and if not, create the directory"""
29+
def sessions_exist(p=False):
30+
"""Check if sessions exist, and if not, create the directory.
31+
Set p=True to print directory path"""
3132
file_extension = ".dat"
3233
sessions_dir = get_sessions_directory()
33-
print("sessions_dir: %s" % sessions_dir) #!
34+
if p:
35+
print("sessions_dir: %s" % sessions_dir)
3436
# Ensure the directory exists
3537
if not os.path.exists(sessions_dir):
3638
os.makedirs(sessions_dir, exist_ok=True)
@@ -40,10 +42,12 @@ def sessions_exist():
4042
return True
4143
return False
4244

43-
def user_dir_exists():
44-
"""Check if user directory exists, and if not, create the directory"""
45+
def user_dir_exists(p=False):
46+
"""Check if user directory exists, and if not, create the directory.
47+
Set p=True to print directory path"""
4548
user_dir = get_user_directory()
46-
print("user_dir: %s" % user_dir) #!
49+
if p:
50+
print("user_dir: %s" % user_dir)
4751
# Ensure the directory exists
4852
if not os.path.exists(user_dir):
4953
os.makedirs(user_dir, exist_ok=True)

0 commit comments

Comments
 (0)