From fd40f6991668090d855164f5fe76b5fa33a2e4c2 Mon Sep 17 00:00:00 2001 From: Benjamin Auquite Date: Sat, 24 Aug 2024 14:05:12 -0500 Subject: [PATCH 1/9] implement modern interfaces with `comtypes` --- winforms/src/toga_winforms/dialogs.py | 395 +++++++++++++++++++++++--- 1 file changed, 350 insertions(+), 45 deletions(-) diff --git a/winforms/src/toga_winforms/dialogs.py b/winforms/src/toga_winforms/dialogs.py index a6a9fc3a14..9b3d8df093 100644 --- a/winforms/src/toga_winforms/dialogs.py +++ b/winforms/src/toga_winforms/dialogs.py @@ -1,6 +1,19 @@ import asyncio +import os from pathlib import Path +import comtypes +import comtypes.client + +from comtypes import COMMETHOD, GUID +from comtypes.hresult import S_OK + +from ctypes import HRESULT, POINTER, Structure, byref, c_int, c_uint, c_ulong, c_void_p, c_wchar_p, windll +from ctypes import cast as cast_with_ctypes +from ctypes.wintypes import BOOL, DWORD, HWND, LPCWSTR, LPWSTR +from enum import IntFlag +from typing import Callable, List, Optional, Tuple, Union + import System.Windows.Forms as WinForms from System.Drawing import ( ContentAlignment, @@ -14,6 +27,247 @@ from .libs.wrapper import WeakrefCallable +class COMDLG_FILTERSPEC(Structure): # noqa: N801 + _fields_ = [ + ("pszName", LPCWSTR), + ("pszSpec", LPCWSTR) + ] + + +class FileOpenOptions(IntFlag): + FOS_OVERWRITEPROMPT = 0x00000002 + FOS_STRICTFILETYPES = 0x00000004 + FOS_NOCHANGEDIR = 0x00000008 + FOS_PICKFOLDERS = 0x00000020 + FOS_FORCEFILESYSTEM = 0x00000040 + FOS_ALLNONSTORAGEITEMS = 0x00000080 + FOS_NOVALIDATE = 0x00000100 + FOS_ALLOWMULTISELECT = 0x00000200 + FOS_PATHMUSTEXIST = 0x00000800 + FOS_FILEMUSTEXIST = 0x00001000 + FOS_CREATEPROMPT = 0x00002000 + FOS_SHAREAWARE = 0x00004000 + FOS_NOREADONLYRETURN = 0x00008000 + FOS_NOTESTFILECREATE = 0x00010000 + FOS_HIDEMRUPLACES = 0x00020000 + FOS_HIDEPINNEDPLACES = 0x00040000 + FOS_NODEREFERENCELINKS = 0x00100000 + FOS_DONTADDTORECENT = 0x02000000 + FOS_FORCESHOWHIDDEN = 0x10000000 + FOS_DEFAULTNOMINIMODE = 0x20000000 + FOS_FORCEPREVIEWPANEON = 0x40000000 + + +IID_IShellItem = GUID("{43826D1E-E718-42EE-BC55-A1E261C37BFE}") +IID_IShellItemArray = GUID("{B63EA76D-1F85-456F-A19C-48159EFA858B}") +IID_IShellItemFilter = GUID("{2659B475-EEB8-48B7-8F07-B378810F48CF}") +IID_IModalWindow = GUID("{B4DB1657-70D7-485E-8E3E-6FCB5A5C1802}") +IID_IFileDialog = GUID("{42F85136-DB7E-439C-85F1-E4075D135FC8}") +IID_IFileOpenDialog = GUID("{D57C7288-D4AD-4768-BE02-9D969532D960}") +IID_IFileSaveDialog = GUID("{84BCCD23-5FDE-4CDB-AEA4-AF64B83D78AB}") +CLSID_FileOpenDialog = GUID("{DC1C5A9C-E88A-4dde-A5A1-60F82A20AEF7}") +CLSID_FileSaveDialog = GUID("{C0B4E2F3-BA21-4773-8DBA-335EC946EB8B}") + + +class IShellItem(comtypes.IUnknown): + _case_insensitive_: bool = True + _iid_: GUID = IID_IShellItem + _methods_ = [ + COMMETHOD([], HRESULT, "BindToHandler", + (["in"], POINTER(comtypes.IUnknown), "pbc"), + (["in"], POINTER(GUID), "bhid"), + (["in"], POINTER(GUID), "riid"), + (["out"], POINTER(c_void_p), "ppv")), + COMMETHOD([], HRESULT, "GetParent", + (["out"], POINTER(POINTER(comtypes.IUnknown)), "ppsi")), + COMMETHOD([], HRESULT, "GetDisplayName", + (["in"], c_ulong, "sigdnName"), + (["out"], POINTER(LPWSTR), "ppszName")), + COMMETHOD([], HRESULT, "GetAttributes", + (["in"], c_ulong, "sfgaoMask"), + (["out"], POINTER(c_ulong), "psfgaoAttribs")), + COMMETHOD([], HRESULT, "Compare", + (["in"], POINTER(comtypes.IUnknown), "psi"), + (["in"], c_ulong, "hint"), + (["out"], POINTER(c_int), "piOrder")) + ] + QueryInterface: Callable[[GUID, comtypes.IUnknown], int] + AddRef: Callable[[], int] + Release: Callable[[], int] + BindToHandler: Callable[[comtypes.IUnknown, GUID, GUID, c_void_p], int] + GetParent: Callable[[], comtypes.IUnknown] + GetDisplayName: Callable[[Union[c_ulong, int]], str] + GetAttributes: Callable[[Union[c_ulong, int]], int] + Compare: Callable[[comtypes.IUnknown, c_ulong, c_int], int] + + +class IShellItemArray(comtypes.IUnknown): + _case_insensitive_: bool = True + _iid_: GUID = IID_IShellItemArray + _methods_ = [ + COMMETHOD([], HRESULT, "BindToHandler", + (["in"], POINTER(comtypes.IUnknown), "pbc"), + (["in"], POINTER(GUID), "bhid"), + (["in"], POINTER(GUID), "riid"), + (["out"], POINTER(c_void_p), "ppv")), + COMMETHOD([], HRESULT, "GetPropertyStore", + (["in"], c_ulong, "flags"), + (["in"], POINTER(GUID), "riid"), + (["out"], POINTER(c_void_p), "ppv")), + COMMETHOD([], HRESULT, "GetPropertyDescriptionList", + (["in"], POINTER(GUID), "keyType"), + (["in"], POINTER(GUID), "riid"), + (["out"], POINTER(c_void_p), "ppv")), + COMMETHOD([], HRESULT, "GetAttributes", + (["in"], c_ulong, "attribFlags"), + (["in"], c_ulong, "sfgaoMask"), + (["out"], POINTER(c_ulong), "psfgaoAttribs")), + COMMETHOD([], HRESULT, "GetCount", + (["out"], POINTER(c_uint), "pdwNumItems")), + COMMETHOD([], HRESULT, "GetItemAt", + (["in"], c_uint, "dwIndex"), + (["out"], POINTER(POINTER(IShellItem)), "ppsi")), + COMMETHOD([], HRESULT, "EnumItems", + (["out"], POINTER(POINTER(comtypes.IUnknown)), "ppenumShellItems")) + ] + QueryInterface: Callable[[GUID, comtypes.IUnknown], int] + AddRef: Callable[[], int] + Release: Callable[[], int] + BindToHandler: Callable[[comtypes.IUnknown, GUID, GUID], int] + GetPropertyStore: Callable[[int, GUID], c_void_p] + GetPropertyDescriptionList: Callable[[GUID, GUID], c_void_p] + GetAttributes: Callable[[int, int], int] + GetCount: Callable[[], int] + GetItemAt: Callable[[Union[int, int]], IShellItem] + EnumItems: Callable[[], comtypes.IUnknown] + + +class IModalWindow(comtypes.IUnknown): + _case_insensitive_: bool = True + _iid_: GUID = IID_IModalWindow + _methods_ = [ + COMMETHOD([], HRESULT, "Show", + (["in"], HWND, "hwndParent")) + ] + Show: Callable[[Union[int, HWND]], int] + + +class IFileDialog(IModalWindow): + _iid_: GUID = IID_IFileDialog + _methods_ = [ + COMMETHOD([], HRESULT, "SetFileTypes", + (["in"], c_uint, "cFileTypes"), + (["in"], POINTER(c_void_p), "rgFilterSpec")), + COMMETHOD([], HRESULT, "SetFileTypeIndex", + (["in"], c_uint, "iFileType")), + COMMETHOD([], HRESULT, "GetFileTypeIndex", + (["out"], POINTER(c_uint), "piFileType")), + COMMETHOD([], HRESULT, "Advise", + (["in"], POINTER(comtypes.IUnknown), "pfde"), + (["out"], POINTER(DWORD), "pdwCookie")), + COMMETHOD([], HRESULT, "Unadvise", + (["in"], DWORD, "dwCookie")), + COMMETHOD([], HRESULT, "SetOptions", + (["in"], c_uint, "fos")), + COMMETHOD([], HRESULT, "GetOptions", + (["out"], POINTER(DWORD), "pfos")), + COMMETHOD([], HRESULT, "SetDefaultFolder", + (["in"], POINTER(IShellItem), "psi")), + COMMETHOD([], HRESULT, "SetFolder", + (["in"], POINTER(IShellItem), "psi")), + COMMETHOD([], HRESULT, "GetFolder", + (["out"], POINTER(POINTER(IShellItem)), "ppsi")), + COMMETHOD([], HRESULT, "GetCurrentSelection", + (["out"], POINTER(POINTER(IShellItem)), "ppsi")), + COMMETHOD([], HRESULT, "SetFileName", + (["in"], LPCWSTR, "pszName")), + COMMETHOD([], HRESULT, "GetFileName", + (["out"], POINTER(LPWSTR), "pszName")), + COMMETHOD([], HRESULT, "SetTitle", + (["in"], LPCWSTR, "pszTitle")), + COMMETHOD([], HRESULT, "SetOkButtonLabel", + (["in"], LPCWSTR, "pszText")), + COMMETHOD([], HRESULT, "SetFileNameLabel", + (["in"], LPCWSTR, "pszLabel")), + COMMETHOD([], HRESULT, "GetResult", + (["out"], POINTER(POINTER(IShellItem)), "ppsi")), + COMMETHOD([], HRESULT, "AddPlace", + (["in"], POINTER(IShellItem), "psi"), + (["in"], c_int, "fdap")), + COMMETHOD([], HRESULT, "SetDefaultExtension", + (["in"], LPCWSTR, "pszDefaultExtension")), + COMMETHOD([], HRESULT, "Close", + (["in"], HRESULT, "hr")), + COMMETHOD([], HRESULT, "SetClientGuid", + (["in"], POINTER(GUID), "guid")), + COMMETHOD([], HRESULT, "ClearClientData"), + COMMETHOD([], HRESULT, "SetFilter", + (["in"], POINTER(comtypes.IUnknown), "pFilter")) # IShellItemFilter + ] + SetFileTypes: Callable[[Union[c_uint, int], c_void_p], int] + SetFileTypeIndex: Callable[[c_uint], int] + GetFileTypeIndex: Callable[[], int] + Advise: Callable[[Union[comtypes.IUnknown, comtypes.COMObject]], int] + Unadvise: Callable[[int], int] + SetOptions: Callable[[Union[int, int]], int] + GetOptions: Callable[[], int] + SetDefaultFolder: Callable[[IShellItem], int] + SetFolder: Callable[[IShellItem], int] + GetFolder: Callable[[], IShellItem] + GetCurrentSelection: Callable[[], IShellItem] + SetFileName: Callable[[str], int] + GetFileName: Callable[[], str] + SetTitle: Callable[[str], int] + SetOkButtonLabel: Callable[[str], int] + SetFileNameLabel: Callable[[str], int] + GetResult: Callable[[], IShellItem] + AddPlace: Callable[[IShellItem, c_int], int] + SetDefaultExtension: Callable[[str], int] + Close: Callable[[HRESULT], int] + SetClientGuid: Callable[[GUID], int] + ClearClientData: Callable[[], int] + SetFilter: Callable[[comtypes.IUnknown], int] + + +class IFileOpenDialog(IFileDialog): + _case_insensitive_: bool = True + _iid_: GUID = IID_IFileOpenDialog + _methods_ = [ + COMMETHOD([], HRESULT, "GetResults", + (["out"], POINTER(POINTER(IShellItemArray)), "ppenum")), + COMMETHOD([], HRESULT, "GetSelectedItems", + (["out"], POINTER(POINTER(IShellItemArray)), "ppsai")) + ] + GetResults: Callable[[], IShellItemArray] + GetSelectedItems: Callable[[], IShellItemArray] + + +class IFileSaveDialog(IFileDialog): + _case_insensitive_: bool = True + _iid_: GUID = IID_IFileSaveDialog + _methods_ = [ + COMMETHOD([], HRESULT, "SetSaveAsItem", + (["in"], POINTER(IShellItem), "psi")), + COMMETHOD([], HRESULT, "SetProperties", + (["in"], POINTER(comtypes.IUnknown), "pStore")), + COMMETHOD([], HRESULT, "SetCollectedProperties", + (["in"], POINTER(comtypes.IUnknown), "pList"), + (["in"], BOOL, "fAppendDefault")), + COMMETHOD([], HRESULT, "GetProperties", + (["out"], POINTER(POINTER(comtypes.IUnknown)), "ppStore")), + COMMETHOD([], HRESULT, "ApplyProperties", + (["in"], POINTER(IShellItem), "psi"), + (["in"], POINTER(comtypes.IUnknown), "pStore"), + (["in"], HWND, "hwnd"), + (["in"], POINTER(comtypes.IUnknown), "pSink")) + ] + SetSaveAsItem: Callable[[IShellItem], int] + SetProperties: Callable[[comtypes.IUnknown], int] + SetCollectedProperties: Callable[[comtypes.IUnknown, BOOL], int] + GetProperties: Callable[[comtypes.IUnknown], int] + ApplyProperties: Callable[[IShellItem, comtypes.IUnknown, HWND, comtypes.IUnknown], int] + + class BaseDialog: def show(self, host_window, future): self.future = future @@ -190,52 +444,77 @@ def winforms_Click_accept(self, sender, event): class FileDialog(BaseDialog): def __init__( self, - native, - title, - initial_directory, + native: Union[IFileOpenDialog, IFileSaveDialog], + title: str, + initial_directory: Union[os.PathLike, str], *, - filename=None, - file_types=None, + filename: Optional[str] = None, + file_types: Optional[List[str]] = None, ): super().__init__() - self.native = native + self.native: Union[IFileOpenDialog, IFileSaveDialog] = native self._set_title(title) if filename is not None: - native.FileName = filename + self.native.SetFileName(filename) if initial_directory is not None: self._set_initial_directory(str(initial_directory)) if file_types is not None: - filters = [f"{ext} files (*.{ext})|*.{ext}" for ext in file_types] + [ - "All files (*.*)|*.*" + filters: List[Tuple[str, str]] = [ + (f"{ext.upper()} files", f"*.{ext}") + for ext in file_types ] - - if len(file_types) > 1: - pattern = ";".join([f"*.{ext}" for ext in file_types]) - filters.insert(0, f"All matching files ({pattern})|{pattern}") - - native.Filter = "|".join(filters) + filterspec = (COMDLG_FILTERSPEC * len(file_types))( + *[ + (c_wchar_p(name), c_wchar_p(spec)) + for name, spec in filters + ] + ) + self.native.SetFileTypes(len(filterspec), cast_with_ctypes(filterspec, POINTER(c_void_p))) def _show(self): - response = self.native.ShowDialog() - if response == DialogResult.OK: + hwnd = HWND(0) + hr: int = self.native.Show(hwnd) + if hr == S_OK: + assert isinstance(self, (SaveFileDialog, OpenFileDialog, SelectFolderDialog)) self.future.set_result(self._get_filenames()) else: self.future.set_result(None) def _set_title(self, title): - self.native.Title = title + self.native.SetTitle(title) def _set_initial_directory(self, initial_directory): - self.native.InitialDirectory = initial_directory + if initial_directory is None: + return + folder_path: Path = Path(initial_directory).resolve() + if folder_path.is_dir(): # sourcery skip: extract-method + SHCreateItemFromParsingName = windll.shell32.SHCreateItemFromParsingName + SHCreateItemFromParsingName.argtypes = [ + c_wchar_p, # LPCWSTR (wide string, null-terminated) + POINTER(comtypes.IUnknown), # IBindCtx* (can be NULL, hence POINTER(IUnknown)) + POINTER(GUID), # REFIID (pointer to the interface ID, typically GUID) + POINTER(POINTER(IShellItem)) # void** (output pointer to the requested interface) + ] + SHCreateItemFromParsingName.restype = HRESULT + shell_item = POINTER(IShellItem)() + hr = SHCreateItemFromParsingName(str(folder_path), None, IShellItem._iid_, byref(shell_item)) + if hr == S_OK: + self.native.SetFolder(shell_item) class SaveFileDialog(FileDialog): - def __init__(self, title, filename, initial_directory, file_types): + def __init__( + self, + title: str, + filename: str, + initial_directory: Union[os.PathLike, str], + file_types: List[str], + ): super().__init__( - WinForms.SaveFileDialog(), + comtypes.client.CreateObject(CLSID_FileSaveDialog, interface=IFileSaveDialog), title, initial_directory, filename=filename, @@ -243,55 +522,81 @@ def __init__(self, title, filename, initial_directory, file_types): ) def _get_filenames(self): - return Path(self.native.FileName) + shell_item: IShellItem = self.native.GetResult() + display_name: str = shell_item.GetDisplayName(0x80058000) # SIGDN_FILESYSPATH + return Path(display_name) class OpenFileDialog(FileDialog): def __init__( self, - title, - initial_directory, - file_types, - multiple_select, + title: str, + initial_directory: Union[os.PathLike, str], + file_types: List[str], + multiple_select: bool, ): super().__init__( - WinForms.OpenFileDialog(), + comtypes.client.CreateObject(CLSID_FileOpenDialog, interface=IFileOpenDialog), title, initial_directory, file_types=file_types, ) if multiple_select: - self.native.Multiselect = True + self.native.SetOptions(FileOpenOptions.FOS_ALLOWMULTISELECT) - # Provided as a stub that can be mocked in test conditions def selected_paths(self): - return self.native.FileNames + # This is a stub method; we provide functionality using the COM API + return self._get_filenames() - def _get_filenames(self): - if self.native.Multiselect: - return [Path(filename) for filename in self.selected_paths()] - else: - return Path(self.native.FileName) + def _get_filenames(self) -> List[Path]: + assert isinstance(self.native, IFileOpenDialog) + results: List[Path] = [] + shell_item_array: IShellItemArray = self.native.GetResults() + item_count: int = shell_item_array.GetCount() + for i in range(item_count): + shell_item: IShellItem = shell_item_array.GetItemAt(i) + szFilePath: str = str(shell_item.GetDisplayName(0x80058000)) # SIGDN_FILESYSPATH + results.append(Path(szFilePath)) + return results class SelectFolderDialog(FileDialog): - def __init__(self, title, initial_directory, multiple_select): + def __init__( + self, + title: str, + initial_directory: Union[os.PathLike, str], + multiple_select: bool, + ): super().__init__( - WinForms.FolderBrowserDialog(), + comtypes.client.CreateObject(CLSID_FileOpenDialog, interface=IFileOpenDialog), title, initial_directory, ) - - # The native dialog doesn't support multiple selection, so the only effect - # this has is to change whether we return a list. + self.native.SetOptions(FileOpenOptions.FOS_PICKFOLDERS) self.multiple_select = multiple_select - def _get_filenames(self): - filename = Path(self.native.SelectedPath) - return [filename] if self.multiple_select else filename + def _get_filenames(self) -> Union[List[Path], Path]: + shell_item: IShellItem = self.native.GetResult() + display_name: str = shell_item.GetDisplayName(0x80058000) # SIGDN_FILESYSPATH + return [Path(display_name)] if self.multiple_select else Path(display_name) def _set_title(self, title): - self.native.Description = title + self.native.SetTitle(title) def _set_initial_directory(self, initial_directory): - self.native.SelectedPath = initial_directory + if initial_directory is None: + return + folder_path: Path = Path(initial_directory).resolve() + if folder_path.is_dir(): # sourcery skip: extract-method + SHCreateItemFromParsingName = windll.shell32.SHCreateItemFromParsingName + SHCreateItemFromParsingName.argtypes = [ + c_wchar_p, # LPCWSTR (wide string, null-terminated) + POINTER(comtypes.IUnknown), # IBindCtx* (can be NULL, hence POINTER(IUnknown)) + POINTER(GUID), # REFIID (pointer to the interface ID, typically GUID) + POINTER(POINTER(IShellItem)) # void** (output pointer to the requested interface) + ] + SHCreateItemFromParsingName.restype = HRESULT + shell_item = POINTER(IShellItem)() + hr = SHCreateItemFromParsingName(str(folder_path), None, IShellItem._iid_, byref(shell_item)) + if hr == S_OK: + self.native.SetFolder(shell_item) From 99b9eeba0679960985dddc1b276106b731fef05f Mon Sep 17 00:00:00 2001 From: Benjamin Auquite Date: Fri, 30 Aug 2024 00:12:31 -0500 Subject: [PATCH 2/9] remove dialog dependency to winforms also organize constants/prototypes into libs --- winforms/src/toga_winforms/dialogs.py | 573 +++---- .../libs/py_wrappers/__init__.py | 0 .../libs/py_wrappers/com/__init__.py | 0 .../libs/py_wrappers/com/com_helpers.py | 163 ++ .../libs/py_wrappers/com/com_types.py | 1 + .../libs/py_wrappers/com/interfaces.py | 1401 +++++++++++++++++ .../libs/py_wrappers/com/progress_dialog.py | 149 ++ .../libs/py_wrappers/com/windialogs copy.py | 826 ++++++++++ .../libs/py_wrappers/com/windialogs.py | 857 ++++++++++ .../toga_winforms/libs/py_wrappers/common.py | 50 + .../libs/py_wrappers/context_menu.py | 209 +++ .../toga_winforms/libs/py_wrappers/hresult.py | 419 +++++ .../toga_winforms/libs/py_wrappers/hwnd.py | 300 ++++ .../libs/py_wrappers/winapi/__init__.py | 0 .../py_wrappers/winapi/device_iocontrol.py | 905 +++++++++++ .../libs/py_wrappers/winapi/input_dialogs.py | 39 + .../libs/py_wrappers/winapi/messagebox.py | 95 ++ .../libs/py_wrappers/winapi/sounds.py | 12 + .../libs/py_wrappers/winapi/tooltip.py | 37 + 19 files changed, 5641 insertions(+), 395 deletions(-) create mode 100644 winforms/src/toga_winforms/libs/py_wrappers/__init__.py create mode 100644 winforms/src/toga_winforms/libs/py_wrappers/com/__init__.py create mode 100644 winforms/src/toga_winforms/libs/py_wrappers/com/com_helpers.py create mode 100644 winforms/src/toga_winforms/libs/py_wrappers/com/com_types.py create mode 100644 winforms/src/toga_winforms/libs/py_wrappers/com/interfaces.py create mode 100644 winforms/src/toga_winforms/libs/py_wrappers/com/progress_dialog.py create mode 100644 winforms/src/toga_winforms/libs/py_wrappers/com/windialogs copy.py create mode 100644 winforms/src/toga_winforms/libs/py_wrappers/com/windialogs.py create mode 100644 winforms/src/toga_winforms/libs/py_wrappers/common.py create mode 100644 winforms/src/toga_winforms/libs/py_wrappers/context_menu.py create mode 100644 winforms/src/toga_winforms/libs/py_wrappers/hresult.py create mode 100644 winforms/src/toga_winforms/libs/py_wrappers/hwnd.py create mode 100644 winforms/src/toga_winforms/libs/py_wrappers/winapi/__init__.py create mode 100644 winforms/src/toga_winforms/libs/py_wrappers/winapi/device_iocontrol.py create mode 100644 winforms/src/toga_winforms/libs/py_wrappers/winapi/input_dialogs.py create mode 100644 winforms/src/toga_winforms/libs/py_wrappers/winapi/messagebox.py create mode 100644 winforms/src/toga_winforms/libs/py_wrappers/winapi/sounds.py create mode 100644 winforms/src/toga_winforms/libs/py_wrappers/winapi/tooltip.py diff --git a/winforms/src/toga_winforms/dialogs.py b/winforms/src/toga_winforms/dialogs.py index 9b3d8df093..f90d775fdc 100644 --- a/winforms/src/toga_winforms/dialogs.py +++ b/winforms/src/toga_winforms/dialogs.py @@ -1,271 +1,66 @@ +from __future__ import annotations + import asyncio -import os +from ctypes import ( + HRESULT, + POINTER, + byref, + c_int, + c_void_p, + c_wchar_p, + sizeof, + windll, +) +from ctypes import cast as cast_with_ctypes +from ctypes.wintypes import HWND, LPCWSTR from pathlib import Path +from typing import TYPE_CHECKING import comtypes import comtypes.client - -from comtypes import COMMETHOD, GUID +from comtypes import GUID from comtypes.hresult import S_OK -from ctypes import HRESULT, POINTER, Structure, byref, c_int, c_uint, c_ulong, c_void_p, c_wchar_p, windll -from ctypes import cast as cast_with_ctypes -from ctypes.wintypes import BOOL, DWORD, HWND, LPCWSTR, LPWSTR -from enum import IntFlag -from typing import Callable, List, Optional, Tuple, Union - -import System.Windows.Forms as WinForms -from System.Drawing import ( - ContentAlignment, - Font as WinFont, - FontFamily, - FontStyle, - SystemFonts, +from toga_winforms.libs.win_wrappers.com.interfaces import ( + COMDLG_FILTERSPEC, + CLSID_FileOpenDialog, + CLSID_FileSaveDialog, + FileOpenOptions, + IFileOpenDialog, + IFileSaveDialog, + IShellItem, + IShellItemArray, +) +from toga_winforms.libs.win_wrappers.hwnd import ( + ES_AUTOVSCROLL, + ES_MULTILINE, + ES_READONLY, + IDCANCEL, + IDOK, + IDYES, + MB_ICONASTERISK, + MB_ICONEXCLAMATION, + MB_ICONHAND, + MB_ICONQUESTION, + MB_OK, + MB_OKCANCEL, + MB_YESNO, + SW_SHOW, + WM_COMMAND, + WM_DESTROY, + WNDPROC, + WS_CHILD, + WS_EX_DLGMODALFRAME, + WS_OVERLAPPEDWINDOW, + WS_VISIBLE, + WS_VSCROLL, + CursorType, + MessageBoxW, + ctypesWNDCLASS, ) -from System.Windows.Forms import DialogResult, MessageBoxButtons, MessageBoxIcon - -from .libs.wrapper import WeakrefCallable - - -class COMDLG_FILTERSPEC(Structure): # noqa: N801 - _fields_ = [ - ("pszName", LPCWSTR), - ("pszSpec", LPCWSTR) - ] - - -class FileOpenOptions(IntFlag): - FOS_OVERWRITEPROMPT = 0x00000002 - FOS_STRICTFILETYPES = 0x00000004 - FOS_NOCHANGEDIR = 0x00000008 - FOS_PICKFOLDERS = 0x00000020 - FOS_FORCEFILESYSTEM = 0x00000040 - FOS_ALLNONSTORAGEITEMS = 0x00000080 - FOS_NOVALIDATE = 0x00000100 - FOS_ALLOWMULTISELECT = 0x00000200 - FOS_PATHMUSTEXIST = 0x00000800 - FOS_FILEMUSTEXIST = 0x00001000 - FOS_CREATEPROMPT = 0x00002000 - FOS_SHAREAWARE = 0x00004000 - FOS_NOREADONLYRETURN = 0x00008000 - FOS_NOTESTFILECREATE = 0x00010000 - FOS_HIDEMRUPLACES = 0x00020000 - FOS_HIDEPINNEDPLACES = 0x00040000 - FOS_NODEREFERENCELINKS = 0x00100000 - FOS_DONTADDTORECENT = 0x02000000 - FOS_FORCESHOWHIDDEN = 0x10000000 - FOS_DEFAULTNOMINIMODE = 0x20000000 - FOS_FORCEPREVIEWPANEON = 0x40000000 - - -IID_IShellItem = GUID("{43826D1E-E718-42EE-BC55-A1E261C37BFE}") -IID_IShellItemArray = GUID("{B63EA76D-1F85-456F-A19C-48159EFA858B}") -IID_IShellItemFilter = GUID("{2659B475-EEB8-48B7-8F07-B378810F48CF}") -IID_IModalWindow = GUID("{B4DB1657-70D7-485E-8E3E-6FCB5A5C1802}") -IID_IFileDialog = GUID("{42F85136-DB7E-439C-85F1-E4075D135FC8}") -IID_IFileOpenDialog = GUID("{D57C7288-D4AD-4768-BE02-9D969532D960}") -IID_IFileSaveDialog = GUID("{84BCCD23-5FDE-4CDB-AEA4-AF64B83D78AB}") -CLSID_FileOpenDialog = GUID("{DC1C5A9C-E88A-4dde-A5A1-60F82A20AEF7}") -CLSID_FileSaveDialog = GUID("{C0B4E2F3-BA21-4773-8DBA-335EC946EB8B}") - - -class IShellItem(comtypes.IUnknown): - _case_insensitive_: bool = True - _iid_: GUID = IID_IShellItem - _methods_ = [ - COMMETHOD([], HRESULT, "BindToHandler", - (["in"], POINTER(comtypes.IUnknown), "pbc"), - (["in"], POINTER(GUID), "bhid"), - (["in"], POINTER(GUID), "riid"), - (["out"], POINTER(c_void_p), "ppv")), - COMMETHOD([], HRESULT, "GetParent", - (["out"], POINTER(POINTER(comtypes.IUnknown)), "ppsi")), - COMMETHOD([], HRESULT, "GetDisplayName", - (["in"], c_ulong, "sigdnName"), - (["out"], POINTER(LPWSTR), "ppszName")), - COMMETHOD([], HRESULT, "GetAttributes", - (["in"], c_ulong, "sfgaoMask"), - (["out"], POINTER(c_ulong), "psfgaoAttribs")), - COMMETHOD([], HRESULT, "Compare", - (["in"], POINTER(comtypes.IUnknown), "psi"), - (["in"], c_ulong, "hint"), - (["out"], POINTER(c_int), "piOrder")) - ] - QueryInterface: Callable[[GUID, comtypes.IUnknown], int] - AddRef: Callable[[], int] - Release: Callable[[], int] - BindToHandler: Callable[[comtypes.IUnknown, GUID, GUID, c_void_p], int] - GetParent: Callable[[], comtypes.IUnknown] - GetDisplayName: Callable[[Union[c_ulong, int]], str] - GetAttributes: Callable[[Union[c_ulong, int]], int] - Compare: Callable[[comtypes.IUnknown, c_ulong, c_int], int] - - -class IShellItemArray(comtypes.IUnknown): - _case_insensitive_: bool = True - _iid_: GUID = IID_IShellItemArray - _methods_ = [ - COMMETHOD([], HRESULT, "BindToHandler", - (["in"], POINTER(comtypes.IUnknown), "pbc"), - (["in"], POINTER(GUID), "bhid"), - (["in"], POINTER(GUID), "riid"), - (["out"], POINTER(c_void_p), "ppv")), - COMMETHOD([], HRESULT, "GetPropertyStore", - (["in"], c_ulong, "flags"), - (["in"], POINTER(GUID), "riid"), - (["out"], POINTER(c_void_p), "ppv")), - COMMETHOD([], HRESULT, "GetPropertyDescriptionList", - (["in"], POINTER(GUID), "keyType"), - (["in"], POINTER(GUID), "riid"), - (["out"], POINTER(c_void_p), "ppv")), - COMMETHOD([], HRESULT, "GetAttributes", - (["in"], c_ulong, "attribFlags"), - (["in"], c_ulong, "sfgaoMask"), - (["out"], POINTER(c_ulong), "psfgaoAttribs")), - COMMETHOD([], HRESULT, "GetCount", - (["out"], POINTER(c_uint), "pdwNumItems")), - COMMETHOD([], HRESULT, "GetItemAt", - (["in"], c_uint, "dwIndex"), - (["out"], POINTER(POINTER(IShellItem)), "ppsi")), - COMMETHOD([], HRESULT, "EnumItems", - (["out"], POINTER(POINTER(comtypes.IUnknown)), "ppenumShellItems")) - ] - QueryInterface: Callable[[GUID, comtypes.IUnknown], int] - AddRef: Callable[[], int] - Release: Callable[[], int] - BindToHandler: Callable[[comtypes.IUnknown, GUID, GUID], int] - GetPropertyStore: Callable[[int, GUID], c_void_p] - GetPropertyDescriptionList: Callable[[GUID, GUID], c_void_p] - GetAttributes: Callable[[int, int], int] - GetCount: Callable[[], int] - GetItemAt: Callable[[Union[int, int]], IShellItem] - EnumItems: Callable[[], comtypes.IUnknown] - - -class IModalWindow(comtypes.IUnknown): - _case_insensitive_: bool = True - _iid_: GUID = IID_IModalWindow - _methods_ = [ - COMMETHOD([], HRESULT, "Show", - (["in"], HWND, "hwndParent")) - ] - Show: Callable[[Union[int, HWND]], int] - - -class IFileDialog(IModalWindow): - _iid_: GUID = IID_IFileDialog - _methods_ = [ - COMMETHOD([], HRESULT, "SetFileTypes", - (["in"], c_uint, "cFileTypes"), - (["in"], POINTER(c_void_p), "rgFilterSpec")), - COMMETHOD([], HRESULT, "SetFileTypeIndex", - (["in"], c_uint, "iFileType")), - COMMETHOD([], HRESULT, "GetFileTypeIndex", - (["out"], POINTER(c_uint), "piFileType")), - COMMETHOD([], HRESULT, "Advise", - (["in"], POINTER(comtypes.IUnknown), "pfde"), - (["out"], POINTER(DWORD), "pdwCookie")), - COMMETHOD([], HRESULT, "Unadvise", - (["in"], DWORD, "dwCookie")), - COMMETHOD([], HRESULT, "SetOptions", - (["in"], c_uint, "fos")), - COMMETHOD([], HRESULT, "GetOptions", - (["out"], POINTER(DWORD), "pfos")), - COMMETHOD([], HRESULT, "SetDefaultFolder", - (["in"], POINTER(IShellItem), "psi")), - COMMETHOD([], HRESULT, "SetFolder", - (["in"], POINTER(IShellItem), "psi")), - COMMETHOD([], HRESULT, "GetFolder", - (["out"], POINTER(POINTER(IShellItem)), "ppsi")), - COMMETHOD([], HRESULT, "GetCurrentSelection", - (["out"], POINTER(POINTER(IShellItem)), "ppsi")), - COMMETHOD([], HRESULT, "SetFileName", - (["in"], LPCWSTR, "pszName")), - COMMETHOD([], HRESULT, "GetFileName", - (["out"], POINTER(LPWSTR), "pszName")), - COMMETHOD([], HRESULT, "SetTitle", - (["in"], LPCWSTR, "pszTitle")), - COMMETHOD([], HRESULT, "SetOkButtonLabel", - (["in"], LPCWSTR, "pszText")), - COMMETHOD([], HRESULT, "SetFileNameLabel", - (["in"], LPCWSTR, "pszLabel")), - COMMETHOD([], HRESULT, "GetResult", - (["out"], POINTER(POINTER(IShellItem)), "ppsi")), - COMMETHOD([], HRESULT, "AddPlace", - (["in"], POINTER(IShellItem), "psi"), - (["in"], c_int, "fdap")), - COMMETHOD([], HRESULT, "SetDefaultExtension", - (["in"], LPCWSTR, "pszDefaultExtension")), - COMMETHOD([], HRESULT, "Close", - (["in"], HRESULT, "hr")), - COMMETHOD([], HRESULT, "SetClientGuid", - (["in"], POINTER(GUID), "guid")), - COMMETHOD([], HRESULT, "ClearClientData"), - COMMETHOD([], HRESULT, "SetFilter", - (["in"], POINTER(comtypes.IUnknown), "pFilter")) # IShellItemFilter - ] - SetFileTypes: Callable[[Union[c_uint, int], c_void_p], int] - SetFileTypeIndex: Callable[[c_uint], int] - GetFileTypeIndex: Callable[[], int] - Advise: Callable[[Union[comtypes.IUnknown, comtypes.COMObject]], int] - Unadvise: Callable[[int], int] - SetOptions: Callable[[Union[int, int]], int] - GetOptions: Callable[[], int] - SetDefaultFolder: Callable[[IShellItem], int] - SetFolder: Callable[[IShellItem], int] - GetFolder: Callable[[], IShellItem] - GetCurrentSelection: Callable[[], IShellItem] - SetFileName: Callable[[str], int] - GetFileName: Callable[[], str] - SetTitle: Callable[[str], int] - SetOkButtonLabel: Callable[[str], int] - SetFileNameLabel: Callable[[str], int] - GetResult: Callable[[], IShellItem] - AddPlace: Callable[[IShellItem, c_int], int] - SetDefaultExtension: Callable[[str], int] - Close: Callable[[HRESULT], int] - SetClientGuid: Callable[[GUID], int] - ClearClientData: Callable[[], int] - SetFilter: Callable[[comtypes.IUnknown], int] - - -class IFileOpenDialog(IFileDialog): - _case_insensitive_: bool = True - _iid_: GUID = IID_IFileOpenDialog - _methods_ = [ - COMMETHOD([], HRESULT, "GetResults", - (["out"], POINTER(POINTER(IShellItemArray)), "ppenum")), - COMMETHOD([], HRESULT, "GetSelectedItems", - (["out"], POINTER(POINTER(IShellItemArray)), "ppsai")) - ] - GetResults: Callable[[], IShellItemArray] - GetSelectedItems: Callable[[], IShellItemArray] - - -class IFileSaveDialog(IFileDialog): - _case_insensitive_: bool = True - _iid_: GUID = IID_IFileSaveDialog - _methods_ = [ - COMMETHOD([], HRESULT, "SetSaveAsItem", - (["in"], POINTER(IShellItem), "psi")), - COMMETHOD([], HRESULT, "SetProperties", - (["in"], POINTER(comtypes.IUnknown), "pStore")), - COMMETHOD([], HRESULT, "SetCollectedProperties", - (["in"], POINTER(comtypes.IUnknown), "pList"), - (["in"], BOOL, "fAppendDefault")), - COMMETHOD([], HRESULT, "GetProperties", - (["out"], POINTER(POINTER(comtypes.IUnknown)), "ppStore")), - COMMETHOD([], HRESULT, "ApplyProperties", - (["in"], POINTER(IShellItem), "psi"), - (["in"], POINTER(comtypes.IUnknown), "pStore"), - (["in"], HWND, "hwnd"), - (["in"], POINTER(comtypes.IUnknown), "pSink")) - ] - SetSaveAsItem: Callable[[IShellItem], int] - SetProperties: Callable[[comtypes.IUnknown], int] - SetCollectedProperties: Callable[[comtypes.IUnknown, BOOL], int] - GetProperties: Callable[[comtypes.IUnknown], int] - ApplyProperties: Callable[[IShellItem, comtypes.IUnknown, HWND, comtypes.IUnknown], int] + +if TYPE_CHECKING: + import os class BaseDialog: @@ -278,14 +73,7 @@ def show(self, host_window, future): class MessageDialog(BaseDialog): - def __init__( - self, - title, - message, - buttons, - icon, - success_result=None, - ): + def __init__(self, title, message, buttons, icon, success_result=None): super().__init__() self.message = message self.title = title @@ -294,12 +82,8 @@ def __init__( self.success_result = success_result def _show(self): - return_value = WinForms.MessageBox.Show( - self.message, - self.title, - self.buttons, - self.icon, - ) + style = self.buttons | self.icon + return_value = MessageBoxW(0, self.message, self.title, style) if self.success_result: self.future.set_result(return_value == self.success_result) else: @@ -308,151 +92,150 @@ def _show(self): class InfoDialog(MessageDialog): def __init__(self, title, message): - super().__init__( - title, - message, - MessageBoxButtons.OK, - MessageBoxIcon.Information, - ) + super().__init__(title, message, MB_OK, MB_ICONASTERISK) class QuestionDialog(MessageDialog): def __init__(self, title, message): - super().__init__( - title, - message, - MessageBoxButtons.YesNo, - MessageBoxIcon.Information, - success_result=DialogResult.Yes, - ) + super().__init__(title, message, MB_YESNO, MB_ICONQUESTION, success_result=IDYES) class ConfirmDialog(MessageDialog): def __init__(self, title, message): - super().__init__( - title, - message, - MessageBoxButtons.OKCancel, - MessageBoxIcon.Warning, - success_result=DialogResult.OK, - ) + super().__init__(title, message, MB_OKCANCEL, MB_ICONEXCLAMATION, success_result=IDOK) class ErrorDialog(MessageDialog): def __init__(self, title, message=None): - super().__init__( - title, - message, - WinForms.MessageBoxButtons.OK, - WinForms.MessageBoxIcon.Error, - ) + super().__init__(title, message, MB_OK, MB_ICONHAND) class StackTraceDialog(BaseDialog): def __init__(self, title, message, content, retry): super().__init__() - - self.native = WinForms.Form() - self.native.MinimizeBox = False - self.native.FormBorderStyle = self.native.FormBorderStyle.FixedSingle - self.native.MaximizeBox = False - self.native.FormClosing += WeakrefCallable(self.winforms_FormClosing) - self.native.Width = 540 - self.native.Height = 320 - self.native.Text = title - - # The top-of-page introductory message - textLabel = WinForms.Label() - textLabel.Left = 10 - textLabel.Top = 10 - textLabel.Width = 520 - textLabel.Alignment = ContentAlignment.MiddleCenter - textLabel.Text = message - - self.native.Controls.Add(textLabel) - - # A scrolling text box for the stack trace. - trace = WinForms.RichTextBox() - trace.Left = 10 - trace.Top = 30 - trace.Width = 504 - trace.Height = 210 - trace.Multiline = True - trace.ReadOnly = True - trace.Font = WinFont( - FontFamily.GenericMonospace, - float(SystemFonts.DefaultFont.Size), - FontStyle.Regular, + self.title = title + self.message = message + self.content = content + self.retry = retry + self.hwnd = None + self.hInstance = windll.kernel32.GetModuleHandleW(None) + self._register_class() + + def _register_class(self): + wnd_class = ctypesWNDCLASS() + wnd_class.cbSize = sizeof(ctypesWNDCLASS) + wnd_class.style = 0 + wnd_class.lpfnWndProc = WNDPROC(self._wnd_proc) + wnd_class.cbClsExtra = 0 + wnd_class.cbWndExtra = 0 + wnd_class.hInstance = self.hInstance + wnd_class.hIcon = windll.user32.LoadIconW(None, c_wchar_p(CursorType.ARROW.value)) # IDI_APPLICATION + wnd_class.hCursor = windll.user32.LoadCursorW(None, c_wchar_p(CursorType.ARROW.value)) # IDC_ARROW + wnd_class.hbrBackground = windll.gdi32.GetStockObject(15) # WHITE_BRUSH + wnd_class.lpszClassName = "StackTraceDialogClass" + wnd_class.hIconSm = windll.user32.LoadIconW(None, c_wchar_p(CursorType.ARROW.value)) # IDI_APPLICATION + + self.class_atom = LPCWSTR(windll.user32.RegisterClassExW(byref(wnd_class))) + + def _create_dialog(self): + self.hwnd = windll.user32.CreateWindowExW( + WS_EX_DLGMODALFRAME, + self.class_atom, + self.title, + WS_OVERLAPPEDWINDOW | WS_VISIBLE, + 100, 100, 540, 320, + None, None, self.hInstance, None + ) + # SetWindowTextW is used to set the window title + windll.user32.SetWindowTextW(self.hwnd, self.title) + + # Create controls + self._create_controls() + + windll.user32.ShowWindow(self.hwnd, SW_SHOW) + windll.user32.UpdateWindow(self.hwnd) + + def _create_controls(self): + # Create the label + hLabel = windll.user32.CreateWindowExW( + 0, "STATIC", self.message, + WS_CHILD | WS_VISIBLE, + 10, 10, 520, 20, + self.hwnd, None, self.hInstance, None ) - trace.Text = content - - self.native.Controls.Add(trace) - - # Add acceptance/close buttons - if retry: - retry = WinForms.Button() - retry.Left = 290 - retry.Top = 250 - retry.Width = 100 - retry.Text = "&Retry" - retry.Click += WeakrefCallable(self.winforms_Click_retry) - - self.native.Controls.Add(retry) - quit = WinForms.Button() - quit.Left = 400 - quit.Top = 250 - quit.Width = 100 - quit.Text = "&Quit" - quit.Click += WeakrefCallable(self.winforms_Click_quit) + # Create the multiline text box for the stack trace + hEdit = windll.user32.CreateWindowExW( + 0, "EDIT", self.content, + WS_CHILD | WS_VISIBLE | ES_MULTILINE | ES_AUTOVSCROLL | ES_READONLY | WS_VSCROLL, + 10, 30, 504, 210, + self.hwnd, None, self.hInstance, None + ) - self.native.Controls.Add(quit) + # Create buttons based on whether retry is needed + if self.retry: + hRetry = windll.user32.CreateWindowExW( + 0, "BUTTON", "&Retry", + WS_CHILD | WS_VISIBLE, + 290, 250, 100, 30, + self.hwnd, IDOK, self.hInstance, None + ) + hQuit = windll.user32.CreateWindowExW( + 0, "BUTTON", "&Quit", + WS_CHILD | WS_VISIBLE, + 400, 250, 100, 30, + self.hwnd, IDCANCEL, self.hInstance, None + ) else: - accept = WinForms.Button() - accept.Left = 400 - accept.Top = 250 - accept.Width = 100 - accept.Text = "&OK" - accept.Click += WeakrefCallable(self.winforms_Click_accept) + hOk = windll.user32.CreateWindowExW( + 0, "BUTTON", "&OK", + WS_CHILD | WS_VISIBLE, + 400, 250, 100, 30, + self.hwnd, IDOK, self.hInstance, None + ) - self.native.Controls.Add(accept) + def _wnd_proc(self, hwnd, msg, wparam, lparam): + if msg == WM_COMMAND: + control_id = wparam & 0xFFFF + if control_id == IDOK: + self._handle_ok() + elif control_id == IDCANCEL: + self._handle_cancel() + windll.user32.DestroyWindow(hwnd) + elif msg == WM_DESTROY: + windll.user32.DestroyWindow(hwnd) + return windll.user32.DefWindowProcW(hwnd, msg, wparam, lparam) def _show(self): - self.native.ShowDialog() - - def winforms_FormClosing(self, sender, event): - # If the close button is pressed, the future won't be done. - # We cancel this event to prevent the dialog from closing. - # If a button is pressed, the future will be set, and a close - # event will be triggered. - if not self.future.done(): - event.Cancel = True # pragma: no cover - - def winforms_Click_quit(self, sender, event): - self.future.set_result(False) - self.native.Close() + self._create_dialog() + # MessageBox to show dialog (optional) + windll.user32.MessageBoxW(self.hwnd, self.message, self.title, MB_OK) + if self.retry: + self.future.set_result(True) + else: + self.future.set_result(None) - def winforms_Click_retry(self, sender, event): + def _handle_ok(self): self.future.set_result(True) - self.native.Close() + windll.user32.DestroyWindow(self.hwnd) - def winforms_Click_accept(self, sender, event): - self.future.set_result(None) - self.native.Close() + def _handle_cancel(self): + self.future.set_result(False) + windll.user32.DestroyWindow(self.hwnd) class FileDialog(BaseDialog): def __init__( self, - native: Union[IFileOpenDialog, IFileSaveDialog], + native: IFileOpenDialog | IFileSaveDialog, title: str, - initial_directory: Union[os.PathLike, str], + initial_directory: os.PathLike | str, *, - filename: Optional[str] = None, - file_types: Optional[List[str]] = None, + filename: str | None = None, + file_types: list[str] | None = None, ): super().__init__() - self.native: Union[IFileOpenDialog, IFileSaveDialog] = native + self.native: IFileOpenDialog | IFileSaveDialog = native self._set_title(title) if filename is not None: @@ -462,7 +245,7 @@ def __init__( self._set_initial_directory(str(initial_directory)) if file_types is not None: - filters: List[Tuple[str, str]] = [ + filters: list[tuple[str, str]] = [ (f"{ext.upper()} files", f"*.{ext}") for ext in file_types ] @@ -483,10 +266,10 @@ def _show(self): else: self.future.set_result(None) - def _set_title(self, title): + def _set_title(self, title: str): self.native.SetTitle(title) - def _set_initial_directory(self, initial_directory): + def _set_initial_directory(self, initial_directory: os.PathLike | str | None): if initial_directory is None: return folder_path: Path = Path(initial_directory).resolve() @@ -510,8 +293,8 @@ def __init__( self, title: str, filename: str, - initial_directory: Union[os.PathLike, str], - file_types: List[str], + initial_directory: os.PathLike | str, + file_types: list[str], ): super().__init__( comtypes.client.CreateObject(CLSID_FileSaveDialog, interface=IFileSaveDialog), @@ -531,8 +314,8 @@ class OpenFileDialog(FileDialog): def __init__( self, title: str, - initial_directory: Union[os.PathLike, str], - file_types: List[str], + initial_directory: os.PathLike | str, + file_types: list[str], multiple_select: bool, ): super().__init__( @@ -548,9 +331,9 @@ def selected_paths(self): # This is a stub method; we provide functionality using the COM API return self._get_filenames() - def _get_filenames(self) -> List[Path]: + def _get_filenames(self) -> list[Path]: assert isinstance(self.native, IFileOpenDialog) - results: List[Path] = [] + results: list[Path] = [] shell_item_array: IShellItemArray = self.native.GetResults() item_count: int = shell_item_array.GetCount() for i in range(item_count): @@ -564,7 +347,7 @@ class SelectFolderDialog(FileDialog): def __init__( self, title: str, - initial_directory: Union[os.PathLike, str], + initial_directory: os.PathLike | str, multiple_select: bool, ): super().__init__( @@ -573,9 +356,9 @@ def __init__( initial_directory, ) self.native.SetOptions(FileOpenOptions.FOS_PICKFOLDERS) - self.multiple_select = multiple_select + self.multiple_select: bool = multiple_select - def _get_filenames(self) -> Union[List[Path], Path]: + def _get_filenames(self) -> list[Path] | Path: shell_item: IShellItem = self.native.GetResult() display_name: str = shell_item.GetDisplayName(0x80058000) # SIGDN_FILESYSPATH return [Path(display_name)] if self.multiple_select else Path(display_name) diff --git a/winforms/src/toga_winforms/libs/py_wrappers/__init__.py b/winforms/src/toga_winforms/libs/py_wrappers/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/winforms/src/toga_winforms/libs/py_wrappers/com/__init__.py b/winforms/src/toga_winforms/libs/py_wrappers/com/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/winforms/src/toga_winforms/libs/py_wrappers/com/com_helpers.py b/winforms/src/toga_winforms/libs/py_wrappers/com/com_helpers.py new file mode 100644 index 0000000000..56a697a951 --- /dev/null +++ b/winforms/src/toga_winforms/libs/py_wrappers/com/com_helpers.py @@ -0,0 +1,163 @@ +from __future__ import annotations + +from contextlib import contextmanager +from ctypes import POINTER, PyDLL, byref, c_void_p, py_object +from ctypes.wintypes import BOOL +from typing import TYPE_CHECKING + +from comtypes import COMObject + +from toga_winforms.libs.win_wrappers.hresult import HRESULT + +if TYPE_CHECKING: + from ctypes import _NamedFuncPointer + + from _win32typing import ( # pyright: ignore[reportMissingModuleSource] + PyIBindCtx, + PyIUnknown, + ) + from comtypes import IUnknown + + +@contextmanager +def HandleCOMCall(action_desc: str = "Unspecified COM function"): + """ + Context manager for handling COM function calls. + + This function facilitates the execution of COM calls by providing a mechanism to manage errors effectively. + It yields a callable that checks the result of the COM operation and raises exceptions if the result + indicates an error. + + Args: + action_desc (str): A description of the COM function being called. Defaults to "Unspecified COM function". + + Returns: + Callable: A function that takes an HRESULT value and raises an exception if the value indicates an error. + + Raises: + HRESULT: If an error occurs during the COM call, an HRESULT exception is raised with a descriptive message. + + Examples: + with HandleCOMCall("My COM Function") as check_hr: + hr = some_com_function() + check_hr(hr) + """ + + print(f"Attempt to call COM func {action_desc}") + try: + from comtypes import ( + COMError, # pyright: ignore[reportMissingTypeStubs, reportMissingModuleSource] + ) + except ImportError: + COMError = OSError + future_error_msg = f"An error has occurred in win32 COM function '{action_desc}'" + try: # sourcery skip: raise-from-previous-error + # Yield back a callable function that will raise if hr is nonzero. + yield lambda hr: HRESULT(hr).raise_for_status(hr, future_error_msg) and hr or hr + except (COMError, OSError) as e: + errcode = getattr(e, "winerror", getattr(e, "hresult", None)) + if errcode is None: + raise + raise HRESULT(errcode).exception(future_error_msg) # noqa: B904 # pyright: ignore[reportAttributeAccessIssue] + + +def comtypes2pywin( + ptr: COMObject, + interface: type[IUnknown] | None = None, +) -> PyIUnknown: + """Convert a comtypes pointer 'ptr' into a pythoncom + PyI object. + + 'interface' specifies the interface we want; it must be a comtypes + interface class. The interface must be implemented by the object; + and the interface must be known to pythoncom. + + If 'interface' is specified, comtypes.IUnknown is used. + """ + import comtypes # pyright: ignore[reportMissingTypeStubs, reportMissingModuleSource] + import pythoncom + if interface is None: + interface = comtypes.IUnknown + # ripped from + # https://github.com/enthought/comtypes/blob/main/comtypes/test/test_win32com_interop.py + # We use the PyCom_PyObjectFromIUnknown function in pythoncomxxx.dll to + # convert a comtypes COM pointer into a pythoncom COM pointer. + # This is the C prototype; we must pass 'True' as third argument: + # PyObject *PyCom_PyObjectFromIUnknown(IUnknown *punk, REFIID riid, BOOL bAddRef) + _PyCom_PyObjectFromIUnknown: _NamedFuncPointer = PyDLL(pythoncom.__file__).PyCom_PyObjectFromIUnknown + _PyCom_PyObjectFromIUnknown.restype = py_object + _PyCom_PyObjectFromIUnknown.argtypes = (POINTER(comtypes.IUnknown), c_void_p, BOOL) + return _PyCom_PyObjectFromIUnknown(ptr, byref(interface._iid_), True) # noqa: FBT003, SLF001 + + +def register_idispatch_object( + com_object: COMObject, + name: str, + interface: type[IUnknown] | None = None, +) -> PyIBindCtx: + import pythoncom + ctx: PyIBindCtx = pythoncom.CreateBindCtx() + py_data: PyIUnknown = comtypes2pywin(com_object, interface) + ctx.RegisterObjectParam(name, py_data) + return ctx + + +if __name__ == "__main__": + # Small test. + from ctypes.wintypes import WIN32_FIND_DATAW + from typing import TYPE_CHECKING, ClassVar, Sequence + + import comtypes + from comtypes import GUID + + from toga_winforms.libs.win_wrappers.com.interfaces import SIGDN + from toga_winforms.libs.win_wrappers.hresult import S_OK + try: + from win32com.shell import shell # pyright: ignore[reportMissingModuleSource] + except ModuleNotFoundError: + raise RuntimeError("Small test requires `pip install pipwin32`") + + if TYPE_CHECKING: + from ctypes import _CArgObject, _Pointer + + from _win32typing import ( + PyIShellItem, # pyright: ignore[reportMissingModuleSource] + ) + from comtypes._memberspec import ( + _ComMemberSpec, # pyright: ignore[reportMissingTypeStubs] + ) + from typing_extensions import Self + IID_IFileSystemBindData = GUID("{01e18d10-4d8b-11d2-855d-006008059367}") + + class IFileSystemBindData(comtypes.IUnknown): + """The IFileSystemBindData interface + https://learn.microsoft.com/en-us/windows/win32/api/shobjidl_core/nn-shobjidl_core-ifilesystembinddata. + """ + _iid_ = IID_IFileSystemBindData + _methods_: ClassVar[list[_ComMemberSpec]] = [ + comtypes.COMMETHOD([], HRESULT, "SetFindData", + (["in"], POINTER(WIN32_FIND_DATAW), "pfd")), + comtypes.COMMETHOD([], HRESULT, "GetFindData", + (["out"], POINTER(WIN32_FIND_DATAW), "pfd")) + ] + + class FileSystemBindData(comtypes.COMObject): + """Implements the IFileSystemBindData interface: + https://learn.microsoft.com/en-us/windows/win32/api/shobjidl_core/nn-shobjidl_core-ifilesystembinddata. + """ + _com_interfaces_: Sequence[type[comtypes.IUnknown]] = [IFileSystemBindData] + + def IFileSystemBindData_SetFindData(self: Self, this: Self, pfd: _Pointer | _CArgObject) -> HRESULT: + self.pfd: _Pointer = pfd # pyright: ignore[reportAttributeAccessIssue] + return S_OK + + def IFileSystemBindData_GetFindData(self: Self, this: Self, pfd: _Pointer | _CArgObject) -> HRESULT: + return S_OK + find_data = WIN32_FIND_DATAW() # from wintypes + bind_data: FileSystemBindData = FileSystemBindData() # pyright: ignore[reportAssignmentType] + bind_data.IFileSystemBindData_SetFindData(bind_data, byref(find_data)) + ctx: PyIBindCtx = register_idispatch_object(bind_data, "File System Bind Data") + + item: PyIShellItem = shell.SHCreateItemFromParsingName( + r"Z:\blah\blah", ctx, shell.IID_IShellItem2) + print(item.GetDisplayName(SIGDN.SIGDN_DESKTOPABSOLUTEPARSING)) # prints Z:\blah\blah diff --git a/winforms/src/toga_winforms/libs/py_wrappers/com/com_types.py b/winforms/src/toga_winforms/libs/py_wrappers/com/com_types.py new file mode 100644 index 0000000000..8b13789179 --- /dev/null +++ b/winforms/src/toga_winforms/libs/py_wrappers/com/com_types.py @@ -0,0 +1 @@ + diff --git a/winforms/src/toga_winforms/libs/py_wrappers/com/interfaces.py b/winforms/src/toga_winforms/libs/py_wrappers/com/interfaces.py new file mode 100644 index 0000000000..bf87d4dbb6 --- /dev/null +++ b/winforms/src/toga_winforms/libs/py_wrappers/com/interfaces.py @@ -0,0 +1,1401 @@ +from __future__ import annotations + +from ctypes import ( + POINTER, + Structure, + byref, + c_bool, + c_char_p, + c_int, + c_uint, + c_ulong, + c_void_p, + c_wchar_p, + windll, +) +from ctypes import POINTER as C_POINTER +from ctypes.wintypes import BOOL, DWORD, HWND, LPCWSTR, LPWSTR, ULONG +from enum import IntFlag +from typing import TYPE_CHECKING, Callable, ClassVar, Sequence + +import comtypes # pyright: ignore[reportMissingTypeStubs] +from comtypes import ( + COMMETHOD, # pyright: ignore[reportMissingTypeStubs] + GUID, +) + +from toga_winforms.libs.win_wrappers.hresult import ( # pyright: ignore[reportMissingTypeStubs] + HRESULT, + S_OK, +) + +if TYPE_CHECKING: + from ctypes import Array, _CData, _FuncPointer, _Pointer + + from comtypes._memberspec import ( + _ComMemberSpec, # pyright: ignore[reportMissingTypeStubs] + ) + +# GUID Definitions +IID_IUnknown = GUID("{00000000-0000-0000-C000-000000000046}") +IID_IDispatch = GUID("{00020400-0000-0000-C000-000000000046}") +IID_IClassFactory = GUID("{00000001-0000-0000-C000-000000000046}") +IID_IStream = GUID("{0000000c-0000-0000-C000-000000000046}") +IID_IStorage = GUID("{0000000b-0000-0000-C000-000000000046}") +IID_IBindCtx = GUID("{0000000e-0000-0000-C000-000000000046}") +IID_IEnumShellItems = GUID("{70629033-E363-4A28-A567-0DB78006E6D7}") +IID_IContextMenu = GUID("{000214e4-0000-0000-c000-000000000046}") +IID_IContextMenu2 = GUID("{000214f4-0000-0000-c000-000000000046}") +IID_IContextMenu3 = GUID("{bcfce0a0-ec17-11d0-8d10-00a0c90f2719}") +IID_IShellFolder = GUID("{000214E6-0000-0000-C000-000000000046}") +IID_IShellFolder2 = GUID("{93F2F68C-1D1B-11D3-A30E-00C04F79ABD1}") +IID_IShellItem = GUID("{43826D1E-E718-42EE-BC55-A1E261C37BFE}") +IID_IShellItem2 = GUID("{7E9FB0D3-919F-4307-AB2E-9B1860310C93}") +IID_IShellLibrary = GUID("{11A66EFA-382E-451A-9234-1E0E12EF3085}") +IID_IShellItemArray = GUID("{B63EA76D-1F85-456F-A19C-48159EFA858B}") +IID_IShellItemFilter = GUID("{2659B475-EEB8-48B7-8F07-B378810F48CF}") +IID_IShellView = GUID("{000214e3-0000-0000-c000-000000000046}") +IID_IModalWindow = GUID("{B4DB1657-70D7-485E-8E3E-6FCB5A5C1802}") +IID_IFileDialog = GUID("{42F85136-DB7E-439C-85F1-E4075D135FC8}") +IID_IFileDialog2 = GUID("{61744FC7-85B5-4791-A9B0-272276309B13}") +IID_IFileSaveDialog = GUID("{84BCCD23-5FDE-4CDB-AEA4-AF64B83D78AB}") +IID_IFileSaveDialogOld = GUID("{2804B74C-AC16-4398-9DC0-DB83F5B7ED14}") +IID_IFileSaveDialogPrivate = GUID("{6CB95A6A-88B6-4DC4-B3EA-3A776D1E8EFF}") +IID_IFileOpenDialog = GUID("{D57C7288-D4AD-4768-BE02-9D969532D960}") +IID_IFileDialogEvents = GUID("{973510DB-7D7F-452B-8975-74A85828D354}") +IID_FileDialogPermissionAttribute = GUID("{0CCCA629-440F-313E-96CD-BA1B4B4997F7}") +IID_FileDialogPermission = GUID("{A8B7138C-8932-3D78-A585-A91569C743AC}") +IID_IFileDialogPrivate = GUID("{9EA5491C-89C8-4BEF-93D3-7F665FB82A33}") +IID_IFileDialogCustomize = GUID("{E6FDD21A-163F-4975-9C8C-A69F1BA37034}") +IID_IFileDialogEventsPrivate = GUID("{050E9E69-BAEA-4C08-AD6A-61666DD32E96}") +IID_IFileDialogControlEvents = GUID("{36116642-D713-4B97-9B83-7484A9D00433}") +IID_IFileDialogResultHandler = GUID("{42841501-194F-478F-9B4C-78985419DA53}") +IID_IShellLink = GUID("{000214f9-0000-0000-c000-000000000046}") +IID_IShellLinkDataList = GUID("{45E2B4AE-B1C3-11D0-BA91-00C04FD7A083}") +IID_IPropertyStore = GUID("{886D8EEB-8CF2-4446-8D02-CDBA1DBDCF99}") +IID_IFileOperationProgressSink = GUID("{04B0F1A7-9490-44BC-96E1-4296A31252E2}") +IID_IFileOperation = GUID("{94EA2B94-E9CC-49E0-C0E3-D20A7D91AA98}") +CLSID_FileOperation = GUID("{3AD05575-8857-4850-9277-11B85BDB8E09}") +CLSID_FileDialog = GUID("{3D9C8F03-50D4-4E40-BB11-70E74D3F10F3}") +CLSID_FileOpenDialog = GUID("{DC1C5A9C-E88A-4dde-A5A1-60F82A20AEF7}") +CLSID_FileOpenDialogLegacy = GUID("{725F645B-EAED-4fc5-B1C5-D9AD0ACCBA5E}") +CLSID_FileSaveDialog = GUID("{C0B4E2F3-BA21-4773-8DBA-335EC946EB8B}") +CLSID_FileSaveDialogLegacy = GUID("{AF02484C-A0A9-4669-9051-058AB12B9195}") +CLSID_ShellItemArrayShellNamespacehelper = GUID("{26671179-2ec2-42bf-93d3-64108589cad5}") +CLSID_ShellItemArrayShellNamespacehelper = GUID("{b77b1cbf-e827-44a9-a33a-6ccfeeaa142a}") # redef?? +CLSID_ShellItemArrayShellNamespacehelper = GUID("{CDC82860-468D-4d4e-B7E7-C298FF23AB2C}") # redef?? +CLSID_ShellItemArrayShellNamespacehelper = GUID("{F6166DAD-D3BE-4ebd-8419-9B5EAD8D0EC7}") # redef?? +CLSID_ShellLibraryAPI = GUID("{d9b3211d-e57f-4426-aaef-30a806add397}") +CLSID_ShellFileSystemFolder = GUID("{F3364BA0-65B9-11CE-A9BA-00AA004AE837}") +CLSID_ShellBindStatusCallbackProxy = GUID("{2B4F54B1-3D6D-11d0-8258-00C04FD5AE38}") +CLSID_ShellURL = GUID("{4bec2015-bfa1-42fa-9c0c-59431bbe880e}") +CLSID_ShellDropTarget = GUID("{4bf684f8-3d29-4403-810d-494e72c4291b}") +CLSID_ShellNameSpace = GUID("{55136805-B2DE-11D1-B9F2-00A0C98BC547}") + + +# Constants +class FileOpenOptions(IntFlag): + FOS_UNKNOWN1 = 0x00000001 + FOS_OVERWRITEPROMPT = 0x00000002 + FOS_STRICTFILETYPES = 0x00000004 + FOS_NOCHANGEDIR = 0x00000008 + FOS_UNKNOWN2 = 0x00000010 + FOS_PICKFOLDERS = 0x00000020 + FOS_FORCEFILESYSTEM = 0x00000040 + FOS_ALLNONSTORAGEITEMS = 0x00000080 + FOS_NOVALIDATE = 0x00000100 + FOS_ALLOWMULTISELECT = 0x00000200 + FOS_UNKNOWN4 = 0x00000400 + FOS_PATHMUSTEXIST = 0x00000800 + FOS_FILEMUSTEXIST = 0x00001000 + FOS_CREATEPROMPT = 0x00002000 + FOS_SHAREAWARE = 0x00004000 + FOS_NOREADONLYRETURN = 0x00008000 + FOS_NOTESTFILECREATE = 0x00010000 + FOS_HIDEMRUPLACES = 0x00020000 + FOS_HIDEPINNEDPLACES = 0x00040000 + FOS_UNKNOWN5 = 0x00080000 + FOS_NODEREFERENCELINKS = 0x00100000 + FOS_UNKNOWN6 = 0x00200000 + FOS_UNKNOWN7 = 0x00400000 + FOS_UNKNOWN8 = 0x00800000 + FOS_UNKNOWN9 = 0x01000000 + FOS_DONTADDTORECENT = 0x02000000 + FOS_UNKNOWN10 = 0x04000000 + FOS_UNKNOWN11 = 0x08000000 + FOS_FORCESHOWHIDDEN = 0x10000000 + FOS_DEFAULTNOMINIMODE = 0x20000000 + FOS_FORCEPREVIEWPANEON = 0x40000000 + FOS_UNKNOWN12 = 0x80000000 + + +# Shell Folder Get Attributes Options +SFGAOF = c_ulong + + +class SFGAO(IntFlag): + SFGAO_CANCOPY = 0x00000001 # Objects can be copied. + SFGAO_CANMOVE = 0x00000002 # Objects can be moved. + SFGAO_CANLINK = 0x00000004 # Objects can be linked. + SFGAO_STORAGE = 0x00000008 # Objects can be stored. + SFGAO_CANRENAME = 0x00000010 # Objects can be renamed. + SFGAO_CANDELETE = 0x00000020 # Objects can be deleted. + SFGAO_HASPROPSHEET = 0x00000040 # Objects have property sheets. + SFGAO_DROPTARGET = 0x00000100 # Objects are drop targets. + SFGAO_CAPABILITYMASK = 0x00000177 # Mask for all capability flags. + SFGAO_ENCRYPTED = 0x00002000 # Object is encrypted (use alt color). + SFGAO_ISSLOW = 0x00004000 # Accessing this object is slow. + SFGAO_GHOSTED = 0x00008000 # Object is ghosted (dimmed). + SFGAO_LINK = 0x00010000 # Shortcut (link). + SFGAO_SHARE = 0x00020000 # Shared. + SFGAO_READONLY = 0x00040000 # Read-only. + SFGAO_HIDDEN = 0x00080000 # Hidden object. + SFGAO_DISPLAYATTRMASK = 0x000FC000 # Mask for display attributes. + SFGAO_FILESYSANCESTOR = 0x10000000 # May contain children with file system folders. + SFGAO_FOLDER = 0x20000000 # Is a folder. + SFGAO_FILESYSTEM = 0x40000000 # Is part of the file system. + SFGAO_HASSUBFOLDER = 0x80000000 # May contain subfolders. + SFGAO_CONTENTSMASK = 0x80000000 # Mask for contents. + SFGAO_VALIDATE = 0x01000000 # Invalidate cached information. + SFGAO_REMOVABLE = 0x02000000 # Is a removable media. + SFGAO_COMPRESSED = 0x04000000 # Object is compressed. + SFGAO_BROWSABLE = 0x08000000 # Supports browsing. + SFGAO_NONENUMERATED = 0x00100000 # Is not enumerated. + SFGAO_NEWCONTENT = 0x00200000 # New content is present. + SFGAO_CANMONIKER = 0x00400000 # Can create monikers for this item. + SFGAO_HASSTORAGE = 0x00400000 # Supports storage interfaces. + SFGAO_STREAM = 0x00400000 # Is a stream object. + SFGAO_STORAGEANCESTOR = 0x00800000 # May contain children with storage folders. + SFGAO_STORAGECAPMASK = 0x70C50008 # Mask for storage capability attributes. + SFGAO_PKEYSFGAOMASK = 0x81044000 # Attributes that are part of the PKEY_SFGAOFlags property. + + +class SIGDN(c_int): + SIGDN_NORMALDISPLAY = 0x00000000 + SIGDN_PARENTRELATIVEPARSING = 0x80018001 + SIGDN_PARENTRELATIVEFORADDRESSBAR = 0x8001C001 + SIGDN_DESKTOPABSOLUTEPARSING = 0x80028000 + SIGDN_PARENTRELATIVEEDITING = 0x80031001 + SIGDN_DESKTOPABSOLUTEEDITING = 0x8004C000 + SIGDN_FILESYSPATH = 0x80058000 + SIGDN_URL = 0x80068000 + + +class FDAP(c_int): + FDAP_BOTTOM = 0x00000000 + FDAP_TOP = 0x00000001 + + +class FDE_SHAREVIOLATION_RESPONSE(c_int): # noqa: N801 + FDESVR_DEFAULT = 0x00000000 + FDESVR_ACCEPT = 0x00000001 + FDESVR_REFUSE = 0x00000002 + + +FDE_OVERWRITE_RESPONSE = FDE_SHAREVIOLATION_RESPONSE + + +class COMFunctionPointers: + def __init__(self): + self.hOle32: _FuncPointer + self.hShell32: _FuncPointer + self.pCoInitialize: _FuncPointer + self.pCoUninitialize: _FuncPointer + self.pCoCreateInstance: _FuncPointer + self.pCoTaskMemFree: _FuncPointer + self.pSHCreateItemFromParsingName: _FuncPointer + + @staticmethod + def load_library(dll_name: str) -> _FuncPointer: + windll.kernel32.LoadLibraryW.argtypes = [LPCWSTR] + windll.kernel32.LoadLibraryW.restype = c_void_p + handle = windll.kernel32.LoadLibraryW(dll_name) + if not handle: + raise ValueError(f"Unable to load library: {dll_name}") + return handle + + @staticmethod + def resolve_function(handle: _FuncPointer, func: bytes, func_type: type[_FuncPointer]) -> _FuncPointer: + windll.kernel32.GetProcAddress.argtypes = [c_void_p, c_char_p] + windll.kernel32.GetProcAddress.restype = c_void_p + address = windll.kernel32.GetProcAddress(handle, func) + assert address is not None + return func_type(address) + + +class COMDLG_FILTERSPEC(Structure): # noqa: N801 + _fields_: Sequence[tuple[str, type[_CData]] | tuple[str, type[_CData], int]] = [ + ("pszName", LPCWSTR), + ("pszSpec", LPCWSTR) + ] + + +class IModalWindow(comtypes.IUnknown): + _case_insensitive_: bool = True + _iid_: GUID = IID_IModalWindow + _methods_: ClassVar[list[_ComMemberSpec]] = [ + COMMETHOD([], HRESULT, "Show", + (["in"], HWND, "hwndParent")) + ] + Show: Callable[[int | HWND], HRESULT] + + +class ModalWindow(comtypes.COMObject): + _com_interfaces_: Sequence[type[comtypes.IUnknown]] = [IModalWindow] + def QueryInterface(self, riid: GUID, ppvObject: comtypes.IUnknown) -> HRESULT: # noqa: N803 + return S_OK + def AddRef(self) -> ULONG: + return ULONG(-1) + def Release(self) -> ULONG: + return ULONG(-1) + def Show(self, hwndParent: HWND | int) -> HRESULT: # noqa: N803 + return S_OK + + +class IShellItem(comtypes.IUnknown): + _case_insensitive_: bool = True + _iid_: GUID = IID_IShellItem + _methods_: ClassVar[list[_ComMemberSpec]] = [ + COMMETHOD([], HRESULT, "BindToHandler", + (["in"], POINTER(comtypes.IUnknown), "pbc"), + (["in"], POINTER(GUID), "bhid"), + (["in"], POINTER(GUID), "riid"), + (["out"], POINTER(c_void_p), "ppv")), + COMMETHOD([], HRESULT, "GetParent", + (["out"], POINTER(POINTER(comtypes.IUnknown)), "ppsi")), + COMMETHOD([], HRESULT, "GetDisplayName", + (["in"], c_ulong, "sigdnName"), + (["out"], POINTER(LPWSTR), "ppszName")), + COMMETHOD([], HRESULT, "GetAttributes", + (["in"], c_ulong, "sfgaoMask"), + (["out"], POINTER(c_ulong), "psfgaoAttribs")), + COMMETHOD([], HRESULT, "Compare", + (["in"], POINTER(comtypes.IUnknown), "psi"), + (["in"], c_ulong, "hint"), + (["out"], POINTER(c_int), "piOrder")) + ] + QueryInterface: Callable[[GUID, _Pointer[_Pointer[comtypes.IUnknown]]], HRESULT] + AddRef: Callable[[], ULONG] + Release: Callable[[], ULONG] + BindToHandler: Callable[[_Pointer[comtypes.IUnknown], GUID, GUID, _Pointer[c_void_p]], HRESULT] + GetParent: Callable[[], comtypes.IUnknown] + GetDisplayName: Callable[[c_ulong | int], str] + GetAttributes: Callable[[c_ulong | int], int] + Compare: Callable[[_Pointer[comtypes.IUnknown], c_ulong, c_int], HRESULT] + + +class ShellItem(comtypes.COMObject): + _com_interfaces_: Sequence[type[comtypes.IUnknown]] = [IShellItem] + def QueryInterface(self, riid: GUID, ppvObject: comtypes.IUnknown) -> HRESULT: # noqa: N803 + return S_OK + def AddRef(self) -> ULONG: + return ULONG(-1) + def Release(self) -> ULONG: + return ULONG(-1) + def BindToHandler(self, pbc: _Pointer[comtypes.IUnknown], bhid: GUID, riid: GUID, ppv: _Pointer[c_void_p]) -> HRESULT: + return S_OK + def GetParent(self, ppsi: comtypes.IUnknown) -> HRESULT: + return S_OK + def GetDisplayName(self, sigdnName: c_ulong | int, ppszName: _Pointer[c_wchar_p]) -> HRESULT: # noqa: N803 + return S_OK + def GetAttributes(self, sfgaoMask: c_ulong | int, psfgaoAttribs: _Pointer[c_ulong]) -> HRESULT: # noqa: N803 + return S_OK + def Compare(self, psi: _Pointer[comtypes.IUnknown], hint: c_ulong | int, piOrder: c_int) -> HRESULT: # noqa: N803 + return S_OK + + +SHCreateItemFromParsingName = windll.shell32.SHCreateItemFromParsingName +SHCreateItemFromParsingName.argtypes = [LPCWSTR, comtypes.POINTER(comtypes.IUnknown), comtypes.POINTER(POINTER(IShellItem))] +SHCreateItemFromParsingName.restype = HRESULT + +def create_shell_item_from_path(path: str) -> _Pointer[IShellItem]: + item = POINTER(IShellItem)() + hr = SHCreateItemFromParsingName(path, None, byref(GUID("{00000000-0000-0000-C000-000000000046}")), byref(item)) + if hr != 0: + raise OSError(f"SHCreateItemFromParsingName failed! HRESULT: {hr}") + return item + + +class IContextMenu(comtypes.IUnknown): + _case_insensitive_: bool = True + _iid_: GUID = IID_IContextMenu + _methods_: ClassVar[list[_ComMemberSpec]] = [ + COMMETHOD([], HRESULT, "QueryContextMenu", + (["in"], c_void_p, "hmenu"), + (["in"], c_uint, "indexMenu"), + (["in"], c_uint, "idCmdFirst"), + (["in"], c_uint, "idCmdLast"), + (["in"], c_uint, "uFlags")), + COMMETHOD([], HRESULT, "InvokeCommand", + (["in"], c_void_p, "pici")), + COMMETHOD([], HRESULT, "GetCommandString", + (["in"], c_uint, "idCmd"), + (["in"], c_uint, "uType"), + (["in"], c_void_p, "pReserved"), + (["out"], c_wchar_p, "pszName"), + (["in"], c_uint, "cchMax")) + ] + QueryInterface: Callable[[GUID, _Pointer[_Pointer[comtypes.IUnknown]]], HRESULT] + AddRef: Callable[[], ULONG] + Release: Callable[[], ULONG] + QueryContextMenu: Callable[[c_void_p, c_uint, c_uint, c_uint, c_uint], HRESULT] + InvokeCommand: Callable[[c_void_p], HRESULT] + GetCommandString: Callable[[c_uint, c_uint, c_void_p, _Pointer[c_wchar_p], c_uint], HRESULT] + + +class ContextMenu(comtypes.COMObject): + _com_interfaces_: Sequence[type[comtypes.IUnknown]] = [IContextMenu] + def QueryInterface(self, riid: GUID, ppvObject: comtypes.IUnknown) -> HRESULT: # noqa: N803 + return S_OK + def AddRef(self) -> ULONG: + return ULONG(-1) + def Release(self) -> ULONG: + return ULONG(-1) + def QueryContextMenu(self, hmenu: c_void_p, indexMenu: c_uint | int, idCmdFirst: c_uint | int, idCmdLast: c_uint | int, uFlags: c_uint | int) -> HRESULT: # noqa: N803 + return S_OK + def InvokeCommand(self, pici: c_void_p) -> HRESULT: + return S_OK + def GetCommandString(self, idCmd: c_uint | int, uType: c_uint | int, pReserved: c_void_p, pszName: c_wchar_p, cchMax: c_uint | int) -> HRESULT: # noqa: N803 + return S_OK + + +class IShellFolder(comtypes.IUnknown): + _case_insensitive_: bool = True + _iid_: GUID = IID_IShellFolder + _methods_: ClassVar[list[_ComMemberSpec]] = [ + COMMETHOD([], HRESULT, "ParseDisplayName", + (["in"], HWND, "hwnd"), + (["in"], POINTER(comtypes.IUnknown), "pbc"), + (["in"], LPCWSTR, "pszDisplayName"), + (["out"], POINTER(ULONG), "pchEaten"), + (["out"], POINTER(c_void_p), "ppidl"), + (["in"], POINTER(ULONG), "pdwAttributes")), + COMMETHOD([], HRESULT, "EnumObjects", + (["in"], HWND, "hwnd"), + (["in"], c_ulong, "grfFlags"), + (["out"], POINTER(POINTER(comtypes.IUnknown)), "ppenumIDList")), + COMMETHOD([], HRESULT, "BindToObject", + (["in"], c_void_p, "pidl"), + (["in"], POINTER(comtypes.IUnknown), "pbc"), + (["in"], POINTER(GUID), "riid"), + (["out"], POINTER(c_void_p), "ppv")), + COMMETHOD([], HRESULT, "BindToStorage", + (["in"], c_void_p, "pidl"), + (["in"], POINTER(comtypes.IUnknown), "pbc"), + (["in"], POINTER(GUID), "riid"), + (["out"], POINTER(c_void_p), "ppv")), + COMMETHOD([], HRESULT, "CompareIDs", + (["in"], c_void_p, "lParam"), + (["in"], c_void_p, "pidl1"), + (["in"], c_void_p, "pidl2")), + COMMETHOD([], HRESULT, "CreateViewObject", + (["in"], HWND, "hwndOwner"), + (["in"], POINTER(GUID), "riid"), + (["out"], POINTER(c_void_p), "ppv")), + COMMETHOD([], HRESULT, "GetAttributesOf", + (["in"], c_uint, "cidl"), + (["in"], C_POINTER(c_void_p), "apidl"), + (["out"], POINTER(c_ulong), "rgfInOut")), + COMMETHOD([], HRESULT, "GetUIObjectOf", + (["in"], HWND, "hwndOwner"), + (["in"], c_uint, "cidl"), + (["in"], C_POINTER(c_void_p), "apidl"), + (["in"], POINTER(GUID), "riid"), + (["in"], POINTER(c_uint), "rgfReserved"), + (["out"], POINTER(c_void_p), "ppv")), + COMMETHOD([], HRESULT, "GetDisplayNameOf", + (["in"], c_void_p, "pidl"), + (["in"], c_ulong, "uFlags"), + (["out"], POINTER(c_wchar_p), "pName")), + COMMETHOD([], HRESULT, "SetNameOf", + (["in"], HWND, "hwnd"), + (["in"], c_void_p, "pidl"), + (["in"], LPCWSTR, "pszName"), + (["in"], c_ulong, "uFlags"), + (["out"], POINTER(c_void_p), "ppidlOut")) + ] + QueryInterface: Callable[[GUID, _Pointer[_Pointer[comtypes.IUnknown]]], HRESULT] + AddRef: Callable[[], ULONG] + Release: Callable[[], ULONG] + ParseDisplayName: Callable[[HWND, _Pointer[comtypes.IUnknown], LPCWSTR, _Pointer[ULONG], _Pointer[c_void_p], _Pointer[ULONG]], HRESULT] + EnumObjects: Callable[[HWND, c_ulong, _Pointer[_Pointer[comtypes.IUnknown]]], HRESULT] + BindToObject: Callable[[c_void_p, _Pointer[comtypes.IUnknown], GUID, _Pointer[c_void_p]], HRESULT] + BindToStorage: Callable[[c_void_p, _Pointer[comtypes.IUnknown], GUID, _Pointer[c_void_p]], HRESULT] + CompareIDs: Callable[[c_void_p, c_void_p, c_void_p], HRESULT] + CreateViewObject: Callable[[HWND, GUID, _Pointer[c_void_p]], HRESULT] + GetAttributesOf: Callable[[c_uint, _Pointer[c_void_p], _Pointer[c_ulong]], HRESULT] + GetUIObjectOf: Callable[[HWND, c_uint, _Pointer[c_void_p], GUID, _Pointer[c_uint], _Pointer[c_void_p]], HRESULT] + GetDisplayNameOf: Callable[[c_void_p, c_ulong, _Pointer[c_wchar_p]], HRESULT] + SetNameOf: Callable[[HWND, c_void_p, LPCWSTR, c_ulong, _Pointer[c_void_p]], HRESULT] +class ShellFolder(comtypes.COMObject): + _com_interfaces_: Sequence[type[comtypes.IUnknown]] = [IShellFolder] + def QueryInterface(self, riid: GUID, ppvObject: comtypes.IUnknown) -> HRESULT: # noqa: N803 + return S_OK + def AddRef(self) -> ULONG: + return ULONG(-1) + def Release(self) -> ULONG: + return ULONG(-1) + def ParseDisplayName(self, hwnd: HWND | int, pbc: _Pointer[comtypes.IUnknown], pszDisplayName: LPCWSTR | str, pchEaten: _Pointer[ULONG], ppidl: _Pointer[c_void_p], pdwAttributes: _Pointer[ULONG]) -> HRESULT: # noqa: N803, PLR0913 + return S_OK + def EnumObjects(self, hwnd: HWND | int, grfFlags: c_ulong | int, ppenumIDList: comtypes.IUnknown) -> HRESULT: # noqa: N803 + return S_OK + def BindToObject(self, pidl: c_void_p, pbc: _Pointer[comtypes.IUnknown], riid: GUID, ppv: _Pointer[c_void_p]) -> HRESULT: + return S_OK + def BindToStorage(self, pidl: c_void_p, pbc: _Pointer[comtypes.IUnknown], riid: GUID, ppv: _Pointer[c_void_p]) -> HRESULT: + return S_OK + def CompareIDs(self, lParam: c_void_p, pidl1: c_void_p, pidl2: c_void_p) -> HRESULT: # noqa: N803 + return S_OK + def CreateViewObject(self, hwndOwner: HWND | int, riid: GUID, ppv: _Pointer[c_void_p]) -> HRESULT: # noqa: N803 + return S_OK + def GetAttributesOf(self, cidl: c_uint | int, apidl: _Pointer[c_void_p], rgfInOut: _Pointer[c_ulong]) -> HRESULT: # noqa: N803 + return S_OK + def GetUIObjectOf(self, hwndOwner: HWND | int, cidl: c_uint | int, apidl: _Pointer[c_void_p], riid: GUID, rgfReserved: _Pointer[c_uint], ppv: _Pointer[c_void_p]) -> HRESULT: # noqa: N803, PLR0913, E501 + return S_OK + def GetDisplayNameOf(self, pidl: c_void_p, uFlags: c_ulong | int, pName: _Pointer[c_wchar_p]) -> HRESULT: # noqa: N803 + return S_OK + def SetNameOf(self, hwnd: HWND | int, pidl: c_void_p, pszName: LPCWSTR | str, uFlags: c_ulong | int, ppidlOut: _Pointer[c_void_p]) -> HRESULT: # noqa: N803 + return S_OK + + +class IShellItemArray(comtypes.IUnknown): + _case_insensitive_: bool = True + _iid_: GUID = IID_IShellItemArray + _methods_: ClassVar[list[_ComMemberSpec]] = [ + COMMETHOD([], HRESULT, "BindToHandler", + (["in"], POINTER(comtypes.IUnknown), "pbc"), + (["in"], POINTER(GUID), "bhid"), + (["in"], POINTER(GUID), "riid"), + (["out"], POINTER(c_void_p), "ppv")), + COMMETHOD([], HRESULT, "GetPropertyStore", + (["in"], c_ulong, "flags"), + (["in"], POINTER(GUID), "riid"), + (["out"], POINTER(c_void_p), "ppv")), + COMMETHOD([], HRESULT, "GetPropertyDescriptionList", + (["in"], POINTER(GUID), "keyType"), + (["in"], POINTER(GUID), "riid"), + (["out"], POINTER(c_void_p), "ppv")), + COMMETHOD([], HRESULT, "GetAttributes", + (["in"], c_ulong, "attribFlags"), + (["in"], c_ulong, "sfgaoMask"), + (["out"], POINTER(c_ulong), "psfgaoAttribs")), + COMMETHOD([], HRESULT, "GetCount", + (["out"], POINTER(c_uint), "pdwNumItems")), + COMMETHOD([], HRESULT, "GetItemAt", + (["in"], c_uint, "dwIndex"), + (["out"], POINTER(POINTER(IShellItem)), "ppsi")), + COMMETHOD([], HRESULT, "EnumItems", + (["out"], POINTER(POINTER(comtypes.IUnknown)), "ppenumShellItems")) + ] + QueryInterface: Callable[[GUID, comtypes.IUnknown], HRESULT] + AddRef: Callable[[], ULONG] + Release: Callable[[], ULONG] + BindToHandler: Callable[[_Pointer[comtypes.IUnknown], GUID, GUID], int] + GetPropertyStore: Callable[[c_ulong, GUID], c_void_p] + GetPropertyDescriptionList: Callable[[GUID, GUID], c_void_p] + GetAttributes: Callable[[c_ulong, c_ulong], _Pointer[c_ulong]] + GetCount: Callable[[], int] + GetItemAt: Callable[[c_uint | int], IShellItem] + EnumItems: Callable[[], comtypes.IUnknown] +class ShellItemArray(comtypes.COMObject): + _com_interfaces_: Sequence[type[comtypes.IUnknown]] = [IShellItemArray] + def QueryInterface(self, riid: GUID, ppvObject: comtypes.IUnknown) -> HRESULT: # noqa: N803 + return S_OK + def AddRef(self) -> ULONG: + return ULONG(-1) + def Release(self) -> ULONG: + return ULONG(-1) + def BindToHandler(self, pbc: _Pointer[comtypes.IUnknown], bhid: GUID, riid: GUID, ppv: _Pointer[c_void_p]) -> HRESULT: + return S_OK + def GetPropertyStore(self, flags: c_ulong | int, riid: GUID, ppv: _Pointer[c_void_p]) -> HRESULT: + return S_OK + def GetPropertyDescriptionList(self, keyType: GUID, riid: GUID, ppv: _Pointer[c_void_p]) -> HRESULT: # noqa: N803 + return S_OK + def GetAttributes(self, attribFlags: c_ulong | int, sfgaoMask: c_ulong | int, psfgaoAttribs: _Pointer[c_ulong]) -> HRESULT: # noqa: N803 + return S_OK + def GetCount(self, pdwNumItems: _Pointer[c_uint]) -> HRESULT: # noqa: N803 + return S_OK + def GetItemAt(self, dwIndex: c_uint | int, ppsi: IShellItem) -> HRESULT: # noqa: N803 + return S_OK + def EnumItems(self, ppenumShellItems: comtypes.IUnknown) -> HRESULT: # noqa: N803 + return S_OK + + +class IShellItemFilter(comtypes.IUnknown): + _case_insensitive_: bool = True + _iid_: GUID = IID_IShellItemFilter + _methods_: ClassVar[list[_ComMemberSpec]] = [ + COMMETHOD([], HRESULT, "IncludeItem", + (["in"], POINTER(IShellItem), "psi")), + COMMETHOD([], HRESULT, "GetEnumFlagsForItem", + (["in"], POINTER(IShellItem), "psi"), + (["out"], POINTER(c_ulong), "pgrfFlags")) + ] + QueryInterface: Callable[[GUID, comtypes.IUnknown], HRESULT] + AddRef: Callable[[], ULONG] + Release: Callable[[], ULONG] + IncludeItem: Callable[[IShellItem], c_ulong] + GetEnumFlagsForItem: Callable[[], HRESULT] +class ShellItemFilter(comtypes.COMObject): + _com_interfaces_: Sequence[type[comtypes.IUnknown]] = [IShellItemFilter] + def QueryInterface(self, riid: GUID, ppvObject: comtypes.IUnknown) -> HRESULT: # noqa: N803 + return S_OK + def AddRef(self) -> ULONG: + return ULONG(-1) + def Release(self) -> ULONG: + return ULONG(-1) + def IncludeItem(self, psi: IShellItem) -> HRESULT: + return S_OK + def GetEnumFlagsForItem(self, psi: IShellItem, pgrfFlags: _Pointer[c_ulong]) -> HRESULT: # noqa: N803 + return S_OK + + +class IEnumShellItems(comtypes.IUnknown): + _case_insensitive_: bool = True + _iid_: GUID = IID_IEnumShellItems + _methods_: ClassVar[list[_ComMemberSpec]] + QueryInterface: Callable[[GUID, comtypes.IUnknown], HRESULT] + AddRef: Callable[[], ULONG] + Release: Callable[[], ULONG] + Next: Callable[[_Pointer[IEnumShellItems], c_ulong, IShellItem, _Pointer[c_ulong]], HRESULT] + Skip: Callable[[_Pointer[IEnumShellItems], c_ulong], HRESULT] + Reset: Callable[[_Pointer[IEnumShellItems]], HRESULT] + Clone: Callable[[_Pointer[IEnumShellItems], _Pointer[_Pointer[IEnumShellItems]]], HRESULT] +IEnumShellItems._methods_ = [ # noqa: SLF001 + COMMETHOD([], HRESULT, "Next", + (["in"], c_ulong, "celt"), + (["out"], POINTER(POINTER(IShellItem)), "rgelt"), + (["out"], POINTER(c_ulong), "pceltFetched")), + COMMETHOD([], HRESULT, "Skip", + (["in"], c_ulong, "celt")), + COMMETHOD([], HRESULT, "Reset"), + COMMETHOD([], HRESULT, "Clone", + (["out"], POINTER(POINTER(IEnumShellItems)), "ppenum")) +] +class EnumShellItems(comtypes.COMObject): + _com_interfaces_: Sequence[type[comtypes.IUnknown]] = [IEnumShellItems] + def QueryInterface(self, riid: GUID, ppvObject: comtypes.IUnknown) -> HRESULT: # noqa: N803 + return S_OK + def AddRef(self) -> ULONG: + return ULONG(-1) + def Release(self) -> ULONG: + return ULONG(-1) + def Next(self, celt: c_ulong | int, rgelt: IShellItem, pceltFetched: _Pointer[c_ulong]) -> HRESULT: # noqa: N803 + return S_OK + def Skip(self, celt: c_ulong | int) -> HRESULT: + return S_OK + def Reset(self) -> HRESULT: + return S_OK + def Clone(self, ppenum: _Pointer[_Pointer[IEnumShellItems]]) -> HRESULT: + return S_OK + + +class IPropertyStore(comtypes.IUnknown): + _case_insensitive_: bool = True + _iid_: GUID = IID_IPropertyStore + _methods_: ClassVar[list[_ComMemberSpec]] = [ + COMMETHOD([], HRESULT, "GetCount", + (["out"], POINTER(c_ulong), "count")), + COMMETHOD([], HRESULT, "GetAt", + (["in"], c_ulong, "index"), + (["out"], POINTER(GUID), "key")), + COMMETHOD([], HRESULT, "GetValue", + (["in"], POINTER(GUID), "key"), + (["out"], POINTER(c_void_p), "pv")), + COMMETHOD([], HRESULT, "SetValue", + (["in"], POINTER(GUID), "key"), + (["in"], POINTER(c_void_p), "propvar")), + COMMETHOD([], HRESULT, "Commit") + ] + QueryInterface: Callable[[GUID, comtypes.IUnknown], HRESULT] + AddRef: Callable[[], ULONG] + Release: Callable[[], ULONG] + GetCount: Callable[[_Pointer[IPropertyStore], _Pointer[c_ulong]], HRESULT] + GetAt: Callable[[_Pointer[IPropertyStore], c_ulong, GUID], HRESULT] + GetValue: Callable[[_Pointer[IPropertyStore], GUID, _Pointer[c_void_p]], HRESULT] + SetValue: Callable[[_Pointer[IPropertyStore], GUID, _Pointer[c_void_p]], HRESULT] + Commit: Callable[[_Pointer[IPropertyStore]], HRESULT] +class PropertyStore(comtypes.COMObject): + _com_interfaces_: Sequence[type[comtypes.IUnknown]] = [IPropertyStore] + def QueryInterface(self, riid: GUID, ppvObject: comtypes.IUnknown) -> HRESULT: # noqa: N803 + return S_OK + def AddRef(self) -> ULONG: + return ULONG(-1) + def Release(self) -> ULONG: + return ULONG(-1) + def GetCount(self, count: _Pointer[c_ulong]) -> HRESULT: + return S_OK + def GetAt(self, index: c_ulong | int, key: GUID) -> HRESULT: + return S_OK + def GetValue(self, key: GUID, pv: _Pointer[c_void_p]) -> HRESULT: + return S_OK + def SetValue(self, key: GUID, propvar: _Pointer[c_void_p]) -> HRESULT: + return S_OK + def Commit(self) -> HRESULT: + return S_OK + +class IFileOperationProgressSink(comtypes.IUnknown): + _case_insensitive_: bool = True + _iid_: GUID = IID_IFileOperationProgressSink + _methods_: ClassVar[list[_ComMemberSpec]] = [ + COMMETHOD([], HRESULT, "StartOperations"), + COMMETHOD([], HRESULT, "FinishOperations", + (["in"], HRESULT, "hr")), + COMMETHOD([], HRESULT, "PreRenameItem", + (["in"], c_ulong, "dwFlags"), + (["in"], POINTER(IShellItem), "psiItem"), + (["in"], c_wchar_p, "pszNewName")), + COMMETHOD([], HRESULT, "PostRenameItem", + (["in"], c_ulong, "dwFlags"), + (["in"], POINTER(IShellItem), "psiItem"), + (["in"], c_wchar_p, "pszNewName"), + (["in"], HRESULT, "hrRename"), + (["in"], POINTER(IShellItem), "psiNewlyCreated")), + COMMETHOD([], HRESULT, "PreMoveItem", + (["in"], c_ulong, "dwFlags"), + (["in"], POINTER(IShellItem), "psiItem"), + (["in"], POINTER(IShellItem), "psiDestinationFolder"), + (["in"], c_wchar_p, "pszNewName")), + COMMETHOD([], HRESULT, "PostMoveItem", + (["in"], c_ulong, "dwFlags"), + (["in"], POINTER(IShellItem), "psiItem"), + (["in"], POINTER(IShellItem), "psiDestinationFolder"), + (["in"], c_wchar_p, "pszNewName"), + (["in"], HRESULT, "hrMove"), + (["in"], POINTER(IShellItem), "psiNewlyCreated")), + COMMETHOD([], HRESULT, "PreCopyItem", + (["in"], c_ulong, "dwFlags"), + (["in"], POINTER(IShellItem), "psiItem"), + (["in"], POINTER(IShellItem), "psiDestinationFolder"), + (["in"], c_wchar_p, "pszNewName")), + COMMETHOD([], HRESULT, "PostCopyItem", + (["in"], c_ulong, "dwFlags"), + (["in"], POINTER(IShellItem), "psiItem"), + (["in"], POINTER(IShellItem), "psiDestinationFolder"), + (["in"], c_wchar_p, "pszNewName"), + (["in"], HRESULT, "hrCopy"), + (["in"], POINTER(IShellItem), "psiNewlyCreated")), + COMMETHOD([], HRESULT, "PreDeleteItem", + (["in"], c_ulong, "dwFlags"), + (["in"], POINTER(IShellItem), "psiItem")), + COMMETHOD([], HRESULT, "PostDeleteItem", + (["in"], c_ulong, "dwFlags"), + (["in"], POINTER(IShellItem), "psiItem"), + (["in"], HRESULT, "hrDelete"), + (["in"], POINTER(IShellItem), "psiNewlyCreated")), + COMMETHOD([], HRESULT, "PreNewItem", + (["in"], c_ulong, "dwFlags"), + (["in"], POINTER(IShellItem), "psiDestinationFolder"), + (["in"], c_wchar_p, "pszNewName")), + COMMETHOD([], HRESULT, "PostNewItem", + (["in"], c_ulong, "dwFlags"), + (["in"], POINTER(IShellItem), "psiDestinationFolder"), + (["in"], c_wchar_p, "pszNewName"), + (["in"], c_wchar_p, "pszTemplateName"), + (["in"], c_ulong, "dwFileAttributes"), + (["in"], HRESULT, "hrNew"), + (["in"], POINTER(IShellItem), "psiNewItem")), + COMMETHOD([], HRESULT, "UpdateProgress", + (["in"], c_ulong, "iWorkTotal"), + (["in"], c_ulong, "iWorkSoFar")), + COMMETHOD([], HRESULT, "ResetTimer"), + COMMETHOD([], HRESULT, "PauseTimer"), + COMMETHOD([], HRESULT, "ResumeTimer") +] + QueryInterface: Callable[[GUID, comtypes.IUnknown], HRESULT] + AddRef: Callable[[], ULONG] + Release: Callable[[], ULONG] + StartOperations: Callable[[], HRESULT] + FinishOperations: Callable[[HRESULT], HRESULT] + PreRenameItem: Callable[[c_ulong, IShellItem, c_wchar_p], HRESULT] + PostRenameItem: Callable[[c_ulong, IShellItem, c_wchar_p, HRESULT, IShellItem], HRESULT] + PreMoveItem: Callable[[c_ulong, IShellItem, IShellItem, c_wchar_p], HRESULT] + PostMoveItem: Callable[[c_ulong, IShellItem, IShellItem, c_wchar_p, HRESULT, IShellItem], HRESULT] + PreCopyItem: Callable[[c_ulong, IShellItem, IShellItem, c_wchar_p], HRESULT] + PostCopyItem: Callable[[c_ulong, IShellItem, IShellItem, c_wchar_p, HRESULT, IShellItem], HRESULT] + PreDeleteItem: Callable[[c_ulong, IShellItem], HRESULT] + PostDeleteItem: Callable[[c_ulong, IShellItem, HRESULT, IShellItem], HRESULT] + PreNewItem: Callable[[c_ulong, IShellItem, c_wchar_p], HRESULT] + PostNewItem: Callable[[c_ulong, IShellItem, c_wchar_p, c_wchar_p, c_ulong, HRESULT, IShellItem], HRESULT] + UpdateProgress: Callable[[c_ulong, c_ulong], HRESULT] + ResetTimer: Callable[[], HRESULT] + PauseTimer: Callable[[], HRESULT] + ResumeTimer: Callable[[], HRESULT] +class FileOperationProgressSink(comtypes.COMObject): + _com_interfaces_: Sequence[type[comtypes.IUnknown]] = [IFileOperationProgressSink] + def StartOperations(self) -> HRESULT: + return S_OK + def FinishOperations(self, hr: HRESULT) -> HRESULT: + return S_OK + def PreRenameItem(self, dwFlags: c_ulong | int, psiItem: IShellItem, pszNewName: LPCWSTR | str) -> HRESULT: # noqa: N803 + return S_OK + def PostRenameItem(self, dwFlags: c_ulong | int, psiItem: IShellItem, pszNewName: LPCWSTR | str, hrRename: HRESULT, psiNewlyCreated: IShellItem) -> HRESULT: # noqa: N803 + return S_OK + def PreMoveItem(self, dwFlags: c_ulong | int, psiItem: IShellItem, psiDestinationFolder: IShellItem, pszNewName: LPCWSTR | str) -> HRESULT: # noqa: N803 + return S_OK + def PostMoveItem(self, dwFlags: c_ulong | int, psiItem: IShellItem, psiDestinationFolder: IShellItem, pszNewName: LPCWSTR | str, hrMove: HRESULT, psiNewlyCreated: IShellItem) -> HRESULT: # noqa: N803, E501 + return S_OK + def PreCopyItem(self, dwFlags: c_ulong | int, psiItem: IShellItem, psiDestinationFolder: IShellItem, pszNewName: LPCWSTR | str) -> HRESULT: # noqa: N803 + return S_OK + def PostCopyItem(self, dwFlags: c_ulong | int, psiItem: IShellItem, psiDestinationFolder: IShellItem, pszNewName: LPCWSTR | str, hrCopy: HRESULT, psiNewlyCreated: IShellItem) -> HRESULT: # noqa: N803, E501 + return S_OK + def PreDeleteItem(self, dwFlags: c_ulong | int, psiItem: IShellItem) -> HRESULT: + return S_OK + def PostDeleteItem(self, dwFlags: c_ulong | int, psiItem: IShellItem, hrDelete: HRESULT, psiNewlyCreated: IShellItem) -> HRESULT: + return S_OK + def PreNewItem(self, dwFlags: c_ulong | int, psiDestinationFolder: IShellItem, pszNewName: LPCWSTR | str) -> HRESULT: + return S_OK + def PostNewItem(self, dwFlags: c_ulong | int, psiDestinationFolder: IShellItem, pszNewName: LPCWSTR | str, pszTemplateName: LPCWSTR | str, dwFileAttributes: c_ulong | int, hrNew: HRESULT, psiNewItem: IShellItem) -> HRESULT: # noqa: N803, E501 + return S_OK + def UpdateProgress(self, iWorkTotal: c_ulong | int, iWorkSoFar: c_ulong | int) -> HRESULT: # noqa: N803 + return S_OK + def ResetTimer(self) -> HRESULT: + return S_OK + def PauseTimer(self) -> HRESULT: + return S_OK + def ResumeTimer(self) -> HRESULT: + return S_OK + + +class IFileOperation(comtypes.IUnknown): + _case_insensitive_ = True + _iid_ = IID_IFileOperation + _idlflags_ = [] + + _methods_: ClassVar[list[_ComMemberSpec]] = [ + # Advise methods + comtypes.COMMETHOD([], HRESULT, "Advise", + (["in"], POINTER(IFileOperationProgressSink), "pfops"), + (["out"], POINTER(c_ulong), "pdwCookie")), + comtypes.COMMETHOD([], HRESULT, "Unadvise", + (["in"], c_ulong, "dwCookie")), + + # Operation control methods + comtypes.COMMETHOD([], HRESULT, "SetOperationFlags", + (["in"], c_ulong, "dwOperationFlags")), + comtypes.COMMETHOD([], HRESULT, "SetProgressMessage", + (["in"], c_wchar_p, "pszMessage")), + comtypes.COMMETHOD([], HRESULT, "SetProgressDialog", + (["in"], POINTER(comtypes.IUnknown), "popd")), + + # Item methods + comtypes.COMMETHOD([], HRESULT, "SetProperties", + (["in"], POINTER(comtypes.IUnknown), "pproparray")), + comtypes.COMMETHOD([], HRESULT, "SetOwnerWindow", + (["in"], c_ulong, "hwndOwner")), + comtypes.COMMETHOD([], HRESULT, "ApplyPropertiesToItem", + (["in"], POINTER(IShellItem), "psiItem")), + comtypes.COMMETHOD([], HRESULT, "ApplyPropertiesToItems", + (["in"], POINTER(comtypes.IUnknown), "punkItems")), + + # Operation methods + comtypes.COMMETHOD([], HRESULT, "RenameItem", + (["in"], POINTER(IShellItem), "psiItem"), + (["in"], c_wchar_p, "pszNewName"), + (["in"], POINTER(IFileOperationProgressSink), "pfopsItem")), + comtypes.COMMETHOD([], HRESULT, "RenameItems", + (["in"], POINTER(comtypes.IUnknown), "pUnkItems"), + (["in"], c_wchar_p, "pszNewName")), + comtypes.COMMETHOD([], HRESULT, "MoveItem", + (["in"], POINTER(IShellItem), "psiItem"), + (["in"], POINTER(IShellItem), "psiDestinationFolder"), + (["in"], c_wchar_p, "pszNewName"), + (["in"], POINTER(IFileOperationProgressSink), "pfopsItem")), + comtypes.COMMETHOD([], HRESULT, "MoveItems", + (["in"], POINTER(comtypes.IUnknown), "punkItems"), + (["in"], POINTER(IShellItem), "psiDestinationFolder")), + comtypes.COMMETHOD([], HRESULT, "CopyItem", + (["in"], POINTER(IShellItem), "psiItem"), + (["in"], POINTER(IShellItem), "psiDestinationFolder"), + (["in"], c_wchar_p, "pszNewName"), + (["in"], POINTER(IFileOperationProgressSink), "pfopsItem")), + comtypes.COMMETHOD([], HRESULT, "CopyItems", + (["in"], POINTER(comtypes.IUnknown), "punkItems"), + (["in"], POINTER(IShellItem), "psiDestinationFolder")), + comtypes.COMMETHOD([], HRESULT, "DeleteItem", + (["in"], POINTER(IShellItem), "psiItem"), + (["in"], POINTER(IFileOperationProgressSink), "pfopsItem")), + comtypes.COMMETHOD([], HRESULT, "DeleteItems", + (["in"], POINTER(comtypes.IUnknown), "punkItems")), + comtypes.COMMETHOD([], HRESULT, "NewItem", + (["in"], POINTER(IShellItem), "psiDestinationFolder"), + (["in"], c_ulong, "dwFileAttributes"), + (["in"], c_wchar_p, "pszName"), + (["in"], c_wchar_p, "pszTemplateName"), + (["in"], POINTER(IFileOperationProgressSink), "pfopsItem")), + + # Execution methods + comtypes.COMMETHOD([], HRESULT, "PerformOperations"), + comtypes.COMMETHOD([], HRESULT, "GetAnyOperationsAborted", + (["out"], POINTER(c_int), "pfAnyOperationsAborted")) + ] + + +class IFileDialogEvents(comtypes.IUnknown): + _case_insensitive_: bool = True + _iid_: GUID = IID_IFileDialogEvents + _methods_: ClassVar[list[_ComMemberSpec]] + QueryInterface: Callable[[GUID, comtypes.IUnknown], HRESULT] + AddRef: Callable[[], ULONG] + Release: Callable[[], ULONG] + OnFileOk: Callable[[IFileDialog], HRESULT] + OnFolderChanging: Callable[[IFileDialog, IShellItem], HRESULT] + OnFolderChange: Callable[[IFileDialog], HRESULT] + OnSelectionChange: Callable[[IFileDialog], HRESULT] + OnShareViolation: Callable[[IFileDialog, IShellItem, c_int], HRESULT] + OnTypeChange: Callable[[IFileDialog], HRESULT] + OnOverwrite: Callable[[IFileDialog, IShellItem, c_int], HRESULT] +class IFileDialog(IModalWindow): + _iid_: GUID = IID_IFileDialog + _methods_: ClassVar[list[_ComMemberSpec]] = [ + COMMETHOD([], HRESULT, "SetFileTypes", + (["in"], c_uint, "cFileTypes"), + (["in"], POINTER(c_void_p), "rgFilterSpec")), + COMMETHOD([], HRESULT, "SetFileTypeIndex", + (["in"], c_uint, "iFileType")), + COMMETHOD([], HRESULT, "GetFileTypeIndex", + (["out"], POINTER(c_uint), "piFileType")), + COMMETHOD([], HRESULT, "Advise", + (["in"], POINTER(comtypes.IUnknown), "pfde"), + (["out"], POINTER(DWORD), "pdwCookie")), + COMMETHOD([], HRESULT, "Unadvise", + (["in"], DWORD, "dwCookie")), + COMMETHOD([], HRESULT, "SetOptions", + (["in"], c_uint, "fos")), + COMMETHOD([], HRESULT, "GetOptions", + (["out"], POINTER(DWORD), "pfos")), + COMMETHOD([], HRESULT, "SetDefaultFolder", + (["in"], POINTER(IShellItem), "psi")), + COMMETHOD([], HRESULT, "SetFolder", + (["in"], POINTER(IShellItem), "psi")), + COMMETHOD([], HRESULT, "GetFolder", + (["out"], POINTER(POINTER(IShellItem)), "ppsi")), + COMMETHOD([], HRESULT, "GetCurrentSelection", + (["out"], POINTER(POINTER(IShellItem)), "ppsi")), + COMMETHOD([], HRESULT, "SetFileName", + (["in"], LPCWSTR, "pszName")), + COMMETHOD([], HRESULT, "GetFileName", + (["out"], POINTER(LPWSTR), "pszName")), + COMMETHOD([], HRESULT, "SetTitle", + (["in"], LPCWSTR, "pszTitle")), + COMMETHOD([], HRESULT, "SetOkButtonLabel", + (["in"], LPCWSTR, "pszText")), + COMMETHOD([], HRESULT, "SetFileNameLabel", + (["in"], LPCWSTR, "pszLabel")), + COMMETHOD([], HRESULT, "GetResult", + (["out"], POINTER(POINTER(IShellItem)), "ppsi")), + COMMETHOD([], HRESULT, "AddPlace", + (["in"], POINTER(IShellItem), "psi"), + (["in"], c_int, "fdap")), + COMMETHOD([], HRESULT, "SetDefaultExtension", + (["in"], LPCWSTR, "pszDefaultExtension")), + COMMETHOD([], HRESULT, "Close", + (["in"], HRESULT, "hr")), + COMMETHOD([], HRESULT, "SetClientGuid", + (["in"], POINTER(GUID), "guid")), + COMMETHOD([], HRESULT, "ClearClientData"), + COMMETHOD([], HRESULT, "SetFilter", + (["in"], POINTER(IShellItemFilter), "pFilter")) + ] + SetFileTypes: Callable[[c_uint | int, _Pointer[c_void_p]], HRESULT] + SetFileTypeIndex: Callable[[c_uint], HRESULT] + GetFileTypeIndex: Callable[[], _Pointer[c_uint]] + Advise: Callable[[comtypes.IUnknown | comtypes.COMObject], int] + Unadvise: Callable[[int], HRESULT] + SetOptions: Callable[[DWORD | int], HRESULT] + GetOptions: Callable[[], int] + SetDefaultFolder: Callable[[_Pointer[IShellItem]], HRESULT] + SetFolder: Callable[[_Pointer[IShellItem]], HRESULT] + GetFolder: Callable[[], IShellItem] + GetCurrentSelection: Callable[[], IShellItem] + SetFileName: Callable[[str], HRESULT] + GetFileName: Callable[[], _Pointer[LPWSTR]] + SetTitle: Callable[[str], HRESULT] + SetOkButtonLabel: Callable[[str], HRESULT] + SetFileNameLabel: Callable[[str], HRESULT] + GetResult: Callable[[], IShellItem] + AddPlace: Callable[[IShellItem, c_int], HRESULT] + SetDefaultExtension: Callable[[str], HRESULT] + Close: Callable[[HRESULT], HRESULT] + SetClientGuid: Callable[[GUID], HRESULT] + ClearClientData: Callable[[], HRESULT] + SetFilter: Callable[[IShellItemFilter], HRESULT] +IFileDialogEvents._methods_ = [ # noqa: SLF001 + COMMETHOD([], HRESULT, "OnFileOk", + (["in"], POINTER(IFileDialog), "pfd")), + COMMETHOD([], HRESULT, "OnFolderChanging", + (["in"], POINTER(IFileDialog), "pfd"), + (["in"], POINTER(IShellItem), "psiFolder")), + COMMETHOD([], HRESULT, "OnFolderChange", + (["in"], POINTER(IFileDialog), "pfd")), + COMMETHOD([], HRESULT, "OnSelectionChange", + (["in"], POINTER(IFileDialog), "pfd")), + COMMETHOD([], HRESULT, "OnShareViolation", + (["in"], POINTER(IFileDialog), "pfd"), + (["in"], POINTER(IShellItem), "psi"), + (["out"], POINTER(c_int), "pResponse")), + COMMETHOD([], HRESULT, "OnTypeChange", + (["in"], POINTER(IFileDialog), "pfd")), + COMMETHOD([], HRESULT, "OnOverwrite", + (["in"], POINTER(IFileDialog), "pfd"), + (["in"], POINTER(IShellItem), "psi"), + (["out"], POINTER(c_int), "pResponse")) + ] +class FileDialogEvents(comtypes.COMObject): + _com_interfaces_: Sequence[type[comtypes.IUnknown]] = [IFileDialogEvents] + def QueryInterface(self, riid: GUID, ppvObject: comtypes.IUnknown) -> HRESULT: # noqa: N803 + return S_OK + def AddRef(self) -> ULONG: + return ULONG(-1) + def Release(self) -> ULONG: + return ULONG(-1) + def OnFileOk(self, ifd: IFileDialog) -> HRESULT: + return S_OK + def OnFolderChanging(self, ifd: IFileDialog, isiFolder: IShellItem) -> HRESULT: # noqa: N803 + return S_OK + def OnFolderChange(self, ifd: IFileDialog) -> HRESULT: + return S_OK + def OnSelectionChange(self, ifd: IFileDialog) -> HRESULT: + return S_OK + def OnShareViolation(self, ifd: IFileDialog, psi: IShellItem, response: c_int) -> HRESULT: + return S_OK + def OnTypeChange(self, ifd: IFileDialog) -> HRESULT: + return S_OK + def OnOverwrite(self, ifd: IFileDialog, psi: IShellItem, response: c_int) -> HRESULT: + return S_OK +class FileDialog(comtypes.COMObject): + _com_interfaces_: Sequence[type[comtypes.IUnknown]] = [IFileDialog] + def QueryInterface(self, riid: GUID, ppvObject: comtypes.IUnknown) -> HRESULT: # noqa: N803 + return S_OK + def AddRef(self) -> ULONG: + return ULONG(-1) + def Release(self) -> ULONG: + return ULONG(-1) + def Show(self, hwndOwner: HWND | int) -> HRESULT: + return S_OK + def SetFileTypes(self, cFileTypes: c_uint | int, rgFilterSpec: Array[COMDLG_FILTERSPEC]) -> HRESULT: # noqa: N803 + return S_OK + def SetFileTypeIndex(self, iFileType: c_uint | int) -> HRESULT: # noqa: N803 + return S_OK + def GetFileTypeIndex(self, piFileType: _Pointer[c_uint]) -> HRESULT: # noqa: N803 + return S_OK + def Advise(self, pfde: _Pointer[comtypes.IUnknown], pdwCookie: _Pointer[DWORD]) -> HRESULT: # noqa: N803 + return S_OK + def Unadvise(self, dwCookie: int) -> HRESULT: # noqa: N803 + return S_OK + def SetOptions(self, fos: int) -> HRESULT: + return S_OK + def GetOptions(self, pfos: _Pointer[DWORD]) -> HRESULT: + return S_OK + def SetDefaultFolder(self, psi: IShellItem) -> HRESULT: + return S_OK + def SetFolder(self, psi: IShellItem) -> HRESULT: + return S_OK + def GetFolder(self, ppsi: IShellItem) -> HRESULT: + return S_OK + def GetCurrentSelection(self, ppsi: IShellItem) -> HRESULT: + return S_OK + def SetFileName(self, pszName: LPCWSTR | str) -> HRESULT: # noqa: N803 + return S_OK + def GetFileName(self, pszName: _Pointer[LPWSTR]) -> HRESULT: # noqa: N803 + return S_OK + def SetTitle(self, pszTitle: LPCWSTR | str) -> HRESULT: # noqa: N803 + return S_OK + def SetOkButtonLabel(self, pszText: LPCWSTR | str) -> HRESULT: # noqa: N803 + return S_OK + def SetFileNameLabel(self, pszLabel: LPCWSTR | str) -> HRESULT: # noqa: N803 + return S_OK + def GetResult(self, ppsi: IShellItem) -> HRESULT: + return S_OK + def AddPlace(self, psi: IShellItem, fdap: c_int) -> HRESULT: + return S_OK + def SetDefaultExtension(self, pszDefaultExtension: LPCWSTR | str) -> HRESULT: # noqa: N803 + return S_OK + def Close(self, hr: HRESULT | int) -> HRESULT: + return S_OK + def SetClientGuid(self, guid: GUID) -> HRESULT: + return S_OK + def ClearClientData(self) -> HRESULT: + return S_OK + def SetFilter(self, pFilter: IShellItemFilter) -> HRESULT: # noqa: N803 + return S_OK + + +class IShellLibrary(comtypes.IUnknown): + _case_insensitive_: bool = True + _iid_: GUID = IID_IShellLibrary + _methods_: ClassVar[list[_ComMemberSpec]] = [ + COMMETHOD([], HRESULT, "LoadLibraryFromItem", + (["in"], POINTER(IShellItem), "psi"), + (["in"], c_ulong, "grfMode")), + COMMETHOD([], HRESULT, "LoadLibraryFromKnownFolder", + (["in"], POINTER(GUID), "kfidLibrary"), + (["in"], c_ulong, "grfMode")), + COMMETHOD([], HRESULT, "AddFolder", + (["in"], POINTER(IShellItem), "psi")), + COMMETHOD([], HRESULT, "RemoveFolder", + (["in"], POINTER(IShellItem), "psi")), + COMMETHOD([], HRESULT, "GetFolders", + (["in"], c_int, "lff"), + (["in"], POINTER(GUID), "riid"), + (["out"], POINTER(POINTER(c_void_p)), "ppv")), + COMMETHOD([], HRESULT, "ResolveFolder", + (["in"], POINTER(IShellItem), "psi"), + (["in"], c_ulong, "grfMode"), + (["in"], POINTER(GUID), "riid"), + (["out"], POINTER(POINTER(c_void_p)), "ppv")), + COMMETHOD([], HRESULT, "GetDefaultSaveFolder", + (["in"], c_int, "dsft"), + (["in"], POINTER(GUID), "riid"), + (["out"], POINTER(POINTER(c_void_p)), "ppv")), + COMMETHOD([], HRESULT, "SetDefaultSaveFolder", + (["in"], c_int, "dsft"), + (["in"], POINTER(IShellItem), "psi")), + COMMETHOD([], HRESULT, "GetOptions", + (["out"], POINTER(c_uint), "pOptions")), + COMMETHOD([], HRESULT, "SetOptions", + (["in"], c_ulong, "stfOptions"), + (["in"], c_ulong, "stfMask")), + COMMETHOD([], HRESULT, "GetFolderType", + (["out"], POINTER(GUID), "pftid")), + COMMETHOD([], HRESULT, "SetFolderType", + (["in"], POINTER(GUID), "ftid")), + COMMETHOD([], HRESULT, "GetIcon", + (["out"], POINTER(LPWSTR), "ppszIcon")), + COMMETHOD([], HRESULT, "SetIcon", + (["in"], LPCWSTR, "pszIcon")), + COMMETHOD([], HRESULT, "Commit"), + COMMETHOD([], HRESULT, "Save", + (["in"], POINTER(IShellItem), "psiFolderToSaveIn"), + (["in"], LPCWSTR, "pszLibraryName"), + (["in"], c_ulong, "lrf"), + (["out"], POINTER(POINTER(IShellItem)), "ppsiNewItem")), + COMMETHOD([], HRESULT, "SaveInKnownFolder", + (["in"], POINTER(GUID), "kfid"), + (["in"], LPCWSTR, "pszLibraryName"), + (["in"], c_ulong, "lrf"), + (["out"], POINTER(POINTER(IShellItem)), "ppsiNewItem")) + ] + QueryInterface: Callable[[GUID, comtypes.IUnknown], HRESULT] + AddRef: Callable[[], ULONG] + Release: Callable[[], ULONG] + LoadLibraryFromItem: Callable[[_Pointer[IShellLibrary], IShellItem, c_ulong], HRESULT] + LoadLibraryFromKnownFolder: Callable[[_Pointer[IShellLibrary], GUID, c_ulong], HRESULT] + AddFolder: Callable[[_Pointer[IShellLibrary], IShellItem], HRESULT] + RemoveFolder: Callable[[_Pointer[IShellLibrary], IShellItem], HRESULT] + GetFolders: Callable[[_Pointer[IShellLibrary], c_int, GUID, _Pointer[_Pointer[c_void_p]]], HRESULT] + ResolveFolder: Callable[[_Pointer[IShellLibrary], IShellItem, c_ulong, GUID, _Pointer[_Pointer[c_void_p]]], HRESULT] + GetDefaultSaveFolder: Callable[[_Pointer[IShellLibrary], c_int, GUID, _Pointer[_Pointer[c_void_p]]], HRESULT] + SetDefaultSaveFolder: Callable[[_Pointer[IShellLibrary], c_int, IShellItem], HRESULT] + GetOptions: Callable[[_Pointer[IShellLibrary], _Pointer[c_uint]], HRESULT] + SetOptions: Callable[[_Pointer[IShellLibrary], c_ulong, c_ulong], HRESULT] + GetFolderType: Callable[[_Pointer[IShellLibrary], GUID], HRESULT] + SetFolderType: Callable[[_Pointer[IShellLibrary], GUID], HRESULT] + GetIcon: Callable[[_Pointer[IShellLibrary], _Pointer[LPWSTR]], HRESULT] + SetIcon: Callable[[_Pointer[IShellLibrary], LPCWSTR], HRESULT] + Commit: Callable[[_Pointer[IShellLibrary]], HRESULT] + Save: Callable[[_Pointer[IShellLibrary], IShellItem, LPCWSTR, c_ulong, IShellItem], HRESULT] + SaveInKnownFolder: Callable[[_Pointer[IShellLibrary], GUID, LPCWSTR, c_ulong, IShellItem], HRESULT] +class ShellLibrary(comtypes.COMObject): + _com_interfaces_: Sequence[type[comtypes.IUnknown]] = [IShellLibrary] + def LoadLibraryFromItem(self, psi: IShellItem, grfMode: c_ulong | int) -> HRESULT: # noqa: N803 + return S_OK + def LoadLibraryFromKnownFolder(self, kfidLibrary: GUID, grfMode: c_ulong | int) -> HRESULT: # noqa: N803 + return S_OK + def AddFolder(self, psi: IShellItem) -> HRESULT: + return S_OK + def RemoveFolder(self, psi: IShellItem) -> HRESULT: + return S_OK + def GetFolders(self, lff: c_int | int, riid: GUID, ppv: _Pointer[_Pointer[c_void_p]]) -> HRESULT: + return S_OK + def ResolveFolder(self, psi: IShellItem, grfMode: c_ulong | int, riid: GUID, ppv: _Pointer[_Pointer[c_void_p]]) -> HRESULT: # noqa: N803 + return S_OK + def GetDefaultSaveFolder(self, dsft: c_int | int, riid: GUID, ppv: _Pointer[_Pointer[c_void_p]]) -> HRESULT: + return S_OK + def SetDefaultSaveFolder(self, dsft: c_int | int, psi: IShellItem) -> HRESULT: + return S_OK + def GetOptions(self, pOptions: _Pointer[c_uint]) -> HRESULT: # noqa: N803 + return S_OK + def SetOptions(self, stfOptions: c_ulong | int, stfMask: c_ulong | int) -> HRESULT: # noqa: N803 + return S_OK + def GetFolderType(self, pftid: GUID) -> HRESULT: + return S_OK + def SetFolderType(self, ftid: GUID) -> HRESULT: + return S_OK + def GetIcon(self, ppszIcon: _Pointer[LPWSTR]) -> HRESULT: # noqa: N803 + return S_OK + def SetIcon(self, pszIcon: LPCWSTR | str) -> HRESULT: # noqa: N803 + return S_OK + def Commit(self) -> HRESULT: + return S_OK + def Save(self, psiFolderToSaveIn: IShellItem, pszLibraryName: LPCWSTR | str, lrf: c_ulong | int, ppsiNewItem: IShellItem) -> HRESULT: # noqa: N803 + return S_OK + def SaveInKnownFolder(self, kfid: GUID, pszLibraryName: LPCWSTR | str, lrf: c_ulong | int, ppsiNewItem: IShellItem) -> HRESULT: # noqa: N803 + return S_OK + + +class IFileOpenDialog(IFileDialog): + _case_insensitive_: bool = True + _iid_: GUID = IID_IFileOpenDialog + _methods_: ClassVar[list[_ComMemberSpec]] = [ + COMMETHOD([], HRESULT, "GetResults", + (["out"], POINTER(POINTER(IShellItemArray)), "ppenum")), + COMMETHOD([], HRESULT, "GetSelectedItems", + (["out"], POINTER(POINTER(IShellItemArray)), "ppsai")) + ] + GetResults: Callable[[], IShellItemArray] + GetSelectedItems: Callable[[], IShellItemArray] +class FileOpenDialog(comtypes.COMObject): + _com_interfaces_: Sequence[type[comtypes.IUnknown]] = [IFileOpenDialog] + def QueryInterface(self, riid: GUID, ppvObject: comtypes.IUnknown) -> HRESULT: # noqa: N803 + return S_OK + def AddRef(self) -> ULONG: + return ULONG(-1) + def Release(self) -> ULONG: + return ULONG(-1) + def Show(self, hwndParent: HWND | int) -> HRESULT: # noqa: N803 + return S_OK + def SetFileTypes(self, cFileTypes: c_uint | int, rgFilterSpec: Array[COMDLG_FILTERSPEC]) -> HRESULT: # noqa: N803 + return S_OK + def SetFileTypeIndex(self, iFileType: c_uint | int) -> HRESULT: # noqa: N803 + return S_OK + def GetFileTypeIndex(self, piFileType: _Pointer[c_uint]) -> HRESULT: # noqa: N803 + return S_OK + def Advise(self, pfde: _Pointer[comtypes.IUnknown], pdwCookie: _Pointer[DWORD]) -> HRESULT: # noqa: N803 + return S_OK + def Unadvise(self, dwCookie: int) -> HRESULT: # noqa: N803 + return S_OK + def SetOptions(self, fos: int) -> HRESULT: + return S_OK + def GetOptions(self, pfos: _Pointer[DWORD]) -> HRESULT: + return S_OK + def SetDefaultFolder(self, psi: IShellItem) -> HRESULT: + return S_OK + def SetFolder(self, psi: IShellItem) -> HRESULT: + return S_OK + def GetFolder(self, ppsi: IShellItem) -> HRESULT: + return S_OK + def GetCurrentSelection(self, ppsi: IShellItem) -> HRESULT: + return S_OK + def SetFileName(self, pszName: LPCWSTR | str) -> HRESULT: # noqa: N803 + return S_OK + def GetFileName(self, pszName: _Pointer[LPWSTR]) -> HRESULT: # noqa: N803 + return S_OK + def SetTitle(self, pszTitle: LPCWSTR | str) -> HRESULT: # noqa: N803 + return S_OK + def SetOkButtonLabel(self, pszText: LPCWSTR | str) -> HRESULT: # noqa: N803 + return S_OK + def SetFileNameLabel(self, pszLabel: LPCWSTR | str) -> HRESULT: # noqa: N803 + return S_OK + def GetResult(self, ppsi: IShellItem) -> HRESULT: + return S_OK + def AddPlace(self, psi: IShellItem, fdap: c_int) -> HRESULT: + return S_OK + def SetDefaultExtension(self, pszDefaultExtension: LPCWSTR | str) -> HRESULT: # noqa: N803 + return S_OK + def Close(self, hr: HRESULT | int) -> HRESULT: + return S_OK + def SetClientGuid(self, guid: GUID) -> HRESULT: + return S_OK + def ClearClientData(self) -> HRESULT: + return S_OK + def SetFilter(self, isFilter: IShellItemFilter) -> HRESULT: # noqa: N803 + return S_OK + def GetResults(self, isArray: IShellItemArray) -> HRESULT: # noqa: N803 + return S_OK + def GetSelectedItems(self, ppsai: IShellItemArray) -> HRESULT: + return S_OK + + +class IFileSaveDialog(IFileDialog): + _case_insensitive_: bool = True + _iid_: GUID = IID_IFileSaveDialog + _methods_: ClassVar[list[_ComMemberSpec]] = [ + COMMETHOD([], HRESULT, "SetSaveAsItem", + (["in"], POINTER(IShellItem), "psi")), + COMMETHOD([], HRESULT, "SetProperties", + (["in"], POINTER(comtypes.IUnknown), "pStore")), + COMMETHOD([], HRESULT, "SetCollectedProperties", + (["in"], POINTER(comtypes.IUnknown), "pList"), + (["in"], BOOL, "fAppendDefault")), + COMMETHOD([], HRESULT, "GetProperties", + (["out"], POINTER(POINTER(comtypes.IUnknown)), "ppStore")), + COMMETHOD([], HRESULT, "ApplyProperties", + (["in"], POINTER(IShellItem), "psi"), + (["in"], POINTER(comtypes.IUnknown), "pStore"), + (["in"], HWND, "hwnd"), + (["in"], POINTER(comtypes.IUnknown), "pSink")) + ] + SetSaveAsItem: Callable[[IShellItem], HRESULT] + SetProperties: Callable[[_Pointer[comtypes.IUnknown]], HRESULT] + SetCollectedProperties: Callable[[_Pointer[comtypes.IUnknown], BOOL], HRESULT] + GetProperties: Callable[[_Pointer[_Pointer[comtypes.IUnknown]]], HRESULT] + ApplyProperties: Callable[[IShellItem, _Pointer[comtypes.IUnknown], HWND, _Pointer[comtypes.IUnknown]], HRESULT] +class FileSaveDialog(comtypes.COMObject): + _com_interfaces_: Sequence[type[comtypes.IUnknown]] = [IFileSaveDialog] + def QueryInterface(self, riid: GUID, ppvObject: comtypes.IUnknown) -> HRESULT: # noqa: N803 + return S_OK + def AddRef(self) -> ULONG: + return ULONG(-1) + def Release(self) -> ULONG: + return ULONG(-1) + def Show(self, hwndParent: HWND | int) -> HRESULT: # noqa: N803 + return S_OK + def SetFileTypes(self, cFileTypes: c_uint | int, rgFilterSpec: _Pointer[COMDLG_FILTERSPEC]) -> HRESULT: # noqa: N803 + return S_OK + def SetFileTypeIndex(self, iFileType: c_uint | int) -> HRESULT: # noqa: N803 + return S_OK + def GetFileTypeIndex(self, piFileType: _Pointer[c_uint]) -> HRESULT: # noqa: N803 + return S_OK + def Advise(self, pfde: _Pointer[comtypes.IUnknown], pdwCookie: _Pointer[DWORD]) -> HRESULT: # noqa: N803 + return S_OK + def Unadvise(self, dwCookie: int) -> HRESULT: # noqa: N803 + return S_OK + def SetOptions(self, fos: int) -> HRESULT: + return S_OK + def GetOptions(self, pfos: _Pointer[DWORD]) -> HRESULT: + return S_OK + def SetDefaultFolder(self, psi: IShellItem) -> HRESULT: + return S_OK + def SetFolder(self, psi: IShellItem) -> HRESULT: + return S_OK + def GetFolder(self, ppsi: IShellItem) -> HRESULT: + return S_OK + def GetCurrentSelection(self, ppsi: IShellItem) -> HRESULT: + return S_OK + def SetFileName(self, pszName: LPCWSTR | str) -> HRESULT: # noqa: N803 + return S_OK + def GetFileName(self, pszName: _Pointer[LPWSTR]) -> HRESULT: # noqa: N803 + return S_OK + def SetTitle(self, pszTitle: LPCWSTR | str) -> HRESULT: # noqa: N803 + return S_OK + def SetOkButtonLabel(self, pszText: LPCWSTR | str) -> HRESULT: # noqa: N803 + return S_OK + def SetFileNameLabel(self, pszLabel: LPCWSTR | str) -> HRESULT: # noqa: N803 + return S_OK + def GetResult(self, ppsi: IShellItem) -> HRESULT: + return S_OK + def AddPlace(self, psi: IShellItem, fdap: c_int) -> HRESULT: + return S_OK + def SetDefaultExtension(self, pszDefaultExtension: LPCWSTR | str) -> HRESULT: # noqa: N803 + return S_OK + def Close(self, hr: HRESULT | int) -> HRESULT: + return S_OK + def SetClientGuid(self, guid: GUID) -> HRESULT: + return S_OK + def ClearClientData(self) -> HRESULT: + return S_OK + def SetFilter(self, pFilter: IShellItemFilter) -> HRESULT: # noqa: N803 + return S_OK + def SetSaveAsItem(self, psi: IShellItem) -> HRESULT: + return S_OK + def SetProperties(self, pStore: _Pointer[comtypes.IUnknown]) -> HRESULT: # noqa: N803 + return S_OK + def SetCollectedProperties(self, pList: _Pointer[comtypes.IUnknown], fAppendDefault: BOOL | int) -> HRESULT: # noqa: N803 + return S_OK + def GetProperties(self, ppStore: comtypes.IUnknown) -> HRESULT: # noqa: N803 + return S_OK + def ApplyProperties(self, psi: IShellItem, pStore: _Pointer[comtypes.IUnknown], hwnd: HWND | int, pSink: _Pointer[comtypes.IUnknown]) -> HRESULT: # noqa: N803 + return S_OK + + +class IFileDialogCustomize(comtypes.IUnknown): + _case_insensitive_ = True + _iid_ = IID_IFileDialogCustomize + _methods_: ClassVar[list[_ComMemberSpec]] = [ + COMMETHOD([], HRESULT, "EnableOpenDropDown", (["in"], c_uint, "dwIDCtl")), + COMMETHOD([], HRESULT, "AddText", (["in"], c_uint, "dwIDCtl"), (["in"], LPCWSTR, "pszText")), + COMMETHOD([], HRESULT, "AddPushButton", (["in"], c_uint, "dwIDCtl"), (["in"], LPCWSTR, "pszLabel")), + COMMETHOD([], HRESULT, "AddCheckButton", (["in"], c_uint, "dwIDCtl"), (["in"], LPCWSTR, "pszLabel"), (["in"], c_int, "bChecked")), + COMMETHOD([], HRESULT, "AddRadioButtonList", (["in"], c_uint, "dwIDCtl")), + COMMETHOD([], HRESULT, "AddComboBox", (["in"], c_uint, "dwIDCtl")), + COMMETHOD([], HRESULT, "AddControlItem", (["in"], c_uint, "dwIDCtl"), (["in"], c_uint, "dwIDItem"), (["in"], LPCWSTR, "pszLabel")), + COMMETHOD([], HRESULT, "AddEditBox", (["in"], c_uint, "dwIDCtl"), (["in"], LPCWSTR, "pszText")), + COMMETHOD([], HRESULT, "AddSeparator", (["in"], c_uint, "dwIDCtl")), + COMMETHOD([], HRESULT, "AddMenu", (["in"], c_uint, "dwIDCtl"), (["in"], LPCWSTR, "pszLabel")), + COMMETHOD([], HRESULT, "SetControlLabel", (["in"], c_uint, "dwIDCtl"), (["in"], LPCWSTR, "pszLabel")), + COMMETHOD([], HRESULT, "SetControlState", (["in"], c_uint, "dwIDCtl"), (["in"], c_int, "dwState")), + COMMETHOD([], HRESULT, "SetCheckButtonState", (["in"], c_uint, "dwIDCtl"), (["in"], c_int, "bChecked")), + COMMETHOD([], HRESULT, "GetCheckButtonState", (["in"], c_uint, "dwIDCtl"), (["out"], POINTER(c_int), "pbChecked")), + COMMETHOD([], HRESULT, "SetEditBoxText", (["in"], c_uint, "dwIDCtl"), (["in"], LPCWSTR, "pszText")), + COMMETHOD([], HRESULT, "GetEditBoxText", (["in"], c_uint, "dwIDCtl"), (["out"], POINTER(LPCWSTR), "ppszText")), + COMMETHOD([], HRESULT, "SetControlItemText", (["in"], c_uint, "dwIDCtl"), (["in"], c_uint, "dwIDItem"), (["in"], LPCWSTR, "pszLabel")), + COMMETHOD([], HRESULT, "GetControlItemState", (["in"], c_uint, "dwIDCtl"), (["in"], c_uint, "dwIDItem"), (["out"], POINTER(c_int), "pdwState")), + COMMETHOD([], HRESULT, "SetControlItemState", (["in"], c_uint, "dwIDCtl"), (["in"], c_uint, "dwIDItem"), (["in"], c_int, "dwState")), + COMMETHOD([], HRESULT, "GetSelectedControlItem", (["in"], c_uint, "dwIDCtl"), (["out"], POINTER(c_uint), "pdwIDItem")), + COMMETHOD([], HRESULT, "SetSelectedControlItem", (["in"], c_uint, "dwIDCtl"), (["in"], c_uint, "dwIDItem")), + COMMETHOD([], HRESULT, "StartVisualGroup", (["in"], c_uint, "dwIDCtl"), (["in"], LPCWSTR, "pszLabel")), + COMMETHOD([], HRESULT, "EndVisualGroup", (["in"], c_uint, "dwIDCtl")), + COMMETHOD([], HRESULT, "MakeProminent", (["in"], c_uint, "dwIDCtl")), + COMMETHOD([], HRESULT, "RemoveControlItem", (["in"], c_uint, "dwIDCtl"), (["in"], c_uint, "dwIDItem")), + COMMETHOD([], HRESULT, "RemoveAllControlItems", (["in"], c_uint, "dwIDCtl")), + COMMETHOD([], HRESULT, "GetControlState", (["in"], c_uint, "dwIDCtl"), (["out"], POINTER(c_int), "pdwState")), + ] + EnableOpenDropDown: Callable[[int], HRESULT] + AddText: Callable[[int, str], HRESULT] + AddPushButton: Callable[[int, str], HRESULT] + AddCheckButton: Callable[[int, str, int], HRESULT] + AddRadioButtonList: Callable[[int], HRESULT] + AddComboBox: Callable[[int], HRESULT] + AddControlItem: Callable[[int, int, str], HRESULT] + AddEditBox: Callable[[int, str], HRESULT] + AddSeparator: Callable[[int], HRESULT] + AddMenu: Callable[[int, str], HRESULT] + SetControlLabel: Callable[[int, str], HRESULT] + SetControlState: Callable[[int, int], HRESULT] + SetCheckButtonState: Callable[[int, int], HRESULT] + GetCheckButtonState: Callable[[int], int] + SetEditBoxText: Callable[[int, str], HRESULT] + GetEditBoxText: Callable[[int], LPCWSTR] + SetControlItemText: Callable[[int, int, str], HRESULT] + GetControlItemState: Callable[[int, int], int] + SetControlItemState: Callable[[int, int, int], HRESULT] + GetSelectedControlItem: Callable[[int], int] + SetSelectedControlItem: Callable[[int, int], HRESULT] + StartVisualGroup: Callable[[int, str], HRESULT] + EndVisualGroup: Callable[[int], HRESULT] + MakeProminent: Callable[[int], HRESULT] + RemoveControlItem: Callable[[int, int], HRESULT] + RemoveAllControlItems: Callable[[int], HRESULT] + GetControlState: Callable[[int], int] + + +class IFileDialogControlEvents(comtypes.IUnknown): + _case_insensitive_ = True + _iid_ = IID_IFileDialogControlEvents + _methods_: ClassVar[list[_ComMemberSpec]] = [ + COMMETHOD([], HRESULT, "OnItemSelected", + (["in"], comtypes.POINTER(IFileDialogCustomize), "pfdc"), + (["in"], c_int, "dwIDCtl"), + (["in"], c_int, "dwIDItem")), + COMMETHOD([], HRESULT, "OnButtonClicked", + (["in"], comtypes.POINTER(IFileDialogCustomize), "pfdc"), + (["in"], c_int, "dwIDCtl")), + COMMETHOD([], HRESULT, "OnCheckButtonToggled", + (["in"], comtypes.POINTER(IFileDialogCustomize), "pfdc"), + (["in"], c_int, "dwIDCtl"), + (["in"], c_bool, "bChecked")), + COMMETHOD([], HRESULT, "OnControlActivating", + (["in"], comtypes.POINTER(IFileDialogCustomize), "pfdc"), + (["in"], c_int, "dwIDCtl")), + ] + OnButtonClicked: Callable[[IFileDialogCustomize, c_uint], HRESULT] + OnCheckButtonToggled: Callable[[IFileDialogCustomize, c_uint, c_int], HRESULT] + OnControlActivating: Callable[[IFileDialogCustomize, c_uint], HRESULT] + OnItemSelected: Callable[[IFileDialogCustomize, c_uint, c_uint], HRESULT] + + +if __name__ == "__main__": + assert ModalWindow() + assert ShellItem() + assert ContextMenu() + assert ShellFolder() + assert ShellItemArray() + assert ShellItemFilter() + assert EnumShellItems() + assert PropertyStore() + assert FileOperationProgressSink() + assert FileDialogEvents() + assert FileDialog() + assert ShellLibrary() + assert FileOpenDialog() + assert FileSaveDialog() diff --git a/winforms/src/toga_winforms/libs/py_wrappers/com/progress_dialog.py b/winforms/src/toga_winforms/libs/py_wrappers/com/progress_dialog.py new file mode 100644 index 0000000000..597fff950f --- /dev/null +++ b/winforms/src/toga_winforms/libs/py_wrappers/com/progress_dialog.py @@ -0,0 +1,149 @@ +from __future__ import annotations + +import errno +import os +from pathlib import WindowsPath +from typing import TYPE_CHECKING + +import comtypes +from comtypes.client import CreateObject +from comtypes.gen import Shell32 +from utility.logger_util import RobustRootLogger + +from toga_winforms.libs.win_wrappers.com.interfaces import ( + CLSID_FileOperation, + IFileOperationProgressSink, +) +from toga_winforms.libs.win_wrappers.hresult import S_OK + +if TYPE_CHECKING: + from typing_extensions import Literal + + +class FileOperationProgressSinkImpl(IFileOperationProgressSink): + def StartOperations(self): + print("Operation started") + return S_OK + + def FinishOperations(self, hr): + print("Operation finished with hr:", hr) + return S_OK + + def PreRenameItem(self, dwFlags, psiItem, pszNewName): + print(f"Preparing to rename item {psiItem.GetDisplayName()} to {pszNewName}") + return S_OK + + def PostRenameItem(self, dwFlags, psiItem, pszNewName, hrRename, psiNewlyCreated): + print(f"Renamed item {psiItem.GetDisplayName()} to {pszNewName}") + return S_OK + + def PreMoveItem(self, dwFlags, psiItem, psiDestinationFolder, pszNewName): + print(f"Preparing to move item {psiItem.GetDisplayName()} to {pszNewName}") + return S_OK + + def PostMoveItem(self, dwFlags, psiItem, psiDestinationFolder, pszNewName, hrMove, psiNewlyCreated): + print(f"Moved item {psiItem.GetDisplayName()} to {pszNewName}") + return S_OK + + def PreCopyItem(self, dwFlags, psiItem, psiDestinationFolder, pszNewName): + print(f"Preparing to copy item {psiItem.GetDisplayName()} to {pszNewName}") + return S_OK + + def PostCopyItem(self, dwFlags, psiItem, psiDestinationFolder, pszNewName, hrCopy, psiNewlyCreated): + print(f"Copied item {psiItem.GetDisplayName()} to {pszNewName}") + return S_OK + + def PreDeleteItem(self, dwFlags, psiItem): + print(f"Preparing to delete item {psiItem.GetDisplayName()}") + return S_OK + + def PostDeleteItem(self, dwFlags, psiItem, hrDelete, psiNewlyCreated): + print(f"Deleted item {psiItem.GetDisplayName()}") + return S_OK + + def PreNewItem(self, dwFlags, psiDestinationFolder, pszNewName): + print(f"Preparing to create new item {pszNewName}") + return S_OK + + def PostNewItem(self, dwFlags, psiDestinationFolder, pszNewName, pszTemplateName, dwFileAttributes, hrNew, psiNewItem): + print(f"Created new item {pszNewName}") + return S_OK + + def UpdateProgress(self, iWorkTotal, iWorkSoFar): + print(f"Progress: {iWorkSoFar}/{iWorkTotal}") + return S_OK + + def ResetTimer(self): + print("Timer reset") + return S_OK + + def PauseTimer(self): + print("Timer paused") + return S_OK + + def ResumeTimer(self): + print("Timer resumed") + return S_OK + +def initialize_com(): + comtypes.CoInitialize() + +def uninitialize_com(): + comtypes.CoUninitialize() + +def create_shell_item(path_obj): + path = WindowsPath(path_obj).resolve() + if not path.exists(): + raise FileNotFoundError(errno.ENOENT, os.strerror(errno.ENOENT), str(path)) + shell = CreateObject(Shell32.Shell, interface=Shell32.IShellDispatch4) + folder = shell.NameSpace(str(path.parent)) + item = folder.ParseName(path.name) + return item.QueryInterface(Shell32.IShellItem) + +def perform_file_operation( + source: os.PathLike | str, + destination: os.PathLike | str, + operation: Literal["copy", "move"] = "copy", +): + initialize_com() + source_path = source + destination_path = destination + + try: + file_operation = CreateObject(CLSID_FileOperation, interface=Shell32.IFileOperation) + + source_item = create_shell_item(source_path) + dest_item = create_shell_item(destination_path) + + progress_sink = FileOperationProgressSinkImpl() + file_operation.Advise(progress_sink, None) + + file_operation.SetOperationFlags(Shell32.FOF_NOCONFIRMATION | Shell32.FOF_SILENT) + + if operation == "copy": + file_operation.CopyItem(source_item, dest_item, None, None) + elif operation == "move": + file_operation.MoveItem(source_item, dest_item, None, None) + elif operation == "delete": + file_operation.DeleteItem(source_item, None) + else: + raise ValueError(f"Unsupported operation: {operation}") # noqa: TRY301 + + result = file_operation.PerformOperations() + if result != 0: + raise comtypes.COMError(result, "Error performing file operation", None) # noqa: TRY301 + + print(f"File operation {operation} from {source_path} to {destination_path} completed") + + except Exception as e: # noqa: BLE001 + RobustRootLogger().exception(f"General error while attempting to perform file operations with the com objects: {e.__class__.__name__}: {e}") + + finally: + uninitialize_com() + +# Example usage +if __name__ == "__main__": + source_path = r"C:\path\to\source\file.txt" + destination_path = r"C:\path\to\destination\folder" + + perform_file_operation(source_path, destination_path, operation="copy") diff --git a/winforms/src/toga_winforms/libs/py_wrappers/com/windialogs copy.py b/winforms/src/toga_winforms/libs/py_wrappers/com/windialogs copy.py new file mode 100644 index 0000000000..5324e0e501 --- /dev/null +++ b/winforms/src/toga_winforms/libs/py_wrappers/com/windialogs copy.py @@ -0,0 +1,826 @@ +from __future__ import annotations + +import errno +import json +import os +import random +from ctypes import ( + HRESULT, + POINTER, + Structure, + WinError, + byref, + c_int, + c_uint, + c_ulong, + c_void_p, + c_wchar_p, + windll, +) +from ctypes import cast as cast_with_ctypes +from ctypes.wintypes import BOOL, DWORD, HWND, LPCWSTR, LPWSTR +from enum import IntFlag +from pathlib import WindowsPath +from typing import TYPE_CHECKING, Callable, ClassVar, Sequence + +import comtypes # pyright: ignore[reportMissingTypeStubs] +import comtypes.client # pyright: ignore[reportMissingTypeStubs] +from comtypes import COMMETHOD, GUID +from comtypes.hresult import S_OK + +if TYPE_CHECKING: + from ctypes import Array, _CData, _Pointer + from ctypes.wintypes import ULONG + + from comtypes._memberspec import _ComMemberSpec + + +class COMDLG_FILTERSPEC(Structure): # noqa: N801 + _fields_: Sequence[tuple[str, type[_CData]] | tuple[str, type[_CData], int]] = [ + ("pszName", LPCWSTR), + ("pszSpec", LPCWSTR) + ] + + +class FileOpenOptions(IntFlag): + FOS_OVERWRITEPROMPT = 0x00000002 + FOS_STRICTFILETYPES = 0x00000004 + FOS_NOCHANGEDIR = 0x00000008 + FOS_PICKFOLDERS = 0x00000020 + FOS_FORCEFILESYSTEM = 0x00000040 + FOS_ALLNONSTORAGEITEMS = 0x00000080 + FOS_NOVALIDATE = 0x00000100 + FOS_ALLOWMULTISELECT = 0x00000200 + FOS_PATHMUSTEXIST = 0x00000800 + FOS_FILEMUSTEXIST = 0x00001000 + FOS_CREATEPROMPT = 0x00002000 + FOS_SHAREAWARE = 0x00004000 + FOS_NOREADONLYRETURN = 0x00008000 + FOS_NOTESTFILECREATE = 0x00010000 + FOS_HIDEMRUPLACES = 0x00020000 + FOS_HIDEPINNEDPLACES = 0x00040000 + FOS_NODEREFERENCELINKS = 0x00100000 + FOS_DONTADDTORECENT = 0x02000000 + FOS_FORCESHOWHIDDEN = 0x10000000 + FOS_DEFAULTNOMINIMODE = 0x20000000 + FOS_FORCEPREVIEWPANEON = 0x40000000 + + + +IID_IShellItem = GUID("{43826D1E-E718-42EE-BC55-A1E261C37BFE}") +IID_IShellItemArray = GUID("{B63EA76D-1F85-456F-A19C-48159EFA858B}") +IID_IShellItemFilter = GUID("{2659B475-EEB8-48B7-8F07-B378810F48CF}") +IID_IModalWindow = GUID("{B4DB1657-70D7-485E-8E3E-6FCB5A5C1802}") +IID_IFileDialog = GUID("{42F85136-DB7E-439C-85F1-E4075D135FC8}") +IID_IFileOpenDialog = GUID("{D57C7288-D4AD-4768-BE02-9D969532D960}") +IID_IFileSaveDialog = GUID("{84BCCD23-5FDE-4CDB-AEA4-AF64B83D78AB}") +CLSID_FileOpenDialog = GUID("{DC1C5A9C-E88A-4dde-A5A1-60F82A20AEF7}") +CLSID_FileSaveDialog = GUID("{C0B4E2F3-BA21-4773-8DBA-335EC946EB8B}") + + +class IShellItem(comtypes.IUnknown): + _case_insensitive_: bool = True + _iid_: GUID = IID_IShellItem + _methods_: ClassVar[list[_ComMemberSpec]] = [ + COMMETHOD([], HRESULT, "BindToHandler", + (["in"], POINTER(comtypes.IUnknown), "pbc"), + (["in"], POINTER(GUID), "bhid"), + (["in"], POINTER(GUID), "riid"), + (["out"], POINTER(c_void_p), "ppv")), + COMMETHOD([], HRESULT, "GetParent", + (["out"], POINTER(POINTER(comtypes.IUnknown)), "ppsi")), + COMMETHOD([], HRESULT, "GetDisplayName", + (["in"], c_ulong, "sigdnName"), + (["out"], POINTER(LPWSTR), "ppszName")), + COMMETHOD([], HRESULT, "GetAttributes", + (["in"], c_ulong, "sfgaoMask"), + (["out"], POINTER(c_ulong), "psfgaoAttribs")), + COMMETHOD([], HRESULT, "Compare", + (["in"], POINTER(comtypes.IUnknown), "psi"), + (["in"], c_ulong, "hint"), + (["out"], POINTER(c_int), "piOrder")) + ] + QueryInterface: Callable[[GUID, comtypes.IUnknown], int] + AddRef: Callable[[], ULONG] + Release: Callable[[], ULONG] + BindToHandler: Callable[[comtypes.IUnknown, GUID, GUID, _Pointer[c_void_p]], int] + GetParent: Callable[[], comtypes.IUnknown] + GetDisplayName: Callable[[c_ulong | int], str] + GetAttributes: Callable[[c_ulong | int], int] + Compare: Callable[[comtypes.IUnknown, c_ulong, c_int], int] + + +SHCreateItemFromParsingName = windll.shell32.SHCreateItemFromParsingName +SHCreateItemFromParsingName.argtypes = [ + c_wchar_p, # LPCWSTR (wide string, null-terminated) + POINTER(comtypes.IUnknown), # IBindCtx* (can be NULL, hence POINTER(IUnknown)) + POINTER(GUID), # REFIID (pointer to the interface ID, typically GUID) + POINTER(POINTER(IShellItem)) # void** (output pointer to the requested interface) +] +SHCreateItemFromParsingName.restype = HRESULT + + +class IShellItemArray(comtypes.IUnknown): + _case_insensitive_: bool = True + _iid_: GUID = IID_IShellItemArray + _methods_: ClassVar[list[_ComMemberSpec]] = [ + COMMETHOD([], HRESULT, "BindToHandler", + (["in"], POINTER(comtypes.IUnknown), "pbc"), + (["in"], POINTER(GUID), "bhid"), + (["in"], POINTER(GUID), "riid"), + (["out"], POINTER(c_void_p), "ppv")), + COMMETHOD([], HRESULT, "GetPropertyStore", + (["in"], c_ulong, "flags"), + (["in"], POINTER(GUID), "riid"), + (["out"], POINTER(c_void_p), "ppv")), + COMMETHOD([], HRESULT, "GetPropertyDescriptionList", + (["in"], POINTER(GUID), "keyType"), + (["in"], POINTER(GUID), "riid"), + (["out"], POINTER(c_void_p), "ppv")), + COMMETHOD([], HRESULT, "GetAttributes", + (["in"], c_ulong, "attribFlags"), + (["in"], c_ulong, "sfgaoMask"), + (["out"], POINTER(c_ulong), "psfgaoAttribs")), + COMMETHOD([], HRESULT, "GetCount", + (["out"], POINTER(c_uint), "pdwNumItems")), + COMMETHOD([], HRESULT, "GetItemAt", + (["in"], c_uint, "dwIndex"), + (["out"], POINTER(POINTER(IShellItem)), "ppsi")), + COMMETHOD([], HRESULT, "EnumItems", + (["out"], POINTER(POINTER(comtypes.IUnknown)), "ppenumShellItems")) + ] + QueryInterface: Callable[[GUID, comtypes.IUnknown], int] + AddRef: Callable[[], ULONG] + Release: Callable[[], ULONG] + BindToHandler: Callable[[_Pointer[comtypes.IUnknown], GUID, GUID], int] + GetPropertyStore: Callable[[c_ulong, GUID], c_void_p] + GetPropertyDescriptionList: Callable[[GUID, GUID], c_void_p] + GetAttributes: Callable[[c_ulong, c_ulong], _Pointer[c_ulong]] + GetCount: Callable[[], int] + GetItemAt: Callable[[c_uint | int], IShellItem] + EnumItems: Callable[[], comtypes.IUnknown] + + +class IModalWindow(comtypes.IUnknown): + _case_insensitive_: bool = True + _iid_: GUID = IID_IModalWindow + _methods_: ClassVar[list[_ComMemberSpec]] = [ + COMMETHOD([], HRESULT, "Show", + (["in"], HWND, "hwndParent")) + ] + Show: Callable[[int | HWND], int] + + +class IFileDialog(IModalWindow): + _iid_: GUID = IID_IFileDialog + _methods_: ClassVar[list[_ComMemberSpec]] = [ + COMMETHOD([], HRESULT, "SetFileTypes", + (["in"], c_uint, "cFileTypes"), + (["in"], POINTER(c_void_p), "rgFilterSpec")), + COMMETHOD([], HRESULT, "SetFileTypeIndex", + (["in"], c_uint, "iFileType")), + COMMETHOD([], HRESULT, "GetFileTypeIndex", + (["out"], POINTER(c_uint), "piFileType")), + COMMETHOD([], HRESULT, "Advise", + (["in"], POINTER(comtypes.IUnknown), "pfde"), + (["out"], POINTER(DWORD), "pdwCookie")), + COMMETHOD([], HRESULT, "Unadvise", + (["in"], DWORD, "dwCookie")), + COMMETHOD([], HRESULT, "SetOptions", + (["in"], c_uint, "fos")), + COMMETHOD([], HRESULT, "GetOptions", + (["out"], POINTER(DWORD), "pfos")), + COMMETHOD([], HRESULT, "SetDefaultFolder", + (["in"], POINTER(IShellItem), "psi")), + COMMETHOD([], HRESULT, "SetFolder", + (["in"], POINTER(IShellItem), "psi")), + COMMETHOD([], HRESULT, "GetFolder", + (["out"], POINTER(POINTER(IShellItem)), "ppsi")), + COMMETHOD([], HRESULT, "GetCurrentSelection", + (["out"], POINTER(POINTER(IShellItem)), "ppsi")), + COMMETHOD([], HRESULT, "SetFileName", + (["in"], LPCWSTR, "pszName")), + COMMETHOD([], HRESULT, "GetFileName", + (["out"], POINTER(LPWSTR), "pszName")), + COMMETHOD([], HRESULT, "SetTitle", + (["in"], LPCWSTR, "pszTitle")), + COMMETHOD([], HRESULT, "SetOkButtonLabel", + (["in"], LPCWSTR, "pszText")), + COMMETHOD([], HRESULT, "SetFileNameLabel", + (["in"], LPCWSTR, "pszLabel")), + COMMETHOD([], HRESULT, "GetResult", + (["out"], POINTER(POINTER(IShellItem)), "ppsi")), + COMMETHOD([], HRESULT, "AddPlace", + (["in"], POINTER(IShellItem), "psi"), + (["in"], c_int, "fdap")), + COMMETHOD([], HRESULT, "SetDefaultExtension", + (["in"], LPCWSTR, "pszDefaultExtension")), + COMMETHOD([], HRESULT, "Close", + (["in"], HRESULT, "hr")), + COMMETHOD([], HRESULT, "SetClientGuid", + (["in"], POINTER(GUID), "guid")), + COMMETHOD([], HRESULT, "ClearClientData"), + COMMETHOD([], HRESULT, "SetFilter", + (["in"], POINTER(comtypes.IUnknown), "pFilter")) # IShellItemFilter + ] + SetFileTypes: Callable[[c_uint | int, _Pointer[c_void_p]], int] + SetFileTypeIndex: Callable[[c_uint], int] + GetFileTypeIndex: Callable[[], _Pointer[c_uint]] + Advise: Callable[[comtypes.IUnknown | comtypes.COMObject], int] + Unadvise: Callable[[int], int] + SetOptions: Callable[[DWORD | int], int] + GetOptions: Callable[[], int] + SetDefaultFolder: Callable[[_Pointer[IShellItem]], int] + SetFolder: Callable[[_Pointer[IShellItem]], int] + GetFolder: Callable[[], IShellItem] + GetCurrentSelection: Callable[[], IShellItem] + SetFileName: Callable[[str], int] + GetFileName: Callable[[], _Pointer[LPWSTR]] + SetTitle: Callable[[str], int] + SetOkButtonLabel: Callable[[str], int] + SetFileNameLabel: Callable[[str], int] + GetResult: Callable[[], IShellItem] + AddPlace: Callable[[IShellItem, c_int], int] + SetDefaultExtension: Callable[[str], int] + Close: Callable[[HRESULT], int] + SetClientGuid: Callable[[GUID], int] + ClearClientData: Callable[[], int] + SetFilter: Callable[[comtypes.IUnknown], int] + + +class IFileOpenDialog(IFileDialog): + _case_insensitive_: bool = True + _iid_: GUID = IID_IFileOpenDialog + _methods_: ClassVar[list[_ComMemberSpec]] = [ + COMMETHOD([], HRESULT, "GetResults", + (["out"], POINTER(POINTER(IShellItemArray)), "ppenum")), + COMMETHOD([], HRESULT, "GetSelectedItems", + (["out"], POINTER(POINTER(IShellItemArray)), "ppsai")) + ] + GetResults: Callable[[], IShellItemArray] + GetSelectedItems: Callable[[], IShellItemArray] + + +class IFileSaveDialog(IFileDialog): + _case_insensitive_: bool = True + _iid_: GUID = IID_IFileSaveDialog + _methods_: ClassVar[list[_ComMemberSpec]] = [ + COMMETHOD([], HRESULT, "SetSaveAsItem", + (["in"], POINTER(IShellItem), "psi")), + COMMETHOD([], HRESULT, "SetProperties", + (["in"], POINTER(comtypes.IUnknown), "pStore")), + COMMETHOD([], HRESULT, "SetCollectedProperties", + (["in"], POINTER(comtypes.IUnknown), "pList"), + (["in"], BOOL, "fAppendDefault")), + COMMETHOD([], HRESULT, "GetProperties", + (["out"], POINTER(POINTER(comtypes.IUnknown)), "ppStore")), + COMMETHOD([], HRESULT, "ApplyProperties", + (["in"], POINTER(IShellItem), "psi"), + (["in"], POINTER(comtypes.IUnknown), "pStore"), + (["in"], HWND, "hwnd"), + (["in"], POINTER(comtypes.IUnknown), "pSink")) + ] + SetSaveAsItem: Callable[[IShellItem], int] + SetProperties: Callable[[comtypes.IUnknown], int] + SetCollectedProperties: Callable[[comtypes.IUnknown, BOOL], int] + GetProperties: Callable[[comtypes.IUnknown], int] + ApplyProperties: Callable[[IShellItem, comtypes.IUnknown, HWND, comtypes.IUnknown], int] + + +def show_file_dialog( + fileDialog: IFileOpenDialog | IFileSaveDialog, # noqa: N803 + hwndOwner: HWND, # noqa: N803 +) -> bool: + """Shows the IFileDialog. Returns True if the user progressed to the end and found a file. False if they cancelled.""" + hr: HRESULT | int = -1 + CANCELLED_BY_USER = -2147023673 + + try: + hr = fileDialog.Show(hwndOwner) + except OSError as e: + if e.winerror == CANCELLED_BY_USER: + return False + raise + else: + if hr: + raise WinError(hr, "An unexpected error occurred showing the file browser dialog") + return True + + +def create_shell_item(path: str) -> _Pointer[IShellItem]: # noqa: N803, ARG001 + shell_item = POINTER(IShellItem)() + hr = SHCreateItemFromParsingName(path, None, IShellItem._iid_, byref(shell_item)) + if hr != S_OK: + raise WinError(hr, f"Failed to create shell item from path: {path}") + return shell_item + + +DEFAULT_FILTERS: list[COMDLG_FILTERSPEC] = [ + COMDLG_FILTERSPEC("All Files", "*.*"), + COMDLG_FILTERSPEC("Text Files", "*.txt"), + COMDLG_FILTERSPEC("Image Files", "*.png;*.jpg;*.jpeg;*.bmp;*.gif"), + COMDLG_FILTERSPEC("Document Files", "*.doc;*.docx;*.pdf;*.xls;*.xlsx"), + COMDLG_FILTERSPEC("Audio Files", "*.mp3;*.wav;*.wma;*.aac"), + COMDLG_FILTERSPEC("Video Files", "*.mp4;*.avi;*.mkv;*.mov;*.wmv"), + COMDLG_FILTERSPEC("Archive Files", "*.zip;*.rar;*.7z;*.tar;*.gz"), +] + + +def configure_file_dialog( # noqa: PLR0913, PLR0912, C901, PLR0915 + file_dialog: IFileSaveDialog | IFileOpenDialog, + title: str | None = None, + options: int = 0, + default_folder: str | None = None, + ok_button_label: str | None = None, + file_name_label: str | None = None, + file_types: list[tuple[str, str]] | None = None, + default_extension: str | None = None, + hwnd: HWND | int | None = None, +) -> list[str] | None: # sourcery skip: low-code-quality + hwnd = HWND(hwnd) if isinstance(hwnd, int) else hwnd + hwnd = HWND(0) if hwnd is None else hwnd + if default_folder: + default_folder_path = WindowsPath(default_folder).resolve() + if not default_folder_path.exists() or not default_folder_path.is_dir(): + raise FileNotFoundError(errno.ENOENT, os.strerror(errno.ENOENT), str(default_folder_path)) + shell_item = create_shell_item(str(default_folder_path)) + file_dialog.SetFolder(shell_item) + file_dialog.SetDefaultFolder(shell_item) + + # Resolve contradictory options + if options & FileOpenOptions.FOS_ALLNONSTORAGEITEMS: + if options & FileOpenOptions.FOS_FORCEFILESYSTEM: + options &= ~FileOpenOptions.FOS_FORCEFILESYSTEM + if options & FileOpenOptions.FOS_PICKFOLDERS: + options &= ~FileOpenOptions.FOS_PICKFOLDERS + + file_dialog.SetOptions(options) + + if not options & FileOpenOptions.FOS_PICKFOLDERS: + filters: Array[COMDLG_FILTERSPEC] + if file_types: + filters = (COMDLG_FILTERSPEC * len(file_types))( + *[ + (c_wchar_p(name), c_wchar_p(spec)) + for name, spec in file_types + ] + ) + else: + filters = (COMDLG_FILTERSPEC * len(DEFAULT_FILTERS))(*DEFAULT_FILTERS) + file_dialog.SetFileTypes(len(filters), cast_with_ctypes(filters, POINTER(c_void_p))) + + if title: + file_dialog.SetTitle(title) + + if ok_button_label: + file_dialog.SetOkButtonLabel(ok_button_label) + elif isinstance(file_dialog, IFileSaveDialog): + file_dialog.SetOkButtonLabel("Save") + elif options & FileOpenOptions.FOS_PICKFOLDERS: + file_dialog.SetOkButtonLabel("Select Folder") + else: + file_dialog.SetOkButtonLabel("Select File") + + if file_name_label: + file_dialog.SetFileNameLabel(file_name_label) + if default_extension: + file_dialog.SetDefaultExtension(default_extension) + + if show_file_dialog(file_dialog, hwnd): + return ( + [get_save_file_dialog_results(file_dialog)] + if isinstance(file_dialog, IFileSaveDialog) + else get_open_file_dialog_results(file_dialog) + ) + return None + + +def open_file_dialog( # noqa: C901, PLR0913, PLR0912 + title: str | None = "Open File", + default_folder: str | None = None, + file_types: list[tuple[str, str]] | None = None, + default_extension: str | None = None, + *, + overwrite_prompt: bool = False, + strict_file_types: bool = False, + no_change_dir: bool = True, + force_filesystem: bool = True, + all_non_storage_items: bool = False, + no_validate: bool = False, + allow_multiple_selection: bool = False, + path_must_exist: bool = True, + file_must_exist: bool = True, + create_prompt: bool = False, + share_aware: bool = False, + no_readonly_return: bool = False, + no_test_file_create: bool = False, + hide_mru_places: bool = False, + hide_pinned_places: bool = False, + no_dereference_links: bool = False, + add_to_recent: bool = True, + show_hidden_files: bool = False, + default_no_minimode: bool = False, + force_preview_pane_on: bool = False, + ok_button_text: str | None = None, +) -> list[str] | None: # sourcery skip: low-code-quality + """Opens a file dialog to select files. + + Args: + title (str | None): The title of the dialog. + default_folder (str | None): The initial folder to open. + file_types (list[tuple[str, str]] | None): A list of file type filters. + default_extension (str | None): The default file extension. + overwrite_prompt (bool): Prompts if the selected file already exists. FileOpenOptions.FOS_OVERWRITEPROMPT. + strict_file_types (bool): Restricts selection to specified file types. FileOpenOptions.FOS_STRICTFILETYPES. + no_change_dir (bool): Prevents changing the current working directory. FileOpenOptions.FOS_NOCHANGEDIR. + force_filesystem (bool): Ensures only file system items are shown. FileOpenOptions.FOS_FORCEFILESYSTEM. + all_non_storage_items (bool): Allows selection of non-file system items. FileOpenOptions.FOS_ALLNONSTORAGEITEMS. + no_validate (bool): Disables file name validation. FileOpenOptions.FOS_NOVALIDATE. + allow_multiple_selection (bool): Allows selecting multiple files. FileOpenOptions.FOS_ALLOWMULTISELECT. + path_must_exist (bool): Requires the path to exist. FileOpenOptions.FOS_PATHMUSTEXIST. + file_must_exist (bool): Requires the file to exist. FileOpenOptions.FOS_FILEMUSTEXIST. + create_prompt (bool): Prompts to create a new file if it doesn't exist. FileOpenOptions.FOS_CREATEPROMPT. + share_aware (bool): Ensures the dialog is aware of sharing conflicts. FileOpenOptions.FOS_SHAREAWARE. + no_readonly_return (bool): Prevents selection of read-only items. FileOpenOptions.FOS_NOREADONLYRETURN. + no_test_file_create (bool): Disables testing file creation ability. FileOpenOptions.FOS_NOTESTFILECREATE. + hide_mru_places (bool): Hides most recently used places. FileOpenOptions.FOS_HIDEMRUPLACES. + hide_pinned_places (bool): Hides pinned places. FileOpenOptions.FOS_HIDEPINNEDPLACES. + no_dereference_links (bool): Prevents dereferencing shortcuts. FileOpenOptions.FOS_NODEREFERENCELINKS. + add_to_recent (bool): Prevents adding the file to recent files. FileOpenOptions.FOS_DONTADDTORECENT. + show_hidden_files (bool): Shows hidden files and folders. FileOpenOptions.FOS_FORCESHOWHIDDEN. + default_no_minimode (bool): Uses default non-minimized mode. FileOpenOptions.FOS_DEFAULTNOMINIMODE. + force_preview_pane_on (bool): Forces the preview pane to be visible. FileOpenOptions.FOS_FORCEPREVIEWPANEON. + ok_button_text (str): The text for the button used to select/confirm the dialog. + + Returns: + list[str] | None: A list of selected file paths or None if cancelled. + """ + options = 0 + if overwrite_prompt: + options |= FileOpenOptions.FOS_OVERWRITEPROMPT + if strict_file_types: + options |= FileOpenOptions.FOS_STRICTFILETYPES + if no_change_dir: + options |= FileOpenOptions.FOS_NOCHANGEDIR + if force_filesystem: + options |= FileOpenOptions.FOS_FORCEFILESYSTEM + if all_non_storage_items: + options |= FileOpenOptions.FOS_ALLNONSTORAGEITEMS + if no_validate: + options |= FileOpenOptions.FOS_NOVALIDATE + if allow_multiple_selection: + options |= FileOpenOptions.FOS_ALLOWMULTISELECT + if path_must_exist: + options |= FileOpenOptions.FOS_PATHMUSTEXIST + if file_must_exist: + options |= FileOpenOptions.FOS_FILEMUSTEXIST + if create_prompt: + options |= FileOpenOptions.FOS_CREATEPROMPT + if share_aware: + options |= FileOpenOptions.FOS_SHAREAWARE + if no_readonly_return: + options |= FileOpenOptions.FOS_NOREADONLYRETURN + if no_test_file_create: + options |= FileOpenOptions.FOS_NOTESTFILECREATE + if hide_mru_places: + options |= FileOpenOptions.FOS_HIDEMRUPLACES + if hide_pinned_places: + options |= FileOpenOptions.FOS_HIDEPINNEDPLACES + if no_dereference_links: + options |= FileOpenOptions.FOS_NODEREFERENCELINKS + if not add_to_recent: + options |= FileOpenOptions.FOS_DONTADDTORECENT + if show_hidden_files: + options |= FileOpenOptions.FOS_FORCESHOWHIDDEN + if default_no_minimode: + options |= FileOpenOptions.FOS_DEFAULTNOMINIMODE + if force_preview_pane_on: + options |= FileOpenOptions.FOS_FORCEPREVIEWPANEON + file_dialog = comtypes.client.CreateObject(CLSID_FileOpenDialog, interface=IFileOpenDialog) + return configure_file_dialog(file_dialog, title, options, default_folder, ok_button_text, None, file_types, default_extension) + + +def save_file_dialog( # noqa: C901, PLR0913, PLR0912 + title: str | None = "Save File", + default_folder: str | None = None, + file_types: list[tuple[str, str]] | None = None, + default_extension: str | None = None, + *, + overwrite_prompt: bool = True, + strict_file_types: bool = False, + no_change_dir: bool = True, + force_filesystem: bool = True, + all_non_storage_items: bool = False, + no_validate: bool = False, + path_must_exist: bool = True, + file_must_exist: bool = False, + create_prompt: bool = False, + share_aware: bool = False, + no_readonly_return: bool = False, + no_test_file_create: bool = False, + hide_mru_places: bool = False, + hide_pinned_places: bool = False, + no_dereference_links: bool = False, + add_to_recent: bool = True, + show_hidden_files: bool = False, + default_no_minimode: bool = False, + force_preview_pane_on: bool = False, + ok_button_text: str | None = None, +) -> list[str] | None: # sourcery skip: low-code-quality + """Opens a file dialog to save a file. + + Args: + title (str | None): The title of the dialog. + default_folder (str | None): The initial folder to open. + file_types (list[tuple[str, str]] | None): A list of file type filters. + default_extension (str | None): The default file extension. + overwrite_prompt (bool): Prompts if the selected file already exists. FileOpenOptions.FOS_OVERWRITEPROMPT. + strict_file_types (bool): Restricts selection to specified file types. FileOpenOptions.FOS_STRICTFILETYPES. + no_change_dir (bool): Prevents changing the current working directory. FileOpenOptions.FOS_NOCHANGEDIR. + force_filesystem (bool): Ensures only file system items are shown. FileOpenOptions.FOS_FORCEFILESYSTEM. + all_non_storage_items (bool): Allows selection of non-file system items. FileOpenOptions.FOS_ALLNONSTORAGEITEMS. + no_validate (bool): Disables file name validation. FileOpenOptions.FOS_NOVALIDATE. + path_must_exist (bool): Requires the path to exist. FileOpenOptions.FOS_PATHMUSTEXIST. + file_must_exist (bool): Requires the file to exist. FileOpenOptions.FOS_FILEMUSTEXIST. + create_prompt (bool): Prompts to create a new file if it doesn't exist. FileOpenOptions.FOS_CREATEPROMPT. + share_aware (bool): Ensures the dialog is aware of sharing conflicts. FileOpenOptions.FOS_SHAREAWARE. + no_readonly_return (bool): Prevents selection of read-only items. FileOpenOptions.FOS_NOREADONLYRETURN. + no_test_file_create (bool): Disables testing file creation ability. FileOpenOptions.FOS_NOTESTFILECREATE. + hide_mru_places (bool): Hides most recently used places. FileOpenOptions.FOS_HIDEMRUPLACES. + hide_pinned_places (bool): Hides pinned places. FileOpenOptions.FOS_HIDEPINNEDPLACES. + no_dereference_links (bool): Prevents dereferencing shortcuts. FileOpenOptions.FOS_NODEREFERENCELINKS. + add_to_recent (bool): Prevents adding the file to recent files. FileOpenOptions.FOS_DONTADDTORECENT. + show_hidden_files (bool): Shows hidden files and folders. FileOpenOptions.FOS_FORCESHOWHIDDEN. + default_no_minimode (bool): Uses default non-minimized mode. FileOpenOptions.FOS_DEFAULTNOMINIMODE. + force_preview_pane_on (bool): Forces the preview pane to be visible. FileOpenOptions.FOS_FORCEPREVIEWPANEON. + ok_button_text (str): The text for the button used to select/confirm the dialog. + + Returns: + list[str] | None: A list of selected file paths or None if cancelled. + """ + options = 0 + if overwrite_prompt: + options |= FileOpenOptions.FOS_OVERWRITEPROMPT + if strict_file_types: + options |= FileOpenOptions.FOS_STRICTFILETYPES + if no_change_dir: + options |= FileOpenOptions.FOS_NOCHANGEDIR + if force_filesystem: + options |= FileOpenOptions.FOS_FORCEFILESYSTEM + if all_non_storage_items: + options |= FileOpenOptions.FOS_ALLNONSTORAGEITEMS + if no_validate: + options |= FileOpenOptions.FOS_NOVALIDATE + if path_must_exist: + options |= FileOpenOptions.FOS_PATHMUSTEXIST + if file_must_exist: + options |= FileOpenOptions.FOS_FILEMUSTEXIST + if create_prompt: + options |= FileOpenOptions.FOS_CREATEPROMPT + if share_aware: + options |= FileOpenOptions.FOS_SHAREAWARE + if no_readonly_return: + options |= FileOpenOptions.FOS_NOREADONLYRETURN + if no_test_file_create: + options |= FileOpenOptions.FOS_NOTESTFILECREATE + if hide_mru_places: + options |= FileOpenOptions.FOS_HIDEMRUPLACES + if hide_pinned_places: + options |= FileOpenOptions.FOS_HIDEPINNEDPLACES + if no_dereference_links: + options |= FileOpenOptions.FOS_NODEREFERENCELINKS + if not add_to_recent: + options |= FileOpenOptions.FOS_DONTADDTORECENT + if show_hidden_files: + options |= FileOpenOptions.FOS_FORCESHOWHIDDEN + if default_no_minimode: + options |= FileOpenOptions.FOS_DEFAULTNOMINIMODE + if force_preview_pane_on: + options |= FileOpenOptions.FOS_FORCEPREVIEWPANEON + options &= ~FileOpenOptions.FOS_PICKFOLDERS # Required (exceptions otherwise) + options &= ~FileOpenOptions.FOS_ALLOWMULTISELECT # Required (exceptions otherwise) + file_dialog = comtypes.client.CreateObject(CLSID_FileSaveDialog, interface=IFileSaveDialog) + return configure_file_dialog(file_dialog, title, options, default_folder, ok_button_text, None, file_types, default_extension) + + +def open_folder_dialog( # noqa: C901, PLR0913, PLR0912 + title: str | None = "Select Folder", + default_folder: str | None = None, + *, + overwrite_prompt: bool = False, + strict_file_types: bool = False, + no_change_dir: bool = False, + force_filesystem: bool = True, + no_validate: bool = False, + allow_multiple_selection: bool = False, + path_must_exist: bool = True, + file_must_exist: bool = False, + create_prompt: bool = False, + share_aware: bool = False, + no_readonly_return: bool = False, + no_test_file_create: bool = False, + hide_mru_places: bool = False, + hide_pinned_places: bool = False, + no_dereference_links: bool = False, + add_to_recent: bool = True, + show_hidden_files: bool = False, + default_no_minimode: bool = False, + force_preview_pane_on: bool = False, + ok_button_text: str | None = None, +) -> list[str] | None: # sourcery skip: low-code-quality + """Opens a dialog to select folders. + + Args: + title (str | None): The title of the dialog. + default_folder (str | None): The initial folder to open. + overwrite_prompt (bool): Prompts if the selected file already exists. FileOpenOptions.FOS_OVERWRITEPROMPT. + strict_file_types (bool): Restricts selection to specified file types. FileOpenOptions.FOS_STRICTFILETYPES. + no_change_dir (bool): Prevents changing the current working directory. FileOpenOptions.FOS_NOCHANGEDIR. + force_filesystem (bool): Ensures only file system items are shown. FileOpenOptions.FOS_FORCEFILESYSTEM. + no_validate (bool): Disables file name validation. FileOpenOptions.FOS_NOVALIDATE. + allow_multiple_selection (bool): Allows selecting multiple files. FileOpenOptions.FOS_ALLOWMULTISELECT. + path_must_exist (bool): Requires the path to exist. FileOpenOptions.FOS_PATHMUSTEXIST. + file_must_exist (bool): Requires the file to exist. FileOpenOptions.FOS_FILEMUSTEXIST. + create_prompt (bool): Prompts to create a new file if it doesn't exist. FileOpenOptions.FOS_CREATEPROMPT. + share_aware (bool): Ensures the dialog is aware of sharing conflicts. FileOpenOptions.FOS_SHAREAWARE. + no_readonly_return (bool): Prevents selection of read-only items. FileOpenOptions.FOS_NOREADONLYRETURN. + no_test_file_create (bool): Disables testing file creation ability. FileOpenOptions.FOS_NOTESTFILECREATE. + hide_mru_places (bool): Hides most recently used places. FileOpenOptions.FOS_HIDEMRUPLACES. + hide_pinned_places (bool): Hides pinned places. FileOpenOptions.FOS_HIDEPINNEDPLACES. + no_dereference_links (bool): Prevents dereferencing shortcuts. FileOpenOptions.FOS_NODEREFERENCELINKS. + add_to_recent (bool): Prevents adding the file to recent files. FileOpenOptions.FOS_DONTADDTORECENT. + show_hidden_files (bool): Shows hidden files and folders. FileOpenOptions.FOS_FORCESHOWHIDDEN. + default_no_minimode (bool): Uses default non-minimized mode. FileOpenOptions.FOS_DEFAULTNOMINIMODE. + force_preview_pane_on (bool): Forces the preview pane to be visible. FileOpenOptions.FOS_FORCEPREVIEWPANEON. + ok_button_text (str): The text for the button used to select/confirm the dialog. + + Returns: + list[str] | None: A list of selected folder paths or None if cancelled. + """ + options = 0 + options |= FileOpenOptions.FOS_PICKFOLDERS + options &= ~FileOpenOptions.FOS_ALLNONSTORAGEITEMS + if overwrite_prompt: + options |= FileOpenOptions.FOS_OVERWRITEPROMPT + if strict_file_types: + options |= FileOpenOptions.FOS_STRICTFILETYPES + if no_change_dir: + options |= FileOpenOptions.FOS_NOCHANGEDIR + if force_filesystem: + options |= FileOpenOptions.FOS_FORCEFILESYSTEM + if no_validate: + options |= FileOpenOptions.FOS_NOVALIDATE + if allow_multiple_selection: + options |= FileOpenOptions.FOS_ALLOWMULTISELECT + if path_must_exist: + options |= FileOpenOptions.FOS_PATHMUSTEXIST + if file_must_exist: + options |= FileOpenOptions.FOS_FILEMUSTEXIST + if create_prompt: + options |= FileOpenOptions.FOS_CREATEPROMPT + if share_aware: + options |= FileOpenOptions.FOS_SHAREAWARE + if no_readonly_return: + options |= FileOpenOptions.FOS_NOREADONLYRETURN + if no_test_file_create: + options |= FileOpenOptions.FOS_NOTESTFILECREATE + if hide_mru_places: + options |= FileOpenOptions.FOS_HIDEMRUPLACES + if hide_pinned_places: + options |= FileOpenOptions.FOS_HIDEPINNEDPLACES + if no_dereference_links: + options |= FileOpenOptions.FOS_NODEREFERENCELINKS + if not add_to_recent: + options |= FileOpenOptions.FOS_DONTADDTORECENT + if show_hidden_files: + options |= FileOpenOptions.FOS_FORCESHOWHIDDEN + if default_no_minimode: + options |= FileOpenOptions.FOS_DEFAULTNOMINIMODE + if force_preview_pane_on: + options |= FileOpenOptions.FOS_FORCEPREVIEWPANEON + file_dialog = comtypes.client.CreateObject(CLSID_FileOpenDialog, interface=IFileOpenDialog) + return configure_file_dialog(file_dialog, title, options, default_folder, ok_button_text, None, None, None) + + +def get_open_file_dialog_results( + file_open_dialog: IFileOpenDialog, +) -> list[str]: + results: list[str] = [] + results_array: IShellItemArray = file_open_dialog.GetResults() + item_count: int = results_array.GetCount() + for i in range(item_count): + shell_item: IShellItem = results_array.GetItemAt(i) + szFilePath: str = shell_item.GetDisplayName(0x80058000) # SIGDN_FILESYSPATH + if szFilePath and szFilePath.strip(): + results.append(szFilePath) + else: + raise FileNotFoundError(errno.ENOENT, os.strerror(errno.ENOENT), szFilePath) + return results + + +def get_save_file_dialog_results(file_save_dialog: IFileSaveDialog) -> str: + results = "" + resultItem: IShellItem = file_save_dialog.GetResult() + szFilePath = resultItem.GetDisplayName(0x80058000) # SIGDN_FILESYSPATH + szFilePathStr = str(szFilePath) + if szFilePathStr and szFilePathStr.strip(): + results = szFilePathStr + else: + raise FileNotFoundError(errno.ENOENT, os.strerror(errno.ENOENT), str(szFilePath)) + resultItem.Release() + return results + + +# Example usage +if __name__ == "__main__": + # Randomizing arguments for open_file_dialog + open_file_args = { + "title": "Open File" if random.choice([True, False]) else None, # noqa: S311 + "default_folder": "C:\\Users" if random.choice([True, False]) else None, # noqa: S311 + "file_types": [("Text Files", "*.txt")] if random.choice([True, False]) else None, # noqa: S311 + "default_extension": "txt" if random.choice([True, False]) else None, # noqa: S311 + "overwrite_prompt": random.choice([True, False]), # noqa: S311 + "strict_file_types": random.choice([True, False]), # noqa: S311 + "no_change_dir": random.choice([True, False]), # noqa: S311 + "force_filesystem": random.choice([True, False]), # noqa: S311 + "all_non_storage_items": False, # random.choice([True, False]), # noqa: S311 + "no_validate": random.choice([True, False]), # noqa: S311 + "allow_multiple_selection": random.choice([True, False]), # noqa: S311 + "path_must_exist": random.choice([True, False]), # noqa: S311 + "file_must_exist": random.choice([True, False]), # noqa: S311 + "create_prompt": random.choice([True, False]), # noqa: S311 + "share_aware": random.choice([True, False]), # noqa: S311 + "no_readonly_return": random.choice([True, False]), # noqa: S311 + "no_test_file_create": random.choice([True, False]), # noqa: S311 + "hide_mru_places": random.choice([True, False]), # noqa: S311 + "hide_pinned_places": random.choice([True, False]), # noqa: S311 + "no_dereference_links": random.choice([True, False]), # noqa: S311 + "add_to_recent": random.choice([True, False]), # noqa: S311 + "show_hidden_files": random.choice([True, False]), # noqa: S311 + "default_no_minimode": random.choice([True, False]), # noqa: S311 + "force_preview_pane_on": random.choice([True, False]), # noqa: S311 + } + print("\nOpen file args") + print(json.dumps(open_file_args, indent=4, sort_keys=True)) + selected_files: list[str] | None = open_file_dialog(**open_file_args) + print("Selected files:", selected_files) + + # Randomizing arguments for open_folder_dialog + open_folder_args = { + "title": "Select Folder" if random.choice([True, False]) else None, # noqa: S311 + "default_folder": "C:\\Users" if random.choice([True, False]) else None, # noqa: S311 + "overwrite_prompt": random.choice([True, False]), # noqa: S311 + "strict_file_types": random.choice([True, False]), # noqa: S311 + "no_change_dir": random.choice([True, False]), # noqa: S311 + "force_filesystem": random.choice([True, False]), # noqa: S311 + "no_validate": random.choice([True, False]), # noqa: S311 + "allow_multiple_selection": random.choice([True, False]), # noqa: S311 + "path_must_exist": random.choice([True, False]), # noqa: S311 + "file_must_exist": random.choice([True, False]), # noqa: S311 + "create_prompt": random.choice([True, False]), # noqa: S311 + "share_aware": random.choice([True, False]), # noqa: S311 + "no_readonly_return": random.choice([True, False]), # noqa: S311 + "no_test_file_create": random.choice([True, False]), # noqa: S311 + "hide_mru_places": random.choice([True, False]), # noqa: S311 + "hide_pinned_places": random.choice([True, False]), # noqa: S311 + "no_dereference_links": random.choice([True, False]), # noqa: S311 + "add_to_recent": random.choice([True, False]), # noqa: S311 + "show_hidden_files": random.choice([True, False]), # noqa: S311 + "default_no_minimode": random.choice([True, False]), # noqa: S311 + "force_preview_pane_on": random.choice([True, False]), # noqa: S311 + } + print("\nOpen folder args") + print(json.dumps(open_folder_args, indent=4, sort_keys=True)) + selected_folders: list[str] | None = open_folder_dialog(**open_folder_args) + print("Selected folders:", selected_folders) + + # Randomizing arguments for save_file_dialog + save_file_args = { + "title": "Save File" if random.choice([True, False]) else None, # noqa: S311 + "default_folder": "C:\\Users" if random.choice([True, False]) else None, # noqa: S311 + "file_types": [("Text Files", "*.txt")] if random.choice([True, False]) else None, # noqa: S311 + "default_extension": "txt" if random.choice([True, False]) else None, # noqa: S311 + "overwrite_prompt": random.choice([True, False]), # noqa: S311 + "strict_file_types": random.choice([True, False]), # noqa: S311 + "no_change_dir": random.choice([True, False]), # noqa: S311 + "force_filesystem": random.choice([True, False]), # noqa: S311 + "all_non_storage_items": random.choice([True, False]), # noqa: S311 + "no_validate": random.choice([True, False]), # noqa: S311 + "path_must_exist": random.choice([True, False]), # noqa: S311 + "file_must_exist": random.choice([True, False]), # noqa: S311 + "create_prompt": random.choice([True, False]), # noqa: S311 + "share_aware": random.choice([True, False]), # noqa: S311 + "no_readonly_return": random.choice([True, False]), # noqa: S311 + "no_test_file_create": random.choice([True, False]), # noqa: S311 + "hide_mru_places": random.choice([True, False]), # noqa: S311 + "hide_pinned_places": random.choice([True, False]), # noqa: S311 + "no_dereference_links": random.choice([True, False]), # noqa: S311 + "add_to_recent": random.choice([True, False]), # noqa: S311 + "show_hidden_files": random.choice([True, False]), # noqa: S311 + "default_no_minimode": random.choice([True, False]), # noqa: S311 + "force_preview_pane_on": random.choice([True, False]), # noqa: S311 + } + print("\nSave file args") + print(json.dumps(save_file_args, indent=4, sort_keys=True)) + saved_file: list[str] | None = save_file_dialog(**save_file_args) + print("Saved file:", saved_file) diff --git a/winforms/src/toga_winforms/libs/py_wrappers/com/windialogs.py b/winforms/src/toga_winforms/libs/py_wrappers/com/windialogs.py new file mode 100644 index 0000000000..d951650f2f --- /dev/null +++ b/winforms/src/toga_winforms/libs/py_wrappers/com/windialogs.py @@ -0,0 +1,857 @@ +from __future__ import annotations + +import errno +import json +import os +import random +from ctypes import POINTER, WINFUNCTYPE, byref, c_ulong, c_void_p, c_wchar_p, windll +from ctypes import cast as cast_with_ctypes +from ctypes.wintypes import HMODULE, HWND, LPCWSTR +from typing import TYPE_CHECKING, Sequence + +import comtypes # pyright: ignore[reportMissingTypeStubs] +import comtypes.client # pyright: ignore[reportMissingTypeStubs] +from utility.logger_util import RobustRootLogger +from utility.system.path import WindowsPath + +from toga_winforms.libs.win_wrappers.com.com_helpers import HandleCOMCall +from toga_winforms.libs.win_wrappers.com.com_types import GUID +from toga_winforms.libs.win_wrappers.com.interfaces import ( + COMDLG_FILTERSPEC, + SFGAO, + SIGDN, + CLSID_FileOpenDialog, + CLSID_FileSaveDialog, + COMFunctionPointers, + FileOpenOptions, + IFileDialogControlEvents, + IFileDialogCustomize, + IFileOpenDialog, + IFileSaveDialog, + IID_IFileDialogCustomize, + IShellItem, +) +from toga_winforms.libs.win_wrappers.hresult import HRESULT, S_OK + +if TYPE_CHECKING: + from ctypes import Array, _FuncPointer, _Pointer + from ctypes.wintypes import BOOL, DWORD, LPWSTR + + from toga_winforms.libs.win_wrappers.com.interfaces import IFileDialog, IShellItemArray + + +class FileDialogControlEvents(comtypes.COMObject): + _com_interfaces_: Sequence[type[comtypes.IUnknown]] = [IFileDialogControlEvents] + + def OnItemSelected(self, pfdc: IFileDialogCustomize, dwIDCtl: DWORD, dwIDItem: DWORD) -> HRESULT: + # Implement the logic for when an item is selected + return S_OK + + def OnButtonClicked(self, pfdc: IFileDialogCustomize, dwIDCtl: DWORD) -> HRESULT: + if dwIDCtl == 1001: + # Implement the specific logic for button with ID 1001 + print("Button with ID 1001 was clicked.") + # Add any other logic you need for this button + else: + # Handle other button IDs if necessary + print(f"Button with ID {dwIDCtl} was clicked.") + + return S_OK + + def OnCheckButtonToggled(self, pfdc: IFileDialogCustomize, dwIDCtl: DWORD, bChecked: BOOL) -> HRESULT: + # Implement the logic for when a check button is toggled + return S_OK + + def OnControlActivating(self, pfdc: IFileDialogCustomize, dwIDCtl: DWORD) -> HRESULT: + # Implement the logic for when a control is activated + return S_OK + + +# Load COM function pointers +def LoadCOMFunctionPointers(dialog_type: type[IFileDialog | IFileOpenDialog | IFileSaveDialog]) -> COMFunctionPointers: + comFuncPtrs = COMFunctionPointers() + comFuncPtrs.hOle32 = comFuncPtrs.load_library("ole32.dll") + comFuncPtrs.hShell32 = comFuncPtrs.load_library("shell32.dll") + + + # Get function pointers + if comFuncPtrs.hOle32: # sourcery skip: extract-method + PFN_CoInitialize: type[_FuncPointer] = WINFUNCTYPE(HRESULT, POINTER(dialog_type)) + PFN_CoUninitialize: type[_FuncPointer] = WINFUNCTYPE(None) + PFN_CoCreateInstance: type[_FuncPointer] = WINFUNCTYPE(HRESULT, POINTER(GUID), c_void_p, c_ulong, POINTER(GUID), POINTER(POINTER(dialog_type))) + PFN_CoTaskMemFree: type[_FuncPointer] = WINFUNCTYPE(None, c_void_p) + comFuncPtrs.pCoInitialize = comFuncPtrs.resolve_function(comFuncPtrs.hOle32, b"CoInitialize", PFN_CoInitialize) + comFuncPtrs.pCoUninitialize = comFuncPtrs.resolve_function(comFuncPtrs.hOle32, b"CoUninitialize", PFN_CoUninitialize) + comFuncPtrs.pCoCreateInstance = comFuncPtrs.resolve_function(comFuncPtrs.hOle32, b"CoCreateInstance", PFN_CoCreateInstance) + comFuncPtrs.pCoTaskMemFree = comFuncPtrs.resolve_function(comFuncPtrs.hOle32, b"CoTaskMemFree", PFN_CoTaskMemFree) + + if comFuncPtrs.hShell32: + PFN_SHCreateItemFromParsingName: type[_FuncPointer] = WINFUNCTYPE(HRESULT, LPCWSTR, c_void_p, POINTER(GUID), POINTER(POINTER(IShellItem))) + comFuncPtrs.pSHCreateItemFromParsingName = comFuncPtrs.resolve_function(comFuncPtrs.hShell32, b"SHCreateItemFromParsingName", PFN_SHCreateItemFromParsingName) + return comFuncPtrs + + +def FreeCOMFunctionPointers(comFuncPtrs: COMFunctionPointers): # noqa: N803 + if comFuncPtrs.hOle32: + windll.kernel32.FreeLibrary(cast_with_ctypes(comFuncPtrs.hOle32, HMODULE)) + if comFuncPtrs.hShell32: + windll.kernel32.FreeLibrary(cast_with_ctypes(comFuncPtrs.hShell32, HMODULE)) + + +def show_file_dialog( + fileDialog: IFileOpenDialog | IFileSaveDialog | IFileDialog, # noqa: N803 + hwndOwner: HWND, # noqa: N803 +) -> bool: + """Shows the IFileDialog. Returns True if the user progressed to the end and found a file. False if they cancelled.""" + hr: HRESULT | int = -1 + CANCELLED_BY_USER = -2147023673 + + try: + hr = fileDialog.Show(hwndOwner) + print(f"Dialog shown successfully, HRESULT: {hr}") + except OSError as e: + if e.winerror == CANCELLED_BY_USER: + print("Operation was canceled by the user.") + return False + raise + else: + HRESULT.raise_for_status(hr, "An unexpected error occurred showing the file browser dialog") + + return True + + +def createShellItem(comFuncs: COMFunctionPointers, path: str) -> _Pointer[IShellItem]: # noqa: N803, ARG001 + if not comFuncs.pSHCreateItemFromParsingName: + raise OSError("comFuncs.pSHCreateItemFromParsingName not found") + shell_item = POINTER(IShellItem)() + hr = comFuncs.pSHCreateItemFromParsingName(path, None, IShellItem._iid_, byref(shell_item)) + if hr != S_OK: + raise HRESULT(hr).exception(f"Failed to create shell item from path: {path}") + return shell_item + + +DEFAULT_FILTERS: list[COMDLG_FILTERSPEC] = [ + COMDLG_FILTERSPEC("All Files", "*.*"), + COMDLG_FILTERSPEC("Text Files", "*.txt"), + COMDLG_FILTERSPEC("Image Files", "*.png;*.jpg;*.jpeg;*.bmp;*.gif"), + COMDLG_FILTERSPEC("Document Files", "*.doc;*.docx;*.pdf;*.xls;*.xlsx"), + COMDLG_FILTERSPEC("Audio Files", "*.mp3;*.wav;*.wma;*.aac"), + COMDLG_FILTERSPEC("Video Files", "*.mp4;*.avi;*.mkv;*.mov;*.wmv"), + COMDLG_FILTERSPEC("Archive Files", "*.zip;*.rar;*.7z;*.tar;*.gz"), + COMDLG_FILTERSPEC("Executable Files", "*.exe;*.bat;*.msi"), + COMDLG_FILTERSPEC("HTML Files", "*.htm;*.html"), + COMDLG_FILTERSPEC("XML Files", "*.xml"), + COMDLG_FILTERSPEC("JavaScript Files", "*.js"), + COMDLG_FILTERSPEC("CSS Files", "*.css"), + COMDLG_FILTERSPEC("Python Files", "*.py"), + COMDLG_FILTERSPEC("C/C++ Files", "*.c;*.cpp;*.h;*.hpp"), + COMDLG_FILTERSPEC("Java Files", "*.java"), + COMDLG_FILTERSPEC("Ruby Files", "*.rb"), + COMDLG_FILTERSPEC("Perl Files", "*.pl"), + COMDLG_FILTERSPEC("PHP Files", "*.php"), + COMDLG_FILTERSPEC("Shell Script Files", "*.sh"), + COMDLG_FILTERSPEC("Batch Files", "*.bat"), + COMDLG_FILTERSPEC("INI Files", "*.ini"), + COMDLG_FILTERSPEC("Log Files", "*.log"), + COMDLG_FILTERSPEC("SVG Files", "*.svg"), + COMDLG_FILTERSPEC("Markdown Files", "*.md"), + COMDLG_FILTERSPEC("YAML Files", "*.yaml;*.yml"), + COMDLG_FILTERSPEC("JSON Files", "*.json"), + COMDLG_FILTERSPEC("PowerShell Files", "*.ps1"), + COMDLG_FILTERSPEC("MATLAB Files", "*.m"), + COMDLG_FILTERSPEC("R Files", "*.r"), + COMDLG_FILTERSPEC("Lua Files", "*.lua"), + COMDLG_FILTERSPEC("Rust Files", "*.rs"), + COMDLG_FILTERSPEC("Go Files", "*.go"), + COMDLG_FILTERSPEC("Swift Files", "*.swift"), + COMDLG_FILTERSPEC("Kotlin Files", "*.kt;*.kts"), + COMDLG_FILTERSPEC("Objective-C Files", "*.m;*.mm"), + COMDLG_FILTERSPEC("SQL Files", "*.sql"), + COMDLG_FILTERSPEC("Config Files", "*.conf"), + COMDLG_FILTERSPEC("CSV Files", "*.csv"), + COMDLG_FILTERSPEC("TSV Files", "*.tsv"), + COMDLG_FILTERSPEC("LaTeX Files", "*.tex"), + COMDLG_FILTERSPEC("BibTeX Files", "*.bib"), + COMDLG_FILTERSPEC("Makefiles", "Makefile"), + COMDLG_FILTERSPEC("Gradle Files", "*.gradle"), + COMDLG_FILTERSPEC("Ant Build Files", "*.build.xml"), + COMDLG_FILTERSPEC("Maven POM Files", "pom.xml"), + COMDLG_FILTERSPEC("Dockerfiles", "Dockerfile"), + COMDLG_FILTERSPEC("Vagrantfiles", "Vagrantfile"), + COMDLG_FILTERSPEC("Terraform Files", "*.tf"), + COMDLG_FILTERSPEC("HCL Files", "*.hcl"), + COMDLG_FILTERSPEC("Kubernetes YAML Files", "*.yaml;*.yml") +] + + +def configure_file_dialog( # noqa: PLR0913, PLR0912, C901, PLR0915 + file_dialog: IFileDialog, + title: str | None = None, + options: int = 0, + default_folder: str | None = None, + ok_button_label: str | None = None, + file_name_label: str | None = None, + file_types: list[tuple[str, str]] | None = None, + default_extension: str | None = None, + dialog_interfaces: list[comtypes.IUnknown | comtypes.COMObject] | None = None, + hwnd: HWND | int | None = None, +) -> list[str] | None: # sourcery skip: low-code-quality + comFuncs: COMFunctionPointers = LoadCOMFunctionPointers(type(file_dialog)) + cookies = [] + if dialog_interfaces: + for interface in dialog_interfaces: + cookie = file_dialog.Advise(interface) + cookies.append(cookie) + hwnd = HWND(hwnd) if isinstance(hwnd, int) else hwnd + hwnd = HWND(0) if hwnd is None else hwnd + try: + if default_folder: + defaultFolder_path = WindowsPath(default_folder).resolve() + if not defaultFolder_path.safe_isdir(): + raise FileNotFoundError(errno.ENOENT, os.strerror(errno.ENOENT), str(defaultFolder_path)) + shell_item = createShellItem(comFuncs, str(defaultFolder_path)) + with HandleCOMCall(f"SetFolder({defaultFolder_path})") as check: + check(file_dialog.SetFolder(shell_item)) + with HandleCOMCall(f"SetDefaultFolder({defaultFolder_path})") as check: + check(file_dialog.SetDefaultFolder(shell_item)) + + # Resolve contradictory options + if options & FileOpenOptions.FOS_ALLNONSTORAGEITEMS: + if options & FileOpenOptions.FOS_FORCEFILESYSTEM: + RobustRootLogger().warning("Removing FileOpenOptions.FOS_FORCEFILESYSTEM to prevent conflict with FOS_ALLNONSTORAGEITEMS") + options &= ~FileOpenOptions.FOS_FORCEFILESYSTEM + if options & FileOpenOptions.FOS_PICKFOLDERS: + RobustRootLogger().warning("Removing FileOpenOptions.FOS_PICKFOLDERS to prevent conflict with FOS_ALLNONSTORAGEITEMS") + options &= ~FileOpenOptions.FOS_PICKFOLDERS + + def get_flag_differences(set_options: int, get_options: int) -> list[str]: + differences = set_options ^ get_options # XOR to find differing bits + differing_flags = [] + for flag in FileOpenOptions: + if differences & flag: + set_in_options = bool(set_options & flag) + set_in_cur_options = bool(get_options & flag) + differing_flags.append( + f"{flag.name}: SetOptions={'SET' if set_in_options else 'UNSET'}, GetOptions={'SET' if set_in_cur_options else 'UNSET'}" + ) + return differing_flags + + original_dialog_options = file_dialog.GetOptions() + print(f"Original dialog options: {original_dialog_options}") + with HandleCOMCall(f"SetOptions({options})") as check: + check(file_dialog.SetOptions(options)) + cur_options = file_dialog.GetOptions() + print(f"GetOptions({cur_options})") + + assert original_dialog_options != cur_options, ( + f"SetOptions call was completely ignored by the dialog interface, attempted to set {options}, " + f"but retrieved {cur_options} (the original)" + ) + if (options != cur_options): + differing_flags = get_flag_differences(options, cur_options) + RobustRootLogger().warning(f"Differing flags: {', '.join(differing_flags)}") + + if not options & FileOpenOptions.FOS_PICKFOLDERS: + filters: Array[COMDLG_FILTERSPEC] + if file_types: + print("Using custom file filters") + filters = (COMDLG_FILTERSPEC * len(file_types))( + *[ + (c_wchar_p(name), c_wchar_p(spec)) + for name, spec in file_types + ] + ) + else: + print("Using default file filters") + filters = (COMDLG_FILTERSPEC * len(DEFAULT_FILTERS))(*DEFAULT_FILTERS) + with HandleCOMCall(f"SetFileTypes({len(filters)})") as check: + check(file_dialog.SetFileTypes(len(filters), cast_with_ctypes(filters, POINTER(c_void_p)))) + + if title: + file_dialog.SetTitle(title) + + if ok_button_label: + file_dialog.SetOkButtonLabel(ok_button_label) + elif isinstance(file_dialog, IFileSaveDialog): + file_dialog.SetOkButtonLabel("Save") + elif options & FileOpenOptions.FOS_PICKFOLDERS: + file_dialog.SetOkButtonLabel("Select Folder") + else: + file_dialog.SetOkButtonLabel("Select File") + + if file_name_label: + file_dialog.SetFileNameLabel(file_name_label) + if default_extension: + file_dialog.SetDefaultExtension(default_extension) + + if show_file_dialog(file_dialog, hwnd): + return ( + [get_save_file_dialog_results(comFuncs, file_dialog)] + if isinstance(file_dialog, IFileSaveDialog) + else get_open_file_dialog_results(file_dialog) + ) + + finally: + for cookie in cookies: + file_dialog.Unadvise(cookie) + return None + + +def open_file_and_folder_dialog( # noqa: C901, PLR0913, PLR0912 + title: str | None = "Open File", + default_folder: str | None = None, + file_types: list[tuple[str, str]] | None = None, + default_extension: str | None = None, + *, + overwrite_prompt: bool = False, + strict_file_types: bool = False, + no_change_dir: bool = True, + force_filesystem: bool = True, + all_non_storage_items: bool = False, + no_validate: bool = False, + allow_multiple_selection: bool = False, + path_must_exist: bool = True, + file_must_exist: bool = True, + create_prompt: bool = False, + share_aware: bool = False, + no_readonly_return: bool = False, + no_test_file_create: bool = False, + hide_mru_places: bool = False, + hide_pinned_places: bool = False, + no_dereference_links: bool = False, + add_to_recent: bool = True, + show_hidden_files: bool = False, + default_no_minimode: bool = False, + force_preview_pane_on: bool = False, + ok_button_text: str | None = None, +) -> list[str] | None: # sourcery skip: low-code-quality + """Opens a file dialog to select files. + + Args: + title (str | None): The title of the dialog. + default_folder (str | None): The initial folder to open. + file_types (list[tuple[str, str]] | None): A list of file type filters. + default_extension (str | None): The default file extension. + overwrite_prompt (bool): Prompts if the selected file already exists. FileOpenOptions.FOS_OVERWRITEPROMPT. + strict_file_types (bool): Restricts selection to specified file types. FileOpenOptions.FOS_STRICTFILETYPES. + no_change_dir (bool): Prevents changing the current working directory. FileOpenOptions.FOS_NOCHANGEDIR. + force_filesystem (bool): Ensures only file system items are shown. FileOpenOptions.FOS_FORCEFILESYSTEM. + all_non_storage_items (bool): Allows selection of non-file system items. FileOpenOptions.FOS_ALLNONSTORAGEITEMS. + no_validate (bool): Disables file name validation. FileOpenOptions.FOS_NOVALIDATE. + allow_multiple_selection (bool): Allows selecting multiple files. FileOpenOptions.FOS_ALLOWMULTISELECT. + path_must_exist (bool): Requires the path to exist. FileOpenOptions.FOS_PATHMUSTEXIST. + file_must_exist (bool): Requires the file to exist. FileOpenOptions.FOS_FILEMUSTEXIST. + create_prompt (bool): Prompts to create a new file if it doesn't exist. FileOpenOptions.FOS_CREATEPROMPT. + share_aware (bool): Ensures the dialog is aware of sharing conflicts. FileOpenOptions.FOS_SHAREAWARE. + no_readonly_return (bool): Prevents selection of read-only items. FileOpenOptions.FOS_NOREADONLYRETURN. + no_test_file_create (bool): Disables testing file creation ability. FileOpenOptions.FOS_NOTESTFILECREATE. + hide_mru_places (bool): Hides most recently used places. FileOpenOptions.FOS_HIDEMRUPLACES. + hide_pinned_places (bool): Hides pinned places. FileOpenOptions.FOS_HIDEPINNEDPLACES. + no_dereference_links (bool): Prevents dereferencing shortcuts. FileOpenOptions.FOS_NODEREFERENCELINKS. + add_to_recent (bool): Prevents adding the file to recent files. FileOpenOptions.FOS_DONTADDTORECENT. + show_hidden_files (bool): Shows hidden files and folders. FileOpenOptions.FOS_FORCESHOWHIDDEN. + default_no_minimode (bool): Uses default non-minimized mode. FileOpenOptions.FOS_DEFAULTNOMINIMODE. + force_preview_pane_on (bool): Forces the preview pane to be visible. FileOpenOptions.FOS_FORCEPREVIEWPANEON. + ok_button_text (str): The text for the button used to select/confirm the dialog. + + Returns: + list[str] | None: A list of selected file paths or None if cancelled. + """ + options = 0 + if overwrite_prompt: + options |= FileOpenOptions.FOS_OVERWRITEPROMPT + if strict_file_types: + options |= FileOpenOptions.FOS_STRICTFILETYPES + if no_change_dir: + options |= FileOpenOptions.FOS_NOCHANGEDIR + if force_filesystem: + options |= FileOpenOptions.FOS_FORCEFILESYSTEM + if all_non_storage_items: + options |= FileOpenOptions.FOS_ALLNONSTORAGEITEMS + if no_validate: + options |= FileOpenOptions.FOS_NOVALIDATE + if allow_multiple_selection: + options |= FileOpenOptions.FOS_ALLOWMULTISELECT + if path_must_exist: + options |= FileOpenOptions.FOS_PATHMUSTEXIST + if file_must_exist: + options |= FileOpenOptions.FOS_FILEMUSTEXIST + if create_prompt: + options |= FileOpenOptions.FOS_CREATEPROMPT + if share_aware: + options |= FileOpenOptions.FOS_SHAREAWARE + if no_readonly_return: + options |= FileOpenOptions.FOS_NOREADONLYRETURN + if no_test_file_create: + options |= FileOpenOptions.FOS_NOTESTFILECREATE + if hide_mru_places: + options |= FileOpenOptions.FOS_HIDEMRUPLACES + if hide_pinned_places: + options |= FileOpenOptions.FOS_HIDEPINNEDPLACES + if no_dereference_links: + options |= FileOpenOptions.FOS_NODEREFERENCELINKS + if not add_to_recent: + options |= FileOpenOptions.FOS_DONTADDTORECENT + if show_hidden_files: + options |= FileOpenOptions.FOS_FORCESHOWHIDDEN + if default_no_minimode: + options |= FileOpenOptions.FOS_DEFAULTNOMINIMODE + if force_preview_pane_on: + options |= FileOpenOptions.FOS_FORCEPREVIEWPANEON + file_dialog = comtypes.client.CreateObject(CLSID_FileOpenDialog, interface=IFileOpenDialog) + customize_handler = file_dialog.QueryInterface(IFileDialogCustomize, IID_IFileDialogCustomize) + folder_button_id = 1001 + customize_handler.AddPushButton(folder_button_id, "Select Folder") + control_event_handler = FileDialogControlEvents() + return configure_file_dialog(file_dialog, title, options, default_folder, ok_button_text, None, file_types, default_extension, [control_event_handler]) + + +def open_file_dialog( # noqa: C901, PLR0913, PLR0912 + title: str | None = "Open File", + default_folder: str | None = None, + file_types: list[tuple[str, str]] | None = None, + default_extension: str | None = None, + *, + overwrite_prompt: bool = False, + strict_file_types: bool = False, + no_change_dir: bool = True, + force_filesystem: bool = True, + all_non_storage_items: bool = False, + no_validate: bool = False, + allow_multiple_selection: bool = False, + path_must_exist: bool = True, + file_must_exist: bool = True, + create_prompt: bool = False, + share_aware: bool = False, + no_readonly_return: bool = False, + no_test_file_create: bool = False, + hide_mru_places: bool = False, + hide_pinned_places: bool = False, + no_dereference_links: bool = False, + add_to_recent: bool = True, + show_hidden_files: bool = False, + default_no_minimode: bool = False, + force_preview_pane_on: bool = False, + ok_button_text: str | None = None, +) -> list[str] | None: # sourcery skip: low-code-quality + """Opens a file dialog to select files. + + Args: + title (str | None): The title of the dialog. + default_folder (str | None): The initial folder to open. + file_types (list[tuple[str, str]] | None): A list of file type filters. + default_extension (str | None): The default file extension. + overwrite_prompt (bool): Prompts if the selected file already exists. FileOpenOptions.FOS_OVERWRITEPROMPT. + strict_file_types (bool): Restricts selection to specified file types. FileOpenOptions.FOS_STRICTFILETYPES. + no_change_dir (bool): Prevents changing the current working directory. FileOpenOptions.FOS_NOCHANGEDIR. + force_filesystem (bool): Ensures only file system items are shown. FileOpenOptions.FOS_FORCEFILESYSTEM. + all_non_storage_items (bool): Allows selection of non-file system items. FileOpenOptions.FOS_ALLNONSTORAGEITEMS. + no_validate (bool): Disables file name validation. FileOpenOptions.FOS_NOVALIDATE. + allow_multiple_selection (bool): Allows selecting multiple files. FileOpenOptions.FOS_ALLOWMULTISELECT. + path_must_exist (bool): Requires the path to exist. FileOpenOptions.FOS_PATHMUSTEXIST. + file_must_exist (bool): Requires the file to exist. FileOpenOptions.FOS_FILEMUSTEXIST. + create_prompt (bool): Prompts to create a new file if it doesn't exist. FileOpenOptions.FOS_CREATEPROMPT. + share_aware (bool): Ensures the dialog is aware of sharing conflicts. FileOpenOptions.FOS_SHAREAWARE. + no_readonly_return (bool): Prevents selection of read-only items. FileOpenOptions.FOS_NOREADONLYRETURN. + no_test_file_create (bool): Disables testing file creation ability. FileOpenOptions.FOS_NOTESTFILECREATE. + hide_mru_places (bool): Hides most recently used places. FileOpenOptions.FOS_HIDEMRUPLACES. + hide_pinned_places (bool): Hides pinned places. FileOpenOptions.FOS_HIDEPINNEDPLACES. + no_dereference_links (bool): Prevents dereferencing shortcuts. FileOpenOptions.FOS_NODEREFERENCELINKS. + add_to_recent (bool): Prevents adding the file to recent files. FileOpenOptions.FOS_DONTADDTORECENT. + show_hidden_files (bool): Shows hidden files and folders. FileOpenOptions.FOS_FORCESHOWHIDDEN. + default_no_minimode (bool): Uses default non-minimized mode. FileOpenOptions.FOS_DEFAULTNOMINIMODE. + force_preview_pane_on (bool): Forces the preview pane to be visible. FileOpenOptions.FOS_FORCEPREVIEWPANEON. + ok_button_text (str): The text for the button used to select/confirm the dialog. + + Returns: + list[str] | None: A list of selected file paths or None if cancelled. + """ + options = 0 + if overwrite_prompt: + options |= FileOpenOptions.FOS_OVERWRITEPROMPT + if strict_file_types: + options |= FileOpenOptions.FOS_STRICTFILETYPES + if no_change_dir: + options |= FileOpenOptions.FOS_NOCHANGEDIR + if force_filesystem: + options |= FileOpenOptions.FOS_FORCEFILESYSTEM + if all_non_storage_items: + options |= FileOpenOptions.FOS_ALLNONSTORAGEITEMS + if no_validate: + options |= FileOpenOptions.FOS_NOVALIDATE + if allow_multiple_selection: + options |= FileOpenOptions.FOS_ALLOWMULTISELECT + if path_must_exist: + options |= FileOpenOptions.FOS_PATHMUSTEXIST + if file_must_exist: + options |= FileOpenOptions.FOS_FILEMUSTEXIST + if create_prompt: + options |= FileOpenOptions.FOS_CREATEPROMPT + if share_aware: + options |= FileOpenOptions.FOS_SHAREAWARE + if no_readonly_return: + options |= FileOpenOptions.FOS_NOREADONLYRETURN + if no_test_file_create: + options |= FileOpenOptions.FOS_NOTESTFILECREATE + if hide_mru_places: + options |= FileOpenOptions.FOS_HIDEMRUPLACES + if hide_pinned_places: + options |= FileOpenOptions.FOS_HIDEPINNEDPLACES + if no_dereference_links: + options |= FileOpenOptions.FOS_NODEREFERENCELINKS + if not add_to_recent: + options |= FileOpenOptions.FOS_DONTADDTORECENT + if show_hidden_files: + options |= FileOpenOptions.FOS_FORCESHOWHIDDEN + if default_no_minimode: + options |= FileOpenOptions.FOS_DEFAULTNOMINIMODE + if force_preview_pane_on: + options |= FileOpenOptions.FOS_FORCEPREVIEWPANEON + file_dialog = comtypes.client.CreateObject(CLSID_FileOpenDialog, interface=IFileOpenDialog) + return configure_file_dialog(file_dialog, title, options, default_folder, ok_button_text, None, file_types, default_extension) + + +def save_file_dialog( # noqa: C901, PLR0913, PLR0912 + title: str | None = "Save File", + default_folder: str | None = None, + file_types: list[tuple[str, str]] | None = None, + default_extension: str | None = None, + *, + overwrite_prompt: bool = True, + strict_file_types: bool = False, + no_change_dir: bool = True, + force_filesystem: bool = True, + all_non_storage_items: bool = False, + no_validate: bool = False, + path_must_exist: bool = True, + file_must_exist: bool = False, + create_prompt: bool = False, + share_aware: bool = False, + no_readonly_return: bool = False, + no_test_file_create: bool = False, + hide_mru_places: bool = False, + hide_pinned_places: bool = False, + no_dereference_links: bool = False, + add_to_recent: bool = True, + show_hidden_files: bool = False, + default_no_minimode: bool = False, + force_preview_pane_on: bool = False, + ok_button_text: str | None = None, +) -> list[str] | None: # sourcery skip: low-code-quality + """Opens a file dialog to save a file. + + Args: + title (str | None): The title of the dialog. + default_folder (str | None): The initial folder to open. + file_types (list[tuple[str, str]] | None): A list of file type filters. + default_extension (str | None): The default file extension. + overwrite_prompt (bool): Prompts if the selected file already exists. FileOpenOptions.FOS_OVERWRITEPROMPT. + strict_file_types (bool): Restricts selection to specified file types. FileOpenOptions.FOS_STRICTFILETYPES. + no_change_dir (bool): Prevents changing the current working directory. FileOpenOptions.FOS_NOCHANGEDIR. + force_filesystem (bool): Ensures only file system items are shown. FileOpenOptions.FOS_FORCEFILESYSTEM. + all_non_storage_items (bool): Allows selection of non-file system items. FileOpenOptions.FOS_ALLNONSTORAGEITEMS. + no_validate (bool): Disables file name validation. FileOpenOptions.FOS_NOVALIDATE. + path_must_exist (bool): Requires the path to exist. FileOpenOptions.FOS_PATHMUSTEXIST. + file_must_exist (bool): Requires the file to exist. FileOpenOptions.FOS_FILEMUSTEXIST. + create_prompt (bool): Prompts to create a new file if it doesn't exist. FileOpenOptions.FOS_CREATEPROMPT. + share_aware (bool): Ensures the dialog is aware of sharing conflicts. FileOpenOptions.FOS_SHAREAWARE. + no_readonly_return (bool): Prevents selection of read-only items. FileOpenOptions.FOS_NOREADONLYRETURN. + no_test_file_create (bool): Disables testing file creation ability. FileOpenOptions.FOS_NOTESTFILECREATE. + hide_mru_places (bool): Hides most recently used places. FileOpenOptions.FOS_HIDEMRUPLACES. + hide_pinned_places (bool): Hides pinned places. FileOpenOptions.FOS_HIDEPINNEDPLACES. + no_dereference_links (bool): Prevents dereferencing shortcuts. FileOpenOptions.FOS_NODEREFERENCELINKS. + add_to_recent (bool): Prevents adding the file to recent files. FileOpenOptions.FOS_DONTADDTORECENT. + show_hidden_files (bool): Shows hidden files and folders. FileOpenOptions.FOS_FORCESHOWHIDDEN. + default_no_minimode (bool): Uses default non-minimized mode. FileOpenOptions.FOS_DEFAULTNOMINIMODE. + force_preview_pane_on (bool): Forces the preview pane to be visible. FileOpenOptions.FOS_FORCEPREVIEWPANEON. + ok_button_text (str): The text for the button used to select/confirm the dialog. + + Returns: + list[str] | None: A list of selected file paths or None if cancelled. + """ + options = 0 + if overwrite_prompt: + options |= FileOpenOptions.FOS_OVERWRITEPROMPT + if strict_file_types: + options |= FileOpenOptions.FOS_STRICTFILETYPES + if no_change_dir: + options |= FileOpenOptions.FOS_NOCHANGEDIR + if force_filesystem: + options |= FileOpenOptions.FOS_FORCEFILESYSTEM + if all_non_storage_items: + options |= FileOpenOptions.FOS_ALLNONSTORAGEITEMS + if no_validate: + options |= FileOpenOptions.FOS_NOVALIDATE + if path_must_exist: + options |= FileOpenOptions.FOS_PATHMUSTEXIST + if file_must_exist: + options |= FileOpenOptions.FOS_FILEMUSTEXIST + if create_prompt: + options |= FileOpenOptions.FOS_CREATEPROMPT + if share_aware: + options |= FileOpenOptions.FOS_SHAREAWARE + if no_readonly_return: + options |= FileOpenOptions.FOS_NOREADONLYRETURN + if no_test_file_create: + options |= FileOpenOptions.FOS_NOTESTFILECREATE + if hide_mru_places: + options |= FileOpenOptions.FOS_HIDEMRUPLACES + if hide_pinned_places: + options |= FileOpenOptions.FOS_HIDEPINNEDPLACES + if no_dereference_links: + options |= FileOpenOptions.FOS_NODEREFERENCELINKS + if not add_to_recent: + options |= FileOpenOptions.FOS_DONTADDTORECENT + if show_hidden_files: + options |= FileOpenOptions.FOS_FORCESHOWHIDDEN + if default_no_minimode: + options |= FileOpenOptions.FOS_DEFAULTNOMINIMODE + if force_preview_pane_on: + options |= FileOpenOptions.FOS_FORCEPREVIEWPANEON + options &= ~FileOpenOptions.FOS_PICKFOLDERS # Required (exceptions otherwise) + options &= ~FileOpenOptions.FOS_ALLOWMULTISELECT # Required (exceptions otherwise) + file_dialog = comtypes.client.CreateObject(CLSID_FileSaveDialog, interface=IFileSaveDialog) + return configure_file_dialog(file_dialog, title, options, default_folder, ok_button_text, None, file_types, default_extension) + + +def open_folder_dialog( # noqa: C901, PLR0913, PLR0912 + title: str | None = "Select Folder", + default_folder: str | None = None, + *, + overwrite_prompt: bool = False, + strict_file_types: bool = False, + no_change_dir: bool = False, + force_filesystem: bool = True, + no_validate: bool = False, + allow_multiple_selection: bool = False, + path_must_exist: bool = True, + file_must_exist: bool = False, + create_prompt: bool = False, + share_aware: bool = False, + no_readonly_return: bool = False, + no_test_file_create: bool = False, + hide_mru_places: bool = False, + hide_pinned_places: bool = False, + no_dereference_links: bool = False, + add_to_recent: bool = True, + show_hidden_files: bool = False, + default_no_minimode: bool = False, + force_preview_pane_on: bool = False, + ok_button_text: str | None = None, +) -> list[str] | None: # sourcery skip: low-code-quality + """Opens a dialog to select folders. + + Args: + title (str | None): The title of the dialog. + default_folder (str | None): The initial folder to open. + overwrite_prompt (bool): Prompts if the selected file already exists. FileOpenOptions.FOS_OVERWRITEPROMPT. + strict_file_types (bool): Restricts selection to specified file types. FileOpenOptions.FOS_STRICTFILETYPES. + no_change_dir (bool): Prevents changing the current working directory. FileOpenOptions.FOS_NOCHANGEDIR. + force_filesystem (bool): Ensures only file system items are shown. FileOpenOptions.FOS_FORCEFILESYSTEM. + no_validate (bool): Disables file name validation. FileOpenOptions.FOS_NOVALIDATE. + allow_multiple_selection (bool): Allows selecting multiple files. FileOpenOptions.FOS_ALLOWMULTISELECT. + path_must_exist (bool): Requires the path to exist. FileOpenOptions.FOS_PATHMUSTEXIST. + file_must_exist (bool): Requires the file to exist. FileOpenOptions.FOS_FILEMUSTEXIST. + create_prompt (bool): Prompts to create a new file if it doesn't exist. FileOpenOptions.FOS_CREATEPROMPT. + share_aware (bool): Ensures the dialog is aware of sharing conflicts. FileOpenOptions.FOS_SHAREAWARE. + no_readonly_return (bool): Prevents selection of read-only items. FileOpenOptions.FOS_NOREADONLYRETURN. + no_test_file_create (bool): Disables testing file creation ability. FileOpenOptions.FOS_NOTESTFILECREATE. + hide_mru_places (bool): Hides most recently used places. FileOpenOptions.FOS_HIDEMRUPLACES. + hide_pinned_places (bool): Hides pinned places. FileOpenOptions.FOS_HIDEPINNEDPLACES. + no_dereference_links (bool): Prevents dereferencing shortcuts. FileOpenOptions.FOS_NODEREFERENCELINKS. + add_to_recent (bool): Prevents adding the file to recent files. FileOpenOptions.FOS_DONTADDTORECENT. + show_hidden_files (bool): Shows hidden files and folders. FileOpenOptions.FOS_FORCESHOWHIDDEN. + default_no_minimode (bool): Uses default non-minimized mode. FileOpenOptions.FOS_DEFAULTNOMINIMODE. + force_preview_pane_on (bool): Forces the preview pane to be visible. FileOpenOptions.FOS_FORCEPREVIEWPANEON. + ok_button_text (str): The text for the button used to select/confirm the dialog. + + Returns: + list[str] | None: A list of selected folder paths or None if cancelled. + """ + options = 0 + options |= FileOpenOptions.FOS_PICKFOLDERS + options &= ~FileOpenOptions.FOS_ALLNONSTORAGEITEMS + if overwrite_prompt: + options |= FileOpenOptions.FOS_OVERWRITEPROMPT + if strict_file_types: + options |= FileOpenOptions.FOS_STRICTFILETYPES + if no_change_dir: + options |= FileOpenOptions.FOS_NOCHANGEDIR + if force_filesystem: + options |= FileOpenOptions.FOS_FORCEFILESYSTEM + if no_validate: + options |= FileOpenOptions.FOS_NOVALIDATE + if allow_multiple_selection: + options |= FileOpenOptions.FOS_ALLOWMULTISELECT + if path_must_exist: + options |= FileOpenOptions.FOS_PATHMUSTEXIST + if file_must_exist: + options |= FileOpenOptions.FOS_FILEMUSTEXIST + if create_prompt: + options |= FileOpenOptions.FOS_CREATEPROMPT + if share_aware: + options |= FileOpenOptions.FOS_SHAREAWARE + if no_readonly_return: + options |= FileOpenOptions.FOS_NOREADONLYRETURN + if no_test_file_create: + options |= FileOpenOptions.FOS_NOTESTFILECREATE + if hide_mru_places: + options |= FileOpenOptions.FOS_HIDEMRUPLACES + if hide_pinned_places: + options |= FileOpenOptions.FOS_HIDEPINNEDPLACES + if no_dereference_links: + options |= FileOpenOptions.FOS_NODEREFERENCELINKS + if not add_to_recent: + options |= FileOpenOptions.FOS_DONTADDTORECENT + if show_hidden_files: + options |= FileOpenOptions.FOS_FORCESHOWHIDDEN + if default_no_minimode: + options |= FileOpenOptions.FOS_DEFAULTNOMINIMODE + if force_preview_pane_on: + options |= FileOpenOptions.FOS_FORCEPREVIEWPANEON + file_dialog = comtypes.client.CreateObject(CLSID_FileOpenDialog, interface=IFileOpenDialog) + return configure_file_dialog(file_dialog, title, options, default_folder, ok_button_text, None, None, None) + + +def get_open_file_dialog_results( + file_open_dialog: IFileOpenDialog, +) -> list[str]: + results: list[str] = [] + results_array: IShellItemArray = file_open_dialog.GetResults() + itemCount: int = results_array.GetCount() + + for i in range(itemCount): + shell_item: IShellItem = results_array.GetItemAt(i) + szFilePath: str = shell_item.GetDisplayName(SIGDN.SIGDN_FILESYSPATH) + if szFilePath and szFilePath.strip(): + results.append(szFilePath) + print(f"Item {i} file path: {szFilePath}") + else: + raise FileNotFoundError(errno.ENOENT, os.strerror(errno.ENOENT), szFilePath) + return results + + +def get_save_file_dialog_results( + comFuncs: COMFunctionPointers, # noqa: N803 + fileSaveDialog: IFileSaveDialog, # noqa: N803 +) -> str: + results = "" + resultItem: IShellItem = fileSaveDialog.GetResult() + + szFilePath = resultItem.GetDisplayName(SIGDN.SIGDN_FILESYSPATH) + szFilePathStr = str(szFilePath) + if szFilePathStr and szFilePathStr.strip(): + results = szFilePathStr + print(f"Selected file path: {szFilePath}") + comFuncs.pCoTaskMemFree(szFilePath) + else: + raise FileNotFoundError(errno.ENOENT, os.strerror(errno.ENOENT), str(szFilePath)) + + attributes: int = resultItem.GetAttributes(SFGAO.SFGAO_FILESYSTEM | SFGAO.SFGAO_FOLDER) + print(f"Selected item attributes: {attributes}") + + parentItem: IShellItem | comtypes.IUnknown = resultItem.GetParent() + if isinstance(parentItem, IShellItem): + szParentName: LPWSTR | str = parentItem.GetDisplayName(SIGDN.SIGDN_NORMALDISPLAY) + print(f"Selected item parent: {szParentName}") + comFuncs.pCoTaskMemFree(szParentName) + parentItem.Release() + + resultItem.Release() + + return results + + +# Example usage +if __name__ == "__main__": + # Randomizing arguments for open_file_dialog + open_file_args = { + "title": "Open File" if random.choice([True, False]) else None, # noqa: S311 + "default_folder": "C:\\Users" if random.choice([True, False]) else None, # noqa: S311 + "file_types": [("Text Files", "*.txt")] if random.choice([True, False]) else None, # noqa: S311 + "default_extension": "txt" if random.choice([True, False]) else None, # noqa: S311 + "overwrite_prompt": random.choice([True, False]), # noqa: S311 + "strict_file_types": random.choice([True, False]), # noqa: S311 + "no_change_dir": random.choice([True, False]), # noqa: S311 + "force_filesystem": random.choice([True, False]), # noqa: S311 + "all_non_storage_items": False, # random.choice([True, False]), # noqa: S311 + "no_validate": random.choice([True, False]), # noqa: S311 + "allow_multiple_selection": random.choice([True, False]), # noqa: S311 + "path_must_exist": random.choice([True, False]), # noqa: S311 + "file_must_exist": random.choice([True, False]), # noqa: S311 + "create_prompt": random.choice([True, False]), # noqa: S311 + "share_aware": random.choice([True, False]), # noqa: S311 + "no_readonly_return": random.choice([True, False]), # noqa: S311 + "no_test_file_create": random.choice([True, False]), # noqa: S311 + "hide_mru_places": random.choice([True, False]), # noqa: S311 + "hide_pinned_places": random.choice([True, False]), # noqa: S311 + "no_dereference_links": random.choice([True, False]), # noqa: S311 + "add_to_recent": random.choice([True, False]), # noqa: S311 + "show_hidden_files": random.choice([True, False]), # noqa: S311 + "default_no_minimode": random.choice([True, False]), # noqa: S311 + "force_preview_pane_on": random.choice([True, False]), # noqa: S311 + } + print("\nOpen file args") + print(json.dumps(open_file_args, indent=4, sort_keys=True)) + selected_files: list[str] | None = open_file_and_folder_dialog(**open_file_args) + print("Selected files:", selected_files) + + # Randomizing arguments for open_folder_dialog + open_folder_args = { + "title": "Select Folder" if random.choice([True, False]) else None, # noqa: S311 + "default_folder": "C:\\Users" if random.choice([True, False]) else None, # noqa: S311 + "overwrite_prompt": random.choice([True, False]), # noqa: S311 + "strict_file_types": random.choice([True, False]), # noqa: S311 + "no_change_dir": random.choice([True, False]), # noqa: S311 + "force_filesystem": random.choice([True, False]), # noqa: S311 + "no_validate": random.choice([True, False]), # noqa: S311 + "allow_multiple_selection": random.choice([True, False]), # noqa: S311 + "path_must_exist": random.choice([True, False]), # noqa: S311 + "file_must_exist": random.choice([True, False]), # noqa: S311 + "create_prompt": random.choice([True, False]), # noqa: S311 + "share_aware": random.choice([True, False]), # noqa: S311 + "no_readonly_return": random.choice([True, False]), # noqa: S311 + "no_test_file_create": random.choice([True, False]), # noqa: S311 + "hide_mru_places": random.choice([True, False]), # noqa: S311 + "hide_pinned_places": random.choice([True, False]), # noqa: S311 + "no_dereference_links": random.choice([True, False]), # noqa: S311 + "add_to_recent": random.choice([True, False]), # noqa: S311 + "show_hidden_files": random.choice([True, False]), # noqa: S311 + "default_no_minimode": random.choice([True, False]), # noqa: S311 + "force_preview_pane_on": random.choice([True, False]), # noqa: S311 + } + print("\nOpen folder args") + print(json.dumps(open_folder_args, indent=4, sort_keys=True)) + selected_folders: list[str] | None = open_folder_dialog(**open_folder_args) + print("Selected folders:", selected_folders) + + # Randomizing arguments for save_file_dialog + save_file_args = { + "title": "Save File" if random.choice([True, False]) else None, # noqa: S311 + "default_folder": "C:\\Users" if random.choice([True, False]) else None, # noqa: S311 + "file_types": [("Text Files", "*.txt")] if random.choice([True, False]) else None, # noqa: S311 + "default_extension": "txt" if random.choice([True, False]) else None, # noqa: S311 + "overwrite_prompt": random.choice([True, False]), # noqa: S311 + "strict_file_types": random.choice([True, False]), # noqa: S311 + "no_change_dir": random.choice([True, False]), # noqa: S311 + "force_filesystem": random.choice([True, False]), # noqa: S311 + "all_non_storage_items": random.choice([True, False]), # noqa: S311 + "no_validate": random.choice([True, False]), # noqa: S311 + "path_must_exist": random.choice([True, False]), # noqa: S311 + "file_must_exist": random.choice([True, False]), # noqa: S311 + "create_prompt": random.choice([True, False]), # noqa: S311 + "share_aware": random.choice([True, False]), # noqa: S311 + "no_readonly_return": random.choice([True, False]), # noqa: S311 + "no_test_file_create": random.choice([True, False]), # noqa: S311 + "hide_mru_places": random.choice([True, False]), # noqa: S311 + "hide_pinned_places": random.choice([True, False]), # noqa: S311 + "no_dereference_links": random.choice([True, False]), # noqa: S311 + "add_to_recent": random.choice([True, False]), # noqa: S311 + "show_hidden_files": random.choice([True, False]), # noqa: S311 + "default_no_minimode": random.choice([True, False]), # noqa: S311 + "force_preview_pane_on": random.choice([True, False]), # noqa: S311 + } + print("\nSave file args") + print(json.dumps(save_file_args, indent=4, sort_keys=True)) + saved_file: list[str] | None = save_file_dialog(**save_file_args) + print("Saved file:", saved_file) diff --git a/winforms/src/toga_winforms/libs/py_wrappers/common.py b/winforms/src/toga_winforms/libs/py_wrappers/common.py new file mode 100644 index 0000000000..8f756fc1ea --- /dev/null +++ b/winforms/src/toga_winforms/libs/py_wrappers/common.py @@ -0,0 +1,50 @@ +from __future__ import annotations + +from ctypes import Structure, c_char_p, c_int, c_ssize_t, c_ulong, c_void_p, windll +from ctypes.wintypes import BOOL, DWORD, HANDLE, HINSTANCE, HKEY +from typing import Any + +# context menu properties +SEE_MASK_NOCLOSEPROCESS = 0x00000040 +SEE_MASK_INVOKEIDLIST = 0x0000000C + +LRESULT = c_ssize_t + + +class SHELLEXECUTEINFO(Structure): + _fields_ = ( + ("cbSize", DWORD), + ("fMask", c_ulong), + ("hwnd", HANDLE), + ("lpVerb", c_char_p), + ("lpFile", c_char_p), + ("lpParameters", c_char_p), + ("lpDirectory", c_char_p), + ("nShow", c_int), + ("hInstApp", HINSTANCE), + ("lpIDList", c_void_p), + ("lpClass", c_char_p), + ("hKeyClass", HKEY), + ("dwHotKey", DWORD), + ("hIconOrMonitor", HANDLE), + ("hProcess", HANDLE), + ) + cbSize: int + fMask: int + hwnd: int + lpVerb: bytes + lpFile: bytes + lpParameters: bytes + lpDirectory: bytes + nShow: int + hInstApp: Any | c_void_p + lpIDList: Any | c_void_p + lpClass: bytes + hKeyClass: Any | c_void_p + dwHotKey: int + hIconOrMonitor: int + hProcess: int + + +ShellExecuteEx = windll.shell32.ShellExecuteEx +ShellExecuteEx.restype = BOOL diff --git a/winforms/src/toga_winforms/libs/py_wrappers/context_menu.py b/winforms/src/toga_winforms/libs/py_wrappers/context_menu.py new file mode 100644 index 0000000000..479aba2662 --- /dev/null +++ b/winforms/src/toga_winforms/libs/py_wrappers/context_menu.py @@ -0,0 +1,209 @@ +from __future__ import annotations + +import ctypes +import errno +from contextlib import ExitStack +from ctypes import byref, windll +from pathlib import WindowsPath +from typing import TYPE_CHECKING, Iterable, Protocol, cast, runtime_checkable + +import comtypes # pyright: ignore[reportMissingTypeStubs] +import comtypes.client # pyright: ignore[reportMissingTypeStubs] + +from toga_winforms.libs.win_wrappers.hwnd import SimplePyHWND + +if TYPE_CHECKING: + import os + from typing import Any, Callable, Sequence + + from typing_extensions import Literal + from win32com.client.dynamic import CDispatch + + +def safe_isfile(path: WindowsPath) -> bool | None: + try: + result: bool = path.is_file() + except (OSError, ValueError): + return None + else: + return result + + +def safe_isdir(path: WindowsPath) -> bool | None: + try: + result: bool = path.is_dir() + except (OSError, ValueError): + return None + else: + return result + + +def create_dispatch_shell() -> CDispatch | ShellNamespace: + try: + import win32com.client + except ImportError: + return comtypes.client.CreateObject("Shell.Application") + else: + return win32com.client.Dispatch("Shell.Application") + + +def get_context_menu_functions() -> tuple[bool, Callable[..., Any], Callable[..., Any], Callable[..., Any], Callable[..., Any], int, int, int]: + try: + import win32con + import win32gui + except ImportError: + return (False, windll.user32.AppendMenuW, windll.user32.CreatePopupMenu, windll.user32.GetCursorPos, windll.user32.TrackPopupMenu, + 0x0000, 0x0000, 0x0100) + else: + return (True, win32gui.AppendMenu, win32gui.CreatePopupMenu, win32gui.GetCursorPos, win32gui.TrackPopupMenu, + win32con.MF_STRING, win32con.TPM_LEFTALIGN, win32con.TPM_RETURNCMD) + + +class _Vector2: + def __init__(self, x: int, y: int): + self.x: int = x + self.y: int = y + + +class _POINT(ctypes.Structure): + _fields_ = [("x", ctypes.c_long), ("y", ctypes.c_long)] # noqa: RUF012 + + +@runtime_checkable +class ShellNamespace(Protocol): + def NameSpace(self, folder: str | Literal[0]) -> ShellFolder: + ... + + +@runtime_checkable +class ShellFolder(Protocol): + def ParseName(self, name: str) -> ShellFolderItem: + ... + + +@runtime_checkable +class ShellFolderItem(Protocol): + def Verbs(self) -> ShellFolderItemVerbs: + ... + + +@runtime_checkable +class ShellFolderItemVerbs(Protocol): + def Item(self, index: int) -> ShellFolderItemVerb: + ... + def __getitem__(self, index: int) -> ShellFolderItemVerb: + ... + def __len__(self) -> int: + ... + + +@runtime_checkable +class ShellFolderItemVerb(Protocol): + def DoIt(self) -> None: + ... + @property + def Name(self) -> str: + ... + + +def get_cursor_pos(c_getcursorpos: Callable, *, use_pywin32: bool) -> _Vector2: + if use_pywin32: + return _Vector2(*c_getcursorpos()) + pt = _POINT() + c_getcursorpos(byref(pt)) + return cast(_Vector2, pt) + + +def show_context_menu(context_menu: CDispatch | ShellFolderItemVerbs, hwnd: int | None): + # assert isinstance(context_menu, Iterable) # this fails! + assert hasattr(context_menu, "__iter__") # this also fails! + if not hasattr(context_menu, "__getitem__"): + raise TypeError(f"Expected arg1 to be Iterable or something similar: {context_menu} ({context_menu.__class__.__name__})") + + pywin32_available, AppendMenu, CreatePopupMenu, GetCursorPos, TrackPopupMenu, MF_STRING, TPM_LEFTALIGN, TPM_RETURNCMD = get_context_menu_functions() + hmenu = CreatePopupMenu() + for i, verb in enumerate(context_menu): # pyright: ignore[reportArgumentType] + if verb.Name: + AppendMenu(hmenu, MF_STRING, i + 1, verb.Name) + pt: _Vector2 = get_cursor_pos(GetCursorPos, use_pywin32=pywin32_available) + with ExitStack() as stack: + hwnd = stack.enter_context(SimplePyHWND()) if hwnd is None else hwnd + cmd = TrackPopupMenu(hmenu, TPM_LEFTALIGN | TPM_RETURNCMD, + pt.x, pt.y, 0, hwnd, None) + if not isinstance(cmd, int): + raise RuntimeError("Unable to open the context manager, reason unknown") # noqa: TRY004 + verb = context_menu.Item(cmd - 1) + if verb: + verb.DoIt() + + +def windows_context_menu_file( + file_path: os.PathLike | str, + hwnd: int | None = None, +) -> None: + parsed_filepath: WindowsPath = WindowsPath(file_path).resolve() + shell = create_dispatch_shell() + folder_object = shell.NameSpace(str(parsed_filepath.parent)) + folder_item = folder_object.ParseName(parsed_filepath.name) + show_context_menu(folder_item.Verbs(), hwnd) + + +def windows_context_menu_multiple( + paths: Sequence[os.PathLike | str], + hwnd: int | None = None, +): + parsed_paths: list[WindowsPath] = [WindowsPath(path).resolve() for path in paths] + folder_items = [] + shell = create_dispatch_shell() + for path in parsed_paths: + folder_object = shell.NameSpace(str(path.parent if safe_isfile(path) else path)) + item = folder_object.ParseName(path.name) # Following happens when path doesn't exist: `AttributeError: 'NoneType' object has no attribute 'ParseName'` + folder_items.append(item) + show_context_menu(folder_items[0].Verbs(), hwnd) + + +def windows_context_menu_folder( + folder_path: os.PathLike | str, + hwnd: int | None = None, +) -> None: + parsed_folderpath: WindowsPath = WindowsPath(folder_path).resolve() + shell = create_dispatch_shell() + desktop_object = shell.NameSpace(0) + folder_item = desktop_object.ParseName(str(parsed_folderpath)) + context_menu = folder_item.Verbs() + show_context_menu(context_menu, hwnd) + + +def windows_context_menu(path: os.PathLike | str | Iterable[os.PathLike | str], hwnd: int | None = None): + if isinstance(path, Iterable): + paths = list(path) + if not paths: + return + if len(paths) > 1: + windows_context_menu_multiple(paths, hwnd) + return + parsed_path: WindowsPath = WindowsPath(paths[0]) + else: + parsed_path = WindowsPath(path) + + if safe_isfile(parsed_path): + windows_context_menu_file(parsed_path, hwnd) + elif safe_isdir(parsed_path): + windows_context_menu_folder(parsed_path, hwnd) + else: + msg = f"Path is neither file nor folder: '{path}'" + raise FileNotFoundError(errno.ENOENT, msg, str(path)) + + +# Example usage +if __name__ == "__main__": + windows_context_menu(r"C:\Users\Wizard\test_folder\City.sol") + + multiple_files = [ + r"C:\Users\Wizard\test_folder\RestoreBackup.ps1", + r"C:\Users\Wizard\test_folder\City.sol", + ] + windows_context_menu_multiple(multiple_files) + + folderpath = r"C:\Users\Wizard\test_folder" + windows_context_menu_folder(folderpath) diff --git a/winforms/src/toga_winforms/libs/py_wrappers/hresult.py b/winforms/src/toga_winforms/libs/py_wrappers/hresult.py new file mode 100644 index 0000000000..32e90c739c --- /dev/null +++ b/winforms/src/toga_winforms/libs/py_wrappers/hresult.py @@ -0,0 +1,419 @@ +from __future__ import annotations + +from ctypes import HRESULT as ctypesHRESULT # noqa: N811 +from ctypes import c_long, get_last_error +from typing import TYPE_CHECKING, ClassVar, cast + +if TYPE_CHECKING: + from typing_extensions import Literal, Self # pyright: ignore[reportMissingModuleSource] + + +class Win32OSError(OSError): + ... + + +class HRESULT(ctypesHRESULT): + FACILITY_CODES: ClassVar[dict[int, str]] = { + 0: "FACILITY_NULL", + 1: "FACILITY_RPC", + 2: "FACILITY_DISPATCH", + 3: "FACILITY_STORAGE", + 4: "FACILITY_ITF", + 7: "FACILITY_WIN32", + 8: "FACILITY_WINDOWS", + 9: "FACILITY_SECURITY", + 10: "FACILITY_CONTROL", + 11: "FACILITY_CERT", + 12: "FACILITY_INTERNET", + 13: "FACILITY_MEDIASERVER", + 14: "FACILITY_MSMQ", + 15: "FACILITY_SETUPAPI", + 16: "FACILITY_SCARD", + 17: "FACILITY_COMPLUS", + 18: "FACILITY_AAF", + 19: "FACILITY_URT", + 20: "FACILITY_ACS", + 21: "FACILITY_DPLAY", + 22: "FACILITY_UMI", + 23: "FACILITY_SXS", + 24: "FACILITY_WINDOWS_CE", + 25: "FACILITY_HTTP", + 26: "FACILITY_USERMODE_COMMONLOG", + 27: "FACILITY_WER", + 28: "FACILITY_USERMODE_FILTER_MANAGER", + 29: "FACILITY_BACKGROUNDCOPY", + 30: "FACILITY_CONFIGURATION", + 31: "FACILITY_STATE_MANAGEMENT", + 32: "FACILITY_METADIRECTORY", + 33: "FACILITY_SYSTEM_INTEGRITY", + 34: "FACILITY_VIRTUALIZATION", + 35: "FACILITY_VOLMGR", + 36: "FACILITY_BCD", + 37: "FACILITY_USERMODE_VHD", + 38: "FACILITY_USERMODE_HYPERVISOR", + 39: "FACILITY_USERMODE_VM", + 40: "FACILITY_USERMODE_VOLSNAP", + 41: "FACILITY_USERMODE_STORLIB", + 42: "FACILITY_USERMODE_LICENSING", + 43: "FACILITY_USERMODE_SMB", + 44: "FACILITY_USERMODE_VSS", + 45: "FACILITY_USERMODE_FILE_REPLICATION", + 46: "FACILITY_USERMODE_NDIS", + 47: "FACILITY_USERMODE_TPM", + 48: "FACILITY_USERMODE_NT", + 49: "FACILITY_USERMODE_USB", + 50: "FACILITY_USERMODE_NTOS", + 51: "FACILITY_USERMODE_COMPLUS", + 52: "FACILITY_USERMODE_NET", + 53: "FACILITY_USERMODE_CONFIGURATION_MANAGER", + 54: "FACILITY_USERMODE_COM", + 55: "FACILITY_USERMODE_DIRECTORY_SERVICE", + 56: "FACILITY_USERMODE_CMI", + 57: "FACILITY_USERMODE_LSA", + 58: "FACILITY_USERMODE_RPC", + 59: "FACILITY_USERMODE_IPSECVPN", + 60: "FACILITY_USERMODE_NETWORK_POLICY", + 61: "FACILITY_USERMODE_DNS_SERVER", + 62: "FACILITY_USERMODE_DNS_SERVER_ADMIN", + 63: "FACILITY_USERMODE_DNS_SERVER_CONFIGURATION", + 64: "FACILITY_USERMODE_DNS_SERVER_TUNING", + 65: "FACILITY_USERMODE_DNS_SERVER_ZONE", + 66: "FACILITY_USERMODE_DNS_SERVER_FORWARDER", + 67: "FACILITY_USERMODE_DNS_SERVER_REPLICATION", + 68: "FACILITY_USERMODE_DNS_SERVER_NDNC", + 69: "FACILITY_USERMODE_DNS_SERVER_FORWARDS", + 70: "FACILITY_USERMODE_DNS_SERVER_DS", + 71: "FACILITY_USERMODE_DNS_SERVER_ROOT_HINTS", + 72: "FACILITY_USERMODE_DNS_SERVER_ZONE_SOURCE", + 73: "FACILITY_USERMODE_DNS_SERVER_DATABASE", + 74: "FACILITY_USERMODE_DNS_SERVER_PROTOCOL", + 75: "FACILITY_USERMODE_DNS_SERVER_SERVICED", + 76: "FACILITY_USERMODE_DNS_SERVER_SOCKETS", + 77: "FACILITY_USERMODE_DNS_SERVER_SERVER_ADMIN", + 78: "FACILITY_USERMODE_DNS_SERVER_SOAP", + 79: "FACILITY_USERMODE_DNS_SERVER_ISAPI", + 80: "FACILITY_USERMODE_DNS_SERVER_WEB", + 81: "FACILITY_USERMODE_DNS_SERVER_SERVER", + 82: "FACILITY_USERMODE_DNS_SERVER_ADMIN_R2", + 83: "FACILITY_USERMODE_DNS_SERVER_ISAPI_FILTER", + 84: "FACILITY_USERMODE_DNS_SERVER_NDNC2", + 85: "FACILITY_USERMODE_DNS_SERVER_EVENTLOG", + 86: "FACILITY_USERMODE_DNS_SERVER_ADMIN2", + 87: "FACILITY_USERMODE_DNS_SERVER_ZONE2", + 88: "FACILITY_USERMODE_DNS_SERVER_NDNC3", + 89: "FACILITY_USERMODE_DNS_SERVER_SOCKETS2", + 90: "FACILITY_USERMODE_DNS_SERVER_ADMIN3", + 91: "FACILITY_USERMODE_DNS_SERVER_WEB2", + 92: "FACILITY_USERMODE_DNS_SERVER_ADMIN4", + 93: "FACILITY_USERMODE_DNS_SERVER_SERVER3", + 94: "FACILITY_USERMODE_DNS_SERVER_SOCKETS3", + 95: "FACILITY_USERMODE_DNS_SERVER_ADMIN5", + 96: "FACILITY_USERMODE_DNS_SERVER_SERVER4", + 97: "FACILITY_USERMODE_DNS_SERVER_ADMIN6", + 98: "FACILITY_USERMODE_DNS_SERVER_SOCKETS4", + 99: "FACILITY_USERMODE_DNS_SERVER_WEB3", + } + + def __new__( + cls, value: HRESULT | ctypesHRESULT | int | c_long | None = None + ) -> Self: + if value is None: + converted_value = 0 + elif isinstance(value, int): + converted_value = value + elif isinstance(getattr(value, "value", None), int): + converted_value = value.value + else: + raise TypeError(f"Invalid type for HRESULT: {type(value)}") + instance = c_long(converted_value) + instance.__class__ = cls + return cast(cls, instance) + + def __init__(self, value: HRESULT | ctypesHRESULT | int | c_long | None = None): + if value is None: + self.value = 0 + elif isinstance(value, int): + self.value = value + elif isinstance(getattr(value, "value", None), int): + self.value = value.value + else: + raise TypeError(f"Invalid type for HRESULT: {type(value)}") + + @staticmethod + def to_hresult(value: int) -> int: + """Convert WinError to HRESULT if necessary.""" + if value & 0x80000000: + value &= 0xFFFFFFFF + return value + + @staticmethod + def from_hresult(value: int) -> int: + """Convert HRESULT to WinError.""" + return value & 0xFFFFFFFF if value < 0 else value + + def decode(self): + severity: int = (self >> 31) & 1 + facility: int = (self >> 16) & 0x1FFF + code: int = self & 0xFFFF + + severity_str: Literal["Success", "Failure"] = "Success" if severity == 0 else "Failure" + facility_str: str = HRESULT.FACILITY_CODES.get(facility, "Unknown Facility") + + return ( + f"HRESULT: 0x{self:08X}\n" + f"Severity: {severity_str}\n" + f"Facility: {facility_str} ({facility})\n" + f"Code: 0x{code:04X} ({code})" + ) + + def __str__(self): + return str(self.to_hresult(self.value)) + + def __repr__(self): + return f"{self.__class__.__name__}({self.value})" + + def __eq__( + self, other: int | ctypesHRESULT + ) -> bool: # sourcery skip: assign-if-exp, reintroduce-else + if not isinstance(other, int) and (not hasattr(other, "value") or not isinstance(other.value, int)): + return NotImplemented + other_int = self.to_hresult(other.value if isinstance(other, (HRESULT, ctypesHRESULT, c_long)) else other) + return self.value == other_int + + def __ne__( + self, other: HRESULT | ctypesHRESULT | int | c_long + ) -> bool: # sourcery skip: assign-if-exp, reintroduce-else + if not isinstance(other, int) and (not hasattr(other, "value") or not isinstance(other.value, int)): + return NotImplemented + other_int = self.to_hresult(other.value if isinstance(other, (HRESULT, ctypesHRESULT, c_long)) else other) + return self.value != other_int + + def __int__(self) -> int: + return self.to_hresult(self.value) + + def __hash__(self) -> int: + return hash(self.value) + + def as_integer_ratio(self) -> tuple[int, int]: + return (self.value, 1) + + def __mod__(self, other: HRESULT | ctypesHRESULT | int | c_long) -> int: + if not isinstance(other, int) and (not hasattr(other, "value") or not isinstance(other.value, int)): + return NotImplemented + other_int = self.to_hresult(other.value if isinstance(other, (HRESULT, ctypesHRESULT, c_long)) else other) + return self.value % other_int + + def __divmod__( + self, other: HRESULT | ctypesHRESULT | int | c_long + ) -> tuple[int, int]: + if not isinstance(other, int) and (not hasattr(other, "value") or not isinstance(other.value, int)): + return NotImplemented + other_int = self.to_hresult(other.value if isinstance(other, (HRESULT, ctypesHRESULT, c_long)) else other) + return divmod(self.value, other_int) + + def __pow__( + self, other: HRESULT | ctypesHRESULT | int | c_long, mod: int | None = None + ) -> int: + if not isinstance(other, int) and (not hasattr(other, "value") or not isinstance(other.value, int)): + return NotImplemented + other_int = self.to_hresult(other.value if isinstance(other, (HRESULT, ctypesHRESULT, c_long)) else other) + if mod is None: + return pow(self.value, other_int) + return pow(self.value, other_int, mod) + + def __rmod__(self, other: HRESULT | ctypesHRESULT | int | c_long) -> int: + if not isinstance(other, int) and (not hasattr(other, "value") or not isinstance(other.value, int)): + return NotImplemented + other_int = self.to_hresult(other.value if isinstance(other, (HRESULT, ctypesHRESULT, c_long)) else other) + return other_int % self.to_hresult(self.value) + + def __rdivmod__( + self, other: HRESULT | ctypesHRESULT | int | c_long + ) -> tuple[int, int]: + if not isinstance(other, int) and (not hasattr(other, "value") or not isinstance(other.value, int)): + return NotImplemented + other_int = self.to_hresult(other.value if isinstance(other, (HRESULT, ctypesHRESULT, c_long)) else other) + return divmod(other_int, self.to_hresult(self.value)) + + def __and__(self, other: HRESULT | ctypesHRESULT | int | c_long) -> int: + if not isinstance(other, int) and (not hasattr(other, "value") or not isinstance(other.value, int)): + return NotImplemented + other_int = self.to_hresult(other.value if isinstance(other, (HRESULT, ctypesHRESULT, c_long)) else other) + return self.to_hresult(self.value) & other_int + + def __or__(self, other: HRESULT | ctypesHRESULT | int | c_long) -> int: + if not isinstance(other, int) and (not hasattr(other, "value") or not isinstance(other.value, int)): + return NotImplemented + other_int = self.to_hresult(other.value if isinstance(other, (HRESULT, ctypesHRESULT, c_long)) else other) + return self.to_hresult(self.value) | other_int + + def __xor__(self, other: HRESULT | ctypesHRESULT | int | c_long) -> int: + if not isinstance(other, int) and (not hasattr(other, "value") or not isinstance(other.value, int)): + return NotImplemented + other_int = self.to_hresult(other.value if isinstance(other, (HRESULT, ctypesHRESULT, c_long)) else other) + return self.to_hresult(self.value) ^ other_int + + def __lshift__(self, other: HRESULT | ctypesHRESULT | int | c_long) -> int: + if not isinstance(other, int) and (not hasattr(other, "value") or not isinstance(other.value, int)): + return NotImplemented + other_int = self.to_hresult(other.value if isinstance(other, (HRESULT, ctypesHRESULT, c_long)) else other) + return self.to_hresult(self.value) << other_int + + def __rshift__(self, other: HRESULT | ctypesHRESULT | int | c_long) -> int: + if not isinstance(other, int) and (not hasattr(other, "value") or not isinstance(other.value, int)): + return NotImplemented + other_int = self.to_hresult(other.value if isinstance(other, (HRESULT, ctypesHRESULT, c_long)) else other) + return self.to_hresult(self.value) >> other_int + + def __rand__(self, other: HRESULT | ctypesHRESULT | int | c_long) -> int: + if not isinstance(other, int) and (not hasattr(other, "value") or not isinstance(other.value, int)): + return NotImplemented + other_int = self.to_hresult(other.value if isinstance(other, (HRESULT, ctypesHRESULT, c_long)) else other) + return other_int & self.value + + def __ror__(self, other: HRESULT | ctypesHRESULT | int | c_long) -> int: + if not isinstance(other, int) and (not hasattr(other, "value") or not isinstance(other.value, int)): + return NotImplemented + other_int = self.to_hresult(other.value if isinstance(other, (HRESULT, ctypesHRESULT, c_long)) else other) + return other_int | self.value + + def __rxor__(self, other: HRESULT | ctypesHRESULT | int | c_long) -> int: + if isinstance(other, (HRESULT, ctypesHRESULT, c_long)): + return self.to_hresult(other.value) ^ self.value + other_int = self.to_hresult(other.value if isinstance(other, (HRESULT, ctypesHRESULT, c_long)) else other) + return other_int ^ self.value + + def __rlshift__(self, other: HRESULT | ctypesHRESULT | int | c_long) -> int: + if not isinstance(other, int) and (not hasattr(other, "value") or not isinstance(other.value, int)): + return NotImplemented + other_int = self.to_hresult(other.value if isinstance(other, (HRESULT, ctypesHRESULT, c_long)) else other) + return other_int << self.value + + def __rrshift__(self, other: HRESULT | ctypesHRESULT | int | c_long) -> int: + if not isinstance(other, int) and (not hasattr(other, "value") or not isinstance(other.value, int)): + return NotImplemented + other_int = self.to_hresult(other.value if isinstance(other, (HRESULT, ctypesHRESULT, c_long)) else other) + return other_int >> self.value + + def __neg__(self) -> int: + return -self.value + + def __pos__(self) -> int: + return +self.value + + def __invert__(self) -> int: + return ~self.value + + def __trunc__(self) -> int: + return self.value + + def __round__(self, ndigits: int = 0) -> int: + return round(self.value, ndigits) + + def __getnewargs__(self) -> tuple[int]: + return (self.value,) + + def __lt__(self, other: HRESULT | ctypesHRESULT | int | c_long) -> bool: + if not isinstance(other, int) and (not hasattr(other, "value") or not isinstance(other.value, int)): + return NotImplemented + other_int = self.to_hresult(other.value if isinstance(other, (HRESULT, ctypesHRESULT, c_long)) else other) + return self.value < other_int + + def __le__(self, other: HRESULT | ctypesHRESULT | int | c_long) -> bool: + if not isinstance(other, int) and (not hasattr(other, "value") or not isinstance(other.value, int)): + return NotImplemented + other_int = self.to_hresult(other.value if isinstance(other, (HRESULT, ctypesHRESULT, c_long)) else other) + return self.value <= other_int + + def __gt__(self, other: HRESULT | ctypesHRESULT | int | c_long) -> bool: + if not isinstance(other, int) and (not hasattr(other, "value") or not isinstance(other.value, int)): + return NotImplemented + other_int = self.to_hresult(other.value if isinstance(other, (HRESULT, ctypesHRESULT, c_long)) else other) + return self.value > other_int + + def __ge__(self, other: HRESULT | ctypesHRESULT | int | c_long) -> bool: + if not isinstance(other, int) and (not hasattr(other, "value") or not isinstance(other.value, int)): + return NotImplemented + other_int = self.to_hresult(other.value if isinstance(other, (HRESULT, ctypesHRESULT, c_long)) else other) + return self.value >= other_int + + def __float__(self) -> float: + return float(self.value) + + def __abs__(self) -> int: + return abs(self.value) + + def __bool__(self) -> bool: + return bool(self.value) + + def __index__(self) -> int: + return self.value + + def __format__(self, format_spec: str) -> str: + if format_spec == "08X": + return f"{self.to_hresult(self.value):08X}" + return format(self.to_hresult(self.value), format_spec) + + def exception(self, short_desc: str | None = None) -> Win32OSError: + return Win32OSError(self.to_hresult(self.value), decode_hresult(self), short_desc or "") + + @classmethod + def raise_for_status( + cls, + hresult: HRESULT | ctypesHRESULT | Self | int | None = None, + short_desc: str | None = None, + *, + ignore_s_false: bool = False, + ): + hresult = HRESULT(get_last_error() if hresult is None else hresult) + hr: Self = hresult if isinstance(hresult, cls) else cls(hresult) + if (hr == 1 and not ignore_s_false) or hr not in (HRESULT(0), HRESULT(1)): + raise hr.exception(short_desc) + return hresult + + +def decode_hresult(hresult: HRESULT | int) -> str: + if isinstance(hresult, HRESULT): + hresult = hresult.value + severity: int = (hresult >> 31) & 1 + facility: int = (hresult >> 16) & 0x1FFF + code: int = hresult & 0xFFFF + + severity_str: Literal["Success", "Failure"] = "Success" if severity == 0 else "Failure" + facility_str = HRESULT.FACILITY_CODES.get(facility, "Unknown Facility") + + return ( + f"HRESULT: 0x{HRESULT.to_hresult(hresult):08X}\n" + f"Severity: {severity_str}\n" + f"Facility: {facility_str} ({facility})\n" + f"Code: 0x{code:04X} ({code})" + ) + + +def print_hresult(hresult: HRESULT | int) -> None: + print(decode_hresult(hresult)) + + +def hresult_to_winerror(hresult: int) -> int: + """Convert a positive HRESULT value to the corresponding WinError value.""" + return hresult - 0x100000000 if hresult & 0x80000000 else hresult + + +def winerror_to_hresult(winerror: int) -> int: + """Convert a WinError value to the corresponding positive HRESULT value.""" + return winerror + 0x100000000 if winerror < 0 else winerror + + +S_OK = HRESULT(0) +S_FALSE = HRESULT(1) + + +if __name__ == "__main__": + # Example usage: + hr1 = HRESULT(10) + hr2 = HRESULT(c_long(20)) + hr3 = HRESULT(ctypesHRESULT(30)) + + print(hr1 == 10) + print(hr2 > hr1) + print(hr3 == ctypesHRESULT(30)) diff --git a/winforms/src/toga_winforms/libs/py_wrappers/hwnd.py b/winforms/src/toga_winforms/libs/py_wrappers/hwnd.py new file mode 100644 index 0000000000..3f913fa018 --- /dev/null +++ b/winforms/src/toga_winforms/libs/py_wrappers/hwnd.py @@ -0,0 +1,300 @@ +from __future__ import annotations + + +from ctypes import POINTER, WINFUNCTYPE, Structure, c_int, c_wchar_p, windll, c_long, c_uint, c_void_p +from ctypes.wintypes import ATOM, BOOL, DWORD, HBRUSH, HICON, HINSTANCE, HMENU, HWND, LPARAM, LPCWSTR, LPVOID, UINT, WPARAM + +from toga_winforms.libs.win_wrappers.common import LRESULT +from enum import IntEnum +from typing import TYPE_CHECKING, Sequence + +if TYPE_CHECKING: + from ctypes import ( + _CData, + _FuncPointer, + ) + from ctypes.wintypes import HMENU, LPVOID + from types import TracebackType + + from _win32typing import PyResourceId # pyright: ignore[reportMissingModuleSource] + + +WNDPROC: type[_FuncPointer] = WINFUNCTYPE(c_long, HWND, c_uint, WPARAM, LPARAM) +WM_DESTROY = 0x0002 +WS_OVERLAPPEDWINDOW = (0x00CF0000) +WS_VISIBLE = 0x10000000 +HCURSOR = c_void_p + + +class ctypesWNDCLASS(Structure): # noqa: N801 + _fields_: Sequence[tuple[str, type[_CData]] | tuple[str, type[_CData], int]] = [ + ("style", UINT), + ("lpfnWndProc", WNDPROC), + ("cbClsExtra", c_int), + ("cbWndExtra", c_int), + ("hInstance", HINSTANCE), + ("hIcon", HICON), + ("hCursor", HCURSOR), + ("hbrBackground", HBRUSH), + ("lpszMenuName", LPCWSTR), + ("lpszClassName", LPCWSTR) + ] + + + + +# Constants for MessageBox +MB_OK = 0x0 +MB_OKCANCEL = 0x1 +MB_ABORTRETRYIGNORE = 0x2 +MB_YESNOCANCEL = 0x3 +MB_YESNO = 0x4 +MB_RETRYCANCEL = 0x5 +MB_CANCELTRYCONTINUE = 0x6 + +MB_ICONHAND = 0x10 +MB_ICONQUESTION = 0x20 +MB_ICONEXCLAMATION = 0x30 +MB_ICONASTERISK = 0x40 + +MB_DEFBUTTON1 = 0x0 +MB_DEFBUTTON2 = 0x100 +MB_DEFBUTTON3 = 0x200 +MB_DEFBUTTON4 = 0x300 + +MB_APPLMODAL = 0x0 +MB_SYSTEMMODAL = 0x1000 +MB_TASKMODAL = 0x2000 + +MB_ICONERROR = MB_ICONHAND +MB_ICONWARNING = MB_ICONEXCLAMATION +MB_ICONINFORMATION = MB_ICONASTERISK + +MB_RIGHT = 0x800 +MB_RTLREADING = 0x1000 + +IDOK = 1 +IDCANCEL = 2 +IDABORT = 3 +IDRETRY = 4 +IDIGNORE = 5 +IDYES = 6 +IDNO = 7 +IDTRYAGAIN = 10 +IDCONTINUE = 11 + +WS_EX_DLGMODALFRAME = 0x00000001 + +windll.user32.MessageBoxW.argtypes = [c_int, c_wchar_p, c_wchar_p, c_int] +windll.user32.MessageBoxW.restype = c_int + +CBTProc = WINFUNCTYPE(c_int, c_int, WPARAM, LPARAM) +SetWindowsHookExW = windll.user32.SetWindowsHookExW +UnhookWindowsHookEx = windll.user32.UnhookWindowsHookEx +CallNextHookEx = windll.user32.CallNextHookEx +GetWindowRect = windll.user32.GetWindowRect +MoveWindow = windll.user32.MoveWindow + +# Windows API function prototypes +MessageBoxW = windll.user32.MessageBoxW +MessageBoxW.argtypes = [HWND, LPCWSTR, LPCWSTR, UINT] +MessageBoxW.restype = c_int + +CreateWindowExW = windll.user32.CreateWindowExW +CreateWindowExW.argtypes = [ + DWORD, LPCWSTR, LPCWSTR, DWORD, + c_int, c_int, c_int, c_int, + HWND, HMENU, HINSTANCE, LPVOID +] +CreateWindowExW.restype = HWND + +DefWindowProcW = windll.user32.DefWindowProcW +DefWindowProcW.argtypes = [HWND, UINT, WPARAM, LPARAM] +DefWindowProcW.restype = LRESULT + +RegisterClassExW = windll.user32.RegisterClassExW +RegisterClassExW.argtypes = [POINTER(ctypesWNDCLASS)] +RegisterClassExW.restype = ATOM + +GetModuleHandleW = windll.kernel32.GetModuleHandleW +GetModuleHandleW.argtypes = [LPCWSTR] +GetModuleHandleW.restype = HINSTANCE + +LoadIconW = windll.user32.LoadIconW +LoadIconW.argtypes = [HINSTANCE, LPCWSTR] +LoadIconW.restype = HICON + +LoadCursorW = windll.user32.LoadCursorW +LoadCursorW.argtypes = [HINSTANCE, LPCWSTR] +LoadCursorW.restype = HCURSOR + +ShowWindow = windll.user32.ShowWindow +ShowWindow.argtypes = [HWND, c_int] +ShowWindow.restype = BOOL + +UpdateWindow = windll.user32.UpdateWindow +UpdateWindow.argtypes = [HWND] +UpdateWindow.restype = BOOL + +SetWindowTextW = windll.user32.SetWindowTextW +SetWindowTextW.argtypes = [HWND, LPCWSTR] +SetWindowTextW.restype = BOOL + +CreateDialogParamW = windll.user32.CreateDialogParamW +CreateDialogParamW.argtypes = [HINSTANCE, LPCWSTR, HWND, LPVOID, LPARAM] +CreateDialogParamW.restype = HWND + +DestroyWindow = windll.user32.DestroyWindow +DestroyWindow.argtypes = [HWND] +DestroyWindow.restype = BOOL + +# Constants for Window Styles +WS_OVERLAPPEDWINDOW = 0x00CF0000 +WS_VISIBLE = 0x10000000 +WS_CHILD = 0x40000000 + +SW_SHOW = 5 +# Constants for window creation and message handling +WM_COMMAND = 0x0111 +WM_DESTROY = 0x0002 +WS_OVERLAPPEDWINDOW = 0x00CF0000 +WS_VISIBLE = 0x10000000 +WS_CHILD = 0x40000000 +SW_SHOW = 5 +SW_HIDE = 0 +ES_MULTILINE = 0x0004 +ES_AUTOVSCROLL = 0x0040 +ES_READONLY = 0x0800 +WS_VSCROLL = 0x00200000 +WS_EX_DLGMODALFRAME = 0x00000001 +IDOK = 1 +IDCANCEL = 2 + + +def wnd_proc( + hwnd: HWND, + message: c_uint, + wparam: WPARAM, + lparam: LPARAM, +) -> c_long: + if message == WM_DESTROY: + windll.user32.PostQuitMessage(0) + return c_long(0) + print(f"wnd_proc(hwnd={hwnd}, message={message}, wparam={wparam}, lparam={lparam})") + result = windll.user32.DefWindowProcW(hwnd, message, wparam, lparam) + if isinstance(result, int): + return c_long(result) + print(result, "result is unexpectedly class:", result.__class__.__name__) + return result + + +class CursorType(IntEnum): + ARROW = 32512 + IBEAM = 32513 + WAIT = 32514 + CROSS = 32515 + UPARROW = 32516 + SIZE = 32640 + ICON = 32641 + SIZENWSE = 32642 + SIZENESW = 32643 + SIZEWE = 32644 + SIZENS = 32645 + SIZEALL = 32646 + NO = 32648 + HAND = 32649 + APPSTARTING = 32650 + HELP = 32651 + + +class SimplePyHWND: + """Context manager for creating and destroying a simple custom window.""" + CLASS_NAME: str = "SimplePyHWND" + DISPLAY_NAME: str = "Python Simple Window" + + def __init__( + self, + *, + visible: bool = False, + dwExStyle: int = 0, # noqa: N803 + lpClassName: PyResourceId | str | None = None, # noqa: N803 + lpWindowName: str | None = None, # noqa: N803 + dwStyle: int | None = None, # noqa: N803 + x: int = 0, + y: int = 0, + nWidth: int = 1280, # noqa: N803 + nHeight: int = 720, # noqa: N803 + hWndParent: HWND | None = None, # noqa: N803 + hMenu: HMENU | None = None, # noqa: N803 + hInstance: HINSTANCE | None = None, # noqa: N803 + lpParam: LPVOID | None = None, # noqa: N803 + ): + self.hwnd: int | None = None + self.visible: bool = visible + self.dwExStyle: int = dwExStyle + self.lpClassName: PyResourceId | str | None = lpClassName or self.CLASS_NAME + self.lpWindowName: str | None = lpWindowName or self.DISPLAY_NAME + self.dwStyle: int | None = WS_OVERLAPPEDWINDOW | WS_VISIBLE if visible else (dwStyle or 0) + self.x: int = x + self.y: int = y + self.nWidth: int = nWidth + self.nHeight: int = nHeight + self.hWndParent: HWND | None = hWndParent + self.hMenu: HMENU | None = hMenu + self.hInstance: HINSTANCE | None = hInstance + self.lpParam: LPVOID | None = lpParam + + def __enter__(self) -> int: + self.register_class() + self.hwnd = self.create_window() + self._class_atom: PyResourceId | None = None + return self.hwnd + + def __exit__( + self, + exc_type: type[BaseException] | None, + exc_val: BaseException | None, + exc_tb: TracebackType | None, + ): + if self.hwnd is not None: + windll.user32.DestroyWindow(self.hwnd) + self.unregister_class() + + def register_class(self): + """Register the window class.""" + import win32gui + wc = win32gui.WNDCLASS() + wc.lpfnWndProc = WNDPROC(wnd_proc) # pyright: ignore[reportAttributeAccessIssue] + wc.lpszClassName = self.CLASS_NAME # pyright: ignore[reportAttributeAccessIssue] + self.hinst = wc.hInstance = win32gui.GetModuleHandle(None) # pyright: ignore[reportAttributeAccessIssue] + wc.hCursor = windll.user32.LoadCursorW(None, CursorType.ARROW.value) # pyright: ignore[reportAttributeAccessIssue] + try: + self._class_atom = win32gui.RegisterClass(wc) + except Exception as e: # pywintypes.error + if getattr(e, "winerror", None) != 1410: # class already registered + raise + print(f"{e} (Class already registered)") + + def unregister_class(self): + """Unregister the window class.""" + if self._class_atom is not None: + import win32gui + win32gui.UnregisterClass(self._class_atom, windll.kernel32.GetModuleHandleW(None)) + + def create_window(self) -> int: + """Create the window.""" + return windll.user32.CreateWindowExW( + self.dwExStyle, self.lpClassName, self.lpWindowName, self.dwStyle, + self.x, self.y, self.nWidth, self.nHeight, + self.hWndParent, self.hMenu, + windll.kernel32.GetModuleHandleW(None) if self.hInstance is None else self.hInstance, + self.lpParam) + + +if __name__ == "__main__": + import time + + with SimplePyHWND(visible=True) as hwnd: + print(f"Created SimplePyHWND with handle: {hwnd}") + time.sleep(3) + print("SimplePyHWND has been destroyed.") diff --git a/winforms/src/toga_winforms/libs/py_wrappers/winapi/__init__.py b/winforms/src/toga_winforms/libs/py_wrappers/winapi/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/winforms/src/toga_winforms/libs/py_wrappers/winapi/device_iocontrol.py b/winforms/src/toga_winforms/libs/py_wrappers/winapi/device_iocontrol.py new file mode 100644 index 0000000000..c0aed445de --- /dev/null +++ b/winforms/src/toga_winforms/libs/py_wrappers/winapi/device_iocontrol.py @@ -0,0 +1,905 @@ +from __future__ import annotations + +from enum import IntEnum # noqa: INP001 + + +class FSCTL(IntEnum): + # Windows XP and Earlier + FSCTL_REQUEST_OPLOCK_LEVEL_1 = 0x00090000 + """Requests an opportunistic lock (oplock) of level 1.""" + + FSCTL_REQUEST_OPLOCK_LEVEL_2 = 0x00090004 + """Requests an opportunistic lock (oplock) of level 2.""" + + FSCTL_REQUEST_BATCH_OPLOCK = 0x00090008 + """Requests a batch opportunistic lock (oplock).""" + + FSCTL_OPLOCK_BREAK_ACKNOWLEDGE = 0x0009000C + """Acknowledges an oplock break.""" + + FSCTL_OPBATCH_ACK_CLOSE_PENDING = 0x00090010 + """Acknowledges that an oplock break is pending.""" + + FSCTL_OPLOCK_BREAK_NOTIFY = 0x00090014 + """Notifies about an oplock break.""" + + FSCTL_LOCK_VOLUME = 0x00090018 + """Locks the volume.""" + + FSCTL_UNLOCK_VOLUME = 0x0009001C + """Unlocks the volume.""" + + FSCTL_DISMOUNT_VOLUME = 0x00090020 + """Dismounts the volume.""" + + FSCTL_IS_VOLUME_MOUNTED = 0x00090028 + """Checks if the volume is mounted.""" + + FSCTL_IS_PATHNAME_VALID = 0x0009002C + """Signals the file system driver not to perform any I/O boundary checks on partition read or write calls.""" + + FSCTL_MARK_VOLUME_DIRTY = 0x00090030 + """Marks the volume as dirty.""" + + FSCTL_QUERY_RETRIEVAL_POINTERS = 0x00090034 + """Queries retrieval pointers.""" + + FSCTL_GET_COMPRESSION = 0x0009003C + """Retrieves the current compression state of a file or directory on a volume whose file system supports per-stream compression.""" + + FSCTL_SET_COMPRESSION = 0x00090040 + """Sets the compression state of a file or directory on a volume whose file system supports per-stream compression.""" + + FSCTL_SET_BOOTLOADER_ACCESSED = 0x0009004C + """Marks the bootloader as accessed.""" + + FSCTL_OPLOCK_BREAK_ACK_NO_2 = 0x00090050 + """Acknowledges an oplock break without taking any further action.""" + + FSCTL_INVALIDATE_VOLUMES = 0x00090054 + """Invalidates the volumes.""" + + FSCTL_QUERY_FAT_BPB = 0x00090058 + """Retrieves the BIOS Parameter Block (BPB) for a FAT file system.""" + + FSCTL_REQUEST_FILTER_OPLOCK = 0x0009005C + """Requests a filter opportunistic lock (oplock).""" + + FSCTL_FILESYSTEM_GET_STATISTICS = 0x00090060 + """Retrieves file system statistics.""" + + FSCTL_GET_NTFS_VOLUME_DATA = 0x00090064 + """Retrieves information about the specified NTFS file system volume.""" + + FSCTL_GET_NTFS_FILE_RECORD = 0x00090068 + """Retrieves the first file record that is in use and is of a lesser than or equal ordinal value to the requested file reference number.""" + + FSCTL_GET_VOLUME_BITMAP = 0x0009006F + """Retrieves a bitmap of occupied and available clusters on a volume.""" + + FSCTL_GET_RETRIEVAL_POINTERS = 0x00090073 + """Given a file handle, retrieves a data structure that describes the allocation and location on disk of a specific file, or, given a volume handle, the locations of bad clusters on a volume.""" # noqa: E501, W505 + + FSCTL_MOVE_FILE = 0x00090074 + """Relocates one or more virtual clusters of a file from one logical cluster to another within the same volume. This operation is used during defragmentation.""" + + FSCTL_IS_VOLUME_DIRTY = 0x00090078 + """Determines whether the volume's dirty bit is set, indicating that the file system may be in an inconsistent state.""" + + FSCTL_ALLOW_EXTENDED_DASD_IO = 0x00090083 + """Allows file systems that reside on large storage devices to issue I/O operations that go beyond the 32-bit address range.""" + + FSCTL_FIND_FILES_BY_SID = 0x0009008F + """Finds files on a volume that belong to a specified security identifier (SID).""" + + FSCTL_SET_OBJECT_ID = 0x000900BC + """Sets the object identifier for the specified file or directory.""" + + FSCTL_GET_OBJECT_ID = 0x0009009C + """Retrieves the object identifier for the specified file or directory.""" + + FSCTL_DELETE_OBJECT_ID = 0x000900A0 + """Deletes the object identifier for the specified file or directory.""" + + FSCTL_SET_REPARSE_POINT = 0x000900A4 + """Sets a reparse point on a file or directory.""" + + FSCTL_GET_REPARSE_POINT = 0x000900A8 + """Retrieves the reparse point data associated with the file or directory identified by the specified handle.""" + + FSCTL_DELETE_REPARSE_POINT = 0x000900AC + """Deletes a reparse point from the specified file or directory.""" + + FSCTL_ENUM_USN_DATA = 0x000900B0 + """Enumerates the update sequence number (USN) change journal data.""" + + FSCTL_SECURITY_ID_CHECK = 0x000900B4 + """Performs a security ID check.""" + + FSCTL_READ_USN_JOURNAL = 0x000900B8 + """Reads the update sequence number (USN) change journal.""" + + FSCTL_SET_OBJECT_ID_EXTENDED = 0x000900C0 + """Sets extended information for an object identifier. This operation is part of the object identifier management on NTFS volumes.""" + + FSCTL_CREATE_OR_GET_OBJECT_ID = 0x000900C3 + """Retrieves the object identifier for the specified file or directory. If no object identifier exists, this operation creates one.""" + + FSCTL_SET_SPARSE = 0x000900C4 + """Marks the indicated file as sparse or not sparse. In a sparse file, large ranges of zeros may not require disk allocation.""" + + FSCTL_SET_ZERO_DATA = 0x000980C8 + """Fills a specified range of a file with zeros (0).""" + + FSCTL_QUERY_ALLOCATED_RANGES = 0x000940CF + """Scans a file or alternate stream looking for ranges that may contain nonzero data.""" + + FSCTL_SET_ZERO_ON_DEALLOCATION = 0x00090194 + """Indicates an NTFS file system file handle should have its clusters filled with zeros when it is deallocated.""" + + FSCTL_MARK_HANDLE = 0x000900FC + """Marks a specified file or directory and its change journal record with information about changes to that file or directory.""" + + FSCTL_GET_RETRIEVAL_POINTER_BASE = 0x00090098 + """Retrieves the base LCN (Logical Cluster Number) of the allocation unit.""" + + FSCTL_IS_CSV_FILE = 0x0009016B + """Determines whether a file is in a Cluster Shared Volume (CSV).""" + + FSCTL_INITIATE_REPAIR = 0x00090354 + """Triggers the NTFS file system to start a self-healing cycle on a single file.""" + + FSCTL_SHRINK_VOLUME = 0x000902A4 + """Shrinks the volume.""" + + FSCTL_TXFS_MODIFY_RM = 0x00090294 + """Modifies the Resource Manager (RM) for a transactional file system.""" + + FSCTL_TXFS_QUERY_RM_INFORMATION = 0x000902A0 + """Queries Resource Manager (RM) information for a transactional file system.""" + + FSCTL_TXFS_ROLLFORWARD_REDO = 0x000902B4 + """Performs a redo of a transaction during a rollforward operation.""" + + FSCTL_TXFS_ROLLFORWARD_UNDO = 0x000902B8 + """Performs an undo of a transaction during a rollforward operation.""" + + FSCTL_TXFS_START_RM = 0x000902BC + """Starts the Resource Manager (RM) for a transactional file system.""" + + FSCTL_TXFS_SHUTDOWN_RM = 0x000902C0 + """Shuts down the Resource Manager (RM) for a transactional file system.""" + + FSCTL_TXFS_READ_BACKUP_INFORMATION = 0x000902C4 + """Reads backup information for a transactional file system.""" + + FSCTL_TXFS_WRITE_BACKUP_INFORMATION = 0x000902C8 + """Writes backup information for a transactional file system.""" + + FSCTL_TXFS_CREATE_SECONDARY_RM = 0x000902CC + """Creates a secondary Resource Manager (RM) for a transactional file system.""" + + FSCTL_TXFS_GET_METADATA_INFO = 0x000902D0 + """Gets metadata information for a transactional file system.""" + + FSCTL_TXFS_GET_TRANSACTED_VERSION = 0x000902D4 + """Gets the transacted version of a file in a transactional file system.""" + + FSCTL_TXFS_SAVEPOINT_INFORMATION = 0x000902E0 + """Gets savepoint information for a transactional file system.""" + + FSCTL_TXFS_CREATE_MINIVERSION = 0x000902F0 + """Creates a mini version of a file in a transactional file system.""" + + FSCTL_TXFS_TRANSACTION_ACTIVE = 0x00090318 + """Indicates whether a transaction is active in a transactional file system.""" + + FSCTL_RESET_VOLUME_ALLOCATION_HINTS = 0x00090274 + """Resets volume allocation hints.""" + + FSCTL_QUERY_USN_JOURNAL = 0x000900F4 + """Queries for information on the current update sequence number (USN) change journal, its records, and its capacity.""" + + FSCTL_DELETE_USN_JOURNAL = 0x000900F8 + """Deletes the update sequence number (USN) change journal.""" + + FSCTL_READ_FILE_USN_DATA = 0x000900EB + """Retrieves the update sequence number (USN) change-journal information for the specified file or directory.""" + + FSCTL_WRITE_USN_CLOSE_RECORD = 0x000900F0 + """Generates a close record in the USN change journal.""" + + FSCTL_SIS_COPYFILE = 0x00090040 + """Performs a copy operation on a single instance store (SIS) volume.""" + + FSCTL_SIS_LINK_FILES = 0x00090044 + """Links two files on a single instance store (SIS) volume.""" + + FSCTL_GET_QUOTA_INFORMATION = 0x00090040 + """Retrieves quota information for the specified volume.""" + + FSCTL_GET_ENCRYPTION = 0x00090062 + """Retrieves the encryption state of a file or directory on a volume that supports the Encrypting File System (EFS).""" + + FSCTL_SET_ENCRYPTION = 0x000900A7 + """Sets the encryption state of a file or directory on a volume that supports the Encrypting File System (EFS).""" + + FSCTL_SET_USN_JOURNAL = 0x000900F8 + """Creates an update sequence number (USN) change journal stream on a target volume, or modifies an existing change journal stream.""" + + FSCTL_QUERY_LOCKS = 0x00094011 + """Queries for lock information on a file.""" + + FSCTL_QUERY_SPARSE_FILE = 0x00094013 + """Queries whether the file is a sparse file.""" + + FSCTL_SET_DEFECT_MANAGEMENT = 0x00090088 + """Disables or enables the defect management feature for the specified file. This feature is typically used for CD/DVD/Blu-ray media.""" + + FSCTL_GET_OBJECT_ID_EXTENDED = 0x000900DB + """Retrieves extended information for an object identifier.""" + + FSCTL_DISABLE_LOCAL_BUFFERING = 0x0009008D + """Disables local buffering for a file.""" + + # Windows Vista + FSCTL_GET_REPAIR = 0x00090274 + """Retrieves information about the NTFS file system's self-healing mechanism.""" + + FSCTL_GET_RETRIEVAL_POINTERS_AND_REFCOUNT = 0x00090173 + """Retrieves a data structure that describes the allocation and reference count information for the specified file.""" + + FSCTL_SET_SHORT_NAME_BEHAVIOR = 0x000980A4 + """Sets the short name behavior for a specified volume.""" + + FSCTL_QUERY_ON_DISK_VOLUME_INFO = 0x0009013C + """Queries information about the on-disk format of a volume.""" + + FSCTL_SET_PURGE_FAILURE_MODE = 0x000980A8 + """Sets the purge failure mode for a specified volume.""" + + # Windows 7 + FSCTL_DUPLICATE_EXTENTS_TO_FILE = 0x00098344 + """Instructs the file system to copy a range of file bytes on behalf of an application.""" + + FSCTL_FILE_LEVEL_TRIM = 0x00098208 + """Indicates ranges within the specified file that do not need to be stored by the storage system.""" + + FSCTL_GET_INTEGRITY_INFORMATION = 0x0009027C + """Retrieves the integrity status of a file or directory on a ReFS volume.""" + + FSCTL_GET_BOOT_AREA_INFO = 0x0009018C + """Retrieves information about the boot area of a volume.""" + + # Windows 8 + FSCTL_QUERY_PAGEFILE_ENCRYPTION = 0x0009019E + """Queries the encryption state of a page file.""" + + FSCTL_QUERY_STORAGE_CLASSES = 0x000902A0 + """Retrieves the storage tiers defined for a volume that supports data tiering.""" + + FSCTL_CSV_QUERY_VETO_FILE_DIRECT_IO = 0x000902D1 + """Determines whether direct I/O is vetoed for a file in a Cluster Shared Volume (CSV).""" + + FSCTL_SET_INTEGRITY_INFORMATION = 0x0009C280 + """Sets the integrity status of a file or directory on a ReFS volume.""" + + FSCTL_QUERY_REGION_INFO = 0x00090314 + """Retrieves storage tier regions defined for a volume that supports data tiering. This function returns detailed information about the regions in the tiered storage volume.""" # noqa: E501 + + FSCTL_QUERY_FILE_SYSTEM_RECOGNITION = 0x00090318 + """Queries for file system recognition information on a volume.""" + + FSCTL_GET_REFS_VOLUME_DATA = 0x00090364 + """Retrieves information about a ReFS volume.""" + + FSCTL_INTEGRITY_CHECKPOINT = 0x00090394 + """Provides a mechanism to trigger an integrity checkpoint on a file or volume.""" + + FSCTL_SCRUB_DATA = 0x0009035C + """Scrubs the data on the volume to detect and potentially repair data corruption.""" + + FSCTL_REPAIR_COPIES = 0x00090384 + """Repairs copies of files that are corrupt.""" + + # Windows 10 + FSCTL_LOOKUP_STREAM_FROM_CLUSTER = 0x000902B1 + """Given a handle to a NTFS volume or a file on a NTFS volume, returns a chain of data structures that describes streams that occupy the specified clusters.""" + + FSCTL_FILESYSTEM_GET_STATISTICS_EX = 0x00090334 + """Retrieves the information from various file system performance counters.""" + + FSCTL_SET_REFS_FILE_STRICTLY_SEQUENTIAL = 0x000903DC + """Sets the file as strictly sequential on ReFS volumes.""" + + FSCTL_QUERY_USN_JOURNAL_EX = 0x000903F4 + """Provides extended information about the USN change journal.""" + + # Unknown + FSCTL_MAKE_MEDIA_COMPATIBLE = 0x0009804C + FSCTL_QUERY_SPARING_INFO = 0x00098054 + FSCTL_SET_VOLUME_COMPRESSION_STATE = 0x00098060 + FSCTL_TXFS_READ_BACKUP_INFORMATION2 = 0x000980A0 + FSCTL_TXFS_WRITE_BACKUP_INFORMATION2 = 0x000980A4 + FSCTL_FILE_TYPE_NOTIFICATION = 0x000980A8 + FSCTL_CSV_GET_VOLUME_PATH_NAME = 0x000980AC + FSCTL_CSV_GET_VOLUME_NAME_FOR_VOLUME_MOUNT_POINT = 0x000980B0 + FSCTL_CSV_GET_VOLUME_PATH_NAMES_FOR_VOLUME_NAME = 0x000980B4 + FSCTL_IS_FILE_ON_CSV_VOLUME = 0x000980B8 + FSCTL_QUERY_DEPENDENT_VOLUME = 0x000980BC + FSCTL_SD_GLOBAL_CHANGE = 0x000980C0 + FSCTL_SET_PERSISTENT_VOLUME_STATE = 0x000980D0 + FSCTL_QUERY_PERSISTENT_VOLUME_STATE = 0x000980D4 + FSCTL_REQUEST_OPLOCK = 0x000980D8 + FSCTL_CSV_TUNNEL_REQUEST = 0x000980DC + FSCTL_ENABLE_UPGRADE = 0x00090034 + FSCTL_FILE_PREFETCH = 0x000900E4 + +class IOCTL(IntEnum): + # Storage IOCTL codes + STORAGE_CHECK_VERIFY = 0x002D1400 + """Checks if the media is accessible.""" + + STORAGE_CHECK_VERIFY2 = 0x002D1400 + """Verifies the media accessibility.""" + + STORAGE_MEDIA_REMOVAL = 0x002D1401 + """Prevents or allows the removal of the media.""" + + STORAGE_EJECT_MEDIA = 0x002D1402 + """Ejects the media from the device.""" + + STORAGE_LOAD_MEDIA = 0x002D1403 + """Loads the media into the device.""" + + STORAGE_LOAD_MEDIA2 = 0x002D1403 + """Loads the media into the device (alternative).""" + + STORAGE_RESERVE = 0x002D1404 + """Reserves the device for exclusive use.""" + + STORAGE_RELEASE = 0x002D1405 + """Releases the reserved device.""" + + STORAGE_FIND_NEW_DEVICES = 0x002D1406 + """Scans for new storage devices.""" + + STORAGE_EJECTION_CONTROL = 0x002D1450 + """Controls the ejection mechanism of the media.""" + + STORAGE_MCN_CONTROL = 0x002D1451 + """Controls the media change notification feature.""" + + STORAGE_GET_MEDIA_TYPES = 0x002D1500 + """Retrieves the media types supported by the device.""" + + STORAGE_GET_MEDIA_TYPES_EX = 0x002D1501 + """Retrieves extended information about supported media types.""" + + STORAGE_GET_MEDIA_SERIAL_NUMBER = 0x002D1504 + """Retrieves the serial number of the media.""" + + STORAGE_GET_HOTPLUG_INFO = 0x002D1505 + """Retrieves hotplug information for the device.""" + + STORAGE_SET_HOTPLUG_INFO = 0x002D1506 + """Sets hotplug information for the device.""" + + STORAGE_RESET_BUS = 0x002D1600 + """Resets the bus on which the storage device is connected.""" + + STORAGE_RESET_DEVICE = 0x002D1601 + """Resets the storage device.""" + + STORAGE_BREAK_RESERVATION = 0x002D1605 + """Breaks the reservation on a storage device.""" + + STORAGE_PERSISTENT_RESERVE_IN = 0x002D1606 + """Issues a persistent reserve in command to the device.""" + + STORAGE_PERSISTENT_RESERVE_OUT = 0x002D1607 + """Issues a persistent reserve out command to the device.""" + + STORAGE_GET_DEVICE_NUMBER = 0x002D1640 + """Retrieves the device number of the storage device.""" + + STORAGE_PREDICT_FAILURE = 0x002D1680 + """Predicts potential failure of the storage device.""" + + STORAGE_READ_CAPACITY = 0x002D1690 + """Reads the capacity of the storage device.""" + + # Disk IOCTL codes + DISK_GET_DRIVE_GEOMETRY = 0x00070000 + """Retrieves the geometry of the disk drive.""" + + DISK_GET_PARTITION_INFO = 0x00070001 + """Retrieves partition information for the disk.""" + + DISK_SET_PARTITION_INFO = 0x00070002 + """Sets partition information for the disk.""" + + DISK_GET_DRIVE_LAYOUT = 0x00070003 + """Retrieves the drive layout of the disk.""" + + DISK_SET_DRIVE_LAYOUT = 0x00070004 + """Sets the drive layout of the disk.""" + + DISK_VERIFY = 0x00070005 + """Verifies the integrity of a region of the disk.""" + + DISK_FORMAT_TRACKS = 0x00070006 + """Formats the specified tracks on the disk.""" + + DISK_REASSIGN_BLOCKS = 0x00070007 + """Reassigns bad blocks on the disk.""" + + DISK_PERFORMANCE = 0x00070008 + """Retrieves performance data for the disk.""" + + DISK_IS_WRITABLE = 0x00070009 + """Checks if the disk is writable.""" + + DISK_LOGGING = 0x0007000A + """Controls logging on the disk.""" + + DISK_FORMAT_TRACKS_EX = 0x0007000B + """Formats the specified tracks on the disk with extended options.""" + + DISK_HISTOGRAM_STRUCTURE = 0x0007000C + """Retrieves the histogram structure for disk performance analysis.""" + + DISK_HISTOGRAM_DATA = 0x0007000D + """Retrieves histogram data for disk performance analysis.""" + + DISK_HISTOGRAM_RESET = 0x0007000E + """Resets the histogram data on the disk.""" + + DISK_REQUEST_STRUCTURE = 0x0007000F + """Retrieves the request structure for the disk.""" + + DISK_REQUEST_DATA = 0x00070010 + """Retrieves request data for the disk.""" + + DISK_PERFORMANCE_OFF = 0x00070018 + """Disables performance monitoring on the disk.""" + + DISK_CONTROLLER_NUMBER = 0x00070011 + """Retrieves the controller number for the disk.""" + + SMART_GET_VERSION = 0x00070020 + """Retrieves the version of the SMART (Self-Monitoring, Analysis, and Reporting Technology) feature.""" + + SMART_SEND_DRIVE_COMMAND = 0x00070021 + """Sends a command to the drive using the SMART feature.""" + + SMART_RCV_DRIVE_DATA = 0x00070022 + """Receives data from the drive using the SMART feature.""" + + DISK_GET_PARTITION_INFO_EX = 0x00070012 + """Retrieves extended partition information for the disk.""" + + DISK_SET_PARTITION_INFO_EX = 0x00070013 + """Sets extended partition information for the disk.""" + + DISK_GET_DRIVE_LAYOUT_EX = 0x00070014 + """Retrieves extended drive layout information for the disk.""" + + DISK_SET_DRIVE_LAYOUT_EX = 0x00070015 + """Sets extended drive layout information for the disk.""" + + DISK_CREATE_DISK = 0x00070016 + """Creates a new disk layout.""" + + DISK_GET_LENGTH_INFO = 0x00070017 + """Retrieves the length information of the disk.""" + + DISK_GET_DRIVE_GEOMETRY_EX = 0x00070028 + """Retrieves extended geometry information for the disk.""" + + DISK_REASSIGN_BLOCKS_EX = 0x00070029 + """Reassigns bad blocks on the disk with extended options.""" + + DISK_UPDATE_DRIVE_SIZE = 0x00070032 + """Updates the size information of the disk.""" + + DISK_GROW_PARTITION = 0x00070034 + """Grows the partition on the disk.""" + + DISK_GET_CACHE_INFORMATION = 0x00070035 + """Retrieves cache information for the disk.""" + + DISK_SET_CACHE_INFORMATION = 0x00070036 + """Sets cache information for the disk.""" + + OBSOLETE_DISK_GET_WRITE_CACHE_STATE = 0x00070037 + """Obsolete. Previously retrieved the write cache state of the disk.""" + + DISK_DELETE_DRIVE_LAYOUT = 0x00070040 + """Deletes the drive layout on the disk.""" + + DISK_UPDATE_PROPERTIES = 0x00070050 + """Updates the properties of the disk.""" + + DISK_FORMAT_DRIVE = 0x000700F3 + """Formats the entire drive.""" + + DISK_SENSE_DEVICE = 0x000700F8 + """Senses the device status for the disk.""" + + DISK_CHECK_VERIFY = 0x00070200 + """Checks if the disk is accessible.""" + + DISK_MEDIA_REMOVAL = 0x00070201 + """Controls the removal of media from the disk.""" + + DISK_EJECT_MEDIA = 0x00070202 + """Ejects the media from the disk.""" + + DISK_LOAD_MEDIA = 0x00070203 + """Loads media into the disk.""" + + DISK_RESERVE = 0x00070204 + """Reserves the disk for exclusive use.""" + + DISK_RELEASE = 0x00070205 + """Releases the reserved disk.""" + + DISK_FIND_NEW_DEVICES = 0x00070206 + """Scans for new devices connected to the disk.""" + + DISK_GET_MEDIA_TYPES = 0x00070300 + """Retrieves the media types supported by the disk.""" + + DISK_RESET_SNAPSHOT_INFO = 0x00070084 + """Resets snapshot information on the disk.""" + + # Changer IOCTL codes + CHANGER_GET_PARAMETERS = 0x00090000 + """Retrieves the parameters of the media changer device.""" + + CHANGER_GET_STATUS = 0x00090001 + """Retrieves the current status of the media changer device.""" + + CHANGER_GET_PRODUCT_DATA = 0x00090002 + """Retrieves product data for the media changer device.""" + + CHANGER_SET_ACCESS = 0x00090004 + """Sets access control for the media changer device.""" + + CHANGER_GET_ELEMENT_STATUS = 0x00090005 + """Retrieves the status of elements in the media changer.""" + + CHANGER_INITIALIZE_ELEMENT_STATUS = 0x00090006 + """Initializes the status of elements in the media changer.""" + + CHANGER_SET_POSITION = 0x00090007 + """Sets the position of elements within the media changer.""" + + CHANGER_EXCHANGE_MEDIUM = 0x00090008 + """Exchanges media between two slots in the media changer.""" + + CHANGER_MOVE_MEDIUM = 0x00090009 + """Moves media from one slot to another within the media changer.""" + + CHANGER_LOCK_UNLOCK_ELEMENT = 0x0009000A + """Locks or unlocks an element in the media changer.""" + + CHANGER_POSITION_ELEMENT = 0x0009000B + """Positions an element in the media changer.""" + + CHANGER_RESERVE = 0x0009000C + """Reserves the media changer for exclusive use.""" + + CHANGER_RELEASE = 0x0009000D + """Releases the reserved media changer.""" + + CHANGER_EXCHANGE_MEDIUM_IN = 0x0009000E + """Exchanges media in from an external slot to the media changer.""" + + CHANGER_EXCHANGE_MEDIUM_OUT = 0x0009000F + """Exchanges media out from the media changer to an external slot.""" + + CHANGER_GET_ELEMENT_STATUS_IN = 0x00090010 + """Retrieves status information for input elements in the media changer.""" + + CHANGER_GET_ELEMENT_STATUS_OUT = 0x00090011 + """Retrieves status information for output elements in the media changer.""" + + CHANGER_MOVE_MEDIUM_IN = 0x00090012 + """Moves media from an external input slot into the media changer.""" + + CHANGER_MOVE_MEDIUM_OUT = 0x00090013 + """Moves media from the media changer to an external output slot.""" + + CHANGER_PREPARE_ELEMENT_FOR_ACCESS = 0x00090014 + """Prepares an element in the media changer for access.""" + + CHANGER_READ_ELEMENT_STATUS = 0x00090015 + """Reads the status of an element in the media changer.""" + + CHANGER_CALIBRATE = 0x00090016 + """Calibrates the elements of the media changer.""" + + CHANGER_SET_ACCESS_CONTROL = 0x00090017 + """Sets access control permissions for the media changer.""" + + CHANGER_GET_POSITION = 0x00090018 + """Retrieves the current position of elements in the media changer.""" + + CHANGER_GET_PRODUCT_DATA_EX = 0x00090019 + """Retrieves extended product data for the media changer device.""" + + CHANGER_SET_POSITION_EX = 0x0009001A + """Sets the position of elements within the media changer with extended options.""" + + CHANGER_EXCHANGE_MEDIUM_EX = 0x0009001B + """Exchanges media between slots in the media changer with extended options.""" + + CHANGER_LOCK_UNLOCK_ELEMENT_EX = 0x0009001C + """Locks or unlocks an element in the media changer with extended options.""" + + CHANGER_SET_ACCESS_EX = 0x0009001D + """Sets access control permissions for the media changer with extended options.""" + + CHANGER_PREPARE_ELEMENT_FOR_ACCESS_EX = 0x0009001E + """Prepares an element in the media changer for access with extended options.""" + + CHANGER_EXCHANGE_MEDIUM_IN_EX = 0x0009001F + """Exchanges media in from an external slot to the media changer with extended options.""" + + CHANGER_EXCHANGE_MEDIUM_OUT_EX = 0x00090020 + """Exchanges media out from the media changer to an external slot with extended options.""" + + CHANGER_MOVE_MEDIUM_IN_EX = 0x00090021 + """Moves media from an external input slot into the media changer with extended options.""" + + CHANGER_MOVE_MEDIUM_OUT_EX = 0x00090022 + """Moves media from the media changer to an external output slot with extended options.""" + + CHANGER_CALIBRATE_EX = 0x00090023 + """Calibrates the elements of the media changer with extended options.""" + + CHANGER_GET_STATUS_EX = 0x00090024 + """Retrieves the current status of the media changer with extended options.""" + + CHANGER_GET_ELEMENT_STATUS_IN_EX = 0x00090025 + """Retrieves status information for input elements in the media changer with extended options.""" + + CHANGER_GET_ELEMENT_STATUS_OUT_EX = 0x00090026 + """Retrieves status information for output elements in the media changer with extended options.""" + + CHANGER_READ_ELEMENT_STATUS_EX = 0x00090027 + """Reads the status of an element in the media changer with extended options.""" + + CHANGER_REINITIALIZE_TRANSPORT = 0x0009000A + CHANGER_QUERY_VOLUME_TAGS = 0x0009000B + + # Tape IOCTL codes + TAPE_ERASE = 0x00160000 + """Erases the tape media.""" + + TAPE_PREPARE = 0x00160001 + """Prepares the tape drive for an operation.""" + + TAPE_WRITE_MARKS = 0x00160002 + """Writes marks on the tape media.""" + + TAPE_GET_POSITION = 0x00160003 + """Retrieves the current position of the tape media.""" + + TAPE_SET_POSITION = 0x00160004 + """Sets the position of the tape media.""" + + TAPE_GET_DRIVE_PARAMS = 0x00160005 + """Retrieves the parameters of the tape drive.""" + + TAPE_SET_DRIVE_PARAMS = 0x00160006 + """Sets the parameters of the tape drive.""" + + TAPE_GET_MEDIA_PARAMS = 0x00160007 + """Retrieves the parameters of the tape media.""" + + TAPE_SET_MEDIA_PARAMS = 0x00160008 + """Sets the parameters of the tape media.""" + + TAPE_GET_STATUS = 0x00160009 + """Retrieves the current status of the tape drive.""" + + TAPE_GET_MEDIA_TYPES = 0x0016000A + """Retrieves the media types supported by the tape drive.""" + + TAPE_QUERY_DRIVE_PARAMETERS = 0x0016000B + """Queries the drive parameters of the tape drive.""" + + TAPE_QUERY_MEDIA_CAPACITY = 0x0016000C + """Queries the media capacity of the tape media.""" + + TAPE_PREPARE_EX = 0x0016000D + """Prepares the tape drive for an operation with extended options.""" + + TAPE_SET_POSITION_EX = 0x0016000E + """Sets the position of the tape media with extended options.""" + + TAPE_ERASE_EX = 0x0016000F + """Erases the tape media with extended options.""" + + TAPE_SET_DRIVE_PARAMS_EX = 0x00160010 + """Sets the parameters of the tape drive with extended options.""" + + TAPE_SET_MEDIA_PARAMS_EX = 0x00160011 + """Sets the parameters of the tape media with extended options.""" + + TAPE_WRITE_MARKS_EX = 0x00160012 + """Writes marks on the tape media with extended options.""" + + TAPE_GET_DRIVE_PARAMS_EX = 0x00160013 + """Retrieves the parameters of the tape drive with extended options.""" + + TAPE_GET_MEDIA_PARAMS_EX = 0x00160014 + """Retrieves the parameters of the tape media with extended options.""" + + TAPE_GET_POSITION_EX = 0x00160015 + """Retrieves the current position of the tape media with extended options.""" + + TAPE_GET_STATUS_EX = 0x00160016 + """Retrieves the current status of the tape drive with extended options.""" + # USB IOCTL codes + USB_GET_NODE_INFORMATION = 0x00220040 + """Retrieves information about a USB node (hub or port).""" + + USB_GET_NODE_CONNECTION_INFORMATION = 0x00220041 + """Retrieves information about a connection to a USB node.""" + + USB_GET_DESCRIPTOR_FROM_NODE_CONNECTION = 0x00220042 + """Retrieves a descriptor for a USB node connection.""" + + USB_GET_NODE_CONNECTION_NAME = 0x00220044 + """Retrieves the name of a USB node connection.""" + + USB_DIAG_IGNORE_HUBS_ON = 0x00220045 + """Enables diagnostic mode that ignores USB hubs.""" + + USB_DIAG_IGNORE_HUBS_OFF = 0x00220046 + """Disables diagnostic mode that ignores USB hubs.""" + + USB_GET_NODE_CONNECTION_DRIVERKEY_NAME = 0x00220047 + """Retrieves the driver key name for a USB node connection.""" + + USB_GET_HUB_CAPABILITIES = 0x00220048 + """Retrieves the capabilities of a USB hub.""" + + USB_GET_NODE_CONNECTION_ATTRIBUTES = 0x00220049 + """Retrieves the attributes of a USB node connection.""" + + USB_GET_NODE_CONNECTION_INFORMATION_EX = 0x00220050 + """Retrieves extended information about a connection to a USB node.""" + + USB_RESET_HUB = 0x00220051 + """Resets a USB hub.""" + + USB_GET_HUB_CAPABILITIES_EX = 0x00220052 + """Retrieves extended capabilities of a USB hub.""" + + USB_CYCLE_PORT = 0x00220053 + """Cycles the power of a USB port.""" + + USB_GET_PORT_CONNECTOR_PROPERTIES = 0x00220054 + """Retrieves the properties of a USB port connector.""" + + USB_GET_NODE_INFORMATION_EX = 0x00220055 + """Retrieves extended information about a USB node (hub or port).""" + + USB_GET_NODE_CONNECTION_INFORMATION_EX_V2 = 0x00220056 + """Retrieves additional extended information about a USB node connection.""" + + USB_RESET_PORT = 0x00220057 + """Resets a USB port.""" + + USB_HUB_CYCLE_PORT = 0x00220058 + """Cycles the power of a USB hub port.""" + + USB_GET_NODE_CONNECTION_INFORMATION_EX_V2_1 = 0x00220059 + """Retrieves extended information about a USB node connection, version 2.1.""" + + USB_GET_NODE_CONNECTION_DRIVERKEY_NAME_EX = 0x00220060 + """Retrieves the driver key name for a USB node connection with extended options.""" + + USB_GET_DESCRIPTOR_FROM_NODE_CONNECTION_EX = 0x00220061 + """Retrieves a descriptor for a USB node connection with extended options.""" + + USB_GET_NODE_INFORMATION_EX_V2 = 0x00220062 + """Retrieves additional extended information about a USB node, version 2.""" + + USB_GET_NODE_INFORMATION_EX_V2_1 = 0x00220063 + """Retrieves extended information about a USB node, version 2.1.""" + + USB_GET_NODE_CONNECTION_INFORMATION_EX_V2_2 = 0x00220064 + """Retrieves additional extended information about a USB node connection, version 2.2.""" + + USB_GET_NODE_INFORMATION_EX_V3 = 0x00220065 + """Retrieves extended information about a USB node, version 3.""" + + USB_HUB_RESET = 0x00220066 + """Resets a USB hub.""" + + USB_HUB_RESET_EX = 0x00220067 + """Resets a USB hub with extended options.""" + + USB_HUB_GET_NODE_INFORMATION = 0x00220068 + """Retrieves information about a USB hub node.""" + + USB_HUB_GET_NODE_INFORMATION_EX = 0x00220069 + """Retrieves extended information about a USB hub node.""" + + USB_HUB_RESET_PORT = 0x00220070 + """Resets a USB hub port.""" + + USB_HUB_RESET_PORT_EX = 0x00220071 + """Resets a USB hub port with extended options.""" + + USB_GET_NODE_INFORMATION_EX_V3_1 = 0x00220072 + """Retrieves extended information about a USB node, version 3.1.""" + + USB_GET_NODE_CONNECTION_INFORMATION_EX_V3 = 0x00220073 + """Retrieves extended information about a USB node connection, version 3.""" + + USB_GET_NODE_INFORMATION_EX_V3_2 = 0x00220074 + """Retrieves additional extended information about a USB node, version 3.2.""" + + USB_HUB_RESET_EX_V2 = 0x00220075 + """Resets a USB hub with extended options, version 2.""" + + USB_HUB_CYCLE_PORT_EX = 0x00220076 + """Cycles the power of a USB hub port with extended options.""" + + USB_GET_PORT_CONNECTOR_PROPERTIES_EX = 0x00220077 + """Retrieves the properties of a USB port connector with extended options.""" + + USB_GET_NODE_CONNECTION_INFORMATION_EX_V3_3 = 0x00220078 + """Retrieves additional extended information about a USB node connection, version 3.3.""" + + USB_HUB_RESET_PORT_EX_V2 = 0x00220079 + """Resets a USB hub port with extended options, version 2.""" + + # Serial IOCTL codes + SERIAL_LSRMST_INSERT = 0x0001001F + """Inserts Line Status Register (LSR) and Modem Status Register (MST) data into the input stream.""" + + SERENUM_EXPOSE_HARDWARE = 0x00010080 + """Exposes the hardware associated with the serial enumerator.""" + + SERENUM_REMOVE_HARDWARE = 0x00010081 + """Removes the hardware associated with the serial enumerator.""" + + SERENUM_PORT_DESC = 0x00010082 + """Retrieves the port description for the serial enumerator.""" + + SERENUM_GET_PORT_NAME = 0x00010083 + """Retrieves the port name for the serial enumerator.""" + + # AVIO IOCTL codes + AVIO_ALLOCATE_STREAM = 0x00060001 + """Allocates a stream for AVIO.""" + + AVIO_FREE_STREAM = 0x00060002 + """Frees a previously allocated stream for AVIO.""" + + AVIO_MODIFY_STREAM = 0x00060003 + """Modifies settings of a stream allocated for AVIO.""" + + # Volume IOCTL codes + VOLUME_GET_VOLUME_DISK_EXTENTS = 0x00560000 + """Retrieves disk extents for the specified volume.""" + + VOLUME_ONLINE = 0x00560002 + """Brings the specified volume online.""" + + VOLUME_OFFLINE = 0x00560003 + """Takes the specified volume offline.""" + + VOLUME_IS_CLUSTERED = 0x0056000C + """Checks if the specified volume is part of a cluster.""" + + VOLUME_GET_GPT_ATTRIBUTES = 0x0056000E + """Retrieves GPT attributes of the specified volume.""" diff --git a/winforms/src/toga_winforms/libs/py_wrappers/winapi/input_dialogs.py b/winforms/src/toga_winforms/libs/py_wrappers/winapi/input_dialogs.py new file mode 100644 index 0000000000..cd5d76666e --- /dev/null +++ b/winforms/src/toga_winforms/libs/py_wrappers/winapi/input_dialogs.py @@ -0,0 +1,39 @@ +from __future__ import annotations + +import ctypes +from ctypes import wintypes +from typing import TYPE_CHECKING, Sequence + +if TYPE_CHECKING: + from ctypes import _CData + +CHOOSECOLOR = ctypes.windll.comdlg32.ChooseColorW +class COLORREF(ctypes.Structure): + _fields_ = [("rgb", wintypes.DWORD)] + +class CHOOSECOLOR(ctypes.Structure): + _fields_: Sequence[tuple[str, type[_CData]] | tuple[str, type[_CData], int]] = [ + ("lStructSize", wintypes.DWORD), + ("hwndOwner", wintypes.HWND), + ("hInstance", wintypes.HINSTANCE), + ("rgbResult", COLORREF), + ("lpCustColors", ctypes.POINTER(COLORREF)), + ("Flags", wintypes.DWORD), + ("lCustData", wintypes.LPARAM), + ("lpfnHook", wintypes.LPVOID), + ("lpTemplateName", wintypes.LPCWSTR) + ] + +def color_picker(): + cc = CHOOSECOLOR() + cc.lStructSize = ctypes.sizeof(CHOOSECOLOR) + cc.Flags = 0x00000100 # CC_RGBINIT + custom_colors = (COLORREF * 16)() + cc.lpCustColors = custom_colors + cc.rgbResult = COLORREF() + if CHOOSECOLOR(ctypes.byref(cc)): + return (cc.rgbResult.rgb & 0xFF, (cc.rgbResult.rgb >> 8) & 0xFF, (cc.rgbResult.rgb >> 16) & 0xFF) + return None + +color = color_picker() +print(f"Selected color: {color}") diff --git a/winforms/src/toga_winforms/libs/py_wrappers/winapi/messagebox.py b/winforms/src/toga_winforms/libs/py_wrappers/winapi/messagebox.py new file mode 100644 index 0000000000..83bd81479f --- /dev/null +++ b/winforms/src/toga_winforms/libs/py_wrappers/winapi/messagebox.py @@ -0,0 +1,95 @@ +from __future__ import annotations + +from ctypes import POINTER, WINFUNCTYPE, Structure, c_int, c_wchar_p, windll +from ctypes.wintypes import ATOM, BOOL, DWORD, HBRUSH, HICON, HINSTANCE, HMENU, HWND, LPARAM, LPCWSTR, LPVOID, UINT, WPARAM + +from toga_winforms.libs.win_wrappers.common import LRESULT +from toga_winforms.libs.win_wrappers.hwnd import HCURSOR + + +def windows_message_box( # noqa: PLR0913 + message: str, + title: str, + box_type: int = MB_OK, + icon: int = 0, + default_button: int = MB_DEFBUTTON1, + modality: int = MB_APPLMODAL, + options: int = 0, + detailed_text: str | None = None, +) -> int: + """Create a message box with specified parameters. + + :param message: The message to be displayed. + :param title: The title of the message box. + :param box_type: The type of the message box (e.g., MB_OK, MB_YESNO). + :param icon: The icon to be displayed (e.g., MB_ICONQUESTION). + :param default_button: The default button (e.g., MB_DEFBUTTON1). + :param modality: The modality of the message box (e.g., MB_APPLMODAL). + :param options: Additional options (e.g., MB_RIGHT). + :param detailed_text: Additional detailed text to be displayed. + :return: The response from the message box. + """ + if detailed_text: + message = f"{message}\n\n{detailed_text}" + + style = box_type | icon | default_button | modality | options + result = windll.user32.MessageBoxW(0, message, title, style) + return result + + +def show_ok_message_box(message: str, title: str, detailed_text: str | None = None) -> int: + return windows_message_box(message, title, MB_OK, MB_ICONINFORMATION, detailed_text=detailed_text) + + +def show_yes_no_message_box(message: str, title: str, detailed_text: str | None = None) -> int: + return windows_message_box(message, title, MB_YESNO, MB_ICONQUESTION, detailed_text=detailed_text) + + +def show_retry_cancel_message_box(message: str, title: str, detailed_text: str | None = None) -> int: + return windows_message_box(message, title, MB_RETRYCANCEL, MB_ICONWARNING, detailed_text=detailed_text) + + +def show_error_message_box(message: str, title: str, detailed_text: str | None = None) -> int: + return windows_message_box(message, title, MB_OK, MB_ICONERROR, detailed_text=detailed_text) + + +def show_warning_message_box(message: str, title: str, detailed_text: str | None = None) -> int: + return windows_message_box(message, title, MB_OK, MB_ICONWARNING, detailed_text=detailed_text) + + +if __name__ == "__main__": + response = windows_message_box( + "Do you want to save changes?", + "Save Changes", + box_type=MB_YESNOCANCEL, + icon=MB_ICONQUESTION, + default_button=MB_DEFBUTTON2, + modality=MB_SYSTEMMODAL, + options=MB_RTLREADING, + detailed_text="This will overwrite the existing file.", + ) + print(f"Response: {response}") # 6=YES, 7=NO, 2=CANCEL/X button. + + response = show_ok_message_box("Operation completed successfully.", "Success") + if response == IDOK: + print("User clicked OK") + + response = show_yes_no_message_box("Do you want to continue?", "Continue?", detailed_text="This action cannot be undone.") + if response == IDYES: + print("User chose Yes") + elif response == IDNO: + print("User chose No") + + response = show_retry_cancel_message_box("Operation failed. Retry?", "Error", detailed_text="Make sure the file is accessible and try again.") + if response == IDRETRY: + print("User chose Retry") + elif response == IDCANCEL: + print("User chose Cancel") + + response = show_error_message_box("An unexpected error occurred.", "Error", detailed_text="Please contact support with the error code 0x1234.") + if response == IDOK: + print("User acknowledged the error") + + response = show_warning_message_box("This action may cause data loss.", "Warning", detailed_text="Ensure you have backed up your data.") + if response == IDOK: + print("User acknowledged the warning") diff --git a/winforms/src/toga_winforms/libs/py_wrappers/winapi/sounds.py b/winforms/src/toga_winforms/libs/py_wrappers/winapi/sounds.py new file mode 100644 index 0000000000..b04b725185 --- /dev/null +++ b/winforms/src/toga_winforms/libs/py_wrappers/winapi/sounds.py @@ -0,0 +1,12 @@ +from __future__ import annotations + +import ctypes + +MB_ICONASTERISK = 0x40 + +def play_system_sound(sound_type): + ctypes.windll.user32.MessageBeep(sound_type) + + +if __name__ == "__main__": + play_system_sound(0x40) # MB_ICONASTERISK \ No newline at end of file diff --git a/winforms/src/toga_winforms/libs/py_wrappers/winapi/tooltip.py b/winforms/src/toga_winforms/libs/py_wrappers/winapi/tooltip.py new file mode 100644 index 0000000000..3a743610d0 --- /dev/null +++ b/winforms/src/toga_winforms/libs/py_wrappers/winapi/tooltip.py @@ -0,0 +1,37 @@ +from __future__ import annotations + +from ctypes import POINTER, Structure, byref, sizeof, windll +from ctypes.wintypes import HINSTANCE, HWND, LPARAM, LPWSTR, RECT, UINT +from typing import TYPE_CHECKING, Sequence + +if TYPE_CHECKING: + from ctypes import _CData + + +class TOOLINFO(Structure): + _fields_: Sequence[tuple[str, type[_CData]] | tuple[str, type[_CData], int]] = [ + ("cbSize", UINT), + ("uFlags", UINT), + ("hwnd", HWND), + ("uId", POINTER(UINT)), + ("rect", RECT), + ("hinst", HINSTANCE), + ("lpszText", LPWSTR), + ("lParam", LPARAM) + ] + + +def create_tooltip(hwnd, text): + TTS_ALWAYSTIP = 0x01 + TTM_ADDTOOL = 0x0400 + 50 + hwndTT = windll.user32.CreateWindowExW( + 0, "tooltips_class32", None, TTS_ALWAYSTIP, + 0, 0, 0, 0, hwnd, None, None, None + ) + ti = TOOLINFO(cbSize=sizeof(TOOLINFO), hwnd=hwnd, lpszText=text) + windll.user32.SendMessageW(hwndTT, TTM_ADDTOOL, 0, byref(ti)) + return hwndTT + +# Example usage: +# hwnd = some_window_handle +# create_tooltip(hwnd, "This is a tooltip") From 61a398589958575c0a17d9e44c8458ffee778a19 Mon Sep 17 00:00:00 2001 From: Benjamin Auquite Date: Fri, 30 Aug 2024 01:15:03 -0500 Subject: [PATCH 3/9] Delete winforms/src/toga_winforms/libs/py_wrappers/com/windialogs copy.py --- .../libs/py_wrappers/com/windialogs copy.py | 826 ------------------ 1 file changed, 826 deletions(-) delete mode 100644 winforms/src/toga_winforms/libs/py_wrappers/com/windialogs copy.py diff --git a/winforms/src/toga_winforms/libs/py_wrappers/com/windialogs copy.py b/winforms/src/toga_winforms/libs/py_wrappers/com/windialogs copy.py deleted file mode 100644 index 5324e0e501..0000000000 --- a/winforms/src/toga_winforms/libs/py_wrappers/com/windialogs copy.py +++ /dev/null @@ -1,826 +0,0 @@ -from __future__ import annotations - -import errno -import json -import os -import random -from ctypes import ( - HRESULT, - POINTER, - Structure, - WinError, - byref, - c_int, - c_uint, - c_ulong, - c_void_p, - c_wchar_p, - windll, -) -from ctypes import cast as cast_with_ctypes -from ctypes.wintypes import BOOL, DWORD, HWND, LPCWSTR, LPWSTR -from enum import IntFlag -from pathlib import WindowsPath -from typing import TYPE_CHECKING, Callable, ClassVar, Sequence - -import comtypes # pyright: ignore[reportMissingTypeStubs] -import comtypes.client # pyright: ignore[reportMissingTypeStubs] -from comtypes import COMMETHOD, GUID -from comtypes.hresult import S_OK - -if TYPE_CHECKING: - from ctypes import Array, _CData, _Pointer - from ctypes.wintypes import ULONG - - from comtypes._memberspec import _ComMemberSpec - - -class COMDLG_FILTERSPEC(Structure): # noqa: N801 - _fields_: Sequence[tuple[str, type[_CData]] | tuple[str, type[_CData], int]] = [ - ("pszName", LPCWSTR), - ("pszSpec", LPCWSTR) - ] - - -class FileOpenOptions(IntFlag): - FOS_OVERWRITEPROMPT = 0x00000002 - FOS_STRICTFILETYPES = 0x00000004 - FOS_NOCHANGEDIR = 0x00000008 - FOS_PICKFOLDERS = 0x00000020 - FOS_FORCEFILESYSTEM = 0x00000040 - FOS_ALLNONSTORAGEITEMS = 0x00000080 - FOS_NOVALIDATE = 0x00000100 - FOS_ALLOWMULTISELECT = 0x00000200 - FOS_PATHMUSTEXIST = 0x00000800 - FOS_FILEMUSTEXIST = 0x00001000 - FOS_CREATEPROMPT = 0x00002000 - FOS_SHAREAWARE = 0x00004000 - FOS_NOREADONLYRETURN = 0x00008000 - FOS_NOTESTFILECREATE = 0x00010000 - FOS_HIDEMRUPLACES = 0x00020000 - FOS_HIDEPINNEDPLACES = 0x00040000 - FOS_NODEREFERENCELINKS = 0x00100000 - FOS_DONTADDTORECENT = 0x02000000 - FOS_FORCESHOWHIDDEN = 0x10000000 - FOS_DEFAULTNOMINIMODE = 0x20000000 - FOS_FORCEPREVIEWPANEON = 0x40000000 - - - -IID_IShellItem = GUID("{43826D1E-E718-42EE-BC55-A1E261C37BFE}") -IID_IShellItemArray = GUID("{B63EA76D-1F85-456F-A19C-48159EFA858B}") -IID_IShellItemFilter = GUID("{2659B475-EEB8-48B7-8F07-B378810F48CF}") -IID_IModalWindow = GUID("{B4DB1657-70D7-485E-8E3E-6FCB5A5C1802}") -IID_IFileDialog = GUID("{42F85136-DB7E-439C-85F1-E4075D135FC8}") -IID_IFileOpenDialog = GUID("{D57C7288-D4AD-4768-BE02-9D969532D960}") -IID_IFileSaveDialog = GUID("{84BCCD23-5FDE-4CDB-AEA4-AF64B83D78AB}") -CLSID_FileOpenDialog = GUID("{DC1C5A9C-E88A-4dde-A5A1-60F82A20AEF7}") -CLSID_FileSaveDialog = GUID("{C0B4E2F3-BA21-4773-8DBA-335EC946EB8B}") - - -class IShellItem(comtypes.IUnknown): - _case_insensitive_: bool = True - _iid_: GUID = IID_IShellItem - _methods_: ClassVar[list[_ComMemberSpec]] = [ - COMMETHOD([], HRESULT, "BindToHandler", - (["in"], POINTER(comtypes.IUnknown), "pbc"), - (["in"], POINTER(GUID), "bhid"), - (["in"], POINTER(GUID), "riid"), - (["out"], POINTER(c_void_p), "ppv")), - COMMETHOD([], HRESULT, "GetParent", - (["out"], POINTER(POINTER(comtypes.IUnknown)), "ppsi")), - COMMETHOD([], HRESULT, "GetDisplayName", - (["in"], c_ulong, "sigdnName"), - (["out"], POINTER(LPWSTR), "ppszName")), - COMMETHOD([], HRESULT, "GetAttributes", - (["in"], c_ulong, "sfgaoMask"), - (["out"], POINTER(c_ulong), "psfgaoAttribs")), - COMMETHOD([], HRESULT, "Compare", - (["in"], POINTER(comtypes.IUnknown), "psi"), - (["in"], c_ulong, "hint"), - (["out"], POINTER(c_int), "piOrder")) - ] - QueryInterface: Callable[[GUID, comtypes.IUnknown], int] - AddRef: Callable[[], ULONG] - Release: Callable[[], ULONG] - BindToHandler: Callable[[comtypes.IUnknown, GUID, GUID, _Pointer[c_void_p]], int] - GetParent: Callable[[], comtypes.IUnknown] - GetDisplayName: Callable[[c_ulong | int], str] - GetAttributes: Callable[[c_ulong | int], int] - Compare: Callable[[comtypes.IUnknown, c_ulong, c_int], int] - - -SHCreateItemFromParsingName = windll.shell32.SHCreateItemFromParsingName -SHCreateItemFromParsingName.argtypes = [ - c_wchar_p, # LPCWSTR (wide string, null-terminated) - POINTER(comtypes.IUnknown), # IBindCtx* (can be NULL, hence POINTER(IUnknown)) - POINTER(GUID), # REFIID (pointer to the interface ID, typically GUID) - POINTER(POINTER(IShellItem)) # void** (output pointer to the requested interface) -] -SHCreateItemFromParsingName.restype = HRESULT - - -class IShellItemArray(comtypes.IUnknown): - _case_insensitive_: bool = True - _iid_: GUID = IID_IShellItemArray - _methods_: ClassVar[list[_ComMemberSpec]] = [ - COMMETHOD([], HRESULT, "BindToHandler", - (["in"], POINTER(comtypes.IUnknown), "pbc"), - (["in"], POINTER(GUID), "bhid"), - (["in"], POINTER(GUID), "riid"), - (["out"], POINTER(c_void_p), "ppv")), - COMMETHOD([], HRESULT, "GetPropertyStore", - (["in"], c_ulong, "flags"), - (["in"], POINTER(GUID), "riid"), - (["out"], POINTER(c_void_p), "ppv")), - COMMETHOD([], HRESULT, "GetPropertyDescriptionList", - (["in"], POINTER(GUID), "keyType"), - (["in"], POINTER(GUID), "riid"), - (["out"], POINTER(c_void_p), "ppv")), - COMMETHOD([], HRESULT, "GetAttributes", - (["in"], c_ulong, "attribFlags"), - (["in"], c_ulong, "sfgaoMask"), - (["out"], POINTER(c_ulong), "psfgaoAttribs")), - COMMETHOD([], HRESULT, "GetCount", - (["out"], POINTER(c_uint), "pdwNumItems")), - COMMETHOD([], HRESULT, "GetItemAt", - (["in"], c_uint, "dwIndex"), - (["out"], POINTER(POINTER(IShellItem)), "ppsi")), - COMMETHOD([], HRESULT, "EnumItems", - (["out"], POINTER(POINTER(comtypes.IUnknown)), "ppenumShellItems")) - ] - QueryInterface: Callable[[GUID, comtypes.IUnknown], int] - AddRef: Callable[[], ULONG] - Release: Callable[[], ULONG] - BindToHandler: Callable[[_Pointer[comtypes.IUnknown], GUID, GUID], int] - GetPropertyStore: Callable[[c_ulong, GUID], c_void_p] - GetPropertyDescriptionList: Callable[[GUID, GUID], c_void_p] - GetAttributes: Callable[[c_ulong, c_ulong], _Pointer[c_ulong]] - GetCount: Callable[[], int] - GetItemAt: Callable[[c_uint | int], IShellItem] - EnumItems: Callable[[], comtypes.IUnknown] - - -class IModalWindow(comtypes.IUnknown): - _case_insensitive_: bool = True - _iid_: GUID = IID_IModalWindow - _methods_: ClassVar[list[_ComMemberSpec]] = [ - COMMETHOD([], HRESULT, "Show", - (["in"], HWND, "hwndParent")) - ] - Show: Callable[[int | HWND], int] - - -class IFileDialog(IModalWindow): - _iid_: GUID = IID_IFileDialog - _methods_: ClassVar[list[_ComMemberSpec]] = [ - COMMETHOD([], HRESULT, "SetFileTypes", - (["in"], c_uint, "cFileTypes"), - (["in"], POINTER(c_void_p), "rgFilterSpec")), - COMMETHOD([], HRESULT, "SetFileTypeIndex", - (["in"], c_uint, "iFileType")), - COMMETHOD([], HRESULT, "GetFileTypeIndex", - (["out"], POINTER(c_uint), "piFileType")), - COMMETHOD([], HRESULT, "Advise", - (["in"], POINTER(comtypes.IUnknown), "pfde"), - (["out"], POINTER(DWORD), "pdwCookie")), - COMMETHOD([], HRESULT, "Unadvise", - (["in"], DWORD, "dwCookie")), - COMMETHOD([], HRESULT, "SetOptions", - (["in"], c_uint, "fos")), - COMMETHOD([], HRESULT, "GetOptions", - (["out"], POINTER(DWORD), "pfos")), - COMMETHOD([], HRESULT, "SetDefaultFolder", - (["in"], POINTER(IShellItem), "psi")), - COMMETHOD([], HRESULT, "SetFolder", - (["in"], POINTER(IShellItem), "psi")), - COMMETHOD([], HRESULT, "GetFolder", - (["out"], POINTER(POINTER(IShellItem)), "ppsi")), - COMMETHOD([], HRESULT, "GetCurrentSelection", - (["out"], POINTER(POINTER(IShellItem)), "ppsi")), - COMMETHOD([], HRESULT, "SetFileName", - (["in"], LPCWSTR, "pszName")), - COMMETHOD([], HRESULT, "GetFileName", - (["out"], POINTER(LPWSTR), "pszName")), - COMMETHOD([], HRESULT, "SetTitle", - (["in"], LPCWSTR, "pszTitle")), - COMMETHOD([], HRESULT, "SetOkButtonLabel", - (["in"], LPCWSTR, "pszText")), - COMMETHOD([], HRESULT, "SetFileNameLabel", - (["in"], LPCWSTR, "pszLabel")), - COMMETHOD([], HRESULT, "GetResult", - (["out"], POINTER(POINTER(IShellItem)), "ppsi")), - COMMETHOD([], HRESULT, "AddPlace", - (["in"], POINTER(IShellItem), "psi"), - (["in"], c_int, "fdap")), - COMMETHOD([], HRESULT, "SetDefaultExtension", - (["in"], LPCWSTR, "pszDefaultExtension")), - COMMETHOD([], HRESULT, "Close", - (["in"], HRESULT, "hr")), - COMMETHOD([], HRESULT, "SetClientGuid", - (["in"], POINTER(GUID), "guid")), - COMMETHOD([], HRESULT, "ClearClientData"), - COMMETHOD([], HRESULT, "SetFilter", - (["in"], POINTER(comtypes.IUnknown), "pFilter")) # IShellItemFilter - ] - SetFileTypes: Callable[[c_uint | int, _Pointer[c_void_p]], int] - SetFileTypeIndex: Callable[[c_uint], int] - GetFileTypeIndex: Callable[[], _Pointer[c_uint]] - Advise: Callable[[comtypes.IUnknown | comtypes.COMObject], int] - Unadvise: Callable[[int], int] - SetOptions: Callable[[DWORD | int], int] - GetOptions: Callable[[], int] - SetDefaultFolder: Callable[[_Pointer[IShellItem]], int] - SetFolder: Callable[[_Pointer[IShellItem]], int] - GetFolder: Callable[[], IShellItem] - GetCurrentSelection: Callable[[], IShellItem] - SetFileName: Callable[[str], int] - GetFileName: Callable[[], _Pointer[LPWSTR]] - SetTitle: Callable[[str], int] - SetOkButtonLabel: Callable[[str], int] - SetFileNameLabel: Callable[[str], int] - GetResult: Callable[[], IShellItem] - AddPlace: Callable[[IShellItem, c_int], int] - SetDefaultExtension: Callable[[str], int] - Close: Callable[[HRESULT], int] - SetClientGuid: Callable[[GUID], int] - ClearClientData: Callable[[], int] - SetFilter: Callable[[comtypes.IUnknown], int] - - -class IFileOpenDialog(IFileDialog): - _case_insensitive_: bool = True - _iid_: GUID = IID_IFileOpenDialog - _methods_: ClassVar[list[_ComMemberSpec]] = [ - COMMETHOD([], HRESULT, "GetResults", - (["out"], POINTER(POINTER(IShellItemArray)), "ppenum")), - COMMETHOD([], HRESULT, "GetSelectedItems", - (["out"], POINTER(POINTER(IShellItemArray)), "ppsai")) - ] - GetResults: Callable[[], IShellItemArray] - GetSelectedItems: Callable[[], IShellItemArray] - - -class IFileSaveDialog(IFileDialog): - _case_insensitive_: bool = True - _iid_: GUID = IID_IFileSaveDialog - _methods_: ClassVar[list[_ComMemberSpec]] = [ - COMMETHOD([], HRESULT, "SetSaveAsItem", - (["in"], POINTER(IShellItem), "psi")), - COMMETHOD([], HRESULT, "SetProperties", - (["in"], POINTER(comtypes.IUnknown), "pStore")), - COMMETHOD([], HRESULT, "SetCollectedProperties", - (["in"], POINTER(comtypes.IUnknown), "pList"), - (["in"], BOOL, "fAppendDefault")), - COMMETHOD([], HRESULT, "GetProperties", - (["out"], POINTER(POINTER(comtypes.IUnknown)), "ppStore")), - COMMETHOD([], HRESULT, "ApplyProperties", - (["in"], POINTER(IShellItem), "psi"), - (["in"], POINTER(comtypes.IUnknown), "pStore"), - (["in"], HWND, "hwnd"), - (["in"], POINTER(comtypes.IUnknown), "pSink")) - ] - SetSaveAsItem: Callable[[IShellItem], int] - SetProperties: Callable[[comtypes.IUnknown], int] - SetCollectedProperties: Callable[[comtypes.IUnknown, BOOL], int] - GetProperties: Callable[[comtypes.IUnknown], int] - ApplyProperties: Callable[[IShellItem, comtypes.IUnknown, HWND, comtypes.IUnknown], int] - - -def show_file_dialog( - fileDialog: IFileOpenDialog | IFileSaveDialog, # noqa: N803 - hwndOwner: HWND, # noqa: N803 -) -> bool: - """Shows the IFileDialog. Returns True if the user progressed to the end and found a file. False if they cancelled.""" - hr: HRESULT | int = -1 - CANCELLED_BY_USER = -2147023673 - - try: - hr = fileDialog.Show(hwndOwner) - except OSError as e: - if e.winerror == CANCELLED_BY_USER: - return False - raise - else: - if hr: - raise WinError(hr, "An unexpected error occurred showing the file browser dialog") - return True - - -def create_shell_item(path: str) -> _Pointer[IShellItem]: # noqa: N803, ARG001 - shell_item = POINTER(IShellItem)() - hr = SHCreateItemFromParsingName(path, None, IShellItem._iid_, byref(shell_item)) - if hr != S_OK: - raise WinError(hr, f"Failed to create shell item from path: {path}") - return shell_item - - -DEFAULT_FILTERS: list[COMDLG_FILTERSPEC] = [ - COMDLG_FILTERSPEC("All Files", "*.*"), - COMDLG_FILTERSPEC("Text Files", "*.txt"), - COMDLG_FILTERSPEC("Image Files", "*.png;*.jpg;*.jpeg;*.bmp;*.gif"), - COMDLG_FILTERSPEC("Document Files", "*.doc;*.docx;*.pdf;*.xls;*.xlsx"), - COMDLG_FILTERSPEC("Audio Files", "*.mp3;*.wav;*.wma;*.aac"), - COMDLG_FILTERSPEC("Video Files", "*.mp4;*.avi;*.mkv;*.mov;*.wmv"), - COMDLG_FILTERSPEC("Archive Files", "*.zip;*.rar;*.7z;*.tar;*.gz"), -] - - -def configure_file_dialog( # noqa: PLR0913, PLR0912, C901, PLR0915 - file_dialog: IFileSaveDialog | IFileOpenDialog, - title: str | None = None, - options: int = 0, - default_folder: str | None = None, - ok_button_label: str | None = None, - file_name_label: str | None = None, - file_types: list[tuple[str, str]] | None = None, - default_extension: str | None = None, - hwnd: HWND | int | None = None, -) -> list[str] | None: # sourcery skip: low-code-quality - hwnd = HWND(hwnd) if isinstance(hwnd, int) else hwnd - hwnd = HWND(0) if hwnd is None else hwnd - if default_folder: - default_folder_path = WindowsPath(default_folder).resolve() - if not default_folder_path.exists() or not default_folder_path.is_dir(): - raise FileNotFoundError(errno.ENOENT, os.strerror(errno.ENOENT), str(default_folder_path)) - shell_item = create_shell_item(str(default_folder_path)) - file_dialog.SetFolder(shell_item) - file_dialog.SetDefaultFolder(shell_item) - - # Resolve contradictory options - if options & FileOpenOptions.FOS_ALLNONSTORAGEITEMS: - if options & FileOpenOptions.FOS_FORCEFILESYSTEM: - options &= ~FileOpenOptions.FOS_FORCEFILESYSTEM - if options & FileOpenOptions.FOS_PICKFOLDERS: - options &= ~FileOpenOptions.FOS_PICKFOLDERS - - file_dialog.SetOptions(options) - - if not options & FileOpenOptions.FOS_PICKFOLDERS: - filters: Array[COMDLG_FILTERSPEC] - if file_types: - filters = (COMDLG_FILTERSPEC * len(file_types))( - *[ - (c_wchar_p(name), c_wchar_p(spec)) - for name, spec in file_types - ] - ) - else: - filters = (COMDLG_FILTERSPEC * len(DEFAULT_FILTERS))(*DEFAULT_FILTERS) - file_dialog.SetFileTypes(len(filters), cast_with_ctypes(filters, POINTER(c_void_p))) - - if title: - file_dialog.SetTitle(title) - - if ok_button_label: - file_dialog.SetOkButtonLabel(ok_button_label) - elif isinstance(file_dialog, IFileSaveDialog): - file_dialog.SetOkButtonLabel("Save") - elif options & FileOpenOptions.FOS_PICKFOLDERS: - file_dialog.SetOkButtonLabel("Select Folder") - else: - file_dialog.SetOkButtonLabel("Select File") - - if file_name_label: - file_dialog.SetFileNameLabel(file_name_label) - if default_extension: - file_dialog.SetDefaultExtension(default_extension) - - if show_file_dialog(file_dialog, hwnd): - return ( - [get_save_file_dialog_results(file_dialog)] - if isinstance(file_dialog, IFileSaveDialog) - else get_open_file_dialog_results(file_dialog) - ) - return None - - -def open_file_dialog( # noqa: C901, PLR0913, PLR0912 - title: str | None = "Open File", - default_folder: str | None = None, - file_types: list[tuple[str, str]] | None = None, - default_extension: str | None = None, - *, - overwrite_prompt: bool = False, - strict_file_types: bool = False, - no_change_dir: bool = True, - force_filesystem: bool = True, - all_non_storage_items: bool = False, - no_validate: bool = False, - allow_multiple_selection: bool = False, - path_must_exist: bool = True, - file_must_exist: bool = True, - create_prompt: bool = False, - share_aware: bool = False, - no_readonly_return: bool = False, - no_test_file_create: bool = False, - hide_mru_places: bool = False, - hide_pinned_places: bool = False, - no_dereference_links: bool = False, - add_to_recent: bool = True, - show_hidden_files: bool = False, - default_no_minimode: bool = False, - force_preview_pane_on: bool = False, - ok_button_text: str | None = None, -) -> list[str] | None: # sourcery skip: low-code-quality - """Opens a file dialog to select files. - - Args: - title (str | None): The title of the dialog. - default_folder (str | None): The initial folder to open. - file_types (list[tuple[str, str]] | None): A list of file type filters. - default_extension (str | None): The default file extension. - overwrite_prompt (bool): Prompts if the selected file already exists. FileOpenOptions.FOS_OVERWRITEPROMPT. - strict_file_types (bool): Restricts selection to specified file types. FileOpenOptions.FOS_STRICTFILETYPES. - no_change_dir (bool): Prevents changing the current working directory. FileOpenOptions.FOS_NOCHANGEDIR. - force_filesystem (bool): Ensures only file system items are shown. FileOpenOptions.FOS_FORCEFILESYSTEM. - all_non_storage_items (bool): Allows selection of non-file system items. FileOpenOptions.FOS_ALLNONSTORAGEITEMS. - no_validate (bool): Disables file name validation. FileOpenOptions.FOS_NOVALIDATE. - allow_multiple_selection (bool): Allows selecting multiple files. FileOpenOptions.FOS_ALLOWMULTISELECT. - path_must_exist (bool): Requires the path to exist. FileOpenOptions.FOS_PATHMUSTEXIST. - file_must_exist (bool): Requires the file to exist. FileOpenOptions.FOS_FILEMUSTEXIST. - create_prompt (bool): Prompts to create a new file if it doesn't exist. FileOpenOptions.FOS_CREATEPROMPT. - share_aware (bool): Ensures the dialog is aware of sharing conflicts. FileOpenOptions.FOS_SHAREAWARE. - no_readonly_return (bool): Prevents selection of read-only items. FileOpenOptions.FOS_NOREADONLYRETURN. - no_test_file_create (bool): Disables testing file creation ability. FileOpenOptions.FOS_NOTESTFILECREATE. - hide_mru_places (bool): Hides most recently used places. FileOpenOptions.FOS_HIDEMRUPLACES. - hide_pinned_places (bool): Hides pinned places. FileOpenOptions.FOS_HIDEPINNEDPLACES. - no_dereference_links (bool): Prevents dereferencing shortcuts. FileOpenOptions.FOS_NODEREFERENCELINKS. - add_to_recent (bool): Prevents adding the file to recent files. FileOpenOptions.FOS_DONTADDTORECENT. - show_hidden_files (bool): Shows hidden files and folders. FileOpenOptions.FOS_FORCESHOWHIDDEN. - default_no_minimode (bool): Uses default non-minimized mode. FileOpenOptions.FOS_DEFAULTNOMINIMODE. - force_preview_pane_on (bool): Forces the preview pane to be visible. FileOpenOptions.FOS_FORCEPREVIEWPANEON. - ok_button_text (str): The text for the button used to select/confirm the dialog. - - Returns: - list[str] | None: A list of selected file paths or None if cancelled. - """ - options = 0 - if overwrite_prompt: - options |= FileOpenOptions.FOS_OVERWRITEPROMPT - if strict_file_types: - options |= FileOpenOptions.FOS_STRICTFILETYPES - if no_change_dir: - options |= FileOpenOptions.FOS_NOCHANGEDIR - if force_filesystem: - options |= FileOpenOptions.FOS_FORCEFILESYSTEM - if all_non_storage_items: - options |= FileOpenOptions.FOS_ALLNONSTORAGEITEMS - if no_validate: - options |= FileOpenOptions.FOS_NOVALIDATE - if allow_multiple_selection: - options |= FileOpenOptions.FOS_ALLOWMULTISELECT - if path_must_exist: - options |= FileOpenOptions.FOS_PATHMUSTEXIST - if file_must_exist: - options |= FileOpenOptions.FOS_FILEMUSTEXIST - if create_prompt: - options |= FileOpenOptions.FOS_CREATEPROMPT - if share_aware: - options |= FileOpenOptions.FOS_SHAREAWARE - if no_readonly_return: - options |= FileOpenOptions.FOS_NOREADONLYRETURN - if no_test_file_create: - options |= FileOpenOptions.FOS_NOTESTFILECREATE - if hide_mru_places: - options |= FileOpenOptions.FOS_HIDEMRUPLACES - if hide_pinned_places: - options |= FileOpenOptions.FOS_HIDEPINNEDPLACES - if no_dereference_links: - options |= FileOpenOptions.FOS_NODEREFERENCELINKS - if not add_to_recent: - options |= FileOpenOptions.FOS_DONTADDTORECENT - if show_hidden_files: - options |= FileOpenOptions.FOS_FORCESHOWHIDDEN - if default_no_minimode: - options |= FileOpenOptions.FOS_DEFAULTNOMINIMODE - if force_preview_pane_on: - options |= FileOpenOptions.FOS_FORCEPREVIEWPANEON - file_dialog = comtypes.client.CreateObject(CLSID_FileOpenDialog, interface=IFileOpenDialog) - return configure_file_dialog(file_dialog, title, options, default_folder, ok_button_text, None, file_types, default_extension) - - -def save_file_dialog( # noqa: C901, PLR0913, PLR0912 - title: str | None = "Save File", - default_folder: str | None = None, - file_types: list[tuple[str, str]] | None = None, - default_extension: str | None = None, - *, - overwrite_prompt: bool = True, - strict_file_types: bool = False, - no_change_dir: bool = True, - force_filesystem: bool = True, - all_non_storage_items: bool = False, - no_validate: bool = False, - path_must_exist: bool = True, - file_must_exist: bool = False, - create_prompt: bool = False, - share_aware: bool = False, - no_readonly_return: bool = False, - no_test_file_create: bool = False, - hide_mru_places: bool = False, - hide_pinned_places: bool = False, - no_dereference_links: bool = False, - add_to_recent: bool = True, - show_hidden_files: bool = False, - default_no_minimode: bool = False, - force_preview_pane_on: bool = False, - ok_button_text: str | None = None, -) -> list[str] | None: # sourcery skip: low-code-quality - """Opens a file dialog to save a file. - - Args: - title (str | None): The title of the dialog. - default_folder (str | None): The initial folder to open. - file_types (list[tuple[str, str]] | None): A list of file type filters. - default_extension (str | None): The default file extension. - overwrite_prompt (bool): Prompts if the selected file already exists. FileOpenOptions.FOS_OVERWRITEPROMPT. - strict_file_types (bool): Restricts selection to specified file types. FileOpenOptions.FOS_STRICTFILETYPES. - no_change_dir (bool): Prevents changing the current working directory. FileOpenOptions.FOS_NOCHANGEDIR. - force_filesystem (bool): Ensures only file system items are shown. FileOpenOptions.FOS_FORCEFILESYSTEM. - all_non_storage_items (bool): Allows selection of non-file system items. FileOpenOptions.FOS_ALLNONSTORAGEITEMS. - no_validate (bool): Disables file name validation. FileOpenOptions.FOS_NOVALIDATE. - path_must_exist (bool): Requires the path to exist. FileOpenOptions.FOS_PATHMUSTEXIST. - file_must_exist (bool): Requires the file to exist. FileOpenOptions.FOS_FILEMUSTEXIST. - create_prompt (bool): Prompts to create a new file if it doesn't exist. FileOpenOptions.FOS_CREATEPROMPT. - share_aware (bool): Ensures the dialog is aware of sharing conflicts. FileOpenOptions.FOS_SHAREAWARE. - no_readonly_return (bool): Prevents selection of read-only items. FileOpenOptions.FOS_NOREADONLYRETURN. - no_test_file_create (bool): Disables testing file creation ability. FileOpenOptions.FOS_NOTESTFILECREATE. - hide_mru_places (bool): Hides most recently used places. FileOpenOptions.FOS_HIDEMRUPLACES. - hide_pinned_places (bool): Hides pinned places. FileOpenOptions.FOS_HIDEPINNEDPLACES. - no_dereference_links (bool): Prevents dereferencing shortcuts. FileOpenOptions.FOS_NODEREFERENCELINKS. - add_to_recent (bool): Prevents adding the file to recent files. FileOpenOptions.FOS_DONTADDTORECENT. - show_hidden_files (bool): Shows hidden files and folders. FileOpenOptions.FOS_FORCESHOWHIDDEN. - default_no_minimode (bool): Uses default non-minimized mode. FileOpenOptions.FOS_DEFAULTNOMINIMODE. - force_preview_pane_on (bool): Forces the preview pane to be visible. FileOpenOptions.FOS_FORCEPREVIEWPANEON. - ok_button_text (str): The text for the button used to select/confirm the dialog. - - Returns: - list[str] | None: A list of selected file paths or None if cancelled. - """ - options = 0 - if overwrite_prompt: - options |= FileOpenOptions.FOS_OVERWRITEPROMPT - if strict_file_types: - options |= FileOpenOptions.FOS_STRICTFILETYPES - if no_change_dir: - options |= FileOpenOptions.FOS_NOCHANGEDIR - if force_filesystem: - options |= FileOpenOptions.FOS_FORCEFILESYSTEM - if all_non_storage_items: - options |= FileOpenOptions.FOS_ALLNONSTORAGEITEMS - if no_validate: - options |= FileOpenOptions.FOS_NOVALIDATE - if path_must_exist: - options |= FileOpenOptions.FOS_PATHMUSTEXIST - if file_must_exist: - options |= FileOpenOptions.FOS_FILEMUSTEXIST - if create_prompt: - options |= FileOpenOptions.FOS_CREATEPROMPT - if share_aware: - options |= FileOpenOptions.FOS_SHAREAWARE - if no_readonly_return: - options |= FileOpenOptions.FOS_NOREADONLYRETURN - if no_test_file_create: - options |= FileOpenOptions.FOS_NOTESTFILECREATE - if hide_mru_places: - options |= FileOpenOptions.FOS_HIDEMRUPLACES - if hide_pinned_places: - options |= FileOpenOptions.FOS_HIDEPINNEDPLACES - if no_dereference_links: - options |= FileOpenOptions.FOS_NODEREFERENCELINKS - if not add_to_recent: - options |= FileOpenOptions.FOS_DONTADDTORECENT - if show_hidden_files: - options |= FileOpenOptions.FOS_FORCESHOWHIDDEN - if default_no_minimode: - options |= FileOpenOptions.FOS_DEFAULTNOMINIMODE - if force_preview_pane_on: - options |= FileOpenOptions.FOS_FORCEPREVIEWPANEON - options &= ~FileOpenOptions.FOS_PICKFOLDERS # Required (exceptions otherwise) - options &= ~FileOpenOptions.FOS_ALLOWMULTISELECT # Required (exceptions otherwise) - file_dialog = comtypes.client.CreateObject(CLSID_FileSaveDialog, interface=IFileSaveDialog) - return configure_file_dialog(file_dialog, title, options, default_folder, ok_button_text, None, file_types, default_extension) - - -def open_folder_dialog( # noqa: C901, PLR0913, PLR0912 - title: str | None = "Select Folder", - default_folder: str | None = None, - *, - overwrite_prompt: bool = False, - strict_file_types: bool = False, - no_change_dir: bool = False, - force_filesystem: bool = True, - no_validate: bool = False, - allow_multiple_selection: bool = False, - path_must_exist: bool = True, - file_must_exist: bool = False, - create_prompt: bool = False, - share_aware: bool = False, - no_readonly_return: bool = False, - no_test_file_create: bool = False, - hide_mru_places: bool = False, - hide_pinned_places: bool = False, - no_dereference_links: bool = False, - add_to_recent: bool = True, - show_hidden_files: bool = False, - default_no_minimode: bool = False, - force_preview_pane_on: bool = False, - ok_button_text: str | None = None, -) -> list[str] | None: # sourcery skip: low-code-quality - """Opens a dialog to select folders. - - Args: - title (str | None): The title of the dialog. - default_folder (str | None): The initial folder to open. - overwrite_prompt (bool): Prompts if the selected file already exists. FileOpenOptions.FOS_OVERWRITEPROMPT. - strict_file_types (bool): Restricts selection to specified file types. FileOpenOptions.FOS_STRICTFILETYPES. - no_change_dir (bool): Prevents changing the current working directory. FileOpenOptions.FOS_NOCHANGEDIR. - force_filesystem (bool): Ensures only file system items are shown. FileOpenOptions.FOS_FORCEFILESYSTEM. - no_validate (bool): Disables file name validation. FileOpenOptions.FOS_NOVALIDATE. - allow_multiple_selection (bool): Allows selecting multiple files. FileOpenOptions.FOS_ALLOWMULTISELECT. - path_must_exist (bool): Requires the path to exist. FileOpenOptions.FOS_PATHMUSTEXIST. - file_must_exist (bool): Requires the file to exist. FileOpenOptions.FOS_FILEMUSTEXIST. - create_prompt (bool): Prompts to create a new file if it doesn't exist. FileOpenOptions.FOS_CREATEPROMPT. - share_aware (bool): Ensures the dialog is aware of sharing conflicts. FileOpenOptions.FOS_SHAREAWARE. - no_readonly_return (bool): Prevents selection of read-only items. FileOpenOptions.FOS_NOREADONLYRETURN. - no_test_file_create (bool): Disables testing file creation ability. FileOpenOptions.FOS_NOTESTFILECREATE. - hide_mru_places (bool): Hides most recently used places. FileOpenOptions.FOS_HIDEMRUPLACES. - hide_pinned_places (bool): Hides pinned places. FileOpenOptions.FOS_HIDEPINNEDPLACES. - no_dereference_links (bool): Prevents dereferencing shortcuts. FileOpenOptions.FOS_NODEREFERENCELINKS. - add_to_recent (bool): Prevents adding the file to recent files. FileOpenOptions.FOS_DONTADDTORECENT. - show_hidden_files (bool): Shows hidden files and folders. FileOpenOptions.FOS_FORCESHOWHIDDEN. - default_no_minimode (bool): Uses default non-minimized mode. FileOpenOptions.FOS_DEFAULTNOMINIMODE. - force_preview_pane_on (bool): Forces the preview pane to be visible. FileOpenOptions.FOS_FORCEPREVIEWPANEON. - ok_button_text (str): The text for the button used to select/confirm the dialog. - - Returns: - list[str] | None: A list of selected folder paths or None if cancelled. - """ - options = 0 - options |= FileOpenOptions.FOS_PICKFOLDERS - options &= ~FileOpenOptions.FOS_ALLNONSTORAGEITEMS - if overwrite_prompt: - options |= FileOpenOptions.FOS_OVERWRITEPROMPT - if strict_file_types: - options |= FileOpenOptions.FOS_STRICTFILETYPES - if no_change_dir: - options |= FileOpenOptions.FOS_NOCHANGEDIR - if force_filesystem: - options |= FileOpenOptions.FOS_FORCEFILESYSTEM - if no_validate: - options |= FileOpenOptions.FOS_NOVALIDATE - if allow_multiple_selection: - options |= FileOpenOptions.FOS_ALLOWMULTISELECT - if path_must_exist: - options |= FileOpenOptions.FOS_PATHMUSTEXIST - if file_must_exist: - options |= FileOpenOptions.FOS_FILEMUSTEXIST - if create_prompt: - options |= FileOpenOptions.FOS_CREATEPROMPT - if share_aware: - options |= FileOpenOptions.FOS_SHAREAWARE - if no_readonly_return: - options |= FileOpenOptions.FOS_NOREADONLYRETURN - if no_test_file_create: - options |= FileOpenOptions.FOS_NOTESTFILECREATE - if hide_mru_places: - options |= FileOpenOptions.FOS_HIDEMRUPLACES - if hide_pinned_places: - options |= FileOpenOptions.FOS_HIDEPINNEDPLACES - if no_dereference_links: - options |= FileOpenOptions.FOS_NODEREFERENCELINKS - if not add_to_recent: - options |= FileOpenOptions.FOS_DONTADDTORECENT - if show_hidden_files: - options |= FileOpenOptions.FOS_FORCESHOWHIDDEN - if default_no_minimode: - options |= FileOpenOptions.FOS_DEFAULTNOMINIMODE - if force_preview_pane_on: - options |= FileOpenOptions.FOS_FORCEPREVIEWPANEON - file_dialog = comtypes.client.CreateObject(CLSID_FileOpenDialog, interface=IFileOpenDialog) - return configure_file_dialog(file_dialog, title, options, default_folder, ok_button_text, None, None, None) - - -def get_open_file_dialog_results( - file_open_dialog: IFileOpenDialog, -) -> list[str]: - results: list[str] = [] - results_array: IShellItemArray = file_open_dialog.GetResults() - item_count: int = results_array.GetCount() - for i in range(item_count): - shell_item: IShellItem = results_array.GetItemAt(i) - szFilePath: str = shell_item.GetDisplayName(0x80058000) # SIGDN_FILESYSPATH - if szFilePath and szFilePath.strip(): - results.append(szFilePath) - else: - raise FileNotFoundError(errno.ENOENT, os.strerror(errno.ENOENT), szFilePath) - return results - - -def get_save_file_dialog_results(file_save_dialog: IFileSaveDialog) -> str: - results = "" - resultItem: IShellItem = file_save_dialog.GetResult() - szFilePath = resultItem.GetDisplayName(0x80058000) # SIGDN_FILESYSPATH - szFilePathStr = str(szFilePath) - if szFilePathStr and szFilePathStr.strip(): - results = szFilePathStr - else: - raise FileNotFoundError(errno.ENOENT, os.strerror(errno.ENOENT), str(szFilePath)) - resultItem.Release() - return results - - -# Example usage -if __name__ == "__main__": - # Randomizing arguments for open_file_dialog - open_file_args = { - "title": "Open File" if random.choice([True, False]) else None, # noqa: S311 - "default_folder": "C:\\Users" if random.choice([True, False]) else None, # noqa: S311 - "file_types": [("Text Files", "*.txt")] if random.choice([True, False]) else None, # noqa: S311 - "default_extension": "txt" if random.choice([True, False]) else None, # noqa: S311 - "overwrite_prompt": random.choice([True, False]), # noqa: S311 - "strict_file_types": random.choice([True, False]), # noqa: S311 - "no_change_dir": random.choice([True, False]), # noqa: S311 - "force_filesystem": random.choice([True, False]), # noqa: S311 - "all_non_storage_items": False, # random.choice([True, False]), # noqa: S311 - "no_validate": random.choice([True, False]), # noqa: S311 - "allow_multiple_selection": random.choice([True, False]), # noqa: S311 - "path_must_exist": random.choice([True, False]), # noqa: S311 - "file_must_exist": random.choice([True, False]), # noqa: S311 - "create_prompt": random.choice([True, False]), # noqa: S311 - "share_aware": random.choice([True, False]), # noqa: S311 - "no_readonly_return": random.choice([True, False]), # noqa: S311 - "no_test_file_create": random.choice([True, False]), # noqa: S311 - "hide_mru_places": random.choice([True, False]), # noqa: S311 - "hide_pinned_places": random.choice([True, False]), # noqa: S311 - "no_dereference_links": random.choice([True, False]), # noqa: S311 - "add_to_recent": random.choice([True, False]), # noqa: S311 - "show_hidden_files": random.choice([True, False]), # noqa: S311 - "default_no_minimode": random.choice([True, False]), # noqa: S311 - "force_preview_pane_on": random.choice([True, False]), # noqa: S311 - } - print("\nOpen file args") - print(json.dumps(open_file_args, indent=4, sort_keys=True)) - selected_files: list[str] | None = open_file_dialog(**open_file_args) - print("Selected files:", selected_files) - - # Randomizing arguments for open_folder_dialog - open_folder_args = { - "title": "Select Folder" if random.choice([True, False]) else None, # noqa: S311 - "default_folder": "C:\\Users" if random.choice([True, False]) else None, # noqa: S311 - "overwrite_prompt": random.choice([True, False]), # noqa: S311 - "strict_file_types": random.choice([True, False]), # noqa: S311 - "no_change_dir": random.choice([True, False]), # noqa: S311 - "force_filesystem": random.choice([True, False]), # noqa: S311 - "no_validate": random.choice([True, False]), # noqa: S311 - "allow_multiple_selection": random.choice([True, False]), # noqa: S311 - "path_must_exist": random.choice([True, False]), # noqa: S311 - "file_must_exist": random.choice([True, False]), # noqa: S311 - "create_prompt": random.choice([True, False]), # noqa: S311 - "share_aware": random.choice([True, False]), # noqa: S311 - "no_readonly_return": random.choice([True, False]), # noqa: S311 - "no_test_file_create": random.choice([True, False]), # noqa: S311 - "hide_mru_places": random.choice([True, False]), # noqa: S311 - "hide_pinned_places": random.choice([True, False]), # noqa: S311 - "no_dereference_links": random.choice([True, False]), # noqa: S311 - "add_to_recent": random.choice([True, False]), # noqa: S311 - "show_hidden_files": random.choice([True, False]), # noqa: S311 - "default_no_minimode": random.choice([True, False]), # noqa: S311 - "force_preview_pane_on": random.choice([True, False]), # noqa: S311 - } - print("\nOpen folder args") - print(json.dumps(open_folder_args, indent=4, sort_keys=True)) - selected_folders: list[str] | None = open_folder_dialog(**open_folder_args) - print("Selected folders:", selected_folders) - - # Randomizing arguments for save_file_dialog - save_file_args = { - "title": "Save File" if random.choice([True, False]) else None, # noqa: S311 - "default_folder": "C:\\Users" if random.choice([True, False]) else None, # noqa: S311 - "file_types": [("Text Files", "*.txt")] if random.choice([True, False]) else None, # noqa: S311 - "default_extension": "txt" if random.choice([True, False]) else None, # noqa: S311 - "overwrite_prompt": random.choice([True, False]), # noqa: S311 - "strict_file_types": random.choice([True, False]), # noqa: S311 - "no_change_dir": random.choice([True, False]), # noqa: S311 - "force_filesystem": random.choice([True, False]), # noqa: S311 - "all_non_storage_items": random.choice([True, False]), # noqa: S311 - "no_validate": random.choice([True, False]), # noqa: S311 - "path_must_exist": random.choice([True, False]), # noqa: S311 - "file_must_exist": random.choice([True, False]), # noqa: S311 - "create_prompt": random.choice([True, False]), # noqa: S311 - "share_aware": random.choice([True, False]), # noqa: S311 - "no_readonly_return": random.choice([True, False]), # noqa: S311 - "no_test_file_create": random.choice([True, False]), # noqa: S311 - "hide_mru_places": random.choice([True, False]), # noqa: S311 - "hide_pinned_places": random.choice([True, False]), # noqa: S311 - "no_dereference_links": random.choice([True, False]), # noqa: S311 - "add_to_recent": random.choice([True, False]), # noqa: S311 - "show_hidden_files": random.choice([True, False]), # noqa: S311 - "default_no_minimode": random.choice([True, False]), # noqa: S311 - "force_preview_pane_on": random.choice([True, False]), # noqa: S311 - } - print("\nSave file args") - print(json.dumps(save_file_args, indent=4, sort_keys=True)) - saved_file: list[str] | None = save_file_dialog(**save_file_args) - print("Saved file:", saved_file) From 3419c9af52a2577dde30d453bf4c46599714f4e0 Mon Sep 17 00:00:00 2001 From: Benjamin Auquite Date: Fri, 30 Aug 2024 01:31:27 -0500 Subject: [PATCH 4/9] rename lib --- winforms/src/toga_winforms/dialogs.py | 4 ++-- .../toga_winforms/libs/py_wrappers/com/com_helpers.py | 6 +++--- .../toga_winforms/libs/py_wrappers/com/interfaces.py | 2 +- .../libs/py_wrappers/com/progress_dialog.py | 4 ++-- .../toga_winforms/libs/py_wrappers/com/windialogs.py | 10 +++++----- .../src/toga_winforms/libs/py_wrappers/context_menu.py | 2 +- winforms/src/toga_winforms/libs/py_wrappers/hwnd.py | 2 +- .../libs/py_wrappers/winapi/messagebox.py | 4 ++-- 8 files changed, 17 insertions(+), 17 deletions(-) diff --git a/winforms/src/toga_winforms/dialogs.py b/winforms/src/toga_winforms/dialogs.py index f90d775fdc..ecdfa61dde 100644 --- a/winforms/src/toga_winforms/dialogs.py +++ b/winforms/src/toga_winforms/dialogs.py @@ -21,7 +21,7 @@ from comtypes import GUID from comtypes.hresult import S_OK -from toga_winforms.libs.win_wrappers.com.interfaces import ( +from toga_winforms.libs.py_wrappers.com.interfaces import ( COMDLG_FILTERSPEC, CLSID_FileOpenDialog, CLSID_FileSaveDialog, @@ -31,7 +31,7 @@ IShellItem, IShellItemArray, ) -from toga_winforms.libs.win_wrappers.hwnd import ( +from toga_winforms.libs.py_wrappers.hwnd import ( ES_AUTOVSCROLL, ES_MULTILINE, ES_READONLY, diff --git a/winforms/src/toga_winforms/libs/py_wrappers/com/com_helpers.py b/winforms/src/toga_winforms/libs/py_wrappers/com/com_helpers.py index 56a697a951..1ed7802ffc 100644 --- a/winforms/src/toga_winforms/libs/py_wrappers/com/com_helpers.py +++ b/winforms/src/toga_winforms/libs/py_wrappers/com/com_helpers.py @@ -7,7 +7,7 @@ from comtypes import COMObject -from toga_winforms.libs.win_wrappers.hresult import HRESULT +from toga_winforms.libs.py_wrappers.hresult import HRESULT if TYPE_CHECKING: from ctypes import _NamedFuncPointer @@ -110,8 +110,8 @@ def register_idispatch_object( import comtypes from comtypes import GUID - from toga_winforms.libs.win_wrappers.com.interfaces import SIGDN - from toga_winforms.libs.win_wrappers.hresult import S_OK + from toga_winforms.libs.py_wrappers.com.interfaces import SIGDN + from toga_winforms.libs.py_wrappers.hresult import S_OK try: from win32com.shell import shell # pyright: ignore[reportMissingModuleSource] except ModuleNotFoundError: diff --git a/winforms/src/toga_winforms/libs/py_wrappers/com/interfaces.py b/winforms/src/toga_winforms/libs/py_wrappers/com/interfaces.py index bf87d4dbb6..272f8fa2c5 100644 --- a/winforms/src/toga_winforms/libs/py_wrappers/com/interfaces.py +++ b/winforms/src/toga_winforms/libs/py_wrappers/com/interfaces.py @@ -24,7 +24,7 @@ GUID, ) -from toga_winforms.libs.win_wrappers.hresult import ( # pyright: ignore[reportMissingTypeStubs] +from toga_winforms.libs.py_wrappers.hresult import ( # pyright: ignore[reportMissingTypeStubs] HRESULT, S_OK, ) diff --git a/winforms/src/toga_winforms/libs/py_wrappers/com/progress_dialog.py b/winforms/src/toga_winforms/libs/py_wrappers/com/progress_dialog.py index 597fff950f..91f5c4bbe1 100644 --- a/winforms/src/toga_winforms/libs/py_wrappers/com/progress_dialog.py +++ b/winforms/src/toga_winforms/libs/py_wrappers/com/progress_dialog.py @@ -10,11 +10,11 @@ from comtypes.gen import Shell32 from utility.logger_util import RobustRootLogger -from toga_winforms.libs.win_wrappers.com.interfaces import ( +from toga_winforms.libs.py_wrappers.com.interfaces import ( CLSID_FileOperation, IFileOperationProgressSink, ) -from toga_winforms.libs.win_wrappers.hresult import S_OK +from toga_winforms.libs.py_wrappers.hresult import S_OK if TYPE_CHECKING: from typing_extensions import Literal diff --git a/winforms/src/toga_winforms/libs/py_wrappers/com/windialogs.py b/winforms/src/toga_winforms/libs/py_wrappers/com/windialogs.py index d951650f2f..ef43e08897 100644 --- a/winforms/src/toga_winforms/libs/py_wrappers/com/windialogs.py +++ b/winforms/src/toga_winforms/libs/py_wrappers/com/windialogs.py @@ -14,9 +14,9 @@ from utility.logger_util import RobustRootLogger from utility.system.path import WindowsPath -from toga_winforms.libs.win_wrappers.com.com_helpers import HandleCOMCall -from toga_winforms.libs.win_wrappers.com.com_types import GUID -from toga_winforms.libs.win_wrappers.com.interfaces import ( +from toga_winforms.libs.py_wrappers.com.com_helpers import HandleCOMCall +from toga_winforms.libs.py_wrappers.com.com_types import GUID +from toga_winforms.libs.py_wrappers.com.interfaces import ( COMDLG_FILTERSPEC, SFGAO, SIGDN, @@ -31,13 +31,13 @@ IID_IFileDialogCustomize, IShellItem, ) -from toga_winforms.libs.win_wrappers.hresult import HRESULT, S_OK +from toga_winforms.libs.py_wrappers.hresult import HRESULT, S_OK if TYPE_CHECKING: from ctypes import Array, _FuncPointer, _Pointer from ctypes.wintypes import BOOL, DWORD, LPWSTR - from toga_winforms.libs.win_wrappers.com.interfaces import IFileDialog, IShellItemArray + from toga_winforms.libs.py_wrappers.com.interfaces import IFileDialog, IShellItemArray class FileDialogControlEvents(comtypes.COMObject): diff --git a/winforms/src/toga_winforms/libs/py_wrappers/context_menu.py b/winforms/src/toga_winforms/libs/py_wrappers/context_menu.py index 479aba2662..4c694d4535 100644 --- a/winforms/src/toga_winforms/libs/py_wrappers/context_menu.py +++ b/winforms/src/toga_winforms/libs/py_wrappers/context_menu.py @@ -10,7 +10,7 @@ import comtypes # pyright: ignore[reportMissingTypeStubs] import comtypes.client # pyright: ignore[reportMissingTypeStubs] -from toga_winforms.libs.win_wrappers.hwnd import SimplePyHWND +from toga_winforms.libs.py_wrappers.hwnd import SimplePyHWND if TYPE_CHECKING: import os diff --git a/winforms/src/toga_winforms/libs/py_wrappers/hwnd.py b/winforms/src/toga_winforms/libs/py_wrappers/hwnd.py index 3f913fa018..d25506293f 100644 --- a/winforms/src/toga_winforms/libs/py_wrappers/hwnd.py +++ b/winforms/src/toga_winforms/libs/py_wrappers/hwnd.py @@ -4,7 +4,7 @@ from ctypes import POINTER, WINFUNCTYPE, Structure, c_int, c_wchar_p, windll, c_long, c_uint, c_void_p from ctypes.wintypes import ATOM, BOOL, DWORD, HBRUSH, HICON, HINSTANCE, HMENU, HWND, LPARAM, LPCWSTR, LPVOID, UINT, WPARAM -from toga_winforms.libs.win_wrappers.common import LRESULT +from toga_winforms.libs.py_wrappers.common import LRESULT from enum import IntEnum from typing import TYPE_CHECKING, Sequence diff --git a/winforms/src/toga_winforms/libs/py_wrappers/winapi/messagebox.py b/winforms/src/toga_winforms/libs/py_wrappers/winapi/messagebox.py index 83bd81479f..05c42c2950 100644 --- a/winforms/src/toga_winforms/libs/py_wrappers/winapi/messagebox.py +++ b/winforms/src/toga_winforms/libs/py_wrappers/winapi/messagebox.py @@ -3,8 +3,8 @@ from ctypes import POINTER, WINFUNCTYPE, Structure, c_int, c_wchar_p, windll from ctypes.wintypes import ATOM, BOOL, DWORD, HBRUSH, HICON, HINSTANCE, HMENU, HWND, LPARAM, LPCWSTR, LPVOID, UINT, WPARAM -from toga_winforms.libs.win_wrappers.common import LRESULT -from toga_winforms.libs.win_wrappers.hwnd import HCURSOR +from toga_winforms.libs.py_wrappers.common import LRESULT +from toga_winforms.libs.py_wrappers.hwnd import HCURSOR def windows_message_box( # noqa: PLR0913 From 37717b7f7a5faafef28d54bf388cbc13fbab6e25 Mon Sep 17 00:00:00 2001 From: Benjamin Auquite Date: Fri, 30 Aug 2024 19:56:13 -0500 Subject: [PATCH 5/9] Revert "remove dialog dependency to winforms" This reverts commit 99b9eeba0679960985dddc1b276106b731fef05f. Revert "rename lib" This reverts commit 3419c9af52a2577dde30d453bf4c46599714f4e0. Revert "Delete winforms/src/toga_winforms/libs/py_wrappers/com/windialogs copy.py" This reverts commit 61a398589958575c0a17d9e44c8458ffee778a19. --- winforms/src/toga_winforms/dialogs.py | 573 ++++--- .../libs/py_wrappers/__init__.py | 0 .../libs/py_wrappers/com/__init__.py | 0 .../libs/py_wrappers/com/com_helpers.py | 163 -- .../libs/py_wrappers/com/com_types.py | 1 - .../libs/py_wrappers/com/interfaces.py | 1401 ----------------- .../libs/py_wrappers/com/progress_dialog.py | 149 -- .../libs/py_wrappers/com/windialogs.py | 857 ---------- .../toga_winforms/libs/py_wrappers/common.py | 50 - .../libs/py_wrappers/context_menu.py | 209 --- .../toga_winforms/libs/py_wrappers/hresult.py | 419 ----- .../toga_winforms/libs/py_wrappers/hwnd.py | 300 ---- .../libs/py_wrappers/winapi/__init__.py | 0 .../py_wrappers/winapi/device_iocontrol.py | 905 ----------- .../libs/py_wrappers/winapi/input_dialogs.py | 39 - .../libs/py_wrappers/winapi/messagebox.py | 95 -- .../libs/py_wrappers/winapi/sounds.py | 12 - .../libs/py_wrappers/winapi/tooltip.py | 37 - 18 files changed, 395 insertions(+), 4815 deletions(-) delete mode 100644 winforms/src/toga_winforms/libs/py_wrappers/__init__.py delete mode 100644 winforms/src/toga_winforms/libs/py_wrappers/com/__init__.py delete mode 100644 winforms/src/toga_winforms/libs/py_wrappers/com/com_helpers.py delete mode 100644 winforms/src/toga_winforms/libs/py_wrappers/com/com_types.py delete mode 100644 winforms/src/toga_winforms/libs/py_wrappers/com/interfaces.py delete mode 100644 winforms/src/toga_winforms/libs/py_wrappers/com/progress_dialog.py delete mode 100644 winforms/src/toga_winforms/libs/py_wrappers/com/windialogs.py delete mode 100644 winforms/src/toga_winforms/libs/py_wrappers/common.py delete mode 100644 winforms/src/toga_winforms/libs/py_wrappers/context_menu.py delete mode 100644 winforms/src/toga_winforms/libs/py_wrappers/hresult.py delete mode 100644 winforms/src/toga_winforms/libs/py_wrappers/hwnd.py delete mode 100644 winforms/src/toga_winforms/libs/py_wrappers/winapi/__init__.py delete mode 100644 winforms/src/toga_winforms/libs/py_wrappers/winapi/device_iocontrol.py delete mode 100644 winforms/src/toga_winforms/libs/py_wrappers/winapi/input_dialogs.py delete mode 100644 winforms/src/toga_winforms/libs/py_wrappers/winapi/messagebox.py delete mode 100644 winforms/src/toga_winforms/libs/py_wrappers/winapi/sounds.py delete mode 100644 winforms/src/toga_winforms/libs/py_wrappers/winapi/tooltip.py diff --git a/winforms/src/toga_winforms/dialogs.py b/winforms/src/toga_winforms/dialogs.py index ecdfa61dde..9b3d8df093 100644 --- a/winforms/src/toga_winforms/dialogs.py +++ b/winforms/src/toga_winforms/dialogs.py @@ -1,66 +1,271 @@ -from __future__ import annotations - import asyncio -from ctypes import ( - HRESULT, - POINTER, - byref, - c_int, - c_void_p, - c_wchar_p, - sizeof, - windll, -) -from ctypes import cast as cast_with_ctypes -from ctypes.wintypes import HWND, LPCWSTR +import os from pathlib import Path -from typing import TYPE_CHECKING import comtypes import comtypes.client -from comtypes import GUID + +from comtypes import COMMETHOD, GUID from comtypes.hresult import S_OK -from toga_winforms.libs.py_wrappers.com.interfaces import ( - COMDLG_FILTERSPEC, - CLSID_FileOpenDialog, - CLSID_FileSaveDialog, - FileOpenOptions, - IFileOpenDialog, - IFileSaveDialog, - IShellItem, - IShellItemArray, -) -from toga_winforms.libs.py_wrappers.hwnd import ( - ES_AUTOVSCROLL, - ES_MULTILINE, - ES_READONLY, - IDCANCEL, - IDOK, - IDYES, - MB_ICONASTERISK, - MB_ICONEXCLAMATION, - MB_ICONHAND, - MB_ICONQUESTION, - MB_OK, - MB_OKCANCEL, - MB_YESNO, - SW_SHOW, - WM_COMMAND, - WM_DESTROY, - WNDPROC, - WS_CHILD, - WS_EX_DLGMODALFRAME, - WS_OVERLAPPEDWINDOW, - WS_VISIBLE, - WS_VSCROLL, - CursorType, - MessageBoxW, - ctypesWNDCLASS, +from ctypes import HRESULT, POINTER, Structure, byref, c_int, c_uint, c_ulong, c_void_p, c_wchar_p, windll +from ctypes import cast as cast_with_ctypes +from ctypes.wintypes import BOOL, DWORD, HWND, LPCWSTR, LPWSTR +from enum import IntFlag +from typing import Callable, List, Optional, Tuple, Union + +import System.Windows.Forms as WinForms +from System.Drawing import ( + ContentAlignment, + Font as WinFont, + FontFamily, + FontStyle, + SystemFonts, ) - -if TYPE_CHECKING: - import os +from System.Windows.Forms import DialogResult, MessageBoxButtons, MessageBoxIcon + +from .libs.wrapper import WeakrefCallable + + +class COMDLG_FILTERSPEC(Structure): # noqa: N801 + _fields_ = [ + ("pszName", LPCWSTR), + ("pszSpec", LPCWSTR) + ] + + +class FileOpenOptions(IntFlag): + FOS_OVERWRITEPROMPT = 0x00000002 + FOS_STRICTFILETYPES = 0x00000004 + FOS_NOCHANGEDIR = 0x00000008 + FOS_PICKFOLDERS = 0x00000020 + FOS_FORCEFILESYSTEM = 0x00000040 + FOS_ALLNONSTORAGEITEMS = 0x00000080 + FOS_NOVALIDATE = 0x00000100 + FOS_ALLOWMULTISELECT = 0x00000200 + FOS_PATHMUSTEXIST = 0x00000800 + FOS_FILEMUSTEXIST = 0x00001000 + FOS_CREATEPROMPT = 0x00002000 + FOS_SHAREAWARE = 0x00004000 + FOS_NOREADONLYRETURN = 0x00008000 + FOS_NOTESTFILECREATE = 0x00010000 + FOS_HIDEMRUPLACES = 0x00020000 + FOS_HIDEPINNEDPLACES = 0x00040000 + FOS_NODEREFERENCELINKS = 0x00100000 + FOS_DONTADDTORECENT = 0x02000000 + FOS_FORCESHOWHIDDEN = 0x10000000 + FOS_DEFAULTNOMINIMODE = 0x20000000 + FOS_FORCEPREVIEWPANEON = 0x40000000 + + +IID_IShellItem = GUID("{43826D1E-E718-42EE-BC55-A1E261C37BFE}") +IID_IShellItemArray = GUID("{B63EA76D-1F85-456F-A19C-48159EFA858B}") +IID_IShellItemFilter = GUID("{2659B475-EEB8-48B7-8F07-B378810F48CF}") +IID_IModalWindow = GUID("{B4DB1657-70D7-485E-8E3E-6FCB5A5C1802}") +IID_IFileDialog = GUID("{42F85136-DB7E-439C-85F1-E4075D135FC8}") +IID_IFileOpenDialog = GUID("{D57C7288-D4AD-4768-BE02-9D969532D960}") +IID_IFileSaveDialog = GUID("{84BCCD23-5FDE-4CDB-AEA4-AF64B83D78AB}") +CLSID_FileOpenDialog = GUID("{DC1C5A9C-E88A-4dde-A5A1-60F82A20AEF7}") +CLSID_FileSaveDialog = GUID("{C0B4E2F3-BA21-4773-8DBA-335EC946EB8B}") + + +class IShellItem(comtypes.IUnknown): + _case_insensitive_: bool = True + _iid_: GUID = IID_IShellItem + _methods_ = [ + COMMETHOD([], HRESULT, "BindToHandler", + (["in"], POINTER(comtypes.IUnknown), "pbc"), + (["in"], POINTER(GUID), "bhid"), + (["in"], POINTER(GUID), "riid"), + (["out"], POINTER(c_void_p), "ppv")), + COMMETHOD([], HRESULT, "GetParent", + (["out"], POINTER(POINTER(comtypes.IUnknown)), "ppsi")), + COMMETHOD([], HRESULT, "GetDisplayName", + (["in"], c_ulong, "sigdnName"), + (["out"], POINTER(LPWSTR), "ppszName")), + COMMETHOD([], HRESULT, "GetAttributes", + (["in"], c_ulong, "sfgaoMask"), + (["out"], POINTER(c_ulong), "psfgaoAttribs")), + COMMETHOD([], HRESULT, "Compare", + (["in"], POINTER(comtypes.IUnknown), "psi"), + (["in"], c_ulong, "hint"), + (["out"], POINTER(c_int), "piOrder")) + ] + QueryInterface: Callable[[GUID, comtypes.IUnknown], int] + AddRef: Callable[[], int] + Release: Callable[[], int] + BindToHandler: Callable[[comtypes.IUnknown, GUID, GUID, c_void_p], int] + GetParent: Callable[[], comtypes.IUnknown] + GetDisplayName: Callable[[Union[c_ulong, int]], str] + GetAttributes: Callable[[Union[c_ulong, int]], int] + Compare: Callable[[comtypes.IUnknown, c_ulong, c_int], int] + + +class IShellItemArray(comtypes.IUnknown): + _case_insensitive_: bool = True + _iid_: GUID = IID_IShellItemArray + _methods_ = [ + COMMETHOD([], HRESULT, "BindToHandler", + (["in"], POINTER(comtypes.IUnknown), "pbc"), + (["in"], POINTER(GUID), "bhid"), + (["in"], POINTER(GUID), "riid"), + (["out"], POINTER(c_void_p), "ppv")), + COMMETHOD([], HRESULT, "GetPropertyStore", + (["in"], c_ulong, "flags"), + (["in"], POINTER(GUID), "riid"), + (["out"], POINTER(c_void_p), "ppv")), + COMMETHOD([], HRESULT, "GetPropertyDescriptionList", + (["in"], POINTER(GUID), "keyType"), + (["in"], POINTER(GUID), "riid"), + (["out"], POINTER(c_void_p), "ppv")), + COMMETHOD([], HRESULT, "GetAttributes", + (["in"], c_ulong, "attribFlags"), + (["in"], c_ulong, "sfgaoMask"), + (["out"], POINTER(c_ulong), "psfgaoAttribs")), + COMMETHOD([], HRESULT, "GetCount", + (["out"], POINTER(c_uint), "pdwNumItems")), + COMMETHOD([], HRESULT, "GetItemAt", + (["in"], c_uint, "dwIndex"), + (["out"], POINTER(POINTER(IShellItem)), "ppsi")), + COMMETHOD([], HRESULT, "EnumItems", + (["out"], POINTER(POINTER(comtypes.IUnknown)), "ppenumShellItems")) + ] + QueryInterface: Callable[[GUID, comtypes.IUnknown], int] + AddRef: Callable[[], int] + Release: Callable[[], int] + BindToHandler: Callable[[comtypes.IUnknown, GUID, GUID], int] + GetPropertyStore: Callable[[int, GUID], c_void_p] + GetPropertyDescriptionList: Callable[[GUID, GUID], c_void_p] + GetAttributes: Callable[[int, int], int] + GetCount: Callable[[], int] + GetItemAt: Callable[[Union[int, int]], IShellItem] + EnumItems: Callable[[], comtypes.IUnknown] + + +class IModalWindow(comtypes.IUnknown): + _case_insensitive_: bool = True + _iid_: GUID = IID_IModalWindow + _methods_ = [ + COMMETHOD([], HRESULT, "Show", + (["in"], HWND, "hwndParent")) + ] + Show: Callable[[Union[int, HWND]], int] + + +class IFileDialog(IModalWindow): + _iid_: GUID = IID_IFileDialog + _methods_ = [ + COMMETHOD([], HRESULT, "SetFileTypes", + (["in"], c_uint, "cFileTypes"), + (["in"], POINTER(c_void_p), "rgFilterSpec")), + COMMETHOD([], HRESULT, "SetFileTypeIndex", + (["in"], c_uint, "iFileType")), + COMMETHOD([], HRESULT, "GetFileTypeIndex", + (["out"], POINTER(c_uint), "piFileType")), + COMMETHOD([], HRESULT, "Advise", + (["in"], POINTER(comtypes.IUnknown), "pfde"), + (["out"], POINTER(DWORD), "pdwCookie")), + COMMETHOD([], HRESULT, "Unadvise", + (["in"], DWORD, "dwCookie")), + COMMETHOD([], HRESULT, "SetOptions", + (["in"], c_uint, "fos")), + COMMETHOD([], HRESULT, "GetOptions", + (["out"], POINTER(DWORD), "pfos")), + COMMETHOD([], HRESULT, "SetDefaultFolder", + (["in"], POINTER(IShellItem), "psi")), + COMMETHOD([], HRESULT, "SetFolder", + (["in"], POINTER(IShellItem), "psi")), + COMMETHOD([], HRESULT, "GetFolder", + (["out"], POINTER(POINTER(IShellItem)), "ppsi")), + COMMETHOD([], HRESULT, "GetCurrentSelection", + (["out"], POINTER(POINTER(IShellItem)), "ppsi")), + COMMETHOD([], HRESULT, "SetFileName", + (["in"], LPCWSTR, "pszName")), + COMMETHOD([], HRESULT, "GetFileName", + (["out"], POINTER(LPWSTR), "pszName")), + COMMETHOD([], HRESULT, "SetTitle", + (["in"], LPCWSTR, "pszTitle")), + COMMETHOD([], HRESULT, "SetOkButtonLabel", + (["in"], LPCWSTR, "pszText")), + COMMETHOD([], HRESULT, "SetFileNameLabel", + (["in"], LPCWSTR, "pszLabel")), + COMMETHOD([], HRESULT, "GetResult", + (["out"], POINTER(POINTER(IShellItem)), "ppsi")), + COMMETHOD([], HRESULT, "AddPlace", + (["in"], POINTER(IShellItem), "psi"), + (["in"], c_int, "fdap")), + COMMETHOD([], HRESULT, "SetDefaultExtension", + (["in"], LPCWSTR, "pszDefaultExtension")), + COMMETHOD([], HRESULT, "Close", + (["in"], HRESULT, "hr")), + COMMETHOD([], HRESULT, "SetClientGuid", + (["in"], POINTER(GUID), "guid")), + COMMETHOD([], HRESULT, "ClearClientData"), + COMMETHOD([], HRESULT, "SetFilter", + (["in"], POINTER(comtypes.IUnknown), "pFilter")) # IShellItemFilter + ] + SetFileTypes: Callable[[Union[c_uint, int], c_void_p], int] + SetFileTypeIndex: Callable[[c_uint], int] + GetFileTypeIndex: Callable[[], int] + Advise: Callable[[Union[comtypes.IUnknown, comtypes.COMObject]], int] + Unadvise: Callable[[int], int] + SetOptions: Callable[[Union[int, int]], int] + GetOptions: Callable[[], int] + SetDefaultFolder: Callable[[IShellItem], int] + SetFolder: Callable[[IShellItem], int] + GetFolder: Callable[[], IShellItem] + GetCurrentSelection: Callable[[], IShellItem] + SetFileName: Callable[[str], int] + GetFileName: Callable[[], str] + SetTitle: Callable[[str], int] + SetOkButtonLabel: Callable[[str], int] + SetFileNameLabel: Callable[[str], int] + GetResult: Callable[[], IShellItem] + AddPlace: Callable[[IShellItem, c_int], int] + SetDefaultExtension: Callable[[str], int] + Close: Callable[[HRESULT], int] + SetClientGuid: Callable[[GUID], int] + ClearClientData: Callable[[], int] + SetFilter: Callable[[comtypes.IUnknown], int] + + +class IFileOpenDialog(IFileDialog): + _case_insensitive_: bool = True + _iid_: GUID = IID_IFileOpenDialog + _methods_ = [ + COMMETHOD([], HRESULT, "GetResults", + (["out"], POINTER(POINTER(IShellItemArray)), "ppenum")), + COMMETHOD([], HRESULT, "GetSelectedItems", + (["out"], POINTER(POINTER(IShellItemArray)), "ppsai")) + ] + GetResults: Callable[[], IShellItemArray] + GetSelectedItems: Callable[[], IShellItemArray] + + +class IFileSaveDialog(IFileDialog): + _case_insensitive_: bool = True + _iid_: GUID = IID_IFileSaveDialog + _methods_ = [ + COMMETHOD([], HRESULT, "SetSaveAsItem", + (["in"], POINTER(IShellItem), "psi")), + COMMETHOD([], HRESULT, "SetProperties", + (["in"], POINTER(comtypes.IUnknown), "pStore")), + COMMETHOD([], HRESULT, "SetCollectedProperties", + (["in"], POINTER(comtypes.IUnknown), "pList"), + (["in"], BOOL, "fAppendDefault")), + COMMETHOD([], HRESULT, "GetProperties", + (["out"], POINTER(POINTER(comtypes.IUnknown)), "ppStore")), + COMMETHOD([], HRESULT, "ApplyProperties", + (["in"], POINTER(IShellItem), "psi"), + (["in"], POINTER(comtypes.IUnknown), "pStore"), + (["in"], HWND, "hwnd"), + (["in"], POINTER(comtypes.IUnknown), "pSink")) + ] + SetSaveAsItem: Callable[[IShellItem], int] + SetProperties: Callable[[comtypes.IUnknown], int] + SetCollectedProperties: Callable[[comtypes.IUnknown, BOOL], int] + GetProperties: Callable[[comtypes.IUnknown], int] + ApplyProperties: Callable[[IShellItem, comtypes.IUnknown, HWND, comtypes.IUnknown], int] class BaseDialog: @@ -73,7 +278,14 @@ def show(self, host_window, future): class MessageDialog(BaseDialog): - def __init__(self, title, message, buttons, icon, success_result=None): + def __init__( + self, + title, + message, + buttons, + icon, + success_result=None, + ): super().__init__() self.message = message self.title = title @@ -82,8 +294,12 @@ def __init__(self, title, message, buttons, icon, success_result=None): self.success_result = success_result def _show(self): - style = self.buttons | self.icon - return_value = MessageBoxW(0, self.message, self.title, style) + return_value = WinForms.MessageBox.Show( + self.message, + self.title, + self.buttons, + self.icon, + ) if self.success_result: self.future.set_result(return_value == self.success_result) else: @@ -92,150 +308,151 @@ def _show(self): class InfoDialog(MessageDialog): def __init__(self, title, message): - super().__init__(title, message, MB_OK, MB_ICONASTERISK) + super().__init__( + title, + message, + MessageBoxButtons.OK, + MessageBoxIcon.Information, + ) class QuestionDialog(MessageDialog): def __init__(self, title, message): - super().__init__(title, message, MB_YESNO, MB_ICONQUESTION, success_result=IDYES) + super().__init__( + title, + message, + MessageBoxButtons.YesNo, + MessageBoxIcon.Information, + success_result=DialogResult.Yes, + ) class ConfirmDialog(MessageDialog): def __init__(self, title, message): - super().__init__(title, message, MB_OKCANCEL, MB_ICONEXCLAMATION, success_result=IDOK) + super().__init__( + title, + message, + MessageBoxButtons.OKCancel, + MessageBoxIcon.Warning, + success_result=DialogResult.OK, + ) class ErrorDialog(MessageDialog): def __init__(self, title, message=None): - super().__init__(title, message, MB_OK, MB_ICONHAND) + super().__init__( + title, + message, + WinForms.MessageBoxButtons.OK, + WinForms.MessageBoxIcon.Error, + ) class StackTraceDialog(BaseDialog): def __init__(self, title, message, content, retry): super().__init__() - self.title = title - self.message = message - self.content = content - self.retry = retry - self.hwnd = None - self.hInstance = windll.kernel32.GetModuleHandleW(None) - self._register_class() - - def _register_class(self): - wnd_class = ctypesWNDCLASS() - wnd_class.cbSize = sizeof(ctypesWNDCLASS) - wnd_class.style = 0 - wnd_class.lpfnWndProc = WNDPROC(self._wnd_proc) - wnd_class.cbClsExtra = 0 - wnd_class.cbWndExtra = 0 - wnd_class.hInstance = self.hInstance - wnd_class.hIcon = windll.user32.LoadIconW(None, c_wchar_p(CursorType.ARROW.value)) # IDI_APPLICATION - wnd_class.hCursor = windll.user32.LoadCursorW(None, c_wchar_p(CursorType.ARROW.value)) # IDC_ARROW - wnd_class.hbrBackground = windll.gdi32.GetStockObject(15) # WHITE_BRUSH - wnd_class.lpszClassName = "StackTraceDialogClass" - wnd_class.hIconSm = windll.user32.LoadIconW(None, c_wchar_p(CursorType.ARROW.value)) # IDI_APPLICATION - - self.class_atom = LPCWSTR(windll.user32.RegisterClassExW(byref(wnd_class))) - - def _create_dialog(self): - self.hwnd = windll.user32.CreateWindowExW( - WS_EX_DLGMODALFRAME, - self.class_atom, - self.title, - WS_OVERLAPPEDWINDOW | WS_VISIBLE, - 100, 100, 540, 320, - None, None, self.hInstance, None - ) - # SetWindowTextW is used to set the window title - windll.user32.SetWindowTextW(self.hwnd, self.title) - - # Create controls - self._create_controls() - - windll.user32.ShowWindow(self.hwnd, SW_SHOW) - windll.user32.UpdateWindow(self.hwnd) - - def _create_controls(self): - # Create the label - hLabel = windll.user32.CreateWindowExW( - 0, "STATIC", self.message, - WS_CHILD | WS_VISIBLE, - 10, 10, 520, 20, - self.hwnd, None, self.hInstance, None - ) - # Create the multiline text box for the stack trace - hEdit = windll.user32.CreateWindowExW( - 0, "EDIT", self.content, - WS_CHILD | WS_VISIBLE | ES_MULTILINE | ES_AUTOVSCROLL | ES_READONLY | WS_VSCROLL, - 10, 30, 504, 210, - self.hwnd, None, self.hInstance, None + self.native = WinForms.Form() + self.native.MinimizeBox = False + self.native.FormBorderStyle = self.native.FormBorderStyle.FixedSingle + self.native.MaximizeBox = False + self.native.FormClosing += WeakrefCallable(self.winforms_FormClosing) + self.native.Width = 540 + self.native.Height = 320 + self.native.Text = title + + # The top-of-page introductory message + textLabel = WinForms.Label() + textLabel.Left = 10 + textLabel.Top = 10 + textLabel.Width = 520 + textLabel.Alignment = ContentAlignment.MiddleCenter + textLabel.Text = message + + self.native.Controls.Add(textLabel) + + # A scrolling text box for the stack trace. + trace = WinForms.RichTextBox() + trace.Left = 10 + trace.Top = 30 + trace.Width = 504 + trace.Height = 210 + trace.Multiline = True + trace.ReadOnly = True + trace.Font = WinFont( + FontFamily.GenericMonospace, + float(SystemFonts.DefaultFont.Size), + FontStyle.Regular, ) + trace.Text = content - # Create buttons based on whether retry is needed - if self.retry: - hRetry = windll.user32.CreateWindowExW( - 0, "BUTTON", "&Retry", - WS_CHILD | WS_VISIBLE, - 290, 250, 100, 30, - self.hwnd, IDOK, self.hInstance, None - ) - hQuit = windll.user32.CreateWindowExW( - 0, "BUTTON", "&Quit", - WS_CHILD | WS_VISIBLE, - 400, 250, 100, 30, - self.hwnd, IDCANCEL, self.hInstance, None - ) + self.native.Controls.Add(trace) + + # Add acceptance/close buttons + if retry: + retry = WinForms.Button() + retry.Left = 290 + retry.Top = 250 + retry.Width = 100 + retry.Text = "&Retry" + retry.Click += WeakrefCallable(self.winforms_Click_retry) + + self.native.Controls.Add(retry) + + quit = WinForms.Button() + quit.Left = 400 + quit.Top = 250 + quit.Width = 100 + quit.Text = "&Quit" + quit.Click += WeakrefCallable(self.winforms_Click_quit) + + self.native.Controls.Add(quit) else: - hOk = windll.user32.CreateWindowExW( - 0, "BUTTON", "&OK", - WS_CHILD | WS_VISIBLE, - 400, 250, 100, 30, - self.hwnd, IDOK, self.hInstance, None - ) + accept = WinForms.Button() + accept.Left = 400 + accept.Top = 250 + accept.Width = 100 + accept.Text = "&OK" + accept.Click += WeakrefCallable(self.winforms_Click_accept) - def _wnd_proc(self, hwnd, msg, wparam, lparam): - if msg == WM_COMMAND: - control_id = wparam & 0xFFFF - if control_id == IDOK: - self._handle_ok() - elif control_id == IDCANCEL: - self._handle_cancel() - windll.user32.DestroyWindow(hwnd) - elif msg == WM_DESTROY: - windll.user32.DestroyWindow(hwnd) - return windll.user32.DefWindowProcW(hwnd, msg, wparam, lparam) + self.native.Controls.Add(accept) def _show(self): - self._create_dialog() - # MessageBox to show dialog (optional) - windll.user32.MessageBoxW(self.hwnd, self.message, self.title, MB_OK) - if self.retry: - self.future.set_result(True) - else: - self.future.set_result(None) + self.native.ShowDialog() - def _handle_ok(self): - self.future.set_result(True) - windll.user32.DestroyWindow(self.hwnd) + def winforms_FormClosing(self, sender, event): + # If the close button is pressed, the future won't be done. + # We cancel this event to prevent the dialog from closing. + # If a button is pressed, the future will be set, and a close + # event will be triggered. + if not self.future.done(): + event.Cancel = True # pragma: no cover - def _handle_cancel(self): + def winforms_Click_quit(self, sender, event): self.future.set_result(False) - windll.user32.DestroyWindow(self.hwnd) + self.native.Close() + + def winforms_Click_retry(self, sender, event): + self.future.set_result(True) + self.native.Close() + + def winforms_Click_accept(self, sender, event): + self.future.set_result(None) + self.native.Close() class FileDialog(BaseDialog): def __init__( self, - native: IFileOpenDialog | IFileSaveDialog, + native: Union[IFileOpenDialog, IFileSaveDialog], title: str, - initial_directory: os.PathLike | str, + initial_directory: Union[os.PathLike, str], *, - filename: str | None = None, - file_types: list[str] | None = None, + filename: Optional[str] = None, + file_types: Optional[List[str]] = None, ): super().__init__() - self.native: IFileOpenDialog | IFileSaveDialog = native + self.native: Union[IFileOpenDialog, IFileSaveDialog] = native self._set_title(title) if filename is not None: @@ -245,7 +462,7 @@ def __init__( self._set_initial_directory(str(initial_directory)) if file_types is not None: - filters: list[tuple[str, str]] = [ + filters: List[Tuple[str, str]] = [ (f"{ext.upper()} files", f"*.{ext}") for ext in file_types ] @@ -266,10 +483,10 @@ def _show(self): else: self.future.set_result(None) - def _set_title(self, title: str): + def _set_title(self, title): self.native.SetTitle(title) - def _set_initial_directory(self, initial_directory: os.PathLike | str | None): + def _set_initial_directory(self, initial_directory): if initial_directory is None: return folder_path: Path = Path(initial_directory).resolve() @@ -293,8 +510,8 @@ def __init__( self, title: str, filename: str, - initial_directory: os.PathLike | str, - file_types: list[str], + initial_directory: Union[os.PathLike, str], + file_types: List[str], ): super().__init__( comtypes.client.CreateObject(CLSID_FileSaveDialog, interface=IFileSaveDialog), @@ -314,8 +531,8 @@ class OpenFileDialog(FileDialog): def __init__( self, title: str, - initial_directory: os.PathLike | str, - file_types: list[str], + initial_directory: Union[os.PathLike, str], + file_types: List[str], multiple_select: bool, ): super().__init__( @@ -331,9 +548,9 @@ def selected_paths(self): # This is a stub method; we provide functionality using the COM API return self._get_filenames() - def _get_filenames(self) -> list[Path]: + def _get_filenames(self) -> List[Path]: assert isinstance(self.native, IFileOpenDialog) - results: list[Path] = [] + results: List[Path] = [] shell_item_array: IShellItemArray = self.native.GetResults() item_count: int = shell_item_array.GetCount() for i in range(item_count): @@ -347,7 +564,7 @@ class SelectFolderDialog(FileDialog): def __init__( self, title: str, - initial_directory: os.PathLike | str, + initial_directory: Union[os.PathLike, str], multiple_select: bool, ): super().__init__( @@ -356,9 +573,9 @@ def __init__( initial_directory, ) self.native.SetOptions(FileOpenOptions.FOS_PICKFOLDERS) - self.multiple_select: bool = multiple_select + self.multiple_select = multiple_select - def _get_filenames(self) -> list[Path] | Path: + def _get_filenames(self) -> Union[List[Path], Path]: shell_item: IShellItem = self.native.GetResult() display_name: str = shell_item.GetDisplayName(0x80058000) # SIGDN_FILESYSPATH return [Path(display_name)] if self.multiple_select else Path(display_name) diff --git a/winforms/src/toga_winforms/libs/py_wrappers/__init__.py b/winforms/src/toga_winforms/libs/py_wrappers/__init__.py deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/winforms/src/toga_winforms/libs/py_wrappers/com/__init__.py b/winforms/src/toga_winforms/libs/py_wrappers/com/__init__.py deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/winforms/src/toga_winforms/libs/py_wrappers/com/com_helpers.py b/winforms/src/toga_winforms/libs/py_wrappers/com/com_helpers.py deleted file mode 100644 index 1ed7802ffc..0000000000 --- a/winforms/src/toga_winforms/libs/py_wrappers/com/com_helpers.py +++ /dev/null @@ -1,163 +0,0 @@ -from __future__ import annotations - -from contextlib import contextmanager -from ctypes import POINTER, PyDLL, byref, c_void_p, py_object -from ctypes.wintypes import BOOL -from typing import TYPE_CHECKING - -from comtypes import COMObject - -from toga_winforms.libs.py_wrappers.hresult import HRESULT - -if TYPE_CHECKING: - from ctypes import _NamedFuncPointer - - from _win32typing import ( # pyright: ignore[reportMissingModuleSource] - PyIBindCtx, - PyIUnknown, - ) - from comtypes import IUnknown - - -@contextmanager -def HandleCOMCall(action_desc: str = "Unspecified COM function"): - """ - Context manager for handling COM function calls. - - This function facilitates the execution of COM calls by providing a mechanism to manage errors effectively. - It yields a callable that checks the result of the COM operation and raises exceptions if the result - indicates an error. - - Args: - action_desc (str): A description of the COM function being called. Defaults to "Unspecified COM function". - - Returns: - Callable: A function that takes an HRESULT value and raises an exception if the value indicates an error. - - Raises: - HRESULT: If an error occurs during the COM call, an HRESULT exception is raised with a descriptive message. - - Examples: - with HandleCOMCall("My COM Function") as check_hr: - hr = some_com_function() - check_hr(hr) - """ - - print(f"Attempt to call COM func {action_desc}") - try: - from comtypes import ( - COMError, # pyright: ignore[reportMissingTypeStubs, reportMissingModuleSource] - ) - except ImportError: - COMError = OSError - future_error_msg = f"An error has occurred in win32 COM function '{action_desc}'" - try: # sourcery skip: raise-from-previous-error - # Yield back a callable function that will raise if hr is nonzero. - yield lambda hr: HRESULT(hr).raise_for_status(hr, future_error_msg) and hr or hr - except (COMError, OSError) as e: - errcode = getattr(e, "winerror", getattr(e, "hresult", None)) - if errcode is None: - raise - raise HRESULT(errcode).exception(future_error_msg) # noqa: B904 # pyright: ignore[reportAttributeAccessIssue] - - -def comtypes2pywin( - ptr: COMObject, - interface: type[IUnknown] | None = None, -) -> PyIUnknown: - """Convert a comtypes pointer 'ptr' into a pythoncom - PyI object. - - 'interface' specifies the interface we want; it must be a comtypes - interface class. The interface must be implemented by the object; - and the interface must be known to pythoncom. - - If 'interface' is specified, comtypes.IUnknown is used. - """ - import comtypes # pyright: ignore[reportMissingTypeStubs, reportMissingModuleSource] - import pythoncom - if interface is None: - interface = comtypes.IUnknown - # ripped from - # https://github.com/enthought/comtypes/blob/main/comtypes/test/test_win32com_interop.py - # We use the PyCom_PyObjectFromIUnknown function in pythoncomxxx.dll to - # convert a comtypes COM pointer into a pythoncom COM pointer. - # This is the C prototype; we must pass 'True' as third argument: - # PyObject *PyCom_PyObjectFromIUnknown(IUnknown *punk, REFIID riid, BOOL bAddRef) - _PyCom_PyObjectFromIUnknown: _NamedFuncPointer = PyDLL(pythoncom.__file__).PyCom_PyObjectFromIUnknown - _PyCom_PyObjectFromIUnknown.restype = py_object - _PyCom_PyObjectFromIUnknown.argtypes = (POINTER(comtypes.IUnknown), c_void_p, BOOL) - return _PyCom_PyObjectFromIUnknown(ptr, byref(interface._iid_), True) # noqa: FBT003, SLF001 - - -def register_idispatch_object( - com_object: COMObject, - name: str, - interface: type[IUnknown] | None = None, -) -> PyIBindCtx: - import pythoncom - ctx: PyIBindCtx = pythoncom.CreateBindCtx() - py_data: PyIUnknown = comtypes2pywin(com_object, interface) - ctx.RegisterObjectParam(name, py_data) - return ctx - - -if __name__ == "__main__": - # Small test. - from ctypes.wintypes import WIN32_FIND_DATAW - from typing import TYPE_CHECKING, ClassVar, Sequence - - import comtypes - from comtypes import GUID - - from toga_winforms.libs.py_wrappers.com.interfaces import SIGDN - from toga_winforms.libs.py_wrappers.hresult import S_OK - try: - from win32com.shell import shell # pyright: ignore[reportMissingModuleSource] - except ModuleNotFoundError: - raise RuntimeError("Small test requires `pip install pipwin32`") - - if TYPE_CHECKING: - from ctypes import _CArgObject, _Pointer - - from _win32typing import ( - PyIShellItem, # pyright: ignore[reportMissingModuleSource] - ) - from comtypes._memberspec import ( - _ComMemberSpec, # pyright: ignore[reportMissingTypeStubs] - ) - from typing_extensions import Self - IID_IFileSystemBindData = GUID("{01e18d10-4d8b-11d2-855d-006008059367}") - - class IFileSystemBindData(comtypes.IUnknown): - """The IFileSystemBindData interface - https://learn.microsoft.com/en-us/windows/win32/api/shobjidl_core/nn-shobjidl_core-ifilesystembinddata. - """ - _iid_ = IID_IFileSystemBindData - _methods_: ClassVar[list[_ComMemberSpec]] = [ - comtypes.COMMETHOD([], HRESULT, "SetFindData", - (["in"], POINTER(WIN32_FIND_DATAW), "pfd")), - comtypes.COMMETHOD([], HRESULT, "GetFindData", - (["out"], POINTER(WIN32_FIND_DATAW), "pfd")) - ] - - class FileSystemBindData(comtypes.COMObject): - """Implements the IFileSystemBindData interface: - https://learn.microsoft.com/en-us/windows/win32/api/shobjidl_core/nn-shobjidl_core-ifilesystembinddata. - """ - _com_interfaces_: Sequence[type[comtypes.IUnknown]] = [IFileSystemBindData] - - def IFileSystemBindData_SetFindData(self: Self, this: Self, pfd: _Pointer | _CArgObject) -> HRESULT: - self.pfd: _Pointer = pfd # pyright: ignore[reportAttributeAccessIssue] - return S_OK - - def IFileSystemBindData_GetFindData(self: Self, this: Self, pfd: _Pointer | _CArgObject) -> HRESULT: - return S_OK - find_data = WIN32_FIND_DATAW() # from wintypes - bind_data: FileSystemBindData = FileSystemBindData() # pyright: ignore[reportAssignmentType] - bind_data.IFileSystemBindData_SetFindData(bind_data, byref(find_data)) - ctx: PyIBindCtx = register_idispatch_object(bind_data, "File System Bind Data") - - item: PyIShellItem = shell.SHCreateItemFromParsingName( - r"Z:\blah\blah", ctx, shell.IID_IShellItem2) - print(item.GetDisplayName(SIGDN.SIGDN_DESKTOPABSOLUTEPARSING)) # prints Z:\blah\blah diff --git a/winforms/src/toga_winforms/libs/py_wrappers/com/com_types.py b/winforms/src/toga_winforms/libs/py_wrappers/com/com_types.py deleted file mode 100644 index 8b13789179..0000000000 --- a/winforms/src/toga_winforms/libs/py_wrappers/com/com_types.py +++ /dev/null @@ -1 +0,0 @@ - diff --git a/winforms/src/toga_winforms/libs/py_wrappers/com/interfaces.py b/winforms/src/toga_winforms/libs/py_wrappers/com/interfaces.py deleted file mode 100644 index 272f8fa2c5..0000000000 --- a/winforms/src/toga_winforms/libs/py_wrappers/com/interfaces.py +++ /dev/null @@ -1,1401 +0,0 @@ -from __future__ import annotations - -from ctypes import ( - POINTER, - Structure, - byref, - c_bool, - c_char_p, - c_int, - c_uint, - c_ulong, - c_void_p, - c_wchar_p, - windll, -) -from ctypes import POINTER as C_POINTER -from ctypes.wintypes import BOOL, DWORD, HWND, LPCWSTR, LPWSTR, ULONG -from enum import IntFlag -from typing import TYPE_CHECKING, Callable, ClassVar, Sequence - -import comtypes # pyright: ignore[reportMissingTypeStubs] -from comtypes import ( - COMMETHOD, # pyright: ignore[reportMissingTypeStubs] - GUID, -) - -from toga_winforms.libs.py_wrappers.hresult import ( # pyright: ignore[reportMissingTypeStubs] - HRESULT, - S_OK, -) - -if TYPE_CHECKING: - from ctypes import Array, _CData, _FuncPointer, _Pointer - - from comtypes._memberspec import ( - _ComMemberSpec, # pyright: ignore[reportMissingTypeStubs] - ) - -# GUID Definitions -IID_IUnknown = GUID("{00000000-0000-0000-C000-000000000046}") -IID_IDispatch = GUID("{00020400-0000-0000-C000-000000000046}") -IID_IClassFactory = GUID("{00000001-0000-0000-C000-000000000046}") -IID_IStream = GUID("{0000000c-0000-0000-C000-000000000046}") -IID_IStorage = GUID("{0000000b-0000-0000-C000-000000000046}") -IID_IBindCtx = GUID("{0000000e-0000-0000-C000-000000000046}") -IID_IEnumShellItems = GUID("{70629033-E363-4A28-A567-0DB78006E6D7}") -IID_IContextMenu = GUID("{000214e4-0000-0000-c000-000000000046}") -IID_IContextMenu2 = GUID("{000214f4-0000-0000-c000-000000000046}") -IID_IContextMenu3 = GUID("{bcfce0a0-ec17-11d0-8d10-00a0c90f2719}") -IID_IShellFolder = GUID("{000214E6-0000-0000-C000-000000000046}") -IID_IShellFolder2 = GUID("{93F2F68C-1D1B-11D3-A30E-00C04F79ABD1}") -IID_IShellItem = GUID("{43826D1E-E718-42EE-BC55-A1E261C37BFE}") -IID_IShellItem2 = GUID("{7E9FB0D3-919F-4307-AB2E-9B1860310C93}") -IID_IShellLibrary = GUID("{11A66EFA-382E-451A-9234-1E0E12EF3085}") -IID_IShellItemArray = GUID("{B63EA76D-1F85-456F-A19C-48159EFA858B}") -IID_IShellItemFilter = GUID("{2659B475-EEB8-48B7-8F07-B378810F48CF}") -IID_IShellView = GUID("{000214e3-0000-0000-c000-000000000046}") -IID_IModalWindow = GUID("{B4DB1657-70D7-485E-8E3E-6FCB5A5C1802}") -IID_IFileDialog = GUID("{42F85136-DB7E-439C-85F1-E4075D135FC8}") -IID_IFileDialog2 = GUID("{61744FC7-85B5-4791-A9B0-272276309B13}") -IID_IFileSaveDialog = GUID("{84BCCD23-5FDE-4CDB-AEA4-AF64B83D78AB}") -IID_IFileSaveDialogOld = GUID("{2804B74C-AC16-4398-9DC0-DB83F5B7ED14}") -IID_IFileSaveDialogPrivate = GUID("{6CB95A6A-88B6-4DC4-B3EA-3A776D1E8EFF}") -IID_IFileOpenDialog = GUID("{D57C7288-D4AD-4768-BE02-9D969532D960}") -IID_IFileDialogEvents = GUID("{973510DB-7D7F-452B-8975-74A85828D354}") -IID_FileDialogPermissionAttribute = GUID("{0CCCA629-440F-313E-96CD-BA1B4B4997F7}") -IID_FileDialogPermission = GUID("{A8B7138C-8932-3D78-A585-A91569C743AC}") -IID_IFileDialogPrivate = GUID("{9EA5491C-89C8-4BEF-93D3-7F665FB82A33}") -IID_IFileDialogCustomize = GUID("{E6FDD21A-163F-4975-9C8C-A69F1BA37034}") -IID_IFileDialogEventsPrivate = GUID("{050E9E69-BAEA-4C08-AD6A-61666DD32E96}") -IID_IFileDialogControlEvents = GUID("{36116642-D713-4B97-9B83-7484A9D00433}") -IID_IFileDialogResultHandler = GUID("{42841501-194F-478F-9B4C-78985419DA53}") -IID_IShellLink = GUID("{000214f9-0000-0000-c000-000000000046}") -IID_IShellLinkDataList = GUID("{45E2B4AE-B1C3-11D0-BA91-00C04FD7A083}") -IID_IPropertyStore = GUID("{886D8EEB-8CF2-4446-8D02-CDBA1DBDCF99}") -IID_IFileOperationProgressSink = GUID("{04B0F1A7-9490-44BC-96E1-4296A31252E2}") -IID_IFileOperation = GUID("{94EA2B94-E9CC-49E0-C0E3-D20A7D91AA98}") -CLSID_FileOperation = GUID("{3AD05575-8857-4850-9277-11B85BDB8E09}") -CLSID_FileDialog = GUID("{3D9C8F03-50D4-4E40-BB11-70E74D3F10F3}") -CLSID_FileOpenDialog = GUID("{DC1C5A9C-E88A-4dde-A5A1-60F82A20AEF7}") -CLSID_FileOpenDialogLegacy = GUID("{725F645B-EAED-4fc5-B1C5-D9AD0ACCBA5E}") -CLSID_FileSaveDialog = GUID("{C0B4E2F3-BA21-4773-8DBA-335EC946EB8B}") -CLSID_FileSaveDialogLegacy = GUID("{AF02484C-A0A9-4669-9051-058AB12B9195}") -CLSID_ShellItemArrayShellNamespacehelper = GUID("{26671179-2ec2-42bf-93d3-64108589cad5}") -CLSID_ShellItemArrayShellNamespacehelper = GUID("{b77b1cbf-e827-44a9-a33a-6ccfeeaa142a}") # redef?? -CLSID_ShellItemArrayShellNamespacehelper = GUID("{CDC82860-468D-4d4e-B7E7-C298FF23AB2C}") # redef?? -CLSID_ShellItemArrayShellNamespacehelper = GUID("{F6166DAD-D3BE-4ebd-8419-9B5EAD8D0EC7}") # redef?? -CLSID_ShellLibraryAPI = GUID("{d9b3211d-e57f-4426-aaef-30a806add397}") -CLSID_ShellFileSystemFolder = GUID("{F3364BA0-65B9-11CE-A9BA-00AA004AE837}") -CLSID_ShellBindStatusCallbackProxy = GUID("{2B4F54B1-3D6D-11d0-8258-00C04FD5AE38}") -CLSID_ShellURL = GUID("{4bec2015-bfa1-42fa-9c0c-59431bbe880e}") -CLSID_ShellDropTarget = GUID("{4bf684f8-3d29-4403-810d-494e72c4291b}") -CLSID_ShellNameSpace = GUID("{55136805-B2DE-11D1-B9F2-00A0C98BC547}") - - -# Constants -class FileOpenOptions(IntFlag): - FOS_UNKNOWN1 = 0x00000001 - FOS_OVERWRITEPROMPT = 0x00000002 - FOS_STRICTFILETYPES = 0x00000004 - FOS_NOCHANGEDIR = 0x00000008 - FOS_UNKNOWN2 = 0x00000010 - FOS_PICKFOLDERS = 0x00000020 - FOS_FORCEFILESYSTEM = 0x00000040 - FOS_ALLNONSTORAGEITEMS = 0x00000080 - FOS_NOVALIDATE = 0x00000100 - FOS_ALLOWMULTISELECT = 0x00000200 - FOS_UNKNOWN4 = 0x00000400 - FOS_PATHMUSTEXIST = 0x00000800 - FOS_FILEMUSTEXIST = 0x00001000 - FOS_CREATEPROMPT = 0x00002000 - FOS_SHAREAWARE = 0x00004000 - FOS_NOREADONLYRETURN = 0x00008000 - FOS_NOTESTFILECREATE = 0x00010000 - FOS_HIDEMRUPLACES = 0x00020000 - FOS_HIDEPINNEDPLACES = 0x00040000 - FOS_UNKNOWN5 = 0x00080000 - FOS_NODEREFERENCELINKS = 0x00100000 - FOS_UNKNOWN6 = 0x00200000 - FOS_UNKNOWN7 = 0x00400000 - FOS_UNKNOWN8 = 0x00800000 - FOS_UNKNOWN9 = 0x01000000 - FOS_DONTADDTORECENT = 0x02000000 - FOS_UNKNOWN10 = 0x04000000 - FOS_UNKNOWN11 = 0x08000000 - FOS_FORCESHOWHIDDEN = 0x10000000 - FOS_DEFAULTNOMINIMODE = 0x20000000 - FOS_FORCEPREVIEWPANEON = 0x40000000 - FOS_UNKNOWN12 = 0x80000000 - - -# Shell Folder Get Attributes Options -SFGAOF = c_ulong - - -class SFGAO(IntFlag): - SFGAO_CANCOPY = 0x00000001 # Objects can be copied. - SFGAO_CANMOVE = 0x00000002 # Objects can be moved. - SFGAO_CANLINK = 0x00000004 # Objects can be linked. - SFGAO_STORAGE = 0x00000008 # Objects can be stored. - SFGAO_CANRENAME = 0x00000010 # Objects can be renamed. - SFGAO_CANDELETE = 0x00000020 # Objects can be deleted. - SFGAO_HASPROPSHEET = 0x00000040 # Objects have property sheets. - SFGAO_DROPTARGET = 0x00000100 # Objects are drop targets. - SFGAO_CAPABILITYMASK = 0x00000177 # Mask for all capability flags. - SFGAO_ENCRYPTED = 0x00002000 # Object is encrypted (use alt color). - SFGAO_ISSLOW = 0x00004000 # Accessing this object is slow. - SFGAO_GHOSTED = 0x00008000 # Object is ghosted (dimmed). - SFGAO_LINK = 0x00010000 # Shortcut (link). - SFGAO_SHARE = 0x00020000 # Shared. - SFGAO_READONLY = 0x00040000 # Read-only. - SFGAO_HIDDEN = 0x00080000 # Hidden object. - SFGAO_DISPLAYATTRMASK = 0x000FC000 # Mask for display attributes. - SFGAO_FILESYSANCESTOR = 0x10000000 # May contain children with file system folders. - SFGAO_FOLDER = 0x20000000 # Is a folder. - SFGAO_FILESYSTEM = 0x40000000 # Is part of the file system. - SFGAO_HASSUBFOLDER = 0x80000000 # May contain subfolders. - SFGAO_CONTENTSMASK = 0x80000000 # Mask for contents. - SFGAO_VALIDATE = 0x01000000 # Invalidate cached information. - SFGAO_REMOVABLE = 0x02000000 # Is a removable media. - SFGAO_COMPRESSED = 0x04000000 # Object is compressed. - SFGAO_BROWSABLE = 0x08000000 # Supports browsing. - SFGAO_NONENUMERATED = 0x00100000 # Is not enumerated. - SFGAO_NEWCONTENT = 0x00200000 # New content is present. - SFGAO_CANMONIKER = 0x00400000 # Can create monikers for this item. - SFGAO_HASSTORAGE = 0x00400000 # Supports storage interfaces. - SFGAO_STREAM = 0x00400000 # Is a stream object. - SFGAO_STORAGEANCESTOR = 0x00800000 # May contain children with storage folders. - SFGAO_STORAGECAPMASK = 0x70C50008 # Mask for storage capability attributes. - SFGAO_PKEYSFGAOMASK = 0x81044000 # Attributes that are part of the PKEY_SFGAOFlags property. - - -class SIGDN(c_int): - SIGDN_NORMALDISPLAY = 0x00000000 - SIGDN_PARENTRELATIVEPARSING = 0x80018001 - SIGDN_PARENTRELATIVEFORADDRESSBAR = 0x8001C001 - SIGDN_DESKTOPABSOLUTEPARSING = 0x80028000 - SIGDN_PARENTRELATIVEEDITING = 0x80031001 - SIGDN_DESKTOPABSOLUTEEDITING = 0x8004C000 - SIGDN_FILESYSPATH = 0x80058000 - SIGDN_URL = 0x80068000 - - -class FDAP(c_int): - FDAP_BOTTOM = 0x00000000 - FDAP_TOP = 0x00000001 - - -class FDE_SHAREVIOLATION_RESPONSE(c_int): # noqa: N801 - FDESVR_DEFAULT = 0x00000000 - FDESVR_ACCEPT = 0x00000001 - FDESVR_REFUSE = 0x00000002 - - -FDE_OVERWRITE_RESPONSE = FDE_SHAREVIOLATION_RESPONSE - - -class COMFunctionPointers: - def __init__(self): - self.hOle32: _FuncPointer - self.hShell32: _FuncPointer - self.pCoInitialize: _FuncPointer - self.pCoUninitialize: _FuncPointer - self.pCoCreateInstance: _FuncPointer - self.pCoTaskMemFree: _FuncPointer - self.pSHCreateItemFromParsingName: _FuncPointer - - @staticmethod - def load_library(dll_name: str) -> _FuncPointer: - windll.kernel32.LoadLibraryW.argtypes = [LPCWSTR] - windll.kernel32.LoadLibraryW.restype = c_void_p - handle = windll.kernel32.LoadLibraryW(dll_name) - if not handle: - raise ValueError(f"Unable to load library: {dll_name}") - return handle - - @staticmethod - def resolve_function(handle: _FuncPointer, func: bytes, func_type: type[_FuncPointer]) -> _FuncPointer: - windll.kernel32.GetProcAddress.argtypes = [c_void_p, c_char_p] - windll.kernel32.GetProcAddress.restype = c_void_p - address = windll.kernel32.GetProcAddress(handle, func) - assert address is not None - return func_type(address) - - -class COMDLG_FILTERSPEC(Structure): # noqa: N801 - _fields_: Sequence[tuple[str, type[_CData]] | tuple[str, type[_CData], int]] = [ - ("pszName", LPCWSTR), - ("pszSpec", LPCWSTR) - ] - - -class IModalWindow(comtypes.IUnknown): - _case_insensitive_: bool = True - _iid_: GUID = IID_IModalWindow - _methods_: ClassVar[list[_ComMemberSpec]] = [ - COMMETHOD([], HRESULT, "Show", - (["in"], HWND, "hwndParent")) - ] - Show: Callable[[int | HWND], HRESULT] - - -class ModalWindow(comtypes.COMObject): - _com_interfaces_: Sequence[type[comtypes.IUnknown]] = [IModalWindow] - def QueryInterface(self, riid: GUID, ppvObject: comtypes.IUnknown) -> HRESULT: # noqa: N803 - return S_OK - def AddRef(self) -> ULONG: - return ULONG(-1) - def Release(self) -> ULONG: - return ULONG(-1) - def Show(self, hwndParent: HWND | int) -> HRESULT: # noqa: N803 - return S_OK - - -class IShellItem(comtypes.IUnknown): - _case_insensitive_: bool = True - _iid_: GUID = IID_IShellItem - _methods_: ClassVar[list[_ComMemberSpec]] = [ - COMMETHOD([], HRESULT, "BindToHandler", - (["in"], POINTER(comtypes.IUnknown), "pbc"), - (["in"], POINTER(GUID), "bhid"), - (["in"], POINTER(GUID), "riid"), - (["out"], POINTER(c_void_p), "ppv")), - COMMETHOD([], HRESULT, "GetParent", - (["out"], POINTER(POINTER(comtypes.IUnknown)), "ppsi")), - COMMETHOD([], HRESULT, "GetDisplayName", - (["in"], c_ulong, "sigdnName"), - (["out"], POINTER(LPWSTR), "ppszName")), - COMMETHOD([], HRESULT, "GetAttributes", - (["in"], c_ulong, "sfgaoMask"), - (["out"], POINTER(c_ulong), "psfgaoAttribs")), - COMMETHOD([], HRESULT, "Compare", - (["in"], POINTER(comtypes.IUnknown), "psi"), - (["in"], c_ulong, "hint"), - (["out"], POINTER(c_int), "piOrder")) - ] - QueryInterface: Callable[[GUID, _Pointer[_Pointer[comtypes.IUnknown]]], HRESULT] - AddRef: Callable[[], ULONG] - Release: Callable[[], ULONG] - BindToHandler: Callable[[_Pointer[comtypes.IUnknown], GUID, GUID, _Pointer[c_void_p]], HRESULT] - GetParent: Callable[[], comtypes.IUnknown] - GetDisplayName: Callable[[c_ulong | int], str] - GetAttributes: Callable[[c_ulong | int], int] - Compare: Callable[[_Pointer[comtypes.IUnknown], c_ulong, c_int], HRESULT] - - -class ShellItem(comtypes.COMObject): - _com_interfaces_: Sequence[type[comtypes.IUnknown]] = [IShellItem] - def QueryInterface(self, riid: GUID, ppvObject: comtypes.IUnknown) -> HRESULT: # noqa: N803 - return S_OK - def AddRef(self) -> ULONG: - return ULONG(-1) - def Release(self) -> ULONG: - return ULONG(-1) - def BindToHandler(self, pbc: _Pointer[comtypes.IUnknown], bhid: GUID, riid: GUID, ppv: _Pointer[c_void_p]) -> HRESULT: - return S_OK - def GetParent(self, ppsi: comtypes.IUnknown) -> HRESULT: - return S_OK - def GetDisplayName(self, sigdnName: c_ulong | int, ppszName: _Pointer[c_wchar_p]) -> HRESULT: # noqa: N803 - return S_OK - def GetAttributes(self, sfgaoMask: c_ulong | int, psfgaoAttribs: _Pointer[c_ulong]) -> HRESULT: # noqa: N803 - return S_OK - def Compare(self, psi: _Pointer[comtypes.IUnknown], hint: c_ulong | int, piOrder: c_int) -> HRESULT: # noqa: N803 - return S_OK - - -SHCreateItemFromParsingName = windll.shell32.SHCreateItemFromParsingName -SHCreateItemFromParsingName.argtypes = [LPCWSTR, comtypes.POINTER(comtypes.IUnknown), comtypes.POINTER(POINTER(IShellItem))] -SHCreateItemFromParsingName.restype = HRESULT - -def create_shell_item_from_path(path: str) -> _Pointer[IShellItem]: - item = POINTER(IShellItem)() - hr = SHCreateItemFromParsingName(path, None, byref(GUID("{00000000-0000-0000-C000-000000000046}")), byref(item)) - if hr != 0: - raise OSError(f"SHCreateItemFromParsingName failed! HRESULT: {hr}") - return item - - -class IContextMenu(comtypes.IUnknown): - _case_insensitive_: bool = True - _iid_: GUID = IID_IContextMenu - _methods_: ClassVar[list[_ComMemberSpec]] = [ - COMMETHOD([], HRESULT, "QueryContextMenu", - (["in"], c_void_p, "hmenu"), - (["in"], c_uint, "indexMenu"), - (["in"], c_uint, "idCmdFirst"), - (["in"], c_uint, "idCmdLast"), - (["in"], c_uint, "uFlags")), - COMMETHOD([], HRESULT, "InvokeCommand", - (["in"], c_void_p, "pici")), - COMMETHOD([], HRESULT, "GetCommandString", - (["in"], c_uint, "idCmd"), - (["in"], c_uint, "uType"), - (["in"], c_void_p, "pReserved"), - (["out"], c_wchar_p, "pszName"), - (["in"], c_uint, "cchMax")) - ] - QueryInterface: Callable[[GUID, _Pointer[_Pointer[comtypes.IUnknown]]], HRESULT] - AddRef: Callable[[], ULONG] - Release: Callable[[], ULONG] - QueryContextMenu: Callable[[c_void_p, c_uint, c_uint, c_uint, c_uint], HRESULT] - InvokeCommand: Callable[[c_void_p], HRESULT] - GetCommandString: Callable[[c_uint, c_uint, c_void_p, _Pointer[c_wchar_p], c_uint], HRESULT] - - -class ContextMenu(comtypes.COMObject): - _com_interfaces_: Sequence[type[comtypes.IUnknown]] = [IContextMenu] - def QueryInterface(self, riid: GUID, ppvObject: comtypes.IUnknown) -> HRESULT: # noqa: N803 - return S_OK - def AddRef(self) -> ULONG: - return ULONG(-1) - def Release(self) -> ULONG: - return ULONG(-1) - def QueryContextMenu(self, hmenu: c_void_p, indexMenu: c_uint | int, idCmdFirst: c_uint | int, idCmdLast: c_uint | int, uFlags: c_uint | int) -> HRESULT: # noqa: N803 - return S_OK - def InvokeCommand(self, pici: c_void_p) -> HRESULT: - return S_OK - def GetCommandString(self, idCmd: c_uint | int, uType: c_uint | int, pReserved: c_void_p, pszName: c_wchar_p, cchMax: c_uint | int) -> HRESULT: # noqa: N803 - return S_OK - - -class IShellFolder(comtypes.IUnknown): - _case_insensitive_: bool = True - _iid_: GUID = IID_IShellFolder - _methods_: ClassVar[list[_ComMemberSpec]] = [ - COMMETHOD([], HRESULT, "ParseDisplayName", - (["in"], HWND, "hwnd"), - (["in"], POINTER(comtypes.IUnknown), "pbc"), - (["in"], LPCWSTR, "pszDisplayName"), - (["out"], POINTER(ULONG), "pchEaten"), - (["out"], POINTER(c_void_p), "ppidl"), - (["in"], POINTER(ULONG), "pdwAttributes")), - COMMETHOD([], HRESULT, "EnumObjects", - (["in"], HWND, "hwnd"), - (["in"], c_ulong, "grfFlags"), - (["out"], POINTER(POINTER(comtypes.IUnknown)), "ppenumIDList")), - COMMETHOD([], HRESULT, "BindToObject", - (["in"], c_void_p, "pidl"), - (["in"], POINTER(comtypes.IUnknown), "pbc"), - (["in"], POINTER(GUID), "riid"), - (["out"], POINTER(c_void_p), "ppv")), - COMMETHOD([], HRESULT, "BindToStorage", - (["in"], c_void_p, "pidl"), - (["in"], POINTER(comtypes.IUnknown), "pbc"), - (["in"], POINTER(GUID), "riid"), - (["out"], POINTER(c_void_p), "ppv")), - COMMETHOD([], HRESULT, "CompareIDs", - (["in"], c_void_p, "lParam"), - (["in"], c_void_p, "pidl1"), - (["in"], c_void_p, "pidl2")), - COMMETHOD([], HRESULT, "CreateViewObject", - (["in"], HWND, "hwndOwner"), - (["in"], POINTER(GUID), "riid"), - (["out"], POINTER(c_void_p), "ppv")), - COMMETHOD([], HRESULT, "GetAttributesOf", - (["in"], c_uint, "cidl"), - (["in"], C_POINTER(c_void_p), "apidl"), - (["out"], POINTER(c_ulong), "rgfInOut")), - COMMETHOD([], HRESULT, "GetUIObjectOf", - (["in"], HWND, "hwndOwner"), - (["in"], c_uint, "cidl"), - (["in"], C_POINTER(c_void_p), "apidl"), - (["in"], POINTER(GUID), "riid"), - (["in"], POINTER(c_uint), "rgfReserved"), - (["out"], POINTER(c_void_p), "ppv")), - COMMETHOD([], HRESULT, "GetDisplayNameOf", - (["in"], c_void_p, "pidl"), - (["in"], c_ulong, "uFlags"), - (["out"], POINTER(c_wchar_p), "pName")), - COMMETHOD([], HRESULT, "SetNameOf", - (["in"], HWND, "hwnd"), - (["in"], c_void_p, "pidl"), - (["in"], LPCWSTR, "pszName"), - (["in"], c_ulong, "uFlags"), - (["out"], POINTER(c_void_p), "ppidlOut")) - ] - QueryInterface: Callable[[GUID, _Pointer[_Pointer[comtypes.IUnknown]]], HRESULT] - AddRef: Callable[[], ULONG] - Release: Callable[[], ULONG] - ParseDisplayName: Callable[[HWND, _Pointer[comtypes.IUnknown], LPCWSTR, _Pointer[ULONG], _Pointer[c_void_p], _Pointer[ULONG]], HRESULT] - EnumObjects: Callable[[HWND, c_ulong, _Pointer[_Pointer[comtypes.IUnknown]]], HRESULT] - BindToObject: Callable[[c_void_p, _Pointer[comtypes.IUnknown], GUID, _Pointer[c_void_p]], HRESULT] - BindToStorage: Callable[[c_void_p, _Pointer[comtypes.IUnknown], GUID, _Pointer[c_void_p]], HRESULT] - CompareIDs: Callable[[c_void_p, c_void_p, c_void_p], HRESULT] - CreateViewObject: Callable[[HWND, GUID, _Pointer[c_void_p]], HRESULT] - GetAttributesOf: Callable[[c_uint, _Pointer[c_void_p], _Pointer[c_ulong]], HRESULT] - GetUIObjectOf: Callable[[HWND, c_uint, _Pointer[c_void_p], GUID, _Pointer[c_uint], _Pointer[c_void_p]], HRESULT] - GetDisplayNameOf: Callable[[c_void_p, c_ulong, _Pointer[c_wchar_p]], HRESULT] - SetNameOf: Callable[[HWND, c_void_p, LPCWSTR, c_ulong, _Pointer[c_void_p]], HRESULT] -class ShellFolder(comtypes.COMObject): - _com_interfaces_: Sequence[type[comtypes.IUnknown]] = [IShellFolder] - def QueryInterface(self, riid: GUID, ppvObject: comtypes.IUnknown) -> HRESULT: # noqa: N803 - return S_OK - def AddRef(self) -> ULONG: - return ULONG(-1) - def Release(self) -> ULONG: - return ULONG(-1) - def ParseDisplayName(self, hwnd: HWND | int, pbc: _Pointer[comtypes.IUnknown], pszDisplayName: LPCWSTR | str, pchEaten: _Pointer[ULONG], ppidl: _Pointer[c_void_p], pdwAttributes: _Pointer[ULONG]) -> HRESULT: # noqa: N803, PLR0913 - return S_OK - def EnumObjects(self, hwnd: HWND | int, grfFlags: c_ulong | int, ppenumIDList: comtypes.IUnknown) -> HRESULT: # noqa: N803 - return S_OK - def BindToObject(self, pidl: c_void_p, pbc: _Pointer[comtypes.IUnknown], riid: GUID, ppv: _Pointer[c_void_p]) -> HRESULT: - return S_OK - def BindToStorage(self, pidl: c_void_p, pbc: _Pointer[comtypes.IUnknown], riid: GUID, ppv: _Pointer[c_void_p]) -> HRESULT: - return S_OK - def CompareIDs(self, lParam: c_void_p, pidl1: c_void_p, pidl2: c_void_p) -> HRESULT: # noqa: N803 - return S_OK - def CreateViewObject(self, hwndOwner: HWND | int, riid: GUID, ppv: _Pointer[c_void_p]) -> HRESULT: # noqa: N803 - return S_OK - def GetAttributesOf(self, cidl: c_uint | int, apidl: _Pointer[c_void_p], rgfInOut: _Pointer[c_ulong]) -> HRESULT: # noqa: N803 - return S_OK - def GetUIObjectOf(self, hwndOwner: HWND | int, cidl: c_uint | int, apidl: _Pointer[c_void_p], riid: GUID, rgfReserved: _Pointer[c_uint], ppv: _Pointer[c_void_p]) -> HRESULT: # noqa: N803, PLR0913, E501 - return S_OK - def GetDisplayNameOf(self, pidl: c_void_p, uFlags: c_ulong | int, pName: _Pointer[c_wchar_p]) -> HRESULT: # noqa: N803 - return S_OK - def SetNameOf(self, hwnd: HWND | int, pidl: c_void_p, pszName: LPCWSTR | str, uFlags: c_ulong | int, ppidlOut: _Pointer[c_void_p]) -> HRESULT: # noqa: N803 - return S_OK - - -class IShellItemArray(comtypes.IUnknown): - _case_insensitive_: bool = True - _iid_: GUID = IID_IShellItemArray - _methods_: ClassVar[list[_ComMemberSpec]] = [ - COMMETHOD([], HRESULT, "BindToHandler", - (["in"], POINTER(comtypes.IUnknown), "pbc"), - (["in"], POINTER(GUID), "bhid"), - (["in"], POINTER(GUID), "riid"), - (["out"], POINTER(c_void_p), "ppv")), - COMMETHOD([], HRESULT, "GetPropertyStore", - (["in"], c_ulong, "flags"), - (["in"], POINTER(GUID), "riid"), - (["out"], POINTER(c_void_p), "ppv")), - COMMETHOD([], HRESULT, "GetPropertyDescriptionList", - (["in"], POINTER(GUID), "keyType"), - (["in"], POINTER(GUID), "riid"), - (["out"], POINTER(c_void_p), "ppv")), - COMMETHOD([], HRESULT, "GetAttributes", - (["in"], c_ulong, "attribFlags"), - (["in"], c_ulong, "sfgaoMask"), - (["out"], POINTER(c_ulong), "psfgaoAttribs")), - COMMETHOD([], HRESULT, "GetCount", - (["out"], POINTER(c_uint), "pdwNumItems")), - COMMETHOD([], HRESULT, "GetItemAt", - (["in"], c_uint, "dwIndex"), - (["out"], POINTER(POINTER(IShellItem)), "ppsi")), - COMMETHOD([], HRESULT, "EnumItems", - (["out"], POINTER(POINTER(comtypes.IUnknown)), "ppenumShellItems")) - ] - QueryInterface: Callable[[GUID, comtypes.IUnknown], HRESULT] - AddRef: Callable[[], ULONG] - Release: Callable[[], ULONG] - BindToHandler: Callable[[_Pointer[comtypes.IUnknown], GUID, GUID], int] - GetPropertyStore: Callable[[c_ulong, GUID], c_void_p] - GetPropertyDescriptionList: Callable[[GUID, GUID], c_void_p] - GetAttributes: Callable[[c_ulong, c_ulong], _Pointer[c_ulong]] - GetCount: Callable[[], int] - GetItemAt: Callable[[c_uint | int], IShellItem] - EnumItems: Callable[[], comtypes.IUnknown] -class ShellItemArray(comtypes.COMObject): - _com_interfaces_: Sequence[type[comtypes.IUnknown]] = [IShellItemArray] - def QueryInterface(self, riid: GUID, ppvObject: comtypes.IUnknown) -> HRESULT: # noqa: N803 - return S_OK - def AddRef(self) -> ULONG: - return ULONG(-1) - def Release(self) -> ULONG: - return ULONG(-1) - def BindToHandler(self, pbc: _Pointer[comtypes.IUnknown], bhid: GUID, riid: GUID, ppv: _Pointer[c_void_p]) -> HRESULT: - return S_OK - def GetPropertyStore(self, flags: c_ulong | int, riid: GUID, ppv: _Pointer[c_void_p]) -> HRESULT: - return S_OK - def GetPropertyDescriptionList(self, keyType: GUID, riid: GUID, ppv: _Pointer[c_void_p]) -> HRESULT: # noqa: N803 - return S_OK - def GetAttributes(self, attribFlags: c_ulong | int, sfgaoMask: c_ulong | int, psfgaoAttribs: _Pointer[c_ulong]) -> HRESULT: # noqa: N803 - return S_OK - def GetCount(self, pdwNumItems: _Pointer[c_uint]) -> HRESULT: # noqa: N803 - return S_OK - def GetItemAt(self, dwIndex: c_uint | int, ppsi: IShellItem) -> HRESULT: # noqa: N803 - return S_OK - def EnumItems(self, ppenumShellItems: comtypes.IUnknown) -> HRESULT: # noqa: N803 - return S_OK - - -class IShellItemFilter(comtypes.IUnknown): - _case_insensitive_: bool = True - _iid_: GUID = IID_IShellItemFilter - _methods_: ClassVar[list[_ComMemberSpec]] = [ - COMMETHOD([], HRESULT, "IncludeItem", - (["in"], POINTER(IShellItem), "psi")), - COMMETHOD([], HRESULT, "GetEnumFlagsForItem", - (["in"], POINTER(IShellItem), "psi"), - (["out"], POINTER(c_ulong), "pgrfFlags")) - ] - QueryInterface: Callable[[GUID, comtypes.IUnknown], HRESULT] - AddRef: Callable[[], ULONG] - Release: Callable[[], ULONG] - IncludeItem: Callable[[IShellItem], c_ulong] - GetEnumFlagsForItem: Callable[[], HRESULT] -class ShellItemFilter(comtypes.COMObject): - _com_interfaces_: Sequence[type[comtypes.IUnknown]] = [IShellItemFilter] - def QueryInterface(self, riid: GUID, ppvObject: comtypes.IUnknown) -> HRESULT: # noqa: N803 - return S_OK - def AddRef(self) -> ULONG: - return ULONG(-1) - def Release(self) -> ULONG: - return ULONG(-1) - def IncludeItem(self, psi: IShellItem) -> HRESULT: - return S_OK - def GetEnumFlagsForItem(self, psi: IShellItem, pgrfFlags: _Pointer[c_ulong]) -> HRESULT: # noqa: N803 - return S_OK - - -class IEnumShellItems(comtypes.IUnknown): - _case_insensitive_: bool = True - _iid_: GUID = IID_IEnumShellItems - _methods_: ClassVar[list[_ComMemberSpec]] - QueryInterface: Callable[[GUID, comtypes.IUnknown], HRESULT] - AddRef: Callable[[], ULONG] - Release: Callable[[], ULONG] - Next: Callable[[_Pointer[IEnumShellItems], c_ulong, IShellItem, _Pointer[c_ulong]], HRESULT] - Skip: Callable[[_Pointer[IEnumShellItems], c_ulong], HRESULT] - Reset: Callable[[_Pointer[IEnumShellItems]], HRESULT] - Clone: Callable[[_Pointer[IEnumShellItems], _Pointer[_Pointer[IEnumShellItems]]], HRESULT] -IEnumShellItems._methods_ = [ # noqa: SLF001 - COMMETHOD([], HRESULT, "Next", - (["in"], c_ulong, "celt"), - (["out"], POINTER(POINTER(IShellItem)), "rgelt"), - (["out"], POINTER(c_ulong), "pceltFetched")), - COMMETHOD([], HRESULT, "Skip", - (["in"], c_ulong, "celt")), - COMMETHOD([], HRESULT, "Reset"), - COMMETHOD([], HRESULT, "Clone", - (["out"], POINTER(POINTER(IEnumShellItems)), "ppenum")) -] -class EnumShellItems(comtypes.COMObject): - _com_interfaces_: Sequence[type[comtypes.IUnknown]] = [IEnumShellItems] - def QueryInterface(self, riid: GUID, ppvObject: comtypes.IUnknown) -> HRESULT: # noqa: N803 - return S_OK - def AddRef(self) -> ULONG: - return ULONG(-1) - def Release(self) -> ULONG: - return ULONG(-1) - def Next(self, celt: c_ulong | int, rgelt: IShellItem, pceltFetched: _Pointer[c_ulong]) -> HRESULT: # noqa: N803 - return S_OK - def Skip(self, celt: c_ulong | int) -> HRESULT: - return S_OK - def Reset(self) -> HRESULT: - return S_OK - def Clone(self, ppenum: _Pointer[_Pointer[IEnumShellItems]]) -> HRESULT: - return S_OK - - -class IPropertyStore(comtypes.IUnknown): - _case_insensitive_: bool = True - _iid_: GUID = IID_IPropertyStore - _methods_: ClassVar[list[_ComMemberSpec]] = [ - COMMETHOD([], HRESULT, "GetCount", - (["out"], POINTER(c_ulong), "count")), - COMMETHOD([], HRESULT, "GetAt", - (["in"], c_ulong, "index"), - (["out"], POINTER(GUID), "key")), - COMMETHOD([], HRESULT, "GetValue", - (["in"], POINTER(GUID), "key"), - (["out"], POINTER(c_void_p), "pv")), - COMMETHOD([], HRESULT, "SetValue", - (["in"], POINTER(GUID), "key"), - (["in"], POINTER(c_void_p), "propvar")), - COMMETHOD([], HRESULT, "Commit") - ] - QueryInterface: Callable[[GUID, comtypes.IUnknown], HRESULT] - AddRef: Callable[[], ULONG] - Release: Callable[[], ULONG] - GetCount: Callable[[_Pointer[IPropertyStore], _Pointer[c_ulong]], HRESULT] - GetAt: Callable[[_Pointer[IPropertyStore], c_ulong, GUID], HRESULT] - GetValue: Callable[[_Pointer[IPropertyStore], GUID, _Pointer[c_void_p]], HRESULT] - SetValue: Callable[[_Pointer[IPropertyStore], GUID, _Pointer[c_void_p]], HRESULT] - Commit: Callable[[_Pointer[IPropertyStore]], HRESULT] -class PropertyStore(comtypes.COMObject): - _com_interfaces_: Sequence[type[comtypes.IUnknown]] = [IPropertyStore] - def QueryInterface(self, riid: GUID, ppvObject: comtypes.IUnknown) -> HRESULT: # noqa: N803 - return S_OK - def AddRef(self) -> ULONG: - return ULONG(-1) - def Release(self) -> ULONG: - return ULONG(-1) - def GetCount(self, count: _Pointer[c_ulong]) -> HRESULT: - return S_OK - def GetAt(self, index: c_ulong | int, key: GUID) -> HRESULT: - return S_OK - def GetValue(self, key: GUID, pv: _Pointer[c_void_p]) -> HRESULT: - return S_OK - def SetValue(self, key: GUID, propvar: _Pointer[c_void_p]) -> HRESULT: - return S_OK - def Commit(self) -> HRESULT: - return S_OK - -class IFileOperationProgressSink(comtypes.IUnknown): - _case_insensitive_: bool = True - _iid_: GUID = IID_IFileOperationProgressSink - _methods_: ClassVar[list[_ComMemberSpec]] = [ - COMMETHOD([], HRESULT, "StartOperations"), - COMMETHOD([], HRESULT, "FinishOperations", - (["in"], HRESULT, "hr")), - COMMETHOD([], HRESULT, "PreRenameItem", - (["in"], c_ulong, "dwFlags"), - (["in"], POINTER(IShellItem), "psiItem"), - (["in"], c_wchar_p, "pszNewName")), - COMMETHOD([], HRESULT, "PostRenameItem", - (["in"], c_ulong, "dwFlags"), - (["in"], POINTER(IShellItem), "psiItem"), - (["in"], c_wchar_p, "pszNewName"), - (["in"], HRESULT, "hrRename"), - (["in"], POINTER(IShellItem), "psiNewlyCreated")), - COMMETHOD([], HRESULT, "PreMoveItem", - (["in"], c_ulong, "dwFlags"), - (["in"], POINTER(IShellItem), "psiItem"), - (["in"], POINTER(IShellItem), "psiDestinationFolder"), - (["in"], c_wchar_p, "pszNewName")), - COMMETHOD([], HRESULT, "PostMoveItem", - (["in"], c_ulong, "dwFlags"), - (["in"], POINTER(IShellItem), "psiItem"), - (["in"], POINTER(IShellItem), "psiDestinationFolder"), - (["in"], c_wchar_p, "pszNewName"), - (["in"], HRESULT, "hrMove"), - (["in"], POINTER(IShellItem), "psiNewlyCreated")), - COMMETHOD([], HRESULT, "PreCopyItem", - (["in"], c_ulong, "dwFlags"), - (["in"], POINTER(IShellItem), "psiItem"), - (["in"], POINTER(IShellItem), "psiDestinationFolder"), - (["in"], c_wchar_p, "pszNewName")), - COMMETHOD([], HRESULT, "PostCopyItem", - (["in"], c_ulong, "dwFlags"), - (["in"], POINTER(IShellItem), "psiItem"), - (["in"], POINTER(IShellItem), "psiDestinationFolder"), - (["in"], c_wchar_p, "pszNewName"), - (["in"], HRESULT, "hrCopy"), - (["in"], POINTER(IShellItem), "psiNewlyCreated")), - COMMETHOD([], HRESULT, "PreDeleteItem", - (["in"], c_ulong, "dwFlags"), - (["in"], POINTER(IShellItem), "psiItem")), - COMMETHOD([], HRESULT, "PostDeleteItem", - (["in"], c_ulong, "dwFlags"), - (["in"], POINTER(IShellItem), "psiItem"), - (["in"], HRESULT, "hrDelete"), - (["in"], POINTER(IShellItem), "psiNewlyCreated")), - COMMETHOD([], HRESULT, "PreNewItem", - (["in"], c_ulong, "dwFlags"), - (["in"], POINTER(IShellItem), "psiDestinationFolder"), - (["in"], c_wchar_p, "pszNewName")), - COMMETHOD([], HRESULT, "PostNewItem", - (["in"], c_ulong, "dwFlags"), - (["in"], POINTER(IShellItem), "psiDestinationFolder"), - (["in"], c_wchar_p, "pszNewName"), - (["in"], c_wchar_p, "pszTemplateName"), - (["in"], c_ulong, "dwFileAttributes"), - (["in"], HRESULT, "hrNew"), - (["in"], POINTER(IShellItem), "psiNewItem")), - COMMETHOD([], HRESULT, "UpdateProgress", - (["in"], c_ulong, "iWorkTotal"), - (["in"], c_ulong, "iWorkSoFar")), - COMMETHOD([], HRESULT, "ResetTimer"), - COMMETHOD([], HRESULT, "PauseTimer"), - COMMETHOD([], HRESULT, "ResumeTimer") -] - QueryInterface: Callable[[GUID, comtypes.IUnknown], HRESULT] - AddRef: Callable[[], ULONG] - Release: Callable[[], ULONG] - StartOperations: Callable[[], HRESULT] - FinishOperations: Callable[[HRESULT], HRESULT] - PreRenameItem: Callable[[c_ulong, IShellItem, c_wchar_p], HRESULT] - PostRenameItem: Callable[[c_ulong, IShellItem, c_wchar_p, HRESULT, IShellItem], HRESULT] - PreMoveItem: Callable[[c_ulong, IShellItem, IShellItem, c_wchar_p], HRESULT] - PostMoveItem: Callable[[c_ulong, IShellItem, IShellItem, c_wchar_p, HRESULT, IShellItem], HRESULT] - PreCopyItem: Callable[[c_ulong, IShellItem, IShellItem, c_wchar_p], HRESULT] - PostCopyItem: Callable[[c_ulong, IShellItem, IShellItem, c_wchar_p, HRESULT, IShellItem], HRESULT] - PreDeleteItem: Callable[[c_ulong, IShellItem], HRESULT] - PostDeleteItem: Callable[[c_ulong, IShellItem, HRESULT, IShellItem], HRESULT] - PreNewItem: Callable[[c_ulong, IShellItem, c_wchar_p], HRESULT] - PostNewItem: Callable[[c_ulong, IShellItem, c_wchar_p, c_wchar_p, c_ulong, HRESULT, IShellItem], HRESULT] - UpdateProgress: Callable[[c_ulong, c_ulong], HRESULT] - ResetTimer: Callable[[], HRESULT] - PauseTimer: Callable[[], HRESULT] - ResumeTimer: Callable[[], HRESULT] -class FileOperationProgressSink(comtypes.COMObject): - _com_interfaces_: Sequence[type[comtypes.IUnknown]] = [IFileOperationProgressSink] - def StartOperations(self) -> HRESULT: - return S_OK - def FinishOperations(self, hr: HRESULT) -> HRESULT: - return S_OK - def PreRenameItem(self, dwFlags: c_ulong | int, psiItem: IShellItem, pszNewName: LPCWSTR | str) -> HRESULT: # noqa: N803 - return S_OK - def PostRenameItem(self, dwFlags: c_ulong | int, psiItem: IShellItem, pszNewName: LPCWSTR | str, hrRename: HRESULT, psiNewlyCreated: IShellItem) -> HRESULT: # noqa: N803 - return S_OK - def PreMoveItem(self, dwFlags: c_ulong | int, psiItem: IShellItem, psiDestinationFolder: IShellItem, pszNewName: LPCWSTR | str) -> HRESULT: # noqa: N803 - return S_OK - def PostMoveItem(self, dwFlags: c_ulong | int, psiItem: IShellItem, psiDestinationFolder: IShellItem, pszNewName: LPCWSTR | str, hrMove: HRESULT, psiNewlyCreated: IShellItem) -> HRESULT: # noqa: N803, E501 - return S_OK - def PreCopyItem(self, dwFlags: c_ulong | int, psiItem: IShellItem, psiDestinationFolder: IShellItem, pszNewName: LPCWSTR | str) -> HRESULT: # noqa: N803 - return S_OK - def PostCopyItem(self, dwFlags: c_ulong | int, psiItem: IShellItem, psiDestinationFolder: IShellItem, pszNewName: LPCWSTR | str, hrCopy: HRESULT, psiNewlyCreated: IShellItem) -> HRESULT: # noqa: N803, E501 - return S_OK - def PreDeleteItem(self, dwFlags: c_ulong | int, psiItem: IShellItem) -> HRESULT: - return S_OK - def PostDeleteItem(self, dwFlags: c_ulong | int, psiItem: IShellItem, hrDelete: HRESULT, psiNewlyCreated: IShellItem) -> HRESULT: - return S_OK - def PreNewItem(self, dwFlags: c_ulong | int, psiDestinationFolder: IShellItem, pszNewName: LPCWSTR | str) -> HRESULT: - return S_OK - def PostNewItem(self, dwFlags: c_ulong | int, psiDestinationFolder: IShellItem, pszNewName: LPCWSTR | str, pszTemplateName: LPCWSTR | str, dwFileAttributes: c_ulong | int, hrNew: HRESULT, psiNewItem: IShellItem) -> HRESULT: # noqa: N803, E501 - return S_OK - def UpdateProgress(self, iWorkTotal: c_ulong | int, iWorkSoFar: c_ulong | int) -> HRESULT: # noqa: N803 - return S_OK - def ResetTimer(self) -> HRESULT: - return S_OK - def PauseTimer(self) -> HRESULT: - return S_OK - def ResumeTimer(self) -> HRESULT: - return S_OK - - -class IFileOperation(comtypes.IUnknown): - _case_insensitive_ = True - _iid_ = IID_IFileOperation - _idlflags_ = [] - - _methods_: ClassVar[list[_ComMemberSpec]] = [ - # Advise methods - comtypes.COMMETHOD([], HRESULT, "Advise", - (["in"], POINTER(IFileOperationProgressSink), "pfops"), - (["out"], POINTER(c_ulong), "pdwCookie")), - comtypes.COMMETHOD([], HRESULT, "Unadvise", - (["in"], c_ulong, "dwCookie")), - - # Operation control methods - comtypes.COMMETHOD([], HRESULT, "SetOperationFlags", - (["in"], c_ulong, "dwOperationFlags")), - comtypes.COMMETHOD([], HRESULT, "SetProgressMessage", - (["in"], c_wchar_p, "pszMessage")), - comtypes.COMMETHOD([], HRESULT, "SetProgressDialog", - (["in"], POINTER(comtypes.IUnknown), "popd")), - - # Item methods - comtypes.COMMETHOD([], HRESULT, "SetProperties", - (["in"], POINTER(comtypes.IUnknown), "pproparray")), - comtypes.COMMETHOD([], HRESULT, "SetOwnerWindow", - (["in"], c_ulong, "hwndOwner")), - comtypes.COMMETHOD([], HRESULT, "ApplyPropertiesToItem", - (["in"], POINTER(IShellItem), "psiItem")), - comtypes.COMMETHOD([], HRESULT, "ApplyPropertiesToItems", - (["in"], POINTER(comtypes.IUnknown), "punkItems")), - - # Operation methods - comtypes.COMMETHOD([], HRESULT, "RenameItem", - (["in"], POINTER(IShellItem), "psiItem"), - (["in"], c_wchar_p, "pszNewName"), - (["in"], POINTER(IFileOperationProgressSink), "pfopsItem")), - comtypes.COMMETHOD([], HRESULT, "RenameItems", - (["in"], POINTER(comtypes.IUnknown), "pUnkItems"), - (["in"], c_wchar_p, "pszNewName")), - comtypes.COMMETHOD([], HRESULT, "MoveItem", - (["in"], POINTER(IShellItem), "psiItem"), - (["in"], POINTER(IShellItem), "psiDestinationFolder"), - (["in"], c_wchar_p, "pszNewName"), - (["in"], POINTER(IFileOperationProgressSink), "pfopsItem")), - comtypes.COMMETHOD([], HRESULT, "MoveItems", - (["in"], POINTER(comtypes.IUnknown), "punkItems"), - (["in"], POINTER(IShellItem), "psiDestinationFolder")), - comtypes.COMMETHOD([], HRESULT, "CopyItem", - (["in"], POINTER(IShellItem), "psiItem"), - (["in"], POINTER(IShellItem), "psiDestinationFolder"), - (["in"], c_wchar_p, "pszNewName"), - (["in"], POINTER(IFileOperationProgressSink), "pfopsItem")), - comtypes.COMMETHOD([], HRESULT, "CopyItems", - (["in"], POINTER(comtypes.IUnknown), "punkItems"), - (["in"], POINTER(IShellItem), "psiDestinationFolder")), - comtypes.COMMETHOD([], HRESULT, "DeleteItem", - (["in"], POINTER(IShellItem), "psiItem"), - (["in"], POINTER(IFileOperationProgressSink), "pfopsItem")), - comtypes.COMMETHOD([], HRESULT, "DeleteItems", - (["in"], POINTER(comtypes.IUnknown), "punkItems")), - comtypes.COMMETHOD([], HRESULT, "NewItem", - (["in"], POINTER(IShellItem), "psiDestinationFolder"), - (["in"], c_ulong, "dwFileAttributes"), - (["in"], c_wchar_p, "pszName"), - (["in"], c_wchar_p, "pszTemplateName"), - (["in"], POINTER(IFileOperationProgressSink), "pfopsItem")), - - # Execution methods - comtypes.COMMETHOD([], HRESULT, "PerformOperations"), - comtypes.COMMETHOD([], HRESULT, "GetAnyOperationsAborted", - (["out"], POINTER(c_int), "pfAnyOperationsAborted")) - ] - - -class IFileDialogEvents(comtypes.IUnknown): - _case_insensitive_: bool = True - _iid_: GUID = IID_IFileDialogEvents - _methods_: ClassVar[list[_ComMemberSpec]] - QueryInterface: Callable[[GUID, comtypes.IUnknown], HRESULT] - AddRef: Callable[[], ULONG] - Release: Callable[[], ULONG] - OnFileOk: Callable[[IFileDialog], HRESULT] - OnFolderChanging: Callable[[IFileDialog, IShellItem], HRESULT] - OnFolderChange: Callable[[IFileDialog], HRESULT] - OnSelectionChange: Callable[[IFileDialog], HRESULT] - OnShareViolation: Callable[[IFileDialog, IShellItem, c_int], HRESULT] - OnTypeChange: Callable[[IFileDialog], HRESULT] - OnOverwrite: Callable[[IFileDialog, IShellItem, c_int], HRESULT] -class IFileDialog(IModalWindow): - _iid_: GUID = IID_IFileDialog - _methods_: ClassVar[list[_ComMemberSpec]] = [ - COMMETHOD([], HRESULT, "SetFileTypes", - (["in"], c_uint, "cFileTypes"), - (["in"], POINTER(c_void_p), "rgFilterSpec")), - COMMETHOD([], HRESULT, "SetFileTypeIndex", - (["in"], c_uint, "iFileType")), - COMMETHOD([], HRESULT, "GetFileTypeIndex", - (["out"], POINTER(c_uint), "piFileType")), - COMMETHOD([], HRESULT, "Advise", - (["in"], POINTER(comtypes.IUnknown), "pfde"), - (["out"], POINTER(DWORD), "pdwCookie")), - COMMETHOD([], HRESULT, "Unadvise", - (["in"], DWORD, "dwCookie")), - COMMETHOD([], HRESULT, "SetOptions", - (["in"], c_uint, "fos")), - COMMETHOD([], HRESULT, "GetOptions", - (["out"], POINTER(DWORD), "pfos")), - COMMETHOD([], HRESULT, "SetDefaultFolder", - (["in"], POINTER(IShellItem), "psi")), - COMMETHOD([], HRESULT, "SetFolder", - (["in"], POINTER(IShellItem), "psi")), - COMMETHOD([], HRESULT, "GetFolder", - (["out"], POINTER(POINTER(IShellItem)), "ppsi")), - COMMETHOD([], HRESULT, "GetCurrentSelection", - (["out"], POINTER(POINTER(IShellItem)), "ppsi")), - COMMETHOD([], HRESULT, "SetFileName", - (["in"], LPCWSTR, "pszName")), - COMMETHOD([], HRESULT, "GetFileName", - (["out"], POINTER(LPWSTR), "pszName")), - COMMETHOD([], HRESULT, "SetTitle", - (["in"], LPCWSTR, "pszTitle")), - COMMETHOD([], HRESULT, "SetOkButtonLabel", - (["in"], LPCWSTR, "pszText")), - COMMETHOD([], HRESULT, "SetFileNameLabel", - (["in"], LPCWSTR, "pszLabel")), - COMMETHOD([], HRESULT, "GetResult", - (["out"], POINTER(POINTER(IShellItem)), "ppsi")), - COMMETHOD([], HRESULT, "AddPlace", - (["in"], POINTER(IShellItem), "psi"), - (["in"], c_int, "fdap")), - COMMETHOD([], HRESULT, "SetDefaultExtension", - (["in"], LPCWSTR, "pszDefaultExtension")), - COMMETHOD([], HRESULT, "Close", - (["in"], HRESULT, "hr")), - COMMETHOD([], HRESULT, "SetClientGuid", - (["in"], POINTER(GUID), "guid")), - COMMETHOD([], HRESULT, "ClearClientData"), - COMMETHOD([], HRESULT, "SetFilter", - (["in"], POINTER(IShellItemFilter), "pFilter")) - ] - SetFileTypes: Callable[[c_uint | int, _Pointer[c_void_p]], HRESULT] - SetFileTypeIndex: Callable[[c_uint], HRESULT] - GetFileTypeIndex: Callable[[], _Pointer[c_uint]] - Advise: Callable[[comtypes.IUnknown | comtypes.COMObject], int] - Unadvise: Callable[[int], HRESULT] - SetOptions: Callable[[DWORD | int], HRESULT] - GetOptions: Callable[[], int] - SetDefaultFolder: Callable[[_Pointer[IShellItem]], HRESULT] - SetFolder: Callable[[_Pointer[IShellItem]], HRESULT] - GetFolder: Callable[[], IShellItem] - GetCurrentSelection: Callable[[], IShellItem] - SetFileName: Callable[[str], HRESULT] - GetFileName: Callable[[], _Pointer[LPWSTR]] - SetTitle: Callable[[str], HRESULT] - SetOkButtonLabel: Callable[[str], HRESULT] - SetFileNameLabel: Callable[[str], HRESULT] - GetResult: Callable[[], IShellItem] - AddPlace: Callable[[IShellItem, c_int], HRESULT] - SetDefaultExtension: Callable[[str], HRESULT] - Close: Callable[[HRESULT], HRESULT] - SetClientGuid: Callable[[GUID], HRESULT] - ClearClientData: Callable[[], HRESULT] - SetFilter: Callable[[IShellItemFilter], HRESULT] -IFileDialogEvents._methods_ = [ # noqa: SLF001 - COMMETHOD([], HRESULT, "OnFileOk", - (["in"], POINTER(IFileDialog), "pfd")), - COMMETHOD([], HRESULT, "OnFolderChanging", - (["in"], POINTER(IFileDialog), "pfd"), - (["in"], POINTER(IShellItem), "psiFolder")), - COMMETHOD([], HRESULT, "OnFolderChange", - (["in"], POINTER(IFileDialog), "pfd")), - COMMETHOD([], HRESULT, "OnSelectionChange", - (["in"], POINTER(IFileDialog), "pfd")), - COMMETHOD([], HRESULT, "OnShareViolation", - (["in"], POINTER(IFileDialog), "pfd"), - (["in"], POINTER(IShellItem), "psi"), - (["out"], POINTER(c_int), "pResponse")), - COMMETHOD([], HRESULT, "OnTypeChange", - (["in"], POINTER(IFileDialog), "pfd")), - COMMETHOD([], HRESULT, "OnOverwrite", - (["in"], POINTER(IFileDialog), "pfd"), - (["in"], POINTER(IShellItem), "psi"), - (["out"], POINTER(c_int), "pResponse")) - ] -class FileDialogEvents(comtypes.COMObject): - _com_interfaces_: Sequence[type[comtypes.IUnknown]] = [IFileDialogEvents] - def QueryInterface(self, riid: GUID, ppvObject: comtypes.IUnknown) -> HRESULT: # noqa: N803 - return S_OK - def AddRef(self) -> ULONG: - return ULONG(-1) - def Release(self) -> ULONG: - return ULONG(-1) - def OnFileOk(self, ifd: IFileDialog) -> HRESULT: - return S_OK - def OnFolderChanging(self, ifd: IFileDialog, isiFolder: IShellItem) -> HRESULT: # noqa: N803 - return S_OK - def OnFolderChange(self, ifd: IFileDialog) -> HRESULT: - return S_OK - def OnSelectionChange(self, ifd: IFileDialog) -> HRESULT: - return S_OK - def OnShareViolation(self, ifd: IFileDialog, psi: IShellItem, response: c_int) -> HRESULT: - return S_OK - def OnTypeChange(self, ifd: IFileDialog) -> HRESULT: - return S_OK - def OnOverwrite(self, ifd: IFileDialog, psi: IShellItem, response: c_int) -> HRESULT: - return S_OK -class FileDialog(comtypes.COMObject): - _com_interfaces_: Sequence[type[comtypes.IUnknown]] = [IFileDialog] - def QueryInterface(self, riid: GUID, ppvObject: comtypes.IUnknown) -> HRESULT: # noqa: N803 - return S_OK - def AddRef(self) -> ULONG: - return ULONG(-1) - def Release(self) -> ULONG: - return ULONG(-1) - def Show(self, hwndOwner: HWND | int) -> HRESULT: - return S_OK - def SetFileTypes(self, cFileTypes: c_uint | int, rgFilterSpec: Array[COMDLG_FILTERSPEC]) -> HRESULT: # noqa: N803 - return S_OK - def SetFileTypeIndex(self, iFileType: c_uint | int) -> HRESULT: # noqa: N803 - return S_OK - def GetFileTypeIndex(self, piFileType: _Pointer[c_uint]) -> HRESULT: # noqa: N803 - return S_OK - def Advise(self, pfde: _Pointer[comtypes.IUnknown], pdwCookie: _Pointer[DWORD]) -> HRESULT: # noqa: N803 - return S_OK - def Unadvise(self, dwCookie: int) -> HRESULT: # noqa: N803 - return S_OK - def SetOptions(self, fos: int) -> HRESULT: - return S_OK - def GetOptions(self, pfos: _Pointer[DWORD]) -> HRESULT: - return S_OK - def SetDefaultFolder(self, psi: IShellItem) -> HRESULT: - return S_OK - def SetFolder(self, psi: IShellItem) -> HRESULT: - return S_OK - def GetFolder(self, ppsi: IShellItem) -> HRESULT: - return S_OK - def GetCurrentSelection(self, ppsi: IShellItem) -> HRESULT: - return S_OK - def SetFileName(self, pszName: LPCWSTR | str) -> HRESULT: # noqa: N803 - return S_OK - def GetFileName(self, pszName: _Pointer[LPWSTR]) -> HRESULT: # noqa: N803 - return S_OK - def SetTitle(self, pszTitle: LPCWSTR | str) -> HRESULT: # noqa: N803 - return S_OK - def SetOkButtonLabel(self, pszText: LPCWSTR | str) -> HRESULT: # noqa: N803 - return S_OK - def SetFileNameLabel(self, pszLabel: LPCWSTR | str) -> HRESULT: # noqa: N803 - return S_OK - def GetResult(self, ppsi: IShellItem) -> HRESULT: - return S_OK - def AddPlace(self, psi: IShellItem, fdap: c_int) -> HRESULT: - return S_OK - def SetDefaultExtension(self, pszDefaultExtension: LPCWSTR | str) -> HRESULT: # noqa: N803 - return S_OK - def Close(self, hr: HRESULT | int) -> HRESULT: - return S_OK - def SetClientGuid(self, guid: GUID) -> HRESULT: - return S_OK - def ClearClientData(self) -> HRESULT: - return S_OK - def SetFilter(self, pFilter: IShellItemFilter) -> HRESULT: # noqa: N803 - return S_OK - - -class IShellLibrary(comtypes.IUnknown): - _case_insensitive_: bool = True - _iid_: GUID = IID_IShellLibrary - _methods_: ClassVar[list[_ComMemberSpec]] = [ - COMMETHOD([], HRESULT, "LoadLibraryFromItem", - (["in"], POINTER(IShellItem), "psi"), - (["in"], c_ulong, "grfMode")), - COMMETHOD([], HRESULT, "LoadLibraryFromKnownFolder", - (["in"], POINTER(GUID), "kfidLibrary"), - (["in"], c_ulong, "grfMode")), - COMMETHOD([], HRESULT, "AddFolder", - (["in"], POINTER(IShellItem), "psi")), - COMMETHOD([], HRESULT, "RemoveFolder", - (["in"], POINTER(IShellItem), "psi")), - COMMETHOD([], HRESULT, "GetFolders", - (["in"], c_int, "lff"), - (["in"], POINTER(GUID), "riid"), - (["out"], POINTER(POINTER(c_void_p)), "ppv")), - COMMETHOD([], HRESULT, "ResolveFolder", - (["in"], POINTER(IShellItem), "psi"), - (["in"], c_ulong, "grfMode"), - (["in"], POINTER(GUID), "riid"), - (["out"], POINTER(POINTER(c_void_p)), "ppv")), - COMMETHOD([], HRESULT, "GetDefaultSaveFolder", - (["in"], c_int, "dsft"), - (["in"], POINTER(GUID), "riid"), - (["out"], POINTER(POINTER(c_void_p)), "ppv")), - COMMETHOD([], HRESULT, "SetDefaultSaveFolder", - (["in"], c_int, "dsft"), - (["in"], POINTER(IShellItem), "psi")), - COMMETHOD([], HRESULT, "GetOptions", - (["out"], POINTER(c_uint), "pOptions")), - COMMETHOD([], HRESULT, "SetOptions", - (["in"], c_ulong, "stfOptions"), - (["in"], c_ulong, "stfMask")), - COMMETHOD([], HRESULT, "GetFolderType", - (["out"], POINTER(GUID), "pftid")), - COMMETHOD([], HRESULT, "SetFolderType", - (["in"], POINTER(GUID), "ftid")), - COMMETHOD([], HRESULT, "GetIcon", - (["out"], POINTER(LPWSTR), "ppszIcon")), - COMMETHOD([], HRESULT, "SetIcon", - (["in"], LPCWSTR, "pszIcon")), - COMMETHOD([], HRESULT, "Commit"), - COMMETHOD([], HRESULT, "Save", - (["in"], POINTER(IShellItem), "psiFolderToSaveIn"), - (["in"], LPCWSTR, "pszLibraryName"), - (["in"], c_ulong, "lrf"), - (["out"], POINTER(POINTER(IShellItem)), "ppsiNewItem")), - COMMETHOD([], HRESULT, "SaveInKnownFolder", - (["in"], POINTER(GUID), "kfid"), - (["in"], LPCWSTR, "pszLibraryName"), - (["in"], c_ulong, "lrf"), - (["out"], POINTER(POINTER(IShellItem)), "ppsiNewItem")) - ] - QueryInterface: Callable[[GUID, comtypes.IUnknown], HRESULT] - AddRef: Callable[[], ULONG] - Release: Callable[[], ULONG] - LoadLibraryFromItem: Callable[[_Pointer[IShellLibrary], IShellItem, c_ulong], HRESULT] - LoadLibraryFromKnownFolder: Callable[[_Pointer[IShellLibrary], GUID, c_ulong], HRESULT] - AddFolder: Callable[[_Pointer[IShellLibrary], IShellItem], HRESULT] - RemoveFolder: Callable[[_Pointer[IShellLibrary], IShellItem], HRESULT] - GetFolders: Callable[[_Pointer[IShellLibrary], c_int, GUID, _Pointer[_Pointer[c_void_p]]], HRESULT] - ResolveFolder: Callable[[_Pointer[IShellLibrary], IShellItem, c_ulong, GUID, _Pointer[_Pointer[c_void_p]]], HRESULT] - GetDefaultSaveFolder: Callable[[_Pointer[IShellLibrary], c_int, GUID, _Pointer[_Pointer[c_void_p]]], HRESULT] - SetDefaultSaveFolder: Callable[[_Pointer[IShellLibrary], c_int, IShellItem], HRESULT] - GetOptions: Callable[[_Pointer[IShellLibrary], _Pointer[c_uint]], HRESULT] - SetOptions: Callable[[_Pointer[IShellLibrary], c_ulong, c_ulong], HRESULT] - GetFolderType: Callable[[_Pointer[IShellLibrary], GUID], HRESULT] - SetFolderType: Callable[[_Pointer[IShellLibrary], GUID], HRESULT] - GetIcon: Callable[[_Pointer[IShellLibrary], _Pointer[LPWSTR]], HRESULT] - SetIcon: Callable[[_Pointer[IShellLibrary], LPCWSTR], HRESULT] - Commit: Callable[[_Pointer[IShellLibrary]], HRESULT] - Save: Callable[[_Pointer[IShellLibrary], IShellItem, LPCWSTR, c_ulong, IShellItem], HRESULT] - SaveInKnownFolder: Callable[[_Pointer[IShellLibrary], GUID, LPCWSTR, c_ulong, IShellItem], HRESULT] -class ShellLibrary(comtypes.COMObject): - _com_interfaces_: Sequence[type[comtypes.IUnknown]] = [IShellLibrary] - def LoadLibraryFromItem(self, psi: IShellItem, grfMode: c_ulong | int) -> HRESULT: # noqa: N803 - return S_OK - def LoadLibraryFromKnownFolder(self, kfidLibrary: GUID, grfMode: c_ulong | int) -> HRESULT: # noqa: N803 - return S_OK - def AddFolder(self, psi: IShellItem) -> HRESULT: - return S_OK - def RemoveFolder(self, psi: IShellItem) -> HRESULT: - return S_OK - def GetFolders(self, lff: c_int | int, riid: GUID, ppv: _Pointer[_Pointer[c_void_p]]) -> HRESULT: - return S_OK - def ResolveFolder(self, psi: IShellItem, grfMode: c_ulong | int, riid: GUID, ppv: _Pointer[_Pointer[c_void_p]]) -> HRESULT: # noqa: N803 - return S_OK - def GetDefaultSaveFolder(self, dsft: c_int | int, riid: GUID, ppv: _Pointer[_Pointer[c_void_p]]) -> HRESULT: - return S_OK - def SetDefaultSaveFolder(self, dsft: c_int | int, psi: IShellItem) -> HRESULT: - return S_OK - def GetOptions(self, pOptions: _Pointer[c_uint]) -> HRESULT: # noqa: N803 - return S_OK - def SetOptions(self, stfOptions: c_ulong | int, stfMask: c_ulong | int) -> HRESULT: # noqa: N803 - return S_OK - def GetFolderType(self, pftid: GUID) -> HRESULT: - return S_OK - def SetFolderType(self, ftid: GUID) -> HRESULT: - return S_OK - def GetIcon(self, ppszIcon: _Pointer[LPWSTR]) -> HRESULT: # noqa: N803 - return S_OK - def SetIcon(self, pszIcon: LPCWSTR | str) -> HRESULT: # noqa: N803 - return S_OK - def Commit(self) -> HRESULT: - return S_OK - def Save(self, psiFolderToSaveIn: IShellItem, pszLibraryName: LPCWSTR | str, lrf: c_ulong | int, ppsiNewItem: IShellItem) -> HRESULT: # noqa: N803 - return S_OK - def SaveInKnownFolder(self, kfid: GUID, pszLibraryName: LPCWSTR | str, lrf: c_ulong | int, ppsiNewItem: IShellItem) -> HRESULT: # noqa: N803 - return S_OK - - -class IFileOpenDialog(IFileDialog): - _case_insensitive_: bool = True - _iid_: GUID = IID_IFileOpenDialog - _methods_: ClassVar[list[_ComMemberSpec]] = [ - COMMETHOD([], HRESULT, "GetResults", - (["out"], POINTER(POINTER(IShellItemArray)), "ppenum")), - COMMETHOD([], HRESULT, "GetSelectedItems", - (["out"], POINTER(POINTER(IShellItemArray)), "ppsai")) - ] - GetResults: Callable[[], IShellItemArray] - GetSelectedItems: Callable[[], IShellItemArray] -class FileOpenDialog(comtypes.COMObject): - _com_interfaces_: Sequence[type[comtypes.IUnknown]] = [IFileOpenDialog] - def QueryInterface(self, riid: GUID, ppvObject: comtypes.IUnknown) -> HRESULT: # noqa: N803 - return S_OK - def AddRef(self) -> ULONG: - return ULONG(-1) - def Release(self) -> ULONG: - return ULONG(-1) - def Show(self, hwndParent: HWND | int) -> HRESULT: # noqa: N803 - return S_OK - def SetFileTypes(self, cFileTypes: c_uint | int, rgFilterSpec: Array[COMDLG_FILTERSPEC]) -> HRESULT: # noqa: N803 - return S_OK - def SetFileTypeIndex(self, iFileType: c_uint | int) -> HRESULT: # noqa: N803 - return S_OK - def GetFileTypeIndex(self, piFileType: _Pointer[c_uint]) -> HRESULT: # noqa: N803 - return S_OK - def Advise(self, pfde: _Pointer[comtypes.IUnknown], pdwCookie: _Pointer[DWORD]) -> HRESULT: # noqa: N803 - return S_OK - def Unadvise(self, dwCookie: int) -> HRESULT: # noqa: N803 - return S_OK - def SetOptions(self, fos: int) -> HRESULT: - return S_OK - def GetOptions(self, pfos: _Pointer[DWORD]) -> HRESULT: - return S_OK - def SetDefaultFolder(self, psi: IShellItem) -> HRESULT: - return S_OK - def SetFolder(self, psi: IShellItem) -> HRESULT: - return S_OK - def GetFolder(self, ppsi: IShellItem) -> HRESULT: - return S_OK - def GetCurrentSelection(self, ppsi: IShellItem) -> HRESULT: - return S_OK - def SetFileName(self, pszName: LPCWSTR | str) -> HRESULT: # noqa: N803 - return S_OK - def GetFileName(self, pszName: _Pointer[LPWSTR]) -> HRESULT: # noqa: N803 - return S_OK - def SetTitle(self, pszTitle: LPCWSTR | str) -> HRESULT: # noqa: N803 - return S_OK - def SetOkButtonLabel(self, pszText: LPCWSTR | str) -> HRESULT: # noqa: N803 - return S_OK - def SetFileNameLabel(self, pszLabel: LPCWSTR | str) -> HRESULT: # noqa: N803 - return S_OK - def GetResult(self, ppsi: IShellItem) -> HRESULT: - return S_OK - def AddPlace(self, psi: IShellItem, fdap: c_int) -> HRESULT: - return S_OK - def SetDefaultExtension(self, pszDefaultExtension: LPCWSTR | str) -> HRESULT: # noqa: N803 - return S_OK - def Close(self, hr: HRESULT | int) -> HRESULT: - return S_OK - def SetClientGuid(self, guid: GUID) -> HRESULT: - return S_OK - def ClearClientData(self) -> HRESULT: - return S_OK - def SetFilter(self, isFilter: IShellItemFilter) -> HRESULT: # noqa: N803 - return S_OK - def GetResults(self, isArray: IShellItemArray) -> HRESULT: # noqa: N803 - return S_OK - def GetSelectedItems(self, ppsai: IShellItemArray) -> HRESULT: - return S_OK - - -class IFileSaveDialog(IFileDialog): - _case_insensitive_: bool = True - _iid_: GUID = IID_IFileSaveDialog - _methods_: ClassVar[list[_ComMemberSpec]] = [ - COMMETHOD([], HRESULT, "SetSaveAsItem", - (["in"], POINTER(IShellItem), "psi")), - COMMETHOD([], HRESULT, "SetProperties", - (["in"], POINTER(comtypes.IUnknown), "pStore")), - COMMETHOD([], HRESULT, "SetCollectedProperties", - (["in"], POINTER(comtypes.IUnknown), "pList"), - (["in"], BOOL, "fAppendDefault")), - COMMETHOD([], HRESULT, "GetProperties", - (["out"], POINTER(POINTER(comtypes.IUnknown)), "ppStore")), - COMMETHOD([], HRESULT, "ApplyProperties", - (["in"], POINTER(IShellItem), "psi"), - (["in"], POINTER(comtypes.IUnknown), "pStore"), - (["in"], HWND, "hwnd"), - (["in"], POINTER(comtypes.IUnknown), "pSink")) - ] - SetSaveAsItem: Callable[[IShellItem], HRESULT] - SetProperties: Callable[[_Pointer[comtypes.IUnknown]], HRESULT] - SetCollectedProperties: Callable[[_Pointer[comtypes.IUnknown], BOOL], HRESULT] - GetProperties: Callable[[_Pointer[_Pointer[comtypes.IUnknown]]], HRESULT] - ApplyProperties: Callable[[IShellItem, _Pointer[comtypes.IUnknown], HWND, _Pointer[comtypes.IUnknown]], HRESULT] -class FileSaveDialog(comtypes.COMObject): - _com_interfaces_: Sequence[type[comtypes.IUnknown]] = [IFileSaveDialog] - def QueryInterface(self, riid: GUID, ppvObject: comtypes.IUnknown) -> HRESULT: # noqa: N803 - return S_OK - def AddRef(self) -> ULONG: - return ULONG(-1) - def Release(self) -> ULONG: - return ULONG(-1) - def Show(self, hwndParent: HWND | int) -> HRESULT: # noqa: N803 - return S_OK - def SetFileTypes(self, cFileTypes: c_uint | int, rgFilterSpec: _Pointer[COMDLG_FILTERSPEC]) -> HRESULT: # noqa: N803 - return S_OK - def SetFileTypeIndex(self, iFileType: c_uint | int) -> HRESULT: # noqa: N803 - return S_OK - def GetFileTypeIndex(self, piFileType: _Pointer[c_uint]) -> HRESULT: # noqa: N803 - return S_OK - def Advise(self, pfde: _Pointer[comtypes.IUnknown], pdwCookie: _Pointer[DWORD]) -> HRESULT: # noqa: N803 - return S_OK - def Unadvise(self, dwCookie: int) -> HRESULT: # noqa: N803 - return S_OK - def SetOptions(self, fos: int) -> HRESULT: - return S_OK - def GetOptions(self, pfos: _Pointer[DWORD]) -> HRESULT: - return S_OK - def SetDefaultFolder(self, psi: IShellItem) -> HRESULT: - return S_OK - def SetFolder(self, psi: IShellItem) -> HRESULT: - return S_OK - def GetFolder(self, ppsi: IShellItem) -> HRESULT: - return S_OK - def GetCurrentSelection(self, ppsi: IShellItem) -> HRESULT: - return S_OK - def SetFileName(self, pszName: LPCWSTR | str) -> HRESULT: # noqa: N803 - return S_OK - def GetFileName(self, pszName: _Pointer[LPWSTR]) -> HRESULT: # noqa: N803 - return S_OK - def SetTitle(self, pszTitle: LPCWSTR | str) -> HRESULT: # noqa: N803 - return S_OK - def SetOkButtonLabel(self, pszText: LPCWSTR | str) -> HRESULT: # noqa: N803 - return S_OK - def SetFileNameLabel(self, pszLabel: LPCWSTR | str) -> HRESULT: # noqa: N803 - return S_OK - def GetResult(self, ppsi: IShellItem) -> HRESULT: - return S_OK - def AddPlace(self, psi: IShellItem, fdap: c_int) -> HRESULT: - return S_OK - def SetDefaultExtension(self, pszDefaultExtension: LPCWSTR | str) -> HRESULT: # noqa: N803 - return S_OK - def Close(self, hr: HRESULT | int) -> HRESULT: - return S_OK - def SetClientGuid(self, guid: GUID) -> HRESULT: - return S_OK - def ClearClientData(self) -> HRESULT: - return S_OK - def SetFilter(self, pFilter: IShellItemFilter) -> HRESULT: # noqa: N803 - return S_OK - def SetSaveAsItem(self, psi: IShellItem) -> HRESULT: - return S_OK - def SetProperties(self, pStore: _Pointer[comtypes.IUnknown]) -> HRESULT: # noqa: N803 - return S_OK - def SetCollectedProperties(self, pList: _Pointer[comtypes.IUnknown], fAppendDefault: BOOL | int) -> HRESULT: # noqa: N803 - return S_OK - def GetProperties(self, ppStore: comtypes.IUnknown) -> HRESULT: # noqa: N803 - return S_OK - def ApplyProperties(self, psi: IShellItem, pStore: _Pointer[comtypes.IUnknown], hwnd: HWND | int, pSink: _Pointer[comtypes.IUnknown]) -> HRESULT: # noqa: N803 - return S_OK - - -class IFileDialogCustomize(comtypes.IUnknown): - _case_insensitive_ = True - _iid_ = IID_IFileDialogCustomize - _methods_: ClassVar[list[_ComMemberSpec]] = [ - COMMETHOD([], HRESULT, "EnableOpenDropDown", (["in"], c_uint, "dwIDCtl")), - COMMETHOD([], HRESULT, "AddText", (["in"], c_uint, "dwIDCtl"), (["in"], LPCWSTR, "pszText")), - COMMETHOD([], HRESULT, "AddPushButton", (["in"], c_uint, "dwIDCtl"), (["in"], LPCWSTR, "pszLabel")), - COMMETHOD([], HRESULT, "AddCheckButton", (["in"], c_uint, "dwIDCtl"), (["in"], LPCWSTR, "pszLabel"), (["in"], c_int, "bChecked")), - COMMETHOD([], HRESULT, "AddRadioButtonList", (["in"], c_uint, "dwIDCtl")), - COMMETHOD([], HRESULT, "AddComboBox", (["in"], c_uint, "dwIDCtl")), - COMMETHOD([], HRESULT, "AddControlItem", (["in"], c_uint, "dwIDCtl"), (["in"], c_uint, "dwIDItem"), (["in"], LPCWSTR, "pszLabel")), - COMMETHOD([], HRESULT, "AddEditBox", (["in"], c_uint, "dwIDCtl"), (["in"], LPCWSTR, "pszText")), - COMMETHOD([], HRESULT, "AddSeparator", (["in"], c_uint, "dwIDCtl")), - COMMETHOD([], HRESULT, "AddMenu", (["in"], c_uint, "dwIDCtl"), (["in"], LPCWSTR, "pszLabel")), - COMMETHOD([], HRESULT, "SetControlLabel", (["in"], c_uint, "dwIDCtl"), (["in"], LPCWSTR, "pszLabel")), - COMMETHOD([], HRESULT, "SetControlState", (["in"], c_uint, "dwIDCtl"), (["in"], c_int, "dwState")), - COMMETHOD([], HRESULT, "SetCheckButtonState", (["in"], c_uint, "dwIDCtl"), (["in"], c_int, "bChecked")), - COMMETHOD([], HRESULT, "GetCheckButtonState", (["in"], c_uint, "dwIDCtl"), (["out"], POINTER(c_int), "pbChecked")), - COMMETHOD([], HRESULT, "SetEditBoxText", (["in"], c_uint, "dwIDCtl"), (["in"], LPCWSTR, "pszText")), - COMMETHOD([], HRESULT, "GetEditBoxText", (["in"], c_uint, "dwIDCtl"), (["out"], POINTER(LPCWSTR), "ppszText")), - COMMETHOD([], HRESULT, "SetControlItemText", (["in"], c_uint, "dwIDCtl"), (["in"], c_uint, "dwIDItem"), (["in"], LPCWSTR, "pszLabel")), - COMMETHOD([], HRESULT, "GetControlItemState", (["in"], c_uint, "dwIDCtl"), (["in"], c_uint, "dwIDItem"), (["out"], POINTER(c_int), "pdwState")), - COMMETHOD([], HRESULT, "SetControlItemState", (["in"], c_uint, "dwIDCtl"), (["in"], c_uint, "dwIDItem"), (["in"], c_int, "dwState")), - COMMETHOD([], HRESULT, "GetSelectedControlItem", (["in"], c_uint, "dwIDCtl"), (["out"], POINTER(c_uint), "pdwIDItem")), - COMMETHOD([], HRESULT, "SetSelectedControlItem", (["in"], c_uint, "dwIDCtl"), (["in"], c_uint, "dwIDItem")), - COMMETHOD([], HRESULT, "StartVisualGroup", (["in"], c_uint, "dwIDCtl"), (["in"], LPCWSTR, "pszLabel")), - COMMETHOD([], HRESULT, "EndVisualGroup", (["in"], c_uint, "dwIDCtl")), - COMMETHOD([], HRESULT, "MakeProminent", (["in"], c_uint, "dwIDCtl")), - COMMETHOD([], HRESULT, "RemoveControlItem", (["in"], c_uint, "dwIDCtl"), (["in"], c_uint, "dwIDItem")), - COMMETHOD([], HRESULT, "RemoveAllControlItems", (["in"], c_uint, "dwIDCtl")), - COMMETHOD([], HRESULT, "GetControlState", (["in"], c_uint, "dwIDCtl"), (["out"], POINTER(c_int), "pdwState")), - ] - EnableOpenDropDown: Callable[[int], HRESULT] - AddText: Callable[[int, str], HRESULT] - AddPushButton: Callable[[int, str], HRESULT] - AddCheckButton: Callable[[int, str, int], HRESULT] - AddRadioButtonList: Callable[[int], HRESULT] - AddComboBox: Callable[[int], HRESULT] - AddControlItem: Callable[[int, int, str], HRESULT] - AddEditBox: Callable[[int, str], HRESULT] - AddSeparator: Callable[[int], HRESULT] - AddMenu: Callable[[int, str], HRESULT] - SetControlLabel: Callable[[int, str], HRESULT] - SetControlState: Callable[[int, int], HRESULT] - SetCheckButtonState: Callable[[int, int], HRESULT] - GetCheckButtonState: Callable[[int], int] - SetEditBoxText: Callable[[int, str], HRESULT] - GetEditBoxText: Callable[[int], LPCWSTR] - SetControlItemText: Callable[[int, int, str], HRESULT] - GetControlItemState: Callable[[int, int], int] - SetControlItemState: Callable[[int, int, int], HRESULT] - GetSelectedControlItem: Callable[[int], int] - SetSelectedControlItem: Callable[[int, int], HRESULT] - StartVisualGroup: Callable[[int, str], HRESULT] - EndVisualGroup: Callable[[int], HRESULT] - MakeProminent: Callable[[int], HRESULT] - RemoveControlItem: Callable[[int, int], HRESULT] - RemoveAllControlItems: Callable[[int], HRESULT] - GetControlState: Callable[[int], int] - - -class IFileDialogControlEvents(comtypes.IUnknown): - _case_insensitive_ = True - _iid_ = IID_IFileDialogControlEvents - _methods_: ClassVar[list[_ComMemberSpec]] = [ - COMMETHOD([], HRESULT, "OnItemSelected", - (["in"], comtypes.POINTER(IFileDialogCustomize), "pfdc"), - (["in"], c_int, "dwIDCtl"), - (["in"], c_int, "dwIDItem")), - COMMETHOD([], HRESULT, "OnButtonClicked", - (["in"], comtypes.POINTER(IFileDialogCustomize), "pfdc"), - (["in"], c_int, "dwIDCtl")), - COMMETHOD([], HRESULT, "OnCheckButtonToggled", - (["in"], comtypes.POINTER(IFileDialogCustomize), "pfdc"), - (["in"], c_int, "dwIDCtl"), - (["in"], c_bool, "bChecked")), - COMMETHOD([], HRESULT, "OnControlActivating", - (["in"], comtypes.POINTER(IFileDialogCustomize), "pfdc"), - (["in"], c_int, "dwIDCtl")), - ] - OnButtonClicked: Callable[[IFileDialogCustomize, c_uint], HRESULT] - OnCheckButtonToggled: Callable[[IFileDialogCustomize, c_uint, c_int], HRESULT] - OnControlActivating: Callable[[IFileDialogCustomize, c_uint], HRESULT] - OnItemSelected: Callable[[IFileDialogCustomize, c_uint, c_uint], HRESULT] - - -if __name__ == "__main__": - assert ModalWindow() - assert ShellItem() - assert ContextMenu() - assert ShellFolder() - assert ShellItemArray() - assert ShellItemFilter() - assert EnumShellItems() - assert PropertyStore() - assert FileOperationProgressSink() - assert FileDialogEvents() - assert FileDialog() - assert ShellLibrary() - assert FileOpenDialog() - assert FileSaveDialog() diff --git a/winforms/src/toga_winforms/libs/py_wrappers/com/progress_dialog.py b/winforms/src/toga_winforms/libs/py_wrappers/com/progress_dialog.py deleted file mode 100644 index 91f5c4bbe1..0000000000 --- a/winforms/src/toga_winforms/libs/py_wrappers/com/progress_dialog.py +++ /dev/null @@ -1,149 +0,0 @@ -from __future__ import annotations - -import errno -import os -from pathlib import WindowsPath -from typing import TYPE_CHECKING - -import comtypes -from comtypes.client import CreateObject -from comtypes.gen import Shell32 -from utility.logger_util import RobustRootLogger - -from toga_winforms.libs.py_wrappers.com.interfaces import ( - CLSID_FileOperation, - IFileOperationProgressSink, -) -from toga_winforms.libs.py_wrappers.hresult import S_OK - -if TYPE_CHECKING: - from typing_extensions import Literal - - -class FileOperationProgressSinkImpl(IFileOperationProgressSink): - def StartOperations(self): - print("Operation started") - return S_OK - - def FinishOperations(self, hr): - print("Operation finished with hr:", hr) - return S_OK - - def PreRenameItem(self, dwFlags, psiItem, pszNewName): - print(f"Preparing to rename item {psiItem.GetDisplayName()} to {pszNewName}") - return S_OK - - def PostRenameItem(self, dwFlags, psiItem, pszNewName, hrRename, psiNewlyCreated): - print(f"Renamed item {psiItem.GetDisplayName()} to {pszNewName}") - return S_OK - - def PreMoveItem(self, dwFlags, psiItem, psiDestinationFolder, pszNewName): - print(f"Preparing to move item {psiItem.GetDisplayName()} to {pszNewName}") - return S_OK - - def PostMoveItem(self, dwFlags, psiItem, psiDestinationFolder, pszNewName, hrMove, psiNewlyCreated): - print(f"Moved item {psiItem.GetDisplayName()} to {pszNewName}") - return S_OK - - def PreCopyItem(self, dwFlags, psiItem, psiDestinationFolder, pszNewName): - print(f"Preparing to copy item {psiItem.GetDisplayName()} to {pszNewName}") - return S_OK - - def PostCopyItem(self, dwFlags, psiItem, psiDestinationFolder, pszNewName, hrCopy, psiNewlyCreated): - print(f"Copied item {psiItem.GetDisplayName()} to {pszNewName}") - return S_OK - - def PreDeleteItem(self, dwFlags, psiItem): - print(f"Preparing to delete item {psiItem.GetDisplayName()}") - return S_OK - - def PostDeleteItem(self, dwFlags, psiItem, hrDelete, psiNewlyCreated): - print(f"Deleted item {psiItem.GetDisplayName()}") - return S_OK - - def PreNewItem(self, dwFlags, psiDestinationFolder, pszNewName): - print(f"Preparing to create new item {pszNewName}") - return S_OK - - def PostNewItem(self, dwFlags, psiDestinationFolder, pszNewName, pszTemplateName, dwFileAttributes, hrNew, psiNewItem): - print(f"Created new item {pszNewName}") - return S_OK - - def UpdateProgress(self, iWorkTotal, iWorkSoFar): - print(f"Progress: {iWorkSoFar}/{iWorkTotal}") - return S_OK - - def ResetTimer(self): - print("Timer reset") - return S_OK - - def PauseTimer(self): - print("Timer paused") - return S_OK - - def ResumeTimer(self): - print("Timer resumed") - return S_OK - -def initialize_com(): - comtypes.CoInitialize() - -def uninitialize_com(): - comtypes.CoUninitialize() - -def create_shell_item(path_obj): - path = WindowsPath(path_obj).resolve() - if not path.exists(): - raise FileNotFoundError(errno.ENOENT, os.strerror(errno.ENOENT), str(path)) - shell = CreateObject(Shell32.Shell, interface=Shell32.IShellDispatch4) - folder = shell.NameSpace(str(path.parent)) - item = folder.ParseName(path.name) - return item.QueryInterface(Shell32.IShellItem) - -def perform_file_operation( - source: os.PathLike | str, - destination: os.PathLike | str, - operation: Literal["copy", "move"] = "copy", -): - initialize_com() - source_path = source - destination_path = destination - - try: - file_operation = CreateObject(CLSID_FileOperation, interface=Shell32.IFileOperation) - - source_item = create_shell_item(source_path) - dest_item = create_shell_item(destination_path) - - progress_sink = FileOperationProgressSinkImpl() - file_operation.Advise(progress_sink, None) - - file_operation.SetOperationFlags(Shell32.FOF_NOCONFIRMATION | Shell32.FOF_SILENT) - - if operation == "copy": - file_operation.CopyItem(source_item, dest_item, None, None) - elif operation == "move": - file_operation.MoveItem(source_item, dest_item, None, None) - elif operation == "delete": - file_operation.DeleteItem(source_item, None) - else: - raise ValueError(f"Unsupported operation: {operation}") # noqa: TRY301 - - result = file_operation.PerformOperations() - if result != 0: - raise comtypes.COMError(result, "Error performing file operation", None) # noqa: TRY301 - - print(f"File operation {operation} from {source_path} to {destination_path} completed") - - except Exception as e: # noqa: BLE001 - RobustRootLogger().exception(f"General error while attempting to perform file operations with the com objects: {e.__class__.__name__}: {e}") - - finally: - uninitialize_com() - -# Example usage -if __name__ == "__main__": - source_path = r"C:\path\to\source\file.txt" - destination_path = r"C:\path\to\destination\folder" - - perform_file_operation(source_path, destination_path, operation="copy") diff --git a/winforms/src/toga_winforms/libs/py_wrappers/com/windialogs.py b/winforms/src/toga_winforms/libs/py_wrappers/com/windialogs.py deleted file mode 100644 index ef43e08897..0000000000 --- a/winforms/src/toga_winforms/libs/py_wrappers/com/windialogs.py +++ /dev/null @@ -1,857 +0,0 @@ -from __future__ import annotations - -import errno -import json -import os -import random -from ctypes import POINTER, WINFUNCTYPE, byref, c_ulong, c_void_p, c_wchar_p, windll -from ctypes import cast as cast_with_ctypes -from ctypes.wintypes import HMODULE, HWND, LPCWSTR -from typing import TYPE_CHECKING, Sequence - -import comtypes # pyright: ignore[reportMissingTypeStubs] -import comtypes.client # pyright: ignore[reportMissingTypeStubs] -from utility.logger_util import RobustRootLogger -from utility.system.path import WindowsPath - -from toga_winforms.libs.py_wrappers.com.com_helpers import HandleCOMCall -from toga_winforms.libs.py_wrappers.com.com_types import GUID -from toga_winforms.libs.py_wrappers.com.interfaces import ( - COMDLG_FILTERSPEC, - SFGAO, - SIGDN, - CLSID_FileOpenDialog, - CLSID_FileSaveDialog, - COMFunctionPointers, - FileOpenOptions, - IFileDialogControlEvents, - IFileDialogCustomize, - IFileOpenDialog, - IFileSaveDialog, - IID_IFileDialogCustomize, - IShellItem, -) -from toga_winforms.libs.py_wrappers.hresult import HRESULT, S_OK - -if TYPE_CHECKING: - from ctypes import Array, _FuncPointer, _Pointer - from ctypes.wintypes import BOOL, DWORD, LPWSTR - - from toga_winforms.libs.py_wrappers.com.interfaces import IFileDialog, IShellItemArray - - -class FileDialogControlEvents(comtypes.COMObject): - _com_interfaces_: Sequence[type[comtypes.IUnknown]] = [IFileDialogControlEvents] - - def OnItemSelected(self, pfdc: IFileDialogCustomize, dwIDCtl: DWORD, dwIDItem: DWORD) -> HRESULT: - # Implement the logic for when an item is selected - return S_OK - - def OnButtonClicked(self, pfdc: IFileDialogCustomize, dwIDCtl: DWORD) -> HRESULT: - if dwIDCtl == 1001: - # Implement the specific logic for button with ID 1001 - print("Button with ID 1001 was clicked.") - # Add any other logic you need for this button - else: - # Handle other button IDs if necessary - print(f"Button with ID {dwIDCtl} was clicked.") - - return S_OK - - def OnCheckButtonToggled(self, pfdc: IFileDialogCustomize, dwIDCtl: DWORD, bChecked: BOOL) -> HRESULT: - # Implement the logic for when a check button is toggled - return S_OK - - def OnControlActivating(self, pfdc: IFileDialogCustomize, dwIDCtl: DWORD) -> HRESULT: - # Implement the logic for when a control is activated - return S_OK - - -# Load COM function pointers -def LoadCOMFunctionPointers(dialog_type: type[IFileDialog | IFileOpenDialog | IFileSaveDialog]) -> COMFunctionPointers: - comFuncPtrs = COMFunctionPointers() - comFuncPtrs.hOle32 = comFuncPtrs.load_library("ole32.dll") - comFuncPtrs.hShell32 = comFuncPtrs.load_library("shell32.dll") - - - # Get function pointers - if comFuncPtrs.hOle32: # sourcery skip: extract-method - PFN_CoInitialize: type[_FuncPointer] = WINFUNCTYPE(HRESULT, POINTER(dialog_type)) - PFN_CoUninitialize: type[_FuncPointer] = WINFUNCTYPE(None) - PFN_CoCreateInstance: type[_FuncPointer] = WINFUNCTYPE(HRESULT, POINTER(GUID), c_void_p, c_ulong, POINTER(GUID), POINTER(POINTER(dialog_type))) - PFN_CoTaskMemFree: type[_FuncPointer] = WINFUNCTYPE(None, c_void_p) - comFuncPtrs.pCoInitialize = comFuncPtrs.resolve_function(comFuncPtrs.hOle32, b"CoInitialize", PFN_CoInitialize) - comFuncPtrs.pCoUninitialize = comFuncPtrs.resolve_function(comFuncPtrs.hOle32, b"CoUninitialize", PFN_CoUninitialize) - comFuncPtrs.pCoCreateInstance = comFuncPtrs.resolve_function(comFuncPtrs.hOle32, b"CoCreateInstance", PFN_CoCreateInstance) - comFuncPtrs.pCoTaskMemFree = comFuncPtrs.resolve_function(comFuncPtrs.hOle32, b"CoTaskMemFree", PFN_CoTaskMemFree) - - if comFuncPtrs.hShell32: - PFN_SHCreateItemFromParsingName: type[_FuncPointer] = WINFUNCTYPE(HRESULT, LPCWSTR, c_void_p, POINTER(GUID), POINTER(POINTER(IShellItem))) - comFuncPtrs.pSHCreateItemFromParsingName = comFuncPtrs.resolve_function(comFuncPtrs.hShell32, b"SHCreateItemFromParsingName", PFN_SHCreateItemFromParsingName) - return comFuncPtrs - - -def FreeCOMFunctionPointers(comFuncPtrs: COMFunctionPointers): # noqa: N803 - if comFuncPtrs.hOle32: - windll.kernel32.FreeLibrary(cast_with_ctypes(comFuncPtrs.hOle32, HMODULE)) - if comFuncPtrs.hShell32: - windll.kernel32.FreeLibrary(cast_with_ctypes(comFuncPtrs.hShell32, HMODULE)) - - -def show_file_dialog( - fileDialog: IFileOpenDialog | IFileSaveDialog | IFileDialog, # noqa: N803 - hwndOwner: HWND, # noqa: N803 -) -> bool: - """Shows the IFileDialog. Returns True if the user progressed to the end and found a file. False if they cancelled.""" - hr: HRESULT | int = -1 - CANCELLED_BY_USER = -2147023673 - - try: - hr = fileDialog.Show(hwndOwner) - print(f"Dialog shown successfully, HRESULT: {hr}") - except OSError as e: - if e.winerror == CANCELLED_BY_USER: - print("Operation was canceled by the user.") - return False - raise - else: - HRESULT.raise_for_status(hr, "An unexpected error occurred showing the file browser dialog") - - return True - - -def createShellItem(comFuncs: COMFunctionPointers, path: str) -> _Pointer[IShellItem]: # noqa: N803, ARG001 - if not comFuncs.pSHCreateItemFromParsingName: - raise OSError("comFuncs.pSHCreateItemFromParsingName not found") - shell_item = POINTER(IShellItem)() - hr = comFuncs.pSHCreateItemFromParsingName(path, None, IShellItem._iid_, byref(shell_item)) - if hr != S_OK: - raise HRESULT(hr).exception(f"Failed to create shell item from path: {path}") - return shell_item - - -DEFAULT_FILTERS: list[COMDLG_FILTERSPEC] = [ - COMDLG_FILTERSPEC("All Files", "*.*"), - COMDLG_FILTERSPEC("Text Files", "*.txt"), - COMDLG_FILTERSPEC("Image Files", "*.png;*.jpg;*.jpeg;*.bmp;*.gif"), - COMDLG_FILTERSPEC("Document Files", "*.doc;*.docx;*.pdf;*.xls;*.xlsx"), - COMDLG_FILTERSPEC("Audio Files", "*.mp3;*.wav;*.wma;*.aac"), - COMDLG_FILTERSPEC("Video Files", "*.mp4;*.avi;*.mkv;*.mov;*.wmv"), - COMDLG_FILTERSPEC("Archive Files", "*.zip;*.rar;*.7z;*.tar;*.gz"), - COMDLG_FILTERSPEC("Executable Files", "*.exe;*.bat;*.msi"), - COMDLG_FILTERSPEC("HTML Files", "*.htm;*.html"), - COMDLG_FILTERSPEC("XML Files", "*.xml"), - COMDLG_FILTERSPEC("JavaScript Files", "*.js"), - COMDLG_FILTERSPEC("CSS Files", "*.css"), - COMDLG_FILTERSPEC("Python Files", "*.py"), - COMDLG_FILTERSPEC("C/C++ Files", "*.c;*.cpp;*.h;*.hpp"), - COMDLG_FILTERSPEC("Java Files", "*.java"), - COMDLG_FILTERSPEC("Ruby Files", "*.rb"), - COMDLG_FILTERSPEC("Perl Files", "*.pl"), - COMDLG_FILTERSPEC("PHP Files", "*.php"), - COMDLG_FILTERSPEC("Shell Script Files", "*.sh"), - COMDLG_FILTERSPEC("Batch Files", "*.bat"), - COMDLG_FILTERSPEC("INI Files", "*.ini"), - COMDLG_FILTERSPEC("Log Files", "*.log"), - COMDLG_FILTERSPEC("SVG Files", "*.svg"), - COMDLG_FILTERSPEC("Markdown Files", "*.md"), - COMDLG_FILTERSPEC("YAML Files", "*.yaml;*.yml"), - COMDLG_FILTERSPEC("JSON Files", "*.json"), - COMDLG_FILTERSPEC("PowerShell Files", "*.ps1"), - COMDLG_FILTERSPEC("MATLAB Files", "*.m"), - COMDLG_FILTERSPEC("R Files", "*.r"), - COMDLG_FILTERSPEC("Lua Files", "*.lua"), - COMDLG_FILTERSPEC("Rust Files", "*.rs"), - COMDLG_FILTERSPEC("Go Files", "*.go"), - COMDLG_FILTERSPEC("Swift Files", "*.swift"), - COMDLG_FILTERSPEC("Kotlin Files", "*.kt;*.kts"), - COMDLG_FILTERSPEC("Objective-C Files", "*.m;*.mm"), - COMDLG_FILTERSPEC("SQL Files", "*.sql"), - COMDLG_FILTERSPEC("Config Files", "*.conf"), - COMDLG_FILTERSPEC("CSV Files", "*.csv"), - COMDLG_FILTERSPEC("TSV Files", "*.tsv"), - COMDLG_FILTERSPEC("LaTeX Files", "*.tex"), - COMDLG_FILTERSPEC("BibTeX Files", "*.bib"), - COMDLG_FILTERSPEC("Makefiles", "Makefile"), - COMDLG_FILTERSPEC("Gradle Files", "*.gradle"), - COMDLG_FILTERSPEC("Ant Build Files", "*.build.xml"), - COMDLG_FILTERSPEC("Maven POM Files", "pom.xml"), - COMDLG_FILTERSPEC("Dockerfiles", "Dockerfile"), - COMDLG_FILTERSPEC("Vagrantfiles", "Vagrantfile"), - COMDLG_FILTERSPEC("Terraform Files", "*.tf"), - COMDLG_FILTERSPEC("HCL Files", "*.hcl"), - COMDLG_FILTERSPEC("Kubernetes YAML Files", "*.yaml;*.yml") -] - - -def configure_file_dialog( # noqa: PLR0913, PLR0912, C901, PLR0915 - file_dialog: IFileDialog, - title: str | None = None, - options: int = 0, - default_folder: str | None = None, - ok_button_label: str | None = None, - file_name_label: str | None = None, - file_types: list[tuple[str, str]] | None = None, - default_extension: str | None = None, - dialog_interfaces: list[comtypes.IUnknown | comtypes.COMObject] | None = None, - hwnd: HWND | int | None = None, -) -> list[str] | None: # sourcery skip: low-code-quality - comFuncs: COMFunctionPointers = LoadCOMFunctionPointers(type(file_dialog)) - cookies = [] - if dialog_interfaces: - for interface in dialog_interfaces: - cookie = file_dialog.Advise(interface) - cookies.append(cookie) - hwnd = HWND(hwnd) if isinstance(hwnd, int) else hwnd - hwnd = HWND(0) if hwnd is None else hwnd - try: - if default_folder: - defaultFolder_path = WindowsPath(default_folder).resolve() - if not defaultFolder_path.safe_isdir(): - raise FileNotFoundError(errno.ENOENT, os.strerror(errno.ENOENT), str(defaultFolder_path)) - shell_item = createShellItem(comFuncs, str(defaultFolder_path)) - with HandleCOMCall(f"SetFolder({defaultFolder_path})") as check: - check(file_dialog.SetFolder(shell_item)) - with HandleCOMCall(f"SetDefaultFolder({defaultFolder_path})") as check: - check(file_dialog.SetDefaultFolder(shell_item)) - - # Resolve contradictory options - if options & FileOpenOptions.FOS_ALLNONSTORAGEITEMS: - if options & FileOpenOptions.FOS_FORCEFILESYSTEM: - RobustRootLogger().warning("Removing FileOpenOptions.FOS_FORCEFILESYSTEM to prevent conflict with FOS_ALLNONSTORAGEITEMS") - options &= ~FileOpenOptions.FOS_FORCEFILESYSTEM - if options & FileOpenOptions.FOS_PICKFOLDERS: - RobustRootLogger().warning("Removing FileOpenOptions.FOS_PICKFOLDERS to prevent conflict with FOS_ALLNONSTORAGEITEMS") - options &= ~FileOpenOptions.FOS_PICKFOLDERS - - def get_flag_differences(set_options: int, get_options: int) -> list[str]: - differences = set_options ^ get_options # XOR to find differing bits - differing_flags = [] - for flag in FileOpenOptions: - if differences & flag: - set_in_options = bool(set_options & flag) - set_in_cur_options = bool(get_options & flag) - differing_flags.append( - f"{flag.name}: SetOptions={'SET' if set_in_options else 'UNSET'}, GetOptions={'SET' if set_in_cur_options else 'UNSET'}" - ) - return differing_flags - - original_dialog_options = file_dialog.GetOptions() - print(f"Original dialog options: {original_dialog_options}") - with HandleCOMCall(f"SetOptions({options})") as check: - check(file_dialog.SetOptions(options)) - cur_options = file_dialog.GetOptions() - print(f"GetOptions({cur_options})") - - assert original_dialog_options != cur_options, ( - f"SetOptions call was completely ignored by the dialog interface, attempted to set {options}, " - f"but retrieved {cur_options} (the original)" - ) - if (options != cur_options): - differing_flags = get_flag_differences(options, cur_options) - RobustRootLogger().warning(f"Differing flags: {', '.join(differing_flags)}") - - if not options & FileOpenOptions.FOS_PICKFOLDERS: - filters: Array[COMDLG_FILTERSPEC] - if file_types: - print("Using custom file filters") - filters = (COMDLG_FILTERSPEC * len(file_types))( - *[ - (c_wchar_p(name), c_wchar_p(spec)) - for name, spec in file_types - ] - ) - else: - print("Using default file filters") - filters = (COMDLG_FILTERSPEC * len(DEFAULT_FILTERS))(*DEFAULT_FILTERS) - with HandleCOMCall(f"SetFileTypes({len(filters)})") as check: - check(file_dialog.SetFileTypes(len(filters), cast_with_ctypes(filters, POINTER(c_void_p)))) - - if title: - file_dialog.SetTitle(title) - - if ok_button_label: - file_dialog.SetOkButtonLabel(ok_button_label) - elif isinstance(file_dialog, IFileSaveDialog): - file_dialog.SetOkButtonLabel("Save") - elif options & FileOpenOptions.FOS_PICKFOLDERS: - file_dialog.SetOkButtonLabel("Select Folder") - else: - file_dialog.SetOkButtonLabel("Select File") - - if file_name_label: - file_dialog.SetFileNameLabel(file_name_label) - if default_extension: - file_dialog.SetDefaultExtension(default_extension) - - if show_file_dialog(file_dialog, hwnd): - return ( - [get_save_file_dialog_results(comFuncs, file_dialog)] - if isinstance(file_dialog, IFileSaveDialog) - else get_open_file_dialog_results(file_dialog) - ) - - finally: - for cookie in cookies: - file_dialog.Unadvise(cookie) - return None - - -def open_file_and_folder_dialog( # noqa: C901, PLR0913, PLR0912 - title: str | None = "Open File", - default_folder: str | None = None, - file_types: list[tuple[str, str]] | None = None, - default_extension: str | None = None, - *, - overwrite_prompt: bool = False, - strict_file_types: bool = False, - no_change_dir: bool = True, - force_filesystem: bool = True, - all_non_storage_items: bool = False, - no_validate: bool = False, - allow_multiple_selection: bool = False, - path_must_exist: bool = True, - file_must_exist: bool = True, - create_prompt: bool = False, - share_aware: bool = False, - no_readonly_return: bool = False, - no_test_file_create: bool = False, - hide_mru_places: bool = False, - hide_pinned_places: bool = False, - no_dereference_links: bool = False, - add_to_recent: bool = True, - show_hidden_files: bool = False, - default_no_minimode: bool = False, - force_preview_pane_on: bool = False, - ok_button_text: str | None = None, -) -> list[str] | None: # sourcery skip: low-code-quality - """Opens a file dialog to select files. - - Args: - title (str | None): The title of the dialog. - default_folder (str | None): The initial folder to open. - file_types (list[tuple[str, str]] | None): A list of file type filters. - default_extension (str | None): The default file extension. - overwrite_prompt (bool): Prompts if the selected file already exists. FileOpenOptions.FOS_OVERWRITEPROMPT. - strict_file_types (bool): Restricts selection to specified file types. FileOpenOptions.FOS_STRICTFILETYPES. - no_change_dir (bool): Prevents changing the current working directory. FileOpenOptions.FOS_NOCHANGEDIR. - force_filesystem (bool): Ensures only file system items are shown. FileOpenOptions.FOS_FORCEFILESYSTEM. - all_non_storage_items (bool): Allows selection of non-file system items. FileOpenOptions.FOS_ALLNONSTORAGEITEMS. - no_validate (bool): Disables file name validation. FileOpenOptions.FOS_NOVALIDATE. - allow_multiple_selection (bool): Allows selecting multiple files. FileOpenOptions.FOS_ALLOWMULTISELECT. - path_must_exist (bool): Requires the path to exist. FileOpenOptions.FOS_PATHMUSTEXIST. - file_must_exist (bool): Requires the file to exist. FileOpenOptions.FOS_FILEMUSTEXIST. - create_prompt (bool): Prompts to create a new file if it doesn't exist. FileOpenOptions.FOS_CREATEPROMPT. - share_aware (bool): Ensures the dialog is aware of sharing conflicts. FileOpenOptions.FOS_SHAREAWARE. - no_readonly_return (bool): Prevents selection of read-only items. FileOpenOptions.FOS_NOREADONLYRETURN. - no_test_file_create (bool): Disables testing file creation ability. FileOpenOptions.FOS_NOTESTFILECREATE. - hide_mru_places (bool): Hides most recently used places. FileOpenOptions.FOS_HIDEMRUPLACES. - hide_pinned_places (bool): Hides pinned places. FileOpenOptions.FOS_HIDEPINNEDPLACES. - no_dereference_links (bool): Prevents dereferencing shortcuts. FileOpenOptions.FOS_NODEREFERENCELINKS. - add_to_recent (bool): Prevents adding the file to recent files. FileOpenOptions.FOS_DONTADDTORECENT. - show_hidden_files (bool): Shows hidden files and folders. FileOpenOptions.FOS_FORCESHOWHIDDEN. - default_no_minimode (bool): Uses default non-minimized mode. FileOpenOptions.FOS_DEFAULTNOMINIMODE. - force_preview_pane_on (bool): Forces the preview pane to be visible. FileOpenOptions.FOS_FORCEPREVIEWPANEON. - ok_button_text (str): The text for the button used to select/confirm the dialog. - - Returns: - list[str] | None: A list of selected file paths or None if cancelled. - """ - options = 0 - if overwrite_prompt: - options |= FileOpenOptions.FOS_OVERWRITEPROMPT - if strict_file_types: - options |= FileOpenOptions.FOS_STRICTFILETYPES - if no_change_dir: - options |= FileOpenOptions.FOS_NOCHANGEDIR - if force_filesystem: - options |= FileOpenOptions.FOS_FORCEFILESYSTEM - if all_non_storage_items: - options |= FileOpenOptions.FOS_ALLNONSTORAGEITEMS - if no_validate: - options |= FileOpenOptions.FOS_NOVALIDATE - if allow_multiple_selection: - options |= FileOpenOptions.FOS_ALLOWMULTISELECT - if path_must_exist: - options |= FileOpenOptions.FOS_PATHMUSTEXIST - if file_must_exist: - options |= FileOpenOptions.FOS_FILEMUSTEXIST - if create_prompt: - options |= FileOpenOptions.FOS_CREATEPROMPT - if share_aware: - options |= FileOpenOptions.FOS_SHAREAWARE - if no_readonly_return: - options |= FileOpenOptions.FOS_NOREADONLYRETURN - if no_test_file_create: - options |= FileOpenOptions.FOS_NOTESTFILECREATE - if hide_mru_places: - options |= FileOpenOptions.FOS_HIDEMRUPLACES - if hide_pinned_places: - options |= FileOpenOptions.FOS_HIDEPINNEDPLACES - if no_dereference_links: - options |= FileOpenOptions.FOS_NODEREFERENCELINKS - if not add_to_recent: - options |= FileOpenOptions.FOS_DONTADDTORECENT - if show_hidden_files: - options |= FileOpenOptions.FOS_FORCESHOWHIDDEN - if default_no_minimode: - options |= FileOpenOptions.FOS_DEFAULTNOMINIMODE - if force_preview_pane_on: - options |= FileOpenOptions.FOS_FORCEPREVIEWPANEON - file_dialog = comtypes.client.CreateObject(CLSID_FileOpenDialog, interface=IFileOpenDialog) - customize_handler = file_dialog.QueryInterface(IFileDialogCustomize, IID_IFileDialogCustomize) - folder_button_id = 1001 - customize_handler.AddPushButton(folder_button_id, "Select Folder") - control_event_handler = FileDialogControlEvents() - return configure_file_dialog(file_dialog, title, options, default_folder, ok_button_text, None, file_types, default_extension, [control_event_handler]) - - -def open_file_dialog( # noqa: C901, PLR0913, PLR0912 - title: str | None = "Open File", - default_folder: str | None = None, - file_types: list[tuple[str, str]] | None = None, - default_extension: str | None = None, - *, - overwrite_prompt: bool = False, - strict_file_types: bool = False, - no_change_dir: bool = True, - force_filesystem: bool = True, - all_non_storage_items: bool = False, - no_validate: bool = False, - allow_multiple_selection: bool = False, - path_must_exist: bool = True, - file_must_exist: bool = True, - create_prompt: bool = False, - share_aware: bool = False, - no_readonly_return: bool = False, - no_test_file_create: bool = False, - hide_mru_places: bool = False, - hide_pinned_places: bool = False, - no_dereference_links: bool = False, - add_to_recent: bool = True, - show_hidden_files: bool = False, - default_no_minimode: bool = False, - force_preview_pane_on: bool = False, - ok_button_text: str | None = None, -) -> list[str] | None: # sourcery skip: low-code-quality - """Opens a file dialog to select files. - - Args: - title (str | None): The title of the dialog. - default_folder (str | None): The initial folder to open. - file_types (list[tuple[str, str]] | None): A list of file type filters. - default_extension (str | None): The default file extension. - overwrite_prompt (bool): Prompts if the selected file already exists. FileOpenOptions.FOS_OVERWRITEPROMPT. - strict_file_types (bool): Restricts selection to specified file types. FileOpenOptions.FOS_STRICTFILETYPES. - no_change_dir (bool): Prevents changing the current working directory. FileOpenOptions.FOS_NOCHANGEDIR. - force_filesystem (bool): Ensures only file system items are shown. FileOpenOptions.FOS_FORCEFILESYSTEM. - all_non_storage_items (bool): Allows selection of non-file system items. FileOpenOptions.FOS_ALLNONSTORAGEITEMS. - no_validate (bool): Disables file name validation. FileOpenOptions.FOS_NOVALIDATE. - allow_multiple_selection (bool): Allows selecting multiple files. FileOpenOptions.FOS_ALLOWMULTISELECT. - path_must_exist (bool): Requires the path to exist. FileOpenOptions.FOS_PATHMUSTEXIST. - file_must_exist (bool): Requires the file to exist. FileOpenOptions.FOS_FILEMUSTEXIST. - create_prompt (bool): Prompts to create a new file if it doesn't exist. FileOpenOptions.FOS_CREATEPROMPT. - share_aware (bool): Ensures the dialog is aware of sharing conflicts. FileOpenOptions.FOS_SHAREAWARE. - no_readonly_return (bool): Prevents selection of read-only items. FileOpenOptions.FOS_NOREADONLYRETURN. - no_test_file_create (bool): Disables testing file creation ability. FileOpenOptions.FOS_NOTESTFILECREATE. - hide_mru_places (bool): Hides most recently used places. FileOpenOptions.FOS_HIDEMRUPLACES. - hide_pinned_places (bool): Hides pinned places. FileOpenOptions.FOS_HIDEPINNEDPLACES. - no_dereference_links (bool): Prevents dereferencing shortcuts. FileOpenOptions.FOS_NODEREFERENCELINKS. - add_to_recent (bool): Prevents adding the file to recent files. FileOpenOptions.FOS_DONTADDTORECENT. - show_hidden_files (bool): Shows hidden files and folders. FileOpenOptions.FOS_FORCESHOWHIDDEN. - default_no_minimode (bool): Uses default non-minimized mode. FileOpenOptions.FOS_DEFAULTNOMINIMODE. - force_preview_pane_on (bool): Forces the preview pane to be visible. FileOpenOptions.FOS_FORCEPREVIEWPANEON. - ok_button_text (str): The text for the button used to select/confirm the dialog. - - Returns: - list[str] | None: A list of selected file paths or None if cancelled. - """ - options = 0 - if overwrite_prompt: - options |= FileOpenOptions.FOS_OVERWRITEPROMPT - if strict_file_types: - options |= FileOpenOptions.FOS_STRICTFILETYPES - if no_change_dir: - options |= FileOpenOptions.FOS_NOCHANGEDIR - if force_filesystem: - options |= FileOpenOptions.FOS_FORCEFILESYSTEM - if all_non_storage_items: - options |= FileOpenOptions.FOS_ALLNONSTORAGEITEMS - if no_validate: - options |= FileOpenOptions.FOS_NOVALIDATE - if allow_multiple_selection: - options |= FileOpenOptions.FOS_ALLOWMULTISELECT - if path_must_exist: - options |= FileOpenOptions.FOS_PATHMUSTEXIST - if file_must_exist: - options |= FileOpenOptions.FOS_FILEMUSTEXIST - if create_prompt: - options |= FileOpenOptions.FOS_CREATEPROMPT - if share_aware: - options |= FileOpenOptions.FOS_SHAREAWARE - if no_readonly_return: - options |= FileOpenOptions.FOS_NOREADONLYRETURN - if no_test_file_create: - options |= FileOpenOptions.FOS_NOTESTFILECREATE - if hide_mru_places: - options |= FileOpenOptions.FOS_HIDEMRUPLACES - if hide_pinned_places: - options |= FileOpenOptions.FOS_HIDEPINNEDPLACES - if no_dereference_links: - options |= FileOpenOptions.FOS_NODEREFERENCELINKS - if not add_to_recent: - options |= FileOpenOptions.FOS_DONTADDTORECENT - if show_hidden_files: - options |= FileOpenOptions.FOS_FORCESHOWHIDDEN - if default_no_minimode: - options |= FileOpenOptions.FOS_DEFAULTNOMINIMODE - if force_preview_pane_on: - options |= FileOpenOptions.FOS_FORCEPREVIEWPANEON - file_dialog = comtypes.client.CreateObject(CLSID_FileOpenDialog, interface=IFileOpenDialog) - return configure_file_dialog(file_dialog, title, options, default_folder, ok_button_text, None, file_types, default_extension) - - -def save_file_dialog( # noqa: C901, PLR0913, PLR0912 - title: str | None = "Save File", - default_folder: str | None = None, - file_types: list[tuple[str, str]] | None = None, - default_extension: str | None = None, - *, - overwrite_prompt: bool = True, - strict_file_types: bool = False, - no_change_dir: bool = True, - force_filesystem: bool = True, - all_non_storage_items: bool = False, - no_validate: bool = False, - path_must_exist: bool = True, - file_must_exist: bool = False, - create_prompt: bool = False, - share_aware: bool = False, - no_readonly_return: bool = False, - no_test_file_create: bool = False, - hide_mru_places: bool = False, - hide_pinned_places: bool = False, - no_dereference_links: bool = False, - add_to_recent: bool = True, - show_hidden_files: bool = False, - default_no_minimode: bool = False, - force_preview_pane_on: bool = False, - ok_button_text: str | None = None, -) -> list[str] | None: # sourcery skip: low-code-quality - """Opens a file dialog to save a file. - - Args: - title (str | None): The title of the dialog. - default_folder (str | None): The initial folder to open. - file_types (list[tuple[str, str]] | None): A list of file type filters. - default_extension (str | None): The default file extension. - overwrite_prompt (bool): Prompts if the selected file already exists. FileOpenOptions.FOS_OVERWRITEPROMPT. - strict_file_types (bool): Restricts selection to specified file types. FileOpenOptions.FOS_STRICTFILETYPES. - no_change_dir (bool): Prevents changing the current working directory. FileOpenOptions.FOS_NOCHANGEDIR. - force_filesystem (bool): Ensures only file system items are shown. FileOpenOptions.FOS_FORCEFILESYSTEM. - all_non_storage_items (bool): Allows selection of non-file system items. FileOpenOptions.FOS_ALLNONSTORAGEITEMS. - no_validate (bool): Disables file name validation. FileOpenOptions.FOS_NOVALIDATE. - path_must_exist (bool): Requires the path to exist. FileOpenOptions.FOS_PATHMUSTEXIST. - file_must_exist (bool): Requires the file to exist. FileOpenOptions.FOS_FILEMUSTEXIST. - create_prompt (bool): Prompts to create a new file if it doesn't exist. FileOpenOptions.FOS_CREATEPROMPT. - share_aware (bool): Ensures the dialog is aware of sharing conflicts. FileOpenOptions.FOS_SHAREAWARE. - no_readonly_return (bool): Prevents selection of read-only items. FileOpenOptions.FOS_NOREADONLYRETURN. - no_test_file_create (bool): Disables testing file creation ability. FileOpenOptions.FOS_NOTESTFILECREATE. - hide_mru_places (bool): Hides most recently used places. FileOpenOptions.FOS_HIDEMRUPLACES. - hide_pinned_places (bool): Hides pinned places. FileOpenOptions.FOS_HIDEPINNEDPLACES. - no_dereference_links (bool): Prevents dereferencing shortcuts. FileOpenOptions.FOS_NODEREFERENCELINKS. - add_to_recent (bool): Prevents adding the file to recent files. FileOpenOptions.FOS_DONTADDTORECENT. - show_hidden_files (bool): Shows hidden files and folders. FileOpenOptions.FOS_FORCESHOWHIDDEN. - default_no_minimode (bool): Uses default non-minimized mode. FileOpenOptions.FOS_DEFAULTNOMINIMODE. - force_preview_pane_on (bool): Forces the preview pane to be visible. FileOpenOptions.FOS_FORCEPREVIEWPANEON. - ok_button_text (str): The text for the button used to select/confirm the dialog. - - Returns: - list[str] | None: A list of selected file paths or None if cancelled. - """ - options = 0 - if overwrite_prompt: - options |= FileOpenOptions.FOS_OVERWRITEPROMPT - if strict_file_types: - options |= FileOpenOptions.FOS_STRICTFILETYPES - if no_change_dir: - options |= FileOpenOptions.FOS_NOCHANGEDIR - if force_filesystem: - options |= FileOpenOptions.FOS_FORCEFILESYSTEM - if all_non_storage_items: - options |= FileOpenOptions.FOS_ALLNONSTORAGEITEMS - if no_validate: - options |= FileOpenOptions.FOS_NOVALIDATE - if path_must_exist: - options |= FileOpenOptions.FOS_PATHMUSTEXIST - if file_must_exist: - options |= FileOpenOptions.FOS_FILEMUSTEXIST - if create_prompt: - options |= FileOpenOptions.FOS_CREATEPROMPT - if share_aware: - options |= FileOpenOptions.FOS_SHAREAWARE - if no_readonly_return: - options |= FileOpenOptions.FOS_NOREADONLYRETURN - if no_test_file_create: - options |= FileOpenOptions.FOS_NOTESTFILECREATE - if hide_mru_places: - options |= FileOpenOptions.FOS_HIDEMRUPLACES - if hide_pinned_places: - options |= FileOpenOptions.FOS_HIDEPINNEDPLACES - if no_dereference_links: - options |= FileOpenOptions.FOS_NODEREFERENCELINKS - if not add_to_recent: - options |= FileOpenOptions.FOS_DONTADDTORECENT - if show_hidden_files: - options |= FileOpenOptions.FOS_FORCESHOWHIDDEN - if default_no_minimode: - options |= FileOpenOptions.FOS_DEFAULTNOMINIMODE - if force_preview_pane_on: - options |= FileOpenOptions.FOS_FORCEPREVIEWPANEON - options &= ~FileOpenOptions.FOS_PICKFOLDERS # Required (exceptions otherwise) - options &= ~FileOpenOptions.FOS_ALLOWMULTISELECT # Required (exceptions otherwise) - file_dialog = comtypes.client.CreateObject(CLSID_FileSaveDialog, interface=IFileSaveDialog) - return configure_file_dialog(file_dialog, title, options, default_folder, ok_button_text, None, file_types, default_extension) - - -def open_folder_dialog( # noqa: C901, PLR0913, PLR0912 - title: str | None = "Select Folder", - default_folder: str | None = None, - *, - overwrite_prompt: bool = False, - strict_file_types: bool = False, - no_change_dir: bool = False, - force_filesystem: bool = True, - no_validate: bool = False, - allow_multiple_selection: bool = False, - path_must_exist: bool = True, - file_must_exist: bool = False, - create_prompt: bool = False, - share_aware: bool = False, - no_readonly_return: bool = False, - no_test_file_create: bool = False, - hide_mru_places: bool = False, - hide_pinned_places: bool = False, - no_dereference_links: bool = False, - add_to_recent: bool = True, - show_hidden_files: bool = False, - default_no_minimode: bool = False, - force_preview_pane_on: bool = False, - ok_button_text: str | None = None, -) -> list[str] | None: # sourcery skip: low-code-quality - """Opens a dialog to select folders. - - Args: - title (str | None): The title of the dialog. - default_folder (str | None): The initial folder to open. - overwrite_prompt (bool): Prompts if the selected file already exists. FileOpenOptions.FOS_OVERWRITEPROMPT. - strict_file_types (bool): Restricts selection to specified file types. FileOpenOptions.FOS_STRICTFILETYPES. - no_change_dir (bool): Prevents changing the current working directory. FileOpenOptions.FOS_NOCHANGEDIR. - force_filesystem (bool): Ensures only file system items are shown. FileOpenOptions.FOS_FORCEFILESYSTEM. - no_validate (bool): Disables file name validation. FileOpenOptions.FOS_NOVALIDATE. - allow_multiple_selection (bool): Allows selecting multiple files. FileOpenOptions.FOS_ALLOWMULTISELECT. - path_must_exist (bool): Requires the path to exist. FileOpenOptions.FOS_PATHMUSTEXIST. - file_must_exist (bool): Requires the file to exist. FileOpenOptions.FOS_FILEMUSTEXIST. - create_prompt (bool): Prompts to create a new file if it doesn't exist. FileOpenOptions.FOS_CREATEPROMPT. - share_aware (bool): Ensures the dialog is aware of sharing conflicts. FileOpenOptions.FOS_SHAREAWARE. - no_readonly_return (bool): Prevents selection of read-only items. FileOpenOptions.FOS_NOREADONLYRETURN. - no_test_file_create (bool): Disables testing file creation ability. FileOpenOptions.FOS_NOTESTFILECREATE. - hide_mru_places (bool): Hides most recently used places. FileOpenOptions.FOS_HIDEMRUPLACES. - hide_pinned_places (bool): Hides pinned places. FileOpenOptions.FOS_HIDEPINNEDPLACES. - no_dereference_links (bool): Prevents dereferencing shortcuts. FileOpenOptions.FOS_NODEREFERENCELINKS. - add_to_recent (bool): Prevents adding the file to recent files. FileOpenOptions.FOS_DONTADDTORECENT. - show_hidden_files (bool): Shows hidden files and folders. FileOpenOptions.FOS_FORCESHOWHIDDEN. - default_no_minimode (bool): Uses default non-minimized mode. FileOpenOptions.FOS_DEFAULTNOMINIMODE. - force_preview_pane_on (bool): Forces the preview pane to be visible. FileOpenOptions.FOS_FORCEPREVIEWPANEON. - ok_button_text (str): The text for the button used to select/confirm the dialog. - - Returns: - list[str] | None: A list of selected folder paths or None if cancelled. - """ - options = 0 - options |= FileOpenOptions.FOS_PICKFOLDERS - options &= ~FileOpenOptions.FOS_ALLNONSTORAGEITEMS - if overwrite_prompt: - options |= FileOpenOptions.FOS_OVERWRITEPROMPT - if strict_file_types: - options |= FileOpenOptions.FOS_STRICTFILETYPES - if no_change_dir: - options |= FileOpenOptions.FOS_NOCHANGEDIR - if force_filesystem: - options |= FileOpenOptions.FOS_FORCEFILESYSTEM - if no_validate: - options |= FileOpenOptions.FOS_NOVALIDATE - if allow_multiple_selection: - options |= FileOpenOptions.FOS_ALLOWMULTISELECT - if path_must_exist: - options |= FileOpenOptions.FOS_PATHMUSTEXIST - if file_must_exist: - options |= FileOpenOptions.FOS_FILEMUSTEXIST - if create_prompt: - options |= FileOpenOptions.FOS_CREATEPROMPT - if share_aware: - options |= FileOpenOptions.FOS_SHAREAWARE - if no_readonly_return: - options |= FileOpenOptions.FOS_NOREADONLYRETURN - if no_test_file_create: - options |= FileOpenOptions.FOS_NOTESTFILECREATE - if hide_mru_places: - options |= FileOpenOptions.FOS_HIDEMRUPLACES - if hide_pinned_places: - options |= FileOpenOptions.FOS_HIDEPINNEDPLACES - if no_dereference_links: - options |= FileOpenOptions.FOS_NODEREFERENCELINKS - if not add_to_recent: - options |= FileOpenOptions.FOS_DONTADDTORECENT - if show_hidden_files: - options |= FileOpenOptions.FOS_FORCESHOWHIDDEN - if default_no_minimode: - options |= FileOpenOptions.FOS_DEFAULTNOMINIMODE - if force_preview_pane_on: - options |= FileOpenOptions.FOS_FORCEPREVIEWPANEON - file_dialog = comtypes.client.CreateObject(CLSID_FileOpenDialog, interface=IFileOpenDialog) - return configure_file_dialog(file_dialog, title, options, default_folder, ok_button_text, None, None, None) - - -def get_open_file_dialog_results( - file_open_dialog: IFileOpenDialog, -) -> list[str]: - results: list[str] = [] - results_array: IShellItemArray = file_open_dialog.GetResults() - itemCount: int = results_array.GetCount() - - for i in range(itemCount): - shell_item: IShellItem = results_array.GetItemAt(i) - szFilePath: str = shell_item.GetDisplayName(SIGDN.SIGDN_FILESYSPATH) - if szFilePath and szFilePath.strip(): - results.append(szFilePath) - print(f"Item {i} file path: {szFilePath}") - else: - raise FileNotFoundError(errno.ENOENT, os.strerror(errno.ENOENT), szFilePath) - return results - - -def get_save_file_dialog_results( - comFuncs: COMFunctionPointers, # noqa: N803 - fileSaveDialog: IFileSaveDialog, # noqa: N803 -) -> str: - results = "" - resultItem: IShellItem = fileSaveDialog.GetResult() - - szFilePath = resultItem.GetDisplayName(SIGDN.SIGDN_FILESYSPATH) - szFilePathStr = str(szFilePath) - if szFilePathStr and szFilePathStr.strip(): - results = szFilePathStr - print(f"Selected file path: {szFilePath}") - comFuncs.pCoTaskMemFree(szFilePath) - else: - raise FileNotFoundError(errno.ENOENT, os.strerror(errno.ENOENT), str(szFilePath)) - - attributes: int = resultItem.GetAttributes(SFGAO.SFGAO_FILESYSTEM | SFGAO.SFGAO_FOLDER) - print(f"Selected item attributes: {attributes}") - - parentItem: IShellItem | comtypes.IUnknown = resultItem.GetParent() - if isinstance(parentItem, IShellItem): - szParentName: LPWSTR | str = parentItem.GetDisplayName(SIGDN.SIGDN_NORMALDISPLAY) - print(f"Selected item parent: {szParentName}") - comFuncs.pCoTaskMemFree(szParentName) - parentItem.Release() - - resultItem.Release() - - return results - - -# Example usage -if __name__ == "__main__": - # Randomizing arguments for open_file_dialog - open_file_args = { - "title": "Open File" if random.choice([True, False]) else None, # noqa: S311 - "default_folder": "C:\\Users" if random.choice([True, False]) else None, # noqa: S311 - "file_types": [("Text Files", "*.txt")] if random.choice([True, False]) else None, # noqa: S311 - "default_extension": "txt" if random.choice([True, False]) else None, # noqa: S311 - "overwrite_prompt": random.choice([True, False]), # noqa: S311 - "strict_file_types": random.choice([True, False]), # noqa: S311 - "no_change_dir": random.choice([True, False]), # noqa: S311 - "force_filesystem": random.choice([True, False]), # noqa: S311 - "all_non_storage_items": False, # random.choice([True, False]), # noqa: S311 - "no_validate": random.choice([True, False]), # noqa: S311 - "allow_multiple_selection": random.choice([True, False]), # noqa: S311 - "path_must_exist": random.choice([True, False]), # noqa: S311 - "file_must_exist": random.choice([True, False]), # noqa: S311 - "create_prompt": random.choice([True, False]), # noqa: S311 - "share_aware": random.choice([True, False]), # noqa: S311 - "no_readonly_return": random.choice([True, False]), # noqa: S311 - "no_test_file_create": random.choice([True, False]), # noqa: S311 - "hide_mru_places": random.choice([True, False]), # noqa: S311 - "hide_pinned_places": random.choice([True, False]), # noqa: S311 - "no_dereference_links": random.choice([True, False]), # noqa: S311 - "add_to_recent": random.choice([True, False]), # noqa: S311 - "show_hidden_files": random.choice([True, False]), # noqa: S311 - "default_no_minimode": random.choice([True, False]), # noqa: S311 - "force_preview_pane_on": random.choice([True, False]), # noqa: S311 - } - print("\nOpen file args") - print(json.dumps(open_file_args, indent=4, sort_keys=True)) - selected_files: list[str] | None = open_file_and_folder_dialog(**open_file_args) - print("Selected files:", selected_files) - - # Randomizing arguments for open_folder_dialog - open_folder_args = { - "title": "Select Folder" if random.choice([True, False]) else None, # noqa: S311 - "default_folder": "C:\\Users" if random.choice([True, False]) else None, # noqa: S311 - "overwrite_prompt": random.choice([True, False]), # noqa: S311 - "strict_file_types": random.choice([True, False]), # noqa: S311 - "no_change_dir": random.choice([True, False]), # noqa: S311 - "force_filesystem": random.choice([True, False]), # noqa: S311 - "no_validate": random.choice([True, False]), # noqa: S311 - "allow_multiple_selection": random.choice([True, False]), # noqa: S311 - "path_must_exist": random.choice([True, False]), # noqa: S311 - "file_must_exist": random.choice([True, False]), # noqa: S311 - "create_prompt": random.choice([True, False]), # noqa: S311 - "share_aware": random.choice([True, False]), # noqa: S311 - "no_readonly_return": random.choice([True, False]), # noqa: S311 - "no_test_file_create": random.choice([True, False]), # noqa: S311 - "hide_mru_places": random.choice([True, False]), # noqa: S311 - "hide_pinned_places": random.choice([True, False]), # noqa: S311 - "no_dereference_links": random.choice([True, False]), # noqa: S311 - "add_to_recent": random.choice([True, False]), # noqa: S311 - "show_hidden_files": random.choice([True, False]), # noqa: S311 - "default_no_minimode": random.choice([True, False]), # noqa: S311 - "force_preview_pane_on": random.choice([True, False]), # noqa: S311 - } - print("\nOpen folder args") - print(json.dumps(open_folder_args, indent=4, sort_keys=True)) - selected_folders: list[str] | None = open_folder_dialog(**open_folder_args) - print("Selected folders:", selected_folders) - - # Randomizing arguments for save_file_dialog - save_file_args = { - "title": "Save File" if random.choice([True, False]) else None, # noqa: S311 - "default_folder": "C:\\Users" if random.choice([True, False]) else None, # noqa: S311 - "file_types": [("Text Files", "*.txt")] if random.choice([True, False]) else None, # noqa: S311 - "default_extension": "txt" if random.choice([True, False]) else None, # noqa: S311 - "overwrite_prompt": random.choice([True, False]), # noqa: S311 - "strict_file_types": random.choice([True, False]), # noqa: S311 - "no_change_dir": random.choice([True, False]), # noqa: S311 - "force_filesystem": random.choice([True, False]), # noqa: S311 - "all_non_storage_items": random.choice([True, False]), # noqa: S311 - "no_validate": random.choice([True, False]), # noqa: S311 - "path_must_exist": random.choice([True, False]), # noqa: S311 - "file_must_exist": random.choice([True, False]), # noqa: S311 - "create_prompt": random.choice([True, False]), # noqa: S311 - "share_aware": random.choice([True, False]), # noqa: S311 - "no_readonly_return": random.choice([True, False]), # noqa: S311 - "no_test_file_create": random.choice([True, False]), # noqa: S311 - "hide_mru_places": random.choice([True, False]), # noqa: S311 - "hide_pinned_places": random.choice([True, False]), # noqa: S311 - "no_dereference_links": random.choice([True, False]), # noqa: S311 - "add_to_recent": random.choice([True, False]), # noqa: S311 - "show_hidden_files": random.choice([True, False]), # noqa: S311 - "default_no_minimode": random.choice([True, False]), # noqa: S311 - "force_preview_pane_on": random.choice([True, False]), # noqa: S311 - } - print("\nSave file args") - print(json.dumps(save_file_args, indent=4, sort_keys=True)) - saved_file: list[str] | None = save_file_dialog(**save_file_args) - print("Saved file:", saved_file) diff --git a/winforms/src/toga_winforms/libs/py_wrappers/common.py b/winforms/src/toga_winforms/libs/py_wrappers/common.py deleted file mode 100644 index 8f756fc1ea..0000000000 --- a/winforms/src/toga_winforms/libs/py_wrappers/common.py +++ /dev/null @@ -1,50 +0,0 @@ -from __future__ import annotations - -from ctypes import Structure, c_char_p, c_int, c_ssize_t, c_ulong, c_void_p, windll -from ctypes.wintypes import BOOL, DWORD, HANDLE, HINSTANCE, HKEY -from typing import Any - -# context menu properties -SEE_MASK_NOCLOSEPROCESS = 0x00000040 -SEE_MASK_INVOKEIDLIST = 0x0000000C - -LRESULT = c_ssize_t - - -class SHELLEXECUTEINFO(Structure): - _fields_ = ( - ("cbSize", DWORD), - ("fMask", c_ulong), - ("hwnd", HANDLE), - ("lpVerb", c_char_p), - ("lpFile", c_char_p), - ("lpParameters", c_char_p), - ("lpDirectory", c_char_p), - ("nShow", c_int), - ("hInstApp", HINSTANCE), - ("lpIDList", c_void_p), - ("lpClass", c_char_p), - ("hKeyClass", HKEY), - ("dwHotKey", DWORD), - ("hIconOrMonitor", HANDLE), - ("hProcess", HANDLE), - ) - cbSize: int - fMask: int - hwnd: int - lpVerb: bytes - lpFile: bytes - lpParameters: bytes - lpDirectory: bytes - nShow: int - hInstApp: Any | c_void_p - lpIDList: Any | c_void_p - lpClass: bytes - hKeyClass: Any | c_void_p - dwHotKey: int - hIconOrMonitor: int - hProcess: int - - -ShellExecuteEx = windll.shell32.ShellExecuteEx -ShellExecuteEx.restype = BOOL diff --git a/winforms/src/toga_winforms/libs/py_wrappers/context_menu.py b/winforms/src/toga_winforms/libs/py_wrappers/context_menu.py deleted file mode 100644 index 4c694d4535..0000000000 --- a/winforms/src/toga_winforms/libs/py_wrappers/context_menu.py +++ /dev/null @@ -1,209 +0,0 @@ -from __future__ import annotations - -import ctypes -import errno -from contextlib import ExitStack -from ctypes import byref, windll -from pathlib import WindowsPath -from typing import TYPE_CHECKING, Iterable, Protocol, cast, runtime_checkable - -import comtypes # pyright: ignore[reportMissingTypeStubs] -import comtypes.client # pyright: ignore[reportMissingTypeStubs] - -from toga_winforms.libs.py_wrappers.hwnd import SimplePyHWND - -if TYPE_CHECKING: - import os - from typing import Any, Callable, Sequence - - from typing_extensions import Literal - from win32com.client.dynamic import CDispatch - - -def safe_isfile(path: WindowsPath) -> bool | None: - try: - result: bool = path.is_file() - except (OSError, ValueError): - return None - else: - return result - - -def safe_isdir(path: WindowsPath) -> bool | None: - try: - result: bool = path.is_dir() - except (OSError, ValueError): - return None - else: - return result - - -def create_dispatch_shell() -> CDispatch | ShellNamespace: - try: - import win32com.client - except ImportError: - return comtypes.client.CreateObject("Shell.Application") - else: - return win32com.client.Dispatch("Shell.Application") - - -def get_context_menu_functions() -> tuple[bool, Callable[..., Any], Callable[..., Any], Callable[..., Any], Callable[..., Any], int, int, int]: - try: - import win32con - import win32gui - except ImportError: - return (False, windll.user32.AppendMenuW, windll.user32.CreatePopupMenu, windll.user32.GetCursorPos, windll.user32.TrackPopupMenu, - 0x0000, 0x0000, 0x0100) - else: - return (True, win32gui.AppendMenu, win32gui.CreatePopupMenu, win32gui.GetCursorPos, win32gui.TrackPopupMenu, - win32con.MF_STRING, win32con.TPM_LEFTALIGN, win32con.TPM_RETURNCMD) - - -class _Vector2: - def __init__(self, x: int, y: int): - self.x: int = x - self.y: int = y - - -class _POINT(ctypes.Structure): - _fields_ = [("x", ctypes.c_long), ("y", ctypes.c_long)] # noqa: RUF012 - - -@runtime_checkable -class ShellNamespace(Protocol): - def NameSpace(self, folder: str | Literal[0]) -> ShellFolder: - ... - - -@runtime_checkable -class ShellFolder(Protocol): - def ParseName(self, name: str) -> ShellFolderItem: - ... - - -@runtime_checkable -class ShellFolderItem(Protocol): - def Verbs(self) -> ShellFolderItemVerbs: - ... - - -@runtime_checkable -class ShellFolderItemVerbs(Protocol): - def Item(self, index: int) -> ShellFolderItemVerb: - ... - def __getitem__(self, index: int) -> ShellFolderItemVerb: - ... - def __len__(self) -> int: - ... - - -@runtime_checkable -class ShellFolderItemVerb(Protocol): - def DoIt(self) -> None: - ... - @property - def Name(self) -> str: - ... - - -def get_cursor_pos(c_getcursorpos: Callable, *, use_pywin32: bool) -> _Vector2: - if use_pywin32: - return _Vector2(*c_getcursorpos()) - pt = _POINT() - c_getcursorpos(byref(pt)) - return cast(_Vector2, pt) - - -def show_context_menu(context_menu: CDispatch | ShellFolderItemVerbs, hwnd: int | None): - # assert isinstance(context_menu, Iterable) # this fails! - assert hasattr(context_menu, "__iter__") # this also fails! - if not hasattr(context_menu, "__getitem__"): - raise TypeError(f"Expected arg1 to be Iterable or something similar: {context_menu} ({context_menu.__class__.__name__})") - - pywin32_available, AppendMenu, CreatePopupMenu, GetCursorPos, TrackPopupMenu, MF_STRING, TPM_LEFTALIGN, TPM_RETURNCMD = get_context_menu_functions() - hmenu = CreatePopupMenu() - for i, verb in enumerate(context_menu): # pyright: ignore[reportArgumentType] - if verb.Name: - AppendMenu(hmenu, MF_STRING, i + 1, verb.Name) - pt: _Vector2 = get_cursor_pos(GetCursorPos, use_pywin32=pywin32_available) - with ExitStack() as stack: - hwnd = stack.enter_context(SimplePyHWND()) if hwnd is None else hwnd - cmd = TrackPopupMenu(hmenu, TPM_LEFTALIGN | TPM_RETURNCMD, - pt.x, pt.y, 0, hwnd, None) - if not isinstance(cmd, int): - raise RuntimeError("Unable to open the context manager, reason unknown") # noqa: TRY004 - verb = context_menu.Item(cmd - 1) - if verb: - verb.DoIt() - - -def windows_context_menu_file( - file_path: os.PathLike | str, - hwnd: int | None = None, -) -> None: - parsed_filepath: WindowsPath = WindowsPath(file_path).resolve() - shell = create_dispatch_shell() - folder_object = shell.NameSpace(str(parsed_filepath.parent)) - folder_item = folder_object.ParseName(parsed_filepath.name) - show_context_menu(folder_item.Verbs(), hwnd) - - -def windows_context_menu_multiple( - paths: Sequence[os.PathLike | str], - hwnd: int | None = None, -): - parsed_paths: list[WindowsPath] = [WindowsPath(path).resolve() for path in paths] - folder_items = [] - shell = create_dispatch_shell() - for path in parsed_paths: - folder_object = shell.NameSpace(str(path.parent if safe_isfile(path) else path)) - item = folder_object.ParseName(path.name) # Following happens when path doesn't exist: `AttributeError: 'NoneType' object has no attribute 'ParseName'` - folder_items.append(item) - show_context_menu(folder_items[0].Verbs(), hwnd) - - -def windows_context_menu_folder( - folder_path: os.PathLike | str, - hwnd: int | None = None, -) -> None: - parsed_folderpath: WindowsPath = WindowsPath(folder_path).resolve() - shell = create_dispatch_shell() - desktop_object = shell.NameSpace(0) - folder_item = desktop_object.ParseName(str(parsed_folderpath)) - context_menu = folder_item.Verbs() - show_context_menu(context_menu, hwnd) - - -def windows_context_menu(path: os.PathLike | str | Iterable[os.PathLike | str], hwnd: int | None = None): - if isinstance(path, Iterable): - paths = list(path) - if not paths: - return - if len(paths) > 1: - windows_context_menu_multiple(paths, hwnd) - return - parsed_path: WindowsPath = WindowsPath(paths[0]) - else: - parsed_path = WindowsPath(path) - - if safe_isfile(parsed_path): - windows_context_menu_file(parsed_path, hwnd) - elif safe_isdir(parsed_path): - windows_context_menu_folder(parsed_path, hwnd) - else: - msg = f"Path is neither file nor folder: '{path}'" - raise FileNotFoundError(errno.ENOENT, msg, str(path)) - - -# Example usage -if __name__ == "__main__": - windows_context_menu(r"C:\Users\Wizard\test_folder\City.sol") - - multiple_files = [ - r"C:\Users\Wizard\test_folder\RestoreBackup.ps1", - r"C:\Users\Wizard\test_folder\City.sol", - ] - windows_context_menu_multiple(multiple_files) - - folderpath = r"C:\Users\Wizard\test_folder" - windows_context_menu_folder(folderpath) diff --git a/winforms/src/toga_winforms/libs/py_wrappers/hresult.py b/winforms/src/toga_winforms/libs/py_wrappers/hresult.py deleted file mode 100644 index 32e90c739c..0000000000 --- a/winforms/src/toga_winforms/libs/py_wrappers/hresult.py +++ /dev/null @@ -1,419 +0,0 @@ -from __future__ import annotations - -from ctypes import HRESULT as ctypesHRESULT # noqa: N811 -from ctypes import c_long, get_last_error -from typing import TYPE_CHECKING, ClassVar, cast - -if TYPE_CHECKING: - from typing_extensions import Literal, Self # pyright: ignore[reportMissingModuleSource] - - -class Win32OSError(OSError): - ... - - -class HRESULT(ctypesHRESULT): - FACILITY_CODES: ClassVar[dict[int, str]] = { - 0: "FACILITY_NULL", - 1: "FACILITY_RPC", - 2: "FACILITY_DISPATCH", - 3: "FACILITY_STORAGE", - 4: "FACILITY_ITF", - 7: "FACILITY_WIN32", - 8: "FACILITY_WINDOWS", - 9: "FACILITY_SECURITY", - 10: "FACILITY_CONTROL", - 11: "FACILITY_CERT", - 12: "FACILITY_INTERNET", - 13: "FACILITY_MEDIASERVER", - 14: "FACILITY_MSMQ", - 15: "FACILITY_SETUPAPI", - 16: "FACILITY_SCARD", - 17: "FACILITY_COMPLUS", - 18: "FACILITY_AAF", - 19: "FACILITY_URT", - 20: "FACILITY_ACS", - 21: "FACILITY_DPLAY", - 22: "FACILITY_UMI", - 23: "FACILITY_SXS", - 24: "FACILITY_WINDOWS_CE", - 25: "FACILITY_HTTP", - 26: "FACILITY_USERMODE_COMMONLOG", - 27: "FACILITY_WER", - 28: "FACILITY_USERMODE_FILTER_MANAGER", - 29: "FACILITY_BACKGROUNDCOPY", - 30: "FACILITY_CONFIGURATION", - 31: "FACILITY_STATE_MANAGEMENT", - 32: "FACILITY_METADIRECTORY", - 33: "FACILITY_SYSTEM_INTEGRITY", - 34: "FACILITY_VIRTUALIZATION", - 35: "FACILITY_VOLMGR", - 36: "FACILITY_BCD", - 37: "FACILITY_USERMODE_VHD", - 38: "FACILITY_USERMODE_HYPERVISOR", - 39: "FACILITY_USERMODE_VM", - 40: "FACILITY_USERMODE_VOLSNAP", - 41: "FACILITY_USERMODE_STORLIB", - 42: "FACILITY_USERMODE_LICENSING", - 43: "FACILITY_USERMODE_SMB", - 44: "FACILITY_USERMODE_VSS", - 45: "FACILITY_USERMODE_FILE_REPLICATION", - 46: "FACILITY_USERMODE_NDIS", - 47: "FACILITY_USERMODE_TPM", - 48: "FACILITY_USERMODE_NT", - 49: "FACILITY_USERMODE_USB", - 50: "FACILITY_USERMODE_NTOS", - 51: "FACILITY_USERMODE_COMPLUS", - 52: "FACILITY_USERMODE_NET", - 53: "FACILITY_USERMODE_CONFIGURATION_MANAGER", - 54: "FACILITY_USERMODE_COM", - 55: "FACILITY_USERMODE_DIRECTORY_SERVICE", - 56: "FACILITY_USERMODE_CMI", - 57: "FACILITY_USERMODE_LSA", - 58: "FACILITY_USERMODE_RPC", - 59: "FACILITY_USERMODE_IPSECVPN", - 60: "FACILITY_USERMODE_NETWORK_POLICY", - 61: "FACILITY_USERMODE_DNS_SERVER", - 62: "FACILITY_USERMODE_DNS_SERVER_ADMIN", - 63: "FACILITY_USERMODE_DNS_SERVER_CONFIGURATION", - 64: "FACILITY_USERMODE_DNS_SERVER_TUNING", - 65: "FACILITY_USERMODE_DNS_SERVER_ZONE", - 66: "FACILITY_USERMODE_DNS_SERVER_FORWARDER", - 67: "FACILITY_USERMODE_DNS_SERVER_REPLICATION", - 68: "FACILITY_USERMODE_DNS_SERVER_NDNC", - 69: "FACILITY_USERMODE_DNS_SERVER_FORWARDS", - 70: "FACILITY_USERMODE_DNS_SERVER_DS", - 71: "FACILITY_USERMODE_DNS_SERVER_ROOT_HINTS", - 72: "FACILITY_USERMODE_DNS_SERVER_ZONE_SOURCE", - 73: "FACILITY_USERMODE_DNS_SERVER_DATABASE", - 74: "FACILITY_USERMODE_DNS_SERVER_PROTOCOL", - 75: "FACILITY_USERMODE_DNS_SERVER_SERVICED", - 76: "FACILITY_USERMODE_DNS_SERVER_SOCKETS", - 77: "FACILITY_USERMODE_DNS_SERVER_SERVER_ADMIN", - 78: "FACILITY_USERMODE_DNS_SERVER_SOAP", - 79: "FACILITY_USERMODE_DNS_SERVER_ISAPI", - 80: "FACILITY_USERMODE_DNS_SERVER_WEB", - 81: "FACILITY_USERMODE_DNS_SERVER_SERVER", - 82: "FACILITY_USERMODE_DNS_SERVER_ADMIN_R2", - 83: "FACILITY_USERMODE_DNS_SERVER_ISAPI_FILTER", - 84: "FACILITY_USERMODE_DNS_SERVER_NDNC2", - 85: "FACILITY_USERMODE_DNS_SERVER_EVENTLOG", - 86: "FACILITY_USERMODE_DNS_SERVER_ADMIN2", - 87: "FACILITY_USERMODE_DNS_SERVER_ZONE2", - 88: "FACILITY_USERMODE_DNS_SERVER_NDNC3", - 89: "FACILITY_USERMODE_DNS_SERVER_SOCKETS2", - 90: "FACILITY_USERMODE_DNS_SERVER_ADMIN3", - 91: "FACILITY_USERMODE_DNS_SERVER_WEB2", - 92: "FACILITY_USERMODE_DNS_SERVER_ADMIN4", - 93: "FACILITY_USERMODE_DNS_SERVER_SERVER3", - 94: "FACILITY_USERMODE_DNS_SERVER_SOCKETS3", - 95: "FACILITY_USERMODE_DNS_SERVER_ADMIN5", - 96: "FACILITY_USERMODE_DNS_SERVER_SERVER4", - 97: "FACILITY_USERMODE_DNS_SERVER_ADMIN6", - 98: "FACILITY_USERMODE_DNS_SERVER_SOCKETS4", - 99: "FACILITY_USERMODE_DNS_SERVER_WEB3", - } - - def __new__( - cls, value: HRESULT | ctypesHRESULT | int | c_long | None = None - ) -> Self: - if value is None: - converted_value = 0 - elif isinstance(value, int): - converted_value = value - elif isinstance(getattr(value, "value", None), int): - converted_value = value.value - else: - raise TypeError(f"Invalid type for HRESULT: {type(value)}") - instance = c_long(converted_value) - instance.__class__ = cls - return cast(cls, instance) - - def __init__(self, value: HRESULT | ctypesHRESULT | int | c_long | None = None): - if value is None: - self.value = 0 - elif isinstance(value, int): - self.value = value - elif isinstance(getattr(value, "value", None), int): - self.value = value.value - else: - raise TypeError(f"Invalid type for HRESULT: {type(value)}") - - @staticmethod - def to_hresult(value: int) -> int: - """Convert WinError to HRESULT if necessary.""" - if value & 0x80000000: - value &= 0xFFFFFFFF - return value - - @staticmethod - def from_hresult(value: int) -> int: - """Convert HRESULT to WinError.""" - return value & 0xFFFFFFFF if value < 0 else value - - def decode(self): - severity: int = (self >> 31) & 1 - facility: int = (self >> 16) & 0x1FFF - code: int = self & 0xFFFF - - severity_str: Literal["Success", "Failure"] = "Success" if severity == 0 else "Failure" - facility_str: str = HRESULT.FACILITY_CODES.get(facility, "Unknown Facility") - - return ( - f"HRESULT: 0x{self:08X}\n" - f"Severity: {severity_str}\n" - f"Facility: {facility_str} ({facility})\n" - f"Code: 0x{code:04X} ({code})" - ) - - def __str__(self): - return str(self.to_hresult(self.value)) - - def __repr__(self): - return f"{self.__class__.__name__}({self.value})" - - def __eq__( - self, other: int | ctypesHRESULT - ) -> bool: # sourcery skip: assign-if-exp, reintroduce-else - if not isinstance(other, int) and (not hasattr(other, "value") or not isinstance(other.value, int)): - return NotImplemented - other_int = self.to_hresult(other.value if isinstance(other, (HRESULT, ctypesHRESULT, c_long)) else other) - return self.value == other_int - - def __ne__( - self, other: HRESULT | ctypesHRESULT | int | c_long - ) -> bool: # sourcery skip: assign-if-exp, reintroduce-else - if not isinstance(other, int) and (not hasattr(other, "value") or not isinstance(other.value, int)): - return NotImplemented - other_int = self.to_hresult(other.value if isinstance(other, (HRESULT, ctypesHRESULT, c_long)) else other) - return self.value != other_int - - def __int__(self) -> int: - return self.to_hresult(self.value) - - def __hash__(self) -> int: - return hash(self.value) - - def as_integer_ratio(self) -> tuple[int, int]: - return (self.value, 1) - - def __mod__(self, other: HRESULT | ctypesHRESULT | int | c_long) -> int: - if not isinstance(other, int) and (not hasattr(other, "value") or not isinstance(other.value, int)): - return NotImplemented - other_int = self.to_hresult(other.value if isinstance(other, (HRESULT, ctypesHRESULT, c_long)) else other) - return self.value % other_int - - def __divmod__( - self, other: HRESULT | ctypesHRESULT | int | c_long - ) -> tuple[int, int]: - if not isinstance(other, int) and (not hasattr(other, "value") or not isinstance(other.value, int)): - return NotImplemented - other_int = self.to_hresult(other.value if isinstance(other, (HRESULT, ctypesHRESULT, c_long)) else other) - return divmod(self.value, other_int) - - def __pow__( - self, other: HRESULT | ctypesHRESULT | int | c_long, mod: int | None = None - ) -> int: - if not isinstance(other, int) and (not hasattr(other, "value") or not isinstance(other.value, int)): - return NotImplemented - other_int = self.to_hresult(other.value if isinstance(other, (HRESULT, ctypesHRESULT, c_long)) else other) - if mod is None: - return pow(self.value, other_int) - return pow(self.value, other_int, mod) - - def __rmod__(self, other: HRESULT | ctypesHRESULT | int | c_long) -> int: - if not isinstance(other, int) and (not hasattr(other, "value") or not isinstance(other.value, int)): - return NotImplemented - other_int = self.to_hresult(other.value if isinstance(other, (HRESULT, ctypesHRESULT, c_long)) else other) - return other_int % self.to_hresult(self.value) - - def __rdivmod__( - self, other: HRESULT | ctypesHRESULT | int | c_long - ) -> tuple[int, int]: - if not isinstance(other, int) and (not hasattr(other, "value") or not isinstance(other.value, int)): - return NotImplemented - other_int = self.to_hresult(other.value if isinstance(other, (HRESULT, ctypesHRESULT, c_long)) else other) - return divmod(other_int, self.to_hresult(self.value)) - - def __and__(self, other: HRESULT | ctypesHRESULT | int | c_long) -> int: - if not isinstance(other, int) and (not hasattr(other, "value") or not isinstance(other.value, int)): - return NotImplemented - other_int = self.to_hresult(other.value if isinstance(other, (HRESULT, ctypesHRESULT, c_long)) else other) - return self.to_hresult(self.value) & other_int - - def __or__(self, other: HRESULT | ctypesHRESULT | int | c_long) -> int: - if not isinstance(other, int) and (not hasattr(other, "value") or not isinstance(other.value, int)): - return NotImplemented - other_int = self.to_hresult(other.value if isinstance(other, (HRESULT, ctypesHRESULT, c_long)) else other) - return self.to_hresult(self.value) | other_int - - def __xor__(self, other: HRESULT | ctypesHRESULT | int | c_long) -> int: - if not isinstance(other, int) and (not hasattr(other, "value") or not isinstance(other.value, int)): - return NotImplemented - other_int = self.to_hresult(other.value if isinstance(other, (HRESULT, ctypesHRESULT, c_long)) else other) - return self.to_hresult(self.value) ^ other_int - - def __lshift__(self, other: HRESULT | ctypesHRESULT | int | c_long) -> int: - if not isinstance(other, int) and (not hasattr(other, "value") or not isinstance(other.value, int)): - return NotImplemented - other_int = self.to_hresult(other.value if isinstance(other, (HRESULT, ctypesHRESULT, c_long)) else other) - return self.to_hresult(self.value) << other_int - - def __rshift__(self, other: HRESULT | ctypesHRESULT | int | c_long) -> int: - if not isinstance(other, int) and (not hasattr(other, "value") or not isinstance(other.value, int)): - return NotImplemented - other_int = self.to_hresult(other.value if isinstance(other, (HRESULT, ctypesHRESULT, c_long)) else other) - return self.to_hresult(self.value) >> other_int - - def __rand__(self, other: HRESULT | ctypesHRESULT | int | c_long) -> int: - if not isinstance(other, int) and (not hasattr(other, "value") or not isinstance(other.value, int)): - return NotImplemented - other_int = self.to_hresult(other.value if isinstance(other, (HRESULT, ctypesHRESULT, c_long)) else other) - return other_int & self.value - - def __ror__(self, other: HRESULT | ctypesHRESULT | int | c_long) -> int: - if not isinstance(other, int) and (not hasattr(other, "value") or not isinstance(other.value, int)): - return NotImplemented - other_int = self.to_hresult(other.value if isinstance(other, (HRESULT, ctypesHRESULT, c_long)) else other) - return other_int | self.value - - def __rxor__(self, other: HRESULT | ctypesHRESULT | int | c_long) -> int: - if isinstance(other, (HRESULT, ctypesHRESULT, c_long)): - return self.to_hresult(other.value) ^ self.value - other_int = self.to_hresult(other.value if isinstance(other, (HRESULT, ctypesHRESULT, c_long)) else other) - return other_int ^ self.value - - def __rlshift__(self, other: HRESULT | ctypesHRESULT | int | c_long) -> int: - if not isinstance(other, int) and (not hasattr(other, "value") or not isinstance(other.value, int)): - return NotImplemented - other_int = self.to_hresult(other.value if isinstance(other, (HRESULT, ctypesHRESULT, c_long)) else other) - return other_int << self.value - - def __rrshift__(self, other: HRESULT | ctypesHRESULT | int | c_long) -> int: - if not isinstance(other, int) and (not hasattr(other, "value") or not isinstance(other.value, int)): - return NotImplemented - other_int = self.to_hresult(other.value if isinstance(other, (HRESULT, ctypesHRESULT, c_long)) else other) - return other_int >> self.value - - def __neg__(self) -> int: - return -self.value - - def __pos__(self) -> int: - return +self.value - - def __invert__(self) -> int: - return ~self.value - - def __trunc__(self) -> int: - return self.value - - def __round__(self, ndigits: int = 0) -> int: - return round(self.value, ndigits) - - def __getnewargs__(self) -> tuple[int]: - return (self.value,) - - def __lt__(self, other: HRESULT | ctypesHRESULT | int | c_long) -> bool: - if not isinstance(other, int) and (not hasattr(other, "value") or not isinstance(other.value, int)): - return NotImplemented - other_int = self.to_hresult(other.value if isinstance(other, (HRESULT, ctypesHRESULT, c_long)) else other) - return self.value < other_int - - def __le__(self, other: HRESULT | ctypesHRESULT | int | c_long) -> bool: - if not isinstance(other, int) and (not hasattr(other, "value") or not isinstance(other.value, int)): - return NotImplemented - other_int = self.to_hresult(other.value if isinstance(other, (HRESULT, ctypesHRESULT, c_long)) else other) - return self.value <= other_int - - def __gt__(self, other: HRESULT | ctypesHRESULT | int | c_long) -> bool: - if not isinstance(other, int) and (not hasattr(other, "value") or not isinstance(other.value, int)): - return NotImplemented - other_int = self.to_hresult(other.value if isinstance(other, (HRESULT, ctypesHRESULT, c_long)) else other) - return self.value > other_int - - def __ge__(self, other: HRESULT | ctypesHRESULT | int | c_long) -> bool: - if not isinstance(other, int) and (not hasattr(other, "value") or not isinstance(other.value, int)): - return NotImplemented - other_int = self.to_hresult(other.value if isinstance(other, (HRESULT, ctypesHRESULT, c_long)) else other) - return self.value >= other_int - - def __float__(self) -> float: - return float(self.value) - - def __abs__(self) -> int: - return abs(self.value) - - def __bool__(self) -> bool: - return bool(self.value) - - def __index__(self) -> int: - return self.value - - def __format__(self, format_spec: str) -> str: - if format_spec == "08X": - return f"{self.to_hresult(self.value):08X}" - return format(self.to_hresult(self.value), format_spec) - - def exception(self, short_desc: str | None = None) -> Win32OSError: - return Win32OSError(self.to_hresult(self.value), decode_hresult(self), short_desc or "") - - @classmethod - def raise_for_status( - cls, - hresult: HRESULT | ctypesHRESULT | Self | int | None = None, - short_desc: str | None = None, - *, - ignore_s_false: bool = False, - ): - hresult = HRESULT(get_last_error() if hresult is None else hresult) - hr: Self = hresult if isinstance(hresult, cls) else cls(hresult) - if (hr == 1 and not ignore_s_false) or hr not in (HRESULT(0), HRESULT(1)): - raise hr.exception(short_desc) - return hresult - - -def decode_hresult(hresult: HRESULT | int) -> str: - if isinstance(hresult, HRESULT): - hresult = hresult.value - severity: int = (hresult >> 31) & 1 - facility: int = (hresult >> 16) & 0x1FFF - code: int = hresult & 0xFFFF - - severity_str: Literal["Success", "Failure"] = "Success" if severity == 0 else "Failure" - facility_str = HRESULT.FACILITY_CODES.get(facility, "Unknown Facility") - - return ( - f"HRESULT: 0x{HRESULT.to_hresult(hresult):08X}\n" - f"Severity: {severity_str}\n" - f"Facility: {facility_str} ({facility})\n" - f"Code: 0x{code:04X} ({code})" - ) - - -def print_hresult(hresult: HRESULT | int) -> None: - print(decode_hresult(hresult)) - - -def hresult_to_winerror(hresult: int) -> int: - """Convert a positive HRESULT value to the corresponding WinError value.""" - return hresult - 0x100000000 if hresult & 0x80000000 else hresult - - -def winerror_to_hresult(winerror: int) -> int: - """Convert a WinError value to the corresponding positive HRESULT value.""" - return winerror + 0x100000000 if winerror < 0 else winerror - - -S_OK = HRESULT(0) -S_FALSE = HRESULT(1) - - -if __name__ == "__main__": - # Example usage: - hr1 = HRESULT(10) - hr2 = HRESULT(c_long(20)) - hr3 = HRESULT(ctypesHRESULT(30)) - - print(hr1 == 10) - print(hr2 > hr1) - print(hr3 == ctypesHRESULT(30)) diff --git a/winforms/src/toga_winforms/libs/py_wrappers/hwnd.py b/winforms/src/toga_winforms/libs/py_wrappers/hwnd.py deleted file mode 100644 index d25506293f..0000000000 --- a/winforms/src/toga_winforms/libs/py_wrappers/hwnd.py +++ /dev/null @@ -1,300 +0,0 @@ -from __future__ import annotations - - -from ctypes import POINTER, WINFUNCTYPE, Structure, c_int, c_wchar_p, windll, c_long, c_uint, c_void_p -from ctypes.wintypes import ATOM, BOOL, DWORD, HBRUSH, HICON, HINSTANCE, HMENU, HWND, LPARAM, LPCWSTR, LPVOID, UINT, WPARAM - -from toga_winforms.libs.py_wrappers.common import LRESULT -from enum import IntEnum -from typing import TYPE_CHECKING, Sequence - -if TYPE_CHECKING: - from ctypes import ( - _CData, - _FuncPointer, - ) - from ctypes.wintypes import HMENU, LPVOID - from types import TracebackType - - from _win32typing import PyResourceId # pyright: ignore[reportMissingModuleSource] - - -WNDPROC: type[_FuncPointer] = WINFUNCTYPE(c_long, HWND, c_uint, WPARAM, LPARAM) -WM_DESTROY = 0x0002 -WS_OVERLAPPEDWINDOW = (0x00CF0000) -WS_VISIBLE = 0x10000000 -HCURSOR = c_void_p - - -class ctypesWNDCLASS(Structure): # noqa: N801 - _fields_: Sequence[tuple[str, type[_CData]] | tuple[str, type[_CData], int]] = [ - ("style", UINT), - ("lpfnWndProc", WNDPROC), - ("cbClsExtra", c_int), - ("cbWndExtra", c_int), - ("hInstance", HINSTANCE), - ("hIcon", HICON), - ("hCursor", HCURSOR), - ("hbrBackground", HBRUSH), - ("lpszMenuName", LPCWSTR), - ("lpszClassName", LPCWSTR) - ] - - - - -# Constants for MessageBox -MB_OK = 0x0 -MB_OKCANCEL = 0x1 -MB_ABORTRETRYIGNORE = 0x2 -MB_YESNOCANCEL = 0x3 -MB_YESNO = 0x4 -MB_RETRYCANCEL = 0x5 -MB_CANCELTRYCONTINUE = 0x6 - -MB_ICONHAND = 0x10 -MB_ICONQUESTION = 0x20 -MB_ICONEXCLAMATION = 0x30 -MB_ICONASTERISK = 0x40 - -MB_DEFBUTTON1 = 0x0 -MB_DEFBUTTON2 = 0x100 -MB_DEFBUTTON3 = 0x200 -MB_DEFBUTTON4 = 0x300 - -MB_APPLMODAL = 0x0 -MB_SYSTEMMODAL = 0x1000 -MB_TASKMODAL = 0x2000 - -MB_ICONERROR = MB_ICONHAND -MB_ICONWARNING = MB_ICONEXCLAMATION -MB_ICONINFORMATION = MB_ICONASTERISK - -MB_RIGHT = 0x800 -MB_RTLREADING = 0x1000 - -IDOK = 1 -IDCANCEL = 2 -IDABORT = 3 -IDRETRY = 4 -IDIGNORE = 5 -IDYES = 6 -IDNO = 7 -IDTRYAGAIN = 10 -IDCONTINUE = 11 - -WS_EX_DLGMODALFRAME = 0x00000001 - -windll.user32.MessageBoxW.argtypes = [c_int, c_wchar_p, c_wchar_p, c_int] -windll.user32.MessageBoxW.restype = c_int - -CBTProc = WINFUNCTYPE(c_int, c_int, WPARAM, LPARAM) -SetWindowsHookExW = windll.user32.SetWindowsHookExW -UnhookWindowsHookEx = windll.user32.UnhookWindowsHookEx -CallNextHookEx = windll.user32.CallNextHookEx -GetWindowRect = windll.user32.GetWindowRect -MoveWindow = windll.user32.MoveWindow - -# Windows API function prototypes -MessageBoxW = windll.user32.MessageBoxW -MessageBoxW.argtypes = [HWND, LPCWSTR, LPCWSTR, UINT] -MessageBoxW.restype = c_int - -CreateWindowExW = windll.user32.CreateWindowExW -CreateWindowExW.argtypes = [ - DWORD, LPCWSTR, LPCWSTR, DWORD, - c_int, c_int, c_int, c_int, - HWND, HMENU, HINSTANCE, LPVOID -] -CreateWindowExW.restype = HWND - -DefWindowProcW = windll.user32.DefWindowProcW -DefWindowProcW.argtypes = [HWND, UINT, WPARAM, LPARAM] -DefWindowProcW.restype = LRESULT - -RegisterClassExW = windll.user32.RegisterClassExW -RegisterClassExW.argtypes = [POINTER(ctypesWNDCLASS)] -RegisterClassExW.restype = ATOM - -GetModuleHandleW = windll.kernel32.GetModuleHandleW -GetModuleHandleW.argtypes = [LPCWSTR] -GetModuleHandleW.restype = HINSTANCE - -LoadIconW = windll.user32.LoadIconW -LoadIconW.argtypes = [HINSTANCE, LPCWSTR] -LoadIconW.restype = HICON - -LoadCursorW = windll.user32.LoadCursorW -LoadCursorW.argtypes = [HINSTANCE, LPCWSTR] -LoadCursorW.restype = HCURSOR - -ShowWindow = windll.user32.ShowWindow -ShowWindow.argtypes = [HWND, c_int] -ShowWindow.restype = BOOL - -UpdateWindow = windll.user32.UpdateWindow -UpdateWindow.argtypes = [HWND] -UpdateWindow.restype = BOOL - -SetWindowTextW = windll.user32.SetWindowTextW -SetWindowTextW.argtypes = [HWND, LPCWSTR] -SetWindowTextW.restype = BOOL - -CreateDialogParamW = windll.user32.CreateDialogParamW -CreateDialogParamW.argtypes = [HINSTANCE, LPCWSTR, HWND, LPVOID, LPARAM] -CreateDialogParamW.restype = HWND - -DestroyWindow = windll.user32.DestroyWindow -DestroyWindow.argtypes = [HWND] -DestroyWindow.restype = BOOL - -# Constants for Window Styles -WS_OVERLAPPEDWINDOW = 0x00CF0000 -WS_VISIBLE = 0x10000000 -WS_CHILD = 0x40000000 - -SW_SHOW = 5 -# Constants for window creation and message handling -WM_COMMAND = 0x0111 -WM_DESTROY = 0x0002 -WS_OVERLAPPEDWINDOW = 0x00CF0000 -WS_VISIBLE = 0x10000000 -WS_CHILD = 0x40000000 -SW_SHOW = 5 -SW_HIDE = 0 -ES_MULTILINE = 0x0004 -ES_AUTOVSCROLL = 0x0040 -ES_READONLY = 0x0800 -WS_VSCROLL = 0x00200000 -WS_EX_DLGMODALFRAME = 0x00000001 -IDOK = 1 -IDCANCEL = 2 - - -def wnd_proc( - hwnd: HWND, - message: c_uint, - wparam: WPARAM, - lparam: LPARAM, -) -> c_long: - if message == WM_DESTROY: - windll.user32.PostQuitMessage(0) - return c_long(0) - print(f"wnd_proc(hwnd={hwnd}, message={message}, wparam={wparam}, lparam={lparam})") - result = windll.user32.DefWindowProcW(hwnd, message, wparam, lparam) - if isinstance(result, int): - return c_long(result) - print(result, "result is unexpectedly class:", result.__class__.__name__) - return result - - -class CursorType(IntEnum): - ARROW = 32512 - IBEAM = 32513 - WAIT = 32514 - CROSS = 32515 - UPARROW = 32516 - SIZE = 32640 - ICON = 32641 - SIZENWSE = 32642 - SIZENESW = 32643 - SIZEWE = 32644 - SIZENS = 32645 - SIZEALL = 32646 - NO = 32648 - HAND = 32649 - APPSTARTING = 32650 - HELP = 32651 - - -class SimplePyHWND: - """Context manager for creating and destroying a simple custom window.""" - CLASS_NAME: str = "SimplePyHWND" - DISPLAY_NAME: str = "Python Simple Window" - - def __init__( - self, - *, - visible: bool = False, - dwExStyle: int = 0, # noqa: N803 - lpClassName: PyResourceId | str | None = None, # noqa: N803 - lpWindowName: str | None = None, # noqa: N803 - dwStyle: int | None = None, # noqa: N803 - x: int = 0, - y: int = 0, - nWidth: int = 1280, # noqa: N803 - nHeight: int = 720, # noqa: N803 - hWndParent: HWND | None = None, # noqa: N803 - hMenu: HMENU | None = None, # noqa: N803 - hInstance: HINSTANCE | None = None, # noqa: N803 - lpParam: LPVOID | None = None, # noqa: N803 - ): - self.hwnd: int | None = None - self.visible: bool = visible - self.dwExStyle: int = dwExStyle - self.lpClassName: PyResourceId | str | None = lpClassName or self.CLASS_NAME - self.lpWindowName: str | None = lpWindowName or self.DISPLAY_NAME - self.dwStyle: int | None = WS_OVERLAPPEDWINDOW | WS_VISIBLE if visible else (dwStyle or 0) - self.x: int = x - self.y: int = y - self.nWidth: int = nWidth - self.nHeight: int = nHeight - self.hWndParent: HWND | None = hWndParent - self.hMenu: HMENU | None = hMenu - self.hInstance: HINSTANCE | None = hInstance - self.lpParam: LPVOID | None = lpParam - - def __enter__(self) -> int: - self.register_class() - self.hwnd = self.create_window() - self._class_atom: PyResourceId | None = None - return self.hwnd - - def __exit__( - self, - exc_type: type[BaseException] | None, - exc_val: BaseException | None, - exc_tb: TracebackType | None, - ): - if self.hwnd is not None: - windll.user32.DestroyWindow(self.hwnd) - self.unregister_class() - - def register_class(self): - """Register the window class.""" - import win32gui - wc = win32gui.WNDCLASS() - wc.lpfnWndProc = WNDPROC(wnd_proc) # pyright: ignore[reportAttributeAccessIssue] - wc.lpszClassName = self.CLASS_NAME # pyright: ignore[reportAttributeAccessIssue] - self.hinst = wc.hInstance = win32gui.GetModuleHandle(None) # pyright: ignore[reportAttributeAccessIssue] - wc.hCursor = windll.user32.LoadCursorW(None, CursorType.ARROW.value) # pyright: ignore[reportAttributeAccessIssue] - try: - self._class_atom = win32gui.RegisterClass(wc) - except Exception as e: # pywintypes.error - if getattr(e, "winerror", None) != 1410: # class already registered - raise - print(f"{e} (Class already registered)") - - def unregister_class(self): - """Unregister the window class.""" - if self._class_atom is not None: - import win32gui - win32gui.UnregisterClass(self._class_atom, windll.kernel32.GetModuleHandleW(None)) - - def create_window(self) -> int: - """Create the window.""" - return windll.user32.CreateWindowExW( - self.dwExStyle, self.lpClassName, self.lpWindowName, self.dwStyle, - self.x, self.y, self.nWidth, self.nHeight, - self.hWndParent, self.hMenu, - windll.kernel32.GetModuleHandleW(None) if self.hInstance is None else self.hInstance, - self.lpParam) - - -if __name__ == "__main__": - import time - - with SimplePyHWND(visible=True) as hwnd: - print(f"Created SimplePyHWND with handle: {hwnd}") - time.sleep(3) - print("SimplePyHWND has been destroyed.") diff --git a/winforms/src/toga_winforms/libs/py_wrappers/winapi/__init__.py b/winforms/src/toga_winforms/libs/py_wrappers/winapi/__init__.py deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/winforms/src/toga_winforms/libs/py_wrappers/winapi/device_iocontrol.py b/winforms/src/toga_winforms/libs/py_wrappers/winapi/device_iocontrol.py deleted file mode 100644 index c0aed445de..0000000000 --- a/winforms/src/toga_winforms/libs/py_wrappers/winapi/device_iocontrol.py +++ /dev/null @@ -1,905 +0,0 @@ -from __future__ import annotations - -from enum import IntEnum # noqa: INP001 - - -class FSCTL(IntEnum): - # Windows XP and Earlier - FSCTL_REQUEST_OPLOCK_LEVEL_1 = 0x00090000 - """Requests an opportunistic lock (oplock) of level 1.""" - - FSCTL_REQUEST_OPLOCK_LEVEL_2 = 0x00090004 - """Requests an opportunistic lock (oplock) of level 2.""" - - FSCTL_REQUEST_BATCH_OPLOCK = 0x00090008 - """Requests a batch opportunistic lock (oplock).""" - - FSCTL_OPLOCK_BREAK_ACKNOWLEDGE = 0x0009000C - """Acknowledges an oplock break.""" - - FSCTL_OPBATCH_ACK_CLOSE_PENDING = 0x00090010 - """Acknowledges that an oplock break is pending.""" - - FSCTL_OPLOCK_BREAK_NOTIFY = 0x00090014 - """Notifies about an oplock break.""" - - FSCTL_LOCK_VOLUME = 0x00090018 - """Locks the volume.""" - - FSCTL_UNLOCK_VOLUME = 0x0009001C - """Unlocks the volume.""" - - FSCTL_DISMOUNT_VOLUME = 0x00090020 - """Dismounts the volume.""" - - FSCTL_IS_VOLUME_MOUNTED = 0x00090028 - """Checks if the volume is mounted.""" - - FSCTL_IS_PATHNAME_VALID = 0x0009002C - """Signals the file system driver not to perform any I/O boundary checks on partition read or write calls.""" - - FSCTL_MARK_VOLUME_DIRTY = 0x00090030 - """Marks the volume as dirty.""" - - FSCTL_QUERY_RETRIEVAL_POINTERS = 0x00090034 - """Queries retrieval pointers.""" - - FSCTL_GET_COMPRESSION = 0x0009003C - """Retrieves the current compression state of a file or directory on a volume whose file system supports per-stream compression.""" - - FSCTL_SET_COMPRESSION = 0x00090040 - """Sets the compression state of a file or directory on a volume whose file system supports per-stream compression.""" - - FSCTL_SET_BOOTLOADER_ACCESSED = 0x0009004C - """Marks the bootloader as accessed.""" - - FSCTL_OPLOCK_BREAK_ACK_NO_2 = 0x00090050 - """Acknowledges an oplock break without taking any further action.""" - - FSCTL_INVALIDATE_VOLUMES = 0x00090054 - """Invalidates the volumes.""" - - FSCTL_QUERY_FAT_BPB = 0x00090058 - """Retrieves the BIOS Parameter Block (BPB) for a FAT file system.""" - - FSCTL_REQUEST_FILTER_OPLOCK = 0x0009005C - """Requests a filter opportunistic lock (oplock).""" - - FSCTL_FILESYSTEM_GET_STATISTICS = 0x00090060 - """Retrieves file system statistics.""" - - FSCTL_GET_NTFS_VOLUME_DATA = 0x00090064 - """Retrieves information about the specified NTFS file system volume.""" - - FSCTL_GET_NTFS_FILE_RECORD = 0x00090068 - """Retrieves the first file record that is in use and is of a lesser than or equal ordinal value to the requested file reference number.""" - - FSCTL_GET_VOLUME_BITMAP = 0x0009006F - """Retrieves a bitmap of occupied and available clusters on a volume.""" - - FSCTL_GET_RETRIEVAL_POINTERS = 0x00090073 - """Given a file handle, retrieves a data structure that describes the allocation and location on disk of a specific file, or, given a volume handle, the locations of bad clusters on a volume.""" # noqa: E501, W505 - - FSCTL_MOVE_FILE = 0x00090074 - """Relocates one or more virtual clusters of a file from one logical cluster to another within the same volume. This operation is used during defragmentation.""" - - FSCTL_IS_VOLUME_DIRTY = 0x00090078 - """Determines whether the volume's dirty bit is set, indicating that the file system may be in an inconsistent state.""" - - FSCTL_ALLOW_EXTENDED_DASD_IO = 0x00090083 - """Allows file systems that reside on large storage devices to issue I/O operations that go beyond the 32-bit address range.""" - - FSCTL_FIND_FILES_BY_SID = 0x0009008F - """Finds files on a volume that belong to a specified security identifier (SID).""" - - FSCTL_SET_OBJECT_ID = 0x000900BC - """Sets the object identifier for the specified file or directory.""" - - FSCTL_GET_OBJECT_ID = 0x0009009C - """Retrieves the object identifier for the specified file or directory.""" - - FSCTL_DELETE_OBJECT_ID = 0x000900A0 - """Deletes the object identifier for the specified file or directory.""" - - FSCTL_SET_REPARSE_POINT = 0x000900A4 - """Sets a reparse point on a file or directory.""" - - FSCTL_GET_REPARSE_POINT = 0x000900A8 - """Retrieves the reparse point data associated with the file or directory identified by the specified handle.""" - - FSCTL_DELETE_REPARSE_POINT = 0x000900AC - """Deletes a reparse point from the specified file or directory.""" - - FSCTL_ENUM_USN_DATA = 0x000900B0 - """Enumerates the update sequence number (USN) change journal data.""" - - FSCTL_SECURITY_ID_CHECK = 0x000900B4 - """Performs a security ID check.""" - - FSCTL_READ_USN_JOURNAL = 0x000900B8 - """Reads the update sequence number (USN) change journal.""" - - FSCTL_SET_OBJECT_ID_EXTENDED = 0x000900C0 - """Sets extended information for an object identifier. This operation is part of the object identifier management on NTFS volumes.""" - - FSCTL_CREATE_OR_GET_OBJECT_ID = 0x000900C3 - """Retrieves the object identifier for the specified file or directory. If no object identifier exists, this operation creates one.""" - - FSCTL_SET_SPARSE = 0x000900C4 - """Marks the indicated file as sparse or not sparse. In a sparse file, large ranges of zeros may not require disk allocation.""" - - FSCTL_SET_ZERO_DATA = 0x000980C8 - """Fills a specified range of a file with zeros (0).""" - - FSCTL_QUERY_ALLOCATED_RANGES = 0x000940CF - """Scans a file or alternate stream looking for ranges that may contain nonzero data.""" - - FSCTL_SET_ZERO_ON_DEALLOCATION = 0x00090194 - """Indicates an NTFS file system file handle should have its clusters filled with zeros when it is deallocated.""" - - FSCTL_MARK_HANDLE = 0x000900FC - """Marks a specified file or directory and its change journal record with information about changes to that file or directory.""" - - FSCTL_GET_RETRIEVAL_POINTER_BASE = 0x00090098 - """Retrieves the base LCN (Logical Cluster Number) of the allocation unit.""" - - FSCTL_IS_CSV_FILE = 0x0009016B - """Determines whether a file is in a Cluster Shared Volume (CSV).""" - - FSCTL_INITIATE_REPAIR = 0x00090354 - """Triggers the NTFS file system to start a self-healing cycle on a single file.""" - - FSCTL_SHRINK_VOLUME = 0x000902A4 - """Shrinks the volume.""" - - FSCTL_TXFS_MODIFY_RM = 0x00090294 - """Modifies the Resource Manager (RM) for a transactional file system.""" - - FSCTL_TXFS_QUERY_RM_INFORMATION = 0x000902A0 - """Queries Resource Manager (RM) information for a transactional file system.""" - - FSCTL_TXFS_ROLLFORWARD_REDO = 0x000902B4 - """Performs a redo of a transaction during a rollforward operation.""" - - FSCTL_TXFS_ROLLFORWARD_UNDO = 0x000902B8 - """Performs an undo of a transaction during a rollforward operation.""" - - FSCTL_TXFS_START_RM = 0x000902BC - """Starts the Resource Manager (RM) for a transactional file system.""" - - FSCTL_TXFS_SHUTDOWN_RM = 0x000902C0 - """Shuts down the Resource Manager (RM) for a transactional file system.""" - - FSCTL_TXFS_READ_BACKUP_INFORMATION = 0x000902C4 - """Reads backup information for a transactional file system.""" - - FSCTL_TXFS_WRITE_BACKUP_INFORMATION = 0x000902C8 - """Writes backup information for a transactional file system.""" - - FSCTL_TXFS_CREATE_SECONDARY_RM = 0x000902CC - """Creates a secondary Resource Manager (RM) for a transactional file system.""" - - FSCTL_TXFS_GET_METADATA_INFO = 0x000902D0 - """Gets metadata information for a transactional file system.""" - - FSCTL_TXFS_GET_TRANSACTED_VERSION = 0x000902D4 - """Gets the transacted version of a file in a transactional file system.""" - - FSCTL_TXFS_SAVEPOINT_INFORMATION = 0x000902E0 - """Gets savepoint information for a transactional file system.""" - - FSCTL_TXFS_CREATE_MINIVERSION = 0x000902F0 - """Creates a mini version of a file in a transactional file system.""" - - FSCTL_TXFS_TRANSACTION_ACTIVE = 0x00090318 - """Indicates whether a transaction is active in a transactional file system.""" - - FSCTL_RESET_VOLUME_ALLOCATION_HINTS = 0x00090274 - """Resets volume allocation hints.""" - - FSCTL_QUERY_USN_JOURNAL = 0x000900F4 - """Queries for information on the current update sequence number (USN) change journal, its records, and its capacity.""" - - FSCTL_DELETE_USN_JOURNAL = 0x000900F8 - """Deletes the update sequence number (USN) change journal.""" - - FSCTL_READ_FILE_USN_DATA = 0x000900EB - """Retrieves the update sequence number (USN) change-journal information for the specified file or directory.""" - - FSCTL_WRITE_USN_CLOSE_RECORD = 0x000900F0 - """Generates a close record in the USN change journal.""" - - FSCTL_SIS_COPYFILE = 0x00090040 - """Performs a copy operation on a single instance store (SIS) volume.""" - - FSCTL_SIS_LINK_FILES = 0x00090044 - """Links two files on a single instance store (SIS) volume.""" - - FSCTL_GET_QUOTA_INFORMATION = 0x00090040 - """Retrieves quota information for the specified volume.""" - - FSCTL_GET_ENCRYPTION = 0x00090062 - """Retrieves the encryption state of a file or directory on a volume that supports the Encrypting File System (EFS).""" - - FSCTL_SET_ENCRYPTION = 0x000900A7 - """Sets the encryption state of a file or directory on a volume that supports the Encrypting File System (EFS).""" - - FSCTL_SET_USN_JOURNAL = 0x000900F8 - """Creates an update sequence number (USN) change journal stream on a target volume, or modifies an existing change journal stream.""" - - FSCTL_QUERY_LOCKS = 0x00094011 - """Queries for lock information on a file.""" - - FSCTL_QUERY_SPARSE_FILE = 0x00094013 - """Queries whether the file is a sparse file.""" - - FSCTL_SET_DEFECT_MANAGEMENT = 0x00090088 - """Disables or enables the defect management feature for the specified file. This feature is typically used for CD/DVD/Blu-ray media.""" - - FSCTL_GET_OBJECT_ID_EXTENDED = 0x000900DB - """Retrieves extended information for an object identifier.""" - - FSCTL_DISABLE_LOCAL_BUFFERING = 0x0009008D - """Disables local buffering for a file.""" - - # Windows Vista - FSCTL_GET_REPAIR = 0x00090274 - """Retrieves information about the NTFS file system's self-healing mechanism.""" - - FSCTL_GET_RETRIEVAL_POINTERS_AND_REFCOUNT = 0x00090173 - """Retrieves a data structure that describes the allocation and reference count information for the specified file.""" - - FSCTL_SET_SHORT_NAME_BEHAVIOR = 0x000980A4 - """Sets the short name behavior for a specified volume.""" - - FSCTL_QUERY_ON_DISK_VOLUME_INFO = 0x0009013C - """Queries information about the on-disk format of a volume.""" - - FSCTL_SET_PURGE_FAILURE_MODE = 0x000980A8 - """Sets the purge failure mode for a specified volume.""" - - # Windows 7 - FSCTL_DUPLICATE_EXTENTS_TO_FILE = 0x00098344 - """Instructs the file system to copy a range of file bytes on behalf of an application.""" - - FSCTL_FILE_LEVEL_TRIM = 0x00098208 - """Indicates ranges within the specified file that do not need to be stored by the storage system.""" - - FSCTL_GET_INTEGRITY_INFORMATION = 0x0009027C - """Retrieves the integrity status of a file or directory on a ReFS volume.""" - - FSCTL_GET_BOOT_AREA_INFO = 0x0009018C - """Retrieves information about the boot area of a volume.""" - - # Windows 8 - FSCTL_QUERY_PAGEFILE_ENCRYPTION = 0x0009019E - """Queries the encryption state of a page file.""" - - FSCTL_QUERY_STORAGE_CLASSES = 0x000902A0 - """Retrieves the storage tiers defined for a volume that supports data tiering.""" - - FSCTL_CSV_QUERY_VETO_FILE_DIRECT_IO = 0x000902D1 - """Determines whether direct I/O is vetoed for a file in a Cluster Shared Volume (CSV).""" - - FSCTL_SET_INTEGRITY_INFORMATION = 0x0009C280 - """Sets the integrity status of a file or directory on a ReFS volume.""" - - FSCTL_QUERY_REGION_INFO = 0x00090314 - """Retrieves storage tier regions defined for a volume that supports data tiering. This function returns detailed information about the regions in the tiered storage volume.""" # noqa: E501 - - FSCTL_QUERY_FILE_SYSTEM_RECOGNITION = 0x00090318 - """Queries for file system recognition information on a volume.""" - - FSCTL_GET_REFS_VOLUME_DATA = 0x00090364 - """Retrieves information about a ReFS volume.""" - - FSCTL_INTEGRITY_CHECKPOINT = 0x00090394 - """Provides a mechanism to trigger an integrity checkpoint on a file or volume.""" - - FSCTL_SCRUB_DATA = 0x0009035C - """Scrubs the data on the volume to detect and potentially repair data corruption.""" - - FSCTL_REPAIR_COPIES = 0x00090384 - """Repairs copies of files that are corrupt.""" - - # Windows 10 - FSCTL_LOOKUP_STREAM_FROM_CLUSTER = 0x000902B1 - """Given a handle to a NTFS volume or a file on a NTFS volume, returns a chain of data structures that describes streams that occupy the specified clusters.""" - - FSCTL_FILESYSTEM_GET_STATISTICS_EX = 0x00090334 - """Retrieves the information from various file system performance counters.""" - - FSCTL_SET_REFS_FILE_STRICTLY_SEQUENTIAL = 0x000903DC - """Sets the file as strictly sequential on ReFS volumes.""" - - FSCTL_QUERY_USN_JOURNAL_EX = 0x000903F4 - """Provides extended information about the USN change journal.""" - - # Unknown - FSCTL_MAKE_MEDIA_COMPATIBLE = 0x0009804C - FSCTL_QUERY_SPARING_INFO = 0x00098054 - FSCTL_SET_VOLUME_COMPRESSION_STATE = 0x00098060 - FSCTL_TXFS_READ_BACKUP_INFORMATION2 = 0x000980A0 - FSCTL_TXFS_WRITE_BACKUP_INFORMATION2 = 0x000980A4 - FSCTL_FILE_TYPE_NOTIFICATION = 0x000980A8 - FSCTL_CSV_GET_VOLUME_PATH_NAME = 0x000980AC - FSCTL_CSV_GET_VOLUME_NAME_FOR_VOLUME_MOUNT_POINT = 0x000980B0 - FSCTL_CSV_GET_VOLUME_PATH_NAMES_FOR_VOLUME_NAME = 0x000980B4 - FSCTL_IS_FILE_ON_CSV_VOLUME = 0x000980B8 - FSCTL_QUERY_DEPENDENT_VOLUME = 0x000980BC - FSCTL_SD_GLOBAL_CHANGE = 0x000980C0 - FSCTL_SET_PERSISTENT_VOLUME_STATE = 0x000980D0 - FSCTL_QUERY_PERSISTENT_VOLUME_STATE = 0x000980D4 - FSCTL_REQUEST_OPLOCK = 0x000980D8 - FSCTL_CSV_TUNNEL_REQUEST = 0x000980DC - FSCTL_ENABLE_UPGRADE = 0x00090034 - FSCTL_FILE_PREFETCH = 0x000900E4 - -class IOCTL(IntEnum): - # Storage IOCTL codes - STORAGE_CHECK_VERIFY = 0x002D1400 - """Checks if the media is accessible.""" - - STORAGE_CHECK_VERIFY2 = 0x002D1400 - """Verifies the media accessibility.""" - - STORAGE_MEDIA_REMOVAL = 0x002D1401 - """Prevents or allows the removal of the media.""" - - STORAGE_EJECT_MEDIA = 0x002D1402 - """Ejects the media from the device.""" - - STORAGE_LOAD_MEDIA = 0x002D1403 - """Loads the media into the device.""" - - STORAGE_LOAD_MEDIA2 = 0x002D1403 - """Loads the media into the device (alternative).""" - - STORAGE_RESERVE = 0x002D1404 - """Reserves the device for exclusive use.""" - - STORAGE_RELEASE = 0x002D1405 - """Releases the reserved device.""" - - STORAGE_FIND_NEW_DEVICES = 0x002D1406 - """Scans for new storage devices.""" - - STORAGE_EJECTION_CONTROL = 0x002D1450 - """Controls the ejection mechanism of the media.""" - - STORAGE_MCN_CONTROL = 0x002D1451 - """Controls the media change notification feature.""" - - STORAGE_GET_MEDIA_TYPES = 0x002D1500 - """Retrieves the media types supported by the device.""" - - STORAGE_GET_MEDIA_TYPES_EX = 0x002D1501 - """Retrieves extended information about supported media types.""" - - STORAGE_GET_MEDIA_SERIAL_NUMBER = 0x002D1504 - """Retrieves the serial number of the media.""" - - STORAGE_GET_HOTPLUG_INFO = 0x002D1505 - """Retrieves hotplug information for the device.""" - - STORAGE_SET_HOTPLUG_INFO = 0x002D1506 - """Sets hotplug information for the device.""" - - STORAGE_RESET_BUS = 0x002D1600 - """Resets the bus on which the storage device is connected.""" - - STORAGE_RESET_DEVICE = 0x002D1601 - """Resets the storage device.""" - - STORAGE_BREAK_RESERVATION = 0x002D1605 - """Breaks the reservation on a storage device.""" - - STORAGE_PERSISTENT_RESERVE_IN = 0x002D1606 - """Issues a persistent reserve in command to the device.""" - - STORAGE_PERSISTENT_RESERVE_OUT = 0x002D1607 - """Issues a persistent reserve out command to the device.""" - - STORAGE_GET_DEVICE_NUMBER = 0x002D1640 - """Retrieves the device number of the storage device.""" - - STORAGE_PREDICT_FAILURE = 0x002D1680 - """Predicts potential failure of the storage device.""" - - STORAGE_READ_CAPACITY = 0x002D1690 - """Reads the capacity of the storage device.""" - - # Disk IOCTL codes - DISK_GET_DRIVE_GEOMETRY = 0x00070000 - """Retrieves the geometry of the disk drive.""" - - DISK_GET_PARTITION_INFO = 0x00070001 - """Retrieves partition information for the disk.""" - - DISK_SET_PARTITION_INFO = 0x00070002 - """Sets partition information for the disk.""" - - DISK_GET_DRIVE_LAYOUT = 0x00070003 - """Retrieves the drive layout of the disk.""" - - DISK_SET_DRIVE_LAYOUT = 0x00070004 - """Sets the drive layout of the disk.""" - - DISK_VERIFY = 0x00070005 - """Verifies the integrity of a region of the disk.""" - - DISK_FORMAT_TRACKS = 0x00070006 - """Formats the specified tracks on the disk.""" - - DISK_REASSIGN_BLOCKS = 0x00070007 - """Reassigns bad blocks on the disk.""" - - DISK_PERFORMANCE = 0x00070008 - """Retrieves performance data for the disk.""" - - DISK_IS_WRITABLE = 0x00070009 - """Checks if the disk is writable.""" - - DISK_LOGGING = 0x0007000A - """Controls logging on the disk.""" - - DISK_FORMAT_TRACKS_EX = 0x0007000B - """Formats the specified tracks on the disk with extended options.""" - - DISK_HISTOGRAM_STRUCTURE = 0x0007000C - """Retrieves the histogram structure for disk performance analysis.""" - - DISK_HISTOGRAM_DATA = 0x0007000D - """Retrieves histogram data for disk performance analysis.""" - - DISK_HISTOGRAM_RESET = 0x0007000E - """Resets the histogram data on the disk.""" - - DISK_REQUEST_STRUCTURE = 0x0007000F - """Retrieves the request structure for the disk.""" - - DISK_REQUEST_DATA = 0x00070010 - """Retrieves request data for the disk.""" - - DISK_PERFORMANCE_OFF = 0x00070018 - """Disables performance monitoring on the disk.""" - - DISK_CONTROLLER_NUMBER = 0x00070011 - """Retrieves the controller number for the disk.""" - - SMART_GET_VERSION = 0x00070020 - """Retrieves the version of the SMART (Self-Monitoring, Analysis, and Reporting Technology) feature.""" - - SMART_SEND_DRIVE_COMMAND = 0x00070021 - """Sends a command to the drive using the SMART feature.""" - - SMART_RCV_DRIVE_DATA = 0x00070022 - """Receives data from the drive using the SMART feature.""" - - DISK_GET_PARTITION_INFO_EX = 0x00070012 - """Retrieves extended partition information for the disk.""" - - DISK_SET_PARTITION_INFO_EX = 0x00070013 - """Sets extended partition information for the disk.""" - - DISK_GET_DRIVE_LAYOUT_EX = 0x00070014 - """Retrieves extended drive layout information for the disk.""" - - DISK_SET_DRIVE_LAYOUT_EX = 0x00070015 - """Sets extended drive layout information for the disk.""" - - DISK_CREATE_DISK = 0x00070016 - """Creates a new disk layout.""" - - DISK_GET_LENGTH_INFO = 0x00070017 - """Retrieves the length information of the disk.""" - - DISK_GET_DRIVE_GEOMETRY_EX = 0x00070028 - """Retrieves extended geometry information for the disk.""" - - DISK_REASSIGN_BLOCKS_EX = 0x00070029 - """Reassigns bad blocks on the disk with extended options.""" - - DISK_UPDATE_DRIVE_SIZE = 0x00070032 - """Updates the size information of the disk.""" - - DISK_GROW_PARTITION = 0x00070034 - """Grows the partition on the disk.""" - - DISK_GET_CACHE_INFORMATION = 0x00070035 - """Retrieves cache information for the disk.""" - - DISK_SET_CACHE_INFORMATION = 0x00070036 - """Sets cache information for the disk.""" - - OBSOLETE_DISK_GET_WRITE_CACHE_STATE = 0x00070037 - """Obsolete. Previously retrieved the write cache state of the disk.""" - - DISK_DELETE_DRIVE_LAYOUT = 0x00070040 - """Deletes the drive layout on the disk.""" - - DISK_UPDATE_PROPERTIES = 0x00070050 - """Updates the properties of the disk.""" - - DISK_FORMAT_DRIVE = 0x000700F3 - """Formats the entire drive.""" - - DISK_SENSE_DEVICE = 0x000700F8 - """Senses the device status for the disk.""" - - DISK_CHECK_VERIFY = 0x00070200 - """Checks if the disk is accessible.""" - - DISK_MEDIA_REMOVAL = 0x00070201 - """Controls the removal of media from the disk.""" - - DISK_EJECT_MEDIA = 0x00070202 - """Ejects the media from the disk.""" - - DISK_LOAD_MEDIA = 0x00070203 - """Loads media into the disk.""" - - DISK_RESERVE = 0x00070204 - """Reserves the disk for exclusive use.""" - - DISK_RELEASE = 0x00070205 - """Releases the reserved disk.""" - - DISK_FIND_NEW_DEVICES = 0x00070206 - """Scans for new devices connected to the disk.""" - - DISK_GET_MEDIA_TYPES = 0x00070300 - """Retrieves the media types supported by the disk.""" - - DISK_RESET_SNAPSHOT_INFO = 0x00070084 - """Resets snapshot information on the disk.""" - - # Changer IOCTL codes - CHANGER_GET_PARAMETERS = 0x00090000 - """Retrieves the parameters of the media changer device.""" - - CHANGER_GET_STATUS = 0x00090001 - """Retrieves the current status of the media changer device.""" - - CHANGER_GET_PRODUCT_DATA = 0x00090002 - """Retrieves product data for the media changer device.""" - - CHANGER_SET_ACCESS = 0x00090004 - """Sets access control for the media changer device.""" - - CHANGER_GET_ELEMENT_STATUS = 0x00090005 - """Retrieves the status of elements in the media changer.""" - - CHANGER_INITIALIZE_ELEMENT_STATUS = 0x00090006 - """Initializes the status of elements in the media changer.""" - - CHANGER_SET_POSITION = 0x00090007 - """Sets the position of elements within the media changer.""" - - CHANGER_EXCHANGE_MEDIUM = 0x00090008 - """Exchanges media between two slots in the media changer.""" - - CHANGER_MOVE_MEDIUM = 0x00090009 - """Moves media from one slot to another within the media changer.""" - - CHANGER_LOCK_UNLOCK_ELEMENT = 0x0009000A - """Locks or unlocks an element in the media changer.""" - - CHANGER_POSITION_ELEMENT = 0x0009000B - """Positions an element in the media changer.""" - - CHANGER_RESERVE = 0x0009000C - """Reserves the media changer for exclusive use.""" - - CHANGER_RELEASE = 0x0009000D - """Releases the reserved media changer.""" - - CHANGER_EXCHANGE_MEDIUM_IN = 0x0009000E - """Exchanges media in from an external slot to the media changer.""" - - CHANGER_EXCHANGE_MEDIUM_OUT = 0x0009000F - """Exchanges media out from the media changer to an external slot.""" - - CHANGER_GET_ELEMENT_STATUS_IN = 0x00090010 - """Retrieves status information for input elements in the media changer.""" - - CHANGER_GET_ELEMENT_STATUS_OUT = 0x00090011 - """Retrieves status information for output elements in the media changer.""" - - CHANGER_MOVE_MEDIUM_IN = 0x00090012 - """Moves media from an external input slot into the media changer.""" - - CHANGER_MOVE_MEDIUM_OUT = 0x00090013 - """Moves media from the media changer to an external output slot.""" - - CHANGER_PREPARE_ELEMENT_FOR_ACCESS = 0x00090014 - """Prepares an element in the media changer for access.""" - - CHANGER_READ_ELEMENT_STATUS = 0x00090015 - """Reads the status of an element in the media changer.""" - - CHANGER_CALIBRATE = 0x00090016 - """Calibrates the elements of the media changer.""" - - CHANGER_SET_ACCESS_CONTROL = 0x00090017 - """Sets access control permissions for the media changer.""" - - CHANGER_GET_POSITION = 0x00090018 - """Retrieves the current position of elements in the media changer.""" - - CHANGER_GET_PRODUCT_DATA_EX = 0x00090019 - """Retrieves extended product data for the media changer device.""" - - CHANGER_SET_POSITION_EX = 0x0009001A - """Sets the position of elements within the media changer with extended options.""" - - CHANGER_EXCHANGE_MEDIUM_EX = 0x0009001B - """Exchanges media between slots in the media changer with extended options.""" - - CHANGER_LOCK_UNLOCK_ELEMENT_EX = 0x0009001C - """Locks or unlocks an element in the media changer with extended options.""" - - CHANGER_SET_ACCESS_EX = 0x0009001D - """Sets access control permissions for the media changer with extended options.""" - - CHANGER_PREPARE_ELEMENT_FOR_ACCESS_EX = 0x0009001E - """Prepares an element in the media changer for access with extended options.""" - - CHANGER_EXCHANGE_MEDIUM_IN_EX = 0x0009001F - """Exchanges media in from an external slot to the media changer with extended options.""" - - CHANGER_EXCHANGE_MEDIUM_OUT_EX = 0x00090020 - """Exchanges media out from the media changer to an external slot with extended options.""" - - CHANGER_MOVE_MEDIUM_IN_EX = 0x00090021 - """Moves media from an external input slot into the media changer with extended options.""" - - CHANGER_MOVE_MEDIUM_OUT_EX = 0x00090022 - """Moves media from the media changer to an external output slot with extended options.""" - - CHANGER_CALIBRATE_EX = 0x00090023 - """Calibrates the elements of the media changer with extended options.""" - - CHANGER_GET_STATUS_EX = 0x00090024 - """Retrieves the current status of the media changer with extended options.""" - - CHANGER_GET_ELEMENT_STATUS_IN_EX = 0x00090025 - """Retrieves status information for input elements in the media changer with extended options.""" - - CHANGER_GET_ELEMENT_STATUS_OUT_EX = 0x00090026 - """Retrieves status information for output elements in the media changer with extended options.""" - - CHANGER_READ_ELEMENT_STATUS_EX = 0x00090027 - """Reads the status of an element in the media changer with extended options.""" - - CHANGER_REINITIALIZE_TRANSPORT = 0x0009000A - CHANGER_QUERY_VOLUME_TAGS = 0x0009000B - - # Tape IOCTL codes - TAPE_ERASE = 0x00160000 - """Erases the tape media.""" - - TAPE_PREPARE = 0x00160001 - """Prepares the tape drive for an operation.""" - - TAPE_WRITE_MARKS = 0x00160002 - """Writes marks on the tape media.""" - - TAPE_GET_POSITION = 0x00160003 - """Retrieves the current position of the tape media.""" - - TAPE_SET_POSITION = 0x00160004 - """Sets the position of the tape media.""" - - TAPE_GET_DRIVE_PARAMS = 0x00160005 - """Retrieves the parameters of the tape drive.""" - - TAPE_SET_DRIVE_PARAMS = 0x00160006 - """Sets the parameters of the tape drive.""" - - TAPE_GET_MEDIA_PARAMS = 0x00160007 - """Retrieves the parameters of the tape media.""" - - TAPE_SET_MEDIA_PARAMS = 0x00160008 - """Sets the parameters of the tape media.""" - - TAPE_GET_STATUS = 0x00160009 - """Retrieves the current status of the tape drive.""" - - TAPE_GET_MEDIA_TYPES = 0x0016000A - """Retrieves the media types supported by the tape drive.""" - - TAPE_QUERY_DRIVE_PARAMETERS = 0x0016000B - """Queries the drive parameters of the tape drive.""" - - TAPE_QUERY_MEDIA_CAPACITY = 0x0016000C - """Queries the media capacity of the tape media.""" - - TAPE_PREPARE_EX = 0x0016000D - """Prepares the tape drive for an operation with extended options.""" - - TAPE_SET_POSITION_EX = 0x0016000E - """Sets the position of the tape media with extended options.""" - - TAPE_ERASE_EX = 0x0016000F - """Erases the tape media with extended options.""" - - TAPE_SET_DRIVE_PARAMS_EX = 0x00160010 - """Sets the parameters of the tape drive with extended options.""" - - TAPE_SET_MEDIA_PARAMS_EX = 0x00160011 - """Sets the parameters of the tape media with extended options.""" - - TAPE_WRITE_MARKS_EX = 0x00160012 - """Writes marks on the tape media with extended options.""" - - TAPE_GET_DRIVE_PARAMS_EX = 0x00160013 - """Retrieves the parameters of the tape drive with extended options.""" - - TAPE_GET_MEDIA_PARAMS_EX = 0x00160014 - """Retrieves the parameters of the tape media with extended options.""" - - TAPE_GET_POSITION_EX = 0x00160015 - """Retrieves the current position of the tape media with extended options.""" - - TAPE_GET_STATUS_EX = 0x00160016 - """Retrieves the current status of the tape drive with extended options.""" - # USB IOCTL codes - USB_GET_NODE_INFORMATION = 0x00220040 - """Retrieves information about a USB node (hub or port).""" - - USB_GET_NODE_CONNECTION_INFORMATION = 0x00220041 - """Retrieves information about a connection to a USB node.""" - - USB_GET_DESCRIPTOR_FROM_NODE_CONNECTION = 0x00220042 - """Retrieves a descriptor for a USB node connection.""" - - USB_GET_NODE_CONNECTION_NAME = 0x00220044 - """Retrieves the name of a USB node connection.""" - - USB_DIAG_IGNORE_HUBS_ON = 0x00220045 - """Enables diagnostic mode that ignores USB hubs.""" - - USB_DIAG_IGNORE_HUBS_OFF = 0x00220046 - """Disables diagnostic mode that ignores USB hubs.""" - - USB_GET_NODE_CONNECTION_DRIVERKEY_NAME = 0x00220047 - """Retrieves the driver key name for a USB node connection.""" - - USB_GET_HUB_CAPABILITIES = 0x00220048 - """Retrieves the capabilities of a USB hub.""" - - USB_GET_NODE_CONNECTION_ATTRIBUTES = 0x00220049 - """Retrieves the attributes of a USB node connection.""" - - USB_GET_NODE_CONNECTION_INFORMATION_EX = 0x00220050 - """Retrieves extended information about a connection to a USB node.""" - - USB_RESET_HUB = 0x00220051 - """Resets a USB hub.""" - - USB_GET_HUB_CAPABILITIES_EX = 0x00220052 - """Retrieves extended capabilities of a USB hub.""" - - USB_CYCLE_PORT = 0x00220053 - """Cycles the power of a USB port.""" - - USB_GET_PORT_CONNECTOR_PROPERTIES = 0x00220054 - """Retrieves the properties of a USB port connector.""" - - USB_GET_NODE_INFORMATION_EX = 0x00220055 - """Retrieves extended information about a USB node (hub or port).""" - - USB_GET_NODE_CONNECTION_INFORMATION_EX_V2 = 0x00220056 - """Retrieves additional extended information about a USB node connection.""" - - USB_RESET_PORT = 0x00220057 - """Resets a USB port.""" - - USB_HUB_CYCLE_PORT = 0x00220058 - """Cycles the power of a USB hub port.""" - - USB_GET_NODE_CONNECTION_INFORMATION_EX_V2_1 = 0x00220059 - """Retrieves extended information about a USB node connection, version 2.1.""" - - USB_GET_NODE_CONNECTION_DRIVERKEY_NAME_EX = 0x00220060 - """Retrieves the driver key name for a USB node connection with extended options.""" - - USB_GET_DESCRIPTOR_FROM_NODE_CONNECTION_EX = 0x00220061 - """Retrieves a descriptor for a USB node connection with extended options.""" - - USB_GET_NODE_INFORMATION_EX_V2 = 0x00220062 - """Retrieves additional extended information about a USB node, version 2.""" - - USB_GET_NODE_INFORMATION_EX_V2_1 = 0x00220063 - """Retrieves extended information about a USB node, version 2.1.""" - - USB_GET_NODE_CONNECTION_INFORMATION_EX_V2_2 = 0x00220064 - """Retrieves additional extended information about a USB node connection, version 2.2.""" - - USB_GET_NODE_INFORMATION_EX_V3 = 0x00220065 - """Retrieves extended information about a USB node, version 3.""" - - USB_HUB_RESET = 0x00220066 - """Resets a USB hub.""" - - USB_HUB_RESET_EX = 0x00220067 - """Resets a USB hub with extended options.""" - - USB_HUB_GET_NODE_INFORMATION = 0x00220068 - """Retrieves information about a USB hub node.""" - - USB_HUB_GET_NODE_INFORMATION_EX = 0x00220069 - """Retrieves extended information about a USB hub node.""" - - USB_HUB_RESET_PORT = 0x00220070 - """Resets a USB hub port.""" - - USB_HUB_RESET_PORT_EX = 0x00220071 - """Resets a USB hub port with extended options.""" - - USB_GET_NODE_INFORMATION_EX_V3_1 = 0x00220072 - """Retrieves extended information about a USB node, version 3.1.""" - - USB_GET_NODE_CONNECTION_INFORMATION_EX_V3 = 0x00220073 - """Retrieves extended information about a USB node connection, version 3.""" - - USB_GET_NODE_INFORMATION_EX_V3_2 = 0x00220074 - """Retrieves additional extended information about a USB node, version 3.2.""" - - USB_HUB_RESET_EX_V2 = 0x00220075 - """Resets a USB hub with extended options, version 2.""" - - USB_HUB_CYCLE_PORT_EX = 0x00220076 - """Cycles the power of a USB hub port with extended options.""" - - USB_GET_PORT_CONNECTOR_PROPERTIES_EX = 0x00220077 - """Retrieves the properties of a USB port connector with extended options.""" - - USB_GET_NODE_CONNECTION_INFORMATION_EX_V3_3 = 0x00220078 - """Retrieves additional extended information about a USB node connection, version 3.3.""" - - USB_HUB_RESET_PORT_EX_V2 = 0x00220079 - """Resets a USB hub port with extended options, version 2.""" - - # Serial IOCTL codes - SERIAL_LSRMST_INSERT = 0x0001001F - """Inserts Line Status Register (LSR) and Modem Status Register (MST) data into the input stream.""" - - SERENUM_EXPOSE_HARDWARE = 0x00010080 - """Exposes the hardware associated with the serial enumerator.""" - - SERENUM_REMOVE_HARDWARE = 0x00010081 - """Removes the hardware associated with the serial enumerator.""" - - SERENUM_PORT_DESC = 0x00010082 - """Retrieves the port description for the serial enumerator.""" - - SERENUM_GET_PORT_NAME = 0x00010083 - """Retrieves the port name for the serial enumerator.""" - - # AVIO IOCTL codes - AVIO_ALLOCATE_STREAM = 0x00060001 - """Allocates a stream for AVIO.""" - - AVIO_FREE_STREAM = 0x00060002 - """Frees a previously allocated stream for AVIO.""" - - AVIO_MODIFY_STREAM = 0x00060003 - """Modifies settings of a stream allocated for AVIO.""" - - # Volume IOCTL codes - VOLUME_GET_VOLUME_DISK_EXTENTS = 0x00560000 - """Retrieves disk extents for the specified volume.""" - - VOLUME_ONLINE = 0x00560002 - """Brings the specified volume online.""" - - VOLUME_OFFLINE = 0x00560003 - """Takes the specified volume offline.""" - - VOLUME_IS_CLUSTERED = 0x0056000C - """Checks if the specified volume is part of a cluster.""" - - VOLUME_GET_GPT_ATTRIBUTES = 0x0056000E - """Retrieves GPT attributes of the specified volume.""" diff --git a/winforms/src/toga_winforms/libs/py_wrappers/winapi/input_dialogs.py b/winforms/src/toga_winforms/libs/py_wrappers/winapi/input_dialogs.py deleted file mode 100644 index cd5d76666e..0000000000 --- a/winforms/src/toga_winforms/libs/py_wrappers/winapi/input_dialogs.py +++ /dev/null @@ -1,39 +0,0 @@ -from __future__ import annotations - -import ctypes -from ctypes import wintypes -from typing import TYPE_CHECKING, Sequence - -if TYPE_CHECKING: - from ctypes import _CData - -CHOOSECOLOR = ctypes.windll.comdlg32.ChooseColorW -class COLORREF(ctypes.Structure): - _fields_ = [("rgb", wintypes.DWORD)] - -class CHOOSECOLOR(ctypes.Structure): - _fields_: Sequence[tuple[str, type[_CData]] | tuple[str, type[_CData], int]] = [ - ("lStructSize", wintypes.DWORD), - ("hwndOwner", wintypes.HWND), - ("hInstance", wintypes.HINSTANCE), - ("rgbResult", COLORREF), - ("lpCustColors", ctypes.POINTER(COLORREF)), - ("Flags", wintypes.DWORD), - ("lCustData", wintypes.LPARAM), - ("lpfnHook", wintypes.LPVOID), - ("lpTemplateName", wintypes.LPCWSTR) - ] - -def color_picker(): - cc = CHOOSECOLOR() - cc.lStructSize = ctypes.sizeof(CHOOSECOLOR) - cc.Flags = 0x00000100 # CC_RGBINIT - custom_colors = (COLORREF * 16)() - cc.lpCustColors = custom_colors - cc.rgbResult = COLORREF() - if CHOOSECOLOR(ctypes.byref(cc)): - return (cc.rgbResult.rgb & 0xFF, (cc.rgbResult.rgb >> 8) & 0xFF, (cc.rgbResult.rgb >> 16) & 0xFF) - return None - -color = color_picker() -print(f"Selected color: {color}") diff --git a/winforms/src/toga_winforms/libs/py_wrappers/winapi/messagebox.py b/winforms/src/toga_winforms/libs/py_wrappers/winapi/messagebox.py deleted file mode 100644 index 05c42c2950..0000000000 --- a/winforms/src/toga_winforms/libs/py_wrappers/winapi/messagebox.py +++ /dev/null @@ -1,95 +0,0 @@ -from __future__ import annotations - -from ctypes import POINTER, WINFUNCTYPE, Structure, c_int, c_wchar_p, windll -from ctypes.wintypes import ATOM, BOOL, DWORD, HBRUSH, HICON, HINSTANCE, HMENU, HWND, LPARAM, LPCWSTR, LPVOID, UINT, WPARAM - -from toga_winforms.libs.py_wrappers.common import LRESULT -from toga_winforms.libs.py_wrappers.hwnd import HCURSOR - - -def windows_message_box( # noqa: PLR0913 - message: str, - title: str, - box_type: int = MB_OK, - icon: int = 0, - default_button: int = MB_DEFBUTTON1, - modality: int = MB_APPLMODAL, - options: int = 0, - detailed_text: str | None = None, -) -> int: - """Create a message box with specified parameters. - - :param message: The message to be displayed. - :param title: The title of the message box. - :param box_type: The type of the message box (e.g., MB_OK, MB_YESNO). - :param icon: The icon to be displayed (e.g., MB_ICONQUESTION). - :param default_button: The default button (e.g., MB_DEFBUTTON1). - :param modality: The modality of the message box (e.g., MB_APPLMODAL). - :param options: Additional options (e.g., MB_RIGHT). - :param detailed_text: Additional detailed text to be displayed. - :return: The response from the message box. - """ - if detailed_text: - message = f"{message}\n\n{detailed_text}" - - style = box_type | icon | default_button | modality | options - result = windll.user32.MessageBoxW(0, message, title, style) - return result - - -def show_ok_message_box(message: str, title: str, detailed_text: str | None = None) -> int: - return windows_message_box(message, title, MB_OK, MB_ICONINFORMATION, detailed_text=detailed_text) - - -def show_yes_no_message_box(message: str, title: str, detailed_text: str | None = None) -> int: - return windows_message_box(message, title, MB_YESNO, MB_ICONQUESTION, detailed_text=detailed_text) - - -def show_retry_cancel_message_box(message: str, title: str, detailed_text: str | None = None) -> int: - return windows_message_box(message, title, MB_RETRYCANCEL, MB_ICONWARNING, detailed_text=detailed_text) - - -def show_error_message_box(message: str, title: str, detailed_text: str | None = None) -> int: - return windows_message_box(message, title, MB_OK, MB_ICONERROR, detailed_text=detailed_text) - - -def show_warning_message_box(message: str, title: str, detailed_text: str | None = None) -> int: - return windows_message_box(message, title, MB_OK, MB_ICONWARNING, detailed_text=detailed_text) - - -if __name__ == "__main__": - response = windows_message_box( - "Do you want to save changes?", - "Save Changes", - box_type=MB_YESNOCANCEL, - icon=MB_ICONQUESTION, - default_button=MB_DEFBUTTON2, - modality=MB_SYSTEMMODAL, - options=MB_RTLREADING, - detailed_text="This will overwrite the existing file.", - ) - print(f"Response: {response}") # 6=YES, 7=NO, 2=CANCEL/X button. - - response = show_ok_message_box("Operation completed successfully.", "Success") - if response == IDOK: - print("User clicked OK") - - response = show_yes_no_message_box("Do you want to continue?", "Continue?", detailed_text="This action cannot be undone.") - if response == IDYES: - print("User chose Yes") - elif response == IDNO: - print("User chose No") - - response = show_retry_cancel_message_box("Operation failed. Retry?", "Error", detailed_text="Make sure the file is accessible and try again.") - if response == IDRETRY: - print("User chose Retry") - elif response == IDCANCEL: - print("User chose Cancel") - - response = show_error_message_box("An unexpected error occurred.", "Error", detailed_text="Please contact support with the error code 0x1234.") - if response == IDOK: - print("User acknowledged the error") - - response = show_warning_message_box("This action may cause data loss.", "Warning", detailed_text="Ensure you have backed up your data.") - if response == IDOK: - print("User acknowledged the warning") diff --git a/winforms/src/toga_winforms/libs/py_wrappers/winapi/sounds.py b/winforms/src/toga_winforms/libs/py_wrappers/winapi/sounds.py deleted file mode 100644 index b04b725185..0000000000 --- a/winforms/src/toga_winforms/libs/py_wrappers/winapi/sounds.py +++ /dev/null @@ -1,12 +0,0 @@ -from __future__ import annotations - -import ctypes - -MB_ICONASTERISK = 0x40 - -def play_system_sound(sound_type): - ctypes.windll.user32.MessageBeep(sound_type) - - -if __name__ == "__main__": - play_system_sound(0x40) # MB_ICONASTERISK \ No newline at end of file diff --git a/winforms/src/toga_winforms/libs/py_wrappers/winapi/tooltip.py b/winforms/src/toga_winforms/libs/py_wrappers/winapi/tooltip.py deleted file mode 100644 index 3a743610d0..0000000000 --- a/winforms/src/toga_winforms/libs/py_wrappers/winapi/tooltip.py +++ /dev/null @@ -1,37 +0,0 @@ -from __future__ import annotations - -from ctypes import POINTER, Structure, byref, sizeof, windll -from ctypes.wintypes import HINSTANCE, HWND, LPARAM, LPWSTR, RECT, UINT -from typing import TYPE_CHECKING, Sequence - -if TYPE_CHECKING: - from ctypes import _CData - - -class TOOLINFO(Structure): - _fields_: Sequence[tuple[str, type[_CData]] | tuple[str, type[_CData], int]] = [ - ("cbSize", UINT), - ("uFlags", UINT), - ("hwnd", HWND), - ("uId", POINTER(UINT)), - ("rect", RECT), - ("hinst", HINSTANCE), - ("lpszText", LPWSTR), - ("lParam", LPARAM) - ] - - -def create_tooltip(hwnd, text): - TTS_ALWAYSTIP = 0x01 - TTM_ADDTOOL = 0x0400 + 50 - hwndTT = windll.user32.CreateWindowExW( - 0, "tooltips_class32", None, TTS_ALWAYSTIP, - 0, 0, 0, 0, hwnd, None, None, None - ) - ti = TOOLINFO(cbSize=sizeof(TOOLINFO), hwnd=hwnd, lpszText=text) - windll.user32.SendMessageW(hwndTT, TTM_ADDTOOL, 0, byref(ti)) - return hwndTT - -# Example usage: -# hwnd = some_window_handle -# create_tooltip(hwnd, "This is a tooltip") From 35cbe8622bc069eb19dc07a205b7f70eacfcc7a7 Mon Sep 17 00:00:00 2001 From: Benjamin Auquite Date: Fri, 30 Aug 2024 21:25:50 -0500 Subject: [PATCH 6/9] organize everything Clearly separate identifiers, interfaces, and constants for PR state --- winforms/src/toga_winforms/dialogs.py | 342 +---- .../src/toga_winforms/libs/com/constants.py | 119 ++ .../src/toga_winforms/libs/com/identifiers.py | 54 + .../src/toga_winforms/libs/com/interfaces.py | 1356 +++++++++++++++++ 4 files changed, 1593 insertions(+), 278 deletions(-) create mode 100644 winforms/src/toga_winforms/libs/com/constants.py create mode 100644 winforms/src/toga_winforms/libs/com/identifiers.py create mode 100644 winforms/src/toga_winforms/libs/com/interfaces.py diff --git a/winforms/src/toga_winforms/dialogs.py b/winforms/src/toga_winforms/dialogs.py index 9b3d8df093..fef6f569d4 100644 --- a/winforms/src/toga_winforms/dialogs.py +++ b/winforms/src/toga_winforms/dialogs.py @@ -1,20 +1,23 @@ import asyncio import os +from ctypes import ( + HRESULT, + POINTER, + byref, + c_void_p, + c_wchar_p, + cast as cast_with_ctypes, + windll, +) +from ctypes.wintypes import HWND from pathlib import Path +from typing import List, Optional, Tuple, Union import comtypes import comtypes.client - -from comtypes import COMMETHOD, GUID -from comtypes.hresult import S_OK - -from ctypes import HRESULT, POINTER, Structure, byref, c_int, c_uint, c_ulong, c_void_p, c_wchar_p, windll -from ctypes import cast as cast_with_ctypes -from ctypes.wintypes import BOOL, DWORD, HWND, LPCWSTR, LPWSTR -from enum import IntFlag -from typing import Callable, List, Optional, Tuple, Union - import System.Windows.Forms as WinForms +from comtypes import GUID +from comtypes.hresult import S_OK from System.Drawing import ( ContentAlignment, Font as WinFont, @@ -24,248 +27,19 @@ ) from System.Windows.Forms import DialogResult, MessageBoxButtons, MessageBoxIcon -from .libs.wrapper import WeakrefCallable - +from toga_winforms.libs.com.constants import COMDLG_FILTERSPEC, FileOpenOptions +from toga_winforms.libs.com.identifiers import ( + CLSID_FileOpenDialog, + CLSID_FileSaveDialog, +) +from toga_winforms.libs.com.interfaces import ( + IFileOpenDialog, + IFileSaveDialog, + IShellItem, + IShellItemArray, +) -class COMDLG_FILTERSPEC(Structure): # noqa: N801 - _fields_ = [ - ("pszName", LPCWSTR), - ("pszSpec", LPCWSTR) - ] - - -class FileOpenOptions(IntFlag): - FOS_OVERWRITEPROMPT = 0x00000002 - FOS_STRICTFILETYPES = 0x00000004 - FOS_NOCHANGEDIR = 0x00000008 - FOS_PICKFOLDERS = 0x00000020 - FOS_FORCEFILESYSTEM = 0x00000040 - FOS_ALLNONSTORAGEITEMS = 0x00000080 - FOS_NOVALIDATE = 0x00000100 - FOS_ALLOWMULTISELECT = 0x00000200 - FOS_PATHMUSTEXIST = 0x00000800 - FOS_FILEMUSTEXIST = 0x00001000 - FOS_CREATEPROMPT = 0x00002000 - FOS_SHAREAWARE = 0x00004000 - FOS_NOREADONLYRETURN = 0x00008000 - FOS_NOTESTFILECREATE = 0x00010000 - FOS_HIDEMRUPLACES = 0x00020000 - FOS_HIDEPINNEDPLACES = 0x00040000 - FOS_NODEREFERENCELINKS = 0x00100000 - FOS_DONTADDTORECENT = 0x02000000 - FOS_FORCESHOWHIDDEN = 0x10000000 - FOS_DEFAULTNOMINIMODE = 0x20000000 - FOS_FORCEPREVIEWPANEON = 0x40000000 - - -IID_IShellItem = GUID("{43826D1E-E718-42EE-BC55-A1E261C37BFE}") -IID_IShellItemArray = GUID("{B63EA76D-1F85-456F-A19C-48159EFA858B}") -IID_IShellItemFilter = GUID("{2659B475-EEB8-48B7-8F07-B378810F48CF}") -IID_IModalWindow = GUID("{B4DB1657-70D7-485E-8E3E-6FCB5A5C1802}") -IID_IFileDialog = GUID("{42F85136-DB7E-439C-85F1-E4075D135FC8}") -IID_IFileOpenDialog = GUID("{D57C7288-D4AD-4768-BE02-9D969532D960}") -IID_IFileSaveDialog = GUID("{84BCCD23-5FDE-4CDB-AEA4-AF64B83D78AB}") -CLSID_FileOpenDialog = GUID("{DC1C5A9C-E88A-4dde-A5A1-60F82A20AEF7}") -CLSID_FileSaveDialog = GUID("{C0B4E2F3-BA21-4773-8DBA-335EC946EB8B}") - - -class IShellItem(comtypes.IUnknown): - _case_insensitive_: bool = True - _iid_: GUID = IID_IShellItem - _methods_ = [ - COMMETHOD([], HRESULT, "BindToHandler", - (["in"], POINTER(comtypes.IUnknown), "pbc"), - (["in"], POINTER(GUID), "bhid"), - (["in"], POINTER(GUID), "riid"), - (["out"], POINTER(c_void_p), "ppv")), - COMMETHOD([], HRESULT, "GetParent", - (["out"], POINTER(POINTER(comtypes.IUnknown)), "ppsi")), - COMMETHOD([], HRESULT, "GetDisplayName", - (["in"], c_ulong, "sigdnName"), - (["out"], POINTER(LPWSTR), "ppszName")), - COMMETHOD([], HRESULT, "GetAttributes", - (["in"], c_ulong, "sfgaoMask"), - (["out"], POINTER(c_ulong), "psfgaoAttribs")), - COMMETHOD([], HRESULT, "Compare", - (["in"], POINTER(comtypes.IUnknown), "psi"), - (["in"], c_ulong, "hint"), - (["out"], POINTER(c_int), "piOrder")) - ] - QueryInterface: Callable[[GUID, comtypes.IUnknown], int] - AddRef: Callable[[], int] - Release: Callable[[], int] - BindToHandler: Callable[[comtypes.IUnknown, GUID, GUID, c_void_p], int] - GetParent: Callable[[], comtypes.IUnknown] - GetDisplayName: Callable[[Union[c_ulong, int]], str] - GetAttributes: Callable[[Union[c_ulong, int]], int] - Compare: Callable[[comtypes.IUnknown, c_ulong, c_int], int] - - -class IShellItemArray(comtypes.IUnknown): - _case_insensitive_: bool = True - _iid_: GUID = IID_IShellItemArray - _methods_ = [ - COMMETHOD([], HRESULT, "BindToHandler", - (["in"], POINTER(comtypes.IUnknown), "pbc"), - (["in"], POINTER(GUID), "bhid"), - (["in"], POINTER(GUID), "riid"), - (["out"], POINTER(c_void_p), "ppv")), - COMMETHOD([], HRESULT, "GetPropertyStore", - (["in"], c_ulong, "flags"), - (["in"], POINTER(GUID), "riid"), - (["out"], POINTER(c_void_p), "ppv")), - COMMETHOD([], HRESULT, "GetPropertyDescriptionList", - (["in"], POINTER(GUID), "keyType"), - (["in"], POINTER(GUID), "riid"), - (["out"], POINTER(c_void_p), "ppv")), - COMMETHOD([], HRESULT, "GetAttributes", - (["in"], c_ulong, "attribFlags"), - (["in"], c_ulong, "sfgaoMask"), - (["out"], POINTER(c_ulong), "psfgaoAttribs")), - COMMETHOD([], HRESULT, "GetCount", - (["out"], POINTER(c_uint), "pdwNumItems")), - COMMETHOD([], HRESULT, "GetItemAt", - (["in"], c_uint, "dwIndex"), - (["out"], POINTER(POINTER(IShellItem)), "ppsi")), - COMMETHOD([], HRESULT, "EnumItems", - (["out"], POINTER(POINTER(comtypes.IUnknown)), "ppenumShellItems")) - ] - QueryInterface: Callable[[GUID, comtypes.IUnknown], int] - AddRef: Callable[[], int] - Release: Callable[[], int] - BindToHandler: Callable[[comtypes.IUnknown, GUID, GUID], int] - GetPropertyStore: Callable[[int, GUID], c_void_p] - GetPropertyDescriptionList: Callable[[GUID, GUID], c_void_p] - GetAttributes: Callable[[int, int], int] - GetCount: Callable[[], int] - GetItemAt: Callable[[Union[int, int]], IShellItem] - EnumItems: Callable[[], comtypes.IUnknown] - - -class IModalWindow(comtypes.IUnknown): - _case_insensitive_: bool = True - _iid_: GUID = IID_IModalWindow - _methods_ = [ - COMMETHOD([], HRESULT, "Show", - (["in"], HWND, "hwndParent")) - ] - Show: Callable[[Union[int, HWND]], int] - - -class IFileDialog(IModalWindow): - _iid_: GUID = IID_IFileDialog - _methods_ = [ - COMMETHOD([], HRESULT, "SetFileTypes", - (["in"], c_uint, "cFileTypes"), - (["in"], POINTER(c_void_p), "rgFilterSpec")), - COMMETHOD([], HRESULT, "SetFileTypeIndex", - (["in"], c_uint, "iFileType")), - COMMETHOD([], HRESULT, "GetFileTypeIndex", - (["out"], POINTER(c_uint), "piFileType")), - COMMETHOD([], HRESULT, "Advise", - (["in"], POINTER(comtypes.IUnknown), "pfde"), - (["out"], POINTER(DWORD), "pdwCookie")), - COMMETHOD([], HRESULT, "Unadvise", - (["in"], DWORD, "dwCookie")), - COMMETHOD([], HRESULT, "SetOptions", - (["in"], c_uint, "fos")), - COMMETHOD([], HRESULT, "GetOptions", - (["out"], POINTER(DWORD), "pfos")), - COMMETHOD([], HRESULT, "SetDefaultFolder", - (["in"], POINTER(IShellItem), "psi")), - COMMETHOD([], HRESULT, "SetFolder", - (["in"], POINTER(IShellItem), "psi")), - COMMETHOD([], HRESULT, "GetFolder", - (["out"], POINTER(POINTER(IShellItem)), "ppsi")), - COMMETHOD([], HRESULT, "GetCurrentSelection", - (["out"], POINTER(POINTER(IShellItem)), "ppsi")), - COMMETHOD([], HRESULT, "SetFileName", - (["in"], LPCWSTR, "pszName")), - COMMETHOD([], HRESULT, "GetFileName", - (["out"], POINTER(LPWSTR), "pszName")), - COMMETHOD([], HRESULT, "SetTitle", - (["in"], LPCWSTR, "pszTitle")), - COMMETHOD([], HRESULT, "SetOkButtonLabel", - (["in"], LPCWSTR, "pszText")), - COMMETHOD([], HRESULT, "SetFileNameLabel", - (["in"], LPCWSTR, "pszLabel")), - COMMETHOD([], HRESULT, "GetResult", - (["out"], POINTER(POINTER(IShellItem)), "ppsi")), - COMMETHOD([], HRESULT, "AddPlace", - (["in"], POINTER(IShellItem), "psi"), - (["in"], c_int, "fdap")), - COMMETHOD([], HRESULT, "SetDefaultExtension", - (["in"], LPCWSTR, "pszDefaultExtension")), - COMMETHOD([], HRESULT, "Close", - (["in"], HRESULT, "hr")), - COMMETHOD([], HRESULT, "SetClientGuid", - (["in"], POINTER(GUID), "guid")), - COMMETHOD([], HRESULT, "ClearClientData"), - COMMETHOD([], HRESULT, "SetFilter", - (["in"], POINTER(comtypes.IUnknown), "pFilter")) # IShellItemFilter - ] - SetFileTypes: Callable[[Union[c_uint, int], c_void_p], int] - SetFileTypeIndex: Callable[[c_uint], int] - GetFileTypeIndex: Callable[[], int] - Advise: Callable[[Union[comtypes.IUnknown, comtypes.COMObject]], int] - Unadvise: Callable[[int], int] - SetOptions: Callable[[Union[int, int]], int] - GetOptions: Callable[[], int] - SetDefaultFolder: Callable[[IShellItem], int] - SetFolder: Callable[[IShellItem], int] - GetFolder: Callable[[], IShellItem] - GetCurrentSelection: Callable[[], IShellItem] - SetFileName: Callable[[str], int] - GetFileName: Callable[[], str] - SetTitle: Callable[[str], int] - SetOkButtonLabel: Callable[[str], int] - SetFileNameLabel: Callable[[str], int] - GetResult: Callable[[], IShellItem] - AddPlace: Callable[[IShellItem, c_int], int] - SetDefaultExtension: Callable[[str], int] - Close: Callable[[HRESULT], int] - SetClientGuid: Callable[[GUID], int] - ClearClientData: Callable[[], int] - SetFilter: Callable[[comtypes.IUnknown], int] - - -class IFileOpenDialog(IFileDialog): - _case_insensitive_: bool = True - _iid_: GUID = IID_IFileOpenDialog - _methods_ = [ - COMMETHOD([], HRESULT, "GetResults", - (["out"], POINTER(POINTER(IShellItemArray)), "ppenum")), - COMMETHOD([], HRESULT, "GetSelectedItems", - (["out"], POINTER(POINTER(IShellItemArray)), "ppsai")) - ] - GetResults: Callable[[], IShellItemArray] - GetSelectedItems: Callable[[], IShellItemArray] - - -class IFileSaveDialog(IFileDialog): - _case_insensitive_: bool = True - _iid_: GUID = IID_IFileSaveDialog - _methods_ = [ - COMMETHOD([], HRESULT, "SetSaveAsItem", - (["in"], POINTER(IShellItem), "psi")), - COMMETHOD([], HRESULT, "SetProperties", - (["in"], POINTER(comtypes.IUnknown), "pStore")), - COMMETHOD([], HRESULT, "SetCollectedProperties", - (["in"], POINTER(comtypes.IUnknown), "pList"), - (["in"], BOOL, "fAppendDefault")), - COMMETHOD([], HRESULT, "GetProperties", - (["out"], POINTER(POINTER(comtypes.IUnknown)), "ppStore")), - COMMETHOD([], HRESULT, "ApplyProperties", - (["in"], POINTER(IShellItem), "psi"), - (["in"], POINTER(comtypes.IUnknown), "pStore"), - (["in"], HWND, "hwnd"), - (["in"], POINTER(comtypes.IUnknown), "pSink")) - ] - SetSaveAsItem: Callable[[IShellItem], int] - SetProperties: Callable[[comtypes.IUnknown], int] - SetCollectedProperties: Callable[[comtypes.IUnknown, BOOL], int] - GetProperties: Callable[[comtypes.IUnknown], int] - ApplyProperties: Callable[[IShellItem, comtypes.IUnknown, HWND, comtypes.IUnknown], int] +from .libs.wrapper import WeakrefCallable class BaseDialog: @@ -463,22 +237,22 @@ def __init__( if file_types is not None: filters: List[Tuple[str, str]] = [ - (f"{ext.upper()} files", f"*.{ext}") - for ext in file_types + (f"{ext.upper()} files", f"*.{ext}") for ext in file_types ] filterspec = (COMDLG_FILTERSPEC * len(file_types))( - *[ - (c_wchar_p(name), c_wchar_p(spec)) - for name, spec in filters - ] + *[(c_wchar_p(name), c_wchar_p(spec)) for name, spec in filters] + ) + self.native.SetFileTypes( + len(filterspec), cast_with_ctypes(filterspec, POINTER(c_void_p)) ) - self.native.SetFileTypes(len(filterspec), cast_with_ctypes(filterspec, POINTER(c_void_p))) def _show(self): hwnd = HWND(0) hr: int = self.native.Show(hwnd) if hr == S_OK: - assert isinstance(self, (SaveFileDialog, OpenFileDialog, SelectFolderDialog)) + assert isinstance( + self, (SaveFileDialog, OpenFileDialog, SelectFolderDialog) + ) self.future.set_result(self._get_filenames()) else: self.future.set_result(None) @@ -493,14 +267,20 @@ def _set_initial_directory(self, initial_directory): if folder_path.is_dir(): # sourcery skip: extract-method SHCreateItemFromParsingName = windll.shell32.SHCreateItemFromParsingName SHCreateItemFromParsingName.argtypes = [ - c_wchar_p, # LPCWSTR (wide string, null-terminated) - POINTER(comtypes.IUnknown), # IBindCtx* (can be NULL, hence POINTER(IUnknown)) - POINTER(GUID), # REFIID (pointer to the interface ID, typically GUID) - POINTER(POINTER(IShellItem)) # void** (output pointer to the requested interface) + c_wchar_p, # LPCWSTR (wide string, null-terminated) + POINTER( + comtypes.IUnknown + ), # IBindCtx* (can be NULL, hence POINTER(IUnknown)) + POINTER(GUID), # REFIID (pointer to the interface ID, typically GUID) + POINTER( + POINTER(IShellItem) + ), # void** (output pointer to the requested interface) ] SHCreateItemFromParsingName.restype = HRESULT shell_item = POINTER(IShellItem)() - hr = SHCreateItemFromParsingName(str(folder_path), None, IShellItem._iid_, byref(shell_item)) + hr = SHCreateItemFromParsingName( + str(folder_path), None, IShellItem._iid_, byref(shell_item) + ) if hr == S_OK: self.native.SetFolder(shell_item) @@ -514,7 +294,9 @@ def __init__( file_types: List[str], ): super().__init__( - comtypes.client.CreateObject(CLSID_FileSaveDialog, interface=IFileSaveDialog), + comtypes.client.CreateObject( + CLSID_FileSaveDialog, interface=IFileSaveDialog + ), title, initial_directory, filename=filename, @@ -536,7 +318,9 @@ def __init__( multiple_select: bool, ): super().__init__( - comtypes.client.CreateObject(CLSID_FileOpenDialog, interface=IFileOpenDialog), + comtypes.client.CreateObject( + CLSID_FileOpenDialog, interface=IFileOpenDialog + ), title, initial_directory, file_types=file_types, @@ -555,7 +339,9 @@ def _get_filenames(self) -> List[Path]: item_count: int = shell_item_array.GetCount() for i in range(item_count): shell_item: IShellItem = shell_item_array.GetItemAt(i) - szFilePath: str = str(shell_item.GetDisplayName(0x80058000)) # SIGDN_FILESYSPATH + szFilePath: str = str( + shell_item.GetDisplayName(0x80058000) + ) # SIGDN_FILESYSPATH results.append(Path(szFilePath)) return results @@ -568,12 +354,15 @@ def __init__( multiple_select: bool, ): super().__init__( - comtypes.client.CreateObject(CLSID_FileOpenDialog, interface=IFileOpenDialog), + comtypes.client.CreateObject( + CLSID_FileOpenDialog, + interface=IFileOpenDialog, + ), title, initial_directory, ) self.native.SetOptions(FileOpenOptions.FOS_PICKFOLDERS) - self.multiple_select = multiple_select + self.multiple_select: bool = multiple_select def _get_filenames(self) -> Union[List[Path], Path]: shell_item: IShellItem = self.native.GetResult() @@ -588,15 +377,12 @@ def _set_initial_directory(self, initial_directory): return folder_path: Path = Path(initial_directory).resolve() if folder_path.is_dir(): # sourcery skip: extract-method - SHCreateItemFromParsingName = windll.shell32.SHCreateItemFromParsingName - SHCreateItemFromParsingName.argtypes = [ - c_wchar_p, # LPCWSTR (wide string, null-terminated) - POINTER(comtypes.IUnknown), # IBindCtx* (can be NULL, hence POINTER(IUnknown)) - POINTER(GUID), # REFIID (pointer to the interface ID, typically GUID) - POINTER(POINTER(IShellItem)) # void** (output pointer to the requested interface) - ] - SHCreateItemFromParsingName.restype = HRESULT shell_item = POINTER(IShellItem)() - hr = SHCreateItemFromParsingName(str(folder_path), None, IShellItem._iid_, byref(shell_item)) + hr = windll.shell32.SHCreateItemFromParsingName( + str(folder_path), + None, + IShellItem._iid_, + byref(shell_item), + ) if hr == S_OK: self.native.SetFolder(shell_item) diff --git a/winforms/src/toga_winforms/libs/com/constants.py b/winforms/src/toga_winforms/libs/com/constants.py new file mode 100644 index 0000000000..2623140c32 --- /dev/null +++ b/winforms/src/toga_winforms/libs/com/constants.py @@ -0,0 +1,119 @@ +from __future__ import annotations + +from ctypes import Structure, c_int, c_ulong +from ctypes.wintypes import LPCWSTR +from enum import IntFlag +from typing import TYPE_CHECKING, Sequence + +if TYPE_CHECKING: + from ctypes import _CData + + +class FileOpenOptions(IntFlag): + FOS_UNKNOWN1 = 0x00000001 + FOS_OVERWRITEPROMPT = 0x00000002 + FOS_STRICTFILETYPES = 0x00000004 + FOS_NOCHANGEDIR = 0x00000008 + FOS_UNKNOWN2 = 0x00000010 + FOS_PICKFOLDERS = 0x00000020 + FOS_FORCEFILESYSTEM = 0x00000040 + FOS_ALLNONSTORAGEITEMS = 0x00000080 + FOS_NOVALIDATE = 0x00000100 + FOS_ALLOWMULTISELECT = 0x00000200 + FOS_UNKNOWN4 = 0x00000400 + FOS_PATHMUSTEXIST = 0x00000800 + FOS_FILEMUSTEXIST = 0x00001000 + FOS_CREATEPROMPT = 0x00002000 + FOS_SHAREAWARE = 0x00004000 + FOS_NOREADONLYRETURN = 0x00008000 + FOS_NOTESTFILECREATE = 0x00010000 + FOS_HIDEMRUPLACES = 0x00020000 + FOS_HIDEPINNEDPLACES = 0x00040000 + FOS_UNKNOWN5 = 0x00080000 + FOS_NODEREFERENCELINKS = 0x00100000 + FOS_UNKNOWN6 = 0x00200000 + FOS_UNKNOWN7 = 0x00400000 + FOS_UNKNOWN8 = 0x00800000 + FOS_UNKNOWN9 = 0x01000000 + FOS_DONTADDTORECENT = 0x02000000 + FOS_UNKNOWN10 = 0x04000000 + FOS_UNKNOWN11 = 0x08000000 + FOS_FORCESHOWHIDDEN = 0x10000000 + FOS_DEFAULTNOMINIMODE = 0x20000000 + FOS_FORCEPREVIEWPANEON = 0x40000000 + FOS_UNKNOWN12 = 0x80000000 + + +# Shell Folder Get Attributes Options +SFGAOF = c_ulong + + +class SFGAO(IntFlag): + SFGAO_CANCOPY = 0x00000001 # Objects can be copied. + SFGAO_CANMOVE = 0x00000002 # Objects can be moved. + SFGAO_CANLINK = 0x00000004 # Objects can be linked. + SFGAO_STORAGE = 0x00000008 # Objects can be stored. + SFGAO_CANRENAME = 0x00000010 # Objects can be renamed. + SFGAO_CANDELETE = 0x00000020 # Objects can be deleted. + SFGAO_HASPROPSHEET = 0x00000040 # Objects have property sheets. + SFGAO_DROPTARGET = 0x00000100 # Objects are drop targets. + SFGAO_CAPABILITYMASK = 0x00000177 # Mask for all capability flags. + SFGAO_ENCRYPTED = 0x00002000 # Object is encrypted (use alt color). + SFGAO_ISSLOW = 0x00004000 # Accessing this object is slow. + SFGAO_GHOSTED = 0x00008000 # Object is ghosted (dimmed). + SFGAO_LINK = 0x00010000 # Shortcut (link). + SFGAO_SHARE = 0x00020000 # Shared. + SFGAO_READONLY = 0x00040000 # Read-only. + SFGAO_HIDDEN = 0x00080000 # Hidden object. + SFGAO_DISPLAYATTRMASK = 0x000FC000 # Mask for display attributes. + SFGAO_FILESYSANCESTOR = 0x10000000 # May contain children with file system folders. + SFGAO_FOLDER = 0x20000000 # Is a folder. + SFGAO_FILESYSTEM = 0x40000000 # Is part of the file system. + SFGAO_HASSUBFOLDER = 0x80000000 # May contain subfolders. + SFGAO_CONTENTSMASK = 0x80000000 # Mask for contents. + SFGAO_VALIDATE = 0x01000000 # Invalidate cached information. + SFGAO_REMOVABLE = 0x02000000 # Is a removable media. + SFGAO_COMPRESSED = 0x04000000 # Object is compressed. + SFGAO_BROWSABLE = 0x08000000 # Supports browsing. + SFGAO_NONENUMERATED = 0x00100000 # Is not enumerated. + SFGAO_NEWCONTENT = 0x00200000 # New content is present. + SFGAO_CANMONIKER = 0x00400000 # Can create monikers for this item. + SFGAO_HASSTORAGE = 0x00400000 # Supports storage interfaces. + SFGAO_STREAM = 0x00400000 # Is a stream object. + SFGAO_STORAGEANCESTOR = 0x00800000 # May contain children with storage folders. + SFGAO_STORAGECAPMASK = 0x70C50008 # Mask for storage capability attributes. + SFGAO_PKEYSFGAOMASK = ( + 0x81044000 # Attributes that are part of the PKEY_SFGAOFlags property. + ) + + +class SIGDN(c_int): + SIGDN_NORMALDISPLAY = 0x00000000 + SIGDN_PARENTRELATIVEPARSING = 0x80018001 + SIGDN_PARENTRELATIVEFORADDRESSBAR = 0x8001C001 + SIGDN_DESKTOPABSOLUTEPARSING = 0x80028000 + SIGDN_PARENTRELATIVEEDITING = 0x80031001 + SIGDN_DESKTOPABSOLUTEEDITING = 0x8004C000 + SIGDN_FILESYSPATH = 0x80058000 + SIGDN_URL = 0x80068000 + + +class FDAP(c_int): + FDAP_BOTTOM = 0x00000000 + FDAP_TOP = 0x00000001 + + +class FDE_SHAREVIOLATION_RESPONSE(c_int): # noqa: N801 + FDESVR_DEFAULT = 0x00000000 + FDESVR_ACCEPT = 0x00000001 + FDESVR_REFUSE = 0x00000002 + + +FDE_OVERWRITE_RESPONSE = FDE_SHAREVIOLATION_RESPONSE + + +class COMDLG_FILTERSPEC(Structure): # noqa: N801 + _fields_: Sequence[tuple[str, type[_CData]] | tuple[str, type[_CData], int]] = [ + ("pszName", LPCWSTR), + ("pszSpec", LPCWSTR), + ] diff --git a/winforms/src/toga_winforms/libs/com/identifiers.py b/winforms/src/toga_winforms/libs/com/identifiers.py new file mode 100644 index 0000000000..2ed8c4f90d --- /dev/null +++ b/winforms/src/toga_winforms/libs/com/identifiers.py @@ -0,0 +1,54 @@ +from __future__ import annotations + +from comtypes import GUID + +IID_IUnknown = GUID("{00000000-0000-0000-C000-000000000046}") +IID_IDispatch = GUID("{00020400-0000-0000-C000-000000000046}") +IID_IClassFactory = GUID("{00000001-0000-0000-C000-000000000046}") +IID_IStream = GUID("{0000000c-0000-0000-C000-000000000046}") +IID_IStorage = GUID("{0000000b-0000-0000-C000-000000000046}") +IID_IBindCtx = GUID("{0000000e-0000-0000-C000-000000000046}") +IID_IEnumShellItems = GUID("{70629033-E363-4A28-A567-0DB78006E6D7}") +IID_IContextMenu = GUID("{000214e4-0000-0000-c000-000000000046}") +IID_IContextMenu2 = GUID("{000214f4-0000-0000-c000-000000000046}") +IID_IContextMenu3 = GUID("{bcfce0a0-ec17-11d0-8d10-00a0c90f2719}") +IID_IShellFolder = GUID("{000214E6-0000-0000-C000-000000000046}") +IID_IShellFolder2 = GUID("{93F2F68C-1D1B-11D3-A30E-00C04F79ABD1}") +IID_IShellItem = GUID("{43826D1E-E718-42EE-BC55-A1E261C37BFE}") +IID_IShellItem2 = GUID("{7E9FB0D3-919F-4307-AB2E-9B1860310C93}") +IID_IShellLibrary = GUID("{11A66EFA-382E-451A-9234-1E0E12EF3085}") +IID_IShellItemArray = GUID("{B63EA76D-1F85-456F-A19C-48159EFA858B}") +IID_IShellItemFilter = GUID("{2659B475-EEB8-48B7-8F07-B378810F48CF}") +IID_IShellView = GUID("{000214e3-0000-0000-c000-000000000046}") +IID_IModalWindow = GUID("{B4DB1657-70D7-485E-8E3E-6FCB5A5C1802}") +IID_IFileDialog = GUID("{42F85136-DB7E-439C-85F1-E4075D135FC8}") +IID_IFileDialog2 = GUID("{61744FC7-85B5-4791-A9B0-272276309B13}") +IID_IFileSaveDialog = GUID("{84BCCD23-5FDE-4CDB-AEA4-AF64B83D78AB}") +IID_IFileSaveDialogOld = GUID("{2804B74C-AC16-4398-9DC0-DB83F5B7ED14}") +IID_IFileSaveDialogPrivate = GUID("{6CB95A6A-88B6-4DC4-B3EA-3A776D1E8EFF}") +IID_IFileOpenDialog = GUID("{D57C7288-D4AD-4768-BE02-9D969532D960}") +IID_IFileDialogEvents = GUID("{973510DB-7D7F-452B-8975-74A85828D354}") +IID_FileDialogPermissionAttribute = GUID("{0CCCA629-440F-313E-96CD-BA1B4B4997F7}") +IID_FileDialogPermission = GUID("{A8B7138C-8932-3D78-A585-A91569C743AC}") +IID_IFileDialogPrivate = GUID("{9EA5491C-89C8-4BEF-93D3-7F665FB82A33}") +IID_IFileDialogCustomize = GUID("{E6FDD21A-163F-4975-9C8C-A69F1BA37034}") +IID_IFileDialogEventsPrivate = GUID("{050E9E69-BAEA-4C08-AD6A-61666DD32E96}") +IID_IFileDialogControlEvents = GUID("{36116642-D713-4B97-9B83-7484A9D00433}") +IID_IFileDialogResultHandler = GUID("{42841501-194F-478F-9B4C-78985419DA53}") +IID_IShellLink = GUID("{000214f9-0000-0000-c000-000000000046}") +IID_IShellLinkDataList = GUID("{45E2B4AE-B1C3-11D0-BA91-00C04FD7A083}") +IID_IPropertyStore = GUID("{886D8EEB-8CF2-4446-8D02-CDBA1DBDCF99}") +IID_IFileOperationProgressSink = GUID("{04B0F1A7-9490-44BC-96E1-4296A31252E2}") +IID_IFileOperation = GUID("{94EA2B94-E9CC-49E0-C0E3-D20A7D91AA98}") +CLSID_FileOperation = GUID("{3AD05575-8857-4850-9277-11B85BDB8E09}") +CLSID_FileDialog = GUID("{3D9C8F03-50D4-4E40-BB11-70E74D3F10F3}") +CLSID_FileOpenDialog = GUID("{DC1C5A9C-E88A-4dde-A5A1-60F82A20AEF7}") +CLSID_FileOpenDialogLegacy = GUID("{725F645B-EAED-4fc5-B1C5-D9AD0ACCBA5E}") +CLSID_FileSaveDialog = GUID("{C0B4E2F3-BA21-4773-8DBA-335EC946EB8B}") +CLSID_FileSaveDialogLegacy = GUID("{AF02484C-A0A9-4669-9051-058AB12B9195}") +CLSID_ShellLibraryAPI = GUID("{d9b3211d-e57f-4426-aaef-30a806add397}") +CLSID_ShellFileSystemFolder = GUID("{F3364BA0-65B9-11CE-A9BA-00AA004AE837}") +CLSID_ShellBindStatusCallbackProxy = GUID("{2B4F54B1-3D6D-11d0-8258-00C04FD5AE38}") +CLSID_ShellURL = GUID("{4bec2015-bfa1-42fa-9c0c-59431bbe880e}") +CLSID_ShellDropTarget = GUID("{4bf684f8-3d29-4403-810d-494e72c4291b}") +CLSID_ShellNameSpace = GUID("{55136805-B2DE-11D1-B9F2-00A0C98BC547}") diff --git a/winforms/src/toga_winforms/libs/com/interfaces.py b/winforms/src/toga_winforms/libs/com/interfaces.py new file mode 100644 index 0000000000..ee46172079 --- /dev/null +++ b/winforms/src/toga_winforms/libs/com/interfaces.py @@ -0,0 +1,1356 @@ +from __future__ import annotations + +from ctypes import ( + HRESULT, + POINTER, + POINTER as C_POINTER, + c_bool, + c_int, + c_uint, + c_ulong, + c_void_p, + c_wchar_p, +) +from ctypes.wintypes import BOOL, DWORD, HWND, LPCWSTR, LPWSTR, ULONG +from typing import TYPE_CHECKING, Callable, ClassVar, Sequence + +import comtypes +from comtypes import COMMETHOD, GUID, COMObject, IUnknown +from comtypes.hresult import S_OK + +from toga_winforms.libs.com.identifiers import ( + IID_IContextMenu, + IID_IEnumShellItems, + IID_IFileDialog, + IID_IFileDialogControlEvents, + IID_IFileDialogCustomize, + IID_IFileDialogEvents, + IID_IFileOpenDialog, + IID_IFileOperation, + IID_IFileOperationProgressSink, + IID_IFileSaveDialog, + IID_IModalWindow, + IID_IPropertyStore, + IID_IShellFolder, + IID_IShellItem, + IID_IShellItemArray, + IID_IShellItemFilter, + IID_IShellLibrary, +) + +if TYPE_CHECKING: + from ctypes import _Pointer + + from comtypes._memberspec import _ComMemberSpec + + +class IModalWindow(IUnknown): + _case_insensitive_: bool = True + _iid_: GUID = IID_IModalWindow + _methods_: ClassVar[list[_ComMemberSpec]] = [ + COMMETHOD([], HRESULT, "Show", (["in"], HWND, "hwndParent")) + ] + Show: Callable[[int | HWND], HRESULT] + + +class IShellItem(IUnknown): + _case_insensitive_: bool = True + _iid_: GUID = IID_IShellItem + _methods_: ClassVar[list[_ComMemberSpec]] = [ + COMMETHOD( + [], + HRESULT, + "BindToHandler", + (["in"], POINTER(IUnknown), "pbc"), + (["in"], POINTER(GUID), "bhid"), + (["in"], POINTER(GUID), "riid"), + (["out"], POINTER(c_void_p), "ppv"), + ), + COMMETHOD( + [], HRESULT, "GetParent", (["out"], POINTER(POINTER(IUnknown)), "ppsi") + ), + COMMETHOD( + [], + HRESULT, + "GetDisplayName", + (["in"], c_ulong, "sigdnName"), + (["out"], POINTER(LPWSTR), "ppszName"), + ), + COMMETHOD( + [], + HRESULT, + "GetAttributes", + (["in"], c_ulong, "sfgaoMask"), + (["out"], POINTER(c_ulong), "psfgaoAttribs"), + ), + COMMETHOD( + [], + HRESULT, + "Compare", + (["in"], POINTER(IUnknown), "psi"), + (["in"], c_ulong, "hint"), + (["out"], POINTER(c_int), "piOrder"), + ), + ] + QueryInterface: Callable[[GUID, _Pointer[_Pointer[IUnknown]]], HRESULT] + AddRef: Callable[[], ULONG] + Release: Callable[[], ULONG] + BindToHandler: Callable[ + [_Pointer[IUnknown], GUID, GUID, _Pointer[c_void_p]], HRESULT + ] + GetParent: Callable[[], IUnknown] + GetDisplayName: Callable[[c_ulong | int], str] + GetAttributes: Callable[[c_ulong | int], int] + Compare: Callable[[_Pointer[IUnknown], c_ulong, c_int], HRESULT] + + +class IContextMenu(IUnknown): + _case_insensitive_: bool = True + _iid_: GUID = IID_IContextMenu + _methods_: ClassVar[list[_ComMemberSpec]] = [ + COMMETHOD( + [], + HRESULT, + "QueryContextMenu", + (["in"], c_void_p, "hmenu"), + (["in"], c_uint, "indexMenu"), + (["in"], c_uint, "idCmdFirst"), + (["in"], c_uint, "idCmdLast"), + (["in"], c_uint, "uFlags"), + ), + COMMETHOD([], HRESULT, "InvokeCommand", (["in"], c_void_p, "pici")), + COMMETHOD( + [], + HRESULT, + "GetCommandString", + (["in"], c_uint, "idCmd"), + (["in"], c_uint, "uType"), + (["in"], c_void_p, "pReserved"), + (["out"], c_wchar_p, "pszName"), + (["in"], c_uint, "cchMax"), + ), + ] + QueryInterface: Callable[[GUID, _Pointer[_Pointer[IUnknown]]], HRESULT] + AddRef: Callable[[], ULONG] + Release: Callable[[], ULONG] + QueryContextMenu: Callable[[c_void_p, c_uint, c_uint, c_uint, c_uint], HRESULT] + InvokeCommand: Callable[[c_void_p], HRESULT] + GetCommandString: Callable[ + [c_uint, c_uint, c_void_p, _Pointer[c_wchar_p], c_uint], HRESULT + ] + + +class IShellFolder(IUnknown): + _case_insensitive_: bool = True + _iid_: GUID = IID_IShellFolder + _methods_: ClassVar[list[_ComMemberSpec]] = [ + COMMETHOD( + [], + HRESULT, + "ParseDisplayName", + (["in"], HWND, "hwnd"), + (["in"], POINTER(IUnknown), "pbc"), + (["in"], LPCWSTR, "pszDisplayName"), + (["out"], POINTER(ULONG), "pchEaten"), + (["out"], POINTER(c_void_p), "ppidl"), + (["in"], POINTER(ULONG), "pdwAttributes"), + ), + COMMETHOD( + [], + HRESULT, + "EnumObjects", + (["in"], HWND, "hwnd"), + (["in"], c_ulong, "grfFlags"), + (["out"], POINTER(POINTER(IUnknown)), "ppenumIDList"), + ), + COMMETHOD( + [], + HRESULT, + "BindToObject", + (["in"], c_void_p, "pidl"), + (["in"], POINTER(IUnknown), "pbc"), + (["in"], POINTER(GUID), "riid"), + (["out"], POINTER(c_void_p), "ppv"), + ), + COMMETHOD( + [], + HRESULT, + "BindToStorage", + (["in"], c_void_p, "pidl"), + (["in"], POINTER(IUnknown), "pbc"), + (["in"], POINTER(GUID), "riid"), + (["out"], POINTER(c_void_p), "ppv"), + ), + COMMETHOD( + [], + HRESULT, + "CompareIDs", + (["in"], c_void_p, "lParam"), + (["in"], c_void_p, "pidl1"), + (["in"], c_void_p, "pidl2"), + ), + COMMETHOD( + [], + HRESULT, + "CreateViewObject", + (["in"], HWND, "hwndOwner"), + (["in"], POINTER(GUID), "riid"), + (["out"], POINTER(c_void_p), "ppv"), + ), + COMMETHOD( + [], + HRESULT, + "GetAttributesOf", + (["in"], c_uint, "cidl"), + (["in"], C_POINTER(c_void_p), "apidl"), + (["out"], POINTER(c_ulong), "rgfInOut"), + ), + COMMETHOD( + [], + HRESULT, + "GetUIObjectOf", + (["in"], HWND, "hwndOwner"), + (["in"], c_uint, "cidl"), + (["in"], C_POINTER(c_void_p), "apidl"), + (["in"], POINTER(GUID), "riid"), + (["in"], POINTER(c_uint), "rgfReserved"), + (["out"], POINTER(c_void_p), "ppv"), + ), + COMMETHOD( + [], + HRESULT, + "GetDisplayNameOf", + (["in"], c_void_p, "pidl"), + (["in"], c_ulong, "uFlags"), + (["out"], POINTER(c_wchar_p), "pName"), + ), + COMMETHOD( + [], + HRESULT, + "SetNameOf", + (["in"], HWND, "hwnd"), + (["in"], c_void_p, "pidl"), + (["in"], LPCWSTR, "pszName"), + (["in"], c_ulong, "uFlags"), + (["out"], POINTER(c_void_p), "ppidlOut"), + ), + ] + QueryInterface: Callable[[GUID, _Pointer[_Pointer[IUnknown]]], HRESULT] + AddRef: Callable[[], ULONG] + Release: Callable[[], ULONG] + ParseDisplayName: Callable[ + [ + HWND, + _Pointer[IUnknown], + LPCWSTR, + _Pointer[ULONG], + _Pointer[c_void_p], + _Pointer[ULONG], + ], + HRESULT, + ] + EnumObjects: Callable[[HWND, c_ulong, _Pointer[_Pointer[IUnknown]]], HRESULT] + BindToObject: Callable[ + [c_void_p, _Pointer[IUnknown], GUID, _Pointer[c_void_p]], HRESULT + ] + BindToStorage: Callable[ + [c_void_p, _Pointer[IUnknown], GUID, _Pointer[c_void_p]], HRESULT + ] + CompareIDs: Callable[[c_void_p, c_void_p, c_void_p], HRESULT] + CreateViewObject: Callable[[HWND, GUID, _Pointer[c_void_p]], HRESULT] + GetAttributesOf: Callable[[c_uint, _Pointer[c_void_p], _Pointer[c_ulong]], HRESULT] + GetUIObjectOf: Callable[ + [HWND, c_uint, _Pointer[c_void_p], GUID, _Pointer[c_uint], _Pointer[c_void_p]], + HRESULT, + ] + GetDisplayNameOf: Callable[[c_void_p, c_ulong, _Pointer[c_wchar_p]], HRESULT] + SetNameOf: Callable[[HWND, c_void_p, LPCWSTR, c_ulong, _Pointer[c_void_p]], HRESULT] + + +class IShellItemArray(IUnknown): + _case_insensitive_: bool = True + _iid_: GUID = IID_IShellItemArray + _methods_: ClassVar[list[_ComMemberSpec]] = [ + COMMETHOD( + [], + HRESULT, + "BindToHandler", + (["in"], POINTER(IUnknown), "pbc"), + (["in"], POINTER(GUID), "bhid"), + (["in"], POINTER(GUID), "riid"), + (["out"], POINTER(c_void_p), "ppv"), + ), + COMMETHOD( + [], + HRESULT, + "GetPropertyStore", + (["in"], c_ulong, "flags"), + (["in"], POINTER(GUID), "riid"), + (["out"], POINTER(c_void_p), "ppv"), + ), + COMMETHOD( + [], + HRESULT, + "GetPropertyDescriptionList", + (["in"], POINTER(GUID), "keyType"), + (["in"], POINTER(GUID), "riid"), + (["out"], POINTER(c_void_p), "ppv"), + ), + COMMETHOD( + [], + HRESULT, + "GetAttributes", + (["in"], c_ulong, "attribFlags"), + (["in"], c_ulong, "sfgaoMask"), + (["out"], POINTER(c_ulong), "psfgaoAttribs"), + ), + COMMETHOD([], HRESULT, "GetCount", (["out"], POINTER(c_uint), "pdwNumItems")), + COMMETHOD( + [], + HRESULT, + "GetItemAt", + (["in"], c_uint, "dwIndex"), + (["out"], POINTER(POINTER(IShellItem)), "ppsi"), + ), + COMMETHOD( + [], + HRESULT, + "EnumItems", + (["out"], POINTER(POINTER(IUnknown)), "ppenumShellItems"), + ), + ] + QueryInterface: Callable[[GUID, IUnknown], HRESULT] + AddRef: Callable[[], ULONG] + Release: Callable[[], ULONG] + BindToHandler: Callable[[_Pointer[IUnknown], GUID, GUID], int] + GetPropertyStore: Callable[[c_ulong, GUID], c_void_p] + GetPropertyDescriptionList: Callable[[GUID, GUID], c_void_p] + GetAttributes: Callable[[c_ulong, c_ulong], _Pointer[c_ulong]] + GetCount: Callable[[], int] + GetItemAt: Callable[[c_uint | int], IShellItem] + EnumItems: Callable[[], IUnknown] + + +class IShellItemFilter(IUnknown): + _case_insensitive_: bool = True + _iid_: GUID = IID_IShellItemFilter + _methods_: ClassVar[list[_ComMemberSpec]] = [ + COMMETHOD([], HRESULT, "IncludeItem", (["in"], POINTER(IShellItem), "psi")), + COMMETHOD( + [], + HRESULT, + "GetEnumFlagsForItem", + (["in"], POINTER(IShellItem), "psi"), + (["out"], POINTER(c_ulong), "pgrfFlags"), + ), + ] + QueryInterface: Callable[[GUID, IUnknown], HRESULT] + AddRef: Callable[[], ULONG] + Release: Callable[[], ULONG] + IncludeItem: Callable[[IShellItem], c_ulong] + GetEnumFlagsForItem: Callable[[], HRESULT] + + +class IEnumShellItems(IUnknown): + _case_insensitive_: bool = True + _iid_: GUID = IID_IEnumShellItems + _methods_: ClassVar[list[_ComMemberSpec]] + QueryInterface: Callable[[GUID, IUnknown], HRESULT] + AddRef: Callable[[], ULONG] + Release: Callable[[], ULONG] + Next: Callable[ + [_Pointer[IEnumShellItems], c_ulong, IShellItem, _Pointer[c_ulong]], HRESULT + ] + Skip: Callable[[_Pointer[IEnumShellItems], c_ulong], HRESULT] + Reset: Callable[[_Pointer[IEnumShellItems]], HRESULT] + Clone: Callable[ + [_Pointer[IEnumShellItems], _Pointer[_Pointer[IEnumShellItems]]], HRESULT + ] + + +IEnumShellItems._methods_ = [ # noqa: SLF001 + COMMETHOD( + [], + HRESULT, + "Next", + (["in"], c_ulong, "celt"), + (["out"], POINTER(POINTER(IShellItem)), "rgelt"), + (["out"], POINTER(c_ulong), "pceltFetched"), + ), + COMMETHOD([], HRESULT, "Skip", (["in"], c_ulong, "celt")), + COMMETHOD([], HRESULT, "Reset"), + COMMETHOD( + [], HRESULT, "Clone", (["out"], POINTER(POINTER(IEnumShellItems)), "ppenum") + ), +] + + +class IPropertyStore(IUnknown): + _case_insensitive_: bool = True + _iid_: GUID = IID_IPropertyStore + _methods_: ClassVar[list[_ComMemberSpec]] = [ + COMMETHOD([], HRESULT, "GetCount", (["out"], POINTER(c_ulong), "count")), + COMMETHOD( + [], + HRESULT, + "GetAt", + (["in"], c_ulong, "index"), + (["out"], POINTER(GUID), "key"), + ), + COMMETHOD( + [], + HRESULT, + "GetValue", + (["in"], POINTER(GUID), "key"), + (["out"], POINTER(c_void_p), "pv"), + ), + COMMETHOD( + [], + HRESULT, + "SetValue", + (["in"], POINTER(GUID), "key"), + (["in"], POINTER(c_void_p), "propvar"), + ), + COMMETHOD([], HRESULT, "Commit"), + ] + QueryInterface: Callable[[GUID, IUnknown], HRESULT] + AddRef: Callable[[], ULONG] + Release: Callable[[], ULONG] + GetCount: Callable[[_Pointer[IPropertyStore], _Pointer[c_ulong]], HRESULT] + GetAt: Callable[[_Pointer[IPropertyStore], c_ulong, GUID], HRESULT] + GetValue: Callable[[_Pointer[IPropertyStore], GUID, _Pointer[c_void_p]], HRESULT] + SetValue: Callable[[_Pointer[IPropertyStore], GUID, _Pointer[c_void_p]], HRESULT] + Commit: Callable[[_Pointer[IPropertyStore]], HRESULT] + + +class IFileOperationProgressSink(IUnknown): + _case_insensitive_: bool = True + _iid_: GUID = IID_IFileOperationProgressSink + _methods_: ClassVar[list[_ComMemberSpec]] = [ + COMMETHOD([], HRESULT, "StartOperations"), + COMMETHOD([], HRESULT, "FinishOperations", (["in"], HRESULT, "hr")), + COMMETHOD( + [], + HRESULT, + "PreRenameItem", + (["in"], c_ulong, "dwFlags"), + (["in"], POINTER(IShellItem), "psiItem"), + (["in"], c_wchar_p, "pszNewName"), + ), + COMMETHOD( + [], + HRESULT, + "PostRenameItem", + (["in"], c_ulong, "dwFlags"), + (["in"], POINTER(IShellItem), "psiItem"), + (["in"], c_wchar_p, "pszNewName"), + (["in"], HRESULT, "hrRename"), + (["in"], POINTER(IShellItem), "psiNewlyCreated"), + ), + COMMETHOD( + [], + HRESULT, + "PreMoveItem", + (["in"], c_ulong, "dwFlags"), + (["in"], POINTER(IShellItem), "psiItem"), + (["in"], POINTER(IShellItem), "psiDestinationFolder"), + (["in"], c_wchar_p, "pszNewName"), + ), + COMMETHOD( + [], + HRESULT, + "PostMoveItem", + (["in"], c_ulong, "dwFlags"), + (["in"], POINTER(IShellItem), "psiItem"), + (["in"], POINTER(IShellItem), "psiDestinationFolder"), + (["in"], c_wchar_p, "pszNewName"), + (["in"], HRESULT, "hrMove"), + (["in"], POINTER(IShellItem), "psiNewlyCreated"), + ), + COMMETHOD( + [], + HRESULT, + "PreCopyItem", + (["in"], c_ulong, "dwFlags"), + (["in"], POINTER(IShellItem), "psiItem"), + (["in"], POINTER(IShellItem), "psiDestinationFolder"), + (["in"], c_wchar_p, "pszNewName"), + ), + COMMETHOD( + [], + HRESULT, + "PostCopyItem", + (["in"], c_ulong, "dwFlags"), + (["in"], POINTER(IShellItem), "psiItem"), + (["in"], POINTER(IShellItem), "psiDestinationFolder"), + (["in"], c_wchar_p, "pszNewName"), + (["in"], HRESULT, "hrCopy"), + (["in"], POINTER(IShellItem), "psiNewlyCreated"), + ), + COMMETHOD( + [], + HRESULT, + "PreDeleteItem", + (["in"], c_ulong, "dwFlags"), + (["in"], POINTER(IShellItem), "psiItem"), + ), + COMMETHOD( + [], + HRESULT, + "PostDeleteItem", + (["in"], c_ulong, "dwFlags"), + (["in"], POINTER(IShellItem), "psiItem"), + (["in"], HRESULT, "hrDelete"), + (["in"], POINTER(IShellItem), "psiNewlyCreated"), + ), + COMMETHOD( + [], + HRESULT, + "PreNewItem", + (["in"], c_ulong, "dwFlags"), + (["in"], POINTER(IShellItem), "psiDestinationFolder"), + (["in"], c_wchar_p, "pszNewName"), + ), + COMMETHOD( + [], + HRESULT, + "PostNewItem", + (["in"], c_ulong, "dwFlags"), + (["in"], POINTER(IShellItem), "psiDestinationFolder"), + (["in"], c_wchar_p, "pszNewName"), + (["in"], c_wchar_p, "pszTemplateName"), + (["in"], c_ulong, "dwFileAttributes"), + (["in"], HRESULT, "hrNew"), + (["in"], POINTER(IShellItem), "psiNewItem"), + ), + COMMETHOD( + [], + HRESULT, + "UpdateProgress", + (["in"], c_ulong, "iWorkTotal"), + (["in"], c_ulong, "iWorkSoFar"), + ), + COMMETHOD([], HRESULT, "ResetTimer"), + COMMETHOD([], HRESULT, "PauseTimer"), + COMMETHOD([], HRESULT, "ResumeTimer"), + ] + QueryInterface: Callable[[GUID, IUnknown], HRESULT] + AddRef: Callable[[], ULONG] + Release: Callable[[], ULONG] + StartOperations: Callable[[], HRESULT] + FinishOperations: Callable[[HRESULT], HRESULT] + PreRenameItem: Callable[[c_ulong, IShellItem, c_wchar_p], HRESULT] + PostRenameItem: Callable[ + [c_ulong, IShellItem, c_wchar_p, HRESULT, IShellItem], HRESULT + ] + PreMoveItem: Callable[[c_ulong, IShellItem, IShellItem, c_wchar_p], HRESULT] + PostMoveItem: Callable[ + [c_ulong, IShellItem, IShellItem, c_wchar_p, HRESULT, IShellItem], HRESULT + ] + PreCopyItem: Callable[[c_ulong, IShellItem, IShellItem, c_wchar_p], HRESULT] + PostCopyItem: Callable[ + [c_ulong, IShellItem, IShellItem, c_wchar_p, HRESULT, IShellItem], HRESULT + ] + PreDeleteItem: Callable[[c_ulong, IShellItem], HRESULT] + PostDeleteItem: Callable[[c_ulong, IShellItem, HRESULT, IShellItem], HRESULT] + PreNewItem: Callable[[c_ulong, IShellItem, c_wchar_p], HRESULT] + PostNewItem: Callable[ + [c_ulong, IShellItem, c_wchar_p, c_wchar_p, c_ulong, HRESULT, IShellItem], + HRESULT, + ] + UpdateProgress: Callable[[c_ulong, c_ulong], HRESULT] + ResetTimer: Callable[[], HRESULT] + PauseTimer: Callable[[], HRESULT] + ResumeTimer: Callable[[], HRESULT] + + +class FileOperationProgressSink(COMObject): + _com_interfaces_: Sequence[type[IUnknown]] = [IFileOperationProgressSink] + + def StartOperations(self) -> HRESULT: + return S_OK + + def FinishOperations(self, hr: HRESULT) -> HRESULT: + return S_OK + + def PreRenameItem( + self, dwFlags: c_ulong | int, psiItem: IShellItem, pszNewName: LPCWSTR | str + ) -> HRESULT: # noqa: N803 + return S_OK + + def PostRenameItem( + self, + dwFlags: c_ulong | int, + psiItem: IShellItem, + pszNewName: LPCWSTR | str, + hrRename: HRESULT, + psiNewlyCreated: IShellItem, + ) -> HRESULT: # noqa: N803 + return S_OK + + def PreMoveItem( + self, + dwFlags: c_ulong | int, + psiItem: IShellItem, + psiDestinationFolder: IShellItem, + pszNewName: LPCWSTR | str, + ) -> HRESULT: # noqa: N803 + return S_OK + + def PostMoveItem( + self, + dwFlags: c_ulong | int, + psiItem: IShellItem, + psiDestinationFolder: IShellItem, + pszNewName: LPCWSTR | str, + hrMove: HRESULT, + psiNewlyCreated: IShellItem, + ) -> HRESULT: # noqa: N803, E501 + return S_OK + + def PreCopyItem( + self, + dwFlags: c_ulong | int, + psiItem: IShellItem, + psiDestinationFolder: IShellItem, + pszNewName: LPCWSTR | str, + ) -> HRESULT: # noqa: N803 + return S_OK + + def PostCopyItem( + self, + dwFlags: c_ulong | int, + psiItem: IShellItem, + psiDestinationFolder: IShellItem, + pszNewName: LPCWSTR | str, + hrCopy: HRESULT, + psiNewlyCreated: IShellItem, + ) -> HRESULT: # noqa: N803, E501 + return S_OK + + def PreDeleteItem(self, dwFlags: c_ulong | int, psiItem: IShellItem) -> HRESULT: + return S_OK + + def PostDeleteItem( + self, + dwFlags: c_ulong | int, + psiItem: IShellItem, + hrDelete: HRESULT, + psiNewlyCreated: IShellItem, + ) -> HRESULT: + return S_OK + + def PreNewItem( + self, + dwFlags: c_ulong | int, + psiDestinationFolder: IShellItem, + pszNewName: LPCWSTR | str, + ) -> HRESULT: + return S_OK + + def PostNewItem( + self, + dwFlags: c_ulong | int, + psiDestinationFolder: IShellItem, + pszNewName: LPCWSTR | str, + pszTemplateName: LPCWSTR | str, + dwFileAttributes: c_ulong | int, + hrNew: HRESULT, + psiNewItem: IShellItem, + ) -> HRESULT: # noqa: N803, E501 + return S_OK + + def UpdateProgress( + self, iWorkTotal: c_ulong | int, iWorkSoFar: c_ulong | int + ) -> HRESULT: # noqa: N803 + return S_OK + + def ResetTimer(self) -> HRESULT: + return S_OK + + def PauseTimer(self) -> HRESULT: + return S_OK + + def ResumeTimer(self) -> HRESULT: + return S_OK + + +class IFileOperation(IUnknown): + _case_insensitive_ = True + _iid_ = IID_IFileOperation + _idlflags_ = [] + + _methods_: ClassVar[list[_ComMemberSpec]] = [ + # Advise methods + comtypes.COMMETHOD( + [], + HRESULT, + "Advise", + (["in"], POINTER(IFileOperationProgressSink), "pfops"), + (["out"], POINTER(c_ulong), "pdwCookie"), + ), + comtypes.COMMETHOD([], HRESULT, "Unadvise", (["in"], c_ulong, "dwCookie")), + # Operation control methods + comtypes.COMMETHOD( + [], HRESULT, "SetOperationFlags", (["in"], c_ulong, "dwOperationFlags") + ), + comtypes.COMMETHOD( + [], HRESULT, "SetProgressMessage", (["in"], c_wchar_p, "pszMessage") + ), + comtypes.COMMETHOD( + [], + HRESULT, + "SetProgressDialog", + (["in"], POINTER(IUnknown), "popd"), + ), + # Item methods + comtypes.COMMETHOD( + [], + HRESULT, + "SetProperties", + (["in"], POINTER(IUnknown), "pproparray"), + ), + comtypes.COMMETHOD( + [], HRESULT, "SetOwnerWindow", (["in"], c_ulong, "hwndOwner") + ), + comtypes.COMMETHOD( + [], + HRESULT, + "ApplyPropertiesToItem", + (["in"], POINTER(IShellItem), "psiItem"), + ), + comtypes.COMMETHOD( + [], + HRESULT, + "ApplyPropertiesToItems", + (["in"], POINTER(IUnknown), "punkItems"), + ), + # Operation methods + comtypes.COMMETHOD( + [], + HRESULT, + "RenameItem", + (["in"], POINTER(IShellItem), "psiItem"), + (["in"], c_wchar_p, "pszNewName"), + (["in"], POINTER(IFileOperationProgressSink), "pfopsItem"), + ), + comtypes.COMMETHOD( + [], + HRESULT, + "RenameItems", + (["in"], POINTER(IUnknown), "pUnkItems"), + (["in"], c_wchar_p, "pszNewName"), + ), + comtypes.COMMETHOD( + [], + HRESULT, + "MoveItem", + (["in"], POINTER(IShellItem), "psiItem"), + (["in"], POINTER(IShellItem), "psiDestinationFolder"), + (["in"], c_wchar_p, "pszNewName"), + (["in"], POINTER(IFileOperationProgressSink), "pfopsItem"), + ), + comtypes.COMMETHOD( + [], + HRESULT, + "MoveItems", + (["in"], POINTER(IUnknown), "punkItems"), + (["in"], POINTER(IShellItem), "psiDestinationFolder"), + ), + comtypes.COMMETHOD( + [], + HRESULT, + "CopyItem", + (["in"], POINTER(IShellItem), "psiItem"), + (["in"], POINTER(IShellItem), "psiDestinationFolder"), + (["in"], c_wchar_p, "pszNewName"), + (["in"], POINTER(IFileOperationProgressSink), "pfopsItem"), + ), + comtypes.COMMETHOD( + [], + HRESULT, + "CopyItems", + (["in"], POINTER(IUnknown), "punkItems"), + (["in"], POINTER(IShellItem), "psiDestinationFolder"), + ), + comtypes.COMMETHOD( + [], + HRESULT, + "DeleteItem", + (["in"], POINTER(IShellItem), "psiItem"), + (["in"], POINTER(IFileOperationProgressSink), "pfopsItem"), + ), + comtypes.COMMETHOD( + [], + HRESULT, + "DeleteItems", + (["in"], POINTER(IUnknown), "punkItems"), + ), + comtypes.COMMETHOD( + [], + HRESULT, + "NewItem", + (["in"], POINTER(IShellItem), "psiDestinationFolder"), + (["in"], c_ulong, "dwFileAttributes"), + (["in"], c_wchar_p, "pszName"), + (["in"], c_wchar_p, "pszTemplateName"), + (["in"], POINTER(IFileOperationProgressSink), "pfopsItem"), + ), + # Execution methods + comtypes.COMMETHOD([], HRESULT, "PerformOperations"), + comtypes.COMMETHOD( + [], + HRESULT, + "GetAnyOperationsAborted", + (["out"], POINTER(c_int), "pfAnyOperationsAborted"), + ), + ] + + +class IFileDialogEvents(IUnknown): + _case_insensitive_: bool = True + _iid_: GUID = IID_IFileDialogEvents + _methods_: ClassVar[list[_ComMemberSpec]] + QueryInterface: Callable[[GUID, IUnknown], HRESULT] + AddRef: Callable[[], ULONG] + Release: Callable[[], ULONG] + OnFileOk: Callable[[IFileDialog], HRESULT] + OnFolderChanging: Callable[[IFileDialog, IShellItem], HRESULT] + OnFolderChange: Callable[[IFileDialog], HRESULT] + OnSelectionChange: Callable[[IFileDialog], HRESULT] + OnShareViolation: Callable[[IFileDialog, IShellItem, c_int], HRESULT] + OnTypeChange: Callable[[IFileDialog], HRESULT] + OnOverwrite: Callable[[IFileDialog, IShellItem, c_int], HRESULT] + + +class IFileDialog(IModalWindow): + _iid_: GUID = IID_IFileDialog + _methods_: ClassVar[list[_ComMemberSpec]] = [ + COMMETHOD( + [], + HRESULT, + "SetFileTypes", + (["in"], c_uint, "cFileTypes"), + (["in"], POINTER(c_void_p), "rgFilterSpec"), + ), + COMMETHOD([], HRESULT, "SetFileTypeIndex", (["in"], c_uint, "iFileType")), + COMMETHOD( + [], HRESULT, "GetFileTypeIndex", (["out"], POINTER(c_uint), "piFileType") + ), + COMMETHOD( + [], + HRESULT, + "Advise", + (["in"], POINTER(IUnknown), "pfde"), + (["out"], POINTER(DWORD), "pdwCookie"), + ), + COMMETHOD([], HRESULT, "Unadvise", (["in"], DWORD, "dwCookie")), + COMMETHOD([], HRESULT, "SetOptions", (["in"], c_uint, "fos")), + COMMETHOD([], HRESULT, "GetOptions", (["out"], POINTER(DWORD), "pfos")), + COMMETHOD( + [], HRESULT, "SetDefaultFolder", (["in"], POINTER(IShellItem), "psi") + ), + COMMETHOD([], HRESULT, "SetFolder", (["in"], POINTER(IShellItem), "psi")), + COMMETHOD( + [], HRESULT, "GetFolder", (["out"], POINTER(POINTER(IShellItem)), "ppsi") + ), + COMMETHOD( + [], + HRESULT, + "GetCurrentSelection", + (["out"], POINTER(POINTER(IShellItem)), "ppsi"), + ), + COMMETHOD([], HRESULT, "SetFileName", (["in"], LPCWSTR, "pszName")), + COMMETHOD([], HRESULT, "GetFileName", (["out"], POINTER(LPWSTR), "pszName")), + COMMETHOD([], HRESULT, "SetTitle", (["in"], LPCWSTR, "pszTitle")), + COMMETHOD([], HRESULT, "SetOkButtonLabel", (["in"], LPCWSTR, "pszText")), + COMMETHOD([], HRESULT, "SetFileNameLabel", (["in"], LPCWSTR, "pszLabel")), + COMMETHOD( + [], HRESULT, "GetResult", (["out"], POINTER(POINTER(IShellItem)), "ppsi") + ), + COMMETHOD( + [], + HRESULT, + "AddPlace", + (["in"], POINTER(IShellItem), "psi"), + (["in"], c_int, "fdap"), + ), + COMMETHOD( + [], HRESULT, "SetDefaultExtension", (["in"], LPCWSTR, "pszDefaultExtension") + ), + COMMETHOD([], HRESULT, "Close", (["in"], HRESULT, "hr")), + COMMETHOD([], HRESULT, "SetClientGuid", (["in"], POINTER(GUID), "guid")), + COMMETHOD([], HRESULT, "ClearClientData"), + COMMETHOD( + [], HRESULT, "SetFilter", (["in"], POINTER(IShellItemFilter), "pFilter") + ), + ] + SetFileTypes: Callable[[c_uint | int, _Pointer[c_void_p]], HRESULT] + SetFileTypeIndex: Callable[[c_uint], HRESULT] + GetFileTypeIndex: Callable[[], _Pointer[c_uint]] + Advise: Callable[[IUnknown | COMObject], int] + Unadvise: Callable[[int], HRESULT] + SetOptions: Callable[[DWORD | int], HRESULT] + GetOptions: Callable[[], int] + SetDefaultFolder: Callable[[_Pointer[IShellItem]], HRESULT] + SetFolder: Callable[[_Pointer[IShellItem]], HRESULT] + GetFolder: Callable[[], IShellItem] + GetCurrentSelection: Callable[[], IShellItem] + SetFileName: Callable[[str], HRESULT] + GetFileName: Callable[[], _Pointer[LPWSTR]] + SetTitle: Callable[[str], HRESULT] + SetOkButtonLabel: Callable[[str], HRESULT] + SetFileNameLabel: Callable[[str], HRESULT] + GetResult: Callable[[], IShellItem] + AddPlace: Callable[[IShellItem, c_int], HRESULT] + SetDefaultExtension: Callable[[str], HRESULT] + Close: Callable[[HRESULT], HRESULT] + SetClientGuid: Callable[[GUID], HRESULT] + ClearClientData: Callable[[], HRESULT] + SetFilter: Callable[[IShellItemFilter], HRESULT] + + +IFileDialogEvents._methods_ = [ # noqa: SLF001 + COMMETHOD([], HRESULT, "OnFileOk", (["in"], POINTER(IFileDialog), "pfd")), + COMMETHOD( + [], + HRESULT, + "OnFolderChanging", + (["in"], POINTER(IFileDialog), "pfd"), + (["in"], POINTER(IShellItem), "psiFolder"), + ), + COMMETHOD([], HRESULT, "OnFolderChange", (["in"], POINTER(IFileDialog), "pfd")), + COMMETHOD([], HRESULT, "OnSelectionChange", (["in"], POINTER(IFileDialog), "pfd")), + COMMETHOD( + [], + HRESULT, + "OnShareViolation", + (["in"], POINTER(IFileDialog), "pfd"), + (["in"], POINTER(IShellItem), "psi"), + (["out"], POINTER(c_int), "pResponse"), + ), + COMMETHOD([], HRESULT, "OnTypeChange", (["in"], POINTER(IFileDialog), "pfd")), + COMMETHOD( + [], + HRESULT, + "OnOverwrite", + (["in"], POINTER(IFileDialog), "pfd"), + (["in"], POINTER(IShellItem), "psi"), + (["out"], POINTER(c_int), "pResponse"), + ), +] + + +class IShellLibrary(IUnknown): + _case_insensitive_: bool = True + _iid_: GUID = IID_IShellLibrary + _methods_: ClassVar[list[_ComMemberSpec]] = [ + COMMETHOD( + [], + HRESULT, + "LoadLibraryFromItem", + (["in"], POINTER(IShellItem), "psi"), + (["in"], c_ulong, "grfMode"), + ), + COMMETHOD( + [], + HRESULT, + "LoadLibraryFromKnownFolder", + (["in"], POINTER(GUID), "kfidLibrary"), + (["in"], c_ulong, "grfMode"), + ), + COMMETHOD([], HRESULT, "AddFolder", (["in"], POINTER(IShellItem), "psi")), + COMMETHOD([], HRESULT, "RemoveFolder", (["in"], POINTER(IShellItem), "psi")), + COMMETHOD( + [], + HRESULT, + "GetFolders", + (["in"], c_int, "lff"), + (["in"], POINTER(GUID), "riid"), + (["out"], POINTER(POINTER(c_void_p)), "ppv"), + ), + COMMETHOD( + [], + HRESULT, + "ResolveFolder", + (["in"], POINTER(IShellItem), "psi"), + (["in"], c_ulong, "grfMode"), + (["in"], POINTER(GUID), "riid"), + (["out"], POINTER(POINTER(c_void_p)), "ppv"), + ), + COMMETHOD( + [], + HRESULT, + "GetDefaultSaveFolder", + (["in"], c_int, "dsft"), + (["in"], POINTER(GUID), "riid"), + (["out"], POINTER(POINTER(c_void_p)), "ppv"), + ), + COMMETHOD( + [], + HRESULT, + "SetDefaultSaveFolder", + (["in"], c_int, "dsft"), + (["in"], POINTER(IShellItem), "psi"), + ), + COMMETHOD([], HRESULT, "GetOptions", (["out"], POINTER(c_uint), "pOptions")), + COMMETHOD( + [], + HRESULT, + "SetOptions", + (["in"], c_ulong, "stfOptions"), + (["in"], c_ulong, "stfMask"), + ), + COMMETHOD([], HRESULT, "GetFolderType", (["out"], POINTER(GUID), "pftid")), + COMMETHOD([], HRESULT, "SetFolderType", (["in"], POINTER(GUID), "ftid")), + COMMETHOD([], HRESULT, "GetIcon", (["out"], POINTER(LPWSTR), "ppszIcon")), + COMMETHOD([], HRESULT, "SetIcon", (["in"], LPCWSTR, "pszIcon")), + COMMETHOD([], HRESULT, "Commit"), + COMMETHOD( + [], + HRESULT, + "Save", + (["in"], POINTER(IShellItem), "psiFolderToSaveIn"), + (["in"], LPCWSTR, "pszLibraryName"), + (["in"], c_ulong, "lrf"), + (["out"], POINTER(POINTER(IShellItem)), "ppsiNewItem"), + ), + COMMETHOD( + [], + HRESULT, + "SaveInKnownFolder", + (["in"], POINTER(GUID), "kfid"), + (["in"], LPCWSTR, "pszLibraryName"), + (["in"], c_ulong, "lrf"), + (["out"], POINTER(POINTER(IShellItem)), "ppsiNewItem"), + ), + ] + QueryInterface: Callable[[GUID, IUnknown], HRESULT] + AddRef: Callable[[], ULONG] + Release: Callable[[], ULONG] + LoadLibraryFromItem: Callable[ + [_Pointer[IShellLibrary], IShellItem, c_ulong], HRESULT + ] + LoadLibraryFromKnownFolder: Callable[ + [_Pointer[IShellLibrary], GUID, c_ulong], HRESULT + ] + AddFolder: Callable[[_Pointer[IShellLibrary], IShellItem], HRESULT] + RemoveFolder: Callable[[_Pointer[IShellLibrary], IShellItem], HRESULT] + GetFolders: Callable[ + [_Pointer[IShellLibrary], c_int, GUID, _Pointer[_Pointer[c_void_p]]], HRESULT + ] + ResolveFolder: Callable[ + [ + _Pointer[IShellLibrary], + IShellItem, + c_ulong, + GUID, + _Pointer[_Pointer[c_void_p]], + ], + HRESULT, + ] + GetDefaultSaveFolder: Callable[ + [_Pointer[IShellLibrary], c_int, GUID, _Pointer[_Pointer[c_void_p]]], HRESULT + ] + SetDefaultSaveFolder: Callable[ + [_Pointer[IShellLibrary], c_int, IShellItem], HRESULT + ] + GetOptions: Callable[[_Pointer[IShellLibrary], _Pointer[c_uint]], HRESULT] + SetOptions: Callable[[_Pointer[IShellLibrary], c_ulong, c_ulong], HRESULT] + GetFolderType: Callable[[_Pointer[IShellLibrary], GUID], HRESULT] + SetFolderType: Callable[[_Pointer[IShellLibrary], GUID], HRESULT] + GetIcon: Callable[[_Pointer[IShellLibrary], _Pointer[LPWSTR]], HRESULT] + SetIcon: Callable[[_Pointer[IShellLibrary], LPCWSTR], HRESULT] + Commit: Callable[[_Pointer[IShellLibrary]], HRESULT] + Save: Callable[ + [_Pointer[IShellLibrary], IShellItem, LPCWSTR, c_ulong, IShellItem], HRESULT + ] + SaveInKnownFolder: Callable[ + [_Pointer[IShellLibrary], GUID, LPCWSTR, c_ulong, IShellItem], HRESULT + ] + + +class IFileOpenDialog(IFileDialog): + _case_insensitive_: bool = True + _iid_: GUID = IID_IFileOpenDialog + _methods_: ClassVar[list[_ComMemberSpec]] = [ + COMMETHOD( + [], + HRESULT, + "GetResults", + (["out"], POINTER(POINTER(IShellItemArray)), "ppenum"), + ), + COMMETHOD( + [], + HRESULT, + "GetSelectedItems", + (["out"], POINTER(POINTER(IShellItemArray)), "ppsai"), + ), + ] + GetResults: Callable[[], IShellItemArray] + GetSelectedItems: Callable[[], IShellItemArray] + + +class IFileSaveDialog(IFileDialog): + _case_insensitive_: bool = True + _iid_: GUID = IID_IFileSaveDialog + _methods_: ClassVar[list[_ComMemberSpec]] = [ + COMMETHOD([], HRESULT, "SetSaveAsItem", (["in"], POINTER(IShellItem), "psi")), + COMMETHOD([], HRESULT, "SetProperties", (["in"], POINTER(IUnknown), "pStore")), + COMMETHOD( + [], + HRESULT, + "SetCollectedProperties", + (["in"], POINTER(IUnknown), "pList"), + (["in"], BOOL, "fAppendDefault"), + ), + COMMETHOD( + [], + HRESULT, + "GetProperties", + (["out"], POINTER(POINTER(IUnknown)), "ppStore"), + ), + COMMETHOD( + [], + HRESULT, + "ApplyProperties", + (["in"], POINTER(IShellItem), "psi"), + (["in"], POINTER(IUnknown), "pStore"), + (["in"], HWND, "hwnd"), + (["in"], POINTER(IUnknown), "pSink"), + ), + ] + SetSaveAsItem: Callable[[IShellItem], HRESULT] + SetProperties: Callable[[_Pointer[IUnknown]], HRESULT] + SetCollectedProperties: Callable[[_Pointer[IUnknown], BOOL], HRESULT] + GetProperties: Callable[[_Pointer[_Pointer[IUnknown]]], HRESULT] + ApplyProperties: Callable[ + [IShellItem, _Pointer[IUnknown], HWND, _Pointer[IUnknown]], HRESULT + ] + + +class IFileDialogCustomize(IUnknown): + _case_insensitive_ = True + _iid_: GUID = IID_IFileDialogCustomize + _methods_: ClassVar[list[_ComMemberSpec]] = [ + COMMETHOD([], HRESULT, "EnableOpenDropDown", (["in"], c_uint, "dwIDCtl")), + COMMETHOD( + [], + HRESULT, + "AddText", + (["in"], c_uint, "dwIDCtl"), + (["in"], LPCWSTR, "pszText"), + ), + COMMETHOD( + [], + HRESULT, + "AddPushButton", + (["in"], c_uint, "dwIDCtl"), + (["in"], LPCWSTR, "pszLabel"), + ), + COMMETHOD( + [], + HRESULT, + "AddCheckButton", + (["in"], c_uint, "dwIDCtl"), + (["in"], LPCWSTR, "pszLabel"), + (["in"], c_int, "bChecked"), + ), + COMMETHOD([], HRESULT, "AddRadioButtonList", (["in"], c_uint, "dwIDCtl")), + COMMETHOD([], HRESULT, "AddComboBox", (["in"], c_uint, "dwIDCtl")), + COMMETHOD( + [], + HRESULT, + "AddControlItem", + (["in"], c_uint, "dwIDCtl"), + (["in"], c_uint, "dwIDItem"), + (["in"], LPCWSTR, "pszLabel"), + ), + COMMETHOD( + [], + HRESULT, + "AddEditBox", + (["in"], c_uint, "dwIDCtl"), + (["in"], LPCWSTR, "pszText"), + ), + COMMETHOD([], HRESULT, "AddSeparator", (["in"], c_uint, "dwIDCtl")), + COMMETHOD( + [], + HRESULT, + "AddMenu", + (["in"], c_uint, "dwIDCtl"), + (["in"], LPCWSTR, "pszLabel"), + ), + COMMETHOD( + [], + HRESULT, + "SetControlLabel", + (["in"], c_uint, "dwIDCtl"), + (["in"], LPCWSTR, "pszLabel"), + ), + COMMETHOD( + [], + HRESULT, + "SetControlState", + (["in"], c_uint, "dwIDCtl"), + (["in"], c_int, "dwState"), + ), + COMMETHOD( + [], + HRESULT, + "SetCheckButtonState", + (["in"], c_uint, "dwIDCtl"), + (["in"], c_int, "bChecked"), + ), + COMMETHOD( + [], + HRESULT, + "GetCheckButtonState", + (["in"], c_uint, "dwIDCtl"), + (["out"], POINTER(c_int), "pbChecked"), + ), + COMMETHOD( + [], + HRESULT, + "SetEditBoxText", + (["in"], c_uint, "dwIDCtl"), + (["in"], LPCWSTR, "pszText"), + ), + COMMETHOD( + [], + HRESULT, + "GetEditBoxText", + (["in"], c_uint, "dwIDCtl"), + (["out"], POINTER(LPCWSTR), "ppszText"), + ), + COMMETHOD( + [], + HRESULT, + "SetControlItemText", + (["in"], c_uint, "dwIDCtl"), + (["in"], c_uint, "dwIDItem"), + (["in"], LPCWSTR, "pszLabel"), + ), + COMMETHOD( + [], + HRESULT, + "GetControlItemState", + (["in"], c_uint, "dwIDCtl"), + (["in"], c_uint, "dwIDItem"), + (["out"], POINTER(c_int), "pdwState"), + ), + COMMETHOD( + [], + HRESULT, + "SetControlItemState", + (["in"], c_uint, "dwIDCtl"), + (["in"], c_uint, "dwIDItem"), + (["in"], c_int, "dwState"), + ), + COMMETHOD( + [], + HRESULT, + "GetSelectedControlItem", + (["in"], c_uint, "dwIDCtl"), + (["out"], POINTER(c_uint), "pdwIDItem"), + ), + COMMETHOD( + [], + HRESULT, + "SetSelectedControlItem", + (["in"], c_uint, "dwIDCtl"), + (["in"], c_uint, "dwIDItem"), + ), + COMMETHOD( + [], + HRESULT, + "StartVisualGroup", + (["in"], c_uint, "dwIDCtl"), + (["in"], LPCWSTR, "pszLabel"), + ), + COMMETHOD([], HRESULT, "EndVisualGroup", (["in"], c_uint, "dwIDCtl")), + COMMETHOD([], HRESULT, "MakeProminent", (["in"], c_uint, "dwIDCtl")), + COMMETHOD( + [], + HRESULT, + "RemoveControlItem", + (["in"], c_uint, "dwIDCtl"), + (["in"], c_uint, "dwIDItem"), + ), + COMMETHOD([], HRESULT, "RemoveAllControlItems", (["in"], c_uint, "dwIDCtl")), + COMMETHOD( + [], + HRESULT, + "GetControlState", + (["in"], c_uint, "dwIDCtl"), + (["out"], POINTER(c_int), "pdwState"), + ), + ] + EnableOpenDropDown: Callable[[int], HRESULT] + AddText: Callable[[int, str], HRESULT] + AddPushButton: Callable[[int, str], HRESULT] + AddCheckButton: Callable[[int, str, int], HRESULT] + AddRadioButtonList: Callable[[int], HRESULT] + AddComboBox: Callable[[int], HRESULT] + AddControlItem: Callable[[int, int, str], HRESULT] + AddEditBox: Callable[[int, str], HRESULT] + AddSeparator: Callable[[int], HRESULT] + AddMenu: Callable[[int, str], HRESULT] + SetControlLabel: Callable[[int, str], HRESULT] + SetControlState: Callable[[int, int], HRESULT] + SetCheckButtonState: Callable[[int, int], HRESULT] + GetCheckButtonState: Callable[[int], int] + SetEditBoxText: Callable[[int, str], HRESULT] + GetEditBoxText: Callable[[int], LPCWSTR] + SetControlItemText: Callable[[int, int, str], HRESULT] + GetControlItemState: Callable[[int, int], int] + SetControlItemState: Callable[[int, int, int], HRESULT] + GetSelectedControlItem: Callable[[int], int] + SetSelectedControlItem: Callable[[int, int], HRESULT] + StartVisualGroup: Callable[[int, str], HRESULT] + EndVisualGroup: Callable[[int], HRESULT] + MakeProminent: Callable[[int], HRESULT] + RemoveControlItem: Callable[[int, int], HRESULT] + RemoveAllControlItems: Callable[[int], HRESULT] + GetControlState: Callable[[int], int] + + +class IFileDialogControlEvents(IUnknown): + _case_insensitive_ = True + _iid_ = IID_IFileDialogControlEvents + _methods_: ClassVar[list[_ComMemberSpec]] = [ + COMMETHOD( + [], + HRESULT, + "OnItemSelected", + (["in"], comtypes.POINTER(IFileDialogCustomize), "pfdc"), + (["in"], c_int, "dwIDCtl"), + (["in"], c_int, "dwIDItem"), + ), + COMMETHOD( + [], + HRESULT, + "OnButtonClicked", + (["in"], comtypes.POINTER(IFileDialogCustomize), "pfdc"), + (["in"], c_int, "dwIDCtl"), + ), + COMMETHOD( + [], + HRESULT, + "OnCheckButtonToggled", + (["in"], comtypes.POINTER(IFileDialogCustomize), "pfdc"), + (["in"], c_int, "dwIDCtl"), + (["in"], c_bool, "bChecked"), + ), + COMMETHOD( + [], + HRESULT, + "OnControlActivating", + (["in"], comtypes.POINTER(IFileDialogCustomize), "pfdc"), + (["in"], c_int, "dwIDCtl"), + ), + ] + OnButtonClicked: Callable[[IFileDialogCustomize, c_uint], HRESULT] + OnCheckButtonToggled: Callable[[IFileDialogCustomize, c_uint, c_int], HRESULT] + OnControlActivating: Callable[[IFileDialogCustomize, c_uint], HRESULT] + OnItemSelected: Callable[[IFileDialogCustomize, c_uint, c_uint], HRESULT] From aae435beb5e692507a44fd23675f23fd29e79f76 Mon Sep 17 00:00:00 2001 From: Benjamin Auquite Date: Fri, 30 Aug 2024 21:57:56 -0500 Subject: [PATCH 7/9] remove/reorganize important interfaces --- .../src/toga_winforms/libs/com/identifiers.py | 38 +- .../src/toga_winforms/libs/com/interfaces.py | 1210 ++--------------- .../toga_winforms/libs/com/more_interfaces.py | 293 ++++ 3 files changed, 381 insertions(+), 1160 deletions(-) create mode 100644 winforms/src/toga_winforms/libs/com/more_interfaces.py diff --git a/winforms/src/toga_winforms/libs/com/identifiers.py b/winforms/src/toga_winforms/libs/com/identifiers.py index 2ed8c4f90d..b6510cc51b 100644 --- a/winforms/src/toga_winforms/libs/com/identifiers.py +++ b/winforms/src/toga_winforms/libs/com/identifiers.py @@ -3,52 +3,18 @@ from comtypes import GUID IID_IUnknown = GUID("{00000000-0000-0000-C000-000000000046}") -IID_IDispatch = GUID("{00020400-0000-0000-C000-000000000046}") -IID_IClassFactory = GUID("{00000001-0000-0000-C000-000000000046}") -IID_IStream = GUID("{0000000c-0000-0000-C000-000000000046}") -IID_IStorage = GUID("{0000000b-0000-0000-C000-000000000046}") -IID_IBindCtx = GUID("{0000000e-0000-0000-C000-000000000046}") -IID_IEnumShellItems = GUID("{70629033-E363-4A28-A567-0DB78006E6D7}") -IID_IContextMenu = GUID("{000214e4-0000-0000-c000-000000000046}") -IID_IContextMenu2 = GUID("{000214f4-0000-0000-c000-000000000046}") -IID_IContextMenu3 = GUID("{bcfce0a0-ec17-11d0-8d10-00a0c90f2719}") -IID_IShellFolder = GUID("{000214E6-0000-0000-C000-000000000046}") -IID_IShellFolder2 = GUID("{93F2F68C-1D1B-11D3-A30E-00C04F79ABD1}") IID_IShellItem = GUID("{43826D1E-E718-42EE-BC55-A1E261C37BFE}") -IID_IShellItem2 = GUID("{7E9FB0D3-919F-4307-AB2E-9B1860310C93}") -IID_IShellLibrary = GUID("{11A66EFA-382E-451A-9234-1E0E12EF3085}") IID_IShellItemArray = GUID("{B63EA76D-1F85-456F-A19C-48159EFA858B}") IID_IShellItemFilter = GUID("{2659B475-EEB8-48B7-8F07-B378810F48CF}") -IID_IShellView = GUID("{000214e3-0000-0000-c000-000000000046}") IID_IModalWindow = GUID("{B4DB1657-70D7-485E-8E3E-6FCB5A5C1802}") IID_IFileDialog = GUID("{42F85136-DB7E-439C-85F1-E4075D135FC8}") -IID_IFileDialog2 = GUID("{61744FC7-85B5-4791-A9B0-272276309B13}") IID_IFileSaveDialog = GUID("{84BCCD23-5FDE-4CDB-AEA4-AF64B83D78AB}") -IID_IFileSaveDialogOld = GUID("{2804B74C-AC16-4398-9DC0-DB83F5B7ED14}") -IID_IFileSaveDialogPrivate = GUID("{6CB95A6A-88B6-4DC4-B3EA-3A776D1E8EFF}") IID_IFileOpenDialog = GUID("{D57C7288-D4AD-4768-BE02-9D969532D960}") IID_IFileDialogEvents = GUID("{973510DB-7D7F-452B-8975-74A85828D354}") -IID_FileDialogPermissionAttribute = GUID("{0CCCA629-440F-313E-96CD-BA1B4B4997F7}") -IID_FileDialogPermission = GUID("{A8B7138C-8932-3D78-A585-A91569C743AC}") -IID_IFileDialogPrivate = GUID("{9EA5491C-89C8-4BEF-93D3-7F665FB82A33}") + IID_IFileDialogCustomize = GUID("{E6FDD21A-163F-4975-9C8C-A69F1BA37034}") -IID_IFileDialogEventsPrivate = GUID("{050E9E69-BAEA-4C08-AD6A-61666DD32E96}") IID_IFileDialogControlEvents = GUID("{36116642-D713-4B97-9B83-7484A9D00433}") -IID_IFileDialogResultHandler = GUID("{42841501-194F-478F-9B4C-78985419DA53}") -IID_IShellLink = GUID("{000214f9-0000-0000-c000-000000000046}") -IID_IShellLinkDataList = GUID("{45E2B4AE-B1C3-11D0-BA91-00C04FD7A083}") -IID_IPropertyStore = GUID("{886D8EEB-8CF2-4446-8D02-CDBA1DBDCF99}") -IID_IFileOperationProgressSink = GUID("{04B0F1A7-9490-44BC-96E1-4296A31252E2}") -IID_IFileOperation = GUID("{94EA2B94-E9CC-49E0-C0E3-D20A7D91AA98}") -CLSID_FileOperation = GUID("{3AD05575-8857-4850-9277-11B85BDB8E09}") + CLSID_FileDialog = GUID("{3D9C8F03-50D4-4E40-BB11-70E74D3F10F3}") CLSID_FileOpenDialog = GUID("{DC1C5A9C-E88A-4dde-A5A1-60F82A20AEF7}") -CLSID_FileOpenDialogLegacy = GUID("{725F645B-EAED-4fc5-B1C5-D9AD0ACCBA5E}") CLSID_FileSaveDialog = GUID("{C0B4E2F3-BA21-4773-8DBA-335EC946EB8B}") -CLSID_FileSaveDialogLegacy = GUID("{AF02484C-A0A9-4669-9051-058AB12B9195}") -CLSID_ShellLibraryAPI = GUID("{d9b3211d-e57f-4426-aaef-30a806add397}") -CLSID_ShellFileSystemFolder = GUID("{F3364BA0-65B9-11CE-A9BA-00AA004AE837}") -CLSID_ShellBindStatusCallbackProxy = GUID("{2B4F54B1-3D6D-11d0-8258-00C04FD5AE38}") -CLSID_ShellURL = GUID("{4bec2015-bfa1-42fa-9c0c-59431bbe880e}") -CLSID_ShellDropTarget = GUID("{4bf684f8-3d29-4403-810d-494e72c4291b}") -CLSID_ShellNameSpace = GUID("{55136805-B2DE-11D1-B9F2-00A0C98BC547}") diff --git a/winforms/src/toga_winforms/libs/com/interfaces.py b/winforms/src/toga_winforms/libs/com/interfaces.py index ee46172079..7a76105fd0 100644 --- a/winforms/src/toga_winforms/libs/com/interfaces.py +++ b/winforms/src/toga_winforms/libs/com/interfaces.py @@ -1,41 +1,19 @@ from __future__ import annotations -from ctypes import ( - HRESULT, - POINTER, - POINTER as C_POINTER, - c_bool, - c_int, - c_uint, - c_ulong, - c_void_p, - c_wchar_p, -) +from ctypes import HRESULT, POINTER as C_POINTER, c_int, c_uint, c_ulong, c_void_p from ctypes.wintypes import BOOL, DWORD, HWND, LPCWSTR, LPWSTR, ULONG -from typing import TYPE_CHECKING, Callable, ClassVar, Sequence +from typing import TYPE_CHECKING, Callable, ClassVar -import comtypes from comtypes import COMMETHOD, GUID, COMObject, IUnknown -from comtypes.hresult import S_OK from toga_winforms.libs.com.identifiers import ( - IID_IContextMenu, - IID_IEnumShellItems, IID_IFileDialog, - IID_IFileDialogControlEvents, - IID_IFileDialogCustomize, - IID_IFileDialogEvents, IID_IFileOpenDialog, - IID_IFileOperation, - IID_IFileOperationProgressSink, IID_IFileSaveDialog, IID_IModalWindow, - IID_IPropertyStore, - IID_IShellFolder, IID_IShellItem, IID_IShellItemArray, IID_IShellItemFilter, - IID_IShellLibrary, ) if TYPE_CHECKING: @@ -50,7 +28,7 @@ class IModalWindow(IUnknown): _methods_: ClassVar[list[_ComMemberSpec]] = [ COMMETHOD([], HRESULT, "Show", (["in"], HWND, "hwndParent")) ] - Show: Callable[[int | HWND], HRESULT] + Show: Callable[[int | HWND], int] class IShellItem(IUnknown): @@ -61,210 +39,45 @@ class IShellItem(IUnknown): [], HRESULT, "BindToHandler", - (["in"], POINTER(IUnknown), "pbc"), - (["in"], POINTER(GUID), "bhid"), - (["in"], POINTER(GUID), "riid"), - (["out"], POINTER(c_void_p), "ppv"), + (["in"], C_POINTER(IUnknown), "pbc"), + (["in"], C_POINTER(GUID), "bhid"), + (["in"], C_POINTER(GUID), "riid"), + (["out"], C_POINTER(c_void_p), "ppv"), ), COMMETHOD( - [], HRESULT, "GetParent", (["out"], POINTER(POINTER(IUnknown)), "ppsi") + [], HRESULT, "GetParent", (["out"], C_POINTER(C_POINTER(IUnknown)), "ppsi") ), COMMETHOD( [], HRESULT, "GetDisplayName", (["in"], c_ulong, "sigdnName"), - (["out"], POINTER(LPWSTR), "ppszName"), + (["out"], C_POINTER(LPWSTR), "ppszName"), ), COMMETHOD( [], HRESULT, "GetAttributes", (["in"], c_ulong, "sfgaoMask"), - (["out"], POINTER(c_ulong), "psfgaoAttribs"), + (["out"], C_POINTER(c_ulong), "psfgaoAttribs"), ), COMMETHOD( [], HRESULT, "Compare", - (["in"], POINTER(IUnknown), "psi"), + (["in"], C_POINTER(IUnknown), "psi"), (["in"], c_ulong, "hint"), - (["out"], POINTER(c_int), "piOrder"), + (["out"], C_POINTER(c_int), "piOrder"), ), ] - QueryInterface: Callable[[GUID, _Pointer[_Pointer[IUnknown]]], HRESULT] + QueryInterface: Callable[[GUID, _Pointer[_Pointer[IUnknown]]], int] AddRef: Callable[[], ULONG] Release: Callable[[], ULONG] - BindToHandler: Callable[ - [_Pointer[IUnknown], GUID, GUID, _Pointer[c_void_p]], HRESULT - ] + BindToHandler: Callable[[_Pointer[IUnknown], GUID, GUID, _Pointer[c_void_p]], int] GetParent: Callable[[], IUnknown] GetDisplayName: Callable[[c_ulong | int], str] GetAttributes: Callable[[c_ulong | int], int] - Compare: Callable[[_Pointer[IUnknown], c_ulong, c_int], HRESULT] - - -class IContextMenu(IUnknown): - _case_insensitive_: bool = True - _iid_: GUID = IID_IContextMenu - _methods_: ClassVar[list[_ComMemberSpec]] = [ - COMMETHOD( - [], - HRESULT, - "QueryContextMenu", - (["in"], c_void_p, "hmenu"), - (["in"], c_uint, "indexMenu"), - (["in"], c_uint, "idCmdFirst"), - (["in"], c_uint, "idCmdLast"), - (["in"], c_uint, "uFlags"), - ), - COMMETHOD([], HRESULT, "InvokeCommand", (["in"], c_void_p, "pici")), - COMMETHOD( - [], - HRESULT, - "GetCommandString", - (["in"], c_uint, "idCmd"), - (["in"], c_uint, "uType"), - (["in"], c_void_p, "pReserved"), - (["out"], c_wchar_p, "pszName"), - (["in"], c_uint, "cchMax"), - ), - ] - QueryInterface: Callable[[GUID, _Pointer[_Pointer[IUnknown]]], HRESULT] - AddRef: Callable[[], ULONG] - Release: Callable[[], ULONG] - QueryContextMenu: Callable[[c_void_p, c_uint, c_uint, c_uint, c_uint], HRESULT] - InvokeCommand: Callable[[c_void_p], HRESULT] - GetCommandString: Callable[ - [c_uint, c_uint, c_void_p, _Pointer[c_wchar_p], c_uint], HRESULT - ] - - -class IShellFolder(IUnknown): - _case_insensitive_: bool = True - _iid_: GUID = IID_IShellFolder - _methods_: ClassVar[list[_ComMemberSpec]] = [ - COMMETHOD( - [], - HRESULT, - "ParseDisplayName", - (["in"], HWND, "hwnd"), - (["in"], POINTER(IUnknown), "pbc"), - (["in"], LPCWSTR, "pszDisplayName"), - (["out"], POINTER(ULONG), "pchEaten"), - (["out"], POINTER(c_void_p), "ppidl"), - (["in"], POINTER(ULONG), "pdwAttributes"), - ), - COMMETHOD( - [], - HRESULT, - "EnumObjects", - (["in"], HWND, "hwnd"), - (["in"], c_ulong, "grfFlags"), - (["out"], POINTER(POINTER(IUnknown)), "ppenumIDList"), - ), - COMMETHOD( - [], - HRESULT, - "BindToObject", - (["in"], c_void_p, "pidl"), - (["in"], POINTER(IUnknown), "pbc"), - (["in"], POINTER(GUID), "riid"), - (["out"], POINTER(c_void_p), "ppv"), - ), - COMMETHOD( - [], - HRESULT, - "BindToStorage", - (["in"], c_void_p, "pidl"), - (["in"], POINTER(IUnknown), "pbc"), - (["in"], POINTER(GUID), "riid"), - (["out"], POINTER(c_void_p), "ppv"), - ), - COMMETHOD( - [], - HRESULT, - "CompareIDs", - (["in"], c_void_p, "lParam"), - (["in"], c_void_p, "pidl1"), - (["in"], c_void_p, "pidl2"), - ), - COMMETHOD( - [], - HRESULT, - "CreateViewObject", - (["in"], HWND, "hwndOwner"), - (["in"], POINTER(GUID), "riid"), - (["out"], POINTER(c_void_p), "ppv"), - ), - COMMETHOD( - [], - HRESULT, - "GetAttributesOf", - (["in"], c_uint, "cidl"), - (["in"], C_POINTER(c_void_p), "apidl"), - (["out"], POINTER(c_ulong), "rgfInOut"), - ), - COMMETHOD( - [], - HRESULT, - "GetUIObjectOf", - (["in"], HWND, "hwndOwner"), - (["in"], c_uint, "cidl"), - (["in"], C_POINTER(c_void_p), "apidl"), - (["in"], POINTER(GUID), "riid"), - (["in"], POINTER(c_uint), "rgfReserved"), - (["out"], POINTER(c_void_p), "ppv"), - ), - COMMETHOD( - [], - HRESULT, - "GetDisplayNameOf", - (["in"], c_void_p, "pidl"), - (["in"], c_ulong, "uFlags"), - (["out"], POINTER(c_wchar_p), "pName"), - ), - COMMETHOD( - [], - HRESULT, - "SetNameOf", - (["in"], HWND, "hwnd"), - (["in"], c_void_p, "pidl"), - (["in"], LPCWSTR, "pszName"), - (["in"], c_ulong, "uFlags"), - (["out"], POINTER(c_void_p), "ppidlOut"), - ), - ] - QueryInterface: Callable[[GUID, _Pointer[_Pointer[IUnknown]]], HRESULT] - AddRef: Callable[[], ULONG] - Release: Callable[[], ULONG] - ParseDisplayName: Callable[ - [ - HWND, - _Pointer[IUnknown], - LPCWSTR, - _Pointer[ULONG], - _Pointer[c_void_p], - _Pointer[ULONG], - ], - HRESULT, - ] - EnumObjects: Callable[[HWND, c_ulong, _Pointer[_Pointer[IUnknown]]], HRESULT] - BindToObject: Callable[ - [c_void_p, _Pointer[IUnknown], GUID, _Pointer[c_void_p]], HRESULT - ] - BindToStorage: Callable[ - [c_void_p, _Pointer[IUnknown], GUID, _Pointer[c_void_p]], HRESULT - ] - CompareIDs: Callable[[c_void_p, c_void_p, c_void_p], HRESULT] - CreateViewObject: Callable[[HWND, GUID, _Pointer[c_void_p]], HRESULT] - GetAttributesOf: Callable[[c_uint, _Pointer[c_void_p], _Pointer[c_ulong]], HRESULT] - GetUIObjectOf: Callable[ - [HWND, c_uint, _Pointer[c_void_p], GUID, _Pointer[c_uint], _Pointer[c_void_p]], - HRESULT, - ] - GetDisplayNameOf: Callable[[c_void_p, c_ulong, _Pointer[c_wchar_p]], HRESULT] - SetNameOf: Callable[[HWND, c_void_p, LPCWSTR, c_ulong, _Pointer[c_void_p]], HRESULT] + Compare: Callable[[_Pointer[IUnknown], c_ulong, c_int], int] class IShellItemArray(IUnknown): @@ -275,26 +88,26 @@ class IShellItemArray(IUnknown): [], HRESULT, "BindToHandler", - (["in"], POINTER(IUnknown), "pbc"), - (["in"], POINTER(GUID), "bhid"), - (["in"], POINTER(GUID), "riid"), - (["out"], POINTER(c_void_p), "ppv"), + (["in"], C_POINTER(IUnknown), "pbc"), + (["in"], C_POINTER(GUID), "bhid"), + (["in"], C_POINTER(GUID), "riid"), + (["out"], C_POINTER(c_void_p), "ppv"), ), COMMETHOD( [], HRESULT, "GetPropertyStore", (["in"], c_ulong, "flags"), - (["in"], POINTER(GUID), "riid"), - (["out"], POINTER(c_void_p), "ppv"), + (["in"], C_POINTER(GUID), "riid"), + (["out"], C_POINTER(c_void_p), "ppv"), ), COMMETHOD( [], HRESULT, "GetPropertyDescriptionList", - (["in"], POINTER(GUID), "keyType"), - (["in"], POINTER(GUID), "riid"), - (["out"], POINTER(c_void_p), "ppv"), + (["in"], C_POINTER(GUID), "keyType"), + (["in"], C_POINTER(GUID), "riid"), + (["out"], C_POINTER(c_void_p), "ppv"), ), COMMETHOD( [], @@ -302,24 +115,24 @@ class IShellItemArray(IUnknown): "GetAttributes", (["in"], c_ulong, "attribFlags"), (["in"], c_ulong, "sfgaoMask"), - (["out"], POINTER(c_ulong), "psfgaoAttribs"), + (["out"], C_POINTER(c_ulong), "psfgaoAttribs"), ), - COMMETHOD([], HRESULT, "GetCount", (["out"], POINTER(c_uint), "pdwNumItems")), + COMMETHOD([], HRESULT, "GetCount", (["out"], C_POINTER(c_uint), "pdwNumItems")), COMMETHOD( [], HRESULT, "GetItemAt", (["in"], c_uint, "dwIndex"), - (["out"], POINTER(POINTER(IShellItem)), "ppsi"), + (["out"], C_POINTER(C_POINTER(IShellItem)), "ppsi"), ), COMMETHOD( [], HRESULT, "EnumItems", - (["out"], POINTER(POINTER(IUnknown)), "ppenumShellItems"), + (["out"], C_POINTER(C_POINTER(IUnknown)), "ppenumShellItems"), ), ] - QueryInterface: Callable[[GUID, IUnknown], HRESULT] + QueryInterface: Callable[[GUID, IUnknown], int] AddRef: Callable[[], ULONG] Release: Callable[[], ULONG] BindToHandler: Callable[[_Pointer[IUnknown], GUID, GUID], int] @@ -335,492 +148,20 @@ class IShellItemFilter(IUnknown): _case_insensitive_: bool = True _iid_: GUID = IID_IShellItemFilter _methods_: ClassVar[list[_ComMemberSpec]] = [ - COMMETHOD([], HRESULT, "IncludeItem", (["in"], POINTER(IShellItem), "psi")), + COMMETHOD([], HRESULT, "IncludeItem", (["in"], C_POINTER(IShellItem), "psi")), COMMETHOD( [], HRESULT, "GetEnumFlagsForItem", - (["in"], POINTER(IShellItem), "psi"), - (["out"], POINTER(c_ulong), "pgrfFlags"), + (["in"], C_POINTER(IShellItem), "psi"), + (["out"], C_POINTER(c_ulong), "pgrfFlags"), ), ] - QueryInterface: Callable[[GUID, IUnknown], HRESULT] + QueryInterface: Callable[[GUID, IUnknown], int] AddRef: Callable[[], ULONG] Release: Callable[[], ULONG] IncludeItem: Callable[[IShellItem], c_ulong] - GetEnumFlagsForItem: Callable[[], HRESULT] - - -class IEnumShellItems(IUnknown): - _case_insensitive_: bool = True - _iid_: GUID = IID_IEnumShellItems - _methods_: ClassVar[list[_ComMemberSpec]] - QueryInterface: Callable[[GUID, IUnknown], HRESULT] - AddRef: Callable[[], ULONG] - Release: Callable[[], ULONG] - Next: Callable[ - [_Pointer[IEnumShellItems], c_ulong, IShellItem, _Pointer[c_ulong]], HRESULT - ] - Skip: Callable[[_Pointer[IEnumShellItems], c_ulong], HRESULT] - Reset: Callable[[_Pointer[IEnumShellItems]], HRESULT] - Clone: Callable[ - [_Pointer[IEnumShellItems], _Pointer[_Pointer[IEnumShellItems]]], HRESULT - ] - - -IEnumShellItems._methods_ = [ # noqa: SLF001 - COMMETHOD( - [], - HRESULT, - "Next", - (["in"], c_ulong, "celt"), - (["out"], POINTER(POINTER(IShellItem)), "rgelt"), - (["out"], POINTER(c_ulong), "pceltFetched"), - ), - COMMETHOD([], HRESULT, "Skip", (["in"], c_ulong, "celt")), - COMMETHOD([], HRESULT, "Reset"), - COMMETHOD( - [], HRESULT, "Clone", (["out"], POINTER(POINTER(IEnumShellItems)), "ppenum") - ), -] - - -class IPropertyStore(IUnknown): - _case_insensitive_: bool = True - _iid_: GUID = IID_IPropertyStore - _methods_: ClassVar[list[_ComMemberSpec]] = [ - COMMETHOD([], HRESULT, "GetCount", (["out"], POINTER(c_ulong), "count")), - COMMETHOD( - [], - HRESULT, - "GetAt", - (["in"], c_ulong, "index"), - (["out"], POINTER(GUID), "key"), - ), - COMMETHOD( - [], - HRESULT, - "GetValue", - (["in"], POINTER(GUID), "key"), - (["out"], POINTER(c_void_p), "pv"), - ), - COMMETHOD( - [], - HRESULT, - "SetValue", - (["in"], POINTER(GUID), "key"), - (["in"], POINTER(c_void_p), "propvar"), - ), - COMMETHOD([], HRESULT, "Commit"), - ] - QueryInterface: Callable[[GUID, IUnknown], HRESULT] - AddRef: Callable[[], ULONG] - Release: Callable[[], ULONG] - GetCount: Callable[[_Pointer[IPropertyStore], _Pointer[c_ulong]], HRESULT] - GetAt: Callable[[_Pointer[IPropertyStore], c_ulong, GUID], HRESULT] - GetValue: Callable[[_Pointer[IPropertyStore], GUID, _Pointer[c_void_p]], HRESULT] - SetValue: Callable[[_Pointer[IPropertyStore], GUID, _Pointer[c_void_p]], HRESULT] - Commit: Callable[[_Pointer[IPropertyStore]], HRESULT] - - -class IFileOperationProgressSink(IUnknown): - _case_insensitive_: bool = True - _iid_: GUID = IID_IFileOperationProgressSink - _methods_: ClassVar[list[_ComMemberSpec]] = [ - COMMETHOD([], HRESULT, "StartOperations"), - COMMETHOD([], HRESULT, "FinishOperations", (["in"], HRESULT, "hr")), - COMMETHOD( - [], - HRESULT, - "PreRenameItem", - (["in"], c_ulong, "dwFlags"), - (["in"], POINTER(IShellItem), "psiItem"), - (["in"], c_wchar_p, "pszNewName"), - ), - COMMETHOD( - [], - HRESULT, - "PostRenameItem", - (["in"], c_ulong, "dwFlags"), - (["in"], POINTER(IShellItem), "psiItem"), - (["in"], c_wchar_p, "pszNewName"), - (["in"], HRESULT, "hrRename"), - (["in"], POINTER(IShellItem), "psiNewlyCreated"), - ), - COMMETHOD( - [], - HRESULT, - "PreMoveItem", - (["in"], c_ulong, "dwFlags"), - (["in"], POINTER(IShellItem), "psiItem"), - (["in"], POINTER(IShellItem), "psiDestinationFolder"), - (["in"], c_wchar_p, "pszNewName"), - ), - COMMETHOD( - [], - HRESULT, - "PostMoveItem", - (["in"], c_ulong, "dwFlags"), - (["in"], POINTER(IShellItem), "psiItem"), - (["in"], POINTER(IShellItem), "psiDestinationFolder"), - (["in"], c_wchar_p, "pszNewName"), - (["in"], HRESULT, "hrMove"), - (["in"], POINTER(IShellItem), "psiNewlyCreated"), - ), - COMMETHOD( - [], - HRESULT, - "PreCopyItem", - (["in"], c_ulong, "dwFlags"), - (["in"], POINTER(IShellItem), "psiItem"), - (["in"], POINTER(IShellItem), "psiDestinationFolder"), - (["in"], c_wchar_p, "pszNewName"), - ), - COMMETHOD( - [], - HRESULT, - "PostCopyItem", - (["in"], c_ulong, "dwFlags"), - (["in"], POINTER(IShellItem), "psiItem"), - (["in"], POINTER(IShellItem), "psiDestinationFolder"), - (["in"], c_wchar_p, "pszNewName"), - (["in"], HRESULT, "hrCopy"), - (["in"], POINTER(IShellItem), "psiNewlyCreated"), - ), - COMMETHOD( - [], - HRESULT, - "PreDeleteItem", - (["in"], c_ulong, "dwFlags"), - (["in"], POINTER(IShellItem), "psiItem"), - ), - COMMETHOD( - [], - HRESULT, - "PostDeleteItem", - (["in"], c_ulong, "dwFlags"), - (["in"], POINTER(IShellItem), "psiItem"), - (["in"], HRESULT, "hrDelete"), - (["in"], POINTER(IShellItem), "psiNewlyCreated"), - ), - COMMETHOD( - [], - HRESULT, - "PreNewItem", - (["in"], c_ulong, "dwFlags"), - (["in"], POINTER(IShellItem), "psiDestinationFolder"), - (["in"], c_wchar_p, "pszNewName"), - ), - COMMETHOD( - [], - HRESULT, - "PostNewItem", - (["in"], c_ulong, "dwFlags"), - (["in"], POINTER(IShellItem), "psiDestinationFolder"), - (["in"], c_wchar_p, "pszNewName"), - (["in"], c_wchar_p, "pszTemplateName"), - (["in"], c_ulong, "dwFileAttributes"), - (["in"], HRESULT, "hrNew"), - (["in"], POINTER(IShellItem), "psiNewItem"), - ), - COMMETHOD( - [], - HRESULT, - "UpdateProgress", - (["in"], c_ulong, "iWorkTotal"), - (["in"], c_ulong, "iWorkSoFar"), - ), - COMMETHOD([], HRESULT, "ResetTimer"), - COMMETHOD([], HRESULT, "PauseTimer"), - COMMETHOD([], HRESULT, "ResumeTimer"), - ] - QueryInterface: Callable[[GUID, IUnknown], HRESULT] - AddRef: Callable[[], ULONG] - Release: Callable[[], ULONG] - StartOperations: Callable[[], HRESULT] - FinishOperations: Callable[[HRESULT], HRESULT] - PreRenameItem: Callable[[c_ulong, IShellItem, c_wchar_p], HRESULT] - PostRenameItem: Callable[ - [c_ulong, IShellItem, c_wchar_p, HRESULT, IShellItem], HRESULT - ] - PreMoveItem: Callable[[c_ulong, IShellItem, IShellItem, c_wchar_p], HRESULT] - PostMoveItem: Callable[ - [c_ulong, IShellItem, IShellItem, c_wchar_p, HRESULT, IShellItem], HRESULT - ] - PreCopyItem: Callable[[c_ulong, IShellItem, IShellItem, c_wchar_p], HRESULT] - PostCopyItem: Callable[ - [c_ulong, IShellItem, IShellItem, c_wchar_p, HRESULT, IShellItem], HRESULT - ] - PreDeleteItem: Callable[[c_ulong, IShellItem], HRESULT] - PostDeleteItem: Callable[[c_ulong, IShellItem, HRESULT, IShellItem], HRESULT] - PreNewItem: Callable[[c_ulong, IShellItem, c_wchar_p], HRESULT] - PostNewItem: Callable[ - [c_ulong, IShellItem, c_wchar_p, c_wchar_p, c_ulong, HRESULT, IShellItem], - HRESULT, - ] - UpdateProgress: Callable[[c_ulong, c_ulong], HRESULT] - ResetTimer: Callable[[], HRESULT] - PauseTimer: Callable[[], HRESULT] - ResumeTimer: Callable[[], HRESULT] - - -class FileOperationProgressSink(COMObject): - _com_interfaces_: Sequence[type[IUnknown]] = [IFileOperationProgressSink] - - def StartOperations(self) -> HRESULT: - return S_OK - - def FinishOperations(self, hr: HRESULT) -> HRESULT: - return S_OK - - def PreRenameItem( - self, dwFlags: c_ulong | int, psiItem: IShellItem, pszNewName: LPCWSTR | str - ) -> HRESULT: # noqa: N803 - return S_OK - - def PostRenameItem( - self, - dwFlags: c_ulong | int, - psiItem: IShellItem, - pszNewName: LPCWSTR | str, - hrRename: HRESULT, - psiNewlyCreated: IShellItem, - ) -> HRESULT: # noqa: N803 - return S_OK - - def PreMoveItem( - self, - dwFlags: c_ulong | int, - psiItem: IShellItem, - psiDestinationFolder: IShellItem, - pszNewName: LPCWSTR | str, - ) -> HRESULT: # noqa: N803 - return S_OK - - def PostMoveItem( - self, - dwFlags: c_ulong | int, - psiItem: IShellItem, - psiDestinationFolder: IShellItem, - pszNewName: LPCWSTR | str, - hrMove: HRESULT, - psiNewlyCreated: IShellItem, - ) -> HRESULT: # noqa: N803, E501 - return S_OK - - def PreCopyItem( - self, - dwFlags: c_ulong | int, - psiItem: IShellItem, - psiDestinationFolder: IShellItem, - pszNewName: LPCWSTR | str, - ) -> HRESULT: # noqa: N803 - return S_OK - - def PostCopyItem( - self, - dwFlags: c_ulong | int, - psiItem: IShellItem, - psiDestinationFolder: IShellItem, - pszNewName: LPCWSTR | str, - hrCopy: HRESULT, - psiNewlyCreated: IShellItem, - ) -> HRESULT: # noqa: N803, E501 - return S_OK - - def PreDeleteItem(self, dwFlags: c_ulong | int, psiItem: IShellItem) -> HRESULT: - return S_OK - - def PostDeleteItem( - self, - dwFlags: c_ulong | int, - psiItem: IShellItem, - hrDelete: HRESULT, - psiNewlyCreated: IShellItem, - ) -> HRESULT: - return S_OK - - def PreNewItem( - self, - dwFlags: c_ulong | int, - psiDestinationFolder: IShellItem, - pszNewName: LPCWSTR | str, - ) -> HRESULT: - return S_OK - - def PostNewItem( - self, - dwFlags: c_ulong | int, - psiDestinationFolder: IShellItem, - pszNewName: LPCWSTR | str, - pszTemplateName: LPCWSTR | str, - dwFileAttributes: c_ulong | int, - hrNew: HRESULT, - psiNewItem: IShellItem, - ) -> HRESULT: # noqa: N803, E501 - return S_OK - - def UpdateProgress( - self, iWorkTotal: c_ulong | int, iWorkSoFar: c_ulong | int - ) -> HRESULT: # noqa: N803 - return S_OK - - def ResetTimer(self) -> HRESULT: - return S_OK - - def PauseTimer(self) -> HRESULT: - return S_OK - - def ResumeTimer(self) -> HRESULT: - return S_OK - - -class IFileOperation(IUnknown): - _case_insensitive_ = True - _iid_ = IID_IFileOperation - _idlflags_ = [] - - _methods_: ClassVar[list[_ComMemberSpec]] = [ - # Advise methods - comtypes.COMMETHOD( - [], - HRESULT, - "Advise", - (["in"], POINTER(IFileOperationProgressSink), "pfops"), - (["out"], POINTER(c_ulong), "pdwCookie"), - ), - comtypes.COMMETHOD([], HRESULT, "Unadvise", (["in"], c_ulong, "dwCookie")), - # Operation control methods - comtypes.COMMETHOD( - [], HRESULT, "SetOperationFlags", (["in"], c_ulong, "dwOperationFlags") - ), - comtypes.COMMETHOD( - [], HRESULT, "SetProgressMessage", (["in"], c_wchar_p, "pszMessage") - ), - comtypes.COMMETHOD( - [], - HRESULT, - "SetProgressDialog", - (["in"], POINTER(IUnknown), "popd"), - ), - # Item methods - comtypes.COMMETHOD( - [], - HRESULT, - "SetProperties", - (["in"], POINTER(IUnknown), "pproparray"), - ), - comtypes.COMMETHOD( - [], HRESULT, "SetOwnerWindow", (["in"], c_ulong, "hwndOwner") - ), - comtypes.COMMETHOD( - [], - HRESULT, - "ApplyPropertiesToItem", - (["in"], POINTER(IShellItem), "psiItem"), - ), - comtypes.COMMETHOD( - [], - HRESULT, - "ApplyPropertiesToItems", - (["in"], POINTER(IUnknown), "punkItems"), - ), - # Operation methods - comtypes.COMMETHOD( - [], - HRESULT, - "RenameItem", - (["in"], POINTER(IShellItem), "psiItem"), - (["in"], c_wchar_p, "pszNewName"), - (["in"], POINTER(IFileOperationProgressSink), "pfopsItem"), - ), - comtypes.COMMETHOD( - [], - HRESULT, - "RenameItems", - (["in"], POINTER(IUnknown), "pUnkItems"), - (["in"], c_wchar_p, "pszNewName"), - ), - comtypes.COMMETHOD( - [], - HRESULT, - "MoveItem", - (["in"], POINTER(IShellItem), "psiItem"), - (["in"], POINTER(IShellItem), "psiDestinationFolder"), - (["in"], c_wchar_p, "pszNewName"), - (["in"], POINTER(IFileOperationProgressSink), "pfopsItem"), - ), - comtypes.COMMETHOD( - [], - HRESULT, - "MoveItems", - (["in"], POINTER(IUnknown), "punkItems"), - (["in"], POINTER(IShellItem), "psiDestinationFolder"), - ), - comtypes.COMMETHOD( - [], - HRESULT, - "CopyItem", - (["in"], POINTER(IShellItem), "psiItem"), - (["in"], POINTER(IShellItem), "psiDestinationFolder"), - (["in"], c_wchar_p, "pszNewName"), - (["in"], POINTER(IFileOperationProgressSink), "pfopsItem"), - ), - comtypes.COMMETHOD( - [], - HRESULT, - "CopyItems", - (["in"], POINTER(IUnknown), "punkItems"), - (["in"], POINTER(IShellItem), "psiDestinationFolder"), - ), - comtypes.COMMETHOD( - [], - HRESULT, - "DeleteItem", - (["in"], POINTER(IShellItem), "psiItem"), - (["in"], POINTER(IFileOperationProgressSink), "pfopsItem"), - ), - comtypes.COMMETHOD( - [], - HRESULT, - "DeleteItems", - (["in"], POINTER(IUnknown), "punkItems"), - ), - comtypes.COMMETHOD( - [], - HRESULT, - "NewItem", - (["in"], POINTER(IShellItem), "psiDestinationFolder"), - (["in"], c_ulong, "dwFileAttributes"), - (["in"], c_wchar_p, "pszName"), - (["in"], c_wchar_p, "pszTemplateName"), - (["in"], POINTER(IFileOperationProgressSink), "pfopsItem"), - ), - # Execution methods - comtypes.COMMETHOD([], HRESULT, "PerformOperations"), - comtypes.COMMETHOD( - [], - HRESULT, - "GetAnyOperationsAborted", - (["out"], POINTER(c_int), "pfAnyOperationsAborted"), - ), - ] - - -class IFileDialogEvents(IUnknown): - _case_insensitive_: bool = True - _iid_: GUID = IID_IFileDialogEvents - _methods_: ClassVar[list[_ComMemberSpec]] - QueryInterface: Callable[[GUID, IUnknown], HRESULT] - AddRef: Callable[[], ULONG] - Release: Callable[[], ULONG] - OnFileOk: Callable[[IFileDialog], HRESULT] - OnFolderChanging: Callable[[IFileDialog, IShellItem], HRESULT] - OnFolderChange: Callable[[IFileDialog], HRESULT] - OnSelectionChange: Callable[[IFileDialog], HRESULT] - OnShareViolation: Callable[[IFileDialog, IShellItem, c_int], HRESULT] - OnTypeChange: Callable[[IFileDialog], HRESULT] - OnOverwrite: Callable[[IFileDialog, IShellItem, c_int], HRESULT] + GetEnumFlagsForItem: Callable[[], int] class IFileDialog(IModalWindow): @@ -831,243 +172,89 @@ class IFileDialog(IModalWindow): HRESULT, "SetFileTypes", (["in"], c_uint, "cFileTypes"), - (["in"], POINTER(c_void_p), "rgFilterSpec"), + (["in"], C_POINTER(c_void_p), "rgFilterSpec"), ), COMMETHOD([], HRESULT, "SetFileTypeIndex", (["in"], c_uint, "iFileType")), COMMETHOD( - [], HRESULT, "GetFileTypeIndex", (["out"], POINTER(c_uint), "piFileType") + [], HRESULT, "GetFileTypeIndex", (["out"], C_POINTER(c_uint), "piFileType") ), COMMETHOD( [], HRESULT, "Advise", - (["in"], POINTER(IUnknown), "pfde"), - (["out"], POINTER(DWORD), "pdwCookie"), + (["in"], C_POINTER(IUnknown), "pfde"), + (["out"], C_POINTER(DWORD), "pdwCookie"), ), COMMETHOD([], HRESULT, "Unadvise", (["in"], DWORD, "dwCookie")), COMMETHOD([], HRESULT, "SetOptions", (["in"], c_uint, "fos")), - COMMETHOD([], HRESULT, "GetOptions", (["out"], POINTER(DWORD), "pfos")), + COMMETHOD([], HRESULT, "GetOptions", (["out"], C_POINTER(DWORD), "pfos")), COMMETHOD( - [], HRESULT, "SetDefaultFolder", (["in"], POINTER(IShellItem), "psi") + [], HRESULT, "SetDefaultFolder", (["in"], C_POINTER(IShellItem), "psi") ), - COMMETHOD([], HRESULT, "SetFolder", (["in"], POINTER(IShellItem), "psi")), + COMMETHOD([], HRESULT, "SetFolder", (["in"], C_POINTER(IShellItem), "psi")), COMMETHOD( - [], HRESULT, "GetFolder", (["out"], POINTER(POINTER(IShellItem)), "ppsi") + [], + HRESULT, + "GetFolder", + (["out"], C_POINTER(C_POINTER(IShellItem)), "ppsi"), ), COMMETHOD( [], HRESULT, "GetCurrentSelection", - (["out"], POINTER(POINTER(IShellItem)), "ppsi"), + (["out"], C_POINTER(C_POINTER(IShellItem)), "ppsi"), ), COMMETHOD([], HRESULT, "SetFileName", (["in"], LPCWSTR, "pszName")), - COMMETHOD([], HRESULT, "GetFileName", (["out"], POINTER(LPWSTR), "pszName")), + COMMETHOD([], HRESULT, "GetFileName", (["out"], C_POINTER(LPWSTR), "pszName")), COMMETHOD([], HRESULT, "SetTitle", (["in"], LPCWSTR, "pszTitle")), COMMETHOD([], HRESULT, "SetOkButtonLabel", (["in"], LPCWSTR, "pszText")), COMMETHOD([], HRESULT, "SetFileNameLabel", (["in"], LPCWSTR, "pszLabel")), COMMETHOD( - [], HRESULT, "GetResult", (["out"], POINTER(POINTER(IShellItem)), "ppsi") + [], + HRESULT, + "GetResult", + (["out"], C_POINTER(C_POINTER(IShellItem)), "ppsi"), ), COMMETHOD( [], HRESULT, "AddPlace", - (["in"], POINTER(IShellItem), "psi"), + (["in"], C_POINTER(IShellItem), "psi"), (["in"], c_int, "fdap"), ), COMMETHOD( [], HRESULT, "SetDefaultExtension", (["in"], LPCWSTR, "pszDefaultExtension") ), COMMETHOD([], HRESULT, "Close", (["in"], HRESULT, "hr")), - COMMETHOD([], HRESULT, "SetClientGuid", (["in"], POINTER(GUID), "guid")), + COMMETHOD([], HRESULT, "SetClientGuid", (["in"], C_POINTER(GUID), "guid")), COMMETHOD([], HRESULT, "ClearClientData"), COMMETHOD( - [], HRESULT, "SetFilter", (["in"], POINTER(IShellItemFilter), "pFilter") + [], HRESULT, "SetFilter", (["in"], C_POINTER(IShellItemFilter), "pFilter") ), ] - SetFileTypes: Callable[[c_uint | int, _Pointer[c_void_p]], HRESULT] - SetFileTypeIndex: Callable[[c_uint], HRESULT] + SetFileTypes: Callable[[c_uint | int, _Pointer[c_void_p]], int] + SetFileTypeIndex: Callable[[c_uint], int] GetFileTypeIndex: Callable[[], _Pointer[c_uint]] Advise: Callable[[IUnknown | COMObject], int] - Unadvise: Callable[[int], HRESULT] - SetOptions: Callable[[DWORD | int], HRESULT] + Unadvise: Callable[[int], int] + SetOptions: Callable[[DWORD | int], int] GetOptions: Callable[[], int] - SetDefaultFolder: Callable[[_Pointer[IShellItem]], HRESULT] - SetFolder: Callable[[_Pointer[IShellItem]], HRESULT] + SetDefaultFolder: Callable[[_Pointer[IShellItem]], int] + SetFolder: Callable[[_Pointer[IShellItem]], int] GetFolder: Callable[[], IShellItem] GetCurrentSelection: Callable[[], IShellItem] - SetFileName: Callable[[str], HRESULT] + SetFileName: Callable[[str], int] GetFileName: Callable[[], _Pointer[LPWSTR]] - SetTitle: Callable[[str], HRESULT] - SetOkButtonLabel: Callable[[str], HRESULT] - SetFileNameLabel: Callable[[str], HRESULT] + SetTitle: Callable[[str], int] + SetOkButtonLabel: Callable[[str], int] + SetFileNameLabel: Callable[[str], int] GetResult: Callable[[], IShellItem] - AddPlace: Callable[[IShellItem, c_int], HRESULT] - SetDefaultExtension: Callable[[str], HRESULT] - Close: Callable[[HRESULT], HRESULT] - SetClientGuid: Callable[[GUID], HRESULT] - ClearClientData: Callable[[], HRESULT] - SetFilter: Callable[[IShellItemFilter], HRESULT] - - -IFileDialogEvents._methods_ = [ # noqa: SLF001 - COMMETHOD([], HRESULT, "OnFileOk", (["in"], POINTER(IFileDialog), "pfd")), - COMMETHOD( - [], - HRESULT, - "OnFolderChanging", - (["in"], POINTER(IFileDialog), "pfd"), - (["in"], POINTER(IShellItem), "psiFolder"), - ), - COMMETHOD([], HRESULT, "OnFolderChange", (["in"], POINTER(IFileDialog), "pfd")), - COMMETHOD([], HRESULT, "OnSelectionChange", (["in"], POINTER(IFileDialog), "pfd")), - COMMETHOD( - [], - HRESULT, - "OnShareViolation", - (["in"], POINTER(IFileDialog), "pfd"), - (["in"], POINTER(IShellItem), "psi"), - (["out"], POINTER(c_int), "pResponse"), - ), - COMMETHOD([], HRESULT, "OnTypeChange", (["in"], POINTER(IFileDialog), "pfd")), - COMMETHOD( - [], - HRESULT, - "OnOverwrite", - (["in"], POINTER(IFileDialog), "pfd"), - (["in"], POINTER(IShellItem), "psi"), - (["out"], POINTER(c_int), "pResponse"), - ), -] - - -class IShellLibrary(IUnknown): - _case_insensitive_: bool = True - _iid_: GUID = IID_IShellLibrary - _methods_: ClassVar[list[_ComMemberSpec]] = [ - COMMETHOD( - [], - HRESULT, - "LoadLibraryFromItem", - (["in"], POINTER(IShellItem), "psi"), - (["in"], c_ulong, "grfMode"), - ), - COMMETHOD( - [], - HRESULT, - "LoadLibraryFromKnownFolder", - (["in"], POINTER(GUID), "kfidLibrary"), - (["in"], c_ulong, "grfMode"), - ), - COMMETHOD([], HRESULT, "AddFolder", (["in"], POINTER(IShellItem), "psi")), - COMMETHOD([], HRESULT, "RemoveFolder", (["in"], POINTER(IShellItem), "psi")), - COMMETHOD( - [], - HRESULT, - "GetFolders", - (["in"], c_int, "lff"), - (["in"], POINTER(GUID), "riid"), - (["out"], POINTER(POINTER(c_void_p)), "ppv"), - ), - COMMETHOD( - [], - HRESULT, - "ResolveFolder", - (["in"], POINTER(IShellItem), "psi"), - (["in"], c_ulong, "grfMode"), - (["in"], POINTER(GUID), "riid"), - (["out"], POINTER(POINTER(c_void_p)), "ppv"), - ), - COMMETHOD( - [], - HRESULT, - "GetDefaultSaveFolder", - (["in"], c_int, "dsft"), - (["in"], POINTER(GUID), "riid"), - (["out"], POINTER(POINTER(c_void_p)), "ppv"), - ), - COMMETHOD( - [], - HRESULT, - "SetDefaultSaveFolder", - (["in"], c_int, "dsft"), - (["in"], POINTER(IShellItem), "psi"), - ), - COMMETHOD([], HRESULT, "GetOptions", (["out"], POINTER(c_uint), "pOptions")), - COMMETHOD( - [], - HRESULT, - "SetOptions", - (["in"], c_ulong, "stfOptions"), - (["in"], c_ulong, "stfMask"), - ), - COMMETHOD([], HRESULT, "GetFolderType", (["out"], POINTER(GUID), "pftid")), - COMMETHOD([], HRESULT, "SetFolderType", (["in"], POINTER(GUID), "ftid")), - COMMETHOD([], HRESULT, "GetIcon", (["out"], POINTER(LPWSTR), "ppszIcon")), - COMMETHOD([], HRESULT, "SetIcon", (["in"], LPCWSTR, "pszIcon")), - COMMETHOD([], HRESULT, "Commit"), - COMMETHOD( - [], - HRESULT, - "Save", - (["in"], POINTER(IShellItem), "psiFolderToSaveIn"), - (["in"], LPCWSTR, "pszLibraryName"), - (["in"], c_ulong, "lrf"), - (["out"], POINTER(POINTER(IShellItem)), "ppsiNewItem"), - ), - COMMETHOD( - [], - HRESULT, - "SaveInKnownFolder", - (["in"], POINTER(GUID), "kfid"), - (["in"], LPCWSTR, "pszLibraryName"), - (["in"], c_ulong, "lrf"), - (["out"], POINTER(POINTER(IShellItem)), "ppsiNewItem"), - ), - ] - QueryInterface: Callable[[GUID, IUnknown], HRESULT] - AddRef: Callable[[], ULONG] - Release: Callable[[], ULONG] - LoadLibraryFromItem: Callable[ - [_Pointer[IShellLibrary], IShellItem, c_ulong], HRESULT - ] - LoadLibraryFromKnownFolder: Callable[ - [_Pointer[IShellLibrary], GUID, c_ulong], HRESULT - ] - AddFolder: Callable[[_Pointer[IShellLibrary], IShellItem], HRESULT] - RemoveFolder: Callable[[_Pointer[IShellLibrary], IShellItem], HRESULT] - GetFolders: Callable[ - [_Pointer[IShellLibrary], c_int, GUID, _Pointer[_Pointer[c_void_p]]], HRESULT - ] - ResolveFolder: Callable[ - [ - _Pointer[IShellLibrary], - IShellItem, - c_ulong, - GUID, - _Pointer[_Pointer[c_void_p]], - ], - HRESULT, - ] - GetDefaultSaveFolder: Callable[ - [_Pointer[IShellLibrary], c_int, GUID, _Pointer[_Pointer[c_void_p]]], HRESULT - ] - SetDefaultSaveFolder: Callable[ - [_Pointer[IShellLibrary], c_int, IShellItem], HRESULT - ] - GetOptions: Callable[[_Pointer[IShellLibrary], _Pointer[c_uint]], HRESULT] - SetOptions: Callable[[_Pointer[IShellLibrary], c_ulong, c_ulong], HRESULT] - GetFolderType: Callable[[_Pointer[IShellLibrary], GUID], HRESULT] - SetFolderType: Callable[[_Pointer[IShellLibrary], GUID], HRESULT] - GetIcon: Callable[[_Pointer[IShellLibrary], _Pointer[LPWSTR]], HRESULT] - SetIcon: Callable[[_Pointer[IShellLibrary], LPCWSTR], HRESULT] - Commit: Callable[[_Pointer[IShellLibrary]], HRESULT] - Save: Callable[ - [_Pointer[IShellLibrary], IShellItem, LPCWSTR, c_ulong, IShellItem], HRESULT - ] - SaveInKnownFolder: Callable[ - [_Pointer[IShellLibrary], GUID, LPCWSTR, c_ulong, IShellItem], HRESULT - ] + AddPlace: Callable[[IShellItem, c_int], int] + SetDefaultExtension: Callable[[str], int] + Close: Callable[[HRESULT], int] + SetClientGuid: Callable[[GUID], int] + ClearClientData: Callable[[], int] + SetFilter: Callable[[IShellItemFilter], int] class IFileOpenDialog(IFileDialog): @@ -1078,13 +265,13 @@ class IFileOpenDialog(IFileDialog): [], HRESULT, "GetResults", - (["out"], POINTER(POINTER(IShellItemArray)), "ppenum"), + (["out"], C_POINTER(C_POINTER(IShellItemArray)), "ppenum"), ), COMMETHOD( [], HRESULT, "GetSelectedItems", - (["out"], POINTER(POINTER(IShellItemArray)), "ppsai"), + (["out"], C_POINTER(C_POINTER(IShellItemArray)), "ppsai"), ), ] GetResults: Callable[[], IShellItemArray] @@ -1095,262 +282,37 @@ class IFileSaveDialog(IFileDialog): _case_insensitive_: bool = True _iid_: GUID = IID_IFileSaveDialog _methods_: ClassVar[list[_ComMemberSpec]] = [ - COMMETHOD([], HRESULT, "SetSaveAsItem", (["in"], POINTER(IShellItem), "psi")), - COMMETHOD([], HRESULT, "SetProperties", (["in"], POINTER(IUnknown), "pStore")), + COMMETHOD([], HRESULT, "SetSaveAsItem", (["in"], C_POINTER(IShellItem), "psi")), + COMMETHOD( + [], HRESULT, "SetProperties", (["in"], C_POINTER(IUnknown), "pStore") + ), COMMETHOD( [], HRESULT, "SetCollectedProperties", - (["in"], POINTER(IUnknown), "pList"), + (["in"], C_POINTER(IUnknown), "pList"), (["in"], BOOL, "fAppendDefault"), ), COMMETHOD( [], HRESULT, "GetProperties", - (["out"], POINTER(POINTER(IUnknown)), "ppStore"), + (["out"], C_POINTER(C_POINTER(IUnknown)), "ppStore"), ), COMMETHOD( [], HRESULT, "ApplyProperties", - (["in"], POINTER(IShellItem), "psi"), - (["in"], POINTER(IUnknown), "pStore"), + (["in"], C_POINTER(IShellItem), "psi"), + (["in"], C_POINTER(IUnknown), "pStore"), (["in"], HWND, "hwnd"), - (["in"], POINTER(IUnknown), "pSink"), + (["in"], C_POINTER(IUnknown), "pSink"), ), ] - SetSaveAsItem: Callable[[IShellItem], HRESULT] - SetProperties: Callable[[_Pointer[IUnknown]], HRESULT] - SetCollectedProperties: Callable[[_Pointer[IUnknown], BOOL], HRESULT] - GetProperties: Callable[[_Pointer[_Pointer[IUnknown]]], HRESULT] + SetSaveAsItem: Callable[[IShellItem], int] + SetProperties: Callable[[_Pointer[IUnknown]], int] + SetCollectedProperties: Callable[[_Pointer[IUnknown], int], int] + GetProperties: Callable[[_Pointer[_Pointer[IUnknown]]], int] ApplyProperties: Callable[ - [IShellItem, _Pointer[IUnknown], HWND, _Pointer[IUnknown]], HRESULT - ] - - -class IFileDialogCustomize(IUnknown): - _case_insensitive_ = True - _iid_: GUID = IID_IFileDialogCustomize - _methods_: ClassVar[list[_ComMemberSpec]] = [ - COMMETHOD([], HRESULT, "EnableOpenDropDown", (["in"], c_uint, "dwIDCtl")), - COMMETHOD( - [], - HRESULT, - "AddText", - (["in"], c_uint, "dwIDCtl"), - (["in"], LPCWSTR, "pszText"), - ), - COMMETHOD( - [], - HRESULT, - "AddPushButton", - (["in"], c_uint, "dwIDCtl"), - (["in"], LPCWSTR, "pszLabel"), - ), - COMMETHOD( - [], - HRESULT, - "AddCheckButton", - (["in"], c_uint, "dwIDCtl"), - (["in"], LPCWSTR, "pszLabel"), - (["in"], c_int, "bChecked"), - ), - COMMETHOD([], HRESULT, "AddRadioButtonList", (["in"], c_uint, "dwIDCtl")), - COMMETHOD([], HRESULT, "AddComboBox", (["in"], c_uint, "dwIDCtl")), - COMMETHOD( - [], - HRESULT, - "AddControlItem", - (["in"], c_uint, "dwIDCtl"), - (["in"], c_uint, "dwIDItem"), - (["in"], LPCWSTR, "pszLabel"), - ), - COMMETHOD( - [], - HRESULT, - "AddEditBox", - (["in"], c_uint, "dwIDCtl"), - (["in"], LPCWSTR, "pszText"), - ), - COMMETHOD([], HRESULT, "AddSeparator", (["in"], c_uint, "dwIDCtl")), - COMMETHOD( - [], - HRESULT, - "AddMenu", - (["in"], c_uint, "dwIDCtl"), - (["in"], LPCWSTR, "pszLabel"), - ), - COMMETHOD( - [], - HRESULT, - "SetControlLabel", - (["in"], c_uint, "dwIDCtl"), - (["in"], LPCWSTR, "pszLabel"), - ), - COMMETHOD( - [], - HRESULT, - "SetControlState", - (["in"], c_uint, "dwIDCtl"), - (["in"], c_int, "dwState"), - ), - COMMETHOD( - [], - HRESULT, - "SetCheckButtonState", - (["in"], c_uint, "dwIDCtl"), - (["in"], c_int, "bChecked"), - ), - COMMETHOD( - [], - HRESULT, - "GetCheckButtonState", - (["in"], c_uint, "dwIDCtl"), - (["out"], POINTER(c_int), "pbChecked"), - ), - COMMETHOD( - [], - HRESULT, - "SetEditBoxText", - (["in"], c_uint, "dwIDCtl"), - (["in"], LPCWSTR, "pszText"), - ), - COMMETHOD( - [], - HRESULT, - "GetEditBoxText", - (["in"], c_uint, "dwIDCtl"), - (["out"], POINTER(LPCWSTR), "ppszText"), - ), - COMMETHOD( - [], - HRESULT, - "SetControlItemText", - (["in"], c_uint, "dwIDCtl"), - (["in"], c_uint, "dwIDItem"), - (["in"], LPCWSTR, "pszLabel"), - ), - COMMETHOD( - [], - HRESULT, - "GetControlItemState", - (["in"], c_uint, "dwIDCtl"), - (["in"], c_uint, "dwIDItem"), - (["out"], POINTER(c_int), "pdwState"), - ), - COMMETHOD( - [], - HRESULT, - "SetControlItemState", - (["in"], c_uint, "dwIDCtl"), - (["in"], c_uint, "dwIDItem"), - (["in"], c_int, "dwState"), - ), - COMMETHOD( - [], - HRESULT, - "GetSelectedControlItem", - (["in"], c_uint, "dwIDCtl"), - (["out"], POINTER(c_uint), "pdwIDItem"), - ), - COMMETHOD( - [], - HRESULT, - "SetSelectedControlItem", - (["in"], c_uint, "dwIDCtl"), - (["in"], c_uint, "dwIDItem"), - ), - COMMETHOD( - [], - HRESULT, - "StartVisualGroup", - (["in"], c_uint, "dwIDCtl"), - (["in"], LPCWSTR, "pszLabel"), - ), - COMMETHOD([], HRESULT, "EndVisualGroup", (["in"], c_uint, "dwIDCtl")), - COMMETHOD([], HRESULT, "MakeProminent", (["in"], c_uint, "dwIDCtl")), - COMMETHOD( - [], - HRESULT, - "RemoveControlItem", - (["in"], c_uint, "dwIDCtl"), - (["in"], c_uint, "dwIDItem"), - ), - COMMETHOD([], HRESULT, "RemoveAllControlItems", (["in"], c_uint, "dwIDCtl")), - COMMETHOD( - [], - HRESULT, - "GetControlState", - (["in"], c_uint, "dwIDCtl"), - (["out"], POINTER(c_int), "pdwState"), - ), - ] - EnableOpenDropDown: Callable[[int], HRESULT] - AddText: Callable[[int, str], HRESULT] - AddPushButton: Callable[[int, str], HRESULT] - AddCheckButton: Callable[[int, str, int], HRESULT] - AddRadioButtonList: Callable[[int], HRESULT] - AddComboBox: Callable[[int], HRESULT] - AddControlItem: Callable[[int, int, str], HRESULT] - AddEditBox: Callable[[int, str], HRESULT] - AddSeparator: Callable[[int], HRESULT] - AddMenu: Callable[[int, str], HRESULT] - SetControlLabel: Callable[[int, str], HRESULT] - SetControlState: Callable[[int, int], HRESULT] - SetCheckButtonState: Callable[[int, int], HRESULT] - GetCheckButtonState: Callable[[int], int] - SetEditBoxText: Callable[[int, str], HRESULT] - GetEditBoxText: Callable[[int], LPCWSTR] - SetControlItemText: Callable[[int, int, str], HRESULT] - GetControlItemState: Callable[[int, int], int] - SetControlItemState: Callable[[int, int, int], HRESULT] - GetSelectedControlItem: Callable[[int], int] - SetSelectedControlItem: Callable[[int, int], HRESULT] - StartVisualGroup: Callable[[int, str], HRESULT] - EndVisualGroup: Callable[[int], HRESULT] - MakeProminent: Callable[[int], HRESULT] - RemoveControlItem: Callable[[int, int], HRESULT] - RemoveAllControlItems: Callable[[int], HRESULT] - GetControlState: Callable[[int], int] - - -class IFileDialogControlEvents(IUnknown): - _case_insensitive_ = True - _iid_ = IID_IFileDialogControlEvents - _methods_: ClassVar[list[_ComMemberSpec]] = [ - COMMETHOD( - [], - HRESULT, - "OnItemSelected", - (["in"], comtypes.POINTER(IFileDialogCustomize), "pfdc"), - (["in"], c_int, "dwIDCtl"), - (["in"], c_int, "dwIDItem"), - ), - COMMETHOD( - [], - HRESULT, - "OnButtonClicked", - (["in"], comtypes.POINTER(IFileDialogCustomize), "pfdc"), - (["in"], c_int, "dwIDCtl"), - ), - COMMETHOD( - [], - HRESULT, - "OnCheckButtonToggled", - (["in"], comtypes.POINTER(IFileDialogCustomize), "pfdc"), - (["in"], c_int, "dwIDCtl"), - (["in"], c_bool, "bChecked"), - ), - COMMETHOD( - [], - HRESULT, - "OnControlActivating", - (["in"], comtypes.POINTER(IFileDialogCustomize), "pfdc"), - (["in"], c_int, "dwIDCtl"), - ), + [IShellItem, _Pointer[IUnknown], HWND, _Pointer[IUnknown]], int ] - OnButtonClicked: Callable[[IFileDialogCustomize, c_uint], HRESULT] - OnCheckButtonToggled: Callable[[IFileDialogCustomize, c_uint, c_int], HRESULT] - OnControlActivating: Callable[[IFileDialogCustomize, c_uint], HRESULT] - OnItemSelected: Callable[[IFileDialogCustomize, c_uint, c_uint], HRESULT] diff --git a/winforms/src/toga_winforms/libs/com/more_interfaces.py b/winforms/src/toga_winforms/libs/com/more_interfaces.py new file mode 100644 index 0000000000..d24cf7ae25 --- /dev/null +++ b/winforms/src/toga_winforms/libs/com/more_interfaces.py @@ -0,0 +1,293 @@ +from __future__ import annotations + +from ctypes import HRESULT, POINTER as C_POINTER, c_bool, c_int, c_uint +from ctypes.wintypes import LPCWSTR, ULONG +from typing import TYPE_CHECKING, Callable, ClassVar + +from comtypes import COMMETHOD, GUID, IUnknown + +from toga_winforms.libs.com.identifiers import ( + IID_IFileDialogControlEvents, + IID_IFileDialogCustomize, + IID_IFileDialogEvents, +) +from toga_winforms.libs.com.interfaces import IFileDialog, IShellItem + +if TYPE_CHECKING: + + from comtypes._memberspec import _ComMemberSpec + + +class IFileDialogEvents(IUnknown): + _case_insensitive_: bool = True + _iid_: GUID = IID_IFileDialogEvents + _methods_: ClassVar[list[_ComMemberSpec]] = [ # noqa: SLF001 + COMMETHOD([], HRESULT, "OnFileOk", (["in"], C_POINTER(IFileDialog), "pfd")), + COMMETHOD( + [], + HRESULT, + "OnFolderChanging", + (["in"], C_POINTER(IFileDialog), "pfd"), + (["in"], C_POINTER(IShellItem), "psiFolder"), + ), + COMMETHOD( + [], HRESULT, "OnFolderChange", (["in"], C_POINTER(IFileDialog), "pfd") + ), + COMMETHOD( + [], HRESULT, "OnSelectionChange", (["in"], C_POINTER(IFileDialog), "pfd") + ), + COMMETHOD( + [], + HRESULT, + "OnShareViolation", + (["in"], C_POINTER(IFileDialog), "pfd"), + (["in"], C_POINTER(IShellItem), "psi"), + (["out"], C_POINTER(c_int), "pResponse"), + ), + COMMETHOD([], HRESULT, "OnTypeChange", (["in"], C_POINTER(IFileDialog), "pfd")), + COMMETHOD( + [], + HRESULT, + "OnOverwrite", + (["in"], C_POINTER(IFileDialog), "pfd"), + (["in"], C_POINTER(IShellItem), "psi"), + (["out"], C_POINTER(c_int), "pResponse"), + ), + ] + QueryInterface: Callable[[GUID, IUnknown], int] + AddRef: Callable[[], ULONG] + Release: Callable[[], ULONG] + OnFileOk: Callable[[IFileDialog], int] + OnFolderChanging: Callable[[IFileDialog, IShellItem], int] + OnFolderChange: Callable[[IFileDialog], int] + OnSelectionChange: Callable[[IFileDialog], int] + OnShareViolation: Callable[[IFileDialog, IShellItem, c_int], int] + OnTypeChange: Callable[[IFileDialog], int] + OnOverwrite: Callable[[IFileDialog, IShellItem, c_int], int] + + +class IFileDialogCustomize(IUnknown): + _case_insensitive_ = True + _iid_: GUID = IID_IFileDialogCustomize + _methods_: ClassVar[list[_ComMemberSpec]] = [ + COMMETHOD([], HRESULT, "EnableOpenDropDown", (["in"], c_uint, "dwIDCtl")), + COMMETHOD( + [], + HRESULT, + "AddText", + (["in"], c_uint, "dwIDCtl"), + (["in"], LPCWSTR, "pszText"), + ), + COMMETHOD( + [], + HRESULT, + "AddPushButton", + (["in"], c_uint, "dwIDCtl"), + (["in"], LPCWSTR, "pszLabel"), + ), + COMMETHOD( + [], + HRESULT, + "AddCheckButton", + (["in"], c_uint, "dwIDCtl"), + (["in"], LPCWSTR, "pszLabel"), + (["in"], c_int, "bChecked"), + ), + COMMETHOD([], HRESULT, "AddRadioButtonList", (["in"], c_uint, "dwIDCtl")), + COMMETHOD([], HRESULT, "AddComboBox", (["in"], c_uint, "dwIDCtl")), + COMMETHOD( + [], + HRESULT, + "AddControlItem", + (["in"], c_uint, "dwIDCtl"), + (["in"], c_uint, "dwIDItem"), + (["in"], LPCWSTR, "pszLabel"), + ), + COMMETHOD( + [], + HRESULT, + "AddEditBox", + (["in"], c_uint, "dwIDCtl"), + (["in"], LPCWSTR, "pszText"), + ), + COMMETHOD([], HRESULT, "AddSeparator", (["in"], c_uint, "dwIDCtl")), + COMMETHOD( + [], + HRESULT, + "AddMenu", + (["in"], c_uint, "dwIDCtl"), + (["in"], LPCWSTR, "pszLabel"), + ), + COMMETHOD( + [], + HRESULT, + "SetControlLabel", + (["in"], c_uint, "dwIDCtl"), + (["in"], LPCWSTR, "pszLabel"), + ), + COMMETHOD( + [], + HRESULT, + "SetControlState", + (["in"], c_uint, "dwIDCtl"), + (["in"], c_int, "dwState"), + ), + COMMETHOD( + [], + HRESULT, + "SetCheckButtonState", + (["in"], c_uint, "dwIDCtl"), + (["in"], c_int, "bChecked"), + ), + COMMETHOD( + [], + HRESULT, + "GetCheckButtonState", + (["in"], c_uint, "dwIDCtl"), + (["out"], C_POINTER(c_int), "pbChecked"), + ), + COMMETHOD( + [], + HRESULT, + "SetEditBoxText", + (["in"], c_uint, "dwIDCtl"), + (["in"], LPCWSTR, "pszText"), + ), + COMMETHOD( + [], + HRESULT, + "GetEditBoxText", + (["in"], c_uint, "dwIDCtl"), + (["out"], C_POINTER(LPCWSTR), "ppszText"), + ), + COMMETHOD( + [], + HRESULT, + "SetControlItemText", + (["in"], c_uint, "dwIDCtl"), + (["in"], c_uint, "dwIDItem"), + (["in"], LPCWSTR, "pszLabel"), + ), + COMMETHOD( + [], + HRESULT, + "GetControlItemState", + (["in"], c_uint, "dwIDCtl"), + (["in"], c_uint, "dwIDItem"), + (["out"], C_POINTER(c_int), "pdwState"), + ), + COMMETHOD( + [], + HRESULT, + "SetControlItemState", + (["in"], c_uint, "dwIDCtl"), + (["in"], c_uint, "dwIDItem"), + (["in"], c_int, "dwState"), + ), + COMMETHOD( + [], + HRESULT, + "GetSelectedControlItem", + (["in"], c_uint, "dwIDCtl"), + (["out"], C_POINTER(c_uint), "pdwIDItem"), + ), + COMMETHOD( + [], + HRESULT, + "SetSelectedControlItem", + (["in"], c_uint, "dwIDCtl"), + (["in"], c_uint, "dwIDItem"), + ), + COMMETHOD( + [], + HRESULT, + "StartVisualGroup", + (["in"], c_uint, "dwIDCtl"), + (["in"], LPCWSTR, "pszLabel"), + ), + COMMETHOD([], HRESULT, "EndVisualGroup", (["in"], c_uint, "dwIDCtl")), + COMMETHOD([], HRESULT, "MakeProminent", (["in"], c_uint, "dwIDCtl")), + COMMETHOD( + [], + HRESULT, + "RemoveControlItem", + (["in"], c_uint, "dwIDCtl"), + (["in"], c_uint, "dwIDItem"), + ), + COMMETHOD([], HRESULT, "RemoveAllControlItems", (["in"], c_uint, "dwIDCtl")), + COMMETHOD( + [], + HRESULT, + "GetControlState", + (["in"], c_uint, "dwIDCtl"), + (["out"], C_POINTER(c_int), "pdwState"), + ), + ] + EnableOpenDropDown: Callable[[int], int] + AddText: Callable[[int, str], int] + AddPushButton: Callable[[int, str], int] + AddCheckButton: Callable[[int, str, int], int] + AddRadioButtonList: Callable[[int], int] + AddComboBox: Callable[[int], int] + AddControlItem: Callable[[int, int, str], int] + AddEditBox: Callable[[int, str], int] + AddSeparator: Callable[[int], int] + AddMenu: Callable[[int, str], int] + SetControlLabel: Callable[[int, str], int] + SetControlState: Callable[[int, int], int] + SetCheckButtonState: Callable[[int, int], int] + GetCheckButtonState: Callable[[int], int] + SetEditBoxText: Callable[[int, str], int] + GetEditBoxText: Callable[[int], LPCWSTR] + SetControlItemText: Callable[[int, int, str], int] + GetControlItemState: Callable[[int, int], int] + SetControlItemState: Callable[[int, int, int], int] + GetSelectedControlItem: Callable[[int], int] + SetSelectedControlItem: Callable[[int, int], int] + StartVisualGroup: Callable[[int, str], int] + EndVisualGroup: Callable[[int], int] + MakeProminent: Callable[[int], int] + RemoveControlItem: Callable[[int, int], int] + RemoveAllControlItems: Callable[[int], int] + GetControlState: Callable[[int], int] + + +class IFileDialogControlEvents(IUnknown): + _case_insensitive_ = True + _iid_ = IID_IFileDialogControlEvents + _methods_: ClassVar[list[_ComMemberSpec]] = [ + COMMETHOD( + [], + HRESULT, + "OnItemSelected", + (["in"], C_POINTER(IFileDialogCustomize), "pfdc"), + (["in"], c_int, "dwIDCtl"), + (["in"], c_int, "dwIDItem"), + ), + COMMETHOD( + [], + HRESULT, + "OnButtonClicked", + (["in"], C_POINTER(IFileDialogCustomize), "pfdc"), + (["in"], c_int, "dwIDCtl"), + ), + COMMETHOD( + [], + HRESULT, + "OnCheckButtonToggled", + (["in"], C_POINTER(IFileDialogCustomize), "pfdc"), + (["in"], c_int, "dwIDCtl"), + (["in"], c_bool, "bChecked"), + ), + COMMETHOD( + [], + HRESULT, + "OnControlActivating", + (["in"], C_POINTER(IFileDialogCustomize), "pfdc"), + (["in"], c_int, "dwIDCtl"), + ), + ] + OnButtonClicked: Callable[[IFileDialogCustomize, c_uint], int] + OnCheckButtonToggled: Callable[[IFileDialogCustomize, c_uint, c_int], int] + OnControlActivating: Callable[[IFileDialogCustomize, c_uint], int] + OnItemSelected: Callable[[IFileDialogCustomize, c_uint, c_uint], int] From 7316e4854c0678662ad211d2cb2b7a643ff16096 Mon Sep 17 00:00:00 2001 From: Benjamin Auquite Date: Fri, 30 Aug 2024 22:04:49 -0500 Subject: [PATCH 8/9] Create 2786.feature.rst --- changes/2786.feature.rst | 1 + 1 file changed, 1 insertion(+) create mode 100644 changes/2786.feature.rst diff --git a/changes/2786.feature.rst b/changes/2786.feature.rst new file mode 100644 index 0000000000..009ef7711a --- /dev/null +++ b/changes/2786.feature.rst @@ -0,0 +1 @@ +Introduced `IFileOpenDialog`, replacing `SHBrowseForFolder` for improved file selection dialogs. From cc97aca81330e3294a3d3bbdb9e634fc5b494a67 Mon Sep 17 00:00:00 2001 From: Benjamin Auquite Date: Thu, 5 Sep 2024 03:27:58 -0500 Subject: [PATCH 9/9] Add `comtypes` to the testbed and see what happens --- testbed/pyproject.toml | 1 + 1 file changed, 1 insertion(+) diff --git a/testbed/pyproject.toml b/testbed/pyproject.toml index 59dcb37726..695faf4b71 100644 --- a/testbed/pyproject.toml +++ b/testbed/pyproject.toml @@ -66,6 +66,7 @@ test_sources = [ ] requires = [ "../winforms", + "comtypes", ] # Mobile deployments