Skip to content

Commit c727733

Browse files
authored
Merge pull request #166 from mathoudebine/feature/132-use-win32apisetconsolectrlhandler-to-receive-shutdownlogoff-events-from-windows
Catch Windows logoff/shutdown event to do a clean stop, fix Ctrl+C for Windows
2 parents e8a7353 + d3e6e15 commit c727733

File tree

2 files changed

+79
-13
lines changed

2 files changed

+79
-13
lines changed

library/log.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,11 +18,13 @@
1818

1919
# Configure logging format
2020
import logging
21+
from datetime import datetime
22+
date = datetime.now().strftime('%Y-%m-%d-%H-%M-%S')
2123

2224
logging.basicConfig(# format='%(asctime)s [%(levelname)s] %(message)s in %(pathname)s:%(lineno)d',
2325
format="%(asctime)s [%(levelname)s] %(message)s",
2426
handlers=[
25-
# logging.FileHandler("log.log", mode='w'), # Log in textfile (erased at each start)
27+
# logging.FileHandler("log_"+date+".log", mode='w'), # Log in textfile
2628
logging.StreamHandler() # Log also in console
2729
],
2830
datefmt='%H:%M:%S')

main.py

Lines changed: 76 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -22,12 +22,8 @@
2222
# along with this program. If not, see <https://www.gnu.org/licenses/>.
2323

2424
# This file is the system monitor main program to display HW sensors on your screen using themes (see README)
25-
import locale
2625
import os
27-
import platform
28-
import signal
2926
import sys
30-
import time
3127

3228
MIN_PYTHON = (3, 7)
3329
if sys.version_info < MIN_PYTHON:
@@ -37,8 +33,18 @@
3733
except:
3834
os._exit(0)
3935

36+
import atexit
37+
import locale
38+
import platform
39+
import signal
40+
import time
4041
from PIL import Image
4142

43+
if platform.system() == 'Windows':
44+
import win32api
45+
import win32con
46+
import win32gui
47+
4248
try:
4349
import pystray
4450
except:
@@ -86,7 +92,7 @@ def clean_stop(tray_icon=None):
8692
os._exit(0)
8793

8894

89-
def sighandler(signum, frame=None):
95+
def on_signal_caught(signum, frame=None):
9096
logger.info("Caught signal %d, exiting" % signum)
9197
clean_stop()
9298

@@ -96,6 +102,25 @@ def on_exit_tray(tray_icon, item):
96102
clean_stop(tray_icon)
97103

98104

105+
def on_clean_exit(*args):
106+
logger.info("Program will now exit")
107+
clean_stop()
108+
109+
110+
if platform.system() == "Windows":
111+
def on_win32_ctrl_event(event):
112+
"""Handle Windows console control events (like Ctrl-C)."""
113+
if event in (win32con.CTRL_C_EVENT, win32con.CTRL_BREAK_EVENT, win32con.CTRL_CLOSE_EVENT):
114+
logger.info("Caught Windows control event %s, exiting" % event)
115+
clean_stop()
116+
return 0
117+
118+
119+
def on_win32_wm_event(hWnd, msg, wParam, lParam):
120+
"""Handle Windows window message events (like ENDSESSION, CLOSE, DESTROY)."""
121+
logger.debug("Caught Windows window message event %s, exiting" % msg)
122+
clean_stop()
123+
99124
# Create a tray icon for the program, with an Exit entry in menu
100125
try:
101126
tray_icon = pystray.Icon(
@@ -116,12 +141,15 @@ def on_exit_tray(tray_icon, item):
116141
tray_icon = None
117142
logger.warning("Tray icon is not supported on your platform")
118143

119-
# Set the signal handlers, to send a complete frame to the LCD before exit
120-
signal.signal(signal.SIGINT, sighandler)
121-
signal.signal(signal.SIGTERM, sighandler)
144+
# Set the different stopping event handlers, to send a complete frame to the LCD before exit
145+
atexit.register(on_clean_exit)
146+
signal.signal(signal.SIGINT, on_signal_caught)
147+
signal.signal(signal.SIGTERM, on_signal_caught)
122148
is_posix = os.name == 'posix'
123149
if is_posix:
124-
signal.signal(signal.SIGQUIT, sighandler)
150+
signal.signal(signal.SIGQUIT, on_signal_caught)
151+
if platform.system() == "Windows":
152+
win32api.SetConsoleCtrlHandler(on_win32_ctrl_event, True)
125153

126154
# Initialize the display
127155
display.initialize_display()
@@ -150,13 +178,49 @@ def on_exit_tray(tray_icon, item):
150178
scheduler.DateStats()
151179
scheduler.QueueHandler()
152180

153-
if tray_icon and platform.system() == "Darwin":
154-
from AppKit import NSBundle, NSApp, NSAutoreleasePool, NSApplicationActivationPolicyRegular, NSApplicationActivationPolicyProhibited
181+
if tray_icon and platform.system() == "Darwin": # macOS-specific
182+
from AppKit import NSBundle, NSApp, NSApplicationActivationPolicyProhibited
155183

156-
# Hide Python Launcher icon from MacOS dock
184+
# Hide Python Launcher icon from macOS dock
157185
info = NSBundle.mainBundle().infoDictionary()
158186
info["LSUIElement"] = "1"
159187
NSApp.setActivationPolicy_(NSApplicationActivationPolicyProhibited)
160188

161189
# For macOS: display the tray icon now with blocking function
162190
tray_icon.run()
191+
192+
elif platform.system() == "Windows": # Windows-specific
193+
# Create a hidden window just to be able to receive window message events (for shutdown/logoff clean stop)
194+
hinst = win32api.GetModuleHandle(None)
195+
wndclass = win32gui.WNDCLASS()
196+
wndclass.hInstance = hinst
197+
wndclass.lpszClassName = "turingEventWndClass"
198+
messageMap = {win32con.WM_QUERYENDSESSION: on_win32_wm_event,
199+
win32con.WM_ENDSESSION: on_win32_wm_event,
200+
win32con.WM_QUIT: on_win32_wm_event,
201+
win32con.WM_DESTROY: on_win32_wm_event,
202+
win32con.WM_CLOSE: on_win32_wm_event}
203+
204+
wndclass.lpfnWndProc = messageMap
205+
206+
try:
207+
myWindowClass = win32gui.RegisterClass(wndclass)
208+
hwnd = win32gui.CreateWindowEx(win32con.WS_EX_LEFT,
209+
myWindowClass,
210+
"turingEventWnd",
211+
0,
212+
0,
213+
0,
214+
win32con.CW_USEDEFAULT,
215+
win32con.CW_USEDEFAULT,
216+
0,
217+
0,
218+
hinst,
219+
None)
220+
while True:
221+
# Receive and dispatch window messages
222+
win32gui.PumpWaitingMessages()
223+
time.sleep(0.5)
224+
225+
except Exception as e:
226+
logger.error("Exception while creating event window: %s" % str(e))

0 commit comments

Comments
 (0)