winpnp 0.0.1__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,177 @@
1
+ # Byte-compiled / optimized / DLL files
2
+ __pycache__/
3
+ *.py[cod]
4
+ *$py.class
5
+
6
+ # C extensions
7
+ *.so
8
+
9
+ # Distribution / packaging
10
+ .Python
11
+ build/
12
+ develop-eggs/
13
+ dist/
14
+ downloads/
15
+ eggs/
16
+ .eggs/
17
+ lib/
18
+ lib64/
19
+ parts/
20
+ sdist/
21
+ var/
22
+ wheels/
23
+ share/python-wheels/
24
+ *.egg-info/
25
+ .installed.cfg
26
+ *.egg
27
+ MANIFEST
28
+
29
+ # PyInstaller
30
+ # Usually these files are written by a python script from a template
31
+ # before PyInstaller builds the exe, so as to inject date/other infos into it.
32
+ *.manifest
33
+ *.spec
34
+
35
+ # Installer logs
36
+ pip-log.txt
37
+ pip-delete-this-directory.txt
38
+
39
+ # Unit test / coverage reports
40
+ htmlcov/
41
+ .tox/
42
+ .nox/
43
+ .coverage
44
+ .coverage.*
45
+ .cache
46
+ nosetests.xml
47
+ coverage.xml
48
+ *.cover
49
+ *.py,cover
50
+ .hypothesis/
51
+ .pytest_cache/
52
+ cover/
53
+
54
+ # Translations
55
+ *.mo
56
+ *.pot
57
+
58
+ # Django stuff:
59
+ *.log
60
+ local_settings.py
61
+ db.sqlite3
62
+ db.sqlite3-journal
63
+
64
+ # Flask stuff:
65
+ instance/
66
+ .webassets-cache
67
+
68
+ # Scrapy stuff:
69
+ .scrapy
70
+
71
+ # Sphinx documentation
72
+ docs/_build/
73
+
74
+ # PyBuilder
75
+ .pybuilder/
76
+ target/
77
+
78
+ # Jupyter Notebook
79
+ .ipynb_checkpoints
80
+
81
+ # IPython
82
+ profile_default/
83
+ ipython_config.py
84
+
85
+ # pyenv
86
+ # For a library or package, you might want to ignore these files since the code is
87
+ # intended to run in multiple environments; otherwise, check them in:
88
+ # .python-version
89
+
90
+ # pipenv
91
+ # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
92
+ # However, in case of collaboration, if having platform-specific dependencies or dependencies
93
+ # having no cross-platform support, pipenv may install dependencies that don't work, or not
94
+ # install all needed dependencies.
95
+ #Pipfile.lock
96
+
97
+ # UV
98
+ # Similar to Pipfile.lock, it is generally recommended to include uv.lock in version control.
99
+ # This is especially recommended for binary packages to ensure reproducibility, and is more
100
+ # commonly ignored for libraries.
101
+ #uv.lock
102
+
103
+ # poetry
104
+ # Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control.
105
+ # This is especially recommended for binary packages to ensure reproducibility, and is more
106
+ # commonly ignored for libraries.
107
+ # https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control
108
+ #poetry.lock
109
+
110
+ # pdm
111
+ # Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control.
112
+ #pdm.lock
113
+ # pdm stores project-wide configurations in .pdm.toml, but it is recommended to not include it
114
+ # in version control.
115
+ # https://pdm.fming.dev/latest/usage/project/#working-with-version-control
116
+ .pdm.toml
117
+ .pdm-python
118
+ .pdm-build/
119
+
120
+ # PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm
121
+ __pypackages__/
122
+
123
+ # Celery stuff
124
+ celerybeat-schedule
125
+ celerybeat.pid
126
+
127
+ # SageMath parsed files
128
+ *.sage.py
129
+
130
+ # Environments
131
+ .env
132
+ .venv
133
+ env/
134
+ venv/
135
+ ENV/
136
+ env.bak/
137
+ venv.bak/
138
+
139
+ # Spyder project settings
140
+ .spyderproject
141
+ .spyproject
142
+
143
+ # Rope project settings
144
+ .ropeproject
145
+
146
+ # mkdocs documentation
147
+ /site
148
+
149
+ # mypy
150
+ .mypy_cache/
151
+ .dmypy.json
152
+ dmypy.json
153
+
154
+ # Pyre type checker
155
+ .pyre/
156
+
157
+ # pytype static type analyzer
158
+ .pytype/
159
+
160
+ # Cython debug symbols
161
+ cython_debug/
162
+
163
+ # PyCharm
164
+ # JetBrains specific template is maintained in a separate JetBrains.gitignore that can
165
+ # be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore
166
+ # and can be added to the global gitignore or merged into this file. For a more nuclear
167
+ # option (not recommended) you can uncomment the following to ignore the entire idea folder.
168
+ #.idea/
169
+
170
+ # Ruff stuff:
171
+ .ruff_cache/
172
+
173
+ # PyPI configuration file
174
+ .pypirc
175
+
176
+ # Private files that should only be local
177
+ .private/
@@ -0,0 +1,28 @@
1
+ {
2
+ "cSpell.words": [
3
+ "ALLCLASSES",
4
+ "DEVINFO",
5
+ "DEVPROP",
6
+ "DEVPROPKEY",
7
+ "DEVPROPTYPE",
8
+ "DICLASSPROP",
9
+ "DIGCF",
10
+ "filetimes",
11
+ "fmtid",
12
+ "guids",
13
+ "HDEVINFO",
14
+ "HTREE",
15
+ "itertools",
16
+ "ntdll",
17
+ "NTSTATUS",
18
+ "Pnp",
19
+ "pytest",
20
+ "setupapi",
21
+ "setupdi",
22
+ "SYSTEMTIME",
23
+ "typecode",
24
+ "TYPEMOD",
25
+ "winerror",
26
+ "winpnp"
27
+ ]
28
+ }
winpnp-0.0.1/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2025 Super Pudding
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
winpnp-0.0.1/PKG-INFO ADDED
@@ -0,0 +1,45 @@
1
+ Metadata-Version: 2.4
2
+ Name: winpnp
3
+ Version: 0.0.1
4
+ Summary: A package for interacting with Windows Plug and Play (PnP) entities
5
+ Project-URL: Homepage, https://github.com/SuperPudding98/winpnp
6
+ Project-URL: Issues, https://github.com/SuperPudding98/winpnp/issues
7
+ Author-email: Supper Pudding <31290828+SuperPudding98@users.noreply.github.com>
8
+ License-Expression: MIT
9
+ License-File: LICENSE
10
+ Keywords: Windows device,device manager,pnp,setupapi,setupdi
11
+ Classifier: Environment :: Win32 (MS Windows)
12
+ Classifier: Operating System :: Microsoft :: Windows
13
+ Classifier: Programming Language :: Python :: 3
14
+ Classifier: Topic :: System :: Hardware
15
+ Classifier: Typing :: Typed
16
+ Requires-Python: >=3.9
17
+ Requires-Dist: more-itertools<11,>=10.2.0
18
+ Provides-Extra: deploy
19
+ Requires-Dist: build<2,>=1.2.2; extra == 'deploy'
20
+ Requires-Dist: twine<7,>=6.1.0; extra == 'deploy'
21
+ Provides-Extra: dev
22
+ Requires-Dist: pytest-cases<4,>=3.8.6; extra == 'dev'
23
+ Requires-Dist: pytest<9,>=8.2.1; extra == 'dev'
24
+ Description-Content-Type: text/markdown
25
+
26
+ # winpnp
27
+
28
+ This is a package for interacting with Windows Plug and Play (PnP) entities (devices, setup classes, etc.)
29
+
30
+ It can be used to query properties of PnP devices using the `winpnp.info.device.DeviceInfo` class,<br/>
31
+ and to query properties of PnP setup classes using the `winpnp.info.setup_class.SetupClassInfo` class.<br/>
32
+ Instances of these classes can be used as mappings, with keys of type `winpnp.properties.pnp_property.PnpPropertyKey`.<br/>
33
+ For your convenience, commonly used property keys are defined in `winpnp.properties.keys`.
34
+
35
+ Here is an example usage:
36
+ ```python
37
+ from winpnp.info.device import DeviceInfo
38
+ from winpnp.properties.keys.device import INSTANCE_ID
39
+
40
+ with DeviceInfo.of_instance_id("HTREE\\ROOT\\0") as device:
41
+ instance_id = device[INSTANCE_ID]
42
+
43
+ instance_id
44
+ ```
45
+ Output: `PnpProperty(value='HTREE\\ROOT\\0', kind=PnpPropertyType(type_id=18, name='STRING'))`
winpnp-0.0.1/README.md ADDED
@@ -0,0 +1,20 @@
1
+ # winpnp
2
+
3
+ This is a package for interacting with Windows Plug and Play (PnP) entities (devices, setup classes, etc.)
4
+
5
+ It can be used to query properties of PnP devices using the `winpnp.info.device.DeviceInfo` class,<br/>
6
+ and to query properties of PnP setup classes using the `winpnp.info.setup_class.SetupClassInfo` class.<br/>
7
+ Instances of these classes can be used as mappings, with keys of type `winpnp.properties.pnp_property.PnpPropertyKey`.<br/>
8
+ For your convenience, commonly used property keys are defined in `winpnp.properties.keys`.
9
+
10
+ Here is an example usage:
11
+ ```python
12
+ from winpnp.info.device import DeviceInfo
13
+ from winpnp.properties.keys.device import INSTANCE_ID
14
+
15
+ with DeviceInfo.of_instance_id("HTREE\\ROOT\\0") as device:
16
+ instance_id = device[INSTANCE_ID]
17
+
18
+ instance_id
19
+ ```
20
+ Output: `PnpProperty(value='HTREE\\ROOT\\0', kind=PnpPropertyType(type_id=18, name='STRING'))`
@@ -0,0 +1 @@
1
+ py -3 -m build
@@ -0,0 +1 @@
1
+ py -3 -m twine upload dist/*
@@ -0,0 +1,33 @@
1
+ [build-system]
2
+ requires = ["hatchling"]
3
+ build-backend = "hatchling.build"
4
+
5
+ [project]
6
+ name = "winpnp"
7
+ version = "0.0.1"
8
+ authors = [
9
+ { name = "Supper Pudding", email = "31290828+SuperPudding98@users.noreply.github.com" },
10
+ ]
11
+ description = "A package for interacting with Windows Plug and Play (PnP) entities"
12
+ readme = "README.md"
13
+ license = "MIT"
14
+ license-files = ["LICEN[CS]E*"]
15
+ requires-python = ">=3.9"
16
+ keywords = ["pnp", "setupapi", "setupdi", "Windows device", "device manager"]
17
+ classifiers = [
18
+ "Programming Language :: Python :: 3",
19
+ "Operating System :: Microsoft :: Windows",
20
+ "Environment :: Win32 (MS Windows)",
21
+ "Topic :: System :: Hardware",
22
+ "Typing :: Typed",
23
+ ]
24
+
25
+ dependencies = ["more-itertools>=10.2.0,<11"]
26
+
27
+ [project.optional-dependencies]
28
+ dev = ["pytest>=8.2.1,<9", "pytest-cases>=3.8.6,<4"]
29
+ deploy = ["build>=1.2.2,<2", "twine>=6.1.0,<7"]
30
+
31
+ [project.urls]
32
+ Homepage = "https://github.com/SuperPudding98/winpnp"
33
+ Issues = "https://github.com/SuperPudding98/winpnp/issues"
@@ -0,0 +1 @@
1
+ from . import info, properties
@@ -0,0 +1,208 @@
1
+ from ctypes import (
2
+ POINTER,
3
+ Structure,
4
+ c_bool,
5
+ c_ubyte,
6
+ c_uint16,
7
+ c_uint32,
8
+ c_void_p,
9
+ c_wchar_p,
10
+ windll,
11
+ )
12
+ from enum import IntFlag
13
+ from uuid import UUID
14
+
15
+ INVALID_HANDLE_VALUE = -1
16
+
17
+ HDEVINFO = c_void_p
18
+
19
+
20
+ class DIGCF(IntFlag):
21
+ DEFAULT = 0x00000001
22
+ PRESENT = 0x00000002
23
+ ALLCLASSES = 0x00000004
24
+ PROFILE = 0x00000008
25
+ DEVICEINTERFACE = 0x00000010
26
+
27
+
28
+ class DICLASSPROP(IntFlag):
29
+ INSTALLER = 0x00000001
30
+ INTERFACE = 0x00000002
31
+
32
+
33
+ class GUID(Structure):
34
+ _fields_ = (
35
+ ("Data1", c_uint32),
36
+ ("Data2", c_uint16),
37
+ ("Data3", c_uint16),
38
+ ("Data4", c_ubyte * 8),
39
+ )
40
+
41
+ def __str__(self) -> str:
42
+ return str(self.to_uuid())
43
+
44
+ def __repr__(self) -> str:
45
+ return f"{type(self).__name__}({{{str(self.to_uuid())}}})"
46
+
47
+ @classmethod
48
+ def from_uuid(cls, guid: UUID) -> "GUID":
49
+ return cls.from_buffer_copy(guid.bytes_le)
50
+
51
+ def to_uuid(self) -> UUID:
52
+ return UUID(bytes_le=bytes(self))
53
+
54
+
55
+ class SP_DEVINFO_DATA(Structure):
56
+ _fields_ = (
57
+ ("cbSize", c_uint32),
58
+ ("ClassGuid", GUID),
59
+ ("DevInst", c_uint32),
60
+ ("Reserved", c_void_p),
61
+ )
62
+
63
+
64
+ class DEVPROPKEY(Structure):
65
+ _fields_ = (
66
+ ("fmtid", GUID),
67
+ ("pid", c_uint32),
68
+ )
69
+
70
+
71
+ class DEVPROP_TYPEMOD(IntFlag):
72
+ NONE = 0x00000000
73
+ ARRAY = 0x00001000 # array of fixed-sized data elements
74
+ LIST = 0x00002000 # list of variable-sized data elements
75
+
76
+
77
+ class DEVPROP_TYPE(IntFlag):
78
+ EMPTY = 0x00000000 # nothing, no property data
79
+ NULL = 0x00000001 # null property data
80
+ SBYTE = 0x00000002 # 8-bit signed int (SBYTE)
81
+ BYTE = 0x00000003 # 8-bit unsigned int (BYTE)
82
+ INT16 = 0x00000004 # 16-bit signed int (SHORT)
83
+ UINT16 = 0x00000005 # 16-bit unsigned int (USHORT)
84
+ INT32 = 0x00000006 # 32-bit signed int (LONG)
85
+ UINT32 = 0x00000007 # 32-bit unsigned int (ULONG)
86
+ INT64 = 0x00000008 # 64-bit signed int (LONG64)
87
+ UINT64 = 0x00000009 # 64-bit unsigned int (ULONG64)
88
+ FLOAT = 0x0000000A # 32-bit floating-point (FLOAT)
89
+ DOUBLE = 0x0000000B # 64-bit floating-point (DOUBLE)
90
+ DECIMAL = 0x0000000C # 128-bit data (DECIMAL)
91
+ GUID = 0x0000000D # 128-bit unique identifier (GUID)
92
+ CURRENCY = 0x0000000E # 64 bit signed int currency value (CURRENCY)
93
+ DATE = 0x0000000F # date (DATE)
94
+ FILETIME = 0x00000010 # file time (FILETIME)
95
+ BOOLEAN = 0x00000011 # 8-bit boolean (DEVPROP_BOOLEAN)
96
+ STRING = 0x00000012 # null-terminated string
97
+ STRING_LIST = STRING | DEVPROP_TYPEMOD.LIST # multi-sz string list
98
+ SECURITY_DESCRIPTOR = 0x00000013 # self-relative binary SECURITY_DESCRIPTOR
99
+ SECURITY_DESCRIPTOR_STRING = 0x00000014 # security descriptor string (SDDL format)
100
+ DEVPROPKEY = 0x00000015 # device property key (DEVPROPKEY)
101
+ DEVPROPTYPE = 0x00000016 # device property type (DEVPROPTYPE)
102
+ BINARY = BYTE | DEVPROP_TYPEMOD.ARRAY # custom binary data
103
+ ERROR = 0x00000017 # 32-bit Win32 system error code
104
+ NTSTATUS = 0x00000018 # 32-bit NTSTATUS code
105
+ STRING_INDIRECT = 0x00000019 # string resource (@[path\]<dllname>,-<strId>)
106
+
107
+
108
+ SetupDiCreateDeviceInfoList = windll.setupapi.SetupDiCreateDeviceInfoList
109
+ SetupDiCreateDeviceInfoList.restype = HDEVINFO
110
+ SetupDiCreateDeviceInfoList.argtypes = (
111
+ POINTER(GUID), # [in, optional] ClassGuid
112
+ c_void_p, # [in, optional] hwndParent
113
+ )
114
+
115
+ SetupDiDestroyDeviceInfoList = windll.setupapi.SetupDiDestroyDeviceInfoList
116
+ SetupDiDestroyDeviceInfoList.restype = c_bool
117
+ SetupDiDestroyDeviceInfoList.argtypes = (HDEVINFO,) # [in] DeviceInfoSet
118
+
119
+ SetupDiGetClassDevsW = windll.setupapi.SetupDiGetClassDevsW
120
+ SetupDiGetClassDevsW.restype = HDEVINFO
121
+ SetupDiGetClassDevsW.argtypes = (
122
+ POINTER(GUID), # [in, optional] ClassGuid
123
+ c_wchar_p, # [in, optional] Enumerator
124
+ c_void_p, # [in, optional] hwndParent
125
+ c_uint32, # [in] Flags
126
+ )
127
+
128
+ SetupDiEnumDeviceInfo = windll.setupapi.SetupDiEnumDeviceInfo
129
+ SetupDiEnumDeviceInfo.restype = c_bool
130
+ SetupDiEnumDeviceInfo.argtypes = (
131
+ HDEVINFO, # [in] DeviceInfoSet
132
+ c_uint32, # [in] MemberIndex
133
+ POINTER(SP_DEVINFO_DATA), # [out] DeviceInfoData
134
+ )
135
+
136
+ SetupDiOpenDeviceInfoW = windll.setupapi.SetupDiOpenDeviceInfoW
137
+ SetupDiOpenDeviceInfoW.restype = c_bool
138
+ SetupDiOpenDeviceInfoW.argtypes = (
139
+ HDEVINFO, # [in] DeviceInfoSet
140
+ c_wchar_p, # [in] DeviceInstanceId
141
+ c_void_p, # [in, optional] hwndParent
142
+ c_uint32, # [in] OpenFlags
143
+ POINTER(SP_DEVINFO_DATA), # [out, optional] DeviceInfoData
144
+ )
145
+
146
+ SetupDiGetDevicePropertyW = windll.setupapi.SetupDiGetDevicePropertyW
147
+ SetupDiGetDevicePropertyW.restype = c_bool
148
+ SetupDiGetDevicePropertyW.argtypes = (
149
+ HDEVINFO, # [in] DeviceInfoSet
150
+ POINTER(SP_DEVINFO_DATA), # [in] DeviceInfoData
151
+ POINTER(DEVPROPKEY), # [in] PropertyKey
152
+ POINTER(c_uint32), # [out] PropertyType
153
+ POINTER(c_ubyte), # [out, optional] PropertyBuffer
154
+ c_uint32, # [in] PropertyBufferSize
155
+ POINTER(c_uint32), # [out, optional] RequiredSize
156
+ c_uint32, # [in] Flags
157
+ )
158
+
159
+ SetupDiGetDevicePropertyKeys = windll.setupapi.SetupDiGetDevicePropertyKeys
160
+ SetupDiGetDevicePropertyKeys.restype = c_bool
161
+ SetupDiGetDevicePropertyKeys.argtypes = (
162
+ HDEVINFO, # [in] DeviceInfoSet
163
+ POINTER(SP_DEVINFO_DATA), # [in] DeviceInfoData
164
+ POINTER(DEVPROPKEY), # [out, optional] PropertyKeyArray
165
+ c_uint32, # [in] PropertyKeyCount
166
+ POINTER(c_uint32), # [out, optional] RequiredPropertyKeyCount
167
+ c_uint32, # [in] Flags
168
+ )
169
+
170
+ SetupDiBuildClassInfoList = windll.setupapi.SetupDiBuildClassInfoList
171
+ SetupDiBuildClassInfoList.restype = c_bool
172
+ SetupDiBuildClassInfoList.argtypes = (
173
+ c_uint32, # [in] Flags
174
+ POINTER(GUID), # [out, optional] ClassGuidList
175
+ c_uint32, # [in] ClassGuidListSize
176
+ POINTER(c_uint32), # [out] RequiredSize
177
+ )
178
+
179
+ SetupDiClassGuidsFromNameW = windll.setupapi.SetupDiClassGuidsFromNameW
180
+ SetupDiClassGuidsFromNameW.restype = c_bool
181
+ SetupDiClassGuidsFromNameW.argtypes = (
182
+ c_wchar_p, # [in] ClassName,
183
+ POINTER(GUID), # [out] ClassGuidList,
184
+ c_uint32, # [in] ClassGuidListSize,
185
+ POINTER(c_uint32), # [out] RequiredSize
186
+ )
187
+
188
+ SetupDiGetClassPropertyW = windll.setupapi.SetupDiGetClassPropertyW
189
+ SetupDiGetClassPropertyW.restype = c_bool
190
+ SetupDiGetClassPropertyW.argtypes = (
191
+ POINTER(GUID), # [in] ClassGuid,
192
+ POINTER(DEVPROPKEY), # [in] PropertyKey,
193
+ POINTER(c_uint32), # [out] PropertyType,
194
+ POINTER(c_ubyte), # [out] PropertyBuffer,
195
+ c_uint32, # [in] PropertyBufferSize,
196
+ POINTER(c_uint32), # [out, optional] RequiredSize,
197
+ c_uint32, # [in] Flags
198
+ )
199
+
200
+ SetupDiGetClassPropertyKeys = windll.setupapi.SetupDiGetClassPropertyKeys
201
+ SetupDiGetClassPropertyKeys.restype = c_bool
202
+ SetupDiGetClassPropertyKeys.argtypes = (
203
+ POINTER(GUID), # [in] ClassGuid,
204
+ POINTER(DEVPROPKEY), # [out, optional] PropertyKeyArray,
205
+ c_uint32, # [in] PropertyKeyCount,
206
+ POINTER(c_uint32), # [out, optional] RequiredPropertyKeyCount,
207
+ c_uint32, # [in] Flags
208
+ )
@@ -0,0 +1 @@
1
+ from . import device, setup_class
@@ -0,0 +1,126 @@
1
+ from collections.abc import Mapping
2
+ from ctypes import (
3
+ POINTER,
4
+ GetLastError,
5
+ WinError,
6
+ byref,
7
+ c_ubyte,
8
+ c_uint32,
9
+ cast,
10
+ create_string_buffer,
11
+ )
12
+ from typing import Any, Iterator, Protocol, TypeVar
13
+
14
+ from winerror import ERROR_INSUFFICIENT_BUFFER, ERROR_NOT_FOUND
15
+
16
+ from winpnp._setupapi import DEVPROPKEY, GUID
17
+ from winpnp.properties.pnp_property import PnpProperty, PnpPropertyKey, PnpPropertyType
18
+
19
+ T = TypeVar("T")
20
+
21
+
22
+ class QueryValueFunc(Protocol):
23
+ def __call__(
24
+ self,
25
+ PropertyKey,
26
+ PropertyType,
27
+ PropertyBuffer,
28
+ PropertyBufferSize,
29
+ RequiredSize,
30
+ ) -> bool: ...
31
+
32
+
33
+ class QueryKeysFunc(Protocol):
34
+ def __call__(
35
+ self, PropertyKeyArray, PropertyKeyCount, RequiredPropertyKeyCount
36
+ ) -> bool: ...
37
+
38
+
39
+ class PnpPropertyMapping(Mapping[PnpPropertyKey[T], PnpProperty[T]]):
40
+ def __init__(
41
+ self,
42
+ description: str,
43
+ query_value_func: QueryValueFunc,
44
+ query_keys_func: QueryKeysFunc,
45
+ ) -> None:
46
+ super().__init__()
47
+
48
+ self.description = description
49
+ self.query_value = query_value_func
50
+ self.query_keys = query_keys_func
51
+
52
+ def __getitem__(self, key: PnpPropertyKey[T]) -> PnpProperty[T]:
53
+ if not isinstance(key, PnpPropertyKey):
54
+ raise KeyError(key)
55
+
56
+ win_prop_key = DEVPROPKEY(GUID.from_uuid(key.category), key.property_id)
57
+ type_id = c_uint32()
58
+ required_size = c_uint32()
59
+
60
+ if self.query_value(
61
+ byref(win_prop_key),
62
+ byref(type_id),
63
+ None,
64
+ 0,
65
+ byref(required_size),
66
+ ):
67
+ return self.__build_property(key, PnpPropertyType(type_id.value), b"")
68
+
69
+ error = GetLastError()
70
+ if error == ERROR_NOT_FOUND:
71
+ raise KeyError(key)
72
+ if error != ERROR_INSUFFICIENT_BUFFER:
73
+ raise WinError(error)
74
+
75
+ value_buffer = create_string_buffer(required_size.value)
76
+ if not self.query_value(
77
+ byref(win_prop_key),
78
+ byref(type_id),
79
+ cast(value_buffer, POINTER(c_ubyte)),
80
+ required_size,
81
+ None,
82
+ ):
83
+ raise WinError()
84
+
85
+ return self.__build_property(
86
+ key, PnpPropertyType(type_id.value), value_buffer.raw
87
+ )
88
+
89
+ def __len__(self) -> int:
90
+ required_count = c_uint32()
91
+ if self.query_keys(None, 0, byref(required_count)):
92
+ return 0
93
+
94
+ error = GetLastError()
95
+ if error != ERROR_INSUFFICIENT_BUFFER:
96
+ raise WinError(error)
97
+
98
+ return required_count.value
99
+
100
+ def __iter__(self) -> Iterator[PnpPropertyKey[T]]:
101
+ keys = (DEVPROPKEY * len(self))()
102
+ if not self.query_keys(keys, len(keys), None):
103
+ raise WinError()
104
+
105
+ for key in keys:
106
+ yield PnpPropertyKey(key.fmtid.to_uuid(), key.pid)
107
+
108
+ def __build_property(
109
+ self,
110
+ key: PnpPropertyKey[T],
111
+ actual_type: PnpPropertyType[Any],
112
+ data: bytes,
113
+ ) -> PnpProperty[T]:
114
+ if (
115
+ key.allowed_types is not None
116
+ and actual_type.type_id not in key.allowed_types
117
+ ):
118
+ raise ValueError(
119
+ f"Property {key} of {self.description} has type {actual_type}, which is not expected."
120
+ )
121
+
122
+ if key.allowed_types is not None:
123
+ # Prefer key from allowed_types over original actual_type in case the one from allowed_types has a custom decoder
124
+ actual_type = key.allowed_types[actual_type.type_id]
125
+
126
+ return PnpProperty(actual_type.decode(data), actual_type)