Global: fixes issues found by AI

This commit is contained in:
李通洲
2026-09-24 21:21:02 +08:00
committed by Carter Li
parent 00ccd8be12
commit bba45429ae
24 changed files with 387 additions and 80 deletions
+6 -5
View File
@@ -172,23 +172,24 @@ static const char* parseBinder(FFlist* results) {
return error;
}
uint32_t handle = 0;
error = ffBinderLookupService(&binder, FF_BATTERY_ANDROID_SERVICE, FF_BINDER_SM_GET_SERVICE, &handle);
// Released on every path out of this function, including the ones below that return early.
[[gnu::cleanup(ffBinderServiceHandleRelease)]] FFBinderServiceHandle service = { .binder = &binder };
error = ffBinderLookupService(&binder, FF_BATTERY_ANDROID_SERVICE, FF_BINDER_SM_GET_SERVICE, &service.handle);
if (error != nullptr) {
FF_DEBUG("Looking up the \"%s\" service failed: %s", FF_BATTERY_ANDROID_SERVICE, error);
return error;
}
FF_DEBUG("The \"%s\" service is handle %u", FF_BATTERY_ANDROID_SERVICE, handle);
FF_DEBUG("The \"%s\" service is handle %u", FF_BATTERY_ANDROID_SERVICE, service.handle);
uint64_t capacity = 0;
error = getProperty(&binder, handle, FF_BATTERY_ANDROID_PROPERTY_CAPACITY, &capacity);
error = getProperty(&binder, service.handle, FF_BATTERY_ANDROID_PROPERTY_CAPACITY, &capacity);
if (error != nullptr) {
FF_DEBUG("The capacity is what failed, so no battery is reported at all");
return error;
}
uint64_t status = 0;
error = getProperty(&binder, handle, FF_BATTERY_ANDROID_PROPERTY_STATUS, &status);
error = getProperty(&binder, service.handle, FF_BATTERY_ANDROID_PROPERTY_STATUS, &status);
if (error != nullptr) {
FF_DEBUG("The status is what failed, so no battery is reported at all");
return error;
@@ -271,6 +271,13 @@ static void setClassOfDevice(FFstrbuf* type, IOBluetoothDevice* ioDevice) {
static FFBluetoothResult* findByName(FFlist* devices, const char* name) {
FFBluetoothResult* found = nullptr;
FF_LIST_FOR_EACH (FFBluetoothResult, device, *devices) {
// Only the classic half counts. The LE pass runs after the classic one and appends its
// entries to the same list, so the peripheral an earlier iteration of this very loop created
// would otherwise make its own name look ambiguous -- and the rule is there to keep one name
// from standing for two classic devices, not to count the LE copy of one of them.
if (!(device->deviceType & FF_BLUETOOTH_DEVICE_TYPE_CLASSIC_BIT)) {
continue;
}
if (!ffStrEqualsIgnCase(device->name.chars, name)) {
continue;
}
+15 -1
View File
@@ -105,7 +105,14 @@ static bool readHidReport(HANDLE hHidFile, uint8_t* buffer, uint32_t length, DWO
*nBytes = read;
return true;
}
CancelIo(hHidFile);
// A timeout leaves the read pending, and the driver writes into `buffer` and `overlapped` when
// it eventually completes -- both of which are gone by then: the buffer belongs to the caller
// and the OVERLAPPED sits on this frame. So the read is cancelled by its own OVERLAPPED rather
// than by handle (CancelIo would take every other read on the same handle with it), and the
// cancellation is waited for before returning.
CancelIoEx(hHidFile, &overlapped);
GetOverlappedResult(hHidFile, &overlapped, &read, TRUE);
return false;
}
@@ -299,6 +306,13 @@ static FFGamepadBatteryKind detectBatteryKind(uint32_t vendorId, uint32_t produc
return FF_GAMEPAD_BATTERY_NONE;
}
// The Nintendo list is deliberately shorter than the name table above. The Charging Grip
// (0x200E) is a rail that charges Joy-Con and has no battery of its own, and the SNES
// online controller (0x2017) and the Switch 2 controllers (0x2066-0x2069) are ones SDL --
// which is what this parsing follows -- reports no battery for: `SDL_hidapi_switch2.c` has
// no power reporting at all, and the SNES entry is commented out of `controller_list.h`.
// Reading the Switch state report from them would be a guess, so they keep the module's
// "unknown".
case 0x057E: // Nintendo
switch (productId) {
case 0x2006: // Joy-Con L
@@ -80,6 +80,10 @@ static void applyWTProfile(yyjson_val* profile, uint8_t priority, FFTerminalFont
// A fragment either defines a profile with "guid" or updates an existing one with "updates",
// the latter being more important, so it is looked up first.
// Note that "guid" and "updates" may be missing: yyjson_get_str() returns nullptr then.
// `fromUpdates` may be null: it is only useful to a caller that ranks the two apart, which a
// fragment does. settings.json does not -- an "updates" entry and a "guid" entry in profiles.list
// both sit at the same point of the inheritance chain, and the two passes above are what pick the
// right one between them.
static yyjson_val* findWTProfileInArray(yyjson_val* profiles, const FFstrbuf* wtProfileId, bool* fromUpdates) {
if (!yyjson_is_arr(profiles)) {
return nullptr;
@@ -91,7 +95,9 @@ static yyjson_val* findWTProfileInArray(yyjson_val* profiles, const FFstrbuf* wt
yyjson_arr_foreach (profiles, idx, max, profile) {
const char* id = yyjson_get_str(yyjson_obj_get(profile, pass == 0 ? "updates" : "guid"));
if (id && ffStrbufEqualS(wtProfileId, id)) {
*fromUpdates = pass == 0;
if (fromUpdates) {
*fromUpdates = pass == 0;
}
return profile;
}
}
@@ -122,8 +128,7 @@ static const char* detectFromWTSettings(FFstrbuf* content, const FFstrbuf* wtPro
}
if (wtProfileId->length > 0) {
bool fromUpdates = false;
yyjson_val* profile = findWTProfileInArray(yyjson_obj_get(profiles, "list"), wtProfileId, &fromUpdates);
yyjson_val* profile = findWTProfileInArray(yyjson_obj_get(profiles, "list"), wtProfileId, nullptr);
if (profile) {
applyWTProfile(profile, WT_FONT_PRIORITY_PROFILE, result);
}
@@ -167,25 +172,40 @@ static void detectWTProfileFromFragmentsIn(const FFstrbuf* fragmentDir, const FF
const uint32_t baseLength = path.length;
ffStrbufAppendC(&path, '*');
WIN32_FIND_DATAA entry;
FF_AUTO_CLOSE_DIR HANDLE hFind = FindFirstFileA(path.chars, &entry);
// The wide enumeration, because the directory above came from SHGetKnownFolderPath(): a profile
// whose name is not ASCII would otherwise hand FindFirstFileA a UTF-8 path that it reads as ANSI,
// and no fragment would be found at all.
wchar_t pathW[MAX_PATH];
if (!NT_SUCCESS(RtlUTF8ToUnicodeN(pathW, (ULONG) sizeof(pathW), nullptr, path.chars, (ULONG) path.length + 1))) {
return;
}
WIN32_FIND_DATAW entry;
FF_AUTO_CLOSE_DIR HANDLE hFind = FindFirstFileW(pathW, &entry);
if (hFind == INVALID_HANDLE_VALUE) {
return;
}
do {
if (!(entry.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) || entry.cFileName[0] == '.') {
if (!(entry.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) || entry.cFileName[0] == L'.') {
continue;
}
ffStrbufSubstrBefore(&path, baseLength);
ffStrbufAppendS(&path, entry.cFileName);
ffStrbufAppendWS(&path, entry.cFileName);
ffStrbufAppendC(&path, '\\');
const uint32_t appLength = path.length;
ffStrbufAppendS(&path, "*.json");
WIN32_FIND_DATAA fileEntry;
FF_AUTO_CLOSE_DIR HANDLE hFile = FindFirstFileA(path.chars, &fileEntry);
// The path changed, so the pattern has to be converted again. A name that cannot be converted
// is skipped rather than failing the whole scan: the next iteration rebuilds `path` from
// `baseLength` anyway.
if (!NT_SUCCESS(RtlUTF8ToUnicodeN(pathW, (ULONG) sizeof(pathW), nullptr, path.chars, (ULONG) path.length + 1))) {
continue;
}
WIN32_FIND_DATAW fileEntry;
FF_AUTO_CLOSE_DIR HANDLE hFile = FindFirstFileW(pathW, &fileEntry);
if (hFile != INVALID_HANDLE_VALUE) {
do {
if (fileEntry.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) {
@@ -193,13 +213,13 @@ static void detectWTProfileFromFragmentsIn(const FFstrbuf* fragmentDir, const FF
}
ffStrbufSubstrBefore(&path, appLength);
ffStrbufAppendS(&path, fileEntry.cFileName);
ffStrbufAppendWS(&path, fileEntry.cFileName);
applyWTFragmentFile(path.chars, wtProfileId, result);
} while (FindNextFileA(hFile, &fileEntry));
} while (FindNextFileW(hFile, &fileEntry));
}
ffStrbufSubstrBefore(&path, baseLength);
} while (FindNextFileA(hFind, &entry));
} while (FindNextFileW(hFind, &entry));
}
static void detectFromWTFragments(const FFstrbuf* wtProfileId, FFTerminalFontWT* result) {
+19 -5
View File
@@ -4,6 +4,7 @@
static FFlist first;
static double startTick;
static FFTopTypes preparedTypes;
void ffPrepareTopProcesses(FFTopTypes showTypes) {
if ((showTypes & (FF_TOP_TYPE_CPU | FF_TOP_TYPE_DISK)) == 0) {
@@ -11,13 +12,26 @@ void ffPrepareTopProcesses(FFTopTypes showTypes) {
}
if (startTick != 0) {
return; // Already prepared
// Already prepared. The baseline above is one snapshot shared by every `top` module in the
// run, so a second module asking for a different set of counters would be measured against a
// snapshot that never collected them: its difference comes out as the process lifetime
// average rather than a rate, and the `newItem->... < oldItem->...` checks in
// `ffDetectTopProcesses` cannot tell, because the missing counters read as 0 there. This is
// a configuration fastfetch cannot serve, so it is refused rather than printed wrong.
if (showTypes != preparedTypes) {
fputs("Error: `top` modules with different `showTypes` cannot share a run\n", stderr);
fputs(" The baseline snapshot is collected once and reused, so it only holds the counters the first module asked for.\n", stderr);
fputs(" Give the modules the same `showTypes`, or run them in separate invocations.\n", stderr);
exit(1);
}
return;
}
// `showTypes` cannot change between this call and `ffDetectTopProcesses`: `ffPrepareCommandOption`
// and `parseStructureCommand` both build the options through `initStructureModuleOptions`, which
// merges the module object from the JSON config. So the baseline always matches what the second
// snapshot collects and needs no re-validation.
// Within one module `showTypes` cannot change between this call and `ffDetectTopProcesses`:
// `ffPrepareCommandOption` and `parseStructureCommand` both build the options through
// `initStructureModuleOptions`, which merges the module object from the JSON config, so the
// baseline always matches what the second snapshot collects.
preparedTypes = showTypes;
ffListInit(&first);
startTick = ffTimeGetTick();
ffTopGetProcessSnapshot(&first, showTypes);
+3 -2
View File
@@ -1,6 +1,5 @@
#include "wallpaper.h"
#include "common/settings.h"
#include "common/apple/osascript.h"
#import <Foundation/Foundation.h>
#import <AppKit/AppKit.h>
@@ -8,7 +7,9 @@
const char* detectFromPlist(FFstrbuf* result) {
// For Sonoma and later (macOS 14.0+)
// https://github.com/JohnCoates/Aerial/issues/1332
NSError* error;
// Initialised because the check below reads it: the method is documented to assign it, but an
// uninitialised read decides between two sources of the wallpaper, so it is not worth relying on.
NSError* error = nil;
NSString* fileName = [NSString stringWithFormat:@"file://%s/Library/Application Support/com.apple.wallpaper/Store/Index.plist", instance.state.platform.homeDir.chars];
NSDictionary* dict = [NSDictionary dictionaryWithContentsOfURL:[NSURL URLWithString:fileName]
error:&error];
+22 -7
View File
@@ -894,8 +894,21 @@ static const char* callIntMethod(FFBinder* binder, uint32_t handle, const char*
return nullptr;
}
// `getOwnPackage` writes at most this many bytes, terminator included.
#define FF_WIFI_ANDROID_PACKAGE_SIZE 128
// The parcel for `getConnectionInfo` is the interface token, the package name and the -1 that
// stands for a null `callingFeatureId`. A string16 costs 4 + 2 * (length + 1) bytes padded to four,
// and the token puts three int32 in front of the descriptor. Deriving the size from the two names
// rather than rounding it up is what keeps the longest package name `getOwnPackage` can produce
// from marking the parcel truncated -- that fails the whole detection, rather than degrading to a
// call without a name.
#define FF_WIFI_ANDROID_PARCEL_SIZE \
(12 + ((4 + 2 * sizeof(FF_WIFI_ANDROID_DESCRIPTOR) + 3) & ~3u) \
+ ((4 + 2 * FF_WIFI_ANDROID_PACKAGE_SIZE + 3) & ~3u) + 4)
static const char* detectWithBinder(FFlist* result) {
char package[128];
char package[FF_WIFI_ANDROID_PACKAGE_SIZE];
if (!getOwnPackage(package, sizeof(package))) {
return "Cannot determine the package name of this process";
}
@@ -913,15 +926,17 @@ static const char* detectWithBinder(FFlist* result) {
return error;
}
uint32_t handle = 0;
error = ffBinderLookupService(&binder, FF_WIFI_ANDROID_SERVICE, FF_BINDER_SM_GET_SERVICE, &handle);
// Released on every path out of this function, including the ones below that return early: the
// references the lookup takes are the process's only claim on the service node.
[[gnu::cleanup(ffBinderServiceHandleRelease)]] FFBinderServiceHandle service = { .binder = &binder };
error = ffBinderLookupService(&binder, FF_WIFI_ANDROID_SERVICE, FF_BINDER_SM_GET_SERVICE, &service.handle);
if (error != nullptr) {
return error;
}
FF_DEBUG("The \"%s\" service is handle %u, %s is transaction %d",
FF_WIFI_ANDROID_SERVICE, handle, FF_WIFI_ANDROID_GET_CONNECTION_INFO, transaction);
FF_WIFI_ANDROID_SERVICE, service.handle, FF_WIFI_ANDROID_GET_CONNECTION_INFO, transaction);
uint8_t parcelBuffer[256];
uint8_t parcelBuffer[FF_WIFI_ANDROID_PARCEL_SIZE];
FFBinderParcel parcel = ffBinderParcelCreate(parcelBuffer, sizeof(parcelBuffer));
ffBinderParcelPutInterfaceToken(&parcel, FF_WIFI_ANDROID_DESCRIPTOR);
ffBinderParcelPutString16(&parcel, package);
@@ -930,7 +945,7 @@ static const char* detectWithBinder(FFlist* result) {
uint8_t replyBuffer[FF_WIFI_ANDROID_REPLY_SIZE];
FFBinderReply reply = ffBinderReplyCreate(replyBuffer, sizeof(replyBuffer));
error = ffBinderTransact(&binder, handle, (uint32_t) transaction, &parcel, &reply);
error = ffBinderTransact(&binder, service.handle, (uint32_t) transaction, &parcel, &reply);
if (error != nullptr) {
return error;
}
@@ -957,7 +972,7 @@ static const char* detectWithBinder(FFlist* result) {
// that is up. WIFI_STATE_DISABLING is a radio that is still up, so only the one value means
// down, and WIFI_STATE_UNKNOWN is the service declining to say.
int32_t state = 0;
const char* stateError = callIntMethod(&binder, handle, FF_WIFI_ANDROID_GET_WIFI_ENABLED_STATE, &state);
const char* stateError = callIntMethod(&binder, service.handle, FF_WIFI_ANDROID_GET_WIFI_ENABLED_STATE, &state);
if (stateError == nullptr && state != FF_WIFI_ANDROID_WIFI_STATE_UNKNOWN) {
connection.up = state != FF_WIFI_ANDROID_WIFI_STATE_DISABLED;
connection.upKnown = true;