diff --git a/ChangeLog.md b/ChangeLog.md index d39d0af9..4b8a0e31 100644 --- a/ChangeLog.md +++ b/ChangeLog.md @@ -1,6 +1,22 @@ This is the master changelog for Elite Dangerous Market Connector. Entries are in reverse chronological order (latest first). --- +Release 4.1.1 +=== + +This release should get the program running again for everyone who had issues +with 4.1.0. + +* Catch any exception when we try to set UTF-8 encoding. We'll log where this + fails but the program should continue running. + +* The use of the tkinter.filedialog code is now contingent on a UTF-8 + encoding being set. If it isn't then we'll revert to the previous + non-tkinter file dialog code. The older OSes that can't handle a UTF-8 + encoding will get that slightly worse file dialog (that was previously + always the case before 4.1.0). Everyone else gets to enjoy the more up to + date file dialog with all the shortcuts etc. + Release 4.1.0 === diff --git a/EDMarketConnector.py b/EDMarketConnector.py index 579346ad..3e1ace27 100755 --- a/EDMarketConnector.py +++ b/EDMarketConnector.py @@ -1094,27 +1094,47 @@ if __name__ == "__main__": edmclogger.set_channels_loglevel(logging.DEBUG) logger.info(f'Startup v{appversion} : Running on Python v{sys.version}') - logger.debug(f'''Platform: {sys.platform} + logger.debug(f'''Platform: {sys.platform} {sys.platform == "win32" and sys.getwindowsversion()} argv[0]: {sys.argv[0]} exec_prefix: {sys.exec_prefix} executable: {sys.executable} sys.path: {sys.path}''' ) - # Change locale to a utf8 one - # Log what we have at startup + + # We prefer a UTF-8 encoding gets set, but older Windows versions have + # issues with this. From Windows 10 1903 onwards we can rely on the + # manifest ActiveCodePage to set this, but that is silently ignored on + # all previous Windows versions. + # Trying to set a UTF-8 encoding on those older versions will fail with + # locale.Error: unsupported locale setting + # but we do need to make the attempt for when we're running from source. log_locale('Initial Locale') - # Make sure the local is actually set as per locale's idea of defaults - locale.setlocale(locale.LC_ALL, '') - log_locale('After LC_ALL defaults set') - # Now find out the current locale, mostly the language - locale_startup = locale.getlocale(locale.LC_CTYPE) - logger.debug(f'Locale LC_CTYPE: {locale_startup}') - # Now set that same language, but utf8 encoding (it was probably cp1252 - # or equivalent for other languages). - # UTF-8, not utf8: - locale.setlocale(locale.LC_ALL, (locale_startup[0], 'UTF-8')) - log_locale('After switching to UTF-8 encoding (same language)') + + try: + locale.setlocale(locale.LC_ALL, '') + + except locale.Error as e: + logger.error("Could not set LC_ALL to ''", exc_info=e) + + else: + log_locale('After LC_ALL defaults set') + + locale_startup = locale.getlocale(locale.LC_CTYPE) + logger.debug(f'Locale LC_CTYPE: {locale_startup}') + + # Set that same language, but utf8 encoding (it was probably cp1252 + # or equivalent for other languages). + # UTF-8, not utf8: + try: + # locale_startup[0] is the 'language' portion + locale.setlocale(locale.LC_ALL, (locale_startup[0], 'UTF-8')) + + except locale.Error as e: + logger.error(f"Could not set LC_ALL to ({locale_startup[0]}, 'UTF_8')", exc_info=e) + + else: + log_locale('After switching to UTF-8 encoding (same language)') # TODO: unittests in place of these # logger.debug('Test from __main__') diff --git a/config.py b/config.py index 97584a2e..0d859c4c 100644 --- a/config.py +++ b/config.py @@ -13,7 +13,7 @@ appcmdname = 'EDMC' # appversion **MUST** follow Semantic Versioning rules: # # Major.Minor.Patch(-prerelease)(+buildmetadata) -appversion = '4.1.0' #-rc1+a872b5f' +appversion = '4.1.1' #-rc1+a872b5f' # For some things we want appversion without (possible) +build metadata appversion_nobuild = str(semantic_version.Version(appversion).truncate('prerelease')) copyright = u'© 2015-2019 Jonathan Harris, 2020 EDCD' diff --git a/prefs.py b/prefs.py index 63e68aa6..1ad2d71a 100644 --- a/prefs.py +++ b/prefs.py @@ -183,7 +183,7 @@ if platform == 'darwin': elif platform == 'win32': import ctypes import winreg - from ctypes.wintypes import HINSTANCE, HWND, LPCWSTR, LPWSTR, MAX_PATH, POINT, RECT, SIZE, UINT + from ctypes.wintypes import HINSTANCE, HWND, LPARAM, LPCWSTR, LPVOID, LPWSTR, MAX_PATH, POINT, RECT, SIZE, UINT is_wine = False try: WINE_REGISTRY_KEY = r'HKEY_LOCAL_MACHINE\Software\Wine' @@ -194,6 +194,16 @@ elif platform == 'win32': except OSError: pass + # https://msdn.microsoft.com/en-us/library/windows/desktop/bb762115 + BIF_RETURNONLYFSDIRS = 0x00000001 + BIF_USENEWUI = 0x00000050 + BFFM_INITIALIZED = 1 + BFFM_SETSELECTION = 0x00000467 + BrowseCallbackProc = ctypes.WINFUNCTYPE(ctypes.c_int, HWND, ctypes.c_uint, LPARAM, LPARAM) + + class BROWSEINFO(ctypes.Structure): + _fields_ = [("hwndOwner", HWND), ("pidlRoot", LPVOID), ("pszDisplayName", LPWSTR), ("lpszTitle", LPCWSTR), ("ulFlags", UINT), ("lpfn", BrowseCallbackProc), ("lParam", LPCWSTR), ("iImage", ctypes.c_int)] + CalculatePopupWindowPosition = None if not is_wine: CalculatePopupWindowPosition = ctypes.windll.user32.CalculatePopupWindowPosition @@ -868,13 +878,43 @@ class PreferencesDialog(tk.Toplevel): :param title: Title of the window :param pathvar: the path to start the dialog on """ - import tkinter.filedialog - directory = tkinter.filedialog.askdirectory( - parent=self, - initialdir=expanduser(pathvar.get()), - title=title, - mustexist=tk.TRUE - ) + import locale + + # If encoding isn't UTF-8 we can't use the tkinter dialog + current_locale = locale.getlocale(locale.LC_CTYPE) + from sys import platform as sys_platform + directory = None + if sys_platform == 'win32' and current_locale[1] not in ('utf8', 'UTF8', 'utf-8', 'UTF-8'): + def browsecallback(hwnd, uMsg, lParam, lpData): + # set initial folder + if uMsg == BFFM_INITIALIZED and lpData: + ctypes.windll.user32.SendMessageW(hwnd, BFFM_SETSELECTION, 1, lpData); + return 0 + + browseInfo = BROWSEINFO() + browseInfo.lpszTitle = title + browseInfo.ulFlags = BIF_RETURNONLYFSDIRS | BIF_USENEWUI + browseInfo.lpfn = BrowseCallbackProc(browsecallback) + browseInfo.lParam = pathvar.get().startswith('~') and join(config.home, + pathvar.get()[2:]) or pathvar.get() + ctypes.windll.ole32.CoInitialize(None) + pidl = ctypes.windll.shell32.SHBrowseForFolderW(ctypes.byref(browseInfo)) + if pidl: + path = ctypes.create_unicode_buffer(MAX_PATH) + ctypes.windll.shell32.SHGetPathFromIDListW(pidl, path) + ctypes.windll.ole32.CoTaskMemFree(pidl) + directory = path.value + else: + directory = None + + else: + import tkinter.filedialog + directory = tkinter.filedialog.askdirectory( + parent=self, + initialdir=expanduser(pathvar.get()), + title=title, + mustexist=tk.TRUE + ) if directory: pathvar.set(directory)