Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

WiP - [#3011|GTK] Catch win32 endsession message #225

Draft
wants to merge 1 commit into
base: develop
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
53 changes: 33 additions & 20 deletions deluge/ui/gtk3/mainwindow.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@
from twisted.internet.error import ReactorNotRunning

import deluge.component as component
from deluge.common import decode_bytes, fspeed, resource_filename
from deluge.common import decode_bytes, fspeed, resource_filename, windows_check
from deluge.configmanager import ConfigManager
from deluge.ui.client import client

Expand Down Expand Up @@ -141,6 +141,13 @@ def patched_connect_signals(*a, **k):
'NewVersionAvailableEvent', self.on_newversionavailable_event
)

if windows_check():
from .utils.win32 import Win32ShutdownCheck

# Ensure we have gdkwindow.
self.window.realize()
Win32ShutdownCheck(self.window.get_window())

def connect_signals(self, mapping_or_class):
self.gtk_builder_signals_holder.connect_signals(mapping_or_class)

Expand Down Expand Up @@ -217,24 +224,7 @@ def quit(self, shutdown=False, restart=False): # noqa: A003 python builtin
Args:
shutdown (bool): Whether or not to shutdown the daemon as well.
restart (bool): Whether or not to restart the application after closing.

"""

def quit_gtkui():
def stop_gtk_reactor(result=None):
self.restart = restart
try:
reactor.callLater(0, reactor.fireSystemEvent, 'gtkui_close')
except ReactorNotRunning:
log.debug('Attempted to stop the reactor but it is not running...')

if shutdown:
client.daemon.shutdown().addCallback(stop_gtk_reactor)
elif not client.is_standalone() and client.connected():
client.disconnect().addCallback(stop_gtk_reactor)
else:
stop_gtk_reactor()

if self.config['lock_tray'] and not self.visible():
dialog = PasswordDialog(_('Enter your password to Quit Deluge...'))

Expand All @@ -244,11 +234,34 @@ def on_dialog_response(response_id):
self.config['tray_password']
== sha(decode_bytes(dialog.get_password()).encode()).hexdigest()
):
quit_gtkui()
self.restart = restart
self._quit_gtkui(shutdown)

dialog.run().addCallback(on_dialog_response)
else:
quit_gtkui()
self.restart = restart
self._quit_gtkui(shutdown)

@staticmethod
def _quit_gtkui(shutdown):
"""Quits the GtkUI.

Args:
shutdown (bool): Whether or not to shutdown the daemon as well.
"""

def stop_gtk_reactor(result):
try:
reactor.callLater(0, reactor.fireSystemEvent, 'gtkui_close')
except ReactorNotRunning:
log.debug('Attempted to stop the reactor but it is not running...')

if shutdown:
client.daemon.shutdown().addCallback(stop_gtk_reactor)
elif not client.is_standalone() and client.connected():
client.disconnect().addCallback(stop_gtk_reactor)
else:
stop_gtk_reactor()

def load_window_state(self):
if (
Expand Down
94 changes: 94 additions & 0 deletions deluge/ui/gtk3/utils/win32.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
import logging
from ctypes import c_wchar_p, windll

from gi.repository import Gtk
from win32gui import CallWindowProc, SetWindowLong

import deluge.component as component
from deluge.ui.client import client

WM_ENDSESSION = 16
WM_QUERYENDSESSION = 17
GWL_WNDPROC = -4

log = logging.getLogger(__name__)


class Win32ShutdownCheck(component):
"""Handle WM_QUERYENDSESSION, WM_ENDSESSION messages to shutdown cleanly."""

def __init__(self, gdk_window):
component.Component.__init__(self, 'Win32ShutdownCheck')
self.shutdown_block = None
# Set WndProc to self._on_wndproc and store old value.
self.prev_wndproc = SetWindowLong(
gdk_window.handle, GWL_WNDPROC, self._on_wndproc
)

def shutdown(self):
self.remove_shutdown_block()

def _on_wndproc(self, hwnd, msg, wparam, lparam):
"""Handles all messages sent to the window from Windows OS.

Args:
hwnd (ctypes.wintypes.HWND): Handle to window
msg (ctypes.wintypes.UINT): Message identifier. We handle
WM_ENDSESSION and WM_QUERYENDSESSION.
wparam (ctypes.wintypes.WPARAM): End-session option. If True with
message WM_ENDSESSION, Deluge GTK will shutdown.
lparam (ctypes.wintypes.LPARAM): Logoff option
Returns:
bool: If msg is WM_QUERYENDSESSION, False to indicate to prevent
shutdown. If msg is WM_ENDSESSION, False to indicate we handled
the massage. Else, the original WndProc return value.
"""

if msg == WM_QUERYENDSESSION:
log.debug('Received WM_QUERYENDSESSION, blocking shutdown')
log.info('Preparing to shutdown Deluge')
retval = windll.user32.ShutdownBlockReasonCreate(
hwnd, c_wchar_p('Shutting down Deluge')
)
log.debug('Shutdown block created: %s', retval != 0)
if retval != 0:
self.shutdown_block = hwnd
if client.connected() and client.is_localhost():
client.register_event_handler(
'SessionPausedEvent', self.remove_shutdown_block
)
# save resume data and pause session
client.core.torrentmanager.save_resume_data()
client.core.torrentmanager.save_resume_data_file()
client.core.pause_all_torrents()
else:
self.remove_shutdown_block()
return True
elif msg == WM_ENDSESSION:
log.debug('Received WM_ENDSESSION, checking status')
if not wparam:
log.info('Shutdown cancelled, resuming normal operation')
self.remove_shutdown_block()
if client.connected():
client.core.resume_all_torrents()
client.deregister_event_handler(
'SessionPausedEvent', self.remove_shutdown_block
)
else:
log.info('Shutting down Deluge GTK')
# wait for block to be destroyed
while Gtk.events_pending() and self.shutdown_block:
Gtk.main_iteration()
component.get('MainWindow')._quit_gtkui(False)
return False
else:
# Pass all messages on to the original WndProc.
return CallWindowProc(self.prev_wndproc, hwnd, msg, wparam, lparam)

def remove_shutdown_block(self):
"""Removes the blocking shutdown reason for Windows"""
if self.shutdown_block:
retval = windll.user32.ShutdownBlockReasonDestroy(self.shutdown_block)
if retval != 0:
self.shutdown_block = None
log.debug('Shutdown block destroyed: %s', retval != 0)
2 changes: 1 addition & 1 deletion setup.cfg
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ frameworks = CoreFoundation, Foundation, AppKit
known_standard_library = future_builtins
known_third_party =
# Ignore gtk modules, primarily for tox testing.
cairo, gi,
cairo, gi, win32gui
# Ignore other module dependencies for pre-commit isort.
twisted, OpenSSL, pytest, recommonmark, chardet, pkg_resources, zope, mock,
sphinx, rencode
Expand Down