mirror of
https://github.com/EDCD/EDMarketConnector.git
synced 2025-04-23 04:10:29 +03:00
Merge branch 'stable' into releases
This commit is contained in:
commit
c0bff277e9
37
ChangeLog.md
37
ChangeLog.md
@ -6,6 +6,41 @@ This is the master changelog for Elite Dangerous Market Connector. Entries are
|
||||
in the source (not distributed with the Windows installer) for the
|
||||
currently used version.
|
||||
---
|
||||
Release 5.12.0
|
||||
===
|
||||
|
||||
This release brings a number of performance enhancements and functionality updates requested by the community to EDMC.
|
||||
Notably, integration with Inara's SLEF notation, custom plugin directories, streamlined logging locations, and
|
||||
performance enhancements are included.
|
||||
|
||||
This release also fixes a few administrative issues regarding licenses to ensure compliance with included libraries.
|
||||
|
||||
**Changes and Enhancements**
|
||||
* Added the ability to export a ship's loadout to Inara SLEF notation
|
||||
* Added the ability for EDMC to restart itself if required after settings changes
|
||||
* Added the ability to change the custom plugins directory to allow for multiple plugin profiles
|
||||
* Added Basic Type 8 Support
|
||||
* Updated the default logging directory from $TEMPDIR or %TEMP% and to the current app data directory
|
||||
* Updated a number of direct win32API calls to use proper prototyped library calls
|
||||
* Updated a number of translations
|
||||
* Updated a number of dependencies
|
||||
* Updated included and bundled licenses to comply with dependency requirements
|
||||
* Updated the game_running check to be more efficient on Windows to reduce program hangs
|
||||
* Minor logic enhancements
|
||||
* Retired most usages of os.path in favor of the preferred PathLib
|
||||
|
||||
**Bug Fixes**
|
||||
* Fixed a bug that would result in Horizons and Odyssey flags not being passed to EDDN
|
||||
|
||||
**Plugin Developers**
|
||||
* nb.Entry is deprecated, and is slated for removal in 6.0 or later. Please migrate to nb.EntryMenu
|
||||
* nb.ColoredButton is deprecated, and is slated for removal in 6.0 or later. Please migrate to tk.Button
|
||||
* Calling internal translations with `_()` is deprecated, and is slated for removal in 6.0 or later. Please migrate to importing `translations` and calling `translations.translate` or `translations.tl` directly
|
||||
* `Translations` as the translate system singleton is deprecated, and is slated for removal in 6.0 or later. Please migrate to the `translations` singleton
|
||||
* `help_open_log_folder()` is deprecated, and is slated for removal in 6.0 or later. Please migrate to open_folder()
|
||||
* `update_feed` is deprecated, and is slated for removal in 6.0 or later. Please migrate to `get_update_feed()`.
|
||||
|
||||
|
||||
Release 5.11.3
|
||||
===
|
||||
|
||||
@ -33,7 +68,7 @@ This release fixes a bug where minimizing to the system tray could cause the pro
|
||||
|
||||
**Changes and Enhancements**
|
||||
* Updated Translations
|
||||
* Added a developer utilty to help speed up changelog development
|
||||
* Added a developer utility to help speed up changelog development
|
||||
|
||||
**Bug Fixes**
|
||||
* Fixed a bug where minimizing to the system tray could cause the program to not un-minimize.
|
||||
|
11
EDMC.py
11
EDMC.py
@ -14,6 +14,7 @@ import locale
|
||||
import os
|
||||
import queue
|
||||
import sys
|
||||
from pathlib import Path
|
||||
from time import sleep, time
|
||||
from typing import TYPE_CHECKING, Any
|
||||
|
||||
@ -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)
|
||||
|
@ -26,12 +26,13 @@ To utilise logging in core code, or internal plugins, include this:
|
||||
|
||||
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
|
||||
@ -41,7 +42,6 @@ import logging
|
||||
import logging.handlers
|
||||
import os
|
||||
import pathlib
|
||||
import tempfile
|
||||
import warnings
|
||||
from contextlib import suppress
|
||||
from fnmatch import fnmatch
|
||||
@ -51,9 +51,7 @@ from threading import get_native_id as thread_native_id
|
||||
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:
|
||||
#
|
||||
@ -104,7 +102,7 @@ warnings.simplefilter('default', DeprecationWarning)
|
||||
|
||||
|
||||
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
|
||||
|
||||
@ -184,8 +182,7 @@ class Logger:
|
||||
# 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'
|
||||
|
||||
@ -489,8 +486,8 @@ class EDMCContextFilter(logging.Filter):
|
||||
: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 != '':
|
||||
|
@ -11,7 +11,7 @@ import locale
|
||||
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
|
||||
@ -19,10 +19,10 @@ 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
|
||||
@ -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:
|
||||
@ -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)
|
||||
|
||||
|
@ -20,8 +20,7 @@ import subprocess
|
||||
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
|
||||
@ -32,26 +31,29 @@ from constants import applongname, appname, protocolhandler_redirect
|
||||
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
|
||||
|
||||
@ -253,24 +255,16 @@ if __name__ == '__main__': # noqa: C901
|
||||
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
|
||||
from ctypes import windll, create_unicode_buffer, WINFUNCTYPE
|
||||
from ctypes.wintypes import BOOL, HWND, LPARAM
|
||||
import win32gui
|
||||
import win32api
|
||||
import win32con
|
||||
|
||||
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
|
||||
|
||||
SW_RESTORE = 9 # noqa: N806
|
||||
SetForegroundWindow = windll.user32.SetForegroundWindow # noqa: N806
|
||||
ShowWindow = windll.user32.ShowWindow # noqa: N806
|
||||
ShowWindowAsync = windll.user32.ShowWindowAsync # noqa: N806
|
||||
|
||||
COINIT_MULTITHREADED = 0 # noqa: N806,F841
|
||||
@ -278,16 +272,9 @@ if __name__ == '__main__': # noqa: C901
|
||||
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)
|
||||
@ -309,7 +296,7 @@ if __name__ == '__main__': # noqa: C901
|
||||
# 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):
|
||||
if cls.value == 'TkTopLevel':
|
||||
if window_title(window_handle) == applongname:
|
||||
if GetProcessHandleFromHwnd(window_handle):
|
||||
@ -317,12 +304,12 @@ if __name__ == '__main__': # noqa: C901
|
||||
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
|
||||
|
||||
@ -334,7 +321,7 @@ if __name__ == '__main__': # noqa: C901
|
||||
# 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."""
|
||||
@ -468,8 +455,8 @@ class AppWindow:
|
||||
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', tk.PhotoImage(file=image_path))
|
||||
|
||||
# TODO: Export to files and merge from them in future ?
|
||||
self.theme_icon = tk.PhotoImage(
|
||||
@ -619,7 +606,7 @@ class AppWindow:
|
||||
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
|
||||
|
||||
@ -701,13 +688,14 @@ class AppWindow:
|
||||
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'))
|
||||
@ -845,9 +833,20 @@ class AppWindow:
|
||||
)
|
||||
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."""
|
||||
@ -1639,7 +1638,7 @@ class AppWindow:
|
||||
# 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(
|
||||
@ -1851,7 +1850,7 @@ class AppWindow:
|
||||
)
|
||||
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(
|
||||
@ -1914,6 +1913,8 @@ class AppWindow:
|
||||
self.w.destroy()
|
||||
|
||||
logger.info('Done.')
|
||||
if restart:
|
||||
os.execv(sys.executable, ['python'] + sys.argv)
|
||||
|
||||
def drag_start(self, event) -> None:
|
||||
"""Initiate dragging the window."""
|
||||
|
@ -346,9 +346,6 @@
|
||||
/* prefs.py: Label for location of third-party plugins folder; In files: prefs.py:908; */
|
||||
"Plugins folder" = "Složka pluginů";
|
||||
|
||||
/* prefs.py: Label on button used to open a filesystem folder; In files: prefs.py:915; */
|
||||
"Open" = "Otevřít";
|
||||
|
||||
/* prefs.py: Tip/label about how to disable plugins; In files: prefs.py:923; */
|
||||
"Tip: You can disable a plugin by{CR}adding '{EXT}' to its folder name" = "Tip: Plugin můžete vypnout přidáním{CR}'{EXT}' do názvu jeho složky";
|
||||
|
||||
|
@ -1,15 +1,3 @@
|
||||
/* prefs.py: Catch & Record Profiler Errors; */
|
||||
"Error in System Profiler" = "Fehler im System Profiler";
|
||||
|
||||
/* EDMarketConnector.py: We didn't have the Fleet Carrier callsign when we should have; In files: EDMarketConnector.py:1223; */
|
||||
"CAPI: Fleet Carrier data incomplete" = "CAPI: Carrier-Daten unvollständig";
|
||||
|
||||
/* EDMarketConnector.py: No data was returned for the Fleet Carrier from the Frontier CAPI; In files: EDMarketConnector.py:1219; */
|
||||
"CAPI: No Fleet Carrier data returned" = "CAPI: Keine Carrier-Daten erhalten";
|
||||
|
||||
/* prefs.py: Configuration - Enable or disable the Fleet Carrier CAPI calls; In files: prefs.py:475; */
|
||||
"Enable Fleet Carrier CAPI Queries" = "Carrier-CAPI-Anfragen aktivieren";
|
||||
|
||||
/* edsm.py:Settings>EDSM - Label on checkbox for 'send data'; In files: edsm.py:316; */
|
||||
"Send flight log and CMDR status to EDSM" = "Sende Fluglog und CMDR-Status an EDSM";
|
||||
|
||||
@ -189,12 +177,12 @@
|
||||
/* EDMarketConnector.py: CAPI fleetcarrier query aborted because of killswitch; In files: EDMarketConnector.py:1157; */
|
||||
"CAPI fleetcarrier disabled by killswitch" = "CAPI Fleet Carrier durch Killswitch deaktiviert";
|
||||
|
||||
/* EDMarketConnector.py: No data was returned for the Fleet Carrier from the Frontier CAPI; In files: EDMarketConnector.py:1219; */
|
||||
"CAPI: No Fleet Carrier data returned" = "CAPI: Keine Carrier-Daten erhalten";
|
||||
|
||||
/* EDMarketConnector.py: No data was returned for the fleetcarrier from the Frontier CAPI; In files: EDMarketConnector.py:1219; */
|
||||
"CAPI: No fleetcarrier data returned" = "CAPI: Keine Fleet Carrier-Daten erhalten";
|
||||
/* EDMarketConnector.py: We didn't have the Fleet Carrier callsign when we should have; In files: EDMarketConnector.py:1223; */
|
||||
"CAPI: Fleet Carrier data incomplete" = "CAPI: Carrier-Daten unvollständig";
|
||||
|
||||
/* EDMarketConnector.py: We didn't have the fleetcarrier callsign when we should have; In files: EDMarketConnector.py:1223; */
|
||||
"CAPI: Fleetcarrier data incomplete" = "CAPI: Fleet Carrier-Daten unvollständig";
|
||||
/* EDMarketConnector.py: No data was returned for the commander from the Frontier CAPI; In files: EDMarketConnector.py:1242; */
|
||||
"CAPI: No commander data returned" = "CAPI: Keine Kommandanten-Daten erhalten";
|
||||
|
||||
@ -399,9 +387,9 @@
|
||||
/* prefs.py: Settings > Configuration - Label for CAPI section; In files: prefs.py:469; */
|
||||
"CAPI Settings" = "CAPI-Einstellungen";
|
||||
|
||||
|
||||
/* prefs.py: Configuration - Enable or disable the Fleet Carrier CAPI calls; In files: prefs.py:475; */
|
||||
"Enable Fleetcarrier CAPI Queries" = "Fleet Carrier CAPI-Anfragen aktivieren";
|
||||
"Enable Fleet Carrier CAPI Queries" = "Carrier-CAPI-Anfragen aktivieren";
|
||||
|
||||
/* prefs.py: Hotkey/Shortcut settings prompt on Windows; In files: prefs.py:492; */
|
||||
"Hotkey" = "Hotkey";
|
||||
|
||||
@ -480,9 +468,6 @@
|
||||
/* prefs.py: Label for location of third-party plugins folder; In files: prefs.py:908; */
|
||||
"Plugins folder" = "Plugin-Ordner";
|
||||
|
||||
/* prefs.py: Label on button used to open a filesystem folder; In files: prefs.py:915; */
|
||||
"Open" = "Öffnen";
|
||||
|
||||
/* prefs.py: Tip/label about how to disable plugins; In files: prefs.py:923; */
|
||||
"Tip: You can disable a plugin by{CR}adding '{EXT}' to its folder name" = "Tipp: Du kannst ein Plugin deaktivieren, indem{CR}du '{EXT}' zu seinem Ordnernamen hinzufügst";
|
||||
|
||||
@ -501,6 +486,9 @@
|
||||
/* prefs.py: Lable on list of user-disabled plugins; In files: prefs.py:977; */
|
||||
"Disabled Plugins" = "Deaktivierte Plugins";
|
||||
|
||||
/* prefs.py: Catch & Record Profiler Errors; */
|
||||
"Error in System Profiler" = "Fehler im System Profiler";
|
||||
|
||||
/* stats.py: Cmdr stats; In files: stats.py:58; */
|
||||
"Balance" = "Kontostand";
|
||||
|
||||
@ -792,7 +780,6 @@
|
||||
/* EDMarketConnector.py: Inform the user the Update Track has changed; */
|
||||
"Update Track Changed to {TRACK}" = "Update-Kanal geändert auf {TRACK}";
|
||||
|
||||
|
||||
/* EDMarketConnector.py: Inform User of Beta -> Stable Transition Risks; */
|
||||
"Update track changed to Stable from Beta. You will no longer receive Beta updates. You will stay on your current Beta version until the next Stable release.\r\n\r\nYou can manually revert to the latest Stable version. To do so, you must download and install the latest Stable version manually. Note that this may introduce bugs or break completely if downgrading between major versions with significant changes.\r\n\r\nDo you want to open GitHub to download the latest release?" = "Update-Kanal von Beta auf Stabil geändert. Du wirst keine weiteren Beta-Updates erhalten. Du wirst bis zum nächsten stabilen Release auf der aktuellen Beta-Version bleiben.\n\nDu kannst manuell zur aktuellen stabilen Version zurückkehren. Um dies zu tun, musst du die aktuelle stabile Version manuell herunterladen und installieren. Beachte, dass dies Fehler verursachen oder gar nicht funktionieren könnte, wenn du von einem größeren Versionssprung mit signifikanten Änderungen downgradest.\n\nMöchtest du GitHub öffnen, um den neuesten Release herunterzuladen?";
|
||||
|
||||
@ -801,3 +788,4 @@
|
||||
|
||||
/* ttkHyperlinkLabel.py: Open Element In Selected Provider; */
|
||||
"Open in {URL}" = "Öffnen in {URL}";
|
||||
|
||||
|
@ -468,8 +468,11 @@
|
||||
/* prefs.py: Label for location of third-party plugins folder; In files: prefs.py:908; */
|
||||
"Plugins folder" = "Plugins folder";
|
||||
|
||||
/* prefs.py: Label on button used to open a filesystem folder; In files: prefs.py:915; */
|
||||
"Open" = "Open";
|
||||
/* prefs.py: Label on button used to open the Plugin Folder; */
|
||||
"Open Plugins Folder" = "Open Plugins Folder";
|
||||
|
||||
/* prefs.py: Selecting the Location of the Plugin Directory on the Filesystem; */
|
||||
"Plugin Directory Location" = "Plugin Directory Location";
|
||||
|
||||
/* prefs.py: Tip/label about how to disable plugins; In files: prefs.py:923; */
|
||||
"Tip: You can disable a plugin by{CR}adding '{EXT}' to its folder name" = "Tip: You can disable a plugin by{CR}adding '{EXT}' to its folder name";
|
||||
@ -804,12 +807,20 @@
|
||||
/* EDMarketConnector.py: Inform the user the Update Track has changed; */
|
||||
"Update Track Changed to {TRACK}" = "Update Track Changed to {TRACK}";
|
||||
|
||||
|
||||
/* EDMarketConnector.py: Inform User of Beta -> Stable Transition Risks; */
|
||||
"Update track changed to Stable from Beta. You will no longer receive Beta updates. You will stay on your current Beta version until the next Stable release.\r\n\r\nYou can manually revert to the latest Stable version. To do so, you must download and install the latest Stable version manually. Note that this may introduce bugs or break completely if downgrading between major versions with significant changes.\r\n\r\nDo you want to open GitHub to download the latest release?" = "Update track changed to Stable from Beta. You will no longer receive Beta updates. You will stay on your current Beta version until the next Stable release.\r\n\r\nYou can manually revert to the latest Stable version. To do so, you must download and install the latest Stable version manually. Note that this may introduce bugs or break completely if downgrading between major versions with significant changes.\r\n\r\nDo you want to open GitHub to download the latest release?";
|
||||
|
||||
/* EDMarketConnector.py: Title of Notification Popup for EDMC Restart; */
|
||||
"Restart Required" = "Restart Required";
|
||||
|
||||
/* EDMarketConnector.py: Text of Notification Popup for EDMC Restart; */
|
||||
"A restart of EDMC is required. EDMC will now restart." = "A restart of EDMC is required. EDMC will now restart.";
|
||||
|
||||
/* myNotebook.py: Can't Paste Images or Files in Text; */
|
||||
"Cannot paste non-text content." = "Cannot paste non-text content.";
|
||||
|
||||
/* ttkHyperlinkLabel.py: Open Element In Selected Provider; */
|
||||
"Open in {URL}" = "Open in {URL}";
|
||||
|
||||
/* ttkHyperlinkLabel.py: Copy the Inara SLEF Format of the active ship to the clipboard; */
|
||||
"Copy Inara SLEF" = "Copy Inara SLEF";
|
||||
|
@ -361,9 +361,6 @@
|
||||
/* prefs.py: Label for location of third-party plugins folder; In files: prefs.py:908; */
|
||||
"Plugins folder" = "Carpeta de Plugins";
|
||||
|
||||
/* prefs.py: Label on button used to open a filesystem folder; In files: prefs.py:915; */
|
||||
"Open" = "Abrir";
|
||||
|
||||
/* prefs.py: Tip/label about how to disable plugins; In files: prefs.py:923; */
|
||||
"Tip: You can disable a plugin by{CR}adding '{EXT}' to its folder name" = "Puedes desactivar un plugin añadiendo{CR}'{EXT}' al nombre de su carpeta";
|
||||
|
||||
|
@ -319,9 +319,6 @@
|
||||
/* prefs.py: Label for location of third-party plugins folder; In files: prefs.py:908; */
|
||||
"Plugins folder" = "Lisäosien hakemistosijainti";
|
||||
|
||||
/* prefs.py: Label on button used to open a filesystem folder; In files: prefs.py:915; */
|
||||
"Open" = "Avaa";
|
||||
|
||||
/* prefs.py: Tip/label about how to disable plugins; In files: prefs.py:923; */
|
||||
"Tip: You can disable a plugin by{CR}adding '{EXT}' to its folder name" = "Vihje: Voit poistaa lisäosan käytöstä{CR}lisäämällä '{EXT}' sen hakemistonimeen";
|
||||
|
||||
|
@ -291,9 +291,6 @@
|
||||
/* prefs.py: Label for location of third-party plugins folder; In files: prefs.py:908; */
|
||||
"Plugins folder" = "Répertoire des plugins";
|
||||
|
||||
/* prefs.py: Label on button used to open a filesystem folder; In files: prefs.py:915; */
|
||||
"Open" = "Ouvrir";
|
||||
|
||||
/* prefs.py: Tip/label about how to disable plugins; In files: prefs.py:923; */
|
||||
"Tip: You can disable a plugin by{CR}adding '{EXT}' to its folder name" = "Astuce : Vous pouvez désactiver un plugin en{CR}ajoutant '{EXT}' à son nom de répertoire";
|
||||
|
||||
|
@ -235,9 +235,6 @@
|
||||
/* prefs.py: Label for location of third-party plugins folder; In files: prefs.py:908; */
|
||||
"Plugins folder" = "Plugin mappa";
|
||||
|
||||
/* prefs.py: Label on button used to open a filesystem folder; In files: prefs.py:915; */
|
||||
"Open" = "Nyisd ki";
|
||||
|
||||
/* prefs.py: Tip/label about how to disable plugins; In files: prefs.py:923; */
|
||||
"Tip: You can disable a plugin by{CR}adding '{EXT}' to its folder name" = "Tipp:a pluginokat kikapcsolhatod hozzà{CR}adsz a mappa nevében egy '{EXT}'";
|
||||
|
||||
|
@ -1,15 +1,3 @@
|
||||
/* prefs.py: Catch & Record Profiler Errors; */
|
||||
"Error in System Profiler" = "Errore nel Sistema di Profilazione";
|
||||
|
||||
/* EDMarketConnector.py: We didn't have the Fleet Carrier callsign when we should have; In files: EDMarketConnector.py:1223; */
|
||||
"CAPI: Fleet Carrier data incomplete" = "CAPI: dati della Fleet Carrier incompleti";
|
||||
|
||||
/* EDMarketConnector.py: No data was returned for the Fleet Carrier from the Frontier CAPI; In files: EDMarketConnector.py:1219; */
|
||||
"CAPI: No Fleet Carrier data returned" = "CAPI: nessun dato della Fleet Carrier ottenuto";
|
||||
|
||||
/* prefs.py: Configuration - Enable or disable the Fleet Carrier CAPI calls; In files: prefs.py:475; */
|
||||
"Enable Fleet Carrier CAPI Queries" = "Abilita le query CAPI per le Fleet Carrier";
|
||||
|
||||
/* edsm.py:Settings>EDSM - Label on checkbox for 'send data'; In files: edsm.py:316; */
|
||||
"Send flight log and CMDR status to EDSM" = "Invia il registro di volo e lo stato del CMDR a EDSM";
|
||||
|
||||
@ -189,12 +177,12 @@
|
||||
/* EDMarketConnector.py: CAPI fleetcarrier query aborted because of killswitch; In files: EDMarketConnector.py:1157; */
|
||||
"CAPI fleetcarrier disabled by killswitch" = "Fleetcarrier CAPI disabilitato dal killswitch";
|
||||
|
||||
/* EDMarketConnector.py: No data was returned for the Fleet Carrier from the Frontier CAPI; In files: EDMarketConnector.py:1219; */
|
||||
"CAPI: No Fleet Carrier data returned" = "CAPI: nessun dato della Fleet Carrier ottenuto";
|
||||
|
||||
/* EDMarketConnector.py: No data was returned for the fleetcarrier from the Frontier CAPI; In files: EDMarketConnector.py:1219; */
|
||||
"CAPI: No fleetcarrier data returned" = "CAPI: Nessun dato della fleetcarrier ricevuto";
|
||||
/* EDMarketConnector.py: We didn't have the Fleet Carrier callsign when we should have; In files: EDMarketConnector.py:1223; */
|
||||
"CAPI: Fleet Carrier data incomplete" = "CAPI: dati della Fleet Carrier incompleti";
|
||||
|
||||
/* EDMarketConnector.py: We didn't have the fleetcarrier callsign when we should have; In files: EDMarketConnector.py:1223; */
|
||||
"CAPI: Fleetcarrier data incomplete" = "CAPI: Dati della Fleetcarrier incompleti";
|
||||
/* EDMarketConnector.py: No data was returned for the commander from the Frontier CAPI; In files: EDMarketConnector.py:1242; */
|
||||
"CAPI: No commander data returned" = "CAPI: Nessun dato sul commandante";
|
||||
|
||||
@ -399,9 +387,9 @@
|
||||
/* prefs.py: Settings > Configuration - Label for CAPI section; In files: prefs.py:469; */
|
||||
"CAPI Settings" = "Impostazioni CAPI";
|
||||
|
||||
|
||||
/* prefs.py: Configuration - Enable or disable the Fleet Carrier CAPI calls; In files: prefs.py:475; */
|
||||
"Enable Fleetcarrier CAPI Queries" = "Abilita le query CAPI della Fleetcarrier";
|
||||
"Enable Fleet Carrier CAPI Queries" = "Abilita le query CAPI per le Fleet Carrier";
|
||||
|
||||
/* prefs.py: Hotkey/Shortcut settings prompt on Windows; In files: prefs.py:492; */
|
||||
"Hotkey" = "Hotkey";
|
||||
|
||||
@ -480,8 +468,11 @@
|
||||
/* prefs.py: Label for location of third-party plugins folder; In files: prefs.py:908; */
|
||||
"Plugins folder" = "Cartella dei plugins";
|
||||
|
||||
/* prefs.py: Label on button used to open a filesystem folder; In files: prefs.py:915; */
|
||||
"Open" = "Sfoglia";
|
||||
/* prefs.py: Label on button used to open the Plugin Folder; */
|
||||
"Open Plugins Folder" = "Apri la cartella dei plugin";
|
||||
|
||||
/* prefs.py: Selecting the Location of the Plugin Directory on the Filesystem; */
|
||||
"Plugin Directory Location" = "Posizione della Cartella del Plugin";
|
||||
|
||||
/* prefs.py: Tip/label about how to disable plugins; In files: prefs.py:923; */
|
||||
"Tip: You can disable a plugin by{CR}adding '{EXT}' to its folder name" = "Suggerimento: è possibile disabilitare un plugin {CR}aggiungendo '{EXT}' nel nome della sua cartella";
|
||||
@ -501,6 +492,9 @@
|
||||
/* prefs.py: Lable on list of user-disabled plugins; In files: prefs.py:977; */
|
||||
"Disabled Plugins" = "Plugin disabilitati";
|
||||
|
||||
/* prefs.py: Catch & Record Profiler Errors; */
|
||||
"Error in System Profiler" = "Errore nel Sistema di Profilazione";
|
||||
|
||||
/* stats.py: Cmdr stats; In files: stats.py:58; */
|
||||
"Balance" = "Saldo";
|
||||
|
||||
@ -813,12 +807,20 @@
|
||||
/* EDMarketConnector.py: Inform the user the Update Track has changed; */
|
||||
"Update Track Changed to {TRACK}" = "Canale di aggiornamento cambiato in {TRACK}";
|
||||
|
||||
|
||||
/* EDMarketConnector.py: Inform User of Beta -> Stable Transition Risks; */
|
||||
"Update track changed to Stable from Beta. You will no longer receive Beta updates. You will stay on your current Beta version until the next Stable release.\r\n\r\nYou can manually revert to the latest Stable version. To do so, you must download and install the latest Stable version manually. Note that this may introduce bugs or break completely if downgrading between major versions with significant changes.\r\n\r\nDo you want to open GitHub to download the latest release?" = "Il canale di aggiornamento è cambiato da Beta a Stabile. Non riceverai più aggiornamenti Beta. Rimarrai sulla tua attuale versione Beta fino alla prossima release Stabile.\n\nPuoi tornare manualmente all'ultima versione Stabile. Per farlo, devi scaricare e installare manualmente l'ultima versione Stabile. Nota che questo potrebbe introdurre bug o causare malfunzionamenti se si esegue il downgrade tra versioni principali con cambiamenti significativi.\n\nVuoi aprire GitHub per scaricare l'ultima release?";
|
||||
|
||||
/* EDMarketConnector.py: Title of Notification Popup for EDMC Restart; */
|
||||
"Restart Required" = "Riavvio necessario";
|
||||
|
||||
/* EDMarketConnector.py: Text of Notification Popup for EDMC Restart; */
|
||||
"A restart of EDMC is required. EDMC will now restart." = "È necessario riavviare EDMC. EDMC si riavvierà ora.";
|
||||
|
||||
/* myNotebook.py: Can't Paste Images or Files in Text; */
|
||||
"Cannot paste non-text content." = "Non si può incollare contenuto non testuale";
|
||||
|
||||
/* ttkHyperlinkLabel.py: Open Element In Selected Provider; */
|
||||
"Open in {URL}" = "Apri {URL}";
|
||||
|
||||
/* ttkHyperlinkLabel.py: Copy the Inara SLEF Format of the active ship to the clipboard; */
|
||||
"Copy Inara SLEF" = "Copia Inara SLEF";
|
||||
|
@ -1,15 +1,3 @@
|
||||
/* prefs.py: Catch & Record Profiler Errors; */
|
||||
"Error in System Profiler" = "システムプロファイラーでエラー";
|
||||
|
||||
/* EDMarketConnector.py: We didn't have the Fleet Carrier callsign when we should have; In files: EDMarketConnector.py:1223; */
|
||||
"CAPI: Fleet Carrier data incomplete" = "CAPI: フリートキャリアデータが不完全";
|
||||
|
||||
/* EDMarketConnector.py: No data was returned for the Fleet Carrier from the Frontier CAPI; In files: EDMarketConnector.py:1219; */
|
||||
"CAPI: No Fleet Carrier data returned" = "CAPI: フリートキャリアデータの返信なし";
|
||||
|
||||
/* prefs.py: Configuration - Enable or disable the Fleet Carrier CAPI calls; In files: prefs.py:475; */
|
||||
"Enable Fleet Carrier CAPI Queries" = "フリートキャリアCAPIクエリを有効にする";
|
||||
|
||||
/* edsm.py:Settings>EDSM - Label on checkbox for 'send data'; In files: edsm.py:316; */
|
||||
"Send flight log and CMDR status to EDSM" = "フライトログとCMDRステータスをEDSMへ送信する";
|
||||
|
||||
@ -189,12 +177,12 @@
|
||||
/* EDMarketConnector.py: CAPI fleetcarrier query aborted because of killswitch; In files: EDMarketConnector.py:1157; */
|
||||
"CAPI fleetcarrier disabled by killswitch" = "CAPIフリートキャリアはkillswitchによって無効にされています";
|
||||
|
||||
/* EDMarketConnector.py: No data was returned for the Fleet Carrier from the Frontier CAPI; In files: EDMarketConnector.py:1219; */
|
||||
"CAPI: No Fleet Carrier data returned" = "CAPI: フリートキャリアデータの返信なし";
|
||||
|
||||
/* EDMarketConnector.py: No data was returned for the fleetcarrier from the Frontier CAPI; In files: EDMarketConnector.py:1219; */
|
||||
"CAPI: No fleetcarrier data returned" = "CAPI: フリートキャリアデータの返信なし";
|
||||
/* EDMarketConnector.py: We didn't have the Fleet Carrier callsign when we should have; In files: EDMarketConnector.py:1223; */
|
||||
"CAPI: Fleet Carrier data incomplete" = "CAPI: フリートキャリアデータが不完全";
|
||||
|
||||
/* EDMarketConnector.py: We didn't have the fleetcarrier callsign when we should have; In files: EDMarketConnector.py:1223; */
|
||||
"CAPI: Fleetcarrier data incomplete" = "CAPI: フリートキャリアデータが不完全";
|
||||
/* EDMarketConnector.py: No data was returned for the commander from the Frontier CAPI; In files: EDMarketConnector.py:1242; */
|
||||
"CAPI: No commander data returned" = "CAPI: コマンダーのデータが返ってきませんでした";
|
||||
|
||||
@ -399,9 +387,9 @@
|
||||
/* prefs.py: Settings > Configuration - Label for CAPI section; In files: prefs.py:469; */
|
||||
"CAPI Settings" = "CAPI設定";
|
||||
|
||||
|
||||
/* prefs.py: Configuration - Enable or disable the Fleet Carrier CAPI calls; In files: prefs.py:475; */
|
||||
"Enable Fleetcarrier CAPI Queries" = "フリートキャリアCAPIクエリを有効にする";
|
||||
"Enable Fleet Carrier CAPI Queries" = "フリートキャリアCAPIクエリを有効にする";
|
||||
|
||||
/* prefs.py: Hotkey/Shortcut settings prompt on Windows; In files: prefs.py:492; */
|
||||
"Hotkey" = "ホットキー";
|
||||
|
||||
@ -480,9 +468,6 @@
|
||||
/* prefs.py: Label for location of third-party plugins folder; In files: prefs.py:908; */
|
||||
"Plugins folder" = "プラグインフォルダ";
|
||||
|
||||
/* prefs.py: Label on button used to open a filesystem folder; In files: prefs.py:915; */
|
||||
"Open" = "開く";
|
||||
|
||||
/* prefs.py: Tip/label about how to disable plugins; In files: prefs.py:923; */
|
||||
"Tip: You can disable a plugin by{CR}adding '{EXT}' to its folder name" = "ヒント: プラグインを無効にするには{CR}フォルダ名に '{EXT}' を追加してください";
|
||||
|
||||
@ -501,6 +486,9 @@
|
||||
/* prefs.py: Lable on list of user-disabled plugins; In files: prefs.py:977; */
|
||||
"Disabled Plugins" = "無効なプラグイン";
|
||||
|
||||
/* prefs.py: Catch & Record Profiler Errors; */
|
||||
"Error in System Profiler" = "システムプロファイラーでエラー";
|
||||
|
||||
/* stats.py: Cmdr stats; In files: stats.py:58; */
|
||||
"Balance" = "口座残高";
|
||||
|
||||
@ -813,7 +801,6 @@
|
||||
/* EDMarketConnector.py: Inform the user the Update Track has changed; */
|
||||
"Update Track Changed to {TRACK}" = "アップデートトラックを {TRACK} に変更します";
|
||||
|
||||
|
||||
/* EDMarketConnector.py: Inform User of Beta -> Stable Transition Risks; */
|
||||
"Update track changed to Stable from Beta. You will no longer receive Beta updates. You will stay on your current Beta version until the next Stable release.\r\n\r\nYou can manually revert to the latest Stable version. To do so, you must download and install the latest Stable version manually. Note that this may introduce bugs or break completely if downgrading between major versions with significant changes.\r\n\r\nDo you want to open GitHub to download the latest release?" = "アップデートトラックをベータから安定版に変更しました。今後ベータ版のアップデートを受け取ることはありません。次の安定版がリリースされるまでこのベータ版をご利用いただけます。\n\n手動で最新の安定版に戻すことができます。そのためには、最新の安定版を手動でダウンロードしてインストールする必要があります。大きな変更を伴うメジャーバージョン間のダウングレードの場合、バグが発生したり、完全に壊れたりする可能性があることに注意してください。\n\nGitHubを開いて最新リリースをダウンロードしますか?";
|
||||
|
||||
@ -822,3 +809,4 @@
|
||||
|
||||
/* ttkHyperlinkLabel.py: Open Element In Selected Provider; */
|
||||
"Open in {URL}" = "{URL} で開く";
|
||||
|
||||
|
@ -388,9 +388,6 @@
|
||||
/* prefs.py: Label for location of third-party plugins folder; In files: prefs.py:908; */
|
||||
"Plugins folder" = "플러그인 폴더";
|
||||
|
||||
/* prefs.py: Label on button used to open a filesystem folder; In files: prefs.py:915; */
|
||||
"Open" = "열기";
|
||||
|
||||
/* prefs.py: Tip/label about how to disable plugins; In files: prefs.py:923; */
|
||||
"Tip: You can disable a plugin by{CR}adding '{EXT}' to its folder name" = "팁: 플러그인을 비활성화하려면{CR} '{EXT}'를 폴더명 뒤에 추가하면 됨";
|
||||
|
||||
|
@ -220,9 +220,6 @@
|
||||
/* prefs.py: Label for location of third-party plugins folder; In files: prefs.py:908; */
|
||||
"Plugins folder" = "Spraudņu mape";
|
||||
|
||||
/* prefs.py: Label on button used to open a filesystem folder; In files: prefs.py:915; */
|
||||
"Open" = "Atvērt";
|
||||
|
||||
/* prefs.py: Label on list of enabled plugins; In files: prefs.py:934; */
|
||||
"Enabled Plugins" = "Iespējoti Spraudņi";
|
||||
|
||||
|
@ -1,6 +1,3 @@
|
||||
/* prefs.py: Catch & Record Profiler Errors; */
|
||||
"Error in System Profiler" = "Fout in systeemprofiler";
|
||||
|
||||
/* edsm.py:Settings>EDSM - Label on checkbox for 'send data'; In files: edsm.py:316; */
|
||||
"Send flight log and CMDR status to EDSM" = "Stuur logboek en CMDR-status naar EDSM";
|
||||
|
||||
@ -162,12 +159,6 @@
|
||||
/* EDMarketConnector.py: CAPI fleetcarrier query aborted because of killswitch; In files: EDMarketConnector.py:1157; */
|
||||
"CAPI fleetcarrier disabled by killswitch" = "CAPI fleetcarrier uitgeschakeld door killswitch";
|
||||
|
||||
|
||||
/* EDMarketConnector.py: No data was returned for the fleetcarrier from the Frontier CAPI; In files: EDMarketConnector.py:1219; */
|
||||
"CAPI: No fleetcarrier data returned" = "CAPI: geen fleet carrier data verkregen";
|
||||
|
||||
/* EDMarketConnector.py: We didn't have the fleetcarrier callsign when we should have; In files: EDMarketConnector.py:1223; */
|
||||
"CAPI: Fleetcarrier data incomplete" = "CAPI: data fleet carrier incompleet";
|
||||
/* EDMarketConnector.py: No data was returned for the commander from the Frontier CAPI; In files: EDMarketConnector.py:1242; */
|
||||
"CAPI: No commander data returned" = "CAPI: geen commander data verkregen";
|
||||
|
||||
@ -375,9 +366,6 @@
|
||||
/* prefs.py: Label for location of third-party plugins folder; In files: prefs.py:908; */
|
||||
"Plugins folder" = "Plugins map";
|
||||
|
||||
/* prefs.py: Label on button used to open a filesystem folder; In files: prefs.py:915; */
|
||||
"Open" = "Openen";
|
||||
|
||||
/* prefs.py: Tip/label about how to disable plugins; In files: prefs.py:923; */
|
||||
"Tip: You can disable a plugin by{CR}adding '{EXT}' to its folder name" = "Tip: Je kan een plugin deactiveren door '{EXT}'{CR}toe te voegen aan de map naam";
|
||||
|
||||
@ -393,6 +381,9 @@
|
||||
/* prefs.py: Lable on list of user-disabled plugins; In files: prefs.py:977; */
|
||||
"Disabled Plugins" = "Gedeactiveerde plugins";
|
||||
|
||||
/* prefs.py: Catch & Record Profiler Errors; */
|
||||
"Error in System Profiler" = "Fout in systeemprofiler";
|
||||
|
||||
/* stats.py: Cmdr stats; In files: stats.py:58; */
|
||||
"Balance" = "Balans";
|
||||
|
||||
|
@ -159,12 +159,6 @@
|
||||
/* EDMarketConnector.py: CAPI fleetcarrier query aborted because of killswitch; In files: EDMarketConnector.py:1157; */
|
||||
"CAPI fleetcarrier disabled by killswitch" = "Fleetcarier CAPI wyłączony \"kill switchem\"";
|
||||
|
||||
|
||||
/* EDMarketConnector.py: No data was returned for the fleetcarrier from the Frontier CAPI; In files: EDMarketConnector.py:1219; */
|
||||
"CAPI: No fleetcarrier data returned" = "CAPI: Nie zwrócono danych dotyczących lotniskowca";
|
||||
|
||||
/* EDMarketConnector.py: We didn't have the fleetcarrier callsign when we should have; In files: EDMarketConnector.py:1223; */
|
||||
"CAPI: Fleetcarrier data incomplete" = "CAPI: Niekompletne dane lotniskowca";
|
||||
/* EDMarketConnector.py: No data was returned for the commander from the Frontier CAPI; In files: EDMarketConnector.py:1242; */
|
||||
"CAPI: No commander data returned" = "CAPI: Nie zwrócono danych dowódcy";
|
||||
|
||||
@ -369,9 +363,6 @@
|
||||
/* prefs.py: Settings > Configuration - Label for CAPI section; In files: prefs.py:469; */
|
||||
"CAPI Settings" = "Ustawienia CAPI";
|
||||
|
||||
|
||||
/* prefs.py: Configuration - Enable or disable the Fleet Carrier CAPI calls; In files: prefs.py:475; */
|
||||
"Enable Fleetcarrier CAPI Queries" = "Włącz zapytania CAPI dla lotniskowca";
|
||||
/* prefs.py: Hotkey/Shortcut settings prompt on Windows; In files: prefs.py:492; */
|
||||
"Hotkey" = "Skr. Klaw.";
|
||||
|
||||
@ -450,9 +441,6 @@
|
||||
/* prefs.py: Label for location of third-party plugins folder; In files: prefs.py:908; */
|
||||
"Plugins folder" = "Folder pluginów";
|
||||
|
||||
/* prefs.py: Label on button used to open a filesystem folder; In files: prefs.py:915; */
|
||||
"Open" = "Otwórz";
|
||||
|
||||
/* prefs.py: Tip/label about how to disable plugins; In files: prefs.py:923; */
|
||||
"Tip: You can disable a plugin by{CR}adding '{EXT}' to its folder name" = "Wskazówka: Możesz wyłączyć pluginy{CR}dodając '{EXT}' do nazwy folderu";
|
||||
|
||||
|
@ -1,15 +1,3 @@
|
||||
/* prefs.py: Catch & Record Profiler Errors; */
|
||||
"Error in System Profiler" = "Erro no Perfil de Sistema";
|
||||
|
||||
/* EDMarketConnector.py: We didn't have the Fleet Carrier callsign when we should have; In files: EDMarketConnector.py:1223; */
|
||||
"CAPI: Fleet Carrier data incomplete" = "CAPI: Dados de porta-frotas incompletos";
|
||||
|
||||
/* EDMarketConnector.py: No data was returned for the Fleet Carrier from the Frontier CAPI; In files: EDMarketConnector.py:1219; */
|
||||
"CAPI: No Fleet Carrier data returned" = "CAPI: Nenhum dado de porta-frotas retornado";
|
||||
|
||||
/* prefs.py: Configuration - Enable or disable the Fleet Carrier CAPI calls; In files: prefs.py:475; */
|
||||
"Enable Fleet Carrier CAPI Queries" = "Ativar requisições CAPI para porta-frotas";
|
||||
|
||||
/* edsm.py:Settings>EDSM - Label on checkbox for 'send data'; In files: edsm.py:316; */
|
||||
"Send flight log and CMDR status to EDSM" = "Enviar registro de voo e status do CMDT para o EDSM";
|
||||
|
||||
@ -189,12 +177,12 @@
|
||||
/* EDMarketConnector.py: CAPI fleetcarrier query aborted because of killswitch; In files: EDMarketConnector.py:1157; */
|
||||
"CAPI fleetcarrier disabled by killswitch" = "CAPI para porta-frotas desativado pelo botão de interrupção.";
|
||||
|
||||
/* EDMarketConnector.py: No data was returned for the Fleet Carrier from the Frontier CAPI; In files: EDMarketConnector.py:1219; */
|
||||
"CAPI: No Fleet Carrier data returned" = "CAPI: Nenhum dado de porta-frotas retornado";
|
||||
|
||||
/* EDMarketConnector.py: No data was returned for the fleetcarrier from the Frontier CAPI; In files: EDMarketConnector.py:1219; */
|
||||
"CAPI: No fleetcarrier data returned" = "CAPI: Nenhum dado de porta-fortas retornado";
|
||||
/* EDMarketConnector.py: We didn't have the Fleet Carrier callsign when we should have; In files: EDMarketConnector.py:1223; */
|
||||
"CAPI: Fleet Carrier data incomplete" = "CAPI: Dados de porta-frotas incompletos";
|
||||
|
||||
/* EDMarketConnector.py: We didn't have the fleetcarrier callsign when we should have; In files: EDMarketConnector.py:1223; */
|
||||
"CAPI: Fleetcarrier data incomplete" = "CAPI: Dados de porta-frota incompletos";
|
||||
/* EDMarketConnector.py: No data was returned for the commander from the Frontier CAPI; In files: EDMarketConnector.py:1242; */
|
||||
"CAPI: No commander data returned" = "CAPI: Nenhum dado de comandante retornado";
|
||||
|
||||
@ -399,9 +387,9 @@
|
||||
/* prefs.py: Settings > Configuration - Label for CAPI section; In files: prefs.py:469; */
|
||||
"CAPI Settings" = "Configurações de CAPI";
|
||||
|
||||
|
||||
/* prefs.py: Configuration - Enable or disable the Fleet Carrier CAPI calls; In files: prefs.py:475; */
|
||||
"Enable Fleetcarrier CAPI Queries" = "Ativar requisições CAPI para porta-frotas";
|
||||
"Enable Fleet Carrier CAPI Queries" = "Ativar requisições CAPI para porta-frotas";
|
||||
|
||||
/* prefs.py: Hotkey/Shortcut settings prompt on Windows; In files: prefs.py:492; */
|
||||
"Hotkey" = "Tecla de atalho";
|
||||
|
||||
@ -480,8 +468,11 @@
|
||||
/* prefs.py: Label for location of third-party plugins folder; In files: prefs.py:908; */
|
||||
"Plugins folder" = "Pasta dos plugins";
|
||||
|
||||
/* prefs.py: Label on button used to open a filesystem folder; In files: prefs.py:915; */
|
||||
"Open" = "Abrir";
|
||||
/* prefs.py: Label on button used to open the Plugin Folder; */
|
||||
"Open Plugins Folder" = "Abrir pasta dos plugins";
|
||||
|
||||
/* prefs.py: Selecting the Location of the Plugin Directory on the Filesystem; */
|
||||
"Plugin Directory Location" = "Localização da pasta dos plugins";
|
||||
|
||||
/* prefs.py: Tip/label about how to disable plugins; In files: prefs.py:923; */
|
||||
"Tip: You can disable a plugin by{CR}adding '{EXT}' to its folder name" = "Dica: Você pode desativar um plugin{CR}adicionando '{EXT}' ao nome da pasta";
|
||||
@ -501,6 +492,9 @@
|
||||
/* prefs.py: Lable on list of user-disabled plugins; In files: prefs.py:977; */
|
||||
"Disabled Plugins" = "Plugins desabilitados";
|
||||
|
||||
/* prefs.py: Catch & Record Profiler Errors; */
|
||||
"Error in System Profiler" = "Erro no Perfil de Sistema";
|
||||
|
||||
/* stats.py: Cmdr stats; In files: stats.py:58; */
|
||||
"Balance" = "Balanço";
|
||||
|
||||
@ -813,12 +807,20 @@
|
||||
/* EDMarketConnector.py: Inform the user the Update Track has changed; */
|
||||
"Update Track Changed to {TRACK}" = "Caminho de Atualizações modificado para {TRACK}";
|
||||
|
||||
|
||||
/* EDMarketConnector.py: Inform User of Beta -> Stable Transition Risks; */
|
||||
"Update track changed to Stable from Beta. You will no longer receive Beta updates. You will stay on your current Beta version until the next Stable release.\r\n\r\nYou can manually revert to the latest Stable version. To do so, you must download and install the latest Stable version manually. Note that this may introduce bugs or break completely if downgrading between major versions with significant changes.\r\n\r\nDo you want to open GitHub to download the latest release?" = "Caminho de atualizações modificado de Estável para Beta. Você não receberá mais atualizações Beta. Você seguirá na versão Beta atual até a próxima atualização Estável.\n\nVocê pode reverter manualmente para a última versão Estável. Para fazer isso, faça download e instale a última versão Estável manualmente. Fique atento: isto pode introduzir bugs ou quebrar completamente a aplicação caso você o retorno volte para uma versão principal com mudanças significativas.\n\nGostaria de abrir o GitHub para fazer download da última versão?";
|
||||
|
||||
/* EDMarketConnector.py: Title of Notification Popup for EDMC Restart; */
|
||||
"Restart Required" = "Reinicialização necessária";
|
||||
|
||||
/* EDMarketConnector.py: Text of Notification Popup for EDMC Restart; */
|
||||
"A restart of EDMC is required. EDMC will now restart." = "É necessário reinicializar o EDMC. Isso será realizado agora.";
|
||||
|
||||
/* myNotebook.py: Can't Paste Images or Files in Text; */
|
||||
"Cannot paste non-text content." = "Só é possível colar texto.";
|
||||
|
||||
/* ttkHyperlinkLabel.py: Open Element In Selected Provider; */
|
||||
"Open in {URL}" = "Abrir em {URL}";
|
||||
|
||||
/* ttkHyperlinkLabel.py: Copy the Inara SLEF Format of the active ship to the clipboard; */
|
||||
"Copy Inara SLEF" = "Copiar formato SLEF";
|
||||
|
@ -1,15 +1,3 @@
|
||||
/* prefs.py: Catch & Record Profiler Errors; */
|
||||
"Error in System Profiler" = "Erro nas Informações do Sistema";
|
||||
|
||||
/* EDMarketConnector.py: We didn't have the Fleet Carrier callsign when we should have; In files: EDMarketConnector.py:1223; */
|
||||
"CAPI: Fleet Carrier data incomplete" = "CAPI: Dados de Transportador de Frota incompletos";
|
||||
|
||||
/* EDMarketConnector.py: No data was returned for the Fleet Carrier from the Frontier CAPI; In files: EDMarketConnector.py:1219; */
|
||||
"CAPI: No Fleet Carrier data returned" = "CAPI: Sem dados de Transportador de Frota";
|
||||
|
||||
/* prefs.py: Configuration - Enable or disable the Fleet Carrier CAPI calls; In files: prefs.py:475; */
|
||||
"Enable Fleet Carrier CAPI Queries" = "Ligar Consultas CAPI de Transportador de Frota";
|
||||
|
||||
/* edsm.py:Settings>EDSM - Label on checkbox for 'send data'; In files: edsm.py:316; */
|
||||
"Send flight log and CMDR status to EDSM" = "Enviar registos de voo e estado de CMDR para o EDSM";
|
||||
|
||||
@ -189,12 +177,12 @@
|
||||
/* EDMarketConnector.py: CAPI fleetcarrier query aborted because of killswitch; In files: EDMarketConnector.py:1157; */
|
||||
"CAPI fleetcarrier disabled by killswitch" = "Consulta à CAPI de Transportador de Frota cancelada por botão";
|
||||
|
||||
/* EDMarketConnector.py: No data was returned for the Fleet Carrier from the Frontier CAPI; In files: EDMarketConnector.py:1219; */
|
||||
"CAPI: No Fleet Carrier data returned" = "CAPI: Sem dados de Transportador de Frota";
|
||||
|
||||
/* EDMarketConnector.py: No data was returned for the fleetcarrier from the Frontier CAPI; In files: EDMarketConnector.py:1219; */
|
||||
"CAPI: No fleetcarrier data returned" = "CAPI: Sem dados de Transportador de Frota";
|
||||
/* EDMarketConnector.py: We didn't have the Fleet Carrier callsign when we should have; In files: EDMarketConnector.py:1223; */
|
||||
"CAPI: Fleet Carrier data incomplete" = "CAPI: Dados de Transportador de Frota incompletos";
|
||||
|
||||
/* EDMarketConnector.py: We didn't have the fleetcarrier callsign when we should have; In files: EDMarketConnector.py:1223; */
|
||||
"CAPI: Fleetcarrier data incomplete" = "CAPI: Dados de Transportador de Frota incompletos";
|
||||
/* EDMarketConnector.py: No data was returned for the commander from the Frontier CAPI; In files: EDMarketConnector.py:1242; */
|
||||
"CAPI: No commander data returned" = "CAPI: Dados do Comandante não recebidos";
|
||||
|
||||
@ -399,9 +387,9 @@
|
||||
/* prefs.py: Settings > Configuration - Label for CAPI section; In files: prefs.py:469; */
|
||||
"CAPI Settings" = "Definições CAPI";
|
||||
|
||||
|
||||
/* prefs.py: Configuration - Enable or disable the Fleet Carrier CAPI calls; In files: prefs.py:475; */
|
||||
"Enable Fleetcarrier CAPI Queries" = "Ligar Consultas CAPI de Transportador de Frota";
|
||||
"Enable Fleet Carrier CAPI Queries" = "Ligar Consultas CAPI de Transportador de Frota";
|
||||
|
||||
/* prefs.py: Hotkey/Shortcut settings prompt on Windows; In files: prefs.py:492; */
|
||||
"Hotkey" = "Tecla Rápida";
|
||||
|
||||
@ -480,9 +468,6 @@
|
||||
/* prefs.py: Label for location of third-party plugins folder; In files: prefs.py:908; */
|
||||
"Plugins folder" = "Pasta dos Plugins";
|
||||
|
||||
/* prefs.py: Label on button used to open a filesystem folder; In files: prefs.py:915; */
|
||||
"Open" = "Abrir";
|
||||
|
||||
/* prefs.py: Tip/label about how to disable plugins; In files: prefs.py:923; */
|
||||
"Tip: You can disable a plugin by{CR}adding '{EXT}' to its folder name" = "Dica: Pode desactivar um plugin{CR}ao adicionar '{EXT}' ao nome da sua pasta";
|
||||
|
||||
@ -501,6 +486,9 @@
|
||||
/* prefs.py: Lable on list of user-disabled plugins; In files: prefs.py:977; */
|
||||
"Disabled Plugins" = "Plugins Desactivados";
|
||||
|
||||
/* prefs.py: Catch & Record Profiler Errors; */
|
||||
"Error in System Profiler" = "Erro nas Informações do Sistema";
|
||||
|
||||
/* stats.py: Cmdr stats; In files: stats.py:58; */
|
||||
"Balance" = "Balanço";
|
||||
|
||||
@ -813,7 +801,6 @@
|
||||
/* EDMarketConnector.py: Inform the user the Update Track has changed; */
|
||||
"Update Track Changed to {TRACK}" = "Caminho de Actualizações alterado para {TRACK}";
|
||||
|
||||
|
||||
/* EDMarketConnector.py: Inform User of Beta -> Stable Transition Risks; */
|
||||
"Update track changed to Stable from Beta. You will no longer receive Beta updates. You will stay on your current Beta version until the next Stable release.\r\n\r\nYou can manually revert to the latest Stable version. To do so, you must download and install the latest Stable version manually. Note that this may introduce bugs or break completely if downgrading between major versions with significant changes.\r\n\r\nDo you want to open GitHub to download the latest release?" = "Caminho de Actualizações alterado de Estável para Beta. Deixará de receber actualizações Beta. Irá continuar na versão Beta até à próxima actualização Estável.\n\nPode, manualmente, reverter para a última versão Estável. Para isso, faça download e instale a última versão Estável manualmente. Note que o retrocesso para versões anteriores com diferenças significativas poderá introduzir bugs ou danificar a instalação.\n\nDeseja abrir o GitHub para fazer download da última versão?";
|
||||
|
||||
@ -822,3 +809,4 @@
|
||||
|
||||
/* ttkHyperlinkLabel.py: Open Element In Selected Provider; */
|
||||
"Open in {URL}" = "Abrir em {URL}";
|
||||
|
||||
|
@ -1,15 +1,3 @@
|
||||
/* prefs.py: Catch & Record Profiler Errors; */
|
||||
"Error in System Profiler" = "Ошибка в системном профайлере";
|
||||
|
||||
/* EDMarketConnector.py: We didn't have the Fleet Carrier callsign when we should have; In files: EDMarketConnector.py:1223; */
|
||||
"CAPI: Fleet Carrier data incomplete" = "CAPI: Неполная информация о корабле-носителе";
|
||||
|
||||
/* EDMarketConnector.py: No data was returned for the Fleet Carrier from the Frontier CAPI; In files: EDMarketConnector.py:1219; */
|
||||
"CAPI: No Fleet Carrier data returned" = "CAPI: Нет данных о корабле-носителе";
|
||||
|
||||
/* prefs.py: Configuration - Enable or disable the Fleet Carrier CAPI calls; In files: prefs.py:475; */
|
||||
"Enable Fleet Carrier CAPI Queries" = "Включить запросы к CAPI о корабле-носителе";
|
||||
|
||||
/* edsm.py:Settings>EDSM - Label on checkbox for 'send data'; In files: edsm.py:316; */
|
||||
"Send flight log and CMDR status to EDSM" = "Отправить журнал полетов и статус командира в EDSM";
|
||||
|
||||
@ -189,12 +177,12 @@
|
||||
/* EDMarketConnector.py: CAPI fleetcarrier query aborted because of killswitch; In files: EDMarketConnector.py:1157; */
|
||||
"CAPI fleetcarrier disabled by killswitch" = "CAPI кораблей-носителей отключен с помощью killswitch";
|
||||
|
||||
/* EDMarketConnector.py: No data was returned for the Fleet Carrier from the Frontier CAPI; In files: EDMarketConnector.py:1219; */
|
||||
"CAPI: No Fleet Carrier data returned" = "CAPI: Нет данных о корабле-носителе";
|
||||
|
||||
/* EDMarketConnector.py: No data was returned for the fleetcarrier from the Frontier CAPI; In files: EDMarketConnector.py:1219; */
|
||||
"CAPI: No fleetcarrier data returned" = "CAPI: Нет данных о флотоносце";
|
||||
/* EDMarketConnector.py: We didn't have the Fleet Carrier callsign when we should have; In files: EDMarketConnector.py:1223; */
|
||||
"CAPI: Fleet Carrier data incomplete" = "CAPI: Неполная информация о корабле-носителе";
|
||||
|
||||
/* EDMarketConnector.py: We didn't have the fleetcarrier callsign when we should have; In files: EDMarketConnector.py:1223; */
|
||||
"CAPI: Fleetcarrier data incomplete" = "CAPI: Неполная информация о флотоносце";
|
||||
/* EDMarketConnector.py: No data was returned for the commander from the Frontier CAPI; In files: EDMarketConnector.py:1242; */
|
||||
"CAPI: No commander data returned" = "CAPI: Нет данных пилота";
|
||||
|
||||
@ -399,9 +387,9 @@
|
||||
/* prefs.py: Settings > Configuration - Label for CAPI section; In files: prefs.py:469; */
|
||||
"CAPI Settings" = "Настройки CAPI";
|
||||
|
||||
|
||||
/* prefs.py: Configuration - Enable or disable the Fleet Carrier CAPI calls; In files: prefs.py:475; */
|
||||
"Enable Fleetcarrier CAPI Queries" = "Включить запросы к CAPI о флотоносце";
|
||||
"Enable Fleet Carrier CAPI Queries" = "Включить запросы к CAPI о корабле-носителе";
|
||||
|
||||
/* prefs.py: Hotkey/Shortcut settings prompt on Windows; In files: prefs.py:492; */
|
||||
"Hotkey" = "Горячая клавиша";
|
||||
|
||||
@ -480,8 +468,11 @@
|
||||
/* prefs.py: Label for location of third-party plugins folder; In files: prefs.py:908; */
|
||||
"Plugins folder" = "Папка плагинов";
|
||||
|
||||
/* prefs.py: Label on button used to open a filesystem folder; In files: prefs.py:915; */
|
||||
"Open" = "Открыть";
|
||||
/* prefs.py: Label on button used to open the Plugin Folder; */
|
||||
"Open Plugins Folder" = "Открыть папку плагинов";
|
||||
|
||||
/* prefs.py: Selecting the Location of the Plugin Directory on the Filesystem; */
|
||||
"Plugin Directory Location" = "Месторасположение директории плагинов";
|
||||
|
||||
/* prefs.py: Tip/label about how to disable plugins; In files: prefs.py:923; */
|
||||
"Tip: You can disable a plugin by{CR}adding '{EXT}' to its folder name" = "Подсказка: Отключить конкретный плагин можно {CR} добавив '{EXT}' к названию папки";
|
||||
@ -501,6 +492,9 @@
|
||||
/* prefs.py: Lable on list of user-disabled plugins; In files: prefs.py:977; */
|
||||
"Disabled Plugins" = "Отключенные плагины";
|
||||
|
||||
/* prefs.py: Catch & Record Profiler Errors; */
|
||||
"Error in System Profiler" = "Ошибка в системном профайлере";
|
||||
|
||||
/* stats.py: Cmdr stats; In files: stats.py:58; */
|
||||
"Balance" = "Баланс";
|
||||
|
||||
@ -813,12 +807,20 @@
|
||||
/* EDMarketConnector.py: Inform the user the Update Track has changed; */
|
||||
"Update Track Changed to {TRACK}" = "Обновить маршрут. Изменено на {TRACK}";
|
||||
|
||||
|
||||
/* EDMarketConnector.py: Inform User of Beta -> Stable Transition Risks; */
|
||||
"Update track changed to Stable from Beta. You will no longer receive Beta updates. You will stay on your current Beta version until the next Stable release.\r\n\r\nYou can manually revert to the latest Stable version. To do so, you must download and install the latest Stable version manually. Note that this may introduce bugs or break completely if downgrading between major versions with significant changes.\r\n\r\nDo you want to open GitHub to download the latest release?" = "Изменен путь обновления с \"Бета\" на \"Стабильный\". Вы больше не будете получать обновления \"Бета\". Вы останетесь на текущей бета-версии до следующего стабильного релиза.\n\nВы можете вручную вернуться к последней версии \"Стабильная\". Для этого необходимо загрузить и установить последнюю версию \"Стабильная\" вручную. Обратите внимание, что при переходе между основными версиями со значительными изменениями могут возникнуть ошибки или полная поломка.\n\nХотите открыть GitHub, чтобы загрузить последнюю версию?";
|
||||
|
||||
/* EDMarketConnector.py: Title of Notification Popup for EDMC Restart; */
|
||||
"Restart Required" = "Требуется перезапуск";
|
||||
|
||||
/* EDMarketConnector.py: Text of Notification Popup for EDMC Restart; */
|
||||
"A restart of EDMC is required. EDMC will now restart." = "Требуется перезапуск EDMC. EDMC сейчас перезапустится.";
|
||||
|
||||
/* myNotebook.py: Can't Paste Images or Files in Text; */
|
||||
"Cannot paste non-text content." = "Невозможно добавить нетекстовое содержимое.";
|
||||
|
||||
/* ttkHyperlinkLabel.py: Open Element In Selected Provider; */
|
||||
"Open in {URL}" = "Открыть через {URL}";
|
||||
|
||||
/* ttkHyperlinkLabel.py: Copy the Inara SLEF Format of the active ship to the clipboard; */
|
||||
"Copy Inara SLEF" = "Скопировать данные SLEF Inara";
|
||||
|
@ -1,15 +1,3 @@
|
||||
/* prefs.py: Catch & Record Profiler Errors; */
|
||||
"Error in System Profiler" = "Greša u System Profileru";
|
||||
|
||||
/* EDMarketConnector.py: We didn't have the Fleet Carrier callsign when we should have; In files: EDMarketConnector.py:1223; */
|
||||
"CAPI: Fleet Carrier data incomplete" = "CAPI: Fleet Carrier podaci nisu kompletni";
|
||||
|
||||
/* EDMarketConnector.py: No data was returned for the Fleet Carrier from the Frontier CAPI; In files: EDMarketConnector.py:1219; */
|
||||
"CAPI: No Fleet Carrier data returned" = "CAPI: Fleet Carrier podaci nisu dobijeni";
|
||||
|
||||
/* prefs.py: Configuration - Enable or disable the Fleet Carrier CAPI calls; In files: prefs.py:475; */
|
||||
"Enable Fleet Carrier CAPI Queries" = "Omogući Fleet Carrier CAPI upite";
|
||||
|
||||
/* edsm.py:Settings>EDSM - Label on checkbox for 'send data'; In files: edsm.py:316; */
|
||||
"Send flight log and CMDR status to EDSM" = "Pošalji log leta i CMDR status na EDSM";
|
||||
|
||||
@ -189,12 +177,12 @@
|
||||
/* EDMarketConnector.py: CAPI fleetcarrier query aborted because of killswitch; In files: EDMarketConnector.py:1157; */
|
||||
"CAPI fleetcarrier disabled by killswitch" = "CAPI fleetcarrier onemogućen putem sistemskog prekidača";
|
||||
|
||||
/* EDMarketConnector.py: No data was returned for the Fleet Carrier from the Frontier CAPI; In files: EDMarketConnector.py:1219; */
|
||||
"CAPI: No Fleet Carrier data returned" = "CAPI: Fleet Carrier podaci nisu dobijeni";
|
||||
|
||||
/* EDMarketConnector.py: No data was returned for the fleetcarrier from the Frontier CAPI; In files: EDMarketConnector.py:1219; */
|
||||
"CAPI: No fleetcarrier data returned" = "CAPI: Fleetcarrier podaci nisu dobijeni";
|
||||
/* EDMarketConnector.py: We didn't have the Fleet Carrier callsign when we should have; In files: EDMarketConnector.py:1223; */
|
||||
"CAPI: Fleet Carrier data incomplete" = "CAPI: Fleet Carrier podaci nisu kompletni";
|
||||
|
||||
/* EDMarketConnector.py: We didn't have the fleetcarrier callsign when we should have; In files: EDMarketConnector.py:1223; */
|
||||
"CAPI: Fleetcarrier data incomplete" = "CAPI: Fleetcarrier podaci nisu potpuni";
|
||||
/* EDMarketConnector.py: No data was returned for the commander from the Frontier CAPI; In files: EDMarketConnector.py:1242; */
|
||||
"CAPI: No commander data returned" = "CAPI: Nema podataka o komandantu";
|
||||
|
||||
@ -399,9 +387,9 @@
|
||||
/* prefs.py: Settings > Configuration - Label for CAPI section; In files: prefs.py:469; */
|
||||
"CAPI Settings" = "CAPI Podešavanja";
|
||||
|
||||
|
||||
/* prefs.py: Configuration - Enable or disable the Fleet Carrier CAPI calls; In files: prefs.py:475; */
|
||||
"Enable Fleetcarrier CAPI Queries" = "Omogući Fleetcarrier CAPI Upite";
|
||||
"Enable Fleet Carrier CAPI Queries" = "Omogući Fleet Carrier CAPI upite";
|
||||
|
||||
/* prefs.py: Hotkey/Shortcut settings prompt on Windows; In files: prefs.py:492; */
|
||||
"Hotkey" = "Prečica";
|
||||
|
||||
@ -480,9 +468,6 @@
|
||||
/* prefs.py: Label for location of third-party plugins folder; In files: prefs.py:908; */
|
||||
"Plugins folder" = "Folder za dodatke (plugins)";
|
||||
|
||||
/* prefs.py: Label on button used to open a filesystem folder; In files: prefs.py:915; */
|
||||
"Open" = "Otvori";
|
||||
|
||||
/* prefs.py: Tip/label about how to disable plugins; In files: prefs.py:923; */
|
||||
"Tip: You can disable a plugin by{CR}adding '{EXT}' to its folder name" = "Savjet: Možete da deaktivirate dodatak (plugin){CR}dodavanjem '{EXT}' na ime njegovog foldera";
|
||||
|
||||
@ -501,6 +486,9 @@
|
||||
/* prefs.py: Lable on list of user-disabled plugins; In files: prefs.py:977; */
|
||||
"Disabled Plugins" = "Deaktivirani dodaci (plugins)";
|
||||
|
||||
/* prefs.py: Catch & Record Profiler Errors; */
|
||||
"Error in System Profiler" = "Greša u System Profileru";
|
||||
|
||||
/* stats.py: Cmdr stats; In files: stats.py:58; */
|
||||
"Balance" = "Balans";
|
||||
|
||||
@ -813,7 +801,6 @@
|
||||
/* EDMarketConnector.py: Inform the user the Update Track has changed; */
|
||||
"Update Track Changed to {TRACK}" = "Kanal za osvježavanje je promijenjen u {TRACK}";
|
||||
|
||||
|
||||
/* EDMarketConnector.py: Inform User of Beta -> Stable Transition Risks; */
|
||||
"Update track changed to Stable from Beta. You will no longer receive Beta updates. You will stay on your current Beta version until the next Stable release.\r\n\r\nYou can manually revert to the latest Stable version. To do so, you must download and install the latest Stable version manually. Note that this may introduce bugs or break completely if downgrading between major versions with significant changes.\r\n\r\nDo you want to open GitHub to download the latest release?" = "Kanal za osvježavanje je promijenjen iz Beta u Stabilni. Više nećete primati Beta osvježavanja. Ostaćete na trenutnoj Beta verziji do sljedeće Stabilne verzije.\n\nMožete se ručno vratiti na posljednju Stabilnu verziju. Da biste to učinili morate da skinete i ručno instališete posljednju Stabilnu verziju. Ova procedura može da uvede nove bugove ili da potpuno onesposobi program ako prelazite između verzija sa mnogo značajnih promjena.\n\nDa li želite da otvorite GitHub stranicu za download posljednje verzije?";
|
||||
|
||||
@ -822,3 +809,4 @@
|
||||
|
||||
/* ttkHyperlinkLabel.py: Open Element In Selected Provider; */
|
||||
"Open in {URL}" = "Otvori u {URL}";
|
||||
|
||||
|
@ -168,12 +168,6 @@
|
||||
/* EDMarketConnector.py: CAPI fleetcarrier query aborted because of killswitch; In files: EDMarketConnector.py:1157; */
|
||||
"CAPI fleetcarrier disabled by killswitch" = "CAPI fleetcarrier deaktiviran preko sistemskog prekidača";
|
||||
|
||||
|
||||
/* EDMarketConnector.py: No data was returned for the fleetcarrier from the Frontier CAPI; In files: EDMarketConnector.py:1219; */
|
||||
"CAPI: No fleetcarrier data returned" = "CAPI: Podaci o fleetcarrier-u nisu dobijeni";
|
||||
|
||||
/* EDMarketConnector.py: We didn't have the fleetcarrier callsign when we should have; In files: EDMarketConnector.py:1223; */
|
||||
"CAPI: Fleetcarrier data incomplete" = "CAPI: Podaci o fleetcarrier-u su nepotpuni";
|
||||
/* EDMarketConnector.py: No data was returned for the commander from the Frontier CAPI; In files: EDMarketConnector.py:1242; */
|
||||
"CAPI: No commander data returned" = "CAPI: Nema podataka o komandiru";
|
||||
|
||||
@ -378,9 +372,6 @@
|
||||
/* prefs.py: Settings > Configuration - Label for CAPI section; In files: prefs.py:469; */
|
||||
"CAPI Settings" = "CAPI Podešavanja";
|
||||
|
||||
|
||||
/* prefs.py: Configuration - Enable or disable the Fleet Carrier CAPI calls; In files: prefs.py:475; */
|
||||
"Enable Fleetcarrier CAPI Queries" = "Aktiviraj Fleetcarrier CAPIU Upite";
|
||||
/* prefs.py: Hotkey/Shortcut settings prompt on Windows; In files: prefs.py:492; */
|
||||
"Hotkey" = "Skraćenica";
|
||||
|
||||
@ -459,9 +450,6 @@
|
||||
/* prefs.py: Label for location of third-party plugins folder; In files: prefs.py:908; */
|
||||
"Plugins folder" = "Folder za dodatke (plugins)";
|
||||
|
||||
/* prefs.py: Label on button used to open a filesystem folder; In files: prefs.py:915; */
|
||||
"Open" = "Otvori";
|
||||
|
||||
/* prefs.py: Tip/label about how to disable plugins; In files: prefs.py:923; */
|
||||
"Tip: You can disable a plugin by{CR}adding '{EXT}' to its folder name" = "Tip: Možete deaktivirati dodatak (plugin) dodavanjem{CR}'{EXT}' na ime njegovog foldera";
|
||||
|
||||
@ -788,3 +776,4 @@
|
||||
|
||||
/* ttkHyperlinkLabel.py: Open Element In Selected Provider; */
|
||||
"Open in {URL}" = "Otvori na {URL}";
|
||||
|
||||
|
@ -241,9 +241,6 @@
|
||||
/* prefs.py: Label for location of third-party plugins folder; In files: prefs.py:908; */
|
||||
"Plugins folder" = "Mapp för plugins";
|
||||
|
||||
/* prefs.py: Label on button used to open a filesystem folder; In files: prefs.py:915; */
|
||||
"Open" = "Öppna";
|
||||
|
||||
/* prefs.py: Tip/label about how to disable plugins; In files: prefs.py:923; */
|
||||
"Tip: You can disable a plugin by{CR}adding '{EXT}' to its folder name" = "Tips: du kan avaktivera en plugin genom{CR}att lägga till '{EXT}' på slutet av mappens namn";
|
||||
|
||||
|
@ -1,15 +1,3 @@
|
||||
/* prefs.py: Catch & Record Profiler Errors; */
|
||||
"Error in System Profiler" = "Sistem Profilcisinde Hata oluştu";
|
||||
|
||||
/* EDMarketConnector.py: We didn't have the Fleet Carrier callsign when we should have; In files: EDMarketConnector.py:1223; */
|
||||
"CAPI: Fleet Carrier data incomplete" = "CAPI: Filo Taşıyıcısı verileri eksik";
|
||||
|
||||
/* EDMarketConnector.py: No data was returned for the Fleet Carrier from the Frontier CAPI; In files: EDMarketConnector.py:1219; */
|
||||
"CAPI: No Fleet Carrier data returned" = "CAPI: Filo Taşıyıcısı verisi bulunamadı";
|
||||
|
||||
/* prefs.py: Configuration - Enable or disable the Fleet Carrier CAPI calls; In files: prefs.py:475; */
|
||||
"Enable Fleet Carrier CAPI Queries" = "Filo Taşıyıcı CAPI Sorgularını Etkinleştir";
|
||||
|
||||
/* edsm.py:Settings>EDSM - Label on checkbox for 'send data'; In files: edsm.py:316; */
|
||||
"Send flight log and CMDR status to EDSM" = "Uçuş günlüğünü ve CMDR durumunu EDSM'e gönder";
|
||||
|
||||
@ -189,12 +177,12 @@
|
||||
/* EDMarketConnector.py: CAPI fleetcarrier query aborted because of killswitch; In files: EDMarketConnector.py:1157; */
|
||||
"CAPI fleetcarrier disabled by killswitch" = "CAPI filo taşıyıcısı killswitch tarafından devre dışı bırakıldı";
|
||||
|
||||
/* EDMarketConnector.py: No data was returned for the Fleet Carrier from the Frontier CAPI; In files: EDMarketConnector.py:1219; */
|
||||
"CAPI: No Fleet Carrier data returned" = "CAPI: Filo Taşıyıcısı verisi bulunamadı";
|
||||
|
||||
/* EDMarketConnector.py: No data was returned for the fleetcarrier from the Frontier CAPI; In files: EDMarketConnector.py:1219; */
|
||||
"CAPI: No fleetcarrier data returned" = "CAPI: Filo taşıyıcı verisi gelmedi.";
|
||||
/* EDMarketConnector.py: We didn't have the Fleet Carrier callsign when we should have; In files: EDMarketConnector.py:1223; */
|
||||
"CAPI: Fleet Carrier data incomplete" = "CAPI: Filo Taşıyıcısı verileri eksik";
|
||||
|
||||
/* EDMarketConnector.py: We didn't have the fleetcarrier callsign when we should have; In files: EDMarketConnector.py:1223; */
|
||||
"CAPI: Fleetcarrier data incomplete" = "CAPI: Filo taşıyıcı verileri eksik";
|
||||
/* EDMarketConnector.py: No data was returned for the commander from the Frontier CAPI; In files: EDMarketConnector.py:1242; */
|
||||
"CAPI: No commander data returned" = "CAPI: Cmdr verisi döndürülmedi";
|
||||
|
||||
@ -399,9 +387,9 @@
|
||||
/* prefs.py: Settings > Configuration - Label for CAPI section; In files: prefs.py:469; */
|
||||
"CAPI Settings" = "CAPI ayarları";
|
||||
|
||||
|
||||
/* prefs.py: Configuration - Enable or disable the Fleet Carrier CAPI calls; In files: prefs.py:475; */
|
||||
"Enable Fleetcarrier CAPI Queries" = "Filo Taşıyıcı CAPI sorgulamalarını etkinleştir";
|
||||
"Enable Fleet Carrier CAPI Queries" = "Filo Taşıyıcı CAPI Sorgularını Etkinleştir";
|
||||
|
||||
/* prefs.py: Hotkey/Shortcut settings prompt on Windows; In files: prefs.py:492; */
|
||||
"Hotkey" = "Kısayoltuşu";
|
||||
|
||||
@ -480,9 +468,6 @@
|
||||
/* prefs.py: Label for location of third-party plugins folder; In files: prefs.py:908; */
|
||||
"Plugins folder" = "Eklentiler klasörü";
|
||||
|
||||
/* prefs.py: Label on button used to open a filesystem folder; In files: prefs.py:915; */
|
||||
"Open" = "Aç";
|
||||
|
||||
/* prefs.py: Tip/label about how to disable plugins; In files: prefs.py:923; */
|
||||
"Tip: You can disable a plugin by{CR}adding '{EXT}' to its folder name" = "İpucu: Bir eklentiyi, klasör adına{CR}'{EXT}' ekleyerek devre dışı bırakabilirsiniz";
|
||||
|
||||
@ -501,6 +486,9 @@
|
||||
/* prefs.py: Lable on list of user-disabled plugins; In files: prefs.py:977; */
|
||||
"Disabled Plugins" = "Devre Dışı Eklentiler";
|
||||
|
||||
/* prefs.py: Catch & Record Profiler Errors; */
|
||||
"Error in System Profiler" = "Sistem Profilcisinde Hata oluştu";
|
||||
|
||||
/* stats.py: Cmdr stats; In files: stats.py:58; */
|
||||
"Balance" = "Bakiye";
|
||||
|
||||
@ -813,7 +801,6 @@
|
||||
/* EDMarketConnector.py: Inform the user the Update Track has changed; */
|
||||
"Update Track Changed to {TRACK}" = "Aktif Takip {TRACK} olarak güncellendi.";
|
||||
|
||||
|
||||
/* EDMarketConnector.py: Inform User of Beta -> Stable Transition Risks; */
|
||||
"Update track changed to Stable from Beta. You will no longer receive Beta updates. You will stay on your current Beta version until the next Stable release.\r\n\r\nYou can manually revert to the latest Stable version. To do so, you must download and install the latest Stable version manually. Note that this may introduce bugs or break completely if downgrading between major versions with significant changes.\r\n\r\nDo you want to open GitHub to download the latest release?" = "Güncelleme takibi Beta'dan Kararlı olarak değiştirildi. Artık Beta güncellemelerini almayacaksınız. Bir sonraki Kararlı sürüme kadar mevcut Beta sürümünüzde kalacaksınız.\n\nEn son Stabil sürüme manuel olarak geri dönebilirsiniz. Bunu yapmak için en son Stabil sürümünü manuel olarak indirip yüklemeniz gerekir. Önemli değişiklikler içeren ana sürümler arasında sürüm düşürme durumunda bunun hatalara yol açabileceğini veya tamamen bozulabileceğini unutmayın.\n\nEn son sürümü indirmek için GitHub'u açmak istiyor musunuz?";
|
||||
|
||||
@ -822,3 +809,4 @@
|
||||
|
||||
/* ttkHyperlinkLabel.py: Open Element In Selected Provider; */
|
||||
"Open in {URL}" = "Şurada aç {URL}";
|
||||
|
||||
|
@ -1,15 +1,3 @@
|
||||
/* prefs.py: Catch & Record Profiler Errors; */
|
||||
"Error in System Profiler" = "Помилка у Профайлері Систем";
|
||||
|
||||
/* EDMarketConnector.py: We didn't have the Fleet Carrier callsign when we should have; In files: EDMarketConnector.py:1223; */
|
||||
"CAPI: Fleet Carrier data incomplete" = "CAPI: Дані корабля-носія неповні";
|
||||
|
||||
/* EDMarketConnector.py: No data was returned for the Fleet Carrier from the Frontier CAPI; In files: EDMarketConnector.py:1219; */
|
||||
"CAPI: No Fleet Carrier data returned" = "CAPI: Немає даних корабля-носія";
|
||||
|
||||
/* prefs.py: Configuration - Enable or disable the Fleet Carrier CAPI calls; In files: prefs.py:475; */
|
||||
"Enable Fleet Carrier CAPI Queries" = "Ввімкнути запити CAPI кораблів-носіїв";
|
||||
|
||||
/* edsm.py:Settings>EDSM - Label on checkbox for 'send data'; In files: edsm.py:316; */
|
||||
"Send flight log and CMDR status to EDSM" = "Відправляти дані бортового журналу до EDSM";
|
||||
|
||||
@ -189,12 +177,12 @@
|
||||
/* EDMarketConnector.py: CAPI fleetcarrier query aborted because of killswitch; In files: EDMarketConnector.py:1157; */
|
||||
"CAPI fleetcarrier disabled by killswitch" = "Запит CAPI корабля-носія скасований функцією аварійного відключення";
|
||||
|
||||
/* EDMarketConnector.py: No data was returned for the Fleet Carrier from the Frontier CAPI; In files: EDMarketConnector.py:1219; */
|
||||
"CAPI: No Fleet Carrier data returned" = "CAPI: Немає даних корабля-носія";
|
||||
|
||||
/* EDMarketConnector.py: No data was returned for the fleetcarrier from the Frontier CAPI; In files: EDMarketConnector.py:1219; */
|
||||
"CAPI: No fleetcarrier data returned" = "CAPI: Немає даних корабля-носія";
|
||||
/* EDMarketConnector.py: We didn't have the Fleet Carrier callsign when we should have; In files: EDMarketConnector.py:1223; */
|
||||
"CAPI: Fleet Carrier data incomplete" = "CAPI: Дані корабля-носія неповні";
|
||||
|
||||
/* EDMarketConnector.py: We didn't have the fleetcarrier callsign when we should have; In files: EDMarketConnector.py:1223; */
|
||||
"CAPI: Fleetcarrier data incomplete" = "CAPI: Дані корабля-носія неповні";
|
||||
/* EDMarketConnector.py: No data was returned for the commander from the Frontier CAPI; In files: EDMarketConnector.py:1242; */
|
||||
"CAPI: No commander data returned" = "CAPI: Не отримано данних пілота";
|
||||
|
||||
@ -399,9 +387,9 @@
|
||||
/* prefs.py: Settings > Configuration - Label for CAPI section; In files: prefs.py:469; */
|
||||
"CAPI Settings" = "Налаштування CAPI";
|
||||
|
||||
|
||||
/* prefs.py: Configuration - Enable or disable the Fleet Carrier CAPI calls; In files: prefs.py:475; */
|
||||
"Enable Fleetcarrier CAPI Queries" = "Ввімкнути запити CAPI кораблів-носіїв";
|
||||
"Enable Fleet Carrier CAPI Queries" = "Ввімкнути запити CAPI кораблів-носіїв";
|
||||
|
||||
/* prefs.py: Hotkey/Shortcut settings prompt on Windows; In files: prefs.py:492; */
|
||||
"Hotkey" = "Гаряча клавіша";
|
||||
|
||||
@ -480,8 +468,11 @@
|
||||
/* prefs.py: Label for location of third-party plugins folder; In files: prefs.py:908; */
|
||||
"Plugins folder" = "Сховище плагінів";
|
||||
|
||||
/* prefs.py: Label on button used to open a filesystem folder; In files: prefs.py:915; */
|
||||
"Open" = "Відкрити";
|
||||
/* prefs.py: Label on button used to open the Plugin Folder; */
|
||||
"Open Plugins Folder" = "Відкрити папку плагінів";
|
||||
|
||||
/* prefs.py: Selecting the Location of the Plugin Directory on the Filesystem; */
|
||||
"Plugin Directory Location" = "Місцезнаходження папки плагінів";
|
||||
|
||||
/* prefs.py: Tip/label about how to disable plugins; In files: prefs.py:923; */
|
||||
"Tip: You can disable a plugin by{CR}adding '{EXT}' to its folder name" = "Підказка: Ви можете відключити плагін через {CR} додання '{EXT}' до назви папки";
|
||||
@ -501,6 +492,9 @@
|
||||
/* prefs.py: Lable on list of user-disabled plugins; In files: prefs.py:977; */
|
||||
"Disabled Plugins" = "Вимкнені плагіни";
|
||||
|
||||
/* prefs.py: Catch & Record Profiler Errors; */
|
||||
"Error in System Profiler" = "Помилка у Профайлері Систем";
|
||||
|
||||
/* stats.py: Cmdr stats; In files: stats.py:58; */
|
||||
"Balance" = "Баланс";
|
||||
|
||||
@ -813,12 +807,20 @@
|
||||
/* EDMarketConnector.py: Inform the user the Update Track has changed; */
|
||||
"Update Track Changed to {TRACK}" = "Шлях оновлень змінено на {TRACK}";
|
||||
|
||||
|
||||
/* EDMarketConnector.py: Inform User of Beta -> Stable Transition Risks; */
|
||||
"Update track changed to Stable from Beta. You will no longer receive Beta updates. You will stay on your current Beta version until the next Stable release.\r\n\r\nYou can manually revert to the latest Stable version. To do so, you must download and install the latest Stable version manually. Note that this may introduce bugs or break completely if downgrading between major versions with significant changes.\r\n\r\nDo you want to open GitHub to download the latest release?" = "Шлях оновлень змінено з \"Бета\" на \"Стабільний\". Ви більше не будете отримувати бета оновлень. Ви залишитеся на версії бета до наступного стабільного релізу.\n\nВи можете вручну вернутися до останньої стабільної версії. Щоб це зробити, вам необхідно завантажити останню стабільну версію вручну. Зауважте що заниження версії між релізами з багатьма змінами може спричинити помилки чи повністю унеможливити роботу програми.\n\nБажаєте відкрити сторінку GitHub для завантаження останнього релізу?";
|
||||
|
||||
/* EDMarketConnector.py: Title of Notification Popup for EDMC Restart; */
|
||||
"Restart Required" = "Необхідно перезавантаження";
|
||||
|
||||
/* EDMarketConnector.py: Text of Notification Popup for EDMC Restart; */
|
||||
"A restart of EDMC is required. EDMC will now restart." = "Необхідно перезавантажити EDMC. EDMC буде зараз перезавантажений.";
|
||||
|
||||
/* myNotebook.py: Can't Paste Images or Files in Text; */
|
||||
"Cannot paste non-text content." = "Неможливо вставити нетекстовий вміст буфера обміну.";
|
||||
|
||||
/* ttkHyperlinkLabel.py: Open Element In Selected Provider; */
|
||||
"Open in {URL}" = "Відкрити у {URL}";
|
||||
|
||||
/* ttkHyperlinkLabel.py: Copy the Inara SLEF Format of the active ship to the clipboard; */
|
||||
"Copy Inara SLEF" = "Копіювати Inara SLEF";
|
||||
|
@ -151,12 +151,6 @@
|
||||
/* EDMarketConnector.py: CAPI fleetcarrier query aborted because of killswitch; In files: EDMarketConnector.py:1157; */
|
||||
"CAPI fleetcarrier disabled by killswitch" = "CAPI 舰队母舰 (fleet carrier) 被 killswitch 禁用";
|
||||
|
||||
|
||||
/* EDMarketConnector.py: No data was returned for the fleetcarrier from the Frontier CAPI; In files: EDMarketConnector.py:1219; */
|
||||
"CAPI: No fleetcarrier data returned" = "CAPI:无舰队母舰 (fleet carrier) 数据";
|
||||
|
||||
/* EDMarketConnector.py: We didn't have the fleetcarrier callsign when we should have; In files: EDMarketConnector.py:1223; */
|
||||
"CAPI: Fleetcarrier data incomplete" = "CAPI:舰队母舰 (fleet carrier) 数据不完整";
|
||||
/* EDMarketConnector.py: No data was returned for the commander from the Frontier CAPI; In files: EDMarketConnector.py:1242; */
|
||||
"CAPI: No commander data returned" = "CAPI:没有指挥官数据";
|
||||
|
||||
@ -343,9 +337,6 @@
|
||||
/* prefs.py: Settings > Configuration - Label for CAPI section; In files: prefs.py:469; */
|
||||
"CAPI Settings" = "CAPI 设置";
|
||||
|
||||
|
||||
/* prefs.py: Configuration - Enable or disable the Fleet Carrier CAPI calls; In files: prefs.py:475; */
|
||||
"Enable Fleetcarrier CAPI Queries" = "开启舰队母舰 (fleet carrier) CAPI 访问";
|
||||
/* prefs.py: Hotkey/Shortcut settings prompt on Windows; In files: prefs.py:492; */
|
||||
"Hotkey" = "快捷键";
|
||||
|
||||
@ -424,9 +415,6 @@
|
||||
/* prefs.py: Label for location of third-party plugins folder; In files: prefs.py:908; */
|
||||
"Plugins folder" = "插件文件夹";
|
||||
|
||||
/* prefs.py: Label on button used to open a filesystem folder; In files: prefs.py:915; */
|
||||
"Open" = "打开";
|
||||
|
||||
/* prefs.py: Tip/label about how to disable plugins; In files: prefs.py:923; */
|
||||
"Tip: You can disable a plugin by{CR}adding '{EXT}' to its folder name" = "提示:想要禁用某一项插件,{CR}你可以添加 {EXT} 到其文件夹名称中";
|
||||
|
||||
|
26
build.py
26
build.py
@ -10,7 +10,6 @@ import shutil
|
||||
import sys
|
||||
import pathlib
|
||||
from string import Template
|
||||
from os.path import join, isdir
|
||||
import py2exe
|
||||
from config import (
|
||||
appcmdname,
|
||||
@ -37,7 +36,7 @@ def iss_build(template_path: str, output_file: str) -> None:
|
||||
new_file.write(newfile)
|
||||
|
||||
|
||||
def system_check(dist_dir: str) -> str:
|
||||
def system_check(dist_dir: pathlib.Path) -> str:
|
||||
"""Check if the system is able to build."""
|
||||
if sys.version_info < (3, 11):
|
||||
sys.exit(f"Unexpected Python version {sys.version}")
|
||||
@ -55,17 +54,18 @@ def system_check(dist_dir: str) -> str:
|
||||
|
||||
print(f"Git short hash: {git_shorthash}")
|
||||
|
||||
if dist_dir and len(dist_dir) > 1 and isdir(dist_dir):
|
||||
if dist_dir and pathlib.Path.is_dir(dist_dir):
|
||||
shutil.rmtree(dist_dir)
|
||||
return gitversion_file
|
||||
|
||||
|
||||
def generate_data_files(
|
||||
app_name: str, gitversion_file: str, plugins: list[str]
|
||||
) -> list[tuple[str, list[str]]]:
|
||||
) -> list[tuple[object, object]]:
|
||||
"""Create the required datafiles to build."""
|
||||
l10n_dir = "L10n"
|
||||
fdevids_dir = "FDevIDs"
|
||||
fdevids_dir = pathlib.Path("FDevIDs")
|
||||
license_dir = pathlib.Path("docs/Licenses")
|
||||
data_files = [
|
||||
(
|
||||
"",
|
||||
@ -88,23 +88,29 @@ def generate_data_files(
|
||||
),
|
||||
(
|
||||
l10n_dir,
|
||||
[join(l10n_dir, x) for x in os.listdir(l10n_dir) if x.endswith(".strings")],
|
||||
[pathlib.Path(l10n_dir) / x for x in os.listdir(l10n_dir) if x.endswith(".strings")]
|
||||
),
|
||||
(
|
||||
fdevids_dir,
|
||||
[
|
||||
join(fdevids_dir, "commodity.csv"),
|
||||
join(fdevids_dir, "rare_commodity.csv"),
|
||||
pathlib.Path(fdevids_dir / "commodity.csv"),
|
||||
pathlib.Path(fdevids_dir / "rare_commodity.csv"),
|
||||
],
|
||||
),
|
||||
("plugins", plugins),
|
||||
]
|
||||
# Add all files recursively from license directories
|
||||
for root, dirs, files in os.walk(license_dir):
|
||||
file_list = [os.path.join(root, f) for f in files]
|
||||
dest_dir = os.path.join(license_dir, os.path.relpath(root, license_dir))
|
||||
data_files.append((dest_dir, file_list))
|
||||
|
||||
return data_files
|
||||
|
||||
|
||||
def build() -> None:
|
||||
"""Build EDMarketConnector using Py2Exe."""
|
||||
dist_dir: str = "dist.win32"
|
||||
dist_dir: pathlib.Path = pathlib.Path("dist.win32")
|
||||
gitversion_filename: str = system_check(dist_dir)
|
||||
|
||||
# Constants
|
||||
@ -142,7 +148,7 @@ def build() -> None:
|
||||
}
|
||||
|
||||
# Function to generate DATA_FILES list
|
||||
data_files: list[tuple[str, list[str]]] = generate_data_files(
|
||||
data_files: list[tuple[object, object]] = generate_data_files(
|
||||
appname, gitversion_filename, plugins
|
||||
)
|
||||
|
||||
|
17
collate.py
17
collate.py
@ -17,7 +17,6 @@ import json
|
||||
import os
|
||||
import pathlib
|
||||
import sys
|
||||
from os.path import isfile
|
||||
from traceback import print_exc
|
||||
|
||||
import companion
|
||||
@ -35,7 +34,7 @@ def __make_backup(file_name: pathlib.Path, suffix: str = '.bak') -> None:
|
||||
"""
|
||||
backup_name = file_name.parent / (file_name.name + suffix)
|
||||
|
||||
if isfile(backup_name):
|
||||
if pathlib.Path.is_file(backup_name):
|
||||
os.unlink(backup_name)
|
||||
|
||||
os.rename(file_name, backup_name)
|
||||
@ -52,13 +51,13 @@ def addcommodities(data) -> None: # noqa: CCR001
|
||||
return
|
||||
|
||||
try:
|
||||
commodityfile = pathlib.Path(config.app_dir_path / 'FDevIDs' / 'commodity.csv')
|
||||
commodityfile = config.app_dir_path / 'FDevIDs' / 'commodity.csv'
|
||||
except FileNotFoundError:
|
||||
commodityfile = pathlib.Path('FDevIDs/commodity.csv')
|
||||
commodities = {}
|
||||
|
||||
# slurp existing
|
||||
if isfile(commodityfile):
|
||||
if pathlib.Path.is_file(commodityfile):
|
||||
with open(commodityfile) as csvfile:
|
||||
reader = csv.DictReader(csvfile)
|
||||
for row in reader:
|
||||
@ -86,7 +85,7 @@ def addcommodities(data) -> None: # noqa: CCR001
|
||||
if len(commodities) <= size_pre:
|
||||
return
|
||||
|
||||
if isfile(commodityfile):
|
||||
if pathlib.Path.is_file(commodityfile):
|
||||
__make_backup(commodityfile)
|
||||
|
||||
with open(commodityfile, 'w', newline='\n') as csvfile:
|
||||
@ -109,7 +108,7 @@ def addmodules(data): # noqa: C901, CCR001
|
||||
fields = ('id', 'symbol', 'category', 'name', 'mount', 'guidance', 'ship', 'class', 'rating', 'entitlement')
|
||||
|
||||
# slurp existing
|
||||
if isfile(outfile):
|
||||
if pathlib.Path.is_file(outfile):
|
||||
with open(outfile) as csvfile:
|
||||
reader = csv.DictReader(csvfile, restval='')
|
||||
for row in reader:
|
||||
@ -147,7 +146,7 @@ def addmodules(data): # noqa: C901, CCR001
|
||||
if not len(modules) > size_pre:
|
||||
return
|
||||
|
||||
if isfile(outfile):
|
||||
if pathlib.Path.is_file(outfile):
|
||||
__make_backup(outfile)
|
||||
|
||||
with open(outfile, 'w', newline='\n') as csvfile:
|
||||
@ -170,7 +169,7 @@ def addships(data) -> None: # noqa: CCR001
|
||||
fields = ('id', 'symbol', 'name')
|
||||
|
||||
# slurp existing
|
||||
if isfile(shipfile):
|
||||
if pathlib.Path.is_file(shipfile):
|
||||
with open(shipfile) as csvfile:
|
||||
reader = csv.DictReader(csvfile, restval='')
|
||||
for row in reader:
|
||||
@ -200,7 +199,7 @@ def addships(data) -> None: # noqa: CCR001
|
||||
if not len(ships) > size_pre:
|
||||
return
|
||||
|
||||
if isfile(shipfile):
|
||||
if pathlib.Path.is_file(shipfile):
|
||||
__make_backup(shipfile)
|
||||
|
||||
with open(shipfile, 'w', newline='\n') as csvfile:
|
||||
|
@ -2,7 +2,7 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
import time
|
||||
from os.path import join
|
||||
from pathlib import Path
|
||||
|
||||
from config import config
|
||||
from edmc_data import commodity_bracketmap as bracketmap
|
||||
@ -29,7 +29,7 @@ def export(data, kind=COMMODITY_DEFAULT, filename=None) -> None:
|
||||
filename_time = time.strftime('%Y-%m-%dT%H.%M.%S', time.localtime(querytime))
|
||||
filename_kind = 'csv'
|
||||
filename = f'{filename_system}.{filename_starport}.{filename_time}.{filename_kind}'
|
||||
filename = join(config.get_str('outdir'), filename)
|
||||
filename = Path(config.get_str('outdir')) / filename
|
||||
|
||||
if kind == COMMODITY_CSV:
|
||||
sep = ';' # BUG: for fixing later after cleanup
|
||||
|
@ -27,6 +27,7 @@ import tkinter as tk
|
||||
import urllib.parse
|
||||
import webbrowser
|
||||
from email.utils import parsedate
|
||||
from pathlib import Path
|
||||
from queue import Queue
|
||||
from typing import TYPE_CHECKING, Any, Mapping, TypeVar
|
||||
import requests
|
||||
@ -1135,7 +1136,7 @@ class Session:
|
||||
|
||||
def dump_capi_data(self, data: CAPIData) -> None:
|
||||
"""Dump CAPI data to file for examination."""
|
||||
if os.path.isdir('dump'):
|
||||
if Path('dump').is_dir():
|
||||
file_name: str = ""
|
||||
if data.source_endpoint == self.FRONTIER_CAPI_PATH_FLEETCARRIER:
|
||||
file_name += f"FleetCarrier.{data['name']['callsign']}"
|
||||
@ -1203,7 +1204,7 @@ def fixup(data: CAPIData) -> CAPIData: # noqa: C901, CCR001 # Can't be usefully
|
||||
if not commodity_map:
|
||||
# Lazily populate
|
||||
for f in ('commodity.csv', 'rare_commodity.csv'):
|
||||
if not os.path.isfile(config.app_dir_path / 'FDevIDs/' / f):
|
||||
if not (config.app_dir_path / 'FDevIDs' / f).is_file():
|
||||
logger.warning(f'FDevID file {f} not found! Generating output without these commodity name rewrites.')
|
||||
continue
|
||||
with open(config.app_dir_path / 'FDevIDs' / f, 'r') as csvfile:
|
||||
|
@ -52,7 +52,7 @@ appcmdname = 'EDMC'
|
||||
# <https://semver.org/#semantic-versioning-specification-semver>
|
||||
# Major.Minor.Patch(-prerelease)(+buildmetadata)
|
||||
# NB: Do *not* import this, use the functions appversion() and appversion_nobuild()
|
||||
_static_appversion = '5.11.3'
|
||||
_static_appversion = '5.12.0'
|
||||
_cached_version: semantic_version.Version | None = None
|
||||
copyright = '© 2015-2019 Jonathan Harris, 2020-2024 EDCD'
|
||||
|
||||
@ -189,6 +189,7 @@ class AbstractConfig(abc.ABC):
|
||||
|
||||
app_dir_path: pathlib.Path
|
||||
plugin_dir_path: pathlib.Path
|
||||
default_plugin_dir_path: pathlib.Path
|
||||
internal_plugin_dir_path: pathlib.Path
|
||||
respath_path: pathlib.Path
|
||||
home_path: pathlib.Path
|
||||
@ -279,6 +280,11 @@ class AbstractConfig(abc.ABC):
|
||||
"""Return a string version of plugin_dir."""
|
||||
return str(self.plugin_dir_path)
|
||||
|
||||
@property
|
||||
def default_plugin_dir(self) -> str:
|
||||
"""Return a string version of plugin_dir."""
|
||||
return str(self.default_plugin_dir_path)
|
||||
|
||||
@property
|
||||
def internal_plugin_dir(self) -> str:
|
||||
"""Return a string version of internal_plugin_dir."""
|
||||
|
@ -31,9 +31,7 @@ class LinuxConfig(AbstractConfig):
|
||||
self.app_dir_path = xdg_data_home / appname
|
||||
self.app_dir_path.mkdir(exist_ok=True, parents=True)
|
||||
|
||||
self.plugin_dir_path = self.app_dir_path / 'plugins'
|
||||
self.plugin_dir_path.mkdir(exist_ok=True)
|
||||
|
||||
self.default_plugin_dir_path = self.app_dir_path / 'plugins'
|
||||
self.respath_path = pathlib.Path(__file__).parent.parent
|
||||
|
||||
self.internal_plugin_dir_path = self.respath_path / 'plugins'
|
||||
@ -62,6 +60,12 @@ class LinuxConfig(AbstractConfig):
|
||||
|
||||
self.config.add_section(self.SECTION)
|
||||
|
||||
if (plugdir_str := self.get_str('plugin_dir')) is None or not pathlib.Path(plugdir_str).is_dir():
|
||||
self.set("plugin_dir", str(self.default_plugin_dir_path))
|
||||
plugdir_str = self.default_plugin_dir
|
||||
self.plugin_dir_path = pathlib.Path(plugdir_str)
|
||||
self.plugin_dir_path.mkdir(exist_ok=True)
|
||||
|
||||
if (outdir := self.get_str('outdir')) is None or not pathlib.Path(outdir).is_dir():
|
||||
self.set('outdir', self.home)
|
||||
|
||||
|
@ -7,41 +7,23 @@ See LICENSE file.
|
||||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
import ctypes
|
||||
import functools
|
||||
import pathlib
|
||||
import sys
|
||||
import uuid
|
||||
import winreg
|
||||
from ctypes.wintypes import DWORD, HANDLE
|
||||
from typing import Literal
|
||||
from config import AbstractConfig, applongname, appname, logger
|
||||
from win32comext.shell import shell
|
||||
|
||||
assert sys.platform == 'win32'
|
||||
|
||||
REG_RESERVED_ALWAYS_ZERO = 0
|
||||
|
||||
# This is the only way to do this from python without external deps (which do this anyway).
|
||||
FOLDERID_Documents = uuid.UUID('{FDD39AD0-238F-46AF-ADB4-6C85480369C7}')
|
||||
FOLDERID_LocalAppData = uuid.UUID('{F1B32785-6FBA-4FCF-9D55-7B8E7F157091}')
|
||||
FOLDERID_Profile = uuid.UUID('{5E6C858F-0E22-4760-9AFE-EA3317B67173}')
|
||||
FOLDERID_SavedGames = uuid.UUID('{4C5C32FF-BB9D-43b0-B5B4-2D72E54EAAA4}')
|
||||
|
||||
SHGetKnownFolderPath = ctypes.windll.shell32.SHGetKnownFolderPath
|
||||
SHGetKnownFolderPath.argtypes = [ctypes.c_char_p, DWORD, HANDLE, ctypes.POINTER(ctypes.c_wchar_p)]
|
||||
|
||||
CoTaskMemFree = ctypes.windll.ole32.CoTaskMemFree
|
||||
CoTaskMemFree.argtypes = [ctypes.c_void_p]
|
||||
|
||||
|
||||
def known_folder_path(guid: uuid.UUID) -> str | None:
|
||||
"""Look up a Windows GUID to actual folder path name."""
|
||||
buf = ctypes.c_wchar_p()
|
||||
if SHGetKnownFolderPath(ctypes.create_string_buffer(guid.bytes_le), 0, 0, ctypes.byref(buf)):
|
||||
return None
|
||||
retval = buf.value # copy data
|
||||
CoTaskMemFree(buf) # and free original
|
||||
return retval
|
||||
return shell.SHGetKnownFolderPath(guid, 0, 0)
|
||||
|
||||
|
||||
class WinConfig(AbstractConfig):
|
||||
@ -49,24 +31,6 @@ class WinConfig(AbstractConfig):
|
||||
|
||||
def __init__(self) -> None:
|
||||
super().__init__()
|
||||
self.app_dir_path = pathlib.Path(known_folder_path(FOLDERID_LocalAppData)) / appname # type: ignore
|
||||
self.app_dir_path.mkdir(exist_ok=True)
|
||||
|
||||
self.plugin_dir_path = self.app_dir_path / 'plugins'
|
||||
self.plugin_dir_path.mkdir(exist_ok=True)
|
||||
|
||||
if getattr(sys, 'frozen', False):
|
||||
self.respath_path = pathlib.Path(sys.executable).parent
|
||||
self.internal_plugin_dir_path = self.respath_path / 'plugins'
|
||||
else:
|
||||
self.respath_path = pathlib.Path(__file__).parent.parent
|
||||
self.internal_plugin_dir_path = self.respath_path / 'plugins'
|
||||
|
||||
self.home_path = pathlib.Path.home()
|
||||
|
||||
journal_dir_path = pathlib.Path(
|
||||
known_folder_path(FOLDERID_SavedGames)) / 'Frontier Developments' / 'Elite Dangerous' # type: ignore
|
||||
self.default_journal_dir_path = journal_dir_path if journal_dir_path.is_dir() else None # type: ignore
|
||||
|
||||
REGISTRY_SUBKEY = r'Software\Marginal\EDMarketConnector' # noqa: N806
|
||||
create_key_defaults = functools.partial(
|
||||
@ -82,9 +46,33 @@ class WinConfig(AbstractConfig):
|
||||
logger.exception('Could not create required registry keys')
|
||||
raise
|
||||
|
||||
if local_appdata := known_folder_path(shell.FOLDERID_LocalAppData):
|
||||
self.app_dir_path = pathlib.Path(local_appdata) / appname
|
||||
self.app_dir_path.mkdir(exist_ok=True)
|
||||
|
||||
self.default_plugin_dir_path = self.app_dir_path / 'plugins'
|
||||
if (plugdir_str := self.get_str('plugin_dir')) is None or not pathlib.Path(plugdir_str).is_dir():
|
||||
self.set("plugin_dir", str(self.default_plugin_dir_path))
|
||||
plugdir_str = self.default_plugin_dir
|
||||
self.plugin_dir_path = pathlib.Path(plugdir_str)
|
||||
self.plugin_dir_path.mkdir(exist_ok=True)
|
||||
|
||||
if getattr(sys, 'frozen', False):
|
||||
self.respath_path = pathlib.Path(sys.executable).parent
|
||||
self.internal_plugin_dir_path = self.respath_path / 'plugins'
|
||||
else:
|
||||
self.respath_path = pathlib.Path(__file__).parent.parent
|
||||
self.internal_plugin_dir_path = self.respath_path / 'plugins'
|
||||
|
||||
self.home_path = pathlib.Path.home()
|
||||
|
||||
journal_dir_path = pathlib.Path(
|
||||
known_folder_path(shell.FOLDERID_SavedGames)) / 'Frontier Developments' / 'Elite Dangerous' # type: ignore
|
||||
self.default_journal_dir_path = journal_dir_path if journal_dir_path.is_dir() else None # type: ignore
|
||||
|
||||
self.identifier = applongname
|
||||
if (outdir_str := self.get_str('outdir')) is None or not pathlib.Path(outdir_str).is_dir():
|
||||
docs = known_folder_path(FOLDERID_Documents)
|
||||
docs = known_folder_path(shell.FOLDERID_Documents)
|
||||
self.set("outdir", docs if docs is not None else self.home)
|
||||
|
||||
def __get_regentry(self, key: str) -> None | list | str | int:
|
||||
|
@ -51,7 +51,8 @@ if __name__ == "__main__":
|
||||
for m in list(data['Ships'].values()):
|
||||
name = coriolis_ship_map.get(m['properties']['name'], str(m['properties']['name']))
|
||||
assert name in reverse_ship_map, name
|
||||
ships[name] = {'hullMass': m['properties']['hullMass']}
|
||||
ships[name] = {'hullMass': m['properties']['hullMass'],
|
||||
'reserveFuelCapacity': m['properties']['reserveFuelCapacity']}
|
||||
for i, bulkhead in enumerate(bulkheads):
|
||||
modules['_'.join([reverse_ship_map[name], 'armour', bulkhead])] = {'mass': m['bulkheads'][i]['mass']}
|
||||
|
||||
|
@ -12,7 +12,7 @@ import sys
|
||||
import time
|
||||
import tkinter as tk
|
||||
from calendar import timegm
|
||||
from os.path import getsize, isdir, isfile, join
|
||||
from pathlib import Path
|
||||
from typing import Any, cast
|
||||
from watchdog.observers.api import BaseObserver
|
||||
from config import config
|
||||
@ -57,7 +57,7 @@ class Dashboard(FileSystemEventHandler):
|
||||
|
||||
logdir = config.get_str('journaldir', default=config.default_journal_dir)
|
||||
logdir = logdir or config.default_journal_dir
|
||||
if not isdir(logdir):
|
||||
if not Path.is_dir(Path(logdir)):
|
||||
logger.info(f"No logdir, or it isn't a directory: {logdir=}")
|
||||
self.stop()
|
||||
return False
|
||||
@ -164,7 +164,8 @@ class Dashboard(FileSystemEventHandler):
|
||||
|
||||
:param event: Watchdog event.
|
||||
"""
|
||||
if event.is_directory or (isfile(event.src_path) and getsize(event.src_path)):
|
||||
modpath = Path(event.src_path)
|
||||
if event.is_directory or (modpath.is_file() and modpath.stat().st_size):
|
||||
# Can get on_modified events when the file is emptied
|
||||
self.process(event.src_path if not event.is_directory else None)
|
||||
|
||||
@ -177,7 +178,7 @@ class Dashboard(FileSystemEventHandler):
|
||||
if config.shutting_down:
|
||||
return
|
||||
try:
|
||||
status_json_path = join(self.currentdir, 'Status.json')
|
||||
status_json_path = Path(self.currentdir) / 'Status.json'
|
||||
with open(status_json_path, 'rb') as h:
|
||||
data = h.read().strip()
|
||||
if data: # Can be empty if polling while the file is being re-written
|
||||
|
@ -4,21 +4,19 @@ from __future__ import annotations
|
||||
import gzip
|
||||
import json
|
||||
import pathlib
|
||||
import tempfile
|
||||
import threading
|
||||
import zlib
|
||||
from http import server
|
||||
from typing import Any, Callable, Literal
|
||||
from urllib.parse import parse_qs
|
||||
|
||||
from config import appname
|
||||
import config
|
||||
from EDMCLogging import get_main_logger
|
||||
|
||||
logger = get_main_logger()
|
||||
|
||||
output_lock = threading.Lock()
|
||||
output_data_path = pathlib.Path(tempfile.gettempdir()) / f'{appname}' / 'http_debug'
|
||||
SAFE_TRANSLATE = str.maketrans({x: '_' for x in "!@#$%^&*()./\\\r\n[]-+='\";:?<>,~`"})
|
||||
output_data_path = pathlib.Path(config.app_dir_path / 'logs' / 'http_debug')
|
||||
SAFE_TRANSLATE = str.maketrans(dict.fromkeys("!@#$%^&*()./\\\r\n[]-+='\";:?<>,~`", '_'))
|
||||
|
||||
|
||||
class LoggingHandler(server.BaseHTTPRequestHandler):
|
||||
|
@ -1,28 +0,0 @@
|
||||
Copyright (c) .NET Foundation and contributors.
|
||||
This software is released under the Microsoft Reciprocal License (MS-RL) (the "License"); you may not use the software except in compliance with the License.
|
||||
|
||||
The text of the Microsoft Reciprocal License (MS-RL) can be found online at:
|
||||
http://opensource.org/licenses/ms-rl
|
||||
|
||||
|
||||
Microsoft Reciprocal License (MS-RL)
|
||||
|
||||
This license governs use of the accompanying software. If you use the software, you accept this license. If you do not accept the license, do not use the software.
|
||||
|
||||
1. Definitions
|
||||
The terms "reproduce," "reproduction," "derivative works," and "distribution" have the same meaning here as under U.S. copyright law.
|
||||
A "contribution" is the original software, or any additions or changes to the software.
|
||||
A "contributor" is any person that distributes its contribution under this license.
|
||||
"Licensed patents" are a contributor's patent claims that read directly on its contribution.
|
||||
|
||||
2. Grant of Rights
|
||||
(A) Copyright Grant- Subject to the terms of this license, including the license conditions and limitations in section 3, each contributor grants you a non-exclusive, worldwide, royalty-free copyright license to reproduce its contribution, prepare derivative works of its contribution, and distribute its contribution or any derivative works that you create.
|
||||
(B) Patent Grant- Subject to the terms of this license, including the license conditions and limitations in section 3, each contributor grants you a non-exclusive, worldwide, royalty-free license under its licensed patents to make, have made, use, sell, offer for sale, import, and/or otherwise dispose of its contribution in the software or derivative works of the contribution in the software.
|
||||
|
||||
3. Conditions and Limitations
|
||||
(A) Reciprocal Grants- For any file you distribute that contains code from the software (in source code or binary format), you must provide recipients the source code to that file along with a copy of this license, which license will govern that file. You may license other files that are entirely your own work and do not contain code from the software under any terms you choose.
|
||||
(B) No Trademark License- This license does not grant you rights to use any contributors' name, logo, or trademarks.
|
||||
(C) If you bring a patent claim against any contributor over patents that you claim are infringed by the software, your patent license from such contributor to the software ends automatically.
|
||||
(D) If you distribute any portion of the software, you must retain all copyright, patent, trademark, and attribution notices that are present in the software.
|
||||
(E) If you distribute any portion of the software in source code form, you may do so only under this license by including a complete copy of this license with your distribution. If you distribute any portion of the software in compiled or object code form, you may only do so under a license that complies with this license.
|
||||
(F) The software is licensed "as-is." You bear the risk of using it. The contributors give no express warranties, guarantees or conditions. You may have additional consumer rights under your local laws which this license cannot change. To the extent permitted under your local laws, the contributors exclude the implied warranties of merchantability, fitness for a particular purpose and non-infringement.
|
30
docs/Licenses/python-packages/pillow/LICENSE
Normal file
30
docs/Licenses/python-packages/pillow/LICENSE
Normal file
@ -0,0 +1,30 @@
|
||||
The Python Imaging Library (PIL) is
|
||||
|
||||
Copyright © 1997-2011 by Secret Labs AB
|
||||
Copyright © 1995-2011 by Fredrik Lundh and contributors
|
||||
|
||||
Pillow is the friendly PIL fork. It is
|
||||
|
||||
Copyright © 2010-2024 by Jeffrey A. Clark and contributors
|
||||
|
||||
Like PIL, Pillow is licensed under the open source HPND License:
|
||||
|
||||
By obtaining, using, and/or copying this software and/or its associated
|
||||
documentation, you agree that you have read, understood, and will comply
|
||||
with the following terms and conditions:
|
||||
|
||||
Permission to use, copy, modify and distribute this software and its
|
||||
documentation for any purpose and without fee is hereby granted,
|
||||
provided that the above copyright notice appears in all copies, and that
|
||||
both that copyright notice and this permission notice appear in supporting
|
||||
documentation, and that the name of Secret Labs AB or the author not be
|
||||
used in advertising or publicity pertaining to distribution of the software
|
||||
without specific, written prior permission.
|
||||
|
||||
SECRET LABS AB AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS
|
||||
SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS.
|
||||
IN NO EVENT SHALL SECRET LABS AB OR THE AUTHOR BE LIABLE FOR ANY SPECIAL,
|
||||
INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
|
||||
LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
|
||||
OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
|
||||
PERFORMANCE OF THIS SOFTWARE.
|
29
docs/Licenses/python-packages/psutil/LICENSE
Normal file
29
docs/Licenses/python-packages/psutil/LICENSE
Normal file
@ -0,0 +1,29 @@
|
||||
BSD 3-Clause License
|
||||
|
||||
Copyright (c) 2009, Jay Loden, Dave Daeschler, Giampaolo Rodola
|
||||
All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without modification,
|
||||
are permitted provided that the following conditions are met:
|
||||
|
||||
* Redistributions of source code must retain the above copyright notice, this
|
||||
list of conditions and the following disclaimer.
|
||||
|
||||
* Redistributions in binary form must reproduce the above copyright notice,
|
||||
this list of conditions and the following disclaimer in the documentation
|
||||
and/or other materials provided with the distribution.
|
||||
|
||||
* Neither the name of the psutil authors nor the names of its contributors
|
||||
may be used to endorse or promote products derived from this software without
|
||||
specific prior written permission.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
|
||||
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
||||
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
|
||||
ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
||||
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
||||
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
|
||||
ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
396
docs/Licenses/python-packages/simplesystray/legalcode.txt
Normal file
396
docs/Licenses/python-packages/simplesystray/legalcode.txt
Normal file
@ -0,0 +1,396 @@
|
||||
Attribution 4.0 International
|
||||
|
||||
=======================================================================
|
||||
|
||||
Creative Commons Corporation ("Creative Commons") is not a law firm and
|
||||
does not provide legal services or legal advice. Distribution of
|
||||
Creative Commons public licenses does not create a lawyer-client or
|
||||
other relationship. Creative Commons makes its licenses and related
|
||||
information available on an "as-is" basis. Creative Commons gives no
|
||||
warranties regarding its licenses, any material licensed under their
|
||||
terms and conditions, or any related information. Creative Commons
|
||||
disclaims all liability for damages resulting from their use to the
|
||||
fullest extent possible.
|
||||
|
||||
Using Creative Commons Public Licenses
|
||||
|
||||
Creative Commons public licenses provide a standard set of terms and
|
||||
conditions that creators and other rights holders may use to share
|
||||
original works of authorship and other material subject to copyright
|
||||
and certain other rights specified in the public license below. The
|
||||
following considerations are for informational purposes only, are not
|
||||
exhaustive, and do not form part of our licenses.
|
||||
|
||||
Considerations for licensors: Our public licenses are
|
||||
intended for use by those authorized to give the public
|
||||
permission to use material in ways otherwise restricted by
|
||||
copyright and certain other rights. Our licenses are
|
||||
irrevocable. Licensors should read and understand the terms
|
||||
and conditions of the license they choose before applying it.
|
||||
Licensors should also secure all rights necessary before
|
||||
applying our licenses so that the public can reuse the
|
||||
material as expected. Licensors should clearly mark any
|
||||
material not subject to the license. This includes other CC-
|
||||
licensed material, or material used under an exception or
|
||||
limitation to copyright. More considerations for licensors:
|
||||
wiki.creativecommons.org/Considerations_for_licensors
|
||||
|
||||
Considerations for the public: By using one of our public
|
||||
licenses, a licensor grants the public permission to use the
|
||||
licensed material under specified terms and conditions. If
|
||||
the licensor's permission is not necessary for any reason--for
|
||||
example, because of any applicable exception or limitation to
|
||||
copyright--then that use is not regulated by the license. Our
|
||||
licenses grant only permissions under copyright and certain
|
||||
other rights that a licensor has authority to grant. Use of
|
||||
the licensed material may still be restricted for other
|
||||
reasons, including because others have copyright or other
|
||||
rights in the material. A licensor may make special requests,
|
||||
such as asking that all changes be marked or described.
|
||||
Although not required by our licenses, you are encouraged to
|
||||
respect those requests where reasonable. More considerations
|
||||
for the public:
|
||||
wiki.creativecommons.org/Considerations_for_licensees
|
||||
|
||||
=======================================================================
|
||||
|
||||
Creative Commons Attribution 4.0 International Public License
|
||||
|
||||
By exercising the Licensed Rights (defined below), You accept and agree
|
||||
to be bound by the terms and conditions of this Creative Commons
|
||||
Attribution 4.0 International Public License ("Public License"). To the
|
||||
extent this Public License may be interpreted as a contract, You are
|
||||
granted the Licensed Rights in consideration of Your acceptance of
|
||||
these terms and conditions, and the Licensor grants You such rights in
|
||||
consideration of benefits the Licensor receives from making the
|
||||
Licensed Material available under these terms and conditions.
|
||||
|
||||
|
||||
Section 1 -- Definitions.
|
||||
|
||||
a. Adapted Material means material subject to Copyright and Similar
|
||||
Rights that is derived from or based upon the Licensed Material
|
||||
and in which the Licensed Material is translated, altered,
|
||||
arranged, transformed, or otherwise modified in a manner requiring
|
||||
permission under the Copyright and Similar Rights held by the
|
||||
Licensor. For purposes of this Public License, where the Licensed
|
||||
Material is a musical work, performance, or sound recording,
|
||||
Adapted Material is always produced where the Licensed Material is
|
||||
synched in timed relation with a moving image.
|
||||
|
||||
b. Adapter's License means the license You apply to Your Copyright
|
||||
and Similar Rights in Your contributions to Adapted Material in
|
||||
accordance with the terms and conditions of this Public License.
|
||||
|
||||
c. Copyright and Similar Rights means copyright and/or similar rights
|
||||
closely related to copyright including, without limitation,
|
||||
performance, broadcast, sound recording, and Sui Generis Database
|
||||
Rights, without regard to how the rights are labeled or
|
||||
categorized. For purposes of this Public License, the rights
|
||||
specified in Section 2(b)(1)-(2) are not Copyright and Similar
|
||||
Rights.
|
||||
|
||||
d. Effective Technological Measures means those measures that, in the
|
||||
absence of proper authority, may not be circumvented under laws
|
||||
fulfilling obligations under Article 11 of the WIPO Copyright
|
||||
Treaty adopted on December 20, 1996, and/or similar international
|
||||
agreements.
|
||||
|
||||
e. Exceptions and Limitations means fair use, fair dealing, and/or
|
||||
any other exception or limitation to Copyright and Similar Rights
|
||||
that applies to Your use of the Licensed Material.
|
||||
|
||||
f. Licensed Material means the artistic or literary work, database,
|
||||
or other material to which the Licensor applied this Public
|
||||
License.
|
||||
|
||||
g. Licensed Rights means the rights granted to You subject to the
|
||||
terms and conditions of this Public License, which are limited to
|
||||
all Copyright and Similar Rights that apply to Your use of the
|
||||
Licensed Material and that the Licensor has authority to license.
|
||||
|
||||
h. Licensor means the individual(s) or entity(ies) granting rights
|
||||
under this Public License.
|
||||
|
||||
i. Share means to provide material to the public by any means or
|
||||
process that requires permission under the Licensed Rights, such
|
||||
as reproduction, public display, public performance, distribution,
|
||||
dissemination, communication, or importation, and to make material
|
||||
available to the public including in ways that members of the
|
||||
public may access the material from a place and at a time
|
||||
individually chosen by them.
|
||||
|
||||
j. Sui Generis Database Rights means rights other than copyright
|
||||
resulting from Directive 96/9/EC of the European Parliament and of
|
||||
the Council of 11 March 1996 on the legal protection of databases,
|
||||
as amended and/or succeeded, as well as other essentially
|
||||
equivalent rights anywhere in the world.
|
||||
|
||||
k. You means the individual or entity exercising the Licensed Rights
|
||||
under this Public License. Your has a corresponding meaning.
|
||||
|
||||
|
||||
Section 2 -- Scope.
|
||||
|
||||
a. License grant.
|
||||
|
||||
1. Subject to the terms and conditions of this Public License,
|
||||
the Licensor hereby grants You a worldwide, royalty-free,
|
||||
non-sublicensable, non-exclusive, irrevocable license to
|
||||
exercise the Licensed Rights in the Licensed Material to:
|
||||
|
||||
a. reproduce and Share the Licensed Material, in whole or
|
||||
in part; and
|
||||
|
||||
b. produce, reproduce, and Share Adapted Material.
|
||||
|
||||
2. Exceptions and Limitations. For the avoidance of doubt, where
|
||||
Exceptions and Limitations apply to Your use, this Public
|
||||
License does not apply, and You do not need to comply with
|
||||
its terms and conditions.
|
||||
|
||||
3. Term. The term of this Public License is specified in Section
|
||||
6(a).
|
||||
|
||||
4. Media and formats; technical modifications allowed. The
|
||||
Licensor authorizes You to exercise the Licensed Rights in
|
||||
all media and formats whether now known or hereafter created,
|
||||
and to make technical modifications necessary to do so. The
|
||||
Licensor waives and/or agrees not to assert any right or
|
||||
authority to forbid You from making technical modifications
|
||||
necessary to exercise the Licensed Rights, including
|
||||
technical modifications necessary to circumvent Effective
|
||||
Technological Measures. For purposes of this Public License,
|
||||
simply making modifications authorized by this Section 2(a)
|
||||
(4) never produces Adapted Material.
|
||||
|
||||
5. Downstream recipients.
|
||||
|
||||
a. Offer from the Licensor -- Licensed Material. Every
|
||||
recipient of the Licensed Material automatically
|
||||
receives an offer from the Licensor to exercise the
|
||||
Licensed Rights under the terms and conditions of this
|
||||
Public License.
|
||||
|
||||
b. No downstream restrictions. You may not offer or impose
|
||||
any additional or different terms or conditions on, or
|
||||
apply any Effective Technological Measures to, the
|
||||
Licensed Material if doing so restricts exercise of the
|
||||
Licensed Rights by any recipient of the Licensed
|
||||
Material.
|
||||
|
||||
6. No endorsement. Nothing in this Public License constitutes or
|
||||
may be construed as permission to assert or imply that You
|
||||
are, or that Your use of the Licensed Material is, connected
|
||||
with, or sponsored, endorsed, or granted official status by,
|
||||
the Licensor or others designated to receive attribution as
|
||||
provided in Section 3(a)(1)(A)(i).
|
||||
|
||||
b. Other rights.
|
||||
|
||||
1. Moral rights, such as the right of integrity, are not
|
||||
licensed under this Public License, nor are publicity,
|
||||
privacy, and/or other similar personality rights; however, to
|
||||
the extent possible, the Licensor waives and/or agrees not to
|
||||
assert any such rights held by the Licensor to the limited
|
||||
extent necessary to allow You to exercise the Licensed
|
||||
Rights, but not otherwise.
|
||||
|
||||
2. Patent and trademark rights are not licensed under this
|
||||
Public License.
|
||||
|
||||
3. To the extent possible, the Licensor waives any right to
|
||||
collect royalties from You for the exercise of the Licensed
|
||||
Rights, whether directly or through a collecting society
|
||||
under any voluntary or waivable statutory or compulsory
|
||||
licensing scheme. In all other cases the Licensor expressly
|
||||
reserves any right to collect such royalties.
|
||||
|
||||
|
||||
Section 3 -- License Conditions.
|
||||
|
||||
Your exercise of the Licensed Rights is expressly made subject to the
|
||||
following conditions.
|
||||
|
||||
a. Attribution.
|
||||
|
||||
1. If You Share the Licensed Material (including in modified
|
||||
form), You must:
|
||||
|
||||
a. retain the following if it is supplied by the Licensor
|
||||
with the Licensed Material:
|
||||
|
||||
i. identification of the creator(s) of the Licensed
|
||||
Material and any others designated to receive
|
||||
attribution, in any reasonable manner requested by
|
||||
the Licensor (including by pseudonym if
|
||||
designated);
|
||||
|
||||
ii. a copyright notice;
|
||||
|
||||
iii. a notice that refers to this Public License;
|
||||
|
||||
iv. a notice that refers to the disclaimer of
|
||||
warranties;
|
||||
|
||||
v. a URI or hyperlink to the Licensed Material to the
|
||||
extent reasonably practicable;
|
||||
|
||||
b. indicate if You modified the Licensed Material and
|
||||
retain an indication of any previous modifications; and
|
||||
|
||||
c. indicate the Licensed Material is licensed under this
|
||||
Public License, and include the text of, or the URI or
|
||||
hyperlink to, this Public License.
|
||||
|
||||
2. You may satisfy the conditions in Section 3(a)(1) in any
|
||||
reasonable manner based on the medium, means, and context in
|
||||
which You Share the Licensed Material. For example, it may be
|
||||
reasonable to satisfy the conditions by providing a URI or
|
||||
hyperlink to a resource that includes the required
|
||||
information.
|
||||
|
||||
3. If requested by the Licensor, You must remove any of the
|
||||
information required by Section 3(a)(1)(A) to the extent
|
||||
reasonably practicable.
|
||||
|
||||
4. If You Share Adapted Material You produce, the Adapter's
|
||||
License You apply must not prevent recipients of the Adapted
|
||||
Material from complying with this Public License.
|
||||
|
||||
|
||||
Section 4 -- Sui Generis Database Rights.
|
||||
|
||||
Where the Licensed Rights include Sui Generis Database Rights that
|
||||
apply to Your use of the Licensed Material:
|
||||
|
||||
a. for the avoidance of doubt, Section 2(a)(1) grants You the right
|
||||
to extract, reuse, reproduce, and Share all or a substantial
|
||||
portion of the contents of the database;
|
||||
|
||||
b. if You include all or a substantial portion of the database
|
||||
contents in a database in which You have Sui Generis Database
|
||||
Rights, then the database in which You have Sui Generis Database
|
||||
Rights (but not its individual contents) is Adapted Material; and
|
||||
|
||||
c. You must comply with the conditions in Section 3(a) if You Share
|
||||
all or a substantial portion of the contents of the database.
|
||||
|
||||
For the avoidance of doubt, this Section 4 supplements and does not
|
||||
replace Your obligations under this Public License where the Licensed
|
||||
Rights include other Copyright and Similar Rights.
|
||||
|
||||
|
||||
Section 5 -- Disclaimer of Warranties and Limitation of Liability.
|
||||
|
||||
a. UNLESS OTHERWISE SEPARATELY UNDERTAKEN BY THE LICENSOR, TO THE
|
||||
EXTENT POSSIBLE, THE LICENSOR OFFERS THE LICENSED MATERIAL AS-IS
|
||||
AND AS-AVAILABLE, AND MAKES NO REPRESENTATIONS OR WARRANTIES OF
|
||||
ANY KIND CONCERNING THE LICENSED MATERIAL, WHETHER EXPRESS,
|
||||
IMPLIED, STATUTORY, OR OTHER. THIS INCLUDES, WITHOUT LIMITATION,
|
||||
WARRANTIES OF TITLE, MERCHANTABILITY, FITNESS FOR A PARTICULAR
|
||||
PURPOSE, NON-INFRINGEMENT, ABSENCE OF LATENT OR OTHER DEFECTS,
|
||||
ACCURACY, OR THE PRESENCE OR ABSENCE OF ERRORS, WHETHER OR NOT
|
||||
KNOWN OR DISCOVERABLE. WHERE DISCLAIMERS OF WARRANTIES ARE NOT
|
||||
ALLOWED IN FULL OR IN PART, THIS DISCLAIMER MAY NOT APPLY TO YOU.
|
||||
|
||||
b. TO THE EXTENT POSSIBLE, IN NO EVENT WILL THE LICENSOR BE LIABLE
|
||||
TO YOU ON ANY LEGAL THEORY (INCLUDING, WITHOUT LIMITATION,
|
||||
NEGLIGENCE) OR OTHERWISE FOR ANY DIRECT, SPECIAL, INDIRECT,
|
||||
INCIDENTAL, CONSEQUENTIAL, PUNITIVE, EXEMPLARY, OR OTHER LOSSES,
|
||||
COSTS, EXPENSES, OR DAMAGES ARISING OUT OF THIS PUBLIC LICENSE OR
|
||||
USE OF THE LICENSED MATERIAL, EVEN IF THE LICENSOR HAS BEEN
|
||||
ADVISED OF THE POSSIBILITY OF SUCH LOSSES, COSTS, EXPENSES, OR
|
||||
DAMAGES. WHERE A LIMITATION OF LIABILITY IS NOT ALLOWED IN FULL OR
|
||||
IN PART, THIS LIMITATION MAY NOT APPLY TO YOU.
|
||||
|
||||
c. The disclaimer of warranties and limitation of liability provided
|
||||
above shall be interpreted in a manner that, to the extent
|
||||
possible, most closely approximates an absolute disclaimer and
|
||||
waiver of all liability.
|
||||
|
||||
|
||||
Section 6 -- Term and Termination.
|
||||
|
||||
a. This Public License applies for the term of the Copyright and
|
||||
Similar Rights licensed here. However, if You fail to comply with
|
||||
this Public License, then Your rights under this Public License
|
||||
terminate automatically.
|
||||
|
||||
b. Where Your right to use the Licensed Material has terminated under
|
||||
Section 6(a), it reinstates:
|
||||
|
||||
1. automatically as of the date the violation is cured, provided
|
||||
it is cured within 30 days of Your discovery of the
|
||||
violation; or
|
||||
|
||||
2. upon express reinstatement by the Licensor.
|
||||
|
||||
For the avoidance of doubt, this Section 6(b) does not affect any
|
||||
right the Licensor may have to seek remedies for Your violations
|
||||
of this Public License.
|
||||
|
||||
c. For the avoidance of doubt, the Licensor may also offer the
|
||||
Licensed Material under separate terms or conditions or stop
|
||||
distributing the Licensed Material at any time; however, doing so
|
||||
will not terminate this Public License.
|
||||
|
||||
d. Sections 1, 5, 6, 7, and 8 survive termination of this Public
|
||||
License.
|
||||
|
||||
|
||||
Section 7 -- Other Terms and Conditions.
|
||||
|
||||
a. The Licensor shall not be bound by any additional or different
|
||||
terms or conditions communicated by You unless expressly agreed.
|
||||
|
||||
b. Any arrangements, understandings, or agreements regarding the
|
||||
Licensed Material not stated herein are separate from and
|
||||
independent of the terms and conditions of this Public License.
|
||||
|
||||
|
||||
Section 8 -- Interpretation.
|
||||
|
||||
a. For the avoidance of doubt, this Public License does not, and
|
||||
shall not be interpreted to, reduce, limit, restrict, or impose
|
||||
conditions on any use of the Licensed Material that could lawfully
|
||||
be made without permission under this Public License.
|
||||
|
||||
b. To the extent possible, if any provision of this Public License is
|
||||
deemed unenforceable, it shall be automatically reformed to the
|
||||
minimum extent necessary to make it enforceable. If the provision
|
||||
cannot be reformed, it shall be severed from this Public License
|
||||
without affecting the enforceability of the remaining terms and
|
||||
conditions.
|
||||
|
||||
c. No term or condition of this Public License will be waived and no
|
||||
failure to comply consented to unless expressly agreed to by the
|
||||
Licensor.
|
||||
|
||||
d. Nothing in this Public License constitutes or may be interpreted
|
||||
as a limitation upon, or waiver of, any privileges and immunities
|
||||
that apply to the Licensor or You, including from the legal
|
||||
processes of any jurisdiction or authority.
|
||||
|
||||
|
||||
=======================================================================
|
||||
|
||||
Creative Commons is not a party to its public
|
||||
licenses. Notwithstanding, Creative Commons may elect to apply one of
|
||||
its public licenses to material it publishes and in those instances
|
||||
will be considered the “Licensor.” The text of the Creative Commons
|
||||
public licenses is dedicated to the public domain under the CC0 Public
|
||||
Domain Dedication. Except for the limited purpose of indicating that
|
||||
material is shared under a Creative Commons public license or as
|
||||
otherwise permitted by the Creative Commons policies published at
|
||||
creativecommons.org/policies, Creative Commons does not authorize the
|
||||
use of the trademark "Creative Commons" or any other trademark or logo
|
||||
of Creative Commons without its prior written consent including,
|
||||
without limitation, in connection with any unauthorized modifications
|
||||
to any of its public licenses or any other arrangements,
|
||||
understandings, or agreements concerning use of licensed material. For
|
||||
the avoidance of doubt, this paragraph does not form part of the
|
||||
public licenses.
|
||||
|
||||
Creative Commons may be contacted at creativecommons.org.
|
||||
|
@ -3,10 +3,10 @@
|
||||
"""Plugin that tests that modules we bundle for plugins are present and working."""
|
||||
|
||||
import logging
|
||||
import os
|
||||
import shutil
|
||||
import sqlite3
|
||||
import zipfile
|
||||
from pathlib import Path
|
||||
|
||||
import semantic_version
|
||||
from SubA import SubA
|
||||
@ -14,7 +14,7 @@ from SubA import SubA
|
||||
from config import appname, appversion, config
|
||||
|
||||
# This could also be returned from plugin_start3()
|
||||
plugin_name = os.path.basename(os.path.dirname(__file__))
|
||||
plugin_name = Path(__file__).resolve().parent.name
|
||||
|
||||
# Logger per found plugin, so the folder name is included in
|
||||
# the logging format.
|
||||
@ -49,17 +49,17 @@ class PluginTest:
|
||||
|
||||
def __init__(self, directory: str):
|
||||
logger.debug(f'directory = "{directory}')
|
||||
dbfile = os.path.join(directory, this.DBFILE)
|
||||
dbfile = Path(directory) / this.DBFILE
|
||||
|
||||
# Test 'import zipfile'
|
||||
with zipfile.ZipFile(dbfile + '.zip', 'w') as zip:
|
||||
if os.path.exists(dbfile):
|
||||
with zipfile.ZipFile(str(dbfile) + '.zip', 'w') as zip:
|
||||
if dbfile.exists():
|
||||
zip.write(dbfile)
|
||||
zip.close()
|
||||
|
||||
# Testing 'import shutil'
|
||||
if os.path.exists(dbfile):
|
||||
shutil.copyfile(dbfile, dbfile + '.bak')
|
||||
if dbfile.exists():
|
||||
shutil.copyfile(dbfile, str(dbfile) + '.bak')
|
||||
|
||||
# Testing 'import sqlite3'
|
||||
self.sqlconn = sqlite3.connect(dbfile)
|
||||
|
@ -507,6 +507,7 @@ ship_name_map = {
|
||||
'testbuggy': 'Scarab',
|
||||
'type6': 'Type-6 Transporter',
|
||||
'type7': 'Type-7 Transporter',
|
||||
'type8': 'Type-8 Transporter',
|
||||
'type9': 'Type-9 Heavy',
|
||||
'type9_military': 'Type-10 Defender',
|
||||
'typex': 'Alliance Chieftain',
|
||||
|
@ -8,7 +8,11 @@ import sys
|
||||
import threading
|
||||
import tkinter as tk
|
||||
import winsound
|
||||
from ctypes.wintypes import DWORD, HWND, LONG, LPWSTR, MSG, ULONG, WORD
|
||||
from ctypes.wintypes import DWORD, LONG, MSG, ULONG, WORD, HWND, BOOL, UINT
|
||||
import pywintypes
|
||||
import win32api
|
||||
import win32gui
|
||||
import win32con
|
||||
from config import config
|
||||
from EDMCLogging import get_main_logger
|
||||
from hotkey import AbstractHotkeyMgr
|
||||
@ -17,53 +21,22 @@ assert sys.platform == 'win32'
|
||||
|
||||
logger = get_main_logger()
|
||||
|
||||
RegisterHotKey = ctypes.windll.user32.RegisterHotKey
|
||||
UnregisterHotKey = ctypes.windll.user32.UnregisterHotKey
|
||||
MOD_ALT = 0x0001
|
||||
MOD_CONTROL = 0x0002
|
||||
MOD_SHIFT = 0x0004
|
||||
MOD_WIN = 0x0008
|
||||
UnregisterHotKey = ctypes.windll.user32.UnregisterHotKey # TODO: Coming Soon
|
||||
UnregisterHotKey.argtypes = [HWND, ctypes.c_int]
|
||||
UnregisterHotKey.restype = BOOL
|
||||
|
||||
MOD_NOREPEAT = 0x4000
|
||||
|
||||
GetMessage = ctypes.windll.user32.GetMessageW
|
||||
TranslateMessage = ctypes.windll.user32.TranslateMessage
|
||||
DispatchMessage = ctypes.windll.user32.DispatchMessageW
|
||||
PostThreadMessage = ctypes.windll.user32.PostThreadMessageW
|
||||
WM_QUIT = 0x0012
|
||||
WM_HOTKEY = 0x0312
|
||||
WM_APP = 0x8000
|
||||
WM_SND_GOOD = WM_APP + 1
|
||||
WM_SND_BAD = WM_APP + 2
|
||||
|
||||
GetKeyState = ctypes.windll.user32.GetKeyState
|
||||
MapVirtualKey = ctypes.windll.user32.MapVirtualKeyW
|
||||
VK_BACK = 0x08
|
||||
VK_CLEAR = 0x0c
|
||||
VK_RETURN = 0x0d
|
||||
VK_SHIFT = 0x10
|
||||
VK_CONTROL = 0x11
|
||||
VK_MENU = 0x12
|
||||
VK_CAPITAL = 0x14
|
||||
VK_MODECHANGE = 0x1f
|
||||
VK_ESCAPE = 0x1b
|
||||
VK_SPACE = 0x20
|
||||
VK_DELETE = 0x2e
|
||||
VK_LWIN = 0x5b
|
||||
VK_RWIN = 0x5c
|
||||
VK_NUMPAD0 = 0x60
|
||||
VK_DIVIDE = 0x6f
|
||||
VK_F1 = 0x70
|
||||
VK_F24 = 0x87
|
||||
WM_SND_GOOD = win32con.WM_APP + 1
|
||||
WM_SND_BAD = win32con.WM_APP + 2
|
||||
VK_OEM_MINUS = 0xbd
|
||||
VK_NUMLOCK = 0x90
|
||||
VK_SCROLL = 0x91
|
||||
VK_PROCESSKEY = 0xe5
|
||||
VK_OEM_CLEAR = 0xfe
|
||||
|
||||
GetForegroundWindow = ctypes.windll.user32.GetForegroundWindow
|
||||
GetWindowText = ctypes.windll.user32.GetWindowTextW
|
||||
GetWindowText.argtypes = [HWND, LPWSTR, ctypes.c_int]
|
||||
GetWindowTextLength = ctypes.windll.user32.GetWindowTextLengthW
|
||||
# VirtualKey mapping values
|
||||
# <https://learn.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-mapvirtualkeyexa>
|
||||
MAPVK_VK_TO_VSC = 0
|
||||
MAPVK_VSC_TO_VK = 1
|
||||
MAPVK_VK_TO_CHAR = 2
|
||||
MAPVK_VSC_TO_VK_EX = 3
|
||||
MAPVK_VK_TO_VSC_EX = 4
|
||||
|
||||
|
||||
def window_title(h) -> str:
|
||||
@ -74,11 +47,7 @@ def window_title(h) -> str:
|
||||
:return: Window title.
|
||||
"""
|
||||
if h:
|
||||
title_length = GetWindowTextLength(h) + 1
|
||||
buf = ctypes.create_unicode_buffer(title_length)
|
||||
if GetWindowText(h, buf, title_length):
|
||||
return buf.value
|
||||
|
||||
return win32gui.GetWindowText(h)
|
||||
return ''
|
||||
|
||||
|
||||
@ -138,6 +107,7 @@ class INPUT(ctypes.Structure):
|
||||
|
||||
SendInput = ctypes.windll.user32.SendInput
|
||||
SendInput.argtypes = [ctypes.c_uint, ctypes.POINTER(INPUT), ctypes.c_int]
|
||||
SendInput.restype = UINT
|
||||
|
||||
INPUT_MOUSE = 0
|
||||
INPUT_KEYBOARD = 1
|
||||
@ -197,7 +167,7 @@ class WindowsHotkeyMgr(AbstractHotkeyMgr):
|
||||
logger.debug('Thread is/was running')
|
||||
self.thread = None # type: ignore
|
||||
logger.debug('Telling thread WM_QUIT')
|
||||
PostThreadMessage(thread.ident, WM_QUIT, 0, 0)
|
||||
win32gui.PostThreadMessage(thread.ident, win32con.WM_QUIT, 0, 0)
|
||||
logger.debug('Joining thread')
|
||||
thread.join() # Wait for it to unregister hotkey and quit
|
||||
|
||||
@ -210,8 +180,10 @@ class WindowsHotkeyMgr(AbstractHotkeyMgr):
|
||||
"""Handle hotkeys."""
|
||||
logger.debug('Begin...')
|
||||
# Hotkey must be registered by the thread that handles it
|
||||
if not RegisterHotKey(None, 1, modifiers | MOD_NOREPEAT, keycode):
|
||||
logger.debug("We're not the right thread?")
|
||||
try:
|
||||
win32gui.RegisterHotKey(None, 1, modifiers | MOD_NOREPEAT, keycode)
|
||||
except pywintypes.error:
|
||||
logger.exception("We're not the right thread?")
|
||||
self.thread = None # type: ignore
|
||||
return
|
||||
|
||||
@ -219,14 +191,14 @@ class WindowsHotkeyMgr(AbstractHotkeyMgr):
|
||||
|
||||
msg = MSG()
|
||||
logger.debug('Entering GetMessage() loop...')
|
||||
while GetMessage(ctypes.byref(msg), None, 0, 0) != 0:
|
||||
while win32gui.GetMessage(ctypes.byref(msg), None, 0, 0) != 0:
|
||||
logger.debug('Got message')
|
||||
if msg.message == WM_HOTKEY:
|
||||
if msg.message == win32con.WM_HOTKEY:
|
||||
logger.debug('WM_HOTKEY')
|
||||
|
||||
if (
|
||||
config.get_int('hotkey_always')
|
||||
or window_title(GetForegroundWindow()).startswith('Elite - Dangerous')
|
||||
config.get_int('hotkey_always')
|
||||
or window_title(win32gui.GetForegroundWindow()).startswith('Elite - Dangerous')
|
||||
):
|
||||
if not config.shutting_down:
|
||||
logger.debug('Sending event <<Invoke>>')
|
||||
@ -236,8 +208,10 @@ class WindowsHotkeyMgr(AbstractHotkeyMgr):
|
||||
logger.debug('Passing key on')
|
||||
UnregisterHotKey(None, 1)
|
||||
SendInput(1, fake, ctypes.sizeof(INPUT))
|
||||
if not RegisterHotKey(None, 1, modifiers | MOD_NOREPEAT, keycode):
|
||||
logger.debug("We aren't registered for this ?")
|
||||
try:
|
||||
win32gui.RegisterHotKey(None, 1, modifiers | MOD_NOREPEAT, keycode)
|
||||
except pywintypes.error:
|
||||
logger.exception("We aren't registered for this ?")
|
||||
break
|
||||
|
||||
elif msg.message == WM_SND_GOOD:
|
||||
@ -250,8 +224,8 @@ class WindowsHotkeyMgr(AbstractHotkeyMgr):
|
||||
|
||||
else:
|
||||
logger.debug('Something else')
|
||||
TranslateMessage(ctypes.byref(msg))
|
||||
DispatchMessage(ctypes.byref(msg))
|
||||
win32gui.TranslateMessage(ctypes.byref(msg))
|
||||
win32gui.DispatchMessage(ctypes.byref(msg))
|
||||
|
||||
logger.debug('Exited GetMessage() loop.')
|
||||
UnregisterHotKey(None, 1)
|
||||
@ -277,40 +251,42 @@ class WindowsHotkeyMgr(AbstractHotkeyMgr):
|
||||
:param event: tk event ?
|
||||
:return: False to retain previous, None to not use, else (keycode, modifiers)
|
||||
"""
|
||||
modifiers = ((GetKeyState(VK_MENU) & 0x8000) and MOD_ALT) \
|
||||
| ((GetKeyState(VK_CONTROL) & 0x8000) and MOD_CONTROL) \
|
||||
| ((GetKeyState(VK_SHIFT) & 0x8000) and MOD_SHIFT) \
|
||||
| ((GetKeyState(VK_LWIN) & 0x8000) and MOD_WIN) \
|
||||
| ((GetKeyState(VK_RWIN) & 0x8000) and MOD_WIN)
|
||||
modifiers = ((win32api.GetKeyState(win32con.VK_MENU) & 0x8000) and win32con.MOD_ALT) \
|
||||
| ((win32api.GetKeyState(win32con.VK_CONTROL) & 0x8000) and win32con.MOD_CONTROL) \
|
||||
| ((win32api.GetKeyState(win32con.VK_SHIFT) & 0x8000) and win32con.MOD_SHIFT) \
|
||||
| ((win32api.GetKeyState(win32con.VK_LWIN) & 0x8000) and win32con.MOD_WIN) \
|
||||
| ((win32api.GetKeyState(win32con.VK_RWIN) & 0x8000) and win32con.MOD_WIN)
|
||||
keycode = event.keycode
|
||||
|
||||
if keycode in (VK_SHIFT, VK_CONTROL, VK_MENU, VK_LWIN, VK_RWIN):
|
||||
if keycode in (win32con.VK_SHIFT, win32con.VK_CONTROL, win32con.VK_MENU, win32con.VK_LWIN, win32con.VK_RWIN):
|
||||
return 0, modifiers
|
||||
|
||||
if not modifiers:
|
||||
if keycode == VK_ESCAPE: # Esc = retain previous
|
||||
if keycode == win32con.VK_ESCAPE: # Esc = retain previous
|
||||
return False
|
||||
|
||||
if keycode in (VK_BACK, VK_DELETE, VK_CLEAR, VK_OEM_CLEAR): # BkSp, Del, Clear = clear hotkey
|
||||
if keycode in (win32con.VK_BACK, win32con.VK_DELETE,
|
||||
win32con.VK_CLEAR, win32con.VK_OEM_CLEAR): # BkSp, Del, Clear = clear hotkey
|
||||
return None
|
||||
|
||||
if (
|
||||
keycode in (VK_RETURN, VK_SPACE, VK_OEM_MINUS) or ord('A') <= keycode <= ord('Z')
|
||||
keycode in (win32con.VK_RETURN, win32con.VK_SPACE, VK_OEM_MINUS) or ord('A') <= keycode <= ord('Z')
|
||||
): # don't allow keys needed for typing in System Map
|
||||
winsound.MessageBeep()
|
||||
return None
|
||||
|
||||
if (keycode in (VK_NUMLOCK, VK_SCROLL, VK_PROCESSKEY)
|
||||
or VK_CAPITAL <= keycode <= VK_MODECHANGE): # ignore unmodified mode switch keys
|
||||
if (keycode in (win32con.VK_NUMLOCK, win32con.VK_SCROLL, win32con.VK_PROCESSKEY)
|
||||
or win32con.VK_CAPITAL <= keycode <= win32con.VK_MODECHANGE): # ignore unmodified mode switch keys
|
||||
return 0, modifiers
|
||||
|
||||
# See if the keycode is usable and available
|
||||
if RegisterHotKey(None, 2, modifiers | MOD_NOREPEAT, keycode):
|
||||
try:
|
||||
win32gui.RegisterHotKey(None, 2, modifiers | MOD_NOREPEAT, keycode)
|
||||
UnregisterHotKey(None, 2)
|
||||
return keycode, modifiers
|
||||
|
||||
winsound.MessageBeep()
|
||||
return None
|
||||
except pywintypes.error:
|
||||
winsound.MessageBeep()
|
||||
return None
|
||||
|
||||
def display(self, keycode, modifiers) -> str:
|
||||
"""
|
||||
@ -321,32 +297,32 @@ class WindowsHotkeyMgr(AbstractHotkeyMgr):
|
||||
:return: string form
|
||||
"""
|
||||
text = ''
|
||||
if modifiers & MOD_WIN:
|
||||
if modifiers & win32con.MOD_WIN:
|
||||
text += '❖+'
|
||||
|
||||
if modifiers & MOD_CONTROL:
|
||||
if modifiers & win32con.MOD_CONTROL:
|
||||
text += 'Ctrl+'
|
||||
|
||||
if modifiers & MOD_ALT:
|
||||
if modifiers & win32con.MOD_ALT:
|
||||
text += 'Alt+'
|
||||
|
||||
if modifiers & MOD_SHIFT:
|
||||
if modifiers & win32con.MOD_SHIFT:
|
||||
text += '⇧+'
|
||||
|
||||
if VK_NUMPAD0 <= keycode <= VK_DIVIDE:
|
||||
if win32con.VK_NUMPAD0 <= keycode <= win32con.VK_DIVIDE:
|
||||
text += '№'
|
||||
|
||||
if not keycode:
|
||||
pass
|
||||
|
||||
elif VK_F1 <= keycode <= VK_F24:
|
||||
text += f'F{keycode + 1 - VK_F1}'
|
||||
elif win32con.VK_F1 <= keycode <= win32con.VK_F24:
|
||||
text += f'F{keycode + 1 - win32con.VK_F1}'
|
||||
|
||||
elif keycode in WindowsHotkeyMgr.DISPLAY: # specials
|
||||
text += WindowsHotkeyMgr.DISPLAY[keycode]
|
||||
|
||||
else:
|
||||
c = MapVirtualKey(keycode, 2) # printable ?
|
||||
c = win32api.MapVirtualKey(keycode, MAPVK_VK_TO_CHAR)
|
||||
if not c: # oops not printable
|
||||
text += '⁈'
|
||||
|
||||
@ -361,9 +337,9 @@ class WindowsHotkeyMgr(AbstractHotkeyMgr):
|
||||
def play_good(self) -> None:
|
||||
"""Play the 'good' sound."""
|
||||
if self.thread:
|
||||
PostThreadMessage(self.thread.ident, WM_SND_GOOD, 0, 0)
|
||||
win32gui.PostThreadMessage(self.thread.ident, WM_SND_GOOD, 0, 0)
|
||||
|
||||
def play_bad(self) -> None:
|
||||
"""Play the 'bad' sound."""
|
||||
if self.thread:
|
||||
PostThreadMessage(self.thread.ident, WM_SND_BAD, 0, 0)
|
||||
win32gui.PostThreadMessage(self.thread.ident, WM_SND_BAD, 0, 0)
|
||||
|
12
installer.py
12
installer.py
@ -5,23 +5,23 @@ Copyright (c) EDCD, All Rights Reserved
|
||||
Licensed under the GNU General Public License.
|
||||
See LICENSE file.
|
||||
"""
|
||||
import os
|
||||
import subprocess
|
||||
from pathlib import Path
|
||||
from build import build
|
||||
|
||||
|
||||
def run_inno_setup_installer(iss_path: str) -> None:
|
||||
def run_inno_setup_installer(iss_path: Path) -> None:
|
||||
"""Run the Inno installer, building the installation exe."""
|
||||
# Get the path to the Inno Setup compiler (iscc.exe) (Currently set to default path)
|
||||
inno_setup_compiler_path: str = "C:\\Program Files (x86)\\Inno Setup 6\\ISCC.exe"
|
||||
inno_setup_compiler_path = Path("C:\\Program Files (x86)\\Inno Setup 6\\ISCC.exe")
|
||||
|
||||
# Check if the Inno Setup compiler executable exists
|
||||
if not os.path.isfile(inno_setup_compiler_path):
|
||||
if not inno_setup_compiler_path.exists():
|
||||
print(f"Error: Inno Setup compiler not found at '{inno_setup_compiler_path}'.")
|
||||
return
|
||||
|
||||
# Check if the provided .iss file exists
|
||||
if not os.path.isfile(iss_file_path):
|
||||
if not iss_file_path.exists():
|
||||
print(f"Error: The provided .iss file '{iss_path}' not found.")
|
||||
return
|
||||
|
||||
@ -40,6 +40,6 @@ def run_inno_setup_installer(iss_path: str) -> None:
|
||||
if __name__ == "__main__":
|
||||
build()
|
||||
# Add the ISS Template File
|
||||
iss_file_path: str = "./EDMC_Installer_Config.iss"
|
||||
iss_file_path = Path("./EDMC_Installer_Config.iss")
|
||||
# Build the ISS file
|
||||
run_inno_setup_installer(iss_file_path)
|
||||
|
45
l10n.py
45
l10n.py
@ -17,10 +17,9 @@ import re
|
||||
import sys
|
||||
import warnings
|
||||
from contextlib import suppress
|
||||
from os import listdir, sep, makedirs
|
||||
from os.path import basename, dirname, isdir, join, abspath, exists
|
||||
from os import listdir, sep
|
||||
from typing import TYPE_CHECKING, Iterable, TextIO, cast
|
||||
|
||||
import pathlib
|
||||
from config import config
|
||||
from EDMCLogging import get_main_logger
|
||||
|
||||
@ -35,7 +34,7 @@ logger = get_main_logger()
|
||||
|
||||
# Language name
|
||||
LANGUAGE_ID = '!Language'
|
||||
LOCALISATION_DIR = 'L10n'
|
||||
LOCALISATION_DIR: pathlib.Path = pathlib.Path('L10n')
|
||||
|
||||
if sys.platform == 'win32':
|
||||
import ctypes
|
||||
@ -50,7 +49,6 @@ if sys.platform == 'win32':
|
||||
GetUserPreferredUILanguages.argtypes = [
|
||||
DWORD, ctypes.POINTER(ctypes.c_ulong), LPCVOID, ctypes.POINTER(ctypes.c_ulong)
|
||||
]
|
||||
|
||||
GetUserPreferredUILanguages.restype = BOOL
|
||||
|
||||
LOCALE_NAME_USER_DEFAULT = None
|
||||
@ -119,10 +117,10 @@ class Translations:
|
||||
|
||||
self.translations = {None: self.contents(cast(str, lang))}
|
||||
for plugin in listdir(config.plugin_dir_path):
|
||||
plugin_path = join(config.plugin_dir_path, plugin, LOCALISATION_DIR)
|
||||
if isdir(plugin_path):
|
||||
plugin_path = config.plugin_dir_path / plugin / LOCALISATION_DIR
|
||||
if pathlib.Path.is_dir(plugin_path):
|
||||
try:
|
||||
self.translations[plugin] = self.contents(cast(str, lang), str(plugin_path))
|
||||
self.translations[plugin] = self.contents(cast(str, lang), plugin_path)
|
||||
|
||||
except UnicodeDecodeError as e:
|
||||
logger.warning(f'Malformed file {lang}.strings in plugin {plugin}: {e}')
|
||||
@ -133,7 +131,7 @@ class Translations:
|
||||
# DEPRECATED: Migrate to translations.translate or tr.tl. Will remove in 6.0 or later.
|
||||
builtins.__dict__['_'] = self.translate
|
||||
|
||||
def contents(self, lang: str, plugin_path: str | None = None) -> dict[str, str]:
|
||||
def contents(self, lang: str, plugin_path: pathlib.Path | None = None) -> dict[str, str]:
|
||||
"""Load all the translations from a translation file."""
|
||||
assert lang in self.available()
|
||||
translations = {}
|
||||
@ -173,12 +171,12 @@ class Translations:
|
||||
:return: The translated string
|
||||
"""
|
||||
plugin_name: str | None = None
|
||||
plugin_path: str | None = None
|
||||
plugin_path: pathlib.Path | None = None
|
||||
|
||||
if context:
|
||||
# TODO: There is probably a better way to go about this now.
|
||||
plugin_name = context[len(config.plugin_dir)+1:].split(sep)[0]
|
||||
plugin_path = join(config.plugin_dir_path, plugin_name, LOCALISATION_DIR)
|
||||
plugin_path = config.plugin_dir_path / plugin_name / LOCALISATION_DIR
|
||||
|
||||
if lang:
|
||||
contents: dict[str, str] = self.contents(lang=lang, plugin_path=plugin_path)
|
||||
@ -225,17 +223,17 @@ class Translations:
|
||||
|
||||
return names
|
||||
|
||||
def respath(self) -> str:
|
||||
def respath(self) -> pathlib.Path:
|
||||
"""Path to localisation files."""
|
||||
if getattr(sys, 'frozen', False):
|
||||
return abspath(join(dirname(sys.executable), LOCALISATION_DIR))
|
||||
return pathlib.Path(sys.executable).parent.joinpath(LOCALISATION_DIR).resolve()
|
||||
|
||||
if __file__:
|
||||
return abspath(join(dirname(__file__), LOCALISATION_DIR))
|
||||
return pathlib.Path(__file__).parent.joinpath(LOCALISATION_DIR).resolve()
|
||||
|
||||
return abspath(LOCALISATION_DIR)
|
||||
return LOCALISATION_DIR.resolve()
|
||||
|
||||
def file(self, lang: str, plugin_path: str | None = None) -> TextIO | None:
|
||||
def file(self, lang: str, plugin_path: pathlib.Path | None = None) -> TextIO | None:
|
||||
"""
|
||||
Open the given lang file for reading.
|
||||
|
||||
@ -244,8 +242,8 @@ class Translations:
|
||||
:return: the opened file (Note: This should be closed when done)
|
||||
"""
|
||||
if plugin_path:
|
||||
file_path = join(plugin_path, f'{lang}.strings')
|
||||
if not exists(file_path):
|
||||
file_path = plugin_path / f"{lang}.strings"
|
||||
if not file_path.exists():
|
||||
return None
|
||||
|
||||
try:
|
||||
@ -253,7 +251,7 @@ class Translations:
|
||||
except OSError:
|
||||
logger.exception(f'could not open {file_path}')
|
||||
|
||||
res_path = join(self.respath(), f'{lang}.strings')
|
||||
res_path = self.respath() / f'{lang}.strings'
|
||||
return open(res_path, encoding='utf-8')
|
||||
|
||||
|
||||
@ -382,9 +380,10 @@ Translations: Translations = translations # type: ignore
|
||||
if __name__ == "__main__":
|
||||
regexp = re.compile(r'''_\([ur]?(['"])(((?<!\\)\\\1|.)+?)\1\)[^#]*(#.+)?''') # match a single line python literal
|
||||
seen: dict[str, str] = {}
|
||||
plugin_dir = pathlib.Path('plugins')
|
||||
for f in (
|
||||
sorted(x for x in listdir('.') if x.endswith('.py')) +
|
||||
sorted(join('plugins', x) for x in (listdir('plugins') if isdir('plugins') else []) if x.endswith('.py'))
|
||||
sorted(plugin_dir.glob('*.py')) if plugin_dir.is_dir() else []
|
||||
):
|
||||
with open(f, encoding='utf-8') as h:
|
||||
lineno = 0
|
||||
@ -393,11 +392,11 @@ if __name__ == "__main__":
|
||||
match = regexp.search(line)
|
||||
if match and not seen.get(match.group(2)): # only record first commented instance of a string
|
||||
seen[match.group(2)] = (
|
||||
(match.group(4) and (match.group(4)[1:].strip()) + '. ' or '') + f'[{basename(f)}]'
|
||||
(match.group(4) and (match.group(4)[1:].strip()) + '. ' or '') + f'[{pathlib.Path(f).name}]'
|
||||
)
|
||||
if seen:
|
||||
target_path = join(LOCALISATION_DIR, 'en.template.new')
|
||||
makedirs(dirname(target_path), exist_ok=True)
|
||||
target_path = LOCALISATION_DIR / 'en.template.new'
|
||||
target_path.parent.mkdir(parents=True, exist_ok=True)
|
||||
with open(target_path, 'w', encoding='utf-8') as target_file:
|
||||
target_file.write(f'/* Language name */\n"{LANGUAGE_ID}" = "English";\n\n')
|
||||
for thing in sorted(seen, key=str.lower):
|
||||
|
@ -11,7 +11,7 @@ import json
|
||||
import re
|
||||
import time
|
||||
from os import listdir
|
||||
from os.path import join
|
||||
from pathlib import Path
|
||||
import companion
|
||||
import util_ships
|
||||
from config import config
|
||||
@ -45,7 +45,7 @@ def export(data: companion.CAPIData, requested_filename: str | None = None) -> N
|
||||
regexp = re.compile(re.escape(ship) + r'\.\d\d\d\d-\d\d-\d\dT\d\d\.\d\d\.\d\d\.txt')
|
||||
oldfiles = sorted([x for x in listdir(config.get_str('outdir')) if regexp.match(x)])
|
||||
if oldfiles:
|
||||
with open(join(config.get_str('outdir'), oldfiles[-1]), 'rU') as h:
|
||||
with open(Path(config.get_str('outdir')) / Path(oldfiles[-1]), 'rU') as h:
|
||||
if h.read() == string:
|
||||
return # same as last time - don't write
|
||||
|
||||
@ -53,9 +53,9 @@ def export(data: companion.CAPIData, requested_filename: str | None = None) -> N
|
||||
|
||||
# Write
|
||||
|
||||
output_directory = config.get_str('outdir')
|
||||
output_directory = Path(config.get_str('outdir'))
|
||||
ship_time = time.strftime('%Y-%m-%dT%H.%M.%S', time.localtime(query_time))
|
||||
file_path = join(output_directory, f"{ship}.{ship_time}.txt")
|
||||
file_path = output_directory / f"{ship}.{ship_time}.txt"
|
||||
|
||||
with open(file_path, 'wt') as h:
|
||||
h.write(string)
|
||||
|
143
monitor.py
143
monitor.py
@ -8,7 +8,7 @@ See LICENSE file.
|
||||
from __future__ import annotations
|
||||
|
||||
import json
|
||||
import pathlib
|
||||
from pathlib import Path
|
||||
import queue
|
||||
import re
|
||||
import sys
|
||||
@ -16,14 +16,15 @@ import threading
|
||||
from calendar import timegm
|
||||
from collections import defaultdict
|
||||
from os import SEEK_END, SEEK_SET, listdir
|
||||
from os.path import basename, expanduser, getctime, isdir, join
|
||||
from time import gmtime, localtime, mktime, sleep, strftime, strptime, time
|
||||
from typing import TYPE_CHECKING, Any, BinaryIO, MutableMapping
|
||||
import psutil
|
||||
import semantic_version
|
||||
import util_ships
|
||||
from config import config
|
||||
from edmc_data import edmc_suit_shortnames, edmc_suit_symbol_localised
|
||||
from config import config, appname, appversion
|
||||
from edmc_data import edmc_suit_shortnames, edmc_suit_symbol_localised, ship_name_map
|
||||
from EDMCLogging import get_main_logger
|
||||
from edshipyard import ships
|
||||
|
||||
if TYPE_CHECKING:
|
||||
import tkinter
|
||||
@ -35,26 +36,10 @@ MAX_NAVROUTE_DISCREPANCY = 5 # Timestamp difference in seconds
|
||||
MAX_FCMATERIALS_DISCREPANCY = 5 # Timestamp difference in seconds
|
||||
|
||||
if sys.platform == 'win32':
|
||||
import ctypes
|
||||
from ctypes.wintypes import BOOL, HWND, LPARAM, LPWSTR
|
||||
|
||||
from watchdog.events import FileSystemEventHandler, FileSystemEvent
|
||||
from watchdog.observers import Observer
|
||||
from watchdog.observers.api import BaseObserver
|
||||
|
||||
EnumWindows = ctypes.windll.user32.EnumWindows
|
||||
EnumWindowsProc = ctypes.WINFUNCTYPE(BOOL, HWND, LPARAM)
|
||||
|
||||
CloseHandle = ctypes.windll.kernel32.CloseHandle
|
||||
|
||||
GetWindowText = ctypes.windll.user32.GetWindowTextW
|
||||
GetWindowText.argtypes = [HWND, LPWSTR, ctypes.c_int]
|
||||
GetWindowTextLength = ctypes.windll.user32.GetWindowTextLengthW
|
||||
GetWindowTextLength.argtypes = [ctypes.wintypes.HWND]
|
||||
GetWindowTextLength.restype = ctypes.c_int
|
||||
|
||||
GetProcessHandleFromHwnd = ctypes.windll.oleacc.GetProcessHandleFromHwnd
|
||||
|
||||
else:
|
||||
# Linux's inotify doesn't work over CIFS or NFS, so poll
|
||||
FileSystemEventHandler = object # dummy
|
||||
@ -70,7 +55,8 @@ class EDLogs(FileSystemEventHandler):
|
||||
"""Monitoring of Journal files."""
|
||||
|
||||
# Magic with FileSystemEventHandler can confuse type checkers when they do not have access to every import
|
||||
_POLL = 1 # Polling is cheap, so do it often
|
||||
_POLL = 1 # Polling while running is cheap, so do it often
|
||||
_INACTIVE_POLL = 10 # Polling while not running isn't as cheap, so do it less often
|
||||
_RE_CANONICALISE = re.compile(r'\$(.+)_name;')
|
||||
_RE_CATEGORY = re.compile(r'\$MICRORESOURCE_CATEGORY_(.+);')
|
||||
_RE_LOGFILE = re.compile(r'^Journal(Alpha|Beta)?\.[0-9]{2,4}(-)?[0-9]{2}(-)?[0-9]{2}(T)?[0-9]{2}[0-9]{2}[0-9]{2}'
|
||||
@ -81,7 +67,7 @@ class EDLogs(FileSystemEventHandler):
|
||||
# TODO(A_D): A bunch of these should be switched to default values (eg '' for strings) and no longer be Optional
|
||||
FileSystemEventHandler.__init__(self) # futureproofing - not need for current version of watchdog
|
||||
self.root: 'tkinter.Tk' = None # type: ignore # Don't use Optional[] - mypy thinks no methods
|
||||
self.currentdir: str | None = None # The actual logdir that we're monitoring
|
||||
self.currentdir: Path | None = None # The actual logdir that we're monitoring
|
||||
self.logfile: str | None = None
|
||||
self.observer: BaseObserver | None = None
|
||||
self.observed = None # a watchdog ObservedWatch, or None if polling
|
||||
@ -100,6 +86,7 @@ class EDLogs(FileSystemEventHandler):
|
||||
self.catching_up = False
|
||||
|
||||
self.game_was_running = False # For generation of the "ShutDown" event
|
||||
self.running_process = None
|
||||
|
||||
# Context for journal handling
|
||||
self.version: str | None = None
|
||||
@ -109,6 +96,7 @@ class EDLogs(FileSystemEventHandler):
|
||||
self.group: str | None = None
|
||||
self.cmdr: str | None = None
|
||||
self.started: int | None = None # Timestamp of the LoadGame event
|
||||
self.slef: str | None = None
|
||||
|
||||
self._navroute_retries_remaining = 0
|
||||
self._last_navroute_journal_timestamp: float | None = None
|
||||
@ -202,9 +190,9 @@ class EDLogs(FileSystemEventHandler):
|
||||
if journal_dir == '' or journal_dir is None:
|
||||
journal_dir = config.default_journal_dir
|
||||
|
||||
logdir = expanduser(journal_dir)
|
||||
logdir = Path(journal_dir).expanduser()
|
||||
|
||||
if not logdir or not isdir(logdir):
|
||||
if not logdir or not Path.is_dir(logdir):
|
||||
logger.error(f'Journal Directory is invalid: "{logdir}"')
|
||||
self.stop()
|
||||
return False
|
||||
@ -277,9 +265,10 @@ class EDLogs(FileSystemEventHandler):
|
||||
# Odyssey Update 11 has, e.g. Journal.2022-03-15T152503.01.log
|
||||
# Horizons Update 11 equivalent: Journal.220315152335.01.log
|
||||
# So we can no longer use a naive sort.
|
||||
journals_dir_path = pathlib.Path(journals_dir)
|
||||
journal_files = (journals_dir_path / pathlib.Path(x) for x in journal_files)
|
||||
return str(max(journal_files, key=getctime))
|
||||
journals_dir_path = Path(journals_dir)
|
||||
journal_files = (journals_dir_path / Path(x) for x in journal_files)
|
||||
latest_file = max(journal_files, key=lambda f: Path(f).stat().st_ctime)
|
||||
return str(latest_file)
|
||||
|
||||
return None
|
||||
|
||||
@ -348,7 +337,7 @@ class EDLogs(FileSystemEventHandler):
|
||||
|
||||
def on_created(self, event: 'FileSystemEvent') -> None:
|
||||
"""Watchdog callback when, e.g. client (re)started."""
|
||||
if not event.is_directory and self._RE_LOGFILE.search(basename(event.src_path)):
|
||||
if not event.is_directory and self._RE_LOGFILE.search(Path(event.src_path).name):
|
||||
|
||||
self.logfile = event.src_path
|
||||
|
||||
@ -472,7 +461,10 @@ class EDLogs(FileSystemEventHandler):
|
||||
loghandle = open(logfile, 'rb', 0) # unbuffered
|
||||
log_pos = 0
|
||||
|
||||
sleep(self._POLL)
|
||||
if self.game_was_running:
|
||||
sleep(self._POLL)
|
||||
else:
|
||||
sleep(self._INACTIVE_POLL)
|
||||
|
||||
# Check whether we're still supposed to be running
|
||||
if threading.current_thread() != self.thread:
|
||||
@ -701,6 +693,34 @@ class EDLogs(FileSystemEventHandler):
|
||||
module.pop('AmmoInHopper')
|
||||
|
||||
self.state['Modules'][module['Slot']] = module
|
||||
# SLEF
|
||||
initial_dict: dict[str, dict[str, Any]] = {
|
||||
"header": {"appName": appname, "appVersion": str(appversion())}
|
||||
}
|
||||
data_dict = {}
|
||||
for module in entry['Modules']:
|
||||
if module.get('Slot') == 'FuelTank':
|
||||
cap = module['Item'].split('size')
|
||||
cap = cap[1].split('_')
|
||||
cap = 2 ** int(cap[0])
|
||||
ship = ship_name_map[entry["Ship"]]
|
||||
fuel = {'Main': cap, 'Reserve': ships[ship]['reserveFuelCapacity']}
|
||||
data_dict.update({"FuelCapacity": fuel})
|
||||
data_dict.update({
|
||||
'Ship': entry["Ship"],
|
||||
'ShipName': entry['ShipName'],
|
||||
'ShipIdent': entry['ShipIdent'],
|
||||
'HullValue': entry['HullValue'],
|
||||
'ModulesValue': entry['ModulesValue'],
|
||||
'Rebuy': entry['Rebuy'],
|
||||
'MaxJumpRange': entry['MaxJumpRange'],
|
||||
'UnladenMass': entry['UnladenMass'],
|
||||
'CargoCapacity': entry['CargoCapacity'],
|
||||
'Modules': entry['Modules'],
|
||||
})
|
||||
initial_dict.update({'data': data_dict})
|
||||
output = json.dumps(initial_dict, indent=4)
|
||||
self.slef = str(f"[{output}]")
|
||||
|
||||
elif event_type == 'modulebuy':
|
||||
self.state['Modules'][entry['Slot']] = {
|
||||
@ -1056,7 +1076,7 @@ class EDLogs(FileSystemEventHandler):
|
||||
self.state['Cargo'] = defaultdict(int)
|
||||
# From 3.3 full Cargo event (after the first one) is written to a separate file
|
||||
if 'Inventory' not in entry:
|
||||
with open(join(self.currentdir, 'Cargo.json'), 'rb') as h: # type: ignore
|
||||
with open(self.currentdir / 'Cargo.json', 'rb') as h: # type: ignore
|
||||
entry = json.load(h)
|
||||
self.state['CargoJSON'] = entry
|
||||
|
||||
@ -1083,7 +1103,7 @@ class EDLogs(FileSystemEventHandler):
|
||||
# Always attempt loading of this, but if it fails we'll hope this was
|
||||
# a startup/boarding version and thus `entry` contains
|
||||
# the data anyway.
|
||||
currentdir_path = pathlib.Path(str(self.currentdir))
|
||||
currentdir_path = Path(str(self.currentdir))
|
||||
shiplocker_filename = currentdir_path / 'ShipLocker.json'
|
||||
shiplocker_max_attempts = 5
|
||||
shiplocker_fail_sleep = 0.01
|
||||
@ -1152,7 +1172,7 @@ class EDLogs(FileSystemEventHandler):
|
||||
|
||||
# TODO: v31 doc says this is`backpack.json` ... but Howard Chalkley
|
||||
# said it's `Backpack.json`
|
||||
backpack_file = pathlib.Path(str(self.currentdir)) / 'Backpack.json'
|
||||
backpack_file = Path(str(self.currentdir)) / 'Backpack.json'
|
||||
backpack_data = None
|
||||
|
||||
if not backpack_file.exists():
|
||||
@ -1528,7 +1548,7 @@ class EDLogs(FileSystemEventHandler):
|
||||
entry = fcmaterials
|
||||
|
||||
elif event_type == 'moduleinfo':
|
||||
with open(join(self.currentdir, 'ModulesInfo.json'), 'rb') as mf: # type: ignore
|
||||
with open(self.currentdir / 'ModulesInfo.json', 'rb') as mf: # type: ignore
|
||||
try:
|
||||
entry = json.load(mf)
|
||||
|
||||
@ -2120,36 +2140,33 @@ class EDLogs(FileSystemEventHandler):
|
||||
|
||||
return entry
|
||||
|
||||
def game_running(self) -> bool: # noqa: CCR001
|
||||
def game_running(self) -> bool:
|
||||
"""
|
||||
Determine if the game is currently running.
|
||||
|
||||
TODO: Implement on Linux
|
||||
|
||||
:return: bool - True if the game is running.
|
||||
"""
|
||||
if sys.platform == 'win32':
|
||||
def WindowTitle(h): # noqa: N802
|
||||
if h:
|
||||
length = GetWindowTextLength(h) + 1
|
||||
buf = ctypes.create_unicode_buffer(length)
|
||||
if GetWindowText(h, buf, length):
|
||||
return buf.value
|
||||
return None
|
||||
|
||||
def callback(hWnd, lParam): # noqa: N803
|
||||
name = WindowTitle(hWnd)
|
||||
if name and name.startswith('Elite - Dangerous'):
|
||||
handle = GetProcessHandleFromHwnd(hWnd)
|
||||
if handle: # If GetProcessHandleFromHwnd succeeds then the app is already running as this user
|
||||
CloseHandle(handle)
|
||||
return False # stop enumeration
|
||||
|
||||
return True
|
||||
|
||||
return not EnumWindows(EnumWindowsProc(callback), 0)
|
||||
|
||||
return False
|
||||
if self.running_process:
|
||||
p = self.running_process
|
||||
try:
|
||||
with p.oneshot():
|
||||
if p.status() not in [psutil.STATUS_RUNNING, psutil.STATUS_SLEEPING]:
|
||||
raise psutil.NoSuchProcess
|
||||
except psutil.NoSuchProcess:
|
||||
# Process likely expired
|
||||
self.running_process = None
|
||||
if not self.running_process:
|
||||
try:
|
||||
edmc_process = psutil.Process()
|
||||
edmc_user = edmc_process.username()
|
||||
for proc in psutil.process_iter(['name', 'username']):
|
||||
if 'EliteDangerous' in proc.info['name'] and proc.info['username'] == edmc_user:
|
||||
self.running_process = proc
|
||||
return True
|
||||
except psutil.NoSuchProcess:
|
||||
pass
|
||||
return False
|
||||
return bool(self.running_process)
|
||||
|
||||
def ship(self, timestamped=True) -> MutableMapping[str, Any] | None:
|
||||
"""
|
||||
@ -2242,14 +2259,14 @@ class EDLogs(FileSystemEventHandler):
|
||||
oldfiles = sorted((x for x in listdir(config.get_str('outdir')) if regexp.match(x)))
|
||||
if oldfiles:
|
||||
try:
|
||||
with open(join(config.get_str('outdir'), oldfiles[-1]), encoding='utf-8') as h:
|
||||
with open(config.get_str('outdir') / Path(oldfiles[-1]), encoding='utf-8') as h:
|
||||
if h.read() == string:
|
||||
return # same as last time - don't write
|
||||
|
||||
except UnicodeError:
|
||||
logger.exception("UnicodeError reading old ship loadout with utf-8 encoding, trying without...")
|
||||
try:
|
||||
with open(join(config.get_str('outdir'), oldfiles[-1])) as h:
|
||||
with open(config.get_str('outdir') / Path(oldfiles[-1])) as h:
|
||||
if h.read() == string:
|
||||
return # same as last time - don't write
|
||||
|
||||
@ -2268,7 +2285,7 @@ class EDLogs(FileSystemEventHandler):
|
||||
|
||||
# Write
|
||||
ts = strftime('%Y-%m-%dT%H.%M.%S', localtime(time()))
|
||||
filename = join(config.get_str('outdir'), f'{ship}.{ts}.txt')
|
||||
filename = config.get_str('outdir') / Path(f'{ship}.{ts}.txt')
|
||||
|
||||
try:
|
||||
with open(filename, 'wt', encoding='utf-8') as h:
|
||||
@ -2355,7 +2372,7 @@ class EDLogs(FileSystemEventHandler):
|
||||
|
||||
try:
|
||||
|
||||
with open(join(self.currentdir, 'NavRoute.json')) as f:
|
||||
with open(self.currentdir / 'NavRoute.json') as f:
|
||||
raw = f.read()
|
||||
|
||||
except Exception as e:
|
||||
@ -2381,7 +2398,7 @@ class EDLogs(FileSystemEventHandler):
|
||||
|
||||
try:
|
||||
|
||||
with open(join(self.currentdir, 'FCMaterials.json')) as f:
|
||||
with open(self.currentdir / 'FCMaterials.json') as f:
|
||||
raw = f.read()
|
||||
|
||||
except Exception as e:
|
||||
|
22
plug.py
22
plug.py
@ -14,6 +14,7 @@ import operator
|
||||
import os
|
||||
import sys
|
||||
import tkinter as tk
|
||||
from pathlib import Path
|
||||
from tkinter import ttk
|
||||
from typing import Any, Mapping, MutableMapping
|
||||
|
||||
@ -47,7 +48,7 @@ last_error = LastError()
|
||||
class Plugin:
|
||||
"""An EDMC plugin."""
|
||||
|
||||
def __init__(self, name: str, loadfile: str | None, plugin_logger: logging.Logger | None): # noqa: CCR001
|
||||
def __init__(self, name: str, loadfile: Path | None, plugin_logger: logging.Logger | None): # noqa: CCR001
|
||||
"""
|
||||
Load a single plugin.
|
||||
|
||||
@ -73,7 +74,7 @@ class Plugin:
|
||||
sys.modules[module.__name__] = module
|
||||
spec.loader.exec_module(module)
|
||||
if getattr(module, 'plugin_start3', None):
|
||||
newname = module.plugin_start3(os.path.dirname(loadfile))
|
||||
newname = module.plugin_start3(Path(loadfile).resolve().parent)
|
||||
self.name = str(newname) if newname else self.name
|
||||
self.module = module
|
||||
elif getattr(module, 'plugin_start', None):
|
||||
@ -171,7 +172,9 @@ def _load_internal_plugins():
|
||||
for name in sorted(os.listdir(config.internal_plugin_dir_path)):
|
||||
if name.endswith('.py') and name[0] not in ('.', '_'):
|
||||
try:
|
||||
plugin = Plugin(name[:-3], os.path.join(config.internal_plugin_dir_path, name), logger)
|
||||
plugin_name = name[:-3]
|
||||
plugin_path = config.internal_plugin_dir_path / name
|
||||
plugin = Plugin(plugin_name, plugin_path, logger)
|
||||
plugin.folder = None
|
||||
internal.append(plugin)
|
||||
except Exception:
|
||||
@ -186,9 +189,12 @@ def _load_found_plugins():
|
||||
# The intent here is to e.g. have EDMC-Overlay load before any plugins
|
||||
# that depend on it.
|
||||
|
||||
for name in sorted(os.listdir(config.plugin_dir_path), key=lambda n: (
|
||||
not os.path.isfile(os.path.join(config.plugin_dir_path, n, '__init__.py')), n.lower())):
|
||||
if not os.path.isdir(os.path.join(config.plugin_dir_path, name)) or name[0] in ('.', '_'):
|
||||
plugin_files = sorted(config.plugin_dir_path.iterdir(), key=lambda p: (
|
||||
not (p / '__init__.py').is_file(), p.name.lower()))
|
||||
|
||||
for plugin_file in plugin_files:
|
||||
name = plugin_file.name
|
||||
if not (config.plugin_dir_path / name).is_dir() or name.startswith(('.', '_')):
|
||||
pass
|
||||
elif name.endswith('.disabled'):
|
||||
name, discard = name.rsplit('.', 1)
|
||||
@ -196,12 +202,12 @@ def _load_found_plugins():
|
||||
else:
|
||||
try:
|
||||
# Add plugin's folder to load path in case plugin has internal package dependencies
|
||||
sys.path.append(os.path.join(config.plugin_dir_path, name))
|
||||
sys.path.append(str(config.plugin_dir_path / name))
|
||||
|
||||
import EDMCLogging
|
||||
# Create a logger for this 'found' plugin. Must be before the load.py is loaded.
|
||||
plugin_logger = EDMCLogging.get_plugin_logger(name)
|
||||
found.append(Plugin(name, os.path.join(config.plugin_dir_path, name, 'load.py'), plugin_logger))
|
||||
found.append(Plugin(name, config.plugin_dir_path / name / 'load.py', plugin_logger))
|
||||
except Exception:
|
||||
PLUGINS_broken.append(Plugin(name, None, logger))
|
||||
logger.exception(f'Failure loading found Plugin "{name}"')
|
||||
|
@ -1416,15 +1416,6 @@ class EDDN:
|
||||
#######################################################################
|
||||
# Elisions
|
||||
#######################################################################
|
||||
# WORKAROUND WIP EDDN schema | 2021-10-17: This will reject with the Odyssey or Horizons flags present
|
||||
if 'odyssey' in entry:
|
||||
del entry['odyssey']
|
||||
|
||||
if 'horizons' in entry:
|
||||
del entry['horizons']
|
||||
|
||||
# END WORKAROUND
|
||||
|
||||
# In case Frontier ever add any
|
||||
entry = filter_localised(entry)
|
||||
#######################################################################
|
||||
|
126
prefs.py
126
prefs.py
@ -4,14 +4,13 @@ from __future__ import annotations
|
||||
|
||||
import contextlib
|
||||
import logging
|
||||
import pathlib
|
||||
from os.path import expandvars
|
||||
from pathlib import Path
|
||||
import subprocess
|
||||
import sys
|
||||
import tempfile
|
||||
import tkinter as tk
|
||||
import warnings
|
||||
from os import system
|
||||
from os.path import expanduser, expandvars, join, normpath
|
||||
from tkinter import colorchooser as tkColorChooser # type: ignore # noqa: N812
|
||||
from tkinter import ttk
|
||||
from types import TracebackType
|
||||
@ -20,7 +19,6 @@ import myNotebook as nb # noqa: N813
|
||||
import plug
|
||||
from config import appversion_nobuild, config
|
||||
from EDMCLogging import edmclogger, get_main_logger
|
||||
from constants import appname
|
||||
from hotkey import hotkeymgr
|
||||
from l10n import translations as tr
|
||||
from monitor import monitor
|
||||
@ -43,10 +41,10 @@ def help_open_log_folder() -> None:
|
||||
"""Open the folder logs are stored in."""
|
||||
warnings.warn('prefs.help_open_log_folder is deprecated, use open_log_folder instead. '
|
||||
'This function will be removed in 6.0 or later', DeprecationWarning, stacklevel=2)
|
||||
open_folder(pathlib.Path(tempfile.gettempdir()) / appname)
|
||||
open_folder(Path(config.app_dir_path / 'logs'))
|
||||
|
||||
|
||||
def open_folder(file: pathlib.Path) -> None:
|
||||
def open_folder(file: Path) -> None:
|
||||
"""Open the given file in the OS file explorer."""
|
||||
if sys.platform.startswith('win'):
|
||||
# On Windows, use the "start" command to open the folder
|
||||
@ -58,7 +56,7 @@ def open_folder(file: pathlib.Path) -> None:
|
||||
|
||||
def help_open_system_profiler(parent) -> None:
|
||||
"""Open the EDMC System Profiler."""
|
||||
profiler_path = pathlib.Path(config.respath_path)
|
||||
profiler_path = config.respath_path
|
||||
try:
|
||||
if getattr(sys, 'frozen', False):
|
||||
profiler_path /= 'EDMCSystemProfiler.exe'
|
||||
@ -188,7 +186,9 @@ class AutoInc(contextlib.AbstractContextManager):
|
||||
if sys.platform == 'win32':
|
||||
import ctypes
|
||||
import winreg
|
||||
from ctypes.wintypes import HINSTANCE, HWND, LPCWSTR, LPWSTR, MAX_PATH, POINT, RECT, SIZE, UINT
|
||||
from ctypes.wintypes import LPCWSTR, LPWSTR, MAX_PATH, POINT, RECT, SIZE, UINT, BOOL
|
||||
import win32gui
|
||||
import win32api
|
||||
is_wine = False
|
||||
try:
|
||||
WINE_REGISTRY_KEY = r'HKEY_LOCAL_MACHINE\Software\Wine'
|
||||
@ -203,6 +203,8 @@ if sys.platform == 'win32':
|
||||
if not is_wine:
|
||||
try:
|
||||
CalculatePopupWindowPosition = ctypes.windll.user32.CalculatePopupWindowPosition
|
||||
CalculatePopupWindowPosition.argtypes = [POINT, SIZE, UINT, RECT, RECT]
|
||||
CalculatePopupWindowPosition.restype = BOOL
|
||||
|
||||
except AttributeError as e:
|
||||
logger.error(
|
||||
@ -219,17 +221,9 @@ if sys.platform == 'win32':
|
||||
ctypes.POINTER(RECT)
|
||||
]
|
||||
|
||||
GetParent = ctypes.windll.user32.GetParent
|
||||
GetParent.argtypes = [HWND]
|
||||
GetWindowRect = ctypes.windll.user32.GetWindowRect
|
||||
GetWindowRect.argtypes = [HWND, ctypes.POINTER(RECT)]
|
||||
|
||||
SHGetLocalizedName = ctypes.windll.shell32.SHGetLocalizedName
|
||||
SHGetLocalizedName.argtypes = [LPCWSTR, LPWSTR, UINT, ctypes.POINTER(ctypes.c_int)]
|
||||
|
||||
LoadString = ctypes.windll.user32.LoadStringW
|
||||
LoadString.argtypes = [HINSTANCE, UINT, LPWSTR, ctypes.c_int]
|
||||
|
||||
|
||||
class PreferencesDialog(tk.Toplevel):
|
||||
"""The EDMC preferences dialog."""
|
||||
@ -239,6 +233,7 @@ class PreferencesDialog(tk.Toplevel):
|
||||
|
||||
self.parent = parent
|
||||
self.callback = callback
|
||||
self.req_restart = False
|
||||
# LANG: File > Settings (macOS)
|
||||
self.title(tr.tl('Settings'))
|
||||
|
||||
@ -314,7 +309,7 @@ class PreferencesDialog(tk.Toplevel):
|
||||
# Ensure fully on-screen
|
||||
if sys.platform == 'win32' and CalculatePopupWindowPosition:
|
||||
position = RECT()
|
||||
GetWindowRect(GetParent(self.winfo_id()), position)
|
||||
win32gui.GetWindowRect(win32gui.GetParent(self.winfo_id()))
|
||||
if CalculatePopupWindowPosition(
|
||||
POINT(parent.winfo_rootx(), parent.winfo_rooty()),
|
||||
SIZE(position.right - position.left, position.bottom - position.top), # type: ignore
|
||||
@ -323,7 +318,7 @@ class PreferencesDialog(tk.Toplevel):
|
||||
self.geometry(f"+{position.left}+{position.top}")
|
||||
|
||||
# Set Log Directory
|
||||
self.logfile_loc = pathlib.Path(tempfile.gettempdir()) / appname
|
||||
self.logfile_loc = Path(config.app_dir_path / 'logs')
|
||||
|
||||
# Set minimum size to prevent content cut-off
|
||||
self.update_idletasks() # Update "requested size" from geometry manager
|
||||
@ -911,20 +906,15 @@ class PreferencesDialog(tk.Toplevel):
|
||||
# Plugin settings and info
|
||||
plugins_frame = nb.Frame(notebook)
|
||||
plugins_frame.columnconfigure(0, weight=1)
|
||||
plugdir = tk.StringVar()
|
||||
plugdir.set(config.plugin_dir)
|
||||
row = AutoInc(start=0)
|
||||
|
||||
# Section heading in settings
|
||||
self.plugdir = tk.StringVar()
|
||||
self.plugdir.set(str(config.get_str('plugin_dir')))
|
||||
# LANG: Label for location of third-party plugins folder
|
||||
nb.Label(plugins_frame, text=tr.tl('Plugins folder') + ':').grid(
|
||||
padx=self.PADX, pady=self.PADY, sticky=tk.W, row=row.get()
|
||||
)
|
||||
|
||||
plugdirentry = ttk.Entry(plugins_frame, justify=tk.LEFT)
|
||||
self.displaypath(plugdir, plugdirentry)
|
||||
plugdirentry.grid(columnspan=2, padx=self.PADX, pady=self.BOXY, sticky=tk.EW, row=row.get())
|
||||
|
||||
self.plugdir_label = nb.Label(plugins_frame, text=tr.tl('Plugins folder') + ':')
|
||||
self.plugdir_label.grid(padx=self.PADX, pady=self.PADY, sticky=tk.W, row=row.get())
|
||||
self.plugdir_entry = ttk.Entry(plugins_frame, takefocus=False,
|
||||
textvariable=self.plugdir) # Link StringVar to Entry widget
|
||||
self.plugdir_entry.grid(columnspan=4, padx=self.PADX, pady=self.BOXY, sticky=tk.EW, row=row.get())
|
||||
with row as cur_row:
|
||||
nb.Label(
|
||||
plugins_frame,
|
||||
@ -932,19 +922,41 @@ class PreferencesDialog(tk.Toplevel):
|
||||
# LANG: Tip/label about how to disable plugins
|
||||
text=tr.tl(
|
||||
"Tip: You can disable a plugin by{CR}adding '{EXT}' to its folder name").format(EXT='.disabled')
|
||||
).grid(columnspan=2, padx=self.PADX, pady=self.PADY, sticky=tk.EW, row=cur_row)
|
||||
).grid(columnspan=1, padx=self.PADX, pady=self.PADY, sticky=tk.EW, row=cur_row)
|
||||
|
||||
ttk.Button(
|
||||
# Open Plugin Folder Button
|
||||
self.open_plug_folder_btn = ttk.Button(
|
||||
plugins_frame,
|
||||
# LANG: Label on button used to open a filesystem folder
|
||||
text=tr.tl('Open'), # Button that opens a folder in Explorer/Finder
|
||||
# LANG: Label on button used to open the Plugin Folder
|
||||
text=tr.tl('Open Plugins Folder'),
|
||||
command=lambda: open_folder(config.plugin_dir_path)
|
||||
).grid(column=1, padx=self.PADX, pady=self.PADY, sticky=tk.N, row=cur_row)
|
||||
)
|
||||
self.open_plug_folder_btn.grid(column=1, padx=self.PADX, pady=self.PADY, sticky=tk.EW, row=cur_row)
|
||||
|
||||
# Browse Button
|
||||
text = tr.tl('Browse...') # LANG: NOT-macOS Settings - files location selection button
|
||||
self.plugbutton = ttk.Button(
|
||||
plugins_frame,
|
||||
text=text,
|
||||
# LANG: Selecting the Location of the Plugin Directory on the Filesystem
|
||||
command=lambda: self.filebrowse(tr.tl('Plugin Directory Location'), self.plugdir)
|
||||
)
|
||||
self.plugbutton.grid(column=2, padx=self.PADX, pady=self.PADY, sticky=tk.EW, row=cur_row)
|
||||
|
||||
if config.default_journal_dir_path:
|
||||
# Appearance theme and language setting
|
||||
ttk.Button(
|
||||
plugins_frame,
|
||||
# LANG: Settings > Configuration - Label on 'reset journal files location to default' button
|
||||
text=tr.tl('Default'),
|
||||
command=self.plugdir_reset,
|
||||
state=tk.NORMAL if config.get_str('plugin_dir') else tk.DISABLED
|
||||
).grid(column=3, padx=self.PADX, pady=self.PADY, sticky=tk.EW, row=cur_row)
|
||||
|
||||
enabled_plugins = list(filter(lambda x: x.folder and x.module, plug.PLUGINS))
|
||||
if enabled_plugins:
|
||||
ttk.Separator(plugins_frame, orient=tk.HORIZONTAL).grid(
|
||||
columnspan=3, padx=self.PADX, pady=self.SEPY, sticky=tk.EW, row=row.get()
|
||||
columnspan=4, padx=self.PADX, pady=self.SEPY, sticky=tk.EW, row=row.get()
|
||||
)
|
||||
nb.Label(
|
||||
plugins_frame,
|
||||
@ -1066,7 +1078,7 @@ class PreferencesDialog(tk.Toplevel):
|
||||
import tkinter.filedialog
|
||||
directory = tkinter.filedialog.askdirectory(
|
||||
parent=self,
|
||||
initialdir=expanduser(pathvar.get()),
|
||||
initialdir=Path(pathvar.get()).expanduser(),
|
||||
title=title,
|
||||
mustexist=tk.TRUE
|
||||
)
|
||||
@ -1088,13 +1100,14 @@ class PreferencesDialog(tk.Toplevel):
|
||||
if sys.platform == 'win32':
|
||||
start = len(config.home.split('\\')) if pathvar.get().lower().startswith(config.home.lower()) else 0
|
||||
display = []
|
||||
components = normpath(pathvar.get()).split('\\')
|
||||
components = Path(pathvar.get()).resolve().parts
|
||||
buf = ctypes.create_unicode_buffer(MAX_PATH)
|
||||
pidsRes = ctypes.c_int() # noqa: N806 # Windows convention
|
||||
for i in range(start, len(components)):
|
||||
try:
|
||||
if (not SHGetLocalizedName('\\'.join(components[:i+1]), buf, MAX_PATH, ctypes.byref(pidsRes)) and
|
||||
LoadString(ctypes.WinDLL(expandvars(buf.value))._handle, pidsRes.value, buf, MAX_PATH)):
|
||||
win32api.LoadString(ctypes.WinDLL(expandvars(buf.value))._handle,
|
||||
pidsRes.value, buf, MAX_PATH)):
|
||||
display.append(buf.value)
|
||||
|
||||
else:
|
||||
@ -1122,6 +1135,13 @@ class PreferencesDialog(tk.Toplevel):
|
||||
|
||||
self.outvarchanged()
|
||||
|
||||
def plugdir_reset(self) -> None:
|
||||
"""Reset the log dir to the default."""
|
||||
if config.default_plugin_dir_path:
|
||||
self.plugdir.set(config.default_plugin_dir)
|
||||
|
||||
self.outvarchanged()
|
||||
|
||||
def disable_autoappupdatecheckingame_changed(self) -> None:
|
||||
"""Save out the auto update check in game config."""
|
||||
config.set('disable_autoappupdatecheckingame', self.disable_autoappupdatecheckingame.get())
|
||||
@ -1217,7 +1237,7 @@ class PreferencesDialog(tk.Toplevel):
|
||||
|
||||
return 'break' # stops further processing - insertion, Tab traversal etc
|
||||
|
||||
def apply(self) -> None:
|
||||
def apply(self) -> None: # noqa: CCR001
|
||||
"""Update the config with the options set on the dialog."""
|
||||
config.set('PrefsVersion', prefsVersion.stringToSerial(appversion_nobuild()))
|
||||
config.set(
|
||||
@ -1233,7 +1253,7 @@ class PreferencesDialog(tk.Toplevel):
|
||||
|
||||
config.set(
|
||||
'outdir',
|
||||
join(config.home_path, self.outdir.get()[2:]) if self.outdir.get().startswith('~') else self.outdir.get()
|
||||
str(config.home_path / self.outdir.get()[2:]) if self.outdir.get().startswith('~') else self.outdir.get()
|
||||
)
|
||||
|
||||
logdir = self.logdir.get()
|
||||
@ -1273,20 +1293,30 @@ class PreferencesDialog(tk.Toplevel):
|
||||
config.set('dark_text', self.theme_colors[0])
|
||||
config.set('dark_highlight', self.theme_colors[1])
|
||||
theme.apply(self.parent)
|
||||
if self.plugdir.get() != config.get('plugin_dir'):
|
||||
config.set(
|
||||
'plugin_dir',
|
||||
str(Path(config.home_path, self.plugdir.get()[2:])) if self.plugdir.get().startswith('~') else
|
||||
str(Path(self.plugdir.get()))
|
||||
)
|
||||
self.req_restart = True
|
||||
|
||||
# Send to the Post Config if we updated the update branch
|
||||
post_flags = {
|
||||
'Update': True if self.curr_update_track != self.update_paths.get() else False,
|
||||
'Track': self.update_paths.get(),
|
||||
'Parent': self
|
||||
}
|
||||
# Notify
|
||||
if self.callback:
|
||||
self.callback(**post_flags)
|
||||
self.callback()
|
||||
|
||||
plug.notify_prefs_changed(monitor.cmdr, monitor.is_beta)
|
||||
|
||||
self._destroy()
|
||||
# Send to the Post Config if we updated the update branch or need to restart
|
||||
post_flags = {
|
||||
'Update': True if self.curr_update_track != self.update_paths.get() else False,
|
||||
'Track': self.update_paths.get(),
|
||||
'Parent': self,
|
||||
'Restart_Req': True if self.req_restart else False
|
||||
}
|
||||
if self.callback:
|
||||
self.callback(**post_flags)
|
||||
|
||||
def _destroy(self) -> None:
|
||||
"""widget.destroy wrapper that does some extra cleanup."""
|
||||
|
79
protocol.py
79
protocol.py
@ -69,13 +69,16 @@ if (config.auth_force_edmc_protocol # noqa: C901
|
||||
# This could be false if you use auth_force_edmc_protocol, but then you get to keep the pieces
|
||||
assert sys.platform == 'win32'
|
||||
# spell-checker: words HBRUSH HICON WPARAM wstring WNDCLASS HMENU HGLOBAL
|
||||
from ctypes import ( # type: ignore
|
||||
windll, POINTER, WINFUNCTYPE, Structure, byref, c_long, c_void_p, create_unicode_buffer, wstring_at
|
||||
from ctypes import (
|
||||
windll, WINFUNCTYPE, Structure, byref, c_long, c_void_p, create_unicode_buffer, wstring_at
|
||||
)
|
||||
from ctypes.wintypes import (
|
||||
ATOM, BOOL, DWORD, HBRUSH, HGLOBAL, HICON, HINSTANCE, HMENU, HWND, INT, LPARAM, LPCWSTR, LPMSG, LPVOID, LPWSTR,
|
||||
ATOM, HBRUSH, HICON, HINSTANCE, HWND, INT, LPARAM, LPCWSTR, LPWSTR,
|
||||
MSG, UINT, WPARAM
|
||||
)
|
||||
import win32gui
|
||||
import win32con
|
||||
import win32api
|
||||
|
||||
class WNDCLASS(Structure):
|
||||
"""
|
||||
@ -98,41 +101,8 @@ if (config.auth_force_edmc_protocol # noqa: C901
|
||||
('lpszClassName', LPCWSTR)
|
||||
]
|
||||
|
||||
CW_USEDEFAULT = 0x80000000
|
||||
|
||||
CreateWindowExW = windll.user32.CreateWindowExW
|
||||
CreateWindowExW.argtypes = [DWORD, LPCWSTR, LPCWSTR, DWORD, INT, INT, INT, INT, HWND, HMENU, HINSTANCE, LPVOID]
|
||||
CreateWindowExW.restype = HWND
|
||||
RegisterClassW = windll.user32.RegisterClassW
|
||||
RegisterClassW.argtypes = [POINTER(WNDCLASS)]
|
||||
# DefWindowProcW
|
||||
# Ref: <https://learn.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-defwindowprocw>
|
||||
# LRESULT DefWindowProcW([in] HWND hWnd,[in] UINT Msg,[in] WPARAM wParam,[in] LPARAM lParam);
|
||||
# As per example at <https://docs.python.org/3/library/ctypes.html#ctypes.WINFUNCTYPE>
|
||||
|
||||
prototype = WINFUNCTYPE(c_long, HWND, UINT, WPARAM, LPARAM)
|
||||
paramflags = (1, "hWnd"), (1, "Msg"), (1, "wParam"), (1, "lParam")
|
||||
DefWindowProcW = prototype(("DefWindowProcW", windll.user32), paramflags)
|
||||
|
||||
GetParent = windll.user32.GetParent
|
||||
SetForegroundWindow = windll.user32.SetForegroundWindow
|
||||
|
||||
# <https://learn.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-getmessagew>
|
||||
# NB: Despite 'BOOL' return type, it *can* be >0, 0 or -1, so is actually
|
||||
# c_long
|
||||
prototype = WINFUNCTYPE(c_long, LPMSG, HWND, UINT, UINT)
|
||||
paramflags = (1, "lpMsg"), (1, "hWnd"), (1, "wMsgFilterMin"), (1, "wMsgFilterMax")
|
||||
GetMessageW = prototype(("GetMessageW", windll.user32), paramflags)
|
||||
|
||||
TranslateMessage = windll.user32.TranslateMessage
|
||||
DispatchMessageW = windll.user32.DispatchMessageW
|
||||
PostThreadMessageW = windll.user32.PostThreadMessageW
|
||||
SendMessageW = windll.user32.SendMessageW
|
||||
SendMessageW.argtypes = [HWND, UINT, WPARAM, LPARAM]
|
||||
PostMessageW = windll.user32.PostMessageW
|
||||
PostMessageW.argtypes = [HWND, UINT, WPARAM, LPARAM]
|
||||
|
||||
WM_QUIT = 0x0012
|
||||
# https://docs.microsoft.com/en-us/windows/win32/dataxchg/wm-dde-initiate
|
||||
WM_DDE_INITIATE = 0x03E0
|
||||
WM_DDE_TERMINATE = 0x03E1
|
||||
@ -148,12 +118,6 @@ if (config.auth_force_edmc_protocol # noqa: C901
|
||||
GlobalGetAtomNameW = windll.kernel32.GlobalGetAtomNameW
|
||||
GlobalGetAtomNameW.argtypes = [ATOM, LPWSTR, INT]
|
||||
GlobalGetAtomNameW.restype = UINT
|
||||
GlobalLock = windll.kernel32.GlobalLock
|
||||
GlobalLock.argtypes = [HGLOBAL]
|
||||
GlobalLock.restype = LPVOID
|
||||
GlobalUnlock = windll.kernel32.GlobalUnlock
|
||||
GlobalUnlock.argtypes = [HGLOBAL]
|
||||
GlobalUnlock.restype = BOOL
|
||||
|
||||
# Windows Message handler stuff (IPC)
|
||||
# https://docs.microsoft.com/en-us/previous-versions/windows/desktop/legacy/ms633573(v=vs.85)
|
||||
@ -171,7 +135,7 @@ if (config.auth_force_edmc_protocol # noqa: C901
|
||||
if message != WM_DDE_INITIATE:
|
||||
# Not a DDE init message, bail and tell windows to do the default
|
||||
# https://docs.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-defwindowproca?redirectedfrom=MSDN
|
||||
return DefWindowProcW(hwnd, message, wParam, lParam)
|
||||
return win32gui.DefWindowProc(hwnd, message, wParam, lParam)
|
||||
|
||||
service = create_unicode_buffer(256)
|
||||
topic = create_unicode_buffer(256)
|
||||
@ -196,7 +160,7 @@ if (config.auth_force_edmc_protocol # noqa: C901
|
||||
|
||||
if target_is_valid and topic_is_valid:
|
||||
# if everything is happy, send an acknowledgement of the DDE request
|
||||
SendMessageW(
|
||||
win32gui.SendMessage(
|
||||
wParam, WM_DDE_ACK, hwnd, PackDDElParam(WM_DDE_ACK, GlobalAddAtomW(appname), GlobalAddAtomW('System'))
|
||||
)
|
||||
|
||||
@ -229,7 +193,7 @@ if (config.auth_force_edmc_protocol # noqa: C901
|
||||
thread = self.thread
|
||||
if thread:
|
||||
self.thread = None
|
||||
PostThreadMessageW(thread.ident, WM_QUIT, 0, 0)
|
||||
win32api.PostThreadMessage(thread.ident, win32con.WM_QUIT, 0, 0)
|
||||
thread.join() # Wait for it to quit
|
||||
|
||||
def worker(self) -> None:
|
||||
@ -239,24 +203,25 @@ if (config.auth_force_edmc_protocol # noqa: C901
|
||||
wndclass.lpfnWndProc = WndProc
|
||||
wndclass.cbClsExtra = 0
|
||||
wndclass.cbWndExtra = 0
|
||||
wndclass.hInstance = windll.kernel32.GetModuleHandleW(0)
|
||||
wndclass.hInstance = win32gui.GetModuleHandle(0)
|
||||
wndclass.hIcon = None
|
||||
wndclass.hCursor = None
|
||||
wndclass.hbrBackground = None
|
||||
wndclass.lpszMenuName = None
|
||||
wndclass.lpszClassName = 'DDEServer'
|
||||
|
||||
if not RegisterClassW(byref(wndclass)):
|
||||
if not win32gui.RegisterClass(byref(wndclass)):
|
||||
print('Failed to register Dynamic Data Exchange for cAPI')
|
||||
return
|
||||
|
||||
# https://docs.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-createwindowexw
|
||||
hwnd = CreateWindowExW(
|
||||
hwnd = win32gui.CreateWindowEx(
|
||||
0, # dwExStyle
|
||||
wndclass.lpszClassName, # lpClassName
|
||||
"DDE Server", # lpWindowName
|
||||
0, # dwStyle
|
||||
CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, # X, Y, nWidth, nHeight
|
||||
# X, Y, nWidth, nHeight
|
||||
win32con.CW_USEDEFAULT, win32con.CW_USEDEFAULT, win32con.CW_USEDEFAULT, win32con.CW_USEDEFAULT,
|
||||
self.master.winfo_id(), # hWndParent # Don't use HWND_MESSAGE since the window won't get DDE broadcasts
|
||||
None, # hMenu
|
||||
wndclass.hInstance, # hInstance
|
||||
@ -276,13 +241,13 @@ if (config.auth_force_edmc_protocol # noqa: C901
|
||||
#
|
||||
# But it does actually work. Either getting a non-0 value and
|
||||
# entering the loop, or getting 0 and exiting it.
|
||||
while GetMessageW(byref(msg), None, 0, 0) != 0:
|
||||
while win32gui.GetMessage(byref(msg), None, 0, 0) != 0:
|
||||
logger.trace_if('frontier-auth.windows', f'DDE message of type: {msg.message}')
|
||||
if msg.message == WM_DDE_EXECUTE:
|
||||
# GlobalLock does some sort of "please dont move this?"
|
||||
# https://docs.microsoft.com/en-us/windows/win32/api/winbase/nf-winbase-globallock
|
||||
args = wstring_at(GlobalLock(msg.lParam)).strip()
|
||||
GlobalUnlock(msg.lParam) # Unlocks the GlobalLock-ed object
|
||||
args = wstring_at(win32gui.GlobalLock(msg.lParam)).strip()
|
||||
win32gui.GlobalUnlock(msg.lParam) # Unlocks the GlobalLock-ed object
|
||||
|
||||
if args.lower().startswith('open("') and args.endswith('")'):
|
||||
logger.trace_if('frontier-auth.windows', f'args are: {args}')
|
||||
@ -291,20 +256,20 @@ if (config.auth_force_edmc_protocol # noqa: C901
|
||||
logger.debug(f'Message starts with {self.redirect}')
|
||||
self.event(url)
|
||||
|
||||
SetForegroundWindow(GetParent(self.master.winfo_id())) # raise app window
|
||||
win32gui.SetForegroundWindow(win32gui.GetParent(self.master.winfo_id())) # raise app window
|
||||
# Send back a WM_DDE_ACK. this is _required_ with WM_DDE_EXECUTE
|
||||
PostMessageW(msg.wParam, WM_DDE_ACK, hwnd, PackDDElParam(WM_DDE_ACK, 0x80, msg.lParam))
|
||||
win32gui.PostMessage(msg.wParam, WM_DDE_ACK, hwnd, PackDDElParam(WM_DDE_ACK, 0x80, msg.lParam))
|
||||
|
||||
else:
|
||||
# Send back a WM_DDE_ACK. this is _required_ with WM_DDE_EXECUTE
|
||||
PostMessageW(msg.wParam, WM_DDE_ACK, hwnd, PackDDElParam(WM_DDE_ACK, 0, msg.lParam))
|
||||
win32gui.PostMessage(msg.wParam, WM_DDE_ACK, hwnd, PackDDElParam(WM_DDE_ACK, 0, msg.lParam))
|
||||
|
||||
elif msg.message == WM_DDE_TERMINATE:
|
||||
PostMessageW(msg.wParam, WM_DDE_TERMINATE, hwnd, 0)
|
||||
win32gui.PostMessage(msg.wParam, WM_DDE_TERMINATE, hwnd, 0)
|
||||
|
||||
else:
|
||||
TranslateMessage(byref(msg)) # "Translates virtual key messages into character messages" ???
|
||||
DispatchMessageW(byref(msg))
|
||||
win32gui.DispatchMessage(byref(msg))
|
||||
|
||||
|
||||
else: # Linux / Run from source
|
||||
|
@ -8,10 +8,10 @@ wheel
|
||||
setuptools==70.0.0
|
||||
|
||||
# Static analysis tools
|
||||
flake8==7.0.0
|
||||
flake8==7.1.0
|
||||
flake8-annotations-coverage==0.0.6
|
||||
flake8-cognitive-complexity==0.1.0
|
||||
flake8-comprehensions==3.14.0
|
||||
flake8-comprehensions==3.15.0
|
||||
flake8-docstrings==1.7.0
|
||||
flake8-json==24.4.0
|
||||
flake8-noqa==1.4.0
|
||||
@ -19,16 +19,16 @@ flake8-polyfill==1.0.2
|
||||
flake8-use-fstring==1.4
|
||||
|
||||
mypy==1.10.0
|
||||
pep8-naming==0.13.3
|
||||
safety==3.2.0
|
||||
types-requests==2.31.0.20240311
|
||||
pep8-naming==0.14.1
|
||||
safety==3.2.5
|
||||
types-requests==2.32.0.20240712
|
||||
types-pkg-resources==0.1.3
|
||||
|
||||
# Code formatting tools
|
||||
autopep8==2.2.0
|
||||
autopep8==2.3.1
|
||||
|
||||
# Git pre-commit checking
|
||||
pre-commit==3.7.1
|
||||
pre-commit==3.8.0
|
||||
|
||||
# HTML changelogs
|
||||
mistune==3.0.2
|
||||
@ -38,12 +38,10 @@ mistune==3.0.2
|
||||
py2exe==0.13.0.1; sys_platform == 'win32'
|
||||
|
||||
# Testing
|
||||
pytest==8.2.0
|
||||
pytest==8.3.2
|
||||
pytest-cov==5.0.0 # Pytest code coverage support
|
||||
coverage[toml]==7.5.0 # pytest-cov dep. This is here to ensure that it includes TOML support for pyproject.toml configs
|
||||
coverage[toml]==7.6.1 # pytest-cov dep. This is here to ensure that it includes TOML support for pyproject.toml configs
|
||||
coverage-conditional-plugin==0.9.0
|
||||
# For manipulating folder permissions and the like.
|
||||
pywin32==306; sys_platform == 'win32'
|
||||
|
||||
|
||||
# All of the normal requirements
|
||||
|
@ -1,5 +1,8 @@
|
||||
requests==2.32.3
|
||||
pillow==10.3.0
|
||||
watchdog==4.0.0
|
||||
pillow==10.4.0
|
||||
watchdog==4.0.1
|
||||
simplesystray==0.1.0; sys_platform == 'win32'
|
||||
semantic-version==2.10.0
|
||||
# For manipulating folder permissions and the like.
|
||||
pywin32==306; sys_platform == 'win32'
|
||||
psutil==6.0.0
|
||||
|
117
ships.json
117
ships.json
@ -1,119 +1,158 @@
|
||||
{
|
||||
"Adder": {
|
||||
"hullMass": 35
|
||||
"hullMass": 35,
|
||||
"reserveFuelCapacity": 0.36
|
||||
},
|
||||
"Alliance Challenger": {
|
||||
"hullMass": 450
|
||||
"hullMass": 450,
|
||||
"reserveFuelCapacity": 0.77
|
||||
},
|
||||
"Alliance Chieftain": {
|
||||
"hullMass": 400
|
||||
"hullMass": 400,
|
||||
"reserveFuelCapacity": 0.77
|
||||
},
|
||||
"Alliance Crusader": {
|
||||
"hullMass": 500
|
||||
"hullMass": 500,
|
||||
"reserveFuelCapacity": 0.77
|
||||
},
|
||||
"Anaconda": {
|
||||
"hullMass": 400
|
||||
"hullMass": 400,
|
||||
"reserveFuelCapacity": 1.07
|
||||
},
|
||||
"Asp Explorer": {
|
||||
"hullMass": 280
|
||||
"hullMass": 280,
|
||||
"reserveFuelCapacity": 0.63
|
||||
},
|
||||
"Asp Scout": {
|
||||
"hullMass": 150
|
||||
"hullMass": 150,
|
||||
"reserveFuelCapacity": 0.47
|
||||
},
|
||||
"Beluga Liner": {
|
||||
"hullMass": 950
|
||||
"hullMass": 950,
|
||||
"reserveFuelCapacity": 0.81
|
||||
},
|
||||
"Cobra MkIII": {
|
||||
"hullMass": 180
|
||||
"hullMass": 180,
|
||||
"reserveFuelCapacity": 0.49
|
||||
},
|
||||
"Cobra MkIV": {
|
||||
"hullMass": 210
|
||||
"hullMass": 210,
|
||||
"reserveFuelCapacity": 0.51
|
||||
},
|
||||
"Diamondback Explorer": {
|
||||
"hullMass": 260
|
||||
"hullMass": 260,
|
||||
"reserveFuelCapacity": 0.52
|
||||
},
|
||||
"Diamondback Scout": {
|
||||
"hullMass": 170
|
||||
"hullMass": 170,
|
||||
"reserveFuelCapacity": 0.49
|
||||
},
|
||||
"Dolphin": {
|
||||
"hullMass": 140
|
||||
"hullMass": 140,
|
||||
"reserveFuelCapacity": 0.5
|
||||
},
|
||||
"Eagle": {
|
||||
"hullMass": 50
|
||||
"hullMass": 50,
|
||||
"reserveFuelCapacity": 0.34
|
||||
},
|
||||
"Federal Assault Ship": {
|
||||
"hullMass": 480
|
||||
"hullMass": 480,
|
||||
"reserveFuelCapacity": 0.72
|
||||
},
|
||||
"Federal Corvette": {
|
||||
"hullMass": 900
|
||||
"hullMass": 900,
|
||||
"reserveFuelCapacity": 1.13
|
||||
},
|
||||
"Federal Dropship": {
|
||||
"hullMass": 580
|
||||
"hullMass": 580,
|
||||
"reserveFuelCapacity": 0.83
|
||||
},
|
||||
"Federal Gunship": {
|
||||
"hullMass": 580
|
||||
"hullMass": 580,
|
||||
"reserveFuelCapacity": 0.82
|
||||
},
|
||||
"Fer-de-Lance": {
|
||||
"hullMass": 250
|
||||
"hullMass": 250,
|
||||
"reserveFuelCapacity": 0.67
|
||||
},
|
||||
"Hauler": {
|
||||
"hullMass": 14
|
||||
"hullMass": 14,
|
||||
"reserveFuelCapacity": 0.25
|
||||
},
|
||||
"Imperial Clipper": {
|
||||
"hullMass": 400
|
||||
"hullMass": 400,
|
||||
"reserveFuelCapacity": 0.74
|
||||
},
|
||||
"Imperial Courier": {
|
||||
"hullMass": 35
|
||||
"hullMass": 35,
|
||||
"reserveFuelCapacity": 0.41
|
||||
},
|
||||
"Imperial Cutter": {
|
||||
"hullMass": 1100
|
||||
"hullMass": 1100,
|
||||
"reserveFuelCapacity": 1.16
|
||||
},
|
||||
"Imperial Eagle": {
|
||||
"hullMass": 50
|
||||
"hullMass": 50,
|
||||
"reserveFuelCapacity": 0.37
|
||||
},
|
||||
"Keelback": {
|
||||
"hullMass": 180
|
||||
"hullMass": 180,
|
||||
"reserveFuelCapacity": 0.39
|
||||
},
|
||||
"Krait MkII": {
|
||||
"hullMass": 320
|
||||
"hullMass": 320,
|
||||
"reserveFuelCapacity": 0.63
|
||||
},
|
||||
"Krait Phantom": {
|
||||
"hullMass": 270
|
||||
"hullMass": 270,
|
||||
"reserveFuelCapacity": 0.63
|
||||
},
|
||||
"Mamba": {
|
||||
"hullMass": 250
|
||||
"hullMass": 250,
|
||||
"reserveFuelCapacity": 0.5
|
||||
},
|
||||
"Orca": {
|
||||
"hullMass": 290
|
||||
"hullMass": 290,
|
||||
"reserveFuelCapacity": 0.79
|
||||
},
|
||||
"Python": {
|
||||
"hullMass": 350
|
||||
"hullMass": 350,
|
||||
"reserveFuelCapacity": 0.83
|
||||
},
|
||||
"Python Mk II": {
|
||||
"hullMass": 450
|
||||
"hullMass": 450,
|
||||
"reserveFuelCapacity": 0.83
|
||||
},
|
||||
"Sidewinder": {
|
||||
"hullMass": 25
|
||||
"hullMass": 25,
|
||||
"reserveFuelCapacity": 0.3
|
||||
},
|
||||
"Type-10 Defender": {
|
||||
"hullMass": 1200
|
||||
"hullMass": 1200,
|
||||
"reserveFuelCapacity": 0.77
|
||||
},
|
||||
"Type-6 Transporter": {
|
||||
"hullMass": 155
|
||||
"hullMass": 155,
|
||||
"reserveFuelCapacity": 0.39
|
||||
},
|
||||
"Type-7 Transporter": {
|
||||
"hullMass": 350
|
||||
"hullMass": 350,
|
||||
"reserveFuelCapacity": 0.52
|
||||
},
|
||||
"Type-9 Heavy": {
|
||||
"hullMass": 850
|
||||
"hullMass": 850,
|
||||
"reserveFuelCapacity": 0.77
|
||||
},
|
||||
"Viper MkIII": {
|
||||
"hullMass": 50
|
||||
"hullMass": 50,
|
||||
"reserveFuelCapacity": 0.41
|
||||
},
|
||||
"Viper MkIV": {
|
||||
"hullMass": 190
|
||||
"hullMass": 190,
|
||||
"reserveFuelCapacity": 0.46
|
||||
},
|
||||
"Vulture": {
|
||||
"hullMass": 230
|
||||
"hullMass": 230,
|
||||
"reserveFuelCapacity": 0.57
|
||||
}
|
||||
}
|
19
stats.py
19
stats.py
@ -25,17 +25,15 @@ logger = EDMCLogging.get_main_logger()
|
||||
|
||||
if sys.platform == 'win32':
|
||||
import ctypes
|
||||
from ctypes.wintypes import HWND, POINT, RECT, SIZE, UINT
|
||||
from ctypes.wintypes import POINT, RECT, SIZE, UINT, BOOL
|
||||
import win32gui
|
||||
|
||||
try:
|
||||
CalculatePopupWindowPosition = ctypes.windll.user32.CalculatePopupWindowPosition
|
||||
CalculatePopupWindowPosition.argtypes = [
|
||||
ctypes.POINTER(POINT), ctypes.POINTER(SIZE), UINT, ctypes.POINTER(RECT), ctypes.POINTER(RECT)
|
||||
]
|
||||
GetParent = ctypes.windll.user32.GetParent
|
||||
GetParent.argtypes = [HWND]
|
||||
GetWindowRect = ctypes.windll.user32.GetWindowRect
|
||||
GetWindowRect.argtypes = [HWND, ctypes.POINTER(RECT)]
|
||||
CalculatePopupWindowPosition.restype = BOOL
|
||||
|
||||
except Exception: # Not supported under Wine 4.0
|
||||
CalculatePopupWindowPosition = None # type: ignore
|
||||
@ -243,7 +241,7 @@ def ships(companion_data: dict[str, Any]) -> list[ShipRet]:
|
||||
"""
|
||||
Return a list of 5 tuples of ship information.
|
||||
|
||||
:param data: [description]
|
||||
:param companion_data: [description]
|
||||
:return: A 5 tuple of strings containing: Ship ID, Ship Type Name (internal), Ship Name, System, Station, and Value
|
||||
"""
|
||||
ships: list[dict[str, Any]] = companion.listify(cast(list, companion_data.get('ships')))
|
||||
@ -253,16 +251,15 @@ def ships(companion_data: dict[str, Any]) -> list[ShipRet]:
|
||||
ships.insert(0, ships.pop(current)) # Put current ship first
|
||||
|
||||
if not companion_data['commander'].get('docked'):
|
||||
out: list[ShipRet] = []
|
||||
# Set current system, not last docked
|
||||
out.append(ShipRet(
|
||||
out: list[ShipRet] = [ShipRet(
|
||||
id=str(ships[0]['id']),
|
||||
type=ship_name_map.get(ships[0]['name'].lower(), ships[0]['name']),
|
||||
name=str(ships[0].get('shipName', '')),
|
||||
system=companion_data['lastSystem']['name'],
|
||||
station='',
|
||||
value=str(ships[0]['value']['total'])
|
||||
))
|
||||
)]
|
||||
out.extend(
|
||||
ShipRet(
|
||||
id=str(ship['id']),
|
||||
@ -302,7 +299,7 @@ def export_ships(companion_data: dict[str, Any], filename: AnyStr) -> None:
|
||||
h.writerow(list(thing))
|
||||
|
||||
|
||||
class StatsDialog():
|
||||
class StatsDialog:
|
||||
"""Status dialog containing all of the current cmdr's stats."""
|
||||
|
||||
def __init__(self, parent: tk.Tk, status: tk.Label) -> None:
|
||||
@ -423,7 +420,7 @@ class StatsResults(tk.Toplevel):
|
||||
# Ensure fully on-screen
|
||||
if sys.platform == 'win32' and CalculatePopupWindowPosition:
|
||||
position = RECT()
|
||||
GetWindowRect(GetParent(self.winfo_id()), position)
|
||||
win32gui.GetWindowRect(win32gui.GetParent(self.winfo_id()))
|
||||
if CalculatePopupWindowPosition(
|
||||
POINT(parent.winfo_rootx(), parent.winfo_rooty()),
|
||||
# - is evidently supported on the C side
|
||||
|
25
theme.py
25
theme.py
@ -13,7 +13,6 @@ from __future__ import annotations
|
||||
import os
|
||||
import sys
|
||||
import tkinter as tk
|
||||
from os.path import join
|
||||
from tkinter import font as tk_font
|
||||
from tkinter import ttk
|
||||
from typing import Callable
|
||||
@ -34,11 +33,13 @@ if sys.platform == "linux":
|
||||
if sys.platform == 'win32':
|
||||
import ctypes
|
||||
from ctypes.wintypes import DWORD, LPCVOID, LPCWSTR
|
||||
import win32gui
|
||||
AddFontResourceEx = ctypes.windll.gdi32.AddFontResourceExW
|
||||
AddFontResourceEx.restypes = [LPCWSTR, DWORD, LPCVOID] # type: ignore
|
||||
FR_PRIVATE = 0x10
|
||||
FR_NOT_ENUM = 0x20
|
||||
AddFontResourceEx(join(config.respath, 'EUROCAPS.TTF'), FR_PRIVATE, 0)
|
||||
font_path = config.respath_path / 'EUROCAPS.TTF'
|
||||
AddFontResourceEx(str(font_path), FR_PRIVATE, 0)
|
||||
|
||||
elif sys.platform == 'linux':
|
||||
# pyright: reportUnboundVariable=false
|
||||
@ -421,14 +422,7 @@ class _Theme:
|
||||
self.active = theme
|
||||
|
||||
if sys.platform == 'win32':
|
||||
GWL_STYLE = -16 # noqa: N806 # ctypes
|
||||
WS_MAXIMIZEBOX = 0x00010000 # noqa: N806 # ctypes
|
||||
# tk8.5.9/win/tkWinWm.c:342
|
||||
GWL_EXSTYLE = -20 # noqa: N806 # ctypes
|
||||
WS_EX_APPWINDOW = 0x00040000 # noqa: N806 # ctypes
|
||||
WS_EX_LAYERED = 0x00080000 # noqa: N806 # ctypes
|
||||
GetWindowLongW = ctypes.windll.user32.GetWindowLongW # noqa: N806 # ctypes
|
||||
SetWindowLongW = ctypes.windll.user32.SetWindowLongW # noqa: N806 # ctypes
|
||||
import win32con
|
||||
|
||||
# FIXME: Lose the "treat this like a boolean" bullshit
|
||||
if theme == self.THEME_DEFAULT:
|
||||
@ -445,14 +439,17 @@ class _Theme:
|
||||
|
||||
root.withdraw()
|
||||
root.update_idletasks() # Size and windows styles get recalculated here
|
||||
hwnd = ctypes.windll.user32.GetParent(root.winfo_id())
|
||||
SetWindowLongW(hwnd, GWL_STYLE, GetWindowLongW(hwnd, GWL_STYLE) & ~WS_MAXIMIZEBOX) # disable maximize
|
||||
hwnd = win32gui.GetParent(root.winfo_id())
|
||||
win32gui.SetWindowLong(hwnd, win32con.GWL_STYLE,
|
||||
win32gui.GetWindowLong(hwnd, win32con.GWL_STYLE)
|
||||
& ~win32con.WS_MAXIMIZEBOX) # disable maximize
|
||||
|
||||
if theme == self.THEME_TRANSPARENT:
|
||||
SetWindowLongW(hwnd, GWL_EXSTYLE, WS_EX_APPWINDOW | WS_EX_LAYERED) # Add to taskbar
|
||||
win32gui.SetWindowLong(hwnd, win32con.GWL_EXSTYLE,
|
||||
win32con.WS_EX_APPWINDOW | win32con.WS_EX_LAYERED) # Add to taskbar
|
||||
|
||||
else:
|
||||
SetWindowLongW(hwnd, GWL_EXSTYLE, WS_EX_APPWINDOW) # Add to taskbar
|
||||
win32gui.SetWindowLong(hwnd, win32con.GWL_EXSTYLE, win32con.WS_EX_APPWINDOW) # Add to taskbar
|
||||
|
||||
root.deiconify()
|
||||
root.wait_visibility() # need main window to be displayed before returning
|
||||
|
@ -28,7 +28,6 @@ from tkinter import font as tk_font
|
||||
from tkinter import ttk
|
||||
from typing import Any
|
||||
import plug
|
||||
from os import path
|
||||
from config import config, logger
|
||||
from l10n import translations as tr
|
||||
from monitor import monitor
|
||||
@ -96,7 +95,7 @@ class HyperlinkLabel(tk.Label or ttk.Label): # type: ignore
|
||||
else:
|
||||
# Avoid file length limits if possible
|
||||
target = plug.invoke(url, '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(
|
||||
@ -194,6 +193,10 @@ class HyperlinkLabel(tk.Label or ttk.Label): # type: ignore
|
||||
menu.add_command(label=tr.tl('Copy'), command=self.copy) # As in Copy and Paste
|
||||
|
||||
if self.name == 'ship':
|
||||
# LANG: Copy the Inara SLEF Format of the active ship to the clipboard
|
||||
menu.add_command(label=tr.tl('Copy Inara SLEF'), command=self.copy_slef, state=tk.DISABLED)
|
||||
menu.entryconfigure(1, state=monitor.slef and tk.NORMAL or tk.DISABLED)
|
||||
|
||||
menu.add_separator()
|
||||
for url in plug.provides('shipyard_url'):
|
||||
menu.add_command(
|
||||
@ -224,3 +227,8 @@ class HyperlinkLabel(tk.Label or ttk.Label): # type: ignore
|
||||
"""Copy the current text to the clipboard."""
|
||||
self.clipboard_clear()
|
||||
self.clipboard_append(self['text'])
|
||||
|
||||
def copy_slef(self) -> None:
|
||||
"""Copy the current text to the clipboard."""
|
||||
self.clipboard_clear()
|
||||
self.clipboard_append(monitor.slef)
|
||||
|
Loading…
x
Reference in New Issue
Block a user