-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathsystray.py
executable file
·171 lines (147 loc) · 5.59 KB
/
systray.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
#!/usr/bin/env python3
from PyQt5.QtGui import *
from PyQt5.QtWidgets import *
from time import sleep
import logging
import os
import psutil
import signal
import subprocess
import sys
import threading
############################### CONFIGURE HERE ###############################
LAUNCH_ONLY = False # (required) If the program will only be launched of started+stopped
PROCESS = [""] # (required) Command to start the process
STOP_PROCESS = [""] # Command to stop the process (if not provided, pid from PROCESS will be killed if LAUNCH_ONLY is False)
ICON_ACTIVE = "" # (required) Systray icon
ICON_INACTIVE = "" # (required if LAUNCH_ONLY is True) Icon for inactive in case behavior is start/stop
ICON_WARNING = "" # (required if LAUNCH_ONLY is True) Icon for pid died while running in case behavior is start/stop
###############################################################################
PID = 0
logging.basicConfig(
level=logging.DEBUG,
format="[%(asctime)s] - %(levelname)s - %(message)s",
datefmt="%d/%b/%Y %H:%M:%S",
stream=sys.stdout,
)
# Check PID status and change systray icon if process was terminated
class CheckPIDStatus(threading.Thread):
def __init__(self, systray, systray_option, icon_warning):
super(CheckPIDStatus, self).__init__()
self.stop = False
self.systray = systray
self.systray_option = systray_option
self.icon_warning = icon_warning
def run(self):
while not (self.stop):
global PID
if PID != 0:
proc = psutil.Process(PID)
if not psutil.pid_exists(PID) or proc.status() == psutil.STATUS_ZOMBIE:
self.systray_option.setText("Warning/Error")
self.systray.setIcon(self.icon_warning)
sleep(5)
def check_variables():
if any(var not in globals() for var in ('LAUNCH_ONLY','PROCESS','ICON_ACTIVE')):
logging.error("Missing one or more required variables: LAUNCH_ONLY, PROCESS, ICON_ACTIVE")
sys.exit(1)
if not LAUNCH_ONLY and any(var not in globals() for var in ('ICON_INACTIVE','ICON_WARNING')):
logging.error("Missing one or more required variables: ICON_INACTIVE, ICON_WARNING (LAUNCH_ONLY is False)")
sys.exit(1)
else:
if 'STOP_PROCESS' in globals():
logging.info("STOP_PROCESS is provided but LAUNCH_ONLY is True")
# Kill app if already running
def kill_running_process():
global PID
if PID != 0:
try:
logging.info(f"Attempting to kill process with PID {PID}")
os.kill(PID, signal.SIGTERM)
logging.info(f"Killed process with PID {PID}")
PID = 0
except OSError as err:
logging.error(f"Error killing process: {err}")
PID = 0
return False
return True
# Start/stop app
def start_stop_app(systray, icon_inactive, icon_active, icon_warning, systray_option):
global PID
if not LAUNCH_ONLY:
if systray_option.text() == "Stop":
if 'STOP_PROCESS' in globals() and STOP_PROCESS[0] != "":
result = subprocess.Popen(STOP_PROCESS, shell=False, preexec_fn=os.setsid)
PID = 0
logging.info(f"Stopped process gracefully")
systray_option.setText("Start")
systray.setIcon(icon_inactive)
elif kill_running_process():
systray_option.setText("Start")
systray.setIcon(icon_inactive)
else:
systray_option.setText("Warning/Error")
systray.setIcon(icon_warning)
return
elif systray_option.text() == "Warning/Error":
logging.warning(
"Not launching process because there was an error with the previous instance"
)
return
else:
systray_option.setText("Stop")
systray.setIcon(icon_active)
result = subprocess.Popen(PROCESS, shell=False, preexec_fn=os.setsid)
PID = result.pid
logging.info(f"Launched process {PROCESS} with PID {PID}")
# Quit app
def quit_app(app, systray, icon_inactive, systray_option, thread):
thread.stop = True
app.quit()
def main():
check_variables()
# Create Qt app
app = QApplication([])
app.setQuitOnLastWindowClosed(False)
# Add an icon
if not LAUNCH_ONLY:
icon_inactive = QIcon(ICON_INACTIVE)
icon_warning = QIcon(ICON_WARNING)
else:
icon_inactive = ""
icon_warning = ""
icon_active = QIcon(ICON_ACTIVE)
# Add item on the menu bar
systray = QSystemTrayIcon()
if LAUNCH_ONLY:
systray.setIcon(icon_active)
else:
systray.setIcon(icon_inactive)
systray.setVisible(True)
systray.activated.connect(
lambda: start_stop_app(
systray, icon_inactive, icon_active, icon_warning, systray_option
)
)
# Create the options
menu = QMenu()
systray.setContextMenu(menu)
systray_option = QAction("Start")
menu.addAction(systray_option)
systray_option.triggered.connect(
lambda: start_stop_app(
systray, icon_inactive, icon_active, icon_warning, systray_option
)
)
# Thread to check if PIDs are still running
thread_pid_status = CheckPIDStatus(systray, systray_option, icon_warning)
if not LAUNCH_ONLY:
thread_pid_status.start()
quit_option = QAction("Quit")
menu.addAction(quit_option)
quit_option.triggered.connect(
lambda: quit_app(app, systray, icon_inactive, systray_option, thread_pid_status)
)
app.exec_()
if __name__ == "__main__":
main()