Skip to content

Commit 49fa4bb

Browse files
Merge pull request #84 from torlando-tech/fix/voip-callkit-background-mode
fix(callkit): add voip background mode so incoming calls work
2 parents f853b6d + a89228a commit 49fa4bb

2 files changed

Lines changed: 88 additions & 0 deletions

File tree

Sources/ColumbaApp/Resources/Info.plist

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,8 @@
2929
<string>Columba shares your location with contacts you explicitly choose, so they can see where you are during an active sharing session.</string>
3030
<key>NSLocationAlwaysAndWhenInUseUsageDescription</key>
3131
<string>Columba keeps sharing your location with the contacts you chose even when the app is in the background, so a sharing session you started doesn't silently stop. You can stop sharing at any time from the conversation or from Settings.</string>
32+
<key>NSMicrophoneUsageDescription</key>
33+
<string>Columba uses the microphone for encrypted voice calls over the mesh network.</string>
3234
<key>UIAppFonts</key>
3335
<array>
3436
<string>materialdesignicons.ttf</string>
@@ -42,9 +44,11 @@
4244
</dict>
4345
<key>UIBackgroundModes</key>
4446
<array>
47+
<string>audio</string>
4548
<string>bluetooth-central</string>
4649
<string>bluetooth-peripheral</string>
4750
<string>location</string>
51+
<string>voip</string>
4852
</array>
4953
<key>UILaunchScreen</key>
5054
<dict/>

Tests/interop/voice_caller.py

Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,84 @@
1+
#!/usr/bin/env python3
2+
"""Headless LXST voice caller — dials the iOS Columba sim's telephony
3+
destination so its incoming-call / CallKit path fires, for debugging the
4+
"call screen dismisses + reappears as Unknown" bug on the sim (full log access).
5+
6+
Uses Sideband's own ReticulumTelephone (which wraps LXST.Telephone), so it's
7+
wire-identical to a Sideband user placing the call. Joins the host's shared
8+
RNS instance (lxmd) exactly like the interop peer, so it shares Columba's
9+
transport.
10+
11+
Usage:
12+
PYTHONPATH unneeded — paths injected below.
13+
python3 voice_caller.py <columba_identity_hex> [ring_seconds]
14+
"""
15+
import sys, os, time
16+
17+
# LXST + Sideband are sibling checkouts; override with LXST_SRC / SIDEBAND_SRC
18+
# (SIDEBAND_SRC matches the interop conftest). Default to ~/repos/<name>.
19+
for _name, _env, _default in (
20+
("LXST", "LXST_SRC", "~/repos/LXST"),
21+
("Sideband", "SIDEBAND_SRC", "~/repos/Sideband"),
22+
):
23+
_path = os.environ.get(_env, os.path.expanduser(_default))
24+
if not os.path.isdir(_path):
25+
sys.exit(f"{_name} checkout not found at {_path} — set {_env} to its path.")
26+
sys.path.insert(0, _path)
27+
28+
import RNS # noqa: E402
29+
from sbapp.sideband.voice import ReticulumTelephone # noqa: E402
30+
31+
COLUMBA_ID_HEX = sys.argv[1] if len(sys.argv) > 1 else "695f23533fe3547183f1b550d8552ae8"
32+
RING_SECONDS = int(sys.argv[2]) if len(sys.argv) > 2 else 25
33+
34+
35+
def main():
36+
RNS.Reticulum() # connect to the shared lxmd instance (share_instance=Yes)
37+
identity = RNS.Identity()
38+
print(f"[caller] my identity={RNS.prettyhexrep(identity.hash)}", flush=True)
39+
40+
phone = ReticulumTelephone(identity, owner=None)
41+
# NB: don't call phone.start() — its run() loop is Sideband hw-state polling
42+
# and isn't present on this build; the wrapped LXST.Telephone drives the
43+
# call protocol itself. We just need to keep the process alive.
44+
time.sleep(1.0)
45+
46+
phone.announce() # so Columba can resolve/path us (matches the 'correct name' case)
47+
print("[caller] announced own telephony", flush=True)
48+
49+
id_bytes = bytes.fromhex(COLUMBA_ID_HEX)
50+
tel_dest = RNS.Destination.hash_from_name_and_identity("lxst.telephony", id_bytes)
51+
print(f"[caller] Columba telephony dest = {tel_dest.hex()}", flush=True)
52+
53+
if not RNS.Transport.has_path(tel_dest):
54+
RNS.Transport.request_path(tel_dest)
55+
deadline = time.time() + 30
56+
while not RNS.Transport.has_path(tel_dest) and time.time() < deadline:
57+
time.sleep(0.5)
58+
if not RNS.Transport.has_path(tel_dest):
59+
print("[caller] NO PATH to Columba telephony after 30s — is the sim "
60+
"announcing lxst.telephony + bridged through lxmd? aborting.", flush=True)
61+
return 1
62+
print(f"[caller] path to telephony resolved ({RNS.Transport.hops_to(tel_dest)} hops); DIALING", flush=True)
63+
64+
result = phone.dial(id_bytes)
65+
print(f"[caller] dial() -> {result}", flush=True)
66+
67+
# state: 0=AVAILABLE 1=CONNECTING 2=RINGING 3=IN_CALL (is_* are @property, no parens)
68+
_names = {0: "AVAILABLE", 1: "CONNECTING", 2: "RINGING", 3: "IN_CALL"}
69+
for i in range(RING_SECONDS):
70+
st = phone.state
71+
print(f"[caller] t={i:>2}s state={st}({_names.get(st, '?')})", flush=True)
72+
time.sleep(1.0)
73+
74+
print("[caller] hanging up", flush=True)
75+
try:
76+
phone.hangup()
77+
except Exception as e:
78+
print(f"[caller] hangup error: {e}", flush=True)
79+
time.sleep(2.0)
80+
return 0
81+
82+
83+
if __name__ == "__main__":
84+
sys.exit(main())

0 commit comments

Comments
 (0)