Skip to content

Commit 5412ea2

Browse files
committed
Load running processes in a thread
1 parent d0e33b5 commit 5412ea2

File tree

1 file changed

+71
-12
lines changed

1 file changed

+71
-12
lines changed

mousetracks2/gui/applist.py

Lines changed: 71 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -3,13 +3,51 @@
33
import sys
44

55
import psutil
6-
from PySide6 import QtCore, QtWidgets
6+
from PySide6 import QtCore, QtGui, QtWidgets
77

88
from .ui import applist
99
from ..applications import LOCAL_PATH, AppList
1010
from ..constants import TRACKING_IGNORE, TRACKING_DISABLE
1111

1212

13+
PROCESS_LOAD_TEXT = 'Loading processes list...'
14+
15+
16+
class ProcessWorker(QtCore.QThread):
17+
"""Load a list of running processes in a thread."""
18+
19+
process_found = QtCore.Signal(str)
20+
loading_finished = QtCore.Signal()
21+
22+
def __init__(self, parent: QtWidgets.QWidget | None = None) -> None:
23+
super().__init__(parent)
24+
self._running = True
25+
26+
def run(self) -> None:
27+
seen: set[str] = set()
28+
for proc in sorted(psutil.process_iter(attrs=['exe', 'create_time']),
29+
key=lambda proc: proc.info['create_time'] or 0.0, reverse=True):
30+
if not self._running:
31+
return
32+
33+
path = proc.info['exe']
34+
if path is None:
35+
continue
36+
37+
exe = os.path.basename(path)
38+
if exe in seen:
39+
continue
40+
seen.add(exe)
41+
42+
self.process_found.emit(exe)
43+
44+
self.loading_finished.emit()
45+
46+
def cancel(self) -> None:
47+
"""Called by the main thread to tell this thread to stop."""
48+
self._running = False
49+
50+
1351
class AppListWindow(QtWidgets.QDialog):
1452
"""Interface to the AppList.txt file."""
1553

@@ -22,9 +60,12 @@ def __init__(self, parent: QtWidgets.QWidget) -> None:
2260
self.ui.setupUi(self)
2361
self.ui.advanced.setChecked(False)
2462

63+
self.worker = ProcessWorker(self)
64+
2565
self.ui.save.clicked.connect(self.save)
2666
self.ui.executable.currentTextChanged.connect(self.update_profile_suggestion)
2767
self.ui.executable.currentTextChanged.connect(self.update_matching_apps)
68+
self.ui.executable.currentTextChanged.connect(self.update_button_state)
2869
self.ui.executable.currentIndexChanged.connect(self.executable_changed)
2970
self.ui.window_title.textChanged.connect(self.update_profile_suggestion)
3071
self.ui.window_title_enabled.toggled.connect(self.update_profile_suggestion)
@@ -36,21 +77,30 @@ def __init__(self, parent: QtWidgets.QWidget) -> None:
3677

3778
self._populate_process_list()
3879

80+
def closeEvent(self, event: QtGui.QCloseEvent) -> None:
81+
"""Safely shut down background threads before closing."""
82+
if self.worker.isRunning():
83+
self.worker.cancel()
84+
self.worker.wait()
85+
86+
super().closeEvent(event)
87+
3988
def _populate_process_list(self) -> None:
4089
"""Load in the list of all processes."""
4190
self.ui.executable.clear()
91+
self.ui.executable.addItem(PROCESS_LOAD_TEXT)
4292

43-
seen = set()
44-
for proc in sorted(psutil.process_iter(attrs=['exe', 'create_time']),
45-
key=lambda proc: proc.info['create_time'], reverse=True):
46-
path = proc.info['exe']
47-
if path is None:
48-
continue
49-
exe = os.path.basename(path)
50-
if exe in seen:
51-
continue
52-
seen.add(exe)
53-
self.ui.executable.addItem(exe)
93+
self.worker.process_found.connect(self.process_found)
94+
self.worker.loading_finished.connect(self.processes_loaded)
95+
self.worker.start()
96+
97+
@QtCore.Slot(str)
98+
def process_found(self, exe: str) -> None:
99+
self.ui.executable.addItem(exe)
100+
101+
@QtCore.Slot()
102+
def processes_loaded(self) -> None:
103+
self.ui.executable.removeItem(0)
54104

55105
@QtCore.Slot(int)
56106
def executable_changed(self, index: int) -> None:
@@ -65,6 +115,8 @@ def update_profile_suggestion(self) -> None:
65115
"""Update the default profile name."""
66116
if self.ui.window_title_enabled.isChecked() and self.ui.window_title.text():
67117
self.ui.profile_name.setPlaceholderText(self.ui.window_title.text())
118+
elif self.ui.executable.currentText() == PROCESS_LOAD_TEXT:
119+
self.ui.profile_name.setPlaceholderText('')
68120
else:
69121
self.ui.profile_name.setPlaceholderText(os.path.splitext(self.ui.executable.currentText())[0])
70122

@@ -95,6 +147,13 @@ def update_matching_apps(self, force: bool = False) -> None:
95147
self.ui.rules.addItem(item)
96148
self.ui.rules.sortItems()
97149

150+
@QtCore.Slot()
151+
def update_button_state(self):
152+
"""Update the state of the create and remove buttons while loading."""
153+
loading = self.ui.executable.currentText() == PROCESS_LOAD_TEXT
154+
self.ui.create.setEnabled(not loading)
155+
self.ui.remove.setEnabled(not loading)
156+
98157
@QtCore.Slot()
99158
def create_new_rule(self) -> None:
100159
"""Create or update a rule with the current data."""

0 commit comments

Comments
 (0)