mirror of
https://github.com/fastfetch-cli/fastfetch.git
synced 2026-10-28 07:44:06 +01:00
Global (Windows): eliminates SHGetKnownFolderPath
This commit is contained in:
@@ -1098,6 +1098,7 @@ elseif(WIN32)
|
||||
src/common/impl/kmod_windows.c
|
||||
src/common/windows/getline.c
|
||||
src/common/windows/com.c
|
||||
src/common/windows/folders.c
|
||||
src/common/windows/registry.c
|
||||
src/common/windows/unicode.c
|
||||
src/common/windows/variant.cpp
|
||||
|
||||
@@ -2,13 +2,13 @@
|
||||
#include "common/io.h"
|
||||
#include "common/library.h"
|
||||
#include "common/strutil.h"
|
||||
#include "common/windows/folders.h"
|
||||
#include "common/windows/unicode.h"
|
||||
#include "common/windows/registry.h"
|
||||
#include "common/windows/nt.h"
|
||||
|
||||
#include <stdalign.h>
|
||||
#include <windows.h>
|
||||
#include <shlobj.h>
|
||||
#include <sddl.h>
|
||||
|
||||
#define SECURITY_WIN32 1 // For secext.h
|
||||
@@ -43,43 +43,27 @@ static void getExePath(FFPlatform* platform) {
|
||||
ffStrbufReplaceAllC(&platform->exePath, '\\', '/');
|
||||
}
|
||||
|
||||
static void getHomeDir(FFPlatform* platform) {
|
||||
PWSTR pPath = nullptr;
|
||||
if (SUCCEEDED(SHGetKnownFolderPath(&FOLDERID_Profile, KF_FLAG_DEFAULT, nullptr, &pPath))) {
|
||||
ffStrbufSetWS(&platform->homeDir, pPath);
|
||||
ffStrbufReplaceAllC(&platform->homeDir, '\\', '/');
|
||||
ffStrbufEnsureEndsWithC(&platform->homeDir, '/');
|
||||
} else {
|
||||
ffStrbufSetS(&platform->homeDir, getenv("USERPROFILE"));
|
||||
ffStrbufReplaceAllC(&platform->homeDir, '\\', '/');
|
||||
ffStrbufEnsureEndsWithC(&platform->homeDir, '/');
|
||||
}
|
||||
CoTaskMemFree(pPath);
|
||||
}
|
||||
|
||||
static void getCacheDir(FFPlatform* platform) {
|
||||
PWSTR pPath = nullptr;
|
||||
if (SUCCEEDED(SHGetKnownFolderPath(&FOLDERID_LocalAppData, KF_FLAG_DEFAULT, nullptr, &pPath))) {
|
||||
ffStrbufSetWS(&platform->cacheDir, pPath);
|
||||
ffStrbufReplaceAllC(&platform->cacheDir, '\\', '/');
|
||||
ffStrbufEnsureEndsWithC(&platform->cacheDir, '/');
|
||||
} else {
|
||||
if (!ffGetKnownFolderPath(FF_KNOWN_FOLDER_LOCAL_APP_DATA, &platform->cacheDir)) {
|
||||
ffStrbufAppend(&platform->cacheDir, &platform->homeDir);
|
||||
ffStrbufAppendS(&platform->cacheDir, "AppData/Local/");
|
||||
return;
|
||||
}
|
||||
CoTaskMemFree(pPath);
|
||||
|
||||
ffStrbufReplaceAllC(&platform->cacheDir, '\\', '/');
|
||||
ffStrbufEnsureEndsWithC(&platform->cacheDir, '/');
|
||||
}
|
||||
|
||||
static void platformPathAddKnownFolder(FFlist* dirs, REFKNOWNFOLDERID folderId) {
|
||||
PWSTR pPath = nullptr;
|
||||
if (SUCCEEDED(SHGetKnownFolderPath(folderId, KF_FLAG_DEFAULT, nullptr, &pPath))) {
|
||||
FF_STRBUF_AUTO_DESTROY buffer = ffStrbufCreateWS(pPath);
|
||||
CoTaskMemFree(pPath);
|
||||
ffStrbufReplaceAllC(&buffer, '\\', '/');
|
||||
ffStrbufEnsureEndsWithC(&buffer, '/');
|
||||
if (!FF_LIST_CONTAINS(*dirs, &buffer, ffStrbufEqual)) {
|
||||
ffStrbufInitMove(FF_LIST_ADD(FFstrbuf, *dirs), &buffer);
|
||||
}
|
||||
static void platformPathAddKnownFolder(FFlist* dirs, FFKnownFolder folder) {
|
||||
FF_STRBUF_AUTO_DESTROY buffer = ffStrbufCreate();
|
||||
if (!ffGetKnownFolderPath(folder, &buffer)) {
|
||||
return;
|
||||
}
|
||||
|
||||
ffStrbufReplaceAllC(&buffer, '\\', '/');
|
||||
ffStrbufEnsureEndsWithC(&buffer, '/');
|
||||
if (!FF_LIST_CONTAINS(*dirs, &buffer, ffStrbufEqual)) {
|
||||
ffStrbufInitMove(FF_LIST_ADD(FFstrbuf, *dirs), &buffer);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -112,9 +96,9 @@ static void getConfigDirs(FFPlatform* platform) {
|
||||
}
|
||||
|
||||
ffPlatformPathAddHome(&platform->configDirs, platform, ".config/");
|
||||
platformPathAddKnownFolder(&platform->configDirs, &FOLDERID_ProgramData);
|
||||
platformPathAddKnownFolder(&platform->configDirs, &FOLDERID_RoamingAppData);
|
||||
platformPathAddKnownFolder(&platform->configDirs, &FOLDERID_LocalAppData);
|
||||
platformPathAddKnownFolder(&platform->configDirs, FF_KNOWN_FOLDER_PROGRAM_DATA);
|
||||
platformPathAddKnownFolder(&platform->configDirs, FF_KNOWN_FOLDER_ROAMING_APP_DATA);
|
||||
platformPathAddKnownFolder(&platform->configDirs, FF_KNOWN_FOLDER_LOCAL_APP_DATA);
|
||||
ffPlatformPathAddHome(&platform->configDirs, platform, "");
|
||||
}
|
||||
|
||||
@@ -126,9 +110,9 @@ static void getDataDirs(FFPlatform* platform) {
|
||||
platformPathAddEnvSuffix(&platform->dataDirs, "MINGW_PREFIX", "share");
|
||||
}
|
||||
ffPlatformPathAddHome(&platform->dataDirs, platform, ".local/share/");
|
||||
platformPathAddKnownFolder(&platform->dataDirs, &FOLDERID_ProgramData);
|
||||
platformPathAddKnownFolder(&platform->dataDirs, &FOLDERID_RoamingAppData);
|
||||
platformPathAddKnownFolder(&platform->dataDirs, &FOLDERID_LocalAppData);
|
||||
platformPathAddKnownFolder(&platform->dataDirs, FF_KNOWN_FOLDER_PROGRAM_DATA);
|
||||
platformPathAddKnownFolder(&platform->dataDirs, FF_KNOWN_FOLDER_ROAMING_APP_DATA);
|
||||
platformPathAddKnownFolder(&platform->dataDirs, FF_KNOWN_FOLDER_LOCAL_APP_DATA);
|
||||
ffPlatformPathAddHome(&platform->dataDirs, platform, "");
|
||||
}
|
||||
|
||||
@@ -156,9 +140,25 @@ static void getUserName(FFPlatform* platform) {
|
||||
TOKEN_USER* tokenUser = (TOKEN_USER*) buf;
|
||||
UNICODE_STRING sidString = { .Buffer = buffer, .Length = 0, .MaximumLength = sizeof(buffer) };
|
||||
if (NT_SUCCESS(RtlConvertSidToUnicodeString(&sidString, tokenUser->User.Sid, FALSE))) {
|
||||
ffStrbufSetNWS(&platform->sid, sidString.Length / sizeof(wchar_t), sidString.Buffer);
|
||||
uint32_t sidLength = sidString.Length / sizeof(wchar_t);
|
||||
ffStrbufSetNWS(&platform->sid, sidLength, sidString.Buffer);
|
||||
|
||||
// The SID is already in hand, so the profile directory costs one registry read rather
|
||||
// than a second NtQueryInformationToken(). ffGetProfilePath() needs a null-terminated
|
||||
// string, which RtlConvertSidToUnicodeString() does not produce.
|
||||
if (sidLength < ARRAY_SIZE(buffer)) {
|
||||
buffer[sidLength] = L'\0';
|
||||
ffGetProfilePath(buffer, &platform->homeDir);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (platform->homeDir.length == 0) {
|
||||
ffStrbufSetS(&platform->homeDir, getenv("USERPROFILE")); // Last resort; ffGetProfilePath() covers the normal case
|
||||
}
|
||||
|
||||
ffStrbufReplaceAllC(&platform->homeDir, '\\', '/');
|
||||
ffStrbufEnsureEndsWithC(&platform->homeDir, '/');
|
||||
}
|
||||
|
||||
static void getHostName(FFPlatform* platform) {
|
||||
@@ -280,12 +280,12 @@ void ffPlatformInitImpl(FFPlatform* platform) {
|
||||
platform->pid = (uint32_t) (uintptr_t) ffGetTeb()->ClientId.UniqueProcess;
|
||||
getExePath(platform);
|
||||
getCwd(platform);
|
||||
getHomeDir(platform);
|
||||
|
||||
getUserName(platform);
|
||||
|
||||
getCacheDir(platform);
|
||||
getConfigDirs(platform);
|
||||
getDataDirs(platform);
|
||||
|
||||
getUserName(platform);
|
||||
getHostName(platform);
|
||||
getUserShell(platform);
|
||||
|
||||
|
||||
@@ -0,0 +1,237 @@
|
||||
#include "folders.h"
|
||||
#include "common/debug.h"
|
||||
#include "common/mallocHelper.h"
|
||||
#include "common/windows/nt.h"
|
||||
#include "common/windows/registry.h"
|
||||
#include "common/windows/unicode.h"
|
||||
|
||||
#include <ntstatus.h>
|
||||
|
||||
// Where the shell keeps the values SHGetKnownFolderPath() would compute.
|
||||
//
|
||||
// `User Shell Folders` is the store folder redirection writes to -- HKCU for the user's own
|
||||
// folders, HKLM for the machine-wide ones -- and it holds the *unexpanded* form, which is why the
|
||||
// environment expansion below is not optional. `ProfileList\<SID>\ProfileImagePath` is what the
|
||||
// logon session recorded as the profile directory, and is read by ffGetProfilePath().
|
||||
#define FF_FOLDERS_HKCU_SHELL_FOLDERS L"Software\\Microsoft\\Windows\\CurrentVersion\\Explorer\\User Shell Folders"
|
||||
#define FF_FOLDERS_HKLM_SHELL_FOLDERS L"SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Explorer\\User Shell Folders"
|
||||
#define FF_FOLDERS_HKLM_CURRENT_VERSION L"SOFTWARE\\Microsoft\\Windows\\CurrentVersion"
|
||||
#define FF_FOLDERS_HKLM_PROFILE_LIST L"SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion\\ProfileList"
|
||||
|
||||
// The environment fallbacks. Two of the registry values above are self-referential -- `Common
|
||||
// AppData` is literally `%ProgramData%` -- so these are the same answer, not a different one.
|
||||
#define FF_FOLDERS_ENV_LOCAL_APP_DATA L"%LOCALAPPDATA%"
|
||||
#define FF_FOLDERS_ENV_ROAMING_APP_DATA L"%APPDATA%"
|
||||
#define FF_FOLDERS_ENV_PROGRAM_DATA L"%ProgramData%"
|
||||
#define FF_FOLDERS_ENV_PROGRAM_FILES L"%ProgramFiles%"
|
||||
|
||||
// Every folder is resolved once and then kept. ffGetKnownFolderPath() is called about a dozen times
|
||||
// per process -- getCacheDir, getConfigDirs and getDataDirs all ask for the same folders -- and
|
||||
// each answer is a pure function of the registry and this process' environment. `states` separates
|
||||
// "not looked up yet" from "looked up and not there", which the path itself cannot: an empty
|
||||
// FFstrbuf is a valid answer.
|
||||
//
|
||||
// No lock guards this: the only thread fastfetch ever creates is the networking module's
|
||||
// asynchronous connect, and no module that resolves a known folder runs on it.
|
||||
typedef enum FFFolderState : uint8_t {
|
||||
FF_FOLDER_UNRESOLVED,
|
||||
FF_FOLDER_FOUND,
|
||||
FF_FOLDER_MISSING,
|
||||
} FFFolderState;
|
||||
|
||||
static FFstrbuf folderPaths[FF_KNOWN_FOLDER_COUNT];
|
||||
static FFFolderState folderStates[FF_KNOWN_FOLDER_COUNT];
|
||||
|
||||
// Expands `%NAME%` placeholders from this process' environment block.
|
||||
//
|
||||
// The work is done in UTF-16 so that a user name outside the ANSI code page survives: getenv()
|
||||
// goes through the CRT's narrow copy of the environment and would mangle it.
|
||||
//
|
||||
// RtlExpandEnvironmentStrings() substitutes nothing and still reports success for a variable it
|
||||
// cannot find (measured), so a leftover '%' is treated as a failure. A directory name that
|
||||
// legitimately contains one is a false negative, and the only thing that costs is the caller's
|
||||
// fallback.
|
||||
static bool expandEnv(const wchar_t* source, uint32_t sourceLen, FFstrbuf* result) {
|
||||
// Asking for the required length is done with a null destination, which the call reports as
|
||||
// STATUS_BUFFER_TOO_SMALL -- a *failure* status that still fills in `size`. The status is
|
||||
// therefore deliberately not checked here; a call that writes nothing leaves `size` at zero.
|
||||
SIZE_T size = 0; // in characters, including the null terminator
|
||||
(void) RtlExpandEnvironmentStrings(nullptr, source, sourceLen, nullptr, 0, &size);
|
||||
if (size == 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
FF_AUTO_FREE wchar_t* expanded = (wchar_t*) malloc(size * sizeof(wchar_t));
|
||||
SIZE_T written = 0;
|
||||
if (!NT_SUCCESS(RtlExpandEnvironmentStrings(nullptr, source, sourceLen, expanded, size, &written)) || written == 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (wcschr(expanded, L'%')) {
|
||||
return false;
|
||||
}
|
||||
|
||||
ffStrbufSetNWS(result, (uint32_t) (written - 1), expanded);
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool expandEnvZ(const wchar_t* source, FFstrbuf* result) {
|
||||
return expandEnv(source, (uint32_t) wcslen(source), result);
|
||||
}
|
||||
|
||||
// `raw` is the payload of a REG_SZ / REG_EXPAND_SZ value: UTF-16, usually null terminated.
|
||||
static bool expandBuffer(const FFArgBuffer* raw, FFstrbuf* result) {
|
||||
uint32_t length = raw->length / sizeof(wchar_t);
|
||||
if (length == 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const wchar_t* valueW = (const wchar_t*) raw->data;
|
||||
if (valueW[length - 1] == L'\0') {
|
||||
--length;
|
||||
}
|
||||
|
||||
return length > 0 && expandEnv(valueW, length, result);
|
||||
}
|
||||
|
||||
// FF_ARG_TYPE_BUFFER rather than FF_ARG_TYPE_STRBUF, because the value has to stay UTF-16 for
|
||||
// expandEnv(); the strbuf flavour converts it to UTF-8 on the way out. The buffer flavour does not
|
||||
// report the registry type either, so REG_SZ and REG_EXPAND_SZ are not told apart -- expanding a
|
||||
// string without placeholders is a no-op, which is what makes that acceptable.
|
||||
static bool readExpandedValue(HANDLE hKey, const wchar_t* valueNameW, FFstrbuf* result) {
|
||||
FFArgBuffer raw = {};
|
||||
if (!ffRegReadData(hKey, valueNameW, &raw, nullptr)) {
|
||||
return false;
|
||||
}
|
||||
FF_AUTO_FREE void* data = raw.data;
|
||||
|
||||
return expandBuffer(&raw, result);
|
||||
}
|
||||
|
||||
static bool readExpandedValueFrom(HKEY hRootKey, const wchar_t* subKeyW, const wchar_t* valueNameW, FFstrbuf* result) {
|
||||
FF_AUTO_CLOSE_FD HANDLE hKey = nullptr;
|
||||
if (!ffRegOpenKeyForRead(hRootKey, subKeyW, &hKey, nullptr)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return readExpandedValue(hKey, valueNameW, result);
|
||||
}
|
||||
|
||||
// The SID comes from the caller rather than from a token query here: FFPlatform_windows.c already
|
||||
// resolves it for `platform->sid`, and the two are the same SID by definition.
|
||||
bool ffGetProfilePath(const wchar_t* sidW, FFstrbuf* result) {
|
||||
assert(sidW);
|
||||
|
||||
FF_AUTO_CLOSE_FD HANDLE hProfileList = nullptr;
|
||||
if (!ffRegOpenKeyForRead(HKEY_LOCAL_MACHINE, FF_FOLDERS_HKLM_PROFILE_LIST, &hProfileList, nullptr)) {
|
||||
FF_DEBUG("Cannot open %ls", FF_FOLDERS_HKLM_PROFILE_LIST);
|
||||
return false;
|
||||
}
|
||||
|
||||
FF_AUTO_CLOSE_FD HANDLE hProfile = nullptr;
|
||||
if (!ffRegOpenSubkeyForRead(hProfileList, sidW, &hProfile, nullptr)) {
|
||||
FF_DEBUG("No profile is registered for %ls", sidW);
|
||||
return false;
|
||||
}
|
||||
|
||||
return readExpandedValue(hProfile, L"ProfileImagePath", result);
|
||||
}
|
||||
|
||||
// `Local AppData` and `AppData` share a key, so they are read together.
|
||||
//
|
||||
// The names have to be exact, spaces and all: `Local AppData` is `Local`, one space, `AppData` --
|
||||
// there is no `Local App Data`, and asking for it fails the whole batch below.
|
||||
static const struct {
|
||||
FFKnownFolder folder;
|
||||
const wchar_t* valueNameW;
|
||||
const wchar_t* envW;
|
||||
} shellFolderEntries[] = {
|
||||
{ FF_KNOWN_FOLDER_LOCAL_APP_DATA, L"Local AppData", FF_FOLDERS_ENV_LOCAL_APP_DATA },
|
||||
{ FF_KNOWN_FOLDER_ROAMING_APP_DATA, L"AppData", FF_FOLDERS_ENV_ROAMING_APP_DATA },
|
||||
};
|
||||
|
||||
// One open plus one NtQueryMultipleValueKey resolves both -- and whichever of the two is asked for
|
||||
// first caches the pair, which is what the callers want anyway.
|
||||
//
|
||||
// NtQueryMultipleValueKey is all-or-nothing: one absent name fails the batch, which would push
|
||||
// *both* folders to the environment fallback. That is survivable, because these values are
|
||||
// themselves just `%LOCALAPPDATA%` and `%APPDATA%` expanded -- but it is also invisible, and an
|
||||
// invisible fallback is how a wrong value name went unnoticed here. So a failed batch is retried
|
||||
// one value at a time, and the extra queries are only paid when the batch has already failed.
|
||||
static void resolveUserShellFolders(void) {
|
||||
FFArgBuffer buffers[ARRAY_SIZE(shellFolderEntries)] = {};
|
||||
FFRegValueArg args[ARRAY_SIZE(shellFolderEntries)];
|
||||
for (uint32_t i = 0; i < ARRAY_SIZE(shellFolderEntries); ++i) {
|
||||
args[i] = (FFRegValueArg) { .type = FF_ARG_TYPE_BUFFER, .value = &buffers[i], .name = shellFolderEntries[i].valueNameW };
|
||||
}
|
||||
|
||||
FF_AUTO_CLOSE_FD HANDLE hKey = nullptr;
|
||||
if (ffRegOpenKeyForRead(HKEY_CURRENT_USER, FF_FOLDERS_HKCU_SHELL_FOLDERS, &hKey, nullptr)) {
|
||||
if (ffRegReadValues(hKey, ARRAY_SIZE(shellFolderEntries), args, nullptr)) {
|
||||
for (uint32_t i = 0; i < ARRAY_SIZE(shellFolderEntries); ++i) {
|
||||
if (expandBuffer(&buffers[i], &folderPaths[shellFolderEntries[i].folder])) {
|
||||
folderStates[shellFolderEntries[i].folder] = FF_FOLDER_FOUND;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
FF_DEBUG("Batched read of %ls failed; retrying one value at a time", FF_FOLDERS_HKCU_SHELL_FOLDERS);
|
||||
for (uint32_t i = 0; i < ARRAY_SIZE(shellFolderEntries); ++i) {
|
||||
if (readExpandedValue(hKey, shellFolderEntries[i].valueNameW, &folderPaths[shellFolderEntries[i].folder])) {
|
||||
folderStates[shellFolderEntries[i].folder] = FF_FOLDER_FOUND;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (uint32_t i = 0; i < ARRAY_SIZE(shellFolderEntries); ++i) {
|
||||
// Every buffer has to be freed even when the read failed part way through, which is why the
|
||||
// free is not tied to the success path above.
|
||||
free(buffers[i].data);
|
||||
|
||||
FFKnownFolder folder = shellFolderEntries[i].folder;
|
||||
if (folderStates[folder] == FF_FOLDER_UNRESOLVED &&
|
||||
expandEnvZ(shellFolderEntries[i].envW, &folderPaths[folder])) {
|
||||
folderStates[folder] = FF_FOLDER_FOUND;
|
||||
}
|
||||
if (folderStates[folder] == FF_FOLDER_UNRESOLVED) {
|
||||
folderStates[folder] = FF_FOLDER_MISSING;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static bool resolveFolder(FFKnownFolder folder) {
|
||||
switch (folder) {
|
||||
case FF_KNOWN_FOLDER_LOCAL_APP_DATA:
|
||||
case FF_KNOWN_FOLDER_ROAMING_APP_DATA:
|
||||
resolveUserShellFolders(); // Resolves both of them
|
||||
return folderStates[folder] == FF_FOLDER_FOUND;
|
||||
|
||||
case FF_KNOWN_FOLDER_PROGRAM_DATA:
|
||||
return readExpandedValueFrom(HKEY_LOCAL_MACHINE, FF_FOLDERS_HKLM_SHELL_FOLDERS, L"Common AppData", &folderPaths[folder]) ||
|
||||
expandEnvZ(FF_FOLDERS_ENV_PROGRAM_DATA, &folderPaths[folder]);
|
||||
|
||||
case FF_KNOWN_FOLDER_PROGRAM_FILES:
|
||||
return readExpandedValueFrom(HKEY_LOCAL_MACHINE, FF_FOLDERS_HKLM_CURRENT_VERSION, L"ProgramFilesDir", &folderPaths[folder]) ||
|
||||
expandEnvZ(FF_FOLDERS_ENV_PROGRAM_FILES, &folderPaths[folder]);
|
||||
|
||||
default:
|
||||
assert(false);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
bool ffGetKnownFolderPath(FFKnownFolder folder, FFstrbuf* result) {
|
||||
assert(result);
|
||||
assert(folder < FF_KNOWN_FOLDER_COUNT);
|
||||
|
||||
if (folderStates[folder] == FF_FOLDER_UNRESOLVED) {
|
||||
folderStates[folder] = resolveFolder(folder) ? FF_FOLDER_FOUND : FF_FOLDER_MISSING;
|
||||
}
|
||||
|
||||
if (folderStates[folder] != FF_FOLDER_FOUND) {
|
||||
return false;
|
||||
}
|
||||
|
||||
ffStrbufSet(result, &folderPaths[folder]);
|
||||
return true;
|
||||
}
|
||||
@@ -0,0 +1,47 @@
|
||||
#pragma once
|
||||
|
||||
#include "fastfetch.h"
|
||||
#include "common/io.h"
|
||||
|
||||
#include <wchar.h> // ffGetProfilePath() takes a SID as a wide string
|
||||
|
||||
// The known folders fastfetch needs. They are spelled out instead of taking a REFKNOWNFOLDERID so
|
||||
// that this header does not have to drag in <shlobj.h> -- the call sites include it for reasons of
|
||||
// their own, and a header that only works when <windows.h> came first is a trap.
|
||||
typedef enum FFKnownFolder : uint8_t {
|
||||
FF_KNOWN_FOLDER_LOCAL_APP_DATA,
|
||||
FF_KNOWN_FOLDER_ROAMING_APP_DATA,
|
||||
FF_KNOWN_FOLDER_PROGRAM_DATA,
|
||||
FF_KNOWN_FOLDER_PROGRAM_FILES,
|
||||
FF_KNOWN_FOLDER_COUNT,
|
||||
} FFKnownFolder;
|
||||
|
||||
// Resolves a known folder without initializing the shell.
|
||||
//
|
||||
// The first SHGetKnownFolderPath() call in a process costs ~2.4 ms (measured on Windows 10 22H2):
|
||||
// the shell's known-folder machinery is built on demand, and whichever folder is asked for first
|
||||
// pays for it. Every later call is free, so a process that needs a handful of folders pays that
|
||||
// price once and can never amortize it. Each folder below is instead one registry value or one
|
||||
// environment variable away -- ~0.03 ms for all of them together -- so the shell is never touched.
|
||||
//
|
||||
// The answer is cached: these five folders are asked for about a dozen times per process, and each
|
||||
// answer is a pure function of the registry and this process' environment. That is safe without a
|
||||
// lock, because nothing that resolves a known folder ever runs on the one thread fastfetch creates.
|
||||
//
|
||||
// Returns false, leaving `result` untouched, when the value is missing or unusable, so that the
|
||||
// caller keeps whatever fallback it had for the shell API failing. The path uses backslashes,
|
||||
// exactly like SHGetKnownFolderPath().
|
||||
bool ffGetKnownFolderPath(FFKnownFolder folder, FFstrbuf* result);
|
||||
|
||||
// Resolves the profile directory recorded for `sidW`, a SID string as produced by
|
||||
// RtlConvertSidToUnicodeString().
|
||||
//
|
||||
// There is no FF_KNOWN_FOLDER_PROFILE because the caller already has the SID:
|
||||
// FFPlatform_windows.c resolves it once for `platform->sid`, and deriving it a second time here
|
||||
// would mean a second NtQueryInformationToken(). The answer is deliberately not cached -- this is
|
||||
// called once per process, and from one place.
|
||||
//
|
||||
// Returns false, leaving `result` untouched, when the value is missing or unusable, so that the
|
||||
// caller keeps whatever fallback it had for the shell API failing. The path uses backslashes,
|
||||
// exactly like SHGetKnownFolderPath().
|
||||
bool ffGetProfilePath(const wchar_t* sidW, FFstrbuf* result);
|
||||
@@ -2,6 +2,7 @@
|
||||
#include "common/processing.h"
|
||||
#include "common/strutil.h"
|
||||
#include "common/path.h"
|
||||
#include "common/windows/folders.h"
|
||||
#include "common/windows/unicode.h"
|
||||
#include "common/windows/registry.h"
|
||||
#include "common/mallocHelper.h"
|
||||
@@ -11,7 +12,6 @@
|
||||
#include <windows.h>
|
||||
#include "common/windows/nt.h"
|
||||
#include <ntstatus.h>
|
||||
#include <shlobj.h>
|
||||
|
||||
static uint32_t getNumElements(const char* searchPath, DWORD type, const wchar_t* ignore) {
|
||||
FF_AUTO_CLOSE_FD HANDLE dfd = CreateFileA(searchPath, FILE_LIST_DIRECTORY | SYNCHRONIZE, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, nullptr, OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS, nullptr);
|
||||
@@ -120,11 +120,9 @@ static void detectScoop(FFPackagesResult* result) {
|
||||
ffStrbufSetJsonVal(&scoopPath, yyjson_obj_get(root, "global_path"));
|
||||
}
|
||||
if (scoopPath.length == 0) {
|
||||
PWSTR pPath = nullptr;
|
||||
if (SUCCEEDED(SHGetKnownFolderPath(&FOLDERID_ProgramData, KF_FLAG_DEFAULT, nullptr, &pPath))) {
|
||||
ffStrbufSetWS(&scoopPath, pPath);
|
||||
CoTaskMemFree(pPath);
|
||||
}
|
||||
// Leaving `scoopPath` empty when the lookup fails makes `getNumElements` report 0, which
|
||||
// is what the shell API failing used to do as well.
|
||||
ffGetKnownFolderPath(FF_KNOWN_FOLDER_PROGRAM_DATA, &scoopPath);
|
||||
ffStrbufAppendS(&scoopPath, "/scoop");
|
||||
}
|
||||
ffStrbufAppendS(&scoopPath, "/apps/");
|
||||
|
||||
@@ -3,13 +3,13 @@
|
||||
#include "common/path.h"
|
||||
#include "common/processing.h"
|
||||
#include "common/properties.h"
|
||||
#include "common/windows/folders.h"
|
||||
#include "common/windows/unicode.h"
|
||||
#include "common/windows/registry.h"
|
||||
#include "common/strutil.h"
|
||||
#include "detection/terminalshell/terminalshell.h"
|
||||
#include "terminalfont.h"
|
||||
|
||||
#include <shlobj.h>
|
||||
#include <windows.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
@@ -225,13 +225,11 @@ static void detectWTProfileFromFragmentsIn(const FFstrbuf* fragmentDir, const FF
|
||||
static void detectFromWTFragments(const FFstrbuf* wtProfileId, FFTerminalFontWT* result) {
|
||||
// Windows Terminal merges the user scoped fragments before the machine scoped ones,
|
||||
// so the user scoped fragments are more important
|
||||
static const KNOWNFOLDERID* const fragmentFolderIds[] = { &FOLDERID_LocalAppData, &FOLDERID_ProgramData };
|
||||
static const FFKnownFolder fragmentFolders[] = { FF_KNOWN_FOLDER_LOCAL_APP_DATA, FF_KNOWN_FOLDER_PROGRAM_DATA };
|
||||
|
||||
for (uint32_t i = 0; i < ARRAY_SIZE(fragmentFolderIds); ++i) {
|
||||
PWSTR folderW = nullptr;
|
||||
if (SUCCEEDED(SHGetKnownFolderPath(fragmentFolderIds[i], KF_FLAG_DEFAULT, nullptr, &folderW))) {
|
||||
FF_STRBUF_AUTO_DESTROY fragmentDir = ffStrbufCreateWS(folderW);
|
||||
CoTaskMemFree(folderW);
|
||||
for (uint32_t i = 0; i < ARRAY_SIZE(fragmentFolders); ++i) {
|
||||
FF_STRBUF_AUTO_DESTROY fragmentDir = ffStrbufCreate();
|
||||
if (ffGetKnownFolderPath(fragmentFolders[i], &fragmentDir)) {
|
||||
ffStrbufAppendS(&fragmentDir, "\\Microsoft\\Windows Terminal\\Fragments\\");
|
||||
|
||||
if (ffPathExists(fragmentDir.chars, FF_PATHTYPE_DIRECTORY)) {
|
||||
@@ -258,11 +256,7 @@ static void detectFromWindowsTerminal(const FFstrbuf* terminalExe, FFTerminalFon
|
||||
error = "Error reading Windows Terminal portable settings JSON file";
|
||||
}
|
||||
} else {
|
||||
PWSTR localAppDataW = nullptr;
|
||||
if (SUCCEEDED(SHGetKnownFolderPath(&FOLDERID_LocalAppData, KF_FLAG_DEFAULT, nullptr, &localAppDataW))) {
|
||||
ffStrbufSetWS(&jsonPath, localAppDataW);
|
||||
CoTaskMemFree(localAppDataW);
|
||||
|
||||
if (ffGetKnownFolderPath(FF_KNOWN_FOLDER_LOCAL_APP_DATA, &jsonPath)) {
|
||||
if (ffStrbufContainIgnCaseS(terminalExe, "_8wekyb3d8bbwe\\")) {
|
||||
// Microsoft Store version
|
||||
if (ffStrbufContainIgnCaseS(terminalExe, ".WindowsTerminalPreview_")) {
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
#include "common/processing.h"
|
||||
#include "common/thread.h"
|
||||
#include "common/mallocHelper.h"
|
||||
#include "common/windows/folders.h"
|
||||
#include "common/windows/registry.h"
|
||||
#include "common/windows/unicode.h"
|
||||
#include "common/windows/version.h"
|
||||
@@ -16,7 +17,6 @@
|
||||
#include <tlhelp32.h>
|
||||
#include <ntstatus.h>
|
||||
#include <winternl.h>
|
||||
#include <shlobj.h>
|
||||
|
||||
static uint32_t getShellInfo(FFShellResult* result, uint32_t pid) {
|
||||
uint32_t ppid = 0;
|
||||
@@ -182,12 +182,7 @@ static bool detectDefaultTerminal(FFTerminalResult* result) {
|
||||
ffStrbufSetS(&result->processName, "WindowsTerminal.exe");
|
||||
ffStrbufSetS(&result->prettyName, "WindowsTerminal");
|
||||
|
||||
PWSTR programFiles = nullptr;
|
||||
if (SUCCEEDED(SHGetKnownFolderPath(&FOLDERID_ProgramFiles, KF_FLAG_DEFAULT, nullptr, &programFiles))) {
|
||||
ffStrbufSetWS(&result->exe, programFiles);
|
||||
CoTaskMemFree(programFiles);
|
||||
programFiles = nullptr;
|
||||
|
||||
if (ffGetKnownFolderPath(FF_KNOWN_FOLDER_PROGRAM_FILES, &result->exe)) {
|
||||
ffStrbufAppendS(&result->exe, "\\WindowsApps\\");
|
||||
ffStrbufAppend(&result->exe, &path);
|
||||
ffStrbufAppendS(&result->exe, "\\WindowsTerminal.exe");
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
#include "common/io.h"
|
||||
#include "common/library.h"
|
||||
#include "common/processing.h"
|
||||
#include "common/windows/folders.h"
|
||||
#include "common/windows/nt.h"
|
||||
#include "common/windows/unicode.h"
|
||||
#include "common/windows/version.h"
|
||||
@@ -10,7 +11,6 @@
|
||||
#include <stdalign.h>
|
||||
#include <windows.h>
|
||||
#include <ntstatus.h>
|
||||
#include <shlobj.h>
|
||||
#include <softpub.h>
|
||||
|
||||
typedef enum {
|
||||
@@ -69,16 +69,18 @@ static bool isProcessTrusted(DWORD processId, FFProcessType processType, UNICODE
|
||||
static wchar_t windowsAppsPath[MAX_PATH];
|
||||
static uint32_t windowsAppsPathLen;
|
||||
if (windowsAppsPathLen == 0) {
|
||||
PWSTR pPath = nullptr;
|
||||
if (SUCCEEDED(SHGetKnownFolderPath(&FOLDERID_ProgramFiles, KF_FLAG_DEFAULT, nullptr, &pPath))) {
|
||||
windowsAppsPathLen = (uint32_t) wcslen(pPath);
|
||||
memcpy(windowsAppsPath, pPath, windowsAppsPathLen * sizeof(wchar_t));
|
||||
memcpy(windowsAppsPath + windowsAppsPathLen, L"\\WindowsApps\\", sizeof(L"\\WindowsApps\\"));
|
||||
windowsAppsPathLen += strlen("\\WindowsApps\\");
|
||||
FF_STRBUF_AUTO_DESTROY programFiles = ffStrbufCreate();
|
||||
ULONG length = 0; // in bytes, including the null terminator
|
||||
if (ffGetKnownFolderPath(FF_KNOWN_FOLDER_PROGRAM_FILES, &programFiles)) {
|
||||
ffStrbufAppendS(&programFiles, "\\WindowsApps\\");
|
||||
if (NT_SUCCESS(RtlUTF8ToUnicodeN(windowsAppsPath, (ULONG) sizeof(windowsAppsPath), &length, programFiles.chars, (ULONG) programFiles.length + 1))) {
|
||||
windowsAppsPathLen = (uint32_t) (length / sizeof(wchar_t) - 1);
|
||||
} else {
|
||||
windowsAppsPathLen = -1u;
|
||||
}
|
||||
} else {
|
||||
windowsAppsPathLen = -1u;
|
||||
}
|
||||
CoTaskMemFree(pPath);
|
||||
}
|
||||
if (windowsAppsPathLen != -1u &&
|
||||
(buffer->Length <= windowsAppsPathLen * sizeof(wchar_t) || // Path is too short to be in WindowsApps
|
||||
@@ -230,14 +232,10 @@ const char* ffDetectWMVersion(const FFstrbuf* wmName, FFstrbuf* result, [[maybe_
|
||||
}
|
||||
|
||||
if (ffStrbufEqualS(wmName, "dwm.exe")) {
|
||||
PWSTR pPath = nullptr;
|
||||
if (SUCCEEDED(SHGetKnownFolderPath(&FOLDERID_System, KF_FLAG_DEFAULT, nullptr, &pPath))) {
|
||||
wchar_t fullPath[MAX_PATH];
|
||||
wcscpy(fullPath, pPath);
|
||||
wcscat(fullPath, L"\\dwm.exe");
|
||||
ffGetFileVersion(fullPath, nullptr, result);
|
||||
}
|
||||
CoTaskMemFree(pPath);
|
||||
wchar_t exePath[MAX_PATH];
|
||||
_snwprintf(exePath, ARRAY_SIZE(exePath), L"%ls\\system32\\%ls", (const wchar_t*) SharedUserData->NtSystemRoot, L"dwm.exe");
|
||||
ffGetFileVersion(exePath, nullptr, result);
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
return "Not supported on this platform";
|
||||
|
||||
Reference in New Issue
Block a user