ohmu
·
2026-10-15
1#include "common/percent.h"
2#include "common/printing.h"
3#include "common/jsonconfig.h"
4#include "common/strutil.h"
5#include "detection/bluetoothradio/bluetoothradio.h"
6#include "modules/bluetoothradio/bluetoothradio.h"
7
8static void destroyBluetoothRadios(FFlist* radios) {
9 FF_LIST_FOR_EACH (FFBluetoothRadioResult, radio, *radios) {
10 ffStrbufDestroy(&radio->name);
11 ffStrbufDestroy(&radio->address);
12 ffStrbufDestroy(&radio->vendor);
13 }
14}
15
16static void printDevice(FFBluetoothRadioOptions* options, const FFBluetoothRadioResult* radio, uint8_t index) {
17 FF_STRBUF_AUTO_DESTROY key = ffStrbufCreate();
18 if (options->moduleArgs.key.length == 0) {
19 ffStrbufAppendF(&key, "%s (%s)", FF_MODULE_GET_DISPLAY_NAME(BluetoothRadio), radio->name.chars);
20 } else {
21 FF_PARSE_FORMAT_STRING_CHECKED(&key, &options->moduleArgs.key, ((FFformatarg[]) {
22 FF_ARG(index, "index"),
23 FF_ARG(radio->name, "name"),
24 FF_ARG(options->moduleArgs.keyIcon, "icon"),
25 FF_ARG(FF_MODULE_GET_DISPLAY_NAME(BluetoothRadio), "module-name"),
26 }));
27 }
28
29 const char* version = nullptr;
30
31 switch (radio->lmpVersion < 0 ? -radio->lmpVersion : radio->lmpVersion) {
32 case 0:
33 version = "1.0b";
34 break;
35 case 1:
36 version = "1.1";
37 break;
38 case 2:
39 version = "1.2";
40 break;
41 case 3:
42 version = "2.0";
43 break;
44 case 4:
45 version = "2.1";
46 break;
47 case 5:
48 version = "3.0";
49 break;
50 case 6:
51 version = "4.0";
52 break;
53 case 7:
54 version = "4.1";
55 break;
56 case 8:
57 version = "4.2";
58 break;
59 case 9:
60 version = "5.0";
61 break;
62 case 10:
63 version = "5.1";
64 break;
65 case 11:
66 version = "5.2";
67 break;
68 case 12:
69 version = "5.3";
70 break;
71 case 13:
72 version = "5.4";
73 break;
74 case 14:
75 version = "6.0";
76 break;
77 case 15:
78 version = "6.1";
79 break;
80 case 16:
81 version = "6.2";
82 break;
83 case 17:
84 version = "6.3";
85 break;
86 }
87
88 if (options->moduleArgs.outputFormat.length == 0) {
89 ffPrintLogoAndKey(key.chars, 0, &options->moduleArgs, FF_PRINT_TYPE_NO_CUSTOM_KEY);
90
91 if (version) {
92 printf("Bluetooth %s%s (%s)\n", version, (radio->lmpVersion < 0 ? "+" : ""), radio->vendor.chars);
93 } else {
94 ffStrbufPutTo(&radio->vendor, stdout);
95 }
96 } else {
97 FF_PRINT_FORMAT_CHECKED(key.chars, 0, &options->moduleArgs, FF_PRINT_TYPE_NO_CUSTOM_KEY, ((FFformatarg[]) {
98 FF_ARG(radio->name, "name"),
99 FF_ARG(radio->address, "address"),
100 FF_ARG(radio->lmpVersion, "lmp-version"),
101 FF_ARG(radio->lmpSubversion, "lmp-subversion"),
102 FF_ARG(version, "version"),
103 FF_ARG(radio->vendor, "vendor"),
104 FF_ARG(radio->discoverable, "discoverable"),
105 FF_ARG(radio->connectable, "connectable"),
106 }));
107 }
108}
109
110bool ffPrintBluetoothRadio(FFBluetoothRadioOptions* options) {
111 FF_LIST_AUTO_DESTROY radios = ffListCreate();
112 const char* error = ffDetectBluetoothRadio(&radios);
113
114 if (error) {
115 ffPrintError(FF_MODULE_GET_DISPLAY_NAME(BluetoothRadio), 0, &options->moduleArgs, FF_PRINT_TYPE_DEFAULT, "%s", error);
116 return false;
117 }
118
119 uint8_t index = 0;
120 FF_LIST_FOR_EACH (FFBluetoothRadioResult, radio, radios) {
121 if (!radio->enabled) {
122 continue;
123 }
124
125 index++;
126 printDevice(options, radio, index);
127 }
128
129 if (index == 0) {
130 if (radios.length > 0) {
131 ffPrintError(FF_MODULE_GET_DISPLAY_NAME(BluetoothRadio), 0, &options->moduleArgs, FF_PRINT_TYPE_DEFAULT, "Bluetooth radios found but none enabled");
132 } else {
133 ffPrintError(FF_MODULE_GET_DISPLAY_NAME(BluetoothRadio), 0, &options->moduleArgs, FF_PRINT_TYPE_DEFAULT, "No devices detected");
134 }
135 destroyBluetoothRadios(&radios);
136 return false;
137 }
138
139 destroyBluetoothRadios(&radios);
140 return true;
141}
142
143void ffParseBluetoothRadioJsonObject(FFBluetoothRadioOptions* options, yyjson_val* module) {
144 yyjson_val *key, *val;
145 size_t idx, max;
146 yyjson_obj_foreach (module, idx, max, key, val) {
147 if (ffJsonConfigParseModuleArgs(key, val, &options->moduleArgs)) {
148 continue;
149 }
150
151 ffPrintError(FF_MODULE_GET_DISPLAY_NAME(BluetoothRadio), 0, &options->moduleArgs, FF_PRINT_TYPE_DEFAULT, "Unknown JSON key %s", unsafe_yyjson_get_str(key));
152 }
153}
154
155[[gnu::cold]]
156void ffGenerateBluetoothRadioJsonConfig(FFBluetoothRadioOptions* options, yyjson_mut_doc* doc, yyjson_mut_val* module) {
157 ffJsonConfigGenerateModuleArgsConfig(doc, module, &options->moduleArgs);
158}
159
160bool ffGenerateBluetoothRadioJsonResult([[maybe_unused]] FFBluetoothRadioOptions* options, yyjson_mut_doc* doc, yyjson_mut_val* module) {
161 FF_LIST_AUTO_DESTROY results = ffListCreate();
162
163 const char* error = ffDetectBluetoothRadio(&results);
164 if (error) {
165 yyjson_mut_obj_add_str(doc, module, "error", error);
166 return false;
167 }
168
169 yyjson_mut_val* arr = yyjson_mut_obj_add_arr(doc, module, "result");
170
171 FF_LIST_FOR_EACH (FFBluetoothRadioResult, item, results) {
172 yyjson_mut_val* obj = yyjson_mut_arr_add_obj(doc, arr);
173 yyjson_mut_obj_add_strbuf(doc, obj, "name", &item->name);
174 yyjson_mut_obj_add_strbuf(doc, obj, "address", &item->address);
175 if (item->lmpVersion == INT_MIN) {
176 yyjson_mut_obj_add_null(doc, obj, "lmpVersion");
177 } else {
178 yyjson_mut_obj_add_int(doc, obj, "lmpVersion", item->lmpVersion);
179 }
180 if (item->lmpSubversion == INT_MIN) {
181 yyjson_mut_obj_add_null(doc, obj, "lmpSubversion");
182 } else {
183 yyjson_mut_obj_add_int(doc, obj, "lmpSubversion", item->lmpSubversion);
184 }
185 yyjson_mut_obj_add_strbuf(doc, obj, "vendor", &item->vendor);
186 yyjson_mut_obj_add_bool(doc, obj, "enabled", item->enabled);
187 yyjson_mut_obj_add_bool(doc, obj, "discoverable", item->discoverable);
188 yyjson_mut_obj_add_bool(doc, obj, "connectable", item->connectable);
189 }
190
191 destroyBluetoothRadios(&results);
192
193 return true;
194}
195
196void ffInitBluetoothRadioOptions(FFBluetoothRadioOptions* options) {
197 ffOptionInitModuleArg(&options->moduleArgs, "");
198}
199
200void ffDestroyBluetoothRadioOptions(FFBluetoothRadioOptions* options) {
201 ffOptionDestroyModuleArg(&options->moduleArgs);
202}
203
204FFModuleBaseInfo ffBluetoothRadioModuleInfo = {
205 .name = "BluetoothRadio",
206 .description = "List Bluetooth radios (supported versions, vendors, etc.)",
207 .displayName = {
208 .en = "Bluetooth Radio",
209 .ar = "راديو بلوتوث",
210 .cs = "Bluetooth rádio",
211 .de = "Bluetooth-Adapter",
212 .es = "Adaptador Bluetooth",
213 .fi = "Bluetooth-adapteri",
214 .fr = "Adaptateur Bluetooth",
215 .gl = "Adaptador Bluetooth",
216 .he = "רדיו בלוטות'",
217 .id = "Radio Bluetooth",
218 .it = "Adattatore Bluetooth",
219 .ja = "ブルートゥースアダプター",
220 .ko = "블루투스 어댑터",
221 .pl = "Adapter Bluetooth",
222 .pt = "Adaptador Bluetooth",
223 .ru = "Bluetooth-адаптер",
224 .tr = "Bluetooth Adaptörü",
225 .uk = "Радіомодуль Bluetooth",
226 .vi = "Radio Bluetooth",
227 .zh_CN = "蓝牙适配器",
228 .zh_TW = "藍牙配接器",
229 },
230 .initOptions = (void*) ffInitBluetoothRadioOptions,
231 .destroyOptions = (void*) ffDestroyBluetoothRadioOptions,
232 .parseJsonObject = (void*) ffParseBluetoothRadioJsonObject,
233 .printModule = (void*) ffPrintBluetoothRadio,
234 .generateJsonResult = (void*) ffGenerateBluetoothRadioJsonResult,
235 .generateJsonConfig = (void*) ffGenerateBluetoothRadioJsonConfig,
236 .formatArgs = FF_FORMAT_ARG_LIST(((FFModuleFormatArg[]) {
237 { "Radio name for discovering *", "name" },
238 { "Address", "address" },
239 { "LMP version", "lmp-version" },
240 { "LMP subversion", "lmp-subversion" },
241 { "Bluetooth version", "version" },
242 { "Vendor", "vendor" },
243 { "Discoverable", "discoverable" },
244 { "Connectable / Pairable", "connectable" },
245 })),
246 .defaultOrder = 60,
247};