Skip to content

Commit

Permalink
Merge branch 'refs/heads/develop' into ttk
Browse files Browse the repository at this point in the history
# Conflicts:
#	config/windows.py
#	prefs.py
#	theme.py
  • Loading branch information
ElSaico committed Jul 22, 2024
2 parents 810fa2b + 5c5682d commit ff35b8d
Show file tree
Hide file tree
Showing 28 changed files with 553 additions and 502 deletions.
11 changes: 7 additions & 4 deletions EDMC.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
import os
import queue
import sys
from pathlib import Path
from time import sleep, time
from typing import TYPE_CHECKING, Any

Expand Down Expand Up @@ -212,22 +213,24 @@ def main(): # noqa: C901, CCR001
# system, chances are its the current locale, and not utf-8. Otherwise if it was copied, its probably
# utf8. Either way, try the system FIRST because reading something like cp1251 in UTF-8 results in garbage
# but the reverse results in an exception.
json_file = os.path.abspath(args.j)
json_file = Path(args.j).resolve()
try:
with open(json_file) as file_handle:
data = json.load(file_handle)
except UnicodeDecodeError:
with open(json_file, encoding='utf-8') as file_handle:
data = json.load(file_handle)
config.set('querytime', int(os.path.getmtime(args.j)))
file_path = Path(args.j)
modification_time = file_path.stat().st_mtime
config.set('querytime', int(modification_time))

else:
# Get state from latest Journal file
logger.debug('Getting state from latest journal file')
try:
monitor.currentdir = config.get_str('journaldir', default=config.default_journal_dir)
monitor.currentdir = Path(config.get_str('journaldir', default=config.default_journal_dir))
if not monitor.currentdir:
monitor.currentdir = config.default_journal_dir
monitor.currentdir = config.default_journal_dir_path

logger.debug(f'logdir = "{monitor.currentdir}"')
logfile = monitor.journal_newest_filename(monitor.currentdir)
Expand Down
21 changes: 9 additions & 12 deletions EDMCLogging.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,12 +26,13 @@
To utilise logging in a 'found' (third-party) plugin, include this:
import os
from pathlib import Path
import logging
plugin_name = os.path.basename(os.path.dirname(__file__))
# Retrieve the name of the plugin folder
plugin_name = Path(__file__).resolve().parent.name
# Set up logger with hierarchical name including appname and plugin_name
# plugin_name here *must* be the name of the folder the plugin resides in
# See, plug.py:load_plugins()
logger = logging.getLogger(f'{appname}.{plugin_name}')
"""
from __future__ import annotations
Expand All @@ -41,7 +42,6 @@
import logging.handlers
import os
import pathlib
import tempfile
import warnings
from contextlib import suppress
from fnmatch import fnmatch
Expand All @@ -51,9 +51,7 @@
from time import gmtime
from traceback import print_exc
from typing import TYPE_CHECKING, cast

import config as config_mod
from config import appcmdname, appname, config
from config import appcmdname, appname, config, trace_on

# TODO: Tests:
#
Expand Down Expand Up @@ -104,7 +102,7 @@


def _trace_if(self: logging.Logger, condition: str, message: str, *args, **kwargs) -> None:
if any(fnmatch(condition, p) for p in config_mod.trace_on):
if any(fnmatch(condition, p) for p in trace_on):
self._log(logging.TRACE, message, args, **kwargs) # type: ignore # we added it
return

Expand Down Expand Up @@ -184,8 +182,7 @@ def __init__(self, logger_name: str, loglevel: int | str = _default_loglevel):
# We want the files in %TEMP%\{appname}\ as {logger_name}-debug.log and
# rotated versions.
# This is {logger_name} so that EDMC.py logs to a different file.
logfile_rotating = pathlib.Path(tempfile.gettempdir())
logfile_rotating /= f'{appname}'
logfile_rotating = pathlib.Path(config.app_dir_path / 'logs')
logfile_rotating.mkdir(exist_ok=True)
logfile_rotating /= f'{logger_name}-debug.log'

Expand Down Expand Up @@ -489,8 +486,8 @@ def munge_module_name(cls, frame_info: inspect.Traceback, module_name: str) -> s
:return: The munged module_name.
"""
file_name = pathlib.Path(frame_info.filename).expanduser()
plugin_dir = pathlib.Path(config.plugin_dir_path).expanduser()
internal_plugin_dir = pathlib.Path(config.internal_plugin_dir_path).expanduser()
plugin_dir = config.plugin_dir_path.expanduser()
internal_plugin_dir = config.internal_plugin_dir_path.expanduser()
# Find the first parent called 'plugins'
plugin_top = file_name
while plugin_top and plugin_top.name != '':
Expand Down
17 changes: 9 additions & 8 deletions EDMCSystemProfiler.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,18 +11,18 @@
import webbrowser
import platform
import sys
from os import chdir, environ, path
from os import chdir, environ
import pathlib
import logging
from journal_lock import JournalLock

