Logo: adds option logo.position: auto

This commit is contained in:
Carter Li
2026-09-25 09:51:34 +08:00
parent c68bedd9bf
commit 537635cd9c
16 changed files with 277 additions and 45 deletions
+3
View File
@@ -7,6 +7,9 @@ Changes:
* Modules selected with `--structure` now honor their options from the JSON config. * Modules selected with `--structure` now honor their options from the JSON config.
Features: 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: * Improved Bluetooth detection support:
* Added Bluetooth Low Energy detection on Windows and macOS. * Added Bluetooth Low Energy detection on Windows and macOS.
* Report device type (LE or classic) and signal quality, if available. * Report device type (LE or classic) and signal quality, if available.
+8
View File
@@ -2253,6 +2253,13 @@ if (BUILD_TESTS)
PRIVATE libfastfetch PRIVATE libfastfetch
) )
add_executable(fastfetch-test-logo
tests/logo.c
)
target_link_libraries(fastfetch-test-logo
PRIVATE libfastfetch
)
if (CMAKE_SYSTEM_NAME STREQUAL "Linux") if (CMAKE_SYSTEM_NAME STREQUAL "Linux")
add_executable(fastfetch-test-cpu add_executable(fastfetch-test-cpu
tests/cpu.c tests/cpu.c
@@ -2379,6 +2386,7 @@ if (BUILD_TESTS)
add_test(NAME test-cpu COMMAND fastfetch-test-cpu) add_test(NAME test-cpu COMMAND fastfetch-test-cpu)
endif() endif()
add_test(NAME test-cache COMMAND fastfetch-test-cache) 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-strbuf COMMAND fastfetch-test-strbuf)
add_test(NAME test-list COMMAND fastfetch-test-list) add_test(NAME test-list COMMAND fastfetch-test-list)
add_test(NAME test-format COMMAND fastfetch-test-format) add_test(NAME test-format COMMAND fastfetch-test-format)
+1
View File
@@ -293,6 +293,7 @@
"arg": { "arg": {
"type": "enum", "type": "enum",
"enum": { "enum": {
"auto": "Automatically choose left or top based on terminal width",
"left": "Left", "left": "Left",
"top": "Top", "top": "Top",
"right": "Right" "right": "Right"
+2 -1
View File
@@ -785,8 +785,9 @@
}, },
"position": { "position": {
"type": "string", "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": [ "enum": [
"auto",
"left", "left",
"top", "top",
"right" "right"
+97 -18
View File
@@ -26,15 +26,16 @@
// 0: title // 0: title
// 1: blank // 1: blank
// 2: logo type row // 2: logo type row
// 3: output mode row // 3: logo position row
// 4: blank // 4: output mode row
// 5: modules title row // 5: blank
// 6: blank // 6: modules title row
// 7..7+listRows-1: module grid // 7: blank
// 7+listRows: description row // 8..8+listRows-1: module grid
// 8+listRows: help line 1 // 8+listRows: description row
// 9+listRows: help line 2 // 9+listRows: help line 1
#define FF_GEN_CONFIG_LIST_TOP 7 // 10+listRows: help line 2
#define FF_GEN_CONFIG_LIST_TOP 8
#define FF_GEN_CONFIG_BOTTOM_CHROME 3 #define FF_GEN_CONFIG_BOTTOM_CHROME 3
// Poll wait timeout (ms) while idle. A longer wait avoids frequent full-screen // 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 cursor;
uint32_t viewOffset; uint32_t viewOffset;
FFLogoType logoType; FFLogoType logoType;
FFLogoPosition logoPosition;
bool fullConfig; bool fullConfig;
bool confirmingOverwrite; bool confirmingOverwrite;
uint16_t rows; uint16_t rows;
@@ -165,7 +167,7 @@ static void enterRawMode(void) {
static void getTerminalSize(uint16_t* rows, uint16_t* cols) { static void getTerminalSize(uint16_t* rows, uint16_t* cols) {
FFTerminalSizeResult size; FFTerminalSizeResult size;
if (ffDetectTerminalSize(&size) && size.rows > 0 && size.columns > 0) { if (ffDetectTerminalSize(&size, true) && size.rows > 0 && size.columns > 0) {
*rows = size.rows; *rows = size.rows;
*cols = size.columns; *cols = size.columns;
} else { } else {
@@ -248,8 +250,8 @@ static void removeAllBreaksSeparators(FFGenConfigUI* ui) {
static void computeLayout(FFGenConfigUI* ui) { static void computeLayout(FFGenConfigUI* ui) {
FFGenConfigLayout* layout = &ui->layout; FFGenConfigLayout* layout = &ui->layout;
if (ui->rows < 10) { if (ui->rows < 11) {
ui->rows = 10; ui->rows = 11;
} }
int32_t listRows = (int32_t) ui->rows - FF_GEN_CONFIG_LIST_TOP - FF_GEN_CONFIG_BOTTOM_CHROME; 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; 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) { static uint32_t countSelectedModules(const FFlist* items) {
uint32_t count = 0; uint32_t count = 0;
FF_LIST_FOR_EACH (FFGenConfigItem, item, *items) { FF_LIST_FOR_EACH (FFGenConfigItem, item, *items) {
@@ -558,7 +580,46 @@ static void renderFrame(FFGenConfigUI* ui, FFstrbuf* out) {
} }
finishRow(&row, out, ++rowCount == ui->rows); 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); rowInit(&row, cols);
rowAppendVisual(&row, " "); rowAppendVisual(&row, " ");
rowAppendRaw(&row, "\e[" FF_COLOR_MODE_BOLD FF_COLOR_MODE_UNDERLINE_ "m"); 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); finishRow(&row, out, ++rowCount == ui->rows);
// Row 4: blank // Row 5: blank
rowInit(&row, cols); rowInit(&row, cols);
finishRow(&row, out, ++rowCount == ui->rows); finishRow(&row, out, ++rowCount == ui->rows);
// Row 5: modules title // Row 6: modules title
rowInit(&row, cols); rowInit(&row, cols);
rowAppendVisual(&row, " "); rowAppendVisual(&row, " ");
rowAppendRaw(&row, FASTFETCH_TEXT_MODIFIER_BOLT); rowAppendRaw(&row, FASTFETCH_TEXT_MODIFIER_BOLT);
@@ -596,7 +657,7 @@ static void renderFrame(FFGenConfigUI* ui, FFstrbuf* out) {
rowAppendRaw(&row, FASTFETCH_TEXT_MODIFIER_RESET); rowAppendRaw(&row, FASTFETCH_TEXT_MODIFIER_RESET);
finishRow(&row, out, ++rowCount == ui->rows); finishRow(&row, out, ++rowCount == ui->rows);
// Row 6: blank // Row 7: blank
rowInit(&row, cols); rowInit(&row, cols);
finishRow(&row, out, ++rowCount == ui->rows); finishRow(&row, out, ++rowCount == ui->rows);
@@ -618,7 +679,7 @@ static void renderFrame(FFGenConfigUI* ui, FFstrbuf* out) {
finishRow(&row, out, ++rowCount == ui->rows); finishRow(&row, out, ++rowCount == ui->rows);
} }
// Row after grid: description // Row 9+listRows: description
rowInit(&row, cols); rowInit(&row, cols);
if (ui->cursor < ui->items.length) { if (ui->cursor < ui->items.length) {
const FFGenConfigItem* item = FF_LIST_GET(FFGenConfigItem, ui->items, ui->cursor); 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); rowInit(&row, cols);
rowAppendRaw(&row, "\e[" FF_COLOR_MODE_DIM_ "m"); 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); rowAppendRaw(&row, FASTFETCH_TEXT_MODIFIER_RESET);
finishRow(&row, out, ++rowCount == ui->rows); finishRow(&row, out, ++rowCount == ui->rows);
@@ -904,6 +965,10 @@ static int handleKey(FFGenConfigUI* ui, FFGenConfigKey key, char ch, bool fileEx
cycleLogoType(ui, 1); cycleLogoType(ui, 1);
} else if (ch == 'L') { } else if (ch == 'L') {
cycleLogoType(ui, -1); 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') { } else if (ch == '\x03' || ch == '\x04' || ch == '\x1a' || ch == '\x1c') {
return 0; 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) { static bool applyConfig(FFdata* data, const FFlist* items, bool fullConfig) {
if (data->resultDoc) { if (data->resultDoc) {
fputs("Error: duplicated `--gen-config` or `--format json` flags found\n", stderr); fputs("Error: duplicated `--gen-config` or `--format json` flags found\n", stderr);
@@ -972,6 +1049,7 @@ bool ffGenConfigInteractive(FFdata* data) {
.cursor = 0, .cursor = 0,
.viewOffset = 0, .viewOffset = 0,
.logoType = initialLogoType(), .logoType = initialLogoType(),
.logoPosition = initialLogoPosition(),
.fullConfig = false, .fullConfig = false,
.confirmingOverwrite = false, .confirmingOverwrite = false,
.rows = 24, .rows = 24,
@@ -993,6 +1071,7 @@ bool ffGenConfigInteractive(FFdata* data) {
if (result == 1) { if (result == 1) {
if (applyConfig(data, &ui.items, ui.fullConfig)) { if (applyConfig(data, &ui.items, ui.fullConfig)) {
instance.config.logo.type = ui.logoType; instance.config.logo.type = ui.logoType;
instance.config.logo.position = ui.logoPosition;
success = true; success = true;
} }
} else { } else {
+2 -1
View File
@@ -10,4 +10,5 @@ typedef struct FFTerminalSizeResult {
uint16_t height; uint16_t height;
} FFTerminalSizeResult; } FFTerminalSizeResult;
bool ffDetectTerminalSize(FFTerminalSizeResult* result); // `fastOnly` avoids terminal query/response sequences and is intended for startup layout decisions.
bool ffDetectTerminalSize(FFTerminalSizeResult* result, bool fastOnly);
+12 -11
View File
@@ -9,7 +9,9 @@
#include <sys/termios.h> #include <sys/termios.h>
#endif #endif
bool ffDetectTerminalSize(FFTerminalSizeResult* result) { bool ffDetectTerminalSize(FFTerminalSizeResult* result, bool fastOnly) {
*result = (FFTerminalSizeResult){};
struct winsize winsize = {}; struct winsize winsize = {};
static int ttyfd = STDOUT_FILENO; static int ttyfd = STDOUT_FILENO;
if (!isatty(ttyfd)) { if (!isatty(ttyfd)) {
@@ -18,21 +20,20 @@ bool ffDetectTerminalSize(FFTerminalSizeResult* result) {
ioctl(ttyfd, TIOCGWINSZ, &winsize); ioctl(ttyfd, TIOCGWINSZ, &winsize);
if (winsize.ws_row == 0 || winsize.ws_col == 0) { if (!fastOnly) {
ffGetTerminalResponse("\e[18t", 2, "\e[8;%hu;%hut", &winsize.ws_row, &winsize.ws_col); 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) { if (winsize.ws_ypixel == 0 || winsize.ws_xpixel == 0) {
ffGetTerminalResponse("\e[14t", 2, "\e[4;%hu;%hut", &winsize.ws_ypixel, &winsize.ws_xpixel); 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;
} }
result->rows = winsize.ws_row; result->rows = winsize.ws_row;
result->columns = winsize.ws_col; result->columns = winsize.ws_col;
result->width = winsize.ws_xpixel; result->width = winsize.ws_xpixel;
result->height = winsize.ws_ypixel; result->height = winsize.ws_ypixel;
return true;
return result->rows > 0 && result->columns > 0;
} }
@@ -3,7 +3,9 @@
#include <windows.h> #include <windows.h>
bool ffDetectTerminalSize(FFTerminalSizeResult* result) { bool ffDetectTerminalSize(FFTerminalSizeResult* result, bool fastOnly) {
*result = (FFTerminalSizeResult){};
HANDLE hOutput = GetStdHandle(STD_OUTPUT_HANDLE); HANDLE hOutput = GetStdHandle(STD_OUTPUT_HANDLE);
FF_AUTO_CLOSE_FD HANDLE hConout = INVALID_HANDLE_VALUE; FF_AUTO_CLOSE_FD HANDLE hConout = INVALID_HANDLE_VALUE;
{ {
@@ -18,16 +20,20 @@ bool ffDetectTerminalSize(FFTerminalSizeResult* result) {
if (GetConsoleScreenBufferInfo(hOutput, &csbi)) { if (GetConsoleScreenBufferInfo(hOutput, &csbi)) {
result->columns = (uint16_t) (csbi.srWindow.Right - csbi.srWindow.Left + 1); result->columns = (uint16_t) (csbi.srWindow.Right - csbi.srWindow.Left + 1);
result->rows = (uint16_t) (csbi.srWindow.Bottom - csbi.srWindow.Top + 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 // Windows Terminal doesn't report `\e` for some reason
ffGetTerminalResponse("\e[18t", 2, "%*[^;];%hu;%hut", &result->rows, &result->columns); 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; return false;
} }
if (fastOnly) {
return true;
}
{ {
CONSOLE_FONT_INFOEX cfi = { .cbSize = sizeof(cfi) }; CONSOLE_FONT_INFOEX cfi = { .cbSize = sizeof(cfi) };
if (GetCurrentConsoleFontEx(hOutput, FALSE, &cfi)) // Only works for ConHost 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;
} }
+9 -2
View File
@@ -854,9 +854,16 @@ static void writeConfigFile(FFdata* data) {
} else if (data->genConfigInteractive) { } else if (data->genConfigInteractive) {
if (instance.config.logo.type == FF_LOGO_TYPE_NONE) { if (instance.config.logo.type == FF_LOGO_TYPE_NONE) {
yyjson_mut_obj_add_null(doc, root, "logo"); 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_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); yyjson_mut_obj_add_val(doc, root, "logo", logo);
} }
} }
+12 -1
View File
@@ -20,6 +20,11 @@
#include <sys/termios.h> #include <sys/termios.h>
#endif #endif
static void resolveImageAutoPosition(uint32_t logoWidth) {
const FFOptionsLogo* options = &instance.config.logo;
ffLogoResolveAutoPosition(logoWidth + options->paddingLeft + options->paddingRight);
}
static bool printImageIterm(bool printError) { static bool printImageIterm(bool printError) {
const FFOptionsLogo* options = &instance.config.logo; const FFOptionsLogo* options = &instance.config.logo;
FF_STRBUF_AUTO_DESTROY buf = ffStrbufCreate(); FF_STRBUF_AUTO_DESTROY buf = ffStrbufCreate();
@@ -633,6 +638,7 @@ static void printImagePixelsNoCache(FFLogoRequestData* requestData, const FFstrb
static void fillCharacterDimensions(FFLogoRequestData* requestData) { static void fillCharacterDimensions(FFLogoRequestData* requestData) {
requestData->logoCharacterWidth = (uint32_t) ceil((double) requestData->logoPixelWidth / requestData->characterPixelWidth); requestData->logoCharacterWidth = (uint32_t) ceil((double) requestData->logoPixelWidth / requestData->characterPixelWidth);
requestData->logoCharacterHeight = (uint32_t) ceil((double) requestData->logoPixelHeight / requestData->characterPixelHeight); requestData->logoCharacterHeight = (uint32_t) ceil((double) requestData->logoPixelHeight / requestData->characterPixelHeight);
resolveImageAutoPosition(requestData->logoCharacterWidth);
} }
static bool printImageSixel(FFLogoRequestData* requestData, const FFstrbuf* result) { 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 && if (requestData->type == FF_LOGO_TYPE_IMAGE_KITTY &&
options->animationFrame == FF_LOGO_ANIMATION_FRAME_ANIMATE) { options->animationFrame == FF_LOGO_ANIMATION_FRAME_ANIMATE) {
// An animation entry holds payloads, not an envelope, so it can not be streamed out like // 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 #endif
FFTerminalSizeResult termSize = {}; FFTerminalSizeResult termSize = {};
if (ffDetectTerminalSize(&termSize)) { if (ffDetectTerminalSize(&termSize, false)) {
requestData->characterPixelWidth = termSize.width / (double) termSize.columns; requestData->characterPixelWidth = termSize.width / (double) termSize.columns;
requestData->characterPixelHeight = termSize.height / (double) termSize.rows; requestData->characterPixelHeight = termSize.height / (double) termSize.rows;
} }
@@ -1773,14 +1781,17 @@ bool ffLogoPrintImageIfExists(FFLogoType type, bool printError) {
} }
if (type == FF_LOGO_TYPE_IMAGE_ITERM) { if (type == FF_LOGO_TYPE_IMAGE_ITERM) {
resolveImageAutoPosition(instance.config.logo.width);
return printImageIterm(printError); return printImageIterm(printError);
} }
if (type == FF_LOGO_TYPE_IMAGE_KITTY_DIRECT) { if (type == FF_LOGO_TYPE_IMAGE_KITTY_DIRECT) {
resolveImageAutoPosition(instance.config.logo.width);
return printImageKittyDirect(printError); return printImageKittyDirect(printError);
} }
if (type == FF_LOGO_TYPE_IMAGE_KITTY_ICAT) { if (type == FF_LOGO_TYPE_IMAGE_KITTY_ICAT) {
resolveImageAutoPosition(instance.config.logo.width);
return printImageKittyIcat(printError); return printImageKittyIcat(printError);
} }
+36
View File
@@ -7,6 +7,7 @@
#include "detection/media/media.h" #include "detection/media/media.h"
#include "detection/os/os.h" #include "detection/os/os.h"
#include "detection/terminalshell/terminalshell.h" #include "detection/terminalshell/terminalshell.h"
#include "detection/terminalsize/terminalsize.h"
#include <ctype.h> #include <ctype.h>
#include <stdlib.h> #include <stdlib.h>
@@ -17,6 +18,37 @@ typedef struct FFLogoCachedLine {
uint32_t width; uint32_t width;
} FFLogoCachedLine; } 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) { static void logoLineCacheClear(FFLogoLineCacheState* cache) {
FF_LIST_FOR_EACH (FFLogoCachedLine, line, cache->lines) { FF_LIST_FOR_EACH (FFLogoCachedLine, line, cache->lines) {
ffStrbufDestroy(&line->chars); ffStrbufDestroy(&line->chars);
@@ -175,6 +207,8 @@ static void logoLineCacheBuild(FFLogoLineCacheState* cache, const char* data, bo
parsedHeight = options->height; parsedHeight = options->height;
} }
ffLogoResolveAutoPosition(maxLineWidth + options->paddingRight);
instance.state.logoHeight = options->paddingTop + parsedHeight; instance.state.logoHeight = options->paddingTop + parsedHeight;
if (options->position == FF_LOGO_POSITION_LEFT) { if (options->position == FF_LOGO_POSITION_LEFT) {
instance.state.logoWidth = maxLineWidth + options->paddingRight; 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; FFOptionsLogo* options = &instance.config.logo;
FF_STRBUF_AUTO_DESTROY buf = ffStrbufCreate(); FF_STRBUF_AUTO_DESTROY buf = ffStrbufCreate();
ffLogoResolveAutoPosition(options->width + options->paddingLeft + options->paddingRight);
if (!options->width || !options->height) { if (!options->width || !options->height) {
if (options->position == FF_LOGO_POSITION_LEFT) { if (options->position == FF_LOGO_POSITION_LEFT) {
ffStrbufAppendF(&buf, "\e[2J\e[3J\e[%u;%uH", (unsigned) options->paddingTop + 1, (unsigned) options->paddingLeft + 1); ffStrbufAppendF(&buf, "\e[2J\e[3J\e[%u;%uH", (unsigned) options->paddingTop + 1, (unsigned) options->paddingLeft + 1);
+4
View File
@@ -24,6 +24,10 @@ typedef struct FFlogo {
} FFlogo; } FFlogo;
// logo.c // 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 ffLogoPrint(void);
void ffLogoPrintChars(const char* data, bool doColorReplacement); void ffLogoPrintChars(const char* data, bool doColorReplacement);
void ffLogoPrintLine(void); void ffLogoPrintLine(void);
+2 -2
View File
@@ -7,7 +7,7 @@
bool ffPrintTerminalSize(FFTerminalSizeOptions* options) { bool ffPrintTerminalSize(FFTerminalSizeOptions* options) {
FFTerminalSizeResult result = {}; 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"); ffPrintError(FF_MODULE_GET_DISPLAY_NAME(TerminalSize), 0, &options->moduleArgs, FF_PRINT_TYPE_DEFAULT, "Failed to detect terminal size");
return false; 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) { bool ffGenerateTerminalSizeJsonResult([[maybe_unused]] FFTerminalSizeOptions* options, yyjson_mut_doc* doc, yyjson_mut_val* module) {
FFTerminalSizeResult result; FFTerminalSizeResult result;
if (!ffDetectTerminalSize(&result)) { if (!ffDetectTerminalSize(&result, false)) {
yyjson_mut_obj_add_str(doc, module, "error", "Failed to detect terminal size"); yyjson_mut_obj_add_str(doc, module, "error", "Failed to detect terminal size");
return false; return false;
} }
+17 -5
View File
@@ -39,6 +39,20 @@ static void logoParseImageFlag(FFOptionsLogo* options, const char* key, const ch
#endif #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) { void ffOptionsInitLogo(FFOptionsLogo* options) {
ffStrbufInit(&options->source); ffStrbufInit(&options->source);
options->type = FF_LOGO_TYPE_AUTO; options->type = FF_LOGO_TYPE_AUTO;
@@ -162,6 +176,7 @@ bool ffOptionsParseLogoCommandLine(FFOptionsLogo* options, const char* key, cons
exit(477); exit(477);
} else if (ffStrEqualsIgnCase(subKey, "position")) { } else if (ffStrEqualsIgnCase(subKey, "position")) {
options->position = (FFLogoPosition) ffOptionParseEnum(key, value, (FFKeyValuePair[]) { options->position = (FFLogoPosition) ffOptionParseEnum(key, value, (FFKeyValuePair[]) {
{ "auto", FF_LOGO_POSITION_AUTO },
{ "left", FF_LOGO_POSITION_LEFT }, { "left", FF_LOGO_POSITION_LEFT },
{ "right", FF_LOGO_POSITION_RIGHT }, { "right", FF_LOGO_POSITION_RIGHT },
{ "top", FF_LOGO_POSITION_TOP }, { "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")) { } else if (unsafe_yyjson_equals_str(key, "position")) {
int value; int value;
const char* error = ffJsonConfigParseEnum(val, &value, (FFKeyValuePair[]) { const char* error = ffJsonConfigParseEnum(val, &value, (FFKeyValuePair[]) {
{ "auto", FF_LOGO_POSITION_AUTO },
{ "left", FF_LOGO_POSITION_LEFT }, { "left", FF_LOGO_POSITION_LEFT },
{ "top", FF_LOGO_POSITION_TOP }, { "top", FF_LOGO_POSITION_TOP },
{ "right", FF_LOGO_POSITION_RIGHT }, { "right", FF_LOGO_POSITION_RIGHT },
@@ -604,11 +620,7 @@ void ffOptionsGenerateLogoJsonConfig(FFdata* data, FFOptionsLogo* options) {
break; break;
} }
yyjson_mut_obj_add_str(doc, obj, "position", ((const char*[]) { yyjson_mut_obj_add_str(doc, obj, "position", ffLogoPositionToString(options->position));
"left",
"top",
"right",
})[options->position]);
yyjson_mut_obj_add_int(doc, obj, "animationFrame", options->animationFrame); yyjson_mut_obj_add_int(doc, obj, "animationFrame", options->animationFrame);
+2
View File
@@ -28,6 +28,7 @@ typedef enum FFLogoPosition: uint8_t {
FF_LOGO_POSITION_LEFT, FF_LOGO_POSITION_LEFT,
FF_LOGO_POSITION_TOP, FF_LOGO_POSITION_TOP,
FF_LOGO_POSITION_RIGHT, FF_LOGO_POSITION_RIGHT,
FF_LOGO_POSITION_AUTO,
} FFLogoPosition; } FFLogoPosition;
typedef enum FFLogoCacheStrategy: uint8_t { typedef enum FFLogoCacheStrategy: uint8_t {
@@ -70,6 +71,7 @@ typedef struct FFOptionsLogo {
#endif #endif
} FFOptionsLogo; } FFOptionsLogo;
const char* ffLogoPositionToString(FFLogoPosition position);
void ffOptionsInitLogo(FFOptionsLogo* options); void ffOptionsInitLogo(FFOptionsLogo* options);
bool ffOptionsParseLogoCommandLine(FFOptionsLogo* options, const char* key, const char* value); bool ffOptionsParseLogoCommandLine(FFOptionsLogo* options, const char* key, const char* value);
void ffOptionsDestroyLogo(FFOptionsLogo* options); void ffOptionsDestroyLogo(FFOptionsLogo* options);
+60
View File
@@ -0,0 +1,60 @@
#include "logo/logo.h"
#include "common/textModifier.h"
#include <stdio.h>
#include <stdlib.h>
#include <stdnoreturn.h>
#include <string.h>
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;
}