summaryrefslogtreecommitdiffstats
path: root/src/common/impl/jsonconfig.c
diff options
context:
space:
mode:
authorsumuel <samuel@yakubos.org>2026-08-17 20:44:55 +0000
committersumuel <samuel@yakubos.org>2026-08-17 20:44:55 +0000
commit76424950e373d3b04ac3dd13019151bfba3e8423 (patch)
tree4c3cfdbda039e592b9186be3e28f8d7cfd439e8a /src/common/impl/jsonconfig.c
Add the files
Diffstat (limited to 'src/common/impl/jsonconfig.c')
-rw-r--r--src/common/impl/jsonconfig.c365
1 files changed, 365 insertions, 0 deletions
diff --git a/src/common/impl/jsonconfig.c b/src/common/impl/jsonconfig.c
new file mode 100644
index 0000000..fccc56c
--- /dev/null
+++ b/src/common/impl/jsonconfig.c
@@ -0,0 +1,365 @@
+#include "fastfetch.h"
+#include "common/color.h"
+#include "common/jsonconfig.h"
+#include "common/printing.h"
+#include "common/io.h"
+#include "common/time.h"
+#include "common/strutil.h"
+#include "detection/version/version.h"
+#include "modules/modules.h"
+
+#include <assert.h>
+#include <ctype.h>
+#include <inttypes.h>
+
+bool ffJsonConfigParseModuleArgs(yyjson_val* key, yyjson_val* val, FFModuleArgs* moduleArgs) {
+ if (unsafe_yyjson_equals_str(key, "type") || unsafe_yyjson_equals_str(key, "condition")) {
+ return true;
+ }
+
+ if (unsafe_yyjson_equals_str(key, "key")) {
+ ffStrbufSetJsonVal(&moduleArgs->key, val);
+ return true;
+ } else if (unsafe_yyjson_equals_str(key, "format")) {
+ ffStrbufSetJsonVal(&moduleArgs->outputFormat, val);
+ return true;
+ } else if (unsafe_yyjson_equals_str(key, "outputColor")) {
+ ffOptionParseColor(yyjson_get_str(val), &moduleArgs->outputColor);
+ return true;
+ } else if (unsafe_yyjson_equals_str(key, "keyColor")) {
+ ffOptionParseColor(yyjson_get_str(val), &moduleArgs->keyColor);
+ return true;
+ } else if (unsafe_yyjson_equals_str(key, "keyWidth")) {
+ moduleArgs->keyWidth = (uint32_t) yyjson_get_uint(val);
+ return true;
+ } else if (unsafe_yyjson_equals_str(key, "keyIcon")) {
+ ffStrbufSetJsonVal(&moduleArgs->keyIcon, val);
+ return true;
+ }
+ return false;
+}
+
+void ffJsonConfigGenerateModuleArgsConfig(yyjson_mut_doc* doc, yyjson_mut_val* module, FFModuleArgs* moduleArgs) {
+ if (moduleArgs->key.length > 0) {
+ yyjson_mut_obj_add_strbuf(doc, module, "key", &moduleArgs->key);
+ }
+ if (moduleArgs->outputFormat.length > 0) {
+ yyjson_mut_obj_add_strbuf(doc, module, "format", &moduleArgs->outputFormat);
+ }
+ if (moduleArgs->outputColor.length > 0) {
+ yyjson_mut_obj_add_strbuf(doc, module, "outputColor", &moduleArgs->outputColor);
+ }
+ if (moduleArgs->keyColor.length > 0) {
+ yyjson_mut_obj_add_strbuf(doc, module, "keyColor", &moduleArgs->keyColor);
+ }
+ if (moduleArgs->keyWidth > 0) {
+ yyjson_mut_obj_add_uint(doc, module, "keyWidth", moduleArgs->keyWidth);
+ }
+ if (moduleArgs->keyIcon.length > 0) {
+ yyjson_mut_obj_add_strbuf(doc, module, "keyIcon", &moduleArgs->keyIcon);
+ }
+}
+
+const char* ffJsonConfigParseEnum(yyjson_val* val, int* result, FFKeyValuePair pairs[]) {
+ if (yyjson_is_int(val)) {
+ int intVal = yyjson_get_int(val);
+
+ for (const FFKeyValuePair* pPair = pairs; pPair->key; ++pPair) {
+ if (intVal == pPair->value) {
+ *result = pPair->value;
+ return NULL;
+ }
+ }
+
+ return "Invalid enum integer";
+ } else if (yyjson_is_str(val)) {
+ const char* strVal = yyjson_get_str(val);
+ for (const FFKeyValuePair* pPair = pairs; pPair->key; ++pPair) {
+ if (ffStrEqualsIgnCase(strVal, pPair->key)) {
+ *result = pPair->value;
+ return NULL;
+ }
+ }
+
+ return "Invalid enum string";
+ } else {
+ return "Invalid enum value type; must be a string or integer";
+ }
+}
+
+static bool parseModuleJsonObject(const char* type, yyjson_val* jsonVal, yyjson_mut_doc* jsonDoc) {
+ if (!ffCharIsEnglishAlphabet(type[0])) {
+ return false;
+ }
+
+ for (FFModuleBaseInfo** modules = ffModuleInfos[toupper(type[0]) - 'A']; *modules; ++modules) {
+ FFModuleBaseInfo* baseInfo = *modules;
+ if (ffStrEqualsIgnCase(type, baseInfo->name)) {
+ uint8_t optionBuf[FF_OPTION_MAX_SIZE];
+ baseInfo->initOptions(optionBuf);
+ if (jsonVal) {
+ baseInfo->parseJsonObject(optionBuf, jsonVal);
+ }
+ bool succeeded;
+ if (jsonDoc) {
+ yyjson_mut_val* module = yyjson_mut_arr_add_obj(jsonDoc, jsonDoc->root);
+ yyjson_mut_obj_add_str(jsonDoc, module, "type", baseInfo->name);
+ if (baseInfo->generateJsonResult) {
+ succeeded = baseInfo->generateJsonResult(optionBuf, jsonDoc, module);
+ } else {
+ yyjson_mut_obj_add_str(jsonDoc, module, "error", "Unsupported for JSON format");
+ succeeded = false;
+ }
+ } else {
+ succeeded = baseInfo->printModule(optionBuf);
+ }
+ baseInfo->destroyOptions(optionBuf);
+ return succeeded;
+ }
+ }
+
+ if (jsonDoc) {
+ yyjson_mut_val* module = yyjson_mut_arr_add_obj(jsonDoc, jsonDoc->root);
+ yyjson_mut_obj_add_strcpy(jsonDoc, module, "type", type);
+ yyjson_mut_obj_add_str(jsonDoc, module, "error", "Unknown module type");
+ } else {
+ FFModuleArgs moduleArgs;
+ ffOptionInitModuleArg(&moduleArgs, "");
+ ffPrintError(type, 0, &moduleArgs, FF_PRINT_TYPE_DEFAULT, "Unknown module type");
+ ffOptionDestroyModuleArg(&moduleArgs);
+ }
+ return false;
+}
+
+static void prepareModuleJsonObject(const char* type, yyjson_val* module) {
+ switch (type[0]) {
+ #if !FF_MODULE_DISABLE_CPUUSAGE
+ case 'c':
+ case 'C': {
+ if (ffStrEqualsIgnCase(type, FF_CPUUSAGE_MODULE_NAME)) {
+ ffPrepareCPUUsage();
+ } else if (ffStrEqualsIgnCase(type, FF_COMMAND_MODULE_NAME)) {
+ FF_A_CLEANUP(ffDestroyCommandOptions) FFCommandOptions options;
+ ffInitCommandOptions(&options);
+ if (module) {
+ ffCommandModuleInfo.parseJsonObject(&options, module);
+ }
+ ffPrepareCommand(&options);
+ }
+ break;
+ }
+ #endif
+
+ #if !FF_MODULE_DISABLE_DISKIO
+ case 'd':
+ case 'D': {
+ if (ffStrEqualsIgnCase(type, FF_DISKIO_MODULE_NAME)) {
+ FF_A_CLEANUP(ffDestroyDiskIOOptions) FFDiskIOOptions options;
+ ffInitDiskIOOptions(&options);
+ if (module) {
+ ffDiskIOModuleInfo.parseJsonObject(&options, module);
+ }
+ ffPrepareDiskIO(&options);
+ }
+ break;
+ }
+ #endif
+
+ #if !FF_MODULE_DISABLE_NETIO
+ case 'n':
+ case 'N': {
+ if (ffStrEqualsIgnCase(type, FF_NETIO_MODULE_NAME)) {
+ FF_A_CLEANUP(ffDestroyNetIOOptions) FFNetIOOptions options;
+ ffInitNetIOOptions(&options);
+ if (module) {
+ ffNetIOModuleInfo.parseJsonObject(&options, module);
+ }
+ ffPrepareNetIO(&options);
+ }
+ break;
+ }
+ #endif
+
+ #if !FF_MODULE_DISABLE_PUBLICIP
+ case 'p':
+ case 'P': {
+ if (ffStrEqualsIgnCase(type, FF_PUBLICIP_MODULE_NAME)) {
+ FF_A_CLEANUP(ffDestroyPublicIpOptions) FFPublicIPOptions options;
+ ffInitPublicIpOptions(&options);
+ if (module) {
+ ffPublicIPModuleInfo.parseJsonObject(&options, module);
+ }
+ ffPreparePublicIp(&options);
+ }
+ break;
+ }
+ #endif
+
+ #if !FF_MODULE_DISABLE_WEATHER
+ case 'w':
+ case 'W': {
+ if (ffStrEqualsIgnCase(type, FF_WEATHER_MODULE_NAME)) {
+ FF_A_CLEANUP(ffDestroyWeatherOptions) FFWeatherOptions options;
+ ffInitWeatherOptions(&options);
+ if (module) {
+ ffWeatherModuleInfo.parseJsonObject(&options, module);
+ }
+ ffPrepareWeather(&options);
+ }
+ break;
+ }
+ #endif
+ }
+}
+
+static bool matchesJsonArray(const char* str, yyjson_val* val) {
+ assert(val);
+
+ if (unsafe_yyjson_is_str(val)) {
+ return ffStrEqualsIgnCase(str, unsafe_yyjson_get_str(val));
+ }
+
+ if (!unsafe_yyjson_is_arr(val)) {
+ return false;
+ }
+
+ size_t idx, max;
+ yyjson_val* item;
+ yyjson_arr_foreach (val, idx, max, item) {
+ if (yyjson_is_str(item) && ffStrEqualsIgnCase(str, unsafe_yyjson_get_str(item))) {
+ return true;
+ }
+ }
+ return false;
+}
+
+static const char* printJsonConfig(FFdata* data, bool prepare) {
+ yyjson_mut_doc* jsonDoc = data->resultDoc;
+ yyjson_val* const root = yyjson_doc_get_root(data->configDoc);
+ assert(root);
+
+ if (!yyjson_is_obj(root)) {
+ return "Invalid JSON config format. Root value must be an object";
+ }
+
+ yyjson_val* modules = yyjson_obj_get(root, "modules");
+ if (!modules) {
+ return NULL;
+ }
+ if (!yyjson_is_arr(modules)) {
+ return "Property 'modules' must be an array of strings or objects";
+ }
+
+ bool succeeded = true;
+ int32_t thres = instance.config.display.stat;
+ yyjson_val* item;
+ size_t idx, max;
+ yyjson_arr_foreach (modules, idx, max, item) {
+ double ms = 0;
+ if (!prepare && thres >= 0) {
+ ms = ffTimeGetTick();
+ }
+
+ yyjson_val* module = item;
+ const char* type = yyjson_get_str(module);
+ if (type) {
+ module = NULL;
+ } else if (yyjson_is_obj(module)) {
+ yyjson_val* conditions = yyjson_obj_get(module, "condition");
+ if (conditions) {
+ if (!yyjson_is_obj(conditions)) {
+ return "Property 'condition' must be an object";
+ }
+
+ yyjson_val* system = yyjson_obj_get(conditions, "system");
+ if (system && !matchesJsonArray(ffVersionResult.sysName, system)) {
+ continue;
+ }
+
+ system = yyjson_obj_get(conditions, "!system");
+ if (system && matchesJsonArray(ffVersionResult.sysName, system)) {
+ continue;
+ }
+
+ yyjson_val* arch = yyjson_obj_get(conditions, "arch");
+ if (arch && !matchesJsonArray(ffVersionResult.architecture, arch)) {
+ continue;
+ }
+
+ arch = yyjson_obj_get(conditions, "!arch");
+ if (arch && matchesJsonArray(ffVersionResult.architecture, arch)) {
+ continue;
+ }
+
+ yyjson_val* previousSucceeded = yyjson_obj_get(conditions, "succeeded");
+ if (previousSucceeded && !unsafe_yyjson_is_null(previousSucceeded)) {
+ if (!unsafe_yyjson_is_bool(previousSucceeded)) {
+ return "Property 'succeeded' in 'condition' must be a boolean";
+ }
+ if (succeeded != unsafe_yyjson_get_bool(previousSucceeded)) {
+ continue;
+ }
+ }
+ }
+
+ type = yyjson_get_str(yyjson_obj_get(module, "type"));
+ if (!type) {
+ return "module object must contain a \"type\" key ( case sensitive )";
+ }
+ if (yyjson_obj_size(module) == 1) { // contains only Property type
+ module = NULL;
+ }
+ } else {
+ return "modules must be an array of strings or objects";
+ }
+
+ if (ffStrbufSeparatedContainIgnCaseS(&data->structureDisabled, type, ':')) {
+ continue;
+ }
+
+ if (prepare) {
+ prepareModuleJsonObject(type, module);
+ } else {
+ succeeded = parseModuleJsonObject(type, module, jsonDoc);
+ }
+
+ if (!prepare && thres >= 0) {
+ ms = ffTimeGetTick() - ms;
+ if (jsonDoc) {
+ yyjson_mut_val* moduleJson = yyjson_mut_arr_get_last(jsonDoc->root);
+ yyjson_mut_obj_add_real(jsonDoc, moduleJson, "stat", ms);
+ } else {
+ char str[64];
+ int len = snprintf(str, sizeof str, "%.3fms", ms);
+ if (thres > 0) {
+ snprintf(str, sizeof str, "\e[%sm%.3fms\e[m", (ms <= thres ? FF_COLOR_FG_GREEN : ms <= 2 * thres ? FF_COLOR_FG_YELLOW
+ : FF_COLOR_FG_RED),
+ ms);
+ }
+ printf("\e7\e[1A\e[9999999C\e[%dD%s\e8", len - 1, str); // Save; Up 1; Right 9999999; Left <len - 1>; Print <str>; Load
+ }
+ }
+
+#if defined(_WIN32)
+ if (!instance.config.display.noBuffer && !jsonDoc) {
+ fflush(stdout);
+ }
+#endif
+ }
+
+ return NULL;
+}
+
+void ffPrintJsonConfig(FFdata* data, bool prepare) {
+ yyjson_mut_doc* jsonDoc = data->resultDoc;
+ const char* error = printJsonConfig(data, prepare);
+ if (error) {
+ if (jsonDoc) {
+ yyjson_mut_val* obj = yyjson_mut_obj(jsonDoc);
+ yyjson_mut_obj_add_str(jsonDoc, obj, "error", error);
+ yyjson_mut_doc_set_root(jsonDoc, obj);
+ } else {
+ ffPrintError("JsonConfig", 0, NULL, FF_PRINT_TYPE_NO_CUSTOM_KEY, "%s", error);
+ }
+ }
+}