diff --git a/EDMarketConnector.py b/EDMarketConnector.py index aba22e7b..971fc170 100755 --- a/EDMarketConnector.py +++ b/EDMarketConnector.py @@ -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) diff --git a/config.py b/config.py index 41d56f3f..7d077d2b 100644 --- a/config.py +++ b/config.py @@ -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: # diff --git a/constants.py b/constants.py new file mode 100644 index 00000000..814471e4 --- /dev/null +++ b/constants.py @@ -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' diff --git a/protocol.py b/protocol.py index 408b3082..71a7ae36 100644 --- a/protocol.py +++ b/protocol.py @@ -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