1
0
mirror of https://github.com/EDCD/EDMarketConnector.git synced 2025-04-16 17:12:21 +03:00

Merge pull request #851 from EDCD/enhancement/848-suppress-dupe-process-popup

Add --suppress-dupe-process-popup and move enforce_single_instance() call after
This commit is contained in:
Athanasius 2021-01-18 21:05:34 +00:00 committed by GitHub
commit 61275b73a1
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 54 additions and 84 deletions

View File

@ -15,9 +15,32 @@ from sys import platform
from time import localtime, strftime, time
from typing import TYPE_CHECKING
from config import applongname, appname, appversion, appversion_nobuild, config, copyright
from constants import applongname, appname, protocolhandler_redirect
# config will now cause an appname logger to be set up, so we need the
# console redirect before this
if __name__ == '__main__':
# Command-line arguments
parser = argparse.ArgumentParser(
prog=appname,
description="Utilises Elite Dangerous Journal files and the Frontier "
"Companion API (CAPI) service to gather data about a "
"player's state and actions to upload to third-party sites "
"such as EDSM, Inara.cz and EDDB."
)
parser.add_argument('--trace',
help='Set the Debug logging loglevel to TRACE',
action='store_true',
)
parser.add_argument('--suppress-dupe-process-popup',
help='Suppress the popup from when the application detects another instance already running',
action='store_true'
)
args = parser.parse_args()
if __name__ == "__main__":
def no_other_instance_running() -> bool: # noqa: CCR001
"""
Ensure only one copy of the app is running under this user account.
@ -70,7 +93,7 @@ if __name__ == "__main__":
and window_title(window_handle) == applongname \
and GetProcessHandleFromHwnd(window_handle):
# If GetProcessHandleFromHwnd succeeds then the app is already running as this user
if len(sys.argv) > 1 and sys.argv[1].startswith(protocolhandler.redirect):
if len(sys.argv) > 1 and sys.argv[1].startswith(protocolhandler_redirect):
CoInitializeEx(0, COINIT_APARTMENTTHREADED | COINIT_DISABLE_OLE1DDE)
# Wait for it to be responsive to avoid ShellExecute recursing
ShowWindow(window_handle, SW_RESTORE)
@ -93,6 +116,10 @@ if __name__ == "__main__":
import tkinter as tk
from tkinter import ttk
# Check for CL arg that suppresses this popup.
if args.suppress_dupe_process_popup:
sys.exit(0)
root = tk.Tk(className=appname.lower())
frame = tk.Frame(root)
@ -136,6 +163,11 @@ if __name__ == "__main__":
# unbuffered not allowed for text in python3, so use `1 for line buffering
sys.stdout = sys.stderr = open(join(tempfile.gettempdir(), f'{appname}.log'), mode='wt', buffering=1)
# TODO: Test: Make *sure* this redirect is working, else py2exe is going to cause an exit popup
# isort: off
from config import appversion, appversion_nobuild, config, copyright
# isort: on
from EDMCLogging import edmclogger, logger, logging
@ -1146,65 +1178,6 @@ class AppWindow(object):
self.blank_menubar.grid(row=0, columnspan=2, sticky=tk.NSEW)
def enforce_single_instance() -> None:
# Ensure only one copy of the app is running under this user account. OSX does this automatically. Linux TODO.
if platform == 'win32':
import ctypes
from ctypes.wintypes import BOOL, HWND, INT, LPARAM, LPCWSTR, LPWSTR
EnumWindows = ctypes.windll.user32.EnumWindows # noqa: N806
GetClassName = ctypes.windll.user32.GetClassNameW # noqa: N806
GetClassName.argtypes = [HWND, LPWSTR, ctypes.c_int]
GetWindowText = ctypes.windll.user32.GetWindowTextW # noqa: N806
GetWindowText.argtypes = [HWND, LPWSTR, ctypes.c_int]
GetWindowTextLength = ctypes.windll.user32.GetWindowTextLengthW # noqa: N806
GetProcessHandleFromHwnd = ctypes.windll.oleacc.GetProcessHandleFromHwnd # noqa: N806
SW_RESTORE = 9 # noqa: N806
SetForegroundWindow = ctypes.windll.user32.SetForegroundWindow # noqa: N806
ShowWindow = ctypes.windll.user32.ShowWindow # noqa: N806
ShowWindowAsync = ctypes.windll.user32.ShowWindowAsync # noqa: N806
COINIT_MULTITHREADED = 0 # noqa: N806,F841
COINIT_APARTMENTTHREADED = 0x2 # noqa: N806
COINIT_DISABLE_OLE1DDE = 0x4 # noqa: N806
CoInitializeEx = ctypes.windll.ole32.CoInitializeEx # noqa: N806
ShellExecute = ctypes.windll.shell32.ShellExecuteW # noqa: N806
ShellExecute.argtypes = [HWND, LPCWSTR, LPCWSTR, LPCWSTR, LPCWSTR, INT]
def window_title(h):
if h:
text_length = GetWindowTextLength(h) + 1
buf = ctypes.create_unicode_buffer(text_length)
if GetWindowText(h, buf, text_length):
return buf.value
return None
@ctypes.WINFUNCTYPE(BOOL, HWND, LPARAM)
def enumwindowsproc(window_handle, l_param):
# class name limited to 256 - https://msdn.microsoft.com/en-us/library/windows/desktop/ms633576
cls = ctypes.create_unicode_buffer(257)
if GetClassName(window_handle, cls, 257) \
and cls.value == 'TkTopLevel' \
and window_title(window_handle) == applongname \
and GetProcessHandleFromHwnd(window_handle):
# If GetProcessHandleFromHwnd succeeds then the app is already running as this user
if len(sys.argv) > 1 and sys.argv[1].startswith(protocolhandler.redirect):
# Browser invoked us directly with auth response. Forward the response to the other app instance.
CoInitializeEx(0, COINIT_APARTMENTTHREADED | COINIT_DISABLE_OLE1DDE)
# Wait for it to be responsive to avoid ShellExecute recursing
ShowWindow(window_handle, SW_RESTORE)
ShellExecute(0, None, sys.argv[1], None, None, SW_RESTORE)
else:
ShowWindowAsync(window_handle, SW_RESTORE)
SetForegroundWindow(window_handle)
sys.exit(0)
return True
EnumWindows(enumwindowsproc, 0)
def test_logging():
logger.debug('Test from EDMarketConnector.py top-level test_logging()')
@ -1221,24 +1194,6 @@ Locale LC_TIME: {locale.getlocale(locale.LC_TIME)}'''
# Run the app
if __name__ == "__main__":
enforce_single_instance()
# Command-line arguments
parser = argparse.ArgumentParser(
prog=appname,
description="Utilises Elite Dangerous Journal files and the Frontier "
"Companion API (CAPI) service to gather data about a "
"player's state and actions to upload to third-party sites "
"such as EDSM, Inara.cz and EDDB."
)
parser.add_argument('--trace',
help='Set the Debug logging loglevel to TRACE',
action='store_true',
)
args = parser.parse_args()
if args.trace:
logger.setLevel(logging.TRACE)
edmclogger.set_channels_loglevel(logging.TRACE)

View File

@ -6,9 +6,9 @@ from os.path import expanduser, dirname, exists, isdir, join, normpath
from sys import platform
import semantic_version
from constants import applongname, appname
# Any of these may be imported by plugins
appname = 'EDMarketConnector'
applongname = 'E:D Market Connector'
appcmdname = 'EDMC'
# appversion **MUST** follow Semantic Versioning rules:
# <https://semver.org/#semantic-versioning-specification-semver>

14
constants.py Normal file
View File

@ -0,0 +1,14 @@
"""
All constants for the application.
This file should contain all constants that the application uses, but that
means migrating the existing ones, so for now it's only the ones that we've
found necessary to move out of other files.
"""
# config.py
appname = 'EDMarketConnector'
applongname = 'E:D Market Connector'
# protocol.py
protocolhandler_redirect = 'edmc://auth'

View File

@ -5,7 +5,8 @@ import threading
import urllib.request, urllib.error, urllib.parse
import sys
from config import appname, config
from config import config
from constants import protocolhandler_redirect
if sys.platform == 'win32':
@ -20,7 +21,7 @@ if sys.platform == 'win32':
class GenericProtocolHandler(object):
def __init__(self):
self.redirect = 'edmc://auth' # Base redirection URL
self.redirect = protocolhandler_redirect # Base redirection URL
self.master = None
self.lastpayload = None