feat: pair controllers and show them in the app

Port the MT76 client functions (send_wlan, associate_client,
pair_client, set_client_key, remove_client) and wire the RX dispatch
into the session: EP IN frames are parsed, ASSOC_REQ associates a
controller (WCID plus chip programming), PAIR_REQ replies with
PAIR_RESP, and DISASSOC or client-lost removes it. The C API exposes
connected controllers and the app lists them by MAC. Bumps
xone_cli/xone_app to C++23: they were falling back to the default
standard once mt76.hpp started using std::span. Adds a unit test pinning
the WCID regions, the 32-byte rxwi, and the new frame control values.

Co-Authored-By: qwen3.8-27b@q2_k_xl: client functions, RX dispatch, GUI list
This commit is contained in:
portersky
2026-08-17 20:40:47 +02:00
parent b3e747b900
commit dae51f085b
9 changed files with 682 additions and 7 deletions
+29 -2
View File
@@ -30,6 +30,8 @@ struct ContentView: View {
// Radio state polled from the background worker each tick; a change here
// is what re-renders the debug section.
@State private var radioState = XONE_STATE_IDLE
// Connected controllers (MAC strings), refreshed each tick.
@State private var controllers: [String] = []
private let timer = Timer.publish(every: 1.0, on: .main, in: .common)
.autoconnect()
@@ -84,6 +86,7 @@ struct ContentView: View {
session = nil
} else if let s = session {
radioState = Int(xone_state(s))
refreshControllers(s)
}
donglePresent = present
}
@@ -130,6 +133,19 @@ struct ContentView: View {
return build.isEmpty ? "not loaded" : build
}
// Query the C API for connected controllers and store their MACs.
private func refreshControllers(_ session: OpaquePointer) {
let count = Int(xone_controller_count(session))
var list: [String] = []
for i in 0..<count {
var buf = [CChar](repeating: 0, count: 18)
if xone_controller_mac(session, Int32(i), &buf, 18) == 0 {
list.append(String(cString: buf))
}
}
controllers = list
}
private var statusView: some View {
HStack(spacing: 8) {
Circle()
@@ -144,8 +160,19 @@ struct ContentView: View {
VStack(alignment: .leading, spacing: 8) {
Text("Controllers")
.font(.headline)
Text("No controllers connected.")
.foregroundStyle(.secondary)
if controllers.isEmpty {
Text("No controllers connected.")
.foregroundStyle(.secondary)
} else {
ForEach(controllers, id: \.self) { mac in
HStack {
Image(systemName: "gamecontroller")
.foregroundStyle(.secondary)
Text(mac)
.font(.system(.body, design: .monospaced))
}
}
}
}
}
}