libretro.py 0.0.0__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.
Files changed (129) hide show
  1. libretro/__init__.py +6 -0
  2. libretro/_utils.py +388 -0
  3. libretro/api/__init__.py +52 -0
  4. libretro/api/_utils.py +172 -0
  5. libretro/api/audio.py +42 -0
  6. libretro/api/av.py +78 -0
  7. libretro/api/camera.py +80 -0
  8. libretro/api/content.py +457 -0
  9. libretro/api/disk.py +75 -0
  10. libretro/api/environment.py +187 -0
  11. libretro/api/input/__init__.py +7 -0
  12. libretro/api/input/analog.py +106 -0
  13. libretro/api/input/device.py +144 -0
  14. libretro/api/input/joypad.py +130 -0
  15. libretro/api/input/keyboard.py +529 -0
  16. libretro/api/input/lightgun.py +134 -0
  17. libretro/api/input/mouse.py +80 -0
  18. libretro/api/input/pointer.py +35 -0
  19. libretro/api/led.py +17 -0
  20. libretro/api/location.py +40 -0
  21. libretro/api/log.py +58 -0
  22. libretro/api/memory.py +86 -0
  23. libretro/api/message.py +70 -0
  24. libretro/api/microphone.py +77 -0
  25. libretro/api/midi.py +34 -0
  26. libretro/api/netpacket.py +64 -0
  27. libretro/api/options.py +159 -0
  28. libretro/api/perf.py +122 -0
  29. libretro/api/power.py +44 -0
  30. libretro/api/proc.py +29 -0
  31. libretro/api/rumble.py +33 -0
  32. libretro/api/savestate.py +54 -0
  33. libretro/api/sensor.py +118 -0
  34. libretro/api/timing.py +81 -0
  35. libretro/api/user.py +84 -0
  36. libretro/api/vfs.py +220 -0
  37. libretro/api/video/__init__.py +4 -0
  38. libretro/api/video/context.py +89 -0
  39. libretro/api/video/frame.py +107 -0
  40. libretro/api/video/negotiate.py +24 -0
  41. libretro/api/video/render.py +48 -0
  42. libretro/builder.py +772 -0
  43. libretro/core.py +674 -0
  44. libretro/drivers/__init__.py +32 -0
  45. libretro/drivers/audio/__init__.py +7 -0
  46. libretro/drivers/audio/array.py +78 -0
  47. libretro/drivers/audio/driver.py +79 -0
  48. libretro/drivers/audio/wave.py +95 -0
  49. libretro/drivers/camera/__init__.py +2 -0
  50. libretro/drivers/camera/driver.py +106 -0
  51. libretro/drivers/camera/generator.py +65 -0
  52. libretro/drivers/content/__init__.py +6 -0
  53. libretro/drivers/content/driver.py +153 -0
  54. libretro/drivers/content/standard.py +506 -0
  55. libretro/drivers/disk/__init__.py +1 -0
  56. libretro/drivers/disk/driver.py +49 -0
  57. libretro/drivers/environment/__init__.py +9 -0
  58. libretro/drivers/environment/composite.py +1441 -0
  59. libretro/drivers/environment/default.py +292 -0
  60. libretro/drivers/environment/dict.py +52 -0
  61. libretro/drivers/environment/driver.py +339 -0
  62. libretro/drivers/input/__init__.py +2 -0
  63. libretro/drivers/input/driver.py +122 -0
  64. libretro/drivers/input/generator.py +587 -0
  65. libretro/drivers/led/__init__.py +2 -0
  66. libretro/drivers/led/dict.py +20 -0
  67. libretro/drivers/led/driver.py +32 -0
  68. libretro/drivers/location/__init__.py +2 -0
  69. libretro/drivers/location/driver.py +117 -0
  70. libretro/drivers/location/generator.py +67 -0
  71. libretro/drivers/log/__init__.py +2 -0
  72. libretro/drivers/log/driver.py +18 -0
  73. libretro/drivers/log/unformatted.py +34 -0
  74. libretro/drivers/message/__init__.py +2 -0
  75. libretro/drivers/message/driver.py +21 -0
  76. libretro/drivers/message/logger.py +50 -0
  77. libretro/drivers/microphone/__init__.py +2 -0
  78. libretro/drivers/microphone/driver.py +225 -0
  79. libretro/drivers/microphone/generator.py +110 -0
  80. libretro/drivers/midi/__init__.py +2 -0
  81. libretro/drivers/midi/driver.py +79 -0
  82. libretro/drivers/midi/generator.py +76 -0
  83. libretro/drivers/netpacket/__init__.py +2 -0
  84. libretro/drivers/netpacket/driver.py +78 -0
  85. libretro/drivers/netpacket/socket.py +71 -0
  86. libretro/drivers/options/__init__.py +6 -0
  87. libretro/drivers/options/dict.py +305 -0
  88. libretro/drivers/options/driver.py +107 -0
  89. libretro/drivers/path/__init__.py +6 -0
  90. libretro/drivers/path/default.py +97 -0
  91. libretro/drivers/path/driver.py +42 -0
  92. libretro/drivers/perf/__init__.py +2 -0
  93. libretro/drivers/perf/default.py +84 -0
  94. libretro/drivers/perf/driver.py +84 -0
  95. libretro/drivers/power/__init__.py +1 -0
  96. libretro/drivers/power/driver.py +35 -0
  97. libretro/drivers/rumble/__init__.py +2 -0
  98. libretro/drivers/rumble/default.py +58 -0
  99. libretro/drivers/rumble/interface.py +51 -0
  100. libretro/drivers/sensor/__init__.py +2 -0
  101. libretro/drivers/sensor/generator.py +414 -0
  102. libretro/drivers/sensor/interface.py +74 -0
  103. libretro/drivers/timing/__init__.py +2 -0
  104. libretro/drivers/timing/default.py +101 -0
  105. libretro/drivers/timing/driver.py +69 -0
  106. libretro/drivers/user/__init__.py +2 -0
  107. libretro/drivers/user/default.py +55 -0
  108. libretro/drivers/user/driver.py +20 -0
  109. libretro/drivers/vfs/__init__.py +3 -0
  110. libretro/drivers/vfs/default.py +182 -0
  111. libretro/drivers/vfs/history.py +268 -0
  112. libretro/drivers/vfs/interface.py +618 -0
  113. libretro/drivers/video/__init__.py +8 -0
  114. libretro/drivers/video/driver.py +146 -0
  115. libretro/drivers/video/multi.py +113 -0
  116. libretro/drivers/video/opengl/__init__.py +4 -0
  117. libretro/drivers/video/opengl/moderngl.py +195 -0
  118. libretro/drivers/video/software/__init__.py +7 -0
  119. libretro/drivers/video/software/array.py +136 -0
  120. libretro/drivers/video/software/base.py +92 -0
  121. libretro/drivers/video/software/pillow.py +171 -0
  122. libretro/error.py +14 -0
  123. libretro/h.py +5 -0
  124. libretro/session.py +335 -0
  125. libretro.py-0.0.0.dist-info/LICENSE +21 -0
  126. libretro.py-0.0.0.dist-info/METADATA +150 -0
  127. libretro.py-0.0.0.dist-info/RECORD +129 -0
  128. libretro.py-0.0.0.dist-info/WHEEL +5 -0
  129. libretro.py-0.0.0.dist-info/top_level.txt +1 -0
