master ohmu/fastfetch-openbsd / src / modules / separator / separator.c
ohmu  ·  2026-10-15
  1#include "common/printing.h"
  2#include "common/jsonconfig.h"
  3#include "common/strutil.h"
  4#include "common/textModifier.h"
  5#include "logo/logo.h"
  6#include "modules/separator/separator.h"
  7
  8bool ffPrintSeparator(FFSeparatorOptions* options) {
  9    ffLogoPrintLine();
 10
 11    if (options->outputColor.length && !instance.config.display.pipe) {
 12        ffPrintColor(&options->outputColor);
 13    }
 14
 15    if (options->times > 0) {
 16        if (__builtin_expect(options->string.length == 1, 1)) {
 17            ffPrintCharTimes(options->string.chars[0], options->times);
 18        } else {
 19            for (uint32_t i = 0; i < options->times; i++) {
 20                fputs(options->string.chars, stdout);
 21            }
 22        }
 23    } else {
 24        const FFPlatform* platform = &instance.state.platform;
 25
 26        uint32_t titleLength = 1                                                                                      // @
 27            + ffUtf8StrWidth(platform->userName.chars, platform->userName.length)                                     // user name
 28            + (instance.state.titleFqdn ? platform->hostName.length : ffStrbufFirstIndexC(&platform->hostName, '.')); // host name
 29
 30        if (__builtin_expect(options->string.length == 1, 1)) {
 31            ffPrintCharTimes(options->string.chars[0], titleLength);
 32        } else {
 33            uint32_t wcsLength = ffUtf8StrWidth(options->string.chars, options->string.length);
 34
 35            int remaining = (int) titleLength;
 36            // Write the whole separator as often as it fits fully into titleLength
 37            for (; remaining >= (int) wcsLength; remaining -= (int) wcsLength) {
 38                ffStrbufWriteTo(&options->string, stdout);
 39            }
 40
 41            if (remaining > 0) {
 42                // Write as much of the separator as needed to fill titleLength
 43                if (wcsLength != options->string.length) {
 44                    // Unicode chars
 45                    const char* ptr = options->string.chars;
 46                    uint32_t remainBytes = options->string.length;
 47                    while (remaining > 0 && remainBytes > 0 && *ptr != '\0') {
 48                        uint8_t charWidth = 0;
 49                        uint8_t bytes = ffUtf8CharLenWidth(ptr, remainBytes, &charWidth);
 50                        if (__builtin_expect(bytes == 0, false)) {
 51                            break;
 52                        }
 53
 54                        remaining -= (int) charWidth;
 55                        ptr += bytes;
 56                        remainBytes -= bytes;
 57                    }
 58                    fwrite(options->string.chars, (size_t) (ptr - options->string.chars), 1, stdout);
 59                } else {
 60                    fwrite(options->string.chars, (size_t) remaining, 1, stdout);
 61                }
 62            }
 63        }
 64    }
 65
 66    if (options->outputColor.length && !instance.config.display.pipe) {
 67        fputs(FASTFETCH_TEXT_MODIFIER_RESET, stdout);
 68    }
 69    putchar('\n');
 70
 71    return true;
 72}
 73
 74void ffParseSeparatorJsonObject(FFSeparatorOptions* options, yyjson_val* module) {
 75    yyjson_val *key, *val;
 76    size_t idx, max;
 77    yyjson_obj_foreach (module, idx, max, key, val) {
 78        if (unsafe_yyjson_equals_str(key, "type") || unsafe_yyjson_equals_str(key, "condition")) {
 79            continue;
 80        }
 81
 82        if (unsafe_yyjson_equals_str(key, "string")) {
 83            ffStrbufSetJsonVal(&options->string, val);
 84            continue;
 85        }
 86
 87        if (unsafe_yyjson_equals_str(key, "outputColor")) {
 88            ffOptionParseColor(yyjson_get_str(val), &options->outputColor);
 89            continue;
 90        }
 91
 92        if (unsafe_yyjson_equals_str(key, "times")) {
 93            if (!ffJsonConfigParseUInt32(val, &options->times, UINT32_MAX)) {
 94                ffPrintError(FF_MODULE_GET_DISPLAY_NAME(Separator), 0, nullptr, FF_PRINT_TYPE_NO_CUSTOM_KEY, "Property 'times' must be a non-negative integer no greater than 4294967295");
 95                continue;
 96            }
 97            continue;
 98        }
 99
100        if (unsafe_yyjson_equals_str(key, "length")) {
101            ffPrintError(FF_MODULE_GET_DISPLAY_NAME(Separator), 0, nullptr, FF_PRINT_TYPE_NO_CUSTOM_KEY, "The option length has been renamed to times.");
102            continue;
103        }
104
105        ffPrintError(FF_MODULE_GET_DISPLAY_NAME(Separator), 0, nullptr, FF_PRINT_TYPE_NO_CUSTOM_KEY, "Unknown JSON key %s", unsafe_yyjson_get_str(key));
106    }
107}
108
109[[gnu::cold]]
110void ffGenerateSeparatorJsonConfig(FFSeparatorOptions* options, yyjson_mut_doc* doc, yyjson_mut_val* module) {
111    yyjson_mut_obj_add_strbuf(doc, module, "string", &options->string);
112    yyjson_mut_obj_add_strbuf(doc, module, "outputColor", &options->outputColor);
113    yyjson_mut_obj_add_uint(doc, module, "times", options->times);
114}
115
116void ffInitSeparatorOptions(FFSeparatorOptions* options) {
117    ffStrbufInitStatic(&options->string, "-");
118    ffStrbufInit(&options->outputColor);
119    options->times = 0;
120}
121
122void ffDestroySeparatorOptions(FFSeparatorOptions* options) {
123    ffStrbufDestroy(&options->string);
124    ffStrbufDestroy(&options->outputColor);
125}
126
127FFModuleBaseInfo ffSeparatorModuleInfo = {
128    .name = "Separator",
129    .description = "Print a separator line",
130    .displayName = {
131        .en = "Separator",
132        .ar = "الفاصل",
133        .cs = "Oddělovač",
134        .de = "Trennlinie",
135        .es = "Separador",
136        .fi = "Erotin",
137        .fr = "Séparateur",
138        .gl = "Separador",
139        .he = "מפריד",
140        .id = "Pemisah",
141        .it = "Separatore",
142        .ja = "セパレーター",
143        .ko = "구분선",
144        .pl = "Separator",
145        .pt = "Separador",
146        .ru = "Разделитель",
147        .tr = "Ayraç",
148        .uk = "Роздільник",
149        .vi = "Dấu phân cách",
150        .zh_CN = "分隔线",
151        .zh_TW = "分隔線",
152    },
153    .initOptions = (void*) ffInitSeparatorOptions,
154    .destroyOptions = (void*) ffDestroySeparatorOptions,
155    .parseJsonObject = (void*) ffParseSeparatorJsonObject,
156    .printModule = (void*) ffPrintSeparator,
157    .generateJsonConfig = (void*) ffGenerateSeparatorJsonConfig,
158    .defaultOrder = 2,
159};