pyglet 3.0.dev5__py3-none-any.whl → 3.0.dev6__py3-none-any.whl

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.
pyglet/__init__.py CHANGED
@@ -18,7 +18,7 @@ if TYPE_CHECKING:
18
18
  from typing import Any, Callable, ItemsView, Sized
19
19
 
20
20
  #: The release version
21
- version = '3.0.dev5'
21
+ version = '3.0.dev6'
22
22
  __version__ = version
23
23
 
24
24
  MIN_PYTHON_VERSION = 3, 8
pyglet/event.py CHANGED
@@ -121,7 +121,6 @@ from __future__ import annotations
121
121
  import inspect
122
122
  import os.path
123
123
 
124
- from functools import partial
125
124
  from typing import TYPE_CHECKING, Literal, Union
126
125
  from weakref import WeakMethod
127
126
 
@@ -193,7 +192,7 @@ class EventDispatcher:
193
192
  msg = f'Unknown event "{name}"'
194
193
  raise EventException(msg)
195
194
  if inspect.ismethod(obj):
196
- yield name, WeakMethod(obj, partial(self._remove_handler, name))
195
+ yield name, WeakMethod(obj)
197
196
  else:
198
197
  yield name, obj
199
198
  else:
@@ -201,7 +200,7 @@ class EventDispatcher:
201
200
  for name in dir(obj):
202
201
  if name in self.event_types:
203
202
  meth = getattr(obj, name)
204
- yield name, WeakMethod(meth, partial(self._remove_handler, name))
203
+ yield name, WeakMethod(meth)
205
204
 
206
205
  for name, handler in kwargs.items():
207
206
  # Function for handling given event (no magic)
@@ -209,7 +208,7 @@ class EventDispatcher:
209
208
  msg = f'Unknown event "{name}"'
210
209
  raise EventException(msg)
211
210
  if inspect.ismethod(handler):
212
- yield name, WeakMethod(handler, partial(self._remove_handler, name))
211
+ yield name, WeakMethod(handler)
213
212
  else:
214
213
  yield name, handler
215
214
 
@@ -303,25 +302,6 @@ class EventDispatcher:
303
302
  except KeyError:
304
303
  pass
305
304
 
306
- def _remove_handler(self, name: str, handler: Callable) -> None:
307
- """Used internally to remove all handler instances for the given event name.
308
-
309
- This is normally called from a dead ``WeakMethod`` to remove itself from the
310
- event stack.
311
- """
312
- # Iterate over a copy as we might mutate the list
313
- for frame in list(self._event_stack):
314
-
315
- if name in frame:
316
- try:
317
- if frame[name] == handler:
318
- del frame[name]
319
- if not frame:
320
- self._event_stack.remove(frame)
321
- except TypeError:
322
- # weakref is already dead
323
- pass
324
-
325
305
  def dispatch_event(self, event_type: str, *args: Any) -> bool | None:
