libmdr 0.1.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- libmdr-0.1.0/LICENSE +21 -0
- libmdr-0.1.0/PKG-INFO +17 -0
- libmdr-0.1.0/README.md +1 -0
- libmdr-0.1.0/pyproject.toml +47 -0
- libmdr-0.1.0/setup.cfg +4 -0
- libmdr-0.1.0/setup.py +283 -0
- libmdr-0.1.0/src/libmdr/__init__.py +18 -0
- libmdr-0.1.0/src/libmdr/_api.py +10 -0
- libmdr-0.1.0/src/libmdr/_dll.py +92 -0
- libmdr-0.1.0/src/libmdr/_generated_api.py +428 -0
- libmdr-0.1.0/src/libmdr/_generated_constants.py +342 -0
- libmdr-0.1.0/src/libmdr/_generated_result.py +25 -0
- libmdr-0.1.0/src/libmdr/connection.py +73 -0
- libmdr-0.1.0/src/libmdr/constants.py +526 -0
- libmdr-0.1.0/src/libmdr/headphones.py +620 -0
- libmdr-0.1.0/src/libmdr/result.py +29 -0
- libmdr-0.1.0/src/libmdr.egg-info/PKG-INFO +17 -0
- libmdr-0.1.0/src/libmdr.egg-info/SOURCES.txt +19 -0
- libmdr-0.1.0/src/libmdr.egg-info/dependency_links.txt +1 -0
- libmdr-0.1.0/src/libmdr.egg-info/requires.txt +8 -0
- libmdr-0.1.0/src/libmdr.egg-info/top_level.txt +1 -0
libmdr-0.1.0/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 mos9527, Amr Satrio and other contributors
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
libmdr-0.1.0/PKG-INFO
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: libmdr
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Python bindings for the mdr-shared C ABI
|
|
5
|
+
License-Expression: MIT
|
|
6
|
+
Project-URL: Source, https://github.com/mos9527/libmdr-py
|
|
7
|
+
Requires-Python: >=3.10
|
|
8
|
+
Description-Content-Type: text/plain
|
|
9
|
+
License-File: LICENSE
|
|
10
|
+
Provides-Extra: dev
|
|
11
|
+
Provides-Extra: bt
|
|
12
|
+
Requires-Dist: libmdr-bt; extra == "bt"
|
|
13
|
+
Provides-Extra: cli
|
|
14
|
+
Requires-Dist: libmdr-cli; extra == "cli"
|
|
15
|
+
Dynamic: license-file
|
|
16
|
+
|
|
17
|
+
ctypes bindings for libmdr (mdr-shared).
|
libmdr-0.1.0/README.md
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
# libmdr
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["setuptools>=77"]
|
|
3
|
+
build-backend = "setuptools.build_meta"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "libmdr"
|
|
7
|
+
version = "0.1.0"
|
|
8
|
+
description = "Python bindings for the mdr-shared C ABI"
|
|
9
|
+
readme = { text = "ctypes bindings for libmdr (mdr-shared).", content-type = "text/plain" }
|
|
10
|
+
requires-python = ">=3.10"
|
|
11
|
+
license = "MIT"
|
|
12
|
+
license-files = ["LICENSE"]
|
|
13
|
+
|
|
14
|
+
[project.optional-dependencies]
|
|
15
|
+
dev = []
|
|
16
|
+
bt = ["libmdr-bt"]
|
|
17
|
+
cli = ["libmdr-cli"]
|
|
18
|
+
|
|
19
|
+
[project.urls]
|
|
20
|
+
Source = "https://github.com/mos9527/libmdr-py"
|
|
21
|
+
|
|
22
|
+
[tool.setuptools.packages.find]
|
|
23
|
+
where = ["src"]
|
|
24
|
+
|
|
25
|
+
[tool.setuptools.package-data]
|
|
26
|
+
libmdr = ["_native/*"]
|
|
27
|
+
|
|
28
|
+
# Wheels are tagged py3-none-<platform> (see setup.py): the native library is
|
|
29
|
+
# loaded via ctypes, so it is built once per platform and cibuildwheel reuses
|
|
30
|
+
# that wheel to run the tests on every other selected interpreter.
|
|
31
|
+
[tool.cibuildwheel]
|
|
32
|
+
skip = ["*-musllinux_*", "cp31[1-9]*-manylinux_armv7l"] # armv7l runs under QEMU: test once
|
|
33
|
+
build-verbosity = 1
|
|
34
|
+
test-command = "python -c \"import libmdr, libmdr._dll as d; print(d.load())\""
|
|
35
|
+
# Ubuntu 22.04 based; manylinux_2_31 (Ubuntu 20.04) defaults to GCC 9, without <span>.
|
|
36
|
+
# The resulting wheels still only need glibc 2.17: auditwheel tags them accordingly.
|
|
37
|
+
manylinux-armv7l-image = "manylinux_2_35"
|
|
38
|
+
|
|
39
|
+
[tool.cibuildwheel.linux]
|
|
40
|
+
before-all = "sh {project}/tooling/ci/linux-before-all.sh"
|
|
41
|
+
|
|
42
|
+
[tool.cibuildwheel.macos]
|
|
43
|
+
environment = { MACOSX_DEPLOYMENT_TARGET = "12.0" }
|
|
44
|
+
|
|
45
|
+
[tool.cibuildwheel.windows]
|
|
46
|
+
# No Python extension modules; mdr-shared.dll links the CRT statically.
|
|
47
|
+
repair-wheel-command = ""
|
libmdr-0.1.0/setup.cfg
ADDED
libmdr-0.1.0/setup.py
ADDED
|
@@ -0,0 +1,283 @@
|
|
|
1
|
+
"""Build script for libmdr (also the setuptools entry point).
|
|
2
|
+
|
|
3
|
+
The CMake build helpers that previously lived in a separate ``cmake_build.py``
|
|
4
|
+
are inlined here. Like that module, this is build-time code and is NOT packaged
|
|
5
|
+
into the wheel; it runs only during ``build_py`` / ``editable_wheel`` to compile
|
|
6
|
+
and stage the native shared library into the package's ``_native`` directory.
|
|
7
|
+
|
|
8
|
+
C++ source resolution order:
|
|
9
|
+
1. ``MDR_SOURCE_DIR`` env var (explicit override)
|
|
10
|
+
2. ``<repo>/cpp`` (git submodule, preferred)
|
|
11
|
+
3. ``<repo>/build/cpp-src`` (shallow fetch of ``_SOURCE_COMMIT`` on every build)
|
|
12
|
+
|
|
13
|
+
The third option lets ``pip install .`` / ``python -m build`` work from a
|
|
14
|
+
plain source checkout or sdist without the caller having to run
|
|
15
|
+
``git submodule update --init`` first.
|
|
16
|
+
"""
|
|
17
|
+
|
|
18
|
+
from __future__ import annotations
|
|
19
|
+
|
|
20
|
+
import configparser
|
|
21
|
+
import os
|
|
22
|
+
import re
|
|
23
|
+
import shutil
|
|
24
|
+
import stat
|
|
25
|
+
import subprocess
|
|
26
|
+
import sys
|
|
27
|
+
import time
|
|
28
|
+
from pathlib import Path
|
|
29
|
+
|
|
30
|
+
from setuptools import Distribution, setup
|
|
31
|
+
from setuptools.command.bdist_wheel import bdist_wheel as _bdist_wheel
|
|
32
|
+
from setuptools.command.build_py import build_py as _build_py
|
|
33
|
+
from setuptools.command.editable_wheel import editable_wheel as _editable_wheel
|
|
34
|
+
from setuptools.command.sdist import sdist as _sdist
|
|
35
|
+
|
|
36
|
+
# Python repo root (this file lives in <repo>/<pkg>/setup.py).
|
|
37
|
+
_PY_ROOT = Path(__file__).resolve().parents[1]
|
|
38
|
+
DEFAULT_BUILD_DIR = _PY_ROOT / "build" / "python"
|
|
39
|
+
# Fresh C++ source clone location (separate from the CMake build dir so the
|
|
40
|
+
# build dir can be wiped between builds without discarding the clone).
|
|
41
|
+
_CLONE_DIR = _PY_ROOT / "build" / "cpp-src"
|
|
42
|
+
# Preferred on-disk location of the C++ sources.
|
|
43
|
+
_SUBMODULE_DIR = _PY_ROOT / "cpp"
|
|
44
|
+
# Upstream coordinates, used only when fetching on demand. The URL is
|
|
45
|
+
# overridden by .gitmodules when present. Upstream has no releases yet, so the
|
|
46
|
+
# sources are pinned to a commit matching the generated bindings.
|
|
47
|
+
_SOURCE_URL = "https://github.com/mos9527/SonyHeadphonesClient.git"
|
|
48
|
+
_SOURCE_COMMIT = "2edf09098db27642c89e533bf86c0a6b162f6ad4"
|
|
49
|
+
|
|
50
|
+
# CMake target -> on-disk base name (OUTPUT_NAME set in the C++ CMakeLists).
|
|
51
|
+
_TARGET_BASENAMES = {
|
|
52
|
+
"mdr_shared": "mdr-shared",
|
|
53
|
+
"mdr-bt_shared": "mdr-bt-shared",
|
|
54
|
+
}
|
|
55
|
+
_SUFFIXES = (".dll", ".so", ".dylib")
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
def _cmake() -> str:
|
|
59
|
+
env = os.environ.get("CMAKE")
|
|
60
|
+
if env:
|
|
61
|
+
return env
|
|
62
|
+
found = shutil.which("cmake")
|
|
63
|
+
if found:
|
|
64
|
+
return found
|
|
65
|
+
raise RuntimeError("cmake not found on PATH; install CMake or set CMAKE.")
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
def _run(cmd: list[str]) -> None:
|
|
69
|
+
print("+", " ".join(cmd), flush=True)
|
|
70
|
+
subprocess.check_call(cmd)
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
def _read_gitmodules() -> str | None:
|
|
74
|
+
"""Return the url of the 'cpp' submodule from .gitmodules, if present."""
|
|
75
|
+
gm = _PY_ROOT / ".gitmodules"
|
|
76
|
+
if not gm.is_file():
|
|
77
|
+
return None
|
|
78
|
+
cp = configparser.ConfigParser()
|
|
79
|
+
cp.read(gm, encoding="utf-8")
|
|
80
|
+
for section in cp.sections():
|
|
81
|
+
if cp.has_option(section, "path") and cp.get(section, "path") == "cpp":
|
|
82
|
+
return cp.get(section, "url", fallback=_SOURCE_URL)
|
|
83
|
+
return None
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
def _rmtree(path: Path) -> None:
|
|
87
|
+
"""Remove a directory tree, tolerating read-only files (git marks .git
|
|
88
|
+
objects read-only on Windows) and transient file locks."""
|
|
89
|
+
if not path.exists():
|
|
90
|
+
return
|
|
91
|
+
|
|
92
|
+
def _clear_ro(func, p, exc): # noqa: ANN001
|
|
93
|
+
try:
|
|
94
|
+
os.chmod(p, stat.S_IWRITE)
|
|
95
|
+
func(p)
|
|
96
|
+
except OSError:
|
|
97
|
+
raise
|
|
98
|
+
|
|
99
|
+
last_err: OSError | None = None
|
|
100
|
+
for _ in range(3):
|
|
101
|
+
try:
|
|
102
|
+
if sys.version_info >= (3, 12):
|
|
103
|
+
shutil.rmtree(path, onexc=_clear_ro)
|
|
104
|
+
else:
|
|
105
|
+
shutil.rmtree(path, onerror=_clear_ro)
|
|
106
|
+
return
|
|
107
|
+
except OSError as exc:
|
|
108
|
+
last_err = exc
|
|
109
|
+
time.sleep(0.5)
|
|
110
|
+
if last_err:
|
|
111
|
+
raise last_err
|
|
112
|
+
|
|
113
|
+
|
|
114
|
+
def _clone_source(dest: Path) -> None:
|
|
115
|
+
url = _read_gitmodules() or _SOURCE_URL
|
|
116
|
+
_rmtree(dest)
|
|
117
|
+
dest.mkdir(parents=True, exist_ok=True)
|
|
118
|
+
_run(["git", "-C", str(dest), "init", "-q"])
|
|
119
|
+
_run(["git", "-C", str(dest), "fetch", "--depth", "1", url, _SOURCE_COMMIT])
|
|
120
|
+
_run(["git", "-C", str(dest), "checkout", "-q", "FETCH_HEAD"])
|
|
121
|
+
|
|
122
|
+
|
|
123
|
+
def _ensure_source() -> Path:
|
|
124
|
+
"""Resolve the C++ source directory, cloning it into the build dir if needed."""
|
|
125
|
+
override = os.environ.get("MDR_SOURCE_DIR")
|
|
126
|
+
if override:
|
|
127
|
+
return Path(override)
|
|
128
|
+
if _SUBMODULE_DIR.is_dir() and any(_SUBMODULE_DIR.iterdir()):
|
|
129
|
+
return _SUBMODULE_DIR
|
|
130
|
+
clone_dir = _CLONE_DIR
|
|
131
|
+
print(f"cpp source not found at {_SUBMODULE_DIR}; fetching {_SOURCE_COMMIT} into {clone_dir}", flush=True)
|
|
132
|
+
_clone_source(clone_dir)
|
|
133
|
+
return clone_dir
|
|
134
|
+
|
|
135
|
+
|
|
136
|
+
def _config() -> str:
|
|
137
|
+
return os.environ.get("MDR_CMAKE_CONFIG", "MinSizeRel")
|
|
138
|
+
|
|
139
|
+
|
|
140
|
+
def _platform_args() -> list[str]:
|
|
141
|
+
args: list[str] = []
|
|
142
|
+
if sys.platform == "darwin":
|
|
143
|
+
# PEP 517 frontends / cibuildwheel request the target architecture(s)
|
|
144
|
+
# via ARCHFLAGS (e.g. "-arch x86_64"), which CMake does not read.
|
|
145
|
+
archs = re.findall(r"-arch\s+(\S+)", os.environ.get("ARCHFLAGS", ""))
|
|
146
|
+
if archs:
|
|
147
|
+
args.append("-DCMAKE_OSX_ARCHITECTURES=" + ";".join(archs))
|
|
148
|
+
return args
|
|
149
|
+
|
|
150
|
+
|
|
151
|
+
def _configure(build_dir: Path | None = None, *, config: str | None = None, extra_args: list[str] | None = None) -> Path:
|
|
152
|
+
build_dir = Path(build_dir or os.environ.get("MDR_PYTHON_BUILD_DIR", DEFAULT_BUILD_DIR))
|
|
153
|
+
# Always start from a clean build dir so a stale CMake cache or an
|
|
154
|
+
# interrupted dependency fetch (e.g. fmt via FetchContent) cannot poison
|
|
155
|
+
# the next configure. The C++ source is re-cloned separately each time.
|
|
156
|
+
_rmtree(build_dir)
|
|
157
|
+
build_dir.mkdir(parents=True, exist_ok=True)
|
|
158
|
+
cmd = [
|
|
159
|
+
_cmake(), "-S", str(_ensure_source()), "-B", str(build_dir),
|
|
160
|
+
# Single-config generators (Makefiles/Ninja) ignore `--build --config`.
|
|
161
|
+
f"-DCMAKE_BUILD_TYPE={config or _config()}",
|
|
162
|
+
"-DMDR_BUILD_TESTING=OFF",
|
|
163
|
+
"-DMDR_BUILD_CLIENT=OFF",
|
|
164
|
+
"-DMDR_BUILD_BT=OFF",
|
|
165
|
+
"-DMDR_BUILD_CODEGEN=OFF",
|
|
166
|
+
"-DMDR_CLIENT_DEBUGGER=OFF",
|
|
167
|
+
"-DMDR_ENABLE_LOG=OFF",
|
|
168
|
+
]
|
|
169
|
+
cmd.extend(_platform_args())
|
|
170
|
+
if extra_args:
|
|
171
|
+
cmd.extend(extra_args)
|
|
172
|
+
gen = os.environ.get("MDR_CMAKE_GENERATOR")
|
|
173
|
+
if gen:
|
|
174
|
+
cmd[1:1] = ["-G", gen]
|
|
175
|
+
_run(cmd)
|
|
176
|
+
return build_dir
|
|
177
|
+
|
|
178
|
+
|
|
179
|
+
def _build(targets: list[str], build_dir: Path | None = None, *, config: str | None = None, extra_args: list[str] | None = None) -> Path:
|
|
180
|
+
config = config or _config()
|
|
181
|
+
build_dir = _configure(build_dir, config=config, extra_args=extra_args)
|
|
182
|
+
cmd = [_cmake(), "--build", str(build_dir), "--config", config, "--parallel"]
|
|
183
|
+
for target in targets:
|
|
184
|
+
cmd.extend(["--target", target])
|
|
185
|
+
_run(cmd)
|
|
186
|
+
return build_dir
|
|
187
|
+
|
|
188
|
+
|
|
189
|
+
def _find_built(destination: Path, target: str) -> Path:
|
|
190
|
+
base = _TARGET_BASENAMES[target]
|
|
191
|
+
names = {f"{base}{suf}" for suf in _SUFFIXES}
|
|
192
|
+
names |= {f"lib{base}{suf}" for suf in (".so", ".dylib")}
|
|
193
|
+
for path in destination.rglob("*"):
|
|
194
|
+
if path.is_file() and path.name in names:
|
|
195
|
+
return path.resolve()
|
|
196
|
+
raise FileNotFoundError(
|
|
197
|
+
f"Built library for {target} not found under {destination}. "
|
|
198
|
+
f"Looked for: {', '.join(sorted(names))}"
|
|
199
|
+
)
|
|
200
|
+
|
|
201
|
+
|
|
202
|
+
def stage_libraries(targets: list[str], destination: Path) -> list[Path]:
|
|
203
|
+
"""Build the shared targets and emit them (flattened) into `destination`."""
|
|
204
|
+
destination = Path(destination).resolve()
|
|
205
|
+
destination.mkdir(parents=True, exist_ok=True)
|
|
206
|
+
# Build into the default build directory (e.g. build/python/) instead of
|
|
207
|
+
# redirecting CMake output directly into _native/. That avoids CMake's
|
|
208
|
+
# per-config subdir (MinSizeRel/ on MSVC) being discovered as a namespace
|
|
209
|
+
# package by setuptools.
|
|
210
|
+
build_dir = _build(targets)
|
|
211
|
+
staged: list[Path] = []
|
|
212
|
+
for target in targets:
|
|
213
|
+
found = _find_built(build_dir, target)
|
|
214
|
+
final = destination / found.name
|
|
215
|
+
if found.resolve() != final.resolve():
|
|
216
|
+
shutil.copy2(found, final)
|
|
217
|
+
staged.append(final)
|
|
218
|
+
return staged
|
|
219
|
+
|
|
220
|
+
|
|
221
|
+
def stage_library(target: str, destination: Path) -> Path:
|
|
222
|
+
"""Build one shared target and emit it (flattened) into `destination`."""
|
|
223
|
+
return stage_libraries([target], destination)[0]
|
|
224
|
+
|
|
225
|
+
|
|
226
|
+
def _stage_native() -> None:
|
|
227
|
+
destination = Path(__file__).resolve().parent / "src" / "libmdr" / "_native"
|
|
228
|
+
stage_library("mdr_shared", destination)
|
|
229
|
+
|
|
230
|
+
|
|
231
|
+
class build_py(_build_py):
|
|
232
|
+
def run(self) -> None:
|
|
233
|
+
_stage_native()
|
|
234
|
+
super().run()
|
|
235
|
+
|
|
236
|
+
|
|
237
|
+
class BinaryDistribution(Distribution):
|
|
238
|
+
"""Mark the distribution as platform specific.
|
|
239
|
+
|
|
240
|
+
There are no ext_modules (the native library is package data), so without
|
|
241
|
+
this the wheel is pure and installs into purelib, which auditwheel rejects.
|
|
242
|
+
"""
|
|
243
|
+
|
|
244
|
+
def has_ext_modules(self) -> bool:
|
|
245
|
+
return True
|
|
246
|
+
|
|
247
|
+
|
|
248
|
+
class bdist_wheel(_bdist_wheel):
|
|
249
|
+
"""Tag wheels ``py3-none-<platform>``.
|
|
250
|
+
|
|
251
|
+
The native library is loaded through ctypes and has no CPython ABI
|
|
252
|
+
dependency, so a single wheel per platform serves every Python 3
|
|
253
|
+
interpreter (CPython of any version, free-threaded builds, PyPy).
|
|
254
|
+
"""
|
|
255
|
+
|
|
256
|
+
def get_tag(self) -> tuple[str, str, str]:
|
|
257
|
+
_, _, plat = super().get_tag()
|
|
258
|
+
return "py3", "none", plat
|
|
259
|
+
|
|
260
|
+
|
|
261
|
+
class editable_wheel(_editable_wheel):
|
|
262
|
+
def run(self) -> None:
|
|
263
|
+
# build_py.run() (called internally by super()) already handles
|
|
264
|
+
# the native build via _stage_native(). Calling it here too
|
|
265
|
+
# would trigger a wasteful second configure+build cycle.
|
|
266
|
+
super().run()
|
|
267
|
+
|
|
268
|
+
|
|
269
|
+
class sdist(_sdist):
|
|
270
|
+
def run(self) -> None:
|
|
271
|
+
# Source distributions do not embed platform binaries.
|
|
272
|
+
super().run()
|
|
273
|
+
|
|
274
|
+
|
|
275
|
+
setup(
|
|
276
|
+
distclass=BinaryDistribution,
|
|
277
|
+
cmdclass={
|
|
278
|
+
"bdist_wheel": bdist_wheel,
|
|
279
|
+
"build_py": build_py,
|
|
280
|
+
"editable_wheel": editable_wheel,
|
|
281
|
+
"sdist": sdist,
|
|
282
|
+
}
|
|
283
|
+
)
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
"""Python bindings for libmdr (mdr-shared C ABI)."""
|
|
2
|
+
|
|
3
|
+
from . import constants, result
|
|
4
|
+
from .connection import Connection, DeviceInfo
|
|
5
|
+
from .headphones import Headphones
|
|
6
|
+
from .result import ABI_VERSION, MDRError, check, result_string
|
|
7
|
+
|
|
8
|
+
__all__ = [
|
|
9
|
+
"ABI_VERSION",
|
|
10
|
+
"Connection",
|
|
11
|
+
"DeviceInfo",
|
|
12
|
+
"Headphones",
|
|
13
|
+
"MDRError",
|
|
14
|
+
"check",
|
|
15
|
+
"constants",
|
|
16
|
+
"result",
|
|
17
|
+
"result_string",
|
|
18
|
+
]
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
"""ctypes loader for mdr-shared."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import os
|
|
6
|
+
import sys
|
|
7
|
+
from ctypes import CDLL, RTLD_GLOBAL
|
|
8
|
+
from pathlib import Path
|
|
9
|
+
|
|
10
|
+
_LIB: CDLL | None = None
|
|
11
|
+
|
|
12
|
+
_ENV_OVERRIDE = "MDR_DLL"
|
|
13
|
+
|
|
14
|
+
_CANDIDATE_NAMES = (
|
|
15
|
+
"mdr-shared.dll",
|
|
16
|
+
"mdr.dll",
|
|
17
|
+
"libmdr-shared.so",
|
|
18
|
+
"libmdr-shared.dylib",
|
|
19
|
+
"mdr-shared.so",
|
|
20
|
+
"mdr-shared.dylib",
|
|
21
|
+
)
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
def _package_native_dir() -> Path:
|
|
25
|
+
return Path(__file__).resolve().parent / "_native"
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
def _candidate_paths() -> list[Path]:
|
|
29
|
+
paths: list[Path] = []
|
|
30
|
+
override = os.environ.get(_ENV_OVERRIDE)
|
|
31
|
+
if override:
|
|
32
|
+
paths.append(Path(override))
|
|
33
|
+
|
|
34
|
+
native = _package_native_dir()
|
|
35
|
+
for name in _CANDIDATE_NAMES:
|
|
36
|
+
paths.append(native / name)
|
|
37
|
+
|
|
38
|
+
# Editable / in-tree builds may leave artifacts in the shared CMake tree.
|
|
39
|
+
repo_build = Path(__file__).resolve().parents[4] / "build" / "python"
|
|
40
|
+
if repo_build.is_dir():
|
|
41
|
+
for name in _CANDIDATE_NAMES:
|
|
42
|
+
paths.extend(repo_build.rglob(name))
|
|
43
|
+
|
|
44
|
+
return paths
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
def _load_path(path: Path) -> CDLL:
|
|
48
|
+
path = path.resolve()
|
|
49
|
+
if not path.is_file():
|
|
50
|
+
raise FileNotFoundError(path)
|
|
51
|
+
|
|
52
|
+
if sys.platform == "win32":
|
|
53
|
+
os.add_dll_directory(str(path.parent))
|
|
54
|
+
return CDLL(str(path))
|
|
55
|
+
|
|
56
|
+
mode = RTLD_GLOBAL if hasattr(os, "RTLD_GLOBAL") else 0
|
|
57
|
+
return CDLL(str(path), mode=mode)
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
def load() -> CDLL:
|
|
61
|
+
global _LIB
|
|
62
|
+
if _LIB is not None:
|
|
63
|
+
return _LIB
|
|
64
|
+
|
|
65
|
+
errors: list[str] = []
|
|
66
|
+
seen: set[Path] = set()
|
|
67
|
+
for candidate in _candidate_paths():
|
|
68
|
+
try:
|
|
69
|
+
resolved = candidate.resolve()
|
|
70
|
+
except OSError as exc:
|
|
71
|
+
errors.append(f"{candidate}: {exc}")
|
|
72
|
+
continue
|
|
73
|
+
if resolved in seen:
|
|
74
|
+
continue
|
|
75
|
+
seen.add(resolved)
|
|
76
|
+
try:
|
|
77
|
+
_LIB = _load_path(resolved)
|
|
78
|
+
return _LIB
|
|
79
|
+
except OSError as exc:
|
|
80
|
+
errors.append(f"{resolved}: {exc}")
|
|
81
|
+
|
|
82
|
+
hint = (
|
|
83
|
+
f"Set {_ENV_OVERRIDE} to the full path of mdr-shared "
|
|
84
|
+
f"(or mdr.dll), or `pip install -e libmdr` to build it."
|
|
85
|
+
)
|
|
86
|
+
raise FileNotFoundError(
|
|
87
|
+
"Unable to load mdr-shared.\n" + "\n".join(errors) + ("\n" + hint if errors else hint)
|
|
88
|
+
)
|
|
89
|
+
|
|
90
|
+
|
|
91
|
+
def lib() -> CDLL:
|
|
92
|
+
return load()
|