libretro/__init__.py ADDED
@@ -0,0 +1,6 @@
1
+ from .api import *
2
+ from .builder import *
3
+ from .core import *
4
+ from .drivers import *
5
+ from .error import *
6
+ from .session import *
libretro/_utils.py ADDED
@@ -0,0 +1,388 @@
1
+ import ctypes
2
+ import sys
3
+ from abc import abstractmethod
4
+ from ctypes import POINTER, Union, c_char, c_char_p, cast
5
+ from typing import Protocol, runtime_checkable
6
+
7
+ from libretro.api._utils import c_uintptr
8
+
9
+ if not hasattr(ctypes, "c_uintptr"):
10
+ ctypes._check_size(c_uintptr)
11
+
12
+
13
+ class UserString:
14
+ def __init__(self, seq):
15
+ if isinstance(seq, bytes):
16
+ self.data = seq
17
+ elif isinstance(seq, UserString):
18
+ self.data = seq.data[:]
19
+ else:
20
+ self.data = str(seq).encode()
21
+
22
+ def __bytes__(self):
23
+ return self.data
24
+
25
+ def __str__(self):
26
+ return self.data.decode()
27
+
28
+ def __repr__(self):
29
+ return repr(self.data)
30
+
31
+ def __int__(self):
32
+ return int(self.data.decode())
33
+
34
+ def __long__(self):
35
+ return int(self.data.decode())
36
+
37
+ def __float__(self):
38
+ return float(self.data.decode())
39
+
40
+ def __complex__(self):
41
+ return complex(self.data.decode())
42
+
43
+ def __hash__(self):
44
+ return hash(self.data)
45
+
46
+ def __le__(self, string):
47
+ if isinstance(string, UserString):
48
+ return self.data <= string.data
49
+ else:
50
+ return self.data <= string
51
+
52
+ def __lt__(self, string):
53
+ if isinstance(string, UserString):
54
+ return self.data < string.data
55
+ else:
56
+ return self.data < string
57
+
58
+ def __ge__(self, string):
59
+ if isinstance(string, UserString):
60
+ return self.data >= string.data
61
+ else:
62
+ return self.data >= string
63
+
64
+ def __gt__(self, string):
65
+ if isinstance(string, UserString):
66
+ return self.data > string.data
67
+ else:
68
+ return self.data > string
69
+
70
+ def __eq__(self, string):
71
+ if isinstance(string, UserString):
72
+ return self.data == string.data
73
+ else:
74
+ return self.data == string
75
+
76
+ def __ne__(self, string):
77
+ if isinstance(string, UserString):
78
+ return self.data != string.data
79
+ else:
80
+ return self.data != string
81
+
82
+ def __contains__(self, char):
83
+ return char in self.data
84
+
85
+ def __len__(self):
86
+ return len(self.data)
87
+
88
+ def __getitem__(self, index):
89
+ return self.__class__(self.data[index])
90
+
91
+ def __getslice__(self, start, end):
92
+ start = max(start, 0)
93
+ end = max(end, 0)
94
+ return self.__class__(self.data[start:end])
95
+
96
+ def __add__(self, other):
97
+ if isinstance(other, UserString):
98
+ return self.__class__(self.data + other.data)
99
+ elif isinstance(other, bytes):
100
+ return self.__class__(self.data + other)
101
+ else:
102
+ return self.__class__(self.data + str(other).encode())
103
+
104
+ def __radd__(self, other):
105
+ if isinstance(other, bytes):
106
+ return self.__class__(other + self.data)
107
+ else:
108
+ return self.__class__(str(other).encode() + self.data)
109
+
110
+ def __mul__(self, n):
111
+ return self.__class__(self.data * n)
112
+
113
+ __rmul__ = __mul__
114
+
115
+ def __mod__(self, args):
116
+ return self.__class__(self.data % args)
117
+
118
+ # the following methods are defined in alphabetical order:
119
+ def capitalize(self):
120
+ return self.__class__(self.data.capitalize())
121
+
122
+ def center(self, width, *args):
123
+ return self.__class__(self.data.center(width, *args))
124
+
125
+ def count(self, sub, start=0, end=sys.maxsize):
126
+ return self.data.count(sub, start, end)
127
+
128
+ def decode(self, encoding=None, errors=None): # XXX improve this?
129
+ if encoding:
130
+ if errors:
131
+ return self.__class__(self.data.decode(encoding, errors))
132
+ else:
133
+ return self.__class__(self.data.decode(encoding))
134
+ else:
135
+ return self.__class__(self.data.decode())
136
+
137
+ def encode(self, encoding=None, errors=None): # XXX improve this?
138
+ if encoding:
139
+ if errors:
140
+ return self.__class__(self.data.encode(encoding, errors))
141
+ else:
142
+ return self.__class__(self.data.encode(encoding))
143
+ else:
144
+ return self.__class__(self.data.encode())
145
+
146
+ def endswith(self, suffix, start=0, end=sys.maxsize):
147
+ return self.data.endswith(suffix, start, end)
148
+
149
+ def expandtabs(self, tabsize=8):
150
+ return self.__class__(self.data.expandtabs(tabsize))
151
+
152
+ def find(self, sub, start=0, end=sys.maxsize):
153
+ return self.data.find(sub, start, end)
154
+
155
+ def index(self, sub, start=0, end=sys.maxsize):
156
+ return self.data.index(sub, start, end)
157
+
158
+ def isalpha(self):
159
+ return self.data.isalpha()
160
+
161
+ def isalnum(self):
162
+ return self.data.isalnum()
163
+
164
+ def isdecimal(self):
165
+ return self.data.isdecimal()
166
+
167
+ def isdigit(self):
168
+ return self.data.isdigit()
169
+
170
+ def islower(self):
171
+ return self.data.islower()
172
+
173
+ def isnumeric(self):
174
+ return self.data.isnumeric()
175
+
176
+ def isspace(self):
177
+ return self.data.isspace()
178
+
179
+ def istitle(self):
180
+ return self.data.istitle()
181
+
182
+ def isupper(self):
183
+ return self.data.isupper()
184
+
185
+ def join(self, seq):
186
+ return self.data.join(seq)
187
+
188
+ def ljust(self, width, *args):
189
+ return self.__class__(self.data.ljust(width, *args))
190
+
191
+ def lower(self):
192
+ return self.__class__(self.data.lower())
193
+
194
+ def lstrip(self, chars=None):
195
+ return self.__class__(self.data.lstrip(chars))
196
+
197
+ def partition(self, sep):
198
+ return self.data.partition(sep)
199
+
200
+ def replace(self, old, new, maxsplit=-1):
201
+ return self.__class__(self.data.replace(old, new, maxsplit))
202
+
203
+ def rfind(self, sub, start=0, end=sys.maxsize):
204
+ return self.data.rfind(sub, start, end)
205
+
206
+ def rindex(self, sub, start=0, end=sys.maxsize):
207
+ return self.data.rindex(sub, start, end)
208
+
209
+ def rjust(self, width, *args):
210
+ return self.__class__(self.data.rjust(width, *args))
211
+
212
+ def rpartition(self, sep):
213
+ return self.data.rpartition(sep)
214
+
215
+ def rstrip(self, chars=None):
216
+ return self.__class__(self.data.rstrip(chars))
217
+
218
+ def split(self, sep=None, maxsplit=-1):
219
+ return self.data.split(sep, maxsplit)
220
+
221
+ def rsplit(self, sep=None, maxsplit=-1):
222
+ return self.data.rsplit(sep, maxsplit)
223
+
224
+ def splitlines(self, keepends=0):
225
+ return self.data.splitlines(keepends)
226
+
227
+ def startswith(self, prefix, start=0, end=sys.maxsize):
228
+ return self.data.startswith(prefix, start, end)
229
+
230
+ def strip(self, chars=None):
231
+ return self.__class__(self.data.strip(chars))
232
+
233
+ def swapcase(self):
234
+ return self.__class__(self.data.swapcase())
235
+
236
+ def title(self):
237
+ return self.__class__(self.data.title())
238
+
239
+ def translate(self, *args):
240
+ return self.__class__(self.data.translate(*args))
241
+
242
+ def upper(self):
243
+ return self.__class__(self.data.upper())
244
+
245
+ def zfill(self, width):
246
+ return self.__class__(self.data.zfill(width))
247
+
248
+
249
+ class MutableString(UserString):
250
+ """mutable string objects
251
+
252
+ Python strings are immutable objects. This has the advantage, that
253
+ strings may be used as dictionary keys. If this property isn't needed
254
+ and you insist on changing string values in place instead, you may cheat
255
+ and use MutableString.
256
+
257
+ But the purpose of this class is an educational one: to prevent
258
+ people from inventing their own mutable string class derived
259
+ from UserString and than forget thereby to remove (override) the
260
+ __hash__ method inherited from UserString. This would lead to
261
+ errors that would be very hard to track down.
262
+
263
+ A faster and better solution is to rewrite your program using lists."""
264
+
265
+ def __init__(self, string=""):
266
+ self.data = string
267
+
268
+ def __hash__(self):
269
+ raise TypeError("unhashable type (it is mutable)")
270
+
271
+ def __setitem__(self, index, sub):
272
+ if index < 0:
273
+ index += len(self.data)
274
+ if index < 0 or index >= len(self.data):
275
+ raise IndexError
276
+ self.data = self.data[:index] + sub + self.data[index + 1 :]
277
+
278
+ def __delitem__(self, index):
279
+ if index < 0:
280
+ index += len(self.data)
281
+ if index < 0 or index >= len(self.data):
282
+ raise IndexError
283
+ self.data = self.data[:index] + self.data[index + 1 :]
284
+
285
+ def __setslice__(self, start, end, sub):
286
+ start = max(start, 0)
287
+ end = max(end, 0)
288
+ if isinstance(sub, UserString):
289
+ self.data = self.data[:start] + sub.data + self.data[end:]
290
+ elif isinstance(sub, bytes):
291
+ self.data = self.data[:start] + sub + self.data[end:]
292
+ else:
293
+ self.data = self.data[:start] + str(sub).encode() + self.data[end:]
294
+
295
+ def __delslice__(self, start, end):
296
+ start = max(start, 0)
297
+ end = max(end, 0)
298
+ self.data = self.data[:start] + self.data[end:]
299
+
300
+ def immutable(self):
301
+ return UserString(self.data)
302
+
303
+ def __iadd__(self, other):
304
+ if isinstance(other, UserString):
305
+ self.data += other.data
306
+ elif isinstance(other, bytes):
307
+ self.data += other
308
+ else:
309
+ self.data += str(other).encode()
310
+ return self
311
+
312
+ def __imul__(self, n):
313
+ self.data *= n
314
+ return self
315
+
316
+
317
+ class String(MutableString, Union):
318
+ _fields_ = [("raw", POINTER(c_char)), ("data", c_char_p)]
319
+
320
+ def __init__(self, obj=b""):
321
+ if isinstance(obj, (bytes, UserString)):
322
+ self.data = bytes(obj)
323
+ else:
324
+ self.raw = obj
325
+
326
+ def __len__(self):
327
+ return self.data and len(self.data) or 0
328
+
329
+ def from_param(cls, obj):
330
+ # Convert None or 0
331
+ if obj is None or obj == 0:
332
+ return cls(POINTER(c_char)())
333
+
334
+ # Convert from String
335
+ elif isinstance(obj, String):
336
+ return obj
337
+
338
+ # Convert from bytes
339
+ elif isinstance(obj, bytes):
340
+ return cls(obj)
341
+
342
+ # Convert from str
343
+ elif isinstance(obj, str):
344
+ return cls(obj.encode())
345
+
346
+ # Convert from c_char_p
347
+ elif isinstance(obj, c_char_p):
348
+ return obj
349
+
350
+ # Convert from POINTER(ctypes.c_char)
351
+ elif isinstance(obj, POINTER(c_char)):
352
+ return obj
353
+
354
+ # Convert from raw pointer
355
+ elif isinstance(obj, int):
356
+ return cls(cast(obj, POINTER(c_char)))
357
+
358
+ # Convert from ctypes.c_char array
359
+ elif isinstance(obj, c_char * len(obj)):
360
+ return obj
361
+
362
+ # Convert from object
363
+ else:
364
+ return String.from_param(obj._as_parameter_)
365
+
366
+ from_param = classmethod(from_param)
367
+
368
+
369
+ def ReturnString(obj, func=None, arguments=None):
370
+ return String.from_param(obj)
371
+
372
+
373
+ def ord_if_char(value):
374
+ """
375
+ Simple helper used for casts to simple builtin types: if the argument is a
376
+ string type, it will be converted to it's ordinal value.
377
+
378
+ This function will raise an exception if the argument is string with more
379
+ than one characters.
380
+ """
381
+ return ord(value) if (isinstance(value, bytes) or isinstance(value, str)) else value
382
+
383
+
384
+ @runtime_checkable
385
+ class Pollable(Protocol):
386
+ @abstractmethod
387
+ def poll(self) -> None:
388
+ ...
@@ -0,0 +1,52 @@
1
+ """
2
+ This module contains types that directly correspond to the libretro API.
3
+
4
+ All ``retro_`` classes in this package are ctypes wrappers around their equivalents in
5
+ `libretro-common <https://github.com/libretro/RetroArch/blob/master/libretro-common/include/libretro.h>`_.
6
+
7
+ Unless otherwise noted, all structs can be copied with ``copy.deepcopy``;
8
+ the struct itself and its fields (including strings and buffers) are all deep-copied.
9
+ For example:
10
+
11
+ .. code-block:: python
12
+
13
+ import copy
14
+ from libretro.api import retro_controller_description
15
+
16
+ desc = retro_controller_description(b'Game Pad', 5)
17
+ desc_copy = copy.deepcopy(desc)
18
+
19
+ assert desc == desc_copy
20
+
21
+ desc.desc = b'Another Game Pad'
22
+ assert desc != desc_copy
23
+
24
+ Additionally, all `c_char_p` fields are converted to Python `bytes` objects when accessed.
25
+ """
26
+
27
+ from .audio import *
28
+ from .av import *
29
+ from .camera import *
30
+ from .content import *
31
+ from .disk import *
32
+ from .environment import *
33
+ from .input import *
34
+ from .led import *
35
+ from .location import *
36
+ from .log import *
37
+ from .memory import *
38
+ from .message import *
39
+ from .microphone import *
40
+ from .midi import *
41
+ from .netpacket import *
42
+ from .options import *
43
+ from .perf import *
44
+ from .power import *
45
+ from .proc import *
46
+ from .rumble import *
47
+ from .savestate import *
48
+ from .sensor import *
49
+ from .timing import *
50
+ from .user import *
51
+ from .vfs import *
52
+ from .video import *
libretro/api/_utils.py ADDED
@@ -0,0 +1,172 @@
1
+ import ctypes
2
+ import mmap
3
+ from collections.abc import Buffer, Iterator
4
+ from contextlib import contextmanager
5
+ from copy import deepcopy
6
+ from ctypes import (
7
+ POINTER,
8
+ Array,
9
+ Structure,
10
+ addressof,
11
+ c_char_p,
12
+ c_double,
13
+ c_int,
14
+ c_int16,
15
+ c_int32,
16
+ c_int64,
17
+ c_size_t,
18
+ c_ssize_t,
19
+ c_ubyte,
20
+ c_uint8,
21
+ c_void_p,
22
+ cast,
23
+ py_object,
24
+ pythonapi,
25
+ sizeof,
26
+ )
27
+ from os import PathLike
28
+ from typing import TypeAlias, get_type_hints
29
+
30
+ # When https://github.com/python/cpython/issues/112015 is merged,
31
+ # use ctypes.memoryview_at instead of this hack
32
+ # taken from https://stackoverflow.com/a/72968176/1089957
33
+ pythonapi.PyMemoryView_FromMemory.argtypes = (c_char_p, c_ssize_t, c_int)
34
+ pythonapi.PyMemoryView_FromMemory.restype = py_object
35
+
36
+ _int_types = (c_int16, c_int32)
37
+ if hasattr(ctypes, "c_int64"):
38
+ # Some builds of ctypes apparently do not have ctypes.c_int64
39
+ # defined; it's a pretty good bet that these builds do not
40
+ # have 64-bit pointers.
41
+ _int_types += (c_int64,)
42
+ for t in _int_types:
43
+ if sizeof(t) == sizeof(c_size_t):
44
+ c_ptrdiff_t = t
45
+ del t
46
+ del _int_types
47
+
48
+
49
+ # From https://blag.nullteilerfrei.de/2021/06/20/prettier-struct-definitions-for-python-ctypes
50
+ # Please use it for all future struct definitions
51
+ class FieldsFromTypeHints(type(Structure)):
52
+ def __new__(cls, name, bases, namespace):
53
+ class AnnotationDummy:
54
+ __annotations__ = namespace.get("__annotations__", {})
55
+
56
+ annotations = get_type_hints(AnnotationDummy)
57
+ namespace["_fields_"] = list(annotations.items())
58
+ namespace["__slots__"] = list(annotations.keys())
59
+ return type(Structure).__new__(cls, name, bases, namespace)
60
+
61
+
62
+ def as_bytes(value: str | bytes | None) -> bytes | None:
63
+ if isinstance(value, str):
64
+ return value.encode("utf-8")
65
+ return value
66
+
67
+
68
+ def is_zeroed(struct: Structure) -> bool:
69
+ view = memoryview_at(addressof(struct), sizeof(struct), True)
70
+
71
+ return not any(view)
72
+
73
+
74
+ def from_zero_terminated[S](ptr) -> Iterator[S]:
75
+ if ptr:
76
+ i = 0
77
+ while not is_zeroed(ptr[i]):
78
+ yield ptr[i]
79
+ i += 1
80
+
81
+
82
+ Pointer: TypeAlias = ctypes._Pointer
83
+
84
+
85
+ def deepcopy_array(array: Pointer, length: int, memo):
86
+ if not array:
87
+ return None
88
+
89
+ arraytype: type[Array] = array._type_ * length
90
+ return arraytype(*(deepcopy(array[i], memo) for i in range(length)))
91
+
92
+
93
+ def deepcopy_buffer(ptr: c_void_p | int, size: int) -> c_void_p | None:
94
+ if ptr is not None and not isinstance(ptr, (c_void_p, int)):
95
+ raise TypeError(f"Expected c_void_p or int, got {type(ptr).__name__}")
96
+
97
+ if not ptr:
98
+ return None
99
+
100
+ if not size:
101
+ return None
102
+
103
+ arraytype: type[Array] = c_uint8 * size
104
+ data = arraytype.from_buffer_copy(c_void_p(ptr))
105
+ return cast(data, c_void_p)
106
+
107
+
108
+ @contextmanager
109
+ def mmap_file(path: str | PathLike, mode: str = "rb"):
110
+ with open(path, mode, buffering=0) as f:
111
+ with mmap.mmap(f.fileno(), 0, access=mmap.ACCESS_COPY) as m:
112
+ view = memoryview(m)
113
+ yield view
114
+ view.release()
115
+ # If we don't release the memoryview manually,
116
+ # we'll get a BufferError when the context manager exits
117
+ f.close()
118
+
119
+
120
+ def addressof_buffer(buffer: Buffer) -> int:
121
+ if not isinstance(buffer, Buffer):
122
+ raise TypeError(f"Expected Buffer, got {type(buffer).__name__}")
123
+ array_type: type[Array] = c_ubyte * len(buffer)
124
+ buffer_array = array_type.from_buffer(buffer)
125
+
126
+ return ctypes.addressof(buffer_array)
127
+
128
+
129
+ def memoryview_at(
130
+ address: c_char_p | c_void_p | int, size: c_ssize_t | int, readonly=False
131
+ ) -> memoryview:
132
+ flags = c_int(0x100 if readonly else 0x200)
133
+ return pythonapi.PyMemoryView_FromMemory(cast(address, c_char_p), c_ssize_t(size), flags)
134
+
135
+
136
+ class c_uintptr(ctypes._SimpleCData):
137
+ _type_ = "P"
138
+
139
+
140
+ c_double_p = POINTER(c_double)
141
+
142
+
143
+ # As of ctypes 1.0, ctypes does not support custom error-checking
144
+ # functions on callbacks, nor does it support custom datatypes on
145
+ # callbacks, so we must ensure that all callbacks return
146
+ # primitive datatypes.
147
+ #
148
+ # Non-primitive return values wrapped with UNCHECKED won't be
149
+ # typechecked, and will be converted to ctypes.c_void_p.
150
+ def UNCHECKED(type):
151
+ if hasattr(type, "_type_") and isinstance(type._type_, str) and type._type_ != "P":
152
+ return type
153
+ else:
154
+ return c_void_p
155
+
156
+
157
+ __all__ = [
158
+ "FieldsFromTypeHints",
159
+ "as_bytes",
160
+ "is_zeroed",
161
+ "from_zero_terminated",
162
+ "Pointer",
163
+ "deepcopy_array",
164
+ "deepcopy_buffer",
165
+ "mmap_file",
166
+ "addressof_buffer",
167
+ "memoryview_at",
168
+ "c_ptrdiff_t",
169
+ "c_uintptr",
170
+ "c_double_p",
171
+ "UNCHECKED",
172
+ ]
libretro/api/audio.py ADDED
@@ -0,0 +1,42 @@
1
+ from ctypes import CFUNCTYPE, POINTER, Structure, c_bool, c_int16, c_size_t, c_uint
2
+ from dataclasses import dataclass
3
+
4
+ from libretro.api._utils import FieldsFromTypeHints
5
+
6
+ retro_audio_sample_t = CFUNCTYPE(None, c_int16, c_int16)
7
+ retro_audio_sample_batch_t = CFUNCTYPE(c_size_t, POINTER(c_int16), c_size_t)
8
+ retro_audio_callback_t = CFUNCTYPE(None)
9
+ retro_audio_set_state_callback_t = CFUNCTYPE(None, c_bool)
10
+ retro_audio_buffer_status_callback_t = CFUNCTYPE(None, c_bool, c_uint, c_bool)
11
+
12
+
13
+ @dataclass
14
+ class retro_audio_callback(Structure, metaclass=FieldsFromTypeHints):
15
+ callback: retro_audio_callback_t
16
+ set_state: retro_audio_set_state_callback_t
17
+
18
+ def __deepcopy__(self, _):
19
+ return retro_audio_callback(callback=self.callback, set_state=self.set_state)
20
+
21
+
22
+ @dataclass
23
+ class retro_audio_buffer_status_callback(Structure, metaclass=FieldsFromTypeHints):
24
+ callback: retro_audio_buffer_status_callback_t
25
+
26
+ def __call__(self, active: bool, occupancy: int, underrun_likely: bool) -> None:
27
+ if self.callback:
28
+ self.callback(active, occupancy, underrun_likely)
29
+
30
+ def __deepcopy__(self, _):
31
+ return retro_audio_buffer_status_callback(callback=self.callback)
32
+
33
+
34
+ __all__ = [
35
+ "retro_audio_sample_t",
36
+ "retro_audio_sample_batch_t",
37
+ "retro_audio_callback_t",
38
+ "retro_audio_set_state_callback_t",
39
+ "retro_audio_buffer_status_callback_t",
40
+ "retro_audio_callback",
41
+ "retro_audio_buffer_status_callback",
42
+ ]