326
306
  """Dispatch an event to the attached event handlers.
327
307
 
@@ -37,6 +37,7 @@ if TYPE_CHECKING:
37
37
  from pyglet.graphics.shader import Shader, ShaderProgram, ComputeShaderProgram, TransformFeedbackShaderProgram # noqa: F401
38
38
  from pyglet.graphics.state import State # noqa: F401
39
39
  from pyglet.graphics.shader import get_default_shader # noqa: F401
40
+ from pyglet.graphics.buffer import UniformBufferRegion # noqa: F401
40
41
  from pyglet.graphics.draw import get_default_batch # noqa: F401
41
42
  from pyglet.graphics.texture import Texture, TextureGrid, Texture3D, TextureArray # noqa: F401
42
43
  from pyglet.graphics.atlas import TextureBin, TextureArrayBin, TextureAtlas # noqa: F401
@@ -53,6 +54,7 @@ else:
53
54
  )
54
55
  from pyglet.graphics.state import State # noqa: F401
55
56
  from pyglet.graphics.api import core # noqa: F401
57
+ from pyglet.graphics.buffer import UniformBufferRegion # noqa: F401
56
58
  from pyglet.graphics.texture import Texture, TextureGrid, Texture3D, TextureArray # noqa: F401
57
59
  from pyglet.graphics.atlas import TextureBin, TextureArrayBin, TextureAtlas # noqa: F401
58
60
  from pyglet.graphics.framebuffer import Framebuffer, Renderbuffer # noqa: F401
@@ -16,6 +16,7 @@ if TYPE_CHECKING:
16
16
  from pyglet.customtypes import ScissorProtocol
17
17
  from pyglet.graphics.draw import DrawContext
18
18
  from pyglet.graphics.api.gl.shader import ShaderProgram
19
+ from pyglet.graphics.buffer import UniformBufferRegion
19
20
  from pyglet.graphics.texture import Texture
20
21
 
21
22
 
@@ -50,8 +51,9 @@ class TextureState(State): # noqa: D101
50
51
 
51
52
  @dataclass(frozen=True)
52
53
  class MultiTextureSamplerState(State):
53
- """Static per-program sampler bindings for multi-texture draws."""
54
+ """Texture bindings and sampler uniforms for multi-texture draws."""
54
55
  program: ShaderProgram
56
+ textures: tuple[tuple[tuple[int, int], int, int], ...]
55
57
  uniforms: tuple[tuple[str, int], ...]
56
58
 
57
59
  sets_state: bool = True
@@ -61,10 +63,20 @@ class MultiTextureSamplerState(State):
61
63
  cls,
62
64
  program: ShaderProgram,
63
65
  textures: dict[str, Texture],
64
- first_texture_unit: int = 0) -> MultiTextureSamplerState:
65
- return cls(program, tuple((name, idx) for idx, name in enumerate(textures, first_texture_unit)))
66
+ first_texture_unit: int = 0,
67
+ set_id: int = 0) -> MultiTextureSamplerState:
68
+ texture_states = tuple(
69
+ ((texture.target, texture.id), texture_unit, set_id)
70
+ for texture_unit, texture in enumerate(textures.values(), first_texture_unit)
71
+ )
72
+ uniforms = tuple((name, idx) for idx, name in enumerate(textures, first_texture_unit))
73
+ return cls(program, texture_states, uniforms)
66
74
 
67
75
  def set_state(self, ctx: DrawContext) -> None:
76
+ for texture, texture_unit, _set_id in self.textures:
77
+ ctx.surface_ctx.glActiveTexture(GL_TEXTURE0 + texture_unit)
78
+ ctx.surface_ctx.glBindTexture(*texture)
79
+
68
80
  for uniform_name, texture_unit in self.uniforms:
69
81
  self.program[uniform_name] = texture_unit
70
82
 
@@ -216,8 +228,13 @@ class ViewportState(_BaseViewportState):
216
228
 
217
229
  @dataclass(frozen=True)
218
230
  class UniformBufferState(State):
219
- name: str
220
- binding: int
231
+ region: UniformBufferRegion
232
+ binding_index: int | None = None
233
+
234
+ sets_state: bool = True
235
+
236
+ def set_state(self, ctx: DrawContext) -> None:
237
+ self.region.bind(binding_index=self.binding_index)
221
238
 
222
239
 
223
240
  @dataclass(frozen=True)
@@ -5,7 +5,7 @@ from dataclasses import dataclass
5
5
  from pyglet.graphics.state import State
6
6
  from typing import TYPE_CHECKING
7
7
  from pyglet.graphics.api.gl.state import (TextureState, MultiTextureSamplerState, BlendState, ShaderUniformState, # noqa: F401
8
- DepthBufferComparison, ScissorState, ViewportState)
8
+ DepthBufferComparison, ScissorState, UniformBufferState, ViewportState)
9
9
 
10
10
 
11
11
  if TYPE_CHECKING:
@@ -24,6 +24,6 @@ class ShaderProgramState(State):
24
24
  ctx.active_shader_program = self.program
25
25
 
26
26
  if ctx.active_camera and ctx.active_camera.view.storage:
27
- ctx.active_camera.view.storage.bind(ctx)
27
+ ctx.active_camera.view.storage.bind_camera(ctx)
28
28
 
29
29
 
@@ -10,6 +10,7 @@ from pyglet.graphics.state import State, _BaseScissorState, _BaseViewportState
10
10
 
11
11
  if TYPE_CHECKING:
12
12
  from pyglet.customtypes import ScissorProtocol
13
+ from pyglet.graphics.buffer import UniformBufferRegion
13
14
  from pyglet.graphics.api.webgl.shader import ShaderProgram
14
15
  from pyglet.graphics.draw import DrawContext
15
16
  from pyglet.graphics.texture import Texture
@@ -53,9 +54,11 @@ class TextureState(State): # noqa: D101
53
54
 
54
55
  @dataclass(frozen=True)
55
56
  class MultiTextureSamplerState(State):
56
- """Static per-program sampler bindings for multi-texture draws."""
57
+ """Texture bindings and sampler uniforms for multi-texture draws."""
57
58
  program: ShaderProgram
59
+ textures: tuple[tuple[tuple[int, int], int, int], ...]
58
60
  uniforms: tuple[tuple[str, int], ...]
61
+ webgl_textures: tuple[Any, ...] = field(hash=False, compare=False)
59
62
 
60
63
  sets_state: bool = True
61
64
 
@@ -64,10 +67,21 @@ class MultiTextureSamplerState(State):
64
67
  cls,
65
68
  program: ShaderProgram,
66
69
  textures: dict[str, Texture],
67
- first_texture_unit: int = 0) -> MultiTextureSamplerState:
68
- return cls(program, tuple((name, idx) for idx, name in enumerate(textures, first_texture_unit)))
70
+ first_texture_unit: int = 0,
71
+ set_id: int = 0) -> MultiTextureSamplerState:
72
+ texture_states = tuple(
73
+ ((texture.target, id(texture.id)), texture_unit, set_id)
74
+ for texture_unit, texture in enumerate(textures.values(), first_texture_unit)
75
+ )
76
+ uniforms = tuple((name, idx) for idx, name in enumerate(textures, first_texture_unit))
77
+ webgl_textures = tuple(texture.id for texture in textures.values())
78
+ return cls(program, texture_states, uniforms, webgl_textures)
69
79
 
70
80
  def set_state(self, ctx: DrawContext) -> None:
81
+ for (texture, texture_unit, _set_id), webgl_texture in zip(self.textures, self.webgl_textures):
82
+ ctx.surface_ctx.gl.activeTexture(GL_TEXTURE0 + texture_unit)
83
+ ctx.surface_ctx.gl.bindTexture(texture[0], webgl_texture)
84
+
71
85
  for uniform_name, texture_unit in self.uniforms:
72
86
  self.program[uniform_name] = texture_unit
73
87
 
@@ -203,8 +217,13 @@ class ViewportState(_BaseViewportState):
203
217
 
204
218
  @dataclass(frozen=True)
205
219
  class UniformBufferState(State):
206
- name: str
207
- binding: int
220
+ region: UniformBufferRegion
221
+ binding_index: int | None = None
222
+
223
+ sets_state: bool = True
224
+
225
+ def set_state(self, ctx: DrawContext) -> None:
226
+ self.region.bind(binding_index=self.binding_index)
208
227
 
209
228
 
210
229
  @dataclass(frozen=True)
pyglet/graphics/buffer.py CHANGED
@@ -464,6 +464,89 @@ class UniformBufferObject(RingBuffer):
464
464
  )
465
465
 
466
466
 
467
+ class UniformBufferRegion:
468
+ """UBO region that uploads through a ring-buffered ``UniformBufferObject``.
469
+
470
+ The region owns one CPU-side data structure and a set of GPU ranges.
471
+
472
+ General practice is to update the data structure, mark it dirty, and then commit when you are done updating
473
+ for that frame.
474
+
475
+ .. warning:: Do not make the CPU-side data dirty after binding/committing during the same frame (for example,
476
+ during the ``on_draw`` call), or GPU stalls may occur.
477
+
478
+ .. versionadded:: 3.0
479
+ """
480
+
481
+ __slots__ = (
482
+ "_current_binding",
483
+ "_current_range",
484
+ "_dirty",
485
+ "_next_range_index",
486
+ "_ranges",
487
+ "_ubo",
488
+ "data",
489
+ )
490
+
491
+ def __init__(
492
+ self,
493
+ ubo: UniformBufferObject,
494
+ *,
495
+ copies_per_resource: int | None = None,
496
+ ) -> None:
497
+ self._ubo = ubo
498
+ self.data = ubo.get_data_structure()
499
+ self._current_binding: BufferBindingSlice | None = None
500
+ self._current_range: BufferRange | None = None
501
+ self._ranges = ubo.reserve_resource_range(copies_per_resource=copies_per_resource)
502
+ self._next_range_index = 0
503
+ self._dirty = True
504
+
505
+ @property
506
+ def ubo(self) -> UniformBufferObject:
507
+ """The underlying ``UniformBufferObject``."""
508
+ return self._ubo
509
+
510
+ @property
511
+ def dirty(self) -> bool:
512
+ """Whether the CPU-side data needs to be uploaded before drawing."""
513
+ return self._dirty
514
+
515
+ def __enter__(self) -> ctypes.Structure:
516
+ return self.data
517
+
518
+ def __exit__(self, _exc_type: Any, _exc_val: Any, _exc_tb: Any) -> None:
519
+ self.mark_dirty()
520
+
521
+ def mark_dirty(self) -> None:
522
+ """Mark the CPU-side data for upload on the next ``bind`` call."""
523
+ self._dirty = True
524
+
525
+ def commit(self) -> None:
526
+ """Upload dirty CPU-side data to the next writable GPU range."""
527
+ if not self._dirty:
528
+ return
529
+
530
+ self._current_binding, self._current_range, self._next_range_index = (
531
+ self._ubo.upload_to_available_binding_from_ranges(
532
+ self.data,
533
+ self._ranges,
534
+ self._next_range_index,
535
+ )
536
+ )
537
+ self._dirty = False
538
+
539
+ def bind(self, *, binding_index: int | None = None) -> None:
540
+ """Upload dirty data if needed, then bind the current GPU range."""
541
+ self.commit()
542
+
543
+ if self._current_binding is None or self._current_range is None:
544
+ return
545
+
546
+ self._ubo.use_range(self._current_range)
547
+ self._ubo.bind_slice(self._current_binding, binding_index=binding_index)
548
+
549
+
467
550
  class MappedBufferObject(AbstractBuffer):
468
551
  """A GPU buffer that supports map/unmap and range mapping."""
469
552
 
pyglet/graphics/draw.py CHANGED
@@ -23,10 +23,12 @@ from pyglet.graphics.state import (
23
23
  ShaderUniformState,
24
24
  State,
25
25
  TextureState,
26
+ UniformBufferState,
26
27
  ViewportState,
27
28
  _expand_states_in_order,
28
29
  )
29
30
  if TYPE_CHECKING:
31
+ from pyglet.graphics.buffer import UniformBufferRegion
30
32
  from pyglet.window.camera.base import BaseCamera, CameraScissor
31
33
  from pyglet.customtypes import ScissorProtocol
32
34
  from pyglet.graphics.shader import ShaderProgram
@@ -99,6 +101,7 @@ class Group:
99
101
 
100
102
  If the state is an enforced state, setting a new state will not update any children.
101
103
  """
