From 791a0c80c2c0b202d27a636c36302229af88c328 Mon Sep 17 00:00:00 2001
From: David Sangrey <rixxan@hullseals.space>
Date: Wed, 27 Mar 2024 18:36:03 -0400
Subject: [PATCH 01/22] [Minor] Remove Deprecated Files and Functions

---
 build.py             |   2 --
 modules.p            | Bin 44802 -> 0 bytes
 ships.p              | Bin 920 -> 0 bytes
 ttkHyperlinkLabel.py |  41 -----------------------------------------
 4 files changed, 43 deletions(-)
 delete mode 100644 modules.p
 delete mode 100644 ships.p

diff --git a/build.py b/build.py
index 1bc96765..904c4c5b 100644
--- a/build.py
+++ b/build.py
@@ -76,10 +76,8 @@ def generate_data_files(
                 "ChangeLog.md",
                 "snd_good.wav",
                 "snd_bad.wav",
-                "modules.p",  # TODO: Remove in 6.0
                 "modules.json",
                 "ships.json",
-                "ships.p",  # TODO: Remove in 6.0
                 f"{app_name}.ico",
                 f"resources/{appcmdname}.ico",
                 "EDMarketConnector - TRACE.bat",
diff --git a/modules.p b/modules.p
deleted file mode 100644
index c48f76c4894fdb061e0a1967f388afe819e679c4..0000000000000000000000000000000000000000
GIT binary patch
literal 0
HcmV?d00001

literal 44802
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

diff --git a/ships.p b/ships.p
deleted file mode 100644
index d289497c9540f529b38d01a580f36eaafcd68c26..0000000000000000000000000000000000000000
GIT binary patch
literal 0
HcmV?d00001

literal 920
zcmZXSzmC&D5XON^LP$am4hrZjXrKrJ3J@J8zT+aty3^&95@}=a#jD%wT08556i5`5
zi-d%s;SH!L5G4hVz!OmM1XNUvy|$ABSJwRI&o?{YtH%2`8)u5obVJEG4K<SjGSl5t
z5mhwuSg5mi+3kbumT9^X#<G{0X~!w&0RhPTn_#ZEoHHUq>I`FoJ;c1oid(_=vtYK?
zD5FQ3Fo7))zD0O>1yu^fwnz3GS=$vPlp-Rl^qvcev;89BQdJ|aVdjNAJPBp0i&Xyu
znBEi0(>Za*OjMa)H^6j<a;AtA9FImLoBIV!rw&eS><5^u9wQ4JJ|p4rKNB0d?sdF2
z`4gD)p5#efF6kRE&22L0)ONm)op&g%Nx0(zkd$j@60@YB!B6abslr34PN>$lFnEpP
zYvd`JKwagX1G8CGJx#@F(%YeG5YfRnKadsi2TW@hN0$%d_p$Y8kx<1@)sV9Uf32YW
zL~BT<id98;_svyNibBhpaRW@hCZ~6A`;_whNv)9l?R`a<E)HTxKE{z3ZE!y(Ld%7X
z_c7iK$YN&g;NudFJr$PbxPi2_ztpiT&Gifeoe7KRb0#WR`PXrq!(~DT_wG6#J)$)U
zKBMTZ0zGgJ6%mj~Rc+*dtk}c<Sdh=@6@`zST}n=tR_#IVwJF1gT=yDYzf%WGuYE(u
POwk6ZDzEbk(mQ_vKH60e

diff --git a/ttkHyperlinkLabel.py b/ttkHyperlinkLabel.py
index 0cc19b4e..0ca87fc7 100644
--- a/ttkHyperlinkLabel.py
+++ b/ttkHyperlinkLabel.py
@@ -146,44 +146,3 @@ class HyperlinkLabel(sys.platform == 'darwin' and tk.Label or ttk.Label):  # typ
         """Copy the current text to the clipboard."""
         self.clipboard_clear()
         self.clipboard_append(self['text'])
-
-
-def openurl(url: str) -> None:
-    r"""
-    Open the given URL in appropriate browser.
-
-    2022-12-06:
-    Firefox itself will gladly attempt to use very long URLs in its URL
-    input.  Up to 16384 was attempted, but the Apache instance this was
-    tested against only allowed up to 8207 total URL length to pass, that
-    being 8190 octets of REQUEST_URI (path + GET params).
-
-    Testing from Windows 10 Home 21H2 cmd.exe with:
-
-        "<path to>\firefox.exe" -osint -url "<test url>"
-
-    only allowed 8115 octest of REQUEST_URI to pass through.
-
-    Microsoft Edge yielded 8092 octets.  Google Chrome yielded 8093 octets.
-
-    However, this is actually the limit of how long a CMD.EXE command-line
-    can be.  The URL was being cut off *there*.
-
-    The 8207 octet URL makes it through `webbrowser.open(<url>)` to:
-
-        Firefox 107.0.1
-        Microsoft Edge 108.0.1462.42
-        Google Chrome 108.0.5359.95
-
-    This was also tested as working *with* the old winreg/subprocess code,
-    so it wasn't even suffering from the same limit as CMD.EXE.
-
-    Conclusion: No reason to not just use `webbrowser.open()`, as prior
-    to e280d6c2833c25867b8139490e68ddf056477917 there was a bug, introduced
-    in 5989acd0d3263e54429ff99769ff73a20476d863, which meant the code always
-    ended up using `webbrowser.open()` *anyway*.
-    :param url: URL to open.
-    """
-    warnings.warn("This function is deprecated. "
-                  "Please use `webbrowser.open() instead.", DeprecationWarning, stacklevel=2)
-    webbrowser.open(url)

From 57b6ecd88ea9bd989518ddc676db13753ba74973 Mon Sep 17 00:00:00 2001
From: David Sangrey <rixxan@hullseals.space>
Date: Wed, 27 Mar 2024 19:16:45 -0400
Subject: [PATCH 02/22] [2186] Remove Config and Hotkey

---
 config/__init__.py |   3 -
 config/darwin.py   | 191 -------------------------------
 hotkey/__init__.py |   4 -
 hotkey/darwin.py   | 276 ---------------------------------------------
 4 files changed, 474 deletions(-)
 delete mode 100644 config/darwin.py
 delete mode 100644 hotkey/darwin.py

diff --git a/config/__init__.py b/config/__init__.py
index 3acb2edd..992710a0 100644
--- a/config/__init__.py
+++ b/config/__init__.py
@@ -468,9 +468,6 @@ def get_config(*args, **kwargs) -> AbstractConfig:
     :param kwargs: Args to be passed through to implementation.
     :return: Instance of the implementation.
     """
-    if sys.platform == "darwin":  # pragma: sys-platform-darwin
-        from .darwin import MacConfig
-        return MacConfig(*args, **kwargs)
 
     if sys.platform == "win32":  # pragma: sys-platform-win32
         from .windows import WinConfig
diff --git a/config/darwin.py b/config/darwin.py
deleted file mode 100644
index 9c15ec32..00000000
--- a/config/darwin.py
+++ /dev/null
@@ -1,191 +0,0 @@
-"""
-darwin.py - Darwin/macOS implementation of AbstractConfig.
-
-Copyright (c) EDCD, All Rights Reserved
-Licensed under the GNU General Public License.
-See LICENSE file.
-"""
-from __future__ import annotations
-
-import pathlib
-import sys
-from typing import Any
-from Foundation import (  # type: ignore
-    NSApplicationSupportDirectory, NSBundle, NSDocumentDirectory, NSSearchPathForDirectoriesInDomains, NSUserDefaults,
-    NSUserDomainMask
-)
-from config import AbstractConfig, appname, logger
-
-assert sys.platform == 'darwin'
-
-
-class MacConfig(AbstractConfig):
-    """MacConfig is the implementation of AbstractConfig for Darwin based OSes."""
-
-    def __init__(self) -> None:
-        super().__init__()
-        support_path = pathlib.Path(
-            NSSearchPathForDirectoriesInDomains(
-                NSApplicationSupportDirectory, NSUserDomainMask, True
-            )[0]
-        )
-
-        self.app_dir_path = support_path / appname
-        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)
-
-        # Bundle IDs identify a singled app though out a system
-
-        if getattr(sys, 'frozen', False):
-            exe_dir = pathlib.Path(sys.executable).parent
-            self.internal_plugin_dir_path = exe_dir.parent / 'Library' / 'plugins'
-            self.respath_path = exe_dir.parent / 'Resources'
-            self.identifier = NSBundle.mainBundle().bundleIdentifier()
-
-        else:
-            file_dir = pathlib.Path(__file__).parent.parent
-            self.internal_plugin_dir_path = file_dir / 'plugins'
-            self.respath_path = file_dir
-
-            self.identifier = f'uk.org.marginal.{appname.lower()}'
-            NSBundle.mainBundle().infoDictionary()['CFBundleIdentifier'] = self.identifier
-
-        self.default_journal_dir_path = support_path / 'Frontier Developments' / 'Elite Dangerous'
-        self._defaults: Any = NSUserDefaults.standardUserDefaults()
-        self._settings: dict[str, int | str | list] = dict(
-            self._defaults.persistentDomainForName_(self.identifier) or {}
-        )  # make writeable
-
-        if (out_dir := self.get_str('out_dir')) is None or not pathlib.Path(out_dir).exists():
-            self.set('outdir', NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, True)[0])
-
-    def __raw_get(self, key: str) -> None | list | str | int:
-        """
-        Retrieve the raw data for the given key.
-
-        :param str: str - The key data is being requested for.
-        :return: The requested data.
-        """
-        res = self._settings.get(key)
-        # On MacOS Catalina, with python.org python 3.9.2 any 'list'
-        # has type __NSCFArray so a simple `isinstance(res, list)` is
-        # False.  So, check it's not-None, and not the other types.
-        #
-        # If we can find where to import the definition of NSCFArray
-        # then we could possibly test against that.
-        if res is not None and not isinstance(res, str) and not isinstance(res, int):
-            return list(res)
-
-        return res
-
-    def get_str(self, key: str, *, default: str = None) -> str:
-        """
-        Return the string referred to by the given key if it exists, or the default.
-
-        Implements :meth:`AbstractConfig.get_str`.
-        """
-        res = self.__raw_get(key)
-        if res is None:
-            return default  # Yes it could be None, but we're _assuming_ that people gave us a default
-
-        if not isinstance(res, str):
-            raise ValueError(f'unexpected data returned from __raw_get: {type(res)=} {res}')
-
-        return res
-
-    def get_list(self, key: str, *, default: list = None) -> list:
-        """
-        Return the list referred to by the given key if it exists, or the default.
-
-        Implements :meth:`AbstractConfig.get_list`.
-        """
-        res = self.__raw_get(key)
-        if res is None:
-            return default  # Yes it could be None, but we're _assuming_ that people gave us a default
-
-        if not isinstance(res, list):
-            raise ValueError(f'__raw_get returned unexpected type {type(res)=} {res!r}')
-
-        return res
-
-    def get_int(self, key: str, *, default: int = 0) -> int:
-        """
-        Return the int referred to by key if it exists in the config.
-
-        Implements :meth:`AbstractConfig.get_int`.
-        """
-        res = self.__raw_get(key)
-        if res is None:
-            return default
-
-        if not isinstance(res, (str, int)):
-            raise ValueError(f'__raw_get returned unexpected type {type(res)=} {res!r}')
-
-        try:
-            return int(res)
-
-        except ValueError as e:
-            logger.error(f'__raw_get returned {res!r} which cannot be parsed to an int: {e}')
-            return default  # Yes it could be None, but we're _assuming_ that people gave us a default
-
-    def get_bool(self, key: str, *, default: bool = None) -> bool:
-        """
-        Return the bool referred to by the given key if it exists, or the default.
-
-        Implements :meth:`AbstractConfig.get_bool`.
-        """
-        res = self.__raw_get(key)
-        if res is None:
-            return default  # Yes it could be None, but we're _assuming_ that people gave us a default
-
-        if not isinstance(res, bool):
-            raise ValueError(f'__raw_get returned unexpected type {type(res)=} {res!r}')
-
-        return res
-
-    def set(self, key: str, val: int | str | list[str] | bool) -> None:
-        """
-        Set the given key's data to the given value.
-
-        Implements :meth:`AbstractConfig.set`.
-        """
-        if self._settings is None:
-            raise ValueError('attempt to use a closed _settings')
-
-        if not isinstance(val, (bool, str, int, list)):
-            raise ValueError(f'Unexpected type for value {type(val)=}')
-
-        self._settings[key] = val
-
-    def delete(self, key: str, *, suppress=False) -> None:
-        """
-        Delete the given key from the config.
-
-        Implements :meth:`AbstractConfig.delete`.
-        """
-        try:
-            del self._settings[key]
-
-        except Exception:
-            if suppress:
-                pass
-
-    def save(self) -> None:
-        """
-        Save the current configuration.
-
-        Implements :meth:`AbstractConfig.save`.
-        """
-        self._defaults.setPersistentDomain_forName_(self._settings, self.identifier)
-        self._defaults.synchronize()
-
-    def close(self) -> None:
-        """
-        Close this config and release any associated resources.
-
-        Implements :meth:`AbstractConfig.close`.
-        """
-        self.save()
-        self._defaults = None
diff --git a/hotkey/__init__.py b/hotkey/__init__.py
index e7515807..313415f9 100644
--- a/hotkey/__init__.py
+++ b/hotkey/__init__.py
@@ -76,10 +76,6 @@ def get_hotkeymgr() -> AbstractHotkeyMgr:
     :return: Appropriate class instance.
     :raises ValueError: If unsupported platform.
     """
-    if sys.platform == 'darwin':
-        from hotkey.darwin import MacHotkeyMgr
-        return MacHotkeyMgr()
-
     if sys.platform == 'win32':
         from hotkey.windows import WindowsHotkeyMgr
         return WindowsHotkeyMgr()
diff --git a/hotkey/darwin.py b/hotkey/darwin.py
deleted file mode 100644
index 6afd0239..00000000
--- a/hotkey/darwin.py
+++ /dev/null
@@ -1,276 +0,0 @@
-"""darwin/macOS implementation of hotkey.AbstractHotkeyMgr."""
-from __future__ import annotations
-
-import pathlib
-import sys
-import tkinter as tk
-from typing import Callable
-assert sys.platform == 'darwin'
-
-import objc
-from AppKit import (
-    NSAlternateKeyMask, NSApplication, NSBeep, NSClearLineFunctionKey, NSCommandKeyMask, NSControlKeyMask,
-    NSDeleteFunctionKey, NSDeviceIndependentModifierFlagsMask, NSEvent, NSF1FunctionKey, NSF35FunctionKey,
-    NSFlagsChanged, NSKeyDown, NSKeyDownMask, NSKeyUp, NSNumericPadKeyMask, NSShiftKeyMask, NSSound, NSWorkspace
-)
-
-from config import config
-from EDMCLogging import get_main_logger
-from hotkey import AbstractHotkeyMgr
-
-logger = get_main_logger()
-
-
-class MacHotkeyMgr(AbstractHotkeyMgr):
-    """Hot key management."""
-
-    POLL = 250
-    # https://developer.apple.com/library/mac/documentation/Cocoa/Reference/ApplicationKit/Classes/NSEvent_Class/#//apple_ref/doc/constant_group/Function_Key_Unicodes
-    DISPLAY = {
-        0x03: u'⌅', 0x09: u'⇥', 0xd: u'↩', 0x19: u'⇤', 0x1b: u'esc', 0x20: u'⏘', 0x7f: u'⌫',
-        0xf700: u'↑', 0xf701: u'↓', 0xf702: u'←', 0xf703: u'→',
-        0xf727: u'Ins',
-        0xf728: u'⌦', 0xf729: u'↖', 0xf72a: u'Fn', 0xf72b: u'↘',
-        0xf72c: u'⇞', 0xf72d: u'⇟', 0xf72e: u'PrtScr', 0xf72f: u'ScrollLock',
-        0xf730: u'Pause', 0xf731: u'SysReq', 0xf732: u'Break', 0xf733: u'Reset',
-        0xf739: u'⌧',
-    }
-    (ACQUIRE_INACTIVE, ACQUIRE_ACTIVE, ACQUIRE_NEW) = range(3)
-
-    def __init__(self):
-        self.MODIFIERMASK = NSShiftKeyMask | NSControlKeyMask | NSAlternateKeyMask | NSCommandKeyMask \
-            | NSNumericPadKeyMask
-        self.root: tk.Tk
-
-        self.keycode = 0
-        self.modifiers = 0
-        self.activated = False
-        self.observer = None
-
-        self.acquire_key = 0
-        self.acquire_state = MacHotkeyMgr.ACQUIRE_INACTIVE
-
-        self.tkProcessKeyEvent_old: Callable
-
-        self.snd_good = NSSound.alloc().initWithContentsOfFile_byReference_(
-            pathlib.Path(config.respath_path) / 'snd_good.wav', False
-        )
-        self.snd_bad = NSSound.alloc().initWithContentsOfFile_byReference_(
-            pathlib.Path(config.respath_path) / 'snd_bad.wav', False
-        )
-
-    def register(self, root: tk.Tk, keycode: int, modifiers: int) -> None:
-        """
-        Register current hotkey for monitoring.
-
-        :param root: parent window.
-        :param keycode: Key to monitor.
-        :param modifiers: Any modifiers to take into account.
-        """
-        self.root = root
-        self.keycode = keycode
-        self.modifiers = modifiers
-        self.activated = False
-
-        if keycode:
-            if not self.observer:
-                self.root.after_idle(self._observe)
-            self.root.after(MacHotkeyMgr.POLL, self._poll)
-
-        # Monkey-patch tk (tkMacOSXKeyEvent.c)
-        if not callable(self.tkProcessKeyEvent_old):
-            sel = b'tkProcessKeyEvent:'
-            cls = NSApplication.sharedApplication().class__()  # type: ignore
-            self.tkProcessKeyEvent_old = NSApplication.sharedApplication().methodForSelector_(sel)  # type: ignore
-            newmethod = objc.selector(  # type: ignore
-                self.tkProcessKeyEvent,
-                selector=self.tkProcessKeyEvent_old.selector,
-                signature=self.tkProcessKeyEvent_old.signature
-            )
-            objc.classAddMethod(cls, sel, newmethod)  # type: ignore
-
-    def tkProcessKeyEvent(self, cls, the_event):  # noqa: N802
-        """
-        Monkey-patch tk (tkMacOSXKeyEvent.c).
-
-        - workaround crash on OSX 10.9 & 10.10 on seeing a composing character
-        - notice when modifier key state changes
-        - keep a copy of NSEvent.charactersIgnoringModifiers, which is what we need for the hotkey
-
-        (Would like to use a decorator but need to ensure the application is created before this is installed)
-        :param cls: ???
-        :param the_event: tk event
-        :return: ???
-        """
-        if self.acquire_state:
-            if the_event.type() == NSFlagsChanged:
-                self.acquire_key = the_event.modifierFlags() & NSDeviceIndependentModifierFlagsMask
-                self.acquire_state = MacHotkeyMgr.ACQUIRE_NEW
-                # suppress the event by not chaining the old function
-                return the_event
-
-            if the_event.type() in (NSKeyDown, NSKeyUp):
-                c = the_event.charactersIgnoringModifiers()
-                self.acquire_key = (c and ord(c[0]) or 0) | \
-                                   (the_event.modifierFlags() & NSDeviceIndependentModifierFlagsMask)
-                self.acquire_state = MacHotkeyMgr.ACQUIRE_NEW
-                # suppress the event by not chaining the old function
-                return the_event
-
-        # replace empty characters with charactersIgnoringModifiers to avoid crash
-        elif the_event.type() in (NSKeyDown, NSKeyUp) and not the_event.characters():
-            the_event = NSEvent.keyEventWithType_location_modifierFlags_timestamp_windowNumber_context_characters_charactersIgnoringModifiers_isARepeat_keyCode_(  # noqa: E501
-                # noqa: E501
-                the_event.type(),
-                the_event.locationInWindow(),
-                the_event.modifierFlags(),
-                the_event.timestamp(),
-                the_event.windowNumber(),
-                the_event.context(),
-                the_event.charactersIgnoringModifiers(),
-                the_event.charactersIgnoringModifiers(),
-                the_event.isARepeat(),
-                the_event.keyCode()
-            )
-        return self.tkProcessKeyEvent_old(cls, the_event)
-
-    def _observe(self):
-        # Must be called after root.mainloop() so that the app's message loop has been created
-        self.observer = NSEvent.addGlobalMonitorForEventsMatchingMask_handler_(NSKeyDownMask, self._handler)
-
-    def _poll(self):
-        if config.shutting_down:
-            return
-
-        # No way of signalling to Tkinter from within the callback handler block that doesn't
-        # cause Python to crash, so poll.
-        if self.activated:
-            self.activated = False
-            self.root.event_generate('<<Invoke>>', when="tail")
-
-        if self.keycode or self.modifiers:
-            self.root.after(MacHotkeyMgr.POLL, self._poll)
-
-    def unregister(self) -> None:
-        """Remove hotkey registration."""
-        self.keycode = 0
-        self.modifiers = 0
-
-    @objc.callbackFor(NSEvent.addGlobalMonitorForEventsMatchingMask_handler_)
-    def _handler(self, event) -> None:
-        # use event.charactersIgnoringModifiers to handle composing characters like Alt-e
-        if (
-            (event.modifierFlags() & self.MODIFIERMASK) == self.modifiers
-            and ord(event.charactersIgnoringModifiers()[0]) == self.keycode
-        ):
-            if config.get_int('hotkey_always'):
-                self.activated = True
-
-            else:  # Only trigger if game client is front process
-                front = NSWorkspace.sharedWorkspace().frontmostApplication()
-                if front and front.bundleIdentifier() == 'uk.co.frontier.EliteDangerous':
-                    self.activated = True
-
-    def acquire_start(self) -> None:
-        """Start acquiring hotkey state via polling."""
-        self.acquire_state = MacHotkeyMgr.ACQUIRE_ACTIVE
-        self.root.after_idle(self._acquire_poll)
-
-    def acquire_stop(self) -> None:
-        """Stop acquiring hotkey state."""
-        self.acquire_state = MacHotkeyMgr.ACQUIRE_INACTIVE
-
-    def _acquire_poll(self) -> None:
-        """Perform a poll of current hotkey state."""
-        if config.shutting_down:
-            return
-
-        # No way of signalling to Tkinter from within the monkey-patched event handler that doesn't
-        # cause Python to crash, so poll.
-        if self.acquire_state:
-            if self.acquire_state == MacHotkeyMgr.ACQUIRE_NEW:
-                # Abuse tkEvent's keycode field to hold our acquired key & modifier
-                self.root.event_generate('<KeyPress>', keycode=self.acquire_key)
-                self.acquire_state = MacHotkeyMgr.ACQUIRE_ACTIVE
-            self.root.after(50, self._acquire_poll)
-
-    def fromevent(self, event) -> bool | tuple | None:
-        """
-        Return configuration (keycode, modifiers) or None=clear or False=retain previous.
-
-        :param event: tk event ?
-        :return: False to retain previous, None to not use, else (keycode, modifiers)
-        """
-        (keycode, modifiers) = (event.keycode & 0xffff, event.keycode & 0xffff0000)  # Set by _acquire_poll()
-        if (
-            keycode
-            and not (modifiers & (NSShiftKeyMask | NSControlKeyMask | NSAlternateKeyMask | NSCommandKeyMask))
-        ):
-            if keycode == 0x1b:  # Esc = retain previous
-                self.acquire_state = MacHotkeyMgr.ACQUIRE_INACTIVE
-                return False
-
-            # BkSp, Del, Clear = clear hotkey
-            if keycode in (0x7f, ord(NSDeleteFunctionKey), ord(NSClearLineFunctionKey)):
-                self.acquire_state = MacHotkeyMgr.ACQUIRE_INACTIVE
-                return None
-
-            # don't allow keys needed for typing in System Map
-            if keycode in (0x13, 0x20, 0x2d) or 0x61 <= keycode <= 0x7a:
-                NSBeep()
-                self.acquire_state = MacHotkeyMgr.ACQUIRE_INACTIVE
-                return None
-
-        return keycode, modifiers
-
-    def display(self, keycode, modifiers) -> str:
-        """
-        Return displayable form of given hotkey + modifiers.
-
-        :param keycode:
-        :param modifiers:
-        :return: string form
-        """
-        text = ''
-        if modifiers & NSControlKeyMask:
-            text += u'⌃'
-
-        if modifiers & NSAlternateKeyMask:
-            text += u'⌥'
-
-        if modifiers & NSShiftKeyMask:
-            text += u'⇧'
-
-        if modifiers & NSCommandKeyMask:
-            text += u'⌘'
-
-        if (modifiers & NSNumericPadKeyMask) and keycode <= 0x7f:
-            text += u'№'
-
-        if not keycode:
-            pass
-
-        elif ord(NSF1FunctionKey) <= keycode <= ord(NSF35FunctionKey):
-            text += f'F{keycode + 1 - ord(NSF1FunctionKey)}'
-
-        elif keycode in MacHotkeyMgr.DISPLAY:  # specials
-            text += MacHotkeyMgr.DISPLAY[keycode]
-
-        elif keycode < 0x20:  # control keys
-            text += chr(keycode + 0x40)
-
-        elif keycode < 0xf700:  # key char
-            text += chr(keycode).upper()
-
-        else:
-            text += u'⁈'
-
-        return text
-
-    def play_good(self):
-        """Play the 'good' sound."""
-        self.snd_good.play()
-
-    def play_bad(self):
-        """Play the 'bad' sound."""
-        self.snd_bad.play()

From 93d26e07e057d1b70f3144898ecf34e35c4cdae2 Mon Sep 17 00:00:00 2001
From: David Sangrey <rixxan@hullseals.space>
Date: Wed, 27 Mar 2024 19:19:15 -0400
Subject: [PATCH 03/22] [2186] Remove Monitor, L10n References

---
 l10n.py    | 50 +++++---------------------------------------------
 monitor.py | 23 ++---------------------
 2 files changed, 7 insertions(+), 66 deletions(-)

diff --git a/l10n.py b/l10n.py
index a2b5185a..183d902a 100755
--- a/l10n.py
+++ b/l10n.py
@@ -17,8 +17,8 @@ import re
 import sys
 import warnings
 from contextlib import suppress
-from os import pardir, listdir, sep, makedirs
-from os.path import basename, dirname, isdir, isfile, join, abspath, exists
+from os import listdir, sep, makedirs
+from os.path import basename, dirname, isdir, join, abspath, exists
 from typing import TYPE_CHECKING, Iterable, TextIO, cast
 from config import config
 from EDMCLogging import get_main_logger
@@ -39,12 +39,7 @@ logger = get_main_logger()
 LANGUAGE_ID = '!Language'
 LOCALISATION_DIR = 'L10n'
 
-if sys.platform == 'darwin':
-    from Foundation import (  # type: ignore # exists on Darwin
-        NSLocale, NSNumberFormatter, NSNumberFormatterDecimalStyle
-    )
-
-elif sys.platform == 'win32':
+if sys.platform == 'win32':
     import ctypes
     from ctypes.wintypes import BOOL, DWORD, LPCVOID, LPCWSTR, LPWSTR
     if TYPE_CHECKING:
@@ -178,14 +173,8 @@ class _Translations:
     def available(self) -> set[str]:
         """Return a list of available language codes."""
         path = self.respath()
-        if getattr(sys, 'frozen', False) and sys.platform == 'darwin':
-            available = {
-                x[:-len('.lproj')] for x in listdir(path)
-                if x.endswith('.lproj') and isfile(join(x, 'Localizable.strings'))
-            }
 
-        else:
-            available = {x[:-len('.strings')] for x in listdir(path) if x.endswith('.strings')}
+        available = {x[:-len('.strings')] for x in listdir(path) if x.endswith('.strings')}
 
         return available
 
@@ -206,9 +195,6 @@ class _Translations:
     def respath(self) -> str:
         """Path to localisation files."""
         if getattr(sys, 'frozen', False):
-            if sys.platform == 'darwin':
-                return abspath(join(dirname(sys.executable), pardir, 'Resources'))
-
             return abspath(join(dirname(sys.executable), LOCALISATION_DIR))
 
         if __file__:
@@ -234,10 +220,6 @@ class _Translations:
             except OSError:
                 logger.exception(f'could not open {file_path}')
 
-        elif getattr(sys, 'frozen', False) and sys.platform == 'darwin':
-            res_path = join(self.respath(), f'{lang}.lproj', 'Localizable.strings')
-            return open(res_path, encoding='utf-16')
-
         res_path = join(self.respath(), f'{lang}.strings')
         return open(res_path, encoding='utf-8')
 
@@ -245,15 +227,6 @@ class _Translations:
 class _Locale:
     """Locale holds a few utility methods to convert data to and from localized versions."""
 
-    def __init__(self) -> None:
-        if sys.platform == 'darwin':
-            self.int_formatter = NSNumberFormatter.alloc().init()
-            self.int_formatter.setNumberStyle_(NSNumberFormatterDecimalStyle)
-            self.float_formatter = NSNumberFormatter.alloc().init()
-            self.float_formatter.setNumberStyle_(NSNumberFormatterDecimalStyle)
-            self.float_formatter.setMinimumFractionDigits_(5)
-            self.float_formatter.setMaximumFractionDigits_(5)
-
     def stringFromNumber(self, number: float | int, decimals: int | None = None) -> str:  # noqa: N802
         warnings.warn(DeprecationWarning('use _Locale.string_from_number instead.'))
         return self.string_from_number(number, decimals)  # type: ignore
@@ -279,14 +252,6 @@ class _Locale:
         if decimals == 0 and not isinstance(number, numbers.Integral):
             number = int(round(number))
 
-        if sys.platform == 'darwin':
-            if not decimals and isinstance(number, numbers.Integral):
-                return self.int_formatter.stringFromNumber_(number)
-
-            self.float_formatter.setMinimumFractionDigits_(decimals)
-            self.float_formatter.setMaximumFractionDigits_(decimals)
-            return self.float_formatter.stringFromNumber_(number)
-
         if not decimals and isinstance(number, numbers.Integral):
             return locale.format_string('%d', number, True)
         return locale.format_string('%.*f', (decimals, number), True)
@@ -299,9 +264,6 @@ class _Locale:
         :param string: The string to convert
         :return: None if the string cannot be parsed, otherwise an int or float dependant on input data.
         """
-        if sys.platform == 'darwin':
-            return self.float_formatter.numberFromString_(string)
-
         with suppress(ValueError):
             return locale.atoi(string)
 
@@ -332,10 +294,8 @@ class _Locale:
         :return: The preferred language list
         """
         languages: Iterable[str]
-        if sys.platform == 'darwin':
-            languages = NSLocale.preferredLanguages()
 
-        elif sys.platform != 'win32':
+        if sys.platform != 'win32':
             # POSIX
             lang = locale.getlocale()[0]
             languages = [lang.replace('_', '-')] if lang else []
diff --git a/monitor.py b/monitor.py
index 8364f95b..d549e533 100644
--- a/monitor.py
+++ b/monitor.py
@@ -38,16 +38,7 @@ if TYPE_CHECKING:
     def _(x: str) -> str:
         return x
 
-if sys.platform == 'darwin':
-    from fcntl import fcntl
-
-    from AppKit import NSWorkspace
-    from watchdog.events import FileSystemEventHandler
-    from watchdog.observers import Observer
-    from watchdog.observers.api import BaseObserver
-    F_GLOBAL_NOCACHE = 55
-
-elif sys.platform == 'win32':
+if sys.platform == 'win32':
     import ctypes
     from ctypes.wintypes import BOOL, HWND, LPARAM, LPWSTR
 
@@ -382,8 +373,6 @@ class EDLogs(FileSystemEventHandler):
         logfile = self.logfile
         if logfile:
             loghandle: BinaryIO = open(logfile, 'rb', 0)  # unbuffered
-            if sys.platform == 'darwin':
-                fcntl(loghandle, F_GLOBAL_NOCACHE, -1)  # required to avoid corruption on macOS over SMB
 
             self.catching_up = True
             for line in loghandle:
@@ -483,9 +472,6 @@ class EDLogs(FileSystemEventHandler):
 
                 if logfile:
                     loghandle = open(logfile, 'rb', 0)  # unbuffered
-                    if sys.platform == 'darwin':
-                        fcntl(loghandle, F_GLOBAL_NOCACHE, -1)  # required to avoid corruption on macOS over SMB
-
                     log_pos = 0
 
             sleep(self._POLL)
@@ -2144,12 +2130,7 @@ class EDLogs(FileSystemEventHandler):
 
         :return: bool - True if the game is running.
         """
-        if sys.platform == 'darwin':
-            for app in NSWorkspace.sharedWorkspace().runningApplications():
-                if app.bundleIdentifier() == 'uk.co.frontier.EliteDangerous':
-                    return True
-
-        elif sys.platform == 'win32':
+        if sys.platform == 'win32':
             def WindowTitle(h):  # noqa: N802
                 if h:
                     length = GetWindowTextLength(h) + 1

From 27093d88620ed760600b0e1df672160e25dffc7c Mon Sep 17 00:00:00 2001
From: David Sangrey <rixxan@hullseals.space>
Date: Wed, 27 Mar 2024 19:26:25 -0400
Subject: [PATCH 04/22] [2186] Main, Dashboard, Prefs

---
 EDMarketConnector.py | 342 ++++++++++++++++---------------------------
 dashboard.py         |   2 +-
 myNotebook.py        |   2 +-
 prefs.py             | 177 ++++------------------
 4 files changed, 161 insertions(+), 362 deletions(-)

diff --git a/EDMarketConnector.py b/EDMarketConnector.py
index 6debf4e9..967488e0 100755
--- a/EDMarketConnector.py
+++ b/EDMarketConnector.py
@@ -496,21 +496,20 @@ class AppWindow:
 
         plug.load_plugins(master)
 
-        if sys.platform != 'darwin':
-            if sys.platform == 'win32':
-                self.w.wm_iconbitmap(default='EDMarketConnector.ico')
+        if sys.platform == 'win32':
+            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')))
+        else:
+            self.w.tk.call('wm', 'iconphoto', self.w, '-default',
+                           tk.PhotoImage(file=path.join(config.respath_path, 'io.edcd.EDMarketConnector.png')))
 
-            # TODO: Export to files and merge from them in future ?
-            self.theme_icon = tk.PhotoImage(
-                data='R0lGODlhFAAQAMZQAAoKCQoKCgsKCQwKCQsLCgwLCg4LCQ4LCg0MCg8MCRAMCRANChINCREOChIOChQPChgQChgRCxwTCyYVCSoXCS0YCTkdCTseCT0fCTsjDU0jB0EnDU8lB1ElB1MnCFIoCFMoCEkrDlkqCFwrCGEuCWIuCGQvCFs0D1w1D2wyCG0yCF82D182EHE0CHM0CHQ1CGQ5EHU2CHc3CHs4CH45CIA6CIE7CJdECIdLEolMEohQE5BQE41SFJBTE5lUE5pVE5RXFKNaFKVbFLVjFbZkFrxnFr9oFsNqFsVrF8RsFshtF89xF9NzGNh1GNl2GP+KG////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////yH5BAEKAH8ALAAAAAAUABAAAAeegAGCgiGDhoeIRDiIjIZGKzmNiAQBQxkRTU6am0tPCJSGShuSAUcLoIIbRYMFra4FAUgQAQCGJz6CDQ67vAFJJBi0hjBBD0w9PMnJOkAiJhaIKEI7HRoc19ceNAolwbWDLD8uAQnl5ga1I9CHEjEBAvDxAoMtFIYCBy+kFDKHAgM3ZtgYSLAGgwkp3pEyBOJCC2ELB31QATGioAoVAwEAOw==')  # noqa: E501
-            self.theme_minimize = tk.BitmapImage(
-                data='#define im_width 16\n#define im_height 16\nstatic unsigned char im_bits[] = {\n   0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,\n   0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xfc, 0x3f,\n   0xfc, 0x3f, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 };\n')  # noqa: E501
-            self.theme_close = tk.BitmapImage(
-                data='#define im_width 16\n#define im_height 16\nstatic unsigned char im_bits[] = {\n   0x00, 0x00, 0x00, 0x00, 0x0c, 0x30, 0x1c, 0x38, 0x38, 0x1c, 0x70, 0x0e,\n   0xe0, 0x07, 0xc0, 0x03, 0xc0, 0x03, 0xe0, 0x07, 0x70, 0x0e, 0x38, 0x1c,\n   0x1c, 0x38, 0x0c, 0x30, 0x00, 0x00, 0x00, 0x00 };\n')  # noqa: E501
+        # TODO: Export to files and merge from them in future ?
+        self.theme_icon = tk.PhotoImage(
+            data='R0lGODlhFAAQAMZQAAoKCQoKCgsKCQwKCQsLCgwLCg4LCQ4LCg0MCg8MCRAMCRANChINCREOChIOChQPChgQChgRCxwTCyYVCSoXCS0YCTkdCTseCT0fCTsjDU0jB0EnDU8lB1ElB1MnCFIoCFMoCEkrDlkqCFwrCGEuCWIuCGQvCFs0D1w1D2wyCG0yCF82D182EHE0CHM0CHQ1CGQ5EHU2CHc3CHs4CH45CIA6CIE7CJdECIdLEolMEohQE5BQE41SFJBTE5lUE5pVE5RXFKNaFKVbFLVjFbZkFrxnFr9oFsNqFsVrF8RsFshtF89xF9NzGNh1GNl2GP+KG////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////yH5BAEKAH8ALAAAAAAUABAAAAeegAGCgiGDhoeIRDiIjIZGKzmNiAQBQxkRTU6am0tPCJSGShuSAUcLoIIbRYMFra4FAUgQAQCGJz6CDQ67vAFJJBi0hjBBD0w9PMnJOkAiJhaIKEI7HRoc19ceNAolwbWDLD8uAQnl5ga1I9CHEjEBAvDxAoMtFIYCBy+kFDKHAgM3ZtgYSLAGgwkp3pEyBOJCC2ELB31QATGioAoVAwEAOw==')  # noqa: E501
+        self.theme_minimize = tk.BitmapImage(
+            data='#define im_width 16\n#define im_height 16\nstatic unsigned char im_bits[] = {\n   0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,\n   0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xfc, 0x3f,\n   0xfc, 0x3f, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 };\n')  # noqa: E501
+        self.theme_close = tk.BitmapImage(
+            data='#define im_width 16\n#define im_height 16\nstatic unsigned char im_bits[] = {\n   0x00, 0x00, 0x00, 0x00, 0x0c, 0x30, 0x1c, 0x38, 0x38, 0x1c, 0x70, 0x0e,\n   0xe0, 0x07, 0xc0, 0x03, 0xc0, 0x03, 0xe0, 0x07, 0x70, 0x0e, 0x38, 0x1c,\n   0x1c, 0x38, 0x0c, 0x30, 0x00, 0x00, 0x00, 0x00 };\n')  # noqa: E501
 
         frame = tk.Frame(self.w, name=appname.lower())
         frame.grid(sticky=tk.NSEW)
@@ -599,7 +598,7 @@ class AppWindow:
         self.theme_button = tk.Label(
             frame,
             name='themed_update_button',
-            width=32 if sys.platform == 'darwin' else 28,
+            width=28,
             state=tk.DISABLED
         )
 
@@ -633,148 +632,104 @@ class AppWindow:
             self.updater = update.Updater(tkroot=self.w)
             self.updater.check_for_updates()  # Sparkle / WinSparkle does this automatically for packaged apps
 
-        if sys.platform == 'darwin':
-            # Can't handle (de)iconify if topmost is set, so suppress iconify button
-            # http://wiki.tcl.tk/13428 and p15 of
-            # https://developer.apple.com/legacy/library/documentation/Carbon/Conceptual/HandlingWindowsControls/windowscontrols.pdf
-            root.call('tk::unsupported::MacWindowStyle', 'style', root, 'document', 'closeBox resizable')
+        self.file_menu = self.view_menu = tk.Menu(self.menubar, tearoff=tk.FALSE)
+        self.file_menu.add_command(command=lambda: stats.StatsDialog(self.w, self.status))
+        self.file_menu.add_command(command=self.save_raw)
+        self.file_menu.add_command(command=lambda: prefs.PreferencesDialog(self.w, self.postprefs))
+        self.file_menu.add_separator()
+        self.file_menu.add_command(command=self.onexit)
+        self.menubar.add_cascade(menu=self.file_menu)
+        self.edit_menu = tk.Menu(self.menubar, tearoff=tk.FALSE)
+        self.edit_menu.add_command(accelerator='Ctrl+C', state=tk.DISABLED, command=self.copy)
+        self.menubar.add_cascade(menu=self.edit_menu)
+        self.help_menu = tk.Menu(self.menubar, tearoff=tk.FALSE)  # type: ignore
+        self.help_menu.add_command(command=self.help_general)  # Documentation
+        self.help_menu.add_command(command=self.help_troubleshooting)  # Troubleshooting
+        self.help_menu.add_command(command=self.help_report_a_bug)  # Report A Bug
+        self.help_menu.add_command(command=self.help_privacy)  # Privacy Policy
+        self.help_menu.add_command(command=self.help_releases)  # Release Notes
+        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))
+        self.help_menu.add_command(command=prefs.help_open_log_folder)  # Open Log Folder
 
-            # https://www.tcl.tk/man/tcl/TkCmd/menu.htm
-            self.system_menu = tk.Menu(self.menubar, name='apple')
-            self.system_menu.add_command(command=lambda: self.w.call('tk::mac::standardAboutPanel'))
-            self.system_menu.add_command(command=lambda: self.updater.check_for_updates())
+        self.menubar.add_cascade(menu=self.help_menu)
+        if sys.platform == 'win32':
+            # Must be added after at least one "real" menu entry
+            self.always_ontop = tk.BooleanVar(value=bool(config.get_int('always_ontop')))
+            self.system_menu = tk.Menu(self.menubar, name='system', tearoff=tk.FALSE)
+            self.system_menu.add_separator()
+            # LANG: Appearance - Label for checkbox to select if application always on top
+            self.system_menu.add_checkbutton(label=_('Always on top'),
+                                             variable=self.always_ontop,
+                                             command=self.ontop_changed)  # Appearance setting
             self.menubar.add_cascade(menu=self.system_menu)
-            self.file_menu = tk.Menu(self.menubar, name='file')
-            self.file_menu.add_command(command=self.save_raw)
-            self.menubar.add_cascade(menu=self.file_menu)
-            self.edit_menu = tk.Menu(self.menubar, name='edit')
-            self.edit_menu.add_command(accelerator='Command-c', state=tk.DISABLED, command=self.copy)
-            self.menubar.add_cascade(menu=self.edit_menu)
-            self.w.bind('<Command-c>', self.copy)
-            self.view_menu = tk.Menu(self.menubar, name='view')
-            self.view_menu.add_command(command=lambda: stats.StatsDialog(self.w, self.status))
-            self.menubar.add_cascade(menu=self.view_menu)
-            window_menu = tk.Menu(self.menubar, name='window')
-            self.menubar.add_cascade(menu=window_menu)
-            self.help_menu = tk.Menu(self.menubar, name='help')
-            self.w.createcommand("::tk::mac::ShowHelp", self.help_general)
-            self.help_menu.add_command(command=self.help_troubleshooting)
-            self.help_menu.add_command(command=self.help_report_a_bug)
-            self.help_menu.add_command(command=self.help_privacy)
-            self.help_menu.add_command(command=self.help_releases)
-            self.menubar.add_cascade(menu=self.help_menu)
-            self.w['menu'] = self.menubar
-            # https://www.tcl.tk/man/tcl/TkCmd/tk_mac.htm
-            self.w.call('set', 'tk::mac::useCompatibilityMetrics', '0')
-            self.w.createcommand('tkAboutDialog', lambda: self.w.call('tk::mac::standardAboutPanel'))
-            self.w.createcommand("::tk::mac::Quit", self.onexit)
-            self.w.createcommand("::tk::mac::ShowPreferences", lambda: prefs.PreferencesDialog(self.w, self.postprefs))
-            self.w.createcommand("::tk::mac::ReopenApplication", self.w.deiconify)  # click on app in dock = restore
-            self.w.protocol("WM_DELETE_WINDOW", self.w.withdraw)  # close button shouldn't quit app
-            self.w.resizable(tk.FALSE, tk.FALSE)  # Can't be only resizable on one axis
-        else:
-            self.file_menu = self.view_menu = tk.Menu(self.menubar, tearoff=tk.FALSE)
-            self.file_menu.add_command(command=lambda: stats.StatsDialog(self.w, self.status))
-            self.file_menu.add_command(command=self.save_raw)
-            self.file_menu.add_command(command=lambda: prefs.PreferencesDialog(self.w, self.postprefs))
-            self.file_menu.add_separator()
-            self.file_menu.add_command(command=self.onexit)
-            self.menubar.add_cascade(menu=self.file_menu)
-            self.edit_menu = tk.Menu(self.menubar, tearoff=tk.FALSE)
-            self.edit_menu.add_command(accelerator='Ctrl+C', state=tk.DISABLED, command=self.copy)
-            self.menubar.add_cascade(menu=self.edit_menu)
-            self.help_menu = tk.Menu(self.menubar, tearoff=tk.FALSE)  # type: ignore
-            self.help_menu.add_command(command=self.help_general)  # Documentation
-            self.help_menu.add_command(command=self.help_troubleshooting)  # Troubleshooting
-            self.help_menu.add_command(command=self.help_report_a_bug)  # Report A Bug
-            self.help_menu.add_command(command=self.help_privacy)  # Privacy Policy
-            self.help_menu.add_command(command=self.help_releases)  # Release Notes
-            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))
-            self.help_menu.add_command(command=prefs.help_open_log_folder)  # Open Log Folder
+        self.w.bind('<Control-c>', self.copy)
 
-            self.menubar.add_cascade(menu=self.help_menu)
-            if sys.platform == 'win32':
-                # Must be added after at least one "real" menu entry
-                self.always_ontop = tk.BooleanVar(value=bool(config.get_int('always_ontop')))
-                self.system_menu = tk.Menu(self.menubar, name='system', tearoff=tk.FALSE)
-                self.system_menu.add_separator()
-                # LANG: Appearance - Label for checkbox to select if application always on top
-                self.system_menu.add_checkbutton(label=_('Always on top'),
-                                                 variable=self.always_ontop,
-                                                 command=self.ontop_changed)  # Appearance setting
-                self.menubar.add_cascade(menu=self.system_menu)
-            self.w.bind('<Control-c>', self.copy)
+        # Bind to the Default theme minimise button
+        self.w.bind("<Unmap>", self.default_iconify)
 
-            # Bind to the Default theme minimise button
-            self.w.bind("<Unmap>", self.default_iconify)
+        self.w.protocol("WM_DELETE_WINDOW", self.onexit)
+        theme.register(self.menubar)  # menus and children aren't automatically registered
+        theme.register(self.file_menu)
+        theme.register(self.edit_menu)
+        theme.register(self.help_menu)
 
-            self.w.protocol("WM_DELETE_WINDOW", self.onexit)
-            theme.register(self.menubar)  # menus and children aren't automatically registered
-            theme.register(self.file_menu)
-            theme.register(self.edit_menu)
-            theme.register(self.help_menu)
-
-            # Alternate title bar and menu for dark theme
-            self.theme_menubar = tk.Frame(frame, name="alternate_menubar")
-            self.theme_menubar.columnconfigure(2, weight=1)
-            theme_titlebar = tk.Label(
-                self.theme_menubar,
-                name="alternate_titlebar",
-                text=applongname,
-                image=self.theme_icon, cursor='fleur',
-                anchor=tk.W, compound=tk.LEFT
-            )
-            theme_titlebar.grid(columnspan=3, padx=2, sticky=tk.NSEW)
-            self.drag_offset: tuple[int | None, int | None] = (None, None)
-            theme_titlebar.bind('<Button-1>', self.drag_start)
-            theme_titlebar.bind('<B1-Motion>', self.drag_continue)
-            theme_titlebar.bind('<ButtonRelease-1>', self.drag_end)
-            theme_minimize = tk.Label(self.theme_menubar, image=self.theme_minimize)
-            theme_minimize.grid(row=0, column=3, padx=2)
-            theme.button_bind(theme_minimize, self.oniconify, image=self.theme_minimize)
-            theme_close = tk.Label(self.theme_menubar, image=self.theme_close)
-            theme_close.grid(row=0, column=4, padx=2)
-            theme.button_bind(theme_close, self.onexit, image=self.theme_close)
-            self.theme_file_menu = tk.Label(self.theme_menubar, anchor=tk.W)
-            self.theme_file_menu.grid(row=1, column=0, padx=self.PADX, sticky=tk.W)
-            theme.button_bind(self.theme_file_menu,
-                              lambda e: self.file_menu.tk_popup(e.widget.winfo_rootx(),
-                                                                e.widget.winfo_rooty()
-                                                                + e.widget.winfo_height()))
-            self.theme_edit_menu = tk.Label(self.theme_menubar, anchor=tk.W)
-            self.theme_edit_menu.grid(row=1, column=1, sticky=tk.W)
-            theme.button_bind(self.theme_edit_menu,
-                              lambda e: self.edit_menu.tk_popup(e.widget.winfo_rootx(),
-                                                                e.widget.winfo_rooty()
-                                                                + e.widget.winfo_height()))
-            self.theme_help_menu = tk.Label(self.theme_menubar, anchor=tk.W)
-            self.theme_help_menu.grid(row=1, column=2, sticky=tk.W)
-            theme.button_bind(self.theme_help_menu,
-                              lambda e: self.help_menu.tk_popup(e.widget.winfo_rootx(),
-                                                                e.widget.winfo_rooty()
-                                                                + e.widget.winfo_height()))
-            tk.Frame(self.theme_menubar, highlightthickness=1).grid(columnspan=5, padx=self.PADX, sticky=tk.EW)
-            theme.register(self.theme_minimize)  # images aren't automatically registered
-            theme.register(self.theme_close)
-            self.blank_menubar = tk.Frame(frame, name="blank_menubar")
-            tk.Label(self.blank_menubar).grid()
-            tk.Label(self.blank_menubar).grid()
-            tk.Frame(self.blank_menubar, height=2).grid()
-            theme.register_alternate((self.menubar, self.theme_menubar, self.blank_menubar),
-                                     {'row': 0, 'columnspan': 2, 'sticky': tk.NSEW})
-            self.w.resizable(tk.TRUE, tk.FALSE)
+        # Alternate title bar and menu for dark theme
+        self.theme_menubar = tk.Frame(frame, name="alternate_menubar")
+        self.theme_menubar.columnconfigure(2, weight=1)
+        theme_titlebar = tk.Label(
+            self.theme_menubar,
+            name="alternate_titlebar",
+            text=applongname,
+            image=self.theme_icon, cursor='fleur',
+            anchor=tk.W, compound=tk.LEFT
+        )
+        theme_titlebar.grid(columnspan=3, padx=2, sticky=tk.NSEW)
+        self.drag_offset: tuple[int | None, int | None] = (None, None)
+        theme_titlebar.bind('<Button-1>', self.drag_start)
+        theme_titlebar.bind('<B1-Motion>', self.drag_continue)
+        theme_titlebar.bind('<ButtonRelease-1>', self.drag_end)
+        theme_minimize = tk.Label(self.theme_menubar, image=self.theme_minimize)
+        theme_minimize.grid(row=0, column=3, padx=2)
+        theme.button_bind(theme_minimize, self.oniconify, image=self.theme_minimize)
+        theme_close = tk.Label(self.theme_menubar, image=self.theme_close)
+        theme_close.grid(row=0, column=4, padx=2)
+        theme.button_bind(theme_close, self.onexit, image=self.theme_close)
+        self.theme_file_menu = tk.Label(self.theme_menubar, anchor=tk.W)
+        self.theme_file_menu.grid(row=1, column=0, padx=self.PADX, sticky=tk.W)
+        theme.button_bind(self.theme_file_menu,
+                          lambda e: self.file_menu.tk_popup(e.widget.winfo_rootx(),
+                                                            e.widget.winfo_rooty()
+                                                            + e.widget.winfo_height()))
+        self.theme_edit_menu = tk.Label(self.theme_menubar, anchor=tk.W)
+        self.theme_edit_menu.grid(row=1, column=1, sticky=tk.W)
+        theme.button_bind(self.theme_edit_menu,
+                          lambda e: self.edit_menu.tk_popup(e.widget.winfo_rootx(),
+                                                            e.widget.winfo_rooty()
+                                                            + e.widget.winfo_height()))
+        self.theme_help_menu = tk.Label(self.theme_menubar, anchor=tk.W)
+        self.theme_help_menu.grid(row=1, column=2, sticky=tk.W)
+        theme.button_bind(self.theme_help_menu,
+                          lambda e: self.help_menu.tk_popup(e.widget.winfo_rootx(),
+                                                            e.widget.winfo_rooty()
+                                                            + e.widget.winfo_height()))
+        tk.Frame(self.theme_menubar, highlightthickness=1).grid(columnspan=5, padx=self.PADX, sticky=tk.EW)
+        theme.register(self.theme_minimize)  # images aren't automatically registered
+        theme.register(self.theme_close)
+        self.blank_menubar = tk.Frame(frame, name="blank_menubar")
+        tk.Label(self.blank_menubar).grid()
+        tk.Label(self.blank_menubar).grid()
+        tk.Frame(self.blank_menubar, height=2).grid()
+        theme.register_alternate((self.menubar, self.theme_menubar, self.blank_menubar),
+                                 {'row': 0, 'columnspan': 2, 'sticky': tk.NSEW})
+        self.w.resizable(tk.TRUE, tk.FALSE)
 
         # update geometry
         if config.get_str('geometry'):
             match = re.match(r'\+([\-\d]+)\+([\-\d]+)', config.get_str('geometry'))
             if match:
-                if sys.platform == 'darwin':
-                    # http://core.tcl.tk/tk/tktview/c84f660833546b1b84e7
-                    if int(match.group(2)) >= 0:
-                        self.w.geometry(config.get_str('geometry'))
-                elif sys.platform == 'win32':
+                if sys.platform == 'win32':
                     # Check that the titlebar will be at least partly on screen
                     import ctypes
                     from ctypes.wintypes import POINT
@@ -910,49 +865,28 @@ class AppWindow:
         self.system_label['text'] = _('System') + ':'  # LANG: Label for 'System' line in main UI
         self.station_label['text'] = _('Station') + ':'  # LANG: Label for 'Station' line in main UI
         self.button['text'] = self.theme_button['text'] = _('Update')  # LANG: Update button in main window
-        if sys.platform == 'darwin':
-            self.menubar.entryconfigure(1, label=_('File'))  # LANG: 'File' menu title on OSX
-            self.menubar.entryconfigure(2, label=_('Edit'))  # LANG: 'Edit' menu title on OSX
-            self.menubar.entryconfigure(3, label=_('View'))  # LANG: 'View' menu title on OSX
-            self.menubar.entryconfigure(4, label=_('Window'))  # LANG: 'Window' menu title on OSX
-            self.menubar.entryconfigure(5, label=_('Help'))  # LANG: Help' menu title on OSX
-            self.system_menu.entryconfigure(
-                0,
-                label=_("About {APP}").format(APP=applongname)  # LANG: App menu entry on OSX
-            )
-            self.system_menu.entryconfigure(1, label=_("Check for Updates..."))  # LANG: Help > Check for Updates...
-            self.file_menu.entryconfigure(0, label=_('Save Raw Data...'))  # LANG: File > Save Raw Data...
-            self.view_menu.entryconfigure(0, label=_('Status'))  # LANG: File > Status
-            self.help_menu.entryconfigure(1, label=_('Documentation'))  # LANG: Help > Documentation
-            self.help_menu.entryconfigure(2, label=_('Troubleshooting'))  # LANG: Help > Troubleshooting
-            self.help_menu.entryconfigure(3, label=_('Report A Bug'))  # LANG: Help > Report A Bug
-            self.help_menu.entryconfigure(4, label=_('Privacy Policy'))  # LANG: Help > Privacy Policy
-            self.help_menu.entryconfigure(5, label=_('Release Notes'))  # LANG: Help > Release Notes
-            self.help_menu.entryconfigure(6, label=_('Open Log Folder'))  # LANG: Help > Open Log Folder
+        self.menubar.entryconfigure(1, label=_('File'))  # LANG: 'File' menu title
+        self.menubar.entryconfigure(2, label=_('Edit'))  # LANG: 'Edit' menu title
+        self.menubar.entryconfigure(3, label=_('Help'))  # LANG: 'Help' menu title
+        self.theme_file_menu['text'] = _('File')  # LANG: 'File' menu title
+        self.theme_edit_menu['text'] = _('Edit')  # LANG: 'Edit' menu title
+        self.theme_help_menu['text'] = _('Help')  # LANG: 'Help' menu title
 
-        else:
-            self.menubar.entryconfigure(1, label=_('File'))  # LANG: 'File' menu title
-            self.menubar.entryconfigure(2, label=_('Edit'))  # LANG: 'Edit' menu title
-            self.menubar.entryconfigure(3, label=_('Help'))  # LANG: 'Help' menu title
-            self.theme_file_menu['text'] = _('File')  # LANG: 'File' menu title
-            self.theme_edit_menu['text'] = _('Edit')  # LANG: 'Edit' menu title
-            self.theme_help_menu['text'] = _('Help')  # LANG: 'Help' menu title
+        # File menu
+        self.file_menu.entryconfigure(0, label=_('Status'))  # LANG: File > Status
+        self.file_menu.entryconfigure(1, label=_('Save Raw Data...'))  # LANG: File > Save Raw Data...
+        self.file_menu.entryconfigure(2, label=_('Settings'))  # LANG: File > Settings
+        self.file_menu.entryconfigure(4, label=_('Exit'))  # LANG: File > Exit
 
-            # File menu
-            self.file_menu.entryconfigure(0, label=_('Status'))  # LANG: File > Status
-            self.file_menu.entryconfigure(1, label=_('Save Raw Data...'))  # LANG: File > Save Raw Data...
-            self.file_menu.entryconfigure(2, label=_('Settings'))  # LANG: File > Settings
-            self.file_menu.entryconfigure(4, label=_('Exit'))  # LANG: File > Exit
-
-            # Help menu
-            self.help_menu.entryconfigure(0, label=_('Documentation'))  # LANG: Help > Documentation
-            self.help_menu.entryconfigure(1, label=_('Troubleshooting'))  # LANG: Help > Troubleshooting
-            self.help_menu.entryconfigure(2, label=_('Report A Bug'))  # LANG: Help > Report A Bug
-            self.help_menu.entryconfigure(3, label=_('Privacy Policy'))  # LANG: Help > Privacy Policy
-            self.help_menu.entryconfigure(4, label=_('Release Notes'))  # LANG: Help > Release Notes
-            self.help_menu.entryconfigure(5, label=_('Check for Updates...'))  # LANG: Help > Check for Updates...
-            self.help_menu.entryconfigure(6, label=_("About {APP}").format(APP=applongname))  # LANG: Help > About App
-            self.help_menu.entryconfigure(7, label=_('Open Log Folder'))  # LANG: Help > Open Log Folder
+        # Help menu
+        self.help_menu.entryconfigure(0, label=_('Documentation'))  # LANG: Help > Documentation
+        self.help_menu.entryconfigure(1, label=_('Troubleshooting'))  # LANG: Help > Troubleshooting
+        self.help_menu.entryconfigure(2, label=_('Report A Bug'))  # LANG: Help > Report A Bug
+        self.help_menu.entryconfigure(3, label=_('Privacy Policy'))  # LANG: Help > Privacy Policy
+        self.help_menu.entryconfigure(4, label=_('Release Notes'))  # LANG: Help > Release Notes
+        self.help_menu.entryconfigure(5, label=_('Check for Updates...'))  # LANG: Help > Check for Updates...
+        self.help_menu.entryconfigure(6, label=_("About {APP}").format(APP=applongname))  # LANG: Help > About App
+        self.help_menu.entryconfigure(7, label=_('Open Log Folder'))  # LANG: Help > Open Log Folder
 
         # Edit menu
         self.edit_menu.entryconfigure(0, label=_('Copy'))  # LANG: Label for 'Copy' as in 'Copy and Paste'
@@ -975,13 +909,8 @@ class AppWindow:
 
         self.button['state'] = self.theme_button['state'] = tk.DISABLED
 
-        if sys.platform == 'darwin':
-            self.view_menu.entryconfigure(0, state=tk.DISABLED)  # Status
-            self.file_menu.entryconfigure(0, state=tk.DISABLED)  # Save Raw Data
-
-        else:
-            self.file_menu.entryconfigure(0, state=tk.DISABLED)  # Status
-            self.file_menu.entryconfigure(1, state=tk.DISABLED)  # Save Raw Data
+        self.file_menu.entryconfigure(0, state=tk.DISABLED)  # Status
+        self.file_menu.entryconfigure(1, state=tk.DISABLED)  # Save Raw Data
 
         self.w.update_idletasks()
         try:
@@ -989,13 +918,8 @@ class AppWindow:
                 # LANG: Successfully authenticated with the Frontier website
                 self.status['text'] = _('Authentication successful')
 
-                if sys.platform == 'darwin':
-                    self.view_menu.entryconfigure(0, state=tk.NORMAL)  # Status
-                    self.file_menu.entryconfigure(0, state=tk.NORMAL)  # Save Raw Data
-
-                else:
-                    self.file_menu.entryconfigure(0, state=tk.NORMAL)  # Status
-                    self.file_menu.entryconfigure(1, state=tk.NORMAL)  # Save Raw Data
+                self.file_menu.entryconfigure(0, state=tk.NORMAL)  # Status
+                self.file_menu.entryconfigure(1, state=tk.NORMAL)  # Save Raw Data
 
         except (companion.CredentialsError, companion.ServerError, companion.ServerLagging) as e:
             self.status['text'] = str(e)
@@ -1666,13 +1590,8 @@ class AppWindow:
             companion.session.auth_callback()
             # LANG: Successfully authenticated with the Frontier website
             self.status['text'] = _('Authentication successful')
-            if sys.platform == 'darwin':
-                self.view_menu.entryconfigure(0, state=tk.NORMAL)  # Status
-                self.file_menu.entryconfigure(0, state=tk.NORMAL)  # Save Raw Data
-
-            else:
-                self.file_menu.entryconfigure(0, state=tk.NORMAL)  # Status
-                self.file_menu.entryconfigure(1, state=tk.NORMAL)  # Save Raw Data
+            self.file_menu.entryconfigure(0, state=tk.NORMAL)  # Status
+            self.file_menu.entryconfigure(1, state=tk.NORMAL)  # Save Raw Data
 
         except companion.ServerError as e:
             self.status['text'] = str(e)
@@ -1831,7 +1750,7 @@ class AppWindow:
 
             # position over parent
             # http://core.tcl.tk/tk/tktview/c84f660833546b1b84e7
-            if sys.platform != 'darwin' or parent.winfo_rooty() > 0:
+            if parent.winfo_rooty() > 0:
                 self.geometry(f'+{parent.winfo_rootx():d}+{parent.winfo_rooty():d}')
 
             # remove decoration
@@ -1916,9 +1835,6 @@ class AppWindow:
         """
         default_extension: str = ''
 
-        if sys.platform == 'darwin':
-            default_extension = '.json'
-
         timestamp: str = strftime('%Y-%m-%dT%H.%M.%S', localtime())
         f = tkinter.filedialog.asksaveasfilename(
             parent=self.w,
@@ -1954,7 +1870,7 @@ class AppWindow:
         config.set_shutdown()  # Signal we're in shutdown now.
 
         # http://core.tcl.tk/tk/tktview/c84f660833546b1b84e7
-        if sys.platform != 'darwin' or self.w.winfo_rooty() > 0:
+        if self.w.winfo_rooty() > 0:
             x, y = self.w.geometry().split('+')[1:3]  # e.g. '212x170+2881+1267'
             config.set('geometry', f'+{x}+{y}')
 
diff --git a/dashboard.py b/dashboard.py
index 1f95a943..3948c92b 100644
--- a/dashboard.py
+++ b/dashboard.py
@@ -20,7 +20,7 @@ from EDMCLogging import get_main_logger
 
 logger = get_main_logger()
 
-if sys.platform in ('darwin', 'win32'):
+if sys.platform == 'win32':
     from watchdog.events import FileSystemEventHandler
     from watchdog.observers import Observer
 else:
diff --git a/myNotebook.py b/myNotebook.py
index 6fa35774..dbfc9250 100644
--- a/myNotebook.py
+++ b/myNotebook.py
@@ -78,7 +78,7 @@ class Label(tk.Label):
 
     def __init__(self, master: ttk.Frame | None = None, **kw):
         # This format chosen over `sys.platform in (...)` as mypy and friends dont understand that
-        if sys.platform in ('darwin', 'win32'):
+        if sys.platform == 'win32':
             kw['foreground'] = kw.pop('foreground', PAGEFG)
             kw['background'] = kw.pop('background', PAGEBG)
         else:
diff --git a/prefs.py b/prefs.py
index 6c2722b6..862bda27 100644
--- a/prefs.py
+++ b/prefs.py
@@ -18,7 +18,7 @@ from typing import TYPE_CHECKING, Any, Callable, Optional, Type
 
 import myNotebook as nb  # noqa: N813
 import plug
-from config import applongname, appversion_nobuild, config
+from config import appversion_nobuild, config
 from EDMCLogging import edmclogger, get_main_logger
 from constants import appname
 from hotkey import hotkeymgr
@@ -49,9 +49,6 @@ def help_open_log_folder() -> None:
     if sys.platform.startswith('win'):
         # On Windows, use the "start" command to open the folder
         system(f'start "" "{logfile_loc}"')
-    elif sys.platform.startswith('darwin'):
-        # On macOS, use the "open" command to open the folder
-        system(f'open "{logfile_loc}"')
     elif sys.platform.startswith('linux'):
         # On Linux, use the "xdg-open" command to open the folder
         system(f'xdg-open "{logfile_loc}"')
@@ -172,32 +169,7 @@ class AutoInc(contextlib.AbstractContextManager):
         return None
 
 
-if sys.platform == 'darwin':
-    import objc  # type: ignore
-    from Foundation import NSFileManager  # type: ignore
-    try:
-        from ApplicationServices import (  # type: ignore
-            AXIsProcessTrusted, AXIsProcessTrustedWithOptions, kAXTrustedCheckOptionPrompt
-        )
-
-    except ImportError:
-        HIServices = objc.loadBundle(
-            'HIServices',
-            globals(),
-            '/System/Library/Frameworks/ApplicationServices.framework/Frameworks/HIServices.framework'
-        )
-
-        objc.loadBundleFunctions(
-            HIServices,
-            globals(),
-            [('AXIsProcessTrusted', 'B'), ('AXIsProcessTrustedWithOptions', 'B@')]
-        )
-
-        objc.loadBundleVariables(HIServices, globals(), [('kAXTrustedCheckOptionPrompt', '@^{__CFString=}')])
-
-    was_accessible_at_launch = AXIsProcessTrusted()  # type: ignore
-
-elif sys.platform == 'win32':
+if sys.platform == 'win32':
     import ctypes
     import winreg
     from ctypes.wintypes import HINSTANCE, HWND, LPCWSTR, LPWSTR, MAX_PATH, POINT, RECT, SIZE, UINT
@@ -251,19 +223,14 @@ class PreferencesDialog(tk.Toplevel):
 
         self.parent = parent
         self.callback = callback
-        if sys.platform == 'darwin':
-            # LANG: File > Preferences menu entry for macOS
-            self.title(_('Preferences'))
-
-        else:
-            # LANG: File > Settings (macOS)
-            self.title(_('Settings'))
+        # LANG: File > Settings (macOS)
+        self.title(_('Settings'))
 
         if parent.winfo_viewable():
             self.transient(parent)
 
         # position over parent
-        if sys.platform != 'darwin' or parent.winfo_rooty() > 0:  # http://core.tcl.tk/tk/tktview/c84f660833546b1b84e7
+        if parent.winfo_rooty() > 0:  # http://core.tcl.tk/tk/tktview/c84f660833546b1b84e7
             # TODO this is fixed supposedly.
             self.geometry(f'+{parent.winfo_rootx()}+{parent.winfo_rooty()}')
 
@@ -271,10 +238,6 @@ class PreferencesDialog(tk.Toplevel):
         if sys.platform == 'win32':
             self.attributes('-toolwindow', tk.TRUE)
 
-        elif sys.platform == 'darwin':
-            # http://wiki.tcl.tk/13428
-            parent.call('tk::unsupported::MacWindowStyle', 'style', self, 'utility')
-
         self.resizable(tk.FALSE, tk.FALSE)
 
         self.cmdr: str | bool | None = False  # Note if Cmdr changes in the Journal
@@ -302,19 +265,15 @@ class PreferencesDialog(tk.Toplevel):
         self.__setup_appearance_tab(notebook)
         self.__setup_plugin_tab(notebook)
 
-        if sys.platform == 'darwin':
-            self.protocol("WM_DELETE_WINDOW", self.apply)  # close button applies changes
-
-        else:
-            buttonframe = ttk.Frame(frame)
-            buttonframe.grid(padx=self.PADX, pady=self.PADX, sticky=tk.NSEW)
-            buttonframe.columnconfigure(0, weight=1)
-            ttk.Label(buttonframe).grid(row=0, column=0)  # spacer
-            # LANG: 'OK' button on Settings/Preferences window
-            button = ttk.Button(buttonframe, text=_('OK'), command=self.apply)
-            button.grid(row=0, column=1, sticky=tk.E)
-            button.bind("<Return>", lambda event: self.apply())
-            self.protocol("WM_DELETE_WINDOW", self._destroy)
+        buttonframe = ttk.Frame(frame)
+        buttonframe.grid(padx=self.PADX, pady=self.PADX, sticky=tk.NSEW)
+        buttonframe.columnconfigure(0, weight=1)
+        ttk.Label(buttonframe).grid(row=0, column=0)  # spacer
+        # LANG: 'OK' button on Settings/Preferences window
+        button = ttk.Button(buttonframe, text=_('OK'), command=self.apply)
+        button.grid(row=0, column=1, sticky=tk.E)
+        button.bind("<Return>", lambda event: self.apply())
+        self.protocol("WM_DELETE_WINDOW", self._destroy)
 
         # FIXME: Why are these being called when *creating* the Settings window?
         # Selectively disable buttons depending on output settings
@@ -405,11 +364,7 @@ class PreferencesDialog(tk.Toplevel):
         self.outdir_entry = nb.Entry(output_frame, takefocus=False)
         self.outdir_entry.grid(columnspan=2, padx=self.PADX, pady=self.BOXY, sticky=tk.EW, row=row.get())
 
-        if sys.platform == 'darwin':
-            text = (_('Change...'))  # LANG: macOS Preferences - files location selection button
-
-        else:
-            text = (_('Browse...'))  # LANG: NOT-macOS Settings - files location selection button
+        text = (_('Browse...'))  # LANG: NOT-macOS Settings - files location selection button
 
         self.outbutton = nb.Button(
             output_frame,
@@ -455,11 +410,7 @@ class PreferencesDialog(tk.Toplevel):
 
         self.logdir_entry.grid(columnspan=4, padx=self.PADX, pady=self.BOXY, sticky=tk.EW, row=row.get())
 
-        if sys.platform == 'darwin':
-            text = (_('Change...'))  # LANG: macOS Preferences - files location selection button
-
-        else:
-            text = (_('Browse...'))  # LANG: NOT-macOS Setting - files location selection button
+        text = (_('Browse...'))  # LANG: NOT-macOS Setting - files location selection button
 
         with row as cur_row:
             self.logbutton = nb.Button(
@@ -499,7 +450,7 @@ class PreferencesDialog(tk.Toplevel):
                 variable=self.capi_fleetcarrier
             ).grid(columnspan=4, padx=self.BUTTONX, pady=self.PADY, sticky=tk.W, row=row.get())
 
-        if sys.platform in ('darwin', 'win32'):
+        if sys.platform == 'win32':
             ttk.Separator(config_frame, orient=tk.HORIZONTAL).grid(
                 columnspan=4, padx=self.PADX, pady=self.SEPY, sticky=tk.EW, row=row.get()
             )
@@ -511,49 +462,21 @@ class PreferencesDialog(tk.Toplevel):
             with row as cur_row:
                 nb.Label(
                     config_frame,
-                    text=_('Keyboard shortcut') if  # LANG: Hotkey/Shortcut settings prompt on OSX
-                    sys.platform == 'darwin' else
-                    _('Hotkey')  # LANG: Hotkey/Shortcut settings prompt on Windows
+                    text=_('Hotkey')  # LANG: Hotkey/Shortcut settings prompt on Windows
                 ).grid(padx=self.PADX, pady=self.PADY, sticky=tk.W, row=cur_row)
 
-                if sys.platform == 'darwin' and not was_accessible_at_launch:
-                    if AXIsProcessTrusted():
-                        # Shortcut settings prompt on OSX
-                        nb.Label(
-                            config_frame,
-                            # LANG: macOS Preferences > Configuration - restart the app message
-                            text=_('Re-start {APP} to use shortcuts').format(APP=applongname),
-                            foreground='firebrick'
-                        ).grid(padx=self.PADX, pady=self.PADY, sticky=tk.W, row=cur_row)
+                self.hotkey_text = nb.Entry(config_frame, width=30, justify=tk.CENTER)
+                self.hotkey_text.insert(
+                    0,
+                    # No hotkey/shortcut currently defined
+                    # TODO: display Only shows up on windows
+                    # LANG: No hotkey/shortcut set
+                    hotkeymgr.display(self.hotkey_code, self.hotkey_mods) if self.hotkey_code else _('None')
+                )
 
-                    else:
-                        # Shortcut settings prompt on OSX
-                        nb.Label(
-                            config_frame,
-                            # LANG: macOS - Configuration - need to grant the app permission for keyboard shortcuts
-                            text=_('{APP} needs permission to use shortcuts').format(APP=applongname),
-                            foreground='firebrick'
-                        ).grid(columnspan=4, padx=self.PADX, pady=self.PADY, sticky=tk.W, row=cur_row)
-
-                        # LANG: Shortcut settings button on OSX
-                        nb.Button(config_frame, text=_('Open System Preferences'), command=self.enableshortcuts).grid(
-                            padx=self.PADX, pady=self.BOXY, sticky=tk.E, row=cur_row
-                        )
-
-                else:
-                    self.hotkey_text = nb.Entry(config_frame, width=(
-                        20 if sys.platform == 'darwin' else 30), justify=tk.CENTER)
-                    self.hotkey_text.insert(
-                        0,
-                        # No hotkey/shortcut currently defined
-                        # TODO: display Only shows up on darwin or windows
-                        # LANG: No hotkey/shortcut set
-                        hotkeymgr.display(self.hotkey_code, self.hotkey_mods) if self.hotkey_code else _('None')
-                    )
-
-                    self.hotkey_text.bind('<FocusIn>', self.hotkeystart)
-                    self.hotkey_text.bind('<FocusOut>', self.hotkeyend)
-                    self.hotkey_text.grid(column=1, columnspan=2, pady=self.BOXY, sticky=tk.W, row=cur_row)
+                self.hotkey_text.bind('<FocusIn>', self.hotkeystart)
+                self.hotkey_text.bind('<FocusOut>', self.hotkeyend)
+                self.hotkey_text.grid(column=1, columnspan=2, pady=self.BOXY, sticky=tk.W, row=cur_row)
 
                 # Hotkey/Shortcut setting
                 self.hotkey_only_btn = nb.Checkbutton(
@@ -1070,14 +993,6 @@ class PreferencesDialog(tk.Toplevel):
     def tabchanged(self, event: tk.Event) -> None:
         """Handle preferences active tab changing."""
         self.outvarchanged()
-        if sys.platform == 'darwin':
-            # Hack to recompute size so that buttons show up under Mojave
-            notebook = event.widget
-            frame = self.nametowidget(notebook.winfo_parent())
-            temp = nb.Label(frame)
-            temp.grid()
-            temp.update_idletasks()
-            temp.destroy()
 
     def outvarchanged(self, event: Optional[tk.Event] = None) -> None:
         """Handle Output tab variable changes."""
@@ -1139,16 +1054,6 @@ class PreferencesDialog(tk.Toplevel):
             entryfield.insert(0, '\\'.join(display))
 
         #                                                   None if path doesn't exist
-        elif sys.platform == 'darwin' and NSFileManager.defaultManager().componentsToDisplayForPath_(pathvar.get()):
-            if pathvar.get().startswith(config.home):
-                display = ['~'] + NSFileManager.defaultManager().componentsToDisplayForPath_(pathvar.get())[
-                    len(NSFileManager.defaultManager().componentsToDisplayForPath_(config.home)):
-                ]
-
-            else:
-                display = NSFileManager.defaultManager().componentsToDisplayForPath_(pathvar.get())
-
-            entryfield.insert(0, '/'.join(display))
         else:
             if pathvar.get().startswith(config.home):
                 entryfield.insert(0, '~' + pathvar.get()[len(config.home):])
@@ -1288,7 +1193,7 @@ class PreferencesDialog(tk.Toplevel):
 
         config.set('capi_fleetcarrier', self.capi_fleetcarrier.get())
 
-        if sys.platform in ('darwin', 'win32'):
+        if sys.platform == 'win32':
             config.set('hotkey_code', self.hotkey_code)
             config.set('hotkey_mods', self.hotkey_mods)
             config.set('hotkey_always', int(not self.hotkey_only.get()))
@@ -1333,25 +1238,3 @@ class PreferencesDialog(tk.Toplevel):
 
         self.parent.wm_attributes('-topmost', 1 if config.get_int('always_ontop') else 0)
         self.destroy()
-
-    if sys.platform == 'darwin':
-        def enableshortcuts(self) -> None:
-            """Set up macOS preferences shortcut."""
-            self.apply()
-            # popup System Preferences dialog
-            try:
-                # http://stackoverflow.com/questions/6652598/cocoa-button-opens-a-system-preference-page/6658201
-                from ScriptingBridge import SBApplication  # type: ignore
-                sysprefs = 'com.apple.systempreferences'
-                prefs = SBApplication.applicationWithBundleIdentifier_(sysprefs)
-                pane = [x for x in prefs.panes() if x.id() == 'com.apple.preference.security'][0]
-                prefs.setCurrentPane_(pane)
-                anchor = [x for x in pane.anchors() if x.name() == 'Privacy_Accessibility'][0]
-                anchor.reveal()
-                prefs.activate()
-
-            except Exception:
-                AXIsProcessTrustedWithOptions({kAXTrustedCheckOptionPrompt: True})
-
-            if not config.shutting_down:
-                self.parent.event_generate('<<Quit>>', when="tail")

From 57cd75e75ebb1a61eebac1f4b3e17b0356d6b57e Mon Sep 17 00:00:00 2001
From: David Sangrey <rixxan@hullseals.space>
Date: Wed, 27 Mar 2024 19:39:51 -0400
Subject: [PATCH 05/22] [2186] Additional Files

---
 journal_lock.py             |  4 --
 plugins/eddn.py             |  4 +-
 protocol.py                 | 63 +------------------------
 stats.py                    | 13 +----
 td.py                       |  2 +-
 tests/config/_old_config.py | 94 +------------------------------------
 theme.py                    | 21 +--------
 update.py                   | 24 ----------
 8 files changed, 9 insertions(+), 216 deletions(-)

diff --git a/journal_lock.py b/journal_lock.py
index 4d04992f..3a4dad52 100644
--- a/journal_lock.py
+++ b/journal_lock.py
@@ -218,10 +218,6 @@ class JournalLock:
             if sys.platform == 'win32':
                 self.attributes('-toolwindow', tk.TRUE)
 
-            elif sys.platform == 'darwin':
-                # http://wiki.tcl.tk/13428
-                parent.call('tk::unsupported::MacWindowStyle', 'style', self, 'utility')
-
             self.resizable(tk.FALSE, tk.FALSE)
 
             frame = ttk.Frame(self)
diff --git a/plugins/eddn.py b/plugins/eddn.py
index 522ce6cb..b7133725 100644
--- a/plugins/eddn.py
+++ b/plugins/eddn.py
@@ -282,7 +282,7 @@ class EDDNSender:
             msg['header'] = {
                 # We have to lie and say it's *this* version, but denote that
                 # it might not actually be this version.
-                'softwareName': f'{applongname} [{system() if sys.platform != "darwin" else "Mac OS"}]'
+                'softwareName': f'{applongname} [{system()}]'
                                 ' (legacy replay)',
                 'softwareVersion': str(appversion_nobuild()),
                 'uploaderID': cmdr,
@@ -1074,7 +1074,7 @@ class EDDN:
             gb = this.game_build
 
         return {
-            'softwareName':    f'{applongname} [{system() if sys.platform != "darwin" else "Mac OS"}]',
+            'softwareName':    f'{applongname} [{system()}]',
             'softwareVersion': str(appversion_nobuild()),
             'uploaderID':      this.cmdr_name,
             'gameversion':     gv,
diff --git a/protocol.py b/protocol.py
index f3b956d2..0f1c157f 100644
--- a/protocol.py
+++ b/protocol.py
@@ -58,66 +58,7 @@ class GenericProtocolHandler:
             self.master.event_generate('<<CompanionAuthEvent>>', when="tail")
 
 
-if sys.platform == 'darwin' and getattr(sys, 'frozen', False):  # noqa: C901 # its guarding ALL macos stuff.
-    import struct
-
-    import objc  # type: ignore
-    from AppKit import NSAppleEventManager, NSObject  # type: ignore
-
-    kInternetEventClass = kAEGetURL = struct.unpack('>l', b'GURL')[0]  # noqa: N816 # API names
-    keyDirectObject = struct.unpack('>l', b'----')[0]  # noqa: N816 # API names
-
-    class DarwinProtocolHandler(GenericProtocolHandler):
-        """
-        MacOS protocol handler implementation.
-
-        Uses macOS event stuff.
-        """
-
-        POLL = 100  # ms
-
-        def start(self, master: 'tkinter.Tk') -> None:
-            """Start Protocol Handler."""
-            GenericProtocolHandler.start(self, master)
-            self.lasturl: str | None = None
-            self.eventhandler = EventHandler.alloc().init()
-
-        def poll(self) -> None:
-            """Poll event until URL is updated."""
-            # No way of signalling to Tkinter from within the callback handler block that doesn't cause Python to crash,
-            # so poll. TODO: Resolved?
-            if self.lasturl and self.lasturl.startswith(self.redirect):
-                self.event(self.lasturl)
-                self.lasturl = None
-
-    class EventHandler(NSObject):
-        """Handle NSAppleEventManager IPC stuff."""
-
-        def init(self) -> None:
-            """
-            Init method for handler.
-
-            (I'd assume this is related to the subclassing of NSObject for why its not __init__)
-            """
-            self = objc.super(EventHandler, self).init()
-            NSAppleEventManager.sharedAppleEventManager().setEventHandler_andSelector_forEventClass_andEventID_(
-                self,
-                'handleEvent:withReplyEvent:',
-                kInternetEventClass,
-                kAEGetURL
-            )
-            return self
-
-        def handleEvent_withReplyEvent_(self, event, replyEvent) -> None:  # noqa: N802 N803 # Required to override
-            """Actual event handling from NSAppleEventManager."""
-            protocolhandler.lasturl = parse.unquote(
-                event.paramDescriptorForKeyword_(keyDirectObject).stringValue()
-            ).strip()
-
-            protocolhandler.master.after(DarwinProtocolHandler.POLL, protocolhandler.poll)
-
-
-elif (config.auth_force_edmc_protocol
+if (config.auth_force_edmc_protocol
       or (
           sys.platform == 'win32'
           and getattr(sys, 'frozen', False)
@@ -480,8 +421,6 @@ def get_handler_impl() -> Type[GenericProtocolHandler]:
 
     :return: An instantiatable GenericProtocolHandler
     """
-    if sys.platform == 'darwin' and getattr(sys, 'frozen', False):
-        return DarwinProtocolHandler  # pyright: reportUnboundVariable=false
 
     if (
         (sys.platform == 'win32' and config.auth_force_edmc_protocol)
diff --git a/stats.py b/stats.py
index 4351e297..c377e5d3 100644
--- a/stats.py
+++ b/stats.py
@@ -374,7 +374,7 @@ class StatsResults(tk.Toplevel):
             self.transient(parent)
 
         # position over parent
-        if sys.platform != 'darwin' or parent.winfo_rooty() > 0:  # http://core.tcl.tk/tk/tktview/c84f660833546b1b84e7
+        if parent.winfo_rooty() > 0:  # http://core.tcl.tk/tk/tktview/c84f660833546b1b84e7
             self.geometry(f"+{parent.winfo_rootx()}+{parent.winfo_rooty()}")
 
         # remove decoration
@@ -382,10 +382,6 @@ class StatsResults(tk.Toplevel):
         if sys.platform == 'win32':
             self.attributes('-toolwindow', tk.TRUE)
 
-        elif sys.platform == 'darwin':
-            # http://wiki.tcl.tk/13428
-            parent.call('tk::unsupported::MacWindowStyle', 'style', self, 'utility')
-
         frame = ttk.Frame(self)
         frame.grid(sticky=tk.NSEW)
 
@@ -423,13 +419,6 @@ class StatsResults(tk.Toplevel):
         ttk.Frame(page).grid(pady=5)         # bottom spacer
         notebook.add(page, text=_('Ships'))  # LANG: Status dialog title
 
-        if sys.platform != 'darwin':
-            buttonframe = ttk.Frame(frame)
-            buttonframe.grid(padx=10, pady=(0, 10), sticky=tk.NSEW)  # type: ignore # the tuple is supported
-            buttonframe.columnconfigure(0, weight=1)
-            ttk.Label(buttonframe).grid(row=0, column=0)  # spacer
-            ttk.Button(buttonframe, text='OK', command=self.destroy).grid(row=0, column=1, sticky=tk.E)
-
         # wait for window to appear on screen before calling grab_set
         self.wait_visibility()
         self.grab_set()
diff --git a/td.py b/td.py
index 6a588f57..d2b5dbdd 100644
--- a/td.py
+++ b/td.py
@@ -32,7 +32,7 @@ def export(data: CAPIData) -> None:
     with open(data_path / data_filename, 'wb') as h:
         # Format described here: https://github.com/eyeonus/Trade-Dangerous/wiki/Price-Data
         h.write('#! trade.py import -\n'.encode('utf-8'))
-        this_platform = "Mac OS" if sys.platform == 'darwin' else system()
+        this_platform = system()
         cmdr_name = data['commander']['name'].strip()
         h.write(
             f'# Created by {applongname} {appversion()} on {this_platform} for Cmdr {cmdr_name}.\n'.encode('utf-8')
diff --git a/tests/config/_old_config.py b/tests/config/_old_config.py
index 690c75eb..35ae19e3 100644
--- a/tests/config/_old_config.py
+++ b/tests/config/_old_config.py
@@ -13,13 +13,7 @@ from EDMCLogging import get_main_logger
 
 logger = get_main_logger()
 
-if sys.platform == 'darwin':
-    from Foundation import (  # type: ignore
-        NSApplicationSupportDirectory, NSBundle, NSDocumentDirectory, NSSearchPathForDirectoriesInDomains,
-        NSUserDefaults, NSUserDomainMask
-    )
-
-elif sys.platform == 'win32':
+if sys.platform == 'win32':
     import ctypes
     import uuid
     from ctypes.wintypes import DWORD, HANDLE, HKEY, LONG, LPCVOID, LPCWSTR
@@ -115,91 +109,7 @@ class OldConfig:
     OUT_EDDN_DELAY = 4096
     OUT_STATION_ANY = OUT_EDDN_SEND_STATION_DATA | OUT_MKT_TD | OUT_MKT_CSV
 
-    if sys.platform == 'darwin':  # noqa: C901 # It's gating *all* the functions
-
-        def __init__(self):
-            self.app_dir = join(
-                NSSearchPathForDirectoriesInDomains(NSApplicationSupportDirectory, NSUserDomainMask, True)[0], appname
-            )
-            if not isdir(self.app_dir):
-                mkdir(self.app_dir)
-
-            self.plugin_dir = join(self.app_dir, 'plugins')
-            if not isdir(self.plugin_dir):
-                mkdir(self.plugin_dir)
-
-            if getattr(sys, 'frozen', False):
-                self.internal_plugin_dir = normpath(join(dirname(sys.executable), pardir, 'Library', 'plugins'))
-                self.respath = normpath(join(dirname(sys.executable), pardir, 'Resources'))
-                self.identifier = NSBundle.mainBundle().bundleIdentifier()
-
-            else:
-                self.internal_plugin_dir = join(dirname(__file__), 'plugins')
-                self.respath = dirname(__file__)
-                # Don't use Python's settings if interactive
-                self.identifier = f'uk.org.marginal.{appname.lower()}'
-                NSBundle.mainBundle().infoDictionary()['CFBundleIdentifier'] = self.identifier
-
-            self.default_journal_dir: str | None = join(
-                NSSearchPathForDirectoriesInDomains(NSApplicationSupportDirectory, NSUserDomainMask, True)[0],
-                'Frontier Developments',
-                'Elite Dangerous'
-            )
-            self.home = expanduser('~')
-
-            self.defaults = NSUserDefaults.standardUserDefaults()
-            self.settings = dict(self.defaults.persistentDomainForName_(self.identifier) or {})  # make writeable
-
-            # Check out_dir exists
-            if not self.get('outdir') or not isdir(str(self.get('outdir'))):
-                self.set('outdir', NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, True)[0])
-
-        def get(self, key: str, default: None | list | str = None) -> None | list | str:
-            """Look up a string configuration value."""
-            val = self.settings.get(key)
-            if val is None:
-                return default
-
-            if isinstance(val, str):
-                return str(val)
-
-            if isinstance(val, list):
-                return list(val)  # make writeable
-
-            return default
-
-        def getint(self, key: str, default: int = 0) -> int:
-            """Look up an integer configuration value."""
-            try:
-                return int(self.settings.get(key, default))  # should already be int, but check by casting
-
-            except ValueError as e:
-                logger.error(f"Failed to int({key=})", exc_info=e)
-                return default
-
-            except Exception as e:
-                logger.debug('The exception type is ...', exc_info=e)
-                return default
-
-        def set(self, key: str, val: int | str | list) -> None:
-            """Set value on the specified configuration key."""
-            self.settings[key] = val
-
-        def delete(self, key: str) -> None:
-            """Delete the specified configuration key."""
-            self.settings.pop(key, None)
-
-        def save(self) -> None:
-            """Save current configuration to disk."""
-            self.defaults.setPersistentDomain_forName_(self.settings, self.identifier)
-            self.defaults.synchronize()
-
-        def close(self) -> None:
-            """Close the configuration."""
-            self.save()
-            self.defaults = None
-
-    elif sys.platform == 'win32':
+    if sys.platform == 'win32':
 
         def __init__(self):
             self.app_dir = join(known_folder_path(FOLDERID_LocalAppData), appname)  # type: ignore
diff --git a/theme.py b/theme.py
index bfb76c55..b128bc91 100644
--- a/theme.py
+++ b/theme.py
@@ -268,8 +268,7 @@ class _Theme:
             # (Mostly) system colors
             style = ttk.Style()
             self.current = {
-                'background': (sys.platform == 'darwin' and 'systemMovableModalBackground' or
-                               style.lookup('TLabel', 'background')),
+                'background': (style.lookup('TLabel', 'background')),
                 'foreground': style.lookup('TLabel', 'foreground'),
                 'activebackground': (sys.platform == 'win32' and 'SystemHighlight' or
                                      style.lookup('TLabel', 'background', ['active'])),
@@ -366,8 +365,6 @@ class _Theme:
                 if 'bg' not in attribs:
                     widget['background'] = self.current['background']
                     widget['activebackground'] = self.current['activebackground']
-                    if sys.platform == 'darwin' and isinstance(widget, tk.Button):
-                        widget['highlightbackground'] = self.current['background']
 
                 if 'font' not in attribs:
                     widget['font'] = self.current['font']
@@ -426,21 +423,7 @@ class _Theme:
             return  # Don't need to mess with the window manager
         self.active = theme
 
-        if sys.platform == 'darwin':
-            from AppKit import NSAppearance, NSApplication, NSMiniaturizableWindowMask, NSResizableWindowMask
-            root.update_idletasks()  # need main window to be created
-            if theme == self.THEME_DEFAULT:
-                appearance = NSAppearance.appearanceNamed_('NSAppearanceNameAqua')
-
-            else:  # Dark (Transparent only on win32)
-                appearance = NSAppearance.appearanceNamed_('NSAppearanceNameDarkAqua')
-
-            for window in NSApplication.sharedApplication().windows():
-                window.setStyleMask_(window.styleMask() & ~(
-                    NSMiniaturizableWindowMask | NSResizableWindowMask))  # disable zoom
-                window.setAppearance_(appearance)
-
-        elif sys.platform == 'win32':
+        if sys.platform == 'win32':
             GWL_STYLE = -16  # noqa: N806 # ctypes
             WS_MAXIMIZEBOX = 0x00010000  # noqa: N806 # ctypes
             # tk8.5.9/win/tkWinWm.c:342
diff --git a/update.py b/update.py
index 024aeb09..e0b8f97b 100644
--- a/update.py
+++ b/update.py
@@ -115,21 +115,6 @@ class Updater:
 
             return
 
-        if sys.platform == 'darwin':
-            import objc
-
-            try:
-                objc.loadBundle(
-                    'Sparkle', globals(), join(dirname(sys.executable), os.pardir, 'Frameworks', 'Sparkle.framework')
-                )
-                # loadBundle presumably supplies `SUUpdater`
-                self.updater = SUUpdater.sharedUpdater()  # noqa: F821
-
-            except Exception:
-                # can't load framework - not frozen or not included in app bundle?
-                print_exc()
-                self.updater = None
-
     def set_automatic_updates_check(self, onoroff: bool) -> None:
         """
         Set (Win)Sparkle to perform automatic update checks, or not.
@@ -142,9 +127,6 @@ class Updater:
         if sys.platform == 'win32' and self.updater:
             self.updater.win_sparkle_set_automatic_check_for_updates(onoroff)
 
-        if sys.platform == 'darwin' and self.updater:
-            self.updater.SUEnableAutomaticChecks(onoroff)
-
     def check_for_updates(self) -> None:
         """Trigger the requisite method to check for an update."""
         if self.use_internal():
@@ -155,9 +137,6 @@ class Updater:
         elif sys.platform == 'win32' and self.updater:
             self.updater.win_sparkle_check_update_with_ui()
 
-        elif sys.platform == 'darwin' and self.updater:
-            self.updater.checkForUpdates_(None)
-
     def check_appcast(self) -> EDMCVersion | None:
         """
         Manually (no Sparkle or WinSparkle) check the update_feed appcast file.
@@ -184,9 +163,6 @@ class Updater:
 
             return None
 
-        if sys.platform == 'darwin':
-            sparkle_platform = 'macos'
-
         else:
             # For *these* purposes anything else is the same as 'windows', as
             # non-win32 would be running from source.

From 05eaf059389a25d16c778ea6861e0836a71184f3 Mon Sep 17 00:00:00 2001
From: David Sangrey <rixxan@hullseals.space>
Date: Wed, 27 Mar 2024 19:41:01 -0400
Subject: [PATCH 06/22] [Minor] Remove Unused Import

For Now
---
 ttkHyperlinkLabel.py | 1 -
 1 file changed, 1 deletion(-)

diff --git a/ttkHyperlinkLabel.py b/ttkHyperlinkLabel.py
index 0ca87fc7..3b65d6b9 100644
--- a/ttkHyperlinkLabel.py
+++ b/ttkHyperlinkLabel.py
@@ -22,7 +22,6 @@ from __future__ import annotations
 
 import sys
 import tkinter as tk
-import warnings
 import webbrowser
 from tkinter import font as tk_font
 from tkinter import ttk

From 08818785d051b81a7ad88c6975b0be0df88a07c7 Mon Sep 17 00:00:00 2001
From: David Sangrey <rixxan@hullseals.space>
Date: Wed, 27 Mar 2024 20:03:23 -0400
Subject: [PATCH 07/22] [2186] HyperLinkLabel first pass

---
 ttkHyperlinkLabel.py | 21 ++++++---------------
 1 file changed, 6 insertions(+), 15 deletions(-)

diff --git a/ttkHyperlinkLabel.py b/ttkHyperlinkLabel.py
index 3b65d6b9..de906510 100644
--- a/ttkHyperlinkLabel.py
+++ b/ttkHyperlinkLabel.py
@@ -32,7 +32,7 @@ if TYPE_CHECKING:
 
 
 # FIXME: Split this into multi-file module to separate the platforms
-class HyperlinkLabel(sys.platform == 'darwin' and tk.Label or ttk.Label):  # type: ignore
+class HyperlinkLabel(tk.Label or ttk.Label):  # type: ignore
     """Clickable label for HTTP links."""
 
     def __init__(self, master: ttk.Frame | tk.Frame | None = None, **kw: Any) -> None:
@@ -50,22 +50,14 @@ class HyperlinkLabel(sys.platform == 'darwin' and tk.Label or ttk.Label):  # typ
         self.foreground = kw.get('foreground', 'blue')
         self.disabledforeground = kw.pop('disabledforeground', ttk.Style().lookup(
             'TLabel', 'foreground', ('disabled',)))  # ttk.Label doesn't support disabledforeground option
-
-        if sys.platform == 'darwin':
-            # Use tk.Label 'cos can't set ttk.Label background - http://www.tkdocs.com/tutorial/styles.html#whydifficult
-            kw['background'] = kw.pop('background', 'systemDialogBackgroundActive')
-            kw['anchor'] = kw.pop('anchor', tk.W)  # like ttk.Label
-            tk.Label.__init__(self, master, **kw)
-
-        else:
-            ttk.Label.__init__(self, master, **kw)
+        ttk.Label.__init__(self, master, **kw)
 
         self.bind('<Button-1>', self._click)
 
         self.menu = tk.Menu(tearoff=tk.FALSE)
         # LANG: Label for 'Copy' as in 'Copy and Paste'
         self.menu.add_command(label=_('Copy'), command=self.copy)  # As in Copy and Paste
-        self.bind(sys.platform == 'darwin' and '<Button-2>' or '<Button-3>', self._contextmenu)
+        self.bind('<Button-3>', self._contextmenu)
 
         self.bind('<Enter>', self._enter)
         self.bind('<Leave>', self._leave)
@@ -106,10 +98,9 @@ class HyperlinkLabel(sys.platform == 'darwin' and tk.Label or ttk.Label):  # typ
             if state == tk.DISABLED:
                 kw['cursor'] = 'arrow'  # System default
             elif self.url and (kw['text'] if 'text' in kw else self['text']):
-                kw['cursor'] = 'pointinghand' if sys.platform == 'darwin' else 'hand2'
+                kw['cursor'] = 'hand2'
             else:
-                kw['cursor'] = 'notallowed' if sys.platform == 'darwin' else (
-                    'no' if sys.platform == 'win32' else 'circle')
+                kw['cursor'] = ('no' if sys.platform == 'win32' else 'circle')
 
         return super().configure(cnf, **kw)
 
@@ -139,7 +130,7 @@ class HyperlinkLabel(sys.platform == 'darwin' and tk.Label or ttk.Label):  # typ
 
     def _contextmenu(self, event: tk.Event) -> None:
         if self['text'] and (self.popup_copy(self['text']) if callable(self.popup_copy) else self.popup_copy):
-            self.menu.post(sys.platform == 'darwin' and event.x_root + 1 or event.x_root, event.y_root)
+            self.menu.post(event.x_root, event.y_root)
 
     def copy(self) -> None:
         """Copy the current text to the clipboard."""

From f7b39f8dafedf453f6f80e278f88d8e12ccced60 Mon Sep 17 00:00:00 2001
From: David Sangrey <rixxan@hullseals.space>
Date: Wed, 27 Mar 2024 20:10:55 -0400
Subject: [PATCH 08/22] [2186] MyNB First Pass

---
 myNotebook.py | 66 +++++++--------------------------------------------
 1 file changed, 9 insertions(+), 57 deletions(-)

diff --git a/myNotebook.py b/myNotebook.py
index dbfc9250..efae591c 100644
--- a/myNotebook.py
+++ b/myNotebook.py
@@ -16,13 +16,7 @@ import sys
 import tkinter as tk
 from tkinter import ttk
 
-# Can't do this with styles on OSX - http://www.tkdocs.com/tutorial/styles.html#whydifficult
-if sys.platform == 'darwin':
-    from platform import mac_ver
-    PAGEFG = 'systemButtonText'
-    PAGEBG = 'systemButtonActiveDarkShadow'
-
-elif sys.platform == 'win32':
+if sys.platform == 'win32':
     PAGEFG = 'SystemWindowText'
     PAGEBG = 'SystemWindow'  # typically white
 
@@ -35,14 +29,7 @@ class Notebook(ttk.Notebook):
         ttk.Notebook.__init__(self, master, **kw)
         style = ttk.Style()
 
-        if sys.platform == 'darwin':
-            if list(map(int, mac_ver()[0].split('.'))) >= [10, 10]:
-                # Hack for tab appearance with 8.5 on Yosemite & El Capitan. For proper fix see
-                # https://github.com/tcltk/tk/commit/55c4dfca9353bbd69bbcec5d63bf1c8dfb461e25
-                style.configure('TNotebook.Tab', padding=(12, 10, 12, 2))
-                style.map('TNotebook.Tab', foreground=[('selected', '!background', 'systemWhite')])
-            self.grid(sticky=tk.NSEW)  # Already padded apropriately
-        elif sys.platform == 'win32':
+        if sys.platform == 'win32':
             style.configure('nb.TFrame',                          background=PAGEBG)
             style.configure('nb.TButton',                         background=PAGEBG)
             style.configure('nb.TCheckbutton', foreground=PAGEFG, background=PAGEBG)
@@ -60,11 +47,7 @@ class Frame(sys.platform == 'darwin' and tk.Frame or ttk.Frame):  # type: ignore
     """Custom t(t)k.Frame class to fix some display issues."""
 
     def __init__(self, master: ttk.Notebook | None = None, **kw):
-        if sys.platform == 'darwin':
-            kw['background'] = kw.pop('background', PAGEBG)
-            tk.Frame.__init__(self, master, **kw)
-            tk.Frame(self).grid(pady=5)
-        elif sys.platform == 'win32':
+        if sys.platform == 'win32':
             ttk.Frame.__init__(self, master, style='nb.TFrame', **kw)
             ttk.Frame(self).grid(pady=5)  # top spacer
         else:
@@ -91,21 +74,14 @@ class Entry(sys.platform == 'darwin' and tk.Entry or ttk.Entry):  # type: ignore
     """Custom t(t)k.Entry class to fix some display issues."""
 
     def __init__(self, master: ttk.Frame | None = None, **kw):
-        if sys.platform == 'darwin':
-            kw['highlightbackground'] = kw.pop('highlightbackground', PAGEBG)
-            tk.Entry.__init__(self, master, **kw)
-        else:
-            ttk.Entry.__init__(self, master, **kw)
+        ttk.Entry.__init__(self, master, **kw)
 
 
 class Button(sys.platform == 'darwin' and tk.Button or ttk.Button):  # type: ignore
     """Custom t(t)k.Button class to fix some display issues."""
 
     def __init__(self, master: ttk.Frame | None = None, **kw):
-        if sys.platform == 'darwin':
-            kw['highlightbackground'] = kw.pop('highlightbackground', PAGEBG)
-            tk.Button.__init__(self, master, **kw)
-        elif sys.platform == 'win32':
+        if sys.platform == 'win32':
             ttk.Button.__init__(self, master, style='nb.TButton', **kw)
         else:
             ttk.Button.__init__(self, master, **kw)
@@ -115,29 +91,14 @@ class ColoredButton(sys.platform == 'darwin' and tk.Label or tk.Button):  # type
     """Custom t(t)k.ColoredButton class to fix some display issues."""
 
     def __init__(self, master: ttk.Frame | None = None, **kw):
-        if sys.platform == 'darwin':
-            # Can't set Button background on OSX, so use a Label instead
-            kw['relief'] = kw.pop('relief', tk.RAISED)
-            self._command = kw.pop('command', None)
-            tk.Label.__init__(self, master, **kw)
-            self.bind('<Button-1>', self._press)
-        else:
-            tk.Button.__init__(self, master, **kw)
-
-    if sys.platform == 'darwin':
-        def _press(self, event):
-            self._command()
+        tk.Button.__init__(self, master, **kw)
 
 
 class Checkbutton(sys.platform == 'darwin' and tk.Checkbutton or ttk.Checkbutton):  # type: ignore
     """Custom t(t)k.Checkbutton class to fix some display issues."""
 
     def __init__(self, master: ttk.Frame | None = None, **kw):
-        if sys.platform == 'darwin':
-            kw['foreground'] = kw.pop('foreground', PAGEFG)
-            kw['background'] = kw.pop('background', PAGEBG)
-            tk.Checkbutton.__init__(self, master, **kw)
-        elif sys.platform == 'win32':
+        if sys.platform == 'win32':
             ttk.Checkbutton.__init__(self, master, style='nb.TCheckbutton', **kw)
         else:
             ttk.Checkbutton.__init__(self, master, **kw)
@@ -147,11 +108,7 @@ class Radiobutton(sys.platform == 'darwin' and tk.Radiobutton or ttk.Radiobutton
     """Custom t(t)k.Radiobutton class to fix some display issues."""
 
     def __init__(self, master: ttk.Frame | None = None, **kw):
-        if sys.platform == 'darwin':
-            kw['foreground'] = kw.pop('foreground', PAGEFG)
-            kw['background'] = kw.pop('background', PAGEBG)
-            tk.Radiobutton.__init__(self, master, **kw)
-        elif sys.platform == 'win32':
+        if sys.platform == 'win32':
             ttk.Radiobutton.__init__(self, master, style='nb.TRadiobutton', **kw)
         else:
             ttk.Radiobutton.__init__(self, master, **kw)
@@ -161,12 +118,7 @@ class OptionMenu(sys.platform == 'darwin' and tk.OptionMenu or ttk.OptionMenu):
     """Custom t(t)k.OptionMenu class to fix some display issues."""
 
     def __init__(self, master, variable, default=None, *values, **kw):
-        if sys.platform == 'darwin':
-            variable.set(default)
-            bg = kw.pop('background', PAGEBG)
-            tk.OptionMenu.__init__(self, master, variable, *values, **kw)
-            self['background'] = bg
-        elif sys.platform == 'win32':
+        if sys.platform == 'win32':
             # OptionMenu derives from Menubutton at the Python level, so uses Menubutton's style
             ttk.OptionMenu.__init__(self, master, variable, default, *values, style='nb.TMenubutton', **kw)
             self['menu'].configure(background=PAGEBG)

From c1b8533cb41c2e33d63311dac120ff7b0ddd4c29 Mon Sep 17 00:00:00 2001
From: David Sangrey <rixxan@hullseals.space>
Date: Wed, 27 Mar 2024 21:22:35 -0400
Subject: [PATCH 09/22] [2186] Simplify myNB Files

---
 docs/examples/click_counter/load.py |  4 +-
 myNotebook.py                       | 95 ++++++++++++-----------------
 plugins/coriolis.py                 |  8 +--
 plugins/edsm.py                     |  8 +--
 plugins/inara.py                    |  4 +-
 prefs.py                            | 22 +++----
 6 files changed, 62 insertions(+), 79 deletions(-)

diff --git a/docs/examples/click_counter/load.py b/docs/examples/click_counter/load.py
index ad90a084..f7f23837 100644
--- a/docs/examples/click_counter/load.py
+++ b/docs/examples/click_counter/load.py
@@ -7,6 +7,8 @@ from __future__ import annotations
 
 import logging
 import tkinter as tk
+from tkinter import ttk
+
 import myNotebook as nb  # noqa: N813
 from config import appname, config
 
@@ -63,7 +65,7 @@ class ClickCounter:
 
         # setup our config in a "Click Count: number"
         nb.Label(frame, text='Click Count').grid(row=current_row)
-        nb.Entry(frame, textvariable=self.click_count).grid(row=current_row, column=1)
+        ttk.Entry(frame, textvariable=self.click_count).grid(row=current_row, column=1)
         current_row += 1  # Always increment our row counter, makes for far easier tkinter design.
         return frame
 
diff --git a/myNotebook.py b/myNotebook.py
index efae591c..528664ba 100644
--- a/myNotebook.py
+++ b/myNotebook.py
@@ -1,12 +1,9 @@
 """
 Custom `ttk.Notebook` to fix various display issues.
 
-Hacks to fix various display issues with notebooks and their child widgets on
-OSX and Windows.
+Hacks to fix various display issues with notebooks and their child widgets on Windows.
 
 - Windows: page background should be White, not SystemButtonFace
-- OSX:     page background should be a darker gray than systemWindowBody
-           selected tab foreground should be White when the window is active
 
 Entire file may be imported by plugins.
 """
@@ -26,24 +23,17 @@ class Notebook(ttk.Notebook):
 
     def __init__(self, master: ttk.Frame | None = None, **kw):
 
-        ttk.Notebook.__init__(self, master, **kw)
+        super().__init__(master, **kw)
         style = ttk.Style()
-
-        if sys.platform == 'win32':
-            style.configure('nb.TFrame',                          background=PAGEBG)
-            style.configure('nb.TButton',                         background=PAGEBG)
-            style.configure('nb.TCheckbutton', foreground=PAGEFG, background=PAGEBG)
-            style.configure('nb.TMenubutton',  foreground=PAGEFG, background=PAGEBG)
-            style.configure('nb.TRadiobutton', foreground=PAGEFG, background=PAGEBG)
-            self.grid(padx=10, pady=10, sticky=tk.NSEW)
-        else:
-            self.grid(padx=10, pady=10, sticky=tk.NSEW)
+        style.configure('nb.TFrame',                          background=PAGEBG)
+        style.configure('nb.TButton',                         background=PAGEBG)
+        style.configure('nb.TCheckbutton', foreground=PAGEFG, background=PAGEBG)
+        style.configure('nb.TMenubutton',  foreground=PAGEFG, background=PAGEBG)
+        style.configure('nb.TRadiobutton', foreground=PAGEFG, background=PAGEBG)
+        self.grid(padx=10, pady=10, sticky=tk.NSEW)
 
 
-# FIXME: The real fix for this 'dynamic type' would be to split this whole
-#  thing into being a module with per-platform files, as we've done with config
-#  That would also make the code cleaner.
-class Frame(sys.platform == 'darwin' and tk.Frame or ttk.Frame):  # type: ignore
+class Frame(tk.Frame or ttk.Frame):  # type: ignore
     """Custom t(t)k.Frame class to fix some display issues."""
 
     def __init__(self, master: ttk.Notebook | None = None, **kw):
@@ -60,26 +50,25 @@ class Label(tk.Label):
     """Custom tk.Label class to fix some display issues."""
 
     def __init__(self, master: ttk.Frame | None = None, **kw):
-        # This format chosen over `sys.platform in (...)` as mypy and friends dont understand that
-        if sys.platform == 'win32':
-            kw['foreground'] = kw.pop('foreground', PAGEFG)
-            kw['background'] = kw.pop('background', PAGEBG)
-        else:
-            kw['foreground'] = kw.pop('foreground', ttk.Style().lookup('TLabel', 'foreground'))
-            kw['background'] = kw.pop('background', ttk.Style().lookup('TLabel', 'background'))
-        tk.Label.__init__(self, master, **kw)  # Just use tk.Label on all platforms
+        kw['foreground'] = kw.pop('foreground', PAGEFG if sys.platform == 'win32'
+                                  else ttk.Style().lookup('TLabel', 'foreground'))
+        kw['background'] = kw.pop('background', PAGEBG if sys.platform == 'win32'
+                                  else ttk.Style().lookup('TLabel', 'background'))
+        super().__init__(master, **kw)
 
 
-class Entry(sys.platform == 'darwin' and tk.Entry or ttk.Entry):  # type: ignore
+class Entry(ttk.Entry):  # type: ignore
     """Custom t(t)k.Entry class to fix some display issues."""
 
+    # DEPRECATED: Migrate to ttk.Entry. Will remove in 5.12 or later.
     def __init__(self, master: ttk.Frame | None = None, **kw):
-        ttk.Entry.__init__(self, master, **kw)
+        super().__init__(master, **kw)
 
 
-class Button(sys.platform == 'darwin' and tk.Button or ttk.Button):  # type: ignore
+class Button(tk.Button or ttk.Button):  # type: ignore
     """Custom t(t)k.Button class to fix some display issues."""
 
+    # DEPRECATED: Migrate to ttk.Button. Will remove in 5.12 or later.
     def __init__(self, master: ttk.Frame | None = None, **kw):
         if sys.platform == 'win32':
             ttk.Button.__init__(self, master, style='nb.TButton', **kw)
@@ -87,47 +76,39 @@ class Button(sys.platform == 'darwin' and tk.Button or ttk.Button):  # type: ign
             ttk.Button.__init__(self, master, **kw)
 
 
-class ColoredButton(sys.platform == 'darwin' and tk.Label or tk.Button):  # type: ignore
+class ColoredButton(tk.Label or tk.Button):  # type: ignore
     """Custom t(t)k.ColoredButton class to fix some display issues."""
 
+    # DEPRECATED: Migrate to tk.Button. Will remove in 5.12 or later.
     def __init__(self, master: ttk.Frame | None = None, **kw):
         tk.Button.__init__(self, master, **kw)
 
 
-class Checkbutton(sys.platform == 'darwin' and tk.Checkbutton or ttk.Checkbutton):  # type: ignore
+class Checkbutton(ttk.Checkbutton):
     """Custom t(t)k.Checkbutton class to fix some display issues."""
 
-    def __init__(self, master: ttk.Frame | None = None, **kw):
-        if sys.platform == 'win32':
-            ttk.Checkbutton.__init__(self, master, style='nb.TCheckbutton', **kw)
-        else:
-            ttk.Checkbutton.__init__(self, master, **kw)
+    def __init__(self, master=None, **kw):
+        style = 'nb.TCheckbutton' if sys.platform == 'win32' else None
+        super().__init__(master, style=style, **kw)  # type: ignore
 
 
-class Radiobutton(sys.platform == 'darwin' and tk.Radiobutton or ttk.Radiobutton):  # type: ignore
+class Radiobutton(ttk.Radiobutton):
     """Custom t(t)k.Radiobutton class to fix some display issues."""
 
     def __init__(self, master: ttk.Frame | None = None, **kw):
-        if sys.platform == 'win32':
-            ttk.Radiobutton.__init__(self, master, style='nb.TRadiobutton', **kw)
-        else:
-            ttk.Radiobutton.__init__(self, master, **kw)
+        style = 'nb.TRadiobutton' if sys.platform == 'win32' else None
+        super().__init__(master, style=style, **kw)  # type: ignore
 
 
-class OptionMenu(sys.platform == 'darwin' and tk.OptionMenu or ttk.OptionMenu):  # type: ignore
-    """Custom t(t)k.OptionMenu class to fix some display issues."""
+class OptionMenu(ttk.OptionMenu):
+    """Custom ttk.OptionMenu class to fix some display issues."""
 
     def __init__(self, master, variable, default=None, *values, **kw):
-        if sys.platform == 'win32':
-            # OptionMenu derives from Menubutton at the Python level, so uses Menubutton's style
-            ttk.OptionMenu.__init__(self, master, variable, default, *values, style='nb.TMenubutton', **kw)
-            self['menu'].configure(background=PAGEBG)
-            # Workaround for https://bugs.python.org/issue25684
-            for i in range(0, self['menu'].index('end')+1):
-                self['menu'].entryconfig(i, variable=variable)
-        else:
-            ttk.OptionMenu.__init__(self, master, variable, default, *values, **kw)
-            self['menu'].configure(background=ttk.Style().lookup('TMenu', 'background'))
-            # Workaround for https://bugs.python.org/issue25684
-            for i in range(0, self['menu'].index('end')+1):
-                self['menu'].entryconfig(i, variable=variable)
+        style = 'nb.TMenubutton' if sys.platform == 'win32' else ttk.Style().lookup('TMenu', 'background')
+        menu_background = PAGEBG if sys.platform == 'win32' else ttk.Style().lookup('TMenu', 'background')
+
+        super().__init__(master, variable, default, *values, style=style, **kw)
+        self['menu'].configure(background=menu_background)
+
+        for i in range(self['menu'].index('end') + 1):
+            self['menu'].entryconfig(i, variable=variable)
diff --git a/plugins/coriolis.py b/plugins/coriolis.py
index c142c686..20da16b1 100644
--- a/plugins/coriolis.py
+++ b/plugins/coriolis.py
@@ -106,12 +106,12 @@ def plugin_prefs(parent: ttk.Notebook, cmdr: str | None, is_beta: bool) -> tk.Fr
 
     # LANG: Settings>Coriolis: Label for 'NOT alpha/beta game version' URL
     nb.Label(conf_frame, text=_('Normal URL')).grid(sticky=tk.W, row=cur_row, column=0, padx=PADX, pady=PADY)
-    nb.Entry(conf_frame,
+    ttk.Entry(conf_frame,
              textvariable=coriolis_config.normal_textvar).grid(
                 sticky=tk.EW, row=cur_row, column=1, padx=PADX, pady=BOXY
             )
     # LANG: Generic 'Reset' button label
-    nb.Button(conf_frame, text=_("Reset"),
+    ttk.Button(conf_frame, text=_("Reset"),
               command=lambda: coriolis_config.normal_textvar.set(value=DEFAULT_NORMAL_URL)).grid(
         sticky=tk.W, row=cur_row, column=2, padx=PADX, pady=0
     )
@@ -119,11 +119,11 @@ def plugin_prefs(parent: ttk.Notebook, cmdr: str | None, is_beta: bool) -> tk.Fr
 
     # LANG: Settings>Coriolis: Label for 'alpha/beta game version' URL
     nb.Label(conf_frame, text=_('Beta URL')).grid(sticky=tk.W, row=cur_row, column=0, padx=PADX, pady=PADY)
-    nb.Entry(conf_frame, textvariable=coriolis_config.beta_textvar).grid(
+    ttk.Entry(conf_frame, textvariable=coriolis_config.beta_textvar).grid(
         sticky=tk.EW, row=cur_row, column=1, padx=PADX, pady=BOXY
     )
     # LANG: Generic 'Reset' button label
-    nb.Button(conf_frame, text=_('Reset'),
+    ttk.Button(conf_frame, text=_('Reset'),
               command=lambda: coriolis_config.beta_textvar.set(value=DEFAULT_BETA_URL)).grid(
         sticky=tk.W, row=cur_row, column=2, padx=PADX, pady=0
     )
diff --git a/plugins/edsm.py b/plugins/edsm.py
index 46d05f4c..146dd5ab 100644
--- a/plugins/edsm.py
+++ b/plugins/edsm.py
@@ -113,10 +113,10 @@ class This:
         self.cmdr_text: nb.Label | None = None
 
         self.user_label: nb.Label | None = None
-        self.user: nb.Entry | None = None
+        self.user: ttk.Entry | None = None
 
         self.apikey_label: nb.Label | None = None
-        self.apikey: nb.Entry | None = None
+        self.apikey: ttk.Entry | None = None
 
 
 this = This()
@@ -345,14 +345,14 @@ def plugin_prefs(parent: ttk.Notebook, cmdr: str | None, is_beta: bool) -> tk.Fr
     # LANG: EDSM Commander name label in EDSM settings
     this.user_label = nb.Label(frame, text=_('Commander Name'))
     this.user_label.grid(row=cur_row, padx=PADX, pady=PADY, sticky=tk.W)
-    this.user = nb.Entry(frame)
+    this.user = ttk.Entry(frame)
     this.user.grid(row=cur_row, column=1, padx=PADX, pady=BOXY, sticky=tk.EW)
 
     cur_row += 1
     # LANG: EDSM API key label
     this.apikey_label = nb.Label(frame, text=_('API Key'))
     this.apikey_label.grid(row=cur_row, padx=PADX, pady=PADY, sticky=tk.W)
-    this.apikey = nb.Entry(frame, show="*", width=50)
+    this.apikey = ttk.Entry(frame, show="*", width=50)
     this.apikey.grid(row=cur_row, column=1, padx=PADX, pady=BOXY, sticky=tk.EW)
     cur_row += 1
 
diff --git a/plugins/inara.py b/plugins/inara.py
index 0e0eb7bf..d96e05ef 100644
--- a/plugins/inara.py
+++ b/plugins/inara.py
@@ -125,7 +125,7 @@ class This:
         self.log: 'tk.IntVar'
         self.log_button: nb.Checkbutton
         self.label: HyperlinkLabel
-        self.apikey: nb.Entry
+        self.apikey: ttk.Entry
         self.apikey_label: tk.Label
 
         self.events: dict[Credentials, Deque[Event]] = defaultdict(deque)
@@ -292,7 +292,7 @@ def plugin_prefs(parent: ttk.Notebook, cmdr: str, is_beta: bool) -> tk.Frame:
     # LANG: Inara API key label
     this.apikey_label = nb.Label(frame, text=_('API Key'))  # Inara setting
     this.apikey_label.grid(row=cur_row, padx=PADX, pady=PADY, sticky=tk.W)
-    this.apikey = nb.Entry(frame, show="*", width=50)
+    this.apikey = ttk.Entry(frame, show="*", width=50)
     this.apikey.grid(row=cur_row, column=1, padx=PADX, pady=BOXY, sticky=tk.EW)
     cur_row += 1
 
diff --git a/prefs.py b/prefs.py
index 862bda27..ef43c5de 100644
--- a/prefs.py
+++ b/prefs.py
@@ -361,12 +361,12 @@ class PreferencesDialog(tk.Toplevel):
         # Type ignored due to incorrect type annotation. a 2 tuple does padding for each side
         self.outdir_label.grid(padx=self.PADX, pady=self.PADY, sticky=tk.W, row=row.get())  # type: ignore
 
-        self.outdir_entry = nb.Entry(output_frame, takefocus=False)
+        self.outdir_entry = ttk.Entry(output_frame, takefocus=False)
         self.outdir_entry.grid(columnspan=2, padx=self.PADX, pady=self.BOXY, sticky=tk.EW, row=row.get())
 
         text = (_('Browse...'))  # LANG: NOT-macOS Settings - files location selection button
 
-        self.outbutton = nb.Button(
+        self.outbutton = ttk.Button(
             output_frame,
             text=text,
             # Technically this is different from the label in Settings > Output, as *this* is used
@@ -399,7 +399,7 @@ class PreferencesDialog(tk.Toplevel):
             logdir = default
 
         self.logdir.set(logdir)
-        self.logdir_entry = nb.Entry(config_frame, takefocus=False)
+        self.logdir_entry = ttk.Entry(config_frame, takefocus=False)
 
         # Location of the Journal files
         nb.Label(
@@ -413,7 +413,7 @@ class PreferencesDialog(tk.Toplevel):
         text = (_('Browse...'))  # LANG: NOT-macOS Setting - files location selection button
 
         with row as cur_row:
-            self.logbutton = nb.Button(
+            self.logbutton = ttk.Button(
                 config_frame,
                 text=text,
                 # LANG: Settings > Configuration - Label for Journal files location
@@ -423,7 +423,7 @@ class PreferencesDialog(tk.Toplevel):
 
             if config.default_journal_dir_path:
                 # Appearance theme and language setting
-                nb.Button(
+                ttk.Button(
                     config_frame,
                     # LANG: Settings > Configuration - Label on 'reset journal files location to default' button
                     text=_('Default'),
@@ -465,7 +465,7 @@ class PreferencesDialog(tk.Toplevel):
                     text=_('Hotkey')  # LANG: Hotkey/Shortcut settings prompt on Windows
                 ).grid(padx=self.PADX, pady=self.PADY, sticky=tk.W, row=cur_row)
 
-                self.hotkey_text = nb.Entry(config_frame, width=30, justify=tk.CENTER)
+                self.hotkey_text = ttk.Entry(config_frame, width=30, justify=tk.CENTER)
                 self.hotkey_text.insert(
                     0,
                     # No hotkey/shortcut currently defined
@@ -623,7 +623,7 @@ class PreferencesDialog(tk.Toplevel):
             self.loglevel_dropdown.configure(width=15)
             self.loglevel_dropdown.grid(column=1, pady=self.BOXY, sticky=tk.W, row=cur_row)
 
-            nb.Button(
+            ttk.Button(
                 config_frame,
                 # LANG: Label on button used to open a filesystem folder
                 text=_('Open Log Folder'),  # Button that opens a folder in Explorer/Finder
@@ -726,7 +726,7 @@ class PreferencesDialog(tk.Toplevel):
             self.theme_label_0.grid(padx=self.PADX, pady=self.PADY, sticky=tk.W, row=cur_row)
 
             # Main window
-            self.theme_button_0 = nb.ColoredButton(
+            self.theme_button_0 = tk.Button(
                 appearance_frame,
                 # LANG: Appearance - Example 'Normal' text
                 text=_('Station'),
@@ -739,7 +739,7 @@ class PreferencesDialog(tk.Toplevel):
         with row as cur_row:
             self.theme_label_1 = nb.Label(appearance_frame, text=self.theme_prompts[1])
             self.theme_label_1.grid(padx=self.PADX, pady=self.PADY, sticky=tk.W, row=cur_row)
-            self.theme_button_1 = nb.ColoredButton(
+            self.theme_button_1 = tk.Button(
                 appearance_frame,
                 text='  Hutton Orbital  ',  # Do not translate
                 background='grey4',
@@ -870,7 +870,7 @@ class PreferencesDialog(tk.Toplevel):
             padx=self.PADX, pady=self.PADY, sticky=tk.W, row=row.get()
         )
 
-        plugdirentry = nb.Entry(plugins_frame, justify=tk.LEFT)
+        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())
 
@@ -882,7 +882,7 @@ class PreferencesDialog(tk.Toplevel):
                 text=_("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)
 
-            nb.Button(
+            ttk.Button(
                 plugins_frame,
                 # LANG: Label on button used to open a filesystem folder
                 text=_('Open'),  # Button that opens a folder in Explorer/Finder

From ae74d949a892b919a275c2cda45e951b97c3082c Mon Sep 17 00:00:00 2001
From: David Sangrey <rixxan@hullseals.space>
Date: Wed, 27 Mar 2024 21:24:11 -0400
Subject: [PATCH 10/22] [Nit] Indentation, Yay!

---
 plugins/coriolis.py | 6 +++---
 1 file changed, 3 insertions(+), 3 deletions(-)

diff --git a/plugins/coriolis.py b/plugins/coriolis.py
index 20da16b1..a97eb6c6 100644
--- a/plugins/coriolis.py
+++ b/plugins/coriolis.py
@@ -107,12 +107,12 @@ def plugin_prefs(parent: ttk.Notebook, cmdr: str | None, is_beta: bool) -> tk.Fr
     # LANG: Settings>Coriolis: Label for 'NOT alpha/beta game version' URL
     nb.Label(conf_frame, text=_('Normal URL')).grid(sticky=tk.W, row=cur_row, column=0, padx=PADX, pady=PADY)
     ttk.Entry(conf_frame,
-             textvariable=coriolis_config.normal_textvar).grid(
+              textvariable=coriolis_config.normal_textvar).grid(
                 sticky=tk.EW, row=cur_row, column=1, padx=PADX, pady=BOXY
             )
     # LANG: Generic 'Reset' button label
     ttk.Button(conf_frame, text=_("Reset"),
-              command=lambda: coriolis_config.normal_textvar.set(value=DEFAULT_NORMAL_URL)).grid(
+               command=lambda: coriolis_config.normal_textvar.set(value=DEFAULT_NORMAL_URL)).grid(
         sticky=tk.W, row=cur_row, column=2, padx=PADX, pady=0
     )
     cur_row += 1
@@ -124,7 +124,7 @@ def plugin_prefs(parent: ttk.Notebook, cmdr: str | None, is_beta: bool) -> tk.Fr
     )
     # LANG: Generic 'Reset' button label
     ttk.Button(conf_frame, text=_('Reset'),
-              command=lambda: coriolis_config.beta_textvar.set(value=DEFAULT_BETA_URL)).grid(
+               command=lambda: coriolis_config.beta_textvar.set(value=DEFAULT_BETA_URL)).grid(
         sticky=tk.W, row=cur_row, column=2, padx=PADX, pady=0
     )
     cur_row += 1

From cfb6f729ab5888238c552aa0bb0d568b69356fbb Mon Sep 17 00:00:00 2001
From: David Sangrey <rixxan@hullseals.space>
Date: Wed, 27 Mar 2024 21:27:22 -0400
Subject: [PATCH 11/22] [2186] Remove Unused Translations

---
 L10n/en.template   | 24 ------------------------
 config/__init__.py |  2 +-
 2 files changed, 1 insertion(+), 25 deletions(-)

diff --git a/L10n/en.template b/L10n/en.template
index f5acb377..7ba67a61 100644
--- a/L10n/en.template
+++ b/L10n/en.template
@@ -78,12 +78,6 @@
 /* EDMarketConnector.py: 'Edit' menu title on OSX; EDMarketConnector.py: 'Edit' menu title; In files: EDMarketConnector.py:922; EDMarketConnector.py:940; EDMarketConnector.py:943; */
 "Edit" = "Edit";
 
-/* EDMarketConnector.py: 'View' menu title on OSX; In files: EDMarketConnector.py:923; */
-"View" = "View";
-
-/* EDMarketConnector.py: 'Window' menu title on OSX; In files: EDMarketConnector.py:924; */
-"Window" = "Window";
-
 /* EDMarketConnector.py: Help' menu title on OSX; EDMarketConnector.py: 'Help' menu title; In files: EDMarketConnector.py:925; EDMarketConnector.py:941; EDMarketConnector.py:944; */
 "Help" = "Help";
 
@@ -351,9 +345,6 @@
 /* inara.py: INARA API returned some kind of error (error message will be contained in {MSG}); In files: inara.py:1650; inara.py:1663; */
 "Error: Inara {MSG}" = "Error: Inara {MSG}";
 
-/* prefs.py: File > Preferences menu entry for macOS; In files: prefs.py:237; */
-"Preferences" = "Preferences";
-
 /* prefs.py: Settings > Output - choosing what data to save to files; In files: prefs.py:335; */
 "Please choose what data to save" = "Please choose what data to save";
 
@@ -372,9 +363,6 @@
 /* prefs.py: Settings > Output - Label for "where files are located"; In files: prefs.py:379; prefs.py:398; */
 "File location" = "File location";
 
-/* prefs.py: macOS Preferences - files location selection button; In files: prefs.py:387; prefs.py:437; */
-"Change..." = "Change...";
-
 /* prefs.py: NOT-macOS Settings - files location selection button; prefs.py: NOT-macOS Setting - files location selection button; In files: prefs.py:390; prefs.py:440; */
 "Browse..." = "Browse...";
 
@@ -390,21 +378,9 @@
 /* prefs.py: Configuration - Enable or disable the Fleet Carrier CAPI calls; In files: prefs.py:475; */
 "Enable Fleetcarrier CAPI Queries" = "Enable Fleetcarrier CAPI Queries";
 
-/* prefs.py: Hotkey/Shortcut settings prompt on OSX; In files: prefs.py:490; */
-"Keyboard shortcut" = "Keyboard shortcut";
-
 /* prefs.py: Hotkey/Shortcut settings prompt on Windows; In files: prefs.py:492; */
 "Hotkey" = "Hotkey";
 
-/* prefs.py: macOS Preferences > Configuration - restart the app message; In files: prefs.py:501; */
-"Re-start {APP} to use shortcuts" = "Re-start {APP} to use shortcuts";
-
-/* prefs.py: macOS - Configuration - need to grant the app permission for keyboard shortcuts; In files: prefs.py:510; */
-"{APP} needs permission to use shortcuts" = "{APP} needs permission to use shortcuts";
-
-/* prefs.py: Shortcut settings button on OSX; In files: prefs.py:515; */
-"Open System Preferences" = "Open System Preferences";
-
 /* prefs.py: Configuration - Act on hotkey only when ED is in foreground; In files: prefs.py:538; */
 "Only when Elite: Dangerous is the active app" = "Only when Elite: Dangerous is the active app";
 
diff --git a/config/__init__.py b/config/__init__.py
index 992710a0..c6c404b9 100644
--- a/config/__init__.py
+++ b/config/__init__.py
@@ -54,7 +54,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.10.3'
+_static_appversion = '5.11.0-alpha0'
 
 _cached_version: semantic_version.Version | None = None
 copyright = '© 2015-2019 Jonathan Harris, 2020-2024 EDCD'

From 016fb96e065b45bd803587b31edb9619809f7198 Mon Sep 17 00:00:00 2001
From: David Sangrey <rixxan@hullseals.space>
Date: Wed, 27 Mar 2024 22:01:49 -0400
Subject: [PATCH 12/22] [2186] General Cleanup

---
 prefs.py             | 4 ++--
 td.py                | 1 -
 ttkHyperlinkLabel.py | 1 -
 update.py            | 9 +++------
 4 files changed, 5 insertions(+), 10 deletions(-)

diff --git a/prefs.py b/prefs.py
index ef43c5de..307e3c7a 100644
--- a/prefs.py
+++ b/prefs.py
@@ -364,7 +364,7 @@ class PreferencesDialog(tk.Toplevel):
         self.outdir_entry = ttk.Entry(output_frame, takefocus=False)
         self.outdir_entry.grid(columnspan=2, padx=self.PADX, pady=self.BOXY, sticky=tk.EW, row=row.get())
 
-        text = (_('Browse...'))  # LANG: NOT-macOS Settings - files location selection button
+        text = _('Browse...')  # LANG: NOT-macOS Settings - files location selection button
 
         self.outbutton = ttk.Button(
             output_frame,
@@ -410,7 +410,7 @@ class PreferencesDialog(tk.Toplevel):
 
         self.logdir_entry.grid(columnspan=4, padx=self.PADX, pady=self.BOXY, sticky=tk.EW, row=row.get())
 
-        text = (_('Browse...'))  # LANG: NOT-macOS Setting - files location selection button
+        text = _('Browse...')  # LANG: NOT-macOS Setting - files location selection button
 
         with row as cur_row:
             self.logbutton = ttk.Button(
diff --git a/td.py b/td.py
index d2b5dbdd..484e8d29 100644
--- a/td.py
+++ b/td.py
@@ -1,7 +1,6 @@
 """Export data for Trade Dangerous."""
 
 import pathlib
-import sys
 import time
 from collections import defaultdict
 from operator import itemgetter
diff --git a/ttkHyperlinkLabel.py b/ttkHyperlinkLabel.py
index de906510..9bb3b9bf 100644
--- a/ttkHyperlinkLabel.py
+++ b/ttkHyperlinkLabel.py
@@ -31,7 +31,6 @@ if TYPE_CHECKING:
     def _(x: str) -> str: return x
 
 
-# FIXME: Split this into multi-file module to separate the platforms
 class HyperlinkLabel(tk.Label or ttk.Label):  # type: ignore
     """Clickable label for HTTP links."""
 
diff --git a/update.py b/update.py
index e0b8f97b..346aff63 100644
--- a/update.py
+++ b/update.py
@@ -7,10 +7,8 @@ See LICENSE file.
 """
 from __future__ import annotations
 
-import os
 import sys
 import threading
-from os.path import dirname, join
 from traceback import print_exc
 from typing import TYPE_CHECKING
 from xml.etree import ElementTree
@@ -163,10 +161,9 @@ class Updater:
 
             return None
 
-        else:
-            # For *these* purposes anything else is the same as 'windows', as
-            # non-win32 would be running from source.
-            sparkle_platform = 'windows'
+        # For *these* purposes anything else is the same as 'windows', as
+        # non-win32 would be running from source.
+        sparkle_platform = 'windows'
 
         for item in feed.findall('channel/item'):
             # xml is a pain with types, hence these ignores

From 1800f8f0b18cb8e13a7cc032d4eac61f7f67cbb1 Mon Sep 17 00:00:00 2001
From: David Sangrey <rixxan@hullseals.space>
Date: Wed, 27 Mar 2024 22:17:21 -0400
Subject: [PATCH 13/22] [2186] Remove Some Comments

---
 config/__init__.py | 1 -
 dashboard.py       | 4 ++--
 monitor.py         | 2 +-
 prefs.py           | 2 +-
 theme.py           | 2 +-
 5 files changed, 5 insertions(+), 6 deletions(-)

diff --git a/config/__init__.py b/config/__init__.py
index c6c404b9..99e8fdc6 100644
--- a/config/__init__.py
+++ b/config/__init__.py
@@ -7,7 +7,6 @@ See LICENSE file.
 
 Windows uses the Registry to store values in a flat manner.
 Linux uses a file, but for commonality it's still a flat data structure.
-macOS uses a 'defaults' object.
 """
 from __future__ import annotations
 
diff --git a/dashboard.py b/dashboard.py
index 3948c92b..4776319c 100644
--- a/dashboard.py
+++ b/dashboard.py
@@ -26,7 +26,7 @@ if sys.platform == 'win32':
 else:
     # Linux's inotify doesn't work over CIFS or NFS, so poll
     class FileSystemEventHandler:  # type: ignore
-        """Dummy class to represent a file system event handler on platforms other than macOS and Windows."""
+        """Dummy class to represent a file system event handler on platforms other than Windows."""
 
 
 class Dashboard(FileSystemEventHandler):
@@ -160,7 +160,7 @@ class Dashboard(FileSystemEventHandler):
 
     def on_modified(self, event) -> None:
         """
-        Watchdog callback - DirModifiedEvent on macOS, FileModifiedEvent on Windows.
+        Watchdog callback - FileModifiedEvent on Windows.
 
         :param event: Watchdog event.
         """
diff --git a/monitor.py b/monitor.py
index d549e533..5acbcbc4 100644
--- a/monitor.py
+++ b/monitor.py
@@ -439,7 +439,7 @@ class EDLogs(FileSystemEventHandler):
                     new_journal_file = None
 
             if logfile:
-                loghandle.seek(0, SEEK_END)		  # required to make macOS notice log change over SMB
+                loghandle.seek(0, SEEK_END)  # required to make macOS notice log change over SMB. # TODO: Do we need this?
                 loghandle.seek(log_pos, SEEK_SET)  # reset EOF flag # TODO: log_pos reported as possibly unbound
                 for line in loghandle:
                     # Paranoia check to see if we're shutting down
diff --git a/prefs.py b/prefs.py
index 307e3c7a..5b045b1a 100644
--- a/prefs.py
+++ b/prefs.py
@@ -285,7 +285,7 @@ class PreferencesDialog(tk.Toplevel):
 
         # wait for window to appear on screen before calling grab_set
         self.parent.update_idletasks()
-        self.parent.wm_attributes('-topmost', 0)  # needed for dialog to appear ontop of parent on OSX & Linux
+        self.parent.wm_attributes('-topmost', 0)  # needed for dialog to appear ontop of parent on Linux
         self.wait_visibility()
         self.grab_set()
 
diff --git a/theme.py b/theme.py
index b128bc91..bbe62ef5 100644
--- a/theme.py
+++ b/theme.py
@@ -150,7 +150,7 @@ class _Theme:
         # the widget has explicit fg or bg attributes.
         assert isinstance(widget, (tk.BitmapImage, tk.Widget)), widget
         if not self.defaults:
-            # Can't initialise this til window is created       # Windows, MacOS
+            # Can't initialise this til window is created       # Windows
             self.defaults = {
                 'fg': tk.Label()['foreground'],         # SystemButtonText, systemButtonText
                 'bg': tk.Label()['background'],         # SystemButtonFace, White

From 56a4ae25e0e598666bd0c8c6f8fa72daacdd90a2 Mon Sep 17 00:00:00 2001
From: David Sangrey <rixxan@hullseals.space>
Date: Wed, 27 Mar 2024 22:21:26 -0400
Subject: [PATCH 14/22] [Nit] Clarify Comments

---
 monitor.py    | 2 +-
 myNotebook.py | 2 +-
 2 files changed, 2 insertions(+), 2 deletions(-)

diff --git a/monitor.py b/monitor.py
index 5acbcbc4..ba634c51 100644
--- a/monitor.py
+++ b/monitor.py
@@ -439,7 +439,7 @@ class EDLogs(FileSystemEventHandler):
                     new_journal_file = None
 
             if logfile:
-                loghandle.seek(0, SEEK_END)  # required to make macOS notice log change over SMB. # TODO: Do we need this?
+                loghandle.seek(0, SEEK_END)  # required for macOS to notice log change over SMB. TODO: Do we need this?
                 loghandle.seek(log_pos, SEEK_SET)  # reset EOF flag # TODO: log_pos reported as possibly unbound
                 for line in loghandle:
                     # Paranoia check to see if we're shutting down
diff --git a/myNotebook.py b/myNotebook.py
index 528664ba..64624550 100644
--- a/myNotebook.py
+++ b/myNotebook.py
@@ -57,7 +57,7 @@ class Label(tk.Label):
         super().__init__(master, **kw)
 
 
-class Entry(ttk.Entry):  # type: ignore
+class Entry(ttk.Entry):
     """Custom t(t)k.Entry class to fix some display issues."""
 
     # DEPRECATED: Migrate to ttk.Entry. Will remove in 5.12 or later.

From b5a4ee6ed2f60c68cec307e9bcd946d6440fc0b7 Mon Sep 17 00:00:00 2001
From: David Sangrey <rixxan@hullseals.space>
Date: Thu, 28 Mar 2024 10:49:01 -0400
Subject: [PATCH 15/22] [Nit] Cleanup some Flake8

---
 config/__init__.py          |  1 -
 plugins/eddn.py             |  1 -
 protocol.py                 | 36 ++++++++++++++++++------------------
 tests/config/_old_config.py |  6 +++---
 4 files changed, 21 insertions(+), 23 deletions(-)

diff --git a/config/__init__.py b/config/__init__.py
index 99e8fdc6..d85956a7 100644
--- a/config/__init__.py
+++ b/config/__init__.py
@@ -467,7 +467,6 @@ def get_config(*args, **kwargs) -> AbstractConfig:
     :param kwargs: Args to be passed through to implementation.
     :return: Instance of the implementation.
     """
-
     if sys.platform == "win32":  # pragma: sys-platform-win32
         from .windows import WinConfig
         return WinConfig(*args, **kwargs)
diff --git a/plugins/eddn.py b/plugins/eddn.py
index b7133725..6345f579 100644
--- a/plugins/eddn.py
+++ b/plugins/eddn.py
@@ -27,7 +27,6 @@ import os
 import pathlib
 import re
 import sqlite3
-import sys
 import tkinter as tk
 from platform import system
 from textwrap import dedent
diff --git a/protocol.py b/protocol.py
index 0f1c157f..4c9e41ca 100644
--- a/protocol.py
+++ b/protocol.py
@@ -26,6 +26,7 @@ is_wine = False
 
 if sys.platform == 'win32':
     from ctypes import windll  # type: ignore
+
     try:
         if windll.ntdll.wine_get_version:
             is_wine = True
@@ -58,13 +59,13 @@ class GenericProtocolHandler:
             self.master.event_generate('<<CompanionAuthEvent>>', when="tail")
 
 
-if (config.auth_force_edmc_protocol
-      or (
-          sys.platform == 'win32'
-          and getattr(sys, 'frozen', False)
-          and not is_wine
-          and not config.auth_force_localserver
-      )):
+if (config.auth_force_edmc_protocol  # noqa: C901
+        or (
+                sys.platform == 'win32'
+                and getattr(sys, 'frozen', False)
+                and not is_wine
+                and not config.auth_force_localserver
+        )):
     # 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
@@ -186,11 +187,11 @@ if (config.auth_force_edmc_protocol
         # which we can read out as shown below, and then compare.
 
         target_is_valid = lparam_low == 0 or (
-            GlobalGetAtomNameW(lparam_low, service, 256) and service.value == appname
+                GlobalGetAtomNameW(lparam_low, service, 256) and service.value == appname
         )
 
         topic_is_valid = lparam_high == 0 or (
-            GlobalGetAtomNameW(lparam_high, topic, 256) and topic.value.lower() == 'system'
+                GlobalGetAtomNameW(lparam_high, topic, 256) and topic.value.lower() == 'system'
         )
 
         if target_is_valid and topic_is_valid:
@@ -251,15 +252,15 @@ if (config.auth_force_edmc_protocol
 
             # https://docs.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-createwindowexw
             hwnd = CreateWindowExW(
-                0,                       # dwExStyle
+                0,  # dwExStyle
                 wndclass.lpszClassName,  # lpClassName
-                "DDE Server",            # lpWindowName
-                0,                       # dwStyle
+                "DDE Server",  # lpWindowName
+                0,  # dwStyle
                 CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT,  # X, Y, nWidth, nHeight
                 self.master.winfo_id(),  # hWndParent # Don't use HWND_MESSAGE since the window won't get DDE broadcasts
-                None,                    # hMenu
-                wndclass.hInstance,      # hInstance
-                None                     # lpParam
+                None,  # hMenu
+                wndclass.hInstance,  # hInstance
+                None  # lpParam
             )
 
             msg = MSG()
@@ -421,10 +422,9 @@ def get_handler_impl() -> Type[GenericProtocolHandler]:
 
     :return: An instantiatable GenericProtocolHandler
     """
-
     if (
-        (sys.platform == 'win32' and config.auth_force_edmc_protocol)
-        or (getattr(sys, 'frozen', False) and not is_wine and not config.auth_force_localserver)
+            (sys.platform == 'win32' and config.auth_force_edmc_protocol)
+            or (getattr(sys, 'frozen', False) and not is_wine and not config.auth_force_localserver)
     ):
         return WindowsProtocolHandler
 
diff --git a/tests/config/_old_config.py b/tests/config/_old_config.py
index 35ae19e3..2b5244b6 100644
--- a/tests/config/_old_config.py
+++ b/tests/config/_old_config.py
@@ -5,8 +5,8 @@ import numbers
 import sys
 import warnings
 from configparser import NoOptionError
-from os import getenv, makedirs, mkdir, pardir
-from os.path import dirname, expanduser, isdir, join, normpath
+from os import getenv, makedirs, mkdir
+from os.path import dirname, expanduser, isdir, join
 from typing import TYPE_CHECKING
 from config import applongname, appname, update_interval
 from EDMCLogging import get_main_logger
@@ -109,7 +109,7 @@ class OldConfig:
     OUT_EDDN_DELAY = 4096
     OUT_STATION_ANY = OUT_EDDN_SEND_STATION_DATA | OUT_MKT_TD | OUT_MKT_CSV
 
-    if sys.platform == 'win32':
+    if sys.platform == 'win32':  # noqa: C901
 
         def __init__(self):
             self.app_dir = join(known_folder_path(FOLDERID_LocalAppData), appname)  # type: ignore

From d8d8540ceeea7b2c9beb57843df4ce3be136e7e2 Mon Sep 17 00:00:00 2001
From: David Sangrey <rixxan@hullseals.space>
Date: Fri, 5 Apr 2024 17:16:25 -0400
Subject: [PATCH 16/22] [2186] Resolve Some Merge Issues From EntryMenu

---
 myNotebook.py | 63 ++++++++++++++++++++++++++++++++++++++++++++++++---
 1 file changed, 60 insertions(+), 3 deletions(-)

diff --git a/myNotebook.py b/myNotebook.py
index 64624550..7379aa5a 100644
--- a/myNotebook.py
+++ b/myNotebook.py
@@ -57,12 +57,69 @@ class Label(tk.Label):
         super().__init__(master, **kw)
 
 
-class Entry(ttk.Entry):
+class EntryMenu(ttk.Entry):
+    """Extended entry widget that includes a context menu with Copy, Cut-and-Paste commands."""
+
+    def __init__(self, *args, **kwargs) -> None:
+        super().__init__(*args, **kwargs)
+
+        self.menu = tk.Menu(self, tearoff=False)
+        self.menu.add_command(label="Copy", command=self.copy)
+        self.menu.add_command(label="Cut", command=self.cut)
+        self.menu.add_separator()
+        self.menu.add_command(label="Paste", command=self.paste)
+        self.menu.add_separator()
+        self.menu.add_command(label="Select All", command=self.select_all)
+
+        self.bind("<Button-3>", self.display_popup)
+
+    def display_popup(self, event: tk.Event) -> None:
+        """Display the menu popup."""
+        self.menu.post(event.x_root, event.y_root)
+
+    def select_all(self) -> None:
+        """Select all the text within the Entry."""
+        self.selection_range(0, tk.END)
+        self.focus_set()
+
+    def copy(self) -> None:
+        """Copy the selected Entry text."""
+        if self.selection_present():
+            self.clipboard_clear()
+            self.clipboard_append(self.selection_get())
+
+    def cut(self) -> None:
+        """Cut the selected Entry text."""
+        if self.selection_present():
+            self.copy()
+            self.delete(tk.SEL_FIRST, tk.SEL_LAST)
+
+    def paste(self) -> None:
+        """Paste the selected Entry text."""
+        try:
+            # Attempt to grab an image from the clipboard (apprently also works for files)
+            img = ImageGrab.grabclipboard()
+            if img:
+                # Hijack existing translation, yes it doesn't exactly match here.
+                # LANG: Generic error prefix - following text is from Frontier auth service;
+                messagebox.showwarning(_('Error'),
+                                       _('Cannot paste non-text content.'))  # LANG: Can't Paste Images or Files in Text
+                return
+            text = self.clipboard_get()
+            if self.selection_present() and text:
+                self.delete(tk.SEL_FIRST, tk.SEL_LAST)
+            self.insert(tk.INSERT, text)
+        except tk.TclError:
+            # No text in clipboard or clipboard is not text
+            pass
+
+
+class Entry(ttk.Entry or EntryMenu):
     """Custom t(t)k.Entry class to fix some display issues."""
 
-    # DEPRECATED: Migrate to ttk.Entry. Will remove in 5.12 or later.
+    # DEPRECATED: Migrate to ttk.Entry or EntryMenu. Will remove in 5.12 or later.
     def __init__(self, master: ttk.Frame | None = None, **kw):
-        super().__init__(master, **kw)
+        EntryMenu.__init__(self, master, **kw)
 
 
 class Button(tk.Button or ttk.Button):  # type: ignore

From d00226f9e3f8dbbf211851433bb501a984334658 Mon Sep 17 00:00:00 2001
From: David Sangrey <rixxan@hullseals.space>
Date: Fri, 5 Apr 2024 17:20:05 -0400
Subject: [PATCH 17/22] [2186] Additional Documentations

---
 .flake8          | 1 -
 .mypy.ini        | 1 -
 Contributing.md  | 2 +-
 pyproject.toml   | 3 ---
 requirements.txt | 3 ---
 5 files changed, 1 insertion(+), 9 deletions(-)

diff --git a/.flake8 b/.flake8
index bdb25ca4..179e00ec 100644
--- a/.flake8
+++ b/.flake8
@@ -7,7 +7,6 @@ exclude =
     FDevIDs/
     venv/
     .venv/
-    hotkey/darwin.py  # FIXME: Check under macOS VM at some point
 
 # Show exactly where in a line the error happened
 #show-source = True
diff --git a/.mypy.ini b/.mypy.ini
index 75f4ccdb..9ac2f227 100644
--- a/.mypy.ini
+++ b/.mypy.ini
@@ -6,5 +6,4 @@ scripts_are_modules = True
 ;   `<var> = <value>`
 ; i.e. no typing info.
 check_untyped_defs = True
-; platform = darwin
 explicit_package_bases = True
diff --git a/Contributing.md b/Contributing.md
index 5f7bc158..f021f1a3 100644
--- a/Contributing.md
+++ b/Contributing.md
@@ -679,7 +679,7 @@ the following does not work:
 
 ```py
 from sys import platform
-if platform == 'darwin':
+if platform == 'win32':
   ...
 ```
 
diff --git a/pyproject.toml b/pyproject.toml
index e32ad617..b98fbf4f 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -23,6 +23,3 @@ sys-platform-not-darwin = "sys_platform == 'darwin'"
 sys-platform-linux = "sys_platform != 'linux'"
 sys-platform-not-linux = "sys_platform == 'linux'"
 sys-platform-not-known = "sys_platform in ('darwin', 'linux', 'win32')"
-
-[tool.pyright]
-# pythonPlatform = 'Darwin'
diff --git a/requirements.txt b/requirements.txt
index e9d4f58d..c9239fdc 100644
--- a/requirements.txt
+++ b/requirements.txt
@@ -10,6 +10,3 @@ infi.systray==0.1.12; sys_platform == 'win32'
 # argh==0.26.2 watchdog dep
 # pyyaml==5.3.1 watchdog dep
 semantic-version==2.10.0
-
-# Base requirement for MacOS
-pyobjc; sys_platform == 'darwin'

From fbdc44139077db712fa39b6d4ae60fc3013e1fc2 Mon Sep 17 00:00:00 2001
From: David Sangrey <rixxan@hullseals.space>
Date: Fri, 5 Apr 2024 17:28:08 -0400
Subject: [PATCH 18/22] [Minor] Return Visual Padding

Just makes it nicer to read.
---
 protocol.py | 8 ++++----
 1 file changed, 4 insertions(+), 4 deletions(-)

diff --git a/protocol.py b/protocol.py
index 4c9e41ca..b052ebae 100644
--- a/protocol.py
+++ b/protocol.py
@@ -254,12 +254,12 @@ if (config.auth_force_edmc_protocol  # noqa: C901
             hwnd = CreateWindowExW(
                 0,  # dwExStyle
                 wndclass.lpszClassName,  # lpClassName
-                "DDE Server",  # lpWindowName
-                0,  # dwStyle
+                "DDE Server",            # lpWindowName
+                0,                       # dwStyle
                 CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT,  # X, Y, nWidth, nHeight
                 self.master.winfo_id(),  # hWndParent # Don't use HWND_MESSAGE since the window won't get DDE broadcasts
-                None,  # hMenu
-                wndclass.hInstance,  # hInstance
+                None,                    # hMenu
+                wndclass.hInstance,      # hInstance
                 None  # lpParam
             )
 

From 3a8227a8741ef2856472d300490ab2e30c2c7d57 Mon Sep 17 00:00:00 2001
From: David Sangrey <rixxan@hullseals.space>
Date: Fri, 5 Apr 2024 17:33:22 -0400
Subject: [PATCH 19/22] [2186] Correct Logic

---
 EDMarketConnector.py | 8 +++-----
 prefs.py             | 6 +++---
 2 files changed, 6 insertions(+), 8 deletions(-)

diff --git a/EDMarketConnector.py b/EDMarketConnector.py
index 967488e0..3ab97eb5 100755
--- a/EDMarketConnector.py
+++ b/EDMarketConnector.py
@@ -1750,8 +1750,7 @@ class AppWindow:
 
             # position over parent
             # http://core.tcl.tk/tk/tktview/c84f660833546b1b84e7
-            if parent.winfo_rooty() > 0:
-                self.geometry(f'+{parent.winfo_rootx():d}+{parent.winfo_rooty():d}')
+            self.geometry(f'+{parent.winfo_rootx():d}+{parent.winfo_rooty():d}')
 
             # remove decoration
             if sys.platform == 'win32':
@@ -1870,9 +1869,8 @@ class AppWindow:
         config.set_shutdown()  # Signal we're in shutdown now.
 
         # http://core.tcl.tk/tk/tktview/c84f660833546b1b84e7
-        if self.w.winfo_rooty() > 0:
-            x, y = self.w.geometry().split('+')[1:3]  # e.g. '212x170+2881+1267'
-            config.set('geometry', f'+{x}+{y}')
+        x, y = self.w.geometry().split('+')[1:3]  # e.g. '212x170+2881+1267'
+        config.set('geometry', f'+{x}+{y}')
 
         # Let the user know we're shutting down.
         # LANG: The application is shutting down
diff --git a/prefs.py b/prefs.py
index 5b045b1a..9e0459b6 100644
--- a/prefs.py
+++ b/prefs.py
@@ -230,9 +230,9 @@ class PreferencesDialog(tk.Toplevel):
             self.transient(parent)
 
         # position over parent
-        if parent.winfo_rooty() > 0:  # http://core.tcl.tk/tk/tktview/c84f660833546b1b84e7
-            # TODO this is fixed supposedly.
-            self.geometry(f'+{parent.winfo_rootx()}+{parent.winfo_rooty()}')
+        # http://core.tcl.tk/tk/tktview/c84f660833546b1b84e7
+        # TODO this is fixed supposedly.
+        self.geometry(f'+{parent.winfo_rootx()}+{parent.winfo_rooty()}')
 
         # remove decoration
         if sys.platform == 'win32':

From e0ef9b52c3cdc5aedc9982c736a7169bd3a5d2f8 Mon Sep 17 00:00:00 2001
From: David Sangrey <rixxan@hullseals.space>
Date: Fri, 5 Apr 2024 17:45:07 -0400
Subject: [PATCH 20/22] [2186] Additional Tweaks

---
 myNotebook.py | 32 ++++++++++++++++++--------------
 1 file changed, 18 insertions(+), 14 deletions(-)

diff --git a/myNotebook.py b/myNotebook.py
index 44488b8a..e8f8779b 100644
--- a/myNotebook.py
+++ b/myNotebook.py
@@ -30,15 +30,16 @@ class Notebook(ttk.Notebook):
 
         super().__init__(master, **kw)
         style = ttk.Style()
-        style.configure('nb.TFrame',                          background=PAGEBG)
-        style.configure('nb.TButton',                         background=PAGEBG)
-        style.configure('nb.TCheckbutton', foreground=PAGEFG, background=PAGEBG)
-        style.configure('nb.TMenubutton',  foreground=PAGEFG, background=PAGEBG)
-        style.configure('nb.TRadiobutton', foreground=PAGEFG, background=PAGEBG)
+        if sys.platform == 'win32':
+            style.configure('nb.TFrame',                          background=PAGEBG)
+            style.configure('nb.TButton',                         background=PAGEBG)
+            style.configure('nb.TCheckbutton', foreground=PAGEFG, background=PAGEBG)
+            style.configure('nb.TMenubutton',  foreground=PAGEFG, background=PAGEBG)
+            style.configure('nb.TRadiobutton', foreground=PAGEFG, background=PAGEBG)
         self.grid(padx=10, pady=10, sticky=tk.NSEW)
 
 
-class Frame(tk.Frame or ttk.Frame):  # type: ignore
+class Frame(ttk.Frame):
     """Custom t(t)k.Frame class to fix some display issues."""
 
     def __init__(self, master: ttk.Notebook | None = None, **kw):
@@ -127,7 +128,7 @@ class Entry(ttk.Entry or EntryMenu):
         EntryMenu.__init__(self, master, **kw)
 
 
-class Button(tk.Button or ttk.Button):  # type: ignore
+class Button(ttk.Button):  # type: ignore
     """Custom t(t)k.Button class to fix some display issues."""
 
     # DEPRECATED: Migrate to ttk.Button. Will remove in 5.12 or later.
@@ -138,7 +139,7 @@ class Button(tk.Button or ttk.Button):  # type: ignore
             ttk.Button.__init__(self, master, **kw)
 
 
-class ColoredButton(tk.Label or tk.Button):  # type: ignore
+class ColoredButton(tk.Button):  # type: ignore
     """Custom t(t)k.ColoredButton class to fix some display issues."""
 
     # DEPRECATED: Migrate to tk.Button. Will remove in 5.12 or later.
@@ -166,11 +167,14 @@ class OptionMenu(ttk.OptionMenu):
     """Custom ttk.OptionMenu class to fix some display issues."""
 
     def __init__(self, master, variable, default=None, *values, **kw):
-        style = 'nb.TMenubutton' if sys.platform == 'win32' else ttk.Style().lookup('TMenu', 'background')
-        menu_background = PAGEBG if sys.platform == 'win32' else ttk.Style().lookup('TMenu', 'background')
+        if sys.platform == 'win32':
+            # OptionMenu derives from Menubutton at the Python level, so uses Menubutton's style
+            ttk.OptionMenu.__init__(self, master, variable, default, *values, style='nb.TMenubutton', **kw)
+            self['menu'].configure(background=PAGEBG)
+        else:
+            ttk.OptionMenu.__init__(self, master, variable, default, *values, **kw)
+            self['menu'].configure(background=ttk.Style().lookup('TMenu', 'background'))
 
-        super().__init__(master, variable, default, *values, style=style, **kw)
-        self['menu'].configure(background=menu_background)
-
-        for i in range(self['menu'].index('end') + 1):
+        # Workaround for https://bugs.python.org/issue25684
+        for i in range(0, self['menu'].index('end') + 1):
             self['menu'].entryconfig(i, variable=variable)

From 3c6ea3c9328a95e2a82fa276a2d1e3865c235f36 Mon Sep 17 00:00:00 2001
From: David Sangrey <rixxan@hullseals.space>
Date: Fri, 5 Apr 2024 17:50:30 -0400
Subject: [PATCH 21/22] [Minor] Comment Clarify

---
 update.py | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/update.py b/update.py
index 346aff63..991558d6 100644
--- a/update.py
+++ b/update.py
@@ -161,7 +161,7 @@ class Updater:
 
             return None
 
-        # For *these* purposes anything else is the same as 'windows', as
+        # For *these* purposes all systems are the same as 'windows', as
         # non-win32 would be running from source.
         sparkle_platform = 'windows'
 

From c14bd826d08daee4509cf2bf2b184245d7f54695 Mon Sep 17 00:00:00 2001
From: David Sangrey <rixxan@hullseals.space>
Date: Sat, 6 Apr 2024 16:52:45 -0400
Subject: [PATCH 22/22] [Minor] Additional Visual Padding Fixes

---
 protocol.py | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/protocol.py b/protocol.py
index b052ebae..1fb88595 100644
--- a/protocol.py
+++ b/protocol.py
@@ -252,7 +252,7 @@ if (config.auth_force_edmc_protocol  # noqa: C901
 
             # https://docs.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-createwindowexw
             hwnd = CreateWindowExW(
-                0,  # dwExStyle
+                0,                       # dwExStyle
                 wndclass.lpszClassName,  # lpClassName
                 "DDE Server",            # lpWindowName
                 0,                       # dwStyle
@@ -260,7 +260,7 @@ if (config.auth_force_edmc_protocol  # noqa: C901
                 self.master.winfo_id(),  # hWndParent # Don't use HWND_MESSAGE since the window won't get DDE broadcasts
                 None,                    # hMenu
                 wndclass.hInstance,      # hInstance
-                None  # lpParam
+                None                     # lpParam
             )
 
             msg = MSG()