if getattr(sys, "frozen", False):
# Under py2exe sys.path[0] is the executable name
if sys.platform == "win32":
chdir(path.dirname(sys.path[0]))
chdir(pathlib.Path(sys.path[0]).parent)
# Allow executable to be invoked from any cwd
environ["TCL_LIBRARY"] = path.join(path.dirname(sys.path[0]), "lib", "tcl")
environ["TK_LIBRARY"] = path.join(path.dirname(sys.path[0]), "lib", "tk")
environ['TCL_LIBRARY'] = str(pathlib.Path(sys.path[0]).parent / 'lib' / 'tcl')
environ['TK_LIBRARY'] = str(pathlib.Path(sys.path[0]).parent / 'lib' / 'tk')

else:
# We still want to *try* to have CWD be where the main script is, even if
Expand All @@ -44,11 +44,12 @@ def get_sys_report(config: config.AbstractConfig) -> str:
plt = platform.uname()
locale.setlocale(locale.LC_ALL, "")
lcl = locale.getlocale()
monitor.currentdir = config.get_str(
monitor.currentdir = pathlib.Path(config.get_str(
"journaldir", default=config.default_journal_dir
)
)
if not monitor.currentdir:
monitor.currentdir = config.default_journal_dir
monitor.currentdir = config.default_journal_dir_path
try:
logfile = monitor.journal_newest_filename(monitor.currentdir)
if logfile is None:
Expand Down Expand Up @@ -115,12 +116,12 @@ def main() -> None:
root.withdraw() # Hide the window initially to calculate the dimensions
try:
icon_image = tk.PhotoImage(
file=path.join(cur_config.respath_path, "io.edcd.EDMarketConnector.png")
file=cur_config.respath_path / "io.edcd.EDMarketConnector.png"
)

root.iconphoto(True, icon_image)
except tk.TclError:
root.iconbitmap(path.join(cur_config.respath_path, "EDMarketConnector.ico"))
root.iconbitmap(cur_config.respath_path / "EDMarketConnector.ico")

sys_report = get_sys_report(cur_config)

Expand Down
95 changes: 48 additions & 47 deletions EDMarketConnector.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,8 +20,7 @@
import sys
import threading
import webbrowser
import tempfile
from os import chdir, environ, path
from os import chdir, environ
from time import localtime, strftime, time
from typing import TYPE_CHECKING, Any, Literal
from constants import applongname, appname, protocolhandler_redirect
Expand All @@ -32,26 +31,29 @@
if getattr(sys, 'frozen', False):
# Under py2exe sys.path[0] is the executable name
if sys.platform == 'win32':
chdir(path.dirname(sys.path[0]))
os.chdir(pathlib.Path(sys.path[0]).parent)
# Allow executable to be invoked from any cwd
environ['TCL_LIBRARY'] = path.join(path.dirname(sys.path[0]), 'lib', 'tcl')
environ['TK_LIBRARY'] = path.join(path.dirname(sys.path[0]), 'lib', 'tk')
environ['TCL_LIBRARY'] = str(pathlib.Path(sys.path[0]).parent / 'lib' / 'tcl')
environ['TK_LIBRARY'] = str(pathlib.Path(sys.path[0]).parent / 'lib' / 'tk')

else:
# We still want to *try* to have CWD be where the main script is, even if
# not frozen.
chdir(pathlib.Path(__file__).parent)


# config will now cause an appname logger to be set up, so we need the
# console redirect before this
if __name__ == '__main__':
# 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):
from config import config
# By default py2exe tries to write log to dirname(sys.executable) which fails when installed
# unbuffered not allowed for text in python3, so use `1 for line buffering
log_file_path = path.join(tempfile.gettempdir(), f'{appname}.log')
log_file_path = pathlib.Path(config.app_dir_path / 'logs')
log_file_path.mkdir(exist_ok=True)
log_file_path /= f'{appname}.log'

sys.stdout = sys.stderr = open(log_file_path, mode='wt', buffering=1) # Do NOT use WITH here.
# TODO: Test: Make *sure* this redirect is working, else py2exe is going to cause an exit popup

Expand Down Expand Up @@ -262,41 +264,26 @@ def handle_edmc_callback_or_foregrounding() -> None: # noqa: CCR001
logger.trace_if('frontier-auth.windows', 'Begin...')

if sys.platform == 'win32':

# If *this* instance hasn't locked, then another already has and we
# now need to do the edmc:// checks for auth callback
if locked != JournalLockResult.LOCKED:
from ctypes import windll, c_int, create_unicode_buffer, WINFUNCTYPE
from ctypes.wintypes import BOOL, HWND, INT, LPARAM, LPCWSTR, LPWSTR

EnumWindows = windll.user32.EnumWindows # noqa: N806
GetClassName = windll.user32.GetClassNameW # noqa: N806
GetClassName.argtypes = [HWND, LPWSTR, c_int]
GetWindowText = windll.user32.GetWindowTextW # noqa: N806
GetWindowText.argtypes = [HWND, LPWSTR, c_int]
GetWindowTextLength = windll.user32.GetWindowTextLengthW # noqa: N806
GetProcessHandleFromHwnd = windll.oleacc.GetProcessHandleFromHwnd # noqa: N806
from ctypes import windll, create_unicode_buffer, WINFUNCTYPE
from ctypes.wintypes import BOOL, HWND, LPARAM
import win32gui
import win32api
import win32con

SW_RESTORE = 9 # noqa: N806
SetForegroundWindow = windll.user32.SetForegroundWindow # noqa: N806
ShowWindow = windll.user32.ShowWindow # noqa: N806
GetProcessHandleFromHwnd = windll.oleacc.GetProcessHandleFromHwnd # noqa: N806
ShowWindowAsync = windll.user32.ShowWindowAsync # noqa: N806

COINIT_MULTITHREADED = 0 # noqa: N806,F841
COINIT_APARTMENTTHREADED = 0x2 # noqa: N806
COINIT_DISABLE_OLE1DDE = 0x4 # noqa: N806
CoInitializeEx = windll.ole32.CoInitializeEx # noqa: N806

ShellExecute = windll.shell32.ShellExecuteW # noqa: N806
ShellExecute.argtypes = [HWND, LPCWSTR, LPCWSTR, LPCWSTR, LPCWSTR, INT]

def window_title(h: int) -> str | None:
if h:
text_length = GetWindowTextLength(h) + 1
buf = create_unicode_buffer(text_length)
if GetWindowText(h, buf, text_length):
return buf.value

return win32gui.GetWindowText(h)
return None

@WINFUNCTYPE(BOOL, HWND, LPARAM)
Expand All @@ -318,20 +305,20 @@ def enumwindowsproc(window_handle, l_param): # noqa: CCR001
# class name limited to 256 - https://msdn.microsoft.com/en-us/library/windows/desktop/ms633576
cls = create_unicode_buffer(257)
# This conditional is exploded to make debugging slightly easier
if GetClassName(window_handle, cls, 257):
if win32gui.GetClassName(window_handle, cls, 257):
if cls.value == 'TkTopLevel':
if window_title(window_handle) == applongname:
if 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)
win32gui.ShowWindow(window_handle, win32con.SW_RESTORE)
win32api.ShellExecute(0, None, sys.argv[1], None, None, win32con.SW_RESTORE)