104
+ assert not self.batches, "New states cannot be set once a group is in a batch."
102
105
  state_type = type(state)
103
106
  self._state_names[state_type.__name__] = state
104
107
  group_states = self._state_names.values()
@@ -145,9 +148,21 @@ class Group:
145
148
  def set_shader_program(self, program: ShaderProgram):
146
149
  self.set_state(ShaderProgramState(program))
147
150
 
148
- def set_shader_uniforms(self, program: ShaderProgram, uniforms: dict[str, Any]):
151
+ def set_shader_uniforms(self, program: ShaderProgram, uniforms: dict[str, Any]) -> None:
149
152
  self.set_state(ShaderUniformState(program, uniforms))
150
153
 
154
+ def set_uniform_buffer(self, region: UniformBufferRegion, binding_index: int | None = None) -> None:
155
+ """Set a Uniform Buffer Object region state.
156
+
157
+ Args:
158
+ region:
159
+ A region created by ``UniformBlock.create_ubo_region``.
160
+ binding_index:
161
+ Optional binding point override. By default, the region uses
162
+ the binding point assigned to its source uniform block.
163
+ """
164
+ self.set_state(UniformBufferState(region, binding_index))
165
+
151
166
  def set_texture(self, texture: Texture, texture_unit: int=0, set_id: int=0) -> None:
