From 537635cd9c88a322303bcebc7f62e1c01484f130 Mon Sep 17 00:00:00 2001 From: Carter Li Date: Fri, 25 Sep 2026 09:51:34 +0800 Subject: [PATCH] Logo: adds option `logo.position: auto` --- CHANGELOG.md | 3 + CMakeLists.txt | 8 ++ doc/help.json | 1 + doc/json_schema.json | 3 +- src/common/impl/genconfig.c | 115 +++++++++++++++--- src/detection/terminalsize/terminalsize.h | 3 +- .../terminalsize/terminalsize_linux.c | 23 ++-- .../terminalsize/terminalsize_windows.c | 14 ++- src/fastfetch.c | 11 +- src/logo/image/image.c | 13 +- src/logo/logo.c | 36 ++++++ src/logo/logo.h | 4 + src/modules/terminalsize/terminalsize.c | 4 +- src/options/logo.c | 22 +++- src/options/logo.h | 2 + tests/logo.c | 60 +++++++++ 16 files changed, 277 insertions(+), 45 deletions(-) create mode 100644 tests/logo.c diff --git a/CHANGELOG.md b/CHANGELOG.md index f98286dfb..360043923 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,9 @@ Changes: * Modules selected with `--structure` now honor their options from the JSON config. Features: +* Added `logo.position: "auto"` and `--logo-position auto` to place wide logos above module output when the terminal has less than 32 columns available for text. (Logo) + * The position can also be selected in interactive `--gen-config`. + * For now, the default position is `left` for compatibility with previous releases. This may change to `auto` in future releases. * Improved Bluetooth detection support: * Added Bluetooth Low Energy detection on Windows and macOS. * Report device type (LE or classic) and signal quality, if available. diff --git a/CMakeLists.txt b/CMakeLists.txt index d476233cc..ad67e2eb9 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -2253,6 +2253,13 @@ if (BUILD_TESTS) PRIVATE libfastfetch ) + add_executable(fastfetch-test-logo + tests/logo.c + ) + target_link_libraries(fastfetch-test-logo + PRIVATE libfastfetch + ) + if (CMAKE_SYSTEM_NAME STREQUAL "Linux") add_executable(fastfetch-test-cpu tests/cpu.c @@ -2379,6 +2386,7 @@ if (BUILD_TESTS) add_test(NAME test-cpu COMMAND fastfetch-test-cpu) endif() add_test(NAME test-cache COMMAND fastfetch-test-cache) + add_test(NAME test-logo COMMAND fastfetch-test-logo) add_test(NAME test-strbuf COMMAND fastfetch-test-strbuf) add_test(NAME test-list COMMAND fastfetch-test-list) add_test(NAME test-format COMMAND fastfetch-test-format) diff --git a/doc/help.json b/doc/help.json index d789505f9..471abc620 100644 --- a/doc/help.json +++ b/doc/help.json @@ -293,6 +293,7 @@ "arg": { "type": "enum", "enum": { + "auto": "Automatically choose left or top based on terminal width", "left": "Left", "top": "Top", "right": "Right" diff --git a/doc/json_schema.json b/doc/json_schema.json index d4f4c5391..85357a8a0 100644 --- a/doc/json_schema.json +++ b/doc/json_schema.json @@ -785,8 +785,9 @@ }, "position": { "type": "string", - "description": "Position of the logo", + "description": "Position of the logo. Auto places a left-side logo above the output when the terminal is too narrow to reserve 32 columns for module output.", "enum": [ + "auto", "left", "top", "right" diff --git a/src/common/impl/genconfig.c b/src/common/impl/genconfig.c index e81950816..4d298ef96 100644 --- a/src/common/impl/genconfig.c +++ b/src/common/impl/genconfig.c @@ -26,15 +26,16 @@ // 0: title // 1: blank // 2: logo type row -// 3: output mode row -// 4: blank -// 5: modules title row -// 6: blank -// 7..7+listRows-1: module grid -// 7+listRows: description row -// 8+listRows: help line 1 -// 9+listRows: help line 2 -#define FF_GEN_CONFIG_LIST_TOP 7 +// 3: logo position row +// 4: output mode row +// 5: blank +// 6: modules title row +// 7: blank +// 8..8+listRows-1: module grid +// 8+listRows: description row +// 9+listRows: help line 1 +// 10+listRows: help line 2 +#define FF_GEN_CONFIG_LIST_TOP 8 #define FF_GEN_CONFIG_BOTTOM_CHROME 3 // Poll wait timeout (ms) while idle. A longer wait avoids frequent full-screen @@ -66,6 +67,7 @@ typedef struct FFGenConfigUI { uint32_t cursor; uint32_t viewOffset; FFLogoType logoType; + FFLogoPosition logoPosition; bool fullConfig; bool confirmingOverwrite; uint16_t rows; @@ -165,7 +167,7 @@ static void enterRawMode(void) { static void getTerminalSize(uint16_t* rows, uint16_t* cols) { FFTerminalSizeResult size; - if (ffDetectTerminalSize(&size) && size.rows > 0 && size.columns > 0) { + if (ffDetectTerminalSize(&size, true) && size.rows > 0 && size.columns > 0) { *rows = size.rows; *cols = size.columns; } else { @@ -248,8 +250,8 @@ static void removeAllBreaksSeparators(FFGenConfigUI* ui) { static void computeLayout(FFGenConfigUI* ui) { FFGenConfigLayout* layout = &ui->layout; - if (ui->rows < 10) { - ui->rows = 10; + if (ui->rows < 11) { + ui->rows = 11; } int32_t listRows = (int32_t) ui->rows - FF_GEN_CONFIG_LIST_TOP - FF_GEN_CONFIG_BOTTOM_CHROME; layout->listRows = listRows < 0 ? 0 : (uint16_t) listRows; @@ -367,6 +369,26 @@ static void cycleLogoType(FFGenConfigUI* ui, int delta) { } } +static void cycleLogoPosition(FFGenConfigUI* ui, int delta) { + static const FFLogoPosition positions[] = { + FF_LOGO_POSITION_AUTO, + FF_LOGO_POSITION_LEFT, + FF_LOGO_POSITION_RIGHT, + FF_LOGO_POSITION_TOP, + }; + + uint32_t index = 0; + for (uint32_t i = 0; i < ARRAY_SIZE(positions); ++i) { + if (positions[i] == ui->logoPosition) { + index = i; + break; + } + } + + index = (index + (delta > 0 ? 1 : ARRAY_SIZE(positions) - 1)) % ARRAY_SIZE(positions); + ui->logoPosition = positions[index]; +} + static uint32_t countSelectedModules(const FFlist* items) { uint32_t count = 0; FF_LIST_FOR_EACH (FFGenConfigItem, item, *items) { @@ -558,7 +580,46 @@ static void renderFrame(FFGenConfigUI* ui, FFstrbuf* out) { } finishRow(&row, out, ++rowCount == ui->rows); - // Row 3: output mode + // Row 3: logo position + rowInit(&row, cols); + rowAppendVisual(&row, " "); + rowAppendRaw(&row, "\e[" FF_COLOR_MODE_BOLD "m"); + rowAppendVisual(&row, "Logo "); + rowAppendRaw(&row, "\e[" FF_COLOR_MODE_UNDERLINE_ "m"); + rowAppendVisual(&row, "p"); + rowAppendRaw(&row, "\e[24m"); // Reset underline + rowAppendVisual(&row, "osition:"); + rowAppendRaw(&row, FASTFETCH_TEXT_MODIFIER_RESET); + rowAppendVisual(&row, " "); + drawLogoOption(&row, "auto", ui->logoPosition == FF_LOGO_POSITION_AUTO); + rowAppendVisual(&row, " "); + drawLogoOption(&row, "left", ui->logoPosition == FF_LOGO_POSITION_LEFT); + rowAppendVisual(&row, " "); + drawLogoOption(&row, "right", ui->logoPosition == FF_LOGO_POSITION_RIGHT); + rowAppendVisual(&row, " "); + drawLogoOption(&row, "top", ui->logoPosition == FF_LOGO_POSITION_TOP); + if (cols >= 120) { + rowAppendRaw(&row, FASTFETCH_TEXT_MODIFIER_SOFT); + switch (ui->logoPosition) { + case FF_LOGO_POSITION_AUTO: + rowAppendVisual(&row, " - Choose left or top from terminal width"); + break; + case FF_LOGO_POSITION_RIGHT: + rowAppendVisual(&row, " - Place the logo to the right"); + break; + case FF_LOGO_POSITION_TOP: + rowAppendVisual(&row, " - Place the logo above module output"); + break; + case FF_LOGO_POSITION_LEFT: + default: + rowAppendVisual(&row, " - Place the logo to the left"); + break; + } + rowAppendRaw(&row, FASTFETCH_TEXT_MODIFIER_RESET); + } + finishRow(&row, out, ++rowCount == ui->rows); + + // Row 4: output mode rowInit(&row, cols); rowAppendVisual(&row, " "); rowAppendRaw(&row, "\e[" FF_COLOR_MODE_BOLD FF_COLOR_MODE_UNDERLINE_ "m"); @@ -581,11 +642,11 @@ static void renderFrame(FFGenConfigUI* ui, FFstrbuf* out) { } finishRow(&row, out, ++rowCount == ui->rows); - // Row 4: blank + // Row 5: blank rowInit(&row, cols); finishRow(&row, out, ++rowCount == ui->rows); - // Row 5: modules title + // Row 6: modules title rowInit(&row, cols); rowAppendVisual(&row, " "); rowAppendRaw(&row, FASTFETCH_TEXT_MODIFIER_BOLT); @@ -596,7 +657,7 @@ static void renderFrame(FFGenConfigUI* ui, FFstrbuf* out) { rowAppendRaw(&row, FASTFETCH_TEXT_MODIFIER_RESET); finishRow(&row, out, ++rowCount == ui->rows); - // Row 6: blank + // Row 7: blank rowInit(&row, cols); finishRow(&row, out, ++rowCount == ui->rows); @@ -618,7 +679,7 @@ static void renderFrame(FFGenConfigUI* ui, FFstrbuf* out) { finishRow(&row, out, ++rowCount == ui->rows); } - // Row after grid: description + // Row 9+listRows: description rowInit(&row, cols); if (ui->cursor < ui->items.length) { const FFGenConfigItem* item = FF_LIST_GET(FFGenConfigItem, ui->items, ui->cursor); @@ -650,7 +711,7 @@ static void renderFrame(FFGenConfigUI* ui, FFstrbuf* out) { rowInit(&row, cols); rowAppendRaw(&row, "\e[" FF_COLOR_MODE_DIM_ "m"); - rowAppendVisual(&row, " l/L logo o minimal/full s/Enter save q/Esc quit g/G top/bottom"); + rowAppendVisual(&row, " l/L logo p/P position o minimal/full s/Enter save q/Esc quit g/G top/bottom"); rowAppendRaw(&row, FASTFETCH_TEXT_MODIFIER_RESET); finishRow(&row, out, ++rowCount == ui->rows); @@ -904,6 +965,10 @@ static int handleKey(FFGenConfigUI* ui, FFGenConfigKey key, char ch, bool fileEx cycleLogoType(ui, 1); } else if (ch == 'L') { cycleLogoType(ui, -1); + } else if (ch == 'p') { + cycleLogoPosition(ui, 1); + } else if (ch == 'P') { + cycleLogoPosition(ui, -1); } else if (ch == '\x03' || ch == '\x04' || ch == '\x1a' || ch == '\x1c') { return 0; } @@ -940,6 +1005,18 @@ static FFLogoType initialLogoType(void) { } } +static FFLogoPosition initialLogoPosition(void) { + switch (instance.config.logo.position) { + case FF_LOGO_POSITION_TOP: + case FF_LOGO_POSITION_RIGHT: + case FF_LOGO_POSITION_AUTO: + return instance.config.logo.position; + case FF_LOGO_POSITION_LEFT: + default: + return FF_LOGO_POSITION_LEFT; + } +} + static bool applyConfig(FFdata* data, const FFlist* items, bool fullConfig) { if (data->resultDoc) { fputs("Error: duplicated `--gen-config` or `--format json` flags found\n", stderr); @@ -972,6 +1049,7 @@ bool ffGenConfigInteractive(FFdata* data) { .cursor = 0, .viewOffset = 0, .logoType = initialLogoType(), + .logoPosition = initialLogoPosition(), .fullConfig = false, .confirmingOverwrite = false, .rows = 24, @@ -993,6 +1071,7 @@ bool ffGenConfigInteractive(FFdata* data) { if (result == 1) { if (applyConfig(data, &ui.items, ui.fullConfig)) { instance.config.logo.type = ui.logoType; + instance.config.logo.position = ui.logoPosition; success = true; } } else { diff --git a/src/detection/terminalsize/terminalsize.h b/src/detection/terminalsize/terminalsize.h index 1ad97e1ba..1046acbe9 100644 --- a/src/detection/terminalsize/terminalsize.h +++ b/src/detection/terminalsize/terminalsize.h @@ -10,4 +10,5 @@ typedef struct FFTerminalSizeResult { uint16_t height; } FFTerminalSizeResult; -bool ffDetectTerminalSize(FFTerminalSizeResult* result); +// `fastOnly` avoids terminal query/response sequences and is intended for startup layout decisions. +bool ffDetectTerminalSize(FFTerminalSizeResult* result, bool fastOnly); diff --git a/src/detection/terminalsize/terminalsize_linux.c b/src/detection/terminalsize/terminalsize_linux.c index e06f958b2..9b64d2371 100644 --- a/src/detection/terminalsize/terminalsize_linux.c +++ b/src/detection/terminalsize/terminalsize_linux.c @@ -9,7 +9,9 @@ #include #endif -bool ffDetectTerminalSize(FFTerminalSizeResult* result) { +bool ffDetectTerminalSize(FFTerminalSizeResult* result, bool fastOnly) { + *result = (FFTerminalSizeResult){}; + struct winsize winsize = {}; static int ttyfd = STDOUT_FILENO; if (!isatty(ttyfd)) { @@ -18,21 +20,20 @@ bool ffDetectTerminalSize(FFTerminalSizeResult* result) { ioctl(ttyfd, TIOCGWINSZ, &winsize); - if (winsize.ws_row == 0 || winsize.ws_col == 0) { - ffGetTerminalResponse("\e[18t", 2, "\e[8;%hu;%hut", &winsize.ws_row, &winsize.ws_col); - } + if (!fastOnly) { + if (winsize.ws_row == 0 || winsize.ws_col == 0) { + ffGetTerminalResponse("\e[18t", 2, "\e[8;%hu;%hut", &winsize.ws_row, &winsize.ws_col); + } - if (winsize.ws_ypixel == 0 || winsize.ws_xpixel == 0) { - ffGetTerminalResponse("\e[14t", 2, "\e[4;%hu;%hut", &winsize.ws_ypixel, &winsize.ws_xpixel); - } - - if (winsize.ws_row == 0 && winsize.ws_col == 0) { - return false; + if (winsize.ws_ypixel == 0 || winsize.ws_xpixel == 0) { + ffGetTerminalResponse("\e[14t", 2, "\e[4;%hu;%hut", &winsize.ws_ypixel, &winsize.ws_xpixel); + } } result->rows = winsize.ws_row; result->columns = winsize.ws_col; result->width = winsize.ws_xpixel; result->height = winsize.ws_ypixel; - return true; + + return result->rows > 0 && result->columns > 0; } diff --git a/src/detection/terminalsize/terminalsize_windows.c b/src/detection/terminalsize/terminalsize_windows.c index 8e8af789a..9cf6844b9 100644 --- a/src/detection/terminalsize/terminalsize_windows.c +++ b/src/detection/terminalsize/terminalsize_windows.c @@ -3,7 +3,9 @@ #include -bool ffDetectTerminalSize(FFTerminalSizeResult* result) { +bool ffDetectTerminalSize(FFTerminalSizeResult* result, bool fastOnly) { + *result = (FFTerminalSizeResult){}; + HANDLE hOutput = GetStdHandle(STD_OUTPUT_HANDLE); FF_AUTO_CLOSE_FD HANDLE hConout = INVALID_HANDLE_VALUE; { @@ -18,16 +20,20 @@ bool ffDetectTerminalSize(FFTerminalSizeResult* result) { if (GetConsoleScreenBufferInfo(hOutput, &csbi)) { result->columns = (uint16_t) (csbi.srWindow.Right - csbi.srWindow.Left + 1); result->rows = (uint16_t) (csbi.srWindow.Bottom - csbi.srWindow.Top + 1); - } else { + } else if (!fastOnly) { // Windows Terminal doesn't report `\e` for some reason ffGetTerminalResponse("\e[18t", 2, "%*[^;];%hu;%hut", &result->rows, &result->columns); } } - if (result->columns == 0 && result->rows == 0) { + if (result->columns == 0 || result->rows == 0) { return false; } + if (fastOnly) { + return true; + } + { CONSOLE_FONT_INFOEX cfi = { .cbSize = sizeof(cfi) }; if (GetCurrentConsoleFontEx(hOutput, FALSE, &cfi)) // Only works for ConHost @@ -41,5 +47,5 @@ bool ffDetectTerminalSize(FFTerminalSizeResult* result) { } } - return result->columns > 0 && result->rows > 0; + return true; } diff --git a/src/fastfetch.c b/src/fastfetch.c index e431969af..4591301b9 100644 --- a/src/fastfetch.c +++ b/src/fastfetch.c @@ -854,9 +854,16 @@ static void writeConfigFile(FFdata* data) { } else if (data->genConfigInteractive) { if (instance.config.logo.type == FF_LOGO_TYPE_NONE) { yyjson_mut_obj_add_null(doc, root, "logo"); - } else if (instance.config.logo.type == FF_LOGO_TYPE_SMALL) { + } else if (instance.config.logo.type == FF_LOGO_TYPE_SMALL && instance.config.logo.position == FF_LOGO_POSITION_LEFT) { + yyjson_mut_obj_add_str(doc, root, "logo", "small"); + } else if (instance.config.logo.position != FF_LOGO_POSITION_LEFT || instance.config.logo.type == FF_LOGO_TYPE_SMALL) { yyjson_mut_val* logo = yyjson_mut_obj(doc); - yyjson_mut_obj_add_str(doc, logo, "type", "small"); + if (instance.config.logo.type == FF_LOGO_TYPE_SMALL) { + yyjson_mut_obj_add_str(doc, logo, "type", "small"); + } + if (instance.config.logo.position != FF_LOGO_POSITION_LEFT) { + yyjson_mut_obj_add_str(doc, logo, "position", ffLogoPositionToString(instance.config.logo.position)); + } yyjson_mut_obj_add_val(doc, root, "logo", logo); } } diff --git a/src/logo/image/image.c b/src/logo/image/image.c index 121f9061e..ce130ede3 100644 --- a/src/logo/image/image.c +++ b/src/logo/image/image.c @@ -20,6 +20,11 @@ #include #endif +static void resolveImageAutoPosition(uint32_t logoWidth) { + const FFOptionsLogo* options = &instance.config.logo; + ffLogoResolveAutoPosition(logoWidth + options->paddingLeft + options->paddingRight); +} + static bool printImageIterm(bool printError) { const FFOptionsLogo* options = &instance.config.logo; FF_STRBUF_AUTO_DESTROY buf = ffStrbufCreate(); @@ -633,6 +638,7 @@ static void printImagePixelsNoCache(FFLogoRequestData* requestData, const FFstrb static void fillCharacterDimensions(FFLogoRequestData* requestData) { requestData->logoCharacterWidth = (uint32_t) ceil((double) requestData->logoPixelWidth / requestData->characterPixelWidth); requestData->logoCharacterHeight = (uint32_t) ceil((double) requestData->logoPixelHeight / requestData->characterPixelHeight); + resolveImageAutoPosition(requestData->logoCharacterWidth); } static bool printImageSixel(FFLogoRequestData* requestData, const FFstrbuf* result) { @@ -1515,6 +1521,8 @@ static bool printCachedPixel(FFLogoRequestData* requestData) { } } + resolveImageAutoPosition(requestData->logoCharacterWidth); + if (requestData->type == FF_LOGO_TYPE_IMAGE_KITTY && options->animationFrame == FF_LOGO_ANIMATION_FRAME_ANIMATE) { // An animation entry holds payloads, not an envelope, so it can not be streamed out like @@ -1610,7 +1618,7 @@ static bool getCharacterPixelDimensions(FFLogoRequestData* requestData) { #endif FFTerminalSizeResult termSize = {}; - if (ffDetectTerminalSize(&termSize)) { + if (ffDetectTerminalSize(&termSize, false)) { requestData->characterPixelWidth = termSize.width / (double) termSize.columns; requestData->characterPixelHeight = termSize.height / (double) termSize.rows; } @@ -1773,14 +1781,17 @@ bool ffLogoPrintImageIfExists(FFLogoType type, bool printError) { } if (type == FF_LOGO_TYPE_IMAGE_ITERM) { + resolveImageAutoPosition(instance.config.logo.width); return printImageIterm(printError); } if (type == FF_LOGO_TYPE_IMAGE_KITTY_DIRECT) { + resolveImageAutoPosition(instance.config.logo.width); return printImageKittyDirect(printError); } if (type == FF_LOGO_TYPE_IMAGE_KITTY_ICAT) { + resolveImageAutoPosition(instance.config.logo.width); return printImageKittyIcat(printError); } diff --git a/src/logo/logo.c b/src/logo/logo.c index e0ba24cd2..9a580e48b 100644 --- a/src/logo/logo.c +++ b/src/logo/logo.c @@ -7,6 +7,7 @@ #include "detection/media/media.h" #include "detection/os/os.h" #include "detection/terminalshell/terminalshell.h" +#include "detection/terminalsize/terminalsize.h" #include #include @@ -17,6 +18,37 @@ typedef struct FFLogoCachedLine { uint32_t width; } FFLogoCachedLine; +FFLogoPosition ffLogoSelectPosition(FFLogoPosition configured, uint32_t terminalColumns, uint32_t logoWidth) { + if (configured != FF_LOGO_POSITION_AUTO) { + return configured; + } + + if (terminalColumns == 0) { + return FF_LOGO_POSITION_LEFT; + } + + return logoWidth <= terminalColumns && terminalColumns - logoWidth >= FF_LOGO_AUTO_MIN_INFO_WIDTH + ? FF_LOGO_POSITION_LEFT + : FF_LOGO_POSITION_TOP; +} + +void ffLogoResolveAutoPosition(uint32_t logoWidth) { + FFOptionsLogo* options = &instance.config.logo; + if (options->position != FF_LOGO_POSITION_AUTO) { + return; + } + + uint32_t terminalColumns = 0; + if (ffIsTerminal(STDOUT_FILENO)) { + FFTerminalSizeResult size = {}; + if (ffDetectTerminalSize(&size, true)) { + terminalColumns = size.columns; + } + } + + options->position = ffLogoSelectPosition(FF_LOGO_POSITION_AUTO, terminalColumns, logoWidth); +} + static void logoLineCacheClear(FFLogoLineCacheState* cache) { FF_LIST_FOR_EACH (FFLogoCachedLine, line, cache->lines) { ffStrbufDestroy(&line->chars); @@ -175,6 +207,8 @@ static void logoLineCacheBuild(FFLogoLineCacheState* cache, const char* data, bo parsedHeight = options->height; } + ffLogoResolveAutoPosition(maxLineWidth + options->paddingRight); + instance.state.logoHeight = options->paddingTop + parsedHeight; if (options->position == FF_LOGO_POSITION_LEFT) { instance.state.logoWidth = maxLineWidth + options->paddingRight; @@ -195,6 +229,8 @@ static bool ffLogoPrintCharsRaw(const char* data, size_t length, bool printError FFOptionsLogo* options = &instance.config.logo; FF_STRBUF_AUTO_DESTROY buf = ffStrbufCreate(); + ffLogoResolveAutoPosition(options->width + options->paddingLeft + options->paddingRight); + if (!options->width || !options->height) { if (options->position == FF_LOGO_POSITION_LEFT) { ffStrbufAppendF(&buf, "\e[2J\e[3J\e[%u;%uH", (unsigned) options->paddingTop + 1, (unsigned) options->paddingLeft + 1); diff --git a/src/logo/logo.h b/src/logo/logo.h index a1cf36ec9..c6422f978 100644 --- a/src/logo/logo.h +++ b/src/logo/logo.h @@ -24,6 +24,10 @@ typedef struct FFlogo { } FFlogo; // logo.c +enum { FF_LOGO_AUTO_MIN_INFO_WIDTH = 32 }; + +FFLogoPosition ffLogoSelectPosition(FFLogoPosition configured, uint32_t terminalColumns, uint32_t logoWidth); +void ffLogoResolveAutoPosition(uint32_t logoWidth); void ffLogoPrint(void); void ffLogoPrintChars(const char* data, bool doColorReplacement); void ffLogoPrintLine(void); diff --git a/src/modules/terminalsize/terminalsize.c b/src/modules/terminalsize/terminalsize.c index cfea3bd2b..23f2099d0 100644 --- a/src/modules/terminalsize/terminalsize.c +++ b/src/modules/terminalsize/terminalsize.c @@ -7,7 +7,7 @@ bool ffPrintTerminalSize(FFTerminalSizeOptions* options) { FFTerminalSizeResult result = {}; - if (!ffDetectTerminalSize(&result)) { + if (!ffDetectTerminalSize(&result, false)) { ffPrintError(FF_MODULE_GET_DISPLAY_NAME(TerminalSize), 0, &options->moduleArgs, FF_PRINT_TYPE_DEFAULT, "Failed to detect terminal size"); return false; } @@ -51,7 +51,7 @@ void ffGenerateTerminalSizeJsonConfig(FFTerminalSizeOptions* options, yyjson_mut bool ffGenerateTerminalSizeJsonResult([[maybe_unused]] FFTerminalSizeOptions* options, yyjson_mut_doc* doc, yyjson_mut_val* module) { FFTerminalSizeResult result; - if (!ffDetectTerminalSize(&result)) { + if (!ffDetectTerminalSize(&result, false)) { yyjson_mut_obj_add_str(doc, module, "error", "Failed to detect terminal size"); return false; } diff --git a/src/options/logo.c b/src/options/logo.c index 5a3de49e7..a46d1d728 100644 --- a/src/options/logo.c +++ b/src/options/logo.c @@ -39,6 +39,20 @@ static void logoParseImageFlag(FFOptionsLogo* options, const char* key, const ch #endif } +const char* ffLogoPositionToString(FFLogoPosition position) { + switch (position) { + case FF_LOGO_POSITION_TOP: + return "top"; + case FF_LOGO_POSITION_RIGHT: + return "right"; + case FF_LOGO_POSITION_AUTO: + return "auto"; + case FF_LOGO_POSITION_LEFT: + default: + return "left"; + } +} + void ffOptionsInitLogo(FFOptionsLogo* options) { ffStrbufInit(&options->source); options->type = FF_LOGO_TYPE_AUTO; @@ -162,6 +176,7 @@ bool ffOptionsParseLogoCommandLine(FFOptionsLogo* options, const char* key, cons exit(477); } else if (ffStrEqualsIgnCase(subKey, "position")) { options->position = (FFLogoPosition) ffOptionParseEnum(key, value, (FFKeyValuePair[]) { + { "auto", FF_LOGO_POSITION_AUTO }, { "left", FF_LOGO_POSITION_LEFT }, { "right", FF_LOGO_POSITION_RIGHT }, { "top", FF_LOGO_POSITION_TOP }, @@ -403,6 +418,7 @@ const char* ffOptionsParseLogoJsonConfig(FFOptionsLogo* options, yyjson_val* roo } else if (unsafe_yyjson_equals_str(key, "position")) { int value; const char* error = ffJsonConfigParseEnum(val, &value, (FFKeyValuePair[]) { + { "auto", FF_LOGO_POSITION_AUTO }, { "left", FF_LOGO_POSITION_LEFT }, { "top", FF_LOGO_POSITION_TOP }, { "right", FF_LOGO_POSITION_RIGHT }, @@ -604,11 +620,7 @@ void ffOptionsGenerateLogoJsonConfig(FFdata* data, FFOptionsLogo* options) { break; } - yyjson_mut_obj_add_str(doc, obj, "position", ((const char*[]) { - "left", - "top", - "right", - })[options->position]); + yyjson_mut_obj_add_str(doc, obj, "position", ffLogoPositionToString(options->position)); yyjson_mut_obj_add_int(doc, obj, "animationFrame", options->animationFrame); diff --git a/src/options/logo.h b/src/options/logo.h index 503afa992..984efd894 100644 --- a/src/options/logo.h +++ b/src/options/logo.h @@ -28,6 +28,7 @@ typedef enum FFLogoPosition: uint8_t { FF_LOGO_POSITION_LEFT, FF_LOGO_POSITION_TOP, FF_LOGO_POSITION_RIGHT, + FF_LOGO_POSITION_AUTO, } FFLogoPosition; typedef enum FFLogoCacheStrategy: uint8_t { @@ -70,6 +71,7 @@ typedef struct FFOptionsLogo { #endif } FFOptionsLogo; +const char* ffLogoPositionToString(FFLogoPosition position); void ffOptionsInitLogo(FFOptionsLogo* options); bool ffOptionsParseLogoCommandLine(FFOptionsLogo* options, const char* key, const char* value); void ffOptionsDestroyLogo(FFOptionsLogo* options); diff --git a/tests/logo.c b/tests/logo.c new file mode 100644 index 000000000..d100cd7df --- /dev/null +++ b/tests/logo.c @@ -0,0 +1,60 @@ +#include "logo/logo.h" +#include "common/textModifier.h" + +#include +#include +#include +#include + +noreturn static void testFailed(const char* expression, int lineNo) { + fprintf(stderr, FASTFETCH_TEXT_MODIFIER_ERROR "[%d] %s\n" FASTFETCH_TEXT_MODIFIER_RESET, lineNo, expression); + exit(1); +} + +noreturn static void testFailedEnum(FFLogoPosition expected, FFLogoPosition actual, const char* what, int lineNo) { + fprintf(stderr, FASTFETCH_TEXT_MODIFIER_ERROR "[%d] %s: expected %u, got %u\n" FASTFETCH_TEXT_MODIFIER_RESET, lineNo, what, (unsigned) expected, (unsigned) actual); + exit(1); +} + +#define VERIFY(expression) \ + if (!(expression)) testFailed(#expression, __LINE__) + +#define VERIFY_POSITION(expected, actual, what) \ + if ((expected) != (actual)) testFailedEnum((expected), (actual), (what), __LINE__) + +int main(void) { + // Unknown terminal size keeps the historical left-side layout. + VERIFY_POSITION(FF_LOGO_POSITION_LEFT, + ffLogoSelectPosition(FF_LOGO_POSITION_AUTO, 0, 40), + "auto fallback"); + + // Auto keeps the logo beside the output while at least 32 columns remain for module text. + VERIFY_POSITION(FF_LOGO_POSITION_LEFT, + ffLogoSelectPosition(FF_LOGO_POSITION_AUTO, 72, 40), + "auto exact side-by-side fit"); + VERIFY_POSITION(FF_LOGO_POSITION_TOP, + ffLogoSelectPosition(FF_LOGO_POSITION_AUTO, 71, 40), + "auto narrow terminal"); + VERIFY_POSITION(FF_LOGO_POSITION_TOP, + ffLogoSelectPosition(FF_LOGO_POSITION_AUTO, 80, UINT32_MAX), + "auto width overflow"); + + // Explicit positions are never overridden. + VERIFY_POSITION(FF_LOGO_POSITION_LEFT, + ffLogoSelectPosition(FF_LOGO_POSITION_LEFT, 20, 40), + "explicit left"); + VERIFY_POSITION(FF_LOGO_POSITION_TOP, + ffLogoSelectPosition(FF_LOGO_POSITION_TOP, 120, 40), + "explicit top"); + VERIFY_POSITION(FF_LOGO_POSITION_RIGHT, + ffLogoSelectPosition(FF_LOGO_POSITION_RIGHT, 20, 40), + "explicit right"); + + VERIFY(strcmp(ffLogoPositionToString(FF_LOGO_POSITION_AUTO), "auto") == 0); + VERIFY(strcmp(ffLogoPositionToString(FF_LOGO_POSITION_LEFT), "left") == 0); + VERIFY(strcmp(ffLogoPositionToString(FF_LOGO_POSITION_TOP), "top") == 0); + VERIFY(strcmp(ffLogoPositionToString(FF_LOGO_POSITION_RIGHT), "right") == 0); + + puts("\033[32mAll tests passed!" FASTFETCH_TEXT_MODIFIER_RESET); + return 0; +}