Properly handle Windows shutdown message

This commit is contained in:
DevilXD committed 2022-09-03 18:12:32 +02:00
1 parent 8ddfdc5114
commit 1e035f1a36
3 files changed
+40 -6

No files matched your search

+34 -4
View File
@@ -5,14 +5,17 @@ import logging
import webbrowser import webbrowser
import tkinter as tk import tkinter as tk
from math import log10, ceil from math import log10, ceil
from functools import partial
from collections import abc, namedtuple from collections import abc, namedtuple
from tkinter.font import Font, nametofont from tkinter.font import Font, nametofont
from functools import partial, cached_property
from datetime import datetime, timedelta, timezone from datetime import datetime, timedelta, timezone
from typing import Any, TypedDict, NoReturn, Generic, TYPE_CHECKING from typing import Any, TypedDict, NoReturn, Generic, TYPE_CHECKING
from tkinter import Tk, ttk, StringVar, DoubleVar, IntVar, PhotoImage from tkinter import Tk, ttk, StringVar, DoubleVar, IntVar, PhotoImage
import pystray import pystray
import win32api
import win32con
import win32gui
from yarl import URL from yarl import URL
from PIL import Image as Image_module from PIL import Image as Image_module
@@ -1631,8 +1634,6 @@ class GUIManager:
# root.resizable(False, True) # root.resizable(False, True)
root.iconbitmap(resource_path("pickaxe.ico")) # window icon root.iconbitmap(resource_path("pickaxe.ico")) # window icon
root.title(WINDOW_TITLE) # window title root.title(WINDOW_TITLE) # window title
root.protocol("WM_DELETE_WINDOW", self.close) # hook the X window closing button
root.protocol("WM_ENDSESSION", self.close) # hook the Windows shutdown signal
root.bind_all("<KeyPress-Escape>", self.unfocus) # pressing ESC unfocuses selection root.bind_all("<KeyPress-Escape>", self.unfocus) # pressing ESC unfocuses selection
# Image cache for displaying images # Image cache for displaying images
self._cache = ImageCache(self) self._cache = ImageCache(self)
@@ -1726,6 +1727,18 @@ class GUIManager:
self._root.after_idle(self.tray.minimize) self._root.after_idle(self.tray.minimize)
else: else:
self._root.deiconify() self._root.deiconify()
# NOTE: this root.update() is required for the below to work - don't remove
root.update()
self._message_map = {
# window close request
win32con.WM_CLOSE: self.close,
# shutdown request
win32con.WM_QUERYENDSESSION: self.close,
}
# This hooks up the wnd_proc function as the message processor for the root window.
self.old_wnd_proc = win32gui.SetWindowLong(
self._handle, win32con.GWL_WNDPROC, self.wnd_proc
)
# https://stackoverflow.com/questions/56329342/tkinter-treeview-background-tag-not-working # https://stackoverflow.com/questions/56329342/tkinter-treeview-background-tag-not-working
def _fixed_map(self, option): def _fixed_map(self, option):
@@ -1742,6 +1755,21 @@ class GUIManager:
if elm[:2] != ("!disabled", "!selected") if elm[:2] != ("!disabled", "!selected")
] ]
def wnd_proc(self, hwnd, msg, w_param, l_param):
"""
This function serves as a message processor for all messages sent
to the application by Windows.
"""
if msg == win32con.WM_DESTROY:
win32api.SetWindowLong(self._handle, win32con.GWL_WNDPROC, self.old_wnd_proc)
if msg in self._message_map:
return self._message_map[msg](w_param, l_param)
return win32gui.CallWindowProc(self.old_wnd_proc, hwnd, msg, w_param, l_param)
@cached_property
def _handle(self) -> int:
return int(self._root.wm_frame(), 16)
@property @property
def running(self) -> bool: def running(self) -> bool:
return self._poll_task is not None return self._poll_task is not None
@@ -1785,7 +1813,7 @@ class GUIManager:
await asyncio.sleep(0.05) await asyncio.sleep(0.05)
self._poll_task = None self._poll_task = None
def close(self, *args): def close(self, *args) -> int:
""" """
Requests the GUI application to close. Requests the GUI application to close.
The window itself will be closed in the closing sequence later. The window itself will be closed in the closing sequence later.
@@ -1793,6 +1821,7 @@ class GUIManager:
self._close_requested.set() self._close_requested.set()
# notify client we're supposed to close # notify client we're supposed to close
self._twitch.close() self._twitch.close()
return 0
def close_window(self): def close_window(self):
""" """
@@ -1828,6 +1857,7 @@ class GUIManager:
if __name__ == "__main__": if __name__ == "__main__":
# Everything below is for debug purposes only # Everything below is for debug purposes only
import aiohttp
from types import SimpleNamespace from types import SimpleNamespace
class StrNamespace(SimpleNamespace): class StrNamespace(SimpleNamespace):
+5 -2
View File
@@ -2,7 +2,6 @@ from __future__ import annotations
import io import io
import sys import sys
import ctypes
import signal import signal
import asyncio import asyncio
import logging import logging
@@ -25,6 +24,10 @@ try:
import PIL # noqa import PIL # noqa
except ModuleNotFoundError as exc: except ModuleNotFoundError as exc:
raise ImportError("You have to run 'pip install pillow' first") from exc raise ImportError("You have to run 'pip install pillow' first") from exc
try:
import win32gui # noqa
except ModuleNotFoundError as exc:
raise ImportError("You have to run 'pip install pywin32' first") from exc
from translate import _ from translate import _
from twitch import Twitch from twitch import Twitch
@@ -126,7 +129,7 @@ root.destroy()
# check if we're not already running # check if we're not already running
try: try:
exists = ctypes.windll.user32.FindWindowW(None, WINDOW_TITLE) exists = win32gui.FindWindow(None, WINDOW_TITLE)
except AttributeError: except AttributeError:
# we're not on Windows - continue # we're not on Windows - continue
exists = False exists = False
+1
View File
@@ -1,3 +1,4 @@
aiohttp>2.0,<4.0 aiohttp>2.0,<4.0
Pillow Pillow
pystray pystray
pywin32