152
167
  """Set the texture state.
153
168
 
@@ -161,6 +176,7 @@ class Group:
161
176
  set_id:
162
177
  The set that the sampler belongs to. Only applicable in Vulkan.
163
178
  """
179
+ self._state_names.pop(MultiTextureSamplerState.__name__, None)
164
180
  self.set_state(TextureState.from_texture(texture, texture_unit, set_id))
165
181
 
166
182
  def set_textures(
@@ -181,10 +197,8 @@ class Group:
181
197
  set_id:
182
198
  The set that the sampler belongs to. Only applicable in Vulkan.
183
199
  """
184
- for texture_unit, texture in enumerate(textures.values(), first_texture_unit):
185
- self.set_texture(texture, texture_unit, set_id)
186
-
187
- self.set_state(MultiTextureSamplerState.from_textures(program, textures, first_texture_unit))
200
+ self._state_names.pop(TextureState.__name__, None)
201
+ self.set_state(MultiTextureSamplerState.from_textures(program, textures, first_texture_unit, set_id))
188
202
 
189
203
  @property
190
204
  def order(self) -> int:
pyglet/graphics/shader.py CHANGED
@@ -9,21 +9,26 @@ import weakref
9
9
  from abc import ABC, abstractmethod
10
10
  from collections import defaultdict
11
11
  from dataclasses import dataclass
12
- from typing import Literal, Sequence, Any, TYPE_CHECKING, Callable, Protocol, overload
12
+ from typing import TYPE_CHECKING, Any, Callable, Literal, Protocol, Sequence, overload
13
13
 
14
14
  import pyglet
15
15
  from pyglet.enums import GraphicsAPI
16
+ from pyglet.graphics.buffer import UniformBufferRegion
16
17
 
17
18
  if TYPE_CHECKING:
18
- from pyglet.graphics.buffer import (
19
- UniformBufferObject,
20
- )
21
- from pyglet.customtypes import DataTypes, CType
22
- from pyglet.graphics.vertexdomain import IndexedVertexList, VertexList, InstanceIndexedVertexList, InstanceVertexList
23
- from pyglet.graphics import Batch, Group
24
- from pyglet.enums import GeometryMode
25
19
  from _weakref import CallableProxyType
26
20
 
21
+ from pyglet.customtypes import CType, DataTypes
22
+ from pyglet.enums import GeometryMode
23
+ from pyglet.graphics import Batch, Group
24
+ from pyglet.graphics.buffer import UniformBufferObject
25
+ from pyglet.graphics.vertexdomain import (
26
+ IndexedVertexList,
27
+ InstanceIndexedVertexList,
28
+ InstanceVertexList,
29
+ VertexList,
30
+ )
31
+
27
32
 
28
33
  class ShaderException(BaseException):
29
34
  pass
@@ -967,6 +972,22 @@ class UniformBlock:
967
972
  strict,
968
973
  )
969
974
 
975
+ def create_ubo_region(
976
+ self,
977
+ *,
978
+ copies_per_resource: int = 3,
979
+ alignment: int | None = None,
980
+ strict: bool = False,
981
+ ) -> UniformBufferRegion:
982
+ """Create a ring-buffered region for updating and binding this uniform block."""
983
+
984
+ ubo = self.create_ubo(
985
+ copies_per_resource=copies_per_resource,
986
+ alignment=alignment,
987
+ strict=strict,
988
+ )
989
+ return UniformBufferRegion(ubo, copies_per_resource=copies_per_resource)
990
+
970
991
  def set_binding(self, binding: int) -> None:
971
992
  """Rebind the Uniform Block to a new binding index number.
972
993
 
pyglet/graphics/state.py CHANGED
@@ -8,8 +8,8 @@ import pyglet
8
8
 
9
9
  from pyglet.enums import GraphicsAPI
10
10
 
11
-
12
11
  if TYPE_CHECKING:
12
+ from pyglet.window.camera.base import _CameraViewBase
13
13
  from pyglet.customtypes import ScissorProtocol
14
14
  from pyglet.graphics.draw import DrawContext
15
15
  from pyglet.window.camera import ViewportType
@@ -62,6 +62,7 @@ class State:
62
62
  @runtime_checkable
63
63
  class CameraScopeProtocol(Protocol):
64
64
  """Protocol for camera objects used by camera scope states."""
65
+ view: _CameraViewBase
65
66
 
66
67
  @property
67
68
  def viewport(self) -> ViewportType:
@@ -180,6 +181,7 @@ MultiTextureSamplerState: type[State] = _backend_state.MultiTextureSamplerState
180
181
  ShaderProgramState: type[State] = _backend_state.ShaderProgramState
181
182
  BlendState: type[State] = _backend_state.BlendState
182
183
  ShaderUniformState: type[State] = _backend_state.ShaderUniformState
184
+ UniformBufferState: type[State] = _backend_state.UniformBufferState
183
185
  DepthBufferComparison: type[State] = _backend_state.DepthBufferComparison
184
186
  ScissorState: type[State] = _backend_state.ScissorState
185
187
  ViewportState: type[State] = _backend_state.ViewportState
@@ -288,6 +288,10 @@ _ole32.CoInitialize.restype = HRESULT
288
288
  _ole32.CoInitialize.argtypes = [LPVOID]
289
289
  _ole32.CoInitializeEx.restype = HRESULT
290
290
  _ole32.CoInitializeEx.argtypes = [LPVOID, DWORD]
291
+ _ole32.OleInitialize.restype = HRESULT
292
+ _ole32.OleInitialize.argtypes = [LPVOID]
293
+ _ole32.OleUninitialize.restype = c_void
294
+ _ole32.OleUninitialize.argtypes = []
291
295
  _ole32.CoUninitialize.restype = HRESULT
292
296
  _ole32.CoUninitialize.argtypes = []
293
297
  _ole32.PropVariantClear.restype = HRESULT
@@ -296,6 +300,12 @@ _ole32.CoCreateInstance.restype = HRESULT
296
300
  _ole32.CoCreateInstance.argtypes = [com.REFIID, c_void_p, DWORD, com.REFIID, c_void_p]
297
301
  _ole32.CoSetProxyBlanket.restype = HRESULT
298
302
  _ole32.CoSetProxyBlanket.argtypes = (c_void_p, DWORD, DWORD, c_void_p, DWORD, DWORD, c_void_p, DWORD)
303
+ _ole32.RegisterDragDrop.restype = HRESULT
304
+ _ole32.RegisterDragDrop.argtypes = [HWND, c_void_p]
305
+ _ole32.RevokeDragDrop.restype = HRESULT
306
+ _ole32.RevokeDragDrop.argtypes = [HWND]
307
+ _ole32.ReleaseStgMedium.restype = c_void
308
+ _ole32.ReleaseStgMedium.argtypes = [POINTER(STGMEDIUM)]
299
309
 
300
310
  # oleaut32
301
311
  _oleaut32.VariantInit.restype = c_void_p
@@ -4159,6 +4159,19 @@ CF_PRIVATEFIRST = 512
4159
4159
  CF_PRIVATELAST = 767
4160
4160
  CF_GDIOBJFIRST = 768
4161
4161
  CF_GDIOBJLAST = 1023
4162
+ DVASPECT_CONTENT = 1
4163
+ TYMED_HGLOBAL = 1
4164
+ TYMED_FILE = 2
4165
+ TYMED_ISTREAM = 4
4166
+ TYMED_ISTORAGE = 8
4167
+ TYMED_GDI = 16
4168
+ TYMED_MFPICT = 32
4169
+ TYMED_ENHMF = 64
4170
+ TYMED_NULL = 0
4171
+ DROPEFFECT_NONE = 0
4172
+ DROPEFFECT_COPY = 1
4173
+ DROPEFFECT_MOVE = 2
4174
+ DROPEFFECT_LINK = 4
4162
4175
  FVIRTKEY =1
4163
4176
  FNOINVERT = 2
4164
4177
  FSHIFT = 4
@@ -30,7 +30,7 @@ After::
30
30
  from __future__ import annotations
31
31
 
32
32
  from contextlib import contextmanager
33
- from typing import Generator, TYPE_CHECKING
33
+ from typing import Any, Callable, Generator, TYPE_CHECKING
34
34
  from ctypes import WinError
35
35
  from pyglet.libs.win32 import _user32 as user32
36
36
 
@@ -72,3 +72,68 @@ def com_context(com_object: COMObject) -> Generator[COMObject, None, None]:
72
72
  yield com_object
73
73
  finally:
74
74
  com_object.Release()
75
+
76
+
77
+ class _HResultManager:
78
+ """Helper for managing multiple HRESULT ctypes calls.
79
+
80
+ Typically, ctypes will output an ``OSError`` when the result < 0.
81
+ """
82
+ error: OSError | None
83
+ last_result: int | None
84
+
85
+ def __init__(self) -> None:
86
+ self.last_result = None
87
+ self.error = None
88
+
89
+ def call(self, callback: Callable[..., int], *args: Any) -> int | None:
90
+ """Invoke a ctypes callback and capture `OSError` as manager state."""
91
+ self.error = None
92
+ try:
93
+ self.last_result = callback(*args)
94
+ return self.last_result
95
+ except OSError as err:
96
+ self.error = err
97
+ self.last_result = None
98
+ return None
99
+
100
+ @staticmethod
101
+ def succeeded(result: int | None, acceptable: tuple[int, ...] = ()) -> bool:
102
+ """Return success for a HRESULT value.
103
+
104
+ Args:
105
+ result:
106
+ The value returned from a HRESULT-style API call.
107
+ acceptable:
108
+ Extra HRESULT values treated as acceptable for this call.
109
+ """
110
+ return result is not None and (result >= 0 or result in acceptable)
111
+
112
+ def call_succeeded(self, callback: Callable[..., int], *args: Any, acceptable: tuple[int, ...] = ()) -> bool:
113
+ """Call and check success in one step.
114
+
115
+ Use `self.error` to inspect the captured `OSError` when this returns `False`.
116
+ """
117
+ return self.succeeded(self.call(callback, *args), acceptable=acceptable)
118
+
119
+ def raise_if_failed(self, result: int | None, acceptable: tuple[int, ...] = ()) -> int:
120
+ """Raise exception if the result is not successful.
121
+
122
+ Raises:
123
+ OSError:
124
+ Re-raises captured ctypes `OSError`.
125
+ """
126
+ if self.succeeded(result, acceptable=acceptable):
127
+ return int(result)
128
+
129
+ if self.error is not None:
130
+ raise self.error
131
+
132
+ msg = f"HRESULT call failed with result {result}"
133
+ raise OSError(msg)
134
+
135
+
136
+ @contextmanager
137
+ def hresult_context() -> Generator[_HResultManager, None, None]:
138
+ """Context manager for grouped HRESULT checks."""
139
+ yield _HResultManager()
@@ -606,6 +606,38 @@ class VARIANT(Structure):
606
606
  ]