else:
ShowWindowAsync(window_handle, SW_RESTORE)
SetForegroundWindow(window_handle)
ShowWindowAsync(window_handle, win32con.SW_RESTORE)
win32gui.SetForegroundWindow(window_handle)

return False # Indicate window found, so stop iterating

Expand All @@ -343,7 +330,7 @@ def enumwindowsproc(window_handle, l_param): # noqa: CCR001
# enumwindwsproc() on each. When an invocation returns False it
# stops iterating.
# Ref: <https://docs.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-enumwindows>
EnumWindows(enumwindowsproc, 0)
win32gui.EnumWindows(enumwindowsproc, 0)

def already_running_popup():
"""Create the "already running" popup."""
Expand Down Expand Up @@ -478,8 +465,8 @@ def open_window(systray: 'SysTrayIcon', *args) -> None:
self.w.wm_iconbitmap(default='EDMarketConnector.ico')

else:
self.w.tk.call('wm', 'iconphoto', self.w, '-default',
tk.PhotoImage(file=path.join(config.respath_path, 'io.edcd.EDMarketConnector.png')))
image_path = config.respath_path / 'io.edcd.EDMarketConnector.png'
self.w.tk.call('wm', 'iconphoto', self.w, '-default', image=tk.PhotoImage(file=image_path))

