master ohmu/fastfetch-openbsd / src / modules / localip / localip.c
ohmu  ·  2026-10-15
  1#include "common/printing.h"
  2#include "common/jsonconfig.h"
  3#include "common/strutil.h"
  4#include "detection/localip/localip.h"
  5#include "modules/localip/localip.h"
  6
  7#pragma GCC diagnostic ignored "-Wsign-conversion"
  8
  9static int sortIps(const FFLocalIpResult* left, const FFLocalIpResult* right) {
 10    return ffStrbufComp(&left->name, &right->name);
 11}
 12
 13static void formatKey(const FFLocalIpOptions* options, FFLocalIpResult* ip, uint32_t index, FFstrbuf* key) {
 14    if (options->moduleArgs.key.length == 0) {
 15        if (!ip->name.length) {
 16            ffStrbufSetF(&ip->name, "unknown %u", (unsigned) index);
 17        }
 18
 19        ffStrbufSetF(key, "%s (%s)", FF_MODULE_GET_DISPLAY_NAME(LocalIP), ip->name.chars);
 20    } else {
 21        ffStrbufClear(key);
 22        FF_PARSE_FORMAT_STRING_CHECKED(key, &options->moduleArgs.key, ((FFformatarg[]) {
 23                                                                          FF_ARG(index, "index"),
 24                                                                          FF_ARG(ip->name, "ifname"),
 25                                                                          FF_ARG(ip->mac, "mac"),
 26                                                                          FF_ARG(options->moduleArgs.keyIcon, "icon"),
 27                                                                          FF_ARG(FF_MODULE_GET_DISPLAY_NAME(LocalIP), "module-name"),
 28                                                                      }));
 29    }
 30}
 31
 32static void appendSpeed(FFLocalIpResult* ip, FFstrbuf* strbuf) {
 33    if (ip->speed >= 1000000) {
 34        ffStrbufAppendDouble(strbuf, ip->speed / 1e6, instance.config.display.fractionNdigits, instance.config.display.fractionTrailingZeros == FF_FRACTION_TRAILING_ZEROS_TYPE_ALWAYS);
 35        ffStrbufAppendS(strbuf, " Tbps");
 36    } else if (ip->speed >= 1000) {
 37        ffStrbufAppendDouble(strbuf, ip->speed / 1e3, instance.config.display.fractionNdigits, instance.config.display.fractionTrailingZeros == FF_FRACTION_TRAILING_ZEROS_TYPE_ALWAYS);
 38        ffStrbufAppendS(strbuf, " Gbps");
 39    } else {
 40        ffStrbufAppendF(strbuf, "%u Mbps", (unsigned) ip->speed);
 41    }
 42}
 43
 44static void printIp(FFLocalIpResult* ip, bool markDefaultRoute, FFstrbuf* buffer) {
 45    if (ip->ipv4.length) {
 46        ffStrbufAppend(buffer, &ip->ipv4);
 47    }
 48    if (ip->ipv6.length) {
 49        if (buffer->length) {
 50            ffStrbufAppendC(buffer, ' ');
 51        }
 52        ffStrbufAppend(buffer, &ip->ipv6);
 53    }
 54    if (ip->mac.length) {
 55        if (buffer->length) {
 56            ffStrbufAppendF(buffer, " (%s)", ip->mac.chars);
 57        } else {
 58            ffStrbufAppend(buffer, &ip->mac);
 59        }
 60    }
 61    if (ip->mtu > 0 || ip->speed > 0) {
 62        bool flag = buffer->length > 0;
 63        if (flag) {
 64            ffStrbufAppendS(buffer, " [");
 65        }
 66        if (ip->speed > 0) {
 67            if (ip->mtu > 0) {
 68                ffStrbufAppendS(buffer, "Speed ");
 69            }
 70            appendSpeed(ip, buffer);
 71            if (ip->mtu > 0) {
 72                ffStrbufAppendS(buffer, " / MTU ");
 73            }
 74        }
 75        if (ip->mtu > 0) {
 76            ffStrbufAppendF(buffer, "%u", (unsigned) ip->mtu);
 77        }
 78        if (flag) {
 79            ffStrbufAppendC(buffer, ']');
 80        }
 81    }
 82    if (ip->flags.length) {
 83        bool flag = buffer->length > 0;
 84        if (flag) {
 85            ffStrbufAppendS(buffer, " <");
 86        }
 87        ffStrbufAppend(buffer, &ip->flags);
 88        if (flag) {
 89            ffStrbufAppendC(buffer, '>');
 90        }
 91    }
 92    if (markDefaultRoute && ip->defaultRoute) {
 93        ffStrbufAppendS(buffer, " *");
 94    }
 95}
 96
 97bool ffPrintLocalIp(FFLocalIpOptions* options) {
 98    FF_LIST_AUTO_DESTROY results = ffListCreate();
 99
100    const char* error = ffDetectLocalIps(options, &results);
101
102    if (error) {
103        ffPrintError(FF_MODULE_GET_DISPLAY_NAME(LocalIP), 0, &options->moduleArgs, FF_PRINT_TYPE_DEFAULT, "%s", error);
104        return false;
105    }
106
107    if (results.length == 0) {
108        ffPrintError(FF_MODULE_GET_DISPLAY_NAME(LocalIP), 0, &options->moduleArgs, FF_PRINT_TYPE_DEFAULT, "Failed to detect any IPs");
109        return false;
110    }
111
112    ffListSort(&results, sizeof(FFLocalIpResult), (const void*) sortIps);
113
114    FF_STRBUF_AUTO_DESTROY buffer = ffStrbufCreate();
115    // `{is-default-route}` reports the address families the interface carries the default route for
116    // ("ipv4", "ipv6" or "ipv4,ipv6") instead of exposing the internal bit field as a number.
117    FF_STRBUF_AUTO_DESTROY defaultRoute = ffStrbufCreate();
118
119    if (options->showType & FF_LOCALIP_TYPE_COMPACT_BIT) {
120        ffPrintLogoAndKey(FF_MODULE_GET_DISPLAY_NAME(LocalIP), 0, &options->moduleArgs, FF_PRINT_TYPE_DEFAULT);
121
122        FF_LIST_FOR_EACH (FFLocalIpResult, ip, results) {
123            if (buffer.length) {
124                ffStrbufAppendS(&buffer, " - ");
125            }
126            printIp(ip, false, &buffer);
127        }
128        ffStrbufPutTo(&buffer, stdout);
129        ffStrbufClear(&buffer);
130    } else {
131        FF_STRBUF_AUTO_DESTROY key = ffStrbufCreate();
132        uint32_t index = 0;
133
134        FF_LIST_FOR_EACH (FFLocalIpResult, ip, results) {
135            formatKey(options, ip, results.length == 1 ? 0 : index + 1, &key);
136            if (options->moduleArgs.outputFormat.length == 0) {
137                ffPrintLogoAndKey(key.chars, 0, &options->moduleArgs, FF_PRINT_TYPE_NO_CUSTOM_KEY);
138                printIp(ip, !(options->showType & FF_LOCALIP_TYPE_DEFAULT_ROUTE_ONLY_BIT), &buffer);
139                ffStrbufPutTo(&buffer, stdout);
140            } else {
141                if (ip->speed > 0) {
142                    appendSpeed(ip, &buffer);
143                }
144                ffStrbufClear(&defaultRoute);
145                if (ip->defaultRoute & FF_LOCALIP_TYPE_IPV4_BIT) {
146                    ffStrbufAppendS(&defaultRoute, "ipv4");
147                }
148                if (ip->defaultRoute & FF_LOCALIP_TYPE_IPV6_BIT) {
149                    if (defaultRoute.length > 0) {
150                        ffStrbufAppendC(&defaultRoute, ',');
151                    }
152                    ffStrbufAppendS(&defaultRoute, "ipv6");
153                }
154                FF_PRINT_FORMAT_CHECKED(key.chars, 0, &options->moduleArgs, FF_PRINT_TYPE_NO_CUSTOM_KEY, ((FFformatarg[]) {
155                                                                                                             FF_ARG(ip->ipv4, "ipv4"),
156                                                                                                             FF_ARG(ip->ipv6, "ipv6"),
157                                                                                                             FF_ARG(ip->mac, "mac"),
158                                                                                                             FF_ARG(ip->name, "ifname"),
159                                                                                                             FF_ARG(defaultRoute, "is-default-route"),
160                                                                                                             FF_ARG(ip->mtu, "mtu"),
161                                                                                                             FF_ARG(buffer, "speed"),
162                                                                                                             FF_ARG(ip->flags, "flags"),
163                                                                                                         }));
164            }
165            ++index;
166            ffStrbufClear(&buffer);
167            ffStrbufClear(&defaultRoute);
168        }
169    }
170
171    FF_LIST_FOR_EACH (FFLocalIpResult, ip, results) {
172        ffStrbufDestroy(&ip->name);
173        ffStrbufDestroy(&ip->ipv4);
174        ffStrbufDestroy(&ip->ipv6);
175        ffStrbufDestroy(&ip->mac);
176        ffStrbufDestroy(&ip->flags);
177    }
178
179    return true;
180}
181
182void ffParseLocalIpJsonObject(FFLocalIpOptions* options, yyjson_val* module) {
183    yyjson_val *key, *val;
184    size_t idx, max;
185    yyjson_obj_foreach (module, idx, max, key, val) {
186        if (ffJsonConfigParseModuleArgs(key, val, &options->moduleArgs)) {
187            continue;
188        }
189
190        if (unsafe_yyjson_equals_str(key, "showIpv4")) {
191            if (yyjson_get_bool(val)) {
192                options->showType |= FF_LOCALIP_TYPE_IPV4_BIT;
193            } else {
194                options->showType &= ~FF_LOCALIP_TYPE_IPV4_BIT;
195            }
196            continue;
197        }
198
199        if (unsafe_yyjson_equals_str(key, "showIpv6")) {
200            if (yyjson_is_bool(val)) {
201                options->ipv6Type = FF_LOCALIP_IPV6_TYPE_AUTO;
202                if (unsafe_yyjson_get_bool(val)) {
203                    options->showType |= FF_LOCALIP_TYPE_IPV6_BIT;
204                } else {
205                    options->showType &= ~FF_LOCALIP_TYPE_IPV6_BIT;
206                }
207            } else if (yyjson_is_str(val)) {
208                int value;
209                const char* error = ffJsonConfigParseEnum(val, &value, (FFKeyValuePair[]) {
210                                                                           { "auto", FF_LOCALIP_IPV6_TYPE_AUTO },
211                                                                           { "gua", FF_LOCALIP_IPV6_TYPE_GUA_BIT },
212                                                                           { "ula", FF_LOCALIP_IPV6_TYPE_ULA_BIT },
213                                                                           { "lla", FF_LOCALIP_IPV6_TYPE_LLA_BIT },
214                                                                           { "unknown", FF_LOCALIP_IPV6_TYPE_UNKNOWN_BIT },
215                                                                           {},
216                                                                       });
217                if (error) {
218                    ffPrintError(FF_MODULE_GET_DISPLAY_NAME(LocalIP), 0, &options->moduleArgs, FF_PRINT_TYPE_DEFAULT, "Invalid %s value: %s", unsafe_yyjson_get_str(key), error);
219                } else {
220                    options->showType |= FF_LOCALIP_TYPE_IPV6_BIT;
221                    options->ipv6Type = (FFLocalIpIpv6Type) value;
222                }
223            }
224            continue;
225        }
226
227        if (unsafe_yyjson_equals_str(key, "showMac")) {
228            if (yyjson_get_bool(val)) {
229                options->showType |= FF_LOCALIP_TYPE_MAC_BIT;
230            } else {
231                options->showType &= ~FF_LOCALIP_TYPE_MAC_BIT;
232            }
233            continue;
234        }
235
236        if (unsafe_yyjson_equals_str(key, "showLoop")) {
237            if (yyjson_get_bool(val)) {
238                options->showType |= FF_LOCALIP_TYPE_LOOP_BIT;
239            } else {
240                options->showType &= ~FF_LOCALIP_TYPE_LOOP_BIT;
241            }
242            continue;
243        }
244
245        if (unsafe_yyjson_equals_str(key, "showPrefixLen")) {
246            if (yyjson_get_bool(val)) {
247                options->showType |= FF_LOCALIP_TYPE_PREFIX_LEN_BIT;
248            } else {
249                options->showType &= ~FF_LOCALIP_TYPE_PREFIX_LEN_BIT;
250            }
251            continue;
252        }
253
254        if (unsafe_yyjson_equals_str(key, "showMtu")) {
255            if (yyjson_get_bool(val)) {
256                options->showType |= FF_LOCALIP_TYPE_MTU_BIT;
257            } else {
258                options->showType &= ~FF_LOCALIP_TYPE_MTU_BIT;
259            }
260            continue;
261        }
262
263        if (unsafe_yyjson_equals_str(key, "showSpeed")) {
264            if (yyjson_get_bool(val)) {
265                options->showType |= FF_LOCALIP_TYPE_SPEED_BIT;
266            } else {
267                options->showType &= ~FF_LOCALIP_TYPE_SPEED_BIT;
268            }
269            continue;
270        }
271
272        if (unsafe_yyjson_equals_str(key, "showFlags")) {
273            if (yyjson_get_bool(val)) {
274                options->showType |= FF_LOCALIP_TYPE_FLAGS_BIT;
275            } else {
276                options->showType &= ~FF_LOCALIP_TYPE_FLAGS_BIT;
277            }
278            continue;
279        }
280
281        if (unsafe_yyjson_equals_str(key, "compact")) {
282            if (yyjson_get_bool(val)) {
283                options->showType |= FF_LOCALIP_TYPE_COMPACT_BIT;
284            } else {
285                options->showType &= ~FF_LOCALIP_TYPE_COMPACT_BIT;
286            }
287            continue;
288        }
289
290        if (unsafe_yyjson_equals_str(key, "defaultRouteOnly")) {
291            if (yyjson_get_bool(val)) {
292                options->showType |= FF_LOCALIP_TYPE_DEFAULT_ROUTE_ONLY_BIT;
293            } else {
294                options->showType &= ~FF_LOCALIP_TYPE_DEFAULT_ROUTE_ONLY_BIT;
295            }
296            continue;
297        }
298
299        if (unsafe_yyjson_equals_str(key, "showAllIps")) {
300            if (yyjson_get_bool(val)) {
301                options->showType |= FF_LOCALIP_TYPE_ALL_IPS_BIT;
302            } else {
303                options->showType &= ~FF_LOCALIP_TYPE_ALL_IPS_BIT;
304            }
305            continue;
306        }
307
308        if (unsafe_yyjson_equals_str(key, "namePrefix")) {
309            ffStrbufSetJsonVal(&options->namePrefix, val);
310            continue;
311        }
312
313        ffPrintError(FF_MODULE_GET_DISPLAY_NAME(LocalIP), 0, &options->moduleArgs, FF_PRINT_TYPE_DEFAULT, "Unknown JSON key %s", unsafe_yyjson_get_str(key));
314    }
315}
316
317[[gnu::cold]]
318void ffGenerateLocalIpJsonConfig(FFLocalIpOptions* options, yyjson_mut_doc* doc, yyjson_mut_val* module) {
319    ffJsonConfigGenerateModuleArgsConfig(doc, module, &options->moduleArgs);
320
321    yyjson_mut_obj_add_bool(doc, module, "showIpv4", !!(options->showType & FF_LOCALIP_TYPE_IPV4_BIT));
322
323    if (options->ipv6Type == FF_LOCALIP_IPV6_TYPE_AUTO) {
324        yyjson_mut_obj_add_bool(doc, module, "showIpv6", !!(options->showType & FF_LOCALIP_TYPE_IPV6_BIT));
325    } else {
326        const char* str = nullptr;
327        switch (options->ipv6Type) {
328            case FF_LOCALIP_IPV6_TYPE_GUA_BIT:
329                str = "gua";
330                break;
331            case FF_LOCALIP_IPV6_TYPE_ULA_BIT:
332                str = "ula";
333                break;
334            case FF_LOCALIP_IPV6_TYPE_LLA_BIT:
335                str = "lla";
336                break;
337            case FF_LOCALIP_IPV6_TYPE_UNKNOWN_BIT:
338                str = "unknown";
339                break;
340            default:
341                str = "auto";
342                break;
343        }
344        yyjson_mut_obj_add_str(doc, module, "showIpv6", str);
345    }
346
347    yyjson_mut_obj_add_bool(doc, module, "showMac", !!(options->showType & FF_LOCALIP_TYPE_MAC_BIT));
348
349    yyjson_mut_obj_add_bool(doc, module, "showLoop", !!(options->showType & FF_LOCALIP_TYPE_LOOP_BIT));
350
351    yyjson_mut_obj_add_bool(doc, module, "showPrefixLen", !!(options->showType & FF_LOCALIP_TYPE_PREFIX_LEN_BIT));
352
353    yyjson_mut_obj_add_bool(doc, module, "showMtu", !!(options->showType & FF_LOCALIP_TYPE_MTU_BIT));
354
355    yyjson_mut_obj_add_bool(doc, module, "showSpeed", !!(options->showType & FF_LOCALIP_TYPE_SPEED_BIT));
356
357    yyjson_mut_obj_add_bool(doc, module, "showFlags", !!(options->showType & FF_LOCALIP_TYPE_FLAGS_BIT));
358
359    yyjson_mut_obj_add_bool(doc, module, "compact", !!(options->showType & FF_LOCALIP_TYPE_COMPACT_BIT));
360
361    yyjson_mut_obj_add_bool(doc, module, "defaultRouteOnly", !!(options->showType & FF_LOCALIP_TYPE_DEFAULT_ROUTE_ONLY_BIT));
362
363    yyjson_mut_obj_add_bool(doc, module, "showAllIps", !!(options->showType & FF_LOCALIP_TYPE_ALL_IPS_BIT));
364
365    yyjson_mut_obj_add_strbuf(doc, module, "namePrefix", &options->namePrefix);
366}
367
368bool ffGenerateLocalIpJsonResult([[maybe_unused]] FFLocalIpOptions* options, yyjson_mut_doc* doc, yyjson_mut_val* module) {
369    FF_LIST_AUTO_DESTROY results = ffListCreate();
370
371    const char* error = ffDetectLocalIps(options, &results);
372
373    if (error) {
374        yyjson_mut_obj_add_str(doc, module, "error", error);
375        return false;
376    }
377
378    yyjson_mut_val* arr = yyjson_mut_obj_add_arr(doc, module, "result");
379    FF_LIST_FOR_EACH (FFLocalIpResult, ip, results) {
380        yyjson_mut_val* obj = yyjson_mut_arr_add_obj(doc, arr);
381        yyjson_mut_obj_add_strbuf(doc, obj, "name", &ip->name);
382        if (options->showType & (FF_LOCALIP_TYPE_IPV4_BIT | FF_LOCALIP_TYPE_IPV6_BIT)) {
383            yyjson_mut_val* defaultRoute = yyjson_mut_obj_add_obj(doc, obj, "defaultRoute");
384            if (options->showType & FF_LOCALIP_TYPE_IPV4_BIT) {
385                yyjson_mut_obj_add_bool(doc, defaultRoute, "ipv4", !!(ip->defaultRoute & FF_LOCALIP_TYPE_IPV4_BIT));
386            }
387            if (options->showType & FF_LOCALIP_TYPE_IPV6_BIT) {
388                yyjson_mut_obj_add_bool(doc, defaultRoute, "ipv6", !!(ip->defaultRoute & FF_LOCALIP_TYPE_IPV6_BIT));
389            }
390        }
391        if (options->showType & FF_LOCALIP_TYPE_IPV4_BIT) {
392            yyjson_mut_obj_add_strbuf(doc, obj, "ipv4", &ip->ipv4);
393        }
394        if (options->showType & FF_LOCALIP_TYPE_IPV6_BIT) {
395            yyjson_mut_obj_add_strbuf(doc, obj, "ipv6", &ip->ipv6);
396        }
397        if (options->showType & FF_LOCALIP_TYPE_MAC_BIT) {
398            yyjson_mut_obj_add_strbuf(doc, obj, "mac", &ip->mac);
399        }
400        if (options->showType & FF_LOCALIP_TYPE_MTU_BIT) {
401            yyjson_mut_obj_add_int(doc, obj, "mtu", ip->mtu);
402        }
403        if (options->showType & FF_LOCALIP_TYPE_SPEED_BIT) {
404            yyjson_mut_obj_add_int(doc, obj, "speed", ip->speed);
405        }
406        if (options->showType & FF_LOCALIP_TYPE_FLAGS_BIT) {
407            yyjson_mut_obj_add_strbuf(doc, obj, "flags", &ip->flags);
408        }
409    }
410
411    FF_LIST_FOR_EACH (FFLocalIpResult, ip, results) {
412        ffStrbufDestroy(&ip->name);
413        ffStrbufDestroy(&ip->ipv4);
414        ffStrbufDestroy(&ip->ipv6);
415        ffStrbufDestroy(&ip->mac);
416        ffStrbufDestroy(&ip->flags);
417    }
418
419    return true;
420}
421
422void ffInitLocalIpOptions(FFLocalIpOptions* options) {
423    ffOptionInitModuleArg(&options->moduleArgs, "󰩟");
424
425    options->showType = FF_LOCALIP_TYPE_IPV4_BIT | FF_LOCALIP_TYPE_PREFIX_LEN_BIT
426#if !__ANDROID__ /*Permission denied*/
427        | FF_LOCALIP_TYPE_DEFAULT_ROUTE_ONLY_BIT
428#endif
429        ;
430    options->ipv6Type = FF_LOCALIP_IPV6_TYPE_AUTO;
431    ffStrbufInit(&options->namePrefix);
432}
433
434void ffDestroyLocalIpOptions(FFLocalIpOptions* options) {
435    ffOptionDestroyModuleArg(&options->moduleArgs);
436    ffStrbufDestroy(&options->namePrefix);
437}
438
439FFModuleBaseInfo ffLocalIPModuleInfo = {
440    .name = "LocalIp",
441    .description = "List local IP addresses (IPv4 or IPv6), MAC addresses, etc",
442    .displayName = {
443        .en = "Local IP",
444        .ar = "عنوان IP المحلي",
445        .cs = "Místní IP",
446        .de = "Lokale IP",
447        .es = "IP local",
448        .fi = "Paikallinen IP",
449        .fr = "IP locale",
450        .gl = "IP local",
451        .he = "IP מקומי",
452        .id = "IP Lokal",
453        .it = "IP locale",
454        .ja = "ローカル IP",
455        .ko = "로컬 IP",
456        .pl = "Lokalny adres IP",
457        .pt = "IP local",
458        .ru = "Локальный IP",
459        .tr = "Yerel IP",
460        .uk = "Локальна IP",
461        .vi = "IP cục bộ",
462        .zh_CN = "本地 IP",
463        .zh_TW = "區域 IP",
464    },
465    .initOptions = (void*) ffInitLocalIpOptions,
466    .destroyOptions = (void*) ffDestroyLocalIpOptions,
467    .parseJsonObject = (void*) ffParseLocalIpJsonObject,
468    .printModule = (void*) ffPrintLocalIp,
469    .generateJsonResult = (void*) ffGenerateLocalIpJsonResult,
470    .generateJsonConfig = (void*) ffGenerateLocalIpJsonConfig,
471    .formatArgs = FF_FORMAT_ARG_LIST(((FFModuleFormatArg[]) {
472        { "IPv4 address", "ipv4" },
473        { "IPv6 address", "ipv6" },
474        { "MAC address *", "mac" },
475        { "Interface name *", "ifname" },
476        { "Is default route", "is-default-route" },
477        { "MTU size in bytes", "mtu" },
478        { "Link speed (formatted)", "speed" },
479        { "Interface flags", "flags" },
480    })),
481    .defaultOrder = 50,
482};