From fb21cdfa944a4dff8c628e8d48d6413adc4c7ade Mon Sep 17 00:00:00 2001 From: Athanasius Date: Tue, 6 Oct 2020 15:03:51 +0100 Subject: [PATCH 1/5] Add reporting of Windows version at startup. --- EDMarketConnector.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/EDMarketConnector.py b/EDMarketConnector.py index 579346ad..659e1d27 100755 --- a/EDMarketConnector.py +++ b/EDMarketConnector.py @@ -1094,7 +1094,7 @@ 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} From b235684dd62a9bd8e7fb3de9359a9d09b82d9e67 Mon Sep 17 00:00:00 2001 From: Athanasius Date: Tue, 6 Oct 2020 15:16:15 +0100 Subject: [PATCH 2/5] Only attempt locale changes if *not* running frozen on win32. Any running from source, or on a non-win32 platform will attempt to force UTF-8 encoding. For frozen win32 we'll rely on the windows manifest setting. On too-old versions of Windows we'll have to ensure all code works with non-UTF-8 encodings. --- EDMarketConnector.py | 39 ++++++++++++++++++++++++++------------- 1 file changed, 26 insertions(+), 13 deletions(-) diff --git a/EDMarketConnector.py b/EDMarketConnector.py index 659e1d27..2c571305 100755 --- a/EDMarketConnector.py +++ b/EDMarketConnector.py @@ -1101,20 +1101,33 @@ executable: {sys.executable} sys.path: {sys.path}''' ) - # Change locale to a utf8 one - # Log what we have at startup + # Log the locale as set at startup 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)') + # Older versions of Windows 10 (pre 1903) and Windows 7, 8, 8.1 don't work + # with UTF-8 encodings in locale. + # We still attempt to use the Unicode codepage via a manifest setting. + if sys.platform != 'win32' or not getattr(sys, 'frozen', False): + # Make sure the locale is actually set as per locale's idea of defaults + 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') + + # Find out the current locale, mostly the language + 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.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')") + else: + log_locale('After switching to UTF-8 encoding (same language)') # TODO: unittests in place of these # logger.debug('Test from __main__') From 2b65bbd768d3be9dfee62d0aff2b20bcdb85e7a4 Mon Sep 17 00:00:00 2001 From: Athanasius Date: Tue, 6 Oct 2020 15:49:17 +0100 Subject: [PATCH 3/5] Re-instate the non-tkinter file dialog. * This will be gated behind win32 *and* not a UTF-8 encoding. This way where we should be able to use tkinter.filedialog, we do. --- prefs.py | 56 ++++++++++++++++++++++++++++++++++++++++++++++++-------- 1 file changed, 48 insertions(+), 8 deletions(-) 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) From 58bf8466aee7bdc9930c21cb295fbfdd7a3bfe8d Mon Sep 17 00:00:00 2001 From: Athanasius Date: Tue, 6 Oct 2020 16:16:21 +0100 Subject: [PATCH 4/5] locale: Remove conditional on locale setting & comments cleanup * We're using try/except so can just attempt the setting of a UTF-8 encoding. In cases where it doesn't work we'll have some obvious log output to help diagnose any issues it causes later. * Cleaned up the comments to be more 'why' than 'what'. --- EDMarketConnector.py | 53 +++++++++++++++++++++++++------------------- 1 file changed, 30 insertions(+), 23 deletions(-) diff --git a/EDMarketConnector.py b/EDMarketConnector.py index 2c571305..3e1ace27 100755 --- a/EDMarketConnector.py +++ b/EDMarketConnector.py @@ -1101,33 +1101,40 @@ executable: {sys.executable} sys.path: {sys.path}''' ) - # Log the locale as set 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') - # Older versions of Windows 10 (pre 1903) and Windows 7, 8, 8.1 don't work - # with UTF-8 encodings in locale. - # We still attempt to use the Unicode codepage via a manifest setting. - if sys.platform != 'win32' or not getattr(sys, 'frozen', False): - # Make sure the locale is actually set as per locale's idea of defaults + + 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.setlocale(locale.LC_ALL, '') + # locale_startup[0] is the 'language' portion + locale.setlocale(locale.LC_ALL, (locale_startup[0], 'UTF-8')) + except locale.Error as e: - logger.error("Could not set LC_ALL to ''", exc_info=e) + logger.error(f"Could not set LC_ALL to ({locale_startup[0]}, 'UTF_8')", exc_info=e) + else: - log_locale('After LC_ALL defaults set') - - # Find out the current locale, mostly the language - 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.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')") - else: - log_locale('After switching to UTF-8 encoding (same language)') + log_locale('After switching to UTF-8 encoding (same language)') # TODO: unittests in place of these # logger.debug('Test from __main__') From 4ee2957913b90df2bbfe271c485d7f8dca0155e0 Mon Sep 17 00:00:00 2001 From: Athanasius Date: Tue, 6 Oct 2020 16:29:04 +0100 Subject: [PATCH 5/5] Release 4.1.1 --- ChangeLog.md | 16 ++++++++++++++++ config.py | 2 +- 2 files changed, 17 insertions(+), 1 deletion(-) 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/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'