Compare commits
3 Commits
5ba4a35f27
...
728d9aeddc
Author | SHA1 | Date | |
---|---|---|---|
728d9aeddc | |||
79cdba7b9c | |||
1cfd6cc066 |
@ -266,30 +266,29 @@ class DWRITE_CLUSTER_METRICS(ctypes.Structure):
|
|||||||
class IDWriteFontFileStream(com.IUnknown):
|
class IDWriteFontFileStream(com.IUnknown):
|
||||||
_methods_ = [
|
_methods_ = [
|
||||||
('ReadFileFragment',
|
('ReadFileFragment',
|
||||||
com.STDMETHOD(c_void_p, POINTER(c_void_p), UINT64, UINT64, POINTER(c_void_p))),
|
com.STDMETHOD(POINTER(c_void_p), UINT64, UINT64, POINTER(c_void_p))),
|
||||||
('ReleaseFileFragment',
|
('ReleaseFileFragment',
|
||||||
com.STDMETHOD(c_void_p, c_void_p)),
|
com.STDMETHOD(c_void_p)),
|
||||||
('GetFileSize',
|
('GetFileSize',
|
||||||
com.STDMETHOD(c_void_p, POINTER(UINT64))),
|
com.STDMETHOD(POINTER(UINT64))),
|
||||||
('GetLastWriteTime',
|
('GetLastWriteTime',
|
||||||
com.STDMETHOD(c_void_p, POINTER(UINT64))),
|
com.STDMETHOD(POINTER(UINT64))),
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
class IDWriteFontFileLoader_LI(com.IUnknown): # Local implementation use only.
|
class IDWriteFontFileLoader_LI(com.IUnknown): # Local implementation use only.
|
||||||
_methods_ = [
|
_methods_ = [
|
||||||
('CreateStreamFromKey',
|
('CreateStreamFromKey',
|
||||||
com.STDMETHOD(c_void_p, c_void_p, UINT32, POINTER(POINTER(IDWriteFontFileStream))))
|
com.STDMETHOD(c_void_p, UINT32, POINTER(POINTER(IDWriteFontFileStream))))
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
class IDWriteFontFileLoader(com.pIUnknown):
|
class IDWriteFontFileLoader(com.pIUnknown):
|
||||||
_methods_ = [
|
_methods_ = [
|
||||||
('CreateStreamFromKey',
|
('CreateStreamFromKey',
|
||||||
com.STDMETHOD(c_void_p, c_void_p, UINT32, POINTER(POINTER(IDWriteFontFileStream))))
|
com.STDMETHOD(c_void_p, UINT32, POINTER(POINTER(IDWriteFontFileStream))))
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
class IDWriteLocalFontFileLoader(IDWriteFontFileLoader, com.pIUnknown):
|
class IDWriteLocalFontFileLoader(IDWriteFontFileLoader, com.pIUnknown):
|
||||||
_methods_ = [
|
_methods_ = [
|
||||||
('GetFilePathLengthFromKey',
|
('GetFilePathLengthFromKey',
|
||||||
@ -452,13 +451,13 @@ DWRITE_READING_DIRECTION_LEFT_TO_RIGHT = 0
|
|||||||
class IDWriteTextAnalysisSource(com.IUnknown):
|
class IDWriteTextAnalysisSource(com.IUnknown):
|
||||||
_methods_ = [
|
_methods_ = [
|
||||||
('GetTextAtPosition',
|
('GetTextAtPosition',
|
||||||
com.METHOD(HRESULT, c_void_p, UINT32, POINTER(c_wchar_p), POINTER(UINT32))),
|
com.STDMETHOD(UINT32, POINTER(c_wchar_p), POINTER(UINT32))),
|
||||||
('GetTextBeforePosition',
|
('GetTextBeforePosition',
|
||||||
com.STDMETHOD(UINT32, c_wchar_p, POINTER(UINT32))),
|
com.STDMETHOD(UINT32, POINTER(c_wchar_p), POINTER(UINT32))),
|
||||||
('GetParagraphReadingDirection',
|
('GetParagraphReadingDirection',
|
||||||
com.METHOD(DWRITE_READING_DIRECTION)),
|
com.METHOD(DWRITE_READING_DIRECTION)),
|
||||||
('GetLocaleName',
|
('GetLocaleName',
|
||||||
com.STDMETHOD(c_void_p, UINT32, POINTER(UINT32), POINTER(c_wchar_p))),
|
com.STDMETHOD(UINT32, POINTER(UINT32), POINTER(c_wchar_p))),
|
||||||
('GetNumberSubstitution',
|
('GetNumberSubstitution',
|
||||||
com.STDMETHOD(UINT32, POINTER(UINT32), c_void_p)),
|
com.STDMETHOD(UINT32, POINTER(UINT32), c_void_p)),
|
||||||
]
|
]
|
||||||
@ -467,7 +466,7 @@ class IDWriteTextAnalysisSource(com.IUnknown):
|
|||||||
class IDWriteTextAnalysisSink(com.IUnknown):
|
class IDWriteTextAnalysisSink(com.IUnknown):
|
||||||
_methods_ = [
|
_methods_ = [
|
||||||
('SetScriptAnalysis',
|
('SetScriptAnalysis',
|
||||||
com.STDMETHOD(c_void_p, UINT32, UINT32, POINTER(DWRITE_SCRIPT_ANALYSIS))),
|
com.STDMETHOD(UINT32, UINT32, POINTER(DWRITE_SCRIPT_ANALYSIS))),
|
||||||
('SetLineBreakpoints',
|
('SetLineBreakpoints',
|
||||||
com.STDMETHOD(UINT32, UINT32, c_void_p)),
|
com.STDMETHOD(UINT32, UINT32, c_void_p)),
|
||||||
('SetBidiLevel',
|
('SetBidiLevel',
|
||||||
@ -524,7 +523,7 @@ class TextAnalysis(com.COMObject):
|
|||||||
|
|
||||||
analyzer.AnalyzeScript(self, 0, text_length, self)
|
analyzer.AnalyzeScript(self, 0, text_length, self)
|
||||||
|
|
||||||
def SetScriptAnalysis(self, this, textPosition, textLength, scriptAnalysis):
|
def SetScriptAnalysis(self, textPosition, textLength, scriptAnalysis):
|
||||||
# textPosition - The index of the first character in the string that the result applies to
|
# textPosition - The index of the first character in the string that the result applies to
|
||||||
# textLength - How many characters of the string from the index that the result applies to
|
# textLength - How many characters of the string from the index that the result applies to
|
||||||
# scriptAnalysis - The analysis information for all glyphs starting at position for length.
|
# scriptAnalysis - The analysis information for all glyphs starting at position for length.
|
||||||
@ -542,10 +541,10 @@ class TextAnalysis(com.COMObject):
|
|||||||
return 0
|
return 0
|
||||||
# return 0x80004001
|
# return 0x80004001
|
||||||
|
|
||||||
def GetTextBeforePosition(self, this, textPosition, textString, textLength):
|
def GetTextBeforePosition(self, textPosition, textString, textLength):
|
||||||
raise Exception("Currently not implemented.")
|
raise Exception("Currently not implemented.")
|
||||||
|
|
||||||
def GetTextAtPosition(self, this, textPosition, textString, textLength):
|
def GetTextAtPosition(self, textPosition, textString, textLength):
|
||||||
# This method will retrieve a substring of the text in this layout
|
# This method will retrieve a substring of the text in this layout
|
||||||
# to be used in an analysis step.
|
# to be used in an analysis step.
|
||||||
# Arguments:
|
# Arguments:
|
||||||
@ -568,7 +567,7 @@ class TextAnalysis(com.COMObject):
|
|||||||
def GetParagraphReadingDirection(self):
|
def GetParagraphReadingDirection(self):
|
||||||
return 0
|
return 0
|
||||||
|
|
||||||
def GetLocaleName(self, this, textPosition, textLength, localeName):
|
def GetLocaleName(self, textPosition, textLength, localeName):
|
||||||
self.__local_name = c_wchar_p("") # TODO: Add more locales.
|
self.__local_name = c_wchar_p("") # TODO: Add more locales.
|
||||||
localeName[0] = self.__local_name
|
localeName[0] = self.__local_name
|
||||||
textLength[0] = self._textlength - textPosition
|
textLength[0] = self._textlength - textPosition
|
||||||
@ -954,16 +953,16 @@ class IDWriteTextLayout1(IDWriteTextLayout, IDWriteTextFormat, com.pIUnknown):
|
|||||||
class IDWriteFontFileEnumerator(com.IUnknown):
|
class IDWriteFontFileEnumerator(com.IUnknown):
|
||||||
_methods_ = [
|
_methods_ = [
|
||||||
('MoveNext',
|
('MoveNext',
|
||||||
com.STDMETHOD(c_void_p, POINTER(BOOL))),
|
com.STDMETHOD(POINTER(BOOL))),
|
||||||
('GetCurrentFontFile',
|
('GetCurrentFontFile',
|
||||||
com.STDMETHOD(c_void_p, c_void_p)),
|
com.STDMETHOD(c_void_p)),
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
class IDWriteFontCollectionLoader(com.IUnknown):
|
class IDWriteFontCollectionLoader(com.IUnknown):
|
||||||
_methods_ = [
|
_methods_ = [
|
||||||
('CreateEnumeratorFromKey',
|
('CreateEnumeratorFromKey',
|
||||||
com.STDMETHOD(c_void_p, c_void_p, c_void_p, UINT32, POINTER(POINTER(IDWriteFontFileEnumerator)))),
|
com.STDMETHOD(c_void_p, c_void_p, UINT32, POINTER(POINTER(IDWriteFontFileEnumerator)))),
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
@ -971,20 +970,12 @@ class MyFontFileStream(com.COMObject):
|
|||||||
_interfaces_ = [IDWriteFontFileStream]
|
_interfaces_ = [IDWriteFontFileStream]
|
||||||
|
|
||||||
def __init__(self, data):
|
def __init__(self, data):
|
||||||
|
super().__init__()
|
||||||
self._data = data
|
self._data = data
|
||||||
self._size = len(data)
|
self._size = len(data)
|
||||||
self._ptrs = []
|
self._ptrs = []
|
||||||
|
|
||||||
def AddRef(self, this):
|
def ReadFileFragment(self, fragmentStart, fileOffset, fragmentSize, fragmentContext):
|
||||||
return 1
|
|
||||||
|
|
||||||
def Release(self, this):
|
|
||||||
return 1
|
|
||||||
|
|
||||||
def QueryInterface(self, this, refiid, tester):
|
|
||||||
return 0
|
|
||||||
|
|
||||||
def ReadFileFragment(self, this, fragmentStart, fileOffset, fragmentSize, fragmentContext):
|
|
||||||
if fileOffset + fragmentSize > self._size:
|
if fileOffset + fragmentSize > self._size:
|
||||||
return 0x80004005 # E_FAIL
|
return 0x80004005 # E_FAIL
|
||||||
|
|
||||||
@ -997,14 +988,14 @@ class MyFontFileStream(com.COMObject):
|
|||||||
fragmentContext[0] = None
|
fragmentContext[0] = None
|
||||||
return 0
|
return 0
|
||||||
|
|
||||||
def ReleaseFileFragment(self, this, fragmentContext):
|
def ReleaseFileFragment(self, fragmentContext):
|
||||||
return 0
|
return 0
|
||||||
|
|
||||||
def GetFileSize(self, this, fileSize):
|
def GetFileSize(self, fileSize):
|
||||||
fileSize[0] = self._size
|
fileSize[0] = self._size
|
||||||
return 0
|
return 0
|
||||||
|
|
||||||
def GetLastWriteTime(self, this, lastWriteTime):
|
def GetLastWriteTime(self, lastWriteTime):
|
||||||
return 0x80004001 # E_NOTIMPL
|
return 0x80004001 # E_NOTIMPL
|
||||||
|
|
||||||
|
|
||||||
@ -1012,21 +1003,13 @@ class LegacyFontFileLoader(com.COMObject):
|
|||||||
_interfaces_ = [IDWriteFontFileLoader_LI]
|
_interfaces_ = [IDWriteFontFileLoader_LI]
|
||||||
|
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
|
super().__init__()
|
||||||
self._streams = {}
|
self._streams = {}
|
||||||
|
|
||||||
def QueryInterface(self, this, refiid, tester):
|
def CreateStreamFromKey(self, fontfileReferenceKey, fontFileReferenceKeySize, fontFileStream):
|
||||||
return 0
|
|
||||||
|
|
||||||
def AddRef(self, this):
|
|
||||||
return 1
|
|
||||||
|
|
||||||
def Release(self, this):
|
|
||||||
return 1
|
|
||||||
|
|
||||||
def CreateStreamFromKey(self, this, fontfileReferenceKey, fontFileReferenceKeySize, fontFileStream):
|
|
||||||
convert_index = cast(fontfileReferenceKey, POINTER(c_uint32))
|
convert_index = cast(fontfileReferenceKey, POINTER(c_uint32))
|
||||||
|
|
||||||
self._ptr = ctypes.cast(self._streams[convert_index.contents.value]._pointers[IDWriteFontFileStream],
|
self._ptr = ctypes.cast(self._streams[convert_index.contents.value].as_interface(IDWriteFontFileStream),
|
||||||
POINTER(IDWriteFontFileStream))
|
POINTER(IDWriteFontFileStream))
|
||||||
fontFileStream[0] = self._ptr
|
fontFileStream[0] = self._ptr
|
||||||
return 0
|
return 0
|
||||||
@ -1039,6 +1022,7 @@ class MyEnumerator(com.COMObject):
|
|||||||
_interfaces_ = [IDWriteFontFileEnumerator]
|
_interfaces_ = [IDWriteFontFileEnumerator]
|
||||||
|
|
||||||
def __init__(self, factory, loader):
|
def __init__(self, factory, loader):
|
||||||
|
super().__init__()
|
||||||
self.factory = cast(factory, IDWriteFactory)
|
self.factory = cast(factory, IDWriteFactory)
|
||||||
self.key = "pyglet_dwrite"
|
self.key = "pyglet_dwrite"
|
||||||
self.size = len(self.key)
|
self.size = len(self.key)
|
||||||
@ -1057,7 +1041,7 @@ class MyEnumerator(com.COMObject):
|
|||||||
def AddFontData(self, fonts):
|
def AddFontData(self, fonts):
|
||||||
self._font_data = fonts
|
self._font_data = fonts
|
||||||
|
|
||||||
def MoveNext(self, this, hasCurrentFile):
|
def MoveNext(self, hasCurrentFile):
|
||||||
|
|
||||||
self.current_index += 1
|
self.current_index += 1
|
||||||
if self.current_index != len(self._font_data):
|
if self.current_index != len(self._font_data):
|
||||||
@ -1087,7 +1071,7 @@ class MyEnumerator(com.COMObject):
|
|||||||
|
|
||||||
pass
|
pass
|
||||||
|
|
||||||
def GetCurrentFontFile(self, this, fontFile):
|
def GetCurrentFontFile(self, fontFile):
|
||||||
fontFile = cast(fontFile, POINTER(IDWriteFontFile))
|
fontFile = cast(fontFile, POINTER(IDWriteFontFile))
|
||||||
fontFile[0] = self._font_files[self.current_index]
|
fontFile[0] = self._font_files[self.current_index]
|
||||||
return 0
|
return 0
|
||||||
@ -1097,24 +1081,14 @@ class LegacyCollectionLoader(com.COMObject):
|
|||||||
_interfaces_ = [IDWriteFontCollectionLoader]
|
_interfaces_ = [IDWriteFontCollectionLoader]
|
||||||
|
|
||||||
def __init__(self, factory, loader):
|
def __init__(self, factory, loader):
|
||||||
|
super().__init__()
|
||||||
self._enumerator = MyEnumerator(factory, loader)
|
self._enumerator = MyEnumerator(factory, loader)
|
||||||
|
|
||||||
def AddFontData(self, fonts):
|
def AddFontData(self, fonts):
|
||||||
self._enumerator.AddFontData(fonts)
|
self._enumerator.AddFontData(fonts)
|
||||||
|
|
||||||
def AddRef(self, this):
|
def CreateEnumeratorFromKey(self, factory, key, key_size, enumerator):
|
||||||
self._i = 1
|
self._ptr = ctypes.cast(self._enumerator.as_interface(IDWriteFontFileEnumerator),
|
||||||
return 1
|
|
||||||
|
|
||||||
def Release(self, this):
|
|
||||||
self._i = 0
|
|
||||||
return 1
|
|
||||||
|
|
||||||
def QueryInterface(self, this, refiid, tester):
|
|
||||||
return 0
|
|
||||||
|
|
||||||
def CreateEnumeratorFromKey(self, this, factory, key, key_size, enumerator):
|
|
||||||
self._ptr = ctypes.cast(self._enumerator._pointers[IDWriteFontFileEnumerator],
|
|
||||||
POINTER(IDWriteFontFileEnumerator))
|
POINTER(IDWriteFontFileEnumerator))
|
||||||
|
|
||||||
enumerator[0] = self._ptr
|
enumerator[0] = self._ptr
|
||||||
@ -2418,7 +2392,7 @@ class Win32DirectWriteFont(base.Font):
|
|||||||
|
|
||||||
# Note: RegisterFontLoader takes a pointer. However, for legacy we implement our own callback interface.
|
# Note: RegisterFontLoader takes a pointer. However, for legacy we implement our own callback interface.
|
||||||
# Therefore we need to pass to the actual pointer directly.
|
# Therefore we need to pass to the actual pointer directly.
|
||||||
cls._write_factory.RegisterFontFileLoader(cls._font_loader.pointers[IDWriteFontFileLoader_LI])
|
cls._write_factory.RegisterFontFileLoader(cls._font_loader.as_interface(IDWriteFontFileLoader_LI))
|
||||||
|
|
||||||
cls._font_collection_loader = LegacyCollectionLoader(cls._write_factory, cls._font_loader)
|
cls._font_collection_loader = LegacyCollectionLoader(cls._write_factory, cls._font_loader)
|
||||||
cls._write_factory.RegisterFontCollectionLoader(cls._font_collection_loader)
|
cls._write_factory.RegisterFontCollectionLoader(cls._font_collection_loader)
|
||||||
@ -2472,7 +2446,7 @@ class Win32DirectWriteFont(base.Font):
|
|||||||
cls._font_collection_loader = LegacyCollectionLoader(cls._write_factory, cls._font_loader)
|
cls._font_collection_loader = LegacyCollectionLoader(cls._write_factory, cls._font_loader)
|
||||||
|
|
||||||
cls._write_factory.RegisterFontCollectionLoader(cls._font_collection_loader)
|
cls._write_factory.RegisterFontCollectionLoader(cls._font_collection_loader)
|
||||||
cls._write_factory.RegisterFontFileLoader(cls._font_loader.pointers[IDWriteFontFileLoader_LI])
|
cls._write_factory.RegisterFontFileLoader(cls._font_loader.as_interface(IDWriteFontFileLoader_LI))
|
||||||
|
|
||||||
cls._font_collection_loader.AddFontData(cls._font_cache)
|
cls._font_collection_loader.AddFontData(cls._font_cache)
|
||||||
|
|
||||||
|
@ -170,14 +170,14 @@ class Attribute:
|
|||||||
self.name = name
|
self.name = name
|
||||||
self.location = location
|
self.location = location
|
||||||
self.count = count
|
self.count = count
|
||||||
|
|
||||||
self.gl_type = gl_type
|
self.gl_type = gl_type
|
||||||
self.c_type = _c_types[gl_type]
|
|
||||||
self.normalize = normalize
|
self.normalize = normalize
|
||||||
|
|
||||||
self.align = sizeof(self.c_type)
|
self.c_type = _c_types[gl_type]
|
||||||
self.size = count * self.align
|
|
||||||
self.stride = self.size
|
self.element_size = sizeof(self.c_type)
|
||||||
|
self.byte_size = count * self.element_size
|
||||||
|
self.stride = self.byte_size
|
||||||
|
|
||||||
def enable(self):
|
def enable(self):
|
||||||
"""Enable the attribute."""
|
"""Enable the attribute."""
|
||||||
@ -216,15 +216,11 @@ class Attribute:
|
|||||||
|
|
||||||
:rtype: `AbstractBufferRegion`
|
:rtype: `AbstractBufferRegion`
|
||||||
"""
|
"""
|
||||||
byte_start = self.stride * start
|
return buffer.get_region(start, count)
|
||||||
byte_size = self.stride * count
|
|
||||||
array_count = self.count * count
|
|
||||||
ptr_type = POINTER(self.c_type * array_count)
|
|
||||||
return buffer.get_region(byte_start, byte_size, ptr_type)
|
|
||||||
|
|
||||||
def set_region(self, buffer, start, count, data):
|
def set_region(self, buffer, start, count, data):
|
||||||
"""Set the data over a region of the buffer.
|
|
||||||
|
|
||||||
|
"""Set the data over a region of the buffer.
|
||||||
:Parameters:
|
:Parameters:
|
||||||
`buffer` : AbstractMappable`
|
`buffer` : AbstractMappable`
|
||||||
The buffer to modify.
|
The buffer to modify.
|
||||||
@ -234,9 +230,10 @@ class Attribute:
|
|||||||
Number of vertices to set.
|
Number of vertices to set.
|
||||||
`data` : A sequence of data components.
|
`data` : A sequence of data components.
|
||||||
"""
|
"""
|
||||||
byte_start = self.stride * start
|
byte_start = self.stride * start # byte offset
|
||||||
byte_size = self.stride * count
|
byte_size = self.stride * count # number of bytes
|
||||||
array_count = self.count * count
|
array_count = self.count * count # umber of values
|
||||||
|
|
||||||
data = (self.c_type * array_count)(*data)
|
data = (self.c_type * array_count)(*data)
|
||||||
buffer.set_data_region(data, byte_start, byte_size)
|
buffer.set_data_region(data, byte_start, byte_size)
|
||||||
|
|
||||||
|
@ -11,6 +11,8 @@ the buffer.
|
|||||||
import sys
|
import sys
|
||||||
import ctypes
|
import ctypes
|
||||||
|
|
||||||
|
from functools import lru_cache
|
||||||
|
|
||||||
import pyglet
|
import pyglet
|
||||||
from pyglet.gl import *
|
from pyglet.gl import *
|
||||||
|
|
||||||
@ -98,36 +100,6 @@ class AbstractBuffer:
|
|||||||
raise NotImplementedError('abstract')
|
raise NotImplementedError('abstract')
|
||||||
|
|
||||||
|
|
||||||
class AbstractMappable:
|
|
||||||
|
|
||||||
def get_region(self, start, size, ptr_type):
|
|
||||||
"""Map a region of the buffer into a ctypes array of the desired
|
|
||||||
type. This region does not need to be unmapped, but will become
|
|
||||||
invalid if the buffer is resized.
|
|
||||||
|
|
||||||
Note that although a pointer type is required, an array is mapped.
|
|
||||||
For example::
|
|
||||||
|
|
||||||
get_region(0, ctypes.sizeof(c_int) * 20, ctypes.POINTER(c_int * 20))
|
|
||||||
|
|
||||||
will map bytes 0 to 80 of the buffer to an array of 20 ints.
|
|
||||||
|
|
||||||
Changes to the array may not be recognised until the region's
|
|
||||||
:py:meth:`AbstractBufferRegion.invalidate` method is called.
|
|
||||||
|
|
||||||
:Parameters:
|
|
||||||
`start` : int
|
|
||||||
Offset into the buffer to map from, in bytes
|
|
||||||
`size` : int
|
|
||||||
Size of the buffer region to map, in bytes
|
|
||||||
`ptr_type` : ctypes pointer type
|
|
||||||
Pointer type describing the array format to create
|
|
||||||
|
|
||||||
:rtype: :py:class:`AbstractBufferRegion`
|
|
||||||
"""
|
|
||||||
raise NotImplementedError('abstract')
|
|
||||||
|
|
||||||
|
|
||||||
class BufferObject(AbstractBuffer):
|
class BufferObject(AbstractBuffer):
|
||||||
"""Lightweight representation of an OpenGL Buffer Object.
|
"""Lightweight representation of an OpenGL Buffer Object.
|
||||||
|
|
||||||
@ -222,27 +194,28 @@ class BufferObject(AbstractBuffer):
|
|||||||
return f"{self.__class__.__name__}(id={self.id}, size={self.size})"
|
return f"{self.__class__.__name__}(id={self.id}, size={self.size})"
|
||||||
|
|
||||||
|
|
||||||
class MappableBufferObject(BufferObject, AbstractMappable):
|
class AttributeBufferObject(BufferObject):
|
||||||
"""A buffer with system-memory backed store.
|
"""A buffer with system-memory backed store.
|
||||||
|
|
||||||
Updates to the data via `set_data`, `set_data_region` and `map` will be
|
Updates to the data via `set_data` and `set_data_region` will be held
|
||||||
held in local memory until `bind` is called. The advantage is that fewer
|
in local memory until `buffer_data` is called. The advantage is that
|
||||||
OpenGL calls are needed, increasing performance.
|
fewer OpenGL calls are needed, which can increasing performance at the
|
||||||
|
expense of system memory.
|
||||||
There may also be less performance penalty for resizing this buffer.
|
|
||||||
|
|
||||||
Updates to data via :py:meth:`map` are committed immediately.
|
|
||||||
"""
|
"""
|
||||||
def __init__(self, size, usage=GL_DYNAMIC_DRAW):
|
def __init__(self, size, attribute, usage=GL_DYNAMIC_DRAW):
|
||||||
super(MappableBufferObject, self).__init__(size, usage)
|
super().__init__(size, usage)
|
||||||
|
self._size = size
|
||||||
self.data = (ctypes.c_byte * size)()
|
self.data = (ctypes.c_byte * size)()
|
||||||
self.data_ptr = ctypes.addressof(self.data)
|
self.data_ptr = ctypes.addressof(self.data)
|
||||||
self._dirty_min = sys.maxsize
|
self._dirty_min = sys.maxsize
|
||||||
self._dirty_max = 0
|
self._dirty_max = 0
|
||||||
|
|
||||||
def bind(self):
|
self.attribute_stride = attribute.stride
|
||||||
# Commit pending data
|
self.attribute_count = attribute.count
|
||||||
super(MappableBufferObject, self).bind()
|
self.attribute_ctype = attribute.c_type
|
||||||
|
|
||||||
|
def bind(self, target=GL_ARRAY_BUFFER):
|
||||||
|
super().bind(target)
|
||||||
size = self._dirty_max - self._dirty_min
|
size = self._dirty_max - self._dirty_min
|
||||||
if size > 0:
|
if size > 0:
|
||||||
if size == self.size:
|
if size == self.size:
|
||||||
@ -253,7 +226,7 @@ class MappableBufferObject(BufferObject, AbstractMappable):
|
|||||||
self._dirty_max = 0
|
self._dirty_max = 0
|
||||||
|
|
||||||
def set_data(self, data):
|
def set_data(self, data):
|
||||||
super(MappableBufferObject, self).set_data(data)
|
super().set_data(data)
|
||||||
ctypes.memmove(self.data, data, self.size)
|
ctypes.memmove(self.data, data, self.size)
|
||||||
self._dirty_min = 0
|
self._dirty_min = 0
|
||||||
self._dirty_max = self.size
|
self._dirty_max = self.size
|
||||||
@ -263,17 +236,16 @@ class MappableBufferObject(BufferObject, AbstractMappable):
|
|||||||
self._dirty_min = min(start, self._dirty_min)
|
self._dirty_min = min(start, self._dirty_min)
|
||||||
self._dirty_max = max(start + length, self._dirty_max)
|
self._dirty_max = max(start + length, self._dirty_max)
|
||||||
|
|
||||||
def map(self, invalidate=False):
|
@lru_cache(maxsize=None)
|
||||||
self._dirty_min = 0
|
def get_region(self, start, count):
|
||||||
self._dirty_max = self.size
|
|
||||||
return self.data
|
|
||||||
|
|
||||||
def unmap(self):
|
byte_start = self.attribute_stride * start # byte offset
|
||||||
pass
|
byte_size = self.attribute_stride * count # number of bytes
|
||||||
|
array_count = self.attribute_count * count # number of values
|
||||||
|
|
||||||
def get_region(self, start, size, ptr_type):
|
ptr_type = ctypes.POINTER(self.attribute_ctype * array_count)
|
||||||
array = ctypes.cast(self.data_ptr + start, ptr_type).contents
|
array = ctypes.cast(self.data_ptr + byte_start, ptr_type).contents
|
||||||
return BufferObjectRegion(self, start, start + size, array)
|
return BufferObjectRegion(self, byte_start, byte_start + byte_size, array)
|
||||||
|
|
||||||
def resize(self, size):
|
def resize(self, size):
|
||||||
data = (ctypes.c_byte * size)()
|
data = (ctypes.c_byte * size)()
|
||||||
@ -289,6 +261,8 @@ class MappableBufferObject(BufferObject, AbstractMappable):
|
|||||||
self._dirty_min = sys.maxsize
|
self._dirty_min = sys.maxsize
|
||||||
self._dirty_max = 0
|
self._dirty_max = 0
|
||||||
|
|
||||||
|
self.get_region.cache_clear()
|
||||||
|
|
||||||
|
|
||||||
class BufferObjectRegion:
|
class BufferObjectRegion:
|
||||||
"""A mapped region of a MappableBufferObject."""
|
"""A mapped region of a MappableBufferObject."""
|
||||||
|
@ -23,11 +23,9 @@ primitives of the same OpenGL primitive mode.
|
|||||||
|
|
||||||
import ctypes
|
import ctypes
|
||||||
|
|
||||||
import pyglet
|
|
||||||
|
|
||||||
from pyglet.gl import *
|
from pyglet.gl import *
|
||||||
from pyglet.graphics import allocation, shader, vertexarray
|
from pyglet.graphics import allocation, shader, vertexarray
|
||||||
from pyglet.graphics.vertexbuffer import BufferObject, MappableBufferObject
|
from pyglet.graphics.vertexbuffer import BufferObject, AttributeBufferObject
|
||||||
|
|
||||||
|
|
||||||
def _nearest_pow2(v):
|
def _nearest_pow2(v):
|
||||||
@ -66,6 +64,22 @@ _gl_types = {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
def _make_attribute_property(attribute):
|
||||||
|
|
||||||
|
attrname = attribute.name
|
||||||
|
|
||||||
|
def _attribute_getter(self):
|
||||||
|
region = attribute.get_region(attribute.buffer, self.start, self.count)
|
||||||
|
region.invalidate()
|
||||||
|
return region.array
|
||||||
|
|
||||||
|
def _attribute_setter(self, values):
|
||||||
|
getattr(self, attrname)[:] = values
|
||||||
|
|
||||||
|
return property(_attribute_getter, _attribute_setter)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
class VertexDomain:
|
class VertexDomain:
|
||||||
"""Management of a set of vertex lists.
|
"""Management of a set of vertex lists.
|
||||||
|
|
||||||
@ -80,8 +94,10 @@ class VertexDomain:
|
|||||||
self.attribute_meta = attribute_meta
|
self.attribute_meta = attribute_meta
|
||||||
self.allocator = allocation.Allocator(self._initial_count)
|
self.allocator = allocation.Allocator(self._initial_count)
|
||||||
|
|
||||||
self.attributes = []
|
self.attribute_names = {} # name: attribute
|
||||||
self.buffer_attributes = [] # list of (buffer, attributes)
|
self.buffer_attributes = [] # list of (buffer, attributes)
|
||||||
|
|
||||||
|
self._property_dict = {} # name: property(_getter, _setter)
|
||||||
|
|
||||||
for name, meta in attribute_meta.items():
|
for name, meta in attribute_meta.items():
|
||||||
assert meta['format'][0] in _gl_types, f"'{meta['format']}' is not a valid atrribute format for '{name}'."
|
assert meta['format'][0] in _gl_types, f"'{meta['format']}' is not a valid atrribute format for '{name}'."
|
||||||
@ -90,14 +106,19 @@ class VertexDomain:
|
|||||||
gl_type = _gl_types[meta['format'][0]]
|
gl_type = _gl_types[meta['format'][0]]
|
||||||
normalize = 'n' in meta['format']
|
normalize = 'n' in meta['format']
|
||||||
attribute = shader.Attribute(name, location, count, gl_type, normalize)
|
attribute = shader.Attribute(name, location, count, gl_type, normalize)
|
||||||
self.attributes.append(attribute)
|
self.attribute_names[attribute.name] = attribute
|
||||||
|
|
||||||
# Create buffer:
|
# Create buffer:
|
||||||
attribute.buffer = MappableBufferObject(attribute.stride * self.allocator.capacity)
|
attribute.buffer = AttributeBufferObject(attribute.stride * self.allocator.capacity, attribute)
|
||||||
attribute.buffer.element_size = attribute.stride
|
|
||||||
attribute.buffer.attributes = (attribute,)
|
|
||||||
self.buffer_attributes.append((attribute.buffer, (attribute,)))
|
self.buffer_attributes.append((attribute.buffer, (attribute,)))
|
||||||
|
|
||||||
|
# Create custom property to be used in the VertexList:
|
||||||
|
self._property_dict[attribute.name] = _make_attribute_property(attribute)
|
||||||
|
|
||||||
|
# Make a custom VertexList class w/ properties for each attribute in the ShaderProgram:
|
||||||
|
self._vertexlist_class = type("VertexList", (VertexList,), self._property_dict)
|
||||||
|
|
||||||
self.vao = vertexarray.VertexArray()
|
self.vao = vertexarray.VertexArray()
|
||||||
self.vao.bind()
|
self.vao.bind()
|
||||||
for buffer, attributes in self.buffer_attributes:
|
for buffer, attributes in self.buffer_attributes:
|
||||||
@ -107,20 +128,6 @@ class VertexDomain:
|
|||||||
attribute.set_pointer(buffer.ptr)
|
attribute.set_pointer(buffer.ptr)
|
||||||
self.vao.unbind()
|
self.vao.unbind()
|
||||||
|
|
||||||
# Create named attributes for each attribute
|
|
||||||
self.attribute_names = {}
|
|
||||||
for attribute in self.attributes:
|
|
||||||
self.attribute_names[attribute.name] = attribute
|
|
||||||
|
|
||||||
def __del__(self):
|
|
||||||
# Break circular refs that Python GC seems to miss even when forced
|
|
||||||
# collection.
|
|
||||||
for attribute in self.attributes:
|
|
||||||
try:
|
|
||||||
del attribute.buffer
|
|
||||||
except AttributeError:
|
|
||||||
pass
|
|
||||||
|
|
||||||
def safe_alloc(self, count):
|
def safe_alloc(self, count):
|
||||||
"""Allocate vertices, resizing the buffers if necessary."""
|
"""Allocate vertices, resizing the buffers if necessary."""
|
||||||
try:
|
try:
|
||||||
@ -129,7 +136,7 @@ class VertexDomain:
|
|||||||
capacity = _nearest_pow2(e.requested_capacity)
|
capacity = _nearest_pow2(e.requested_capacity)
|
||||||
self.version += 1
|
self.version += 1
|
||||||
for buffer, _ in self.buffer_attributes:
|
for buffer, _ in self.buffer_attributes:
|
||||||
buffer.resize(capacity * buffer.element_size)
|
buffer.resize(capacity * buffer.attribute_stride)
|
||||||
self.allocator.set_capacity(capacity)
|
self.allocator.set_capacity(capacity)
|
||||||
return self.allocator.alloc(count)
|
return self.allocator.alloc(count)
|
||||||
|
|
||||||
@ -141,7 +148,7 @@ class VertexDomain:
|
|||||||
capacity = _nearest_pow2(e.requested_capacity)
|
capacity = _nearest_pow2(e.requested_capacity)
|
||||||
self.version += 1
|
self.version += 1
|
||||||
for buffer, _ in self.buffer_attributes:
|
for buffer, _ in self.buffer_attributes:
|
||||||
buffer.resize(capacity * buffer.element_size)
|
buffer.resize(capacity * buffer.attribute_stride)
|
||||||
self.allocator.set_capacity(capacity)
|
self.allocator.set_capacity(capacity)
|
||||||
return self.allocator.realloc(start, count, new_count)
|
return self.allocator.realloc(start, count, new_count)
|
||||||
|
|
||||||
@ -157,7 +164,7 @@ class VertexDomain:
|
|||||||
:rtype: :py:class:`VertexList`
|
:rtype: :py:class:`VertexList`
|
||||||
"""
|
"""
|
||||||
start = self.safe_alloc(count)
|
start = self.safe_alloc(count)
|
||||||
return VertexList(self, start, count)
|
return self._vertexlist_class(self, start, count)
|
||||||
|
|
||||||
def draw(self, mode):
|
def draw(self, mode):
|
||||||
"""Draw all vertices in the domain.
|
"""Draw all vertices in the domain.
|
||||||
@ -221,8 +228,6 @@ class VertexList:
|
|||||||
self.domain = domain
|
self.domain = domain
|
||||||
self.start = start
|
self.start = start
|
||||||
self.count = count
|
self.count = count
|
||||||
self._caches = {}
|
|
||||||
self._cache_versions = {}
|
|
||||||
|
|
||||||
def draw(self, mode):
|
def draw(self, mode):
|
||||||
"""Draw this vertex list in the given OpenGL mode.
|
"""Draw this vertex list in the given OpenGL mode.
|
||||||
@ -247,7 +252,7 @@ class VertexList:
|
|||||||
new_start = self.domain.safe_realloc(self.start, self.count, count)
|
new_start = self.domain.safe_realloc(self.start, self.count, count)
|
||||||
if new_start != self.start:
|
if new_start != self.start:
|
||||||
# Copy contents to new location
|
# Copy contents to new location
|
||||||
for attribute in self.domain.attributes:
|
for attribute in self.domain.attribute_names.values():
|
||||||
old = attribute.get_region(attribute.buffer, self.start, self.count)
|
old = attribute.get_region(attribute.buffer, self.start, self.count)
|
||||||
new = attribute.get_region(attribute.buffer, new_start, self.count)
|
new = attribute.get_region(attribute.buffer, new_start, self.count)
|
||||||
new.array[:] = old.array[:]
|
new.array[:] = old.array[:]
|
||||||
@ -255,9 +260,6 @@ class VertexList:
|
|||||||
self.start = new_start
|
self.start = new_start
|
||||||
self.count = count
|
self.count = count
|
||||||
|
|
||||||
for version in self._cache_versions:
|
|
||||||
self._cache_versions[version] = None
|
|
||||||
|
|
||||||
def delete(self):
|
def delete(self):
|
||||||
"""Delete this group."""
|
"""Delete this group."""
|
||||||
self.domain.allocator.dealloc(self.start, self.count)
|
self.domain.allocator.dealloc(self.start, self.count)
|
||||||
@ -287,33 +289,10 @@ class VertexList:
|
|||||||
self.domain = domain
|
self.domain = domain
|
||||||
self.start = new_start
|
self.start = new_start
|
||||||
|
|
||||||
for version in self._cache_versions:
|
|
||||||
self._cache_versions[version] = None
|
|
||||||
|
|
||||||
def set_attribute_data(self, name, data):
|
def set_attribute_data(self, name, data):
|
||||||
attribute = self.domain.attribute_names[name]
|
attribute = self.domain.attribute_names[name]
|
||||||
attribute.set_region(attribute.buffer, self.start, self.count, data)
|
attribute.set_region(attribute.buffer, self.start, self.count, data)
|
||||||
|
|
||||||
def __getattr__(self, name):
|
|
||||||
"""dynamic access to vertex attributes, for backwards compatibility.
|
|
||||||
"""
|
|
||||||
domain = self.domain
|
|
||||||
if self._cache_versions.get(name, None) != domain.version:
|
|
||||||
attribute = domain.attribute_names[name]
|
|
||||||
self._caches[name] = attribute.get_region(attribute.buffer, self.start, self.count)
|
|
||||||
self._cache_versions[name] = domain.version
|
|
||||||
|
|
||||||
region = self._caches[name]
|
|
||||||
region.invalidate()
|
|
||||||
return region.array
|
|
||||||
|
|
||||||
def __setattr__(self, name, value):
|
|
||||||
# Allow setting vertex attributes directly without overwriting them:
|
|
||||||
if 'domain' in self.__dict__ and name in self.__dict__['domain'].attribute_names:
|
|
||||||
getattr(self, name)[:] = value
|
|
||||||
return
|
|
||||||
super().__setattr__(name, value)
|
|
||||||
|
|
||||||
|
|
||||||
class IndexedVertexDomain(VertexDomain):
|
class IndexedVertexDomain(VertexDomain):
|
||||||
"""Management of a set of indexed vertex lists.
|
"""Management of a set of indexed vertex lists.
|
||||||
@ -337,6 +316,9 @@ class IndexedVertexDomain(VertexDomain):
|
|||||||
self.index_buffer.bind_to_index_buffer()
|
self.index_buffer.bind_to_index_buffer()
|
||||||
self.vao.unbind()
|
self.vao.unbind()
|
||||||
|
|
||||||
|
# Make a custom VertexList class w/ properties for each attribute in the ShaderProgram:
|
||||||
|
self._vertexlist_class = type("IndexedVertexList", (IndexedVertexList,), self._property_dict)
|
||||||
|
|
||||||
def safe_index_alloc(self, count):
|
def safe_index_alloc(self, count):
|
||||||
"""Allocate indices, resizing the buffers if necessary."""
|
"""Allocate indices, resizing the buffers if necessary."""
|
||||||
try:
|
try:
|
||||||
@ -371,7 +353,7 @@ class IndexedVertexDomain(VertexDomain):
|
|||||||
"""
|
"""
|
||||||
start = self.safe_alloc(count)
|
start = self.safe_alloc(count)
|
||||||
index_start = self.safe_index_alloc(index_count)
|
index_start = self.safe_index_alloc(index_count)
|
||||||
return IndexedVertexList(self, start, count, index_start, index_count)
|
return self._vertexlist_class(self, start, count, index_start, index_count)
|
||||||
|
|
||||||
def get_index_region(self, start, count):
|
def get_index_region(self, start, count):
|
||||||
"""Get a data from a region of the index buffer.
|
"""Get a data from a region of the index buffer.
|
||||||
|
@ -18,7 +18,7 @@ Interfaces can define methods::
|
|||||||
...
|
...
|
||||||
]
|
]
|
||||||
|
|
||||||
Only use STDMETHOD or METHOD for the method types (not ordinary ctypes
|
Only use METHOD, STDMETHOD or VOIDMETHOD for the method types (not ordinary ctypes
|
||||||
function types). The 'this' pointer is bound automatically... e.g., call::
|
function types). The 'this' pointer is bound automatically... e.g., call::
|
||||||
|
|
||||||
device = IDirectSound8()
|
device = IDirectSound8()
|
||||||
@ -50,7 +50,7 @@ class GUID(ctypes.Structure):
|
|||||||
('Data1', ctypes.c_ulong),
|
('Data1', ctypes.c_ulong),
|
||||||
('Data2', ctypes.c_ushort),
|
('Data2', ctypes.c_ushort),
|
||||||
('Data3', ctypes.c_ushort),
|
('Data3', ctypes.c_ushort),
|
||||||
('Data4', ctypes.c_ubyte * 8)
|
('Data4', ctypes.c_ubyte * 8),
|
||||||
]
|
]
|
||||||
|
|
||||||
def __init__(self, l, w1, w2, b1, b2, b3, b4, b5, b6, b7, b8):
|
def __init__(self, l, w1, w2, b1, b2, b3, b4, b5, b6, b7, b8):
|
||||||
@ -64,11 +64,6 @@ class GUID(ctypes.Structure):
|
|||||||
return 'GUID(%x, %x, %x, %x, %x, %x, %x, %x, %x, %x, %x)' % (
|
return 'GUID(%x, %x, %x, %x, %x, %x, %x, %x, %x, %x, %x)' % (
|
||||||
self.Data1, self.Data2, self.Data3, b1, b2, b3, b4, b5, b6, b7, b8)
|
self.Data1, self.Data2, self.Data3, b1, b2, b3, b4, b5, b6, b7, b8)
|
||||||
|
|
||||||
def __cmp__(self, other):
|
|
||||||
if isinstance(other, GUID):
|
|
||||||
return ctypes.cmp(bytes(self), bytes(other))
|
|
||||||
return -1
|
|
||||||
|
|
||||||
def __eq__(self, other):
|
def __eq__(self, other):
|
||||||
return isinstance(other, GUID) and bytes(self) == bytes(other)
|
return isinstance(other, GUID) and bytes(self) == bytes(other)
|
||||||
|
|
||||||
@ -80,6 +75,10 @@ LPGUID = ctypes.POINTER(GUID)
|
|||||||
IID = GUID
|
IID = GUID
|
||||||
REFIID = ctypes.POINTER(IID)
|
REFIID = ctypes.POINTER(IID)
|
||||||
|
|
||||||
|
S_OK = 0x00000000
|
||||||
|
E_NOTIMPL = 0x80004001
|
||||||
|
E_NOINTERFACE = 0x80004002
|
||||||
|
|
||||||
|
|
||||||
class METHOD:
|
class METHOD:
|
||||||
"""COM method."""
|
"""COM method."""
|
||||||
@ -88,244 +87,147 @@ class METHOD:
|
|||||||
self.restype = restype
|
self.restype = restype
|
||||||
self.argtypes = args
|
self.argtypes = args
|
||||||
|
|
||||||
def get_field(self):
|
self.prototype = ctypes.WINFUNCTYPE(self.restype, *self.argtypes)
|
||||||
# ctypes caches WINFUNCTYPE's so this should be ok.
|
self.direct_prototype = ctypes.WINFUNCTYPE(self.restype, ctypes.c_void_p, *self.argtypes)
|
||||||
return ctypes.WINFUNCTYPE(self.restype, *self.argtypes)
|
|
||||||
|
def get_com_proxy(self, i, name):
|
||||||
|
return self.prototype(i, name)
|
||||||
|
|
||||||
|
|
||||||
class STDMETHOD(METHOD):
|
class STDMETHOD(METHOD):
|
||||||
"""COM method with HRESULT return value."""
|
"""COM method with HRESULT return value."""
|
||||||
|
|
||||||
def __init__(self, *args):
|
def __init__(self, *args):
|
||||||
super(STDMETHOD, self).__init__(ctypes.HRESULT, *args)
|
super().__init__(ctypes.HRESULT, *args)
|
||||||
|
|
||||||
|
|
||||||
class COMMethodInstance:
|
class VOIDMETHOD(METHOD):
|
||||||
"""Binds a COM interface method."""
|
"""COM method with no return value."""
|
||||||
|
|
||||||
def __init__(self, name, i, method):
|
def __init__(self, *args):
|
||||||
self.name = name
|
super().__init__(None, *args)
|
||||||
self.i = i
|
|
||||||
self.method = method
|
|
||||||
|
|
||||||
def __get__(self, obj, tp):
|
|
||||||
if obj is not None:
|
|
||||||
def _call(*args):
|
|
||||||
assert _debug_com('COM: #{} IN {}({}, {})'.format(self.i, self.name, obj.__class__.__name__, args))
|
|
||||||
ret = self.method.get_field()(self.i, self.name)(obj, *args)
|
|
||||||
assert _debug_com('COM: #{} OUT {}({}, {})'.format(self.i, self.name, obj.__class__.__name__, args))
|
|
||||||
assert _debug_com('COM: RETURN {}'.format(ret))
|
|
||||||
return ret
|
|
||||||
|
|
||||||
return _call
|
|
||||||
|
|
||||||
raise AttributeError()
|
|
||||||
|
|
||||||
|
|
||||||
class COMInterface(ctypes.Structure):
|
_DummyPointerType = ctypes.POINTER(ctypes.c_int)
|
||||||
"""Dummy struct to serve as the type of all COM pointers."""
|
_PointerMeta = type(_DummyPointerType)
|
||||||
_fields_ = [
|
_StructMeta = type(ctypes.Structure)
|
||||||
('lpVtbl', ctypes.c_void_p),
|
|
||||||
]
|
|
||||||
|
|
||||||
|
|
||||||
class InterfacePtrMeta(type(ctypes.POINTER(COMInterface))):
|
class _InterfaceMeta(_StructMeta):
|
||||||
"""Allows interfaces to be subclassed as ctypes POINTER and expects to be populated with data from a COM object.
|
def __new__(cls, name, bases, dct, /, create_pointer_type=True):
|
||||||
TODO: Phase this out and properly use POINTER(Interface) where applicable.
|
if len(bases) > 1:
|
||||||
"""
|
assert _debug_com(f"Ignoring {len(bases) - 1} bases on {name}")
|
||||||
|
bases = (bases[0],)
|
||||||
|
|
||||||
|
if not '_methods_' in dct:
|
||||||
|
dct['_methods_'] = ()
|
||||||
|
|
||||||
|
inh_methods = []
|
||||||
|
if bases[0] is not ctypes.Structure: # Method does not exist for first definition below
|
||||||
|
for interface_type in (bases[0].get_interface_inheritance()):
|
||||||
|
inh_methods.extend(interface_type.__dict__['_methods_'])
|
||||||
|
|
||||||
|
inh_methods = tuple(inh_methods)
|
||||||
|
new_methods = tuple(dct['_methods_'])
|
||||||
|
|
||||||
|
vtbl_own_offset = len(inh_methods)
|
||||||
|
|
||||||
|
all_methods = tuple(inh_methods) + new_methods
|
||||||
|
for i, (method_name, mt) in enumerate(all_methods):
|
||||||
|
assert _debug_com(f"{name}[{i}]: {method_name}: "
|
||||||
|
f"{(', '.join(t.__name__ for t in mt.argtypes) or 'void')} -> "
|
||||||
|
f"{'void' if mt.restype is None else mt.restype.__name__}")
|
||||||
|
|
||||||
|
vtbl_struct_type = _StructMeta(f"Vtable_{name}",
|
||||||
|
(ctypes.Structure,),
|
||||||
|
{'_fields_': [(n, x.direct_prototype) for n, x in all_methods]})
|
||||||
|
dct['_vtbl_struct_type'] = vtbl_struct_type
|
||||||
|
dct['vtbl_own_offset'] = vtbl_own_offset
|
||||||
|
|
||||||
|
dct['_fields_'] = (('vtbl_ptr', ctypes.POINTER(vtbl_struct_type)),)
|
||||||
|
|
||||||
|
res_type = super().__new__(cls, name, bases, dct)
|
||||||
|
if create_pointer_type:
|
||||||
|
# If we're not being created from a pInterface subclass as helper Interface (so likely
|
||||||
|
# being explicitly defined from user code for later use), create the special
|
||||||
|
# pInterface pointer subclass so it registers itself into the pointer cache
|
||||||
|
_pInterfaceMeta(f"p{name}", (ctypes.POINTER(bases[0]),), {'_type_': res_type})
|
||||||
|
|
||||||
|
return res_type
|
||||||
|
|
||||||
|
|
||||||
|
class _pInterfaceMeta(_PointerMeta):
|
||||||
def __new__(cls, name, bases, dct):
|
def __new__(cls, name, bases, dct):
|
||||||
methods = []
|
# Interfaces can also be declared by inheritance of pInterface subclasses.
|
||||||
for base in bases[::-1]:
|
# If this happens, create the interface and then become pointer to its struct.
|
||||||
methods.extend(base.__dict__.get('_methods_', ()))
|
|
||||||
methods.extend(dct.get('_methods_', ()))
|
|
||||||
|
|
||||||
for i, (n, method) in enumerate(methods):
|
target = dct.get('_type_', None)
|
||||||
dct[n] = COMMethodInstance(n, i, method)
|
# If we weren't created due to an Interface subclass definition (don't have a _type_),
|
||||||
|
# just define that Interface subclass from our base's _type_
|
||||||
|
if target is None:
|
||||||
|
interface_base = bases[0]._type_
|
||||||
|
|
||||||
dct['_type_'] = COMInterface
|
# Create corresponding interface type and then set it as target
|
||||||
|
target = _InterfaceMeta(f"_{name}_HelperInterface",
|
||||||
|
(interface_base,),
|
||||||
|
{'_methods_': dct.get('_methods_', ())},
|
||||||
|
create_pointer_type=False)
|
||||||
|
dct['_type_'] = target
|
||||||
|
|
||||||
return super(InterfacePtrMeta, cls).__new__(cls, name, bases, dct)
|
# Create method proxies that will forward ourselves into the interface's methods
|
||||||
|
for i, (method_name, method) in enumerate(target._methods_):
|
||||||
|
m = method.get_com_proxy(i + target.vtbl_own_offset, method_name)
|
||||||
|
def pinterface_method_forward(self, *args, _m=m, _i=i):
|
||||||
|
assert _debug_com(f'Calling COM {_i} of {target.__name__} ({_m}) through '
|
||||||
|
f'pointer: ({", ".join(map(repr, (self, *args)))})')
|
||||||
|
return _m(self, *args)
|
||||||
|
dct[method_name] = pinterface_method_forward
|
||||||
|
|
||||||
|
pointer_type = super().__new__(cls, name, bases, dct)
|
||||||
|
|
||||||
class pInterface(ctypes.POINTER(COMInterface), metaclass=InterfacePtrMeta):
|
# Hack selves into the ctypes pointer cache so all uses of `ctypes.POINTER` on the
|
||||||
"""Base COM interface pointer."""
|
# interface type will yield it instead of the inflexible standard pointer type.
|
||||||
|
# NOTE: This is done pretty much exclusively to help convert COMObjects.
|
||||||
|
# Some additional work from callers like
|
||||||
class COMInterfaceMeta(type):
|
# RegisterCallback(callback_obj.as_interface(ICallback))
|
||||||
"""This differs in the original as an implemented interface object, not a POINTER object.
|
# instead of
|
||||||
Used when the user must implement their own functions within an interface rather than
|
# RegisterCallback(callback_obj)
|
||||||
being created and generated by the COM object itself. The types are automatically inserted in the ctypes type
|
# could make it obsolete.
|
||||||
cache so it can recognize the type arguments.
|
|
||||||
"""
|
|
||||||
|
|
||||||
def __new__(mcs, name, bases, dct):
|
|
||||||
methods = dct.pop("_methods_", None)
|
|
||||||
cls = type.__new__(mcs, name, bases, dct)
|
|
||||||
|
|
||||||
if methods is not None:
|
|
||||||
cls._methods_ = methods
|
|
||||||
|
|
||||||
if not bases:
|
|
||||||
_ptr_bases = (cls, COMPointer)
|
|
||||||
else:
|
|
||||||
_ptr_bases = (cls, ctypes.POINTER(bases[0]))
|
|
||||||
|
|
||||||
# Class type is dynamically created inside __new__ based on metaclass inheritence; update ctypes cache manually.
|
|
||||||
from ctypes import _pointer_type_cache
|
from ctypes import _pointer_type_cache
|
||||||
_pointer_type_cache[cls] = type(COMPointer)("POINTER({})".format(cls.__name__),
|
_pointer_type_cache[target] = pointer_type
|
||||||
_ptr_bases,
|
|
||||||
{"__interface__": cls})
|
|
||||||
|
|
||||||
return cls
|
return pointer_type
|
||||||
|
|
||||||
def __get_subclassed_methodcount(self):
|
|
||||||
"""Returns the amount of COM methods in all subclasses to determine offset of methods.
|
class Interface(ctypes.Structure, metaclass=_InterfaceMeta, create_pointer_type=False):
|
||||||
Order must be exact from the source when calling COM methods.
|
@classmethod
|
||||||
|
def get_interface_inheritance(cls):
|
||||||
|
"""Returns the types of all interfaces implemented by this interface, up to but not
|
||||||
|
including the base `Interface`.
|
||||||
|
`Interface` does not represent an actual interface, but merely the base concept of
|
||||||
|
them, so viewing it as part of an interface's inheritance chain is meaningless.
|
||||||
"""
|
"""
|
||||||
try:
|
return cls.__mro__[:cls.__mro__.index(Interface)]
|
||||||
result = 0
|
|
||||||
for itf in self.mro()[1:-1]:
|
|
||||||
result += len(itf.__dict__["_methods_"])
|
|
||||||
return result
|
|
||||||
except KeyError as err:
|
|
||||||
(name,) = err.args
|
|
||||||
if name == "_methods_":
|
|
||||||
raise TypeError("Interface '{}' requires a _methods_ attribute.".format(itf.__name__))
|
|
||||||
raise
|
|
||||||
|
|
||||||
|
|
||||||
class COMPointerMeta(type(ctypes.c_void_p), COMInterfaceMeta):
|
class pInterface(_DummyPointerType, metaclass=_pInterfaceMeta):
|
||||||
"""Required to prevent metaclass conflicts with inheritance."""
|
_type_ = Interface
|
||||||
|
|
||||||
|
|
||||||
class COMPointer(ctypes.c_void_p, metaclass=COMPointerMeta):
|
|
||||||
"""COM Pointer base, could use c_void_p but need to override from_param ."""
|
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def from_param(cls, obj):
|
def from_param(cls, obj):
|
||||||
"""Allows obj to return ctypes pointers, even if its base is not a ctype.
|
"""When dealing with a COMObject, pry a fitting interface out of it"""
|
||||||
In this case, all we simply want is a ctypes pointer matching the cls interface from the obj.
|
|
||||||
"""
|
|
||||||
if obj is None:
|
|
||||||
return
|
|
||||||
|
|
||||||
try:
|
if not isinstance(obj, COMObject):
|
||||||
ptr_dct = obj._pointers
|
return obj
|
||||||
except AttributeError:
|
|
||||||
raise Exception("Interface method argument specified incorrectly, or passed wrong argument.", cls)
|
return obj.as_interface(cls._type_)
|
||||||
else:
|
|
||||||
try:
|
|
||||||
return ptr_dct[cls.__interface__]
|
|
||||||
except KeyError:
|
|
||||||
raise TypeError("Interface {} doesn't have a pointer in this class.".format(cls.__name__))
|
|
||||||
|
|
||||||
|
|
||||||
def _missing_impl(interface_name, method_name):
|
class IUnknown(Interface):
|
||||||
"""Functions that are not implemented use this to prevent errors when called."""
|
|
||||||
|
|
||||||
def missing_cb_func(*args):
|
|
||||||
"""Return E_NOTIMPL because the method is not implemented."""
|
|
||||||
assert _debug_com("Undefined method: {0} was called in interface: {1}".format(method_name, interface_name))
|
|
||||||
return 0
|
|
||||||
|
|
||||||
return missing_cb_func
|
|
||||||
|
|
||||||
|
|
||||||
def _found_impl(interface_name, method_name, method_func):
|
|
||||||
"""If a method was found in class, we can set it as a callback."""
|
|
||||||
|
|
||||||
def cb_func(*args, **kw):
|
|
||||||
try:
|
|
||||||
result = method_func(*args, **kw)
|
|
||||||
except Exception as err:
|
|
||||||
raise err
|
|
||||||
|
|
||||||
if not result: # QOL so callbacks don't need to specify a return for assumed OK's.
|
|
||||||
return 0
|
|
||||||
|
|
||||||
return result
|
|
||||||
|
|
||||||
return cb_func
|
|
||||||
|
|
||||||
|
|
||||||
def _make_callback_func(interface, name, method_func):
|
|
||||||
"""Create a callback function for ctypes if possible."""
|
|
||||||
if method_func is None:
|
|
||||||
return _missing_impl(interface, name)
|
|
||||||
|
|
||||||
return _found_impl(interface, name, method_func)
|
|
||||||
|
|
||||||
|
|
||||||
# Store structures with same fields to prevent duplicate table creations.
|
|
||||||
_cached_structures = {}
|
|
||||||
|
|
||||||
|
|
||||||
def create_vtbl_structure(fields, interface):
|
|
||||||
"""Create virtual table structure with fields for use in COM's."""
|
|
||||||
try:
|
|
||||||
return _cached_structures[fields]
|
|
||||||
except KeyError:
|
|
||||||
Vtbl = type("Vtbl_{}".format(interface.__name__), (ctypes.Structure,), {"_fields_": fields})
|
|
||||||
_cached_structures[fields] = Vtbl
|
|
||||||
return Vtbl
|
|
||||||
|
|
||||||
|
|
||||||
class COMObject:
|
|
||||||
"""A base class for defining a COM object for use with callbacks and custom implementations."""
|
|
||||||
_interfaces_ = []
|
|
||||||
|
|
||||||
def __new__(cls, *args, **kw):
|
|
||||||
new_cls = super(COMObject, cls).__new__(cls)
|
|
||||||
assert len(cls._interfaces_) > 0, "Atleast one interface must be defined to use a COMObject."
|
|
||||||
new_cls._pointers = {}
|
|
||||||
new_cls.__create_interface_pointers()
|
|
||||||
return new_cls
|
|
||||||
|
|
||||||
def __create_interface_pointers(cls):
|
|
||||||
"""Create a custom ctypes structure to handle COM functions in a COM Object."""
|
|
||||||
interfaces = tuple(cls._interfaces_)
|
|
||||||
for itf in interfaces[::-1]:
|
|
||||||
methods = []
|
|
||||||
fields = []
|
|
||||||
for interface in itf.__mro__[-2::-1]:
|
|
||||||
for method in interface._methods_:
|
|
||||||
name, com_method = method
|
|
||||||
|
|
||||||
found_method = getattr(cls, name, None)
|
|
||||||
mth = _make_callback_func(itf.__name__, name, found_method)
|
|
||||||
|
|
||||||
proto = ctypes.WINFUNCTYPE(com_method.restype, *com_method.argtypes)
|
|
||||||
|
|
||||||
fields.append((name, proto))
|
|
||||||
methods.append(proto(mth))
|
|
||||||
|
|
||||||
# Make a structure dynamically with the fields given.
|
|
||||||
itf_structure = create_vtbl_structure(tuple(fields), interface)
|
|
||||||
|
|
||||||
# Assign the methods to the fields
|
|
||||||
vtbl = itf_structure(*methods)
|
|
||||||
|
|
||||||
cls._pointers[itf] = ctypes.pointer(ctypes.pointer(vtbl))
|
|
||||||
|
|
||||||
@property
|
|
||||||
def pointers(self):
|
|
||||||
"""Returns pointers to the implemented interfaces in this COMObject. Read-only.
|
|
||||||
|
|
||||||
:type: dict
|
|
||||||
"""
|
|
||||||
return self._pointers
|
|
||||||
|
|
||||||
class Interface(metaclass=COMInterfaceMeta):
|
|
||||||
_methods_ = []
|
|
||||||
|
|
||||||
|
|
||||||
class IUnknown(metaclass=COMInterfaceMeta):
|
|
||||||
"""These methods are not implemented by default yet. Strictly for COM method ordering."""
|
|
||||||
_methods_ = [
|
_methods_ = [
|
||||||
('QueryInterface', STDMETHOD(ctypes.c_void_p, REFIID, ctypes.c_void_p)),
|
('QueryInterface', STDMETHOD(REFIID, ctypes.c_void_p)),
|
||||||
('AddRef', METHOD(ctypes.c_int, ctypes.c_void_p)),
|
('AddRef', METHOD(ctypes.c_int)),
|
||||||
('Release', METHOD(ctypes.c_int, ctypes.c_void_p))
|
('Release', METHOD(ctypes.c_int)),
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
@ -333,5 +235,163 @@ class pIUnknown(pInterface):
|
|||||||
_methods_ = [
|
_methods_ = [
|
||||||
('QueryInterface', STDMETHOD(REFIID, ctypes.c_void_p)),
|
('QueryInterface', STDMETHOD(REFIID, ctypes.c_void_p)),
|
||||||
('AddRef', METHOD(ctypes.c_int)),
|
('AddRef', METHOD(ctypes.c_int)),
|
||||||
('Release', METHOD(ctypes.c_int))
|
('Release', METHOD(ctypes.c_int)),
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
|
def _missing_impl(interface_name, method_name):
|
||||||
|
"""Create a callback returning E_NOTIMPL for methods not present on a COMObject."""
|
||||||
|
|
||||||
|
def missing_cb_func(*_):
|
||||||
|
assert _debug_com(f"Non-implemented method {method_name} called in {interface_name}")
|
||||||
|
return E_NOTIMPL
|
||||||
|
|
||||||
|
return missing_cb_func
|
||||||
|
|
||||||
|
|
||||||
|
def _found_impl(interface_name, method_name, method_func, self_distance):
|
||||||
|
"""If a method was found in class, create a callback extracting self from the struct
|
||||||
|
pointer.
|
||||||
|
"""
|
||||||
|
|
||||||
|
def self_extracting_cb_func(p, *args):
|
||||||
|
assert _debug_com(f"COMObject method {method_name} called through interface {interface_name}")
|
||||||
|
self = ctypes.cast(p + self_distance, ctypes.POINTER(ctypes.py_object)).contents.value
|
||||||
|
result = method_func(self, *args)
|
||||||
|
# Assume no return statement translates to success
|
||||||
|
return S_OK if result is None else result
|
||||||
|
|
||||||
|
return self_extracting_cb_func
|
||||||
|
|
||||||
|
|
||||||
|
def _adjust_impl(interface_name, method_name, original_method, offset):
|
||||||
|
"""A method implemented in a previous interface modifies the COMOboject pointer so it
|
||||||
|
corresponds to an earlier interface and passes it on to the actual implementation.
|
||||||
|
"""
|
||||||
|
|
||||||
|
def adjustor_cb_func(p, *args):
|
||||||
|
assert _debug_com(f"COMObject method {method_name} called through interface "
|
||||||
|
f"{interface_name}, adjusting pointer by {offset}")
|
||||||
|
return original_method(p + offset, *args)
|
||||||
|
|
||||||
|
return adjustor_cb_func
|
||||||
|
|
||||||
|
|
||||||
|
class COMObject:
|
||||||
|
"""A COMObject for implementing C callbacks in Python.
|
||||||
|
Specify the interface types it supports in `_interfaces_`, and any methods to be implemented
|
||||||
|
by those interfaces as standard python methods. If the names match, they will be run as
|
||||||
|
callbacks with all arguments supplied as the types specified in the corresponding interface,
|
||||||
|
and `self` available as usual.
|
||||||
|
Remember to call `super().__init__()`.
|
||||||
|
|
||||||
|
COMObjects can be passed to ctypes functions directly as long as the corresponding argtype is
|
||||||
|
an `Interface` pointer, or a `pInterface` subclass.
|
||||||
|
|
||||||
|
IUnknown's methods will be autogenerated in case IUnknown is implemented.
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init_subclass__(cls, /, **kwargs):
|
||||||
|
super().__init_subclass__(**kwargs)
|
||||||
|
|
||||||
|
implemented_leaf_interfaces = cls.__dict__.get('_interfaces_', ())
|
||||||
|
if not implemented_leaf_interfaces:
|
||||||
|
raise TypeError("At least one interface must be defined to use a COMObject")
|
||||||
|
|
||||||
|
for interface_type in implemented_leaf_interfaces:
|
||||||
|
for other in implemented_leaf_interfaces:
|
||||||
|
if interface_type is other:
|
||||||
|
continue
|
||||||
|
if issubclass(interface_type, other):
|
||||||
|
raise TypeError("Only specify the leaf interfaces")
|
||||||
|
|
||||||
|
# Sanity check done
|
||||||
|
|
||||||
|
_ptr_size = ctypes.sizeof(ctypes.c_void_p)
|
||||||
|
|
||||||
|
_vtbl_pointers = []
|
||||||
|
implemented_methods = {}
|
||||||
|
|
||||||
|
# Map all leaf and inherited interfaces to the offset of the vtable containing
|
||||||
|
# their implementations
|
||||||
|
_interface_to_vtbl_offset = {}
|
||||||
|
for i, interface_type in enumerate(implemented_leaf_interfaces):
|
||||||
|
bases = interface_type.get_interface_inheritance()
|
||||||
|
for base in bases:
|
||||||
|
if base not in _interface_to_vtbl_offset:
|
||||||
|
_interface_to_vtbl_offset[base] = i * _ptr_size
|
||||||
|
|
||||||
|
if IUnknown in _interface_to_vtbl_offset:
|
||||||
|
def QueryInterface(self, iid_ptr, res_ptr):
|
||||||
|
ctypes.cast(res_ptr, ctypes.POINTER(ctypes.c_void_p))[0] = 0
|
||||||
|
return E_NOINTERFACE
|
||||||
|
|
||||||
|
def AddRef(self):
|
||||||
|
self._vrefcount += 1
|
||||||
|
return self._vrefcount
|
||||||
|
|
||||||
|
def Release(self):
|
||||||
|
if self._vrefcount <= 0:
|
||||||
|
assert _debug_com(
|
||||||
|
f"COMObject {self}: Release while refcount was {self._vrefcount}"
|
||||||
|
)
|
||||||
|
self._vrefcount -= 1
|
||||||
|
return self._vrefcount
|
||||||
|
|
||||||
|
cls.QueryInterface = QueryInterface
|
||||||
|
cls.AddRef = AddRef
|
||||||
|
cls.Release = Release
|
||||||
|
|
||||||
|
for i, interface_type in enumerate(implemented_leaf_interfaces):
|
||||||
|
wrappers = []
|
||||||
|
|
||||||
|
for method_name, method_type in interface_type._vtbl_struct_type._fields_:
|
||||||
|
if method_name in implemented_methods:
|
||||||
|
# Method is already implemented on a previous interface; redirect to it
|
||||||
|
# See https://devblogs.microsoft.com/oldnewthing/20040206-00/?p=40723
|
||||||
|
# NOTE: Never tested, might be totally wrong
|
||||||
|
func, implementing_vtbl_idx = implemented_methods[method_name]
|
||||||
|
mth = _adjust_impl(interface_type.__name__,
|
||||||
|
method_name,
|
||||||
|
func,
|
||||||
|
(implementing_vtbl_idx - i) * _ptr_size)
|
||||||
|
|
||||||
|
else:
|
||||||
|
if (found_method := getattr(cls, method_name, None)) is None:
|
||||||
|
mth = _missing_impl(interface_type.__name__, method_name)
|
||||||
|
else:
|
||||||
|
mth = _found_impl(interface_type.__name__,
|
||||||
|
method_name,
|
||||||
|
found_method,
|
||||||
|
(len(implemented_leaf_interfaces) - i) * _ptr_size)
|
||||||
|
|
||||||
|
implemented_methods[method_name] = (mth, i)
|
||||||
|
|
||||||
|
wrappers.append(method_type(mth))
|
||||||
|
|
||||||
|
vtbl = interface_type._vtbl_struct_type(*wrappers)
|
||||||
|
_vtbl_pointers.append(ctypes.pointer(vtbl))
|
||||||
|
|
||||||
|
fields = []
|
||||||
|
for i, itf in enumerate(implemented_leaf_interfaces):
|
||||||
|
fields.append((f'vtbl_ptr_{i}', ctypes.POINTER(itf._vtbl_struct_type)))
|
||||||
|
fields.append(('self_', ctypes.py_object))
|
||||||
|
|
||||||
|
cls._interface_to_vtbl_offset = _interface_to_vtbl_offset
|
||||||
|
cls._vtbl_pointers = _vtbl_pointers
|
||||||
|
cls._struct_type = _StructMeta(f"{cls.__name__}_Struct", (ctypes.Structure,), {'_fields_': fields})
|
||||||
|
|
||||||
|
def __init__(self):
|
||||||
|
self._vrefcount = 1
|
||||||
|
self._struct = self._struct_type(*self._vtbl_pointers, ctypes.py_object(self))
|
||||||
|
|
||||||
|
def as_interface(self, interface_type):
|
||||||
|
# This method ignores the QueryInterface mechanism completely; no GUIDs are
|
||||||
|
# associated with Interfaces on the python side, it can't be supported.
|
||||||
|
# Still works, as so far none of the python-made COMObjects are expected to
|
||||||
|
# support it by any C code.
|
||||||
|
# (Also no need to always implement it, some COMObjects do not inherit from IUnknown.)
|
||||||
|
if (offset := self._interface_to_vtbl_offset.get(interface_type, None)) is None:
|
||||||
|
raise TypeError(f"Does not implement {interface_type}")
|
||||||
|
|
||||||
|
return ctypes.byref(self._struct, offset)
|
||||||
|
@ -83,15 +83,15 @@ IID_IMMDeviceEnumerator = com.GUID(0xa95664d2, 0x9614, 0x4f35, 0xa7, 0x46, 0xde,
|
|||||||
class IMMNotificationClient(com.IUnknown):
|
class IMMNotificationClient(com.IUnknown):
|
||||||
_methods_ = [
|
_methods_ = [
|
||||||
('OnDeviceStateChanged',
|
('OnDeviceStateChanged',
|
||||||
com.METHOD(ctypes.c_void_p, ctypes.c_void_p, LPCWSTR, DWORD)),
|
com.STDMETHOD(LPCWSTR, DWORD)),
|
||||||
('OnDeviceAdded',
|
('OnDeviceAdded',
|
||||||
com.METHOD(ctypes.c_void_p, ctypes.c_void_p, LPCWSTR)),
|
com.STDMETHOD(LPCWSTR)),
|
||||||
('OnDeviceRemoved',
|
('OnDeviceRemoved',
|
||||||
com.METHOD(ctypes.c_void_p, ctypes.c_void_p, LPCWSTR)),
|
com.STDMETHOD(LPCWSTR)),
|
||||||
('OnDefaultDeviceChanged',
|
('OnDefaultDeviceChanged',
|
||||||
com.METHOD(ctypes.c_void_p, ctypes.c_void_p, EDataFlow, ERole, LPCWSTR)),
|
com.STDMETHOD(EDataFlow, ERole, LPCWSTR)),
|
||||||
('OnPropertyValueChanged',
|
('OnPropertyValueChanged',
|
||||||
com.METHOD(ctypes.c_void_p, ctypes.c_void_p, LPCWSTR, PROPERTYKEY)),
|
com.STDMETHOD(LPCWSTR, PROPERTYKEY)),
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
@ -113,7 +113,7 @@ class AudioNotificationCB(com.COMObject):
|
|||||||
self.audio_devices = audio_devices
|
self.audio_devices = audio_devices
|
||||||
self._lost = False
|
self._lost = False
|
||||||
|
|
||||||
def OnDeviceStateChanged(self, this, pwstrDeviceId, dwNewState):
|
def OnDeviceStateChanged(self, pwstrDeviceId, dwNewState):
|
||||||
device = self.audio_devices.get_cached_device(pwstrDeviceId)
|
device = self.audio_devices.get_cached_device(pwstrDeviceId)
|
||||||
|
|
||||||
old_state = device.state
|
old_state = device.state
|
||||||
@ -126,17 +126,17 @@ class AudioNotificationCB(com.COMObject):
|
|||||||
device.state = dwNewState
|
device.state = dwNewState
|
||||||
self.audio_devices.dispatch_event('on_device_state_changed', device, pyglet_old_state, pyglet_new_state)
|
self.audio_devices.dispatch_event('on_device_state_changed', device, pyglet_old_state, pyglet_new_state)
|
||||||
|
|
||||||
def OnDeviceAdded(self, this, pwstrDeviceId):
|
def OnDeviceAdded(self, pwstrDeviceId):
|
||||||
dev = self.audio_devices.add_device(pwstrDeviceId)
|
dev = self.audio_devices.add_device(pwstrDeviceId)
|
||||||
assert _debug(f"Audio device was added {pwstrDeviceId}: {dev}")
|
assert _debug(f"Audio device was added {pwstrDeviceId}: {dev}")
|
||||||
self.audio_devices.dispatch_event('on_device_added', dev)
|
self.audio_devices.dispatch_event('on_device_added', dev)
|
||||||
|
|
||||||
def OnDeviceRemoved(self, this, pwstrDeviceId):
|
def OnDeviceRemoved(self, pwstrDeviceId):
|
||||||
dev = self.audio_devices.remove_device(pwstrDeviceId)
|
dev = self.audio_devices.remove_device(pwstrDeviceId)
|
||||||
assert _debug(f"Audio device was removed {pwstrDeviceId} : {dev}")
|
assert _debug(f"Audio device was removed {pwstrDeviceId} : {dev}")
|
||||||
self.audio_devices.dispatch_event('on_device_removed', dev)
|
self.audio_devices.dispatch_event('on_device_removed', dev)
|
||||||
|
|
||||||
def OnDefaultDeviceChanged(self, this, flow, role, pwstrDeviceId):
|
def OnDefaultDeviceChanged(self, flow, role, pwstrDeviceId):
|
||||||
# Only support eConsole role right now
|
# Only support eConsole role right now
|
||||||
if role == 0:
|
if role == 0:
|
||||||
if pwstrDeviceId is None:
|
if pwstrDeviceId is None:
|
||||||
@ -149,7 +149,7 @@ class AudioNotificationCB(com.COMObject):
|
|||||||
|
|
||||||
self.audio_devices.dispatch_event('on_default_changed', device, pyglet_flow)
|
self.audio_devices.dispatch_event('on_default_changed', device, pyglet_flow)
|
||||||
|
|
||||||
def OnPropertyValueChanged(self, this, pwstrDeviceId, key):
|
def OnPropertyValueChanged(self, pwstrDeviceId, key):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
@ -270,7 +270,6 @@ class IDirectSound(com.pIUnknown):
|
|||||||
('Initialize',
|
('Initialize',
|
||||||
com.STDMETHOD(com.LPGUID)),
|
com.STDMETHOD(com.LPGUID)),
|
||||||
]
|
]
|
||||||
_type_ = com.COMInterface
|
|
||||||
|
|
||||||
DirectSoundCreate = lib.DirectSoundCreate
|
DirectSoundCreate = lib.DirectSoundCreate
|
||||||
DirectSoundCreate.argtypes = \
|
DirectSoundCreate.argtypes = \
|
||||||
|
@ -192,17 +192,19 @@ XAUDIO2_NO_VIRTUAL_AUDIO_CLIENT = 0x10000 # Used in CreateMasteringVoice to cr
|
|||||||
class IXAudio2VoiceCallback(com.Interface):
|
class IXAudio2VoiceCallback(com.Interface):
|
||||||
_methods_ = [
|
_methods_ = [
|
||||||
('OnVoiceProcessingPassStart',
|
('OnVoiceProcessingPassStart',
|
||||||
com.STDMETHOD(UINT32)),
|
com.VOIDMETHOD(UINT32)),
|
||||||
('OnVoiceProcessingPassEnd',
|
('OnVoiceProcessingPassEnd',
|
||||||
com.STDMETHOD()),
|
com.VOIDMETHOD()),
|
||||||
('onStreamEnd',
|
('OnStreamEnd',
|
||||||
com.STDMETHOD()),
|
com.VOIDMETHOD()),
|
||||||
('onBufferStart',
|
('OnBufferStart',
|
||||||
com.STDMETHOD(ctypes.c_void_p)),
|
com.VOIDMETHOD(ctypes.c_void_p)),
|
||||||
('OnBufferEnd',
|
('OnBufferEnd',
|
||||||
com.STDMETHOD(ctypes.c_void_p)),
|
com.VOIDMETHOD(ctypes.c_void_p)),
|
||||||
('OnLoopEnd',
|
('OnLoopEnd',
|
||||||
com.STDMETHOD(ctypes.c_void_p)),
|
com.VOIDMETHOD(ctypes.c_void_p)),
|
||||||
|
('OnVoiceError',
|
||||||
|
com.VOIDMETHOD(ctypes.c_void_p, HRESULT))
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
@ -220,20 +222,9 @@ class XA2SourceCallback(com.COMObject):
|
|||||||
_interfaces_ = [IXAudio2VoiceCallback]
|
_interfaces_ = [IXAudio2VoiceCallback]
|
||||||
|
|
||||||
def __init__(self, xa2_player):
|
def __init__(self, xa2_player):
|
||||||
|
super().__init__()
|
||||||
self.xa2_player = xa2_player
|
self.xa2_player = xa2_player
|
||||||
|
|
||||||
def OnVoiceProcessingPassStart(self, bytesRequired):
|
|
||||||
pass
|
|
||||||
|
|
||||||
def OnVoiceProcessingPassEnd(self):
|
|
||||||
pass
|
|
||||||
|
|
||||||
def onStreamEnd(self):
|
|
||||||
pass
|
|
||||||
|
|
||||||
def onBufferStart(self, pBufferContext):
|
|
||||||
pass
|
|
||||||
|
|
||||||
def OnBufferEnd(self, pBufferContext):
|
def OnBufferEnd(self, pBufferContext):
|
||||||
"""At the end of playing one buffer, attempt to refill again.
|
"""At the end of playing one buffer, attempt to refill again.
|
||||||
Even if the player is out of sources, it needs to be called to purge all buffers.
|
Even if the player is out of sources, it needs to be called to purge all buffers.
|
||||||
@ -241,10 +232,7 @@ class XA2SourceCallback(com.COMObject):
|
|||||||
if self.xa2_player:
|
if self.xa2_player:
|
||||||
self.xa2_player.refill_source_player()
|
self.xa2_player.refill_source_player()
|
||||||
|
|
||||||
def OnLoopEnd(self, this, pBufferContext):
|
def OnVoiceError(self, pBufferContext, hresult):
|
||||||
pass
|
|
||||||
|
|
||||||
def onVoiceError(self, this, pBufferContext, hresult):
|
|
||||||
raise Exception("Error occurred during audio playback.", hresult)
|
raise Exception("Error occurred during audio playback.", hresult)
|
||||||
|
|
||||||
|
|
||||||
@ -362,24 +350,18 @@ class IXAudio2MasteringVoice(IXAudio2Voice):
|
|||||||
class IXAudio2EngineCallback(com.Interface):
|
class IXAudio2EngineCallback(com.Interface):
|
||||||
_methods_ = [
|
_methods_ = [
|
||||||
('OnProcessingPassStart',
|
('OnProcessingPassStart',
|
||||||
com.METHOD(ctypes.c_void_p)),
|
com.VOIDMETHOD()),
|
||||||
('OnProcessingPassEnd',
|
('OnProcessingPassEnd',
|
||||||
com.METHOD(ctypes.c_void_p)),
|
com.VOIDMETHOD()),
|
||||||
('OnCriticalError',
|
('OnCriticalError',
|
||||||
com.METHOD(ctypes.c_void_p, ctypes.c_void_p, ctypes.c_ulong)),
|
com.VOIDMETHOD(HRESULT)),
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
class XA2EngineCallback(com.COMObject):
|
class XA2EngineCallback(com.COMObject):
|
||||||
_interfaces_ = [IXAudio2EngineCallback]
|
_interfaces_ = [IXAudio2EngineCallback]
|
||||||
|
|
||||||
def OnProcessingPassStart(self):
|
def OnCriticalError(self, hresult):
|
||||||
pass
|
|
||||||
|
|
||||||
def OnProcessingPassEnd(self):
|
|
||||||
pass
|
|
||||||
|
|
||||||
def OnCriticalError(self, this, hresult):
|
|
||||||
raise Exception("Critical Error:", hresult)
|
raise Exception("Critical Error:", hresult)
|
||||||
|
|
||||||
|
|
||||||
|
@ -98,7 +98,7 @@ class Caret:
|
|||||||
|
|
||||||
colors = r, g, b, self._visible_alpha, r, g, b, self._visible_alpha
|
colors = r, g, b, self._visible_alpha, r, g, b, self._visible_alpha
|
||||||
|
|
||||||
self._list = self._group.program.vertex_list(2, gl.GL_LINES, batch, self._group, colors=('Bn', colors))
|
self._list = self._group.program.vertex_list(2, gl.GL_LINES, self._batch, self._group, colors=('Bn', colors))
|
||||||
self._ideal_x = None
|
self._ideal_x = None
|
||||||
self._ideal_line = None
|
self._ideal_line = None
|
||||||
self._next_attributes = {}
|
self._next_attributes = {}
|
||||||
@ -127,6 +127,7 @@ class Caret:
|
|||||||
|
|
||||||
Also disconnects the caret from further layout events.
|
Also disconnects the caret from further layout events.
|
||||||
"""
|
"""
|
||||||
|
clock.unschedule(self._blink)
|
||||||
self._list.delete()
|
self._list.delete()
|
||||||
self._layout.remove_handlers(self)
|
self._layout.remove_handlers(self)
|
||||||
|
|
||||||
|
@ -6,6 +6,7 @@
|
|||||||
|
|
||||||
import time
|
import time
|
||||||
|
|
||||||
|
from string import Template
|
||||||
from typing import List
|
from typing import List
|
||||||
from lib_not_dr.types.options import Options
|
from lib_not_dr.types.options import Options
|
||||||
|
|
||||||
@ -51,11 +52,12 @@ class TimeFormatter(BaseFormatter):
|
|||||||
name = 'TimeFormatter'
|
name = 'TimeFormatter'
|
||||||
|
|
||||||
time_format: str = '%Y-%m-%d %H:%M:%S'
|
time_format: str = '%Y-%m-%d %H:%M:%S'
|
||||||
|
msec_time_format: str = '{}-{:03d}'
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def _info(cls) -> str:
|
def _info(cls) -> str:
|
||||||
return cls.add_info('log_time', 'when the log message was created', 'The time format string'
|
return cls.add_info('log_time', 'formatted time when logging', 'The time format string'
|
||||||
'. See https://docs.python.org/3/library/time.html#time.strftime for more information.')
|
'. See https://docs.python.org/3/library/time.html#time.strftime for more information.')
|
||||||
|
|
||||||
def format(self, message: LogMessage) -> str:
|
def format(self, message: LogMessage) -> str:
|
||||||
return f'[{message.log_time}]'
|
return f'[{message.log_time}]'
|
||||||
|
@ -2,6 +2,9 @@
|
|||||||
# DR basic running from source
|
# DR basic running from source
|
||||||
# DR build (by nuitka)
|
# DR build (by nuitka)
|
||||||
|
|
||||||
|
# for function
|
||||||
|
lib-not-dr
|
||||||
|
|
||||||
# for images
|
# for images
|
||||||
# not for pypy >= 3.10
|
# not for pypy >= 3.10
|
||||||
pillow >= 10.0.0; (platform_python_implementation == "PyPy" and python_version < "3.10") or platform_python_implementation == "CPython"
|
pillow >= 10.0.0; (platform_python_implementation == "PyPy" and python_version < "3.10") or platform_python_implementation == "CPython"
|
||||||
|
@ -3,6 +3,9 @@
|
|||||||
# DR build (by nuitka)
|
# DR build (by nuitka)
|
||||||
# DR contributing
|
# DR contributing
|
||||||
|
|
||||||
|
# for function
|
||||||
|
lib-not-dr
|
||||||
|
|
||||||
# for images
|
# for images
|
||||||
# not for pypy >= 3.10
|
# not for pypy >= 3.10
|
||||||
pillow >= 10.0.0; (platform_python_implementation == "PyPy" and python_version < "3.10") or platform_python_implementation == "CPython"
|
pillow >= 10.0.0; (platform_python_implementation == "PyPy" and python_version < "3.10") or platform_python_implementation == "CPython"
|
||||||
|
@ -1,6 +1,9 @@
|
|||||||
# this requirement is for
|
# this requirement is for
|
||||||
# DR basic running from source
|
# DR basic running from source
|
||||||
|
|
||||||
|
# for function
|
||||||
|
lib-not-dr
|
||||||
|
|
||||||
# for images
|
# for images
|
||||||
# not for pypy >= 3.10
|
# not for pypy >= 3.10
|
||||||
pillow >= 10.0.0; (platform_python_implementation == "PyPy" and python_version < "3.10") or platform_python_implementation == "CPython"
|
pillow >= 10.0.0; (platform_python_implementation == "PyPy" and python_version < "3.10") or platform_python_implementation == "CPython"
|
||||||
|
Loading…
Reference in New Issue
Block a user