607
607
 
608
608
 
609
+ class FORMATETC(Structure):
610
+ _fields_ = [
611
+ ('cfFormat', WORD),
612
+ ('ptd', c_void_p),
613
+ ('dwAspect', DWORD),
614
+ ('lindex', LONG),
615
+ ('tymed', DWORD),
616
+ ]
617
+
618
+
619
+ class _STGMEDIUM_UNION(Union):
620
+ _fields_ = [
621
+ ('hBitmap', HANDLE),
622
+ ('hMetaFilePict', HANDLE),
623
+ ('hEnhMetaFile', HANDLE),
624
+ ('hGlobal', HANDLE),
625
+ ('lpszFileName', LPOLESTR),
626
+ ('pstm', LPSTREAM),
627
+ ('pstg', c_void_p),
628
+ ]
629
+
630
+
631
+ class STGMEDIUM(Structure):
632
+ _anonymous_ = ['union']
633
+
634
+ _fields_ = [
635
+ ('tymed', DWORD),
636
+ ('union', _STGMEDIUM_UNION),
637
+ ('pUnkForRelease', com.pIUnknown),
638
+ ]
639
+
640
+
609
641
  class DWM_BLURBEHIND(Structure):
610
642
  _fields_ = [
611
643
  ("dwFlags", DWORD),
@@ -663,6 +695,45 @@ class IStream(com.pIUnknown):
663
695
  ]
