Emy/btt_diagnose.py

214 lines
8.9 KiB
Python
Raw Normal View History

2026-06-17 21:26:21 +00:00
#!/usr/bin/env python3
"""
BTT Diagnose-Script
Findet duplizierte und verdächtige Keyboard Shortcuts in der BTT-Datenbank.
Einfach ausführen: python3 ~/bin/btt_diagnose.py
"""
import sqlite3
import os
import glob
import sys
import shutil
import tempfile
from datetime import datetime
# ── Key Code → Lesbare Bezeichnung ──────────────────────────────────────────
KEYCODES = {
0:'A', 1:'S', 2:'D', 3:'F', 4:'H', 5:'G', 6:'Z', 7:'X', 8:'C', 9:'V',
11:'B', 12:'Q', 13:'W', 14:'E', 15:'R', 16:'Y', 17:'T',
18:'1', 19:'2', 20:'3', 21:'4', 23:'5', 24:'=', 25:'9', 26:'7',
27:'-', 28:'8', 29:'0', 30:']', 31:'O', 32:'U', 33:'[', 34:'I', 35:'P',
36:'Return', 37:'L', 38:'J', 39:"'", 40:'K', 41:';', 42:'\\',
43:',', 44:'/', 45:'N', 46:'M', 47:'.', 48:'Tab', 49:'Space',
50:'`', 51:'Delete', 53:'Escape', 65:'Numpad.', 67:'Numpad*',
69:'Numpad+', 75:'Numpad/', 76:'NumpadEnter', 78:'Numpad-',
81:'Numpad=', 82:'Numpad0', 83:'Numpad1', 84:'Numpad2', 85:'Numpad3',
86:'Numpad4', 87:'Numpad5', 88:'Numpad6', 89:'Numpad7',
91:'Numpad8', 92:'Numpad9',
96:'F5', 97:'F6', 98:'F7', 99:'F3', 100:'F8', 101:'F9', 103:'F11',
105:'F13', 106:'F16', 107:'F14', 109:'F10', 111:'F12', 113:'F15',
115:'Home', 116:'PageUp', 117:'ForwardDelete', 118:'F4', 119:'End',
120:'F2', 121:'PageDown', 122:'F1',
123:'', 124:'', 125:'', 126:'',
-1:'(kein Key)',
}
def decode_key(code):
return KEYCODES.get(code, f'KeyCode_{code}')
# ── Modifier Flags → Lesbare Bezeichnung ────────────────────────────────────
MOD_FLAGS = [
(8388608, 'Fn'),
(1048576, ''),
(524288, ''),
(262144, ''),
(131072, ''),
(65536, 'CapsLock'),
]
def decode_mod(mod):
if mod is None or mod <= 0:
return '(kein Modifier)'
parts = []
for flag, name in MOD_FLAGS:
if mod & flag:
parts.append(name)
return '+'.join(parts) if parts else f'Mod_{mod}'
def shortcut_str(keycode, mod):
return f"{decode_mod(mod)}+{decode_key(keycode)}"
# ── BTT Datenbank finden ─────────────────────────────────────────────────────
def find_btt_db():
base = os.path.expanduser('~/Library/Application Support/BetterTouchTool/')
pattern = os.path.join(base, 'btt_data_store.version_*')
candidates = [
f for f in glob.glob(pattern)
if not f.endswith(('-shm', '-wal'))
and '_tmp_backup' not in f
]
if not candidates:
return None
return max(candidates, key=os.path.getmtime)
# ── Analyse ──────────────────────────────────────────────────────────────────
def analyse(db_path):
print(f"\n{'='*60}")
print(f" BTT SHORTCUT DIAGNOSE")
print(f" {datetime.now().strftime('%d.%m.%Y %H:%M:%S')}")
print(f"{'='*60}")
print(f" DB: {os.path.basename(db_path)}\n")
tmp = tempfile.mktemp(suffix='.db')
shutil.copy2(db_path, tmp)
conn = sqlite3.connect(tmp)
cur = conn.cursor()
# ── Integrity Check ──────────────────────────────────────────────────────
cur.execute("PRAGMA integrity_check")
ic = cur.fetchone()[0]
status = "✓ OK" if ic == 'ok' else f"✗ FEHLER: {ic}"
print(f" Datenbankintegrität: {status}")
# ── Gesamt-Statistik ─────────────────────────────────────────────────────
cur.execute("SELECT COUNT(*) FROM ZBTTBASEENTITY WHERE ZGESTURETYPE = 0")
total = cur.fetchone()[0]
cur.execute("SELECT COUNT(*) FROM ZBTTBASEENTITY WHERE ZGESTURETYPE = 0 AND ZISENABLED = 1")
enabled = cur.fetchone()[0]
print(f" Keyboard Shortcuts: {total} gesamt, {enabled} aktiv\n")
# ── 1. Duplikate ─────────────────────────────────────────────────────────
cur.execute('''
SELECT ZKEYCODE, ZMODIFIERKEYS, COUNT(*) as cnt, GROUP_CONCAT(Z_PK) as pks
FROM ZBTTBASEENTITY
WHERE ZGESTURETYPE = 0 AND ZKEYCODE >= 0
GROUP BY ZKEYCODE, ZMODIFIERKEYS
HAVING COUNT(*) > 1
ORDER BY cnt DESC, ZKEYCODE
''')
dupes = cur.fetchall()
if dupes:
print(f" ⚠ DUPLIZIERTE SHORTCUTS ({len(dupes)} Kombinationen)")
print(f" {''*54}")
for keycode, mod, cnt, pks in dupes:
shortcut = shortcut_str(keycode, mod)
pk_list = [int(p) for p in pks.split(',')]
entries = []
for pk in pk_list:
cur.execute('''
SELECT ZTRIGGERLABEL, ZACTIONLABEL, ZNAME, ZISENABLED,
ZBELONGSTOPRESET, ZBUNDLEIDENTIFIER
FROM ZBTTBASEENTITY WHERE Z_PK = ?
''', (pk,))
row = cur.fetchone()
if row:
label = row[0] or row[1] or row[2] or '(kein Label)'
enabled_str = '' if row[3] == 1 else ''
app = row[5] or 'Global'
entries.append((pk, label, enabled_str, app))
print(f"\n [{cnt}x] {shortcut}")
for pk, label, en, app in entries:
print(f" PK={pk:5} {en} App: {app:<35} Label: {label}")
else:
print(" ✓ Keine duplizierten Shortcuts gefunden")
print()
# ── 2. Shortcuts ohne Action ─────────────────────────────────────────────
cur.execute('''
SELECT Z_PK, ZKEYCODE, ZMODIFIERKEYS, ZTRIGGERLABEL, ZISENABLED
FROM ZBTTBASEENTITY
WHERE ZGESTURETYPE = 0
AND ZISENABLED = 1
AND ZKEYCODE >= 0
AND ZACTIONDATA IS NULL
AND ZADDITIONALACTIONSTRING IS NULL
AND ZGESTURECONFIG IS NULL
''')
no_action = cur.fetchall()
if no_action:
print(f" ⚠ AKTIVE SHORTCUTS OHNE AKTION ({len(no_action)} Stück)")
print(f" {''*54}")
for pk, kc, mod, label, en in no_action:
print(f" PK={pk:5} {shortcut_str(kc, mod):<25} Label: {label or '(kein Label)'}")
print()
else:
print(" ✓ Alle aktiven Shortcuts haben eine Aktion\n")
# ── 3. Shortcuts mit KeyCode -1 (defekt) ────────────────────────────────
cur.execute('''
SELECT COUNT(*) FROM ZBTTBASEENTITY
WHERE ZGESTURETYPE = 0 AND ZKEYCODE = -1 AND ZISENABLED = 1
''')
broken = cur.fetchone()[0]
if broken > 0:
print(f" ⚠ AKTIVE SHORTCUTS MIT UNGÜLTIGEM KEYCODE (-1): {broken}")
cur.execute('''
SELECT Z_PK, ZMODIFIERKEYS, ZTRIGGERLABEL, ZACTIONLABEL
FROM ZBTTBASEENTITY
WHERE ZGESTURETYPE = 0 AND ZKEYCODE = -1 AND ZISENABLED = 1
''')
for pk, mod, tl, al in cur.fetchall():
label = tl or al or '(kein Label)'
print(f" PK={pk:5} Mod={decode_mod(mod)} Label: {label}")
print()
else:
print(" ✓ Keine Shortcuts mit ungültigem KeyCode\n")
# ── Zusammenfassung ──────────────────────────────────────────────────────
print(f"{'='*60}")
total_issues = len(dupes) + (len(no_action) if no_action else 0) + broken
if total_issues == 0:
print(" ✓ Alles in Ordnung keine Probleme gefunden")
else:
print(f"{total_issues} Problem(e) gefunden")
if dupes:
print(f"{len(dupes)} duplizierte Shortcut-Kombinationen")
print(f" → Löschen und neu anlegen behebt das Problem")
if no_action:
print(f"{len(no_action)} aktive Shortcuts ohne Aktion")
if broken:
print(f"{broken} Shortcuts mit ungültigem KeyCode")
print(f"{'='*60}\n")
conn.close()
os.unlink(tmp)
# ── Main ─────────────────────────────────────────────────────────────────────
if __name__ == '__main__':
if len(sys.argv) > 1:
db = sys.argv[1]
else:
db = find_btt_db()
if not db or not os.path.exists(db):
print("✗ BTT-Datenbank nicht gefunden.")
print(" Pfad manuell angeben: python3 ~/bin/btt_diagnose.py /pfad/zur/db")
sys.exit(1)
analyse(db)