mirror of
https://github.com/EDCD/EDMarketConnector.git
synced 2025-04-16 09:10:35 +03:00
Merge pull request #791 from EDCD/enhancement/main-for-4.1.5
Cherry-pick in some key commits for upcoming 4.1.5
This commit is contained in:
commit
ec93dec8a7
@ -287,11 +287,16 @@ class EDMCContextFilter(logging.Filter):
|
||||
frame_info = inspect.getframeinfo(frame)
|
||||
args, _, _, value_dict = inspect.getargvalues(frame)
|
||||
if len(args) and args[0] in ('self', 'cls'):
|
||||
frame_class = value_dict[args[0]]
|
||||
frame_class: 'object' = value_dict[args[0]]
|
||||
|
||||
if frame_class:
|
||||
# See https://en.wikipedia.org/wiki/Name_mangling#Python for how name mangling works.
|
||||
name = frame_info.function
|
||||
if name.startswith("__") and not name.endswith("__"):
|
||||
name = f'_{frame_class.__class__.__name__}{frame_info.function}'
|
||||
|
||||
# Find __qualname__ of the caller
|
||||
fn = getattr(frame_class, frame_info.function)
|
||||
fn = getattr(frame_class, name, None)
|
||||
if fn and fn.__qualname__:
|
||||
caller_qualname = fn.__qualname__
|
||||
|
||||
|
@ -17,8 +17,117 @@ from typing import TYPE_CHECKING
|
||||
|
||||
from config import applongname, appname, appversion, appversion_nobuild, config, copyright
|
||||
|
||||
# TODO: Test: Make *sure* this redirect is working, else py2exe is going to cause an exit popup
|
||||
if __name__ == "__main__":
|
||||
def no_other_instance_running() -> bool: # noqa: CCR001
|
||||
"""
|
||||
Ensure only one copy of the app is running under this user account.
|
||||
|
||||
OSX does this automatically.
|
||||
|
||||
:returns: True if we are the single instance, else False.
|
||||
"""
|
||||
# TODO: Linux implementation
|
||||
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):
|
||||
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)
|
||||
|
||||
return False
|
||||
|
||||
return True
|
||||
|
||||
return EnumWindows(enumwindowsproc, 0)
|
||||
|
||||
return True
|
||||
|
||||
def already_running_popup():
|
||||
"""Create the "already running" popup."""
|
||||
import tkinter as tk
|
||||
from tkinter import ttk
|
||||
|
||||
root = tk.Tk(className=appname.lower())
|
||||
|
||||
frame = tk.Frame(root)
|
||||
frame.grid(row=1, column=0, sticky=tk.NSEW)
|
||||
|
||||
label = tk.Label(frame)
|
||||
label['text'] = 'An EDMarketConnector.exe process was already running, exiting.'
|
||||
label.grid(row=1, column=0, sticky=tk.NSEW)
|
||||
|
||||
button = ttk.Button(frame, text='OK', command=lambda: sys.exit(0))
|
||||
button.grid(row=2, column=0, sticky=tk.S)
|
||||
|
||||
root.mainloop()
|
||||
|
||||
if not no_other_instance_running():
|
||||
# There's a copy already running. We want to inform the user by
|
||||
# **appending** to the log file, not truncating it.
|
||||
if getattr(sys, 'frozen', False):
|
||||
# By default py2exe tries to write log to dirname(sys.executable) which fails when installed
|
||||
import tempfile
|
||||
|
||||
# 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='a', buffering=1)
|
||||
|
||||
# Logging isn't set up yet.
|
||||
# We'll keep this print, but it will be over-written by any subsequent
|
||||
# write by the already-running process.
|
||||
print("An EDMarketConnector.exe process was already running, exiting.")
|
||||
|
||||
# To be sure the user knows, we need a popup
|
||||
already_running_popup()
|
||||
# If the user closes the popup with the 'X', not the 'OK' button we'll
|
||||
# reach here.
|
||||
sys.exit(0)
|
||||
|
||||
# Keep this as the very first code run to be as sure as possible of no
|
||||
# output until after this redirect is done, if needed.
|
||||
if getattr(sys, 'frozen', False):
|
||||
@ -36,6 +145,9 @@ if TYPE_CHECKING:
|
||||
from logging import trace, TRACE # type: ignore # noqa: F401
|
||||
# isort: on
|
||||
|
||||
def _(x: str) -> str:
|
||||
"""Fake the l10n translation functions for typing."""
|
||||
return x
|
||||
|
||||
if getattr(sys, 'frozen', False):
|
||||
# Under py2exe sys.path[0] is the executable name
|
||||
@ -947,18 +1059,43 @@ class AppWindow(object):
|
||||
if platform != 'darwin' or self.w.winfo_rooty() > 0:
|
||||
x, y = self.w.geometry().split('+')[1:3] # e.g. '212x170+2881+1267'
|
||||
config.set('geometry', f'+{x}+{y}')
|
||||
self.w.withdraw() # Following items can take a few seconds, so hide the main window while they happen
|
||||
|
||||
# Let the user know we're shutting down.
|
||||
self.status['text'] = 'Shutting down...'
|
||||
self.w.update_idletasks()
|
||||
logger.info('Starting shutdown procedures...')
|
||||
|
||||
logger.info('Closing protocol handler...')
|
||||
protocolhandler.close()
|
||||
|
||||
logger.info('Unregistering hotkey manager...')
|
||||
hotkeymgr.unregister()
|
||||
|
||||
logger.info('Closing dashboard...')
|
||||
dashboard.close()
|
||||
|
||||
logger.info('Closing journal monitor...')
|
||||
monitor.close()
|
||||
|
||||
logger.info('Notifying plugins to stop...')
|
||||
plug.notify_stop()
|
||||
|
||||
logger.info('Closing update checker...')
|
||||
self.updater.close()
|
||||
|
||||
logger.info('Closing Frontier CAPI sessions...')
|
||||
companion.session.close()
|
||||
|
||||
logger.info('Closing config...')
|
||||
config.close()
|
||||
|
||||
logger.info('Destroying app window...')
|
||||
self.w.destroy()
|
||||
|
||||
def drag_start(self, event):
|
||||
logger.info('Done.')
|
||||
|
||||
def drag_start(self, event) -> None:
|
||||
"""Initiate dragging the window."""
|
||||
self.drag_offset = (event.x_root - self.w.winfo_rootx(), event.y_root - self.w.winfo_rooty())
|
||||
|
||||
def drag_continue(self, event):
|
||||
@ -1165,6 +1302,10 @@ sys.path: {sys.path}'''
|
||||
|
||||
def __init__(self):
|
||||
logger.debug('A call from A.B.__init__')
|
||||
self.__test()
|
||||
|
||||
def __test(self):
|
||||
logger.debug("A call from A.B.__test")
|
||||
|
||||
# abinit = A.B()
|
||||
|
||||
|
@ -33,29 +33,16 @@ You will need several pieces of software installed, or the files from their
|
||||
[v3.7.9](https://www.python.org/downloads/release/python-379/) is the most
|
||||
recently tested version. You need the `Windows x86 executable installer`
|
||||
file, for the 32-bit version.
|
||||
1. [py2exe](https://github.com/albertosottile/py2exe):
|
||||
1. Install the python module. There are two options here.
|
||||
1. You can use the latest release version [0.9.3.2](https://github.com/albertosottile/py2exe/releases/tag/v0.9.3.2)
|
||||
and the current Marginal 'python3' branch as-is. This contains a
|
||||
small hack in `setup.py` to ensure `sqlite3.dll` is packaged.
|
||||
|
||||
pip install py2exe-0.9.3.2-cp37-none-win32.whl
|
||||
1. Or you can use a pre-release version, [0.9.4.0](https://bintray.com/alby128/py2exe/download_file?file_path=py2exe-0.9.4.0-cp37-none-win32.whl), see [this py2exe issue](https://github.com/albertosottile/py2exe/issues/23#issuecomment-541359225),
|
||||
which packages that DLL file correctly.
|
||||
|
||||
pip install py2exe-0.9.4.0-cp37-none-win32.whl
|
||||
You can then edit out the following line from `setup.py`, but it
|
||||
does no harm:
|
||||
|
||||
%s/DLLs/sqlite3.dll' % (sys.base_prefix),
|
||||
1. [py2exe](https://github.com/albertosottile/py2exe) - Now available via PyPi,
|
||||
so will be picked up with the `pip install` below. Latest tested as per
|
||||
`requirements-dev.txt`.
|
||||
|
||||
1. You'll now need to 'pip install' several python modules.
|
||||
1. Ensure you have `pip` installed. If needs be see
|
||||
[Installing pip](https://pip.pypa.io/en/stable/installing/)
|
||||
1. The easiest way is to utilise the `requirements-dev.txt` file:
|
||||
`python -m pip install -r requirements-dev.txt`. This will install all
|
||||
dependencies plus anything required for development *other than py2exe, see
|
||||
above*.
|
||||
`python -m pip install --user -r requirements-dev.txt`. This will install
|
||||
all dependencies plus anything required for development.
|
||||
1. Else check the contents of both `requirements.txt` and `requirements-dev.txt`,
|
||||
and ensure the modules listed there are installed as per the version
|
||||
requirements.
|
||||
|
@ -20,9 +20,8 @@ autopep8==1.5.4
|
||||
grip>=4.5.2
|
||||
|
||||
# Packaging
|
||||
# This isn't available via 'pip install', so has to be commented out in order for
|
||||
# GitHub Action Workflows to not error out
|
||||
#py2exe==0.9.3.2
|
||||
# We only need py2exe on windows.
|
||||
py2exe==0.10.0.2; sys_platform == 'win32'
|
||||
|
||||
# All of the normal requirements
|
||||
-r requirements.txt
|
||||
|
Loading…
x
Reference in New Issue
Block a user