664
696
 
665
697
 
698
+ class IDataObject(com.pIUnknown):
699
+ _methods_ = [
700
+ ('GetData',
701
+ com.STDMETHOD(POINTER(FORMATETC), POINTER(STGMEDIUM))),
702
+ ('GetDataHere',
703
+ com.STDMETHOD(POINTER(FORMATETC), POINTER(STGMEDIUM))),
704
+ ('QueryGetData',
705
+ com.STDMETHOD(POINTER(FORMATETC))),
706
+ ('GetCanonicalFormatEtc',
707
+ com.STDMETHOD(POINTER(FORMATETC), POINTER(FORMATETC))),
708
+ ('SetData',
709
+ com.STDMETHOD(POINTER(FORMATETC), POINTER(STGMEDIUM), BOOL)),
710
+ ('EnumFormatEtc',
711
+ com.STDMETHOD(DWORD, c_void_p)),
712
+ ('DAdvise',
713
+ com.STDMETHOD(POINTER(FORMATETC), DWORD, c_void_p, POINTER(DWORD))),
714
+ ('DUnadvise',
715
+ com.STDMETHOD(DWORD)),
716
+ ('EnumDAdvise',
717
+ com.STDMETHOD(c_void_p)),
718
+ ]
719
+
720
+
721
+ IID_IUNKNOWN = com.GUID(0x00000000, 0x0000, 0x0000, 0xC0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x46)
722
+ IID_IDROPTARGET = com.GUID(0x00000122, 0x0000, 0x0000, 0xC0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x46)
723
+
724
+ class IDropTarget(com.IUnknown):
725
+ _methods_ = [
726
+ ('DragEnter',
727
+ com.STDMETHOD(IDataObject, DWORD, POINT, POINTER(DWORD))),
728
+ ('DragOver',
729
+ com.STDMETHOD(DWORD, POINT, POINTER(DWORD))),
730
+ ('DragLeave',
731
+ com.STDMETHOD()),
732
+ ('Drop',
733
+ com.STDMETHOD(IDataObject, DWORD, POINT, POINTER(DWORD))),
734
+ ]
735
+
736
+
666
737
  class DEV_BROADCAST_HDR(Structure):
667
738
  _fields_ = (
668
739
  ('dbch_size', DWORD),