frame = ttk.Frame(self.w, name=appname.lower())
frame.grid(sticky=tk.NSEW)
Expand Down Expand Up @@ -605,7 +592,7 @@ def open_window(systray: 'SysTrayIcon', *args) -> None:
self.help_menu.add_command(command=lambda: self.updater.check_for_updates()) # Check for Updates...
# About E:D Market Connector
self.help_menu.add_command(command=lambda: not self.HelpAbout.showing and self.HelpAbout(self.w))
logfile_loc = pathlib.Path(tempfile.gettempdir()) / appname
logfile_loc = pathlib.Path(config.app_dir_path / 'logs')
self.help_menu.add_command(command=lambda: prefs.open_folder(logfile_loc)) # Open Log Folder
self.help_menu.add_command(command=lambda: prefs.help_open_system_profiler(self)) # Open Log Folde

Expand Down Expand Up @@ -644,13 +631,14 @@ def open_window(systray: 'SysTrayIcon', *args) -> None:
if match:
if sys.platform == 'win32':
# Check that the titlebar will be at least partly on screen
import ctypes
from ctypes.wintypes import POINT
import win32api
import win32con

x = int(match.group(1)) + 16
y = int(match.group(2)) + 16
point = (x, y)
# https://msdn.microsoft.com/en-us/library/dd145064
MONITOR_DEFAULTTONULL = 0 # noqa: N806
if ctypes.windll.user32.MonitorFromPoint(POINT(int(match.group(1)) + 16, int(match.group(2)) + 16),
MONITOR_DEFAULTTONULL):
if win32api.MonitorFromPoint(point, win32con.MONITOR_DEFAULTTONULL):
self.w.geometry(config.get_str('geometry'))
else:
self.w.geometry(config.get_str('geometry'))
Expand Down Expand Up @@ -781,9 +769,20 @@ def postprefs(self, dologin: bool = True, **postargs):
)
update_msg = update_msg.replace('\\n', '\n')
update_msg = update_msg.replace('\\r', '\r')
stable_popup = tk.messagebox.askyesno(title=title, message=update_msg, parent=postargs.get('Parent'))
stable_popup = tk.messagebox.askyesno(title=title, message=update_msg)
if stable_popup:
webbrowser.open("https://github.com/edCD/eDMarketConnector/releases/latest")
webbrowser.open("https://github.com/EDCD/eDMarketConnector/releases/latest")
if postargs.get('Restart_Req'):
# LANG: Text of Notification Popup for EDMC Restart
restart_msg = tr.tl('A restart of EDMC is required. EDMC will now restart.')
restart_box = tk.messagebox.Message(
title=tr.tl('Restart Required'), # LANG: Title of Notification Popup for EDMC Restart
message=restart_msg,
type=tk.messagebox.OK
)
restart_box.show()
if restart_box:
app.onexit(restart=True)

def set_labels(self):
"""Set main window labels, e.g. after language change."""
Expand Down Expand Up @@ -1572,7 +1571,7 @@ def shipyard_url(self, shipname: str) -> str | None:
# Avoid file length limits if possible
provider = config.get_str('shipyard_provider', default='EDSY')
target = plug.invoke(provider, 'EDSY', 'shipyard_url', loadout, monitor.is_beta)
file_name = path.join(config.app_dir_path, "last_shipyard.html")
file_name = config.app_dir_path / "last_shipyard.html"

with open(file_name, 'w') as f:
f.write(SHIPYARD_HTML_TEMPLATE.format(
Expand Down Expand Up @@ -1784,7 +1783,7 @@ def exit_tray(self, systray: 'SysTrayIcon') -> None:
)
exit_thread.start()

def onexit(self, event=None) -> None:
def onexit(self, event=None, restart: bool = False) -> None:
"""Application shutdown procedure."""
if sys.platform == 'win32':
shutdown_thread = threading.Thread(
Expand Down Expand Up @@ -1847,6 +1846,8 @@ def onexit(self, event=None) -> None:
self.w.destroy()

logger.info('Done.')
if restart:
os.execv(sys.executable, ['python'] + sys.argv)

def default_iconify(self, event=None) -> None:
"""Handle the Windows 'minimize' button."""
Expand Down
Loading

0 comments on commit ff35b8d

Please sign in to comment.