Initial public release

This commit is contained in:
2026-07-17 15:29:53 -04:00
commit 2d71ce77a1
81 changed files with 32056 additions and 0 deletions
+90
View File
@@ -0,0 +1,90 @@
"""Saved device profiles for the BT Keyboard streamer.
A profile is a named list of devices, each ``{"address", "label"}``. Saving
records the MAC + friendly name of every device currently in the streamer;
loading re-adds and reconnects them in one click, naming each automatically.
All profiles live in a single JSON file in the app config dir so they're in
one place and trivially portable:
{ "profiles": { "<name>": [ {"address": "AA:..", "label": "Left PC"}, ... ] } }
"""
import json
import os
import threading
from utils.constants import APPDATA_DIR
_FILE = os.path.join(APPDATA_DIR, "bt_kbd_profiles.json")
_lock = threading.Lock()
def _load_all() -> dict:
try:
with open(_FILE, "r", encoding="utf-8") as f:
d = json.load(f)
except (OSError, json.JSONDecodeError):
return {}
profs = d.get("profiles") if isinstance(d, dict) else None
return profs if isinstance(profs, dict) else {}
def _save_all(profs: dict) -> None:
os.makedirs(APPDATA_DIR, exist_ok=True)
tmp = _FILE + ".tmp"
with open(tmp, "w", encoding="utf-8") as f:
json.dump({"profiles": profs}, f, indent=2)
os.replace(tmp, _FILE)
def list_profiles() -> list:
"""Names of all saved profiles, sorted."""
with _lock:
return sorted(_load_all().keys())
def load_profile(name: str) -> list:
"""Return a profile's devices as a list of {"address","label"} dicts.
Tolerates a malformed/missing file by returning whatever is valid."""
with _lock:
devs = _load_all().get(name, [])
out = []
if isinstance(devs, list):
for d in devs:
if isinstance(d, dict) and d.get("address"):
out.append({"address": str(d["address"]),
"label": str(d.get("label") or "")})
return out
def save_profile(name: str, devices, transport: str = "mesh") -> None:
"""Save ``devices`` (iterable of (address, label) tuples or dicts) under
``name``, replacing any existing profile of that name.
``transport`` is recorded per device ("mesh" for ESP-NOW STA MACs,
"ble" for legacy Bleak BT MACs) so the loader can migrate old
profiles. Extra keys are ignored by load_profile for back-compat."""
norm = []
for d in devices:
if isinstance(d, dict):
addr, lbl = d.get("address"), d.get("label") or ""
else:
addr, lbl = d
if addr:
norm.append({"address": str(addr), "label": str(lbl or ""),
"transport": transport})
with _lock:
profs = _load_all()
profs[name] = norm
_save_all(profs)
def delete_profile(name: str) -> bool:
with _lock:
profs = _load_all()
if name in profs:
del profs[name]
_save_all(profs)
return True
return False