mirror of
https://github.com/fastfetch-cli/fastfetch.git
synced 2026-10-27 23:30:47 +01:00
Global: adds general cache layer
This commit is contained in:
@@ -90,6 +90,7 @@ Bugfixes:
|
||||
* Worked around an iTerm bug where the image logo may be moved out of view. (Logo)
|
||||
* Increased the terminal response wait time to 1s to improve compatibility with slower terminals. (General)
|
||||
* Fixed the `PublicIp` and `Weather` modules exiting with "can only be used once due to internal limitations" on the round after a failed request when `--dynamic-interval` is used, instead of retrying. (PublicIp / Weather)
|
||||
* Fixed the `Display`, `Monitor`, `WM`, `DE`, `Media`, `Player`, `Shell` and `Terminal` modules replaying the first round's result when `--dynamic-interval` is used. The detection results that are cached for the whole run are now dropped between rounds, so these modules detect again on every round. (Display / Monitor / WM / DE / Media / Player / Shell / Terminal)
|
||||
* Some internal cleanups and optimizations.
|
||||
|
||||
Logos:
|
||||
|
||||
@@ -462,6 +462,7 @@ set(LIBFASTFETCH_SRC
|
||||
src/common/impl/time.c
|
||||
src/common/impl/edidHelper.c
|
||||
src/common/impl/base64.c
|
||||
src/common/impl/FFcache.c
|
||||
src/common/impl/FFlist.c
|
||||
src/common/impl/FFstrbuf.c
|
||||
src/common/impl/path.c
|
||||
@@ -2197,6 +2198,13 @@ endif()
|
||||
###################
|
||||
|
||||
if (BUILD_TESTS)
|
||||
add_executable(fastfetch-test-cache
|
||||
tests/cache.c
|
||||
)
|
||||
target_link_libraries(fastfetch-test-cache
|
||||
PRIVATE libfastfetch
|
||||
)
|
||||
|
||||
add_executable(fastfetch-test-strbuf
|
||||
tests/strbuf.c
|
||||
)
|
||||
|
||||
@@ -0,0 +1,48 @@
|
||||
#pragma once
|
||||
|
||||
#include "fastfetch.h"
|
||||
|
||||
// A cache for a value that is built at most once per generation.
|
||||
//
|
||||
// The value itself is owned by the entry owner (usually a file scope `static`); the entry only
|
||||
// remembers whether that value is still valid and knows how to drop it. `--dynamic-interval`
|
||||
// starts a new generation for every round, dropping all the values that have been built so far,
|
||||
// so the modules re-detect them instead of replaying the first round's snapshot.
|
||||
//
|
||||
// An entry registers itself the first time it is used. There is therefore no central list to keep
|
||||
// in sync, and a cache cannot be forgotten in it. Do not use this type for state that is carried
|
||||
// across calls to derive a rate (cpuusage, diskio, netio, top): that is a baseline rather than a
|
||||
// snapshot, and dropping it makes the first sample of a round read as zero.
|
||||
//
|
||||
// The storage pointed to by `storage` must stay valid for the whole run; only the value behind it
|
||||
// is dropped by an invalidation.
|
||||
//
|
||||
// Not thread safe: entries are only touched from a single thread, on a frame boundary, while the
|
||||
// modules are not running.
|
||||
typedef struct FFcacheEntry {
|
||||
const char* name; // For diagnostics.
|
||||
void* storage; // Owned by the entry owner. May be nullptr.
|
||||
void (*init)(void* storage); // Optional. Runs whenever the entry is stale.
|
||||
void (*destroy)(void* storage); // Optional. Must be idempotent, and tolerate a partially initialized storage.
|
||||
bool initialized;
|
||||
bool registered;
|
||||
struct FFcacheEntry* next; // Internal: registry link.
|
||||
} FFcacheEntry;
|
||||
|
||||
// Returns `entry->storage`, running `init` on it first when the entry is stale. The entry is marked
|
||||
// initialized before `init` runs, so a call made from within `init` returns the partially built
|
||||
// value instead of recursing.
|
||||
[[gnu::nonnull(1)]] void* ffCacheGet(FFcacheEntry* entry);
|
||||
|
||||
// Staleness test for entries whose initialization needs arguments that are not part of the cache
|
||||
// identity (such as the cover file request of `ffDetectMedia`) and therefore cannot live behind
|
||||
// `init(void*)`. Returns true exactly once per generation: the caller must then build the value
|
||||
// itself.
|
||||
[[gnu::nonnull(1), nodiscard]] bool ffCacheBeginInit(FFcacheEntry* entry);
|
||||
|
||||
// Drops the cached value of one entry, so that the next request builds it again. Idempotent, and
|
||||
// safe on an entry that has never been used.
|
||||
[[gnu::nonnull(1)]] void ffCacheInvalidate(FFcacheEntry* entry);
|
||||
|
||||
// Drops every value that has been built so far. Called once per `--dynamic-interval` round.
|
||||
void ffCacheInvalidateAll(void);
|
||||
@@ -0,0 +1,53 @@
|
||||
#include "common/FFcache.h"
|
||||
|
||||
// Entries are file scope statics owned by the cache owner, so they can be linked into a list
|
||||
// without any allocation. The list holds every entry that has been used at least once: an entry
|
||||
// that was never used has nothing to drop.
|
||||
static FFcacheEntry* entries = nullptr;
|
||||
|
||||
static void registerEntry(FFcacheEntry* entry) {
|
||||
if (entry->registered) {
|
||||
return;
|
||||
}
|
||||
|
||||
entry->registered = true;
|
||||
entry->next = entries;
|
||||
entries = entry;
|
||||
}
|
||||
|
||||
void* ffCacheGet(FFcacheEntry* entry) {
|
||||
if (ffCacheBeginInit(entry) && entry->init) {
|
||||
entry->init(entry->storage);
|
||||
}
|
||||
|
||||
return entry->storage;
|
||||
}
|
||||
|
||||
bool ffCacheBeginInit(FFcacheEntry* entry) {
|
||||
registerEntry(entry);
|
||||
|
||||
if (entry->initialized) {
|
||||
return false;
|
||||
}
|
||||
|
||||
entry->initialized = true;
|
||||
return true;
|
||||
}
|
||||
|
||||
void ffCacheInvalidate(FFcacheEntry* entry) {
|
||||
if (!entry->initialized) {
|
||||
return;
|
||||
}
|
||||
|
||||
entry->initialized = false;
|
||||
|
||||
if (entry->destroy) {
|
||||
entry->destroy(entry->storage);
|
||||
}
|
||||
}
|
||||
|
||||
void ffCacheInvalidateAll(void) {
|
||||
for (FFcacheEntry* entry = entries; entry; entry = entry->next) {
|
||||
ffCacheInvalidate(entry);
|
||||
}
|
||||
}
|
||||
+39
-14
@@ -1,22 +1,47 @@
|
||||
#include "common/netif.h"
|
||||
#include "common/FFcache.h"
|
||||
|
||||
#ifndef _WIN32
|
||||
#include <net/if.h>
|
||||
#include <netinet/in.h>
|
||||
#endif
|
||||
#include <string.h>
|
||||
|
||||
// netio, localip and dns resolve the default route on every round, and it can change while a
|
||||
// `--dynamic-interval` run is going on: a laptop switching from Wi-Fi to Ethernet, a VPN coming up.
|
||||
static FFNetifDefaultRouteResult defaultRouteV4;
|
||||
static FFNetifDefaultRouteResult defaultRouteV6;
|
||||
|
||||
static void resetDefaultRoute(void* storage) {
|
||||
// The impls only write the fields they found, so without this a round that finds no default
|
||||
// route would keep reporting the interface of the previous one.
|
||||
memset(storage, 0, sizeof(FFNetifDefaultRouteResult));
|
||||
}
|
||||
|
||||
static void initDefaultRouteV4(void* storage) {
|
||||
FFNetifDefaultRouteResult* result = storage;
|
||||
result->status = ffNetifGetDefaultRouteImplV4(result) ? FF_NETIF_OK : FF_NETIF_INVALID;
|
||||
}
|
||||
|
||||
static void initDefaultRouteV6(void* storage) {
|
||||
FFNetifDefaultRouteResult* result = storage;
|
||||
result->status = ffNetifGetDefaultRouteImplV6(result) ? FF_NETIF_OK : FF_NETIF_INVALID;
|
||||
}
|
||||
|
||||
static FFcacheEntry ffCacheEntryDefaultRouteV4 = {
|
||||
.name = "default route V4",
|
||||
.storage = &defaultRouteV4,
|
||||
.init = initDefaultRouteV4,
|
||||
.destroy = resetDefaultRoute,
|
||||
};
|
||||
|
||||
static FFcacheEntry ffCacheEntryDefaultRouteV6 = {
|
||||
.name = "default route V6",
|
||||
.storage = &defaultRouteV6,
|
||||
.init = initDefaultRouteV6,
|
||||
.destroy = resetDefaultRoute,
|
||||
};
|
||||
|
||||
const FFNetifDefaultRouteResult* ffNetifGetDefaultRouteV4(void) {
|
||||
static FFNetifDefaultRouteResult result;
|
||||
if (result.status == FF_NETIF_UNINITIALIZED) {
|
||||
result.status = ffNetifGetDefaultRouteImplV4(&result) ? FF_NETIF_OK : FF_NETIF_INVALID;
|
||||
}
|
||||
return &result;
|
||||
return ffCacheGet(&ffCacheEntryDefaultRouteV4);
|
||||
}
|
||||
|
||||
const FFNetifDefaultRouteResult* ffNetifGetDefaultRouteV6(void) {
|
||||
static FFNetifDefaultRouteResult result;
|
||||
if (result.status == FF_NETIF_UNINITIALIZED) {
|
||||
result.status = ffNetifGetDefaultRouteImplV6(&result) ? FF_NETIF_OK : FF_NETIF_INVALID;
|
||||
}
|
||||
return &result;
|
||||
return ffCacheGet(&ffCacheEntryDefaultRouteV6);
|
||||
}
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
#include "displayserver.h"
|
||||
#include "common/FFcache.h"
|
||||
|
||||
FFDisplayResult* ffdsAppendDisplay(
|
||||
FFDisplayServerResult* result,
|
||||
@@ -50,18 +51,45 @@ FFDisplayResult* ffdsAppendDisplay(
|
||||
|
||||
void ffConnectDisplayServerImpl(FFDisplayServerResult* ds);
|
||||
|
||||
const FFDisplayServerResult* ffConnectDisplayServer() {
|
||||
static FFDisplayServerResult result;
|
||||
static bool initialized = false;
|
||||
if (!initialized) {
|
||||
initialized = true;
|
||||
ffStrbufInit(&result.wmProcessName);
|
||||
ffStrbufInit(&result.wmPrettyName);
|
||||
ffStrbufInit(&result.wmProtocolName);
|
||||
ffStrbufInit(&result.deProcessName);
|
||||
ffStrbufInit(&result.dePrettyName);
|
||||
ffListInit(&result.displays);
|
||||
ffConnectDisplayServerImpl(&result);
|
||||
}
|
||||
return &result;
|
||||
static FFDisplayServerResult result;
|
||||
|
||||
static void initDisplayServerResult(void* storage) {
|
||||
FFDisplayServerResult* ds = storage;
|
||||
|
||||
ffStrbufInit(&ds->wmProcessName);
|
||||
ffStrbufInit(&ds->wmPrettyName);
|
||||
ffStrbufInit(&ds->wmProtocolName);
|
||||
ffStrbufInit(&ds->deProcessName);
|
||||
ffStrbufInit(&ds->dePrettyName);
|
||||
ffListInit(&ds->displays);
|
||||
|
||||
ffConnectDisplayServerImpl(ds);
|
||||
}
|
||||
|
||||
static void destroyDisplayServerResult(void* storage) {
|
||||
FFDisplayServerResult* ds = storage;
|
||||
|
||||
ffStrbufDestroy(&ds->wmProcessName);
|
||||
ffStrbufDestroy(&ds->wmPrettyName);
|
||||
ffStrbufDestroy(&ds->wmProtocolName);
|
||||
ffStrbufDestroy(&ds->deProcessName);
|
||||
ffStrbufDestroy(&ds->dePrettyName);
|
||||
|
||||
// Every display owns its name and serial, which are lost if the list is dropped as a whole
|
||||
FF_LIST_FOR_EACH (FFDisplayResult, display, ds->displays) {
|
||||
ffStrbufDestroy(&display->name);
|
||||
ffStrbufDestroy(&display->serial);
|
||||
}
|
||||
ffListDestroy(&ds->displays);
|
||||
}
|
||||
|
||||
static FFcacheEntry ffCacheEntryDisplayServer = {
|
||||
.name = "displayServer",
|
||||
.storage = &result,
|
||||
.init = initDisplayServerResult,
|
||||
.destroy = destroyDisplayServerResult,
|
||||
};
|
||||
|
||||
const FFDisplayServerResult* ffConnectDisplayServer() {
|
||||
return ffCacheGet(&ffCacheEntryDisplayServer);
|
||||
}
|
||||
|
||||
+117
-47
@@ -1,7 +1,7 @@
|
||||
#include "fastfetch.h"
|
||||
#include "common/properties.h"
|
||||
#include "common/thread.h"
|
||||
#include "common/settings.h"
|
||||
#include "common/FFcache.h"
|
||||
#include "detection/gtk_qt/gtk_qt.h"
|
||||
#include "detection/displayserver/displayserver.h"
|
||||
|
||||
@@ -11,29 +11,42 @@ static inline bool allPropertiesSet(FFGTKResult* result) {
|
||||
result->font.length > 0;
|
||||
}
|
||||
|
||||
static inline void applyGTKSettings(FFGTKResult* result, const char* themeName, const char* iconsName, const char* fontName, const char* cursorTheme, int cursorSize, const char* wallpaper) {
|
||||
// The values read from the desktop settings daemons, shared by the GTK2/3/4 results. They are
|
||||
// owned copies on purpose: `settings.c` hands out strings it leaks deliberately (see the
|
||||
// "Leaks value.chars" notes there), so borrowing them past a `--dynamic-interval` round would
|
||||
// leak one set per round. Copying them also lets `destroy` release them.
|
||||
typedef struct FFGTKSettings {
|
||||
FFstrbuf theme;
|
||||
FFstrbuf icons;
|
||||
FFstrbuf font;
|
||||
FFstrbuf cursor;
|
||||
FFstrbuf wallpaper;
|
||||
int32_t cursorSize;
|
||||
} FFGTKSettings;
|
||||
|
||||
static inline void applyGTKSettings(FFGTKResult* result, const FFGTKSettings* settings) {
|
||||
if (result->theme.length == 0) {
|
||||
ffStrbufAppendS(&result->theme, themeName);
|
||||
ffStrbufAppend(&result->theme, &settings->theme);
|
||||
}
|
||||
|
||||
if (result->icons.length == 0) {
|
||||
ffStrbufAppendS(&result->icons, iconsName);
|
||||
ffStrbufAppend(&result->icons, &settings->icons);
|
||||
}
|
||||
|
||||
if (result->font.length == 0) {
|
||||
ffStrbufAppendS(&result->font, fontName);
|
||||
ffStrbufAppend(&result->font, &settings->font);
|
||||
}
|
||||
|
||||
if (result->cursor.length == 0) {
|
||||
ffStrbufAppendS(&result->cursor, cursorTheme);
|
||||
ffStrbufAppend(&result->cursor, &settings->cursor);
|
||||
}
|
||||
|
||||
if (result->cursorSize.length == 0 && cursorSize > 0) {
|
||||
ffStrbufAppendF(&result->cursorSize, "%i", cursorSize);
|
||||
if (result->cursorSize.length == 0 && settings->cursorSize > 0) {
|
||||
ffStrbufAppendSInt(&result->cursorSize, settings->cursorSize);
|
||||
}
|
||||
|
||||
if (result->wallpaper.length == 0) {
|
||||
ffStrbufAppendS(&result->wallpaper, wallpaper);
|
||||
ffStrbufAppend(&result->wallpaper, &settings->wallpaper);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -43,22 +56,23 @@ static bool testXfconfWallpaperPropKey([[maybe_unused]] void* data, const char*
|
||||
return count == 0;
|
||||
}
|
||||
|
||||
static void detectGTKFromSettings(FFGTKResult* result) {
|
||||
static const char* themeName = nullptr;
|
||||
static const char* iconsName = nullptr;
|
||||
static const char* fontName = nullptr;
|
||||
static const char* cursorTheme = nullptr;
|
||||
static int cursorSize = 0;
|
||||
static const char* wallpaper = nullptr;
|
||||
static FFGTKSettings gtkSettings;
|
||||
|
||||
static bool init = false;
|
||||
static void initGTKSettings(void* storage) {
|
||||
FFGTKSettings* settings = storage;
|
||||
|
||||
if (init) {
|
||||
applyGTKSettings(result, themeName, iconsName, fontName, cursorTheme, cursorSize, wallpaper);
|
||||
return;
|
||||
}
|
||||
ffStrbufInit(&settings->theme);
|
||||
ffStrbufInit(&settings->icons);
|
||||
ffStrbufInit(&settings->font);
|
||||
ffStrbufInit(&settings->cursor);
|
||||
ffStrbufInit(&settings->wallpaper);
|
||||
|
||||
init = true;
|
||||
const char* themeName = nullptr;
|
||||
const char* iconsName = nullptr;
|
||||
const char* fontName = nullptr;
|
||||
const char* cursorTheme = nullptr;
|
||||
int cursorSize = 0;
|
||||
const char* wallpaper = nullptr;
|
||||
|
||||
const FFDisplayServerResult* wmde = ffConnectDisplayServer();
|
||||
|
||||
@@ -98,14 +112,14 @@ static void detectGTKFromSettings(FFGTKResult* result) {
|
||||
wallpaper = ffSettingsGetGnome("/org/gnome/desktop/background/picture-uri", "org.gnome.desktop.background", nullptr, "picture-uri", FF_VARIANT_TYPE_STRING).strValue;
|
||||
} else if (
|
||||
ffStrbufIgnCaseEqualS(&wmde->dePrettyName, FF_DE_PRETTY_ENLIGHTENMENT)) {
|
||||
ffEnlightenmentSettings settings = {};
|
||||
if (ffSettingsGetEnlightenmentProperty(&settings)) {
|
||||
themeName = settings.theme;
|
||||
iconsName = settings.icon_theme;
|
||||
fontName = settings.font;
|
||||
cursorTheme = settings.use_e_cursor ? "Enlightenment" : "Application";
|
||||
cursorSize = settings.cursor_size;
|
||||
wallpaper = settings.desktop_default_background;
|
||||
ffEnlightenmentSettings enlightenmentSettings = {};
|
||||
if (ffSettingsGetEnlightenmentProperty(&enlightenmentSettings)) {
|
||||
themeName = enlightenmentSettings.theme;
|
||||
iconsName = enlightenmentSettings.icon_theme;
|
||||
fontName = enlightenmentSettings.font;
|
||||
cursorTheme = enlightenmentSettings.use_e_cursor ? "Enlightenment" : "Application";
|
||||
cursorSize = enlightenmentSettings.cursor_size;
|
||||
wallpaper = enlightenmentSettings.desktop_default_background;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
@@ -121,9 +135,34 @@ static void detectGTKFromSettings(FFGTKResult* result) {
|
||||
cursorSize = ffSettingsGetDConf("/org/gnome/desktop/interface/cursor-size", FF_VARIANT_TYPE_INT).intValue;
|
||||
}
|
||||
|
||||
applyGTKSettings(result, themeName, iconsName, fontName, cursorTheme, cursorSize, wallpaper);
|
||||
// The strings above are owned by `settings.c`, which leaks them on purpose, so copy them into
|
||||
// the cache instead of borrowing them: this runs again on every `--dynamic-interval` round.
|
||||
ffStrbufSetS(&settings->theme, themeName);
|
||||
ffStrbufSetS(&settings->icons, iconsName);
|
||||
ffStrbufSetS(&settings->font, fontName);
|
||||
ffStrbufSetS(&settings->cursor, cursorTheme);
|
||||
ffStrbufSetS(&settings->wallpaper, wallpaper);
|
||||
settings->cursorSize = cursorSize;
|
||||
}
|
||||
|
||||
static void destroyGTKSettings(void* storage) {
|
||||
FFGTKSettings* settings = storage;
|
||||
|
||||
ffStrbufDestroy(&settings->theme);
|
||||
ffStrbufDestroy(&settings->icons);
|
||||
ffStrbufDestroy(&settings->font);
|
||||
ffStrbufDestroy(&settings->cursor);
|
||||
ffStrbufDestroy(&settings->wallpaper);
|
||||
settings->cursorSize = 0;
|
||||
}
|
||||
|
||||
static FFcacheEntry ffCacheEntryGTKSettings = {
|
||||
.name = "gtk-settings",
|
||||
.storage = >kSettings,
|
||||
.init = initGTKSettings,
|
||||
.destroy = destroyGTKSettings,
|
||||
};
|
||||
|
||||
static void detectGTKFromConfigFile(const char* filename, FFGTKResult* result) {
|
||||
ffParsePropFileValues(filename, 5, (FFpropquery[]) { { "gtk-theme-name =", &result->theme }, { "gtk-icon-theme-name =", &result->icons }, { "gtk-font-name =", &result->font }, { "gtk-cursor-theme-name =", &result->cursor }, { "gtk-cursor-theme-size =", &result->cursorSize } });
|
||||
}
|
||||
@@ -172,7 +211,7 @@ static void detectGTKFromConfigDir(FFstrbuf* configDir, const char* version, FFG
|
||||
static void detectGTK(const char* version, FFGTKResult* result) {
|
||||
// Mate, Cinnamon, GNOME, Unity, Budgie use dconf to save theme config
|
||||
// On other DEs, this will do nothing
|
||||
detectGTKFromSettings(result);
|
||||
applyGTKSettings(result, ffCacheGet(&ffCacheEntryGTKSettings));
|
||||
if (allPropertiesSet(result)) {
|
||||
return;
|
||||
}
|
||||
@@ -189,20 +228,51 @@ static void detectGTK(const char* version, FFGTKResult* result) {
|
||||
}
|
||||
}
|
||||
|
||||
#define FF_DETECT_GTK_IMPL(version) \
|
||||
static FFGTKResult result; \
|
||||
static bool init = false; \
|
||||
if (init) \
|
||||
return &result; \
|
||||
init = true; \
|
||||
ffStrbufInit(&result.theme); \
|
||||
ffStrbufInit(&result.icons); \
|
||||
ffStrbufInit(&result.font); \
|
||||
ffStrbufInit(&result.cursor); \
|
||||
ffStrbufInit(&result.cursorSize); \
|
||||
ffStrbufInit(&result.wallpaper); \
|
||||
detectGTK(#version, &result); \
|
||||
return &result;
|
||||
static void initGTKResult(const char* version, FFGTKResult* result) {
|
||||
ffStrbufInit(&result->theme);
|
||||
ffStrbufInit(&result->icons);
|
||||
ffStrbufInit(&result->font);
|
||||
ffStrbufInit(&result->cursor);
|
||||
ffStrbufInit(&result->cursorSize);
|
||||
ffStrbufInit(&result->wallpaper);
|
||||
detectGTK(version, result);
|
||||
}
|
||||
|
||||
static void destroyGTKResult(void* storage) {
|
||||
FFGTKResult* result = storage;
|
||||
|
||||
ffStrbufDestroy(&result->theme);
|
||||
ffStrbufDestroy(&result->icons);
|
||||
ffStrbufDestroy(&result->font);
|
||||
ffStrbufDestroy(&result->cursor);
|
||||
ffStrbufDestroy(&result->cursorSize);
|
||||
ffStrbufDestroy(&result->wallpaper);
|
||||
}
|
||||
|
||||
static void initGTK2Result(void* storage) {
|
||||
initGTKResult("2", storage);
|
||||
}
|
||||
|
||||
static void initGTK3Result(void* storage) {
|
||||
initGTKResult("3", storage);
|
||||
}
|
||||
|
||||
static void initGTK4Result(void* storage) {
|
||||
initGTKResult("4", storage);
|
||||
}
|
||||
|
||||
// Each version gets its own result and its own entry, so a refresh drops all three (each one is
|
||||
// then rebuilt on demand, in the same lazy order as a single-shot run). They share the single
|
||||
// `gtk-settings` entry above, which keeps a round at one settings query instead of three.
|
||||
#define FF_DETECT_GTK_IMPL(version) \
|
||||
static FFGTKResult result; \
|
||||
static FFcacheEntry entry = { \
|
||||
.name = "gtk" #version, \
|
||||
.storage = &result, \
|
||||
.init = initGTK##version##Result, \
|
||||
.destroy = destroyGTKResult, \
|
||||
}; \
|
||||
return ffCacheGet(&entry);
|
||||
|
||||
const FFGTKResult* ffDetectGTK2(void) {
|
||||
FF_DETECT_GTK_IMPL(2)
|
||||
@@ -216,4 +286,4 @@ const FFGTKResult* ffDetectGTK4(void) {
|
||||
FF_DETECT_GTK_IMPL(4)
|
||||
}
|
||||
|
||||
#undef FF_CALCULATE_GTK_IMPL
|
||||
#undef FF_DETECT_GTK_IMPL
|
||||
|
||||
+36
-21
@@ -1,7 +1,7 @@
|
||||
#include "fastfetch.h"
|
||||
#include "common/properties.h"
|
||||
#include "common/thread.h"
|
||||
#include "common/strutil.h"
|
||||
#include "common/FFcache.h"
|
||||
#include "detection/gtk_qt/gtk_qt.h"
|
||||
#include "detection/displayserver/displayserver.h"
|
||||
|
||||
@@ -172,38 +172,53 @@ static void detectKvantum(FFQtResult* result) {
|
||||
});
|
||||
}
|
||||
|
||||
const FFQtResult* ffDetectQt(void) {
|
||||
static FFQtResult result;
|
||||
static FFQtResult result;
|
||||
|
||||
static bool init = false;
|
||||
if (init) {
|
||||
return &result;
|
||||
}
|
||||
init = true;
|
||||
static void initQtResult(void* storage) {
|
||||
FFQtResult* qt = storage;
|
||||
|
||||
ffStrbufInit(&result.widgetStyle);
|
||||
ffStrbufInit(&result.colorScheme);
|
||||
ffStrbufInit(&result.icons);
|
||||
ffStrbufInit(&result.font);
|
||||
ffStrbufInit(&result.wallpaper);
|
||||
ffStrbufInit(&qt->widgetStyle);
|
||||
ffStrbufInit(&qt->colorScheme);
|
||||
ffStrbufInit(&qt->icons);
|
||||
ffStrbufInit(&qt->font);
|
||||
ffStrbufInit(&qt->wallpaper);
|
||||
|
||||
const FFDisplayServerResult* wmde = ffConnectDisplayServer();
|
||||
|
||||
if (ffStrbufIgnCaseEqualS(&wmde->dePrettyName, FF_DE_PRETTY_PLASMA)) {
|
||||
detectPlasma(&result);
|
||||
detectPlasma(qt);
|
||||
} else if (ffStrbufIgnCaseEqualS(&wmde->dePrettyName, FF_DE_PRETTY_LXQT)) {
|
||||
detectLXQt(&result);
|
||||
detectLXQt(qt);
|
||||
} else {
|
||||
const char* qPlatformTheme = getenv("QT_QPA_PLATFORMTHEME");
|
||||
if (qPlatformTheme && (ffStrEquals(qPlatformTheme, "qt5ct") || ffStrEquals(qPlatformTheme, "qt6ct"))) {
|
||||
detectQtCt(qPlatformTheme[2], &result);
|
||||
detectQtCt(qPlatformTheme[2], qt);
|
||||
}
|
||||
}
|
||||
|
||||
if (ffStrbufEqualS(&result.widgetStyle, "kvantum") || ffStrbufEqualS(&result.widgetStyle, "kvantum-dark")) {
|
||||
ffStrbufClear(&result.widgetStyle);
|
||||
detectKvantum(&result);
|
||||
if (ffStrbufEqualS(&qt->widgetStyle, "kvantum") || ffStrbufEqualS(&qt->widgetStyle, "kvantum-dark")) {
|
||||
ffStrbufClear(&qt->widgetStyle);
|
||||
detectKvantum(qt);
|
||||
}
|
||||
|
||||
return &result;
|
||||
}
|
||||
|
||||
static void destroyQtResult(void* storage) {
|
||||
FFQtResult* qt = storage;
|
||||
|
||||
ffStrbufDestroy(&qt->widgetStyle);
|
||||
ffStrbufDestroy(&qt->colorScheme);
|
||||
ffStrbufDestroy(&qt->icons);
|
||||
ffStrbufDestroy(&qt->font);
|
||||
ffStrbufDestroy(&qt->wallpaper);
|
||||
}
|
||||
|
||||
static FFcacheEntry ffCacheEntryQt = {
|
||||
.name = "qt",
|
||||
.storage = &result,
|
||||
.init = initQtResult,
|
||||
.destroy = destroyQtResult,
|
||||
};
|
||||
|
||||
const FFQtResult* ffDetectQt(void) {
|
||||
return ffCacheGet(&ffCacheEntryQt);
|
||||
}
|
||||
|
||||
@@ -1,19 +1,53 @@
|
||||
#include "media.h"
|
||||
#include "common/FFcache.h"
|
||||
#include "common/io.h"
|
||||
|
||||
void ffDetectMediaImpl(FFMediaResult* media, bool saveCover);
|
||||
|
||||
static FFMediaResult result;
|
||||
|
||||
// The cover file of this run, if there is one. It is tracked here rather than in `result` because
|
||||
// the cached result is dropped between `--dynamic-interval` rounds, while the file has to stay on
|
||||
// disk for the whole run: the `kitty-direct` logo type lets the terminal open it by path, and the
|
||||
// logo is replayed from the line cache on every round.
|
||||
static FFstrbuf coverFile;
|
||||
static bool coverFileRegistered = false;
|
||||
|
||||
static void removeMediaCoverFile(void) {
|
||||
if (result.cover.length > 0) {
|
||||
ffRemoveFile(result.cover.chars);
|
||||
ffStrbufDestroy(&result.cover);
|
||||
if (coverFile.length > 0) {
|
||||
ffRemoveFile(coverFile.chars);
|
||||
ffStrbufDestroy(&coverFile);
|
||||
}
|
||||
}
|
||||
|
||||
static void destroyMediaResult(void* storage) {
|
||||
FFMediaResult* media = storage;
|
||||
|
||||
ffStrbufDestroy(&media->error);
|
||||
ffStrbufDestroy(&media->playerId);
|
||||
ffStrbufDestroy(&media->player);
|
||||
ffStrbufDestroy(&media->song);
|
||||
ffStrbufDestroy(&media->artist);
|
||||
ffStrbufDestroy(&media->album);
|
||||
ffStrbufDestroy(&media->url);
|
||||
ffStrbufDestroy(&media->status);
|
||||
ffStrbufDestroy(&media->cover);
|
||||
media->length = 0;
|
||||
media->position = 0;
|
||||
media->removeCoverAfterUse = false;
|
||||
}
|
||||
|
||||
// `saveCover` asks for a side effect and is not part of the cache identity, so the entry cannot be
|
||||
// initialized through `init(void*)`: the first caller of a generation decides, and the callers that
|
||||
// follow reuse its result, as they did when the result was cached for the whole run.
|
||||
static FFcacheEntry ffCacheEntryMedia = {
|
||||
.name = "media",
|
||||
.storage = &result,
|
||||
.destroy = destroyMediaResult,
|
||||
};
|
||||
|
||||
const FFMediaResult* ffDetectMedia(bool saveCover) {
|
||||
if (result.error.chars == nullptr) {
|
||||
if (ffCacheBeginInit(&ffCacheEntryMedia)) {
|
||||
ffStrbufInit(&result.error);
|
||||
ffStrbufInit(&result.playerId);
|
||||
ffStrbufInit(&result.player);
|
||||
@@ -37,7 +71,11 @@ const FFMediaResult* ffDetectMedia(bool saveCover) {
|
||||
ffStrbufTrimRightSpace(&result.player);
|
||||
|
||||
if (saveCover && result.removeCoverAfterUse) {
|
||||
atexit(removeMediaCoverFile);
|
||||
ffStrbufSet(&coverFile, &result.cover);
|
||||
if (!coverFileRegistered) {
|
||||
coverFileRegistered = true;
|
||||
atexit(removeMediaCoverFile);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+43
-16
@@ -1,21 +1,48 @@
|
||||
#include "os.h"
|
||||
#include "common/FFcache.h"
|
||||
|
||||
void ffDetectOSImpl(FFOSResult* os);
|
||||
|
||||
const FFOSResult* ffDetectOS(void) {
|
||||
static FFOSResult result;
|
||||
if (result.name.chars == nullptr) {
|
||||
ffStrbufInit(&result.name);
|
||||
ffStrbufInit(&result.prettyName);
|
||||
ffStrbufInit(&result.id);
|
||||
ffStrbufInit(&result.version);
|
||||
ffStrbufInit(&result.versionID);
|
||||
ffStrbufInit(&result.codename);
|
||||
ffStrbufInit(&result.buildID);
|
||||
ffStrbufInit(&result.idLike);
|
||||
ffStrbufInit(&result.variant);
|
||||
ffStrbufInit(&result.variantID);
|
||||
ffDetectOSImpl(&result);
|
||||
}
|
||||
return &result;
|
||||
static FFOSResult result;
|
||||
|
||||
static void initOSResult(void* storage) {
|
||||
FFOSResult* os = storage;
|
||||
|
||||
ffStrbufInit(&os->name);
|
||||
ffStrbufInit(&os->prettyName);
|
||||
ffStrbufInit(&os->id);
|
||||
ffStrbufInit(&os->version);
|
||||
ffStrbufInit(&os->versionID);
|
||||
ffStrbufInit(&os->codename);
|
||||
ffStrbufInit(&os->buildID);
|
||||
ffStrbufInit(&os->idLike);
|
||||
ffStrbufInit(&os->variant);
|
||||
ffStrbufInit(&os->variantID);
|
||||
ffDetectOSImpl(os);
|
||||
}
|
||||
|
||||
static void destroyOSResult(void* storage) {
|
||||
FFOSResult* os = storage;
|
||||
|
||||
ffStrbufDestroy(&os->name);
|
||||
ffStrbufDestroy(&os->prettyName);
|
||||
ffStrbufDestroy(&os->id);
|
||||
ffStrbufDestroy(&os->version);
|
||||
ffStrbufDestroy(&os->versionID);
|
||||
ffStrbufDestroy(&os->codename);
|
||||
ffStrbufDestroy(&os->buildID);
|
||||
ffStrbufDestroy(&os->idLike);
|
||||
ffStrbufDestroy(&os->variant);
|
||||
ffStrbufDestroy(&os->variantID);
|
||||
}
|
||||
|
||||
static FFcacheEntry ffCacheEntryOS = {
|
||||
.name = "os",
|
||||
.storage = &result,
|
||||
.init = initOSResult,
|
||||
.destroy = destroyOSResult,
|
||||
};
|
||||
|
||||
const FFOSResult* ffDetectOS(void) {
|
||||
return ffCacheGet(&ffCacheEntryOS);
|
||||
}
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
#include "terminalshell.h"
|
||||
#include "common/FFcache.h"
|
||||
#include "common/io.h"
|
||||
#include "common/parsing.h"
|
||||
#include "common/processing.h"
|
||||
@@ -376,22 +377,43 @@ static void setTerminalInfoDetails(FFTerminalResult* result) {
|
||||
#define FF_EXE_PATH_LEN 260
|
||||
#endif
|
||||
|
||||
const FFShellResult* ffDetectShell() {
|
||||
static FFShellResult result;
|
||||
static bool init = false;
|
||||
if (init) {
|
||||
return &result;
|
||||
}
|
||||
init = true;
|
||||
static FFShellResult shellResult;
|
||||
|
||||
ffStrbufInit(&result.processName);
|
||||
ffStrbufInitA(&result.exe, FF_EXE_PATH_LEN);
|
||||
result.exeName = result.exe.chars;
|
||||
ffStrbufInit(&result.exePath);
|
||||
ffStrbufInit(&result.version);
|
||||
result.pid = 0;
|
||||
result.ppid = 0;
|
||||
result.tty = -1;
|
||||
static void destroyShellResult(void* storage) {
|
||||
FFShellResult* result = storage;
|
||||
|
||||
ffStrbufDestroy(&result->processName);
|
||||
ffStrbufDestroy(&result->exe);
|
||||
result->exeName = result->exe.chars; // `exeName` points into `exe`, which is empty again
|
||||
ffStrbufDestroy(&result->exePath);
|
||||
ffStrbufDestroy(&result->prettyName);
|
||||
ffStrbufDestroy(&result->version);
|
||||
result->pid = 0;
|
||||
result->ppid = 0;
|
||||
result->tty = -1;
|
||||
}
|
||||
|
||||
static FFcacheEntry ffCacheEntryShell = {
|
||||
.name = "shell",
|
||||
.storage = &shellResult,
|
||||
.destroy = destroyShellResult,
|
||||
};
|
||||
|
||||
const FFShellResult* ffDetectShell() {
|
||||
FFShellResult* result = &shellResult;
|
||||
|
||||
if (!ffCacheBeginInit(&ffCacheEntryShell)) {
|
||||
return result;
|
||||
}
|
||||
|
||||
ffStrbufInit(&result->processName);
|
||||
ffStrbufInitA(&result->exe, FF_EXE_PATH_LEN);
|
||||
result->exeName = result->exe.chars;
|
||||
ffStrbufInit(&result->exePath);
|
||||
ffStrbufInit(&result->version);
|
||||
result->pid = 0;
|
||||
result->ppid = 0;
|
||||
result->tty = -1;
|
||||
|
||||
pid_t ppid = getppid();
|
||||
|
||||
@@ -401,49 +423,70 @@ const FFShellResult* ffDetectShell() {
|
||||
ffProcessGetBasicInfoLinux(ppid, &_, &ppid, nullptr);
|
||||
}
|
||||
|
||||
ppid = getShellInfo(&result, ppid);
|
||||
getUserShellFromEnv(&result);
|
||||
ppid = getShellInfo(result, ppid);
|
||||
getUserShellFromEnv(result);
|
||||
|
||||
if (result.processName.length > 0) {
|
||||
setShellInfoDetails(&result);
|
||||
if (result->processName.length > 0) {
|
||||
setShellInfoDetails(result);
|
||||
if (instance.config.general.detectVersion) {
|
||||
fftsGetShellVersion(result.exePath.length > 0 ? &result.exePath : &result.exe, result.exeName, &result.version);
|
||||
fftsGetShellVersion(result->exePath.length > 0 ? &result->exePath : &result->exe, result->exeName, &result->version);
|
||||
}
|
||||
}
|
||||
|
||||
return &result;
|
||||
return result;
|
||||
}
|
||||
|
||||
const FFTerminalResult* ffDetectTerminal() {
|
||||
static FFTerminalResult result;
|
||||
static bool init = false;
|
||||
if (init) {
|
||||
return &result;
|
||||
}
|
||||
init = true;
|
||||
static FFTerminalResult terminalResult;
|
||||
|
||||
ffStrbufInit(&result.processName);
|
||||
ffStrbufInitA(&result.exe, FF_EXE_PATH_LEN);
|
||||
result.exeName = result.exe.chars;
|
||||
ffStrbufInit(&result.exePath);
|
||||
ffStrbufInit(&result.version);
|
||||
ffStrbufInitS(&result.tty, ttyname(STDOUT_FILENO));
|
||||
result.pid = 0;
|
||||
result.ppid = 0;
|
||||
static void destroyTerminalResult(void* storage) {
|
||||
FFTerminalResult* result = storage;
|
||||
|
||||
ffStrbufDestroy(&result->processName);
|
||||
ffStrbufDestroy(&result->exe);
|
||||
result->exeName = result->exe.chars; // `exeName` points into `exe`, which is empty again
|
||||
ffStrbufDestroy(&result->exePath);
|
||||
ffStrbufDestroy(&result->prettyName);
|
||||
ffStrbufDestroy(&result->version);
|
||||
ffStrbufDestroy(&result->tty);
|
||||
result->pid = 0;
|
||||
result->ppid = 0;
|
||||
}
|
||||
|
||||
static FFcacheEntry ffCacheEntryTerminal = {
|
||||
.name = "terminal",
|
||||
.storage = &terminalResult,
|
||||
.destroy = destroyTerminalResult,
|
||||
};
|
||||
|
||||
const FFTerminalResult* ffDetectTerminal() {
|
||||
FFTerminalResult* result = &terminalResult;
|
||||
|
||||
if (!ffCacheBeginInit(&ffCacheEntryTerminal)) {
|
||||
return result;
|
||||
}
|
||||
|
||||
ffStrbufInit(&result->processName);
|
||||
ffStrbufInitA(&result->exe, FF_EXE_PATH_LEN);
|
||||
result->exeName = result->exe.chars;
|
||||
ffStrbufInit(&result->exePath);
|
||||
ffStrbufInit(&result->version);
|
||||
ffStrbufInitS(&result->tty, ttyname(STDOUT_FILENO));
|
||||
result->pid = 0;
|
||||
result->ppid = 0;
|
||||
|
||||
pid_t ppid = (pid_t) ffDetectShell()->ppid;
|
||||
|
||||
if (ppid) {
|
||||
ppid = getTerminalInfo(&result, ppid);
|
||||
ppid = getTerminalInfo(result, ppid);
|
||||
}
|
||||
getTerminalFromEnv(&result);
|
||||
getTerminalFromEnv(result);
|
||||
|
||||
if (result.processName.length > 0) {
|
||||
setTerminalInfoDetails(&result);
|
||||
if (result->processName.length > 0) {
|
||||
setTerminalInfoDetails(result);
|
||||
if (instance.config.general.detectVersion) {
|
||||
fftsGetTerminalVersion(&result.processName, result.exePath.length > 0 ? &result.exePath : &result.exe, &result.version);
|
||||
fftsGetTerminalVersion(&result->processName, result->exePath.length > 0 ? &result->exePath : &result->exe, &result->version);
|
||||
}
|
||||
}
|
||||
|
||||
return &result;
|
||||
return result;
|
||||
}
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
#include "terminalshell.h"
|
||||
#include "common/FFcache.h"
|
||||
#include "common/io.h"
|
||||
#include "common/processing.h"
|
||||
#include "common/thread.h"
|
||||
@@ -293,27 +294,48 @@ static void setTerminalInfoDetails(FFTerminalResult* result) {
|
||||
}
|
||||
}
|
||||
|
||||
const FFShellResult* ffDetectShell(void) {
|
||||
static FFShellResult result;
|
||||
static bool init = false;
|
||||
if (init) {
|
||||
return &result;
|
||||
}
|
||||
init = true;
|
||||
static FFShellResult shellResult;
|
||||
|
||||
ffStrbufInit(&result.processName);
|
||||
ffStrbufInitA(&result.exe, MAX_PATH);
|
||||
result.exeName = "";
|
||||
ffStrbufInit(&result.exePath);
|
||||
ffStrbufInit(&result.prettyName);
|
||||
ffStrbufInit(&result.version);
|
||||
result.pid = 0;
|
||||
result.ppid = 0;
|
||||
result.tty = -1;
|
||||
static void destroyShellResult(void* storage) {
|
||||
FFShellResult* result = storage;
|
||||
|
||||
ffStrbufDestroy(&result->processName);
|
||||
ffStrbufDestroy(&result->exe);
|
||||
result->exeName = ""; // The `exe` buffer is empty again, so nothing points into it
|
||||
ffStrbufDestroy(&result->exePath);
|
||||
ffStrbufDestroy(&result->prettyName);
|
||||
ffStrbufDestroy(&result->version);
|
||||
result->pid = 0;
|
||||
result->ppid = 0;
|
||||
result->tty = -1;
|
||||
}
|
||||
|
||||
static FFcacheEntry ffCacheEntryShell = {
|
||||
.name = "shell",
|
||||
.storage = &shellResult,
|
||||
.destroy = destroyShellResult,
|
||||
};
|
||||
|
||||
const FFShellResult* ffDetectShell(void) {
|
||||
FFShellResult* result = &shellResult;
|
||||
|
||||
if (!ffCacheBeginInit(&ffCacheEntryShell)) {
|
||||
return result;
|
||||
}
|
||||
|
||||
ffStrbufInit(&result->processName);
|
||||
ffStrbufInitA(&result->exe, MAX_PATH);
|
||||
result->exeName = "";
|
||||
ffStrbufInit(&result->exePath);
|
||||
ffStrbufInit(&result->prettyName);
|
||||
ffStrbufInit(&result->version);
|
||||
result->pid = 0;
|
||||
result->ppid = 0;
|
||||
result->tty = -1;
|
||||
|
||||
uint32_t ppid;
|
||||
if (!ffProcessGetInfoWindows(0, &ppid, nullptr, nullptr, nullptr, nullptr, nullptr)) {
|
||||
return &result;
|
||||
return result;
|
||||
}
|
||||
|
||||
const char* ignoreParent = getenv("FFTS_IGNORE_PARENT");
|
||||
@@ -321,60 +343,81 @@ const FFShellResult* ffDetectShell(void) {
|
||||
ffProcessGetInfoWindows(ppid, &ppid, nullptr, nullptr, nullptr, nullptr, nullptr);
|
||||
}
|
||||
|
||||
ppid = getShellInfo(&result, ppid);
|
||||
ppid = getShellInfo(result, ppid);
|
||||
|
||||
if (result.processName.length > 0) {
|
||||
setShellInfoDetails(&result);
|
||||
if (result->processName.length > 0) {
|
||||
setShellInfoDetails(result);
|
||||
char tmp[MAX_PATH];
|
||||
strcpy(tmp, result.exeName);
|
||||
strcpy(tmp, result->exeName);
|
||||
char* ext = strrchr(tmp, '.');
|
||||
if (ext) {
|
||||
*ext = '\0';
|
||||
}
|
||||
if (instance.config.general.detectVersion) {
|
||||
fftsGetShellVersion(result.exePath.length > 0 ? &result.exePath : &result.exe, tmp, &result.version);
|
||||
fftsGetShellVersion(result->exePath.length > 0 ? &result->exePath : &result->exe, tmp, &result->version);
|
||||
}
|
||||
}
|
||||
|
||||
return &result;
|
||||
return result;
|
||||
}
|
||||
|
||||
const FFTerminalResult* ffDetectTerminal(void) {
|
||||
static FFTerminalResult result;
|
||||
static bool init = false;
|
||||
if (init) {
|
||||
return &result;
|
||||
}
|
||||
init = true;
|
||||
static FFTerminalResult terminalResult;
|
||||
|
||||
ffStrbufInit(&result.processName);
|
||||
ffStrbufInitA(&result.exe, MAX_PATH);
|
||||
result.exeName = "";
|
||||
ffStrbufInit(&result.exePath);
|
||||
ffStrbufInit(&result.prettyName);
|
||||
ffStrbufInit(&result.version);
|
||||
ffStrbufInit(&result.tty);
|
||||
result.pid = 0;
|
||||
result.ppid = 0;
|
||||
static void destroyTerminalResult(void* storage) {
|
||||
FFTerminalResult* result = storage;
|
||||
|
||||
ffStrbufDestroy(&result->processName);
|
||||
ffStrbufDestroy(&result->exe);
|
||||
result->exeName = ""; // The `exe` buffer is empty again, so nothing points into it
|
||||
ffStrbufDestroy(&result->exePath);
|
||||
ffStrbufDestroy(&result->prettyName);
|
||||
ffStrbufDestroy(&result->version);
|
||||
ffStrbufDestroy(&result->tty);
|
||||
result->pid = 0;
|
||||
result->ppid = 0;
|
||||
}
|
||||
|
||||
static FFcacheEntry ffCacheEntryTerminal = {
|
||||
.name = "terminal",
|
||||
.storage = &terminalResult,
|
||||
.destroy = destroyTerminalResult,
|
||||
};
|
||||
|
||||
const FFTerminalResult* ffDetectTerminal(void) {
|
||||
FFTerminalResult* result = &terminalResult;
|
||||
|
||||
if (!ffCacheBeginInit(&ffCacheEntryTerminal)) {
|
||||
return result;
|
||||
}
|
||||
|
||||
ffStrbufInit(&result->processName);
|
||||
ffStrbufInitA(&result->exe, MAX_PATH);
|
||||
result->exeName = "";
|
||||
ffStrbufInit(&result->exePath);
|
||||
ffStrbufInit(&result->prettyName);
|
||||
ffStrbufInit(&result->version);
|
||||
ffStrbufInit(&result->tty);
|
||||
result->pid = 0;
|
||||
result->ppid = 0;
|
||||
|
||||
uint32_t ppid = ffDetectShell()->ppid;
|
||||
if (ppid) {
|
||||
getTerminalInfo(&result, ppid);
|
||||
getTerminalInfo(result, ppid);
|
||||
}
|
||||
|
||||
if (result.processName.length == 0) {
|
||||
getTerminalFromEnv(&result);
|
||||
if (result->processName.length == 0) {
|
||||
getTerminalFromEnv(result);
|
||||
}
|
||||
if (result.processName.length == 0) {
|
||||
detectDefaultTerminal(&result);
|
||||
if (result->processName.length == 0) {
|
||||
detectDefaultTerminal(result);
|
||||
}
|
||||
|
||||
if (result.processName.length > 0) {
|
||||
setTerminalInfoDetails(&result);
|
||||
if (result->processName.length > 0) {
|
||||
setTerminalInfoDetails(result);
|
||||
if (instance.config.general.detectVersion) {
|
||||
fftsGetTerminalVersion(&result.processName, result.exePath.length > 0 ? &result.exePath : &result.exe, &result.version);
|
||||
fftsGetTerminalVersion(&result->processName, result->exePath.length > 0 ? &result->exePath : &result->exe, &result->version);
|
||||
}
|
||||
}
|
||||
|
||||
return &result;
|
||||
return result;
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
#include "common/ffdata.h"
|
||||
#include "detection/version/version.h"
|
||||
#include "logo/logo.h"
|
||||
#include "common/FFcache.h"
|
||||
#include "common/commandoption.h"
|
||||
#include "common/genconfig.h"
|
||||
#include "common/init.h"
|
||||
@@ -815,6 +816,7 @@ static void run(FFdata* data) {
|
||||
ffTimeSleep(instance.state.dynamicInterval);
|
||||
fputs("\e[H", stdout); // Move cursor to the top left corner to overwrite the previous output
|
||||
instance.state.keysHeight = 0; // Reset keysHeight so `ffLogoPrintRemaining` will recalculate it
|
||||
ffCacheInvalidateAll(); // Drop the detection results cached for the previous round, so that this round re-detects them
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
|
||||
+120
@@ -0,0 +1,120 @@
|
||||
#include "common/FFcache.h"
|
||||
#include "common/textModifier.h"
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <stdnoreturn.h>
|
||||
|
||||
typedef struct TestStorage {
|
||||
int value;
|
||||
int initCalls;
|
||||
int destroyCalls;
|
||||
} TestStorage;
|
||||
|
||||
static TestStorage storageA, storageB, storageC, storageD, storageUnused;
|
||||
|
||||
static void initStorage(void* storage) {
|
||||
TestStorage* test = storage;
|
||||
++test->initCalls;
|
||||
test->value = 42;
|
||||
}
|
||||
|
||||
static void destroyStorage(void* storage) {
|
||||
TestStorage* test = storage;
|
||||
++test->destroyCalls;
|
||||
test->value = -1;
|
||||
}
|
||||
|
||||
static FFcacheEntry entryA = { .name = "A", .storage = &storageA, .init = initStorage, .destroy = destroyStorage };
|
||||
static FFcacheEntry entryB = { .name = "B", .storage = &storageB, .init = initStorage, .destroy = destroyStorage };
|
||||
static FFcacheEntry entryC = { .name = "C", .storage = &storageC, .init = initStorage, .destroy = destroyStorage };
|
||||
static FFcacheEntry entryD = { .name = "D", .storage = &storageD };
|
||||
static FFcacheEntry entryNoStorage = { .name = "no storage" };
|
||||
static FFcacheEntry entryUnused = { .name = "unused", .storage = &storageUnused, .init = initStorage, .destroy = destroyStorage };
|
||||
|
||||
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 testFailedInt(int expected, int actual, const char* what, int lineNo) {
|
||||
fprintf(stderr, FASTFETCH_TEXT_MODIFIER_ERROR "[%d] %s: expected %d, got %d\n" FASTFETCH_TEXT_MODIFIER_RESET, lineNo, what, expected, actual);
|
||||
exit(1);
|
||||
}
|
||||
|
||||
#define VERIFY(expression) \
|
||||
if (!(expression)) testFailed(#expression, __LINE__)
|
||||
|
||||
#define VERIFY_INT(expected, actual, what) \
|
||||
if ((expected) != (actual)) testFailedInt((expected), (actual), (what), __LINE__)
|
||||
|
||||
int main(void) {
|
||||
// A stale entry is built on the first get, and only then
|
||||
VERIFY(ffCacheGet(&entryA) == &storageA);
|
||||
VERIFY_INT(1, storageA.initCalls, "A init calls after the first get");
|
||||
VERIFY_INT(42, storageA.value, "A value after the first get");
|
||||
|
||||
VERIFY(ffCacheGet(&entryA) == &storageA);
|
||||
VERIFY_INT(1, storageA.initCalls, "A init calls after the second get");
|
||||
|
||||
// Nothing happens to the entry that has not been used yet
|
||||
VERIFY_INT(0, storageUnused.initCalls, "unused init calls");
|
||||
|
||||
// Invalidating drops the value and rearms the entry, and doing it twice is a no-op
|
||||
ffCacheInvalidate(&entryA);
|
||||
VERIFY_INT(1, storageA.destroyCalls, "A destroy calls after invalidating");
|
||||
ffCacheInvalidate(&entryA);
|
||||
VERIFY_INT(1, storageA.destroyCalls, "A destroy calls after invalidating twice");
|
||||
|
||||
VERIFY(ffCacheGet(&entryA) == &storageA);
|
||||
VERIFY_INT(2, storageA.initCalls, "A init calls after rebuilding");
|
||||
VERIFY_INT(42, storageA.value, "A value after rebuilding");
|
||||
|
||||
// An entry that has never been used has nothing to drop
|
||||
ffCacheInvalidate(&entryC);
|
||||
VERIFY_INT(0, storageC.destroyCalls, "C destroy calls after invalidating an unused entry");
|
||||
VERIFY(ffCacheGet(&entryC) == &storageC);
|
||||
VERIFY_INT(1, storageC.initCalls, "C init calls");
|
||||
|
||||
// An entry without an initializer tracks staleness on its own
|
||||
VERIFY(ffCacheGet(&entryD) == &storageD);
|
||||
VERIFY(!ffCacheBeginInit(&entryD));
|
||||
ffCacheInvalidate(&entryD);
|
||||
VERIFY(ffCacheBeginInit(&entryD));
|
||||
VERIFY(!ffCacheBeginInit(&entryD));
|
||||
|
||||
// An entry without storage and without callbacks is tolerated
|
||||
VERIFY(ffCacheGet(&entryNoStorage) == nullptr);
|
||||
ffCacheInvalidate(&entryNoStorage);
|
||||
VERIFY(ffCacheGet(&entryNoStorage) == nullptr);
|
||||
|
||||
// An entry whose owner builds the value itself (which is what `ffCacheBeginInit` is for) is only
|
||||
// marked, never built by the cache; and it must survive a `destroy` on a storage that the owner
|
||||
// never filled in
|
||||
VERIFY(ffCacheBeginInit(&entryB));
|
||||
VERIFY_INT(0, storageB.initCalls, "B init calls (the owner builds the value itself)");
|
||||
|
||||
// Invalidating everything drops exactly the entries that have been used so far
|
||||
ffCacheInvalidateAll();
|
||||
VERIFY_INT(2, storageA.destroyCalls, "A destroy calls after invalidating all");
|
||||
VERIFY_INT(1, storageC.destroyCalls, "C destroy calls after invalidating all");
|
||||
VERIFY_INT(0, storageD.destroyCalls, "D destroy calls"); // D has no callback
|
||||
VERIFY_INT(1, storageB.destroyCalls, "B destroy calls after invalidating a value that was never built");
|
||||
VERIFY_INT(0, entryB.initialized, "B must be stale again after invalidating all");
|
||||
VERIFY_INT(0, storageUnused.initCalls, "unused init calls after invalidating all");
|
||||
|
||||
// Rebuilding after invalidating all
|
||||
VERIFY(ffCacheGet(&entryA) == &storageA);
|
||||
VERIFY_INT(3, storageA.initCalls, "A init calls after invalidating all");
|
||||
VERIFY_INT(42, storageA.value, "A value after invalidating all");
|
||||
|
||||
// An entry joins the registry when it is first used, not when it is created
|
||||
VERIFY(ffCacheGet(&entryUnused) == &storageUnused);
|
||||
VERIFY_INT(1, storageUnused.initCalls, "unused init calls after its first get");
|
||||
ffCacheInvalidateAll();
|
||||
VERIFY_INT(1, storageUnused.destroyCalls, "unused destroy calls after invalidating all");
|
||||
VERIFY_INT(3, storageA.destroyCalls, "A destroy calls after the second invalidating all");
|
||||
|
||||
// Success
|
||||
puts("\033[32mAll tests passed!" FASTFETCH_TEXT_MODIFIER_RESET);
|
||||
}
|
||||
Reference in New Issue
Block a user