ohmu
·
2026-10-15
1#include "logo/logo.h"
2#include "common/io.h"
3#include "common/printing.h"
4#include "common/processing.h"
5#include "common/textModifier.h"
6#include "common/strutil.h"
7#include "detection/media/media.h"
8#include "detection/os/os.h"
9#include "detection/terminalshell/terminalshell.h"
10#include "detection/terminalsize/terminalsize.h"
11
12#include <ctype.h>
13#include <stdlib.h>
14#include <string.h>
15
16typedef struct FFLogoCachedLine {
17 FFstrbuf chars;
18 uint32_t width;
19} FFLogoCachedLine;
20
21FFLogoPosition ffLogoSelectPosition(FFLogoPosition configured, uint32_t terminalColumns, uint32_t logoWidth) {
22 if (configured != FF_LOGO_POSITION_AUTO) {
23 return configured;
24 }
25
26 if (terminalColumns == 0) {
27 return FF_LOGO_POSITION_LEFT;
28 }
29
30 return logoWidth <= terminalColumns && terminalColumns - logoWidth >= FF_LOGO_AUTO_MIN_INFO_WIDTH
31 ? FF_LOGO_POSITION_LEFT
32 : FF_LOGO_POSITION_TOP;
33}
34
35void ffLogoResolveAutoPosition(uint32_t logoWidth) {
36 FFOptionsLogo* options = &instance.config.logo;
37 if (options->position != FF_LOGO_POSITION_AUTO) {
38 return;
39 }
40
41 uint32_t terminalColumns = 0;
42 if (ffIsTerminal(STDOUT_FILENO)) {
43 FFTerminalSizeResult size = {};
44 if (ffDetectTerminalSize(&size, true)) {
45 terminalColumns = size.columns;
46 }
47 }
48
49 options->position = ffLogoSelectPosition(FF_LOGO_POSITION_AUTO, terminalColumns, logoWidth);
50}
51
52static void logoLineCacheClear(FFLogoLineCacheState* cache) {
53 FF_LIST_FOR_EACH (FFLogoCachedLine, line, cache->lines) {
54 ffStrbufDestroy(&line->chars);
55 }
56 ffListDestroy(&cache->lines);
57 cache->nextLine = 0;
58 cache->rightOffset = 0;
59}
60
61static void logoLineCachePush(const FFstrbuf* chars, uint32_t width, FFLogoLineCacheState* cache) {
62 FFLogoCachedLine* line = FF_LIST_ADD(FFLogoCachedLine, cache->lines);
63 if (width > 0) {
64 ffStrbufInitCopy(&line->chars, chars);
65 if (!instance.config.display.pipe) {
66 ffStrbufAppendS(&line->chars, FASTFETCH_TEXT_MODIFIER_RESET);
67 }
68 } else {
69 ffStrbufInit(&line->chars);
70 }
71 line->width = width;
72}
73
74static void logoLineCacheBuild(FFLogoLineCacheState* cache, const char* data, bool doColorReplacement) {
75 FFOptionsLogo* options = &instance.config.logo;
76 bool keepCarryColor = options->type != FF_LOGO_TYPE_IMAGE_CHAFA;
77
78 logoLineCacheClear(cache);
79
80 // Overrides the auto detected max-width with the configured width, if set.
81 // In case we fail to get the actual width of the logo, as we don't use `wcwidth`
82 uint32_t maxLineWidth = options->width;
83 uint32_t parsedHeight = 0;
84
85 FF_STRBUF_AUTO_DESTROY carryColor = ffStrbufCreate();
86 if (keepCarryColor && doColorReplacement && !instance.config.display.pipe) {
87 ffStrbufSetF(&carryColor, "\e[%sm", options->colors[0].chars);
88 }
89
90 for (uint32_t i = 0; i < options->paddingTop; ++i) {
91 logoLineCachePush(nullptr, 0, cache);
92 }
93
94 if (*data != '\0') {
95 while (true) {
96 FF_STRBUF_AUTO_DESTROY line = ffStrbufCreateA(256);
97 uint32_t lineWidth = 0;
98
99 if (!instance.config.display.pipe && instance.config.display.brightColor) {
100 ffStrbufAppendS(&line, FASTFETCH_TEXT_MODIFIER_BOLT);
101 }
102
103 if (keepCarryColor && carryColor.length > 0) {
104 ffStrbufAppend(&line, &carryColor);
105 }
106
107 if ((options->position != FF_LOGO_POSITION_RIGHT) && options->paddingLeft > 0) {
108 ffStrbufAppendNC(&line, options->paddingLeft, ' ');
109 lineWidth += options->paddingLeft;
110 }
111
112 while (*data != '\0' && *data != '\n' && !(*data == '\r' && *(data + 1) == '\n')) {
113 if (*data == '\t') {
114 ffStrbufAppendNC(&line, 4, ' ');
115 lineWidth += 4;
116 ++data;
117 continue;
118 }
119
120 if (*data == '\e' && *(data + 1) == '[') {
121 const char* start = data;
122 data += 2;
123
124 while (ffCharIsDigit(*data) || *data == ';') {
125 ++data;
126 }
127
128 if (isascii(*data)) {
129 ++data;
130
131 uint32_t escLen = (uint32_t) (data - start);
132 ffStrbufAppendNS(&line, escLen, start);
133
134 if (keepCarryColor && start[escLen - 1] == 'm') {
135 ffStrbufSetNS(&carryColor, escLen, start);
136 }
137 continue;
138 }
139
140 lineWidth += (uint32_t) (data - start - 1);
141 }
142
143 if (doColorReplacement && *data == '$') {
144 ++data;
145
146 if (*data == '$' || *data == '\0') {
147 ffStrbufAppendC(&line, '$');
148 ++lineWidth;
149 ++data;
150 continue;
151 }
152
153 if (!instance.config.display.pipe) {
154 int index = *data - '1';
155 if (index >= 0 && index < FASTFETCH_LOGO_MAX_COLORS) {
156 if (keepCarryColor) {
157 ffStrbufSetF(&carryColor, "\e[%sm", options->colors[index].chars);
158 ffStrbufAppend(&line, &carryColor);
159 } else {
160 ffStrbufAppendF(&line, "\e[%sm", options->colors[index].chars);
161 }
162 ++data;
163 continue;
164 }
165
166 ffStrbufAppendC(&line, '$');
167 ++lineWidth;
168 } else {
169 ++data;
170 continue;
171 }
172 }
173
174 uint8_t charWidth;
175 uint8_t bytes = ffUtf8CharLenWidth(data, UINT32_MAX, &charWidth);
176 lineWidth += charWidth;
177
178 for (uint8_t i = 0; i < bytes; ++i) {
179 if (*data == '\0') {
180 break;
181 }
182
183 ffStrbufAppendC(&line, *data);
184 ++data;
185 }
186 }
187
188 logoLineCachePush(&line, lineWidth, cache);
189 if (lineWidth > maxLineWidth) {
190 maxLineWidth = lineWidth;
191 }
192
193 if (*data == '\n' || (*data == '\r' && *(data + 1) == '\n')) {
194 if (*data == '\r') {
195 ++data;
196 }
197 ++data;
198 ++parsedHeight;
199 continue;
200 }
201
202 break;
203 }
204 }
205
206 if (options->type != FF_LOGO_TYPE_IMAGE_CHAFA && options->height > parsedHeight) {
207 parsedHeight = options->height;
208 }
209
210 ffLogoResolveAutoPosition(maxLineWidth + options->paddingRight);
211
212 instance.state.logoHeight = options->paddingTop + parsedHeight;
213 if (options->position == FF_LOGO_POSITION_LEFT) {
214 instance.state.logoWidth = maxLineWidth + options->paddingRight;
215 } else {
216 instance.state.logoWidth = 0;
217 }
218
219 uint32_t totalLines = instance.state.logoHeight + 1;
220 while (cache->lines.length < totalLines) {
221 logoLineCachePush(nullptr, 0, cache);
222 }
223
224 cache->nextLine = 0;
225 cache->rightOffset = maxLineWidth + options->paddingRight - 1;
226}
227
228static bool ffLogoPrintCharsRaw(const char* data, size_t length, bool printError) {
229 FFOptionsLogo* options = &instance.config.logo;
230 FF_STRBUF_AUTO_DESTROY buf = ffStrbufCreate();
231
232 ffLogoResolveAutoPosition(options->width + options->paddingLeft + options->paddingRight);
233
234 if (!options->width || !options->height) {
235 if (options->position == FF_LOGO_POSITION_LEFT) {
236 ffStrbufAppendF(&buf, "\e[2J\e[3J\e[%u;%uH", (unsigned) options->paddingTop + 1, (unsigned) options->paddingLeft + 1);
237 } else if (options->position == FF_LOGO_POSITION_TOP) {
238 ffStrbufAppendNC(&buf, options->paddingTop, '\n');
239 ffStrbufAppendNC(&buf, options->paddingLeft, ' ');
240 } else if (options->position == FF_LOGO_POSITION_RIGHT) {
241 if (!options->width) {
242 if (printError) {
243 fputs("Logo (image-raw): Must set logo width when using position right\n", stderr);
244 }
245 return false;
246 }
247 ffStrbufAppendF(&buf, "\e[2J\e[3J\e[%u;9999999H\e[%uD", (unsigned) options->paddingTop + 1, (unsigned) options->paddingRight + options->width);
248 }
249 ffStrbufAppendNS(&buf, (uint32_t) length, data);
250 ffWriteFDBuffer(FFUnixFD2NativeFD(STDOUT_FILENO), &buf);
251
252 if (options->position == FF_LOGO_POSITION_LEFT || options->position == FF_LOGO_POSITION_RIGHT) {
253 uint16_t X = 0, Y = 0;
254 // Windows Terminal doesn't report `\e` for some reason
255 const char* error = ffGetTerminalResponse("\e[6n", 2, "%*[^0-9]%hu;%huR", &Y, &X); // %*[^0-9]: ignore optional \e[
256 if (error) {
257 if (printError) {
258 fprintf(stderr, "\nLogo (image-raw): fail to query cursor position: %s\n", error);
259 }
260 return true;
261 }
262 if (options->position == FF_LOGO_POSITION_LEFT) {
263 if (options->width + options->paddingLeft > X) {
264 X = (uint16_t) (options->width + options->paddingLeft);
265 }
266 instance.state.logoWidth = X + instance.config.logo.paddingRight - 1;
267 }
268 instance.state.logoHeight = Y;
269 fputs("\e[H", stdout);
270 } else if (options->position == FF_LOGO_POSITION_TOP) {
271 instance.state.logoWidth = instance.state.logoHeight = 0;
272 ffPrintCharTimes('\n', options->paddingRight);
273 }
274 } else {
275 ffStrbufAppendNC(&buf, options->paddingTop, '\n');
276
277 if (options->position == FF_LOGO_POSITION_RIGHT) {
278 ffStrbufAppendF(&buf, "\e[9999999C\e[%uD", (unsigned) options->paddingRight + options->width);
279 } else if (options->paddingLeft) {
280 ffStrbufAppendF(&buf, "\e[%uC", (unsigned) options->paddingLeft);
281 }
282
283 ffStrbufAppendNS(&buf, (uint32_t) length, data);
284 ffStrbufAppendC(&buf, '\n');
285
286 if (options->position == FF_LOGO_POSITION_LEFT) {
287 instance.state.logoWidth = options->width + options->paddingLeft + options->paddingRight;
288 instance.state.logoHeight = options->paddingTop + options->height;
289 ffStrbufAppendF(&buf, "\e[%uA", (unsigned) instance.state.logoHeight);
290 } else if (options->position == FF_LOGO_POSITION_TOP) {
291 instance.state.logoWidth = instance.state.logoHeight = 0;
292 ffStrbufAppendNC(&buf, options->paddingRight, '\n');
293 } else if (options->position == FF_LOGO_POSITION_RIGHT) {
294 instance.state.logoWidth = instance.state.logoHeight = 0;
295 ffStrbufAppendF(&buf, "\e[%uA", (unsigned) options->height);
296 }
297 ffWriteFDBuffer(FFUnixFD2NativeFD(STDOUT_FILENO), &buf);
298 }
299
300 return true;
301}
302
303void ffLogoPrintChars(const char* data, bool doColorReplacement) {
304 FFOptionsLogo* options = &instance.config.logo;
305 FFLogoLineCacheState* cache = &instance.state.logoLineCache;
306
307 logoLineCacheBuild(cache, data, doColorReplacement);
308
309 if (options->position != FF_LOGO_POSITION_TOP) {
310 return;
311 }
312
313 FF_STRBUF_AUTO_DESTROY result = ffStrbufCreateA(4096);
314 FF_LIST_FOR_EACH (FFLogoCachedLine, line, cache->lines) {
315 ffStrbufAppend(&result, &line->chars);
316 ffStrbufAppendC(&result, '\n');
317 }
318 ffStrbufAppendNC(&result, options->paddingBottom, '\n');
319 ffWriteFDBuffer(FFUnixFD2NativeFD(STDOUT_FILENO), &result);
320 instance.state.logoWidth = instance.state.logoHeight = 0;
321 logoLineCacheClear(cache);
322}
323
324static void logoApplyColors(const FFlogo* logo, bool replacement) {
325 if (instance.config.display.colorTitle.length == 0) {
326 ffStrbufSetStatic(&instance.config.display.colorTitle, logo->colorTitle ?: logo->colors[0]);
327 }
328
329 if (instance.config.display.colorKeys.length == 0) {
330 ffStrbufSetStatic(&instance.config.display.colorKeys, logo->colorKeys ?: logo->colors[1]);
331 }
332
333 if (replacement) {
334 FFOptionsLogo* options = &instance.config.logo;
335
336 const char* const* colors = logo->colors;
337 for (int i = 0; *colors != nullptr && i < FASTFETCH_LOGO_MAX_COLORS; i++, colors++) {
338 if (options->colors[i].length == 0) {
339 ffStrbufSetStatic(&options->colors[i], *colors);
340 }
341 }
342 }
343}
344
345static bool logoHasName(const FFlogo* logo, const FFstrbuf* name, bool small) {
346 for (
347 const char* const* logoName = logo->names;
348 *logoName != nullptr && logoName <= &logo->names[FASTFETCH_LOGO_MAX_NAMES];
349 ++logoName) {
350 if (small) {
351 uint32_t logoNameLength = (uint32_t) (strlen(*logoName) - strlen("_small"));
352 if (name->length == logoNameLength && strncasecmp(*logoName, name->chars, logoNameLength) == 0) {
353 return true;
354 }
355 }
356 if (ffStrbufIgnCaseEqualS(name, *logoName)) {
357 return true;
358 }
359 }
360
361 return false;
362}
363
364static const FFlogo* logoGetBuiltin(const FFstrbuf* name, FFLogoSize size) {
365 if (name->length == 0 || !isalpha(name->chars[0])) {
366 return nullptr;
367 }
368
369 for (const FFlogo* logo = ffLogoBuiltins[toupper(name->chars[0]) - 'A']; *logo->names; ++logo) {
370 switch (size) {
371 // Never use alternate logos
372 case FF_LOGO_SIZE_NORMAL:
373 if (logo->type != FF_LOGO_LINE_TYPE_NORMAL) {
374 continue;
375 }
376 break;
377 case FF_LOGO_SIZE_SMALL:
378 if (logo->type != FF_LOGO_LINE_TYPE_SMALL_BIT) {
379 continue;
380 }
381 break;
382 default:
383 break;
384 }
385
386 if (logoHasName(logo, name, size == FF_LOGO_SIZE_SMALL)) {
387 return logo;
388 }
389 }
390
391 return nullptr;
392}
393
394static const FFlogo* logoGetBuiltinDetected(FFLogoSize size) {
395 const FFOSResult* os = ffDetectOS();
396
397 const FFlogo* logo = logoGetBuiltin(&os->id, size);
398 if (logo != nullptr) {
399 return logo;
400 }
401
402 logo = logoGetBuiltin(&os->name, size);
403 if (logo != nullptr) {
404 return logo;
405 }
406
407 if (ffStrbufContainC(&os->idLike, ' ')) {
408 FF_STRBUF_AUTO_DESTROY buf = ffStrbufCreate();
409 for (
410 uint32_t start = 0, end = ffStrbufFirstIndexC(&os->idLike, ' ');
411 true;
412 start = end + 1, end = ffStrbufNextIndexC(&os->idLike, start, ' ')) {
413 ffStrbufSetNS(&buf, end - start, os->idLike.chars + start);
414 logo = logoGetBuiltin(&buf, size);
415 if (logo != nullptr) {
416 return logo;
417 }
418
419 if (end >= os->idLike.length) {
420 break;
421 }
422 }
423 } else {
424 logo = logoGetBuiltin(&os->idLike, size);
425 if (logo != nullptr) {
426 return logo;
427 }
428 }
429
430 logo = logoGetBuiltin(&instance.state.platform.sysinfo.name, size);
431 if (logo != nullptr) {
432 return logo;
433 }
434
435 return &ffLogoUnknown;
436}
437
438static void logoPrintStruct(const FFlogo* logo) {
439 logoApplyColors(logo, true);
440
441 ffLogoPrintChars(logo->lines, true);
442}
443
444static void logoPrintNone(void) {
445 if (!instance.config.display.pipe) {
446 logoApplyColors(logoGetBuiltinDetected(FF_LOGO_SIZE_NORMAL), false);
447 }
448 instance.state.logoHeight = 0;
449 instance.state.logoWidth = 0;
450}
451
452static bool logoPrintBuiltinIfExists(const FFstrbuf* name, FFLogoSize size) {
453 if (name->chars[0] == '~' || name->chars[0] == '.' || name->chars[0] == '/'
454#if _WIN32
455 || (ffCharIsEnglishAlphabet(name->chars[0]) && name->chars[1] == ':') // Windows drive letter
456#endif
457 )
458 return false; // Paths
459
460 if (ffStrbufIgnCaseEqualS(name, "none")) {
461 logoPrintNone();
462 return true;
463 }
464
465 const FFlogo* logo = ffLogoGetBuiltinForName(name, size);
466 if (logo == nullptr) {
467 return false;
468 }
469
470 logoPrintStruct(logo);
471
472 return true;
473}
474
475void ffLogoPrintDetected(FFLogoSize size) {
476 logoPrintStruct(logoGetBuiltinDetected(size));
477}
478
479static bool logoPrintData(bool doColorReplacement, FFstrbuf* source) {
480 if (source->length == 0) {
481 return false;
482 }
483
484 logoApplyColors(logoGetBuiltinDetected(FF_LOGO_SIZE_NORMAL), doColorReplacement);
485 ffLogoPrintChars(source->chars, doColorReplacement);
486 return true;
487}
488
489static bool updateLogoPath(void) {
490 FFOptionsLogo* options = &instance.config.logo;
491
492 if (ffPathExists(options->source.chars, FF_PATHTYPE_FILE)) {
493 return true;
494 }
495
496 if (ffStrbufEqualS(&options->source, "-")) { // stdin
497 return true;
498 }
499
500#if !FF_MODULE_DISABLE_MEDIA
501 if (ffStrbufIgnCaseEqualS(&options->source, "media-cover")) {
502 const FFMediaResult* media = ffDetectMedia(true);
503 if (media->cover.length == 0) {
504 return false;
505 }
506 ffStrbufSet(&options->source, &media->cover);
507 return true;
508 }
509#endif
510
511 FF_STRBUF_AUTO_DESTROY fullPath = ffStrbufCreateA(128);
512 if (ffPathExpandEnv(options->source.chars, &fullPath) && ffPathExists(fullPath.chars, FF_PATHTYPE_FILE)) {
513 ffStrbufDestroy(&options->source);
514 ffStrbufInitMove(&options->source, &fullPath);
515 return true;
516 }
517
518 FF_LIST_FOR_EACH (FFstrbuf, dataDir, instance.state.platform.dataDirs) {
519 // We need to copy it, because multiple threads might be using dataDirs at the same time
520 ffStrbufSet(&fullPath, dataDir);
521 ffStrbufAppendS(&fullPath, "fastfetch/logos/");
522 ffStrbufAppend(&fullPath, &options->source);
523
524 if (ffPathExists(fullPath.chars, FF_PATHTYPE_FILE)) {
525 ffStrbufDestroy(&options->source);
526 ffStrbufInitMove(&options->source, &fullPath);
527 return true;
528 }
529 }
530
531 return false;
532}
533
534static bool logoPrintFileIfExists(bool doColorReplacement, bool raw) {
535 FFOptionsLogo* options = &instance.config.logo;
536
537 FF_STRBUF_AUTO_DESTROY content = ffStrbufCreate();
538
539 if (ffStrbufEqualS(&options->source, "-")
540 ? !ffAppendFDBuffer(FFUnixFD2NativeFD(STDIN_FILENO), &content)
541 : !ffAppendFileBuffer(options->source.chars, &content)) {
542 if (instance.config.display.showErrors) {
543 fprintf(stderr, "Logo: Failed to load file content from logo source: %s\n", options->source.chars);
544 }
545 return false;
546 }
547
548 logoApplyColors(logoGetBuiltinDetected(FF_LOGO_SIZE_NORMAL), doColorReplacement);
549 if (raw) {
550 return ffLogoPrintCharsRaw(content.chars, content.length, instance.config.display.showErrors);
551 }
552
553 ffLogoPrintChars(content.chars, doColorReplacement);
554 return true;
555}
556
557#if FF_HAVE_IMAGE_LOGO
558static bool logoPrintImageIfExists(FFLogoType logo, bool printError) {
559 if (!ffLogoPrintImageIfExists(logo, printError)) {
560 return false;
561 }
562
563 logoApplyColors(logoGetBuiltinDetected(FF_LOGO_SIZE_NORMAL), false);
564 return true;
565}
566#endif
567
568static bool logoTryKnownType(void) {
569 FFOptionsLogo* options = &instance.config.logo;
570
571 if (options->type == FF_LOGO_TYPE_NONE) {
572 logoPrintNone();
573 return true;
574 }
575
576 if (options->type == FF_LOGO_TYPE_BUILTIN) {
577 return logoPrintBuiltinIfExists(&options->source, FF_LOGO_SIZE_UNKNOWN);
578 }
579
580 if (options->type == FF_LOGO_TYPE_SMALL) {
581 return logoPrintBuiltinIfExists(&options->source, FF_LOGO_SIZE_SMALL);
582 }
583
584 if (options->type == FF_LOGO_TYPE_DATA) {
585 return logoPrintData(true, &options->source);
586 }
587
588 if (options->type == FF_LOGO_TYPE_DATA_RAW) {
589 return logoPrintData(false, &options->source);
590 }
591
592 if (options->type == FF_LOGO_TYPE_COMMAND_RAW) {
593 FF_STRBUF_AUTO_DESTROY source = ffStrbufCreate();
594
595 const char* error = ffProcessAppendStdOut(&source, (char* const[]){
596#ifdef _WIN32
597 "cmd.exe", "/c",
598#else
599 "/bin/sh", "-c",
600#endif
601 options->source.chars,
602 nullptr });
603
604 if (error) {
605 if (instance.config.display.showErrors) {
606 fprintf(stderr, "Logo: failed to execute command `%s`: %s\n", options->source.chars, error);
607 }
608 return false;
609 }
610
611 return logoPrintData(false, &source);
612 }
613
614 // We sure have a file, resolve relative paths
615 if (!updateLogoPath()) {
616 if (instance.config.display.showErrors) {
617 fprintf(stderr, "Logo: Failed to resolve logo source: %s\n", options->source.chars);
618 }
619 return false;
620 }
621
622 if (options->type == FF_LOGO_TYPE_FILE) {
623 return logoPrintFileIfExists(true, false);
624 }
625
626 if (options->type == FF_LOGO_TYPE_FILE_RAW) {
627 return logoPrintFileIfExists(false, false);
628 }
629
630 if (options->type == FF_LOGO_TYPE_IMAGE_RAW) {
631 return logoPrintFileIfExists(false, true);
632 }
633
634#if FF_HAVE_IMAGE_LOGO
635 return logoPrintImageIfExists(options->type, instance.config.display.showErrors);
636#else
637 // No image logo type can be selected in this build (see ffOptionsParseLogoCommandLine), and
638 // FF_LOGO_TYPE_IMAGE_RAW was handled above, so nothing printable is left for this source.
639 if (instance.config.display.showErrors) {
640 fprintf(stderr, "Logo: Unsupported logo type for source: %s\n", options->source.chars);
641 }
642 return false;
643#endif
644}
645
646void ffLogoPrint(void) {
647 const FFOptionsLogo* options = &instance.config.logo;
648
649 if (options->type == FF_LOGO_TYPE_NONE) {
650 logoPrintNone();
651 return;
652 }
653
654 // If the source is not set, we can directly print the detected logo.
655 if (options->source.length == 0) {
656 ffLogoPrintDetected(options->type == FF_LOGO_TYPE_SMALL ? FF_LOGO_SIZE_SMALL : FF_LOGO_SIZE_NORMAL);
657 return;
658 }
659
660 // If the source and source type is set to something else than auto, always print with the set type.
661 if (options->source.length > 0 && options->type != FF_LOGO_TYPE_AUTO) {
662 if (!logoTryKnownType()) {
663 if (instance.config.display.showErrors) {
664 // Image logo should have been handled
665 if (options->type == FF_LOGO_TYPE_BUILTIN || options->type == FF_LOGO_TYPE_SMALL) {
666 fprintf(stderr, "Logo: Failed to load %s logo: %s\n", options->type == FF_LOGO_TYPE_BUILTIN ? "builtin" : "builtin small", options->source.chars);
667 }
668 }
669
670 ffLogoPrintDetected(FF_LOGO_SIZE_UNKNOWN);
671 }
672 return;
673 }
674
675 // If source matches the name of a builtin logo, print it and return.
676 if (logoPrintBuiltinIfExists(&options->source, FF_LOGO_SIZE_UNKNOWN)) {
677 return;
678 }
679
680 // Make sure the logo path is set correctly.
681 if (updateLogoPath()) {
682 if (ffStrbufEndsWithIgnCaseS(&options->source, ".raw")) {
683 if (logoPrintFileIfExists(false, true)) {
684 return;
685 }
686 }
687
688#if FF_HAVE_IMAGE_LOGO
689 // Try to load the logo as an image.
690 if (!ffStrbufEndsWithIgnCaseS(&options->source, ".txt")) {
691 #if !FF_MODULE_DISABLE_TERMINAL
692 const FFTerminalResult* terminal = ffDetectTerminal();
693
694 bool supportsIterm2 = ffStrbufEqualS(&terminal->prettyName, "iTerm");
695
696 if (supportsIterm2 && logoPrintImageIfExists(FF_LOGO_TYPE_IMAGE_ITERM, false)) {
697 return;
698 }
699
700 // Terminal emulators that support kitty graphics protocol.
701 bool supportsKitty =
702 ffStrbufIgnCaseEqualS(&terminal->processName, "kitty") ||
703 ffStrbufIgnCaseEqualS(&terminal->processName, "konsole") ||
704 ffStrbufIgnCaseEqualS(&terminal->processName, "wezterm") ||
705 ffStrbufIgnCaseEqualS(&terminal->processName, "wayst") ||
706 ffStrbufIgnCaseEqualS(&terminal->processName, "ghostty") ||
707 #ifdef __APPLE__
708 ffStrbufIgnCaseEqualS(&terminal->processName, "WarpTerminal") ||
709 #else
710 ffStrbufIgnCaseEqualS(&terminal->processName, "warp") ||
711 #endif
712 false;
713 #else
714 bool supportsKitty = false;
715 #endif
716
717 // Try to load the logo as an image. If it succeeds, print it and return.
718 if (logoPrintImageIfExists(supportsKitty ? FF_LOGO_TYPE_IMAGE_KITTY : FF_LOGO_TYPE_IMAGE_CHAFA, false)) {
719 return;
720 }
721 }
722#endif
723
724 // Try to load the logo as a file. If it succeeds, print it and return.
725 if (logoPrintFileIfExists(true, false)) {
726 return;
727 }
728 } else {
729 if (instance.config.display.showErrors) {
730 fprintf(stderr, "Logo: Failed to resolve logo source: %s\n", options->source.chars);
731 }
732 }
733
734 ffLogoPrintDetected(FF_LOGO_SIZE_UNKNOWN);
735}
736
737void ffLogoPrintLine(void) {
738 FFLogoLineCacheState* cache = &instance.state.logoLineCache;
739 FFOptionsLogo* logo = &instance.config.logo;
740
741 if (cache->lines.length > 0) {
742 // Line cache is enabled. Always move cursor with whitespaces to make lolcat happy
743 if (cache->nextLine < cache->lines.length) {
744 // Print logo line and move cursor
745 FFLogoCachedLine* line = FF_LIST_GET(FFLogoCachedLine, cache->lines, cache->nextLine);
746
747 if (logo->position == FF_LOGO_POSITION_RIGHT) {
748 printf("\033[9999999C\033[%uD", cache->rightOffset);
749 ffStrbufWriteTo(&line->chars, stdout);
750
751 fputs("\033[G", stdout);
752 } else {
753 ffStrbufWriteTo(&line->chars, stdout);
754
755 uint32_t remaining = instance.state.logoWidth;
756 remaining = line->width < remaining ? remaining - line->width : 0;
757 ffPrintCharTimes(' ', remaining);
758 }
759 ++cache->nextLine;
760 } else if (logo->position == FF_LOGO_POSITION_LEFT) {
761 // Move cursor to the start position
762 ffPrintCharTimes(' ', instance.state.logoWidth);
763 }
764 } else if (instance.state.logoWidth > 0) {
765 printf("\033[%uC", instance.state.logoWidth);
766 }
767
768 if (instance.state.dynamicInterval > 0 && logo->position == FF_LOGO_POSITION_LEFT) {
769 fputs("\033[K", stdout); // Clear to the end of the line
770 // Note that we don't clear the line when the logo is on the right, as it will also clear the logo itself
771 }
772
773 ++instance.state.keysHeight;
774}
775
776void ffLogoPrintRemaining(void) {
777 FFLogoLineCacheState* cache = &instance.state.logoLineCache;
778 FFOptionsLogo* logo = &instance.config.logo;
779
780 if (cache->lines.length > 0 && (logo->position == FF_LOGO_POSITION_LEFT || logo->position == FF_LOGO_POSITION_RIGHT)) {
781 while (cache->nextLine < cache->lines.length) {
782 FFLogoCachedLine* line = FF_LIST_GET(FFLogoCachedLine, cache->lines, cache->nextLine);
783
784 if (logo->position == FF_LOGO_POSITION_RIGHT) {
785 printf("\033[9999999C\033[%uD", cache->rightOffset);
786 }
787 ffStrbufPutTo(&line->chars, stdout);
788
789 ++cache->nextLine;
790 }
791
792 if (!instance.config.display.pipe) {
793 fputs(FASTFETCH_TEXT_MODIFIER_RESET, stdout);
794 }
795
796 instance.state.keysHeight = instance.state.logoHeight + 1;
797 logoLineCacheClear(cache);
798 return;
799 }
800
801 if (instance.state.keysHeight <= instance.state.logoHeight) {
802 ffPrintCharTimes('\n', instance.state.logoHeight - instance.state.keysHeight + 1);
803 }
804 instance.state.keysHeight = instance.state.logoHeight + 1;
805}
806
807void ffLogoBuiltinPrint(void) {
808 FFOptionsLogo* options = &instance.config.logo;
809 options->position = FF_LOGO_POSITION_TOP;
810 options->paddingRight = 2; // empty line after logo printing
811 FF_STRBUF_AUTO_DESTROY buf = ffStrbufCreate();
812
813 for (uint8_t ch = 0; ch < 26; ++ch) {
814 for (const FFlogo* logo = ffLogoBuiltins[ch]; *logo->names; ++logo) {
815 if (instance.config.display.pipe) {
816 ffStrbufSetF(&buf, "%s:\n", logo->names[0]);
817 } else {
818 ffStrbufSetF(&buf, "\e[%sm%s:\e[0m\n", logo->colors[0], logo->names[0]);
819 }
820 ffWriteFDBuffer(FFUnixFD2NativeFD(STDOUT_FILENO), &buf);
821 logoPrintStruct(logo);
822
823 for (uint8_t i = 0; i < FASTFETCH_LOGO_MAX_COLORS; i++) {
824 ffStrbufClear(&options->colors[i]);
825 }
826 }
827 }
828}
829
830void ffLogoBuiltinList(void) {
831 uint32_t counter = 0;
832 for (uint8_t ch = 0; ch < 26; ++ch) {
833 for (const FFlogo* logo = ffLogoBuiltins[ch]; *logo->names; ++logo) {
834 ++counter;
835 printf("%u)%s ", counter, counter < 10 ? " " : "");
836
837 for (
838 const char* const* names = logo->names;
839 *names != nullptr && names <= &logo->names[FASTFETCH_LOGO_MAX_NAMES];
840 ++names) {
841 printf("\"%s\" ", *names);
842 }
843
844 putchar('\n');
845 }
846 }
847}
848
849void ffLogoBuiltinListAutocompletion(void) {
850 for (uint8_t ch = 0; ch < 26; ++ch) {
851 for (const FFlogo* logo = ffLogoBuiltins[ch]; *logo->names; ++logo) {
852 printf("%s\n", logo->names[0]);
853 }
854 }
855}
856
857const FFlogo* ffLogoGetBuiltinForName(const FFstrbuf* name, FFLogoSize size) {
858 return ffStrbufEqualS(name, "?") ? &ffLogoUnknown : logoGetBuiltin(name, size);
859}
860
861const FFlogo* ffLogoGetBuiltinDetected(FFLogoSize size) {
862 return logoGetBuiltinDetected(size);
863}