Properly handle Windows shutdown message
This commit is contained in:
1 parent
8ddfdc5114
commit
1e035f1a36
3 files changed
+40
-6
No files matched your search
@@ -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):
|
||||||
|
|||||||
@@ -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,3 +1,4 @@
|
|||||||
aiohttp>2.0,<4.0
|
aiohttp>2.0,<4.0
|
||||||
Pillow
|
Pillow
|
||||||
pystray
|
pystray
|
||||||
|
pywin32
|
||||||
Reference in new issue
Block a user