diff --git a/.flake8 b/.flake8 index bdb25ca44..179e00ece 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 75f4ccdb6..9ac2f2273 100644 --- a/.mypy.ini +++ b/.mypy.ini @@ -6,5 +6,4 @@ scripts_are_modules = True ; ` = ` ; i.e. no typing info. check_untyped_defs = True -; platform = darwin explicit_package_bases = True diff --git a/Contributing.md b/Contributing.md index 5f7bc158b..f021f1a32 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/EDMarketConnector.py b/EDMarketConnector.py index 6debf4e98..3ab97eb52 100755 --- a/EDMarketConnector.py +++ b/EDMarketConnector.py @@ -496,21 +496,20 @@ def open_window(systray: 'SysTrayIcon') -> None: 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 @@ def open_window(systray: 'SysTrayIcon') -> None: 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 @@ def open_window(systray: 'SysTrayIcon') -> None: 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') - - # 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.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.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('', 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.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('', self.copy) - - # Bind to the Default theme minimise button - self.w.bind("", 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) - - # 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('', self.drag_start) - theme_titlebar.bind('', self.drag_continue) - theme_titlebar.bind('', 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) + self.w.bind('', self.copy) + + # Bind to the Default theme minimise button + self.w.bind("", 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) + + # 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('', self.drag_start) + theme_titlebar.bind('', self.drag_continue) + theme_titlebar.bind('', 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 @@ def set_labels(self): 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 - - 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 - - # 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 + 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 + + # 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 @@ def login(self): 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 @@ def login(self): # 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 @@ def auth(self, event=None) -> None: 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,8 +1750,7 @@ def __init__(self, parent: tk.Tk) -> None: # position over parent # http://core.tcl.tk/tk/tktview/c84f660833546b1b84e7 - if sys.platform != 'darwin' or 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': @@ -1916,9 +1834,6 @@ def save_raw(self) -> None: """ 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,9 +1869,8 @@ def onexit(self, event=None) -> None: 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: - 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/L10n/en.template b/L10n/en.template index bf8d022e4..200743c82 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/build.py b/build.py index 2d4ef7902..851f391b2 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/config/__init__.py b/config/__init__.py index 3acb2edd3..d85956a78 100644 --- a/config/__init__.py +++ b/config/__init__.py @@ -7,7 +7,6 @@ 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 @@ -54,7 +53,7 @@ # # 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' @@ -468,10 +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 == "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 return WinConfig(*args, **kwargs) diff --git a/config/darwin.py b/config/darwin.py deleted file mode 100644 index 9c15ec32d..000000000 --- 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/dashboard.py b/dashboard.py index 1f95a9436..4776319c5 100644 --- a/dashboard.py +++ b/dashboard.py @@ -20,13 +20,13 @@ 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: # 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 @@ def poll(self, first_time: bool = False) -> None: 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/docs/examples/click_counter/load.py b/docs/examples/click_counter/load.py index ad90a084a..f7f238377 100644 --- a/docs/examples/click_counter/load.py +++ b/docs/examples/click_counter/load.py @@ -7,6 +7,8 @@ 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 @@ def setup_preferences(self, parent: nb.Notebook, cmdr: str, is_beta: bool) -> tk # 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/hotkey/__init__.py b/hotkey/__init__.py index e75158075..313415f9f 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 6afd0239c..000000000 --- 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('<>', 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('', 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() diff --git a/journal_lock.py b/journal_lock.py index 4d04992f8..3a4dad52d 100644 --- a/journal_lock.py +++ b/journal_lock.py @@ -218,10 +218,6 @@ def __init__(self, parent: tk.Tk, callback: Callable) -> None: 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/l10n.py b/l10n.py index a2b5185ae..183d902a5 100755 --- a/l10n.py +++ b/l10n.py @@ -17,8 +17,8 @@ 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 @@ def _(x: str) -> str: return x 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 @@ def translate(self, x: str, context: str | None = None) -> str: 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 @@ def available_names(self) -> dict[str | None, str]: 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 @@ def file(self, lang: str, plugin_path: str | None = None) -> TextIO | None: 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 @@ def file(self, lang: str, plugin_path: str | None = None) -> TextIO | None: 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 @@ def string_from_number(self, number: float | int, decimals: int = 5) -> str: 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 @@ def number_from_string(self, string: str) -> int | float | None: :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 @@ def preferred_languages(self) -> Iterable[str]: :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/modules.p b/modules.p deleted file mode 100644 index c48f76c48..000000000 Binary files a/modules.p and /dev/null differ diff --git a/monitor.py b/monitor.py index e7f99543d..02b7f91cc 100644 --- a/monitor.py +++ b/monitor.py @@ -38,16 +38,7 @@ 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 @@ -384,8 +375,6 @@ def worker(self) -> None: # noqa: C901, CCR001 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: @@ -452,7 +441,7 @@ def worker(self) -> None: # noqa: C901, CCR001 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 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 @@ -485,9 +474,6 @@ def worker(self) -> None: # noqa: C901, CCR001 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) @@ -2146,12 +2132,7 @@ def game_running(self) -> bool: # noqa: CCR001 :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 diff --git a/myNotebook.py b/myNotebook.py index 398b21360..e8f8779b4 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. """ @@ -21,13 +18,7 @@ if TYPE_CHECKING: def _(x: str) -> str: return x -# 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 @@ -37,39 +28,22 @@ 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 == '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) 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) + 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(ttk.Frame): """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: @@ -82,14 +56,11 @@ 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 don't understand that - if sys.platform in ('darwin', '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 EntryMenu(ttk.Entry): @@ -149,95 +120,61 @@ def paste(self) -> None: pass -class Entry(sys.platform == 'darwin' and tk.Entry or EntryMenu or ttk.Entry): # type: ignore +class Entry(ttk.Entry or EntryMenu): """Custom t(t)k.Entry class to fix some display issues.""" + # DEPRECATED: Migrate to ttk.Entry or EntryMenu. Will remove in 5.12 or later. 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: - EntryMenu.__init__(self, master, **kw) + EntryMenu.__init__(self, master, **kw) -class Button(sys.platform == 'darwin' and 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. 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) -class ColoredButton(sys.platform == 'darwin' and 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. 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('', self._press) - else: - tk.Button.__init__(self, master, **kw) + tk.Button.__init__(self, master, **kw) - if sys.platform == 'darwin': - def _press(self, event): - self._command() - -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 == 'darwin': - kw['foreground'] = kw.pop('foreground', PAGEFG) - kw['background'] = kw.pop('background', PAGEBG) - tk.Checkbutton.__init__(self, master, **kw) - elif 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 == 'darwin': - kw['foreground'] = kw.pop('foreground', PAGEFG) - kw['background'] = kw.pop('background', PAGEBG) - tk.Radiobutton.__init__(self, master, **kw) - elif 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 == '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) - # 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) + + # Workaround for https://bugs.python.org/issue25684 + for i in range(0, self['menu'].index('end') + 1): + self['menu'].entryconfig(i, variable=variable) diff --git a/plugins/coriolis.py b/plugins/coriolis.py index c142c686e..a97eb6c6f 100644 --- a/plugins/coriolis.py +++ b/plugins/coriolis.py @@ -106,25 +106,25 @@ 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, - textvariable=coriolis_config.normal_textvar).grid( + 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"), - command=lambda: coriolis_config.normal_textvar.set(value=DEFAULT_NORMAL_URL)).grid( + 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 ) cur_row += 1 # 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'), - command=lambda: coriolis_config.beta_textvar.set(value=DEFAULT_BETA_URL)).grid( + 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 ) cur_row += 1 diff --git a/plugins/eddn.py b/plugins/eddn.py index 8ac1c41fe..c5a978dbc 100644 --- a/plugins/eddn.py +++ b/plugins/eddn.py @@ -27,7 +27,6 @@ import pathlib import re import sqlite3 -import sys import tkinter as tk from platform import system from textwrap import dedent @@ -282,7 +281,7 @@ def add_message(self, cmdr: str, msg: MutableMapping[str, Any]) -> int: 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 +1073,7 @@ def standard_header( 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/plugins/edsm.py b/plugins/edsm.py index 46d05f4c1..146dd5ab6 100644 --- a/plugins/edsm.py +++ b/plugins/edsm.py @@ -113,10 +113,10 @@ def __init__(self): 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 0e0eb7bff..d96e05ef5 100644 --- a/plugins/inara.py +++ b/plugins/inara.py @@ -125,7 +125,7 @@ def __init__(self): 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 6c2722b67..9e0459b64 100644 --- a/prefs.py +++ b/prefs.py @@ -18,7 +18,7 @@ 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 @@ def __exit__( 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,30 +223,21 @@ def __init__(self, parent: tk.Tk, callback: Optional[Callable]): 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 - # 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': 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 @@ def __init__(self, parent: tk.Tk, callback: Optional[Callable]): 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("", 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("", 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 @@ -326,7 +285,7 @@ def __init__(self, parent: tk.Tk, callback: Optional[Callable]): # 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() @@ -402,16 +361,12 @@ def __setup_output_tab(self, root_notebook: ttk.Notebook) -> None: # 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()) - 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( + self.outbutton = ttk.Button( output_frame, text=text, # Technically this is different from the label in Settings > Output, as *this* is used @@ -444,7 +399,7 @@ def __setup_config_tab(self, notebook: ttk.Notebook) -> None: # noqa: CCR001 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( @@ -455,14 +410,10 @@ def __setup_config_tab(self, notebook: ttk.Notebook) -> None: # noqa: CCR001 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( + self.logbutton = ttk.Button( config_frame, text=text, # LANG: Settings > Configuration - Label for Journal files location @@ -472,7 +423,7 @@ def __setup_config_tab(self, notebook: ttk.Notebook) -> None: # noqa: CCR001 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'), @@ -499,7 +450,7 @@ def __setup_config_tab(self, notebook: ttk.Notebook) -> None: # noqa: CCR001 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 @@ def __setup_config_tab(self, notebook: ttk.Notebook) -> None: # noqa: CCR001 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) - - 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 = ttk.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') + ) - self.hotkey_text.bind('', self.hotkeystart) - self.hotkey_text.bind('', self.hotkeyend) - self.hotkey_text.grid(column=1, columnspan=2, pady=self.BOXY, sticky=tk.W, row=cur_row) + self.hotkey_text.bind('', self.hotkeystart) + self.hotkey_text.bind('', 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( @@ -700,7 +623,7 @@ def __setup_config_tab(self, notebook: ttk.Notebook) -> None: # noqa: CCR001 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 @@ -803,7 +726,7 @@ def __setup_appearance_tab(self, notebook: ttk.Notebook) -> None: 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'), @@ -816,7 +739,7 @@ def __setup_appearance_tab(self, notebook: ttk.Notebook) -> None: 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', @@ -947,7 +870,7 @@ def __setup_plugin_tab(self, notebook: ttk.Notebook) -> None: # noqa: CCR001 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()) @@ -959,7 +882,7 @@ def __setup_plugin_tab(self, notebook: ttk.Notebook) -> None: # noqa: CCR001 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 @@ -1070,14 +993,6 @@ def cmdrchanged(self, event=None): 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 @@ def displaypath(self, pathvar: tk.StringVar, entryfield: tk.Entry) -> None: 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 @@ def apply(self) -> None: 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 @@ def _destroy(self) -> None: 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('<>', when="tail") diff --git a/protocol.py b/protocol.py index f3b956d28..1fb885957 100644 --- a/protocol.py +++ b/protocol.py @@ -26,6 +26,7 @@ if sys.platform == 'win32': from ctypes import windll # type: ignore + try: if windll.ntdll.wine_get_version: is_wine = True @@ -58,72 +59,13 @@ def event(self, url: str) -> None: self.master.event_generate('<>', 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 - 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 @@ -245,11 +187,11 @@ def WndProc(hwnd: HWND, message: UINT, wParam: WPARAM, lParam: LPARAM) -> c_long # 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: @@ -480,12 +422,9 @@ 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) - 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/pyproject.toml b/pyproject.toml index e32ad617f..b98fbf4f5 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 e9d4f58d5..c9239fdc2 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' diff --git a/ships.p b/ships.p deleted file mode 100644 index d289497c9..000000000 Binary files a/ships.p and /dev/null differ diff --git a/stats.py b/stats.py index 4351e2974..c377e5d3a 100644 --- a/stats.py +++ b/stats.py @@ -374,7 +374,7 @@ def __init__(self, parent: tk.Tk, data: dict[str, Any]) -> None: 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 @@ def __init__(self, parent: tk.Tk, data: dict[str, Any]) -> None: 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 @@ def __init__(self, parent: tk.Tk, data: dict[str, Any]) -> None: 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 6a588f579..484e8d29c 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 @@ -32,7 +31,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 690c75eb8..2b5244b67 100644 --- a/tests/config/_old_config.py +++ b/tests/config/_old_config.py @@ -5,21 +5,15 @@ 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 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': # noqa: C901 def __init__(self): self.app_dir = join(known_folder_path(FOLDERID_LocalAppData), appname) # type: ignore diff --git a/theme.py b/theme.py index bfb76c55d..bbe62ef5f 100644 --- a/theme.py +++ b/theme.py @@ -150,7 +150,7 @@ def register(self, widget: tk.Widget | tk.BitmapImage) -> None: # noqa: CCR001, # 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 @@ -268,8 +268,7 @@ def _colors(self, root: tk.Tk, theme: int) -> None: # (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 @@ def _update_widget(self, widget: tk.Widget | tk.BitmapImage) -> None: # noqa: C 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 @@ def apply(self, root: tk.Tk) -> None: # noqa: CCR001, C901 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/ttkHyperlinkLabel.py b/ttkHyperlinkLabel.py index 0cc19b4e1..9bb3b9bff 100644 --- a/ttkHyperlinkLabel.py +++ b/ttkHyperlinkLabel.py @@ -22,7 +22,6 @@ import sys import tkinter as tk -import warnings import webbrowser from tkinter import font as tk_font from tkinter import ttk @@ -32,8 +31,7 @@ def _(x: str) -> str: return x -# 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: @@ -51,22 +49,14 @@ def __init__(self, master: ttk.Frame | tk.Frame | None = None, **kw: Any) -> Non 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('', 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 '' or '', self._contextmenu) + self.bind('', self._contextmenu) self.bind('', self._enter) self.bind('', self._leave) @@ -107,10 +97,9 @@ def configure( # noqa: CCR001 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) @@ -140,50 +129,9 @@ def _click(self, event: tk.Event) -> None: 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.""" 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: - - "\firefox.exe" -osint -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()` 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) diff --git a/update.py b/update.py index 024aeb098..991558d64 100644 --- a/update.py +++ b/update.py @@ -7,10 +7,8 @@ """ 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 @@ -115,21 +113,6 @@ def __init__(self, tkroot: tk.Tk | None = None, provider: str = 'internal'): 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 +125,6 @@ def set_automatic_updates_check(self, onoroff: bool) -> None: 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 +135,6 @@ def check_for_updates(self) -> None: 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,13 +161,9 @@ def check_appcast(self) -> EDMCVersion | None: 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. - sparkle_platform = 'windows' + # For *these* purposes all systems are 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