mirror of
https://github.com/EDCD/EDMarketConnector.git
synced 2025-04-13 15:57:14 +03:00
Merge pull request #2187 from HullSeals/enhancement/2186/remove-darwin
[2186] Remove MacOS Support
This commit is contained in:
commit
065e2ae7de
1
.flake8
1
.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
|
||||
|
@ -6,5 +6,4 @@ scripts_are_modules = True
|
||||
; `<var> = <value>`
|
||||
; i.e. no typing info.
|
||||
check_untyped_defs = True
|
||||
; platform = darwin
|
||||
explicit_package_bases = True
|
||||
|
@ -679,7 +679,7 @@ the following does not work:
|
||||
|
||||
```py
|
||||
from sys import platform
|
||||
if platform == 'darwin':
|
||||
if platform == 'win32':
|
||||
...
|
||||
```
|
||||
|
||||
|
@ -496,21 +496,20 @@ class AppWindow:
|
||||
|
||||
plug.load_plugins(master)
|
||||
|
||||
if sys.platform != 'darwin':
|
||||
if sys.platform == 'win32':
|
||||
self.w.wm_iconbitmap(default='EDMarketConnector.ico')
|
||||
if sys.platform == 'win32':
|
||||
self.w.wm_iconbitmap(default='EDMarketConnector.ico')
|
||||
|
||||
else:
|
||||
self.w.tk.call('wm', 'iconphoto', self.w, '-default',
|
||||
tk.PhotoImage(file=path.join(config.respath_path, 'io.edcd.EDMarketConnector.png')))
|
||||
else:
|
||||
self.w.tk.call('wm', 'iconphoto', self.w, '-default',
|
||||
tk.PhotoImage(file=path.join(config.respath_path, 'io.edcd.EDMarketConnector.png')))
|
||||
|
||||
# TODO: Export to files and merge from them in future ?
|
||||
self.theme_icon = tk.PhotoImage(
|
||||
data='R0lGODlhFAAQAMZQAAoKCQoKCgsKCQwKCQsLCgwLCg4LCQ4LCg0MCg8MCRAMCRANChINCREOChIOChQPChgQChgRCxwTCyYVCSoXCS0YCTkdCTseCT0fCTsjDU0jB0EnDU8lB1ElB1MnCFIoCFMoCEkrDlkqCFwrCGEuCWIuCGQvCFs0D1w1D2wyCG0yCF82D182EHE0CHM0CHQ1CGQ5EHU2CHc3CHs4CH45CIA6CIE7CJdECIdLEolMEohQE5BQE41SFJBTE5lUE5pVE5RXFKNaFKVbFLVjFbZkFrxnFr9oFsNqFsVrF8RsFshtF89xF9NzGNh1GNl2GP+KG////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////yH5BAEKAH8ALAAAAAAUABAAAAeegAGCgiGDhoeIRDiIjIZGKzmNiAQBQxkRTU6am0tPCJSGShuSAUcLoIIbRYMFra4FAUgQAQCGJz6CDQ67vAFJJBi0hjBBD0w9PMnJOkAiJhaIKEI7HRoc19ceNAolwbWDLD8uAQnl5ga1I9CHEjEBAvDxAoMtFIYCBy+kFDKHAgM3ZtgYSLAGgwkp3pEyBOJCC2ELB31QATGioAoVAwEAOw==') # noqa: E501
|
||||
self.theme_minimize = tk.BitmapImage(
|
||||
data='#define im_width 16\n#define im_height 16\nstatic unsigned char im_bits[] = {\n 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,\n 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xfc, 0x3f,\n 0xfc, 0x3f, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 };\n') # noqa: E501
|
||||
self.theme_close = tk.BitmapImage(
|
||||
data='#define im_width 16\n#define im_height 16\nstatic unsigned char im_bits[] = {\n 0x00, 0x00, 0x00, 0x00, 0x0c, 0x30, 0x1c, 0x38, 0x38, 0x1c, 0x70, 0x0e,\n 0xe0, 0x07, 0xc0, 0x03, 0xc0, 0x03, 0xe0, 0x07, 0x70, 0x0e, 0x38, 0x1c,\n 0x1c, 0x38, 0x0c, 0x30, 0x00, 0x00, 0x00, 0x00 };\n') # noqa: E501
|
||||
# TODO: Export to files and merge from them in future ?
|
||||
self.theme_icon = tk.PhotoImage(
|
||||
data='R0lGODlhFAAQAMZQAAoKCQoKCgsKCQwKCQsLCgwLCg4LCQ4LCg0MCg8MCRAMCRANChINCREOChIOChQPChgQChgRCxwTCyYVCSoXCS0YCTkdCTseCT0fCTsjDU0jB0EnDU8lB1ElB1MnCFIoCFMoCEkrDlkqCFwrCGEuCWIuCGQvCFs0D1w1D2wyCG0yCF82D182EHE0CHM0CHQ1CGQ5EHU2CHc3CHs4CH45CIA6CIE7CJdECIdLEolMEohQE5BQE41SFJBTE5lUE5pVE5RXFKNaFKVbFLVjFbZkFrxnFr9oFsNqFsVrF8RsFshtF89xF9NzGNh1GNl2GP+KG////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////yH5BAEKAH8ALAAAAAAUABAAAAeegAGCgiGDhoeIRDiIjIZGKzmNiAQBQxkRTU6am0tPCJSGShuSAUcLoIIbRYMFra4FAUgQAQCGJz6CDQ67vAFJJBi0hjBBD0w9PMnJOkAiJhaIKEI7HRoc19ceNAolwbWDLD8uAQnl5ga1I9CHEjEBAvDxAoMtFIYCBy+kFDKHAgM3ZtgYSLAGgwkp3pEyBOJCC2ELB31QATGioAoVAwEAOw==') # noqa: E501
|
||||
self.theme_minimize = tk.BitmapImage(
|
||||
data='#define im_width 16\n#define im_height 16\nstatic unsigned char im_bits[] = {\n 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,\n 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xfc, 0x3f,\n 0xfc, 0x3f, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 };\n') # noqa: E501
|
||||
self.theme_close = tk.BitmapImage(
|
||||
data='#define im_width 16\n#define im_height 16\nstatic unsigned char im_bits[] = {\n 0x00, 0x00, 0x00, 0x00, 0x0c, 0x30, 0x1c, 0x38, 0x38, 0x1c, 0x70, 0x0e,\n 0xe0, 0x07, 0xc0, 0x03, 0xc0, 0x03, 0xe0, 0x07, 0x70, 0x0e, 0x38, 0x1c,\n 0x1c, 0x38, 0x0c, 0x30, 0x00, 0x00, 0x00, 0x00 };\n') # noqa: E501
|
||||
|
||||
frame = tk.Frame(self.w, name=appname.lower())
|
||||
frame.grid(sticky=tk.NSEW)
|
||||
@ -599,7 +598,7 @@ class AppWindow:
|
||||
self.theme_button = tk.Label(
|
||||
frame,
|
||||
name='themed_update_button',
|
||||
width=32 if sys.platform == 'darwin' else 28,
|
||||
width=28,
|
||||
state=tk.DISABLED
|
||||
)
|
||||
|
||||
@ -633,148 +632,104 @@ class AppWindow:
|
||||
self.updater = update.Updater(tkroot=self.w)
|
||||
self.updater.check_for_updates() # Sparkle / WinSparkle does this automatically for packaged apps
|
||||
|
||||
if sys.platform == 'darwin':
|
||||
# Can't handle (de)iconify if topmost is set, so suppress iconify button
|
||||
# http://wiki.tcl.tk/13428 and p15 of
|
||||
# https://developer.apple.com/legacy/library/documentation/Carbon/Conceptual/HandlingWindowsControls/windowscontrols.pdf
|
||||
root.call('tk::unsupported::MacWindowStyle', 'style', root, 'document', 'closeBox resizable')
|
||||
self.file_menu = self.view_menu = tk.Menu(self.menubar, tearoff=tk.FALSE)
|
||||
self.file_menu.add_command(command=lambda: stats.StatsDialog(self.w, self.status))
|
||||
self.file_menu.add_command(command=self.save_raw)
|
||||
self.file_menu.add_command(command=lambda: prefs.PreferencesDialog(self.w, self.postprefs))
|
||||
self.file_menu.add_separator()
|
||||
self.file_menu.add_command(command=self.onexit)
|
||||
self.menubar.add_cascade(menu=self.file_menu)
|
||||
self.edit_menu = tk.Menu(self.menubar, tearoff=tk.FALSE)
|
||||
self.edit_menu.add_command(accelerator='Ctrl+C', state=tk.DISABLED, command=self.copy)
|
||||
self.menubar.add_cascade(menu=self.edit_menu)
|
||||
self.help_menu = tk.Menu(self.menubar, tearoff=tk.FALSE) # type: ignore
|
||||
self.help_menu.add_command(command=self.help_general) # Documentation
|
||||
self.help_menu.add_command(command=self.help_troubleshooting) # Troubleshooting
|
||||
self.help_menu.add_command(command=self.help_report_a_bug) # Report A Bug
|
||||
self.help_menu.add_command(command=self.help_privacy) # Privacy Policy
|
||||
self.help_menu.add_command(command=self.help_releases) # Release Notes
|
||||
self.help_menu.add_command(command=lambda: self.updater.check_for_updates()) # Check for Updates...
|
||||
# About E:D Market Connector
|
||||
self.help_menu.add_command(command=lambda: not self.HelpAbout.showing and self.HelpAbout(self.w))
|
||||
self.help_menu.add_command(command=prefs.help_open_log_folder) # Open Log Folder
|
||||
|
||||
# https://www.tcl.tk/man/tcl/TkCmd/menu.htm
|
||||
self.system_menu = tk.Menu(self.menubar, name='apple')
|
||||
self.system_menu.add_command(command=lambda: self.w.call('tk::mac::standardAboutPanel'))
|
||||
self.system_menu.add_command(command=lambda: self.updater.check_for_updates())
|
||||
self.menubar.add_cascade(menu=self.help_menu)
|
||||
if sys.platform == 'win32':
|
||||
# Must be added after at least one "real" menu entry
|
||||
self.always_ontop = tk.BooleanVar(value=bool(config.get_int('always_ontop')))
|
||||
self.system_menu = tk.Menu(self.menubar, name='system', tearoff=tk.FALSE)
|
||||
self.system_menu.add_separator()
|
||||
# LANG: Appearance - Label for checkbox to select if application always on top
|
||||
self.system_menu.add_checkbutton(label=_('Always on top'),
|
||||
variable=self.always_ontop,
|
||||
command=self.ontop_changed) # Appearance setting
|
||||
self.menubar.add_cascade(menu=self.system_menu)
|
||||
self.file_menu = tk.Menu(self.menubar, name='file')
|
||||
self.file_menu.add_command(command=self.save_raw)
|
||||
self.menubar.add_cascade(menu=self.file_menu)
|
||||
self.edit_menu = tk.Menu(self.menubar, name='edit')
|
||||
self.edit_menu.add_command(accelerator='Command-c', state=tk.DISABLED, command=self.copy)
|
||||
self.menubar.add_cascade(menu=self.edit_menu)
|
||||
self.w.bind('<Command-c>', self.copy)
|
||||
self.view_menu = tk.Menu(self.menubar, name='view')
|
||||
self.view_menu.add_command(command=lambda: stats.StatsDialog(self.w, self.status))
|
||||
self.menubar.add_cascade(menu=self.view_menu)
|
||||
window_menu = tk.Menu(self.menubar, name='window')
|
||||
self.menubar.add_cascade(menu=window_menu)
|
||||
self.help_menu = tk.Menu(self.menubar, name='help')
|
||||
self.w.createcommand("::tk::mac::ShowHelp", self.help_general)
|
||||
self.help_menu.add_command(command=self.help_troubleshooting)
|
||||
self.help_menu.add_command(command=self.help_report_a_bug)
|
||||
self.help_menu.add_command(command=self.help_privacy)
|
||||
self.help_menu.add_command(command=self.help_releases)
|
||||
self.menubar.add_cascade(menu=self.help_menu)
|
||||
self.w['menu'] = self.menubar
|
||||
# https://www.tcl.tk/man/tcl/TkCmd/tk_mac.htm
|
||||
self.w.call('set', 'tk::mac::useCompatibilityMetrics', '0')
|
||||
self.w.createcommand('tkAboutDialog', lambda: self.w.call('tk::mac::standardAboutPanel'))
|
||||
self.w.createcommand("::tk::mac::Quit", self.onexit)
|
||||
self.w.createcommand("::tk::mac::ShowPreferences", lambda: prefs.PreferencesDialog(self.w, self.postprefs))
|
||||
self.w.createcommand("::tk::mac::ReopenApplication", self.w.deiconify) # click on app in dock = restore
|
||||
self.w.protocol("WM_DELETE_WINDOW", self.w.withdraw) # close button shouldn't quit app
|
||||
self.w.resizable(tk.FALSE, tk.FALSE) # Can't be only resizable on one axis
|
||||
else:
|
||||
self.file_menu = self.view_menu = tk.Menu(self.menubar, tearoff=tk.FALSE)
|
||||
self.file_menu.add_command(command=lambda: stats.StatsDialog(self.w, self.status))
|
||||
self.file_menu.add_command(command=self.save_raw)
|
||||
self.file_menu.add_command(command=lambda: prefs.PreferencesDialog(self.w, self.postprefs))
|
||||
self.file_menu.add_separator()
|
||||
self.file_menu.add_command(command=self.onexit)
|
||||
self.menubar.add_cascade(menu=self.file_menu)
|
||||
self.edit_menu = tk.Menu(self.menubar, tearoff=tk.FALSE)
|
||||
self.edit_menu.add_command(accelerator='Ctrl+C', state=tk.DISABLED, command=self.copy)
|
||||
self.menubar.add_cascade(menu=self.edit_menu)
|
||||
self.help_menu = tk.Menu(self.menubar, tearoff=tk.FALSE) # type: ignore
|
||||
self.help_menu.add_command(command=self.help_general) # Documentation
|
||||
self.help_menu.add_command(command=self.help_troubleshooting) # Troubleshooting
|
||||
self.help_menu.add_command(command=self.help_report_a_bug) # Report A Bug
|
||||
self.help_menu.add_command(command=self.help_privacy) # Privacy Policy
|
||||
self.help_menu.add_command(command=self.help_releases) # Release Notes
|
||||
self.help_menu.add_command(command=lambda: self.updater.check_for_updates()) # Check for Updates...
|
||||
# About E:D Market Connector
|
||||
self.help_menu.add_command(command=lambda: not self.HelpAbout.showing and self.HelpAbout(self.w))
|
||||
self.help_menu.add_command(command=prefs.help_open_log_folder) # Open Log Folder
|
||||
self.w.bind('<Control-c>', self.copy)
|
||||
|
||||
self.menubar.add_cascade(menu=self.help_menu)
|
||||
if sys.platform == 'win32':
|
||||
# Must be added after at least one "real" menu entry
|
||||
self.always_ontop = tk.BooleanVar(value=bool(config.get_int('always_ontop')))
|
||||
self.system_menu = tk.Menu(self.menubar, name='system', tearoff=tk.FALSE)
|
||||
self.system_menu.add_separator()
|
||||
# LANG: Appearance - Label for checkbox to select if application always on top
|
||||
self.system_menu.add_checkbutton(label=_('Always on top'),
|
||||
variable=self.always_ontop,
|
||||
command=self.ontop_changed) # Appearance setting
|
||||
self.menubar.add_cascade(menu=self.system_menu)
|
||||
self.w.bind('<Control-c>', self.copy)
|
||||
# Bind to the Default theme minimise button
|
||||
self.w.bind("<Unmap>", self.default_iconify)
|
||||
|
||||
# Bind to the Default theme minimise button
|
||||
self.w.bind("<Unmap>", self.default_iconify)
|
||||
self.w.protocol("WM_DELETE_WINDOW", self.onexit)
|
||||
theme.register(self.menubar) # menus and children aren't automatically registered
|
||||
theme.register(self.file_menu)
|
||||
theme.register(self.edit_menu)
|
||||
theme.register(self.help_menu)
|
||||
|
||||
self.w.protocol("WM_DELETE_WINDOW", self.onexit)
|
||||
theme.register(self.menubar) # menus and children aren't automatically registered
|
||||
theme.register(self.file_menu)
|
||||
theme.register(self.edit_menu)
|
||||
theme.register(self.help_menu)
|
||||
|
||||
# Alternate title bar and menu for dark theme
|
||||
self.theme_menubar = tk.Frame(frame, name="alternate_menubar")
|
||||
self.theme_menubar.columnconfigure(2, weight=1)
|
||||
theme_titlebar = tk.Label(
|
||||
self.theme_menubar,
|
||||
name="alternate_titlebar",
|
||||
text=applongname,
|
||||
image=self.theme_icon, cursor='fleur',
|
||||
anchor=tk.W, compound=tk.LEFT
|
||||
)
|
||||
theme_titlebar.grid(columnspan=3, padx=2, sticky=tk.NSEW)
|
||||
self.drag_offset: tuple[int | None, int | None] = (None, None)
|
||||
theme_titlebar.bind('<Button-1>', self.drag_start)
|
||||
theme_titlebar.bind('<B1-Motion>', self.drag_continue)
|
||||
theme_titlebar.bind('<ButtonRelease-1>', self.drag_end)
|
||||
theme_minimize = tk.Label(self.theme_menubar, image=self.theme_minimize)
|
||||
theme_minimize.grid(row=0, column=3, padx=2)
|
||||
theme.button_bind(theme_minimize, self.oniconify, image=self.theme_minimize)
|
||||
theme_close = tk.Label(self.theme_menubar, image=self.theme_close)
|
||||
theme_close.grid(row=0, column=4, padx=2)
|
||||
theme.button_bind(theme_close, self.onexit, image=self.theme_close)
|
||||
self.theme_file_menu = tk.Label(self.theme_menubar, anchor=tk.W)
|
||||
self.theme_file_menu.grid(row=1, column=0, padx=self.PADX, sticky=tk.W)
|
||||
theme.button_bind(self.theme_file_menu,
|
||||
lambda e: self.file_menu.tk_popup(e.widget.winfo_rootx(),
|
||||
e.widget.winfo_rooty()
|
||||
+ e.widget.winfo_height()))
|
||||
self.theme_edit_menu = tk.Label(self.theme_menubar, anchor=tk.W)
|
||||
self.theme_edit_menu.grid(row=1, column=1, sticky=tk.W)
|
||||
theme.button_bind(self.theme_edit_menu,
|
||||
lambda e: self.edit_menu.tk_popup(e.widget.winfo_rootx(),
|
||||
e.widget.winfo_rooty()
|
||||
+ e.widget.winfo_height()))
|
||||
self.theme_help_menu = tk.Label(self.theme_menubar, anchor=tk.W)
|
||||
self.theme_help_menu.grid(row=1, column=2, sticky=tk.W)
|
||||
theme.button_bind(self.theme_help_menu,
|
||||
lambda e: self.help_menu.tk_popup(e.widget.winfo_rootx(),
|
||||
e.widget.winfo_rooty()
|
||||
+ e.widget.winfo_height()))
|
||||
tk.Frame(self.theme_menubar, highlightthickness=1).grid(columnspan=5, padx=self.PADX, sticky=tk.EW)
|
||||
theme.register(self.theme_minimize) # images aren't automatically registered
|
||||
theme.register(self.theme_close)
|
||||
self.blank_menubar = tk.Frame(frame, name="blank_menubar")
|
||||
tk.Label(self.blank_menubar).grid()
|
||||
tk.Label(self.blank_menubar).grid()
|
||||
tk.Frame(self.blank_menubar, height=2).grid()
|
||||
theme.register_alternate((self.menubar, self.theme_menubar, self.blank_menubar),
|
||||
{'row': 0, 'columnspan': 2, 'sticky': tk.NSEW})
|
||||
self.w.resizable(tk.TRUE, tk.FALSE)
|
||||
# Alternate title bar and menu for dark theme
|
||||
self.theme_menubar = tk.Frame(frame, name="alternate_menubar")
|
||||
self.theme_menubar.columnconfigure(2, weight=1)
|
||||
theme_titlebar = tk.Label(
|
||||
self.theme_menubar,
|
||||
name="alternate_titlebar",
|
||||
text=applongname,
|
||||
image=self.theme_icon, cursor='fleur',
|
||||
anchor=tk.W, compound=tk.LEFT
|
||||
)
|
||||
theme_titlebar.grid(columnspan=3, padx=2, sticky=tk.NSEW)
|
||||
self.drag_offset: tuple[int | None, int | None] = (None, None)
|
||||
theme_titlebar.bind('<Button-1>', self.drag_start)
|
||||
theme_titlebar.bind('<B1-Motion>', self.drag_continue)
|
||||
theme_titlebar.bind('<ButtonRelease-1>', self.drag_end)
|
||||
theme_minimize = tk.Label(self.theme_menubar, image=self.theme_minimize)
|
||||
theme_minimize.grid(row=0, column=3, padx=2)
|
||||
theme.button_bind(theme_minimize, self.oniconify, image=self.theme_minimize)
|
||||
theme_close = tk.Label(self.theme_menubar, image=self.theme_close)
|
||||
theme_close.grid(row=0, column=4, padx=2)
|
||||
theme.button_bind(theme_close, self.onexit, image=self.theme_close)
|
||||
self.theme_file_menu = tk.Label(self.theme_menubar, anchor=tk.W)
|
||||
self.theme_file_menu.grid(row=1, column=0, padx=self.PADX, sticky=tk.W)
|
||||
theme.button_bind(self.theme_file_menu,
|
||||
lambda e: self.file_menu.tk_popup(e.widget.winfo_rootx(),
|
||||
e.widget.winfo_rooty()
|
||||
+ e.widget.winfo_height()))
|
||||
self.theme_edit_menu = tk.Label(self.theme_menubar, anchor=tk.W)
|
||||
self.theme_edit_menu.grid(row=1, column=1, sticky=tk.W)
|
||||
theme.button_bind(self.theme_edit_menu,
|
||||
lambda e: self.edit_menu.tk_popup(e.widget.winfo_rootx(),
|
||||
e.widget.winfo_rooty()
|
||||
+ e.widget.winfo_height()))
|
||||
self.theme_help_menu = tk.Label(self.theme_menubar, anchor=tk.W)
|
||||
self.theme_help_menu.grid(row=1, column=2, sticky=tk.W)
|
||||
theme.button_bind(self.theme_help_menu,
|
||||
lambda e: self.help_menu.tk_popup(e.widget.winfo_rootx(),
|
||||
e.widget.winfo_rooty()
|
||||
+ e.widget.winfo_height()))
|
||||
tk.Frame(self.theme_menubar, highlightthickness=1).grid(columnspan=5, padx=self.PADX, sticky=tk.EW)
|
||||
theme.register(self.theme_minimize) # images aren't automatically registered
|
||||
theme.register(self.theme_close)
|
||||
self.blank_menubar = tk.Frame(frame, name="blank_menubar")
|
||||
tk.Label(self.blank_menubar).grid()
|
||||
tk.Label(self.blank_menubar).grid()
|
||||
tk.Frame(self.blank_menubar, height=2).grid()
|
||||
theme.register_alternate((self.menubar, self.theme_menubar, self.blank_menubar),
|
||||
{'row': 0, 'columnspan': 2, 'sticky': tk.NSEW})
|
||||
self.w.resizable(tk.TRUE, tk.FALSE)
|
||||
|
||||
# update geometry
|
||||
if config.get_str('geometry'):
|
||||
match = re.match(r'\+([\-\d]+)\+([\-\d]+)', config.get_str('geometry'))
|
||||
if match:
|
||||
if sys.platform == 'darwin':
|
||||
# http://core.tcl.tk/tk/tktview/c84f660833546b1b84e7
|
||||
if int(match.group(2)) >= 0:
|
||||
self.w.geometry(config.get_str('geometry'))
|
||||
elif sys.platform == 'win32':
|
||||
if sys.platform == 'win32':
|
||||
# Check that the titlebar will be at least partly on screen
|
||||
import ctypes
|
||||
from ctypes.wintypes import POINT
|
||||
@ -910,49 +865,28 @@ class AppWindow:
|
||||
self.system_label['text'] = _('System') + ':' # LANG: Label for 'System' line in main UI
|
||||
self.station_label['text'] = _('Station') + ':' # LANG: Label for 'Station' line in main UI
|
||||
self.button['text'] = self.theme_button['text'] = _('Update') # LANG: Update button in main window
|
||||
if sys.platform == 'darwin':
|
||||
self.menubar.entryconfigure(1, label=_('File')) # LANG: 'File' menu title on OSX
|
||||
self.menubar.entryconfigure(2, label=_('Edit')) # LANG: 'Edit' menu title on OSX
|
||||
self.menubar.entryconfigure(3, label=_('View')) # LANG: 'View' menu title on OSX
|
||||
self.menubar.entryconfigure(4, label=_('Window')) # LANG: 'Window' menu title on OSX
|
||||
self.menubar.entryconfigure(5, label=_('Help')) # LANG: Help' menu title on OSX
|
||||
self.system_menu.entryconfigure(
|
||||
0,
|
||||
label=_("About {APP}").format(APP=applongname) # LANG: App menu entry on OSX
|
||||
)
|
||||
self.system_menu.entryconfigure(1, label=_("Check for Updates...")) # LANG: Help > Check for Updates...
|
||||
self.file_menu.entryconfigure(0, label=_('Save Raw Data...')) # LANG: File > Save Raw Data...
|
||||
self.view_menu.entryconfigure(0, label=_('Status')) # LANG: File > Status
|
||||
self.help_menu.entryconfigure(1, label=_('Documentation')) # LANG: Help > Documentation
|
||||
self.help_menu.entryconfigure(2, label=_('Troubleshooting')) # LANG: Help > Troubleshooting
|
||||
self.help_menu.entryconfigure(3, label=_('Report A Bug')) # LANG: Help > Report A Bug
|
||||
self.help_menu.entryconfigure(4, label=_('Privacy Policy')) # LANG: Help > Privacy Policy
|
||||
self.help_menu.entryconfigure(5, label=_('Release Notes')) # LANG: Help > Release Notes
|
||||
self.help_menu.entryconfigure(6, label=_('Open Log Folder')) # LANG: Help > Open Log Folder
|
||||
self.menubar.entryconfigure(1, label=_('File')) # LANG: 'File' menu title
|
||||
self.menubar.entryconfigure(2, label=_('Edit')) # LANG: 'Edit' menu title
|
||||
self.menubar.entryconfigure(3, label=_('Help')) # LANG: 'Help' menu title
|
||||
self.theme_file_menu['text'] = _('File') # LANG: 'File' menu title
|
||||
self.theme_edit_menu['text'] = _('Edit') # LANG: 'Edit' menu title
|
||||
self.theme_help_menu['text'] = _('Help') # LANG: 'Help' menu title
|
||||
|
||||
else:
|
||||
self.menubar.entryconfigure(1, label=_('File')) # LANG: 'File' menu title
|
||||
self.menubar.entryconfigure(2, label=_('Edit')) # LANG: 'Edit' menu title
|
||||
self.menubar.entryconfigure(3, label=_('Help')) # LANG: 'Help' menu title
|
||||
self.theme_file_menu['text'] = _('File') # LANG: 'File' menu title
|
||||
self.theme_edit_menu['text'] = _('Edit') # LANG: 'Edit' menu title
|
||||
self.theme_help_menu['text'] = _('Help') # LANG: 'Help' menu title
|
||||
# File menu
|
||||
self.file_menu.entryconfigure(0, label=_('Status')) # LANG: File > Status
|
||||
self.file_menu.entryconfigure(1, label=_('Save Raw Data...')) # LANG: File > Save Raw Data...
|
||||
self.file_menu.entryconfigure(2, label=_('Settings')) # LANG: File > Settings
|
||||
self.file_menu.entryconfigure(4, label=_('Exit')) # LANG: File > Exit
|
||||
|
||||
# File menu
|
||||
self.file_menu.entryconfigure(0, label=_('Status')) # LANG: File > Status
|
||||
self.file_menu.entryconfigure(1, label=_('Save Raw Data...')) # LANG: File > Save Raw Data...
|
||||
self.file_menu.entryconfigure(2, label=_('Settings')) # LANG: File > Settings
|
||||
self.file_menu.entryconfigure(4, label=_('Exit')) # LANG: File > Exit
|
||||
|
||||
# Help menu
|
||||
self.help_menu.entryconfigure(0, label=_('Documentation')) # LANG: Help > Documentation
|
||||
self.help_menu.entryconfigure(1, label=_('Troubleshooting')) # LANG: Help > Troubleshooting
|
||||
self.help_menu.entryconfigure(2, label=_('Report A Bug')) # LANG: Help > Report A Bug
|
||||
self.help_menu.entryconfigure(3, label=_('Privacy Policy')) # LANG: Help > Privacy Policy
|
||||
self.help_menu.entryconfigure(4, label=_('Release Notes')) # LANG: Help > Release Notes
|
||||
self.help_menu.entryconfigure(5, label=_('Check for Updates...')) # LANG: Help > Check for Updates...
|
||||
self.help_menu.entryconfigure(6, label=_("About {APP}").format(APP=applongname)) # LANG: Help > About App
|
||||
self.help_menu.entryconfigure(7, label=_('Open Log Folder')) # LANG: Help > Open Log Folder
|
||||
# Help menu
|
||||
self.help_menu.entryconfigure(0, label=_('Documentation')) # LANG: Help > Documentation
|
||||
self.help_menu.entryconfigure(1, label=_('Troubleshooting')) # LANG: Help > Troubleshooting
|
||||
self.help_menu.entryconfigure(2, label=_('Report A Bug')) # LANG: Help > Report A Bug
|
||||
self.help_menu.entryconfigure(3, label=_('Privacy Policy')) # LANG: Help > Privacy Policy
|
||||
self.help_menu.entryconfigure(4, label=_('Release Notes')) # LANG: Help > Release Notes
|
||||
self.help_menu.entryconfigure(5, label=_('Check for Updates...')) # LANG: Help > Check for Updates...
|
||||
self.help_menu.entryconfigure(6, label=_("About {APP}").format(APP=applongname)) # LANG: Help > About App
|
||||
self.help_menu.entryconfigure(7, label=_('Open Log Folder')) # LANG: Help > Open Log Folder
|
||||
|
||||
# Edit menu
|
||||
self.edit_menu.entryconfigure(0, label=_('Copy')) # LANG: Label for 'Copy' as in 'Copy and Paste'
|
||||
@ -975,13 +909,8 @@ class AppWindow:
|
||||
|
||||
self.button['state'] = self.theme_button['state'] = tk.DISABLED
|
||||
|
||||
if sys.platform == 'darwin':
|
||||
self.view_menu.entryconfigure(0, state=tk.DISABLED) # Status
|
||||
self.file_menu.entryconfigure(0, state=tk.DISABLED) # Save Raw Data
|
||||
|
||||
else:
|
||||
self.file_menu.entryconfigure(0, state=tk.DISABLED) # Status
|
||||
self.file_menu.entryconfigure(1, state=tk.DISABLED) # Save Raw Data
|
||||
self.file_menu.entryconfigure(0, state=tk.DISABLED) # Status
|
||||
self.file_menu.entryconfigure(1, state=tk.DISABLED) # Save Raw Data
|
||||
|
||||
self.w.update_idletasks()
|
||||
try:
|
||||
@ -989,13 +918,8 @@ class AppWindow:
|
||||
# LANG: Successfully authenticated with the Frontier website
|
||||
self.status['text'] = _('Authentication successful')
|
||||
|
||||
if sys.platform == 'darwin':
|
||||
self.view_menu.entryconfigure(0, state=tk.NORMAL) # Status
|
||||
self.file_menu.entryconfigure(0, state=tk.NORMAL) # Save Raw Data
|
||||
|
||||
else:
|
||||
self.file_menu.entryconfigure(0, state=tk.NORMAL) # Status
|
||||
self.file_menu.entryconfigure(1, state=tk.NORMAL) # Save Raw Data
|
||||
self.file_menu.entryconfigure(0, state=tk.NORMAL) # Status
|
||||
self.file_menu.entryconfigure(1, state=tk.NORMAL) # Save Raw Data
|
||||
|
||||
except (companion.CredentialsError, companion.ServerError, companion.ServerLagging) as e:
|
||||
self.status['text'] = str(e)
|
||||
@ -1666,13 +1590,8 @@ class AppWindow:
|
||||
companion.session.auth_callback()
|
||||
# LANG: Successfully authenticated with the Frontier website
|
||||
self.status['text'] = _('Authentication successful')
|
||||
if sys.platform == 'darwin':
|
||||
self.view_menu.entryconfigure(0, state=tk.NORMAL) # Status
|
||||
self.file_menu.entryconfigure(0, state=tk.NORMAL) # Save Raw Data
|
||||
|
||||
else:
|
||||
self.file_menu.entryconfigure(0, state=tk.NORMAL) # Status
|
||||
self.file_menu.entryconfigure(1, state=tk.NORMAL) # Save Raw Data
|
||||
self.file_menu.entryconfigure(0, state=tk.NORMAL) # Status
|
||||
self.file_menu.entryconfigure(1, state=tk.NORMAL) # Save Raw Data
|
||||
|
||||
except companion.ServerError as e:
|
||||
self.status['text'] = str(e)
|
||||
@ -1831,8 +1750,7 @@ class AppWindow:
|
||||
|
||||
# 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 @@ class AppWindow:
|
||||
"""
|
||||
default_extension: str = ''
|
||||
|
||||
if sys.platform == 'darwin':
|
||||
default_extension = '.json'
|
||||
|
||||
timestamp: str = strftime('%Y-%m-%dT%H.%M.%S', localtime())
|
||||
f = tkinter.filedialog.asksaveasfilename(
|
||||
parent=self.w,
|
||||
@ -1954,9 +1869,8 @@ class AppWindow:
|
||||
config.set_shutdown() # Signal we're in shutdown now.
|
||||
|
||||
# http://core.tcl.tk/tk/tktview/c84f660833546b1b84e7
|
||||
if sys.platform != 'darwin' or self.w.winfo_rooty() > 0:
|
||||
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
|
||||
|
@ -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";
|
||||
|
||||
|
2
build.py
2
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",
|
||||
|
@ -7,7 +7,6 @@ See LICENSE file.
|
||||
|
||||
Windows uses the Registry to store values in a flat manner.
|
||||
Linux uses a file, but for commonality it's still a flat data structure.
|
||||
macOS uses a 'defaults' object.
|
||||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
@ -54,7 +53,7 @@ appcmdname = 'EDMC'
|
||||
# <https://semver.org/#semantic-versioning-specification-semver>
|
||||
# Major.Minor.Patch(-prerelease)(+buildmetadata)
|
||||
# NB: Do *not* import this, use the functions appversion() and appversion_nobuild()
|
||||
_static_appversion = '5.10.3'
|
||||
_static_appversion = '5.11.0-alpha0'
|
||||
|
||||
_cached_version: semantic_version.Version | None = None
|
||||
copyright = '© 2015-2019 Jonathan Harris, 2020-2024 EDCD'
|
||||
@ -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)
|
||||
|
191
config/darwin.py
191
config/darwin.py
@ -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
|
@ -20,13 +20,13 @@ from EDMCLogging import get_main_logger
|
||||
|
||||
logger = get_main_logger()
|
||||
|
||||
if sys.platform in ('darwin', 'win32'):
|
||||
if sys.platform == 'win32':
|
||||
from watchdog.events import FileSystemEventHandler
|
||||
from watchdog.observers import Observer
|
||||
else:
|
||||
# Linux's inotify doesn't work over CIFS or NFS, so poll
|
||||
class FileSystemEventHandler: # type: ignore
|
||||
"""Dummy class to represent a file system event handler on platforms other than macOS and Windows."""
|
||||
"""Dummy class to represent a file system event handler on platforms other than Windows."""
|
||||
|
||||
|
||||
class Dashboard(FileSystemEventHandler):
|
||||
@ -160,7 +160,7 @@ class Dashboard(FileSystemEventHandler):
|
||||
|
||||
def on_modified(self, event) -> None:
|
||||
"""
|
||||
Watchdog callback - DirModifiedEvent on macOS, FileModifiedEvent on Windows.
|
||||
Watchdog callback - FileModifiedEvent on Windows.
|
||||
|
||||
:param event: Watchdog event.
|
||||
"""
|
||||
|
@ -7,6 +7,8 @@ from __future__ import annotations
|
||||
|
||||
import logging
|
||||
import tkinter as tk
|
||||
from tkinter import ttk
|
||||
|
||||
import myNotebook as nb # noqa: N813
|
||||
from config import appname, config
|
||||
|
||||
@ -63,7 +65,7 @@ class ClickCounter:
|
||||
|
||||
# setup our config in a "Click Count: number"
|
||||
nb.Label(frame, text='Click Count').grid(row=current_row)
|
||||
nb.Entry(frame, textvariable=self.click_count).grid(row=current_row, column=1)
|
||||
ttk.Entry(frame, textvariable=self.click_count).grid(row=current_row, column=1)
|
||||
current_row += 1 # Always increment our row counter, makes for far easier tkinter design.
|
||||
return frame
|
||||
|
||||
|
@ -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()
|
||||
|
276
hotkey/darwin.py
276
hotkey/darwin.py
@ -1,276 +0,0 @@
|
||||
"""darwin/macOS implementation of hotkey.AbstractHotkeyMgr."""
|
||||
from __future__ import annotations
|
||||
|
||||
import pathlib
|
||||
import sys
|
||||
import tkinter as tk
|
||||
from typing import Callable
|
||||
assert sys.platform == 'darwin'
|
||||
|
||||
import objc
|
||||
from AppKit import (
|
||||
NSAlternateKeyMask, NSApplication, NSBeep, NSClearLineFunctionKey, NSCommandKeyMask, NSControlKeyMask,
|
||||
NSDeleteFunctionKey, NSDeviceIndependentModifierFlagsMask, NSEvent, NSF1FunctionKey, NSF35FunctionKey,
|
||||
NSFlagsChanged, NSKeyDown, NSKeyDownMask, NSKeyUp, NSNumericPadKeyMask, NSShiftKeyMask, NSSound, NSWorkspace
|
||||
)
|
||||
|
||||
from config import config
|
||||
from EDMCLogging import get_main_logger
|
||||
from hotkey import AbstractHotkeyMgr
|
||||
|
||||
logger = get_main_logger()
|
||||
|
||||
|
||||
class MacHotkeyMgr(AbstractHotkeyMgr):
|
||||
"""Hot key management."""
|
||||
|
||||
POLL = 250
|
||||
# https://developer.apple.com/library/mac/documentation/Cocoa/Reference/ApplicationKit/Classes/NSEvent_Class/#//apple_ref/doc/constant_group/Function_Key_Unicodes
|
||||
DISPLAY = {
|
||||
0x03: u'⌅', 0x09: u'⇥', 0xd: u'↩', 0x19: u'⇤', 0x1b: u'esc', 0x20: u'⏘', 0x7f: u'⌫',
|
||||
0xf700: u'↑', 0xf701: u'↓', 0xf702: u'←', 0xf703: u'→',
|
||||
0xf727: u'Ins',
|
||||
0xf728: u'⌦', 0xf729: u'↖', 0xf72a: u'Fn', 0xf72b: u'↘',
|
||||
0xf72c: u'⇞', 0xf72d: u'⇟', 0xf72e: u'PrtScr', 0xf72f: u'ScrollLock',
|
||||
0xf730: u'Pause', 0xf731: u'SysReq', 0xf732: u'Break', 0xf733: u'Reset',
|
||||
0xf739: u'⌧',
|
||||
}
|
||||
(ACQUIRE_INACTIVE, ACQUIRE_ACTIVE, ACQUIRE_NEW) = range(3)
|
||||
|
||||
def __init__(self):
|
||||
self.MODIFIERMASK = NSShiftKeyMask | NSControlKeyMask | NSAlternateKeyMask | NSCommandKeyMask \
|
||||
| NSNumericPadKeyMask
|
||||
self.root: tk.Tk
|
||||
|
||||
self.keycode = 0
|
||||
self.modifiers = 0
|
||||
self.activated = False
|
||||
self.observer = None
|
||||
|
||||
self.acquire_key = 0
|
||||
self.acquire_state = MacHotkeyMgr.ACQUIRE_INACTIVE
|
||||
|
||||
self.tkProcessKeyEvent_old: Callable
|
||||
|
||||
self.snd_good = NSSound.alloc().initWithContentsOfFile_byReference_(
|
||||
pathlib.Path(config.respath_path) / 'snd_good.wav', False
|
||||
)
|
||||
self.snd_bad = NSSound.alloc().initWithContentsOfFile_byReference_(
|
||||
pathlib.Path(config.respath_path) / 'snd_bad.wav', False
|
||||
)
|
||||
|
||||
def register(self, root: tk.Tk, keycode: int, modifiers: int) -> None:
|
||||
"""
|
||||
Register current hotkey for monitoring.
|
||||
|
||||
:param root: parent window.
|
||||
:param keycode: Key to monitor.
|
||||
:param modifiers: Any modifiers to take into account.
|
||||
"""
|
||||
self.root = root
|
||||
self.keycode = keycode
|
||||
self.modifiers = modifiers
|
||||
self.activated = False
|
||||
|
||||
if keycode:
|
||||
if not self.observer:
|
||||
self.root.after_idle(self._observe)
|
||||
self.root.after(MacHotkeyMgr.POLL, self._poll)
|
||||
|
||||
# Monkey-patch tk (tkMacOSXKeyEvent.c)
|
||||
if not callable(self.tkProcessKeyEvent_old):
|
||||
sel = b'tkProcessKeyEvent:'
|
||||
cls = NSApplication.sharedApplication().class__() # type: ignore
|
||||
self.tkProcessKeyEvent_old = NSApplication.sharedApplication().methodForSelector_(sel) # type: ignore
|
||||
newmethod = objc.selector( # type: ignore
|
||||
self.tkProcessKeyEvent,
|
||||
selector=self.tkProcessKeyEvent_old.selector,
|
||||
signature=self.tkProcessKeyEvent_old.signature
|
||||
)
|
||||
objc.classAddMethod(cls, sel, newmethod) # type: ignore
|
||||
|
||||
def tkProcessKeyEvent(self, cls, the_event): # noqa: N802
|
||||
"""
|
||||
Monkey-patch tk (tkMacOSXKeyEvent.c).
|
||||
|
||||
- workaround crash on OSX 10.9 & 10.10 on seeing a composing character
|
||||
- notice when modifier key state changes
|
||||
- keep a copy of NSEvent.charactersIgnoringModifiers, which is what we need for the hotkey
|
||||
|
||||
(Would like to use a decorator but need to ensure the application is created before this is installed)
|
||||
:param cls: ???
|
||||
:param the_event: tk event
|
||||
:return: ???
|
||||
"""
|
||||
if self.acquire_state:
|
||||
if the_event.type() == NSFlagsChanged:
|
||||
self.acquire_key = the_event.modifierFlags() & NSDeviceIndependentModifierFlagsMask
|
||||
self.acquire_state = MacHotkeyMgr.ACQUIRE_NEW
|
||||
# suppress the event by not chaining the old function
|
||||
return the_event
|
||||
|
||||
if the_event.type() in (NSKeyDown, NSKeyUp):
|
||||
c = the_event.charactersIgnoringModifiers()
|
||||
self.acquire_key = (c and ord(c[0]) or 0) | \
|
||||
(the_event.modifierFlags() & NSDeviceIndependentModifierFlagsMask)
|
||||
self.acquire_state = MacHotkeyMgr.ACQUIRE_NEW
|
||||
# suppress the event by not chaining the old function
|
||||
return the_event
|
||||
|
||||
# replace empty characters with charactersIgnoringModifiers to avoid crash
|
||||
elif the_event.type() in (NSKeyDown, NSKeyUp) and not the_event.characters():
|
||||
the_event = NSEvent.keyEventWithType_location_modifierFlags_timestamp_windowNumber_context_characters_charactersIgnoringModifiers_isARepeat_keyCode_( # noqa: E501
|
||||
# noqa: E501
|
||||
the_event.type(),
|
||||
the_event.locationInWindow(),
|
||||
the_event.modifierFlags(),
|
||||
the_event.timestamp(),
|
||||
the_event.windowNumber(),
|
||||
the_event.context(),
|
||||
the_event.charactersIgnoringModifiers(),
|
||||
the_event.charactersIgnoringModifiers(),
|
||||
the_event.isARepeat(),
|
||||
the_event.keyCode()
|
||||
)
|
||||
return self.tkProcessKeyEvent_old(cls, the_event)
|
||||
|
||||
def _observe(self):
|
||||
# Must be called after root.mainloop() so that the app's message loop has been created
|
||||
self.observer = NSEvent.addGlobalMonitorForEventsMatchingMask_handler_(NSKeyDownMask, self._handler)
|
||||
|
||||
def _poll(self):
|
||||
if config.shutting_down:
|
||||
return
|
||||
|
||||
# No way of signalling to Tkinter from within the callback handler block that doesn't
|
||||
# cause Python to crash, so poll.
|
||||
if self.activated:
|
||||
self.activated = False
|
||||
self.root.event_generate('<<Invoke>>', when="tail")
|
||||
|
||||
if self.keycode or self.modifiers:
|
||||
self.root.after(MacHotkeyMgr.POLL, self._poll)
|
||||
|
||||
def unregister(self) -> None:
|
||||
"""Remove hotkey registration."""
|
||||
self.keycode = 0
|
||||
self.modifiers = 0
|
||||
|
||||
@objc.callbackFor(NSEvent.addGlobalMonitorForEventsMatchingMask_handler_)
|
||||
def _handler(self, event) -> None:
|
||||
# use event.charactersIgnoringModifiers to handle composing characters like Alt-e
|
||||
if (
|
||||
(event.modifierFlags() & self.MODIFIERMASK) == self.modifiers
|
||||
and ord(event.charactersIgnoringModifiers()[0]) == self.keycode
|
||||
):
|
||||
if config.get_int('hotkey_always'):
|
||||
self.activated = True
|
||||
|
||||
else: # Only trigger if game client is front process
|
||||
front = NSWorkspace.sharedWorkspace().frontmostApplication()
|
||||
if front and front.bundleIdentifier() == 'uk.co.frontier.EliteDangerous':
|
||||
self.activated = True
|
||||
|
||||
def acquire_start(self) -> None:
|
||||
"""Start acquiring hotkey state via polling."""
|
||||
self.acquire_state = MacHotkeyMgr.ACQUIRE_ACTIVE
|
||||
self.root.after_idle(self._acquire_poll)
|
||||
|
||||
def acquire_stop(self) -> None:
|
||||
"""Stop acquiring hotkey state."""
|
||||
self.acquire_state = MacHotkeyMgr.ACQUIRE_INACTIVE
|
||||
|
||||
def _acquire_poll(self) -> None:
|
||||
"""Perform a poll of current hotkey state."""
|
||||
if config.shutting_down:
|
||||
return
|
||||
|
||||
# No way of signalling to Tkinter from within the monkey-patched event handler that doesn't
|
||||
# cause Python to crash, so poll.
|
||||
if self.acquire_state:
|
||||
if self.acquire_state == MacHotkeyMgr.ACQUIRE_NEW:
|
||||
# Abuse tkEvent's keycode field to hold our acquired key & modifier
|
||||
self.root.event_generate('<KeyPress>', keycode=self.acquire_key)
|
||||
self.acquire_state = MacHotkeyMgr.ACQUIRE_ACTIVE
|
||||
self.root.after(50, self._acquire_poll)
|
||||
|
||||
def fromevent(self, event) -> bool | tuple | None:
|
||||
"""
|
||||
Return configuration (keycode, modifiers) or None=clear or False=retain previous.
|
||||
|
||||
:param event: tk event ?
|
||||
:return: False to retain previous, None to not use, else (keycode, modifiers)
|
||||
"""
|
||||
(keycode, modifiers) = (event.keycode & 0xffff, event.keycode & 0xffff0000) # Set by _acquire_poll()
|
||||
if (
|
||||
keycode
|
||||
and not (modifiers & (NSShiftKeyMask | NSControlKeyMask | NSAlternateKeyMask | NSCommandKeyMask))
|
||||
):
|
||||
if keycode == 0x1b: # Esc = retain previous
|
||||
self.acquire_state = MacHotkeyMgr.ACQUIRE_INACTIVE
|
||||
return False
|
||||
|
||||
# BkSp, Del, Clear = clear hotkey
|
||||
if keycode in (0x7f, ord(NSDeleteFunctionKey), ord(NSClearLineFunctionKey)):
|
||||
self.acquire_state = MacHotkeyMgr.ACQUIRE_INACTIVE
|
||||
return None
|
||||
|
||||
# don't allow keys needed for typing in System Map
|
||||
if keycode in (0x13, 0x20, 0x2d) or 0x61 <= keycode <= 0x7a:
|
||||
NSBeep()
|
||||
self.acquire_state = MacHotkeyMgr.ACQUIRE_INACTIVE
|
||||
return None
|
||||
|
||||
return keycode, modifiers
|
||||
|
||||
def display(self, keycode, modifiers) -> str:
|
||||
"""
|
||||
Return displayable form of given hotkey + modifiers.
|
||||
|
||||
:param keycode:
|
||||
:param modifiers:
|
||||
:return: string form
|
||||
"""
|
||||
text = ''
|
||||
if modifiers & NSControlKeyMask:
|
||||
text += u'⌃'
|
||||
|
||||
if modifiers & NSAlternateKeyMask:
|
||||
text += u'⌥'
|
||||
|
||||
if modifiers & NSShiftKeyMask:
|
||||
text += u'⇧'
|
||||
|
||||
if modifiers & NSCommandKeyMask:
|
||||
text += u'⌘'
|
||||
|
||||
if (modifiers & NSNumericPadKeyMask) and keycode <= 0x7f:
|
||||
text += u'№'
|
||||
|
||||
if not keycode:
|
||||
pass
|
||||
|
||||
elif ord(NSF1FunctionKey) <= keycode <= ord(NSF35FunctionKey):
|
||||
text += f'F{keycode + 1 - ord(NSF1FunctionKey)}'
|
||||
|
||||
elif keycode in MacHotkeyMgr.DISPLAY: # specials
|
||||
text += MacHotkeyMgr.DISPLAY[keycode]
|
||||
|
||||
elif keycode < 0x20: # control keys
|
||||
text += chr(keycode + 0x40)
|
||||
|
||||
elif keycode < 0xf700: # key char
|
||||
text += chr(keycode).upper()
|
||||
|
||||
else:
|
||||
text += u'⁈'
|
||||
|
||||
return text
|
||||
|
||||
def play_good(self):
|
||||
"""Play the 'good' sound."""
|
||||
self.snd_good.play()
|
||||
|
||||
def play_bad(self):
|
||||
"""Play the 'bad' sound."""
|
||||
self.snd_bad.play()
|
@ -218,10 +218,6 @@ class JournalLock:
|
||||
if sys.platform == 'win32':
|
||||
self.attributes('-toolwindow', tk.TRUE)
|
||||
|
||||
elif sys.platform == 'darwin':
|
||||
# http://wiki.tcl.tk/13428
|
||||
parent.call('tk::unsupported::MacWindowStyle', 'style', self, 'utility')
|
||||
|
||||
self.resizable(tk.FALSE, tk.FALSE)
|
||||
|
||||
frame = ttk.Frame(self)
|
||||
|
50
l10n.py
50
l10n.py
@ -17,8 +17,8 @@ import re
|
||||
import sys
|
||||
import warnings
|
||||
from contextlib import suppress
|
||||
from os import pardir, listdir, sep, makedirs
|
||||
from os.path import basename, dirname, isdir, isfile, join, abspath, exists
|
||||
from os import listdir, sep, makedirs
|
||||
from os.path import basename, dirname, isdir, join, abspath, exists
|
||||
from typing import TYPE_CHECKING, Iterable, TextIO, cast
|
||||
from config import config
|
||||
from EDMCLogging import get_main_logger
|
||||
@ -39,12 +39,7 @@ logger = get_main_logger()
|
||||
LANGUAGE_ID = '!Language'
|
||||
LOCALISATION_DIR = 'L10n'
|
||||
|
||||
if sys.platform == 'darwin':
|
||||
from Foundation import ( # type: ignore # exists on Darwin
|
||||
NSLocale, NSNumberFormatter, NSNumberFormatterDecimalStyle
|
||||
)
|
||||
|
||||
elif sys.platform == 'win32':
|
||||
if sys.platform == 'win32':
|
||||
import ctypes
|
||||
from ctypes.wintypes import BOOL, DWORD, LPCVOID, LPCWSTR, LPWSTR
|
||||
if TYPE_CHECKING:
|
||||
@ -178,14 +173,8 @@ class _Translations:
|
||||
def available(self) -> set[str]:
|
||||
"""Return a list of available language codes."""
|
||||
path = self.respath()
|
||||
if getattr(sys, 'frozen', False) and sys.platform == 'darwin':
|
||||
available = {
|
||||
x[:-len('.lproj')] for x in listdir(path)
|
||||
if x.endswith('.lproj') and isfile(join(x, 'Localizable.strings'))
|
||||
}
|
||||
|
||||
else:
|
||||
available = {x[:-len('.strings')] for x in listdir(path) if x.endswith('.strings')}
|
||||
available = {x[:-len('.strings')] for x in listdir(path) if x.endswith('.strings')}
|
||||
|
||||
return available
|
||||
|
||||
@ -206,9 +195,6 @@ class _Translations:
|
||||
def respath(self) -> str:
|
||||
"""Path to localisation files."""
|
||||
if getattr(sys, 'frozen', False):
|
||||
if sys.platform == 'darwin':
|
||||
return abspath(join(dirname(sys.executable), pardir, 'Resources'))
|
||||
|
||||
return abspath(join(dirname(sys.executable), LOCALISATION_DIR))
|
||||
|
||||
if __file__:
|
||||
@ -234,10 +220,6 @@ class _Translations:
|
||||
except OSError:
|
||||
logger.exception(f'could not open {file_path}')
|
||||
|
||||
elif getattr(sys, 'frozen', False) and sys.platform == 'darwin':
|
||||
res_path = join(self.respath(), f'{lang}.lproj', 'Localizable.strings')
|
||||
return open(res_path, encoding='utf-16')
|
||||
|
||||
res_path = join(self.respath(), f'{lang}.strings')
|
||||
return open(res_path, encoding='utf-8')
|
||||
|
||||
@ -245,15 +227,6 @@ class _Translations:
|
||||
class _Locale:
|
||||
"""Locale holds a few utility methods to convert data to and from localized versions."""
|
||||
|
||||
def __init__(self) -> None:
|
||||
if sys.platform == 'darwin':
|
||||
self.int_formatter = NSNumberFormatter.alloc().init()
|
||||
self.int_formatter.setNumberStyle_(NSNumberFormatterDecimalStyle)
|
||||
self.float_formatter = NSNumberFormatter.alloc().init()
|
||||
self.float_formatter.setNumberStyle_(NSNumberFormatterDecimalStyle)
|
||||
self.float_formatter.setMinimumFractionDigits_(5)
|
||||
self.float_formatter.setMaximumFractionDigits_(5)
|
||||
|
||||
def stringFromNumber(self, number: float | int, decimals: int | None = None) -> str: # noqa: N802
|
||||
warnings.warn(DeprecationWarning('use _Locale.string_from_number instead.'))
|
||||
return self.string_from_number(number, decimals) # type: ignore
|
||||
@ -279,14 +252,6 @@ class _Locale:
|
||||
if decimals == 0 and not isinstance(number, numbers.Integral):
|
||||
number = int(round(number))
|
||||
|
||||
if sys.platform == 'darwin':
|
||||
if not decimals and isinstance(number, numbers.Integral):
|
||||
return self.int_formatter.stringFromNumber_(number)
|
||||
|
||||
self.float_formatter.setMinimumFractionDigits_(decimals)
|
||||
self.float_formatter.setMaximumFractionDigits_(decimals)
|
||||
return self.float_formatter.stringFromNumber_(number)
|
||||
|
||||
if not decimals and isinstance(number, numbers.Integral):
|
||||
return locale.format_string('%d', number, True)
|
||||
return locale.format_string('%.*f', (decimals, number), True)
|
||||
@ -299,9 +264,6 @@ class _Locale:
|
||||
:param string: The string to convert
|
||||
:return: None if the string cannot be parsed, otherwise an int or float dependant on input data.
|
||||
"""
|
||||
if sys.platform == 'darwin':
|
||||
return self.float_formatter.numberFromString_(string)
|
||||
|
||||
with suppress(ValueError):
|
||||
return locale.atoi(string)
|
||||
|
||||
@ -332,10 +294,8 @@ class _Locale:
|
||||
:return: The preferred language list
|
||||
"""
|
||||
languages: Iterable[str]
|
||||
if sys.platform == 'darwin':
|
||||
languages = NSLocale.preferredLanguages()
|
||||
|
||||
elif sys.platform != 'win32':
|
||||
if sys.platform != 'win32':
|
||||
# POSIX
|
||||
lang = locale.getlocale()[0]
|
||||
languages = [lang.replace('_', '-')] if lang else []
|
||||
|
25
monitor.py
25
monitor.py
@ -38,16 +38,7 @@ if TYPE_CHECKING:
|
||||
def _(x: str) -> str:
|
||||
return x
|
||||
|
||||
if sys.platform == 'darwin':
|
||||
from fcntl import fcntl
|
||||
|
||||
from AppKit import NSWorkspace
|
||||
from watchdog.events import FileSystemEventHandler
|
||||
from watchdog.observers import Observer
|
||||
from watchdog.observers.api import BaseObserver
|
||||
F_GLOBAL_NOCACHE = 55
|
||||
|
||||
elif sys.platform == 'win32':
|
||||
if sys.platform == 'win32':
|
||||
import ctypes
|
||||
from ctypes.wintypes import BOOL, HWND, LPARAM, LPWSTR
|
||||
|
||||
@ -384,8 +375,6 @@ class EDLogs(FileSystemEventHandler):
|
||||
logfile = self.logfile
|
||||
if logfile:
|
||||
loghandle: BinaryIO = open(logfile, 'rb', 0) # unbuffered
|
||||
if sys.platform == 'darwin':
|
||||
fcntl(loghandle, F_GLOBAL_NOCACHE, -1) # required to avoid corruption on macOS over SMB
|
||||
|
||||
self.catching_up = True
|
||||
for line in loghandle:
|
||||
@ -452,7 +441,7 @@ class EDLogs(FileSystemEventHandler):
|
||||
new_journal_file = None
|
||||
|
||||
if logfile:
|
||||
loghandle.seek(0, SEEK_END) # required to make macOS notice log change over SMB
|
||||
loghandle.seek(0, SEEK_END) # required 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 @@ class EDLogs(FileSystemEventHandler):
|
||||
|
||||
if logfile:
|
||||
loghandle = open(logfile, 'rb', 0) # unbuffered
|
||||
if sys.platform == 'darwin':
|
||||
fcntl(loghandle, F_GLOBAL_NOCACHE, -1) # required to avoid corruption on macOS over SMB
|
||||
|
||||
log_pos = 0
|
||||
|
||||
sleep(self._POLL)
|
||||
@ -2146,12 +2132,7 @@ class EDLogs(FileSystemEventHandler):
|
||||
|
||||
:return: bool - True if the game is running.
|
||||
"""
|
||||
if sys.platform == 'darwin':
|
||||
for app in NSWorkspace.sharedWorkspace().runningApplications():
|
||||
if app.bundleIdentifier() == 'uk.co.frontier.EliteDangerous':
|
||||
return True
|
||||
|
||||
elif sys.platform == 'win32':
|
||||
if sys.platform == 'win32':
|
||||
def WindowTitle(h): # noqa: N802
|
||||
if h:
|
||||
length = GetWindowTextLength(h) + 1
|
||||
|
133
myNotebook.py
133
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 @@ from PIL import ImageGrab
|
||||
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 @@ class EntryMenu(ttk.Entry):
|
||||
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('<Button-1>', self._press)
|
||||
else:
|
||||
tk.Button.__init__(self, master, **kw)
|
||||
|
||||
if sys.platform == 'darwin':
|
||||
def _press(self, event):
|
||||
self._command()
|
||||
tk.Button.__init__(self, master, **kw)
|
||||
|
||||
|
||||
class Checkbutton(sys.platform == 'darwin' and tk.Checkbutton or ttk.Checkbutton): # type: ignore
|
||||
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)
|
||||
|
@ -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
|
||||
|
@ -27,7 +27,6 @@ import os
|
||||
import pathlib
|
||||
import re
|
||||
import sqlite3
|
||||
import sys
|
||||
import tkinter as tk
|
||||
from platform import system
|
||||
from textwrap import dedent
|
||||
@ -282,7 +281,7 @@ class EDDNSender:
|
||||
msg['header'] = {
|
||||
# We have to lie and say it's *this* version, but denote that
|
||||
# it might not actually be this version.
|
||||
'softwareName': f'{applongname} [{system() if sys.platform != "darwin" else "Mac OS"}]'
|
||||
'softwareName': f'{applongname} [{system()}]'
|
||||
' (legacy replay)',
|
||||
'softwareVersion': str(appversion_nobuild()),
|
||||
'uploaderID': cmdr,
|
||||
@ -1074,7 +1073,7 @@ class EDDN:
|
||||
gb = this.game_build
|
||||
|
||||
return {
|
||||
'softwareName': f'{applongname} [{system() if sys.platform != "darwin" else "Mac OS"}]',
|
||||
'softwareName': f'{applongname} [{system()}]',
|
||||
'softwareVersion': str(appversion_nobuild()),
|
||||
'uploaderID': this.cmdr_name,
|
||||
'gameversion': gv,
|
||||
|
@ -113,10 +113,10 @@ class This:
|
||||
self.cmdr_text: nb.Label | None = None
|
||||
|
||||
self.user_label: nb.Label | None = None
|
||||
self.user: nb.Entry | None = None
|
||||
self.user: ttk.Entry | None = None
|
||||
|
||||
self.apikey_label: nb.Label | None = None
|
||||
self.apikey: nb.Entry | None = None
|
||||
self.apikey: ttk.Entry | None = None
|
||||
|
||||
|
||||
this = This()
|
||||
@ -345,14 +345,14 @@ def plugin_prefs(parent: ttk.Notebook, cmdr: str | None, is_beta: bool) -> tk.Fr
|
||||
# LANG: EDSM Commander name label in EDSM settings
|
||||
this.user_label = nb.Label(frame, text=_('Commander Name'))
|
||||
this.user_label.grid(row=cur_row, padx=PADX, pady=PADY, sticky=tk.W)
|
||||
this.user = nb.Entry(frame)
|
||||
this.user = ttk.Entry(frame)
|
||||
this.user.grid(row=cur_row, column=1, padx=PADX, pady=BOXY, sticky=tk.EW)
|
||||
|
||||
cur_row += 1
|
||||
# LANG: EDSM API key label
|
||||
this.apikey_label = nb.Label(frame, text=_('API Key'))
|
||||
this.apikey_label.grid(row=cur_row, padx=PADX, pady=PADY, sticky=tk.W)
|
||||
this.apikey = nb.Entry(frame, show="*", width=50)
|
||||
this.apikey = ttk.Entry(frame, show="*", width=50)
|
||||
this.apikey.grid(row=cur_row, column=1, padx=PADX, pady=BOXY, sticky=tk.EW)
|
||||
cur_row += 1
|
||||
|
||||
|
@ -125,7 +125,7 @@ class This:
|
||||
self.log: 'tk.IntVar'
|
||||
self.log_button: nb.Checkbutton
|
||||
self.label: HyperlinkLabel
|
||||
self.apikey: nb.Entry
|
||||
self.apikey: ttk.Entry
|
||||
self.apikey_label: tk.Label
|
||||
|
||||
self.events: dict[Credentials, Deque[Event]] = defaultdict(deque)
|
||||
@ -292,7 +292,7 @@ def plugin_prefs(parent: ttk.Notebook, cmdr: str, is_beta: bool) -> tk.Frame:
|
||||
# LANG: Inara API key label
|
||||
this.apikey_label = nb.Label(frame, text=_('API Key')) # Inara setting
|
||||
this.apikey_label.grid(row=cur_row, padx=PADX, pady=PADY, sticky=tk.W)
|
||||
this.apikey = nb.Entry(frame, show="*", width=50)
|
||||
this.apikey = ttk.Entry(frame, show="*", width=50)
|
||||
this.apikey.grid(row=cur_row, column=1, padx=PADX, pady=BOXY, sticky=tk.EW)
|
||||
cur_row += 1
|
||||
|
||||
|
203
prefs.py
203
prefs.py
@ -18,7 +18,7 @@ from typing import TYPE_CHECKING, Any, Callable, Optional, Type
|
||||
|
||||
import myNotebook as nb # noqa: N813
|
||||
import plug
|
||||
from config import applongname, appversion_nobuild, config
|
||||
from config import appversion_nobuild, config
|
||||
from EDMCLogging import edmclogger, get_main_logger
|
||||
from constants import appname
|
||||
from hotkey import hotkeymgr
|
||||
@ -49,9 +49,6 @@ def help_open_log_folder() -> None:
|
||||
if sys.platform.startswith('win'):
|
||||
# On Windows, use the "start" command to open the folder
|
||||
system(f'start "" "{logfile_loc}"')
|
||||
elif sys.platform.startswith('darwin'):
|
||||
# On macOS, use the "open" command to open the folder
|
||||
system(f'open "{logfile_loc}"')
|
||||
elif sys.platform.startswith('linux'):
|
||||
# On Linux, use the "xdg-open" command to open the folder
|
||||
system(f'xdg-open "{logfile_loc}"')
|
||||
@ -172,32 +169,7 @@ class AutoInc(contextlib.AbstractContextManager):
|
||||
return None
|
||||
|
||||
|
||||
if sys.platform == 'darwin':
|
||||
import objc # type: ignore
|
||||
from Foundation import NSFileManager # type: ignore
|
||||
try:
|
||||
from ApplicationServices import ( # type: ignore
|
||||
AXIsProcessTrusted, AXIsProcessTrustedWithOptions, kAXTrustedCheckOptionPrompt
|
||||
)
|
||||
|
||||
except ImportError:
|
||||
HIServices = objc.loadBundle(
|
||||
'HIServices',
|
||||
globals(),
|
||||
'/System/Library/Frameworks/ApplicationServices.framework/Frameworks/HIServices.framework'
|
||||
)
|
||||
|
||||
objc.loadBundleFunctions(
|
||||
HIServices,
|
||||
globals(),
|
||||
[('AXIsProcessTrusted', 'B'), ('AXIsProcessTrustedWithOptions', 'B@')]
|
||||
)
|
||||
|
||||
objc.loadBundleVariables(HIServices, globals(), [('kAXTrustedCheckOptionPrompt', '@^{__CFString=}')])
|
||||
|
||||
was_accessible_at_launch = AXIsProcessTrusted() # type: ignore
|
||||
|
||||
elif sys.platform == 'win32':
|
||||
if sys.platform == 'win32':
|
||||
import ctypes
|
||||
import winreg
|
||||
from ctypes.wintypes import HINSTANCE, HWND, LPCWSTR, LPWSTR, MAX_PATH, POINT, RECT, SIZE, UINT
|
||||
@ -251,30 +223,21 @@ class PreferencesDialog(tk.Toplevel):
|
||||
|
||||
self.parent = parent
|
||||
self.callback = callback
|
||||
if sys.platform == 'darwin':
|
||||
# LANG: File > Preferences menu entry for macOS
|
||||
self.title(_('Preferences'))
|
||||
|
||||
else:
|
||||
# LANG: File > Settings (macOS)
|
||||
self.title(_('Settings'))
|
||||
# LANG: File > Settings (macOS)
|
||||
self.title(_('Settings'))
|
||||
|
||||
if parent.winfo_viewable():
|
||||
self.transient(parent)
|
||||
|
||||
# position over parent
|
||||
if sys.platform != 'darwin' or parent.winfo_rooty() > 0: # http://core.tcl.tk/tk/tktview/c84f660833546b1b84e7
|
||||
# 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 @@ class PreferencesDialog(tk.Toplevel):
|
||||
self.__setup_appearance_tab(notebook)
|
||||
self.__setup_plugin_tab(notebook)
|
||||
|
||||
if sys.platform == 'darwin':
|
||||
self.protocol("WM_DELETE_WINDOW", self.apply) # close button applies changes
|
||||
|
||||
else:
|
||||
buttonframe = ttk.Frame(frame)
|
||||
buttonframe.grid(padx=self.PADX, pady=self.PADX, sticky=tk.NSEW)
|
||||
buttonframe.columnconfigure(0, weight=1)
|
||||
ttk.Label(buttonframe).grid(row=0, column=0) # spacer
|
||||
# LANG: 'OK' button on Settings/Preferences window
|
||||
button = ttk.Button(buttonframe, text=_('OK'), command=self.apply)
|
||||
button.grid(row=0, column=1, sticky=tk.E)
|
||||
button.bind("<Return>", lambda event: self.apply())
|
||||
self.protocol("WM_DELETE_WINDOW", self._destroy)
|
||||
buttonframe = ttk.Frame(frame)
|
||||
buttonframe.grid(padx=self.PADX, pady=self.PADX, sticky=tk.NSEW)
|
||||
buttonframe.columnconfigure(0, weight=1)
|
||||
ttk.Label(buttonframe).grid(row=0, column=0) # spacer
|
||||
# LANG: 'OK' button on Settings/Preferences window
|
||||
button = ttk.Button(buttonframe, text=_('OK'), command=self.apply)
|
||||
button.grid(row=0, column=1, sticky=tk.E)
|
||||
button.bind("<Return>", lambda event: self.apply())
|
||||
self.protocol("WM_DELETE_WINDOW", self._destroy)
|
||||
|
||||
# FIXME: Why are these being called when *creating* the Settings window?
|
||||
# Selectively disable buttons depending on output settings
|
||||
@ -326,7 +285,7 @@ class PreferencesDialog(tk.Toplevel):
|
||||
|
||||
# wait for window to appear on screen before calling grab_set
|
||||
self.parent.update_idletasks()
|
||||
self.parent.wm_attributes('-topmost', 0) # needed for dialog to appear ontop of parent on OSX & Linux
|
||||
self.parent.wm_attributes('-topmost', 0) # needed for dialog to appear ontop of parent on Linux
|
||||
self.wait_visibility()
|
||||
self.grab_set()
|
||||
|
||||
@ -402,16 +361,12 @@ class PreferencesDialog(tk.Toplevel):
|
||||
# Type ignored due to incorrect type annotation. a 2 tuple does padding for each side
|
||||
self.outdir_label.grid(padx=self.PADX, pady=self.PADY, sticky=tk.W, row=row.get()) # type: ignore
|
||||
|
||||
self.outdir_entry = nb.Entry(output_frame, takefocus=False)
|
||||
self.outdir_entry = ttk.Entry(output_frame, takefocus=False)
|
||||
self.outdir_entry.grid(columnspan=2, padx=self.PADX, pady=self.BOXY, sticky=tk.EW, row=row.get())
|
||||
|
||||
if sys.platform == 'darwin':
|
||||
text = (_('Change...')) # LANG: macOS Preferences - files location selection button
|
||||
text = _('Browse...') # LANG: NOT-macOS Settings - files location selection button
|
||||
|
||||
else:
|
||||
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 @@ class PreferencesDialog(tk.Toplevel):
|
||||
logdir = default
|
||||
|
||||
self.logdir.set(logdir)
|
||||
self.logdir_entry = nb.Entry(config_frame, takefocus=False)
|
||||
self.logdir_entry = ttk.Entry(config_frame, takefocus=False)
|
||||
|
||||
# Location of the Journal files
|
||||
nb.Label(
|
||||
@ -455,14 +410,10 @@ class PreferencesDialog(tk.Toplevel):
|
||||
|
||||
self.logdir_entry.grid(columnspan=4, padx=self.PADX, pady=self.BOXY, sticky=tk.EW, row=row.get())
|
||||
|
||||
if sys.platform == 'darwin':
|
||||
text = (_('Change...')) # LANG: macOS Preferences - files location selection button
|
||||
|
||||
else:
|
||||
text = (_('Browse...')) # LANG: NOT-macOS Setting - files location selection button
|
||||
text = _('Browse...') # LANG: NOT-macOS Setting - files location selection button
|
||||
|
||||
with row as cur_row:
|
||||
self.logbutton = nb.Button(
|
||||
self.logbutton = ttk.Button(
|
||||
config_frame,
|
||||
text=text,
|
||||
# LANG: Settings > Configuration - Label for Journal files location
|
||||
@ -472,7 +423,7 @@ class PreferencesDialog(tk.Toplevel):
|
||||
|
||||
if config.default_journal_dir_path:
|
||||
# Appearance theme and language setting
|
||||
nb.Button(
|
||||
ttk.Button(
|
||||
config_frame,
|
||||
# LANG: Settings > Configuration - Label on 'reset journal files location to default' button
|
||||
text=_('Default'),
|
||||
@ -499,7 +450,7 @@ class PreferencesDialog(tk.Toplevel):
|
||||
variable=self.capi_fleetcarrier
|
||||
).grid(columnspan=4, padx=self.BUTTONX, pady=self.PADY, sticky=tk.W, row=row.get())
|
||||
|
||||
if sys.platform in ('darwin', 'win32'):
|
||||
if sys.platform == 'win32':
|
||||
ttk.Separator(config_frame, orient=tk.HORIZONTAL).grid(
|
||||
columnspan=4, padx=self.PADX, pady=self.SEPY, sticky=tk.EW, row=row.get()
|
||||
)
|
||||
@ -511,49 +462,21 @@ class PreferencesDialog(tk.Toplevel):
|
||||
with row as cur_row:
|
||||
nb.Label(
|
||||
config_frame,
|
||||
text=_('Keyboard shortcut') if # LANG: Hotkey/Shortcut settings prompt on OSX
|
||||
sys.platform == 'darwin' else
|
||||
_('Hotkey') # LANG: Hotkey/Shortcut settings prompt on Windows
|
||||
text=_('Hotkey') # LANG: Hotkey/Shortcut settings prompt on Windows
|
||||
).grid(padx=self.PADX, pady=self.PADY, sticky=tk.W, row=cur_row)
|
||||
|
||||
if sys.platform == 'darwin' and not was_accessible_at_launch:
|
||||
if AXIsProcessTrusted():
|
||||
# Shortcut settings prompt on OSX
|
||||
nb.Label(
|
||||
config_frame,
|
||||
# LANG: macOS Preferences > Configuration - restart the app message
|
||||
text=_('Re-start {APP} to use shortcuts').format(APP=applongname),
|
||||
foreground='firebrick'
|
||||
).grid(padx=self.PADX, pady=self.PADY, sticky=tk.W, row=cur_row)
|
||||
self.hotkey_text = 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')
|
||||
)
|
||||
|
||||
else:
|
||||
# Shortcut settings prompt on OSX
|
||||
nb.Label(
|
||||
config_frame,
|
||||
# LANG: macOS - Configuration - need to grant the app permission for keyboard shortcuts
|
||||
text=_('{APP} needs permission to use shortcuts').format(APP=applongname),
|
||||
foreground='firebrick'
|
||||
).grid(columnspan=4, padx=self.PADX, pady=self.PADY, sticky=tk.W, row=cur_row)
|
||||
|
||||
# LANG: Shortcut settings button on OSX
|
||||
nb.Button(config_frame, text=_('Open System Preferences'), command=self.enableshortcuts).grid(
|
||||
padx=self.PADX, pady=self.BOXY, sticky=tk.E, row=cur_row
|
||||
)
|
||||
|
||||
else:
|
||||
self.hotkey_text = nb.Entry(config_frame, width=(
|
||||
20 if sys.platform == 'darwin' else 30), justify=tk.CENTER)
|
||||
self.hotkey_text.insert(
|
||||
0,
|
||||
# No hotkey/shortcut currently defined
|
||||
# TODO: display Only shows up on darwin or windows
|
||||
# LANG: No hotkey/shortcut set
|
||||
hotkeymgr.display(self.hotkey_code, self.hotkey_mods) if self.hotkey_code else _('None')
|
||||
)
|
||||
|
||||
self.hotkey_text.bind('<FocusIn>', self.hotkeystart)
|
||||
self.hotkey_text.bind('<FocusOut>', self.hotkeyend)
|
||||
self.hotkey_text.grid(column=1, columnspan=2, pady=self.BOXY, sticky=tk.W, row=cur_row)
|
||||
self.hotkey_text.bind('<FocusIn>', self.hotkeystart)
|
||||
self.hotkey_text.bind('<FocusOut>', self.hotkeyend)
|
||||
self.hotkey_text.grid(column=1, columnspan=2, pady=self.BOXY, sticky=tk.W, row=cur_row)
|
||||
|
||||
# Hotkey/Shortcut setting
|
||||
self.hotkey_only_btn = nb.Checkbutton(
|
||||
@ -700,7 +623,7 @@ class PreferencesDialog(tk.Toplevel):
|
||||
self.loglevel_dropdown.configure(width=15)
|
||||
self.loglevel_dropdown.grid(column=1, pady=self.BOXY, sticky=tk.W, row=cur_row)
|
||||
|
||||
nb.Button(
|
||||
ttk.Button(
|
||||
config_frame,
|
||||
# LANG: Label on button used to open a filesystem folder
|
||||
text=_('Open Log Folder'), # Button that opens a folder in Explorer/Finder
|
||||
@ -803,7 +726,7 @@ class PreferencesDialog(tk.Toplevel):
|
||||
self.theme_label_0.grid(padx=self.PADX, pady=self.PADY, sticky=tk.W, row=cur_row)
|
||||
|
||||
# Main window
|
||||
self.theme_button_0 = nb.ColoredButton(
|
||||
self.theme_button_0 = tk.Button(
|
||||
appearance_frame,
|
||||
# LANG: Appearance - Example 'Normal' text
|
||||
text=_('Station'),
|
||||
@ -816,7 +739,7 @@ class PreferencesDialog(tk.Toplevel):
|
||||
with row as cur_row:
|
||||
self.theme_label_1 = nb.Label(appearance_frame, text=self.theme_prompts[1])
|
||||
self.theme_label_1.grid(padx=self.PADX, pady=self.PADY, sticky=tk.W, row=cur_row)
|
||||
self.theme_button_1 = nb.ColoredButton(
|
||||
self.theme_button_1 = tk.Button(
|
||||
appearance_frame,
|
||||
text=' Hutton Orbital ', # Do not translate
|
||||
background='grey4',
|
||||
@ -947,7 +870,7 @@ class PreferencesDialog(tk.Toplevel):
|
||||
padx=self.PADX, pady=self.PADY, sticky=tk.W, row=row.get()
|
||||
)
|
||||
|
||||
plugdirentry = nb.Entry(plugins_frame, justify=tk.LEFT)
|
||||
plugdirentry = ttk.Entry(plugins_frame, justify=tk.LEFT)
|
||||
self.displaypath(plugdir, plugdirentry)
|
||||
plugdirentry.grid(columnspan=2, padx=self.PADX, pady=self.BOXY, sticky=tk.EW, row=row.get())
|
||||
|
||||
@ -959,7 +882,7 @@ class PreferencesDialog(tk.Toplevel):
|
||||
text=_("Tip: You can disable a plugin by{CR}adding '{EXT}' to its folder name").format(EXT='.disabled')
|
||||
).grid(columnspan=2, padx=self.PADX, pady=self.PADY, sticky=tk.EW, row=cur_row)
|
||||
|
||||
nb.Button(
|
||||
ttk.Button(
|
||||
plugins_frame,
|
||||
# LANG: Label on button used to open a filesystem folder
|
||||
text=_('Open'), # Button that opens a folder in Explorer/Finder
|
||||
@ -1070,14 +993,6 @@ class PreferencesDialog(tk.Toplevel):
|
||||
def tabchanged(self, event: tk.Event) -> None:
|
||||
"""Handle preferences active tab changing."""
|
||||
self.outvarchanged()
|
||||
if sys.platform == 'darwin':
|
||||
# Hack to recompute size so that buttons show up under Mojave
|
||||
notebook = event.widget
|
||||
frame = self.nametowidget(notebook.winfo_parent())
|
||||
temp = nb.Label(frame)
|
||||
temp.grid()
|
||||
temp.update_idletasks()
|
||||
temp.destroy()
|
||||
|
||||
def outvarchanged(self, event: Optional[tk.Event] = None) -> None:
|
||||
"""Handle Output tab variable changes."""
|
||||
@ -1139,16 +1054,6 @@ class PreferencesDialog(tk.Toplevel):
|
||||
entryfield.insert(0, '\\'.join(display))
|
||||
|
||||
# None if path doesn't exist
|
||||
elif sys.platform == 'darwin' and NSFileManager.defaultManager().componentsToDisplayForPath_(pathvar.get()):
|
||||
if pathvar.get().startswith(config.home):
|
||||
display = ['~'] + NSFileManager.defaultManager().componentsToDisplayForPath_(pathvar.get())[
|
||||
len(NSFileManager.defaultManager().componentsToDisplayForPath_(config.home)):
|
||||
]
|
||||
|
||||
else:
|
||||
display = NSFileManager.defaultManager().componentsToDisplayForPath_(pathvar.get())
|
||||
|
||||
entryfield.insert(0, '/'.join(display))
|
||||
else:
|
||||
if pathvar.get().startswith(config.home):
|
||||
entryfield.insert(0, '~' + pathvar.get()[len(config.home):])
|
||||
@ -1288,7 +1193,7 @@ class PreferencesDialog(tk.Toplevel):
|
||||
|
||||
config.set('capi_fleetcarrier', self.capi_fleetcarrier.get())
|
||||
|
||||
if sys.platform in ('darwin', 'win32'):
|
||||
if sys.platform == 'win32':
|
||||
config.set('hotkey_code', self.hotkey_code)
|
||||
config.set('hotkey_mods', self.hotkey_mods)
|
||||
config.set('hotkey_always', int(not self.hotkey_only.get()))
|
||||
@ -1333,25 +1238,3 @@ class PreferencesDialog(tk.Toplevel):
|
||||
|
||||
self.parent.wm_attributes('-topmost', 1 if config.get_int('always_ontop') else 0)
|
||||
self.destroy()
|
||||
|
||||
if sys.platform == 'darwin':
|
||||
def enableshortcuts(self) -> None:
|
||||
"""Set up macOS preferences shortcut."""
|
||||
self.apply()
|
||||
# popup System Preferences dialog
|
||||
try:
|
||||
# http://stackoverflow.com/questions/6652598/cocoa-button-opens-a-system-preference-page/6658201
|
||||
from ScriptingBridge import SBApplication # type: ignore
|
||||
sysprefs = 'com.apple.systempreferences'
|
||||
prefs = SBApplication.applicationWithBundleIdentifier_(sysprefs)
|
||||
pane = [x for x in prefs.panes() if x.id() == 'com.apple.preference.security'][0]
|
||||
prefs.setCurrentPane_(pane)
|
||||
anchor = [x for x in pane.anchors() if x.name() == 'Privacy_Accessibility'][0]
|
||||
anchor.reveal()
|
||||
prefs.activate()
|
||||
|
||||
except Exception:
|
||||
AXIsProcessTrustedWithOptions({kAXTrustedCheckOptionPrompt: True})
|
||||
|
||||
if not config.shutting_down:
|
||||
self.parent.event_generate('<<Quit>>', when="tail")
|
||||
|
85
protocol.py
85
protocol.py
@ -26,6 +26,7 @@ is_wine = False
|
||||
|
||||
if sys.platform == 'win32':
|
||||
from ctypes import windll # type: ignore
|
||||
|
||||
try:
|
||||
if windll.ntdll.wine_get_version:
|
||||
is_wine = True
|
||||
@ -58,72 +59,13 @@ class GenericProtocolHandler:
|
||||
self.master.event_generate('<<CompanionAuthEvent>>', when="tail")
|
||||
|
||||
|
||||
if sys.platform == 'darwin' and getattr(sys, 'frozen', False): # noqa: C901 # its guarding ALL macos stuff.
|
||||
import struct
|
||||
|
||||
import objc # type: ignore
|
||||
from AppKit import NSAppleEventManager, NSObject # type: ignore
|
||||
|
||||
kInternetEventClass = kAEGetURL = struct.unpack('>l', b'GURL')[0] # noqa: N816 # API names
|
||||
keyDirectObject = struct.unpack('>l', b'----')[0] # noqa: N816 # API names
|
||||
|
||||
class DarwinProtocolHandler(GenericProtocolHandler):
|
||||
"""
|
||||
MacOS protocol handler implementation.
|
||||
|
||||
Uses macOS event stuff.
|
||||
"""
|
||||
|
||||
POLL = 100 # ms
|
||||
|
||||
def start(self, master: 'tkinter.Tk') -> None:
|
||||
"""Start Protocol Handler."""
|
||||
GenericProtocolHandler.start(self, master)
|
||||
self.lasturl: str | None = None
|
||||
self.eventhandler = EventHandler.alloc().init()
|
||||
|
||||
def poll(self) -> None:
|
||||
"""Poll event until URL is updated."""
|
||||
# No way of signalling to Tkinter from within the callback handler block that doesn't cause Python to crash,
|
||||
# so poll. TODO: Resolved?
|
||||
if self.lasturl and self.lasturl.startswith(self.redirect):
|
||||
self.event(self.lasturl)
|
||||
self.lasturl = None
|
||||
|
||||
class EventHandler(NSObject):
|
||||
"""Handle NSAppleEventManager IPC stuff."""
|
||||
|
||||
def init(self) -> None:
|
||||
"""
|
||||
Init method for handler.
|
||||
|
||||
(I'd assume this is related to the subclassing of NSObject for why its not __init__)
|
||||
"""
|
||||
self = objc.super(EventHandler, self).init()
|
||||
NSAppleEventManager.sharedAppleEventManager().setEventHandler_andSelector_forEventClass_andEventID_(
|
||||
self,
|
||||
'handleEvent:withReplyEvent:',
|
||||
kInternetEventClass,
|
||||
kAEGetURL
|
||||
)
|
||||
return self
|
||||
|
||||
def handleEvent_withReplyEvent_(self, event, replyEvent) -> None: # noqa: N802 N803 # Required to override
|
||||
"""Actual event handling from NSAppleEventManager."""
|
||||
protocolhandler.lasturl = parse.unquote(
|
||||
event.paramDescriptorForKeyword_(keyDirectObject).stringValue()
|
||||
).strip()
|
||||
|
||||
protocolhandler.master.after(DarwinProtocolHandler.POLL, protocolhandler.poll)
|
||||
|
||||
|
||||
elif (config.auth_force_edmc_protocol
|
||||
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 @@ elif (config.auth_force_edmc_protocol
|
||||
# which we can read out as shown below, and then compare.
|
||||
|
||||
target_is_valid = lparam_low == 0 or (
|
||||
GlobalGetAtomNameW(lparam_low, service, 256) and service.value == appname
|
||||
GlobalGetAtomNameW(lparam_low, service, 256) and service.value == appname
|
||||
)
|
||||
|
||||
topic_is_valid = lparam_high == 0 or (
|
||||
GlobalGetAtomNameW(lparam_high, topic, 256) and topic.value.lower() == 'system'
|
||||
GlobalGetAtomNameW(lparam_high, topic, 256) and topic.value.lower() == 'system'
|
||||
)
|
||||
|
||||
if target_is_valid and topic_is_valid:
|
||||
@ -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
|
||||
|
||||
|
@ -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'
|
||||
|
@ -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'
|
||||
|
13
stats.py
13
stats.py
@ -374,7 +374,7 @@ class StatsResults(tk.Toplevel):
|
||||
self.transient(parent)
|
||||
|
||||
# position over parent
|
||||
if sys.platform != 'darwin' or parent.winfo_rooty() > 0: # http://core.tcl.tk/tk/tktview/c84f660833546b1b84e7
|
||||
if parent.winfo_rooty() > 0: # http://core.tcl.tk/tk/tktview/c84f660833546b1b84e7
|
||||
self.geometry(f"+{parent.winfo_rootx()}+{parent.winfo_rooty()}")
|
||||
|
||||
# remove decoration
|
||||
@ -382,10 +382,6 @@ class StatsResults(tk.Toplevel):
|
||||
if sys.platform == 'win32':
|
||||
self.attributes('-toolwindow', tk.TRUE)
|
||||
|
||||
elif sys.platform == 'darwin':
|
||||
# http://wiki.tcl.tk/13428
|
||||
parent.call('tk::unsupported::MacWindowStyle', 'style', self, 'utility')
|
||||
|
||||
frame = ttk.Frame(self)
|
||||
frame.grid(sticky=tk.NSEW)
|
||||
|
||||
@ -423,13 +419,6 @@ class StatsResults(tk.Toplevel):
|
||||
ttk.Frame(page).grid(pady=5) # bottom spacer
|
||||
notebook.add(page, text=_('Ships')) # LANG: Status dialog title
|
||||
|
||||
if sys.platform != 'darwin':
|
||||
buttonframe = ttk.Frame(frame)
|
||||
buttonframe.grid(padx=10, pady=(0, 10), sticky=tk.NSEW) # type: ignore # the tuple is supported
|
||||
buttonframe.columnconfigure(0, weight=1)
|
||||
ttk.Label(buttonframe).grid(row=0, column=0) # spacer
|
||||
ttk.Button(buttonframe, text='OK', command=self.destroy).grid(row=0, column=1, sticky=tk.E)
|
||||
|
||||
# wait for window to appear on screen before calling grab_set
|
||||
self.wait_visibility()
|
||||
self.grab_set()
|
||||
|
3
td.py
3
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')
|
||||
|
@ -5,21 +5,15 @@ import numbers
|
||||
import sys
|
||||
import warnings
|
||||
from configparser import NoOptionError
|
||||
from os import getenv, makedirs, mkdir, pardir
|
||||
from os.path import dirname, expanduser, isdir, join, normpath
|
||||
from os import getenv, makedirs, mkdir
|
||||
from os.path import dirname, expanduser, isdir, join
|
||||
from typing import TYPE_CHECKING
|
||||
from config import applongname, appname, update_interval
|
||||
from EDMCLogging import get_main_logger
|
||||
|
||||
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
|
||||
|
23
theme.py
23
theme.py
@ -150,7 +150,7 @@ class _Theme:
|
||||
# the widget has explicit fg or bg attributes.
|
||||
assert isinstance(widget, (tk.BitmapImage, tk.Widget)), widget
|
||||
if not self.defaults:
|
||||
# Can't initialise this til window is created # Windows, MacOS
|
||||
# Can't initialise this til window is created # Windows
|
||||
self.defaults = {
|
||||
'fg': tk.Label()['foreground'], # SystemButtonText, systemButtonText
|
||||
'bg': tk.Label()['background'], # SystemButtonFace, White
|
||||
@ -268,8 +268,7 @@ class _Theme:
|
||||
# (Mostly) system colors
|
||||
style = ttk.Style()
|
||||
self.current = {
|
||||
'background': (sys.platform == 'darwin' and 'systemMovableModalBackground' or
|
||||
style.lookup('TLabel', 'background')),
|
||||
'background': (style.lookup('TLabel', 'background')),
|
||||
'foreground': style.lookup('TLabel', 'foreground'),
|
||||
'activebackground': (sys.platform == 'win32' and 'SystemHighlight' or
|
||||
style.lookup('TLabel', 'background', ['active'])),
|
||||
@ -366,8 +365,6 @@ class _Theme:
|
||||
if 'bg' not in attribs:
|
||||
widget['background'] = self.current['background']
|
||||
widget['activebackground'] = self.current['activebackground']
|
||||
if sys.platform == 'darwin' and isinstance(widget, tk.Button):
|
||||
widget['highlightbackground'] = self.current['background']
|
||||
|
||||
if 'font' not in attribs:
|
||||
widget['font'] = self.current['font']
|
||||
@ -426,21 +423,7 @@ class _Theme:
|
||||
return # Don't need to mess with the window manager
|
||||
self.active = theme
|
||||
|
||||
if sys.platform == 'darwin':
|
||||
from AppKit import NSAppearance, NSApplication, NSMiniaturizableWindowMask, NSResizableWindowMask
|
||||
root.update_idletasks() # need main window to be created
|
||||
if theme == self.THEME_DEFAULT:
|
||||
appearance = NSAppearance.appearanceNamed_('NSAppearanceNameAqua')
|
||||
|
||||
else: # Dark (Transparent only on win32)
|
||||
appearance = NSAppearance.appearanceNamed_('NSAppearanceNameDarkAqua')
|
||||
|
||||
for window in NSApplication.sharedApplication().windows():
|
||||
window.setStyleMask_(window.styleMask() & ~(
|
||||
NSMiniaturizableWindowMask | NSResizableWindowMask)) # disable zoom
|
||||
window.setAppearance_(appearance)
|
||||
|
||||
elif sys.platform == 'win32':
|
||||
if sys.platform == 'win32':
|
||||
GWL_STYLE = -16 # noqa: N806 # ctypes
|
||||
WS_MAXIMIZEBOX = 0x00010000 # noqa: N806 # ctypes
|
||||
# tk8.5.9/win/tkWinWm.c:342
|
||||
|
@ -22,7 +22,6 @@ from __future__ import annotations
|
||||
|
||||
import sys
|
||||
import tkinter as tk
|
||||
import warnings
|
||||
import webbrowser
|
||||
from tkinter import font as tk_font
|
||||
from tkinter import ttk
|
||||
@ -32,8 +31,7 @@ if TYPE_CHECKING:
|
||||
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 @@ class HyperlinkLabel(sys.platform == 'darwin' and tk.Label or ttk.Label): # typ
|
||||
self.foreground = kw.get('foreground', 'blue')
|
||||
self.disabledforeground = kw.pop('disabledforeground', ttk.Style().lookup(
|
||||
'TLabel', 'foreground', ('disabled',))) # ttk.Label doesn't support disabledforeground option
|
||||
|
||||
if sys.platform == 'darwin':
|
||||
# Use tk.Label 'cos can't set ttk.Label background - http://www.tkdocs.com/tutorial/styles.html#whydifficult
|
||||
kw['background'] = kw.pop('background', 'systemDialogBackgroundActive')
|
||||
kw['anchor'] = kw.pop('anchor', tk.W) # like ttk.Label
|
||||
tk.Label.__init__(self, master, **kw)
|
||||
|
||||
else:
|
||||
ttk.Label.__init__(self, master, **kw)
|
||||
ttk.Label.__init__(self, master, **kw)
|
||||
|
||||
self.bind('<Button-1>', self._click)
|
||||
|
||||
self.menu = tk.Menu(tearoff=tk.FALSE)
|
||||
# LANG: Label for 'Copy' as in 'Copy and Paste'
|
||||
self.menu.add_command(label=_('Copy'), command=self.copy) # As in Copy and Paste
|
||||
self.bind(sys.platform == 'darwin' and '<Button-2>' or '<Button-3>', self._contextmenu)
|
||||
self.bind('<Button-3>', self._contextmenu)
|
||||
|
||||
self.bind('<Enter>', self._enter)
|
||||
self.bind('<Leave>', self._leave)
|
||||
@ -107,10 +97,9 @@ class HyperlinkLabel(sys.platform == 'darwin' and tk.Label or ttk.Label): # typ
|
||||
if state == tk.DISABLED:
|
||||
kw['cursor'] = 'arrow' # System default
|
||||
elif self.url and (kw['text'] if 'text' in kw else self['text']):
|
||||
kw['cursor'] = 'pointinghand' if sys.platform == 'darwin' else 'hand2'
|
||||
kw['cursor'] = 'hand2'
|
||||
else:
|
||||
kw['cursor'] = 'notallowed' if sys.platform == 'darwin' else (
|
||||
'no' if sys.platform == 'win32' else 'circle')
|
||||
kw['cursor'] = ('no' if sys.platform == 'win32' else 'circle')
|
||||
|
||||
return super().configure(cnf, **kw)
|
||||
|
||||
@ -140,50 +129,9 @@ class HyperlinkLabel(sys.platform == 'darwin' and tk.Label or ttk.Label): # typ
|
||||
|
||||
def _contextmenu(self, event: tk.Event) -> None:
|
||||
if self['text'] and (self.popup_copy(self['text']) if callable(self.popup_copy) else self.popup_copy):
|
||||
self.menu.post(sys.platform == 'darwin' and event.x_root + 1 or event.x_root, event.y_root)
|
||||
self.menu.post(event.x_root, event.y_root)
|
||||
|
||||
def copy(self) -> None:
|
||||
"""Copy the current text to the clipboard."""
|
||||
self.clipboard_clear()
|
||||
self.clipboard_append(self['text'])
|
||||
|
||||
|
||||
def openurl(url: str) -> None:
|
||||
r"""
|
||||
Open the given URL in appropriate browser.
|
||||
|
||||
2022-12-06:
|
||||
Firefox itself will gladly attempt to use very long URLs in its URL
|
||||
input. Up to 16384 was attempted, but the Apache instance this was
|
||||
tested against only allowed up to 8207 total URL length to pass, that
|
||||
being 8190 octets of REQUEST_URI (path + GET params).
|
||||
|
||||
Testing from Windows 10 Home 21H2 cmd.exe with:
|
||||
|
||||
"<path to>\firefox.exe" -osint -url "<test url>"
|
||||
|
||||
only allowed 8115 octest of REQUEST_URI to pass through.
|
||||
|
||||
Microsoft Edge yielded 8092 octets. Google Chrome yielded 8093 octets.
|
||||
|
||||
However, this is actually the limit of how long a CMD.EXE command-line
|
||||
can be. The URL was being cut off *there*.
|
||||
|
||||
The 8207 octet URL makes it through `webbrowser.open(<url>)` to:
|
||||
|
||||
Firefox 107.0.1
|
||||
Microsoft Edge 108.0.1462.42
|
||||
Google Chrome 108.0.5359.95
|
||||
|
||||
This was also tested as working *with* the old winreg/subprocess code,
|
||||
so it wasn't even suffering from the same limit as CMD.EXE.
|
||||
|
||||
Conclusion: No reason to not just use `webbrowser.open()`, as prior
|
||||
to e280d6c2833c25867b8139490e68ddf056477917 there was a bug, introduced
|
||||
in 5989acd0d3263e54429ff99769ff73a20476d863, which meant the code always
|
||||
ended up using `webbrowser.open()` *anyway*.
|
||||
:param url: URL to open.
|
||||
"""
|
||||
warnings.warn("This function is deprecated. "
|
||||
"Please use `webbrowser.open() instead.", DeprecationWarning, stacklevel=2)
|
||||
webbrowser.open(url)
|
||||
|
33
update.py
33
update.py
@ -7,10 +7,8 @@ See LICENSE file.
|
||||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
import os
|
||||
import sys
|
||||
import threading
|
||||
from os.path import dirname, join
|
||||
from traceback import print_exc
|
||||
from typing import TYPE_CHECKING
|
||||
from xml.etree import ElementTree
|
||||
@ -115,21 +113,6 @@ class Updater:
|
||||
|
||||
return
|
||||
|
||||
if sys.platform == 'darwin':
|
||||
import objc
|
||||
|
||||
try:
|
||||
objc.loadBundle(
|
||||
'Sparkle', globals(), join(dirname(sys.executable), os.pardir, 'Frameworks', 'Sparkle.framework')
|
||||
)
|
||||
# loadBundle presumably supplies `SUUpdater`
|
||||
self.updater = SUUpdater.sharedUpdater() # noqa: F821
|
||||
|
||||
except Exception:
|
||||
# can't load framework - not frozen or not included in app bundle?
|
||||
print_exc()
|
||||
self.updater = None
|
||||
|
||||
def set_automatic_updates_check(self, onoroff: bool) -> None:
|
||||
"""
|
||||
Set (Win)Sparkle to perform automatic update checks, or not.
|
||||
@ -142,9 +125,6 @@ class Updater:
|
||||
if sys.platform == 'win32' and self.updater:
|
||||
self.updater.win_sparkle_set_automatic_check_for_updates(onoroff)
|
||||
|
||||
if sys.platform == 'darwin' and self.updater:
|
||||
self.updater.SUEnableAutomaticChecks(onoroff)
|
||||
|
||||
def check_for_updates(self) -> None:
|
||||
"""Trigger the requisite method to check for an update."""
|
||||
if self.use_internal():
|
||||
@ -155,9 +135,6 @@ class Updater:
|
||||
elif sys.platform == 'win32' and self.updater:
|
||||
self.updater.win_sparkle_check_update_with_ui()
|
||||
|
||||
elif sys.platform == 'darwin' and self.updater:
|
||||
self.updater.checkForUpdates_(None)
|
||||
|
||||
def check_appcast(self) -> EDMCVersion | None:
|
||||
"""
|
||||
Manually (no Sparkle or WinSparkle) check the update_feed appcast file.
|
||||
@ -184,13 +161,9 @@ class Updater:
|
||||
|
||||
return None
|
||||
|
||||
if sys.platform == 'darwin':
|
||||
sparkle_platform = 'macos'
|
||||
|
||||
else:
|
||||
# For *these* purposes anything else is the same as 'windows', as
|
||||
# non-win32 would be running from source.
|
||||
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
|
||||
|
Loading…
x
Reference in New Issue
Block a user