From da530f135e4181d473cc5e72e51e4c52ee079bca Mon Sep 17 00:00:00 2001 From: Athanasius Date: Thu, 17 Nov 2022 20:52:25 +0000 Subject: [PATCH] prefs.py: Attempting to fix non-utf-8 case of "choose Output file location" 1. `SHGetPathFromIDListW` needing fixing, which was achieved, but... 2. ... then `SHBrowseForFolderW()` as-was returned `int` instead of a pointer to the correct structure. Trying to fix 2 has proven intractable: a. Trying to cast the `int` return just results in `exception: access violation reading
`. b. Trying to define `SHBrowseForFolderW` properly, so it returns the correct type results in a *writing* access violation when called, despite passing the exact same data in as for the 'raw' call version. So, this commit is a record, and I'm next going to try switching to `IFileDialog` as recommended by the docs for `SHBrowseForFolderW` ('For Windows Vista or later'). --- prefs.py | 81 ++++++++++++++++++++++++++++++++++++++++++++++++-------- 1 file changed, 70 insertions(+), 11 deletions(-) diff --git a/prefs.py b/prefs.py index e5ab242b..e4964785 100644 --- a/prefs.py +++ b/prefs.py @@ -181,7 +181,10 @@ if sys.platform == 'darwin': elif sys.platform == 'win32': import ctypes import winreg - from ctypes.wintypes import HINSTANCE, HWND, LPARAM, LPCWSTR, LPVOID, LPWSTR, MAX_PATH, POINT, RECT, SIZE, UINT + from ctypes import POINTER, WINFUNCTYPE, Structure + from ctypes.wintypes import ( + BOOL, BYTE, HINSTANCE, HWND, LPARAM, LPCWSTR, LPWSTR, MAX_PATH, POINT, RECT, SIZE, UINT, USHORT + ) is_wine = False try: WINE_REGISTRY_KEY = r'HKEY_LOCAL_MACHINE\Software\Wine' @@ -192,18 +195,68 @@ elif sys.platform == 'win32': except OSError: pass + ########################################################################### + # From + class _SHITEMID(ctypes.Structure): + _fields_ = [ + ("cb", USHORT), + ("abID", BYTE * (1)), + ] + + SHITEMID = _SHITEMID + + class _ITEMIDLIST(Structure): + _fields_ = [ + ("mkid", SHITEMID), + ] + + ITEMIDLIST = _ITEMIDLIST + PCIDLIST_ABSOLUTE = ctypes.POINTER(_ITEMIDLIST) + PIDLIST_ABSOLUTE = ctypes.POINTER(_ITEMIDLIST) + ########################################################################### + + ########################################################################### + # From: + BrowseCallbackProc = WINFUNCTYPE(ctypes.c_int, HWND, ctypes.c_uint, LPARAM, LPARAM) + + class BROWSEINFOW(ctypes.Structure): + """ + Windows file browser fields. + + Ref: + """ + + _fields_ = [ + ("hwndOwner", HWND), + ("pidlRoot", PCIDLIST_ABSOLUTE), + ("pszDisplayName", LPWSTR), + ("lpszTitle", LPCWSTR), + ("ulFlags", UINT), + ("lpfn", BrowseCallbackProc), + ("lParam", LPARAM), + ("iImage", ctypes.c_int) + ] + LPBROWSEINFOW = POINTER(BROWSEINFOW) + ########################################################################### + # 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) + # SHGetPathFromIDListW + # Ref: + # BOOL SHGetPathFromIDListW([in] PCIDLIST_ABSOLUTE pidl,[out] LPWSTR pszPath); + prototype = WINFUNCTYPE(BOOL, PCIDLIST_ABSOLUTE, LPCWSTR) + paramflags = (1, "pidl"), (2, "pszPath", "") + SHGetPathFromIDListW = prototype(("SHGetPathFromIDListW", ctypes.windll.shell32), paramflags) - class BROWSEINFO(ctypes.Structure): - """Windows file browser fields.""" - - _fields_ = [("hwndOwner", HWND), ("pidlRoot", LPVOID), ("pszDisplayName", LPWSTR), ("lpszTitle", LPCWSTR), - ("ulFlags", UINT), ("lpfn", BrowseCallbackProc), ("lParam", LPCWSTR), ("iImage", ctypes.c_int)] + # SHBrowseForFolderW + # Ref: + # PIDLIST_ABSOLUTE SHBrowseForFolderW([in] LPBROWSEINFOWW lpbi); + prototype = WINFUNCTYPE(PIDLIST_ABSOLUTE, LPBROWSEINFOW) + paramflags2 = (1, "lpbi"), + SHBrowseForFolderW = prototype(("SHGetPathFromIDListW", ctypes.windll.shell32), paramflags2) CalculatePopupWindowPosition = None if not is_wine: @@ -1037,17 +1090,23 @@ class PreferencesDialog(tk.Toplevel): ctypes.windll.user32.SendMessageW(hwnd, BFFM_SETSELECTION, 1, lpData) return 0 - browseInfo = BROWSEINFO() # noqa: N806 # Windows convention + browseInfo = BROWSEINFOW() # noqa: N806 # Windows convention + # browseInfo.pidlRoot = None + browseInfo.pszDisplayName = ctypes.c_wchar_p("x" * MAX_PATH) browseInfo.lpszTitle = title browseInfo.ulFlags = BIF_RETURNONLYFSDIRS | BIF_USENEWUI browseInfo.lpfn = BrowseCallbackProc(browsecallback) - browseInfo.lParam = pathvar.get().startswith('~') and join(config.home_path, - pathvar.get()[2:]) or pathvar.get() + # browseInfo.lParam = pathvar.get().startswith('~') and join(config.home_path, + # pathvar.get()[2:]) or pathvar.get() ctypes.windll.ole32.CoInitialize(None) pidl = ctypes.windll.shell32.SHBrowseForFolderW(ctypes.byref(browseInfo)) + # ctypes.ArgumentError: argument 1: : expected LP__ITEMIDLIST instance instead of int + # pidl = SHBrowseForFolderW(ctypes.byref(browseInfo)) + # OSError: exception: access violation writing 0x00007FFB582DC9BF if pidl: + pidl = ctypes.cast(pidl, PIDLIST_ABSOLUTE) path = ctypes.create_unicode_buffer(MAX_PATH) - ctypes.windll.shell32.SHGetPathFromIDListW(pidl, path) + path = SHGetPathFromIDListW(pidl) ctypes.windll.ole32.CoTaskMemFree(pidl) directory = path.value else: