Global (Windows): eliminates SHGetKnownFolderPath

This commit is contained in:
Carter Li
2026-09-28 23:02:34 +08:00
parent cdbe8f54c6
commit 4fb57bb39b
8 changed files with 353 additions and 83 deletions
+1
View File
@@ -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
+42 -42
View File
@@ -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);
+237
View File
@@ -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;
}
+47
View File
@@ -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);
+4 -6
View File
@@ -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");
+14 -16
View File
@@ -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";