From 6979b1efc57bbac0a38dd696d6e546f31abd4f23 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=9D=8E=E9=80=9A=E6=B4=B2?= Date: Sun, 26 Apr 2026 16:00:01 +0800 Subject: [PATCH 01/92] Dep (3rdparty): upgrades display-library --- src/3rdparty/display-library/adl_defines.h | 229 +++++++++++------- src/3rdparty/display-library/adl_sdk.h | 8 +- src/3rdparty/display-library/adl_structures.h | 65 +++-- src/3rdparty/display-library/repo.json | 2 +- 4 files changed, 173 insertions(+), 131 deletions(-) diff --git a/src/3rdparty/display-library/adl_defines.h b/src/3rdparty/display-library/adl_defines.h index b3aa731b64..f24143e73f 100644 --- a/src/3rdparty/display-library/adl_defines.h +++ b/src/3rdparty/display-library/adl_defines.h @@ -21,7 +21,7 @@ // SOFTWARE. /// \file adl_defines.h -/// \brief Contains all definitions exposed by ADL for \ALL platforms.\n Included in ADL SDK +/// \brief Contains all definitions exposed by ADL for \WIN platforms.\n Included in ADL SDK /// /// This file contains all definitions used by ADL. /// The ADL definitions include the following: @@ -61,6 +61,8 @@ #define ADL_ADAPTER_INDEX_ALL -1 /// Defines APIs with iOption none #define ADL_MAIN_API_OPTION_NONE 0 +/// Defines the maximum of RPC endpoint +#define ADL_MAX_RPC_ENDPOINT 32 /// @} /// \name Definitions for iOption parameter used by @@ -141,6 +143,12 @@ #define ADL_ERR_FEATURESYNC_NOT_STARTED -24 /// Adapter is in an invalid power state #define ADL_ERR_INVALID_POWER_STATE -25 +/// The RPC server is in busy state +#define ADL_ERR_SERVER_BUSY -26 +/// The GPU is occupied by application which cannot be powered off +#define ADL_ERR_GPU_IN_USE -27 +/// The general RPC connection error +#define ADL_ERR_RPC -28 /// @} /// @@ -380,6 +388,7 @@ #define ADL_BUSTYPE_PCIE_GEN2 3 /* PCI Express 2nd generation bus */ #define ADL_BUSTYPE_PCIE_GEN3 4 /* PCI Express 3rd generation bus */ #define ADL_BUSTYPE_PCIE_GEN4 5 /* PCI Express 4th generation bus */ +#define ADL_BUSTYPE_PCIE_GEN5 6 /* PCI Express 5th generation bus */ /// @} /// \defgroup define_ws_caps Workstation Capabilities @@ -872,7 +881,7 @@ typedef enum ADLThreadingModel { ADL_THREADING_UNLOCKED = 0, /*!< Default behavior. ADL will not enforce serialization of ADL API executions by multiple threads. Multiple threads will be allowed to enter to ADL at the same time. Note that ADL library is not guaranteed to be thread-safe. Client that calls ADL_Main_Control_Create have to provide its own mechanism for ADL calls serialization. */ - ADL_THREADING_LOCKED /*!< ADL will enforce serialization of ADL API when called by multiple threads. Only single thread will be allowed to enter ADL API at the time. This option makes ADL calls thread-safe. You shouldn't use this option if ADL calls will be executed on Linux on x-server rendering thread. It can cause the application to hung. */ + ADL_THREADING_LOCKED /*!< ADL will enforce serialization of ADL API when called by multiple threads. Only single thread will be allowed to enter ADL API at the time. This option makes ADL calls thread-safe. */ }ADLThreadingModel; /// @} @@ -1844,12 +1853,12 @@ enum ADLOD8FeatureControl ADL_OD8_TEMPERATURE_FAN = 1 << 6, //FanTargetTemperature ADL_OD8_TEMPERATURE_SYSTEM = 1 << 7, //MaxOpTemp ADL_OD8_MEMORY_TIMING_TUNE = 1 << 8, - ADL_OD8_FAN_ZERO_RPM_CONTROL = 1 << 9 , - ADL_OD8_AUTO_UV_ENGINE = 1 << 10, //Auto under voltage - ADL_OD8_AUTO_OC_ENGINE = 1 << 11, //Auto overclock engine - ADL_OD8_AUTO_OC_MEMORY = 1 << 12, //Auto overclock memory - ADL_OD8_FAN_CURVE = 1 << 13, //Fan curve - ADL_OD8_WS_AUTO_FAN_ACOUSTIC_LIMIT = 1 << 14, //Workstation Manual Fan controller + ADL_OD8_FAN_ZERO_RPM_CONTROL = 1 << 9, + ADL_OD8_AUTO_UV_ENGINE = 1 << 10, //Auto under voltage + ADL_OD8_AUTO_OC_ENGINE = 1 << 11, //Auto overclock engine + ADL_OD8_AUTO_OC_MEMORY = 1 << 12, //Auto overclock memory + ADL_OD8_FAN_CURVE = 1 << 13, //Fan curve + ADL_OD8_WS_AUTO_FAN_ACOUSTIC_LIMIT = 1 << 14, //Workstation Manual Fan controller ADL_OD8_GFXCLK_QUADRATIC_CURVE = 1 << 15, ADL_OD8_OPTIMIZED_GPU_POWER_MODE = 1 << 16, ADL_OD8_ODVOLTAGE_LIMIT = 1 << 17, @@ -1860,43 +1869,45 @@ enum ADLOD8FeatureControl ADL_OD8_TDC_LIMIT = 1 << 22, //TDC slider ADL_OD8_FULL_CONTROL_MODE = 1 << 23, //Full control ADL_OD8_POWER_SAVING_FEATURE_CONTROL = 1 << 24, //Power saving feature control - ADL_OD8_POWER_GAUGE = 1 << 25 //Power Gauge + ADL_OD8_ACTIMING_PARAMETERS_TUNE = 1 << 25, // AC Timing Parameters Tuning + ADL_OD8_OVERDRIVE_INTERFACE = 1 << 26, // Version info feature ID for RSX, do not expose in ADL. + ADL_OD8_AUTO_UV_ENGINE_V2 = 1 << 27, // Auto UV 2.0 with actual stress testing. + ADL_OD8_POWER_GAUGE = 1 << 28 //Power Gauge }; - typedef enum ADLOD8SettingId { - OD8_GFXCLK_FMAX = 0, - OD8_GFXCLK_FMIN, - OD8_GFXCLK_FREQ1, - OD8_GFXCLK_VOLTAGE1, - OD8_GFXCLK_FREQ2, - OD8_GFXCLK_VOLTAGE2, - OD8_GFXCLK_FREQ3, - OD8_GFXCLK_VOLTAGE3, - OD8_UCLK_FMAX, - OD8_POWER_PERCENTAGE, - OD8_FAN_MIN_SPEED, - OD8_FAN_ACOUSTIC_LIMIT, - OD8_FAN_TARGET_TEMP, - OD8_OPERATING_TEMP_MAX, - OD8_AC_TIMING, - OD8_FAN_ZERORPM_CONTROL, - OD8_AUTO_UV_ENGINE_CONTROL, - OD8_AUTO_OC_ENGINE_CONTROL, - OD8_AUTO_OC_MEMORY_CONTROL, - OD8_FAN_CURVE_TEMPERATURE_1, - OD8_FAN_CURVE_SPEED_1, - OD8_FAN_CURVE_TEMPERATURE_2, - OD8_FAN_CURVE_SPEED_2, - OD8_FAN_CURVE_TEMPERATURE_3, - OD8_FAN_CURVE_SPEED_3, - OD8_FAN_CURVE_TEMPERATURE_4, - OD8_FAN_CURVE_SPEED_4, - OD8_FAN_CURVE_TEMPERATURE_5, - OD8_FAN_CURVE_SPEED_5, - OD8_WS_FAN_AUTO_FAN_ACOUSTIC_LIMIT, - OD8_GFXCLK_CURVE_COEFFICIENT_A, // As part of the agreement with UI team, the min/max voltage limits for the + OD8_GFXCLK_FMAX = 0, + OD8_GFXCLK_FMIN, + OD8_GFXCLK_FREQ1, + OD8_GFXCLK_VOLTAGE1, + OD8_GFXCLK_FREQ2, + OD8_GFXCLK_VOLTAGE2, + OD8_GFXCLK_FREQ3, + OD8_GFXCLK_VOLTAGE3, + OD8_UCLK_FMAX, + OD8_POWER_PERCENTAGE, + OD8_FAN_MIN_SPEED, //10 + OD8_FAN_ACOUSTIC_LIMIT, + OD8_FAN_TARGET_TEMP, + OD8_OPERATING_TEMP_MAX, + OD8_AC_TIMING, + OD8_FAN_ZERORPM_CONTROL, + OD8_AUTO_UV_ENGINE_CONTROL, + OD8_AUTO_OC_ENGINE_CONTROL, + OD8_AUTO_OC_MEMORY_CONTROL, + OD8_FAN_CURVE_TEMPERATURE_1, + OD8_FAN_CURVE_SPEED_1, //20 + OD8_FAN_CURVE_TEMPERATURE_2, + OD8_FAN_CURVE_SPEED_2, + OD8_FAN_CURVE_TEMPERATURE_3, + OD8_FAN_CURVE_SPEED_3, + OD8_FAN_CURVE_TEMPERATURE_4, + OD8_FAN_CURVE_SPEED_4, + OD8_FAN_CURVE_TEMPERATURE_5, + OD8_FAN_CURVE_SPEED_5, + OD8_WS_FAN_AUTO_FAN_ACOUSTIC_LIMIT, + OD8_GFXCLK_CURVE_COEFFICIENT_A, // 30 As part of the agreement with UI team, the min/max voltage limits for the OD8_GFXCLK_CURVE_COEFFICIENT_B, // quadratic curve graph will be stored in the min and max limits of OD8_GFXCLK_CURVE_COEFFICIENT_C, // coefficient a, b and c. A, b and c themselves do not have limits. OD8_GFXCLK_CURVE_VFT_FMIN, @@ -1906,7 +1917,7 @@ typedef enum ADLOD8SettingId OD8_OD_VOLTAGE,// RSX - voltage offset feature OD8_ADV_OC_LIMITS_SETTING, OD8_PER_ZONE_GFX_VOLTAGE_OFFSET_POINT_1, - OD8_PER_ZONE_GFX_VOLTAGE_OFFSET_POINT_2, + OD8_PER_ZONE_GFX_VOLTAGE_OFFSET_POINT_2, //40 OD8_PER_ZONE_GFX_VOLTAGE_OFFSET_POINT_3, OD8_PER_ZONE_GFX_VOLTAGE_OFFSET_POINT_4, OD8_PER_ZONE_GFX_VOLTAGE_OFFSET_POINT_5, @@ -1915,8 +1926,33 @@ typedef enum ADLOD8SettingId OD8_GFX_VOLTAGE_LIMIT_SETTING, OD8_TDC_PERCENTAGE, OD8_FULL_CONTROL_MODE_SETTING, + OD8_FULL_CONTROL_MODE_GFXCLK, + OD8_FULL_CONTROL_MODE_UCLK, // 50, OD8_IDLE_POWER_SAVING_FEATURE_CONTROL, OD8_RUNTIME_POWER_SAVING_FEATURE_CONTROL, + OD8_FULL_CONTROL_MODE_FEATURE_CONTROL, + OD8_PER_ZONE_GFX_VOLTAGE_OFFSET_FREQ_ANCHOR_1, + OD8_PER_ZONE_GFX_VOLTAGE_OFFSET_FREQ_ANCHOR_2, + OD8_PER_ZONE_GFX_VOLTAGE_OFFSET_FREQ_ANCHOR_3, + OD8_PER_ZONE_GFX_VOLTAGE_OFFSET_FREQ_ANCHOR_4, + OD8_PER_ZONE_GFX_VOLTAGE_OFFSET_FREQ_ANCHOR_5, + OD8_PER_ZONE_GFX_VOLTAGE_OFFSET_FREQ_ANCHOR_6, + OD8_PER_ZONE_GFX_VOLTAGE_OFFSET_VOLTAGE_LIMIT, //60 + OD8_ACTIMING_PARAMETER_TRRDS, + OD8_ACTIMING_PARAMETER_TCL, + OD8_ACTIMING_PARAMETER_TCWL, + OD8_ACTIMING_PARAMETER_TRCDRD, + OD8_ACTIMING_PARAMETER_TRCDWR, + OD8_ACTIMING_PARAMETER_TRAS, + OD8_ACTIMING_PARAMETER_TRPAB, + OD8_ACTIMING_PARAMETER_TRFC, + OD8_ACTIMING_PARAMETER_TRFCPB, + OD8_ACTIMING_PARAMETER_TRREFD, //70 + OD8_ACTIMING_PARAMETER_TREF, + OD8_ACTIMING_PARAMETER_TWR, + OD8_ACTIMING_PARAMETER_TWTRS, + OD8_OVERDRIVE_INTERFACE_ID, //74 + OD8_AUTO_UV_ENGINE_V2_ID, //75 OD8_POWER_GAUGE, OD8_COUNT } ADLOD8SettingId; @@ -1980,28 +2016,28 @@ typedef enum ADLSensorType PMLOG_SSDGPU_POWERLIMIT = 49, // DGPU Power limit PMLOG_TEMPERATURE_HOTSPOT_GCD = 50, PMLOG_TEMPERATURE_HOTSPOT_MCD = 51, - PMLOG_THROTTLER_TEMP_EDGE_PERCENTAGE = 52, - PMLOG_THROTTLER_TEMP_HOTSPOT_PERCENTAGE = 53, - PMLOG_THROTTLER_TEMP_HOTSPOT_GCD_PERCENTAGE = 54, - PMLOG_THROTTLER_TEMP_HOTSPOT_MCD_PERCENTAGE = 55, - PMLOG_THROTTLER_TEMP_MEM_PERCENTAGE = 56, - PMLOG_THROTTLER_TEMP_VR_GFX_PERCENTAGE = 57, - PMLOG_THROTTLER_TEMP_VR_MEM0_PERCENTAGE = 58, - PMLOG_THROTTLER_TEMP_VR_MEM1_PERCENTAGE = 59, - PMLOG_THROTTLER_TEMP_VR_SOC_PERCENTAGE = 60, - PMLOG_THROTTLER_TEMP_LIQUID0_PERCENTAGE = 61, - PMLOG_THROTTLER_TEMP_LIQUID1_PERCENTAGE = 62, - PMLOG_THROTTLER_TEMP_PLX_PERCENTAGE = 63, - PMLOG_THROTTLER_TDC_GFX_PERCENTAGE = 64, - PMLOG_THROTTLER_TDC_SOC_PERCENTAGE = 65, - PMLOG_THROTTLER_TDC_USR_PERCENTAGE = 66, - PMLOG_THROTTLER_PPT0_PERCENTAGE = 67, - PMLOG_THROTTLER_PPT1_PERCENTAGE = 68, - PMLOG_THROTTLER_PPT2_PERCENTAGE = 69, - PMLOG_THROTTLER_PPT3_PERCENTAGE = 70, - PMLOG_THROTTLER_FIT_PERCENTAGE = 71, - PMLOG_THROTTLER_GFX_APCC_PLUS_PERCENTAGE = 72, - PMLOG_BOARD_POWER = 73, + PMLOG_THROTTLE_PERCENTAGE_TEMP_GFX = 52, + PMLOG_THROTTLE_PERCENTAGE_TEMP_MEM = 53, + PMLOG_THROTTLE_PERCENTAGE_TEMP_VR = 54, + PMLOG_THROTTLE_PERCENTAGE_POWER = 55, + PMLOG_THROTTLE_PERCENTAGE_TDC = 56, + PMLOG_THROTTLE_PERCENTAGE_VMAX = 57, + PMLOG_BUS_CURR_MAX_SPEED = 58, + PMLOG_RESERVED_1 = 59, //Currently Unused + PMLOG_RESERVED_2 = 60, //Currently Unused + PMLOG_RESERVED_3 = 61, //Currently Unused + PMLOG_RESERVED_4 = 62, //Currently Unused + PMLOG_RESERVED_5 = 63, //Currently Unused + PMLOG_RESERVED_6 = 64, //Currently Unused + PMLOG_RESERVED_7 = 65, //Currently Unused + PMLOG_RESERVED_8 = 66, //Currently Unused + PMLOG_RESERVED_9 = 67, //Currently Unused + PMLOG_RESERVED_10 = 68, //Currently Unused + PMLOG_RESERVED_11 = 69, //Currently Unused + PMLOG_RESERVED_12 = 70, //Currently Unused + PMLOG_RESERVED_13 = 71, //Currently Unused + PMLOG_RESERVED_14 = 72, + PMLOG_BOARD_POWER = 73, PMLOG_MAX_SENSORS_REAL } ADLSensorType; @@ -2068,28 +2104,29 @@ typedef enum ADL_PMLOG_SENSORS ADL_PMLOG_SSDGPU_POWERLIMIT = 49, // DGPU Power limit ADL_PMLOG_TEMPERATURE_HOTSPOT_GCD = 50, ADL_PMLOG_TEMPERATURE_HOTSPOT_MCD = 51, - ADL_PMLOG_THROTTLER_TEMP_EDGE_PERCENTAGE = 52, - ADL_PMLOG_THROTTLER_TEMP_HOTSPOT_PERCENTAGE = 53, - ADL_PMLOG_THROTTLER_TEMP_HOTSPOT_GCD_PERCENTAGE = 54, - ADL_PMLOG_THROTTLER_TEMP_HOTSPOT_MCD_PERCENTAGE = 55, - ADL_PMLOG_THROTTLER_TEMP_MEM_PERCENTAGE = 56, - ADL_PMLOG_THROTTLER_TEMP_VR_GFX_PERCENTAGE = 57, - ADL_PMLOG_THROTTLER_TEMP_VR_MEM0_PERCENTAGE = 58, - ADL_PMLOG_THROTTLER_TEMP_VR_MEM1_PERCENTAGE = 59, - ADL_PMLOG_THROTTLER_TEMP_VR_SOC_PERCENTAGE = 60, - ADL_PMLOG_THROTTLER_TEMP_LIQUID0_PERCENTAGE = 61, - ADL_PMLOG_THROTTLER_TEMP_LIQUID1_PERCENTAGE = 62, - ADL_PMLOG_THROTTLER_TEMP_PLX_PERCENTAGE = 63, - ADL_PMLOG_THROTTLER_TDC_GFX_PERCENTAGE = 64, - ADL_PMLOG_THROTTLER_TDC_SOC_PERCENTAGE = 65, - ADL_PMLOG_THROTTLER_TDC_USR_PERCENTAGE = 66, - ADL_PMLOG_THROTTLER_PPT0_PERCENTAGE = 67, - ADL_PMLOG_THROTTLER_PPT1_PERCENTAGE = 68, - ADL_PMLOG_THROTTLER_PPT2_PERCENTAGE = 69, - ADL_PMLOG_THROTTLER_PPT3_PERCENTAGE = 70, - ADL_PMLOG_THROTTLER_FIT_PERCENTAGE = 71, - ADL_PMLOG_THROTTLER_GFX_APCC_PLUS_PERCENTAGE = 72, - ADL_PMLOG_BOARD_POWER = 73, + ADL_PMLOG_THROTTLE_PERCENTAGE_TEMP_GFX = 52, + ADL_PMLOG_THROTTLE_PERCENTAGE_TEMP_MEM = 53, + ADL_PMLOG_THROTTLE_PERCENTAGE_TEMP_VR = 54, + ADL_PMLOG_THROTTLE_PERCENTAGE_POWER = 55, + ADL_PMLOG_THROTTLE_PERCENTAGE_TDC = 56, + ADL_PMLOG_THROTTLE_PERCENTAGE_VMAX = 57, + ADL_PMLOG_BUS_CURR_MAX_SPEED = 58, + ADL_PMLOG_RESERVED_1 = 59, //Currently Unused + ADL_PMLOG_RESERVED_2 = 60, //Currently Unused + ADL_PMLOG_RESERVED_3 = 61, //Currently Unused + ADL_PMLOG_RESERVED_4 = 62, //Currently Unused + ADL_PMLOG_RESERVED_5 = 63, //Currently Unused + ADL_PMLOG_RESERVED_6 = 64, //Currently Unused + ADL_PMLOG_RESERVED_7 = 65, //Currently Unused + ADL_PMLOG_RESERVED_8 = 66, //Currently Unused + ADL_PMLOG_RESERVED_9 = 67, //Currently Unused + ADL_PMLOG_RESERVED_10 = 68, //Currently Unused + ADL_PMLOG_RESERVED_11 = 69, //Currently Unused + ADL_PMLOG_RESERVED_12 = 70, //Currently Unused + ADL_PMLOG_CLK_NPUCLK = 71, //NPU frequency + ADL_PMLOG_NPU_BUSY_AVG = 72, //NPU busy + ADL_PMLOG_BOARD_POWER = 73, + ADL_PMLOG_TEMPERATURE_INTAKE = 74, ADL_PMLOG_MAX_SENSORS_REAL } ADL_PMLOG_SENSORS; @@ -2393,6 +2430,9 @@ typedef enum ADL_PMLOG_SENSORS /// Indicates there is crossGPU clone #define ADL_CROSSGPUDISPLAYCLONE 0x2 + +/// @} + /// @} /// \defgroup define_D3DKMT_HANDLE @@ -2493,7 +2533,11 @@ typedef enum ADL_UIFEATURES_GROUP ADL_UIFEATURES_GROUP_PROVSR = 12, ADL_UIFEATURES_GROUP_SMA = 13, ADL_UIFEATURES_GROUP_CAMERA = 14, - ADL_UIFEATURES_GROUP_FRTCPRO = 15 + ADL_UIFEATURES_GROUP_FRTCPRO = 15, + ADL_UIFEATURES_GROUP_DELAGNEXT = 16, + ADL_UIFEATURES_GROUP_RTBOOST = 17, + ADL_UIFEATURES_GROUP_UAI = 18 + } ADL_UIFEATURES_GROUP; @@ -2587,9 +2631,18 @@ typedef enum ADL_USER_SETTINGS ADL_USER_SETTINGS_USU_PROFILE = 1 << 4, //notify USU settings change ADL_USER_SETTINGS_CVDC_PROFILE = 1 << 5, //notify Color Vision Deficiency Corretion settings change ADL_USER_SETTINGS_SCE_PROFILE = 1 << 6, - ADL_USER_SETTINGS_PROVSR = 1 << 7 + ADL_USER_SETTINGS_PROVSR = 1 << 7, + ADL_USER_SETTINGS_RTBOOST_PROFILE=1 << 8, + ADL_USER_SETTINGS_USU2_PROFILE = 1 << 9, //notify USU2 settings change } ADL_USER_SETTINGS; +//FRAME_TIMESTAMPS_SHARED_MEMORY type +typedef enum FRAME_TIMESTAMPS_SHARED_MEMORY_TYPE +{ + FRAME_TIMESTAMPS_SHARED_MEMORY_TYPE_UNKNOWN = 0, + FRAME_TIMESTAMPS_SHARED_MEMORY_TYPE_LEGACY, + FRAME_TIMESTAMPS_SHARED_MEMORY_TYPE_INDEXED +}FRAME_TIMESTAMPS_SHARED_MEMORY_TYPE; #define ADL_REG_DEVICE_FUNCTION_1 0x00000001 #endif /* ADL_DEFINES_H_ */ diff --git a/src/3rdparty/display-library/adl_sdk.h b/src/3rdparty/display-library/adl_sdk.h index 0923a6abbf..f649431177 100644 --- a/src/3rdparty/display-library/adl_sdk.h +++ b/src/3rdparty/display-library/adl_sdk.h @@ -33,14 +33,10 @@ #include "adl_structures.h" -#if defined (LINUX) -#define __stdcall -#endif /* (LINUX) */ - /// Memory Allocation Call back typedef void* ( __stdcall *ADL_MAIN_MALLOC_CALLBACK )( int ); -#define ADL_SDK_MAJOR_VERSION 17 -#define ADL_SDK_MINOR_VERSION 1 +#define ADL_SDK_MAJOR_VERSION 18 +#define ADL_SDK_MINOR_VERSION 0 #endif /* ADL_SDK_H_ */ diff --git a/src/3rdparty/display-library/adl_structures.h b/src/3rdparty/display-library/adl_structures.h index 601ad74bd8..8311fe4851 100644 --- a/src/3rdparty/display-library/adl_structures.h +++ b/src/3rdparty/display-library/adl_structures.h @@ -21,7 +21,7 @@ // SOFTWARE. /// \file adl_structures.h -///\brief This file contains the structure declarations that are used by the public ADL interfaces for \ALL platforms.\n Included in ADL SDK +///\brief This file contains the structure declarations that are used by the public ADL interfaces for \WIN platforms.\n Included in ADL SDK /// /// All data structures used in AMD Display Library (ADL) public interfaces should be defined in this header file. /// @@ -41,7 +41,7 @@ //////////////////////////////////////////////////////////////////////////////////////////// typedef struct AdapterInfo { -/// \ALL_STRUCT_MEM +/// \WIN_STRUCT_MEM /// Size of the structure. int iSize; @@ -59,7 +59,7 @@ typedef struct AdapterInfo int iVendorID; /// Adapter name. char strAdapterName[ADL_MAX_PATH]; -/// Display name. For example, "\\\\Display0" for Windows or ":0:0" for Linux. +/// Display name. For example, "\\\\Display0" for Windows. char strDisplayName[ADL_MAX_PATH]; /// Present or not; 1 if present and 0 if not present.It the logical adapter is present, the display name such as \\\\.\\Display1 can be found from OS int iPresent; @@ -80,38 +80,8 @@ typedef struct AdapterInfo #endif /* (_WIN32) || (_WIN64) */ -#if defined (LINUX) -/// \LNX_STRUCT_MEM - -/// Internal X screen number from GPUMapInfo (DEPRICATED use XScreenInfo) - int iXScreenNum; -/// Internal driver index from GPUMapInfo - int iDrvIndex; -/// \deprecated Internal x config file screen identifier name. Use XScreenInfo instead. - char strXScreenConfigName[ADL_MAX_PATH]; - -#endif /* (LINUX) */ } AdapterInfo, *LPAdapterInfo; -///////////////////////////////////////////////////////////////////////////////////////////// -///\brief Structure containing information about the Linux X screen information. -/// -/// This structure is used to store the current screen number and xorg.conf ID name assoicated with an adapter index. -/// This structure is updated during ADL_Main_Control_Refresh or ADL_ScreenInfo_Update. -/// Note: This structure should be used in place of iXScreenNum and strXScreenConfigName in AdapterInfo as they will be -/// deprecated. -/// \nosubgrouping -//////////////////////////////////////////////////////////////////////////////////////////// -#if defined (LINUX) -typedef struct XScreenInfo -{ -/// Internal X screen number from GPUMapInfo. - int iXScreenNum; -/// Internal x config file screen identifier name. - char strXScreenConfigName[ADL_MAX_PATH]; -} XScreenInfo, *LPXScreenInfo; -#endif /* (LINUX) */ - ///////////////////////////////////////////////////////////////////////////////////////////// ///\brief Structure containing information about an controller mode /// @@ -453,8 +423,12 @@ typedef struct ADLDDCInfo2 int ulMaxBacklightMinLuminanceData; int ulMinBacklightMinLuminanceData; + // Display screen width/height + int ulScreenWidth; + int ulScreenHeight; + // Reserved for future use - int iReserved[4]; + int iReserved[2]; } ADLDDCInfo2, *LPADLDDCInfo2; ///////////////////////////////////////////////////////////////////////////////////////////// @@ -3614,6 +3588,23 @@ typedef struct ADL_BOOST_SETTINGS int GlobalMinRes_Step; //Gloabl Min Resolution step value }ADL_BOOST_SETTINGS; +///////////////////////////////////////////////////////////////////////////////////////////// +///\brief Structure containing information about BOOST Settings +/// +/// Elements of BOOST settings. +/// \nosubgrouping +//////////////////////////////////////////////////////////////////////////////////////////// +typedef struct ADL_BOOST_SETTINGSX2 +{ + int Hotkey; // Hotkey value + int GlobalEnable; //Global enable value + int GlobalMinRes; //Gloabl Min Resolution value + int GlobalMinRes_MinLimit; //Gloabl Min Resolution slider min limit value + int GlobalMinRes_MaxLimit; //Gloabl Min Resolution slider max limit value + int GlobalMinRes_Step; //Gloabl Min Resolution step value + int VsrSupported; //Allows for interop with Upscaling/RSR +}ADL_BOOST_SETTINGSX2; + ///////////////////////////////////////////////////////////////////////////////////////////// ///\brief Structure containing information about ProVSR Settings change reason @@ -3817,7 +3808,7 @@ typedef struct ADL_RADEON_LED_PATTERN_CONFIG //////////////////////////////////////////////////////////////////////////////////////////// typedef struct AdapterInfoX2 { - /// \ALL_STRUCT_MEM + /// \WIN_STRUCT_MEM /// Size of the structure. int iSize; @@ -4164,6 +4155,7 @@ typedef struct ADLHDCPSettings /// /// This structure is used to store the Mantle Driver information /// \nosubgrouping +/// \deprecated This structure has been deprecated. //////////////////////////////////////////////////////////////////////////////////////////// typedef struct ADLMantleAppInfo @@ -4286,4 +4278,5 @@ typedef struct ADLSmartShiftSettings int iCurrentValue; int iFlags; //refer to define_smartshift_bits }ADLSmartShiftSettings, *LPADLSmartShiftSettings; -#endif /* ADL_STRUCTURES_H_ */ + +#endif /* ADL_STRUCTURES_H_ */ \ No newline at end of file diff --git a/src/3rdparty/display-library/repo.json b/src/3rdparty/display-library/repo.json index 2d053d1e31..d318cb6863 100644 --- a/src/3rdparty/display-library/repo.json +++ b/src/3rdparty/display-library/repo.json @@ -1,6 +1,6 @@ { "home": "https://github.com/GPUOpen-LibrariesAndSDKs/display-library", "license": "MIT (embeded in source)", - "version": "ADL SDK 17.1", + "version": "ADL SDK 18.1", "author": "Advanced Micro Devices, Inc" } From ba7e1eb18b812634a60bea33aa9b4cfab33d578b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=9D=8E=E9=80=9A=E6=B4=B2?= Date: Sun, 26 Apr 2026 16:02:51 +0800 Subject: [PATCH 02/92] Netif (Windows): correctly selects lowest-metric default route on IPv4 ... and improve performance slightly --- src/common/impl/netif_windows.c | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/src/common/impl/netif_windows.c b/src/common/impl/netif_windows.c index 702c249e64..7531ae0640 100644 --- a/src/common/impl/netif_windows.c +++ b/src/common/impl/netif_windows.c @@ -1,5 +1,4 @@ #include "common/netif.h" -#include "common/mallocHelper.h" #include // AF_INET6, IN6_IS_ADDR_UNSPECIFIED #include @@ -7,7 +6,7 @@ bool ffNetifGetDefaultRouteImplV4(FFNetifDefaultRouteResult* result) { PMIB_IPFORWARD_TABLE2 pIpForwardTable = NULL; - if (!NETIO_SUCCESS(GetIpForwardTable2(AF_UNSPEC, &pIpForwardTable))) { + if (!NETIO_SUCCESS(GetIpForwardTable2(AF_INET, &pIpForwardTable))) { return false; } @@ -18,7 +17,6 @@ bool ffNetifGetDefaultRouteImplV4(FFNetifDefaultRouteResult* result) { MIB_IPFORWARD_ROW2* row = &pIpForwardTable->Table[i]; if (row->DestinationPrefix.PrefixLength == 0 && - row->DestinationPrefix.Prefix.Ipv4.sin_family == AF_INET && row->DestinationPrefix.Prefix.Ipv4.sin_addr.S_un.S_addr == 0) { MIB_IF_ROW2 ifRow = { .InterfaceIndex = row->InterfaceIndex, @@ -39,7 +37,6 @@ bool ffNetifGetDefaultRouteImplV4(FFNetifDefaultRouteResult* result) { smallestMetric = realMetric; result->ifIndex = row->InterfaceIndex; foundDefault = true; - break; } } } @@ -53,7 +50,7 @@ bool ffNetifGetDefaultRouteImplV4(FFNetifDefaultRouteResult* result) { bool ffNetifGetDefaultRouteImplV6(FFNetifDefaultRouteResult* result) { PMIB_IPFORWARD_TABLE2 pIpForwardTable = NULL; - if (!NETIO_SUCCESS(GetIpForwardTable2(AF_UNSPEC, &pIpForwardTable))) { + if (!NETIO_SUCCESS(GetIpForwardTable2(AF_INET6, &pIpForwardTable))) { return false; } @@ -64,7 +61,6 @@ bool ffNetifGetDefaultRouteImplV6(FFNetifDefaultRouteResult* result) { MIB_IPFORWARD_ROW2* row = &pIpForwardTable->Table[i]; if (row->DestinationPrefix.PrefixLength == 0 && - row->DestinationPrefix.Prefix.Ipv6.sin6_family == AF_INET6 && IN6_IS_ADDR_UNSPECIFIED(&row->DestinationPrefix.Prefix.Ipv6.sin6_addr)) { MIB_IF_ROW2 ifRow = { .InterfaceIndex = row->InterfaceIndex, From 8b611899d1fc65107bad5f7e9533dcbaa1645a4e Mon Sep 17 00:00:00 2001 From: tnut <51124784+tnut@users.noreply.github.com> Date: Mon, 27 Apr 2026 13:31:08 +0200 Subject: [PATCH 03/92] Packages (Linux): adds `cards` counting (NuTyX) (#2287) * Packages:add cards counting (NuTyX) * package.c no need to add counts.catds * Sort packages print alphabetically * ditto * ditto * packages.c update FF_ARG and ffpackagesModule.info --------- Co-authored-by: Carter Li --- src/detection/packages/packages.h | 1 + src/detection/packages/packages_linux.c | 3 +++ src/modules/packages/option.h | 1 + src/modules/packages/packages.c | 6 ++++++ 4 files changed, 11 insertions(+) diff --git a/src/detection/packages/packages.h b/src/detection/packages/packages.h index d92bfce1b8..0a2c4040b5 100644 --- a/src/detection/packages/packages.h +++ b/src/detection/packages/packages.h @@ -10,6 +10,7 @@ typedef struct FFPackagesResult { uint32_t appimage; uint32_t brew; uint32_t brewCask; + uint32_t cards; uint32_t choco; uint32_t dpkg; uint32_t emerge; diff --git a/src/detection/packages/packages_linux.c b/src/detection/packages/packages_linux.c index 042557d065..98a01b23a0 100644 --- a/src/detection/packages/packages_linux.c +++ b/src/detection/packages/packages_linux.c @@ -537,6 +537,9 @@ static void getPackageCounts(FFstrbuf* baseDir, FFPackagesResult* packageCounts, if (!(options->disabled & FF_PACKAGES_FLAG_MOSS_BIT)) { packageCounts->moss += getSQLite3Int(baseDir, "/.moss/db/state", "SELECT COUNT(*) FROM state_selections WHERE state_id = (SELECT MAX(id) FROM state)", "moss"); } + if (!(options->disabled & FF_PACKAGES_FLAG_CARDS_BIT)) { + packageCounts->cards += getNumElements(baseDir, "/var/lib/pkg/DB", true); + } } static void getPackageCountsRegular(FFstrbuf* baseDir, FFPackagesResult* packageCounts, FFPackagesOptions* options) { diff --git a/src/modules/packages/option.h b/src/modules/packages/option.h index cac1f35f24..6018494374 100644 --- a/src/modules/packages/option.h +++ b/src/modules/packages/option.h @@ -38,6 +38,7 @@ typedef enum FF_A_PACKED FFPackagesFlags { FF_PACKAGES_FLAG_KISS_BIT = 1ULL << 31, FF_PACKAGES_FLAG_MOSS_BIT = 1ULL << 32, FF_PACKAGES_FLAG_APPIMAGE_BIT = 1ULL << 33, + FF_PACKAGES_FLAG_CARDS_BIT = 1ULL << 34, FF_PACKAGES_FLAG_FORCE_UNSIGNED = UINT64_MAX, } FFPackagesFlags; static_assert(sizeof(FFPackagesFlags) == sizeof(uint64_t), ""); diff --git a/src/modules/packages/packages.c b/src/modules/packages/packages.c index 2120b7dc9b..a0012047a8 100644 --- a/src/modules/packages/packages.c +++ b/src/modules/packages/packages.c @@ -61,6 +61,7 @@ bool ffPrintPackages(FFPackagesOptions* options) { FF_PRINT_PACKAGE_NAME(brew, "brew") FF_PRINT_PACKAGE_NAME(brewCask, "brew-cask") } + FF_PRINT_PACKAGE(cards) FF_PRINT_PACKAGE(choco) FF_PRINT_PACKAGE(dpkg) FF_PRINT_PACKAGE(emerge) @@ -151,6 +152,7 @@ bool ffPrintPackages(FFPackagesOptions* options) { FF_ARG(counts.brew, "brew"), FF_ARG(brewAll, "brew-all"), FF_ARG(counts.brewCask, "brew-cask"), + FF_ARG(counts.cards, "cards"), FF_ARG(counts.choco, "choco"), FF_ARG(counts.dpkg, "dpkg"), FF_ARG(counts.emerge, "emerge"), @@ -247,6 +249,7 @@ void ffParsePackagesJsonObject(FFPackagesOptions* options, yyjson_val* module) { case 'C': if (false) ; + FF_TEST_PACKAGE_NAME(CARDS) FF_TEST_PACKAGE_NAME(CHOCO) break; case 'D': @@ -371,6 +374,7 @@ void ffGeneratePackagesJsonConfig(FFPackagesOptions* options, yyjson_mut_doc* do FF_TEST_PACKAGE_NAME(APK) FF_TEST_PACKAGE_NAME(APPIMAGE) FF_TEST_PACKAGE_NAME(BREW) + FF_TEST_PACKAGE_NAME(CARDS) FF_TEST_PACKAGE_NAME(CHOCO) FF_TEST_PACKAGE_NAME(DPKG) FF_TEST_PACKAGE_NAME(EMERGE) @@ -429,6 +433,7 @@ bool ffGeneratePackagesJsonResult(FF_A_UNUSED FFPackagesOptions* options, yyjson FF_APPEND_PACKAGE_COUNT(apk) FF_APPEND_PACKAGE_COUNT(brew) FF_APPEND_PACKAGE_COUNT(brewCask) + FF_APPEND_PACKAGE_COUNT(cards) FF_APPEND_PACKAGE_COUNT(choco) FF_APPEND_PACKAGE_COUNT(dpkg) FF_APPEND_PACKAGE_COUNT(emerge) @@ -502,6 +507,7 @@ FFModuleBaseInfo ffPackagesModuleInfo = { { "Number of brew packages", "brew" }, { "Total number of all brew packages", "brew-all" }, { "Number of brew-cask packages", "brew-cask" }, + { "Number of cards packages", "cards" }, { "Number of choco packages", "choco" }, { "Number of dpkg packages", "dpkg" }, { "Number of emerge packages", "emerge" }, From 0db820a6c1e5771055841f192b17adf0638f48b4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=9D=8E=E9=80=9A=E6=B4=B2?= Date: Mon, 27 Apr 2026 19:51:04 +0800 Subject: [PATCH 04/92] Binary: skips short string entirely to improve performance Co-authored-by: Copilot --- src/common/impl/binary_apple.c | 3 +-- src/common/impl/binary_linux.c | 3 ++- src/common/impl/binary_windows.c | 3 ++- 3 files changed, 5 insertions(+), 4 deletions(-) diff --git a/src/common/impl/binary_apple.c b/src/common/impl/binary_apple.c index d3358460dd..de2c87638a 100644 --- a/src/common/impl/binary_apple.c +++ b/src/common/impl/binary_apple.c @@ -72,6 +72,7 @@ static bool handleMachSection(const FFMemoryMapping* mapping, const char* name, } uint32_t len = (uint32_t) strnlen(p, size - off); if (len < minLength) { + off += len; // Skip short strings continue; } if (*p >= ' ' && *p <= '~') { // Ignore control characters @@ -177,8 +178,6 @@ static const char* dumpMachHeader(const FFMemoryMapping* mapping, off_t offset, } } } - - return NULL; } return NULL; diff --git a/src/common/impl/binary_linux.c b/src/common/impl/binary_linux.c index 240e758700..ac04bf71b8 100644 --- a/src/common/impl/binary_linux.c +++ b/src/common/impl/binary_linux.c @@ -112,8 +112,9 @@ const char* ffBinaryExtractStrings(const char* elfFile, bool (*cb)(const char* s if (*p == '\0') { continue; } - uint32_t len = (uint32_t) strlen(p); + uint32_t len = (uint32_t) strnlen(p, data->d_size - off); if (len < minLength) { + off += len; continue; } // Only process printable ASCII characters diff --git a/src/common/impl/binary_windows.c b/src/common/impl/binary_windows.c index 9dc21e5913..4bf2f4644a 100644 --- a/src/common/impl/binary_windows.c +++ b/src/common/impl/binary_windows.c @@ -49,8 +49,9 @@ const char* ffBinaryExtractStrings(const char* peFile, bool (*cb)(const char* st if (*p == '\0') { continue; } - uint32_t len = (uint32_t) strlen(p); + uint32_t len = (uint32_t) strnlen(p, section->SizeOfRawData - off); if (len < minLength) { + off += len; continue; } // Only process printable ASCII characters From 039fa1ba77d7da35c6e3582ec40e02a76548e1b2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=9D=8E=E9=80=9A=E6=B4=B2?= Date: Mon, 27 Apr 2026 20:28:11 +0800 Subject: [PATCH 05/92] CommandOption: generates JSON result entry even for unknown modules Co-authored-by: Copilot --- src/common/impl/commandoption.c | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/src/common/impl/commandoption.c b/src/common/impl/commandoption.c index 19e5b93339..d32c0fe086 100644 --- a/src/common/impl/commandoption.c +++ b/src/common/impl/commandoption.c @@ -157,7 +157,7 @@ static void genJsonResult(FFdata* data, FFModuleBaseInfo* baseInfo, void* option } } -static void parseStructureCommand( +static bool parseStructureCommand( FFdata* data, const char* line, void (*fn)(FFdata*, FFModuleBaseInfo* baseInfo, void* options)) { @@ -167,18 +167,26 @@ static void parseStructureCommand( if (ffStrEqualsIgnCase(line, baseInfo->name)) { uint8_t optionBuf[FF_OPTION_MAX_SIZE]; baseInfo->initOptions(optionBuf); - if (__builtin_expect(data->resultDoc != NULL, false)) { + if (data->resultDoc != NULL) { fn(data, baseInfo, optionBuf); } else { baseInfo->printModule(optionBuf); } baseInfo->destroyOptions(optionBuf); - return; + return true; } } } - ffPrintError(line, 0, NULL, FF_PRINT_TYPE_NO_CUSTOM_KEY, ""); + if (fn == genJsonResult) { + yyjson_mut_doc* doc = data->resultDoc; + yyjson_mut_val* module = yyjson_mut_arr_add_obj(doc, doc->root); + yyjson_mut_obj_add_str(doc, module, "type", line); + yyjson_mut_obj_add_str(doc, module, "error", "Unknown module type"); + } else { + ffPrintError(line, 0, NULL, FF_PRINT_TYPE_NO_CUSTOM_KEY, ""); + } + return false; } void ffPrintCommandOption(FFdata* data) { From c10027f1d759fb5b64546b66f1d74928b83e646d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=9D=8E=E9=80=9A=E6=B4=B2?= Date: Mon, 27 Apr 2026 21:56:52 +0800 Subject: [PATCH 06/92] JsonConfig: fixes `CPUUsage` preparation never executed Co-authored-by: Copilot --- src/common/impl/jsonconfig.c | 13 ++++--------- 1 file changed, 4 insertions(+), 9 deletions(-) diff --git a/src/common/impl/jsonconfig.c b/src/common/impl/jsonconfig.c index 0e71cde513..e88feafc97 100644 --- a/src/common/impl/jsonconfig.c +++ b/src/common/impl/jsonconfig.c @@ -133,16 +133,11 @@ static bool parseModuleJsonObject(const char* type, yyjson_val* jsonVal, yyjson_ static void prepareModuleJsonObject(const char* type, yyjson_val* module) { switch (type[0]) { - case 'b': - case 'B': { - if (ffStrEqualsIgnCase(type, FF_CPUUSAGE_MODULE_NAME)) { - ffPrepareCPUUsage(); - } - break; - } case 'c': case 'C': { - if (ffStrEqualsIgnCase(type, FF_COMMAND_MODULE_NAME)) { + if (ffStrEqualsIgnCase(type, FF_CPUUSAGE_MODULE_NAME)) { + ffPrepareCPUUsage(); + } else if (ffStrEqualsIgnCase(type, FF_COMMAND_MODULE_NAME)) { FF_A_CLEANUP(ffDestroyCommandOptions) FFCommandOptions options; ffInitCommandOptions(&options); if (module) { @@ -259,7 +254,7 @@ static const char* printJsonConfig(FFdata* data, bool prepare) { yyjson_val* conditions = yyjson_obj_get(module, "condition"); if (conditions) { if (!yyjson_is_obj(conditions)) { - return "Property 'conditions' must be an object"; + return "Property 'condition' must be an object"; } yyjson_val* system = yyjson_obj_get(conditions, "system"); From 37ee193c40c0fb2e80ce458f7b38d3573d6e9085 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=9D=8E=E9=80=9A=E6=B4=B2?= Date: Mon, 27 Apr 2026 22:11:34 +0800 Subject: [PATCH 07/92] DBus: improves message handling --- src/common/impl/dbus.c | 30 +++++++++++++++++++++++++++--- 1 file changed, 27 insertions(+), 3 deletions(-) diff --git a/src/common/impl/dbus.c b/src/common/impl/dbus.c index e8ef68fdd4..dfd8b87df3 100644 --- a/src/common/impl/dbus.c +++ b/src/common/impl/dbus.c @@ -76,7 +76,7 @@ bool ffDBusGetString(FFDBusData* dbus, DBusMessageIter* iter, FFstrbuf* result) uint8_t value; dbus->lib->ffdbus_message_iter_get_basic(iter, &value); ffStrbufAppendC(result, (char) value); - return false; // Don't append a comma + return true; } if (argType != DBUS_TYPE_VARIANT && argType != DBUS_TYPE_ARRAY) { @@ -92,6 +92,25 @@ bool ffDBusGetString(FFDBusData* dbus, DBusMessageIter* iter, FFstrbuf* result) // At this point we have an array + int subArgType = dbus->lib->ffdbus_message_iter_get_arg_type(&subIter); + if (subArgType == DBUS_TYPE_INVALID) { + return false; + } + + if (subArgType == DBUS_TYPE_BYTE) { + while (true) { + uint8_t value; + dbus->lib->ffdbus_message_iter_get_basic(&subIter, &value); + ffStrbufAppendC(result, (char) value); + + if (!dbus->lib->ffdbus_message_iter_next(&subIter)) { + break; + } + } + + return true; + } + bool foundAValue = false; while (true) { @@ -189,12 +208,17 @@ bool ffDBusGetInt(FFDBusData* dbus, DBusMessageIter* iter, int32_t* result) { if (argType == DBUS_TYPE_UINT16) { uint16_t value = 0; dbus->lib->ffdbus_message_iter_get_basic(iter, &value); - *result = (int16_t) value; + *result = (int32_t) value; return true; } if (argType == DBUS_TYPE_UINT32) { - dbus->lib->ffdbus_message_iter_get_basic(iter, result); + uint32_t value = 0; + dbus->lib->ffdbus_message_iter_get_basic(iter, &value); + if (value > 0x7FFFFFFFu) { + return false; + } + *result = (int32_t) value; return true; } From ffc38eb0f1a8b2e292f0c7dec9f21df4a7d73d4d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=9D=8E=E9=80=9A=E6=B4=B2?= Date: Mon, 27 Apr 2026 22:21:45 +0800 Subject: [PATCH 08/92] FFlist: add `__restrict` --- src/common/FFlist.h | 4 ++-- src/common/impl/FFlist.c | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/common/FFlist.h b/src/common/FFlist.h index 44ef922971..80cef0b542 100644 --- a/src/common/FFlist.h +++ b/src/common/FFlist.h @@ -18,9 +18,9 @@ typedef struct FFlist { void* ffListAdd(FFlist* list, uint32_t elementSize); // Removes the first element, and copy its value to `*result` -bool ffListShift(FFlist* list, uint32_t elementSize, void* result); +bool ffListShift(FFlist* list, uint32_t elementSize, void* __restrict result); // Removes the last element, and copy its value to `*result` -bool ffListPop(FFlist* list, uint32_t elementSize, void* result); +bool ffListPop(FFlist* list, uint32_t elementSize, void* __restrict result); static inline void ffListInit(FFlist* list) { list->capacity = 0; diff --git a/src/common/impl/FFlist.c b/src/common/impl/FFlist.c index 74df1e4681..eddd0fa464 100644 --- a/src/common/impl/FFlist.c +++ b/src/common/impl/FFlist.c @@ -12,7 +12,7 @@ void* ffListAdd(FFlist* list, uint32_t elementSize) { return ffListGet(list, elementSize, list->length - 1); } -bool ffListShift(FFlist* list, uint32_t elementSize, void* result) { +bool ffListShift(FFlist* list, uint32_t elementSize, void* __restrict result) { if (list->length == 0) { return false; } @@ -23,7 +23,7 @@ bool ffListShift(FFlist* list, uint32_t elementSize, void* result) { return true; } -bool ffListPop(FFlist* list, uint32_t elementSize, void* result) { +bool ffListPop(FFlist* list, uint32_t elementSize, void* __restrict result) { if (list->length == 0) { return false; } From 08b79ff4aedcdae2b1536db1e88b3ae01e76b4c9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=9D=8E=E9=80=9A=E6=B4=B2?= Date: Mon, 27 Apr 2026 22:27:10 +0800 Subject: [PATCH 09/92] Platform (OpenBSD): silences a compiler warning Co-authored-by: Copilot --- src/common/impl/FFPlatform_unix.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/common/impl/FFPlatform_unix.c b/src/common/impl/FFPlatform_unix.c index 8320a17719..76afb3597e 100644 --- a/src/common/impl/FFPlatform_unix.c +++ b/src/common/impl/FFPlatform_unix.c @@ -18,6 +18,7 @@ #include #elif defined(__OpenBSD__) #include + #include #include #include "common/path.h" #elif defined(__HAIKU__) @@ -97,7 +98,7 @@ static void getExePath(FFPlatform* platform) { struct stat st; if (stat(exePath, &st) == 0 && S_ISREG(st.st_mode)) { int cntp; - struct kinfo_file* kf = kvm_getfiles(kd, KERN_FILE_BYPID, platform->pid, sizeof(*kf), &cntp); + struct kinfo_file* kf = kvm_getfiles(kd, KERN_FILE_BYPID, (pid_t) platform->pid, sizeof(*kf), &cntp); if (kf) { int i; for (i = 0; i < cntp; i++) { From 4eebc1b6ee0eaa6909f0369162f43e2e5e48132c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=9D=8E=E9=80=9A=E6=B4=B2?= Date: Mon, 27 Apr 2026 22:30:48 +0800 Subject: [PATCH 10/92] Platform (Windows): adds comments Co-authored-by: Copilot --- src/common/impl/FFPlatform_windows.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/common/impl/FFPlatform_windows.c b/src/common/impl/FFPlatform_windows.c index e352af1403..e483071e01 100644 --- a/src/common/impl/FFPlatform_windows.c +++ b/src/common/impl/FFPlatform_windows.c @@ -145,7 +145,7 @@ static void getUserName(FFPlatform* platform) { PLSA_UNICODE_STRING userName = NULL; if (NT_SUCCESS(LsaGetUserName(&userName, NULL))) { ffStrbufSetNWS(&platform->userName, userName->Length / sizeof(wchar_t), userName->Buffer); - RtlFreeUnicodeString(userName); + RtlFreeUnicodeString(userName); // Required. userName.Buffer is allocated separately LsaFreeMemory(userName); } else { ffStrbufSetS(&platform->userName, getenv("USERNAME")); From 3883047dcec924b3161298e589a1d10dcae63d84 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=9D=8E=E9=80=9A=E6=B4=B2?= Date: Mon, 27 Apr 2026 22:35:11 +0800 Subject: [PATCH 11/92] Platform: initializes `pageSize` Noop, because the whole platform is statically initialized Co-authored-by: Copilot --- src/common/impl/FFPlatform.c | 1 + 1 file changed, 1 insertion(+) diff --git a/src/common/impl/FFPlatform.c b/src/common/impl/FFPlatform.c index 2e4d523a97..4366bc10ed 100644 --- a/src/common/impl/FFPlatform.c +++ b/src/common/impl/FFPlatform.c @@ -26,6 +26,7 @@ void ffPlatformInit(FFPlatform* platform) { ffStrbufInit(&info->release); ffStrbufInit(&info->version); ffStrbufInit(&info->architecture); + info->pageSize = 0; ffPlatformInitImpl(platform); From d79e1d7fbcbb332c92a8f8a85cc7a1f048f2a656 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=9D=8E=E9=80=9A=E6=B4=B2?= Date: Mon, 27 Apr 2026 22:49:31 +0800 Subject: [PATCH 12/92] FFstrbuf: improves robustness of `ffStrbufMatchSeparatedNS` Co-authored-by: Copilot --- src/common/impl/FFstrbuf.c | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/src/common/impl/FFstrbuf.c b/src/common/impl/FFstrbuf.c index a530790556..d0a38f9ce5 100644 --- a/src/common/impl/FFstrbuf.c +++ b/src/common/impl/FFstrbuf.c @@ -790,9 +790,10 @@ bool ffStrbufMatchSeparatedNS(const FFstrbuf* strbuf, uint32_t compLength, const } for (const char* p = comp; p < comp + compLength;) { - const char* colon = memchr(p, separator, compLength); + const char* colon = memchr(p, separator, (size_t)(comp + compLength - p)); if (colon == NULL) { - return strcmp(strbuf->chars, p) == 0; + uint32_t remainingLen = (uint32_t)(comp + compLength - p); + return strbuf->length == remainingLen && memcmp(strbuf->chars, p, remainingLen) == 0; } uint32_t substrLength = (uint32_t) (colon - p); @@ -817,9 +818,10 @@ bool ffStrbufMatchSeparatedIgnCaseNS(const FFstrbuf* strbuf, uint32_t compLength } for (const char* p = comp; p < comp + compLength;) { - const char* colon = memchr(p, separator, compLength); + const char* colon = memchr(p, separator, (size_t)(comp + compLength - p)); if (colon == NULL) { - return strcasecmp(strbuf->chars, p) == 0; + uint32_t remainingLen = (uint32_t)(comp + compLength - p); + return strbuf->length == remainingLen && strncasecmp(strbuf->chars, p, remainingLen) == 0; } uint32_t substrLength = (uint32_t) (colon - p); From 31c73543e8ecf6645a8a3344235d2d9e7fe6e089 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=9D=8E=E9=80=9A=E6=B4=B2?= Date: Mon, 27 Apr 2026 22:57:29 +0800 Subject: [PATCH 13/92] Font: improves `pt` handling Co-authored-by: Copilot --- src/common/impl/font.c | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/src/common/impl/font.c b/src/common/impl/font.c index b754c4c181..3bd19d4524 100644 --- a/src/common/impl/font.c +++ b/src/common/impl/font.c @@ -425,10 +425,11 @@ void ffFontInitXft(FFfont* font, const char* xft) { if (value.length > 0) { if ( (keyLen == 4 && ffStrStartsWithIgnCase(keyStart, "size")) || - (keyLen == 9 && ffStrStartsWithIgnCase(keyStart, "pixelsize"))) { + (keyLen == 9 && (ffStrStartsWithIgnCase(keyStart, "pixelsize") || ffStrStartsWithIgnCase(keyStart, "pointsize")))) { if (sizeEmpty && ffCharIsDigit(value.chars[0])) { ffStrbufAppend(&font->size, &value); - ffStrbufAppendS(&font->size, keyLen == 4 ? "pt" : "px"); + ffStrbufAppendS(&font->size, + (keyLen == 9 && ffStrStartsWithIgnCase(keyStart, "pixelsize")) ? "px" : "pt"); } } else if (keyLen == 5 && ffStrStartsWithIgnCase(keyStart, "style")) { // style may contain multiple words: "Bold Italic" @@ -457,6 +458,10 @@ void ffFontInitXft(FFfont* font, const char* xft) { ffStrbufInit(style); strbufAppendNSExcludingC(style, value.length, value.chars, '-'); ffStrbufTrim(style, ' '); + if (style->length == 0) { + ffStrbufDestroy(style); + --font->styles.length; + } } } } From c4c934d23eb3bbeb1d7079583940a39ea9e781e3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=9D=8E=E9=80=9A=E6=B4=B2?= Date: Mon, 27 Apr 2026 23:00:52 +0800 Subject: [PATCH 14/92] Format: fixes `UINT64` handling Co-authored-by: Copilot --- src/common/impl/format.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/common/impl/format.c b/src/common/impl/format.c index 37e7db623f..175941410b 100644 --- a/src/common/impl/format.c +++ b/src/common/impl/format.c @@ -108,7 +108,7 @@ static inline void appendInvalidPlaceholder(FFstrbuf* buffer, const char* start, } static inline bool formatArgSet(const FFformatarg* arg) { - return arg->value != NULL && ((arg->type == FF_ARG_TYPE_DOUBLE && *(double*) arg->value > 0.0) || (arg->type == FF_ARG_TYPE_FLOAT && *(float*) arg->value > 0.0) || (arg->type == FF_ARG_TYPE_INT && *(int*) arg->value > 0) || (arg->type == FF_ARG_TYPE_STRBUF && ((FFstrbuf*) arg->value)->length > 0) || (arg->type == FF_ARG_TYPE_STRING && ffStrSet((char*) arg->value)) || (arg->type == FF_ARG_TYPE_UINT8 && *(uint8_t*) arg->value > 0) || (arg->type == FF_ARG_TYPE_UINT16 && *(uint16_t*) arg->value > 0) || (arg->type == FF_ARG_TYPE_UINT && *(uint32_t*) arg->value > 0) || (arg->type == FF_ARG_TYPE_BOOL && *(bool*) arg->value) || (arg->type == FF_ARG_TYPE_LIST && ((FFlist*) arg->value)->length > 0)); + return arg->value != NULL && ((arg->type == FF_ARG_TYPE_DOUBLE && *(double*) arg->value > 0.0) || (arg->type == FF_ARG_TYPE_FLOAT && *(float*) arg->value > 0.0) || (arg->type == FF_ARG_TYPE_INT && *(int32_t*) arg->value > 0) || (arg->type == FF_ARG_TYPE_STRBUF && ((FFstrbuf*) arg->value)->length > 0) || (arg->type == FF_ARG_TYPE_STRING && ffStrSet((char*) arg->value)) || (arg->type == FF_ARG_TYPE_UINT8 && *(uint8_t*) arg->value > 0) || (arg->type == FF_ARG_TYPE_UINT16 && *(uint16_t*) arg->value > 0) || (arg->type == FF_ARG_TYPE_UINT && *(uint32_t*) arg->value > 0) || (arg->type == FF_ARG_TYPE_UINT64 && *(uint64_t*) arg->value > 0) || (arg->type == FF_ARG_TYPE_BOOL && *(bool*) arg->value) || (arg->type == FF_ARG_TYPE_LIST && ((FFlist*) arg->value)->length > 0)); } void ffParseFormatString(FFstrbuf* buffer, const FFstrbuf* formatstr, uint32_t numArgs, const FFformatarg* arguments) { From 181b775f73cac833c4f0ee3354dd9bb4fcc6eb9a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=9D=8E=E9=80=9A=E6=B4=B2?= Date: Mon, 27 Apr 2026 23:15:30 +0800 Subject: [PATCH 15/92] Init: don't print unecessary ANSI escape codes Co-authored-by: Copilot --- src/common/impl/init.c | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/src/common/impl/init.c b/src/common/impl/init.c index 767eb7fdce..91aa015eb2 100644 --- a/src/common/impl/init.c +++ b/src/common/impl/init.c @@ -57,9 +57,9 @@ void ffInitInstance(void) { initState(&instance.state); } -static volatile bool ffDisableLinewrap = true; -static volatile bool ffHideCursor = true; -#if _WIN32 +static volatile bool ffDisableLinewrap = false; +static volatile bool ffHideCursor = false; +#ifdef _WIN32 static volatile UINT oldCp = CP_UTF8; #endif @@ -122,7 +122,10 @@ void ffStart(void) { if (instance.config.display.noBuffer) { setvbuf(stdout, NULL, _IONBF, 0); } - struct sigaction action = { .sa_handler = exitSignalHandler }; + struct sigaction action; + sigemptyset(&action.sa_mask); + action.sa_flags = 0; + action.sa_handler = exitSignalHandler; sigaction(SIGINT, &action, NULL); sigaction(SIGTERM, &action, NULL); sigaction(SIGQUIT, &action, NULL); From e1dffe794be453b2661bd1e47ea4d3b5d7130dc3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=9D=8E=E9=80=9A=E6=B4=B2?= Date: Mon, 27 Apr 2026 23:36:30 +0800 Subject: [PATCH 16/92] Library: improves code quality Co-authored-by: Copilot --- src/common/impl/library.c | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/common/impl/library.c b/src/common/impl/library.c index 863f851f31..954619a366 100644 --- a/src/common/impl/library.c +++ b/src/common/impl/library.c @@ -30,7 +30,7 @@ static void* libraryLoad(const char* path, int maxVersion) { void* result = dlopen(path, FF_DLOPEN_FLAGS); - #ifdef _WIN32 + #if _WIN32 // libX.dll.1 never exists on Windows, while libX-1.dll may exist FF_UNUSED(maxVersion) @@ -114,7 +114,7 @@ void* dlopen(const char* path, FF_A_UNUSED int mode) { PVOID module = NULL; status = LdrLoadDll(NULL, NULL, &(UNICODE_STRING) { - .Length = (USHORT) pathWBytes - sizeof(wchar_t), // Exclude null terminator + .Length = (USHORT) (pathWBytes - sizeof(wchar_t)), // Exclude null terminator .MaximumLength = (USHORT) pathWBytes, .Buffer = pathW, }, @@ -139,7 +139,7 @@ int dlclose(void* handle) { void* dlsym(void* handle, const char* symbol) { void* address; - USHORT symbolBytes = (USHORT) strlen(symbol) + 1; + USHORT symbolBytes = (USHORT) (strlen(symbol) + 1); NTSTATUS status = LdrGetProcedureAddress(handle, &(ANSI_STRING) { .Length = symbolBytes - sizeof(char), .MaximumLength = symbolBytes, @@ -158,7 +158,7 @@ void* ffLibraryGetModule(const wchar_t* libraryFileName) { assert(libraryFileName != NULL && "Use \"ffGetPeb()->ImageBaseAddress\" instead"); void* module = NULL; - USHORT libraryFileNameBytes = (USHORT) (wcslen(libraryFileName) * sizeof(wchar_t)) + sizeof(wchar_t); + USHORT libraryFileNameBytes = (USHORT) (wcslen(libraryFileName) * sizeof(wchar_t) + sizeof(wchar_t)); NTSTATUS status = LdrGetDllHandle(NULL, NULL, &(UNICODE_STRING) { .Length = libraryFileNameBytes - sizeof(wchar_t), .MaximumLength = libraryFileNameBytes, From 414f7ce0678885b06cd6897d3c9d86334a4470ed Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=9D=8E=E9=80=9A=E6=B4=B2?= Date: Tue, 28 Apr 2026 00:48:09 +0800 Subject: [PATCH 17/92] Netif: improves code quality Co-authored-by: Copilot --- src/common/impl/netif_apple.c | 8 ++++---- src/common/impl/netif_bsd.c | 8 ++++---- src/common/impl/netif_gnu.c | 2 +- src/common/impl/netif_haiku.c | 2 +- 4 files changed, 10 insertions(+), 10 deletions(-) diff --git a/src/common/impl/netif_apple.c b/src/common/impl/netif_apple.c index b37ea2cb9c..191a55c46f 100644 --- a/src/common/impl/netif_apple.c +++ b/src/common/impl/netif_apple.c @@ -25,8 +25,8 @@ get_rt_address(struct rt_msghdr* rtm, int desired) { struct sockaddr* sa = (struct sockaddr*) (rtm + 1); for (int i = 0; i < RTAX_MAX; i++) { - if (rtm->rtm_addrs & (1 << i)) { - if ((1 << i) == desired) { + if (rtm->rtm_addrs & (1u << i)) { + if ((1u << i) == (unsigned) desired) { return sa; } @@ -110,7 +110,7 @@ bool ffNetifGetDefaultRouteImplV4(FFNetifDefaultRouteResult* result) { && sdl->sdl_len #endif ) { - assert(sdl->sdl_nlen <= IF_NAMESIZE); + if (sdl->sdl_nlen > IF_NAMESIZE) return false; memcpy(result->ifName, sdl->sdl_data, sdl->sdl_nlen); result->ifName[sdl->sdl_nlen] = '\0'; result->ifIndex = sdl->sdl_index; @@ -183,7 +183,7 @@ bool ffNetifGetDefaultRouteImplV6(FFNetifDefaultRouteResult* result) { && sdl->sdl_len #endif ) { - assert(sdl->sdl_nlen <= IF_NAMESIZE); + if (sdl->sdl_nlen > IF_NAMESIZE) return false; memcpy(result->ifName, sdl->sdl_data, sdl->sdl_nlen); result->ifName[sdl->sdl_nlen] = '\0'; result->ifIndex = sdl->sdl_index; diff --git a/src/common/impl/netif_bsd.c b/src/common/impl/netif_bsd.c index 72ef96b74f..c6ae8f6103 100644 --- a/src/common/impl/netif_bsd.c +++ b/src/common/impl/netif_bsd.c @@ -32,8 +32,8 @@ get_rt_address(struct rt_msghdr* rtm, int desired) { struct sockaddr* sa = (struct sockaddr*) (rtm + 1); for (int i = 0; i < RTAX_MAX; i++) { - if (rtm->rtm_addrs & (1 << i)) { - if ((1 << i) == desired) { + if (rtm->rtm_addrs & (1u << i)) { + if ((1u << i) == (unsigned) desired) { return sa; } sa = (struct sockaddr*) (ROUNDUP(sa->sa_len) + (char*) sa); @@ -65,7 +65,7 @@ bool ffNetifGetDefaultRouteImplV4(FFNetifDefaultRouteResult* result) { if ((rtm->rtm_flags & RTF_GATEWAY) && !(rtm->rtm_flags & RTF_REJECT) && (sa->sa_family == AF_INET)) { struct sockaddr_dl* sdl = (struct sockaddr_dl*) get_rt_address(rtm, RTA_IFP); if (sdl && sdl->sdl_family == AF_LINK) { - assert(sdl->sdl_nlen <= IF_NAMESIZE); + if (sdl->sdl_nlen > IF_NAMESIZE) continue; memcpy(result->ifName, sdl->sdl_data, sdl->sdl_nlen); result->ifName[sdl->sdl_nlen] = '\0'; result->ifIndex = sdl->sdl_index; @@ -106,7 +106,7 @@ bool ffNetifGetDefaultRouteImplV6(FFNetifDefaultRouteResult* result) { if ((rtm->rtm_flags & RTF_GATEWAY) && !(rtm->rtm_flags & RTF_REJECT) && (sa->sa_family == AF_INET6)) { struct sockaddr_dl* sdl = (struct sockaddr_dl*) get_rt_address(rtm, RTA_IFP); if (sdl && sdl->sdl_family == AF_LINK) { - assert(sdl->sdl_nlen <= IF_NAMESIZE); + if (sdl->sdl_nlen > IF_NAMESIZE) continue; memcpy(result->ifName, sdl->sdl_data, sdl->sdl_nlen); result->ifName[sdl->sdl_nlen] = '\0'; result->ifIndex = sdl->sdl_index; diff --git a/src/common/impl/netif_gnu.c b/src/common/impl/netif_gnu.c index d2cf5d009e..d2feed5be9 100644 --- a/src/common/impl/netif_gnu.c +++ b/src/common/impl/netif_gnu.c @@ -25,7 +25,7 @@ bool ffNetifGetDefaultRouteImplV4(FFNetifDefaultRouteResult* result) { // TODO: Get the preferred source address return true; } - result->ifName[0] = '0'; + result->ifName[0] = '\0'; return false; } diff --git a/src/common/impl/netif_haiku.c b/src/common/impl/netif_haiku.c index f04183a1fb..a32138c68e 100644 --- a/src/common/impl/netif_haiku.c +++ b/src/common/impl/netif_haiku.c @@ -71,7 +71,7 @@ bool ffNetifGetDefaultRouteImplV4(FFNetifDefaultRouteResult* result) { } bool ffNetifGetDefaultRouteImplV6(FFNetifDefaultRouteResult* result) { - FF_AUTO_CLOSE_FD int pfRoute = socket(AF_INET, SOCK_RAW, AF_INET6); + FF_AUTO_CLOSE_FD int pfRoute = socket(AF_INET6, SOCK_RAW, AF_INET6); if (pfRoute < 0) { return false; } From 4ee7dd72d0d6d6c7be98902b11b21c4fb023a91e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=9D=8E=E9=80=9A=E6=B4=B2?= Date: Tue, 28 Apr 2026 09:03:24 +0800 Subject: [PATCH 18/92] IO: improves code quality & reliablity --- src/common/impl/io_unix.c | 8 +++----- src/common/impl/io_windows.c | 16 ++++++++++++---- 2 files changed, 15 insertions(+), 9 deletions(-) diff --git a/src/common/impl/io_unix.c b/src/common/impl/io_unix.c index 65cc9121a4..a7e0b041dc 100644 --- a/src/common/impl/io_unix.c +++ b/src/common/impl/io_unix.c @@ -252,14 +252,14 @@ bool ffSuppressIO(bool suppress) { } void listFilesRecursively(uint32_t baseLength, FFstrbuf* folder, uint8_t indentation, const char* folderName, bool pretty) { - FF_AUTO_CLOSE_FD int dfd = open(folder->chars, O_RDONLY | O_CLOEXEC); + int dfd = open(folder->chars, O_RDONLY | O_CLOEXEC | O_DIRECTORY); // Ownership of dfd will be transformed to dir if (dfd < 0) { return; } - DIR* dir = fdopendir(dfd); + FF_AUTO_CLOSE_DIR DIR* dir = fdopendir(dfd); if (dir == NULL) { - return; + return; // Should not happen } uint32_t folderLength = folder->length; @@ -310,8 +310,6 @@ void listFilesRecursively(uint32_t baseLength, FFstrbuf* folder, uint8_t indenta puts(entry->d_name); } - - closedir(dir); } void ffListFilesRecursively(const char* path, bool pretty) { diff --git a/src/common/impl/io_windows.c b/src/common/impl/io_windows.c index 49b49df094..6e720396e4 100644 --- a/src/common/impl/io_windows.c +++ b/src/common/impl/io_windows.c @@ -387,10 +387,12 @@ void ffListFilesRecursively(const char* path, bool pretty) { const char* ffGetTerminalResponse(const char* request, int nParams, const char* format, ...) { HANDLE hInput = GetStdHandle(STD_INPUT_HANDLE); FF_AUTO_CLOSE_FD HANDLE hConin = INVALID_HANDLE_VALUE; - DWORD inputMode; - if (!GetConsoleMode(hInput, &inputMode)) { + DWORD inputMode = 0; + bool hasInputMode = !!GetConsoleMode(hInput, &inputMode); + if (!hasInputMode) { hConin = CreateFileW(L"CONIN$", GENERIC_READ | GENERIC_WRITE, FILE_SHARE_READ, 0, OPEN_EXISTING, 0, NULL); hInput = hConin; + hasInputMode = !!GetConsoleMode(hInput, &inputMode); } SetConsoleMode(hInput, 0); @@ -439,12 +441,16 @@ const char* ffGetTerminalResponse(const char* request, int nParams, const char* while (true) { DWORD bytes = 0; - if (!ReadFile(hInput, buffer, sizeof(buffer) - 1, &bytes, NULL) || bytes == 0) { + if (!ReadFile(hInput, buffer + bytesRead, (DWORD)(sizeof(buffer) - 1 - bytesRead), &bytes, NULL) || bytes == 0) { va_end(args); return "ReadFile() failed"; } bytesRead += bytes; + if (__builtin_expect(bytesRead >= sizeof(buffer) - 1, false)) { + va_end(args); + return "terminal response buffer overflow"; + } buffer[bytesRead] = '\0'; va_list cargs; @@ -461,7 +467,9 @@ const char* ffGetTerminalResponse(const char* request, int nParams, const char* } } - SetConsoleMode(hInput, inputMode); + if (hasInputMode) { + SetConsoleMode(hInput, inputMode); + } va_end(args); From 94ceeb44821310beb81924b930a9b2a7770327dd Mon Sep 17 00:00:00 2001 From: Or-simen <161470874+Or-simen@users.noreply.github.com> Date: Tue, 28 Apr 2026 05:23:42 +0300 Subject: [PATCH 19/92] Chore: fixes typo from `FF_UNITIALIZED` to `FF_UNINITIALIZED` (#2290) Co-authored-by: or sim --- src/detection/publicip/publicip.c | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/detection/publicip/publicip.c b/src/detection/publicip/publicip.c index aa93c99edb..abca57c3e1 100644 --- a/src/detection/publicip/publicip.c +++ b/src/detection/publicip/publicip.c @@ -1,14 +1,14 @@ #include "publicip.h" #include "common/networking.h" -#define FF_UNITIALIZED ((const char*) (uintptr_t) -1) +#define FF_UNINITIALIZED ((const char*) (uintptr_t) -1) static FFNetworkingState states[2]; -static const char* statuses[2] = { FF_UNITIALIZED, FF_UNITIALIZED }; +static const char* statuses[2] = { FF_UNINITIALIZED, FF_UNINITIALIZED }; void ffPreparePublicIp(FFPublicIPOptions* options) { FFNetworkingState* state = &states[options->ipv6]; const char** status = &statuses[options->ipv6]; - if (*status != FF_UNITIALIZED) { + if (*status != FF_UNINITIALIZED) { fputs("Error: PublicIp module can only be used once due to internal limitations\n", stderr); exit(1); } @@ -53,7 +53,7 @@ static inline void wrapYyjsonFree(yyjson_doc** doc) { const char* ffDetectPublicIp(FFPublicIPOptions* options, FFPublicIpResult* result) { FFNetworkingState* state = &states[options->ipv6]; const char** status = &statuses[options->ipv6]; - if (*status == FF_UNITIALIZED) { + if (*status == FF_UNINITIALIZED) { ffPreparePublicIp(options); } @@ -65,7 +65,7 @@ const char* ffDetectPublicIp(FFPublicIPOptions* options, FFPublicIpResult* resul const char* error = ffNetworkingRecvHttpResponse(state, &response); *state = (FFNetworkingState) {}; - *status = FF_UNITIALIZED; + *status = FF_UNINITIALIZED; if (error == NULL) { ffStrbufSubstrAfterFirstS(&response, "\r\n\r\n"); From 594c73461ee3718fe023549a3159500ae9591e1d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=9D=8E=E9=80=9A=E6=B4=B2?= Date: Tue, 28 Apr 2026 09:37:14 +0800 Subject: [PATCH 20/92] Netif: fixes compiler warnings --- src/common/impl/netif_apple.c | 4 ++-- src/common/impl/netif_bsd.c | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/common/impl/netif_apple.c b/src/common/impl/netif_apple.c index 191a55c46f..288f04543b 100644 --- a/src/common/impl/netif_apple.c +++ b/src/common/impl/netif_apple.c @@ -25,8 +25,8 @@ get_rt_address(struct rt_msghdr* rtm, int desired) { struct sockaddr* sa = (struct sockaddr*) (rtm + 1); for (int i = 0; i < RTAX_MAX; i++) { - if (rtm->rtm_addrs & (1u << i)) { - if ((1u << i) == (unsigned) desired) { + if (rtm->rtm_addrs & (1 << i)) { + if ((1 << i) == desired) { return sa; } diff --git a/src/common/impl/netif_bsd.c b/src/common/impl/netif_bsd.c index c6ae8f6103..e2632be79d 100644 --- a/src/common/impl/netif_bsd.c +++ b/src/common/impl/netif_bsd.c @@ -32,8 +32,8 @@ get_rt_address(struct rt_msghdr* rtm, int desired) { struct sockaddr* sa = (struct sockaddr*) (rtm + 1); for (int i = 0; i < RTAX_MAX; i++) { - if (rtm->rtm_addrs & (1u << i)) { - if ((1u << i) == (unsigned) desired) { + if (rtm->rtm_addrs & (1 << i)) { + if ((1 << i) == desired) { return sa; } sa = (struct sockaddr*) (ROUNDUP(sa->sa_len) + (char*) sa); From 55e48965241a82d6f04b5bb86650bbab209dbd32 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=9D=8E=E9=80=9A=E6=B4=B2?= Date: Tue, 28 Apr 2026 19:45:19 +0800 Subject: [PATCH 21/92] Chore: runs `clang-format` --- src/common/impl/FFstrbuf.c | 8 ++++---- src/common/impl/io_windows.c | 2 +- src/common/impl/netif_apple.c | 8 ++++++-- src/common/impl/netif_bsd.c | 8 ++++++-- src/detection/host/host_linux.c | 1 - src/detection/terminalshell/terminalshell_windows.c | 7 ++++--- src/logo/builtin.c | 6 +++--- 7 files changed, 24 insertions(+), 16 deletions(-) diff --git a/src/common/impl/FFstrbuf.c b/src/common/impl/FFstrbuf.c index d0a38f9ce5..8005753dff 100644 --- a/src/common/impl/FFstrbuf.c +++ b/src/common/impl/FFstrbuf.c @@ -790,9 +790,9 @@ bool ffStrbufMatchSeparatedNS(const FFstrbuf* strbuf, uint32_t compLength, const } for (const char* p = comp; p < comp + compLength;) { - const char* colon = memchr(p, separator, (size_t)(comp + compLength - p)); + const char* colon = memchr(p, separator, (size_t) (comp + compLength - p)); if (colon == NULL) { - uint32_t remainingLen = (uint32_t)(comp + compLength - p); + uint32_t remainingLen = (uint32_t) (comp + compLength - p); return strbuf->length == remainingLen && memcmp(strbuf->chars, p, remainingLen) == 0; } @@ -818,9 +818,9 @@ bool ffStrbufMatchSeparatedIgnCaseNS(const FFstrbuf* strbuf, uint32_t compLength } for (const char* p = comp; p < comp + compLength;) { - const char* colon = memchr(p, separator, (size_t)(comp + compLength - p)); + const char* colon = memchr(p, separator, (size_t) (comp + compLength - p)); if (colon == NULL) { - uint32_t remainingLen = (uint32_t)(comp + compLength - p); + uint32_t remainingLen = (uint32_t) (comp + compLength - p); return strbuf->length == remainingLen && strncasecmp(strbuf->chars, p, remainingLen) == 0; } diff --git a/src/common/impl/io_windows.c b/src/common/impl/io_windows.c index 6e720396e4..cfac99e8e5 100644 --- a/src/common/impl/io_windows.c +++ b/src/common/impl/io_windows.c @@ -441,7 +441,7 @@ const char* ffGetTerminalResponse(const char* request, int nParams, const char* while (true) { DWORD bytes = 0; - if (!ReadFile(hInput, buffer + bytesRead, (DWORD)(sizeof(buffer) - 1 - bytesRead), &bytes, NULL) || bytes == 0) { + if (!ReadFile(hInput, buffer + bytesRead, (DWORD) (sizeof(buffer) - 1 - bytesRead), &bytes, NULL) || bytes == 0) { va_end(args); return "ReadFile() failed"; } diff --git a/src/common/impl/netif_apple.c b/src/common/impl/netif_apple.c index 288f04543b..1b12a88889 100644 --- a/src/common/impl/netif_apple.c +++ b/src/common/impl/netif_apple.c @@ -110,7 +110,9 @@ bool ffNetifGetDefaultRouteImplV4(FFNetifDefaultRouteResult* result) { && sdl->sdl_len #endif ) { - if (sdl->sdl_nlen > IF_NAMESIZE) return false; + if (sdl->sdl_nlen > IF_NAMESIZE) { + return false; + } memcpy(result->ifName, sdl->sdl_data, sdl->sdl_nlen); result->ifName[sdl->sdl_nlen] = '\0'; result->ifIndex = sdl->sdl_index; @@ -183,7 +185,9 @@ bool ffNetifGetDefaultRouteImplV6(FFNetifDefaultRouteResult* result) { && sdl->sdl_len #endif ) { - if (sdl->sdl_nlen > IF_NAMESIZE) return false; + if (sdl->sdl_nlen > IF_NAMESIZE) { + return false; + } memcpy(result->ifName, sdl->sdl_data, sdl->sdl_nlen); result->ifName[sdl->sdl_nlen] = '\0'; result->ifIndex = sdl->sdl_index; diff --git a/src/common/impl/netif_bsd.c b/src/common/impl/netif_bsd.c index e2632be79d..2b59c6d533 100644 --- a/src/common/impl/netif_bsd.c +++ b/src/common/impl/netif_bsd.c @@ -65,7 +65,9 @@ bool ffNetifGetDefaultRouteImplV4(FFNetifDefaultRouteResult* result) { if ((rtm->rtm_flags & RTF_GATEWAY) && !(rtm->rtm_flags & RTF_REJECT) && (sa->sa_family == AF_INET)) { struct sockaddr_dl* sdl = (struct sockaddr_dl*) get_rt_address(rtm, RTA_IFP); if (sdl && sdl->sdl_family == AF_LINK) { - if (sdl->sdl_nlen > IF_NAMESIZE) continue; + if (sdl->sdl_nlen > IF_NAMESIZE) { + continue; + } memcpy(result->ifName, sdl->sdl_data, sdl->sdl_nlen); result->ifName[sdl->sdl_nlen] = '\0'; result->ifIndex = sdl->sdl_index; @@ -106,7 +108,9 @@ bool ffNetifGetDefaultRouteImplV6(FFNetifDefaultRouteResult* result) { if ((rtm->rtm_flags & RTF_GATEWAY) && !(rtm->rtm_flags & RTF_REJECT) && (sa->sa_family == AF_INET6)) { struct sockaddr_dl* sdl = (struct sockaddr_dl*) get_rt_address(rtm, RTA_IFP); if (sdl && sdl->sdl_family == AF_LINK) { - if (sdl->sdl_nlen > IF_NAMESIZE) continue; + if (sdl->sdl_nlen > IF_NAMESIZE) { + continue; + } memcpy(result->ifName, sdl->sdl_data, sdl->sdl_nlen); result->ifName[sdl->sdl_nlen] = '\0'; result->ifIndex = sdl->sdl_index; diff --git a/src/detection/host/host_linux.c b/src/detection/host/host_linux.c index 051dd80994..81418a81d2 100644 --- a/src/detection/host/host_linux.c +++ b/src/detection/host/host_linux.c @@ -67,7 +67,6 @@ const char* ffDetectHost(FFHostResult* host) { if (ffStrbufStartsWithS(&host->name, "Standard PC")) { ffStrbufPrependS(&host->name, "KVM/QEMU "); } - #if __aarch64__ else if (host->family.length == 0 && ffStrbufEqualS(&host->vendor, "Apple Inc.") && ffStrbufStartsWithS(&host->name, "Mac")) { // Hack for Asahi Linux diff --git a/src/detection/terminalshell/terminalshell_windows.c b/src/detection/terminalshell/terminalshell_windows.c index 2ce9bb5fd6..cd687e892c 100644 --- a/src/detection/terminalshell/terminalshell_windows.c +++ b/src/detection/terminalshell/terminalshell_windows.c @@ -163,9 +163,10 @@ static bool detectDefaultTerminal(FFTerminalResult* result) { FF_AUTO_CLOSE_FD HANDLE hkcu = NULL; if (ffRegOpenKeyForRead(HKEY_CURRENT_USER, L"Console\\%%Startup", &hkcu, NULL) && ffRegReadData(hkcu, L"DelegationTerminal", &(FFArgBuffer) { - .data = uuid, - .length = (uint32_t) (sizeof(regPath) - (size_t) (uuid - regPath) * sizeof(wchar_t)), - }, NULL)) { + .data = uuid, + .length = (uint32_t) (sizeof(regPath) - (size_t) (uuid - regPath) * sizeof(wchar_t)), + }, + NULL)) { if (wcscmp(uuid, L"{00000000-0000-0000-0000-000000000000}") == 0 || // Let Windows decide wcscmp(uuid, L"{B23D10C0-E52E-411E-9D5B-C09FDF709C7D}") == 0) // Conhost { diff --git a/src/logo/builtin.c b/src/logo/builtin.c index 0cd83efe3b..f969f43795 100644 --- a/src/logo/builtin.c +++ b/src/logo/builtin.c @@ -1563,7 +1563,7 @@ static const FFlogo E[] = { }, // EN-OS { - .names = {"ENOS"}, + .names = { "ENOS" }, .lines = FASTFETCH_DATATEXT_LOGO_ENOS, .colors = { FF_COLOR_FG_LIGHT_BLUE, @@ -2665,7 +2665,7 @@ static const FFlogo L[] = { }, // LimeOS { - .names = {"LimeOS"}, + .names = { "LimeOS" }, .lines = FASTFETCH_DATATEXT_LOGO_LIMEOS, .colors = { FF_COLOR_FG_DEFAULT, @@ -4264,7 +4264,7 @@ static const FFlogo R[] = { }, // Redrose { - .names = {"Redrose"}, + .names = { "Redrose" }, .lines = FASTFETCH_DATATEXT_LOGO_REDROSE, .colors = { FF_COLOR_FG_RED, From e7cd5f618db4a555aa6944c00db456809f19d129 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=9D=8E=E9=80=9A=E6=B4=B2?= Date: Tue, 28 Apr 2026 19:52:38 +0800 Subject: [PATCH 22/92] Bluetooth (Windows): uses CM API to detect BTH battery detection Co-authored-by: Copilot --- CMakeLists.txt | 1 - src/detection/bluetooth/bluetooth_windows.c | 95 ++++++++++++++- src/detection/bluetooth/bluetooth_windows.cpp | 108 ------------------ 3 files changed, 93 insertions(+), 111 deletions(-) delete mode 100644 src/detection/bluetooth/bluetooth_windows.cpp diff --git a/CMakeLists.txt b/CMakeLists.txt index 2c6c8bdbb3..ad73c93f0e 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1038,7 +1038,6 @@ elseif(WIN32) src/detection/battery/battery_windows.c src/detection/bios/bios_windows.c src/detection/bluetooth/bluetooth_windows.c - src/detection/bluetooth/bluetooth_windows.cpp src/detection/bluetoothradio/bluetoothradio_windows.c src/detection/board/board_windows.c src/detection/bootmgr/bootmgr_windows.c diff --git a/src/detection/bluetooth/bluetooth_windows.c b/src/detection/bluetooth/bluetooth_windows.c index bc148609bf..8932aef505 100644 --- a/src/detection/bluetooth/bluetooth_windows.c +++ b/src/detection/bluetooth/bluetooth_windows.c @@ -1,12 +1,102 @@ #include "bluetooth.h" #include "common/library.h" +#include "common/mallocHelper.h" #include "common/windows/unicode.h" +#define INITGUID #include #include +#include +#include #pragma GCC diagnostic ignored "-Wpointer-sign" +// https://github.com/wine-mirror/wine/blob/ab6f4584b89f28504b0b277c0b4c723a86b4d6b7/include/ddk/bthguid.h#L4 +/* DEVPROP_TYPE_STRING */ +DEFINE_DEVPROPKEY(DEVPKEY_Bluetooth_DeviceAddress, 0x2bd67d8b, 0x8beb, 0x48d5, 0x87, 0xe0, 0x6c, 0xda, 0x34, 0x28, 0x04, 0x0a, 1); +/* DEVPROP_TYPE_UINT32 */ +DEFINE_DEVPROPKEY(DEVPKEY_Bluetooth_ClassOfDevice, 0x2bd67d8b, 0x8beb, 0x48d5, 0x87, 0xe0, 0x6c, 0xda, 0x34, 0x28, 0x04, 0x0a, 10); +/* DEVPROP_TYPE_FILETIME */ +DEFINE_DEVPROPKEY(DEVPKEY_Bluetooth_LastConnectedTime, 0x2bd67d8b, 0x8beb, 0x48d5, 0x87, 0xe0, 0x6c, 0xda, 0x34, 0x28, 0x04, 0x0a, 11); +/* DEVPROP_TYPE_GUID */ +DEFINE_DEVPROPKEY(DEVPKEY_Bluetooth_ServiceGUID, 0x2bd67d8b, 0x8beb, 0x48d5, 0x87, 0xe0, 0x6c, 0xda, 0x34, 0x28, 0x04, 0x0a, 2); +/* DEVPROP_TYPE_UINT8 */ +DEFINE_DEVPROPKEY(DEVPKEY_Bluetooth_BatteryLevel, 0x104ea319, 0x6ee2, 0x4701, 0xbd, 0x47, 0x8d, 0xdb, 0xf4, 0x25, 0xbb, 0xe5, 2); + +// TODO: use CM API to fetch bluetooth devices instead of BluetoothFindFirstDevice, if we find DEVPKEY_Bluetooth_IsConnected or similar +#define GUID_DEVCLASS_BLUETOOTH_STRING L"{e0cbf06c-cd8b-4647-bb8a-263b43f0f974}" // Found in +#define GUID_DEVCLASS_MEDIA_STRING L"{4d36e96c-e325-11ce-bfc1-08002be10318}" // Found in + +static const char* ffBluetoothDetectBattery(FFlist* devices) { + ULONG idListLength = 0; + CONFIGRET status = CM_Get_Device_ID_List_SizeW(&idListLength, GUID_DEVCLASS_MEDIA_STRING, CM_GETIDLIST_FILTER_PRESENT); + if (status != CR_SUCCESS) { + return "CM_Get_Device_ID_List_SizeW failed"; + } + + if (idListLength == 0) { + return NULL; + } + + wchar_t* FF_AUTO_FREE idList = (wchar_t*) malloc((size_t) idListLength * sizeof(wchar_t)); + if (!idList) { + return "malloc() failed"; + } + + status = CM_Get_Device_ID_ListW(GUID_DEVCLASS_MEDIA_STRING, idList, idListLength, CM_GETIDLIST_FILTER_PRESENT); + if (status != CR_SUCCESS) { + return "CM_Get_Device_ID_ListW failed"; + } + + for (const wchar_t* deviceId = idList; *deviceId; deviceId += wcslen(deviceId) + 1) { + DEVINST devInst = 0; + + // Hands-Free profile service; headsets often expose battery level through this media device node rather than the Bluetooth device node + // BthHFEnum + if (CM_Locate_DevNodeW(&devInst, (DEVINSTID_W) deviceId, CM_LOCATE_DEVNODE_NORMAL) != CR_SUCCESS) { + continue; + } + + uint8_t battery = 0; + { + DEVPROPTYPE devPropertyType = DEVPROP_TYPE_EMPTY; + ULONG propertySize = sizeof(battery); + if (CM_Get_DevNode_PropertyW(devInst, &DEVPKEY_Bluetooth_BatteryLevel, &devPropertyType, (PBYTE) &battery, &propertySize, 0) != CR_SUCCESS || devPropertyType != DEVPROP_TYPE_BYTE || propertySize != sizeof(battery)) { + continue; + } + } + + WCHAR deviceAddress[13]; // 6 bytes in hex + null terminator + { + DEVPROPTYPE devPropertyType = DEVPROP_TYPE_EMPTY; + ULONG propertySize = sizeof(deviceAddress); + if (CM_Get_DevNode_PropertyW(devInst, &DEVPKEY_Bluetooth_DeviceAddress, &devPropertyType, (PBYTE) deviceAddress, &propertySize, 0) != CR_SUCCESS || devPropertyType != DEVPROP_TYPE_STRING || propertySize != sizeof(deviceAddress)) { + continue; + } + } + + FF_LIST_FOR_EACH (FFBluetoothResult, bt, *devices) { + if (deviceAddress[0] == bt->address.chars[0] && + deviceAddress[1] == bt->address.chars[1] && + deviceAddress[2] == bt->address.chars[3] && + deviceAddress[3] == bt->address.chars[4] && + deviceAddress[4] == bt->address.chars[6] && + deviceAddress[5] == bt->address.chars[7] && + deviceAddress[6] == bt->address.chars[9] && + deviceAddress[7] == bt->address.chars[10] && + deviceAddress[8] == bt->address.chars[12] && + deviceAddress[9] == bt->address.chars[13] && + deviceAddress[10] == bt->address.chars[15] && + deviceAddress[11] == bt->address.chars[16]) { + bt->battery = battery; + break; + } + } + } + + return NULL; +} + const char* ffDetectBluetooth(FFBluetoothOptions* options, FFlist* devices /* FFBluetoothResult */) { FF_LIBRARY_LOAD_MESSAGE(bluetoothapis, "bluetoothapis.dll", 1) FF_LIBRARY_LOAD_SYMBOL_MESSAGE(bluetoothapis, BluetoothFindFirstDevice) @@ -125,8 +215,9 @@ const char* ffDetectBluetooth(FFBluetoothOptions* options, FFlist* devices /* FF ffBluetoothFindDeviceClose(hFind); - const char* ffBluetoothDetectBattery(FFlist * result); - ffBluetoothDetectBattery(devices); + if (devices->length > 0) { + ffBluetoothDetectBattery(devices); + } return NULL; } diff --git a/src/detection/bluetooth/bluetooth_windows.cpp b/src/detection/bluetooth/bluetooth_windows.cpp deleted file mode 100644 index 61f462efa8..0000000000 --- a/src/detection/bluetooth/bluetooth_windows.cpp +++ /dev/null @@ -1,108 +0,0 @@ -extern "C" { -#include "bluetooth.h" -} -#include "common/windows/wmi.hpp" -#include "common/windows/unicode.hpp" -#include "common/windows/util.hpp" - -extern "C" const char* ffBluetoothDetectBattery(FFlist* devices) { - FFWmiQuery query(L"SELECT __PATH FROM Win32_PnPEntity WHERE Service = 'BthHFEnum'", nullptr, FFWmiNamespace::CIMV2); - if (!query) { - return "Query WMI service failed"; - } - - IWbemClassObject* pInParams = nullptr; - on_scope_exit releaseInParams([&] { pInParams && pInParams->Release(); }); - { - IWbemClassObject* pnpEntityClass = nullptr; - - if (FAILED(query.pService->GetObjectW(bstr_t(L"Win32_PnPEntity"), 0, nullptr, &pnpEntityClass, nullptr))) { - return "Failed to get PnP entity class"; - } - on_scope_exit releasePnpEntityClass([&] { pnpEntityClass && pnpEntityClass->Release(); }); - - if (FAILED(pnpEntityClass->GetMethod(bstr_t(L"GetDeviceProperties"), 0, &pInParams, NULL))) { - return "Failed to get GetDeviceProperties method"; - } - - FFWmiVariant devicePropertyKeys({ L"{104EA319-6EE2-4701-BD47-8DDBF425BBE5} 2", L"DEVPKEY_Bluetooth_DeviceAddress" }); - if (FAILED(pInParams->Put(L"devicePropertyKeys", 0, &devicePropertyKeys, CIM_FLAG_ARRAY | CIM_STRING))) { - return "Failed to put devicePropertyKeys"; - } - } - - while (FFWmiRecord record = query.next()) { - IWbemCallResult* pCallResult = nullptr; - - if (FAILED(query.pService->ExecMethod(record.get(L"__PATH").bstrVal, bstr_t(L"GetDeviceProperties"), 0, nullptr, pInParams, nullptr, &pCallResult))) { - continue; - } - on_scope_exit releaseCallResult([&] { pCallResult && pCallResult->Release(); }); - - IWbemClassObject* pResultObject = nullptr; - if (FAILED(pCallResult->GetResultObject((LONG) WBEM_INFINITE, &pResultObject))) { - continue; - } - on_scope_exit releaseResultObject([&] { pResultObject && pResultObject->Release(); }); - - VARIANT propArray; - if (FAILED(pResultObject->Get(L"deviceProperties", 0, &propArray, nullptr, nullptr))) { - continue; - } - on_scope_exit releasePropArray([&] { VariantClear(&propArray); }); - - if (propArray.vt != (VT_ARRAY | VT_UNKNOWN) || - (propArray.parray->fFeatures & FADF_UNKNOWN) == 0 || - propArray.parray->cDims != 1 || - propArray.parray->rgsabound[0].cElements != 2) { - continue; - } - - uint8_t batt = 0; - for (LONG i = 0; i < 2; i++) { - IWbemClassObject* object = nullptr; - if (FAILED(SafeArrayGetElement(propArray.parray, &i, &object))) { - continue; - } - - FFWmiRecord rec(object); - auto data = rec.get(L"Data"); - if (data.vt == VT_EMPTY) { - break; - } - - if (i == 0) { - batt = data.get(); - } else { - FF_STRBUF_AUTO_DESTROY addr = ffStrbufCreateWSV(data.get()); // MAC address without colon - if (__builtin_expect(addr.length != 12, 0)) { - continue; - } - - FF_LIST_FOR_EACH (FFBluetoothResult, bt, *devices) { - if (bt->address.length != 12 + 5) { - continue; - } - - if (addr.chars[0] == bt->address.chars[0] && - addr.chars[1] == bt->address.chars[1] && - addr.chars[2] == bt->address.chars[3] && - addr.chars[3] == bt->address.chars[4] && - addr.chars[4] == bt->address.chars[6] && - addr.chars[5] == bt->address.chars[7] && - addr.chars[6] == bt->address.chars[9] && - addr.chars[7] == bt->address.chars[10] && - addr.chars[8] == bt->address.chars[12] && - addr.chars[9] == bt->address.chars[13] && - addr.chars[10] == bt->address.chars[15] && - addr.chars[11] == bt->address.chars[16]) { - bt->battery = batt; - break; - } - } - } - } - } - - return NULL; -} From 6e50aaf3ff590da1d0e6ecc8c53de198fda3c360 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=9D=8E=E9=80=9A=E6=B4=B2?= Date: Tue, 28 Apr 2026 23:41:44 +0800 Subject: [PATCH 23/92] Brightness (Windows): uses COM-less method to query WMI Co-authored-by: Copilot --- CMakeLists.txt | 2 +- src/common/windows/wmi.h | 57 +++++ src/detection/brightness/brightness_windows.c | 227 ++++++++++++++++++ .../brightness/brightness_windows.cpp | 140 ----------- 4 files changed, 285 insertions(+), 141 deletions(-) create mode 100644 src/common/windows/wmi.h create mode 100644 src/detection/brightness/brightness_windows.c delete mode 100644 src/detection/brightness/brightness_windows.cpp diff --git a/CMakeLists.txt b/CMakeLists.txt index ad73c93f0e..d8d8d00a8b 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1041,7 +1041,7 @@ elseif(WIN32) src/detection/bluetoothradio/bluetoothradio_windows.c src/detection/board/board_windows.c src/detection/bootmgr/bootmgr_windows.c - src/detection/brightness/brightness_windows.cpp + src/detection/brightness/brightness_windows.c src/detection/btrfs/btrfs_nosupport.c src/detection/chassis/chassis_windows.c src/detection/cpu/cpu_windows.c diff --git a/src/common/windows/wmi.h b/src/common/windows/wmi.h new file mode 100644 index 0000000000..da10f21b09 --- /dev/null +++ b/src/common/windows/wmi.h @@ -0,0 +1,57 @@ +#pragma once + +#include +#include +#include + +/** + * The WmiOpenBlock function opens the WMI data block object for the specified WMI class. + * + * \param Guid Specifies the GUID for WMI class. + * \param DesiredAccess Specifies the desired access rights to the data block object. + * \param DataBlockHandle Pointer to a memory location where the routine returns a handle to the data block object. + * \return ULONG Successful or errant status. + */ +NTSYSAPI ULONG NTAPI +WmiOpenBlock( + _In_ LPCGUID Guid, + _In_ ACCESS_MASK DesiredAccess, + _Out_ PHANDLE DataBlockHandle +); + +/** + * The WmiQueryAllDataW function returns all WMI data blocks that implement a given WMI class (Unicode). + * + * \param DataBlockHandle Handle to a WMI data block object. + * \param BufferLength Pointer to a memory location that specifies the size of the buffer. + * \param Buffer Pointer to the buffer where the routine returns the WMI data. + * \return ULONG Successful or errant status. + */ +NTSYSAPI ULONG NTAPI +WmiQueryAllDataW( + _In_ HANDLE DataBlockHandle, + _Inout_ PULONG BufferLength, + _Out_writes_bytes_opt_(*BufferLength) PVOID Buffer +); + +/** + * The WmiCloseBlock function closes a WMI data block object. + * + * \param DataBlockHandle Handle to the data block object to be closed. + * \return ULONG Successful or errant status. + */ +NTSYSAPI ULONG NTAPI +WmiCloseBlock( + _In_ HANDLE DataBlockHandle +); + +static inline void ffCloseWmiBlock(HANDLE* hBlock) { + assert(hBlock); + if (*hBlock) { + WmiCloseBlock(*hBlock); + } +} + +#define FF_AUTO_CLOSE_WMI_BLOCK __attribute__((cleanup(ffCloseWmiBlock))) + +// MOF: https://github.com/tpn/winsdk-10/blob/master/Include/10.0.16299.0/km/wmicore.mof diff --git a/src/detection/brightness/brightness_windows.c b/src/detection/brightness/brightness_windows.c new file mode 100644 index 0000000000..5f8cd77ed0 --- /dev/null +++ b/src/detection/brightness/brightness_windows.c @@ -0,0 +1,227 @@ +#include "brightness.h" +#include "detection/displayserver/displayserver.h" +#include "common/debug.h" +#include "common/library.h" +#include "common/mallocHelper.h" +#include "common/windows/wmi.h" +#include "common/windows/unicode.h" + +#include +#include +#include + +NTSYSAPI NTSTATUS WINAPI GetPhysicalMonitors( + _In_ UNICODE_STRING* pstrDeviceName, + _In_ DWORD dwPhysicalMonitorArraySize, + _Out_ DWORD* pdwNumPhysicalMonitorHandlesInArray, + _Out_ HANDLE* phPhysicalMonitorArray); + +typedef enum _MC_VCP_CODE_TYPE { + MC_MOMENTARY, + MC_SET_PARAMETER +} MC_VCP_CODE_TYPE, + *LPMC_VCP_CODE_TYPE; + +NTSYSAPI NTSTATUS WINAPI DDCCIGetVCPFeature( + _In_ HANDLE hMonitor, + _In_ DWORD dwVCPCode, + _Out_opt_ LPMC_VCP_CODE_TYPE pvct, + _Out_ DWORD* pdwCurrentValue, + _Out_opt_ DWORD* pdwMaximumValue); + +NTSYSAPI NTSTATUS WINAPI DestroyPhysicalMonitorInternal( + _In_ HANDLE hMonitor); + +NTSTATUS WINAPI GetPhysicalMonitorDescription( + _In_ HANDLE hMonitor, + _In_ DWORD dwPhysicalMonitorDescriptionSizeInChars, + _Out_ LPWSTR szPhysicalMonitorDescription); + +static const char* detectWithWmi(FFlist* result) { + FF_DEBUG("WMI: start detection"); + + // https://github.com/tpn/winsdk-10/blob/master/Include/10.0.16299.0/km/wmicore.mof#L21200 + const GUID WmiMonitorBrightnessGuid = { + 0xd43412ac, 0x67f9, 0x4fbb, { 0xa0, 0x81, 0x17, 0x52, 0xa2, 0xc3, 0x3e, 0x84 } + }; + + FF_AUTO_CLOSE_WMI_BLOCK HANDLE hBlock = NULL; + + ULONG status = WmiOpenBlock(&WmiMonitorBrightnessGuid, WMIGUID_QUERY, &hBlock); + if (status != 0) { + FF_DEBUG("WMI: WmiOpenBlock failed, status=%lu, error=%s", status, ffDebugWin32Error(GetLastError())); + return "WmiOpenBlock() failed"; + } + + ULONG bufferSize = 0; + status = WmiQueryAllDataW(hBlock, &bufferSize, NULL); + if (status != ERROR_SUCCESS && status != ERROR_INSUFFICIENT_BUFFER) { + FF_DEBUG("WMI: first WmiQueryAllDataW failed, status=%lu, bufferSize=%lu, error=%s", status, bufferSize, ffDebugWin32Error(GetLastError())); + return "WmiQueryAllDataW() failed"; + } + + FF_DEBUG("WMI: initial query status=%lu, bufferSize=%lu", status, bufferSize); + + if (bufferSize == 0) { + FF_DEBUG("WMI: WmiQueryAllDataW returned empty buffer"); + return "WmiQueryAllDataW() returned no data"; + } + + FF_AUTO_FREE PWNODE_ALL_DATA pAllData = (PWNODE_ALL_DATA) malloc(bufferSize); + if (!pAllData) { + FF_DEBUG("WMI: malloc failed, bufferSize=%lu", bufferSize); + return "malloc() failed"; + } + + status = WmiQueryAllDataW(hBlock, &bufferSize, pAllData); + if (status != ERROR_SUCCESS) { + FF_DEBUG("WMI: second WmiQueryAllDataW failed, status=%lu, bufferSize=%lu, error=%s", status, bufferSize, ffDebugWin32Error(GetLastError())); + return "WmiQueryAllDataW() failed"; + } + + if (bufferSize < sizeof(WNODE_ALL_DATA)) { + FF_DEBUG("WMI: insufficient buffer for WNODE_ALL_DATA, bufferSize=%lu", bufferSize); + return "WmiQueryAllDataW() returned insufficient data for WNODE_ALL_DATA"; + } + + FF_DEBUG("WMI: instanceCount=%lu, flags=0x%lX", pAllData->InstanceCount, pAllData->WnodeHeader.Flags); + + PULONG pNameOffsets = (PULONG) ((PUCHAR) pAllData + pAllData->OffsetInstanceNameOffsets); + + for (ULONG i = 0; i < pAllData->InstanceCount; i++) { + ULONG dataOffset = 0; + ULONG dataLength = 0; + + if (pAllData->WnodeHeader.Flags & WNODE_FLAG_FIXED_INSTANCE_SIZE) { + dataLength = pAllData->FixedInstanceSize; + dataOffset = pAllData->DataBlockOffset + i * dataLength; + } else { + dataOffset = pAllData->OffsetInstanceDataAndLength[i].OffsetInstanceData; + dataLength = pAllData->OffsetInstanceDataAndLength[i].LengthInstanceData; + } + + if (dataLength == 0 || dataOffset >= bufferSize || dataLength > bufferSize - dataOffset) { + FF_DEBUG("WMI: skip invalid instance %lu (dataOffset=%lu, dataLength=%lu, bufferSize=%lu)", i, dataOffset, dataLength, bufferSize); + continue; + } + + USHORT nameCharsCount = *(PUSHORT) ((PUCHAR) pAllData + pNameOffsets[i]) / sizeof(WCHAR); + PCWSTR pNameChars = (PCWSTR) ((PUCHAR) pAllData + (pNameOffsets[i] + sizeof(USHORT))); + + PUCHAR pDataBlock = (PUCHAR) pAllData + dataOffset; + UCHAR currentBrightness = pDataBlock[0]; + + FFBrightnessResult* brightness = FF_LIST_ADD(FFBrightnessResult, *result); + brightness->max = 100; + brightness->min = 0; + brightness->current = currentBrightness; + brightness->builtin = true; + ffStrbufInitNWS(&brightness->name, nameCharsCount, pNameChars); + ffStrbufSubstrAfterFirstC(&brightness->name, '\\'); + ffStrbufSubstrBeforeFirstC(&brightness->name, '\\'); + + FF_DEBUG("WMI: detected builtin display '%s', current=%u", brightness->name.chars, (unsigned) currentBrightness); + } + + FF_DEBUG("WMI: finished detection, total results=%u", result->length); + + return NULL; +} + +static const char* detectWithDdcci(const FFDisplayServerResult* displayServer, FFlist* result) { + FF_DEBUG("DDC/CI: start detection, displayCount=%u", displayServer->displays.length); + + void* gdi32 = ffLibraryGetModule(L"gdi32.dll"); + if (!gdi32) { + FF_DEBUG("DDC/CI: failed to load gdi32.dll: %s", ffDebugWin32Error(GetLastError())); + return "ffLibraryGetModule(gdi32.dll) failed"; + } + FF_LIBRARY_LOAD_SYMBOL_MESSAGE(gdi32, GetPhysicalMonitors) + FF_LIBRARY_LOAD_SYMBOL_MESSAGE(gdi32, DDCCIGetVCPFeature) + FF_LIBRARY_LOAD_SYMBOL_MESSAGE(gdi32, DestroyPhysicalMonitorInternal) + + FF_LIST_FOR_EACH (FFDisplayResult, display, displayServer->displays) { + if (display->type == FF_DISPLAY_TYPE_BUILTIN) { + FF_DEBUG("DDC/CI: skip builtin display id=%" PRIu64, display->id); + continue; + } + + MONITORINFOEXW mi; + mi.cbSize = sizeof(mi); + if (!GetMonitorInfoW((HMONITOR) (uintptr_t) display->id, (LPMONITORINFO) &mi)) { + FF_DEBUG("DDC/CI: GetMonitorInfoW failed for display id=%" PRIu64 ": %s", display->id, ffDebugWin32Error(GetLastError())); + continue; + } + + UNICODE_STRING deviceName = { + .Length = (USHORT) (wcslen(mi.szDevice) * sizeof(wchar_t)), + .MaximumLength = 0, + .Buffer = mi.szDevice, + }; + HANDLE physicalMonitor; + DWORD monitorCount = 0; + NTSTATUS monitorStatus = ffGetPhysicalMonitors(&deviceName, 1, &monitorCount, &physicalMonitor); + if (NT_SUCCESS(monitorStatus) && monitorCount >= 1) { + DWORD curr = 0, max = 0; + if (NT_SUCCESS(ffDDCCIGetVCPFeature(physicalMonitor, 0x10 /* luminance */, NULL, &curr, &max))) { + FFBrightnessResult* brightness = FF_LIST_ADD(FFBrightnessResult, *result); + if (display->name.length > 0) { + ffStrbufInitCopy(&brightness->name, &display->name); + } else { + FF_LIBRARY_LOAD_SYMBOL_LAZY(gdi32, GetPhysicalMonitorDescription) + if (ffGetPhysicalMonitorDescription) { + wchar_t description[128 /*MUST be PHYSICAL_MONITOR_DESCRIPTION_SIZE*/]; + if (NT_SUCCESS(ffGetPhysicalMonitorDescription(physicalMonitor, ARRAY_SIZE(description), description))) { + ffStrbufInitWS(&brightness->name, description); + } + } + if (brightness->name.length == 0) { + ffStrbufSetNWS(&brightness->name, deviceName.Length / 2, deviceName.Buffer); + } + } + brightness->max = max; + brightness->min = 0; + brightness->current = curr; + brightness->builtin = false; + + FF_DEBUG("DDC/CI: detected external display '%s', current=%u, max=%u", brightness->name.chars, (unsigned) curr, (unsigned) max); + } else { + FF_DEBUG("DDC/CI: DDCCIGetVCPFeature failed for monitor '%ls': %s", deviceName.Buffer, ffDebugWin32Error(GetLastError())); + } + + ffDestroyPhysicalMonitorInternal(physicalMonitor); + } else { + FF_DEBUG("DDC/CI: GetPhysicalMonitors failed for '%ls', status=0x%08X, monitorCount=%lu, error=%s", deviceName.Buffer, (unsigned) monitorStatus, monitorCount, ffDebugWin32Error(GetLastError())); + } + } + + FF_DEBUG("DDC/CI: finished detection, total results=%u", result->length); + return NULL; +} + +static bool hasBuiltinDisplay(const FFDisplayServerResult* displayServer) { + FF_LIST_FOR_EACH (FFDisplayResult, display, displayServer->displays) { + if (display->type == FF_DISPLAY_TYPE_BUILTIN || display->type == FF_DISPLAY_TYPE_UNKNOWN) { + return true; + } + } + return false; +} + +const char* ffDetectBrightness(FF_A_UNUSED FFBrightnessOptions* options, FFlist* result) { + const FFDisplayServerResult* displayServer = ffConnectDisplayServer(); + FF_DEBUG("start, displayCount=%u", displayServer->displays.length); + + if (hasBuiltinDisplay(displayServer)) { + FF_DEBUG("builtin display detected, trying WMI"); + detectWithWmi(result); + } + + if (result->length < displayServer->displays.length) { + FF_DEBUG("resultCount=%u < displayCount=%u, trying DDC/CI", result->length, displayServer->displays.length); + detectWithDdcci(displayServer, result); + } + + FF_DEBUG("finished, resultCount=%u", result->length); + return NULL; +} diff --git a/src/detection/brightness/brightness_windows.cpp b/src/detection/brightness/brightness_windows.cpp deleted file mode 100644 index 43e78a82fc..0000000000 --- a/src/detection/brightness/brightness_windows.cpp +++ /dev/null @@ -1,140 +0,0 @@ -extern "C" { -#include "brightness.h" -#include "detection/displayserver/displayserver.h" -#include "common/library.h" -} -#include "common/windows/wmi.hpp" -#include "common/windows/unicode.hpp" - -#include - -NTSYSAPI NTSTATUS WINAPI GetPhysicalMonitors( - _In_ UNICODE_STRING* pstrDeviceName, - _In_ DWORD dwPhysicalMonitorArraySize, - _Out_ DWORD* pdwNumPhysicalMonitorHandlesInArray, - _Out_ HANDLE* phPhysicalMonitorArray); - -typedef enum _MC_VCP_CODE_TYPE { - MC_MOMENTARY, - MC_SET_PARAMETER -} MC_VCP_CODE_TYPE, - *LPMC_VCP_CODE_TYPE; - -NTSYSAPI NTSTATUS WINAPI DDCCIGetVCPFeature( - _In_ HANDLE hMonitor, - _In_ DWORD dwVCPCode, - _Out_opt_ LPMC_VCP_CODE_TYPE pvct, - _Out_ DWORD* pdwCurrentValue, - _Out_opt_ DWORD* pdwMaximumValue); - -NTSYSAPI NTSTATUS WINAPI DestroyPhysicalMonitorInternal( - _In_ HANDLE hMonitor); - -NTSTATUS WINAPI GetPhysicalMonitorDescription( - _In_ HANDLE hMonitor, - _In_ DWORD dwPhysicalMonitorDescriptionSizeInChars, - _Out_ LPWSTR szPhysicalMonitorDescription); - -static const char* detectWithWmi(FFlist* result) { - FFWmiQuery query(L"SELECT CurrentBrightness, InstanceName FROM WmiMonitorBrightness WHERE Active = true", nullptr, FFWmiNamespace::WMI); - if (!query) { - return "Query WMI service failed"; - } - - while (FFWmiRecord record = query.next()) { - if (FFWmiVariant vtValue = record.get(L"CurrentBrightness")) { - FFBrightnessResult* brightness = FF_LIST_ADD(FFBrightnessResult, *result); - brightness->max = 100; - brightness->min = 0; - brightness->current = vtValue.get(); - brightness->builtin = true; - - ffStrbufInit(&brightness->name); - if (FFWmiVariant vtName = record.get(L"InstanceName")) { - ffStrbufSetWSV(&brightness->name, vtName.get()); - ffStrbufSubstrAfterFirstC(&brightness->name, '\\'); - ffStrbufSubstrBeforeFirstC(&brightness->name, '\\'); - } - } - } - return NULL; -} - -static const char* detectWithDdcci(const FFDisplayServerResult* displayServer, FFlist* result) { - void* gdi32 = ffLibraryGetModule(L"gdi32.dll"); - if (!gdi32) { - return "ffLibraryGetModule(gdi32.dll) failed"; - } - FF_LIBRARY_LOAD_SYMBOL_MESSAGE(gdi32, GetPhysicalMonitors) - FF_LIBRARY_LOAD_SYMBOL_MESSAGE(gdi32, DDCCIGetVCPFeature) - FF_LIBRARY_LOAD_SYMBOL_MESSAGE(gdi32, DestroyPhysicalMonitorInternal) - - FF_LIST_FOR_EACH (FFDisplayResult, display, displayServer->displays) { - if (display->type == FF_DISPLAY_TYPE_BUILTIN) { - continue; - } - - MONITORINFOEXW mi; - mi.cbSize = sizeof(mi); - if (!GetMonitorInfoW((HMONITOR) (uintptr_t) display->id, (LPMONITORINFO) &mi)) { - continue; - } - - UNICODE_STRING deviceName = { - .Length = (USHORT) (wcslen(mi.szDevice) * sizeof(wchar_t)), - .MaximumLength = 0, - .Buffer = mi.szDevice, - }; - HANDLE physicalMonitor; - DWORD monitorCount = 0; - if (NT_SUCCESS(ffGetPhysicalMonitors(&deviceName, 1, &monitorCount, &physicalMonitor)) && monitorCount >= 1) { - DWORD curr = 0, max = 0; - if (NT_SUCCESS(ffDDCCIGetVCPFeature(physicalMonitor, 0x10 /* luminance */, NULL, &curr, &max))) { - FFBrightnessResult* brightness = FF_LIST_ADD(FFBrightnessResult, *result); - if (display->name.length > 0) { - ffStrbufInitCopy(&brightness->name, &display->name); - } else { - FF_LIBRARY_LOAD_SYMBOL_LAZY(gdi32, GetPhysicalMonitorDescription) - if (ffGetPhysicalMonitorDescription) { - wchar_t description[128 /*MUST be PHYSICAL_MONITOR_DESCRIPTION_SIZE*/]; - if (NT_SUCCESS(ffGetPhysicalMonitorDescription(physicalMonitor, ARRAY_SIZE(description), description))) { - ffStrbufInitWS(&brightness->name, description); - } - } - if (brightness->name.length == 0) { - ffStrbufSetNWS(&brightness->name, deviceName.Length / 2, deviceName.Buffer); - } - } - brightness->max = max; - brightness->min = 0; - brightness->current = curr; - brightness->builtin = false; - } - - ffDestroyPhysicalMonitorInternal(physicalMonitor); - } - } - return NULL; -} - -static bool hasBuiltinDisplay(const FFDisplayServerResult* displayServer) { - FF_LIST_FOR_EACH (FFDisplayResult, display, displayServer->displays) { - if (display->type == FF_DISPLAY_TYPE_BUILTIN || display->type == FF_DISPLAY_TYPE_UNKNOWN) { - return true; - } - } - return false; -} - -extern "C" const char* ffDetectBrightness(FF_A_UNUSED FFBrightnessOptions* options, FFlist* result) { - const FFDisplayServerResult* displayServer = ffConnectDisplayServer(); - - if (hasBuiltinDisplay(displayServer)) { - detectWithWmi(result); - } - - if (result->length < displayServer->displays.length) { - detectWithDdcci(displayServer, result); - } - return NULL; -} From 24ec0e60351eb9460269187b1ea121e260f01ab0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=9D=8E=E9=80=9A=E6=B4=B2?= Date: Tue, 28 Apr 2026 23:54:40 +0800 Subject: [PATCH 24/92] Common (Windows): removes old COM based WMI query implementation --- CMakeLists.txt | 1 - src/common/windows/wmi.cpp | 284 ------------------------------------- src/common/windows/wmi.hpp | 97 ------------- 3 files changed, 382 deletions(-) delete mode 100644 src/common/windows/wmi.cpp delete mode 100644 src/common/windows/wmi.hpp diff --git a/CMakeLists.txt b/CMakeLists.txt index d8d8d00a8b..b1dffb22fc 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1032,7 +1032,6 @@ elseif(WIN32) src/common/windows/com.cpp src/common/windows/registry.c src/common/windows/unicode.c - src/common/windows/wmi.cpp src/common/windows/variant.cpp src/common/windows/version.c src/detection/battery/battery_windows.c diff --git a/src/common/windows/wmi.cpp b/src/common/windows/wmi.cpp deleted file mode 100644 index 381bf59224..0000000000 --- a/src/common/windows/wmi.cpp +++ /dev/null @@ -1,284 +0,0 @@ -#include "wmi.hpp" -#include "common/windows/com.hpp" -#include "common/windows/unicode.hpp" - -#include -#include -#include - -static const char* doInitService(const wchar_t* networkResource, IWbemServices** result) { - HRESULT hres; - - // Obtain the initial locator to WMI - IWbemLocator* pLoc = nullptr; - hres = CoCreateInstance( - CLSID_WbemLocator, - nullptr, - CLSCTX_INPROC_SERVER, - IID_IWbemLocator, - (LPVOID*) &pLoc); - - if (FAILED(hres)) { - return "Failed to create IWbemLocator object"; - } - - // Connect to WMI through the IWbemLocator::ConnectServer method - IWbemServices* pSvc = nullptr; - - // Connect to the root\cimv2 namespace with - // the current user and obtain pointer pSvc - // to make IWbemServices calls. - hres = pLoc->ConnectServer( - bstr_t(networkResource), // Object path of WMI namespace - nullptr, // User name. nullptr = current user - nullptr, // User password. nullptr = current - 0, // Locale. nullptr indicates current - 0, // Security flags. - 0, // Authority (for example, Kerberos) - 0, // Context object - &pSvc // pointer to IWbemServices proxy - ); - pLoc->Release(); - pLoc = nullptr; - - if (FAILED(hres)) { - return "Could not connect WMI server"; - } - - *result = pSvc; - return NULL; -} - -FFWmiQuery::FFWmiQuery(const wchar_t* queryStr, FFstrbuf* error, FFWmiNamespace wmiNs) { - const char* errStr; - if ((errStr = ffInitCom())) { - if (error) { - ffStrbufSetS(error, errStr); - } - return; - } - - static IWbemServices* contexts[(int) FFWmiNamespace::LAST]; - - IWbemServices* context = contexts[(int) wmiNs]; - if (!context) { - if ((errStr = doInitService(wmiNs == FFWmiNamespace::CIMV2 ? L"ROOT\\CIMV2" : L"ROOT\\WMI", &context))) { - if (error) { - ffStrbufSetS(error, errStr); - } - return; - } - contexts[(int) wmiNs] = context; - } - - this->pService = context; - - // Use the IWbemServices pointer to make requests of WMI - HRESULT hres = context->ExecQuery( - bstr_t(L"WQL"), - bstr_t(queryStr), - WBEM_FLAG_FORWARD_ONLY | WBEM_FLAG_RETURN_IMMEDIATELY, - nullptr, - &this->pEnumerator); - - if (FAILED(hres)) { - if (error) { - ffStrbufAppendF(error, "Query for '%ls' failed. Error code = 0x%lX", queryStr, hres); - } - } -} - -bool FFWmiRecord::getString(const wchar_t* key, FFstrbuf* strbuf) { - bool result = true; - - FFWmiVariant vtProp; - - CIMTYPE type; - if (FAILED(obj->Get(key, 0, &vtProp, &type, nullptr)) || vtProp.vt != VT_BSTR) { - result = false; - } else { - switch (vtProp.vt) { - case VT_BSTR: - if (type == CIM_DATETIME) { - FF_AUTO_RELEASE_COM_OBJECT ISWbemDateTime* pDateTime = nullptr; - BSTR dateStr; - if (FAILED(CoCreateInstance(__uuidof(SWbemDateTime), 0, CLSCTX_INPROC_SERVER, IID_PPV_ARGS(&pDateTime)))) { - result = false; - } else if (FAILED(pDateTime->put_Value(vtProp.bstrVal))) { - result = false; - } else if (FAILED(pDateTime->GetFileTime(VARIANT_TRUE, &dateStr))) { - result = false; - } else { - ffStrbufSetNWS(strbuf, SysStringLen(dateStr), dateStr); - } - } else { - ffStrbufSetNWS(strbuf, SysStringLen(vtProp.bstrVal), vtProp.bstrVal); - } - break; - - case VT_LPSTR: - ffStrbufAppendS(strbuf, vtProp.pcVal); - break; - - case VT_LPWSTR: - default: - ffStrbufSetWS(strbuf, vtProp.bstrVal); - break; - } - } - return result; -} - -bool FFWmiRecord::getSigned(const wchar_t* key, int64_t* integer) { - bool result = true; - - FFWmiVariant vtProp; - - CIMTYPE type; - if (FAILED(obj->Get(key, 0, &vtProp, &type, nullptr))) { - result = false; - } else { - switch (vtProp.vt) { - case VT_BSTR: - *integer = wcstoll(vtProp.bstrVal, nullptr, 10); - break; - case VT_I1: - *integer = vtProp.cVal; - break; - case VT_I2: - *integer = vtProp.iVal; - break; - case VT_INT: - case VT_I4: - *integer = vtProp.intVal; - break; - case VT_I8: - *integer = vtProp.llVal; - break; - case VT_UI1: - *integer = (int64_t) vtProp.bVal; - break; - case VT_UI2: - *integer = (int64_t) vtProp.uiVal; - break; - case VT_UINT: - case VT_UI4: - *integer = (int64_t) vtProp.uintVal; - break; - case VT_UI8: - *integer = (int64_t) vtProp.ullVal; - break; - case VT_BOOL: - *integer = vtProp.boolVal != VARIANT_FALSE; - break; - default: - *integer = 0; - result = false; - } - } - return result; -} - -bool FFWmiRecord::getUnsigned(const wchar_t* key, uint64_t* integer) { - bool result = true; - - FFWmiVariant vtProp; - - if (FAILED(obj->Get(key, 0, &vtProp, nullptr, nullptr))) { - result = false; - } else { - switch (vtProp.vt) { - case VT_BSTR: - *integer = wcstoull(vtProp.bstrVal, nullptr, 10); - break; - case VT_I1: - *integer = (uint64_t) vtProp.cVal; - break; - case VT_I2: - *integer = (uint64_t) vtProp.iVal; - break; - case VT_INT: - case VT_I4: - *integer = (uint64_t) vtProp.intVal; - break; - case VT_I8: - *integer = (uint64_t) vtProp.llVal; - break; - case VT_UI1: - *integer = vtProp.bVal; - break; - case VT_UI2: - *integer = vtProp.uiVal; - break; - case VT_UINT: - case VT_UI4: - *integer = vtProp.uintVal; - break; - case VT_UI8: - *integer = vtProp.ullVal; - break; - case VT_BOOL: - *integer = vtProp.boolVal != VARIANT_FALSE; - break; - default: - *integer = 0; - result = false; - } - } - return result; -} - -bool FFWmiRecord::getReal(const wchar_t* key, double* real) { - bool result = true; - - FFWmiVariant vtProp; - - if (FAILED(obj->Get(key, 0, &vtProp, nullptr, nullptr))) { - result = false; - } else { - switch (vtProp.vt) { - case VT_BSTR: - *real = wcstod(vtProp.bstrVal, nullptr); - break; - case VT_I1: - *real = vtProp.cVal; - break; - case VT_I2: - *real = vtProp.iVal; - break; - case VT_INT: - case VT_I4: - *real = vtProp.intVal; - break; - case VT_I8: - *real = (double) vtProp.llVal; - break; - case VT_UI1: - *real = vtProp.bVal; - break; - case VT_UI2: - *real = vtProp.uiVal; - break; - case VT_UINT: - case VT_UI4: - *real = vtProp.uintVal; - break; - case VT_UI8: - *real = (double) vtProp.ullVal; - break; - case VT_R4: - *real = vtProp.fltVal; - break; - case VT_R8: - *real = vtProp.dblVal; - break; - case VT_BOOL: - *real = vtProp.boolVal != VARIANT_FALSE; - break; - default: - *real = -DBL_MAX; - result = false; - } - } - return result; -} diff --git a/src/common/windows/wmi.hpp b/src/common/windows/wmi.hpp deleted file mode 100644 index 47334f78ec..0000000000 --- a/src/common/windows/wmi.hpp +++ /dev/null @@ -1,97 +0,0 @@ -#pragma once - -#ifdef __cplusplus - -extern "C" { - #include "fastfetch.h" -} - - #include - #include - - #include "variant.hpp" - -enum class FFWmiNamespace { - CIMV2, - WMI, - LAST, -}; - -struct FFWmiRecord { - IWbemClassObject* obj = nullptr; - - explicit FFWmiRecord(IWbemClassObject* obj) : obj(obj) {}; - FFWmiRecord(const FFWmiRecord&) = delete; - FFWmiRecord(FFWmiRecord&& other) { - *this = (FFWmiRecord&&) other; - } - ~FFWmiRecord() { - if (obj) { - obj->Release(); - } - } - explicit operator bool() { - return !!obj; - } - FFWmiRecord& operator=(FFWmiRecord&& other) { - if (obj) { - obj->Release(); - } - obj = other.obj; - other.obj = nullptr; - return *this; - } - - bool getString(const wchar_t* key, FFstrbuf* strbuf); - bool getSigned(const wchar_t* key, int64_t* integer); - bool getUnsigned(const wchar_t* key, uint64_t* integer); - bool getReal(const wchar_t* key, double* real); - FFWmiVariant get(const wchar_t* key) { - FFWmiVariant result; - obj->Get(key, 0, &result, nullptr, nullptr); - return result; - } -}; - -struct FFWmiQuery { - IWbemServices* pService = nullptr; - IEnumWbemClassObject* pEnumerator = nullptr; - - FFWmiQuery(const wchar_t* queryStr, FFstrbuf* error = nullptr, FFWmiNamespace wmiNs = FFWmiNamespace::CIMV2); - explicit FFWmiQuery(IEnumWbemClassObject* pEnumerator) : pEnumerator(pEnumerator) {} - FFWmiQuery(const FFWmiQuery& other) = delete; - FFWmiQuery(FFWmiQuery&& other) { - *this = (FFWmiQuery&&) other; - } - ~FFWmiQuery() { - if (pEnumerator) { - pEnumerator->Release(); - } - } - - explicit operator bool() { - return !!pEnumerator; - } - FFWmiQuery& operator=(FFWmiQuery&& other) { - if (pEnumerator) { - pEnumerator->Release(); - } - pEnumerator = other.pEnumerator; - other.pEnumerator = nullptr; - return *this; - } - - FFWmiRecord next() { - IWbemClassObject* obj = nullptr; - ULONG ret; - pEnumerator->Next(instance.config.general.wmiTimeout, 1, &obj, &ret); - - FFWmiRecord result(obj); - return result; - } -}; - -#else - // Win32 COM headers requires C++ compiler - #error Must be included in C++ source file -#endif //__cplusplus From 2e4dd1d2627bac798c9d7f9660f3c7cde687988b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=9D=8E=E9=80=9A=E6=B4=B2?= Date: Wed, 29 Apr 2026 00:25:52 +0800 Subject: [PATCH 25/92] Chore: silenses compiler warnings --- src/common/windows/version.c | 10 ++-------- 1 file changed, 2 insertions(+), 8 deletions(-) diff --git a/src/common/windows/version.c b/src/common/windows/version.c index 6b4c589fed..4d61c78ae6 100644 --- a/src/common/windows/version.c +++ b/src/common/windows/version.c @@ -11,10 +11,7 @@ bool ffGetFileVersion(const wchar_t* filePath, const wchar_t* stringName, FFstrb DWORD handle; DWORD size = GetFileVersionInfoSizeW(filePath, &handle); if (size == 0) { - DWORD err = GetLastError(); - FF_DEBUG("GetFileVersionInfoSizeW failed: err=%lu (%s)", - (unsigned long) err, - ffDebugWin32Error(err)); + FF_DEBUG("GetFileVersionInfoSizeW failed: %s", ffDebugWin32Error(GetLastError())); return false; } @@ -29,10 +26,7 @@ bool ffGetFileVersion(const wchar_t* filePath, const wchar_t* stringName, FFstrb } if (!GetFileVersionInfoW(filePath, handle, size, versionData)) { - DWORD err = GetLastError(); - FF_DEBUG("GetFileVersionInfoW failed: err=%lu (%s)", - (unsigned long) err, - ffDebugWin32Error(err)); + FF_DEBUG("GetFileVersionInfoW failed: %s", ffDebugWin32Error(GetLastError())); return false; } From 69cc19d6f5415469e286a7782e65ee7988c6cf0e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=9D=8E=E9=80=9A=E6=B4=B2?= Date: Wed, 29 Apr 2026 18:34:21 +0800 Subject: [PATCH 26/92] Brightness (Windows): cleanups debug log --- src/detection/brightness/brightness_windows.c | 14 +++++--------- 1 file changed, 5 insertions(+), 9 deletions(-) diff --git a/src/detection/brightness/brightness_windows.c b/src/detection/brightness/brightness_windows.c index 5f8cd77ed0..57b53cff28 100644 --- a/src/detection/brightness/brightness_windows.c +++ b/src/detection/brightness/brightness_windows.c @@ -49,18 +49,18 @@ static const char* detectWithWmi(FFlist* result) { ULONG status = WmiOpenBlock(&WmiMonitorBrightnessGuid, WMIGUID_QUERY, &hBlock); if (status != 0) { - FF_DEBUG("WMI: WmiOpenBlock failed, status=%lu, error=%s", status, ffDebugWin32Error(GetLastError())); + FF_DEBUG("WMI: WmiOpenBlock failed: %s", ffDebugWin32Error(status)); return "WmiOpenBlock() failed"; } ULONG bufferSize = 0; status = WmiQueryAllDataW(hBlock, &bufferSize, NULL); if (status != ERROR_SUCCESS && status != ERROR_INSUFFICIENT_BUFFER) { - FF_DEBUG("WMI: first WmiQueryAllDataW failed, status=%lu, bufferSize=%lu, error=%s", status, bufferSize, ffDebugWin32Error(GetLastError())); + FF_DEBUG("WMI: first WmiQueryAllDataW failed, bufferSize=%lu: %s", bufferSize, ffDebugWin32Error(status)); return "WmiQueryAllDataW() failed"; } - FF_DEBUG("WMI: initial query status=%lu, bufferSize=%lu", status, bufferSize); + FF_DEBUG("WMI: initial query bufferSize=%lu", bufferSize); if (bufferSize == 0) { FF_DEBUG("WMI: WmiQueryAllDataW returned empty buffer"); @@ -68,14 +68,10 @@ static const char* detectWithWmi(FFlist* result) { } FF_AUTO_FREE PWNODE_ALL_DATA pAllData = (PWNODE_ALL_DATA) malloc(bufferSize); - if (!pAllData) { - FF_DEBUG("WMI: malloc failed, bufferSize=%lu", bufferSize); - return "malloc() failed"; - } status = WmiQueryAllDataW(hBlock, &bufferSize, pAllData); if (status != ERROR_SUCCESS) { - FF_DEBUG("WMI: second WmiQueryAllDataW failed, status=%lu, bufferSize=%lu, error=%s", status, bufferSize, ffDebugWin32Error(GetLastError())); + FF_DEBUG("WMI: second WmiQueryAllDataW failed, bufferSize=%lu: %s", bufferSize, ffDebugWin32Error(status)); return "WmiQueryAllDataW() failed"; } @@ -133,7 +129,7 @@ static const char* detectWithDdcci(const FFDisplayServerResult* displayServer, F void* gdi32 = ffLibraryGetModule(L"gdi32.dll"); if (!gdi32) { - FF_DEBUG("DDC/CI: failed to load gdi32.dll: %s", ffDebugWin32Error(GetLastError())); + FF_DEBUG("DDC/CI: failed to load gdi32.dll"); return "ffLibraryGetModule(gdi32.dll) failed"; } FF_LIBRARY_LOAD_SYMBOL_MESSAGE(gdi32, GetPhysicalMonitors) From 025febc78d30778b6e7a0fc1f57cb1f152c64083 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=9D=8E=E9=80=9A=E6=B4=B2?= Date: Wed, 29 Apr 2026 15:45:18 +0800 Subject: [PATCH 27/92] Battery (Windows): switches to query WMI with NtApi as a fast path Co-authored-by: Copilot --- src/detection/battery/battery_windows.c | 556 ++++++++++++++---------- 1 file changed, 318 insertions(+), 238 deletions(-) diff --git a/src/detection/battery/battery_windows.c b/src/detection/battery/battery_windows.c index de94d75a8e..777c93e35e 100644 --- a/src/detection/battery/battery_windows.c +++ b/src/detection/battery/battery_windows.c @@ -1,298 +1,378 @@ +#define INITGUID + #include "battery.h" -#include "common/io.h" -#include "common/windows/nt.h" -#include "common/windows/unicode.h" +#include "common/debug.h" #include "common/mallocHelper.h" -#include "common/smbios.h" +#include "common/windows/unicode.h" +#include "common/windows/wmi.h" + +#include + +typedef void(WINAPI* PINTERFACE_REFERENCE)(PVOID Context); +typedef void(WINAPI* PINTERFACE_DEREFERENCE)(PVOID Context); +typedef struct _DEVICE_OBJECT* PDEVICE_OBJECT; +typedef struct _IRP* PIRP; +#ifdef _WINDOWS_ + #undef _WINDOWS_ +#endif -#undef WIN32_LEAN_AND_MEAN -#include #include -#include -#include - -static const char* detectWithCmApi(FFBatteryOptions* options, FFlist* results) { - // https://learn.microsoft.com/en-us/windows-hardware/drivers/install/using-device-interfaces - ULONG cchDeviceInterfaces = 0; - if (CM_Get_Device_Interface_List_SizeW( - &cchDeviceInterfaces, - (LPGUID) &GUID_DEVCLASS_BATTERY, - NULL, - CM_GET_DEVICE_INTERFACE_LIST_PRESENT) != CR_SUCCESS) { - return "CM_Get_Device_Interface_List_SizeW() failed"; + +#pragma GCC diagnostic ignored "-Wmultichar" + +typedef struct FFBatteryWmiEntry { + ULONG tag; + FFBatteryResult* result; +} FFBatteryWmiEntry; + +static FFBatteryWmiEntry* getBatteryEntry(FFlist* entries, FFlist* results, ULONG tag) { + FF_LIST_FOR_EACH (FFBatteryWmiEntry, entry, *entries) { + if (entry->tag == tag) { + return entry; + } } - if (cchDeviceInterfaces <= 1) { - return NULL; // Not found + FFBatteryWmiEntry* entry = FF_LIST_ADD(FFBatteryWmiEntry, *entries); + entry->tag = tag; + FFBatteryResult* battery = FF_LIST_ADD(FFBatteryResult, *results); + entry->result = battery; + ffStrbufInit(&battery->manufacturer); + ffStrbufInit(&battery->manufactureDate); + ffStrbufInit(&battery->modelName); + ffStrbufInit(&battery->technology); + ffStrbufInit(&battery->serial); + battery->status = FF_BATTERY_STATUS_NONE; + battery->capacity = -1; + battery->temperature = FF_BATTERY_TEMP_UNSET; + battery->cycleCount = 0; + battery->timeRemaining = -1; + return entry; +} + +static const char* queryWmiAllData(const GUID* guid, const char* guidStr, PWNODE_ALL_DATA* pAllData, ULONG* pBufferSize) { + FF_AUTO_CLOSE_WMI_BLOCK HANDLE hBlock = NULL; + ULONG status = WmiOpenBlock(guid, WMIGUID_QUERY, &hBlock); + if (status != ERROR_SUCCESS) { + FF_DEBUG("WMI: WmiOpenBlock() failed for %s: %s", guidStr, ffDebugWin32Error(status)); + return "WmiOpenBlock() failed"; } - wchar_t* FF_AUTO_FREE mszDeviceInterfaces = (wchar_t*) malloc(cchDeviceInterfaces * sizeof(wchar_t)); - if (CM_Get_Device_Interface_ListW( - (LPGUID) &GUID_DEVCLASS_BATTERY, - NULL, - mszDeviceInterfaces, - cchDeviceInterfaces, - CM_GET_DEVICE_INTERFACE_LIST_PRESENT) != CR_SUCCESS) { - return "CM_Get_Device_Interface_ListW() failed"; + status = WmiQueryAllDataW(hBlock, pBufferSize, NULL); + if (status != ERROR_SUCCESS && status != ERROR_INSUFFICIENT_BUFFER) { + FF_DEBUG("WMI: first WmiQueryAllDataW() failed: %s", ffDebugWin32Error(status)); + return "WmiQueryAllDataW(NULL) failed"; } - for (const wchar_t* p = mszDeviceInterfaces; *p; p += wcslen(p) + 1) { - HANDLE FF_AUTO_CLOSE_FD hBattery = - CreateFileW(p, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL); + if (*pBufferSize == 0) { + return "WmiQueryAllDataW(NULL) returned no data"; + } - if (hBattery == INVALID_HANDLE_VALUE) { - continue; - } + if (*pBufferSize < sizeof(WNODE_ALL_DATA)) { + FF_DEBUG("WMI: WmiQueryAllDataW() returned insufficient buffer size: %lu", *pBufferSize); + return "WmiQueryAllDataW() returned insufficient data for WNODE_ALL_DATA"; + } - BATTERY_QUERY_INFORMATION bqi = { .InformationLevel = BatteryInformation }; + *pAllData = (PWNODE_ALL_DATA) malloc(*pBufferSize); - DWORD dwWait = 0; - DWORD dwOut; + status = WmiQueryAllDataW(hBlock, pBufferSize, *pAllData); + if (status != ERROR_SUCCESS) { + FF_DEBUG("WMI: second WmiQueryAllDataW failed: %s", ffDebugWin32Error(status)); + free(*pAllData); + *pAllData = NULL; + return "WmiQueryAllDataW(*pAllData) failed"; + } - if (!DeviceIoControl(hBattery, IOCTL_BATTERY_QUERY_TAG, &dwWait, sizeof(dwWait), &bqi.BatteryTag, sizeof(bqi.BatteryTag), &dwOut, NULL) && bqi.BatteryTag) { - continue; - } + return NULL; +} - BATTERY_INFORMATION bi = { 0 }; - if (!DeviceIoControl(hBattery, IOCTL_BATTERY_QUERY_INFORMATION, &bqi, sizeof(bqi), &bi, sizeof(bi), &dwOut, NULL)) { - continue; - } +static bool getInstanceData(const PWNODE_ALL_DATA allData, ULONG bufferSize, ULONG index, const uint8_t** instanceData, ULONG* instanceLength) { + ULONG dataOffset = 0; + ULONG dataLength = 0; - if (!(bi.Capabilities & BATTERY_SYSTEM_BATTERY)) { + if (allData->WnodeHeader.Flags & WNODE_FLAG_FIXED_INSTANCE_SIZE) { + dataLength = allData->FixedInstanceSize; + dataOffset = allData->DataBlockOffset + index * dataLength; + } else { + dataOffset = allData->OffsetInstanceDataAndLength[index].OffsetInstanceData; + dataLength = allData->OffsetInstanceDataAndLength[index].LengthInstanceData; + } + + if (dataLength == 0 || dataOffset >= bufferSize || dataLength > bufferSize - dataOffset) { + return false; + } + + *instanceData = (const uint8_t*) allData + dataOffset; + *instanceLength = dataLength; + return true; +} + +static void detectStaticData(FFlist* entries, FFlist* results) { + FF_DEBUG("detectStaticData"); + FF_AUTO_FREE PWNODE_ALL_DATA allData = NULL; + ULONG bufferSize = 0; + const char* error = queryWmiAllData(&BATTERY_STATIC_DATA_WMI_GUID, "BATTERY_STATIC_DATA_WMI_GUID", &allData, &bufferSize); + if (error) { + return; + } + + for (ULONG i = 0; i < allData->InstanceCount; ++i) { + const uint8_t* instanceData = NULL; + ULONG instanceLength = 0; + if (!getInstanceData(allData, bufferSize, i, &instanceData, &instanceLength) || instanceLength < offsetof(BATTERY_WMI_STATIC_DATA, Strings)) { continue; } - FFBatteryResult* battery = FF_LIST_ADD(FFBatteryResult, *results); - - if (memcmp(bi.Chemistry, "PbAc", 4) == 0) { - ffStrbufInitStatic(&battery->technology, "Lead Acid"); - } else if (memcmp(bi.Chemistry, "LION", 4) == 0 || memcmp(bi.Chemistry, "Li-I", 4) == 0) { - ffStrbufInitStatic(&battery->technology, "Lithium Ion"); - } else if (memcmp(bi.Chemistry, "NiCd", 4) == 0) { - ffStrbufInitStatic(&battery->technology, "Nickel Cadmium"); - } else if (memcmp(bi.Chemistry, "NiMH", 4) == 0) { - ffStrbufInitStatic(&battery->technology, "Nickel Metal Hydride"); - } else if (memcmp(bi.Chemistry, "NiZn", 4) == 0) { - ffStrbufInitStatic(&battery->technology, "Nickel Zinc"); - } else if (memcmp(bi.Chemistry, "RAM\0", 4) == 0) { - ffStrbufInitStatic(&battery->technology, "Rechargeable Alkaline-Manganese"); - } else { - ffStrbufInitStatic(&battery->technology, "Unknown"); + const BATTERY_WMI_STATIC_DATA* data = (const BATTERY_WMI_STATIC_DATA*) instanceData; + FFBatteryWmiEntry* entry = getBatteryEntry(entries, results, data->Tag); + + FF_DEBUG("chemistry: %.4s", (const char*) &data->Chemistry); + switch (data->Chemistry) { + case 'cAbP': + ffStrbufSetStatic(&entry->result->technology, "Lead Acid"); + break; + case 'NOIL': + case 'I-iL': + ffStrbufSetStatic(&entry->result->technology, "Lithium Ion"); + break; + case 'dCiN': + ffStrbufSetStatic(&entry->result->technology, "Nickel Cadmium"); + break; + case 'HMiN': + ffStrbufSetStatic(&entry->result->technology, "Nickel Metal Hydride"); + break; + case 'nZiN': + ffStrbufSetStatic(&entry->result->technology, "Nickel Zinc"); + break; + case '\0MAR': + ffStrbufSetStatic(&entry->result->technology, "Rechargeable Alkaline-Manganese"); + break; + default: + ffStrbufSetStatic(&entry->result->technology, data->Technology ? "Rechargeable" : "Non Rechargeable"); + break; } - { - ffStrbufInit(&battery->modelName); - bqi.InformationLevel = BatteryDeviceName; - wchar_t name[64]; - if (DeviceIoControl(hBattery, IOCTL_BATTERY_QUERY_INFORMATION, &bqi, sizeof(bqi), name, sizeof(name), &dwOut, NULL)) { - ffStrbufSetWS(&battery->modelName, name); - } + const BATTERY_MANUFACTURE_DATE* manufactureDate = (const BATTERY_MANUFACTURE_DATE*) data->ManufactureDate; + if (manufactureDate->Year > 0 && manufactureDate->Month >= 1 && manufactureDate->Month <= 12 && manufactureDate->Day >= 1 && manufactureDate->Day <= 31) { + uint16_t year = manufactureDate->Year; + ffStrbufSetF(&entry->result->manufactureDate, "%.4u-%.2u-%.2u", (unsigned) (year < 1000 ? (year + 1900) : year), (unsigned) manufactureDate->Month, (unsigned) manufactureDate->Day); } - { - ffStrbufInit(&battery->manufacturer); - bqi.InformationLevel = BatteryManufactureName; - wchar_t name[64]; - if (DeviceIoControl(hBattery, IOCTL_BATTERY_QUERY_INFORMATION, &bqi, sizeof(bqi), name, sizeof(name), &dwOut, NULL)) { - ffStrbufSetWS(&battery->manufacturer, name); + // Device Name, Manufacture Name, Serial Number, UniqueID + const struct { + uint16_t size; // in bytes, including the null terminator + wchar_t value[]; + }* cursor = (const void*) data->Strings; + + FFstrbuf* strings[] = { + &entry->result->modelName, + &entry->result->manufacturer, + &entry->result->serial, + }; + + for (size_t i = 0; i < ARRAY_SIZE(strings); ++i) { + if (cursor->size > sizeof(wchar_t)) { + ffStrbufSetNWS(strings[i], cursor->size / sizeof(wchar_t) - 1, cursor->value); } + cursor = (const void*) ((const uint8_t*) cursor + sizeof(uint16_t) + cursor->size); } + } +} - { - ffStrbufInit(&battery->manufactureDate); - bqi.InformationLevel = BatteryManufactureDate; - BATTERY_MANUFACTURE_DATE date; - if (DeviceIoControl(hBattery, IOCTL_BATTERY_QUERY_INFORMATION, &bqi, sizeof(bqi), &date, sizeof(date), &dwOut, NULL)) { - ffStrbufSetF(&battery->manufactureDate, "%.4d-%.2d-%.2d", date.Year < 1000 ? date.Year + 1900 : date.Year, date.Month, date.Day); - } - } +static void detectStatus(FFlist* entries, FFlist* results) { + FF_DEBUG("detectStatus"); + FF_AUTO_FREE PWNODE_ALL_DATA allData = NULL; + ULONG bufferSize = 0; + const char* error = queryWmiAllData(&BATTERY_STATUS_WMI_GUID, "BATTERY_STATUS_WMI_GUID", &allData, &bufferSize); + if (error) { + return; + } - { - ffStrbufInit(&battery->serial); - bqi.InformationLevel = BatterySerialNumber; - wchar_t name[64]; - if (DeviceIoControl(hBattery, IOCTL_BATTERY_QUERY_INFORMATION, &bqi, sizeof(bqi), name, sizeof(name), &dwOut, NULL)) { - ffStrbufSetWS(&battery->serial, name); - } + for (ULONG i = 0; i < allData->InstanceCount; ++i) { + const uint8_t* instanceData = NULL; + ULONG instanceLength = 0; + if (!getInstanceData(allData, bufferSize, i, &instanceData, &instanceLength) || instanceLength < sizeof(BATTERY_WMI_STATUS)) { + continue; } - battery->cycleCount = bi.CycleCount; + const BATTERY_WMI_STATUS* data = (const BATTERY_WMI_STATUS*) instanceData; + FFBatteryWmiEntry* entry = getBatteryEntry(entries, results, data->Tag); + if (data->RemainingCapacity != BATTERY_UNKNOWN_CAPACITY) { + entry->result->capacity = data->RemainingCapacity; + } - battery->temperature = FF_BATTERY_TEMP_UNSET; - if (options->temp) { - bqi.InformationLevel = BatteryTemperature; - ULONG temp; - if (DeviceIoControl(hBattery, IOCTL_BATTERY_QUERY_INFORMATION, &bqi, sizeof(bqi), &temp, sizeof(temp), &dwOut, NULL)) { - battery->temperature = temp / 10.0 - 273.15; - } + entry->result->status = FF_BATTERY_STATUS_NONE; + if (data->PowerOnline) { + entry->result->status |= FF_BATTERY_STATUS_AC_CONNECTED; + } + if (data->Charging) { + entry->result->status |= FF_BATTERY_STATUS_CHARGING; + } + if (data->Discharging) { + entry->result->status |= FF_BATTERY_STATUS_DISCHARGING; + } + if (data->Critical) { + entry->result->status |= FF_BATTERY_STATUS_CRITICAL; } + } +} - { - bqi.InformationLevel = BatteryEstimatedTime; - ULONG time; - if (DeviceIoControl(hBattery, IOCTL_BATTERY_QUERY_INFORMATION, &bqi, sizeof(bqi), &time, sizeof(time), &dwOut, NULL)) { - battery->timeRemaining = time == BATTERY_UNKNOWN_TIME ? -1 : (int32_t) time; - } +static void detectRuntime(FFlist* entries, FFlist* results) { + FF_DEBUG("detectRuntime"); + FF_AUTO_FREE PWNODE_ALL_DATA allData = NULL; + ULONG bufferSize = 0; + const char* error = queryWmiAllData(&BATTERY_RUNTIME_WMI_GUID, "BATTERY_RUNTIME_WMI_GUID", &allData, &bufferSize); + if (error) { + return; + } + + for (ULONG i = 0; i < allData->InstanceCount; ++i) { + const uint8_t* instanceData = NULL; + ULONG instanceLength = 0; + if (!getInstanceData(allData, bufferSize, i, &instanceData, &instanceLength) || instanceLength < sizeof(BATTERY_WMI_RUNTIME)) { + continue; } - { - BATTERY_STATUS bs; - BATTERY_WAIT_STATUS bws = { .BatteryTag = bqi.BatteryTag }; - if (DeviceIoControl(hBattery, IOCTL_BATTERY_QUERY_STATUS, &bws, sizeof(bws), &bs, sizeof(bs), &dwOut, NULL) && bs.Capacity != BATTERY_UNKNOWN_CAPACITY && bi.FullChargedCapacity != 0) { - battery->capacity = bs.Capacity * 100.0 / bi.FullChargedCapacity; - - battery->status = FF_BATTERY_STATUS_NONE; - if (bs.PowerState & BATTERY_POWER_ON_LINE) { - battery->status |= FF_BATTERY_STATUS_AC_CONNECTED; - } - if (bs.PowerState & BATTERY_DISCHARGING) { - battery->status |= FF_BATTERY_STATUS_DISCHARGING; - } - if (bs.PowerState & BATTERY_CHARGING) { - battery->status |= FF_BATTERY_STATUS_CHARGING; - } - if (bs.PowerState & BATTERY_CRITICAL) { - battery->status |= FF_BATTERY_STATUS_CRITICAL; - } - } else { - battery->status = FF_BATTERY_STATUS_UNKNOWN; - battery->capacity = 0; - } + const BATTERY_WMI_RUNTIME* data = (const BATTERY_WMI_RUNTIME*) instanceData; + FFBatteryWmiEntry* entry = getBatteryEntry(entries, results, data->Tag); + if (data->EstimatedRuntime != BATTERY_UNKNOWN_TIME) { + entry->result->timeRemaining = (int32_t) data->EstimatedRuntime; } } - return NULL; } -typedef struct FFSmbiosPortableBattery { - FFSmbiosHeader Header; - - // 2.1+ - uint8_t Location; // string - uint8_t Manufacturer; // string - uint8_t ManufactureDate; // string - uint8_t SerialNumber; // string - uint8_t DeviceName; // string - uint8_t DeviceChemistry; // enum - uint16_t DesignCapacity; // varies - uint16_t DesignVoltage; // varies - uint8_t SbdsVersionNumber; // string - uint8_t MaximumErrorInBatteryData; // varies - - // 2.2+ - uint16_t SbdsSerialNumber; // varies - uint16_t SbdsManufactureDate; // varies - uint8_t SbdsDeviceChemistry; // string - uint8_t DesignCapacityMultiplier; // varies - uint16_t OEMSpecific; // varies -} FF_A_PACKED FFSmbiosPortableBattery; - -static_assert(offsetof(FFSmbiosPortableBattery, OEMSpecific) == 0x16, - "FFSmbiosPortableBattery: Wrong struct alignment"); - -static const char* detectBySmbios(FFBatteryResult* battery) { - const FFSmbiosHeaderTable* smbiosTable = ffGetSmbiosHeaderTable(); - if (!smbiosTable) { - return "Failed to get SMBIOS data"; +static void detectFullChargedCapacity(FFlist* entries, FFlist* results) { + FF_DEBUG("detectFullChargedCapacity"); + FF_AUTO_FREE PWNODE_ALL_DATA allData = NULL; + ULONG bufferSize = 0; + const char* error = queryWmiAllData(&BATTERY_FULL_CHARGED_CAPACITY_WMI_GUID, "BATTERY_FULL_CHARGED_CAPACITY_WMI_GUID", &allData, &bufferSize); + if (error) { + return; } - const FFSmbiosPortableBattery* data = (const FFSmbiosPortableBattery*) (*smbiosTable)[FF_SMBIOS_TYPE_PORTABLE_BATTERY]; - if (!data) { - return "Portable battery section is not found in SMBIOS data"; + for (ULONG i = 0; i < allData->InstanceCount; ++i) { + const uint8_t* instanceData = NULL; + ULONG instanceLength = 0; + if (!getInstanceData(allData, bufferSize, i, &instanceData, &instanceLength) || instanceLength < sizeof(BATTERY_WMI_FULL_CHARGED_CAPACITY)) { + continue; + } + + const BATTERY_WMI_FULL_CHARGED_CAPACITY* data = (const BATTERY_WMI_FULL_CHARGED_CAPACITY*) instanceData; + FFBatteryWmiEntry* entry = getBatteryEntry(entries, results, data->Tag); + + if (data->FullChargedCapacity != BATTERY_UNKNOWN_CAPACITY && entry->result->capacity >= 0) { + entry->result->capacity *= 100; + entry->result->capacity /= data->FullChargedCapacity; + } } +} - const char* strings = (const char*) data + data->Header.Length; - - ffStrbufSetStatic(&battery->modelName, ffSmbiosLocateString(strings, data->DeviceName)); - ffCleanUpSmbiosValue(&battery->modelName); - ffStrbufSetStatic(&battery->manufacturer, ffSmbiosLocateString(strings, data->Manufacturer)); - ffCleanUpSmbiosValue(&battery->manufacturer); - - if (data->ManufactureDate) { - ffStrbufSetStatic(&battery->manufactureDate, ffSmbiosLocateString(strings, data->ManufactureDate)); - ffCleanUpSmbiosValue(&battery->manufactureDate); - } else if (data->Header.Length > offsetof(FFSmbiosPortableBattery, SbdsManufactureDate)) { - int day = data->SbdsManufactureDate & 0b11111; - int month = (data->SbdsManufactureDate >> 5) & 0b1111; - int year = (data->SbdsManufactureDate >> 9) + 1800; - ffStrbufSetF(&battery->manufactureDate, "%.4d-%.2d-%.2d", year, month, day); +static void detectCycleCount(FFlist* entries, FFlist* results) { + FF_DEBUG("detectCycleCount"); + FF_AUTO_FREE PWNODE_ALL_DATA allData = NULL; + ULONG bufferSize = 0; + const char* error = queryWmiAllData(&BATTERY_CYCLE_COUNT_WMI_GUID, "BATTERY_CYCLE_COUNT_WMI_GUID", &allData, &bufferSize); + if (error) { + return; } - switch (data->DeviceChemistry) { - case 0x01: - ffStrbufSetStatic(&battery->technology, "Other"); - break; - case 0x02: - ffStrbufSetStatic(&battery->technology, "Unknown"); - break; - case 0x03: - ffStrbufSetStatic(&battery->technology, "Lead Acid"); - break; - case 0x04: - ffStrbufSetStatic(&battery->technology, "Nickel Cadmium"); - break; - case 0x05: - ffStrbufSetStatic(&battery->technology, "Nickel metal hydride"); - break; - case 0x06: - ffStrbufSetStatic(&battery->technology, "Lithium-ion"); - break; - case 0x07: - ffStrbufSetStatic(&battery->technology, "Zinc air"); - break; - case 0x08: - ffStrbufSetStatic(&battery->technology, "Lithium Polymer"); - break; + for (ULONG i = 0; i < allData->InstanceCount; ++i) { + const uint8_t* instanceData = NULL; + ULONG instanceLength = 0; + if (!getInstanceData(allData, bufferSize, i, &instanceData, &instanceLength) || instanceLength < sizeof(BATTERY_WMI_CYCLE_COUNT)) { + continue; + } + + const BATTERY_WMI_CYCLE_COUNT* data = (const BATTERY_WMI_CYCLE_COUNT*) instanceData; + getBatteryEntry(entries, results, data->Tag)->result->cycleCount = data->CycleCount; } +} - if (data->SerialNumber) { - ffStrbufSetStatic(&battery->serial, ffSmbiosLocateString(strings, data->SerialNumber)); - ffCleanUpSmbiosValue(&battery->serial); - } else if (data->Header.Length > offsetof(FFSmbiosPortableBattery, SbdsSerialNumber)) { - ffStrbufSetF(&battery->serial, "%4X", data->SbdsSerialNumber); +static void detectTemperature(FFlist* entries, FFlist* results) { + FF_DEBUG("detectTemperature"); + FF_AUTO_FREE PWNODE_ALL_DATA allData = NULL; + ULONG bufferSize = 0; + const char* error = queryWmiAllData(&BATTERY_TEMPERATURE_WMI_GUID, "BATTERY_TEMPERATURE_WMI_GUID", &allData, &bufferSize); + if (error) { + return; } - return NULL; + for (ULONG i = 0; i < allData->InstanceCount; ++i) { + const uint8_t* instanceData = NULL; + ULONG instanceLength = 0; + if (!getInstanceData(allData, bufferSize, i, &instanceData, &instanceLength) || instanceLength < sizeof(BATTERY_WMI_TEMPERATURE)) { + continue; + } + + const BATTERY_WMI_TEMPERATURE* data = (const BATTERY_WMI_TEMPERATURE*) instanceData; + getBatteryEntry(entries, results, data->Tag)->result->temperature = data->Temperature / 10.0 - 273.15; + } } -static const char* detectWithNtApi(FF_A_UNUSED FFBatteryOptions* options, FFlist* results) { +static const char* detectWithNtApi(FFBatteryResult* battery) { + // Reports summary battery information, not per battery + FF_DEBUG("NtApi: start detection"); SYSTEM_BATTERY_STATE info; - if (NT_SUCCESS(NtPowerInformation(SystemBatteryState, NULL, 0, &info, sizeof(info))) && - info.BatteryPresent) { - FFBatteryResult* battery = FF_LIST_ADD(FFBatteryResult, *results); - ffStrbufInit(&battery->modelName); - ffStrbufInit(&battery->manufacturer); - ffStrbufInit(&battery->manufactureDate); - ffStrbufInit(&battery->technology); - ffStrbufInit(&battery->serial); - battery->temperature = FF_BATTERY_TEMP_UNSET; - battery->cycleCount = 0; - battery->timeRemaining = info.EstimatedTime == BATTERY_UNKNOWN_TIME ? -1 : (int32_t) info.EstimatedTime; - battery->status = FF_BATTERY_STATUS_NONE; + NTSTATUS status = NtPowerInformation(SystemBatteryState, NULL, 0, &info, sizeof(info)); + if (!NT_SUCCESS(status)) { + FF_DEBUG("NtApi: NtPowerInformation(SystemBatteryState) failed: %s", ffDebugNtStatus(status)); + return "NtPowerInformation(SystemBatteryState) failed"; + } + if (!info.BatteryPresent) { + FF_DEBUG("NtApi reports no battery present"); + return "No battery present"; + } + if (info.MaxCapacity != BATTERY_UNKNOWN_CAPACITY && info.RemainingCapacity != BATTERY_UNKNOWN_CAPACITY) { battery->capacity = info.RemainingCapacity * 100.0 / info.MaxCapacity; - if (info.AcOnLine) { - battery->status |= FF_BATTERY_STATUS_AC_CONNECTED; - } - if (info.Charging) { - battery->status |= FF_BATTERY_STATUS_CHARGING; - } - if (info.Discharging) { - battery->status |= FF_BATTERY_STATUS_DISCHARGING; - } - if (info.DefaultAlert1 > 0 && info.RemainingCapacity <= info.DefaultAlert1) { - battery->status |= FF_BATTERY_STATUS_CRITICAL; - } + } + battery->status = FF_BATTERY_STATUS_NONE; + if (info.AcOnLine) { + battery->status |= FF_BATTERY_STATUS_AC_CONNECTED; + } + if (info.Charging) { + battery->status |= FF_BATTERY_STATUS_CHARGING; + } + if (info.Discharging) { + battery->status |= FF_BATTERY_STATUS_DISCHARGING; + } + if (info.DefaultAlert1 > 0 && info.RemainingCapacity <= info.DefaultAlert1) { + battery->status |= FF_BATTERY_STATUS_CRITICAL; + } + battery->timeRemaining = info.EstimatedTime == BATTERY_UNKNOWN_TIME ? -1 : (int32_t) info.EstimatedTime; + return NULL; +} - detectBySmbios(battery); +const char* ffDetectBattery(FFBatteryOptions* options, FFlist* results) { + FF_DEBUG("WMI: start detection"); + FF_LIST_AUTO_DESTROY entries = ffListCreate(); + detectStaticData(&entries, results); + if (results->length == 0) { return NULL; + } else if (results->length == 1) { + // Fast path for single battery + detectWithNtApi(FF_LIST_FIRST(FFBatteryWmiEntry, entries)->result); + } else { + detectStatus(&entries, results); + detectFullChargedCapacity(&entries, results); + detectRuntime(&entries, results); + } + detectCycleCount(&entries, results); + if (options->temp) { + detectTemperature(&entries, results); } - return "NtPowerInformation(SystemBatteryState) failed"; -} -const char* ffDetectBattery(FFBatteryOptions* options, FFlist* results) { - return options->useSetupApi - ? detectWithCmApi(options, results) - : detectWithNtApi(options, results); + FF_LIST_FOR_EACH (FFBatteryWmiEntry, entry, entries) { + FF_DEBUG( + "WMI: detected battery tag=%lu, name='%s', charge=%.2f%%, status=0x%x, runtime=%d seconds", + entry->tag, + entry->result->modelName.length ? entry->result->modelName.chars : "", + entry->result->capacity, + entry->result->status, + entry->result->timeRemaining); + } + + FF_DEBUG("WMI: finished detection, total results=%u", results->length); + return NULL; } From d9eccd70ed6fa7a1b277a1894501be8d3293d73e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=9D=8E=E9=80=9A=E6=B4=B2?= Date: Wed, 29 Apr 2026 18:54:18 +0800 Subject: [PATCH 28/92] Battery (Windows): removes option `useSetupApi` --- doc/json_schema.json | 5 ----- src/modules/battery/battery.c | 15 --------------- src/modules/battery/option.h | 4 ---- 3 files changed, 24 deletions(-) diff --git a/doc/json_schema.json b/doc/json_schema.json index 8d1d168be0..95224cced8 100644 --- a/doc/json_schema.json +++ b/doc/json_schema.json @@ -1540,11 +1540,6 @@ "const": "battery", "description": "Print battery information" }, - "useSetupApi": { - "description": "Whether to use the `CM API` on Windows to detect battery information. Supports multiple batteries, but is slower (Windows only)", - "type": "boolean", - "default": false - }, "temp": { "$ref": "#/$defs/temperature" }, diff --git a/src/modules/battery/battery.c b/src/modules/battery/battery.c index 70ec611393..f59375c9c7 100644 --- a/src/modules/battery/battery.c +++ b/src/modules/battery/battery.c @@ -202,13 +202,6 @@ void ffParseBatteryJsonObject(FFBatteryOptions* options, yyjson_val* module) { continue; } -#ifdef _WIN32 - if (unsafe_yyjson_equals_str(key, "useSetupApi")) { - options->useSetupApi = yyjson_get_bool(val); - continue; - } -#endif - if (ffTempsParseJsonObject(key, val, &options->temp, &options->tempConfig)) { continue; } @@ -224,10 +217,6 @@ void ffParseBatteryJsonObject(FFBatteryOptions* options, yyjson_val* module) { void ffGenerateBatteryJsonConfig(FFBatteryOptions* options, yyjson_mut_doc* doc, yyjson_mut_val* module) { ffJsonConfigGenerateModuleArgsConfig(doc, module, &options->moduleArgs); -#ifdef _WIN32 - yyjson_mut_obj_add_bool(doc, module, "useSetupApi", options->useSetupApi); -#endif - ffTempsGenerateJsonConfig(doc, module, options->temp, options->tempConfig); ffPercentGenerateJsonConfig(doc, module, options->percent); } @@ -302,10 +291,6 @@ void ffInitBatteryOptions(FFBatteryOptions* options) { options->temp = false; options->tempConfig = (FFColorRangeConfig) { 60, 80 }; options->percent = (FFPercentageModuleConfig) { 50, 20, 0 }; - -#ifdef _WIN32 - options->useSetupApi = false; -#endif } void ffDestroyBatteryOptions(FFBatteryOptions* options) { diff --git a/src/modules/battery/option.h b/src/modules/battery/option.h index 9f01ffb66c..d65fd1fa4c 100644 --- a/src/modules/battery/option.h +++ b/src/modules/battery/option.h @@ -9,10 +9,6 @@ typedef struct FFBatteryOptions { bool temp; FFColorRangeConfig tempConfig; FFPercentageModuleConfig percent; - -#ifdef _WIN32 - bool useSetupApi; -#endif } FFBatteryOptions; static_assert(sizeof(FFBatteryOptions) <= FF_OPTION_MAX_SIZE, "FFBatteryOptions size exceeds maximum allowed size"); From ab5cf940996fd1e40d58fd69fc411f9551ae72ba Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=9D=8E=E9=80=9A=E6=B4=B2?= Date: Wed, 29 Apr 2026 23:38:50 +0800 Subject: [PATCH 29/92] Networking: improves reliablity Co-authored-by: Copilot --- src/common/impl/networking_common.c | 12 ++++++++++-- src/common/impl/networking_linux.c | 16 ++++++++-------- src/common/impl/networking_windows.c | 20 ++++++++++---------- 3 files changed, 28 insertions(+), 20 deletions(-) diff --git a/src/common/impl/networking_common.c b/src/common/impl/networking_common.c index ac0930756d..c716195ae3 100644 --- a/src/common/impl/networking_common.c +++ b/src/common/impl/networking_common.c @@ -146,16 +146,24 @@ bool ffNetworkingDecompressGzip(FFstrbuf* buffer, char* headerEnd) { result = zlibData.ffinflate(&zs, Z_FINISH); } + // Check for decompression errors before using result + if (result != Z_STREAM_END) { + FF_DEBUG("Decompression failed with zlib error: %d", result); + zlibData.ffinflateEnd(&zs); + return false; + } + zlibData.ffinflateEnd(&zs); - // Calculate decompressed size + // Calculate decompressed size (from the last inflate call) uint32_t decompressedSize = (uint32_t) (availableOut - zs.avail_out); decompressedBuffer.length += decompressedSize; decompressedBuffer.chars[decompressedBuffer.length] = '\0'; FF_DEBUG("Successfully decompressed %u bytes compressed data to %u bytes", compressedSize, decompressedBuffer.length); // Modify Content-Length header and remove Content-Encoding header - FF_STRBUF_AUTO_DESTROY newBuffer = ffStrbufCreateA(headerSize + decompressedSize + 64); + // Use decompressedBuffer.length (total) not decompressedSize (last chunk only) + FF_STRBUF_AUTO_DESTROY newBuffer = ffStrbufCreateA(headerSize + decompressedBuffer.length + 64); char* line = NULL; size_t len = 0; diff --git a/src/common/impl/networking_linux.c b/src/common/impl/networking_linux.c index f63bc47aa3..0db8d88294 100644 --- a/src/common/impl/networking_linux.c +++ b/src/common/impl/networking_linux.c @@ -448,7 +448,7 @@ const char* ffNetworkingRecvHttpResponse(FFNetworkingState* state, FFstrbuf* buf // Check for Content-Length header to pre-allocate enough memory const char* clHeader = strcasestr(buffer->chars, "Content-Length:"); if (clHeader) { - contentLength = (uint32_t) strtoul(clHeader + 16, NULL, 10); + contentLength = (uint32_t) strtoul(clHeader + 15, NULL, 10); if (contentLength > 0) { FF_DEBUG("Detected Content-Length: %u, pre-allocating buffer", contentLength); // Ensure buffer is large enough, adding header size and some margin @@ -473,17 +473,17 @@ const char* ffNetworkingRecvHttpResponse(FFNetworkingState* state, FFstrbuf* buf FF_DEBUG("No HTTP header end marker found"); return "No HTTP header end found"; } - if (contentLength > 0 && buffer->length != contentLength + headerEnd + 4) { - FF_DEBUG("Received content length mismatches: %u != %u", buffer->length, contentLength + headerEnd + 4); - return "Content length mismatch"; - } - if (ffStrbufStartsWithS(buffer, "HTTP/1.0 200 OK\r\n")) { - FF_DEBUG("Received valid HTTP 200 response, content %u bytes, total %u bytes", contentLength, buffer->length); - } else { + if (!ffStrbufStartsWithS(buffer, "HTTP/1.0 200 OK\r\n")) { FF_DEBUG("Invalid response: %.40s...", buffer->chars); return "Invalid response"; } + FF_DEBUG("Received valid HTTP 200 response, content %u bytes, total %u bytes", contentLength, buffer->length); + + if (contentLength > 0 && buffer->length != contentLength + headerEnd + 4) { + FF_DEBUG("Received content length mismatches: %u != %u", buffer->length, contentLength + headerEnd + 4); + return "Content length mismatch"; + } // If compression was used, try to decompress #ifdef FF_HAVE_ZLIB diff --git a/src/common/impl/networking_windows.c b/src/common/impl/networking_windows.c index 65eaf24454..dd90948dcc 100644 --- a/src/common/impl/networking_windows.c +++ b/src/common/impl/networking_windows.c @@ -320,7 +320,7 @@ const char* ffNetworkingRecvHttpResponse(FFNetworkingState* state, FFstrbuf* buf // Check for Content-Length header to pre-allocate enough memory const char* clHeader = strcasestr(buffer->chars, "Content-Length:"); if (clHeader) { - contentLength = (uint32_t) strtoul(clHeader + 16, NULL, 10); + contentLength = (uint32_t) strtoul(clHeader + 15, NULL, 10); if (contentLength > 0) { FF_DEBUG("Detected Content-Length: %u, pre-allocating buffer", contentLength); // Ensure buffer is large enough, adding header size and some margin @@ -345,19 +345,19 @@ const char* ffNetworkingRecvHttpResponse(FFNetworkingState* state, FFstrbuf* buf FF_DEBUG("No HTTP header end marker found"); return "No HTTP header end found"; } - if (contentLength > 0 && buffer->length != contentLength + headerEnd + 4) { - FF_DEBUG("Received content length mismatches: %u != %u", buffer->length, contentLength + headerEnd + 4); - return "Content length mismatch"; - } - if (ffStrbufStartsWithS(buffer, "HTTP/1.0 200 OK\r\n")) { - FF_DEBUG("Received valid HTTP 200 response, content length: %u bytes, total length: %u bytes", - contentLength, - buffer->length); - } else { + if (!ffStrbufStartsWithS(buffer, "HTTP/1.0 200 OK\r\n")) { FF_DEBUG("Invalid response: %.40s...", buffer->chars); return "Invalid response"; } + FF_DEBUG("Received valid HTTP 200 response, content length: %u bytes, total length: %u bytes", + contentLength, + buffer->length); + + if (contentLength > 0 && buffer->length != contentLength + headerEnd + 4) { + FF_DEBUG("Received content length mismatches: %u != %u", buffer->length, contentLength + headerEnd + 4); + return "Content length mismatch"; + } // If compression was used, try to decompress #ifdef FF_HAVE_ZLIB From e91f62aa7085e0e44fae4b7a8135bbb5527e0c9b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=9D=8E=E9=80=9A=E6=B4=B2?= Date: Wed, 29 Apr 2026 23:52:38 +0800 Subject: [PATCH 30/92] Option: improve reliablity --- src/common/impl/option.c | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/src/common/impl/option.c b/src/common/impl/option.c index a6dc09673b..7390f7b667 100644 --- a/src/common/impl/option.c +++ b/src/common/impl/option.c @@ -3,8 +3,12 @@ #include "common/color.h" #include "common/stringUtils.h" +#include + // Return start position of the inner key if the argument key belongs to the module specified, NULL otherwise const char* ffOptionTestPrefix(const char* argumentKey, const char* moduleName) { + assert(argumentKey && moduleName); + const char* subKey = argumentKey; if (!(subKey[0] == '-' && subKey[1] == '-')) { return NULL; @@ -47,13 +51,13 @@ uint32_t ffOptionParseUInt32(const char* argumentKey, const char* value) { } char* end; - uint32_t num = (uint32_t) strtoul(value, &end, 10); - if (*end != '\0') { + unsigned long num = strtoul(value, &end, 10); + if (value[0] == '-' || *end != '\0' || num > UINT32_MAX) { fprintf(stderr, "Error: usage: %s \n", argumentKey); exit(479); } - return num; + return (uint32_t) num; } int32_t ffOptionParseInt32(const char* argumentKey, const char* value) { @@ -63,13 +67,13 @@ int32_t ffOptionParseInt32(const char* argumentKey, const char* value) { } char* end; - int32_t num = (int32_t) strtol(value, &end, 10); - if (*end != '\0') { + long num = strtol(value, &end, 10); + if (*end != '\0' || num < INT32_MIN || num > INT32_MAX) { fprintf(stderr, "Error: usage: %s \n", argumentKey); exit(479); } - return num; + return (int32_t) num; } int ffOptionParseEnum(const char* argumentKey, const char* requestedKey, FFKeyValuePair pairs[]) { From 6cb186f229216f3b359fe155beabdc771c9fae23 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=9D=8E=E9=80=9A=E6=B4=B2?= Date: Wed, 29 Apr 2026 23:55:37 +0800 Subject: [PATCH 31/92] Parsing: improves reliablity Co-authored-by: Copilot --- src/common/impl/parsing.c | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/src/common/impl/parsing.c b/src/common/impl/parsing.c index ad8c41c8d1..7fec0a77f1 100644 --- a/src/common/impl/parsing.c +++ b/src/common/impl/parsing.c @@ -1,7 +1,6 @@ #include "fastfetch.h" #include "common/parsing.h" -#include #ifdef _WIN32 #pragma GCC diagnostic push @@ -101,6 +100,16 @@ void ffParseGTK(FFstrbuf* buffer, const FFstrbuf* gtk2, const FFstrbuf* gtk3, co ffStrbufAppend(buffer, gtk3); ffStrbufAppendS(buffer, " [GTK3]"); } + } else if (gtk2->length > 0 && gtk4->length > 0) { + if (ffStrbufIgnCaseEqual(gtk2, gtk4)) { + ffStrbufAppend(buffer, gtk4); + ffStrbufAppendS(buffer, " [GTK2/4]"); + } else { + ffStrbufAppend(buffer, gtk2); + ffStrbufAppendS(buffer, " [GTK2], "); + ffStrbufAppend(buffer, gtk4); + ffStrbufAppendS(buffer, " [GTK4]"); + } } else if (gtk3->length > 0 && gtk4->length > 0) { if (ffStrbufIgnCaseEqual(gtk3, gtk4)) { ffStrbufAppend(buffer, gtk4); From c5141855a2ea0a4b154d4461d1965e10a9823a42 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=9D=8E=E9=80=9A=E6=B4=B2?= Date: Thu, 30 Apr 2026 00:05:43 +0800 Subject: [PATCH 32/92] Path: removes dead code --- src/common/impl/path.c | 8 -------- 1 file changed, 8 deletions(-) diff --git a/src/common/impl/path.c b/src/common/impl/path.c index 03662502c0..97dc9993a7 100644 --- a/src/common/impl/path.c +++ b/src/common/impl/path.c @@ -128,13 +128,6 @@ char* frealpath(HANDLE hFile, char* resolved_name) { errno = E2BIG; return NULL; } - - if (outBytes > MAX_PATH) { - errno = E2BIG; - return NULL; - } - - return resolved_name; } else { /* UTF-8 worst-case: up to 4 bytes per UTF-16 code unit */ char tmp[(MAX_PATH + 4) * 4]; @@ -152,7 +145,6 @@ char* frealpath(HANDLE hFile, char* resolved_name) { } memcpy(resolved_name, tmp, outBytes); - return resolved_name; } return resolved_name; From dfff57dd855cced85dcceed254bdd2c717f8f697 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=9D=8E=E9=80=9A=E6=B4=B2?= Date: Thu, 30 Apr 2026 00:10:35 +0800 Subject: [PATCH 33/92] Percent: improve reliability Co-authored-by: Copilot --- src/common/impl/percent.c | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/src/common/impl/percent.c b/src/common/impl/percent.c index ee0b8cf3ff..f19fbacb7c 100644 --- a/src/common/impl/percent.c +++ b/src/common/impl/percent.c @@ -59,9 +59,6 @@ void ffPercentAppendBar(FFstrbuf* buffer, double percent, FFPercentageModuleConf const bool borderAsValue = options->barBorderLeftElapsed.length && options->barBorderRightElapsed.length; - uint8_t blocksPercent = (uint8_t) (percent / 100.0 * options->barWidth + 0.5); - assert(blocksPercent <= options->barWidth); - if (!borderAsValue && options->barBorderLeft.length) { if (!options->pipe && options->barColorBorder.length > 0) { ffStrbufAppendF(buffer, "\e[%sm", options->barColorBorder.chars); @@ -86,6 +83,9 @@ void ffPercentAppendBar(FFstrbuf* buffer, double percent, FFPercentageModuleConf FFPercentageTypeFlags percentType = config.type == 0 ? options->percentType : config.type; + uint8_t blocksPercent = (uint8_t) (percent / 100.0 * options->barWidth + 0.5); + assert(blocksPercent <= options->barWidth); + bool autoColorElapsed = ffStrbufIgnCaseEqualS(&options->barColorElapsed, "auto"); bool monochrome = (percentType & FF_PERCENTAGE_TYPE_BAR_MONOCHROME_BIT) || !autoColorElapsed; @@ -190,7 +190,11 @@ void ffPercentAppendNum(FFstrbuf* buffer, double percent, FFPercentageModuleConf } } } - ffStrbufAppendF(buffer, "%*.*f%s%%", options->percentWidth, options->percentNdigits, percent, options->percentSpaceBeforeUnit == FF_SPACE_BEFORE_UNIT_ALWAYS ? " " : ""); + if (percent == -DBL_MAX) { + ffStrbufAppendS(buffer, "-"); + } else { + ffStrbufAppendF(buffer, "%*.*f%s%%", options->percentWidth, options->percentNdigits, percent, options->percentSpaceBeforeUnit == FF_SPACE_BEFORE_UNIT_ALWAYS ? " " : ""); + } if (colored && !options->pipe) { ffStrbufAppendS(buffer, FASTFETCH_TEXT_MODIFIER_RESET); From e694c8ac84d94924a0b400e78f57140281a56b30 Mon Sep 17 00:00:00 2001 From: Carter Li Date: Thu, 30 Apr 2026 08:50:23 +0800 Subject: [PATCH 34/92] Properties: fixes a typo Co-authored-by: Copilot --- src/common/impl/properties.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/common/impl/properties.c b/src/common/impl/properties.c index 33192b75dd..c13ed1cd6d 100644 --- a/src/common/impl/properties.c +++ b/src/common/impl/properties.c @@ -55,7 +55,7 @@ bool ffParsePropLinePointer(const char** line, const char* start, FFstrbuf* buff ++(*line); } - // Allow faster parsing of quotet values + // Allow faster parsing of quoted values if (**line == '"' || **line == '\'') { valueEnd = **line; ++(*line); From 3d6bffe7fe0beb0827292d415c2ba8b8920989a4 Mon Sep 17 00:00:00 2001 From: Carter Li Date: Thu, 30 Apr 2026 09:01:16 +0800 Subject: [PATCH 35/92] Settings: fixes DBusMessage reply leaks in XFConf functions Co-authored-by: Copilot --- src/common/impl/settings.c | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/common/impl/settings.c b/src/common/impl/settings.c index e62342ded0..bd7da7424a 100644 --- a/src/common/impl/settings.c +++ b/src/common/impl/settings.c @@ -225,6 +225,7 @@ FFvariant ffSettingsGetXFConf(const char* channelName, const char* propertyName, dbus.lib->ffdbus_message_unref(reply); return (FFvariant) { .intValue = value }; } + dbus.lib->ffdbus_message_unref(reply); return FF_VARIANT_NULL; } @@ -234,6 +235,7 @@ FFvariant ffSettingsGetXFConf(const char* channelName, const char* propertyName, dbus.lib->ffdbus_message_unref(reply); return (FFvariant) { .strValue = value.chars }; // Leaks value.chars } + dbus.lib->ffdbus_message_unref(reply); return FF_VARIANT_NULL; } @@ -245,6 +247,7 @@ FFvariant ffSettingsGetXFConf(const char* channelName, const char* propertyName, } } + dbus.lib->ffdbus_message_unref(reply); return FF_VARIANT_NULL; } @@ -297,6 +300,7 @@ FFvariant ffSettingsGetXFConfFirstMatch(const char* channelName, const char* pro dbus.lib->ffdbus_message_unref(reply); return (FFvariant) { .intValue = value }; } + dbus.lib->ffdbus_message_unref(reply); return FF_VARIANT_NULL; } @@ -306,6 +310,7 @@ FFvariant ffSettingsGetXFConfFirstMatch(const char* channelName, const char* pro dbus.lib->ffdbus_message_unref(reply); return (FFvariant) { .strValue = value.chars }; // Leaks value.chars } + dbus.lib->ffdbus_message_unref(reply); return FF_VARIANT_NULL; } @@ -317,9 +322,11 @@ FFvariant ffSettingsGetXFConfFirstMatch(const char* channelName, const char* pro } } + dbus.lib->ffdbus_message_unref(reply); return FF_VARIANT_NULL; } + dbus.lib->ffdbus_message_unref(reply); return FF_VARIANT_NULL; } #else // FF_HAVE_DBUS From cd25803e32305a6617dd4ac8e2126bc298f6c47b Mon Sep 17 00:00:00 2001 From: Carter Li Date: Thu, 30 Apr 2026 09:27:05 +0800 Subject: [PATCH 36/92] Option: adds a missing validation --- src/options/display.c | 39 ++++++++++++++++++++++++++++++++++++--- 1 file changed, 36 insertions(+), 3 deletions(-) diff --git a/src/options/display.c b/src/options/display.c index 7dcff16e91..e4498ca3a4 100644 --- a/src/options/display.c +++ b/src/options/display.c @@ -101,7 +101,21 @@ const char* ffOptionsParseDisplayJsonConfig(FFOptionsDisplay* options, yyjson_va yyjson_val* maxPrefix = yyjson_obj_get(val, "maxPrefix"); if (maxPrefix) { int value; - const char* error = ffJsonConfigParseEnum(maxPrefix, &value, (FFKeyValuePair[]) { { "B", 0 }, { "kB", 1 }, { "MB", 2 }, { "GB", 3 }, { "TB", 4 }, { "PB", 5 }, { "EB", 6 }, { "ZB", 7 }, { "YB", 8 }, {} }); + const char* error = ffJsonConfigParseEnum( + maxPrefix, + &value, + (FFKeyValuePair[]) { + { "B", 0 }, + { "kB", 1 }, + { "MB", 2 }, + { "GB", 3 }, + { "TB", 4 }, + { "PB", 5 }, + { "EB", 6 }, + { "ZB", 7 }, + { "YB", 8 }, + {}, + }); if (error) { return error; } @@ -659,9 +673,28 @@ bool ffOptionsParseDisplayCommandLine(FFOptionsDisplay* options, const char* key if (ffStrEqualsIgnCase(subkey, "binary-prefix")) { options->sizeBinaryPrefix = (FFSizeBinaryPrefixType) ffOptionParseEnum(key, value, (FFKeyValuePair[]) { { "iec", FF_SIZE_BINARY_PREFIX_TYPE_IEC }, { "si", FF_SIZE_BINARY_PREFIX_TYPE_SI }, { "jedec", FF_SIZE_BINARY_PREFIX_TYPE_JEDEC }, {} }); } else if (ffStrEqualsIgnCase(subkey, "ndigits")) { - options->sizeNdigits = (uint8_t) ffOptionParseUInt32(key, value); + uint32_t num = ffOptionParseUInt32(key, value); + if (num > 9) { + fprintf(stderr, "Error: %s must be between 0 and 9\n", key); + exit(479); + } + options->sizeNdigits = (uint8_t) num; } else if (ffStrEqualsIgnCase(subkey, "max-prefix")) { - options->sizeMaxPrefix = (uint8_t) ffOptionParseEnum(key, value, (FFKeyValuePair[]) { { "B", 0 }, { "kB", 1 }, { "MB", 2 }, { "GB", 3 }, { "TB", 4 }, { "PB", 5 }, { "EB", 6 }, { "ZB", 7 }, { "YB", 8 }, {} }); + options->sizeMaxPrefix = (uint8_t) ffOptionParseEnum( + key, + value, + (FFKeyValuePair[]) { + { "B", 0 }, + { "kB", 1 }, + { "MB", 2 }, + { "GB", 3 }, + { "TB", 4 }, + { "PB", 5 }, + { "EB", 6 }, + { "ZB", 7 }, + { "YB", 8 }, + {}, + }); } else if (ffStrEqualsIgnCase(subkey, "space-before-unit")) { options->sizeSpaceBeforeUnit = (FFSpaceBeforeUnitType) ffOptionParseEnum(key, value, (FFKeyValuePair[]) { { "default", FF_SPACE_BEFORE_UNIT_DEFAULT }, From b68ad3626c84facfa9652abd8e71b42613b4f003 Mon Sep 17 00:00:00 2001 From: Carter Li Date: Thu, 30 Apr 2026 09:31:28 +0800 Subject: [PATCH 37/92] Sysctl: returns error on failed string fetch and removes malloc null check Co-authored-by: Copilot --- src/common/impl/sysctl.c | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/src/common/impl/sysctl.c b/src/common/impl/sysctl.c index 62268754a0..5974698147 100644 --- a/src/common/impl/sysctl.c +++ b/src/common/impl/sysctl.c @@ -47,10 +47,12 @@ const char* ffSysctlGetString(const char* propName, FFstrbuf* result) { ffStrbufEnsureFree(result, (uint32_t) neededLength - 1); - if (sysctlbyname(propName, result->chars + result->length, &neededLength, NULL, 0) == 0) { - result->length += (uint32_t) neededLength - 1; + if (sysctlbyname(propName, result->chars + result->length, &neededLength, NULL, 0) != 0) { + return "sysctlbyname() failed to retrieve string data"; } + result->length += (uint32_t) neededLength - 1; + result->chars[result->length] = '\0'; return NULL; @@ -81,9 +83,6 @@ void* ffSysctlGetData(int* request, u_int requestLength, size_t* resultLength) { } void* data = malloc(*resultLength); - if (data == NULL) { - return NULL; - } if (sysctl(request, requestLength, data, resultLength, NULL, 0) != 0) { free(data); From 14b8bb2bb52f71580b79316b38775caed0c07202 Mon Sep 17 00:00:00 2001 From: Carter Li Date: Thu, 30 Apr 2026 09:33:48 +0800 Subject: [PATCH 38/92] Temps: validates temp color thresholds as integers in JSON parsing Co-authored-by: Copilot --- src/common/impl/temps.c | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/src/common/impl/temps.c b/src/common/impl/temps.c index 7bb2a66652..ae51e65b3b 100644 --- a/src/common/impl/temps.c +++ b/src/common/impl/temps.c @@ -123,7 +123,12 @@ bool ffTempsParseJsonObject(yyjson_val* key, yyjson_val* value, bool* useTemp, F yyjson_val* greenVal = yyjson_obj_get(value, "green"); if (greenVal) { - int num = yyjson_get_int(greenVal); + if (!yyjson_is_int(greenVal)) { + fputs("Error: usage: temp.green must be an integer between 0 and 100\n", stderr); + exit(480); + } + + int num = unsafe_yyjson_get_int(greenVal); if (num < 0 || num > 100) { fputs("Error: usage: temp.green must be between 0 and 100\n", stderr); exit(480); @@ -133,7 +138,12 @@ bool ffTempsParseJsonObject(yyjson_val* key, yyjson_val* value, bool* useTemp, F yyjson_val* yellowVal = yyjson_obj_get(value, "yellow"); if (yellowVal) { - int num = yyjson_get_int(yellowVal); + if (!yyjson_is_int(yellowVal)) { + fputs("Error: usage: temp.yellow must be an integer between 0 and 100\n", stderr); + exit(480); + } + + int num = unsafe_yyjson_get_int(yellowVal); if (num < 0 || num > 100) { fputs("Error: usage: temp.yellow must be between 0 and 100\n", stderr); exit(480); From 42f2e5a7459110df483be4e9418b396d365734d3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=9D=8E=E9=80=9A=E6=B4=B2?= Date: Thu, 30 Apr 2026 11:00:22 +0800 Subject: [PATCH 39/92] Netif (macOS): improves reliability --- src/common/impl/netif_apple.c | 25 ++++++++++++++++++++++--- 1 file changed, 22 insertions(+), 3 deletions(-) diff --git a/src/common/impl/netif_apple.c b/src/common/impl/netif_apple.c index 1b12a88889..85614acb29 100644 --- a/src/common/impl/netif_apple.c +++ b/src/common/impl/netif_apple.c @@ -1,6 +1,5 @@ #include "common/netif.h" #include "common/io.h" -#include "common/mallocHelper.h" #include #include @@ -98,7 +97,16 @@ bool ffNetifGetDefaultRouteImplV4(FFNetifDefaultRouteResult* result) { return false; } - while (recv(pfRoute, &rtmsg, sizeof(rtmsg), 0) > 0 && !(rtmsg.hdr.rtm_seq == 1 && rtmsg.hdr.rtm_pid == (pid_t) pid)); + bool gotResponse = false; + while (recv(pfRoute, &rtmsg, sizeof(rtmsg), 0) > 0) { + if (rtmsg.hdr.rtm_seq == 1 && rtmsg.hdr.rtm_pid == (pid_t) pid) { + gotResponse = true; + break; + } + } + if (!gotResponse) { + return false; + } #ifndef __sun // On Solaris, the RTF_GATEWAY flag is not set for default routes for some reason if ((rtmsg.hdr.rtm_flags & (RTF_UP | RTF_GATEWAY)) == (RTF_UP | RTF_GATEWAY)) @@ -109,6 +117,7 @@ bool ffNetifGetDefaultRouteImplV4(FFNetifDefaultRouteResult* result) { #ifndef __sun && sdl->sdl_len #endif + && sdl->sdl_family == AF_LINK ) { if (sdl->sdl_nlen > IF_NAMESIZE) { return false; @@ -173,7 +182,16 @@ bool ffNetifGetDefaultRouteImplV6(FFNetifDefaultRouteResult* result) { return false; } - while (recv(pfRoute, &rtmsg, sizeof(rtmsg), 0) > 0 && !(rtmsg.hdr.rtm_seq == 2 && rtmsg.hdr.rtm_pid == (pid_t) pid)); + bool gotResponse = false; + while (recv(pfRoute, &rtmsg, sizeof(rtmsg), 0) > 0) { + if (rtmsg.hdr.rtm_seq == 2 && rtmsg.hdr.rtm_pid == (pid_t) pid) { + gotResponse = true; + break; + } + } + if (!gotResponse) { + return false; + } #ifndef __sun // On Solaris, the RTF_GATEWAY flag is not set for default routes for some reason if ((rtmsg.hdr.rtm_flags & (RTF_UP | RTF_GATEWAY)) == (RTF_UP | RTF_GATEWAY)) @@ -184,6 +202,7 @@ bool ffNetifGetDefaultRouteImplV6(FFNetifDefaultRouteResult* result) { #ifndef __sun && sdl->sdl_len #endif + && sdl->sdl_family == AF_LINK ) { if (sdl->sdl_nlen > IF_NAMESIZE) { return false; From 383a4190caacb3ab3fc951898e02c64f2f77c3dc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=9D=8E=E9=80=9A=E6=B4=B2?= Date: Thu, 30 Apr 2026 13:26:46 +0800 Subject: [PATCH 40/92] Comon (macOS): tidy Co-authored-by: Copilot --- src/common/apple/cf_helpers.c | 6 +++--- src/common/apple/osascript.m | 2 -- src/common/apple/smc_temps.c | 1 - 3 files changed, 3 insertions(+), 6 deletions(-) diff --git a/src/common/apple/cf_helpers.c b/src/common/apple/cf_helpers.c index e080f4decb..e7a9c4fa10 100644 --- a/src/common/apple/cf_helpers.c +++ b/src/common/apple/cf_helpers.c @@ -24,10 +24,10 @@ const char* ffCfNumGetInt(CFTypeRef cf, int32_t* result) { } return NULL; } else if (CFGetTypeID(cf) == CFDataGetTypeID()) { - if (CFDataGetLength((CFDataRef) cf) != sizeof(int)) { - return "Data length is not sizeof(int)"; + if (CFDataGetLength((CFDataRef) cf) != sizeof(*result)) { + return "Data length is not sizeof(int32_t)"; } - CFDataGetBytes((CFDataRef) cf, CFRangeMake(0, sizeof(int)), (uint8_t*) result); + CFDataGetBytes((CFDataRef) cf, CFRangeMake(0, sizeof(*result)), (uint8_t*) result); return NULL; } diff --git a/src/common/apple/osascript.m b/src/common/apple/osascript.m index 8b01ba0697..f17af03698 100644 --- a/src/common/apple/osascript.m +++ b/src/common/apple/osascript.m @@ -1,8 +1,6 @@ #include "osascript.h" #import -#import -#import bool ffOsascript(const char* input, FFstrbuf* result) { diff --git a/src/common/apple/smc_temps.c b/src/common/apple/smc_temps.c index 07b2d3bc95..a71f94a5da 100644 --- a/src/common/apple/smc_temps.c +++ b/src/common/apple/smc_temps.c @@ -3,7 +3,6 @@ #include "common/stringUtils.h" #include -#include #include static const char kSmcCmdReadBytes = 5; From 994bd53d305ba1c7a15d1cf8329f60e9b47a4822 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=9D=8E=E9=80=9A=E6=B4=B2?= Date: Thu, 30 Apr 2026 20:26:12 +0800 Subject: [PATCH 41/92] Media (Windows): major code refactor moves detection code into fastfetch binary Co-authored-by: Copilot --- CMakeLists.txt | 47 ++- src/common/impl/init.c | 3 + src/common/windows/com.hpp | 1 + src/detection/media/media_windows.c | 42 --- src/detection/media/media_windows.cpp | 344 ++++++++++++++++++++++ src/detection/media/media_windows.dll.cpp | 99 ------- src/detection/media/media_windows.dll.h | 17 -- 7 files changed, 371 insertions(+), 182 deletions(-) delete mode 100644 src/detection/media/media_windows.c create mode 100644 src/detection/media/media_windows.cpp delete mode 100644 src/detection/media/media_windows.dll.cpp delete mode 100644 src/detection/media/media_windows.dll.h diff --git a/CMakeLists.txt b/CMakeLists.txt index b1dffb22fc..1f4d3b2f87 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -160,7 +160,11 @@ set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} ${WARNING_FLAGS} -Werror=incompatible-pointe if(WIN32 OR Haiku) enable_language(CXX) - set(CMAKE_CXX_STANDARD 17) + if(WIN32) + set(CMAKE_CXX_STANDARD 20) + else() + set(CMAKE_CXX_STANDARD 17) + endif() set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} ${WARNING_FLAGS}") endif() @@ -1065,7 +1069,7 @@ elseif(WIN32) src/detection/locale/locale_windows.c src/detection/localip/localip_windows.c src/detection/gamepad/gamepad_windows.c - src/detection/media/media_windows.c + src/detection/media/media_windows.cpp src/detection/memory/memory_windows.c src/detection/mouse/mouse_windows.c src/detection/physicalmemory/physicalmemory_linux.c @@ -1332,6 +1336,14 @@ endif() if(WIN32) list(APPEND LIBFASTFETCH_SRC src/detection/gpu/gpu_intel.c) list(APPEND LIBFASTFETCH_SRC src/detection/gpu/gpu_amd.c) + + include(CheckIncludeFileCXX) + CHECK_INCLUDE_FILE_CXX("winrt/Windows.Foundation.h" HAVE_WINRT) + if(HAVE_WINRT) + message(STATUS "WinRT headers are available, media detection will use WinRT APIs") + else() + message(STATUS "WinRT headers are NOT available, media detection will be disabled") + endif() endif() include(CheckFunctionExists) check_function_exists(wcwidth HAVE_WCWIDTH) @@ -1434,7 +1446,7 @@ if(LINUX) elseif(ANDROID) target_compile_definitions(libfastfetch PUBLIC _GNU_SOURCE _XOPEN_SOURCE _FILE_OFFSET_BITS=64 "$<$:__BIONIC_FORTIFY>" "$<$:__BIONIC_FORTIFY_RUNTIME_CHECKS_ENABLED>") elseif(WIN32) - target_compile_definitions(libfastfetch PUBLIC _GNU_SOURCE WIN32_LEAN_AND_MEAN _WIN32_WINNT=0x0A00 NOMINMAX UNICODE) # "$<$:_FORTIFY_SOURCE=3>" + target_compile_definitions(libfastfetch PUBLIC _GNU_SOURCE WIN32_LEAN_AND_MEAN WINRT_LEAN_AND_MEAN _WIN32_WINNT=0x0A00 NOMINMAX UNICODE) # "$<$:_FORTIFY_SOURCE=3>" elseif(APPLE) target_compile_definitions(libfastfetch PUBLIC _GNU_SOURCE _XOPEN_SOURCE __STDC_WANT_LIB_EXT1__ _FILE_OFFSET_BITS=64 _DARWIN_C_SOURCE) elseif(OpenBSD) @@ -1745,6 +1757,12 @@ elseif(WIN32) PRIVATE "mincore" ) endif() + if(HAVE_WINRT) + target_link_libraries(libfastfetch + PRIVATE + "RuntimeObject" + ) + endif() elseif(FreeBSD) target_link_libraries(libfastfetch PRIVATE "m" @@ -1831,20 +1849,6 @@ target_link_libraries(libfastfetch target_compile_options(libfastfetch PRIVATE $<$:-fno-exceptions -fno-rtti>) -if(WIN32) - set(CMAKE_CXX_STANDARD 20) - include(CheckIncludeFileCXX) - CHECK_INCLUDE_FILE_CXX("winrt/Windows.Foundation.h" HAVE_WINRT) - if(HAVE_WINRT) - add_library(ffwinrt MODULE src/detection/media/media_windows.dll.cpp) - target_link_libraries(ffwinrt PRIVATE "RuntimeObject") - target_compile_definitions(ffwinrt PRIVATE WIN32_LEAN_AND_MEAN=1 WINRT_LEAN_AND_MEAN=1) - target_link_options(ffwinrt - PRIVATE "-static" # stdc++, winpthread, gcc_s, etc. - ) - endif() - set(CMAKE_CXX_STANDARD 17) -endif() if(FreeBSD) set(CMAKE_REQUIRED_INCLUDES "/usr/local/include" "/usr/include") endif() @@ -1891,6 +1895,8 @@ if(NOT WIN32) message(WARNING "pthread_timedjoin_np was not found; networking timeout will not work") endif() endif() +elseif(HAVE_WINRT) + target_compile_definitions(libfastfetch PRIVATE FF_HAVE_WINRT=1) endif() set(PACKAGES_DISABLE_LIST "") @@ -2037,13 +2043,6 @@ if (TARGET flashfetch) ) endif() -if (TARGET ffwinrt) - install( - TARGETS ffwinrt - DESTINATION "${CMAKE_INSTALL_BINDIR}" - ) -endif() - install( FILES "${CMAKE_SOURCE_DIR}/completions/${CMAKE_PROJECT_NAME}.bash" DESTINATION "${CMAKE_INSTALL_DATAROOTDIR}/bash-completion/completions" diff --git a/src/common/impl/init.c b/src/common/impl/init.c index 91aa015eb2..80fb371242 100644 --- a/src/common/impl/init.c +++ b/src/common/impl/init.c @@ -260,6 +260,9 @@ void ffListFeatures(void) { #if FF_HAVE_EMBEDDED_PCIIDS "Embedded pciids\n" #endif +#if FF_HAVE_WINRT + "WinRT headers\n" +#endif #if FF_WIN81_COMPAT "Windows 8.1 Compatibility\n" #endif diff --git a/src/common/windows/com.hpp b/src/common/windows/com.hpp index 16bb1ac71c..8a0de559f5 100644 --- a/src/common/windows/com.hpp +++ b/src/common/windows/com.hpp @@ -2,6 +2,7 @@ #ifdef __cplusplus + #include "common/attributes.h" #include const char* ffInitCom(void); diff --git a/src/detection/media/media_windows.c b/src/detection/media/media_windows.c deleted file mode 100644 index c9d534a8d9..0000000000 --- a/src/detection/media/media_windows.c +++ /dev/null @@ -1,42 +0,0 @@ -#include "common/library.h" -#include "common/windows/unicode.h" -#include "media.h" -#include "media_windows.dll.h" - -static const char* getMedia(FFMediaResult* media, bool saveCover) { - FF_LIBRARY_LOAD_MESSAGE(libffwinrt, "libffwinrt" FF_LIBRARY_EXTENSION, 0) - FF_LIBRARY_LOAD_SYMBOL_MESSAGE(libffwinrt, ffWinrtDetectMedia) - libffwinrt = NULL; // Don't close libffwinrt or it may crash - - FFWinrtMediaResult result = {}; - - const char* error = ffffWinrtDetectMedia(&result, saveCover); - if (error) { - ffStrbufSetStatic(&media->error, error); - return NULL; - } - - ffStrbufSetWS(&media->playerId, result.playerId); - if (result.playerName[0]) { - ffStrbufSetWS(&media->player, result.playerName); - } else { - ffStrbufSet(&media->player, &media->playerId); - if (ffStrbufEndsWithIgnCaseS(&media->player, ".exe")) { - ffStrbufSubstrBefore(&media->player, media->player.length - 4); - } - } - ffStrbufSetWS(&media->song, result.song); - ffStrbufSetWS(&media->artist, result.artist); - ffStrbufSetWS(&media->album, result.album); - ffStrbufSetWS(&media->cover, result.cover); - ffStrbufSetStatic(&media->status, result.status); - if (media->cover.length > 0) { - media->removeCoverAfterUse = true; - } - return NULL; -} - -void ffDetectMediaImpl(FFMediaResult* media, bool saveCover) { - const char* error = getMedia(media, saveCover); - ffStrbufAppendS(&media->error, error); -} diff --git a/src/detection/media/media_windows.cpp b/src/detection/media/media_windows.cpp new file mode 100644 index 0000000000..7cf9eacc4c --- /dev/null +++ b/src/detection/media/media_windows.cpp @@ -0,0 +1,344 @@ +extern "C" { +#include "media.h" +#include "common/time.h" +#include "common/windows/unicode.h" +} + +#if FF_HAVE_WINRT + #include + #include + #include + #include + + #include + #include + #include + #include + + #include "common/windows/com.hpp" + +using winrt::impl::abi_t; +using winrt::Windows::Foundation::IAsyncOperation; +using winrt::Windows::Foundation::IAsyncOperationWithProgress; + +static inline void deleteHstring(HSTRING* pstr) { + if (*pstr) { + WindowsDeleteString(*pstr); + } +} + +static inline void ffStrbufSetHstring(FFstrbuf* destination, HSTRING value) { + uint32_t length = 0; + const wchar_t* raw = WindowsGetStringRawBuffer(value, &length); + ffStrbufSetNWS(destination, length, raw); +} + +template +static inline HRESULT ffGetActivationFactory(const wchar_t* className, REFIID iid, Interface** factory) { + HSTRING_HEADER header; + FF_A_CLEANUP(deleteHstring) HSTRING runtimeClass = NULL; + HRESULT hr = WindowsCreateStringReference(className, (UINT32)::wcslen(className), &header, &runtimeClass); + if (FAILED(hr)) { + return hr; + } + + return RoGetActivationFactory(runtimeClass, iid, reinterpret_cast(factory)); +} + +template +static inline HRESULT ffQueryInterface(SourceAbi* source, TargetAbi** target) { + *target = NULL; + return reinterpret_cast(source)->QueryInterface(winrt::guid_of(), reinterpret_cast(target)); +} + +template +static HRESULT ffWaitForAsyncOperation(TOperationAbi* operation, TResultAbi** result) { + *result = NULL; + + IAsyncInfo* FF_AUTO_RELEASE_COM_OBJECT asyncInfo = NULL; + HRESULT hr = ffQueryInterface(operation, &asyncInfo); + if (FAILED(hr)) { + return hr; + } + + AsyncStatus status = AsyncStatus::Started; + + for (;;) { + hr = asyncInfo->get_Status(&status); + if (FAILED(hr)) { + return hr; + } + if (status == AsyncStatus::Started) { + ffTimeSleep(0); + } else { + break; + } + } + + if (status != AsyncStatus::Completed) { + HRESULT errorCode = E_FAIL; + asyncInfo->get_ErrorCode(&errorCode); + return FAILED(errorCode) ? errorCode : E_FAIL; + } + + return operation->GetResults((void**) result); +} + +static HRESULT ffSaveThumbnailToTempPath( + abi_t* thumbnail, + FFstrbuf* destination) { + abi_t>* FF_AUTO_RELEASE_COM_OBJECT openOperation = NULL; + HRESULT hr = thumbnail->OpenReadAsync(reinterpret_cast(&openOperation)); + if (FAILED(hr)) { + return hr; + } + + abi_t* FF_AUTO_RELEASE_COM_OBJECT contentStream = NULL; + hr = ffWaitForAsyncOperation(openOperation, &contentStream); + if (FAILED(hr) || !contentStream) { + return FAILED(hr) ? hr : E_FAIL; + } + + abi_t* FF_AUTO_RELEASE_COM_OBJECT randomAccessStream = NULL; + hr = ffQueryInterface(contentStream, &randomAccessStream); + if (FAILED(hr)) { + return hr; + } + + UINT64 size = 0; + hr = randomAccessStream->get_Size(&size); + if (FAILED(hr) || size == 0) { + return FAILED(hr) ? hr : S_FALSE; + } + + if (size > 0xFFFFFFFFu) { + return HRESULT_FROM_WIN32(ERROR_FILE_TOO_LARGE); + } + + abi_t* FF_AUTO_RELEASE_COM_OBJECT bufferFactory = NULL; + hr = ffGetActivationFactory(L"Windows.Storage.Streams.Buffer", winrt::guid_of(), &bufferFactory); + if (FAILED(hr)) { + return hr; + } + + abi_t* FF_AUTO_RELEASE_COM_OBJECT buffer = NULL; + hr = bufferFactory->Create((UINT32) size, reinterpret_cast(&buffer)); + if (FAILED(hr) || !buffer) { + return FAILED(hr) ? hr : E_FAIL; + } + + abi_t* FF_AUTO_RELEASE_COM_OBJECT inputStream = NULL; + hr = ffQueryInterface(contentStream, &inputStream); + if (FAILED(hr)) { + return hr; + } + + abi_t>* FF_AUTO_RELEASE_COM_OBJECT readOperation = NULL; + hr = inputStream->ReadAsync(buffer, (UINT32) size, static_cast(winrt::Windows::Storage::Streams::InputStreamOptions::None), reinterpret_cast(&readOperation)); + if (FAILED(hr)) { + return hr; + } + + abi_t* FF_AUTO_RELEASE_COM_OBJECT readBuffer = NULL; + hr = ffWaitForAsyncOperation(readOperation, &readBuffer); + if (FAILED(hr) || !readBuffer) { + return FAILED(hr) ? hr : E_FAIL; + } + + UINT32 length = 0; + hr = readBuffer->get_Length(&length); + if (FAILED(hr) || length == 0) { + return FAILED(hr) ? hr : S_FALSE; + } + + Windows::Storage::Streams::IBufferByteAccess* FF_AUTO_RELEASE_COM_OBJECT byteAccess = NULL; + hr = reinterpret_cast(readBuffer)->QueryInterface(IID_PPV_ARGS(&byteAccess)); + if (FAILED(hr)) { + return hr; + } + + byte* bytes = NULL; + hr = byteAccess->Buffer(&bytes); + if (FAILED(hr) || !bytes) { + return FAILED(hr) ? hr : E_FAIL; + } + + wchar_t tempDirectory[MAX_PATH]; + DWORD tempLength = GetTempPathW(MAX_PATH, tempDirectory); + if (tempLength == 0 || tempLength >= MAX_PATH) { + return HRESULT_FROM_WIN32(GetLastError()); + } + + wchar_t tempFilePath[MAX_PATH]; + if (!GetTempFileNameW(tempDirectory, L"fft", 0, tempFilePath)) { + return HRESULT_FROM_WIN32(GetLastError()); + } + + HANDLE file = CreateFileW(tempFilePath, GENERIC_WRITE, 0, NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL); + if (file == INVALID_HANDLE_VALUE) { + DeleteFileW(tempFilePath); + return HRESULT_FROM_WIN32(GetLastError()); + } + + DWORD written = 0; + BOOL writtenOk = WriteFile(file, bytes, length, &written, NULL); + DWORD writeError = writtenOk ? ERROR_SUCCESS : GetLastError(); + CloseHandle(file); + + if (!writtenOk || written != length) { + DeleteFileW(tempFilePath); + return HRESULT_FROM_WIN32(writtenOk ? ERROR_WRITE_FAULT : writeError); + } + + ffStrbufSetWS(destination, tempFilePath); + return S_OK; +} + +static const char* getMedia(FFMediaResult* result, bool saveCover) { + // RO_INIT_MULTITHREADED is required for IRandomAccessStreamReference::OpenReadAsync to work correctly + HRESULT initHr = RoInitialize(RO_INIT_MULTITHREADED); + bool shouldUninitialize = SUCCEEDED(initHr); + if (FAILED(initHr) && initHr != RPC_E_CHANGED_MODE) { + return "winrt: RoInitialize() failed"; + } + + const char* error = NULL; + + do { + abi_t* FF_AUTO_RELEASE_COM_OBJECT managerStatics = NULL; + HRESULT hr = ffGetActivationFactory(L"Windows.Media.Control.GlobalSystemMediaTransportControlsSessionManager", winrt::guid_of(), &managerStatics); + if (FAILED(hr) || !managerStatics) { + error = "winrt: RoGetActivationFactory(GlobalSystemMediaTransportControlsSessionManager) failed"; + break; + } + + abi_t>* FF_AUTO_RELEASE_COM_OBJECT managerOperation = NULL; + hr = managerStatics->RequestAsync(reinterpret_cast(&managerOperation)); + if (FAILED(hr) || !managerOperation) { + error = "winrt: RequestAsync() failed"; + break; + } + + abi_t* FF_AUTO_RELEASE_COM_OBJECT manager = NULL; + hr = ffWaitForAsyncOperation(managerOperation, &manager); + if (FAILED(hr) || !manager) { + error = "winrt: RequestAsync().GetResults() failed"; + break; + } + + abi_t* FF_AUTO_RELEASE_COM_OBJECT session = NULL; + hr = manager->GetCurrentSession(reinterpret_cast(&session)); + if (FAILED(hr) || !session) { + error = "winrt: GetCurrentSession() failed"; + break; + } + + abi_t>* FF_AUTO_RELEASE_COM_OBJECT mediaPropsOperation = NULL; + hr = session->TryGetMediaPropertiesAsync(reinterpret_cast(&mediaPropsOperation)); + if (FAILED(hr) || !mediaPropsOperation) { + error = "winrt: TryGetMediaPropertiesAsync() failed"; + break; + } + + abi_t* FF_AUTO_RELEASE_COM_OBJECT mediaProps = NULL; + hr = ffWaitForAsyncOperation(mediaPropsOperation, &mediaProps); + if (FAILED(hr) || !mediaProps) { + error = "winrt: TryGetMediaPropertiesAsync().GetResults() failed"; + break; + } + + abi_t* FF_AUTO_RELEASE_COM_OBJECT playbackInfo = NULL; + hr = session->GetPlaybackInfo(reinterpret_cast(&playbackInfo)); + if (SUCCEEDED(hr) && playbackInfo) { + int32_t playbackStatusValue = 0; + if (SUCCEEDED(playbackInfo->get_PlaybackStatus(&playbackStatusValue))) { + switch (static_cast(playbackStatusValue)) { + #define FF_MEDIA_SET_STATUS(status_code) \ + case winrt::Windows::Media::Control::GlobalSystemMediaTransportControlsSessionPlaybackStatus::status_code: \ + ffStrbufSetStatic(&result->status, #status_code); \ + break; + FF_MEDIA_SET_STATUS(Closed) + FF_MEDIA_SET_STATUS(Opened) + FF_MEDIA_SET_STATUS(Changing) + FF_MEDIA_SET_STATUS(Stopped) + FF_MEDIA_SET_STATUS(Playing) + FF_MEDIA_SET_STATUS(Paused) + #undef FF_MEDIA_SET_STATUS + } + } + } + + FF_A_CLEANUP(deleteHstring) HSTRING playerId = NULL; + hr = session->get_SourceAppUserModelId(reinterpret_cast(&playerId)); + if (FAILED(hr)) { + error = "winrt: get_SourceAppUserModelId() failed"; + break; + } + + ffStrbufSetHstring(&result->playerId, playerId); + + FF_A_CLEANUP(deleteHstring) HSTRING title = NULL; + if (SUCCEEDED(mediaProps->get_Title(reinterpret_cast(&title)))) { + ffStrbufSetHstring(&result->song, title); + } + + FF_A_CLEANUP(deleteHstring) HSTRING artist = NULL; + if (SUCCEEDED(mediaProps->get_Artist(reinterpret_cast(&artist)))) { + ffStrbufSetHstring(&result->artist, artist); + } + + FF_A_CLEANUP(deleteHstring) HSTRING album = NULL; + if (SUCCEEDED(mediaProps->get_AlbumTitle(reinterpret_cast(&album)))) { + ffStrbufSetHstring(&result->album, album); + } + + abi_t* FF_AUTO_RELEASE_COM_OBJECT appInfoStatics = NULL; + hr = ffGetActivationFactory(L"Windows.ApplicationModel.AppInfo", winrt::guid_of(), &appInfoStatics); + if (SUCCEEDED(hr) && appInfoStatics) { + abi_t* FF_AUTO_RELEASE_COM_OBJECT appInfo = NULL; + if (SUCCEEDED(appInfoStatics->GetFromAppUserModelId(reinterpret_cast(playerId), reinterpret_cast(&appInfo))) && appInfo) { + abi_t* FF_AUTO_RELEASE_COM_OBJECT displayInfo = NULL; + if (SUCCEEDED(appInfo->get_DisplayInfo(reinterpret_cast(&displayInfo))) && displayInfo) { + FF_A_CLEANUP(deleteHstring) HSTRING displayName = NULL; + if (SUCCEEDED(displayInfo->get_DisplayName(reinterpret_cast(&displayName)))) { + ffStrbufSetHstring(&result->player, displayName); + } + } + } + } + + if (result->player.length == 0) { + ffStrbufSet(&result->player, &result->playerId); + if (ffStrbufEndsWithIgnCaseS(&result->player, ".exe")) { + ffStrbufSubstrBefore(&result->player, result->player.length - 4); + } + } + + if (saveCover) { + abi_t* FF_AUTO_RELEASE_COM_OBJECT thumbnail = NULL; + hr = mediaProps->get_Thumbnail(reinterpret_cast(&thumbnail)); + if (SUCCEEDED(hr) && thumbnail) { + if (SUCCEEDED(ffSaveThumbnailToTempPath(thumbnail, &result->cover)) && result->cover.length > 0) { + result->removeCoverAfterUse = true; + } + } + } + } while (false); + + if (shouldUninitialize) { + RoUninitialize(); + } + + return error; +} +#else +static const char* getMedia(FFMediaResult* media, bool saveCover) { + FF_UNUSED(media, saveCover); + return "Fastfetch is not compiled with WinRT support"; +} +#endif // FF_HAVE_WINRT + +extern "C" void ffDetectMediaImpl(FFMediaResult* media, bool saveCover) { + const char* error = getMedia(media, saveCover); + ffStrbufAppendS(&media->error, error); +} diff --git a/src/detection/media/media_windows.dll.cpp b/src/detection/media/media_windows.dll.cpp deleted file mode 100644 index b89ced9c73..0000000000 --- a/src/detection/media/media_windows.dll.cpp +++ /dev/null @@ -1,99 +0,0 @@ -#include -#include -#include -#include -#include -#include -#include - -extern "C" { -#include "media_windows.dll.h" - -const char* ffWinrtDetectMedia(FFWinrtMediaResult* result, bool saveCover) { - // C++/WinRT requires Windows 8.1+ and C++ runtime (std::string, exceptions and other stuff) - // Make it a separate dll in order not to break Windows 7 support - using namespace winrt::Windows::Media::Control; - using namespace winrt::Windows::ApplicationModel; - - try { - auto manager = GlobalSystemMediaTransportControlsSessionManager::RequestAsync().get(); - if (!manager) { - return "winrt: GlobalSystemMediaTransportControlsSessionManager::RequestAsync() failed"; - } - - auto session = manager.GetCurrentSession(); - if (!session) { - return "winrt: GetCurrentSession() failed"; - } - - auto mediaProps = session - .TryGetMediaPropertiesAsync() - .get(); - if (!mediaProps) { - return "winrt: TryGetMediaPropertiesAsync() failed"; - } - - if (auto playbackInfo = session.GetPlaybackInfo()) { - switch (playbackInfo.PlaybackStatus()) { -#define FF_MEDIA_SET_STATUS(status_code) \ - case GlobalSystemMediaTransportControlsSessionPlaybackStatus::status_code: \ - result->status = #status_code; \ - break; - FF_MEDIA_SET_STATUS(Closed) - FF_MEDIA_SET_STATUS(Opened) - FF_MEDIA_SET_STATUS(Changing) - FF_MEDIA_SET_STATUS(Stopped) - FF_MEDIA_SET_STATUS(Playing) - FF_MEDIA_SET_STATUS(Paused) -#undef FF_MEDIA_SET_STATUS - } - } - - ::wcsncpy(result->playerId, session.SourceAppUserModelId().data(), FF_MEDIA_WIN_RESULT_BUFLEN); - result->playerId[FF_MEDIA_WIN_RESULT_BUFLEN - 1] = L'\0'; - ::wcsncpy(result->song, mediaProps.Title().data(), FF_MEDIA_WIN_RESULT_BUFLEN); - result->song[FF_MEDIA_WIN_RESULT_BUFLEN - 1] = L'\0'; - ::wcsncpy(result->artist, mediaProps.Artist().data(), FF_MEDIA_WIN_RESULT_BUFLEN); - result->artist[FF_MEDIA_WIN_RESULT_BUFLEN - 1] = L'\0'; - ::wcsncpy(result->album, mediaProps.AlbumTitle().data(), FF_MEDIA_WIN_RESULT_BUFLEN); - result->album[FF_MEDIA_WIN_RESULT_BUFLEN - 1] = L'\0'; - try { - // Only works for UWP apps - ::wcsncpy(result->playerName, AppInfo::GetFromAppUserModelId(session.SourceAppUserModelId()).DisplayInfo().DisplayName().data(), FF_MEDIA_WIN_RESULT_BUFLEN); - result->playerName[FF_MEDIA_WIN_RESULT_BUFLEN - 1] = L'\0'; - } catch (...) {} - - if (saveCover) { - using namespace winrt::Windows::Storage; - using namespace winrt::Windows::Storage::Streams; - if (auto thumbRef = mediaProps.Thumbnail()) { - try { - if (auto stream = thumbRef.OpenReadAsync().get()) { - if (stream.Size() > 0) { - Buffer buffer(static_cast(stream.Size())); - stream.ReadAsync(buffer, buffer.Capacity(), InputStreamOptions::None).get(); - - wchar_t tempPath[MAX_PATH]; - if (GetTempPathW(MAX_PATH, tempPath) > 0) { - auto tempFolder = StorageFolder::GetFolderFromPathAsync(tempPath).get(); - auto tempFile = tempFolder.CreateFileAsync(L"ff_thumb.img", CreationCollisionOption::GenerateUniqueName).get(); - FileIO::WriteBufferAsync(tempFile, buffer).get(); - - ::wcsncpy(result->cover, tempFile.Path().data(), FF_MEDIA_WIN_RESULT_BUFLEN); - result->cover[FF_MEDIA_WIN_RESULT_BUFLEN - 1] = L'\0'; - } - } - } - } catch (...) { - // Ignore thumbnail errors - } - } - } - - return NULL; - } catch (...) { - return "A C++ exception is thrown"; - } -} - -} // extern "C" diff --git a/src/detection/media/media_windows.dll.h b/src/detection/media/media_windows.dll.h deleted file mode 100644 index b9b942d74a..0000000000 --- a/src/detection/media/media_windows.dll.h +++ /dev/null @@ -1,17 +0,0 @@ -#pragma once - -#define FF_MEDIA_WIN_RESULT_BUFLEN 256 - -typedef struct FFWinrtMediaResult { - wchar_t playerId[FF_MEDIA_WIN_RESULT_BUFLEN]; - wchar_t playerName[FF_MEDIA_WIN_RESULT_BUFLEN]; - wchar_t song[FF_MEDIA_WIN_RESULT_BUFLEN]; - wchar_t artist[FF_MEDIA_WIN_RESULT_BUFLEN]; - wchar_t album[FF_MEDIA_WIN_RESULT_BUFLEN]; - wchar_t cover[FF_MEDIA_WIN_RESULT_BUFLEN]; - const char* status; -} FFWinrtMediaResult; - -__attribute__((__dllexport__)) -const char* -ffWinrtDetectMedia(FFWinrtMediaResult* result, bool saveCover); From fe1c5c8f6f8139a56e20fd50effd144d09682744 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=9D=8E=E9=80=9A=E6=B4=B2?= Date: Fri, 1 May 2026 10:25:51 +0800 Subject: [PATCH 42/92] Media (Windows): improves readability Co-authored-by: Copilot --- src/detection/media/media_windows.cpp | 84 +++++++++++++-------------- 1 file changed, 42 insertions(+), 42 deletions(-) diff --git a/src/detection/media/media_windows.cpp b/src/detection/media/media_windows.cpp index 7cf9eacc4c..946b3e745c 100644 --- a/src/detection/media/media_windows.cpp +++ b/src/detection/media/media_windows.cpp @@ -17,6 +17,8 @@ extern "C" { #include "common/windows/com.hpp" + #define FF_BIND_FRONT(method, pobject) std::bind_front(&std::remove_cvref_t::method, (pobject)) + using winrt::impl::abi_t; using winrt::Windows::Foundation::IAsyncOperation; using winrt::Windows::Foundation::IAsyncOperationWithProgress; @@ -28,7 +30,7 @@ static inline void deleteHstring(HSTRING* pstr) { } static inline void ffStrbufSetHstring(FFstrbuf* destination, HSTRING value) { - uint32_t length = 0; + uint32_t length; const wchar_t* raw = WindowsGetStringRawBuffer(value, &length); ffStrbufSetNWS(destination, length, raw); } @@ -36,7 +38,7 @@ static inline void ffStrbufSetHstring(FFstrbuf* destination, HSTRING value) { template static inline HRESULT ffGetActivationFactory(const wchar_t* className, REFIID iid, Interface** factory) { HSTRING_HEADER header; - FF_A_CLEANUP(deleteHstring) HSTRING runtimeClass = NULL; + HSTRING runtimeClass; HRESULT hr = WindowsCreateStringReference(className, (UINT32)::wcslen(className), &header, &runtimeClass); if (FAILED(hr)) { return hr; @@ -45,16 +47,13 @@ static inline HRESULT ffGetActivationFactory(const wchar_t* className, REFIID ii return RoGetActivationFactory(runtimeClass, iid, reinterpret_cast(factory)); } -template -static inline HRESULT ffQueryInterface(SourceAbi* source, TargetAbi** target) { - *target = NULL; - return reinterpret_cast(source)->QueryInterface(winrt::guid_of(), reinterpret_cast(target)); +template +static inline HRESULT ffQueryInterface(SourceAbi* source, abi_t** target) { + return source->QueryInterface(winrt::guid_of(), reinterpret_cast(target)); } template static HRESULT ffWaitForAsyncOperation(TOperationAbi* operation, TResultAbi** result) { - *result = NULL; - IAsyncInfo* FF_AUTO_RELEASE_COM_OBJECT asyncInfo = NULL; HRESULT hr = ffQueryInterface(operation, &asyncInfo); if (FAILED(hr)) { @@ -84,17 +83,35 @@ static HRESULT ffWaitForAsyncOperation(TOperationAbi* operation, TResultAbi** re return operation->GetResults((void**) result); } -static HRESULT ffSaveThumbnailToTempPath( - abi_t* thumbnail, - FFstrbuf* destination) { - abi_t>* FF_AUTO_RELEASE_COM_OBJECT openOperation = NULL; - HRESULT hr = thumbnail->OpenReadAsync(reinterpret_cast(&openOperation)); - if (FAILED(hr)) { +template +static HRESULT ffRunAndWait(TOperation&& operation, abi_t** result, TArgs&&... args) { + abi_t>* FF_AUTO_RELEASE_COM_OBJECT opResult = NULL; + HRESULT hr = operation(std::forward(args)..., reinterpret_cast(&opResult)); + if (FAILED(hr) || !opResult) { + return hr; + } + + return ffWaitForAsyncOperation(opResult, result); +} + +template +static HRESULT ffRunAndWait2(TOperation&& operation, abi_t** result, TArgs&&... args) { + *result = NULL; + + abi_t>* FF_AUTO_RELEASE_COM_OBJECT opResult = NULL; + HRESULT hr = operation(std::forward(args)..., reinterpret_cast(&opResult)); + if (FAILED(hr) || !opResult) { return hr; } + return ffWaitForAsyncOperation(opResult, result); +} + +static HRESULT ffSaveThumbnailToTempPath( + abi_t* thumbnail, + FFstrbuf* destination) { abi_t* FF_AUTO_RELEASE_COM_OBJECT contentStream = NULL; - hr = ffWaitForAsyncOperation(openOperation, &contentStream); + HRESULT hr = ffRunAndWait(FF_BIND_FRONT(OpenReadAsync, thumbnail), &contentStream); if (FAILED(hr) || !contentStream) { return FAILED(hr) ? hr : E_FAIL; } @@ -133,14 +150,8 @@ static HRESULT ffSaveThumbnailToTempPath( return hr; } - abi_t>* FF_AUTO_RELEASE_COM_OBJECT readOperation = NULL; - hr = inputStream->ReadAsync(buffer, (UINT32) size, static_cast(winrt::Windows::Storage::Streams::InputStreamOptions::None), reinterpret_cast(&readOperation)); - if (FAILED(hr)) { - return hr; - } - abi_t* FF_AUTO_RELEASE_COM_OBJECT readBuffer = NULL; - hr = ffWaitForAsyncOperation(readOperation, &readBuffer); + hr = ffRunAndWait2(FF_BIND_FRONT(ReadAsync, inputStream), &readBuffer, buffer, (uint32_t) size, static_cast(winrt::Windows::Storage::Streams::InputStreamOptions::None)); if (FAILED(hr) || !readBuffer) { return FAILED(hr) ? hr : E_FAIL; } @@ -152,7 +163,7 @@ static HRESULT ffSaveThumbnailToTempPath( } Windows::Storage::Streams::IBufferByteAccess* FF_AUTO_RELEASE_COM_OBJECT byteAccess = NULL; - hr = reinterpret_cast(readBuffer)->QueryInterface(IID_PPV_ARGS(&byteAccess)); + hr = readBuffer->QueryInterface(IID_PPV_ARGS(&byteAccess)); if (FAILED(hr)) { return hr; } @@ -176,16 +187,18 @@ static HRESULT ffSaveThumbnailToTempPath( HANDLE file = CreateFileW(tempFilePath, GENERIC_WRITE, 0, NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL); if (file == INVALID_HANDLE_VALUE) { + DWORD writeError = GetLastError(); DeleteFileW(tempFilePath); - return HRESULT_FROM_WIN32(GetLastError()); + return HRESULT_FROM_WIN32(writeError); } DWORD written = 0; BOOL writtenOk = WriteFile(file, bytes, length, &written, NULL); - DWORD writeError = writtenOk ? ERROR_SUCCESS : GetLastError(); - CloseHandle(file); + NtClose(file); + file = NULL; if (!writtenOk || written != length) { + DWORD writeError = GetLastError(); DeleteFileW(tempFilePath); return HRESULT_FROM_WIN32(writtenOk ? ERROR_WRITE_FAULT : writeError); } @@ -212,15 +225,8 @@ static const char* getMedia(FFMediaResult* result, bool saveCover) { break; } - abi_t>* FF_AUTO_RELEASE_COM_OBJECT managerOperation = NULL; - hr = managerStatics->RequestAsync(reinterpret_cast(&managerOperation)); - if (FAILED(hr) || !managerOperation) { - error = "winrt: RequestAsync() failed"; - break; - } - abi_t* FF_AUTO_RELEASE_COM_OBJECT manager = NULL; - hr = ffWaitForAsyncOperation(managerOperation, &manager); + hr = ffRunAndWait(FF_BIND_FRONT(RequestAsync, managerStatics), &manager); if (FAILED(hr) || !manager) { error = "winrt: RequestAsync().GetResults() failed"; break; @@ -228,20 +234,14 @@ static const char* getMedia(FFMediaResult* result, bool saveCover) { abi_t* FF_AUTO_RELEASE_COM_OBJECT session = NULL; hr = manager->GetCurrentSession(reinterpret_cast(&session)); + if (FAILED(hr) || !session) { error = "winrt: GetCurrentSession() failed"; break; } - abi_t>* FF_AUTO_RELEASE_COM_OBJECT mediaPropsOperation = NULL; - hr = session->TryGetMediaPropertiesAsync(reinterpret_cast(&mediaPropsOperation)); - if (FAILED(hr) || !mediaPropsOperation) { - error = "winrt: TryGetMediaPropertiesAsync() failed"; - break; - } - abi_t* FF_AUTO_RELEASE_COM_OBJECT mediaProps = NULL; - hr = ffWaitForAsyncOperation(mediaPropsOperation, &mediaProps); + hr = ffRunAndWait(FF_BIND_FRONT(TryGetMediaPropertiesAsync, session), &mediaProps); if (FAILED(hr) || !mediaProps) { error = "winrt: TryGetMediaPropertiesAsync().GetResults() failed"; break; From 3cddf31987c6e7a05dfd4b1e02a5ba87f55459b4 Mon Sep 17 00:00:00 2001 From: Christopher L Fox Junior Date: Fri, 1 May 2026 04:13:32 -0500 Subject: [PATCH 43/92] Add ASCII logo for KibaOS (#2294) * Add ASCII logo for KibaOS * Add KibaOS logo to builtin.c * Update builtin.c * Update builtin.c * Add KibaOS logo to builtin logo configurations Added KibaOS logo configuration to the logo data structure. * Fix KibaOS Syntax --- src/logo/ascii/kibaos.txt | 6 ++++++ src/logo/builtin.c | 14 ++++++++++++-- 2 files changed, 18 insertions(+), 2 deletions(-) create mode 100644 src/logo/ascii/kibaos.txt diff --git a/src/logo/ascii/kibaos.txt b/src/logo/ascii/kibaos.txt new file mode 100644 index 0000000000..b9ee09a35e --- /dev/null +++ b/src/logo/ascii/kibaos.txt @@ -0,0 +1,6 @@ + _ ___ _ ___ ____ +| |/ (_) |__ __ _ / _ \/ ___| +| ' /| | '_ \ / _` | | | \___ \ +| . \| | |_) | (_| | |_| |___) | +|_|\_\_|_.__/ \__,_|\___/|____/ + diff --git a/src/logo/builtin.c b/src/logo/builtin.c index f969f43795..3ccee08f1e 100644 --- a/src/logo/builtin.c +++ b/src/logo/builtin.c @@ -2481,6 +2481,15 @@ static const FFlogo K[] = { FF_COLOR_FG_DEFAULT, }, }, + // KibaOS + { + .names = { "KibaOS" }, + .lines = FASTFETCH_DATATEXT_LOGO_KIBAOS, + .colors = { + FF_COLOR_BG_WHITE, + FF_COLOR_FG_BLUE, + }, + }, // Kibojoe { .names = { "Kibojoe" }, @@ -4551,7 +4560,7 @@ static const FFlogo S[] = { FF_COLOR_FG_CYAN, FF_COLOR_FG_WHITE, } }, - // SleeperOS + // SleeperOSSmall { .names = { "SleeperOS_small" }, .type = FF_LOGO_LINE_TYPE_SMALL_BIT, .lines = FASTFETCH_DATATEXT_LOGO_SLEEPEROS_SMALL, .colors = { FF_COLOR_FG_CYAN, FF_COLOR_FG_WHITE, @@ -5163,7 +5172,7 @@ static const FFlogo U[] = { FF_COLOR_FG_256 "52", }, }, - // LAST + // Uzbek { .names = { "Uzbek" }, .lines = FASTFETCH_DATATEXT_LOGO_UZBEK, @@ -5171,6 +5180,7 @@ static const FFlogo U[] = { FF_COLOR_FG_GREEN, }, }, + // LAST {}, }; From 0dd8d80e028696074615f1a2f7a342a0016422e5 Mon Sep 17 00:00:00 2001 From: Angelo Verlain <37999241+vixalien@users.noreply.github.com> Date: Fri, 1 May 2026 19:06:47 +0200 Subject: [PATCH 44/92] Logo (Builtin): correctly detects GNOME OS (#2296) * Correctly detect GNOME OS * Update builtin.c --------- Co-authored-by: Carter Li --- src/logo/builtin.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/logo/builtin.c b/src/logo/builtin.c index 3ccee08f1e..0f15f8b62a 100644 --- a/src/logo/builtin.c +++ b/src/logo/builtin.c @@ -2056,9 +2056,9 @@ static const FFlogo G[] = { .colorKeys = FF_COLOR_FG_BLUE, .colorTitle = FF_COLOR_FG_BLUE, }, - // GNOME + // GNOME OS { - .names = { "GNOME" }, + .names = { "GNOME OS" }, // matches "NAME" .lines = FASTFETCH_DATATEXT_LOGO_GNOME, .colors = { FF_COLOR_FG_BLUE, From 90e2d8f8d9d3a5976e787b8891cde3d39ed0c9da Mon Sep 17 00:00:00 2001 From: Oliver Lin Date: Sat, 2 May 2026 21:54:33 +0800 Subject: [PATCH 45/92] OS (Linux): detects Ubuntu Studio before Kubuntu on KDE systems (#2298) * fix(os): detect Ubuntu Studio before KDE-based flavor checks Add an explicit Ubuntu Studio detection in `getUbuntuFlavour()` by checking for `/usr/share/doc/ubuntustudio-desktop` and setting `name`, `id`, and `idLike` accordingly. This prevents Ubuntu Studio systems from being misidentified as Kubuntu when KDE/Plasma-related XDG config paths are present.fix(os): detect Ubuntu Studio before KDE-based flavor checks Add an explicit Ubuntu Studio detection in `getUbuntuFlavour()` by checking for `/usr/share/doc/ubuntustudio-desktop` and setting `name`, `id`, and `idLike` accordingly. This prevents Ubuntu Studio systems from being misidentified as Kubuntu when KDE/Plasma-related XDG config paths are present. * refactor(os): detect Ubuntu Studio via package presence --- src/detection/os/os_linux.c | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/detection/os/os_linux.c b/src/detection/os/os_linux.c index f8d9aac3e9..081bf89af5 100644 --- a/src/detection/os/os_linux.c +++ b/src/detection/os/os_linux.c @@ -99,6 +99,14 @@ FF_A_UNUSED static bool getUbuntuFlavour(FFOSResult* result) { return false; } + if (ffPathExists("/var/lib/dpkg/info/ubuntustudio-desktop.list", FF_PATHTYPE_FILE)) + { + ffStrbufSetStatic(&result->name, "Ubuntu Studio"); + ffStrbufSetStatic(&result->id, "ubuntu-studio"); + ffStrbufSetStatic(&result->idLike, "ubuntu"); + return false; + } + if (ffStrContains(xdgConfigDirs, "kde") || ffStrContains(xdgConfigDirs, "plasma") || ffStrContains(xdgConfigDirs, "kubuntu")) { ffStrbufSetStatic(&result->name, "Kubuntu"); ffStrbufSetStatic(&result->id, "kubuntu"); From 08e252a44cd7e18f3ce65eb79fcf518293294a50 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=9D=8E=E9=80=9A=E6=B4=B2?= Date: Sat, 2 May 2026 21:59:22 +0800 Subject: [PATCH 46/92] OS (Linux): adds Ubuntu Kylin detection --- src/detection/os/os_linux.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/detection/os/os_linux.c b/src/detection/os/os_linux.c index 081bf89af5..381bc2bb98 100644 --- a/src/detection/os/os_linux.c +++ b/src/detection/os/os_linux.c @@ -149,9 +149,9 @@ FF_A_UNUSED static bool getUbuntuFlavour(FFOSResult* result) { return false; } - if (ffStrContains(xdgConfigDirs, "studio")) { - ffStrbufSetStatic(&result->name, "Ubuntu Studio"); - ffStrbufSetStatic(&result->id, "ubuntu-studio"); + if (ffStrContains(xdgConfigDirs, "ukui")) { + ffStrbufSetStatic(&result->name, "Ubuntu Kylin"); + ffStrbufSetStatic(&result->id, "ubuntu-kylin"); ffStrbufSetStatic(&result->idLike, "ubuntu"); return false; } From 0557f16c1668481dd190b590b8eed0d96d7f6213 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=9D=8E=E9=80=9A=E6=B4=B2?= Date: Sat, 2 May 2026 22:06:18 +0800 Subject: [PATCH 47/92] OS (Linux): adds Ubuntu Unity detection --- src/detection/os/os_linux.c | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/detection/os/os_linux.c b/src/detection/os/os_linux.c index 381bc2bb98..c05c036b5b 100644 --- a/src/detection/os/os_linux.c +++ b/src/detection/os/os_linux.c @@ -170,6 +170,13 @@ FF_A_UNUSED static bool getUbuntuFlavour(FFOSResult* result) { return false; } + if (ffStrContains(xdgConfigDirs, "unity")) { + ffStrbufSetStatic(&result->name, "Ubuntu Unity"); + ffStrbufSetStatic(&result->id, "ubuntu-unity"); + ffStrbufSetStatic(&result->idLike, "ubuntu"); + return false; + } + return false; } From 2e6b9cd5035d802ac55516d006019127886e69f9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=9D=8E=E9=80=9A=E6=B4=B2?= Date: Sat, 2 May 2026 22:18:08 +0800 Subject: [PATCH 48/92] OS (Linux): adds comments --- src/detection/os/os_linux.c | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/src/detection/os/os_linux.c b/src/detection/os/os_linux.c index c05c036b5b..2c6fd47851 100644 --- a/src/detection/os/os_linux.c +++ b/src/detection/os/os_linux.c @@ -94,11 +94,7 @@ FF_A_UNUSED static bool getUbuntuFlavour(FFOSResult* result) { return true; } - const char* xdgConfigDirs = getenv("XDG_CONFIG_DIRS"); - if (!ffStrSet(xdgConfigDirs)) { - return false; - } - + // xdgConfigDirs contains plasma only if (ffPathExists("/var/lib/dpkg/info/ubuntustudio-desktop.list", FF_PATHTYPE_FILE)) { ffStrbufSetStatic(&result->name, "Ubuntu Studio"); @@ -107,6 +103,11 @@ FF_A_UNUSED static bool getUbuntuFlavour(FFOSResult* result) { return false; } + const char* xdgConfigDirs = getenv("XDG_CONFIG_DIRS"); + if (!ffStrSet(xdgConfigDirs)) { + return false; + } + if (ffStrContains(xdgConfigDirs, "kde") || ffStrContains(xdgConfigDirs, "plasma") || ffStrContains(xdgConfigDirs, "kubuntu")) { ffStrbufSetStatic(&result->name, "Kubuntu"); ffStrbufSetStatic(&result->id, "kubuntu"); From b465052ad2a95cabe1536a568e34de96f08c9d15 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=9D=8E=E9=80=9A=E6=B4=B2?= Date: Sat, 2 May 2026 22:19:26 +0800 Subject: [PATCH 49/92] Logo (Builtin): removes Ubuntu KDE --- src/logo/builtin.c | 11 ----------- 1 file changed, 11 deletions(-) diff --git a/src/logo/builtin.c b/src/logo/builtin.c index 0f15f8b62a..74b798d5d3 100644 --- a/src/logo/builtin.c +++ b/src/logo/builtin.c @@ -5052,17 +5052,6 @@ static const FFlogo U[] = { .colorKeys = FF_COLOR_FG_GREEN, .colorTitle = FF_COLOR_FG_DEFAULT, }, - // UbuntuKde - { - .names = { "ubuntu kde", "ubuntu-kde", "ubuntu-plasma" }, - .lines = FASTFETCH_DATATEXT_LOGO_KUBUNTU, - .colors = { - FF_COLOR_FG_BLUE, - FF_COLOR_FG_WHITE, - }, - .colorKeys = FF_COLOR_FG_BLUE, - .colorTitle = FF_COLOR_FG_BLUE, - }, // UbuntuStudio { .names = { "ubuntu studio", "ubuntu-studio" }, From d9e4389ed47276a6d68e1b22b715978061daad75 Mon Sep 17 00:00:00 2001 From: Sarp Mateson Date: Mon, 4 May 2026 14:42:51 +0300 Subject: [PATCH 50/92] Logo (Builtin): add NebiOS (#2295) * added logo with nebisoft's color palette, updated builtin.c * forgot to update the nebios logo with color placeholders --- src/logo/ascii/nebios.txt | 19 +++++++++++++++++++ src/logo/builtin.c | 10 ++++++++++ 2 files changed, 29 insertions(+) create mode 100644 src/logo/ascii/nebios.txt diff --git a/src/logo/ascii/nebios.txt b/src/logo/ascii/nebios.txt new file mode 100644 index 0000000000..eace0c14a6 --- /dev/null +++ b/src/logo/ascii/nebios.txt @@ -0,0 +1,19 @@ + $2:ddl. $3:O0x' + $2.dddddd: $3x00000c + $2:dddddddl $3.O000000k + $2cdddddddo $3.00000000c + $2,dddddddd. $3:000000000. + $1.: $2.dddddddd' $3l0000000000 + $1:oo. $2dddddddd; $3.00000000000 + $1oooo. $2ddddddddc $3.000000000k + $1ooooo, $2ldddddddl $300000000c + $1.oooooo; $2:dddddddo $3k000000. + $1'oooooooc $2'dddddddd. $3c00000 + $1:ooooooool $2.dddddddd' $3'0000 + $1ooooooooooo $2dddddddd; $3.00k + $1oooooooooo. $2ddddddddc $3.0: + $1ooooooooo. $2cdddddddl +$1.oooooooo. $2,dddddddo +$1:ooooooo. $2.dddddddd. +$1loooooo. $2.ddddddd: + $1coooo $2ldddd, diff --git a/src/logo/builtin.c b/src/logo/builtin.c index 74b798d5d3..f362d9a9a2 100644 --- a/src/logo/builtin.c +++ b/src/logo/builtin.c @@ -3247,6 +3247,16 @@ static const FFlogo N[] = { FF_COLOR_FG_WHITE, }, }, + // NebiOS + { + .names = { "NebiOS" }, + .lines = FASTFETCH_DATATEXT_LOGO_NEBIOS, + .colors = { + FF_COLOR_FG_RGB "255;95;95", + FF_COLOR_FG_RGB "252;146;84", + FF_COLOR_FG_RGB "248;196;72", + }, + }, // Nekos { .names = { "Nekos" }, From 93f9ed1bf5ac6bcf38fdef5c72200ad8ef9f4da0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=9D=8E=E9=80=9A=E6=B4=B2?= Date: Mon, 4 May 2026 19:44:13 +0800 Subject: [PATCH 51/92] Wmtheme (Linux): adds NebiDE support Ref: #2295 --- src/detection/displayserver/displayserver.h | 1 + src/detection/gtk_qt/gtk.c | 3 ++- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/src/detection/displayserver/displayserver.h b/src/detection/displayserver/displayserver.h index 523f32f3f8..d6adf4d1da 100644 --- a/src/detection/displayserver/displayserver.h +++ b/src/detection/displayserver/displayserver.h @@ -15,6 +15,7 @@ #define FF_DE_PRETTY_CDE "CDE" #define FF_DE_PRETTY_UNITY "Unity" #define FF_DE_PRETTY_UKUI "UKUI" +#define FF_DE_PRETTY_NEBIDE "NebiDE" #define FF_WM_PRETTY_KWIN "KWin" #define FF_WM_PRETTY_MUTTER "Mutter" diff --git a/src/detection/gtk_qt/gtk.c b/src/detection/gtk_qt/gtk.c index c128e54a43..41da10e70a 100644 --- a/src/detection/gtk_qt/gtk.c +++ b/src/detection/gtk_qt/gtk.c @@ -87,7 +87,8 @@ static void detectGTKFromSettings(FFGTKResult* result) { ffStrbufIgnCaseEqualS(&wmde->dePrettyName, FF_DE_PRETTY_GNOME) || ffStrbufIgnCaseEqualS(&wmde->dePrettyName, FF_DE_PRETTY_GNOME_CLASSIC) || ffStrbufIgnCaseEqualS(&wmde->dePrettyName, FF_DE_PRETTY_UNITY) || - ffStrbufIgnCaseEqualS(&wmde->dePrettyName, FF_DE_PRETTY_BUDGIE)) { + ffStrbufIgnCaseEqualS(&wmde->dePrettyName, FF_DE_PRETTY_BUDGIE) || + ffStrbufIgnCaseEqualS(&wmde->dePrettyName, FF_DE_PRETTY_NEBIDE)) { themeName = ffSettingsGetGnome("/org/gnome/desktop/interface/gtk-theme", "org.gnome.desktop.interface", NULL, "gtk-theme", FF_VARIANT_TYPE_STRING).strValue; iconsName = ffSettingsGetGnome("/org/gnome/desktop/interface/icon-theme", "org.gnome.desktop.interface", NULL, "icon-theme", FF_VARIANT_TYPE_STRING).strValue; fontName = ffSettingsGetGnome("/org/gnome/desktop/interface/font-name", "org.gnome.desktop.interface", NULL, "font-name", FF_VARIANT_TYPE_STRING).strValue; From 451bc394eeffe2e1b709cf4fd102de8400db528d Mon Sep 17 00:00:00 2001 From: Oliver Lin Date: Wed, 6 May 2026 09:43:17 +0800 Subject: [PATCH 52/92] OS (Linux): enhances Deepin version detection using /etc/os-version (#2300) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * feat(os): enhance Deepin detection with os-version data Add a Deepin-specific enhancement in Linux OS detection that reads `/etc/os-version` for `MinorVersion` and `EditionName`. When available, it now: - sets `versionID` to `MinorVersion` - updates `prettyName` to include version and optional edition This improves Deepin version accuracy and provides a clearer OS name than relying solely on generic `os-release` fields.feat(os): enhance Deepin detection with os-version data Add a Deepin-specific enhancement in Linux OS detection that reads `/etc/os-version` for `MinorVersion` and `EditionName`. When available, it now: - sets `versionID` to `MinorVersion` - updates `prettyName` to include version and optional edition This improves Deepin version accuracy and provides a clearer OS name than relying solely on generic `os-release` fields. * fix(os): use distro name when formatting Deepin prettyName Update Deepin enhancement formatting to build `prettyName` from `result->name` instead of hardcoding "Deepin". This keeps output consistent with the detected distro name while still appending version and edition details.fix(os): use distro name when formatting Deepin prettyName Update Deepin enhancement formatting to build `prettyName` from `result->name` instead of hardcoding "Deepin". This keeps output consistent with the detected distro name while still appending version and edition details. * fix(os/linux): guard Deepin minor version override Only apply Deepin’s `minor` value to `versionID` when an edition is present and `prettyName` does not already include parenthesized details. This avoids incorrectly overriding parsed version info in enhanced names.fix(os/linux): guard Deepin minor version override Only apply Deepin’s `minor` value to `versionID` when an edition is present and `prettyName` does not already include parenthesized details. This avoids incorrectly overriding parsed version info in enhanced names. Co-authored-by: Copilot * Refactor Deepin enhancement detection logic * Refactor detectDeepinEnhancement function --------- Co-authored-by: Copilot Co-authored-by: Carter Li --- src/detection/os/os_linux.c | 34 ++++++++++++++++++++++++++++++++-- 1 file changed, 32 insertions(+), 2 deletions(-) diff --git a/src/detection/os/os_linux.c b/src/detection/os/os_linux.c index 2c6fd47851..b07bc3a2ec 100644 --- a/src/detection/os/os_linux.c +++ b/src/detection/os/os_linux.c @@ -95,8 +95,7 @@ FF_A_UNUSED static bool getUbuntuFlavour(FFOSResult* result) { } // xdgConfigDirs contains plasma only - if (ffPathExists("/var/lib/dpkg/info/ubuntustudio-desktop.list", FF_PATHTYPE_FILE)) - { + if (ffPathExists("/var/lib/dpkg/info/ubuntustudio-desktop.list", FF_PATHTYPE_FILE)) { ffStrbufSetStatic(&result->name, "Ubuntu Studio"); ffStrbufSetStatic(&result->id, "ubuntu-studio"); ffStrbufSetStatic(&result->idLike, "ubuntu"); @@ -306,6 +305,34 @@ static bool detectBedrock(FFOSResult* os) { return parseOsRelease(FASTFETCH_TARGET_DIR_ROOT "/bedrock/strata/bedrock/etc/os-release", os); } +static void detectDeepinEnhancement(FFOSResult* result) { + if (ffStrbufContainC(&result->prettyName, '(')) { + return; + } + + FF_STRBUF_AUTO_DESTROY minor = ffStrbufCreate(); + FF_STRBUF_AUTO_DESTROY edition = ffStrbufCreate(); + + if (!ffParsePropFileValues( + FASTFETCH_TARGET_DIR_ETC "/os-version", + 2, + (FFpropquery[]) { + { "MinorVersion=", &minor }, + { "EditionName=", &edition }, + }) || + minor.length == 0) { + return; + } + + ffStrbufSet(&result->versionID, &minor); + + if (edition.length > 0) { + ffStrbufSetF(&result->prettyName, "%s %s (%s)", result->name.chars, minor.chars, edition.chars); + } else { + ffStrbufSetF(&result->prettyName, "%s %s", result->name.chars, minor.chars); + } +} + static void detectOS(FFOSResult* os) { #ifdef FF_CUSTOM_OS_RELEASE_PATH parseOsRelease(FF_STR(FF_CUSTOM_OS_RELEASE_PATH), os); @@ -322,6 +349,7 @@ static void detectOS(FFOSResult* os) { // Refer: https://gist.github.com/natefoo/814c5bf936922dad97ff parseOsRelease(FASTFETCH_TARGET_DIR_ETC "/os-release", os); + if (os->id.length == 0 || os->version.length == 0 || os->prettyName.length == 0 || os->codename.length == 0) { parseLsbRelease(FASTFETCH_TARGET_DIR_ETC "/lsb-release", os); } @@ -360,6 +388,8 @@ void ffDetectOSImpl(FFOSResult* os) { ffStrbufSetS(&os->id, "lmde"); ffStrbufSetS(&os->idLike, "linuxmint"); } + } else if (ffStrbufEqualS(&os->id, "deepin")) { + detectDeepinEnhancement(os); } #endif } From ba54763dde6aace6dc72f3c0aba43afb7c9df364 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=9D=8E=E9=80=9A=E6=B4=B2?= Date: Wed, 6 May 2026 11:06:07 +0800 Subject: [PATCH 53/92] Camera (Windows): adds support for Display P3 color space --- src/detection/camera/camera_windows.cpp | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/detection/camera/camera_windows.cpp b/src/detection/camera/camera_windows.cpp index e1fa90e07f..fb7a653ea7 100644 --- a/src/detection/camera/camera_windows.cpp +++ b/src/detection/camera/camera_windows.cpp @@ -133,6 +133,9 @@ extern "C" const char* ffDetectCamera(FF_A_UNUSED FFlist* result) { case MFVideoPrimaries_ACES: ffStrbufSetStatic(&camera->colorspace, "ACES"); break; + case (MFVideoPrimaries)13: // MFVideoPrimaries_Display_P3 + ffStrbufSetStatic(&camera->colorspace, "Display P3"); + break; default: break; } From 79b61f139183a9ded915024851ea36fa6fbd706b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=9D=8E=E9=80=9A=E6=B4=B2?= Date: Wed, 6 May 2026 14:38:10 +0800 Subject: [PATCH 54/92] Common (Windows): inits WinRT/COM globally --- src/common/windows/com.cpp | 48 +++++++++++---------------- src/common/windows/com.hpp | 1 + src/detection/media/media_windows.cpp | 14 ++------ 3 files changed, 23 insertions(+), 40 deletions(-) diff --git a/src/common/windows/com.cpp b/src/common/windows/com.cpp index 4ad067b8d2..924baf4142 100644 --- a/src/common/windows/com.cpp +++ b/src/common/windows/com.cpp @@ -1,40 +1,30 @@ #include "com.hpp" -#include "fastfetch.h" -#include +#include -// https://learn.microsoft.com/en-us/windows/win32/wmisdk/example--getting-wmi-data-from-the-local-computer -// https://learn.microsoft.com/en-us/windows/win32/cimwin32prov/computer-system-hardware-classes -static void CoUninitializeWrap(void) { - CoUninitialize(); +static void RoUninitializeWrap(void) { + RoUninitialize(); } static const char* doInitCom() { - // Initialize COM - if (FAILED(CoInitializeEx(NULL, COINIT_MULTITHREADED))) { - return "CoInitializeEx() failed"; + HRESULT res = RoInitialize(RO_INIT_MULTITHREADED); + if (FAILED(res)) { + switch (res) { + case E_INVALIDARG: + return "RoInitialize() failed: invalid argument"; + case E_OUTOFMEMORY: + return "RoInitialize() failed: out of memory"; + case E_UNEXPECTED: + return "RoInitialize() failed: unexpected error"; + case RPC_E_CHANGED_MODE: + // COM was already initialized with a different concurrency model + return NULL; + default: + return "RoInitialize() failed: unknown error"; + } } - // Set general COM security levels - - HRESULT hRes = CoInitializeSecurity( - NULL, - -1, // COM authentication - NULL, // Authentication services - NULL, // Reserved - RPC_C_AUTHN_LEVEL_DEFAULT, // Default authentication - RPC_C_IMP_LEVEL_IMPERSONATE, // Default Impersonation - NULL, // Authentication info - EOAC_NONE, // Additional capabilities - NULL // Reserved - ); - - if (FAILED(hRes) && hRes != RPC_E_TOO_LATE /* Has been set by a random dll */) { - CoUninitialize(); - return "CoInitializeSecurity() failed"; - } - - atexit(CoUninitializeWrap); + atexit(RoUninitializeWrap); return NULL; } diff --git a/src/common/windows/com.hpp b/src/common/windows/com.hpp index 8a0de559f5..eb6b106116 100644 --- a/src/common/windows/com.hpp +++ b/src/common/windows/com.hpp @@ -5,6 +5,7 @@ #include "common/attributes.h" #include +// Initialize COM & WinRT const char* ffInitCom(void); static inline void ffReleaseComObject(void* ppUnknown) { diff --git a/src/detection/media/media_windows.cpp b/src/detection/media/media_windows.cpp index 946b3e745c..9a92440e38 100644 --- a/src/detection/media/media_windows.cpp +++ b/src/detection/media/media_windows.cpp @@ -208,15 +208,11 @@ static HRESULT ffSaveThumbnailToTempPath( } static const char* getMedia(FFMediaResult* result, bool saveCover) { - // RO_INIT_MULTITHREADED is required for IRandomAccessStreamReference::OpenReadAsync to work correctly - HRESULT initHr = RoInitialize(RO_INIT_MULTITHREADED); - bool shouldUninitialize = SUCCEEDED(initHr); - if (FAILED(initHr) && initHr != RPC_E_CHANGED_MODE) { - return "winrt: RoInitialize() failed"; + const char* error = ffInitCom(); + if (error) { + return error; } - const char* error = NULL; - do { abi_t* FF_AUTO_RELEASE_COM_OBJECT managerStatics = NULL; HRESULT hr = ffGetActivationFactory(L"Windows.Media.Control.GlobalSystemMediaTransportControlsSessionManager", winrt::guid_of(), &managerStatics); @@ -325,10 +321,6 @@ static const char* getMedia(FFMediaResult* result, bool saveCover) { } } while (false); - if (shouldUninitialize) { - RoUninitialize(); - } - return error; } #else From dca3b154ef5cff40611d11723848c6f5c51e6b10 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=9D=8E=E9=80=9A=E6=B4=B2?= Date: Wed, 6 May 2026 15:00:52 +0800 Subject: [PATCH 55/92] Global (Windows): fixes possible use-before-init errors --- src/detection/camera/camera_windows.cpp | 4 ++-- src/detection/sound/sound_windows.cpp | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/detection/camera/camera_windows.cpp b/src/detection/camera/camera_windows.cpp index fb7a653ea7..48ee9dc455 100644 --- a/src/detection/camera/camera_windows.cpp +++ b/src/detection/camera/camera_windows.cpp @@ -72,13 +72,13 @@ extern "C" const char* ffDetectCamera(FF_A_UNUSED FFlist* result) { continue; } - IMFStreamDescriptor* FF_AUTO_RELEASE_COM_OBJECT sd = NULL; + IMFStreamDescriptor* FF_AUTO_RELEASE_COM_OBJECT sd = nullptr; BOOL selected; if (FAILED(pd->GetStreamDescriptorByIndex(0, &selected, &sd))) { continue; } - IMFMediaTypeHandler* FF_AUTO_RELEASE_COM_OBJECT handler = NULL; + IMFMediaTypeHandler* FF_AUTO_RELEASE_COM_OBJECT handler = nullptr; if (FAILED(sd->GetMediaTypeHandler(&handler))) { continue; } diff --git a/src/detection/sound/sound_windows.cpp b/src/detection/sound/sound_windows.cpp index c770f5c970..835eb63a2a 100644 --- a/src/detection/sound/sound_windows.cpp +++ b/src/detection/sound/sound_windows.cpp @@ -25,7 +25,7 @@ static const char* detectSoundDevice(FFlist* devices /* List of FFSoundDevice */ return "immDevice->GetId() failed"; } - IPropertyStore* FF_AUTO_RELEASE_COM_OBJECT immPropStore; + IPropertyStore* FF_AUTO_RELEASE_COM_OBJECT immPropStore = NULL; if (FAILED(immDevice->OpenPropertyStore(STGM_READ, &immPropStore))) { return "immDevice->OpenPropertyStore() failed"; } @@ -54,7 +54,7 @@ static const char* detectSoundDevice(FFlist* devices /* List of FFSoundDevice */ } } - IAudioEndpointVolume* FF_AUTO_RELEASE_COM_OBJECT immEndpointVolume; + IAudioEndpointVolume* FF_AUTO_RELEASE_COM_OBJECT immEndpointVolume = NULL; if (SUCCEEDED(immDevice->Activate(IID_IAudioEndpointVolume, CLSCTX_ALL, NULL, (void**) &immEndpointVolume))) { BOOL muted; if (FAILED(immEndpointVolume->GetMute(&muted)) || !muted) { From 8159f473ccb69db78c1de1a896fb3eeea0902925 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=9D=8E=E9=80=9A=E6=B4=B2?= Date: Wed, 6 May 2026 15:01:40 +0800 Subject: [PATCH 56/92] Common (Windows): allows COM be used in C sources --- CMakeLists.txt | 2 +- src/common/windows/{com.cpp => com.c} | 2 +- src/common/windows/{com.hpp => com.h} | 20 ++++++++++---------- src/detection/camera/camera_windows.cpp | 2 +- src/detection/media/media_windows.cpp | 3 +-- src/detection/sound/sound_windows.cpp | 2 +- 6 files changed, 15 insertions(+), 16 deletions(-) rename src/common/windows/{com.cpp => com.c} (98%) rename src/common/windows/{com.hpp => com.h} (52%) diff --git a/CMakeLists.txt b/CMakeLists.txt index 1f4d3b2f87..5b3ebf061f 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1033,7 +1033,7 @@ elseif(WIN32) src/common/impl/debug_windows.c src/common/impl/kmod_windows.c src/common/windows/getline.c - src/common/windows/com.cpp + src/common/windows/com.c src/common/windows/registry.c src/common/windows/unicode.c src/common/windows/variant.cpp diff --git a/src/common/windows/com.cpp b/src/common/windows/com.c similarity index 98% rename from src/common/windows/com.cpp rename to src/common/windows/com.c index 924baf4142..0eb566f8b4 100644 --- a/src/common/windows/com.cpp +++ b/src/common/windows/com.c @@ -1,4 +1,4 @@ -#include "com.hpp" +#include "com.h" #include diff --git a/src/common/windows/com.hpp b/src/common/windows/com.h similarity index 52% rename from src/common/windows/com.hpp rename to src/common/windows/com.h index eb6b106116..57b8c6d812 100644 --- a/src/common/windows/com.hpp +++ b/src/common/windows/com.h @@ -1,23 +1,23 @@ #pragma once -#ifdef __cplusplus - - #include "common/attributes.h" - #include +#include "common/attributes.h" +#include +#include // Initialize COM & WinRT const char* ffInitCom(void); static inline void ffReleaseComObject(void* ppUnknown) { + assert(ppUnknown); IUnknown* pUnknown = *(IUnknown**) ppUnknown; if (pUnknown) { +#ifdef __cplusplus pUnknown->Release(); +#else + pUnknown->lpVtbl->Release(pUnknown); +#endif + *(IUnknown**) ppUnknown = NULL; } } - #define FF_AUTO_RELEASE_COM_OBJECT FF_A_CLEANUP(ffReleaseComObject) - -#else - // Win32 COM headers requires C++ compiler - #error Must be included in C++ source file -#endif +#define FF_AUTO_RELEASE_COM_OBJECT FF_A_CLEANUP(ffReleaseComObject) diff --git a/src/detection/camera/camera_windows.cpp b/src/detection/camera/camera_windows.cpp index 48ee9dc455..087cc6d5fb 100644 --- a/src/detection/camera/camera_windows.cpp +++ b/src/detection/camera/camera_windows.cpp @@ -1,8 +1,8 @@ extern "C" { #include "camera.h" #include "common/library.h" +#include "common/windows/com.h" } -#include "common/windows/com.hpp" #include "common/windows/unicode.hpp" #include "common/windows/util.hpp" diff --git a/src/detection/media/media_windows.cpp b/src/detection/media/media_windows.cpp index 9a92440e38..10d9ee5d39 100644 --- a/src/detection/media/media_windows.cpp +++ b/src/detection/media/media_windows.cpp @@ -2,6 +2,7 @@ extern "C" { #include "media.h" #include "common/time.h" #include "common/windows/unicode.h" +#include "common/windows/com.h" } #if FF_HAVE_WINRT @@ -15,8 +16,6 @@ extern "C" { #include #include - #include "common/windows/com.hpp" - #define FF_BIND_FRONT(method, pobject) std::bind_front(&std::remove_cvref_t::method, (pobject)) using winrt::impl::abi_t; diff --git a/src/detection/sound/sound_windows.cpp b/src/detection/sound/sound_windows.cpp index 835eb63a2a..6d6307a5db 100644 --- a/src/detection/sound/sound_windows.cpp +++ b/src/detection/sound/sound_windows.cpp @@ -1,8 +1,8 @@ extern "C" { #include "sound.h" +#include "common/windows/com.h" } #include "common/windows/unicode.hpp" -#include "common/windows/com.hpp" #include "common/windows/variant.hpp" #include From 8c55c06fa7c1627cc3870f3c42bff36396c5a810 Mon Sep 17 00:00:00 2001 From: Carter Li Date: Wed, 6 May 2026 18:15:57 +0800 Subject: [PATCH 57/92] OS (Linux): detects bedrock only on Linux --- src/detection/os/os_linux.c | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/detection/os/os_linux.c b/src/detection/os/os_linux.c index b07bc3a2ec..f6223e5ca9 100644 --- a/src/detection/os/os_linux.c +++ b/src/detection/os/os_linux.c @@ -297,7 +297,7 @@ FF_A_UNUSED static bool detectFedoraVariant(FFOSResult* result) { return false; } -static bool detectBedrock(FFOSResult* os) { +FF_A_UNUSED static bool detectBedrock(FFOSResult* os) { const char* bedrockRestrict = getenv("BEDROCK_RESTRICT"); if (bedrockRestrict && bedrockRestrict[0] == '1') { return false; @@ -305,7 +305,7 @@ static bool detectBedrock(FFOSResult* os) { return parseOsRelease(FASTFETCH_TARGET_DIR_ROOT "/bedrock/strata/bedrock/etc/os-release", os); } -static void detectDeepinEnhancement(FFOSResult* result) { +FF_A_UNUSED static void detectDeepinEnhancement(FFOSResult* result) { if (ffStrbufContainC(&result->prettyName, '(')) { return; } @@ -342,9 +342,11 @@ static void detectOS(FFOSResult* os) { return; #endif +#ifdef __linux__ if (detectBedrock(os)) { return; } +#endif // Refer: https://gist.github.com/natefoo/814c5bf936922dad97ff From f5dfec6dcb0ea3048826b5e5f558f0ab4c9f4506 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=9D=8E=E9=80=9A=E6=B4=B2?= Date: Fri, 8 May 2026 14:24:59 +0800 Subject: [PATCH 58/92] Battery (Windows): tidy Co-authored-by: Copilot --- src/detection/battery/battery_windows.c | 20 +++++++++++++------- 1 file changed, 13 insertions(+), 7 deletions(-) diff --git a/src/detection/battery/battery_windows.c b/src/detection/battery/battery_windows.c index 777c93e35e..84f2a907cf 100644 --- a/src/detection/battery/battery_windows.c +++ b/src/detection/battery/battery_windows.c @@ -127,30 +127,36 @@ static void detectStaticData(FFlist* entries, FFlist* results) { FFBatteryWmiEntry* entry = getBatteryEntry(entries, results, data->Tag); FF_DEBUG("chemistry: %.4s", (const char*) &data->Chemistry); +#if __BIG_ENDIAN__ + #define htobe32(x) (x) // Should not happen on Windows, but just in case +#else + #define htobe32(x) __builtin_bswap32(x) +#endif switch (data->Chemistry) { - case 'cAbP': + case htobe32('PbAc'): ffStrbufSetStatic(&entry->result->technology, "Lead Acid"); break; - case 'NOIL': - case 'I-iL': + case htobe32('LION'): + case htobe32('Li-I'): ffStrbufSetStatic(&entry->result->technology, "Lithium Ion"); break; - case 'dCiN': + case htobe32('NiCd'): ffStrbufSetStatic(&entry->result->technology, "Nickel Cadmium"); break; - case 'HMiN': + case htobe32('NiMH'): ffStrbufSetStatic(&entry->result->technology, "Nickel Metal Hydride"); break; - case 'nZiN': + case htobe32('NiZn'): ffStrbufSetStatic(&entry->result->technology, "Nickel Zinc"); break; - case '\0MAR': + case htobe32('RAM\0'): ffStrbufSetStatic(&entry->result->technology, "Rechargeable Alkaline-Manganese"); break; default: ffStrbufSetStatic(&entry->result->technology, data->Technology ? "Rechargeable" : "Non Rechargeable"); break; } +#undef htobe32 const BATTERY_MANUFACTURE_DATE* manufactureDate = (const BATTERY_MANUFACTURE_DATE*) data->ManufactureDate; if (manufactureDate->Year > 0 && manufactureDate->Month >= 1 && manufactureDate->Month <= 12 && manufactureDate->Day >= 1 && manufactureDate->Day <= 31) { From 9a156662480620bbc817c553b3de7d8169f957ab Mon Sep 17 00:00:00 2001 From: Carter Li Date: Fri, 8 May 2026 15:53:09 +0800 Subject: [PATCH 59/92] Wifi (Linux): switches to netlink impl --- src/detection/wifi/wifi_linux.c | 1014 ++++++++++++++++++------------- 1 file changed, 605 insertions(+), 409 deletions(-) diff --git a/src/detection/wifi/wifi_linux.c b/src/detection/wifi/wifi_linux.c index df944ddf1e..63b0efdc65 100644 --- a/src/detection/wifi/wifi_linux.c +++ b/src/detection/wifi/wifi_linux.c @@ -1,468 +1,670 @@ #include "wifi.h" -#include "common/dbus.h" #include "common/io.h" -#include "common/processing.h" -#include "common/properties.h" -#include "common/stringUtils.h" #include "common/debug.h" +#include +#include +#include +#include #include -#ifdef FF_HAVE_DBUS -// https://people.freedesktop.org/~lkundrak/nm-docs/nm-dbus-types.html#NM80211ApFlags -typedef enum { - NM_802_11_AP_FLAGS_NONE = 0x00000000, - NM_802_11_AP_FLAGS_PRIVACY = 0x00000001, - NM_802_11_AP_FLAGS_WPS = 0x00000002, - NM_802_11_AP_FLAGS_WPS_PBC = 0x00000004, - NM_802_11_AP_FLAGS_WPS_PIN = 0x00000008, -} NM80211ApFlags; - -// https://people.freedesktop.org/~lkundrak/nm-docs/nm-dbus-types.html#NM80211ApSecurityFlags -typedef enum { - NM_802_11_AP_SEC_NONE = 0x00000000, - NM_802_11_AP_SEC_PAIR_WEP40 = 0x00000001, - NM_802_11_AP_SEC_PAIR_WEP104 = 0x00000002, - NM_802_11_AP_SEC_PAIR_TKIP = 0x00000004, - NM_802_11_AP_SEC_PAIR_CCMP = 0x00000008, - NM_802_11_AP_SEC_GROUP_WEP40 = 0x00000010, - NM_802_11_AP_SEC_GROUP_WEP104 = 0x00000020, - NM_802_11_AP_SEC_GROUP_TKIP = 0x00000040, - NM_802_11_AP_SEC_GROUP_CCMP = 0x00000080, - NM_802_11_AP_SEC_KEY_MGMT_PSK = 0x00000100, - NM_802_11_AP_SEC_KEY_MGMT_802_1X = 0x00000200, - NM_802_11_AP_SEC_KEY_MGMT_SAE = 0x00000400, - NM_802_11_AP_SEC_KEY_MGMT_OWE = 0x00000800, - NM_802_11_AP_SEC_KEY_MGMT_OWE_TM = 0x00001000, - NM_802_11_AP_SEC_KEY_MGMT_EAP_SUITE_B_192 = 0x00002000, -} NM80211ApSecurityFlags; - - #define FF_DBUS_ITER_CONTINUE(dbus, iterator) \ - { \ - if (!(dbus).lib->ffdbus_message_iter_next(iterator)) \ - break; \ - continue; \ - } - -static const char* detectWifiWithNm(FFWifiResult* item, FFstrbuf* buffer) { - FF_DEBUG("Starting NetworkManager wifi detection for interface %s", item->inf.description.chars); - FF_DBUS_AUTO_DESTROY_DATA FFDBusData dbus = {}; - const char* error = ffDBusLoadData(DBUS_BUS_SYSTEM, &dbus); - if (error) { - FF_DEBUG("Failed to load DBus data: %s", error); - return error; - } - - { - FF_DEBUG("Getting device by IP interface name"); - DBusMessage* device = ffDBusGetMethodReply(&dbus, "org.freedesktop.NetworkManager", "/org/freedesktop/NetworkManager", "org.freedesktop.NetworkManager", "GetDeviceByIpIface", item->inf.description.chars, NULL); - if (!device) { - FF_DEBUG("GetDeviceByIpIface failed for interface %s", item->inf.description.chars); - return "Failed to call GetDeviceByIpIface"; - } +// Silence warning of `NLA_HDRLEN` and `NLA_ALIGN` +#pragma GCC diagnostic ignored "-Wsign-conversion" + +typedef struct FFWifiNlContext { + int sockFd; + uint16_t nl80211FamilyId; + uint32_t portId; + uint32_t seq; +} FFWifiNlContext; + +typedef struct FFWifiSecurityFlags { + bool privacy : 1; + bool wep : 1; + bool wpa : 1; + bool wpa2 : 1; + bool wpa3 : 1; + bool owe : 1; + bool eap : 1; +} FFWifiSecurityFlags; + +static inline double rssiToSignalQuality(int rssi) +{ + return (double) (rssi >= -50 ? 100 : rssi <= -100 ? 0 : (rssi + 100) * 2); +} - ffStrbufClear(buffer); - DBusMessageIter rootIter; - if (!dbus.lib->ffdbus_message_iter_init(device, &rootIter) || !ffDBusGetString(&dbus, &rootIter, buffer)) { - FF_DEBUG("Failed to initialize message iterator or get device path"); - dbus.lib->ffdbus_message_unref(device); - return "Failed to get device path"; - } - FF_DEBUG("Got device path: %s", buffer->chars); - dbus.lib->ffdbus_message_unref(device); +static inline uint32_t ffWifiGetNetlinkPortId(int sockFd) { + struct sockaddr_nl addr = {}; + socklen_t addrLen = sizeof(addr); + if (getsockname(sockFd, (struct sockaddr*) &addr, &addrLen) < 0) { + FF_DEBUG("Failed to query netlink socket address (use PID instead): %s", strerror(errno)); + return instance.state.platform.pid; } - if (item->conn.txRate == -DBL_MAX) { - FF_DEBUG("Getting bitrate from NetworkManager"); - uint32_t bitrate; - if (ffDBusGetPropertyUint(&dbus, "org.freedesktop.NetworkManager", buffer->chars, "org.freedesktop.NetworkManager.Device.Wireless", "Bitrate", &bitrate)) { - item->conn.txRate = bitrate / 1000.; - FF_DEBUG("Got bitrate: %.2f Mbps", item->conn.txRate); - } else { - FF_DEBUG("Failed to get bitrate"); - } - } + return addr.nl_pid; +} - FF_DEBUG("Getting active access point path"); - FF_STRBUF_AUTO_DESTROY apPath = ffStrbufCreate(); - if (!ffDBusGetPropertyString(&dbus, "org.freedesktop.NetworkManager", buffer->chars, "org.freedesktop.NetworkManager.Device.Wireless", "ActiveAccessPoint", &apPath)) { - FF_DEBUG("Failed to get active access point path"); - return "Failed to get active access point path"; - } - FF_DEBUG("Got access point path: %s", apPath.chars); +static inline bool ffWifiNlAttrOk(const struct nlattr* attr, size_t remaining) { + return remaining >= sizeof(*attr) && + attr->nla_len >= sizeof(*attr) && + attr->nla_len <= remaining; +} - if (!item->conn.status.length) { - ffStrbufSetStatic(&item->conn.status, "connected"); - FF_DEBUG("Setting connection status to 'connected'"); +static const struct nlattr* ffWifiNlAttrNext(const struct nlattr* attr, size_t* remaining) { + size_t alignedLen = NLA_ALIGN(attr->nla_len); + if (alignedLen > *remaining) { + *remaining = 0; + return NULL; } - FF_DEBUG("Getting access point properties"); - DBusMessage* reply = ffDBusGetAllProperties(&dbus, "org.freedesktop.NetworkManager", apPath.chars, "org.freedesktop.NetworkManager.AccessPoint"); - if (reply == NULL) { - FF_DEBUG("Failed to get access point properties"); - return "Failed to get access point properties"; + *remaining -= alignedLen; + return (const struct nlattr*) ((const char*) attr + alignedLen); +} + +static inline size_t ffWifiNlAttrPayload(const struct nlattr* attr) { + return attr->nla_len > NLA_HDRLEN ? attr->nla_len - NLA_HDRLEN : 0; +} + +static inline const void* ffWifiNlAttrData(const struct nlattr* attr) { + return (const uint8_t*) attr + NLA_HDRLEN; +} + +static bool ffWifiNlAppendAttr(struct nlmsghdr* nlh, size_t maxLen, uint16_t type, const void* data, uint16_t dataLen) { + size_t offset = NLMSG_ALIGN(nlh->nlmsg_len); + size_t attrLen = NLA_HDRLEN + dataLen; + size_t alignedLen = NLA_ALIGN(attrLen); + size_t newLen = offset + alignedLen; + if (newLen > maxLen || attrLen > UINT16_MAX || newLen > UINT32_MAX) { + return false; } - DBusMessageIter rootIterator; - if (!dbus.lib->ffdbus_message_iter_init(reply, &rootIterator) && - dbus.lib->ffdbus_message_iter_get_arg_type(&rootIterator) != DBUS_TYPE_ARRAY) { - FF_DEBUG("Invalid type of access point properties"); - dbus.lib->ffdbus_message_unref(reply); - return "Invalid type of access point properties"; + struct nlattr* attr = (struct nlattr*) ((char*) nlh + offset); + attr->nla_type = type; + attr->nla_len = (uint16_t) attrLen; + memcpy((char*) attr + NLA_HDRLEN, data, dataLen); + memset((char*) attr + attrLen, 0, alignedLen - attrLen); + nlh->nlmsg_len = (uint32_t) newLen; + return true; +} + +static bool ffWifiNlGetFamilyId(FFWifiNlContext* ctx) { + struct { + struct nlmsghdr nlh; + struct genlmsghdr genl; + char attrs[64]; + } req = { + .nlh = { + .nlmsg_len = NLMSG_LENGTH(sizeof(struct genlmsghdr)), + .nlmsg_type = GENL_ID_CTRL, + .nlmsg_flags = NLM_F_REQUEST | NLM_F_ACK, + .nlmsg_seq = ++ctx->seq, + .nlmsg_pid = ctx->portId, + }, + .genl = { + .cmd = CTRL_CMD_GETFAMILY, + .version = 1, + }, + }; + + if (!ffWifiNlAppendAttr(&req.nlh, sizeof(req), CTRL_ATTR_FAMILY_NAME, "nl80211", sizeof("nl80211"))) { + FF_DEBUG("Failed to append CTRL_ATTR_FAMILY_NAME attribute"); + return false; } - DBusMessageIter arrayIterator; - dbus.lib->ffdbus_message_iter_recurse(&rootIterator, &arrayIterator); + struct sockaddr_nl addr = { + .nl_family = AF_NETLINK, + }; - NM80211ApFlags flags; - NM80211ApSecurityFlags wpaFlags, rsnFlags; - int flagCount = 0; + ssize_t sent = sendto(ctx->sockFd, &req, req.nlh.nlmsg_len, 0, (struct sockaddr*) &addr, sizeof(addr)); + if (sent != (ssize_t) req.nlh.nlmsg_len) { + FF_DEBUG("Failed to send nl80211 family request: sent=%zd expected=%u", sent, req.nlh.nlmsg_len); + return false; + } - FF_DEBUG("Parsing access point properties"); + uint8_t buffer[8192]; while (true) { - if (dbus.lib->ffdbus_message_iter_get_arg_type(&arrayIterator) != DBUS_TYPE_DICT_ENTRY) { - FF_DBUS_ITER_CONTINUE(dbus, &arrayIterator) + ssize_t received = recvfrom(ctx->sockFd, buffer, sizeof(buffer), 0, NULL, NULL); + if (received < 0) { + FF_DEBUG("Failed to receive nl80211 family reply: %s", strerror(errno)); + return false; } - DBusMessageIter dictIterator; - dbus.lib->ffdbus_message_iter_recurse(&arrayIterator, &dictIterator); - - const char* key; - dbus.lib->ffdbus_message_iter_get_basic(&dictIterator, &key); - - dbus.lib->ffdbus_message_iter_next(&dictIterator); + for (const struct nlmsghdr* nlh = (const struct nlmsghdr*) buffer; + NLMSG_OK(nlh, received); + nlh = NLMSG_NEXT(nlh, received)) { + if (nlh->nlmsg_seq != req.nlh.nlmsg_seq) { + continue; + } - if (ffStrEquals(key, "Ssid")) { - if (!item->conn.ssid.length) { - FF_DEBUG("Found SSID property"); - ffDBusGetString(&dbus, &dictIterator, &item->conn.ssid); - FF_DEBUG("SSID: %s", item->conn.ssid.chars); + if (nlh->nlmsg_type == NLMSG_ERROR) { + const struct nlmsgerr* err = (const struct nlmsgerr*) NLMSG_DATA(nlh); + if (err->error != 0) { + FF_DEBUG("nl80211 family query failed: %s", strerror(-err->error)); + return false; + } + continue; } - } else if (ffStrEquals(key, "HwAddress")) { - if (!item->conn.bssid.length) { - FF_DEBUG("Found HwAddress property"); - ffDBusGetString(&dbus, &dictIterator, &item->conn.bssid); - FF_DEBUG("BSSID: %s", item->conn.bssid.chars); + + if (nlh->nlmsg_type != GENL_ID_CTRL) { + continue; } - } else if (ffStrEquals(key, "Strength")) { - if (item->conn.signalQuality == -DBL_MAX) { - FF_DEBUG("Found Strength property"); - uint32_t strengthPercent; - if (ffDBusGetUint(&dbus, &dictIterator, &strengthPercent)) { - item->conn.signalQuality = strengthPercent; - FF_DEBUG("Signal quality: %u%%", strengthPercent); - } + + const struct genlmsghdr* genl = (const struct genlmsghdr*) NLMSG_DATA(nlh); + if (genl->cmd != CTRL_CMD_NEWFAMILY) { + continue; } - } else if (ffStrEquals(key, "Frequency")) { - if (item->conn.frequency == 0) { - FF_DEBUG("Found Frequency property"); - uint32_t frequency; - if (ffDBusGetUint(&dbus, &dictIterator, &frequency)) { - item->conn.frequency = (uint16_t) frequency; - FF_DEBUG("Frequency: %u MHz", item->conn.frequency); - if (item->conn.channel == 0) { - item->conn.channel = ffWifiFreqToChannel(item->conn.frequency); - FF_DEBUG("Calculated channel: %u", item->conn.channel); - } + + size_t attrRemaining = nlh->nlmsg_len - NLMSG_HDRLEN - GENL_HDRLEN; + for (const struct nlattr* attr = (const struct nlattr*) ((const char*) genl + GENL_HDRLEN); + ffWifiNlAttrOk(attr, attrRemaining); + attr = ffWifiNlAttrNext(attr, &attrRemaining)) { + if ((attr->nla_type & NLA_TYPE_MASK) != CTRL_ATTR_FAMILY_ID || ffWifiNlAttrPayload(attr) < sizeof(uint16_t)) { + continue; } + + ctx->nl80211FamilyId = *(const uint16_t*) ffWifiNlAttrData(attr); + return true; } - } else if ((ffStrEquals(key, "Flags") && ffDBusGetUint(&dbus, &dictIterator, &flags)) || - (ffStrEquals(key, "WpaFlags") && ffDBusGetUint(&dbus, &dictIterator, &wpaFlags)) || - (ffStrEquals(key, "RsnFlags") && ffDBusGetUint(&dbus, &dictIterator, &rsnFlags))) { - ++flagCount; } + } +} - FF_DBUS_ITER_CONTINUE(dbus, &arrayIterator) +static bool ffWifiNlInit(FFWifiNlContext* ctx) { + FF_AUTO_CLOSE_FD int _ = ctx->sockFd = socket(AF_NETLINK, SOCK_RAW | SOCK_CLOEXEC, NETLINK_GENERIC); + if (ctx->sockFd < 0) { + FF_DEBUG("Failed to create generic netlink socket: %s", strerror(errno)); + return false; } - if (flagCount == 3) { - FF_DEBUG("Determining security type from flags (Flags: 0x%08x, WPA: 0x%08x, RSN: 0x%08x)", - flags, - wpaFlags, - rsnFlags); - if ((flags & NM_802_11_AP_FLAGS_PRIVACY) && (wpaFlags == NM_802_11_AP_SEC_NONE) && (rsnFlags == NM_802_11_AP_SEC_NONE)) { - ffStrbufAppendS(&item->conn.security, "WEP/"); - FF_DEBUG("Adding security: WEP"); - } - if (wpaFlags != NM_802_11_AP_SEC_NONE) { - ffStrbufAppendS(&item->conn.security, "WPA/"); - FF_DEBUG("Adding security: WPA"); - } - if ((rsnFlags & NM_802_11_AP_SEC_KEY_MGMT_PSK) || (rsnFlags & NM_802_11_AP_SEC_KEY_MGMT_802_1X)) { - ffStrbufAppendS(&item->conn.security, "WPA2/"); - FF_DEBUG("Adding security: WPA2"); - } - if (rsnFlags & NM_802_11_AP_SEC_KEY_MGMT_SAE) { - ffStrbufAppendS(&item->conn.security, "WPA3/"); - FF_DEBUG("Adding security: WPA3"); - } - if ((rsnFlags & NM_802_11_AP_SEC_KEY_MGMT_OWE) || (rsnFlags & NM_802_11_AP_SEC_KEY_MGMT_OWE_TM)) { - ffStrbufAppendS(&item->conn.security, "OWE/"); - FF_DEBUG("Adding security: OWE"); - } - if ((wpaFlags & NM_802_11_AP_SEC_KEY_MGMT_802_1X) || (rsnFlags & NM_802_11_AP_SEC_KEY_MGMT_802_1X)) { - ffStrbufAppendS(&item->conn.security, "802.1X/"); - FF_DEBUG("Adding security: 802.1X"); + struct sockaddr_nl addr = { + .nl_family = AF_NETLINK, + }; + + if (bind(ctx->sockFd, (struct sockaddr*) &addr, sizeof(addr)) < 0) { + FF_DEBUG("Failed to bind generic netlink socket: %s", strerror(errno)); + return false; + } + + if (setsockopt( + ctx->sockFd, + SOL_SOCKET, + SO_RCVTIMEO, + &(struct timeval) { .tv_sec = 0, .tv_usec = 250000 }, + sizeof(struct timeval)) < 0) { + FF_DEBUG("Failed to set netlink receive timeout: %s", strerror(errno)); + return false; + } + + ctx->portId = ffWifiGetNetlinkPortId(ctx->sockFd); + if (!ffWifiNlGetFamilyId(ctx)) { + return false; + } + + _ = -1; // We are ok now + return true; +} + +static double ffWifiParseBitrateFromRateInfo(const struct nlattr* rateAttr, FFstrbuf* protocol) { + double rate = -DBL_MAX; + size_t remaining = ffWifiNlAttrPayload(rateAttr); + + for (const struct nlattr* info = (const struct nlattr*) ffWifiNlAttrData(rateAttr); + ffWifiNlAttrOk(info, remaining); + info = ffWifiNlAttrNext(info, &remaining)) { + uint16_t type = (uint16_t) (info->nla_type & NLA_TYPE_MASK); + size_t payload = ffWifiNlAttrPayload(info); + + switch (type) { + case 30 /* NL80211_RATE_INFO_UHR_MCS*/: + ffStrbufSetStatic(protocol, "802.11bn (Wi-Fi 8)"); + break; + case 23 /*NL80211_RATE_INFO_S1G_MCS*/: + ffStrbufSetStatic(protocol, "802.11ah (Wi-Fi HaLow)"); + break; + case 19 /* NL80211_RATE_INFO_EHT_MCS */: + ffStrbufSetStatic(protocol, "802.11be (Wi-Fi 7)"); + break; + case 13 /* NL80211_RATE_INFO_HE_MCS */: + ffStrbufSetStatic(protocol, "802.11ax (Wi-Fi 6)"); + break; + case NL80211_RATE_INFO_VHT_MCS: + ffStrbufSetStatic(protocol, "802.11ac (Wi-Fi 5)"); + break; + case NL80211_RATE_INFO_MCS: + ffStrbufSetStatic(protocol, "802.11n (Wi-Fi 4)"); + break; + case NL80211_RATE_INFO_BITRATE32: + if (payload >= sizeof(uint32_t)) { + rate = *(uint32_t*) ffWifiNlAttrData(info) / 10.0; + } + break; + case NL80211_RATE_INFO_BITRATE: + if (payload >= sizeof(uint16_t) && rate == -DBL_MAX) { + rate = *(uint16_t*) ffWifiNlAttrData(info) / 10.0; + } + break; } - if (!item->conn.security.length) { - ffStrbufAppendS(&item->conn.security, "Insecure"); - FF_DEBUG("No security detected, marking as 'Insecure'"); + } + + return rate; +} + +static void ffWifiApplySecurityFlags(FFWifiResult* item, const FFWifiSecurityFlags* sec) { + ffStrbufClear(&item->conn.security); + + if (sec->wep) { + ffStrbufAppendS(&item->conn.security, "WEP/"); + } + if (sec->wpa) { + ffStrbufAppendS(&item->conn.security, "WPA/"); + } + if (sec->wpa2) { + ffStrbufAppendS(&item->conn.security, "WPA2/"); + } + if (sec->wpa3) { + ffStrbufAppendS(&item->conn.security, "WPA3/"); + } + if (sec->owe) { + ffStrbufAppendS(&item->conn.security, "OWE/"); + } + if (sec->eap) { + ffStrbufAppendS(&item->conn.security, "802.1X/"); + } + + if (!item->conn.security.length) { + if (sec->privacy) { + ffStrbufSetStatic(&item->conn.security, "WEP"); } else { - ffStrbufTrimRight(&item->conn.security, '/'); - FF_DEBUG("Final security string: %s", item->conn.security.chars); + ffStrbufSetStatic(&item->conn.security, "Insecure"); } + } else { + ffStrbufTrimRight(&item->conn.security, '/'); + } +} + +static void ffWifiParseRsnIe(const uint8_t* ie, size_t len, FFWifiSecurityFlags* sec) { + if (len < 8) { + return; + } + + sec->wpa2 = true; + size_t pos = 0; - if (wpaFlags & NM_802_11_AP_SEC_PAIR_TKIP || rsnFlags & NM_802_11_AP_SEC_PAIR_TKIP) { - FF_DEBUG("Detected TKIP encryption"); + pos += 2; + if (pos + 4 > len) { + return; + } + pos += 4; + + if (pos + 2 > len) { + return; + } + uint16_t pairwiseCount = *(uint16_t*) (ie + pos); + pos += 2; + + size_t pairwiseLen = (size_t) pairwiseCount * 4; + if (pos + pairwiseLen > len) { + return; + } + pos += pairwiseLen; + + if (pos + 2 > len) { + return; + } + uint16_t akmCount = *(uint16_t*) (ie + pos); + pos += 2; + + for (uint16_t i = 0; i < akmCount && pos + 4 <= len; ++i, pos += 4) { + const uint8_t* akm = ie + pos; + if (akm[0] != 0x00 || akm[1] != 0x0f || akm[2] != 0xac) { + continue; } - if (wpaFlags & NM_802_11_AP_SEC_PAIR_CCMP || rsnFlags & NM_802_11_AP_SEC_PAIR_CCMP) { - FF_DEBUG("Detected CCMP/AES encryption"); + + switch (akm[3]) { + case 1: + case 5: + case 11: + case 12: + sec->eap = true; + break; + case 8: + sec->wpa3 = true; + break; + case 18: + sec->owe = true; + break; + default: + break; } } - FF_DEBUG("NetworkManager wifi detection completed successfully"); - dbus.lib->ffdbus_message_unref(reply); - return NULL; + if (sec->owe) { + sec->wpa2 = false; + } } -#endif // FF_HAVE_DBUS -static const char* detectWifiWithIw(FFWifiResult* item, FFstrbuf* buffer) { - FF_DEBUG("Starting iw wifi detection for interface %s", item->inf.description.chars); - const char* error = NULL; - FF_STRBUF_AUTO_DESTROY output = ffStrbufCreate(); - FF_DEBUG("Executing 'iw dev %s link'", item->inf.description.chars); - if ((error = ffProcessAppendStdOut(&output, (char* const[]) { "iw", "dev", item->inf.description.chars, "link", NULL }))) { - FF_DEBUG("iw command execution failed: %s", error); - return error; +static void ffWifiParseWpaVendorIe(const uint8_t* ie, size_t len, FFWifiSecurityFlags* sec) { + if (len < 8) { + return; } - if (output.length == 0) { - FF_DEBUG("iw command output is empty"); - return "iw command execution failed"; + if (!(ie[0] == 0x00 && ie[1] == 0x50 && ie[2] == 0xf2 && ie[3] == 0x01)) { + return; } - if (!ffParsePropLines(output.chars, "Connected to ", &item->conn.bssid)) { - FF_DEBUG("Not connected to any access point"); - ffStrbufAppendS(&item->conn.status, "disconnected"); - return NULL; + sec->wpa = true; + + size_t pos = 4; + if (pos + 2 > len) { + return; + } + pos += 2; + + if (pos + 4 > len) { + return; } + pos += 4; - FF_DEBUG("Connected to an access point"); - ffStrbufAppendS(&item->conn.status, "connected"); - ffStrbufSubstrBeforeFirstC(&item->conn.bssid, ' '); - ffStrbufUpperCase(&item->conn.bssid); - FF_DEBUG("BSSID: %s", item->conn.bssid.chars); + if (pos + 2 > len) { + return; + } + uint16_t pairwiseCount = *(uint16_t*) (ie + pos); + pos += 2 + (size_t) pairwiseCount * 4; + + if (pos + 2 > len) { + return; + } + uint16_t akmCount = *(uint16_t*) (ie + pos); + pos += 2; - if (ffParsePropLines(output.chars, "SSID: ", &item->conn.ssid)) { - FF_DEBUG("SSID: %s", item->conn.ssid.chars); - if (ffStrbufDecodeHexEscapeSequences(&item->conn.ssid)) { - FF_DEBUG("Decoded SSID: %s", item->conn.ssid.chars); + for (uint16_t i = 0; i < akmCount && pos + 4 <= len; ++i, pos += 4) { + const uint8_t* akm = ie + pos; + if (!(akm[0] == 0x00 && akm[1] == 0x50 && akm[2] == 0xf2)) { + continue; + } + if (akm[3] == 1) { + sec->eap = true; } - } else { - FF_DEBUG("SSID not found in iw output"); } +} - ffStrbufClear(buffer); - if (ffParsePropLines(output.chars, "signal: ", buffer)) { - int level = (int) ffStrbufToSInt(buffer, INT_MAX); - if (level != INT_MAX) { - item->conn.signalQuality = level >= -50 ? 100 : level <= -100 ? 0 - : (level + 100) * 2; - FF_DEBUG("Signal level: %d dBm, quality: %.0f%%", level, item->conn.signalQuality); +static void ffWifiParseInformationElements(const uint8_t* ies, size_t length, FFWifiResult* item, FFWifiSecurityFlags* sec) { + size_t pos = 0; + while (pos + 2 <= length) { + uint8_t id = ies[pos]; + uint8_t len = ies[pos + 1]; + pos += 2; + if (pos + len > length) { + break; } + + const uint8_t* ie = ies + pos; + if (id == 0 && !item->conn.ssid.length) { + ffStrbufSetNS(&item->conn.ssid, len, (const char*) ie); + } else if (id == 48) { + ffWifiParseRsnIe(ie, len, sec); + } else if (id == 221) { + ffWifiParseWpaVendorIe(ie, len, sec); + } + + pos += len; } +} + +static bool ffWifiParseBssAttr(const struct nlattr* bssAttr, FFWifiResult* item, bool* associated) { + FFWifiSecurityFlags sec = {}; + size_t remaining = ffWifiNlAttrPayload(bssAttr); + + for (const struct nlattr* attr = (const struct nlattr*) ffWifiNlAttrData(bssAttr); + ffWifiNlAttrOk(attr, remaining); + attr = ffWifiNlAttrNext(attr, &remaining)) { + uint16_t type = (uint16_t) (attr->nla_type & NLA_TYPE_MASK); + size_t payload = ffWifiNlAttrPayload(attr); - ffStrbufClear(buffer); - if (ffParsePropLines(output.chars, "rx bitrate: ", buffer)) { - item->conn.rxRate = ffStrbufToDouble(buffer, -DBL_MAX); - FF_DEBUG("RX bitrate: %.2f Mbps", item->conn.rxRate); - } - - ffStrbufClear(buffer); - if (ffParsePropLines(output.chars, "tx bitrate: ", buffer)) { - item->conn.txRate = ffStrbufToDouble(buffer, -DBL_MAX); - FF_DEBUG("TX bitrate: %.2f Mbps (raw: %s)", item->conn.txRate, buffer->chars); - - if (ffStrbufContainS(buffer, " EHT-MCS ")) { - ffStrbufSetStatic(&item->conn.protocol, "802.11be (Wi-Fi 7)"); - FF_DEBUG("Detected protocol: Wi-Fi 7"); - } else if (ffStrbufContainS(buffer, " HE-MCS ")) { - ffStrbufSetStatic(&item->conn.protocol, "802.11ax (Wi-Fi 6)"); - FF_DEBUG("Detected protocol: Wi-Fi 6"); - } else if (ffStrbufContainS(buffer, " VHT-MCS ")) { - ffStrbufSetStatic(&item->conn.protocol, "802.11ac (Wi-Fi 5)"); - FF_DEBUG("Detected protocol: Wi-Fi 5"); - } else if (ffStrbufContainS(buffer, " MCS ")) { - ffStrbufSetStatic(&item->conn.protocol, "802.11n (Wi-Fi 4)"); - FF_DEBUG("Detected protocol: Wi-Fi 4"); + if (type == NL80211_BSS_STATUS && payload >= sizeof(uint32_t)) { + if (*(uint32_t*) ffWifiNlAttrData(attr) == NL80211_BSS_STATUS_ASSOCIATED) { + *associated = true; + } + } else if (type == NL80211_BSS_BSSID && payload >= 6) { + const uint8_t* mac = (const uint8_t*) ffWifiNlAttrData(attr); + ffStrbufSetF(&item->conn.bssid, "%02X:%02X:%02X:%02X:%02X:%02X", mac[0], mac[1], mac[2], mac[3], mac[4], mac[5]); + } else if (type == NL80211_BSS_FREQUENCY && payload >= sizeof(uint32_t)) { + item->conn.frequency = (uint16_t) *(uint32_t*) ffWifiNlAttrData(attr); + item->conn.channel = ffWifiFreqToChannel(item->conn.frequency); + } else if (type == NL80211_BSS_SIGNAL_MBM && payload >= sizeof(int32_t)) { + int rssi = *(int32_t*) ffWifiNlAttrData(attr) / 100; + item->conn.signalQuality = rssiToSignalQuality(rssi); + } else if (type == NL80211_BSS_CAPABILITY && payload >= sizeof(uint16_t)) { + uint16_t capability = *(uint16_t*) ffWifiNlAttrData(attr); + sec.privacy = (capability & (1u << 4u)) != 0; + } else if (type == NL80211_BSS_INFORMATION_ELEMENTS || type == NL80211_BSS_BEACON_IES) { + ffWifiParseInformationElements((const uint8_t*) ffWifiNlAttrData(attr), payload, item, &sec); } } - ffStrbufClear(buffer); - if (ffParsePropLines(output.chars, "freq: ", buffer)) { - item->conn.frequency = (uint16_t) ffStrbufToUInt(buffer, 0); - item->conn.channel = ffWifiFreqToChannel(item->conn.frequency); - FF_DEBUG("Frequency: %u MHz, Channel: %u", item->conn.frequency, item->conn.channel); + if (!*associated) { + return false; } - FF_DEBUG("iw wifi detection completed successfully"); - return NULL; + ffWifiApplySecurityFlags(item, &sec); + return true; } -#if FF_HAVE_LINUX_WIRELESS - #include - #include - #include - #include - -static const char* detectWifiWithIoctls(FFWifiResult* item) { - FF_DEBUG("Starting ioctl wifi detection for interface %s", item->inf.description.chars); - FF_AUTO_CLOSE_FD int sock = socket(AF_INET, SOCK_DGRAM | SOCK_CLOEXEC, 0); - if (sock < 0) { - FF_DEBUG("Failed to create socket: %s", strerror(errno)); - return "socket() failed"; - } - - struct iwreq iwr; - ffStrCopy(iwr.ifr_name, item->inf.description.chars, IFNAMSIZ); - - // Get SSID - FF_DEBUG("Getting SSID via ioctl"); - ffStrbufEnsureFree(&item->conn.ssid, IW_ESSID_MAX_SIZE); - iwr.u.essid.pointer = (caddr_t) item->conn.ssid.chars; - iwr.u.essid.length = IW_ESSID_MAX_SIZE + 1; - iwr.u.essid.flags = 0; - if (ioctl(sock, SIOCGIWESSID, &iwr) >= 0) { - ffStrbufSetStatic(&item->conn.status, "connected"); - ffStrbufRecalculateLength(&item->conn.ssid); - FF_DEBUG("SSID: %s", item->conn.ssid.chars); - } else { - FF_DEBUG("Failed to get SSID via ioctl: %s", strerror(errno)); +static bool ffWifiFetchScanInfo(FFWifiNlContext* ctx, FFWifiResult* item, uint32_t ifIndex) { + struct { + struct nlmsghdr nlh; + struct genlmsghdr genl; + char attrs[32]; + } req = { + .nlh = { + .nlmsg_len = NLMSG_LENGTH(sizeof(struct genlmsghdr)), + .nlmsg_type = ctx->nl80211FamilyId, + .nlmsg_flags = NLM_F_REQUEST | NLM_F_DUMP | NLM_F_ACK, + .nlmsg_seq = ++ctx->seq, + .nlmsg_pid = ctx->portId, + }, + .genl = { + .cmd = NL80211_CMD_GET_SCAN, + .version = 0, + }, + }; + + if (!ffWifiNlAppendAttr(&req.nlh, sizeof(req), NL80211_ATTR_IFINDEX, &ifIndex, sizeof(ifIndex))) { + FF_DEBUG("Failed to build nl80211 scan request"); + return false; } - // Get protocol name - FF_DEBUG("Getting protocol name via ioctl"); - if (ioctl(sock, SIOCGIWNAME, &iwr) >= 0 && !ffStrEqualsIgnCase(iwr.u.name, "IEEE 802.11")) { - if (ffStrStartsWithIgnCase(iwr.u.name, "IEEE ")) { - ffStrbufSetS(&item->conn.protocol, iwr.u.name + strlen("IEEE ")); - } else { - ffStrbufSetS(&item->conn.protocol, iwr.u.name); + struct sockaddr_nl addr = { + .nl_family = AF_NETLINK, + }; + + ssize_t sent = sendto(ctx->sockFd, &req, req.nlh.nlmsg_len, 0, (struct sockaddr*) &addr, sizeof(addr)); + if (sent != (ssize_t) req.nlh.nlmsg_len) { + FF_DEBUG("Failed to send nl80211 scan request"); + return false; + } + + uint8_t buffer[1024 * 16]; + bool associated = false; + while (true) { + ssize_t received = recvfrom(ctx->sockFd, buffer, sizeof(buffer), 0, NULL, NULL); + if (received < 0) { + FF_DEBUG("Failed to receive nl80211 scan reply: %s", strerror(errno)); + return false; + } + + for (const struct nlmsghdr* nlh = (const struct nlmsghdr*) buffer; + NLMSG_OK(nlh, received); + nlh = NLMSG_NEXT(nlh, received)) { + if (nlh->nlmsg_seq != req.nlh.nlmsg_seq) { + continue; + } + + if (nlh->nlmsg_type == NLMSG_DONE) { + return associated; + } + + if (nlh->nlmsg_type == NLMSG_ERROR) { + const struct nlmsgerr* err = (const struct nlmsgerr*) NLMSG_DATA(nlh); + if (err->error == 0) { + continue; + } + FF_DEBUG("nl80211 scan request failed: %s", strerror(-err->error)); + return associated; + } + + if (nlh->nlmsg_type != ctx->nl80211FamilyId) { + continue; + } + + const struct genlmsghdr* genl = (const struct genlmsghdr*) NLMSG_DATA(nlh); + size_t attrRemaining = nlh->nlmsg_len - NLMSG_HDRLEN - GENL_HDRLEN; + for (const struct nlattr* attr = (const struct nlattr*) ((const char*) genl + GENL_HDRLEN); + ffWifiNlAttrOk(attr, attrRemaining); + attr = ffWifiNlAttrNext(attr, &attrRemaining)) { + if ((attr->nla_type & NLA_TYPE_MASK) != NL80211_ATTR_BSS) { + continue; + } + + bool thisAssociated = false; + if (ffWifiParseBssAttr(attr, item, &thisAssociated) && thisAssociated) { + associated = true; + } + } } - FF_DEBUG("Protocol: %s", item->conn.protocol.chars); - } else { - FF_DEBUG("Failed to get protocol name via ioctl: %s", strerror(errno)); } +} - // Get BSSID - FF_DEBUG("Getting BSSID via ioctl"); - if (ioctl(sock, SIOCGIWAP, &iwr) >= 0) { - for (int i = 0; i < 6; ++i) { - ffStrbufAppendF(&item->conn.bssid, "%.2X:", (uint8_t) iwr.u.ap_addr.sa_data[i]); +static void ffWifiParseStationInfo(const struct nlattr* staInfoAttr, FFWifiResult* item) { + size_t remaining = ffWifiNlAttrPayload(staInfoAttr); + for (const struct nlattr* attr = (const struct nlattr*) ffWifiNlAttrData(staInfoAttr); + ffWifiNlAttrOk(attr, remaining); + attr = ffWifiNlAttrNext(attr, &remaining)) { + uint16_t type = (uint16_t) (attr->nla_type & NLA_TYPE_MASK); + size_t payload = ffWifiNlAttrPayload(attr); + + if (type == NL80211_STA_INFO_SIGNAL && payload >= sizeof(uint8_t) && item->conn.signalQuality == -DBL_MAX) { + int rssi = (int8_t) *(const uint8_t*) ffWifiNlAttrData(attr); + item->conn.signalQuality = rssiToSignalQuality(rssi); + } else if (type == NL80211_STA_INFO_TX_BITRATE) { + double tx = ffWifiParseBitrateFromRateInfo(attr, &item->conn.protocol); + if (tx != -DBL_MAX) { + item->conn.txRate = tx; + } + } else if (type == NL80211_STA_INFO_RX_BITRATE) { + double rx = ffWifiParseBitrateFromRateInfo(attr, &item->conn.protocol); + if (rx != -DBL_MAX) { + item->conn.rxRate = rx; + } } - ffStrbufTrimRight(&item->conn.bssid, ':'); - FF_DEBUG("BSSID: %s", item->conn.bssid.chars); - } else { - FF_DEBUG("Failed to get BSSID via ioctl: %s", strerror(errno)); } +} - // Get bitrate - FF_DEBUG("Getting bitrate via ioctl"); - if (ioctl(sock, SIOCGIWRATE, &iwr) >= 0) { - item->conn.txRate = iwr.u.bitrate.value / 1000000.; - FF_DEBUG("TX bitrate: %.2f Mbps", item->conn.txRate); - } else { - FF_DEBUG("Failed to get bitrate via ioctl: %s", strerror(errno)); +static bool ffWifiFetchStationInfo(FFWifiNlContext* ctx, FFWifiResult* item, uint32_t ifIndex) { + struct { + struct nlmsghdr nlh; + struct genlmsghdr genl; + char attrs[32]; + } req = { + .nlh = { + .nlmsg_len = NLMSG_LENGTH(sizeof(struct genlmsghdr)), + .nlmsg_type = ctx->nl80211FamilyId, + .nlmsg_flags = NLM_F_REQUEST | NLM_F_DUMP | NLM_F_ACK, + .nlmsg_seq = ++ctx->seq, + .nlmsg_pid = ctx->portId, + }, + .genl = { + .cmd = NL80211_CMD_GET_STATION, + .version = 0, + }, + }; + + if (!ffWifiNlAppendAttr(&req.nlh, sizeof(req), NL80211_ATTR_IFINDEX, &ifIndex, sizeof(ifIndex))) { + FF_DEBUG("Failed to build nl80211 station request"); + return false; } - // Get frequency/channel - FF_DEBUG("Getting frequency via ioctl"); - if (ioctl(sock, SIOCGIWFREQ, &iwr) >= 0) { - if (iwr.u.freq.e == 0 && iwr.u.freq.m <= 1000) { - item->conn.channel = (uint16_t) iwr.u.freq.m; - FF_DEBUG("Direct channel value: %u", item->conn.channel); - } else { - // convert it to MHz - while (iwr.u.freq.e < 6) { - iwr.u.freq.m /= 10; - iwr.u.freq.e++; + struct sockaddr_nl addr = { + .nl_family = AF_NETLINK, + }; + + ssize_t sent = sendto(ctx->sockFd, &req, req.nlh.nlmsg_len, 0, (struct sockaddr*) &addr, sizeof(addr)); + if (sent != (ssize_t) req.nlh.nlmsg_len) { + FF_DEBUG("Failed to send nl80211 station request"); + return false; + } + + uint8_t buffer[8192]; + bool gotStation = false; + while (true) { + ssize_t received = recvfrom(ctx->sockFd, buffer, sizeof(buffer), 0, NULL, NULL); + if (received < 0) { + FF_DEBUG("Failed to receive nl80211 station reply: %s", strerror(errno)); + return gotStation; + } + + for (const struct nlmsghdr* nlh = (const struct nlmsghdr*) buffer; + NLMSG_OK(nlh, received); + nlh = NLMSG_NEXT(nlh, received)) { + if (nlh->nlmsg_seq != req.nlh.nlmsg_seq) { + continue; } - while (iwr.u.freq.e > 6) { - iwr.u.freq.m *= 10; - iwr.u.freq.e--; + + if (nlh->nlmsg_type == NLMSG_DONE) { + return gotStation; + } + + if (nlh->nlmsg_type == NLMSG_ERROR) { + const struct nlmsgerr* err = (const struct nlmsgerr*) NLMSG_DATA(nlh); + if (err->error != 0) { + FF_DEBUG("nl80211 station request failed: %s", strerror(-err->error)); + } + return gotStation; + } + + if (nlh->nlmsg_type != ctx->nl80211FamilyId) { + continue; + } + + const struct genlmsghdr* genl = (const struct genlmsghdr*) NLMSG_DATA(nlh); + size_t attrRemaining = nlh->nlmsg_len - NLMSG_HDRLEN - GENL_HDRLEN; + for (const struct nlattr* attr = (const struct nlattr*) ((const char*) genl + GENL_HDRLEN); + ffWifiNlAttrOk(attr, attrRemaining); + attr = ffWifiNlAttrNext(attr, &attrRemaining)) { + if ((attr->nla_type & NLA_TYPE_MASK) != NL80211_ATTR_STA_INFO) { + continue; + } + + ffWifiParseStationInfo(attr, item); + gotStation = true; } - item->conn.frequency = (uint16_t) iwr.u.freq.m; - item->conn.channel = ffWifiFreqToChannel(item->conn.frequency); - FF_DEBUG("Frequency: %u MHz, Channel: %u", item->conn.frequency, item->conn.channel); } - } else { - FF_DEBUG("Failed to get frequency via ioctl: %s", strerror(errno)); } +} - // Get signal strength - FF_DEBUG("Getting signal stats via ioctl"); - struct iw_statistics stats; - iwr.u.data.pointer = &stats; - iwr.u.data.length = sizeof(stats); - iwr.u.data.flags = 0; +static void ffWifiDetectWithNetlink(FFWifiNlContext* ctx, FFWifiResult* item, uint32_t ifIndex) { + bool associated = ffWifiFetchScanInfo(ctx, item, ifIndex); + if (associated) { + ffStrbufSetStatic(&item->conn.status, "connected"); + } else if (!item->conn.status.length) { + ffStrbufSetStatic(&item->conn.status, "disconnected"); + } - if (ioctl(sock, SIOCGIWSTATS, &iwr) >= 0) { - int8_t level = (int8_t) stats.qual.level; - item->conn.signalQuality = level >= -50 ? 100 : level <= -100 ? 0 - : (level + 100) * 2; - FF_DEBUG("Signal level: %d dBm, quality: %.0f%%", level, item->conn.signalQuality); - } else { - FF_DEBUG("Failed to get signal stats via ioctl: %s", strerror(errno)); - } - - // Get security info - FF_DEBUG("Getting security info via ioctl"); - struct iw_encode_ext iwe; - iwr.u.data.pointer = &iwe; - iwr.u.data.length = sizeof(iwe); - iwr.u.data.flags = 0; - if (ioctl(sock, SIOCGIWENCODEEXT, &iwr) >= 0) { - switch (iwe.alg) { - case IW_ENCODE_ALG_WEP: - ffStrbufAppendS(&item->conn.security, "WEP"); - FF_DEBUG("Security: WEP"); - break; - case IW_ENCODE_ALG_TKIP: - ffStrbufAppendS(&item->conn.security, "TKIP"); - FF_DEBUG("Security: TKIP"); - break; - case IW_ENCODE_ALG_CCMP: - ffStrbufAppendS(&item->conn.security, "CCMP"); - FF_DEBUG("Security: CCMP"); - break; - case IW_ENCODE_ALG_PMK: - ffStrbufAppendS(&item->conn.security, "PMK"); - FF_DEBUG("Security: PMK"); - break; - case IW_ENCODE_ALG_AES_CMAC: - ffStrbufAppendS(&item->conn.security, "CMAC"); - FF_DEBUG("Security: CMAC"); - break; - default: - ffStrbufAppendF(&item->conn.security, "Unknown (%d)", (int) iwe.alg); - FF_DEBUG("Security: Unknown (%d)", (int) iwe.alg); - break; - } - } else { - FF_DEBUG("Failed to get security info via ioctl: %s", strerror(errno)); + if (associated) { + ffWifiFetchStationInfo(ctx, item, ifIndex); } - FF_DEBUG("ioctl wifi detection completed"); - return NULL; + if (!item->conn.protocol.length && item->conn.txRate != -DBL_MAX) { + FF_DEBUG("nl80211 station info did not include MCS family fields"); + } } -#endif // FF_HAVE_LINUX_WIRELESS -const char* ffDetectWifi(FF_A_UNUSED FFlist* result) { +const char* ffDetectWifi(FFlist* result) { FF_DEBUG("Starting wifi detection"); + + FFWifiNlContext nl = { .sockFd = -1 }; + struct if_nameindex* infs = if_nameindex(); - if (!infs) { - FF_DEBUG("if_nameindex() failed: %s", strerror(errno)); - return "if_nameindex() failed"; - } FF_STRBUF_AUTO_DESTROY buffer = ffStrbufCreate(); @@ -474,7 +676,13 @@ const char* ffDetectWifi(FF_A_UNUSED FFlist* result) { continue; } - FF_DEBUG("Found wifi interface: %s", i->if_name); + if (nl.sockFd < 0) { + if (!ffWifiNlInit(&nl)) { + FF_DEBUG("Failed to initialize netlink context, skipping wifi detection"); + break; + } + } + FFWifiResult* item = FF_LIST_ADD(FFWifiResult, *result); ffStrbufInitS(&item->inf.description, i->if_name); ffStrbufInit(&item->inf.status); @@ -492,54 +700,42 @@ const char* ffDetectWifi(FF_A_UNUSED FFlist* result) { char operstate; ffStrbufSetF(&buffer, "/sys/class/net/%s/operstate", i->if_name); if (!ffReadFileData(buffer.chars, 1, &operstate)) { - FF_DEBUG("Failed to read operstate file"); + ffStrbufSetStatic(&item->inf.status, "unknown"); + ffStrbufSetStatic(&item->conn.status, "disconnected"); continue; } - FF_DEBUG("Connection status: %c", operstate); - if (operstate != 'u') { - FF_DEBUG("Skipping interface as it's not up"); + if (operstate == 'u') { + ffStrbufSetStatic(&item->inf.status, "up"); + ffWifiDetectWithNetlink(&nl, item, i->if_index); + if (!item->conn.status.length) { + ffStrbufSetStatic(&item->conn.status, "disconnected"); + } + } else { ffStrbufSetStatic(&item->conn.status, "disconnected"); ffStrbufSetF(&buffer, "/sys/class/net/%s/flags", i->if_name); char flags[16]; - ssize_t len = ffReadFileData(buffer.chars, sizeof(flags), flags); + ssize_t len = ffReadFileData(buffer.chars, sizeof(flags) - 1, flags); if (len <= 0) { - FF_DEBUG("Failed to read flags file"); ffStrbufSetStatic(&item->inf.status, "unknown"); continue; } flags[len] = '\0'; - FF_DEBUG("Interface flags: %s", flags); + unsigned flagsVal = (unsigned) strtoul(flags, NULL, 16); if (flagsVal & IFF_UP) { ffStrbufSetStatic(&item->inf.status, "up"); - FF_DEBUG("Interface is up but not connected"); } else { ffStrbufSetStatic(&item->inf.status, "down"); - FF_DEBUG("Interface is down"); } - - continue; } - - ffStrbufSetStatic(&item->inf.status, "up"); - - FF_DEBUG("Trying to detect wifi with iw"); - if (detectWifiWithIw(item, &buffer) != NULL) { - FF_DEBUG("iw detection failed, trying fallback methods"); -#ifdef FF_HAVE_LINUX_WIRELESS - FF_DEBUG("Trying to detect wifi with ioctls"); - detectWifiWithIoctls(item); -#endif - } - -#ifdef FF_HAVE_DBUS - FF_DEBUG("Enhancing wifi info with NetworkManager"); - detectWifiWithNm(item, &buffer); -#endif } + if_freenameindex(infs); + if (nl.sockFd >= 0) { + close(nl.sockFd); + } FF_DEBUG("Wifi detection completed, found %u wifi interfaces", result->length); return NULL; From b86acf6c36eacff0dc3fefe798239ce4b5851fa9 Mon Sep 17 00:00:00 2001 From: Carter Li Date: Sat, 9 May 2026 11:00:40 +0800 Subject: [PATCH 60/92] Wifi (Linux): fixes bugs when multiple BSS exists; adds ioctl fallback --- src/detection/wifi/wifi_linux.c | 288 +++++++++++++++++++++++++++----- 1 file changed, 243 insertions(+), 45 deletions(-) diff --git a/src/detection/wifi/wifi_linux.c b/src/detection/wifi/wifi_linux.c index 63b0efdc65..c7bac1eb18 100644 --- a/src/detection/wifi/wifi_linux.c +++ b/src/detection/wifi/wifi_linux.c @@ -1,12 +1,17 @@ #include "wifi.h" #include "common/io.h" #include "common/debug.h" +#include "common/stringUtils.h" #include #include +#include +#include +#include #include #include -#include +#include +#include // Silence warning of `NLA_HDRLEN` and `NLA_ALIGN` #pragma GCC diagnostic ignored "-Wsign-conversion" @@ -18,6 +23,10 @@ typedef struct FFWifiNlContext { uint32_t seq; } FFWifiNlContext; +typedef struct FFWifiIcContext { + int sockFd; +} FFWifiIcContext; + typedef struct FFWifiSecurityFlags { bool privacy : 1; bool wep : 1; @@ -28,9 +37,9 @@ typedef struct FFWifiSecurityFlags { bool eap : 1; } FFWifiSecurityFlags; -static inline double rssiToSignalQuality(int rssi) -{ - return (double) (rssi >= -50 ? 100 : rssi <= -100 ? 0 : (rssi + 100) * 2); +static inline double rssiToSignalQuality(int rssi) { + return (double) (rssi >= -50 ? 100 : rssi <= -100 ? 0 + : (rssi + 100) * 2); } static inline uint32_t ffWifiGetNetlinkPortId(int sockFd) { @@ -66,6 +75,7 @@ static inline size_t ffWifiNlAttrPayload(const struct nlattr* attr) { } static inline const void* ffWifiNlAttrData(const struct nlattr* attr) { + // Big endian? return (const uint8_t*) attr + NLA_HDRLEN; } @@ -399,7 +409,7 @@ static void ffWifiParseInformationElements(const uint8_t* ies, size_t length, FF } const uint8_t* ie = ies + pos; - if (id == 0 && !item->conn.ssid.length) { + if (id == 0) { ffStrbufSetNS(&item->conn.ssid, len, (const char*) ie); } else if (id == 48) { ffWifiParseRsnIe(ie, len, sec); @@ -411,7 +421,23 @@ static void ffWifiParseInformationElements(const uint8_t* ies, size_t length, FF } } -static bool ffWifiParseBssAttr(const struct nlattr* bssAttr, FFWifiResult* item, bool* associated) { +static bool ffWifiIsBssAssociated(const struct nlattr* bssAttr) { + size_t remaining = ffWifiNlAttrPayload(bssAttr); + for (const struct nlattr* attr = (const struct nlattr*) ffWifiNlAttrData(bssAttr); + ffWifiNlAttrOk(attr, remaining); + attr = ffWifiNlAttrNext(attr, &remaining)) { + uint16_t type = (uint16_t) (attr->nla_type & NLA_TYPE_MASK); + size_t payload = ffWifiNlAttrPayload(attr); + + if (type == NL80211_BSS_STATUS && payload >= sizeof(uint32_t)) { + return *(uint32_t*) ffWifiNlAttrData(attr) == NL80211_BSS_STATUS_ASSOCIATED; + } + } + + return false; +} + +static void ffWifiParseBssAttr(const struct nlattr* bssAttr, FFWifiResult* item) { FFWifiSecurityFlags sec = {}; size_t remaining = ffWifiNlAttrPayload(bssAttr); @@ -421,11 +447,7 @@ static bool ffWifiParseBssAttr(const struct nlattr* bssAttr, FFWifiResult* item, uint16_t type = (uint16_t) (attr->nla_type & NLA_TYPE_MASK); size_t payload = ffWifiNlAttrPayload(attr); - if (type == NL80211_BSS_STATUS && payload >= sizeof(uint32_t)) { - if (*(uint32_t*) ffWifiNlAttrData(attr) == NL80211_BSS_STATUS_ASSOCIATED) { - *associated = true; - } - } else if (type == NL80211_BSS_BSSID && payload >= 6) { + if (type == NL80211_BSS_BSSID && payload >= 6) { const uint8_t* mac = (const uint8_t*) ffWifiNlAttrData(attr); ffStrbufSetF(&item->conn.bssid, "%02X:%02X:%02X:%02X:%02X:%02X", mac[0], mac[1], mac[2], mac[3], mac[4], mac[5]); } else if (type == NL80211_BSS_FREQUENCY && payload >= sizeof(uint32_t)) { @@ -442,12 +464,8 @@ static bool ffWifiParseBssAttr(const struct nlattr* bssAttr, FFWifiResult* item, } } - if (!*associated) { - return false; - } - ffWifiApplySecurityFlags(item, &sec); - return true; + return; } static bool ffWifiFetchScanInfo(FFWifiNlContext* ctx, FFWifiResult* item, uint32_t ifIndex) { @@ -485,7 +503,6 @@ static bool ffWifiFetchScanInfo(FFWifiNlContext* ctx, FFWifiResult* item, uint32 } uint8_t buffer[1024 * 16]; - bool associated = false; while (true) { ssize_t received = recvfrom(ctx->sockFd, buffer, sizeof(buffer), 0, NULL, NULL); if (received < 0) { @@ -501,7 +518,7 @@ static bool ffWifiFetchScanInfo(FFWifiNlContext* ctx, FFWifiResult* item, uint32 } if (nlh->nlmsg_type == NLMSG_DONE) { - return associated; + return false; } if (nlh->nlmsg_type == NLMSG_ERROR) { @@ -510,7 +527,7 @@ static bool ffWifiFetchScanInfo(FFWifiNlContext* ctx, FFWifiResult* item, uint32 continue; } FF_DEBUG("nl80211 scan request failed: %s", strerror(-err->error)); - return associated; + return false; } if (nlh->nlmsg_type != ctx->nl80211FamilyId) { @@ -526,13 +543,18 @@ static bool ffWifiFetchScanInfo(FFWifiNlContext* ctx, FFWifiResult* item, uint32 continue; } - bool thisAssociated = false; - if (ffWifiParseBssAttr(attr, item, &thisAssociated) && thisAssociated) { - associated = true; + if (!ffWifiIsBssAssociated(attr)) { + continue; } + + ffWifiParseBssAttr(attr, item); + ffStrbufSetStatic(&item->conn.status, "connected"); + return true; } } } + + return false; } static void ffWifiParseStationInfo(const struct nlattr* staInfoAttr, FFWifiResult* item) { @@ -546,12 +568,12 @@ static void ffWifiParseStationInfo(const struct nlattr* staInfoAttr, FFWifiResul if (type == NL80211_STA_INFO_SIGNAL && payload >= sizeof(uint8_t) && item->conn.signalQuality == -DBL_MAX) { int rssi = (int8_t) *(const uint8_t*) ffWifiNlAttrData(attr); item->conn.signalQuality = rssiToSignalQuality(rssi); - } else if (type == NL80211_STA_INFO_TX_BITRATE) { + } else if (type == NL80211_STA_INFO_TX_BITRATE && item->conn.txRate == -DBL_MAX) { double tx = ffWifiParseBitrateFromRateInfo(attr, &item->conn.protocol); if (tx != -DBL_MAX) { item->conn.txRate = tx; } - } else if (type == NL80211_STA_INFO_RX_BITRATE) { + } else if (type == NL80211_STA_INFO_RX_BITRATE && item->conn.rxRate == -DBL_MAX) { double rx = ffWifiParseBitrateFromRateInfo(attr, &item->conn.protocol); if (rx != -DBL_MAX) { item->conn.rxRate = rx; @@ -642,29 +664,211 @@ static bool ffWifiFetchStationInfo(FFWifiNlContext* ctx, FFWifiResult* item, uin } } -static void ffWifiDetectWithNetlink(FFWifiNlContext* ctx, FFWifiResult* item, uint32_t ifIndex) { - bool associated = ffWifiFetchScanInfo(ctx, item, ifIndex); - if (associated) { +static const char* detectWithNetlink(FFWifiNlContext* ctx, FFWifiResult* item, uint32_t ifIndex) { + if (ctx->sockFd < 0) { + if (ctx->sockFd == -1) { + if (!ffWifiNlInit(ctx)) { + FF_DEBUG("Failed to initialize netlink context, skipping"); + ctx->sockFd = -2; // Don't try again + return "Netlink initialization failed"; + } + } else { + FF_DEBUG("Netlink socket is not available, skipping"); + return "Netlink socket unavailable"; + } + } + + FF_DEBUG("Starting netlink wifi detection for interface %s", item->inf.description.chars); + if (ffWifiFetchScanInfo(ctx, item, ifIndex)) { + FF_DEBUG("found associated BSS: %s", item->conn.ssid.chars); ffStrbufSetStatic(&item->conn.status, "connected"); - } else if (!item->conn.status.length) { + ffWifiFetchStationInfo(ctx, item, ifIndex); + if (!item->conn.protocol.length && item->conn.txRate != -DBL_MAX) { + FF_DEBUG("nl80211 station info did not include MCS family fields"); + } + } else { + FF_DEBUG("No associated BSS found"); ffStrbufSetStatic(&item->conn.status, "disconnected"); } - if (associated) { - ffWifiFetchStationInfo(ctx, item, ifIndex); + FF_DEBUG("Netlink wifi detection completed"); + return NULL; +} + +static const char* detectWithIoctl(FFWifiIcContext* ctx, FFWifiResult* item, char ifName[static IFNAMSIZ]) { + int sock = -1; + if (ctx->sockFd < 0) { + if (ctx->sockFd == -1) { + sock = socket(AF_INET, SOCK_DGRAM | SOCK_CLOEXEC, 0); + if (sock < 0) { + FF_DEBUG("Failed to initialize ioctl context, skipping: %s", strerror(errno)); + ctx->sockFd = -2; // Don't try again + return "socket() failed"; + } + ctx->sockFd = sock; + } else { + FF_DEBUG("Ioctl socket is not available, skipping"); + return "ioctl socket unavailable"; + } + } else { + sock = ctx->sockFd; + } + + FF_DEBUG("Starting ioctl wifi detection for interface %s", ifName); + struct iwreq iwr = {}; + strcpy(iwr.ifr_name, ifName); + + if (!item->conn.ssid.length) { + FF_DEBUG("Getting SSID via ioctl"); + ffStrbufEnsureFree(&item->conn.ssid, IW_ESSID_MAX_SIZE); + iwr.u.essid.pointer = (caddr_t) item->conn.ssid.chars; + iwr.u.essid.length = IW_ESSID_MAX_SIZE + 1; + iwr.u.essid.flags = 0; + if (ioctl(sock, SIOCGIWESSID, &iwr) >= 0) { + ffStrbufSetStatic(&item->conn.status, "connected"); + ffStrbufRecalculateLength(&item->conn.ssid); + FF_DEBUG("SSID: %s", item->conn.ssid.chars); + } else { + FF_DEBUG("Failed to get SSID via ioctl: %s", strerror(errno)); + } } - if (!item->conn.protocol.length && item->conn.txRate != -DBL_MAX) { - FF_DEBUG("nl80211 station info did not include MCS family fields"); + if (!item->conn.protocol.length) { + FF_DEBUG("Getting protocol name via ioctl"); + if (ioctl(sock, SIOCGIWNAME, &iwr) >= 0 && !ffStrEqualsIgnCase(iwr.u.name, "IEEE 802.11")) { + if (ffStrStartsWithIgnCase(iwr.u.name, "IEEE ")) { + ffStrbufSetS(&item->conn.protocol, iwr.u.name + strlen("IEEE ")); + } else { + ffStrbufSetS(&item->conn.protocol, iwr.u.name); + } + FF_DEBUG("Protocol: %s", item->conn.protocol.chars); + } else { + FF_DEBUG("Failed to get protocol name via ioctl: %s", strerror(errno)); + } + } + + if (!item->conn.bssid.length) { + FF_DEBUG("Getting BSSID via ioctl"); + if (ioctl(sock, SIOCGIWAP, &iwr) >= 0) { + for (int i = 0; i < 6; ++i) { + ffStrbufAppendF(&item->conn.bssid, "%.2X:", (uint8_t) iwr.u.ap_addr.sa_data[i]); + } + ffStrbufTrimRight(&item->conn.bssid, ':'); + FF_DEBUG("BSSID: %s", item->conn.bssid.chars); + } else { + FF_DEBUG("Failed to get BSSID via ioctl: %s", strerror(errno)); + } + } + + if (item->conn.txRate == -DBL_MAX) { + FF_DEBUG("Getting bitrate via ioctl"); + if (ioctl(sock, SIOCGIWRATE, &iwr) >= 0) { + if (iwr.u.bitrate.value > 0) { + item->conn.txRate = iwr.u.bitrate.value / 1000000.; + FF_DEBUG("TX bitrate: %.2f Mbps", item->conn.txRate); + } else { + FF_DEBUG("Bitrate value is zero or negative, ignoring"); + } + } else { + FF_DEBUG("Failed to get bitrate via ioctl: %s", strerror(errno)); + } + } + + if (item->conn.frequency == 0 && item->conn.channel == 0) { + FF_DEBUG("Getting frequency via ioctl"); + if (ioctl(sock, SIOCGIWFREQ, &iwr) >= 0) { + if (iwr.u.freq.e == 0 && iwr.u.freq.m <= 1000) { + item->conn.channel = (uint16_t) iwr.u.freq.m; + FF_DEBUG("Direct channel value: %u", item->conn.channel); + } else { + // convert it to MHz + while (iwr.u.freq.e < 6) { + iwr.u.freq.m /= 10; + iwr.u.freq.e++; + } + while (iwr.u.freq.e > 6) { + iwr.u.freq.m *= 10; + iwr.u.freq.e--; + } + item->conn.frequency = (uint16_t) iwr.u.freq.m; + item->conn.channel = ffWifiFreqToChannel(item->conn.frequency); + FF_DEBUG("Frequency: %u MHz, Channel: %u", item->conn.frequency, item->conn.channel); + } + } else { + FF_DEBUG("Failed to get frequency via ioctl: %s", strerror(errno)); + } } + + if (item->conn.signalQuality == -DBL_MAX) { + FF_DEBUG("Getting signal stats via ioctl"); + struct iw_statistics stats; + iwr.u.data.pointer = &stats; + iwr.u.data.length = sizeof(stats); + iwr.u.data.flags = 0; + + if (ioctl(sock, SIOCGIWSTATS, &iwr) >= 0) { + int8_t level = (int8_t) stats.qual.level; + item->conn.signalQuality = level >= -50 ? 100 : level <= -100 ? 0 + : (level + 100) * 2; + FF_DEBUG("Signal level: %d dBm, quality: %.0f%%", level, item->conn.signalQuality); + } else { + FF_DEBUG("Failed to get signal stats via ioctl: %s", strerror(errno)); + } + } + + if (!item->conn.security.length) { + FF_DEBUG("Getting security info via ioctl"); + struct iw_encode_ext iwe; + iwr.u.data.pointer = &iwe; + iwr.u.data.length = sizeof(iwe); + iwr.u.data.flags = 0; + if (ioctl(sock, SIOCGIWENCODEEXT, &iwr) >= 0) { + switch (iwe.alg) { + case IW_ENCODE_ALG_WEP: + ffStrbufAppendS(&item->conn.security, "WEP"); + FF_DEBUG("Security: WEP"); + break; + case IW_ENCODE_ALG_TKIP: + ffStrbufAppendS(&item->conn.security, "TKIP"); + FF_DEBUG("Security: TKIP"); + break; + case IW_ENCODE_ALG_CCMP: + ffStrbufAppendS(&item->conn.security, "CCMP"); + FF_DEBUG("Security: CCMP"); + break; + case IW_ENCODE_ALG_PMK: + ffStrbufAppendS(&item->conn.security, "PMK"); + FF_DEBUG("Security: PMK"); + break; + case IW_ENCODE_ALG_AES_CMAC: + ffStrbufAppendS(&item->conn.security, "CMAC"); + FF_DEBUG("Security: CMAC"); + break; + default: + ffStrbufAppendF(&item->conn.security, "Unknown (%d)", (int) iwe.alg); + FF_DEBUG("Security: Unknown (%d)", (int) iwe.alg); + break; + } + } else { + FF_DEBUG("Failed to get security info via ioctl: %s", strerror(errno)); + } + } + + FF_DEBUG("Ioctl wifi detection completed"); + return NULL; } const char* ffDetectWifi(FFlist* result) { FF_DEBUG("Starting wifi detection"); - FFWifiNlContext nl = { .sockFd = -1 }; - struct if_nameindex* infs = if_nameindex(); + if (!infs) { + FF_DEBUG("if_nameindex failed: %s", strerror(errno)); + return "if_nameindex() failed"; + } + + FFWifiNlContext nl = { .sockFd = -1 }; + FFWifiIcContext ic = { .sockFd = -1 }; FF_STRBUF_AUTO_DESTROY buffer = ffStrbufCreate(); @@ -676,13 +880,6 @@ const char* ffDetectWifi(FFlist* result) { continue; } - if (nl.sockFd < 0) { - if (!ffWifiNlInit(&nl)) { - FF_DEBUG("Failed to initialize netlink context, skipping wifi detection"); - break; - } - } - FFWifiResult* item = FF_LIST_ADD(FFWifiResult, *result); ffStrbufInitS(&item->inf.description, i->if_name); ffStrbufInit(&item->inf.status); @@ -707,10 +904,8 @@ const char* ffDetectWifi(FFlist* result) { if (operstate == 'u') { ffStrbufSetStatic(&item->inf.status, "up"); - ffWifiDetectWithNetlink(&nl, item, i->if_index); - if (!item->conn.status.length) { - ffStrbufSetStatic(&item->conn.status, "disconnected"); - } + detectWithNetlink(&nl, item, i->if_index); + detectWithIoctl(&ic, item, i->if_name); } else { ffStrbufSetStatic(&item->conn.status, "disconnected"); @@ -736,6 +931,9 @@ const char* ffDetectWifi(FFlist* result) { if (nl.sockFd >= 0) { close(nl.sockFd); } + if (ic.sockFd >= 0) { + close(ic.sockFd); + } FF_DEBUG("Wifi detection completed, found %u wifi interfaces", result->length); return NULL; From 44e2948b1da1fe1ed5e52253fa222720eb251f91 Mon Sep 17 00:00:00 2001 From: Carter Li Date: Sat, 9 May 2026 11:01:05 +0800 Subject: [PATCH 61/92] Wifi: adds 6 GHz channels detection --- src/detection/wifi/wifi.h | 31 ++++++++++++++++++++++++++----- 1 file changed, 26 insertions(+), 5 deletions(-) diff --git a/src/detection/wifi/wifi.h b/src/detection/wifi/wifi.h index df29a43e31..e457295c36 100644 --- a/src/detection/wifi/wifi.h +++ b/src/detection/wifi/wifi.h @@ -29,13 +29,34 @@ typedef struct FFWifiResult { const char* ffDetectWifi(FFlist* result /*list of FFWifiItem*/); static inline uint16_t ffWifiFreqToChannel(uint16_t frequency) { - // https://github.com/opetryna/win32wifi/blob/master/win32wifi/Win32Wifi.py#L140 - // FIXME: Does it work for 6 GHz? + // Return 0 for unknown / non-standard frequencies. if (frequency == 2484) { return 14; - } else if (frequency < 2484) { + } + + // 2.4 GHz channels 1-13 + if (frequency >= 2412 && frequency <= 2472 && ((frequency - 2407) % 5) == 0) { return (uint16_t) ((frequency - 2407) / 5); - } else { - return (uint16_t) ((frequency / 5) - 1000); } + + // 4.9 GHz public safety band (e.g. channels 182-196) + if (frequency >= 4910 && frequency <= 4980 && ((frequency - 4000) % 5) == 0) { + return (uint16_t) ((frequency - 4000) / 5); + } + + // 5 GHz channels + if (frequency >= 5000 && frequency <= 5895 && ((frequency - 5000) % 5) == 0) { + return (uint16_t) ((frequency - 5000) / 5); + } + + // 6 GHz channels (Wi-Fi 6E/7) + // 5935 MHz is a special case mapped to channel 2. + if (frequency == 5935) { + return 2; + } + if (frequency >= 5955 && frequency <= 7115 && ((frequency - 5950) % 5) == 0) { + return (uint16_t) ((frequency - 5950) / 5); + } + + return 0; } From 91f1c4db1080a614662ef2581ea331bde472a54f Mon Sep 17 00:00:00 2001 From: Carter Li Date: Sat, 9 May 2026 11:01:50 +0800 Subject: [PATCH 62/92] Wifi: fixes 60 GHz band detection --- src/modules/wifi/wifi.c | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/modules/wifi/wifi.c b/src/modules/wifi/wifi.c index b6005a3f7e..26284a32b7 100644 --- a/src/modules/wifi/wifi.c +++ b/src/modules/wifi/wifi.c @@ -27,8 +27,7 @@ bool ffPrintWifi(FFWifiOptions* options) { char bandStr[8]; if (item->conn.frequency > 58000) { strcpy(bandStr, "60"); - } - if (item->conn.frequency > 40000) { + } else if (item->conn.frequency > 40000) { strcpy(bandStr, "45"); } else if (item->conn.frequency > 5900) { strcpy(bandStr, "6"); From fdb5f30cb810fdff18d78fa55cfbd464dea152ae Mon Sep 17 00:00:00 2001 From: Carter Li Date: Sat, 9 May 2026 11:04:39 +0800 Subject: [PATCH 63/92] CMake (Linux): removes `linux/wireless` detection Always required now --- CMakeLists.txt | 6 ------ src/common/impl/init.c | 3 --- 2 files changed, 9 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 5b3ebf061f..b671ea527e 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1858,12 +1858,6 @@ if(LINUX OR FreeBSD OR OpenBSD OR NetBSD) target_compile_definitions(libfastfetch PRIVATE FF_HAVE_LINUX_VIDEODEV2=1) endif() endif() -if(LINUX) - CHECK_INCLUDE_FILE("linux/wireless.h" HAVE_LINUX_WIRELESS) - if(HAVE_LINUX_WIRELESS) - target_compile_definitions(libfastfetch PRIVATE FF_HAVE_LINUX_WIRELESS=1) - endif() -endif() if(NOT WIN32) CHECK_INCLUDE_FILE("utmpx.h" HAVE_UTMPX) if(HAVE_UTMPX) diff --git a/src/common/impl/init.c b/src/common/impl/init.c index 80fb371242..123a9dd99e 100644 --- a/src/common/impl/init.c +++ b/src/common/impl/init.c @@ -254,9 +254,6 @@ void ffListFeatures(void) { #if FF_HAVE_LINUX_VIDEODEV2 "linux/videodev2\n" #endif -#if FF_HAVE_LINUX_WIRELESS - "linux/wireless\n" -#endif #if FF_HAVE_EMBEDDED_PCIIDS "Embedded pciids\n" #endif From f46aa316cb7a4d50b09957d7bc3caaf494dbc35a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=9D=8E=E9=80=9A=E6=B4=B2?= Date: Sat, 9 May 2026 13:46:05 +0800 Subject: [PATCH 64/92] Wifi (Linux): improves protocol reporting --- src/detection/wifi/wifi_linux.c | 40 +++++++++++++++++++++++++++------ 1 file changed, 33 insertions(+), 7 deletions(-) diff --git a/src/detection/wifi/wifi_linux.c b/src/detection/wifi/wifi_linux.c index c7bac1eb18..419f343758 100644 --- a/src/detection/wifi/wifi_linux.c +++ b/src/detection/wifi/wifi_linux.c @@ -225,10 +225,10 @@ static double ffWifiParseBitrateFromRateInfo(const struct nlattr* rateAttr, FFst size_t payload = ffWifiNlAttrPayload(info); switch (type) { - case 30 /* NL80211_RATE_INFO_UHR_MCS*/: + case 30 /* NL80211_RATE_INFO_UHR_MCS */: ffStrbufSetStatic(protocol, "802.11bn (Wi-Fi 8)"); break; - case 23 /*NL80211_RATE_INFO_S1G_MCS*/: + case 23 /* NL80211_RATE_INFO_S1G_MCS */: ffStrbufSetStatic(protocol, "802.11ah (Wi-Fi HaLow)"); break; case 19 /* NL80211_RATE_INFO_EHT_MCS */: @@ -735,13 +735,39 @@ static const char* detectWithIoctl(FFWifiIcContext* ctx, FFWifiResult* item, cha if (!item->conn.protocol.length) { FF_DEBUG("Getting protocol name via ioctl"); - if (ioctl(sock, SIOCGIWNAME, &iwr) >= 0 && !ffStrEqualsIgnCase(iwr.u.name, "IEEE 802.11")) { + if (ioctl(sock, SIOCGIWNAME, &iwr) >= 0) { + char* token = iwr.u.name; if (ffStrStartsWithIgnCase(iwr.u.name, "IEEE ")) { - ffStrbufSetS(&item->conn.protocol, iwr.u.name + strlen("IEEE ")); - } else { - ffStrbufSetS(&item->conn.protocol, iwr.u.name); + token += strlen("IEEE "); + } + if (ffStrStartsWith(token, "802.11")) { + token += strlen("802.11"); + if (*token) { + if (*token == ' ') { + token++; + } + for (char* c = token; *c; ++c) { + if (*c >= 'A' && *c <= 'Z') { + *c += 'a' - 'A'; + } + } + if (ffStrEquals(token, "n")) { + ffStrbufSetStatic(&item->conn.protocol, "802.11n (Wi-Fi 4)"); + } else if (ffStrEquals(token, "ac")) { + ffStrbufSetStatic(&item->conn.protocol, "802.11ac (Wi-Fi 5)"); + } else if (ffStrEquals(token, "ax")) { + ffStrbufSetStatic(&item->conn.protocol, "802.11ax (Wi-Fi 6)"); + } else if (ffStrEquals(token, "be")) { + ffStrbufSetStatic(&item->conn.protocol, "802.11be (Wi-Fi 7)"); + } else if (ffStrEquals(token, "bn")) { + ffStrbufSetStatic(&item->conn.protocol, "802.11bn (Wi-Fi 8)"); + } else { + ffStrbufSetStatic(&item->conn.protocol, "802.11"); + ffStrbufAppendS(&item->conn.protocol, token); + } + } } - FF_DEBUG("Protocol: %s", item->conn.protocol.chars); + FF_DEBUG("Protocol: %s", item->conn.protocol.length ? item->conn.protocol.chars : "(unknown)"); } else { FF_DEBUG("Failed to get protocol name via ioctl: %s", strerror(errno)); } From 87d89130ad072eb4429cadf765f338c6845b6b0d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=9D=8E=E9=80=9A=E6=B4=B2?= Date: Sat, 9 May 2026 15:41:49 +0800 Subject: [PATCH 65/92] Common (Windows): fixes Windows 11 detection code --- src/common/windows/nt.h | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/common/windows/nt.h b/src/common/windows/nt.h index 1c2d08269c..a8bf508390 100644 --- a/src/common/windows/nt.h +++ b/src/common/windows/nt.h @@ -634,7 +634,8 @@ static inline bool ffIsWindows10OrGreater() { } static inline bool ffIsWindows11OrGreater() { - return ffIsWindows10OrGreater() && SharedUserData->NtBuildNumber >= 22000; + return SharedUserData->NtMajorVersion > 10 || + (SharedUserData->NtMajorVersion == 10 && SharedUserData->NtBuildNumber >= 22000); } NTSYSAPI NTSTATUS NTAPI NtOpenProcessToken( From bc0a515998c4241998b75d26b9530de97b716d33 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=9D=8E=E9=80=9A=E6=B4=B2?= Date: Sat, 9 May 2026 15:42:46 +0800 Subject: [PATCH 66/92] Common (Windows): works around a compiler optimise bug --- src/common/windows/nt.h | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/src/common/windows/nt.h b/src/common/windows/nt.h index a8bf508390..2f476b8d64 100644 --- a/src/common/windows/nt.h +++ b/src/common/windows/nt.h @@ -602,7 +602,15 @@ typedef struct _KUSER_SHARED_DATA { // ... more fields follow, but we don't need them } KUSER_SHARED_DATA, *PKUSER_SHARED_DATA; -#define SharedUserData ((const KUSER_SHARED_DATA*) 0x7FFE0000UL) +#ifdef __aarch64__ + #define SharedUserData ({ \ + __auto_type shared_user_data = (const volatile KUSER_SHARED_DATA*) (uintptr_t) 0x7FFE0000UL; \ + __asm__("" : "+r"(shared_user_data)); /* https://github.com/lhmouse/mcfgthread/issues/330 */ \ + shared_user_data; \ + }) +#else + #define SharedUserData ((const volatile KUSER_SHARED_DATA*) (uintptr_t) 0x7FFE0000UL) +#endif static inline uint64_t ffKSystemTimeToUInt64(const volatile KSYSTEM_TIME* pTime) { #if _WIN64 From a923f61115897a2f3349b4629d99b28870ab9c5e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fran=C3=A7ois=20Revol?= Date: Sun, 10 May 2026 03:35:25 +0200 Subject: [PATCH 67/92] Wallpaper (Haiku): adds support (#2314) * Wallpaper (Haiku): Add support * Refactor wallpaper detection logic and memory handling --------- Co-authored-by: Carter Li --- CMakeLists.txt | 2 +- src/detection/wallpaper/wallpaper_haiku.cpp | 54 +++++++++++++++++++++ 2 files changed, 55 insertions(+), 1 deletion(-) create mode 100644 src/detection/wallpaper/wallpaper_haiku.cpp diff --git a/CMakeLists.txt b/CMakeLists.txt index b671ea527e..eca8868449 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1240,7 +1240,7 @@ elseif(Haiku) src/detection/tpm/tpm_nosupport.c src/detection/uptime/uptime_haiku.c src/detection/users/users_linux.c - src/detection/wallpaper/wallpaper_nosupport.c + src/detection/wallpaper/wallpaper_haiku.cpp src/detection/wifi/wifi_nosupport.c src/detection/wm/wm_nosupport.c src/detection/de/de_nosupport.c diff --git a/src/detection/wallpaper/wallpaper_haiku.cpp b/src/detection/wallpaper/wallpaper_haiku.cpp new file mode 100644 index 0000000000..0bfad62bb7 --- /dev/null +++ b/src/detection/wallpaper/wallpaper_haiku.cpp @@ -0,0 +1,54 @@ +extern "C" { +#include "wallpaper.h" +#include "common/mallocHelper.h" +} + +#include +#include +#include +#include +#include +#include +#include +#include + +const char* ffDetectWallpaper(FFstrbuf* result) { + BMessage backgrounds; + BPath pDesktop; + BString path; + + if (find_directory(B_DESKTOP_DIRECTORY, &pDesktop) < B_OK) { + return "find_directory(B_DESKTOP_DIRECTORY) failed"; + } + + // We need a valid be_app to query the app_server here. + BApplication app("application/x-vnd.fastfetch-cli-fastfetch"); + + BNode nDesktop(pDesktop.Path()); + if (nDesktop.InitCheck() == B_OK) { + struct attr_info ai; + if (nDesktop.GetAttrInfo(B_BACKGROUND_INFO, &ai) == B_OK && ai.size > 0) { + FF_AUTO_FREE char* pAttr = (char*) malloc(ai.size); + if (nDesktop.ReadAttr(B_BACKGROUND_INFO, ai.type, 0LL, pAttr, (size_t) ai.size) >= B_OK) { + if (backgrounds.Unflatten(pAttr) == B_OK) { + for (int i = 0; backgrounds.FindString(B_BACKGROUND_IMAGE, i, &path) == B_OK; i++) { + int32 ws; + if (backgrounds.FindInt32(B_BACKGROUND_WORKSPACES, i, &ws) == B_OK) { + if (ws & (1 << current_workspace())) { + // We try to match the one for the current workspace + break; + } + } + } + } + } + } + } + + if (path.Length() < 1) { + return "Failed to detect the current wallpaper path"; + } + + ffStrbufAppendS(result, path.String()); + return NULL; +} From fbc9569e1372f4468acebec89a24421f21db6e5f Mon Sep 17 00:00:00 2001 From: Carter Li Date: Sun, 10 May 2026 14:59:16 +0800 Subject: [PATCH 68/92] DisplayServer (Linux): updates `kde-output-device-v2` again --- .../kde-output-device-v2-client-protocol.h | 84 ++++++++++++++++--- .../wayland/kde-output-device-v2-protocol.c | 11 ++- .../displayserver/linux/wayland/kde-output.c | 3 + 3 files changed, 84 insertions(+), 14 deletions(-) diff --git a/src/detection/displayserver/linux/wayland/kde-output-device-v2-client-protocol.h b/src/detection/displayserver/linux/wayland/kde-output-device-v2-client-protocol.h index cc6db8ed8f..44ab8ab13a 100644 --- a/src/detection/displayserver/linux/wayland/kde-output-device-v2-client-protocol.h +++ b/src/detection/displayserver/linux/wayland/kde-output-device-v2-client-protocol.h @@ -249,10 +249,12 @@ kde_output_device_registry_v2_destroy(struct kde_output_device_registry_v2* kde_ * request until the kde_output_device_registry_v2.finished event is sent. */ // static inline void -// kde_output_device_registry_v2_stop(struct kde_output_device_registry_v2 *kde_output_device_registry_v2) -// { -// wl_proxy_marshal_flags((struct wl_proxy *) kde_output_device_registry_v2, -// KDE_OUTPUT_DEVICE_REGISTRY_V2_STOP, NULL, wl_proxy_get_version((struct wl_proxy *) kde_output_device_registry_v2), 0); +// kde_output_device_registry_v2_stop(struct kde_output_device_registry_v2* kde_output_device_registry_v2) { +// wl_proxy_marshal_flags((struct wl_proxy*) kde_output_device_registry_v2, +// KDE_OUTPUT_DEVICE_REGISTRY_V2_STOP, +// NULL, +// wl_proxy_get_version((struct wl_proxy*) kde_output_device_registry_v2), +// 0); // } #ifndef KDE_OUTPUT_DEVICE_V2_SUBPIXEL_ENUM @@ -383,6 +385,16 @@ enum kde_output_device_v2_capability { * @since 19 */ KDE_OUTPUT_DEVICE_V2_CAPABILITY_AUTO_BRIGHTNESS = 0x4000, + /** + * if this outputdevice supports HDR ICC profiles + * @since 22 + */ + KDE_OUTPUT_DEVICE_V2_CAPABILITY_HDR_ICC_PROFILE = 0x8000, + /** + * if this outputdevice supports the abm level setting + * @since 23 + */ + KDE_OUTPUT_DEVICE_V2_CAPABILITY_ABM_LEVEL = 0x10000, }; /** * @ingroup iface_kde_output_device_v2 @@ -432,6 +444,14 @@ enum kde_output_device_v2_capability { * @ingroup iface_kde_output_device_v2 */ #define KDE_OUTPUT_DEVICE_V2_CAPABILITY_AUTO_BRIGHTNESS_SINCE_VERSION 19 + /** + * @ingroup iface_kde_output_device_v2 + */ + #define KDE_OUTPUT_DEVICE_V2_CAPABILITY_HDR_ICC_PROFILE_SINCE_VERSION 22 + /** + * @ingroup iface_kde_output_device_v2 + */ + #define KDE_OUTPUT_DEVICE_V2_CAPABILITY_ABM_LEVEL_SINCE_VERSION 23 #endif /* KDE_OUTPUT_DEVICE_V2_CAPABILITY_ENUM */ #ifndef KDE_OUTPUT_DEVICE_V2_VRR_POLICY_ENUM @@ -772,7 +792,7 @@ struct kde_output_device_v2_listener { struct kde_output_device_v2* kde_output_device_v2, uint32_t policy); /** - * describes when auto rotate is used + * describes the path to the ICC profile used in SDR mode * * * @since 5 @@ -821,7 +841,7 @@ struct kde_output_device_v2_listener { struct kde_output_device_v2* kde_output_device_v2, uint32_t gamut_wideness); /** - * describes which source the compositor uses for the color profile on an output + * describes which source the compositor uses for the color profile on an output in SDR mode * * * @since 7 @@ -983,6 +1003,36 @@ struct kde_output_device_v2_listener { */ void (*removed)(void* data, struct kde_output_device_v2* kde_output_device_v2); + /** + * describes the path to the ICC profile used in HDR mode + * + * + * @since 22 + */ + void (*hdr_icc_profile_path)(void* data, + struct kde_output_device_v2* kde_output_device_v2, + const char* profile_path); + /** + * describes which source the compositor uses for the color profile on an output in HDR mode + * + * + * @since 22 + */ + void (*hdr_color_profile_source)(void* data, + struct kde_output_device_v2* kde_output_device_v2, + uint32_t source); + /** + * allowed level of adaptive backlight modulation + * + * Adaptive backlight modulation is a feature that reduces the + * backlight and increases contrast of colors on the screen to + * improve power usage. + * @param level 0 is off, 4 is the maximum level + * @since 23 + */ + void (*abm_level)(void* data, + struct kde_output_device_v2* kde_output_device_v2, + uint32_t level); }; /** @@ -1147,6 +1197,18 @@ kde_output_device_v2_add_listener(struct kde_output_device_v2* kde_output_device * @ingroup iface_kde_output_device_v2 */ #define KDE_OUTPUT_DEVICE_V2_REMOVED_SINCE_VERSION 21 +/** + * @ingroup iface_kde_output_device_v2 + */ +#define KDE_OUTPUT_DEVICE_V2_HDR_ICC_PROFILE_PATH_SINCE_VERSION 22 +/** + * @ingroup iface_kde_output_device_v2 + */ +#define KDE_OUTPUT_DEVICE_V2_HDR_COLOR_PROFILE_SOURCE_SINCE_VERSION 22 +/** + * @ingroup iface_kde_output_device_v2 + */ +#define KDE_OUTPUT_DEVICE_V2_ABM_LEVEL_SINCE_VERSION 23 /** * @ingroup iface_kde_output_device_v2 @@ -1183,10 +1245,12 @@ kde_output_device_v2_destroy(struct kde_output_device_v2* kde_output_device_v2) * the kde_output_device_v2 object. */ // static inline void -// kde_output_device_v2_release(struct kde_output_device_v2 *kde_output_device_v2) -// { -// wl_proxy_marshal_flags((struct wl_proxy *) kde_output_device_v2, -// KDE_OUTPUT_DEVICE_V2_RELEASE, NULL, wl_proxy_get_version((struct wl_proxy *) kde_output_device_v2), WL_MARSHAL_FLAG_DESTROY); +// kde_output_device_v2_release(struct kde_output_device_v2* kde_output_device_v2) { +// wl_proxy_marshal_flags((struct wl_proxy*) kde_output_device_v2, +// KDE_OUTPUT_DEVICE_V2_RELEASE, +// NULL, +// wl_proxy_get_version((struct wl_proxy*) kde_output_device_v2), +// WL_MARSHAL_FLAG_DESTROY); // } #ifndef KDE_OUTPUT_DEVICE_MODE_V2_FLAGS_ENUM diff --git a/src/detection/displayserver/linux/wayland/kde-output-device-v2-protocol.c b/src/detection/displayserver/linux/wayland/kde-output-device-v2-protocol.c index bcafaf1523..9dd7556765 100644 --- a/src/detection/displayserver/linux/wayland/kde-output-device-v2-protocol.c +++ b/src/detection/displayserver/linux/wayland/kde-output-device-v2-protocol.c @@ -45,7 +45,7 @@ static const struct wl_message kde_output_device_registry_v2_events[] = { WL_EXPORT const struct wl_interface kde_output_device_registry_v2_interface = { "kde_output_device_registry_v2", - 21, + 23, 1, kde_output_device_registry_v2_requests, 2, @@ -94,14 +94,17 @@ static const struct wl_message kde_output_device_v2_events[] = { { "priority", "18u", kde_output_device_v2_types + 0 }, { "auto_brightness", "20u", kde_output_device_v2_types + 0 }, { "removed", "21", kde_output_device_v2_types + 0 }, + { "hdr_icc_profile_path", "22s", kde_output_device_v2_types + 0 }, + { "hdr_color_profile_source", "22u", kde_output_device_v2_types + 0 }, + { "abm_level", "23u", kde_output_device_v2_types + 0 }, }; WL_EXPORT const struct wl_interface kde_output_device_v2_interface = { "kde_output_device_v2", - 21, + 23, 1, kde_output_device_v2_requests, - 37, + 40, kde_output_device_v2_events, }; @@ -115,7 +118,7 @@ static const struct wl_message kde_output_device_mode_v2_events[] = { WL_EXPORT const struct wl_interface kde_output_device_mode_v2_interface = { "kde_output_device_mode_v2", - 21, + 22, 0, NULL, 5, diff --git a/src/detection/displayserver/linux/wayland/kde-output.c b/src/detection/displayserver/linux/wayland/kde-output.c index 26be57e60b..feb9c146da 100644 --- a/src/detection/displayserver/linux/wayland/kde-output.c +++ b/src/detection/displayserver/linux/wayland/kde-output.c @@ -180,6 +180,9 @@ static struct kde_output_device_v2_listener outputListener = { .priority = (void*) stubListener, .auto_brightness = (void*) stubListener, .removed = (void*) stubListener, + .hdr_icc_profile_path = (void*) stubListener, + .hdr_color_profile_source = (void*) stubListener, + .abm_level = (void*) stubListener, }; const char* ffWaylandHandleKdeOutput(WaylandData* wldata, struct wl_registry* registry, uint32_t name, uint32_t version) { From b37f0202701eb0ae595ccbab450cc50bbaf404ff Mon Sep 17 00:00:00 2001 From: Carter Li Date: Sun, 10 May 2026 15:36:52 +0800 Subject: [PATCH 69/92] DisplayServer (Linux): binds minimal version to avoid breakage --- .../linux/wayland/global-output.c | 13 +++++++---- .../displayserver/linux/wayland/kde-output.c | 23 ++++++++----------- .../displayserver/linux/wayland/wayland.h | 4 ++++ .../displayserver/linux/wayland/zwlr-output.c | 15 ++++++------ 4 files changed, 30 insertions(+), 25 deletions(-) diff --git a/src/detection/displayserver/linux/wayland/global-output.c b/src/detection/displayserver/linux/wayland/global-output.c index 50c4577d32..7627682d5a 100644 --- a/src/detection/displayserver/linux/wayland/global-output.c +++ b/src/detection/displayserver/linux/wayland/global-output.c @@ -71,7 +71,9 @@ static struct zxdg_output_v1_listener zxdgOutputListener = { }; const char* ffWaylandHandleGlobalOutput(WaylandData* wldata, struct wl_registry* registry, uint32_t name, uint32_t version) { - struct wl_proxy* output = wldata->ffwl_proxy_marshal_constructor_versioned((struct wl_proxy*) registry, WL_REGISTRY_BIND, wldata->ffwl_output_interface, version, name, wldata->ffwl_output_interface->name, version, NULL); + const char* api = "wayland-global"; + uint32_t bindVersion = min(version, WL_OUTPUT_DESCRIPTION_SINCE_VERSION); + struct wl_proxy* output = wldata->ffwl_proxy_marshal_constructor_versioned((struct wl_proxy*) registry, WL_REGISTRY_BIND, wldata->ffwl_output_interface, bindVersion, name, wldata->ffwl_output_interface->name, bindVersion, NULL); if (output == NULL) { return "Failed to create wl_output"; } @@ -95,12 +97,14 @@ const char* ffWaylandHandleGlobalOutput(WaylandData* wldata, struct wl_registry* } if (wldata->zxdgOutputManager) { - struct wl_proxy* zxdgOutput = wldata->ffwl_proxy_marshal_constructor_versioned(wldata->zxdgOutputManager, ZXDG_OUTPUT_MANAGER_V1_GET_XDG_OUTPUT, &zxdg_output_v1_interface, version, NULL, output); + uint32_t bindVersion = min(version, ZXDG_OUTPUT_V1_DESCRIPTION_SINCE_VERSION); + struct wl_proxy* zxdgOutput = wldata->ffwl_proxy_marshal_constructor_versioned(wldata->zxdgOutputManager, ZXDG_OUTPUT_MANAGER_V1_GET_XDG_OUTPUT, &zxdg_output_v1_interface, bindVersion, NULL, output); if (zxdgOutput) { wldata->ffwl_proxy_add_listener(zxdgOutput, (void (**)(void)) &zxdgOutputListener, &display); wldata->ffwl_display_roundtrip(wldata->display); wldata->ffwl_proxy_destroy(zxdgOutput); + api = "wayland-global-zxdg"; } } @@ -132,7 +136,7 @@ const char* ffWaylandHandleGlobalOutput(WaylandData* wldata, struct wl_registry* display.id, (uint32_t) display.physicalWidth, (uint32_t) display.physicalHeight, - "wayland-global"); + api); if (item) { if (display.hdrSupported) { item->hdrStatus = FF_DISPLAY_HDR_STATUS_SUPPORTED; @@ -155,7 +159,8 @@ const char* ffWaylandHandleGlobalOutput(WaylandData* wldata, struct wl_registry* } const char* ffWaylandHandleZxdgOutput(WaylandData* wldata, struct wl_registry* registry, uint32_t name, uint32_t version) { - struct wl_proxy* manager = wldata->ffwl_proxy_marshal_constructor_versioned((struct wl_proxy*) registry, WL_REGISTRY_BIND, &zxdg_output_manager_v1_interface, version, name, zxdg_output_manager_v1_interface.name, version, NULL); + uint32_t bindVersion = min(version, ZXDG_OUTPUT_MANAGER_V1_GET_XDG_OUTPUT_SINCE_VERSION); + struct wl_proxy* manager = wldata->ffwl_proxy_marshal_constructor_versioned((struct wl_proxy*) registry, WL_REGISTRY_BIND, &zxdg_output_manager_v1_interface, bindVersion, name, zxdg_output_manager_v1_interface.name, bindVersion, NULL); if (manager == NULL) { return "Failed to create zxdg_output_manager_v1"; } diff --git a/src/detection/displayserver/linux/wayland/kde-output.c b/src/detection/displayserver/linux/wayland/kde-output.c index feb9c146da..43a6f373a4 100644 --- a/src/detection/displayserver/linux/wayland/kde-output.c +++ b/src/detection/displayserver/linux/wayland/kde-output.c @@ -176,17 +176,11 @@ static struct kde_output_device_v2_listener outputListener = { .max_bits_per_color_range = (void*) stubListener, .automatic_max_bits_per_color_limit = (void*) stubListener, .edr_policy = (void*) stubListener, - .sharpness = (void*) stubListener, - .priority = (void*) stubListener, - .auto_brightness = (void*) stubListener, - .removed = (void*) stubListener, - .hdr_icc_profile_path = (void*) stubListener, - .hdr_color_profile_source = (void*) stubListener, - .abm_level = (void*) stubListener, }; const char* ffWaylandHandleKdeOutput(WaylandData* wldata, struct wl_registry* registry, uint32_t name, uint32_t version) { - struct wl_proxy* output = wldata->ffwl_proxy_marshal_constructor_versioned((struct wl_proxy*) registry, WL_REGISTRY_BIND, &kde_output_device_v2_interface, version, name, kde_output_device_v2_interface.name, version, NULL); + uint32_t bindVersion = min(version, KDE_OUTPUT_DEVICE_V2_MAX_BITS_PER_COLOR_SINCE_VERSION); + struct wl_proxy* output = wldata->ffwl_proxy_marshal_constructor_versioned((struct wl_proxy*) registry, WL_REGISTRY_BIND, &kde_output_device_v2_interface, bindVersion, name, kde_output_device_v2_interface.name, bindVersion, NULL); if (output == NULL) { return "Failed to create kde_output_device_v2"; } @@ -268,17 +262,18 @@ static void waylandKdeOutputOrderListener(void* data, FF_A_UNUSED struct kde_out } } +static const struct kde_output_order_v1_listener orderListener = { + .output = waylandKdeOutputOrderListener, + .done = (void*) stubListener, +}; + const char* ffWaylandHandleKdeOutputOrder(WaylandData* wldata, struct wl_registry* registry, uint32_t name, uint32_t version) { - struct wl_proxy* output = wldata->ffwl_proxy_marshal_constructor_versioned((struct wl_proxy*) registry, WL_REGISTRY_BIND, &kde_output_order_v1_interface, version, name, kde_output_order_v1_interface.name, version, NULL); + uint32_t bindVersion = min(version, KDE_OUTPUT_ORDER_V1_OUTPUT_SINCE_VERSION); + struct wl_proxy* output = wldata->ffwl_proxy_marshal_constructor_versioned((struct wl_proxy*) registry, WL_REGISTRY_BIND, &kde_output_order_v1_interface, bindVersion, name, kde_output_order_v1_interface.name, bindVersion, NULL); if (output == NULL) { return "Failed to create kde_output_order_v1"; } - struct kde_output_order_v1_listener orderListener = { - .output = waylandKdeOutputOrderListener, - .done = (void*) stubListener, - }; - if (wldata->ffwl_proxy_add_listener(output, (void (**)(void)) &orderListener, &wldata->primaryDisplayId) < 0) { wldata->ffwl_proxy_destroy(output); return "Failed to add listener to kde_output_order_v1"; diff --git a/src/detection/displayserver/linux/wayland/wayland.h b/src/detection/displayserver/linux/wayland/wayland.h index ac0a1d3867..141b3964d0 100644 --- a/src/detection/displayserver/linux/wayland/wayland.h +++ b/src/detection/displayserver/linux/wayland/wayland.h @@ -9,6 +9,10 @@ #include "../displayserver_linux.h" +static inline uint32_t min(uint32_t a, uint32_t b) { + return a < b ? a : b; +} + typedef enum FF_A_PACKED WaylandProtocolType { FF_WAYLAND_PROTOCOL_TYPE_NONE, FF_WAYLAND_PROTOCOL_TYPE_GLOBAL, diff --git a/src/detection/displayserver/linux/wayland/zwlr-output.c b/src/detection/displayserver/linux/wayland/zwlr-output.c index 1caae8454c..632bd19b40 100644 --- a/src/detection/displayserver/linux/wayland/zwlr-output.c +++ b/src/detection/displayserver/linux/wayland/zwlr-output.c @@ -183,18 +183,19 @@ static void waylandHandleZwlrHead(void* data, FF_A_UNUSED struct zwlr_output_man wldata->ffwl_proxy_destroy((void*) head); } +static const struct zwlr_output_manager_v1_listener outputListener = { + .head = waylandHandleZwlrHead, + .done = (void*) stubListener, + .finished = (void*) stubListener, +}; + const char* ffWaylandHandleZwlrOutput(WaylandData* wldata, struct wl_registry* registry, uint32_t name, uint32_t version) { - struct wl_proxy* output = wldata->ffwl_proxy_marshal_constructor_versioned((struct wl_proxy*) registry, WL_REGISTRY_BIND, &zwlr_output_manager_v1_interface, version, name, zwlr_output_manager_v1_interface.name, version, NULL); + uint32_t bindVersion = min(version, ZWLR_OUTPUT_MANAGER_V1_HEAD_SINCE_VERSION); + struct wl_proxy* output = wldata->ffwl_proxy_marshal_constructor_versioned((struct wl_proxy*) registry, WL_REGISTRY_BIND, &zwlr_output_manager_v1_interface, bindVersion, name, zwlr_output_manager_v1_interface.name, bindVersion, NULL); if (output == NULL) { return "Failed to bind zwlr_output_manager_v1"; } - const struct zwlr_output_manager_v1_listener outputListener = { - .head = waylandHandleZwlrHead, - .done = (void*) stubListener, - .finished = (void*) stubListener, - }; - if (wldata->ffwl_proxy_add_listener(output, (void (**)(void)) &outputListener, wldata) < 0) { wldata->ffwl_proxy_destroy(output); return "Failed to add listener to zwlr_output_manager_v1"; From 1c89bbc34e34ec1071dec9359ae3cec767bfc85e Mon Sep 17 00:00:00 2001 From: Carter Li Date: Sun, 10 May 2026 16:00:22 +0800 Subject: [PATCH 70/92] Chore: `clang-format` --- src/common/impl/netif_apple.c | 6 ++---- src/common/impl/parsing.c | 1 - src/common/windows/wmi.h | 9 +++------ src/detection/camera/camera_windows.cpp | 2 +- 4 files changed, 6 insertions(+), 12 deletions(-) diff --git a/src/common/impl/netif_apple.c b/src/common/impl/netif_apple.c index 85614acb29..2d42735ffd 100644 --- a/src/common/impl/netif_apple.c +++ b/src/common/impl/netif_apple.c @@ -117,8 +117,7 @@ bool ffNetifGetDefaultRouteImplV4(FFNetifDefaultRouteResult* result) { #ifndef __sun && sdl->sdl_len #endif - && sdl->sdl_family == AF_LINK - ) { + && sdl->sdl_family == AF_LINK) { if (sdl->sdl_nlen > IF_NAMESIZE) { return false; } @@ -202,8 +201,7 @@ bool ffNetifGetDefaultRouteImplV6(FFNetifDefaultRouteResult* result) { #ifndef __sun && sdl->sdl_len #endif - && sdl->sdl_family == AF_LINK - ) { + && sdl->sdl_family == AF_LINK) { if (sdl->sdl_nlen > IF_NAMESIZE) { return false; } diff --git a/src/common/impl/parsing.c b/src/common/impl/parsing.c index 7fec0a77f1..e88d10cd59 100644 --- a/src/common/impl/parsing.c +++ b/src/common/impl/parsing.c @@ -1,7 +1,6 @@ #include "fastfetch.h" #include "common/parsing.h" - #ifdef _WIN32 #pragma GCC diagnostic push #pragma GCC diagnostic ignored "-Wformat" diff --git a/src/common/windows/wmi.h b/src/common/windows/wmi.h index da10f21b09..aa50c00439 100644 --- a/src/common/windows/wmi.h +++ b/src/common/windows/wmi.h @@ -16,8 +16,7 @@ NTSYSAPI ULONG NTAPI WmiOpenBlock( _In_ LPCGUID Guid, _In_ ACCESS_MASK DesiredAccess, - _Out_ PHANDLE DataBlockHandle -); + _Out_ PHANDLE DataBlockHandle); /** * The WmiQueryAllDataW function returns all WMI data blocks that implement a given WMI class (Unicode). @@ -31,8 +30,7 @@ NTSYSAPI ULONG NTAPI WmiQueryAllDataW( _In_ HANDLE DataBlockHandle, _Inout_ PULONG BufferLength, - _Out_writes_bytes_opt_(*BufferLength) PVOID Buffer -); + _Out_writes_bytes_opt_(*BufferLength) PVOID Buffer); /** * The WmiCloseBlock function closes a WMI data block object. @@ -42,8 +40,7 @@ WmiQueryAllDataW( */ NTSYSAPI ULONG NTAPI WmiCloseBlock( - _In_ HANDLE DataBlockHandle -); + _In_ HANDLE DataBlockHandle); static inline void ffCloseWmiBlock(HANDLE* hBlock) { assert(hBlock); diff --git a/src/detection/camera/camera_windows.cpp b/src/detection/camera/camera_windows.cpp index 087cc6d5fb..1f42306962 100644 --- a/src/detection/camera/camera_windows.cpp +++ b/src/detection/camera/camera_windows.cpp @@ -133,7 +133,7 @@ extern "C" const char* ffDetectCamera(FF_A_UNUSED FFlist* result) { case MFVideoPrimaries_ACES: ffStrbufSetStatic(&camera->colorspace, "ACES"); break; - case (MFVideoPrimaries)13: // MFVideoPrimaries_Display_P3 + case (MFVideoPrimaries) 13: // MFVideoPrimaries_Display_P3 ffStrbufSetStatic(&camera->colorspace, "Display P3"); break; default: From f8667d9faae43a96104a5d937e384c6fb4dfe10c Mon Sep 17 00:00:00 2001 From: Carter Li Date: Sun, 10 May 2026 16:12:22 +0800 Subject: [PATCH 71/92] Doc: updates changelog --- CHANGELOG.md | 27 +++++++++++++++++++++++++++ 1 file changed, 27 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 8e5ea889ff..40353b73ad 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,30 @@ +# 2.63.0 + +Changes: +* On Windows, multi-battery detection is now always enabled + * Removes the old Windows-specific `useSetupApi` option. (Battery, Windows) + +Features: +* Adds wallpaper detection support on Haiku (#2314, Wallpaper, Haiku) +* Adds Wi-Fi 6 GHz channel detection and improves protocol reporting (Wifi, Linux) +* Improves Deepin version detection using `/etc/os-version` (#2300, OS, Linux) +* Adds Ubuntu Kylin and Ubuntu Unity flavor detection (OS, Linux) +* Adds NebiDE support (WMTheme, Linux) +* Adds package counting for `cards` on NuTyX (#2287, Packages, Linux) + +Bugfixes: +* Improves DisplayServer compatibility on Linux by handling newer `kde-output-device-v2` protocol updates (DisplayServer, Linux) + * This fixes a long-standing issue where display detection fails with an `interface 'kde_output_device_mode_v2' has no event X` error. +* Improves Wi-Fi reliability on Linux by switching to a netlink implementation (Wifi, Linux) +* Improves network interface reliability and default route selection on macOS and Windows (Netif, macOS / Windows) +* Validates temperature color thresholds as integers when parsing JSON config (Temps) +* Fixes GNOME OS builtin logo detection (#2296) +* Various internal cleanups and optimizations + +Logos: +* Adds KibaOS and NebiOS +* Removes Ubuntu KDE + # 2.62.1 Bugfixes: From 60fbe1fd0b821e044bf47628a75cffa3bc757234 Mon Sep 17 00:00:00 2001 From: Carter Li Date: Sun, 10 May 2026 16:37:00 +0800 Subject: [PATCH 72/92] Common (Windows): uses WinRT APIs only when `FF_HAVE_WINRT` is defined --- CMakeLists.txt | 7 +++---- src/common/windows/com.c | 30 +++++++++++++++++++++++++++++- 2 files changed, 32 insertions(+), 5 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index eca8868449..c2c84cd5fc 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1340,9 +1340,9 @@ if(WIN32) include(CheckIncludeFileCXX) CHECK_INCLUDE_FILE_CXX("winrt/Windows.Foundation.h" HAVE_WINRT) if(HAVE_WINRT) - message(STATUS "WinRT headers are available, media detection will use WinRT APIs") + message(STATUS "WinRT headers are available, media detection will be enabled") else() - message(STATUS "WinRT headers are NOT available, media detection will be disabled") + message(WARNING "WinRT headers are NOT available, media detection will be disabled") endif() endif() include(CheckFunctionExists) @@ -1759,8 +1759,7 @@ elseif(WIN32) endif() if(HAVE_WINRT) target_link_libraries(libfastfetch - PRIVATE - "RuntimeObject" + PRIVATE "runtimeobject" ) endif() elseif(FreeBSD) diff --git a/src/common/windows/com.c b/src/common/windows/com.c index 0eb566f8b4..3f2fa3987b 100644 --- a/src/common/windows/com.c +++ b/src/common/windows/com.c @@ -1,6 +1,7 @@ #include "com.h" -#include +#if FF_HAVE_WINRT + #include static void RoUninitializeWrap(void) { RoUninitialize(); @@ -27,6 +28,33 @@ static const char* doInitCom() { atexit(RoUninitializeWrap); return NULL; } +#else + #include + +static void CoUninitializeWrap(void) { + CoUninitialize(); +} + +static const char* doInitCom() { + HRESULT res = CoInitializeEx(NULL, COINIT_MULTITHREADED); + if (FAILED(res)) { + switch (res) { + case E_INVALIDARG: + return "CoInitializeEx() failed: invalid argument"; + case E_OUTOFMEMORY: + return "CoInitializeEx() failed: out of memory"; + case RPC_E_CHANGED_MODE: + // COM was already initialized with a different concurrency model + return NULL; + default: + return "CoInitializeEx() failed: unknown error"; + } + } + + atexit(CoUninitializeWrap); + return NULL; +} +#endif const char* ffInitCom(void) { static const char* error = ""; From 0747fc088768665f0770b830598e6d798dab0a7e Mon Sep 17 00:00:00 2001 From: Carter Li Date: Sun, 10 May 2026 17:33:40 +0800 Subject: [PATCH 73/92] CI (OmniOS): fixes build --- .github/workflows/build-omnios-amd64.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/build-omnios-amd64.yml b/.github/workflows/build-omnios-amd64.yml index b7a9298186..9667cbe677 100644 --- a/.github/workflows/build-omnios-amd64.yml +++ b/.github/workflows/build-omnios-amd64.yml @@ -20,7 +20,7 @@ jobs: envs: 'CMAKE_BUILD_TYPE' prepare: | uname -a - pkg update --accept + pkg update --accept || true pkg install gcc14 cmake git pkg-config glib2 dbus sqlite-3 imagemagick ninja run: | From 26b3a84b8afc6b76e2ccab4403878a85ef72f224 Mon Sep 17 00:00:00 2001 From: Carter Li Date: Mon, 11 May 2026 08:51:41 +0800 Subject: [PATCH 74/92] Terminal: fixes TDE konsole version detection Fixes #2319 --- CHANGELOG.md | 3 ++- src/detection/terminalshell/terminalshell.c | 6 +++++- 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 40353b73ad..8aed4586bd 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -18,11 +18,12 @@ Bugfixes: * Improves Wi-Fi reliability on Linux by switching to a netlink implementation (Wifi, Linux) * Improves network interface reliability and default route selection on macOS and Windows (Netif, macOS / Windows) * Validates temperature color thresholds as integers when parsing JSON config (Temps) -* Fixes GNOME OS builtin logo detection (#2296) +* Fixes TDE konsole version detection (#2319, Terminal, Linux) * Various internal cleanups and optimizations Logos: * Adds KibaOS and NebiOS +* Fixes GNOME OS builtin logo detection (#2296) * Removes Ubuntu KDE # 2.62.1 diff --git a/src/detection/terminalshell/terminalshell.c b/src/detection/terminalshell/terminalshell.c index c02cc6c465..10f365cfc3 100644 --- a/src/detection/terminalshell/terminalshell.c +++ b/src/detection/terminalshell/terminalshell.c @@ -365,7 +365,11 @@ FF_A_UNUSED static bool getTerminalVersionKonsole(FFstrbuf* exe, FFstrbuf* versi } } - return getExeVersionGeneral(exe, version); + // Likely TDE konsole. See #2319 + if (!getExeVersionRaw(exe, version)) { + return false; + } + return ffStrbufSubstrAfterLastC(version, ' '); } FF_A_UNUSED static bool getTerminalVersionFoot(FFstrbuf* exe, FFstrbuf* version) { From 92082eed549b06ca3d1082ddaa64cb7d3f70c75b Mon Sep 17 00:00:00 2001 From: Carter Li Date: Mon, 11 May 2026 08:56:31 +0800 Subject: [PATCH 75/92] Doc: update PR template [ci skip] --- .github/pull_request_template.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md index d99cb91ff0..3d62b16b6f 100644 --- a/.github/pull_request_template.md +++ b/.github/pull_request_template.md @@ -21,6 +21,10 @@ Closes # - +## Screenshots + + + ## Checklist - [ ] I have tested my changes locally. From 6c7a8a8c41e84c6f0c6e30665fbdd13700c08e76 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=9B=BD=E4=BC=81=E9=B1=BC?= <120998576+GuoqiFish@users.noreply.github.com> Date: Mon, 11 May 2026 09:36:32 +0800 Subject: [PATCH 76/92] Logo (Builtin): adds XJ380 OS Logo (#2309) --- src/logo/ascii/xj380.txt | 19 +++++++++++++++++++ src/logo/builtin.c | 9 +++++++++ 2 files changed, 28 insertions(+) create mode 100644 src/logo/ascii/xj380.txt diff --git a/src/logo/ascii/xj380.txt b/src/logo/ascii/xj380.txt new file mode 100644 index 0000000000..6d405efc2a --- /dev/null +++ b/src/logo/ascii/xj380.txt @@ -0,0 +1,19 @@ +$1 %% +$1 #*++++++% +$1 %**++++++++++++% +$1 @**+++++++++++++++++++% +$1 %**+++++++++++++++++++++++++% +$1 **+++++*++++++++++$2%%$1++++++++++++++% +$1 @%*++++++++++$2*%%*$1+++++++++++$2%$1++++++++++++% +$1 *%+++++++++++++++++$2%%%%$1+++++++++++++$2%$1++++++++++% +$1*++++++++++++++++++$2*%*$1+$2%%%%%%%%%$1++++++$2%$1+++++++++% +$1*++++++++++++++$2**$2%%%%%**$2%*$1++$2%%$1++++++++$2%%$1++++++++% +$1*+++++++++++$2%%%*$1++$2*%$1++++$2%%%%*$1++++++++$2%%%%$1+++++++% +$1*+++++$2%%%*$1++++$2%%%$1+$2%%$1++++$2%%%$1++++++++++$2*%%*$1+++++++% +$1*+++++$2%%%%$1+++++++$2%%*$1++$2*%%*%*$1++++++++++++++++++++% +$1*++++++$2%$1+++++++++$2%%%%%%$1+++$2%%$1++++++++++++++++++++% +$1*++++++$2%*$1+++++++$2*%+%%*%%*$1++$2%%$1+++++++++++++++++++% +$1*+++++++$2%$1+++++++$2%%%*$1++++$2*%%*%*$1++++++++++++++++++% +$1*++++++++$2*%*$1+++$2%%*$1+++++++++$2%%%$1++++++++++++++++++% +$1*++++++++++++$2%%$1++++++++++++++$2%$1++++++++++++++++++% +$1*+++++++++++++++++++++++++++++++++++++++++++++++% \ No newline at end of file diff --git a/src/logo/builtin.c b/src/logo/builtin.c index f362d9a9a2..a058cccabb 100644 --- a/src/logo/builtin.c +++ b/src/logo/builtin.c @@ -5496,6 +5496,15 @@ static const FFlogo X[] = { FF_COLOR_FG_BLUE, FF_COLOR_FG_CYAN, } }, + // XJ380 + { + .names = { "XJ380" }, + .lines = FASTFETCH_DATATEXT_LOGO_XJ380, + .colors = { + FF_COLOR_FG_RGB "0;162;232", + FF_COLOR_FG_RGB "255;242;0", + } + }, // LAST {}, }; From 50964fcddfe4d6129b32cfc763a5b11500159eb1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?HNO=E2=82=83?= Date: Mon, 11 May 2026 09:37:02 +0800 Subject: [PATCH 77/92] Logo (Builtin): adds openRuyi (#2318) * Logo: Add openRuyi * Update ASCII art in openruyi.txt * Fix casing of 'openRuyi' in builtin.c --------- Co-authored-by: Z572 Co-authored-by: Carter Li --- src/logo/ascii/openruyi.txt | 12 ++++++++++++ src/logo/builtin.c | 12 ++++++++++++ 2 files changed, 24 insertions(+) create mode 100644 src/logo/ascii/openruyi.txt diff --git a/src/logo/ascii/openruyi.txt b/src/logo/ascii/openruyi.txt new file mode 100644 index 0000000000..525bb66fa5 --- /dev/null +++ b/src/logo/ascii/openruyi.txt @@ -0,0 +1,12 @@ + ⣠⣴⣶⣾⣿⣿⣷⣶⣦⣄ + ⣠⣾⣿⡿⠟⠛⠉⠉⠛⠻⢿⣿⣷⣄ + ⣼⣿⡿⠋ ⠙⢿⣿⣧ + ⣀⣠⣿⣿⠁ ⠈⣿⣿⣄⣀ + ⢀⣴⣾⣿⣿⡿⠟$2 ⢀⣀⣀⣄⣀⣀ $1⠻⢿⣿⣿⣷⣦⡀ +$1 ⣰⣿⣿⠟⠉ $2 ⣠⣾⣿⡿⠿⠿⠿⢿⣿⣦⡀ $1 ⠉⠻⣿⣿⣆ +$1⢰⣿⡿⠁ $2⢰⣿⡿⠁$3⢀⣠⣄⡀$2 ⠙⣿⣷$1 ⠘⣿⣿⡆ +$1⣿⣿⡇ $2⠙⣿⠁$3⣴⣿⡿⠿⠟$2 ⢀⣿⣿$1 ⢸⣿⣿ +$1⣿⣿⡇ $3 ⣿⡟$2⢀⣀⣀⣴⣾⣿⠋ $1 ⢸⣿⣿ +$1⠸⣿⣷⡀ $3 ⣿⡇$2⠘⢿⣿⣿⡋ $1 ⢠⣾⣿⠇ +$1 ⠙⣿⣿⣶⣤⣀⣀⣀$3⣀⣀⣰⣿⡇$2 ⠙⢿⣿⣦⣀⣀$1⣀⣀⣤⣶⣿⣿⠋ +$1 ⠙⠿⢿⣿⣿⣿$3⣿⣿⣿⠟ $2 ⠉⠻⣿⣿$1⣿⣿⡿⠟⠋ diff --git a/src/logo/builtin.c b/src/logo/builtin.c index a058cccabb..6b92e9261b 100644 --- a/src/logo/builtin.c +++ b/src/logo/builtin.c @@ -3544,6 +3544,18 @@ static const FFlogo O[] = { FF_COLOR_FG_GREEN, }, }, + // openRuyi + { + .names = { "openRuyi" }, + .lines = FASTFETCH_DATATEXT_LOGO_OPENRUYI, + .colors = { + FF_COLOR_FG_BLUE, + FF_COLOR_FG_YELLOW, + FF_COLOR_FG_LIGHT_YELLOW, + }, + .colorKeys = FF_COLOR_FG_BLUE, + .colorTitle = FF_COLOR_FG_DEFAULT, + }, // OpenStage { .names = { "OpenStage" }, From 38e98d3d514453574d491bcb203744c448223122 Mon Sep 17 00:00:00 2001 From: Carter Li Date: Mon, 11 May 2026 09:37:57 +0800 Subject: [PATCH 78/92] Logo (Builtin): cleans up xj380 --- src/logo/ascii/xj380.txt | 38 +++++++++++++++++++------------------- 1 file changed, 19 insertions(+), 19 deletions(-) diff --git a/src/logo/ascii/xj380.txt b/src/logo/ascii/xj380.txt index 6d405efc2a..fd86e45f21 100644 --- a/src/logo/ascii/xj380.txt +++ b/src/logo/ascii/xj380.txt @@ -1,19 +1,19 @@ -$1 %% -$1 #*++++++% -$1 %**++++++++++++% -$1 @**+++++++++++++++++++% -$1 %**+++++++++++++++++++++++++% -$1 **+++++*++++++++++$2%%$1++++++++++++++% -$1 @%*++++++++++$2*%%*$1+++++++++++$2%$1++++++++++++% -$1 *%+++++++++++++++++$2%%%%$1+++++++++++++$2%$1++++++++++% -$1*++++++++++++++++++$2*%*$1+$2%%%%%%%%%$1++++++$2%$1+++++++++% -$1*++++++++++++++$2**$2%%%%%**$2%*$1++$2%%$1++++++++$2%%$1++++++++% -$1*+++++++++++$2%%%*$1++$2*%$1++++$2%%%%*$1++++++++$2%%%%$1+++++++% -$1*+++++$2%%%*$1++++$2%%%$1+$2%%$1++++$2%%%$1++++++++++$2*%%*$1+++++++% -$1*+++++$2%%%%$1+++++++$2%%*$1++$2*%%*%*$1++++++++++++++++++++% -$1*++++++$2%$1+++++++++$2%%%%%%$1+++$2%%$1++++++++++++++++++++% -$1*++++++$2%*$1+++++++$2*%+%%*%%*$1++$2%%$1+++++++++++++++++++% -$1*+++++++$2%$1+++++++$2%%%*$1++++$2*%%*%*$1++++++++++++++++++% -$1*++++++++$2*%*$1+++$2%%*$1+++++++++$2%%%$1++++++++++++++++++% -$1*++++++++++++$2%%$1++++++++++++++$2%$1++++++++++++++++++% -$1*+++++++++++++++++++++++++++++++++++++++++++++++% \ No newline at end of file + % + *++++++% + *+++++++++++++% + *++++++++++++++++++++% + *++++++++++++++++++++++++++% + *++++++*++++++++++$2%%$1+++++++++++++% + *+++++++++++$2*%%*$1+++++++++++$2%$1+++++++++++% + *++++++++++++++++$2%%%%$1+++++++++++++$2%$1+++++++++% +*++++++++++++++++++$2*%*$1+$2%%%%%%%%%$1++++++$2%$1++++++++% +*++++++++++++++$2**$2%%%%%**$2%*$1++$2%%$1++++++++$2%%$1+++++++% +*+++++++++++$2%%%*$1++$2*%$1++++$2%%%%*$1++++++++$2%%%%$1++++++% +*+++++$2%%%*$1++++$2%%%$1+$2%%$1++++$2%%%$1++++++++++$2*%%*$1++++++% +*+++++$2%%%%$1+++++++$2%%*$1++$2*%%*%*$1+++++++++++++++++++% +*++++++$2%$1+++++++++$2%%%%%%$1+++$2%%$1+++++++++++++++++++% +*++++++$2%*$1+++++++$2*%+%%*%%*$1++$2%%$1++++++++++++++++++% +*+++++++$2%$1+++++++$2%%%*$1++++$2*%%*%*$1+++++++++++++++++% +*++++++++$2*%*$1+++$2%%*$1+++++++++$2%%%$1+++++++++++++++++% +*++++++++++++$2%%$1++++++++++++++$2%$1+++++++++++++++++% +*++++++++++++++++++++++++++++++++++++++++++++++% \ No newline at end of file From 320e1bf4e0b0f28e1bee930497c909e7709d652e Mon Sep 17 00:00:00 2001 From: Carter Li Date: Mon, 11 May 2026 09:42:48 +0800 Subject: [PATCH 79/92] Doc: update changelog [ci skip] --- CHANGELOG.md | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 8aed4586bd..e4e1d05b53 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -22,9 +22,8 @@ Bugfixes: * Various internal cleanups and optimizations Logos: -* Adds KibaOS and NebiOS +* Adds KibaOS, NebiOS, XJ380 and openRuyi * Fixes GNOME OS builtin logo detection (#2296) -* Removes Ubuntu KDE # 2.62.1 From 8daec2568cf31388f1a0e0a35151fb362a93969c Mon Sep 17 00:00:00 2001 From: Carter Li Date: Mon, 11 May 2026 10:13:48 +0800 Subject: [PATCH 80/92] Common (Windows): adds a missing `#include` --- src/common/windows/com.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/common/windows/com.c b/src/common/windows/com.c index 3f2fa3987b..a400ca9c65 100644 --- a/src/common/windows/com.c +++ b/src/common/windows/com.c @@ -1,5 +1,7 @@ #include "com.h" +#include + #if FF_HAVE_WINRT #include From 5d29e26509f295c5e627c6760f2dfff068e5c094 Mon Sep 17 00:00:00 2001 From: Carter Li Date: Mon, 11 May 2026 11:03:15 +0800 Subject: [PATCH 81/92] Logo (Builtin): cleans up Kylin --- src/logo/builtin.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/logo/builtin.c b/src/logo/builtin.c index 6b92e9261b..26a6937892 100644 --- a/src/logo/builtin.c +++ b/src/logo/builtin.c @@ -2583,7 +2583,7 @@ static const FFlogo K[] = { }, // Kylin { - .names = { "Kylin", "kylin" }, + .names = { "Kylin" }, .lines = FASTFETCH_DATATEXT_LOGO_KYLIN, .colors = { FF_COLOR_FG_BLUE, FF_COLOR_FG_CYAN, FF_COLOR_FG_WHITE, FF_COLOR_FG_LIGHT_BLACK }, .colorKeys = FF_COLOR_FG_BLUE, From 43ddbdca85cd088d4af8e635e81820c0cf808e11 Mon Sep 17 00:00:00 2001 From: Carter Li Date: Mon, 11 May 2026 11:06:01 +0800 Subject: [PATCH 82/92] Logo (Builtin): tidy Fk `clang-format` --- .clang-format-ignore | 1 + src/logo/builtin.c | 329 ++++++++++++++++++++++++++++++------------- 2 files changed, 232 insertions(+), 98 deletions(-) diff --git a/.clang-format-ignore b/.clang-format-ignore index 2602732186..7f7c31cfef 100644 --- a/.clang-format-ignore +++ b/.clang-format-ignore @@ -1,2 +1,3 @@ src/3rdparty/** build/** +src/logo/builtin.c diff --git a/src/logo/builtin.c b/src/logo/builtin.c index 26a6937892..660aba1af0 100644 --- a/src/logo/builtin.c +++ b/src/logo/builtin.c @@ -174,25 +174,37 @@ static const FFlogo A[] = { .colorTitle = FF_COLOR_FG_YELLOW, }, // Amazon - { .names = { "Amazon" }, .lines = FASTFETCH_DATATEXT_LOGO_AMAZON, .colors = { - FF_COLOR_FG_YELLOW, - FF_COLOR_FG_WHITE, - } }, + { + .names = { "Amazon" }, + .lines = FASTFETCH_DATATEXT_LOGO_AMAZON, + .colors = { + FF_COLOR_FG_YELLOW, + FF_COLOR_FG_WHITE, + }, + }, // AmazonLinux - { .names = { "Amazon Linux", "amzn" }, .lines = FASTFETCH_DATATEXT_LOGO_AMAZON_LINUX, .colors = { - FF_COLOR_FG_WHITE, - FF_COLOR_FG_256 "178", - } }, + { + .names = { "Amazon Linux", "amzn" }, + .lines = FASTFETCH_DATATEXT_LOGO_AMAZON_LINUX, + .colors = { + FF_COLOR_FG_WHITE, + FF_COLOR_FG_256 "178", + }, + }, // Amiga - { .names = { "Amiga" }, .lines = FASTFETCH_DATATEXT_LOGO_AMIGA, .colors = { - FF_COLOR_FG_RED, - FF_COLOR_FG_LIGHT_RED, - FF_COLOR_FG_YELLOW, - FF_COLOR_FG_BLUE, - FF_COLOR_FG_CYAN, - FF_COLOR_FG_LIGHT_YELLOW, - FF_COLOR_FG_GREEN, - } }, + { + .names = { "Amiga" }, + .lines = FASTFETCH_DATATEXT_LOGO_AMIGA, + .colors = { + FF_COLOR_FG_RED, + FF_COLOR_FG_LIGHT_RED, + FF_COLOR_FG_YELLOW, + FF_COLOR_FG_BLUE, + FF_COLOR_FG_CYAN, + FF_COLOR_FG_LIGHT_YELLOW, + FF_COLOR_FG_GREEN, + }, + }, // AmogOS { .names = { "AmogOS" }, @@ -647,7 +659,12 @@ static const FFlogo A[] = { { .names = { "AsteroidOS" }, .lines = FASTFETCH_DATATEXT_LOGO_ASTEROIDOS, - .colors = { FF_COLOR_FG_256 "160", FF_COLOR_FG_256 "208", FF_COLOR_FG_256 "202", FF_COLOR_FG_256 "214" }, + .colors = { + FF_COLOR_FG_256 "160", + FF_COLOR_FG_256 "208", + FF_COLOR_FG_256 "202", + FF_COLOR_FG_256 "214", + }, .colorKeys = FF_COLOR_FG_256 "160", .colorTitle = FF_COLOR_FG_256 "208", }, @@ -727,10 +744,14 @@ static const FFlogo A[] = { }, }, // Azos - { .names = { "Azos" }, .lines = FASTFETCH_DATATEXT_LOGO_AZOS, .colors = { - FF_COLOR_FG_CYAN, - FF_COLOR_FG_RED, - } }, + { + .names = { "Azos" }, + .lines = FASTFETCH_DATATEXT_LOGO_AZOS, + .colors = { + FF_COLOR_FG_CYAN, + FF_COLOR_FG_RED, + }, + }, // LAST {}, }; @@ -1038,7 +1059,13 @@ static const FFlogo C[] = { { .names = { "Cereus", "Cereus Linux" }, .lines = FASTFETCH_DATATEXT_LOGO_CEREUS, - .colors = { FF_COLOR_FG_256 "173", FF_COLOR_FG_256 "108", FF_COLOR_FG_256 "71", FF_COLOR_FG_256 "151", FF_COLOR_FG_256 "72" }, + .colors = { + FF_COLOR_FG_256 "173", + FF_COLOR_FG_256 "108", + FF_COLOR_FG_256 "71", + FF_COLOR_FG_256 "151", + FF_COLOR_FG_256 "72", + }, .colorKeys = FF_COLOR_FG_256 "108", .colorTitle = FF_COLOR_MODE_BOLD FF_COLOR_FG_WHITE, }, @@ -1056,7 +1083,10 @@ static const FFlogo C[] = { { .names = { "ChaletOS" }, .lines = FASTFETCH_DATATEXT_LOGO_CHALETOS, - .colors = { FF_COLOR_FG_BLUE, FF_COLOR_FG_WHITE }, + .colors = { + FF_COLOR_FG_BLUE, + FF_COLOR_FG_WHITE, + }, .colorKeys = FF_COLOR_FG_BLUE, .colorTitle = FF_COLOR_FG_DEFAULT, }, @@ -1179,13 +1209,19 @@ static const FFlogo C[] = { { .names = { "Codex Linux" }, .lines = FASTFETCH_DATATEXT_LOGO_CODEX, - .colors = { FF_COLOR_FG_WHITE }, + .colors = { + FF_COLOR_FG_WHITE, + }, }, // Condres { .names = { "Condres" }, .lines = FASTFETCH_DATATEXT_LOGO_CONDRES, - .colors = { FF_COLOR_FG_GREEN, FF_COLOR_FG_YELLOW, FF_COLOR_FG_CYAN }, + .colors = { + FF_COLOR_FG_GREEN, + FF_COLOR_FG_YELLOW, + FF_COLOR_FG_CYAN, + }, .colorKeys = FF_COLOR_FG_GREEN, .colorTitle = FF_COLOR_FG_YELLOW, }, @@ -1205,7 +1241,11 @@ static const FFlogo C[] = { { .names = { "common-torizon" }, .lines = FASTFETCH_DATATEXT_LOGO_TORIZONCORE, - .colors = { FF_COLOR_FG_LIGHT_WHITE, FF_COLOR_FG_YELLOW, FF_COLOR_FG_BLUE }, + .colors = { + FF_COLOR_FG_LIGHT_WHITE, + FF_COLOR_FG_YELLOW, + FF_COLOR_FG_BLUE, + }, }, // Cosmic DE { @@ -1608,7 +1648,10 @@ static const FFlogo E[] = { { .names = { "Endless" }, .lines = FASTFETCH_DATATEXT_LOGO_ENDLESS, - .colors = { FF_COLOR_FG_RED, FF_COLOR_FG_WHITE }, + .colors = { + FF_COLOR_FG_RED, + FF_COLOR_FG_WHITE, + }, .colorKeys = FF_COLOR_FG_RED, .colorTitle = FF_COLOR_FG_DEFAULT, }, @@ -1909,7 +1952,9 @@ static const FFlogo F[] = { { .names = { "FreeMiNT" }, .lines = FASTFETCH_DATATEXT_LOGO_FREEMINT, - .colors = { FF_COLOR_FG_WHITE }, + .colors = { + FF_COLOR_FG_WHITE, + }, .colorKeys = FF_COLOR_FG_DEFAULT, .colorTitle = FF_COLOR_FG_DEFAULT, }, @@ -2191,7 +2236,11 @@ static const FFlogo H[] = { { .names = { "HamoniKR" }, .lines = FASTFETCH_DATATEXT_LOGO_HAMONIKR, - .colors = { FF_COLOR_FG_BLUE, FF_COLOR_FG_WHITE, FF_COLOR_FG_256 "99" }, + .colors = { + FF_COLOR_FG_BLUE, + FF_COLOR_FG_WHITE, + FF_COLOR_FG_256 "99", + }, .colorKeys = FF_COLOR_FG_BLUE, .colorTitle = FF_COLOR_FG_DEFAULT, }, @@ -2466,12 +2515,23 @@ static const FFlogo K[] = { .colorTitle = FF_COLOR_FG_DEFAULT, }, // KernelOS - { .names = { "KernelOS" }, .lines = FASTFETCH_DATATEXT_LOGO_KERNELOS, .colors = { - FF_COLOR_FG_RED, - FF_COLOR_FG_MAGENTA, - } }, + { + .names = { "KernelOS" }, + .lines = FASTFETCH_DATATEXT_LOGO_KERNELOS, + .colors = { + FF_COLOR_FG_RED, + FF_COLOR_FG_MAGENTA, + }, + }, // KDELinux - { .names = { "kdelinux", "kde-linux" }, .lines = FASTFETCH_DATATEXT_LOGO_KDELINUX, .colors = { FF_COLOR_FG_YELLOW, FF_COLOR_FG_WHITE } }, + { + .names = { "kdelinux", "kde-linux" }, + .lines = FASTFETCH_DATATEXT_LOGO_KDELINUX, + .colors = { + FF_COLOR_FG_YELLOW, + FF_COLOR_FG_WHITE, + }, + }, // KDE Neon { .names = { "KDE Neon" }, // Distro ID is "neon"; Distro name is "KDE Neon" @@ -2585,7 +2645,12 @@ static const FFlogo K[] = { { .names = { "Kylin" }, .lines = FASTFETCH_DATATEXT_LOGO_KYLIN, - .colors = { FF_COLOR_FG_BLUE, FF_COLOR_FG_CYAN, FF_COLOR_FG_WHITE, FF_COLOR_FG_LIGHT_BLACK }, + .colors = { + FF_COLOR_FG_BLUE, + FF_COLOR_FG_CYAN, + FF_COLOR_FG_WHITE, + FF_COLOR_FG_LIGHT_BLACK, + }, .colorKeys = FF_COLOR_FG_BLUE, .colorTitle = FF_COLOR_FG_BLUE, }, @@ -3475,11 +3540,15 @@ static const FFlogo O[] = { }, }, // OmniOS - { .names = { "OmniOS" }, .lines = FASTFETCH_DATATEXT_LOGO_OMNIOS, .colors = { - FF_COLOR_FG_WHITE, - FF_COLOR_FG_YELLOW, - FF_COLOR_FG_LIGHT_BLACK, - } }, + { + .names = { "OmniOS" }, + .lines = FASTFETCH_DATATEXT_LOGO_OMNIOS, + .colors = { + FF_COLOR_FG_WHITE, + FF_COLOR_FG_YELLOW, + FF_COLOR_FG_LIGHT_BLACK, + }, + }, // OpenKylin { .names = { "openkylin", "open-kylin" }, @@ -3743,10 +3812,14 @@ static const FFlogo O[] = { }, }, // OS/2 Warp - { .names = { "OS2Warp" }, .lines = FASTFETCH_DATATEXT_LOGO_OS2WARP, .colors = { - FF_COLOR_FG_LIGHT_WHITE, - FF_COLOR_FG_LIGHT_BLUE, - } }, + { + .names = { "OS2Warp" }, + .lines = FASTFETCH_DATATEXT_LOGO_OS2WARP, + .colors = { + FF_COLOR_FG_LIGHT_WHITE, + FF_COLOR_FG_LIGHT_BLUE, + }, + }, // OS_Elbrus { .names = { "OS Elbrus" }, @@ -3941,7 +4014,9 @@ static const FFlogo P[] = { { .names = { "Peropesis", "Peropesis Linux" }, .lines = FASTFETCH_DATATEXT_LOGO_PEROPESIS, - .colors = { FF_COLOR_FG_WHITE }, + .colors = { + FF_COLOR_FG_WHITE, + }, }, // PhyOS { @@ -3973,7 +4048,12 @@ static const FFlogo P[] = { { .names = { "PNM Linux" }, .lines = FASTFETCH_DATATEXT_LOGO_PNM_LINUX, - .colors = { FF_COLOR_FG_BLUE, FF_COLOR_FG_RED, FF_COLOR_FG_WHITE, FF_COLOR_FG_256 "202" }, + .colors = { + FF_COLOR_FG_BLUE, + FF_COLOR_FG_RED, + FF_COLOR_FG_WHITE, + FF_COLOR_FG_256 "202", + }, }, // Pop { @@ -4029,7 +4109,10 @@ static const FFlogo P[] = { { .names = { "Proxmox", "pve" }, .lines = FASTFETCH_DATATEXT_LOGO_PROXMOX, - .colors = { FF_COLOR_FG_WHITE, FF_COLOR_FG_256 "202" }, + .colors = { + FF_COLOR_FG_WHITE, + FF_COLOR_FG_256 "202", + }, .colorKeys = FF_COLOR_FG_DEFAULT, .colorTitle = FF_COLOR_FG_256 "202", }, @@ -4564,10 +4647,14 @@ static const FFlogo S[] = { }, }, // Siduction - { .names = { "Siduction" }, .lines = FASTFETCH_DATATEXT_LOGO_SIDUCTION, .colors = { - FF_COLOR_FG_BLUE, - FF_COLOR_FG_WHITE, - } }, + { + .names = { "Siduction" }, + .lines = FASTFETCH_DATATEXT_LOGO_SIDUCTION, + .colors = { + FF_COLOR_FG_BLUE, + FF_COLOR_FG_WHITE, + }, + }, // SkiffOS { .names = { "SkiffOS" }, @@ -4578,15 +4665,24 @@ static const FFlogo S[] = { }, }, // SleeperOS - { .names = { "SleeperOS" }, .lines = FASTFETCH_DATATEXT_LOGO_SLEEPEROS, .colors = { - FF_COLOR_FG_CYAN, - FF_COLOR_FG_WHITE, - } }, + { + .names = { "SleeperOS" }, + .lines = FASTFETCH_DATATEXT_LOGO_SLEEPEROS, + .colors = { + FF_COLOR_FG_CYAN, + FF_COLOR_FG_WHITE, + }, + }, // SleeperOSSmall - { .names = { "SleeperOS_small" }, .type = FF_LOGO_LINE_TYPE_SMALL_BIT, .lines = FASTFETCH_DATATEXT_LOGO_SLEEPEROS_SMALL, .colors = { - FF_COLOR_FG_CYAN, - FF_COLOR_FG_WHITE, - } }, + { + .names = { "SleeperOS_small" }, + .type = FF_LOGO_LINE_TYPE_SMALL_BIT, + .lines = FASTFETCH_DATATEXT_LOGO_SLEEPEROS_SMALL, + .colors = { + FF_COLOR_FG_CYAN, + FF_COLOR_FG_WHITE, + }, + }, // Slitaz { .names = { "Slitaz" }, @@ -4709,7 +4805,10 @@ static const FFlogo S[] = { { .names = { "Sparky" }, .lines = FASTFETCH_DATATEXT_LOGO_SPARKY, - .colors = { FF_COLOR_FG_RED, FF_COLOR_FG_WHITE }, + .colors = { + FF_COLOR_FG_RED, + FF_COLOR_FG_WHITE, + }, }, // Star { @@ -4744,7 +4843,10 @@ static const FFlogo S[] = { { .names = { "SteamDeck" }, .lines = FASTFETCH_DATATEXT_LOGO_STEAMDECK, - .colors = { FF_COLOR_FG_BLUE, FF_COLOR_FG_WHITE }, + .colors = { + FF_COLOR_FG_BLUE, + FF_COLOR_FG_WHITE, + }, .colorKeys = FF_COLOR_FG_BLUE, .colorTitle = FF_COLOR_FG_BLUE, }, @@ -4752,7 +4854,10 @@ static const FFlogo S[] = { { .names = { "SteamDeck_small" }, .lines = FASTFETCH_DATATEXT_LOGO_STEAMDECK_SMALL, - .colors = { FF_COLOR_FG_BLUE, FF_COLOR_FG_WHITE }, + .colors = { + FF_COLOR_FG_BLUE, + FF_COLOR_FG_WHITE, + }, .colorKeys = FF_COLOR_FG_BLUE, .colorTitle = FF_COLOR_FG_BLUE, }, @@ -4760,7 +4865,10 @@ static const FFlogo S[] = { { .names = { "SteamDeckOled" }, .lines = FASTFETCH_DATATEXT_LOGO_STEAMDECK, - .colors = { FF_COLOR_FG_RED, FF_COLOR_FG_WHITE }, + .colors = { + FF_COLOR_FG_RED, + FF_COLOR_FG_WHITE, + }, .colorKeys = FF_COLOR_FG_RED, .colorTitle = FF_COLOR_FG_RED, }, @@ -4888,7 +4996,11 @@ static const FFlogo T[] = { { .names = { "Torizon OS", "TorizonCore" }, .lines = FASTFETCH_DATATEXT_LOGO_TORIZONCORE, - .colors = { FF_COLOR_FG_LIGHT_WHITE, FF_COLOR_FG_YELLOW, FF_COLOR_FG_BLUE }, + .colors = { + FF_COLOR_FG_LIGHT_WHITE, + FF_COLOR_FG_YELLOW, + FF_COLOR_FG_BLUE, + }, }, // Trisquel { @@ -5166,13 +5278,17 @@ static const FFlogo U[] = { .colorTitle = FF_COLOR_FG_BLUE, }, // UrukOS - { .names = { "UrukOS" }, .lines = FASTFETCH_DATATEXT_LOGO_URUKOS, .colors = { - FF_COLOR_FG_LIGHT_BLUE, - FF_COLOR_FG_LIGHT_BLUE, - FF_COLOR_FG_WHITE, - FF_COLOR_FG_LIGHT_BLUE, - FF_COLOR_FG_BLUE, - } }, + { + .names = { "UrukOS" }, + .lines = FASTFETCH_DATATEXT_LOGO_URUKOS, + .colors = { + FF_COLOR_FG_LIGHT_BLUE, + FF_COLOR_FG_LIGHT_BLUE, + FF_COLOR_FG_WHITE, + FF_COLOR_FG_LIGHT_BLUE, + FF_COLOR_FG_BLUE, + }, + }, // Uwuntu { .names = { "uwuntu" }, @@ -5465,20 +5581,29 @@ static const FFlogo X[] = { .colorTitle = FF_COLOR_FG_RED, }, // Xenia_old - { .names = { "Xenia_old" }, .lines = FASTFETCH_DATATEXT_LOGO_XENIA_OLD, .type = FF_LOGO_LINE_TYPE_ALTER_BIT, .colors = { - FF_COLOR_FG_YELLOW, - FF_COLOR_FG_GREEN, - FF_COLOR_FG_RED, - } }, + { + .names = { "Xenia_old" }, + .lines = FASTFETCH_DATATEXT_LOGO_XENIA_OLD, + .type = FF_LOGO_LINE_TYPE_ALTER_BIT, + .colors = { + FF_COLOR_FG_YELLOW, + FF_COLOR_FG_GREEN, + FF_COLOR_FG_RED, + }, + }, // XeroArch - { .names = { "XeroArch" }, .lines = FASTFETCH_DATATEXT_LOGO_XEROARCH, .colors = { - FF_COLOR_FG_256 "50", - FF_COLOR_FG_256 "14", - FF_COLOR_FG_256 "50", - FF_COLOR_FG_256 "93", - FF_COLOR_FG_256 "16", - FF_COLOR_FG_256 "15", - } }, + { + .names = { "XeroArch" }, + .lines = FASTFETCH_DATATEXT_LOGO_XEROARCH, + .colors = { + FF_COLOR_FG_256 "50", + FF_COLOR_FG_256 "14", + FF_COLOR_FG_256 "50", + FF_COLOR_FG_256 "93", + FF_COLOR_FG_256 "16", + FF_COLOR_FG_256 "15", + }, + }, // Xferience { .names = { "Xferience" }, @@ -5498,24 +5623,32 @@ static const FFlogo X[] = { }, }, // Xray_OS - { .names = { "Xray_OS" }, .lines = FASTFETCH_DATATEXT_LOGO_XRAY_OS, .colors = { - FF_COLOR_FG_256 "15", - FF_COLOR_FG_256 "14", - FF_COLOR_FG_256 "16", - } }, + { + .names = { "Xray_OS" }, + .lines = FASTFETCH_DATATEXT_LOGO_XRAY_OS, + .colors = { + FF_COLOR_FG_256 "15", + FF_COLOR_FG_256 "14", + FF_COLOR_FG_256 "16", + }, + }, // Xinux - { .names = { "Xinux" }, .lines = FASTFETCH_DATATEXT_LOGO_XINUX, .colors = { - FF_COLOR_FG_BLUE, - FF_COLOR_FG_CYAN, - } }, + { + .names = { "Xinux" }, + .lines = FASTFETCH_DATATEXT_LOGO_XINUX, + .colors = { + FF_COLOR_FG_BLUE, + FF_COLOR_FG_CYAN, + }, + }, // XJ380 - { - .names = { "XJ380" }, - .lines = FASTFETCH_DATATEXT_LOGO_XJ380, + { + .names = { "XJ380" }, + .lines = FASTFETCH_DATATEXT_LOGO_XJ380, .colors = { FF_COLOR_FG_RGB "0;162;232", FF_COLOR_FG_RGB "255;242;0", - } + }, }, // LAST {}, From efca4f3597907488366ce3860403eca3521fa14f Mon Sep 17 00:00:00 2001 From: Carter Li Date: Mon, 11 May 2026 23:24:45 +0800 Subject: [PATCH 83/92] DBus: expands `Get(U)Int` to `(u)int64` --- src/common/dbus.h | 6 ++-- src/common/impl/dbus.c | 31 ++++++++++++++++--- src/common/impl/settings.c | 8 ++--- src/detection/bluetooth/bluetooth_linux.c | 2 +- .../bluetoothradio/bluetoothradio_linux.c | 9 ++++-- 5 files changed, 40 insertions(+), 16 deletions(-) diff --git a/src/common/dbus.h b/src/common/dbus.h index 864040241d..e0689c3fee 100644 --- a/src/common/dbus.h +++ b/src/common/dbus.h @@ -29,12 +29,12 @@ typedef struct FFDBusData { const char* ffDBusLoadData(DBusBusType busType, FFDBusData* data); // Returns an error message or NULL on success bool ffDBusGetString(FFDBusData* dbus, DBusMessageIter* iter, FFstrbuf* result); bool ffDBusGetBool(FFDBusData* dbus, DBusMessageIter* iter, bool* result); -bool ffDBusGetUint(FFDBusData* dbus, DBusMessageIter* iter, uint32_t* result); +bool ffDBusGetUint(FFDBusData* dbus, DBusMessageIter* iter, uint64_t* result); DBusMessage* ffDBusGetMethodReply(FFDBusData* dbus, const char* busName, const char* objectPath, const char* interface, const char* method, const char* arg1, const char* arg2); DBusMessage* ffDBusGetProperty(FFDBusData* dbus, const char* busName, const char* objectPath, const char* interface, const char* property); bool ffDBusGetPropertyString(FFDBusData* dbus, const char* busName, const char* objectPath, const char* interface, const char* property, FFstrbuf* result); -bool ffDBusGetInt(FFDBusData* dbus, DBusMessageIter* iter, int32_t* result); -bool ffDBusGetPropertyUint(FFDBusData* dbus, const char* busName, const char* objectPath, const char* interface, const char* property, uint32_t* result); +bool ffDBusGetInt(FFDBusData* dbus, DBusMessageIter* iter, int64_t* result); +bool ffDBusGetPropertyUint(FFDBusData* dbus, const char* busName, const char* objectPath, const char* interface, const char* property, uint64_t* result); void ffDBusDestroyData(FFDBusData* data); static inline DBusMessage* ffDBusGetAllProperties(FFDBusData* dbus, const char* busName, const char* objectPath, const char* interface) { diff --git a/src/common/impl/dbus.c b/src/common/impl/dbus.c index dfd8b87df3..5b7ae73895 100644 --- a/src/common/impl/dbus.c +++ b/src/common/impl/dbus.c @@ -152,7 +152,7 @@ bool ffDBusGetBool(FFDBusData* dbus, DBusMessageIter* iter, bool* result) { return ffDBusGetBool(dbus, &subIter, result); } -bool ffDBusGetUint(FFDBusData* dbus, DBusMessageIter* iter, uint32_t* result) { +bool ffDBusGetUint(FFDBusData* dbus, DBusMessageIter* iter, uint64_t* result) { int argType = dbus->lib->ffdbus_message_iter_get_arg_type(iter); if (argType == DBUS_TYPE_BYTE) { @@ -170,6 +170,13 @@ bool ffDBusGetUint(FFDBusData* dbus, DBusMessageIter* iter, uint32_t* result) { } if (argType == DBUS_TYPE_UINT32) { + uint32_t value = 0; + dbus->lib->ffdbus_message_iter_get_basic(iter, &value); + *result = value; + return true; + } + + if (argType == DBUS_TYPE_UINT64) { dbus->lib->ffdbus_message_iter_get_basic(iter, result); return true; } @@ -183,7 +190,7 @@ bool ffDBusGetUint(FFDBusData* dbus, DBusMessageIter* iter, uint32_t* result) { return ffDBusGetUint(dbus, &subIter, result); } -bool ffDBusGetInt(FFDBusData* dbus, DBusMessageIter* iter, int32_t* result) { +bool ffDBusGetInt(FFDBusData* dbus, DBusMessageIter* iter, int64_t* result) { int argType = dbus->lib->ffdbus_message_iter_get_arg_type(iter); if (argType == DBUS_TYPE_INT16) { @@ -194,6 +201,13 @@ bool ffDBusGetInt(FFDBusData* dbus, DBusMessageIter* iter, int32_t* result) { } if (argType == DBUS_TYPE_INT32) { + int32_t value = 0; + dbus->lib->ffdbus_message_iter_get_basic(iter, &value); + *result = value; + return true; + } + + if (argType == DBUS_TYPE_INT64) { dbus->lib->ffdbus_message_iter_get_basic(iter, result); return true; } @@ -215,10 +229,17 @@ bool ffDBusGetInt(FFDBusData* dbus, DBusMessageIter* iter, int32_t* result) { if (argType == DBUS_TYPE_UINT32) { uint32_t value = 0; dbus->lib->ffdbus_message_iter_get_basic(iter, &value); - if (value > 0x7FFFFFFFu) { + *result = (int32_t) value; + return true; + } + + if (argType == DBUS_TYPE_UINT64) { + uint64_t value = 0; + dbus->lib->ffdbus_message_iter_get_basic(iter, &value); + if (value > INT64_MAX) { return false; } - *result = (int32_t) value; + *result = (int64_t) value; return true; } @@ -291,7 +312,7 @@ bool ffDBusGetPropertyString(FFDBusData* dbus, const char* busName, const char* return ret; } -bool ffDBusGetPropertyUint(FFDBusData* dbus, const char* busName, const char* objectPath, const char* interface, const char* property, uint32_t* result) { +bool ffDBusGetPropertyUint(FFDBusData* dbus, const char* busName, const char* objectPath, const char* interface, const char* property, uint64_t* result) { DBusMessage* reply = ffDBusGetProperty(dbus, busName, objectPath, interface, property); if (reply == NULL) { return false; diff --git a/src/common/impl/settings.c b/src/common/impl/settings.c index bd7da7424a..a44a66fd39 100644 --- a/src/common/impl/settings.c +++ b/src/common/impl/settings.c @@ -220,10 +220,10 @@ FFvariant ffSettingsGetXFConf(const char* channelName, const char* propertyName, } if (type == FF_VARIANT_TYPE_INT) { - int32_t value; + int64_t value; if (ffDBusGetInt(&dbus, &rootIterator, &value)) { dbus.lib->ffdbus_message_unref(reply); - return (FFvariant) { .intValue = value }; + return (FFvariant) { .intValue = (int32_t) value }; } dbus.lib->ffdbus_message_unref(reply); return FF_VARIANT_NULL; @@ -295,10 +295,10 @@ FFvariant ffSettingsGetXFConfFirstMatch(const char* channelName, const char* pro dbus.lib->ffdbus_message_iter_next(&dictIterator); if (type == FF_VARIANT_TYPE_INT) { - int32_t value; + int64_t value; if (ffDBusGetInt(&dbus, &dictIterator, &value)) { dbus.lib->ffdbus_message_unref(reply); - return (FFvariant) { .intValue = value }; + return (FFvariant) { .intValue = (int32_t) value }; } dbus.lib->ffdbus_message_unref(reply); return FF_VARIANT_NULL; diff --git a/src/detection/bluetooth/bluetooth_linux.c b/src/detection/bluetooth/bluetooth_linux.c index a5adfcf705..8d02127552 100644 --- a/src/detection/bluetooth/bluetooth_linux.c +++ b/src/detection/bluetooth/bluetooth_linux.c @@ -69,7 +69,7 @@ static bool detectBluetoothValue(FFDBusData* dbus, DBusMessageIter* iter, FFBlue } else if (ffStrEquals(deviceProperty, "Icon")) { ffDBusGetString(dbus, &dictIter, &device->type); } else if (ffStrEquals(deviceProperty, "Percentage")) { - uint32_t percentage; + uint64_t percentage; if (ffDBusGetUint(dbus, &dictIter, &percentage)) { device->battery = (uint8_t) percentage; } diff --git a/src/detection/bluetoothradio/bluetoothradio_linux.c b/src/detection/bluetoothradio/bluetoothradio_linux.c index 0e545f0d42..5f5554a356 100644 --- a/src/detection/bluetoothradio/bluetoothradio_linux.c +++ b/src/detection/bluetoothradio/bluetoothradio_linux.c @@ -56,12 +56,15 @@ static const char* detectBluetoothProperty(FFBluetoothRadioResult* device, FFDBu } else if (ffStrEquals(deviceProperty, "Alias")) { ffDBusGetString(dbus, &dictIter, &device->name); } else if (ffStrEquals(deviceProperty, "Manufacturer")) { - uint32_t vendorId; + uint64_t vendorId; if (ffDBusGetUint(dbus, &dictIter, &vendorId)) { - ffStrbufSetStatic(&device->vendor, ffBluetoothRadioGetVendor(vendorId)); + ffStrbufSetStatic(&device->vendor, ffBluetoothRadioGetVendor((uint32_t) vendorId)); } } else if (ffStrEquals(deviceProperty, "Version")) { - ffDBusGetUint(dbus, &dictIter, (uint32_t*) &device->lmpVersion); + uint64_t version; + if (ffDBusGetUint(dbus, &dictIter, &version)) { + device->lmpVersion = (int32_t) version; + } } else if (ffStrEquals(deviceProperty, "Powered")) { ffDBusGetBool(dbus, &dictIter, &device->enabled); } else if (ffStrEquals(deviceProperty, "Discoverable")) { From 1942dce9805306502c51d647e334d926df3f7b51 Mon Sep 17 00:00:00 2001 From: Carter Li Date: Mon, 11 May 2026 23:25:52 +0800 Subject: [PATCH 84/92] Media (Linux): detects song length & play position --- src/detection/media/media.c | 2 ++ src/detection/media/media.h | 2 ++ src/detection/media/media_linux.c | 14 ++++++++++++-- src/modules/media/media.c | 2 ++ 4 files changed, 18 insertions(+), 2 deletions(-) diff --git a/src/detection/media/media.c b/src/detection/media/media.c index e488259d2d..7adfb991bc 100644 --- a/src/detection/media/media.c +++ b/src/detection/media/media.c @@ -23,6 +23,8 @@ const FFMediaResult* ffDetectMedia(bool saveCover) { ffStrbufInit(&result.url); ffStrbufInit(&result.status); ffStrbufInit(&result.cover); + result.length = 0; + result.position = 0; result.removeCoverAfterUse = false; ffDetectMediaImpl(&result, saveCover); diff --git a/src/detection/media/media.h b/src/detection/media/media.h index e2a67df5da..ea241e6174 100644 --- a/src/detection/media/media.h +++ b/src/detection/media/media.h @@ -13,6 +13,8 @@ typedef struct FFMediaResult { FFstrbuf url; FFstrbuf status; FFstrbuf cover; + uint32_t length; // In milliseconds + uint32_t position; // In milliseconds bool removeCoverAfterUse; } FFMediaResult; diff --git a/src/detection/media/media_linux.c b/src/detection/media/media_linux.c index a3922bc31a..9c7b99cc1b 100644 --- a/src/detection/media/media_linux.c +++ b/src/detection/media/media_linux.c @@ -69,8 +69,8 @@ static bool parseMprisMetadata(FFDBusData* data, DBusMessageIter* rootIterator, break; } } else if (ffStrStartsWith(key, "mpris:")) { - const char* xesam = key + strlen("mpris:"); - if (ffStrEquals(xesam, "artUrl")) { + const char* mpris = key + strlen("mpris:"); + if (ffStrEquals(mpris, "artUrl")) { FF_STRBUF_AUTO_DESTROY path = ffStrbufCreate(); ffDBusGetString(data, &dictIterator, &path); if (ffStrbufStartsWithS(&path, "file:///")) { @@ -94,6 +94,11 @@ static bool parseMprisMetadata(FFDBusData* data, DBusMessageIter* rootIterator, } } } + } else if (ffStrEquals(mpris, "length")) { + uint64_t length = 0; // microseconds + if (ffDBusGetUint(data, &dictIterator, &length)) { + result->length = (uint32_t) (length / 1000); + } } } @@ -137,6 +142,11 @@ static bool getBusProperties(FFDBusData* data, const char* busName, FFMediaResul parseMprisMetadata(data, &dictIterator, result); } else if (ffStrEquals(key, "PlaybackStatus")) { ffDBusGetString(data, &dictIterator, &result->status); + } else if (ffStrEquals(key, "Position")) { + int64_t position = 0; // microseconds + if (ffDBusGetInt(data, &dictIterator, &position) && position > 0) { + result->position = (uint32_t) (position / 1000); + } } FF_DBUS_ITER_CONTINUE(data, &arrayIterator) diff --git a/src/modules/media/media.c b/src/modules/media/media.c index 319feb3f65..59fab0831e 100644 --- a/src/modules/media/media.c +++ b/src/modules/media/media.c @@ -158,6 +158,8 @@ bool ffGenerateMediaJsonResult(FF_A_UNUSED FFMediaOptions* options, yyjson_mut_d yyjson_mut_obj_add_strbuf(doc, song, "artist", &media->artist); yyjson_mut_obj_add_strbuf(doc, song, "album", &media->album); yyjson_mut_obj_add_strbuf(doc, song, "status", &media->status); + yyjson_mut_obj_add_uint(doc, song, "length", media->length); + yyjson_mut_obj_add_uint(doc, song, "position", media->position); if (media->cover.length > 0) { yyjson_mut_obj_add_strbuf(doc, song, "cover", &media->cover); } else { From 470e3b4e5774eef17eedc0f49871d9775a5d42c8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=9D=8E=E9=80=9A=E6=B4=B2?= Date: Tue, 12 May 2026 00:08:27 +0800 Subject: [PATCH 85/92] IO (Linux): fixes a possible fd leak --- src/common/impl/io_unix.c | 1 + 1 file changed, 1 insertion(+) diff --git a/src/common/impl/io_unix.c b/src/common/impl/io_unix.c index a7e0b041dc..c760861ef3 100644 --- a/src/common/impl/io_unix.c +++ b/src/common/impl/io_unix.c @@ -259,6 +259,7 @@ void listFilesRecursively(uint32_t baseLength, FFstrbuf* folder, uint8_t indenta FF_AUTO_CLOSE_DIR DIR* dir = fdopendir(dfd); if (dir == NULL) { + close(dfd); return; // Should not happen } From 235a2018fc484366cdfa9ea49e63464db76b2e87 Mon Sep 17 00:00:00 2001 From: Carter Li Date: Mon, 11 May 2026 14:36:01 +0800 Subject: [PATCH 86/92] Chore: update `.clang-format` [ci skip] --- .clang-format | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.clang-format b/.clang-format index 06f89607d3..957f565d32 100644 --- a/.clang-format +++ b/.clang-format @@ -1,4 +1,4 @@ -# find . -type f \( -name "*.c" -o -name "*.h" -o -name "*.cpp" -o -name "*.hpp" \) ! -path "src/3rdparty/*" -print0 | xargs -0 clang-format -i +# find . -type f \( -name "*.c" -o -name "*.h" -o -name "*.cpp" -o -name "*.hpp" \) -print0 | xargs -0 clang-format -i BasedOnStyle: LLVM Language: Cpp From 8426833c369b7ce777406e44c140042df782433b Mon Sep 17 00:00:00 2001 From: Carter Li Date: Mon, 11 May 2026 14:36:40 +0800 Subject: [PATCH 87/92] DE (Linux): adds Enlightenment version detection support --- src/detection/de/de_linux.c | 41 +++++++++++++++++++++ src/detection/displayserver/displayserver.h | 1 + src/detection/displayserver/linux/wmde.c | 5 +++ 3 files changed, 47 insertions(+) diff --git a/src/detection/de/de_linux.c b/src/detection/de/de_linux.c index 3e05886a5a..801e8fe9cb 100644 --- a/src/detection/de/de_linux.c +++ b/src/detection/de/de_linux.c @@ -211,6 +211,45 @@ static const char* getCosmic(FFstrbuf* result, FF_A_UNUSED FFDEOptions* options) return "All methods failed"; } +static const char* getEnlightenmentByDbus(FF_A_UNUSED FFstrbuf* result) { +#ifdef FF_HAVE_DBUS + FF_DBUS_AUTO_DESTROY_DATA FFDBusData dbus = {}; + if (ffDBusLoadData(DBUS_BUS_SESSION, &dbus) != NULL) { + return "ffDBusLoadData() failed"; + } + + DBusMessage* reply = ffDBusGetMethodReply(&dbus, "org.enlightenment.wm.service", "/org/enlightenment/wm/RemoteObject", "org.enlightenment.wm.Core", "Version", NULL, NULL); + if (!reply) { + return "ffDBusGetMethodReply() failed"; + } + + DBusMessageIter rootIterator; + if (!dbus.lib->ffdbus_message_iter_init(reply, &rootIterator)) { + dbus.lib->ffdbus_message_unref(reply); + return "dbus_message_iter_init() failed"; + } + if (ffDBusGetString(&dbus, &rootIterator, result)) { + dbus.lib->ffdbus_message_unref(reply); + return "ffDBusGetString() failed"; + } + dbus.lib->ffdbus_message_unref(reply); + + return NULL; +#else // FF_HAVE_DBUS + return "ffDBusLoadData() failed: dbus support not compiled in"; +#endif // FF_HAVE_DBUS +} + +static void getEnlightenment(FFstrbuf* result, FF_A_UNUSED FFDEOptions* options) { + getEnlightenmentByDbus(result); + + if (result->length == 0) { + if (ffProcessAppendStdOut(result, (char* const[]) { "enlightenment", "--version", NULL }) == NULL) { // ...\nVersion: 0.27.1\n... + ffStrbufSubstrAfterFirstS(result, "Version: "); + ffStrbufSubstrBeforeFirstC(result, '\n'); + } + } +} const char* ffDetectDEVersion(const FFstrbuf* deName, FFstrbuf* result, FFDEOptions* options) { if (!instance.config.general.detectVersion) { return "Disabled by config"; @@ -236,6 +275,8 @@ const char* ffDetectDEVersion(const FFstrbuf* deName, FFstrbuf* result, FFDEOpti getTrinity(result, options); } else if (ffStrbufEqualS(deName, "COSMIC")) { getCosmic(result, options); + } else if (ffStrbufEqualS(deName, FF_DE_PRETTY_ENLIGHTENMENT)) { + getEnlightenment(result, options); } else { return "Unsupported DE"; } diff --git a/src/detection/displayserver/displayserver.h b/src/detection/displayserver/displayserver.h index d6adf4d1da..9dd41ec234 100644 --- a/src/detection/displayserver/displayserver.h +++ b/src/detection/displayserver/displayserver.h @@ -16,6 +16,7 @@ #define FF_DE_PRETTY_UNITY "Unity" #define FF_DE_PRETTY_UKUI "UKUI" #define FF_DE_PRETTY_NEBIDE "NebiDE" +#define FF_DE_PRETTY_ENLIGHTENMENT "Enlightenment" #define FF_WM_PRETTY_KWIN "KWin" #define FF_WM_PRETTY_MUTTER "Mutter" diff --git a/src/detection/displayserver/linux/wmde.c b/src/detection/displayserver/linux/wmde.c index d4ccf08016..135eb1c9d9 100644 --- a/src/detection/displayserver/linux/wmde.c +++ b/src/detection/displayserver/linux/wmde.c @@ -267,6 +267,11 @@ static void applyPrettyNameIfDE(FFDisplayServerResult* result, const char* name) ffStrbufSetS(&result->deProcessName, "unity-session"); ffStrbufSetS(&result->dePrettyName, FF_DE_PRETTY_UNITY); } + + else if (ffStrEqualsIgnCase(name, "Enlightenment")) { + ffStrbufSetS(&result->deProcessName, "enlightenment_start"); + ffStrbufSetS(&result->dePrettyName, FF_DE_PRETTY_ENLIGHTENMENT); + } } static const char* getFromProcesses(FFDisplayServerResult* result) { From 7799dbe481b199e99d58c41047f97d0b8d72a924 Mon Sep 17 00:00:00 2001 From: Carter Li Date: Tue, 12 May 2026 10:56:14 +0800 Subject: [PATCH 88/92] WM: adds full support of Enlightenment Fixes #2165 --- CHANGELOG.md | 1 + CMakeLists.txt | 5 + src/common/impl/init.c | 3 + src/common/impl/settings.c | 124 ++++++++++++++++++++ src/common/settings.h | 10 ++ src/detection/displayserver/displayserver.h | 1 + src/detection/gtk_qt/gtk.c | 11 ++ src/detection/wmtheme/wmtheme_linux.c | 4 + 8 files changed, 159 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index e4e1d05b53..fc5e69485d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,6 +11,7 @@ Features: * Adds Ubuntu Kylin and Ubuntu Unity flavor detection (OS, Linux) * Adds NebiDE support (WMTheme, Linux) * Adds package counting for `cards` on NuTyX (#2287, Packages, Linux) +* Adds support for Enlightenment desktop environment (#2165, WM, Linux) Bugfixes: * Improves DisplayServer compatibility on Linux by handling newer `kde-output-device-v2` protocol updates (DisplayServer, Linux) diff --git a/CMakeLists.txt b/CMakeLists.txt index c2c84cd5fc..53de960e7a 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -77,6 +77,7 @@ cmake_dependent_option(ENABLE_DRM "Enable libdrm" ON "LINUX OR FreeBSD OR OpenBS cmake_dependent_option(ENABLE_DRM_AMDGPU "Enable libdrm_amdgpu" ON "LINUX OR FreeBSD OR GNU" OFF) cmake_dependent_option(ENABLE_GIO "Enable gio-2.0" ON "LINUX OR FreeBSD OR OpenBSD OR NetBSD OR ANDROID OR SunOS OR GNU" OFF) cmake_dependent_option(ENABLE_DCONF "Enable dconf" ON "LINUX OR FreeBSD OR OpenBSD OR NetBSD OR ANDROID OR SunOS OR GNU" OFF) +cmake_dependent_option(ENABLE_EET "Enable eet" ON "LINUX OR FreeBSD OR OpenBSD OR NetBSD OR ANDROID OR SunOS OR GNU" OFF) cmake_dependent_option(ENABLE_DBUS "Enable dbus-1" ON "LINUX OR FreeBSD OR OpenBSD OR NetBSD OR ANDROID OR SunOS OR Haiku OR GNU" OFF) cmake_dependent_option(ENABLE_SQLITE3 "Enable sqlite3" ON "LINUX OR FreeBSD OR APPLE OR OpenBSD OR NetBSD OR SunOS OR GNU" OFF) cmake_dependent_option(ENABLE_RPM "Enable rpm" ON "LINUX OR GNU" OFF) @@ -1614,6 +1615,10 @@ ff_lib_enable(DCONF "dconf" "DConf" ) +ff_lib_enable(EET + "eet" + "Eet" +) ff_lib_enable(DBUS "dbus-1" "DBus" diff --git a/src/common/impl/init.c b/src/common/impl/init.c index 123a9dd99e..1d3fa8cccd 100644 --- a/src/common/impl/init.c +++ b/src/common/impl/init.c @@ -203,6 +203,9 @@ void ffListFeatures(void) { #if FF_HAVE_DCONF "dconf\n" #endif +#if FF_HAVE_EET + "eet\n" +#endif #if FF_HAVE_DBUS "dbus\n" #endif diff --git a/src/common/impl/settings.c b/src/common/impl/settings.c index a44a66fd39..2c3b6d02d2 100644 --- a/src/common/impl/settings.c +++ b/src/common/impl/settings.c @@ -485,3 +485,127 @@ bool ffSettingsGetFreeBSDKenv(const char* propName, FFstrbuf* result) { return true; } #endif + +#ifdef FF_HAVE_EET + #if defined(__clang__) + #pragma clang diagnostic push + #pragma clang diagnostic ignored "-Weverything" + #elif defined(__GNUC__) + #pragma GCC diagnostic push + #pragma GCC diagnostic ignored "-Wall" + #pragma GCC diagnostic ignored "-Wextra" + #pragma GCC diagnostic ignored "-Wpedantic" + #pragma GCC diagnostic ignored "-Wunknown-pragmas" + #endif + #include + #if defined(__clang__) + #pragma clang diagnostic pop + #elif defined(__GNUC__) + #pragma GCC diagnostic pop + #endif + +typedef struct E_Font_Default { + char* text_class; + char* font; + int size; +} E_Font_Default; + +typedef struct E_Config { + char* theme_default_border_style; + char* icon_theme; + int use_e_cursor; + int cursor_size; + char* desktop_default_background; + Eina_List* font_defaults; +} E_Config; // Must be the same name as the top level struct in e.cfg + + #define FF_EET_EINA_FILE_DATA_DESCRIPTOR_CLASS_SET(clas, type) \ + (ffeet_eina_file_data_descriptor_class_set(clas, sizeof(*(clas)), #type, sizeof(type))) + #define FF_EET_DATA_DESCRIPTOR_ADD_BASIC(edd, struct_type, member, type) \ + do { \ + struct_type ___ett; \ + ffeet_data_descriptor_element_add(edd, #member, type, EET_G_UNKNOWN, (char*) (&(___ett.member)) - (char*) (&(___ett)), 0, /* 0, */ NULL, NULL); \ + } while (0) + #define FF_EET_DATA_DESCRIPTOR_ADD_LIST(edd, struct_type, member, subtype) \ + do { \ + struct_type ___ett; \ + ffeet_data_descriptor_element_add(edd, #member, EET_T_UNKNOW, EET_G_LIST, (char*) (&(___ett.member)) - (char*) (&(___ett)), 0, /* 0, */ NULL, subtype); \ + } while (0) + +bool ffSettingsGetEnlightenmentProperty(ffEnlightenmentSettings* result) { + FF_LIBRARY_LOAD(libeet, false, "libeet" FF_LIBRARY_EXTENSION, 1); + FF_LIBRARY_LOAD_SYMBOL(libeet, eet_init, false); + FF_LIBRARY_LOAD_SYMBOL(libeet, eet_open, false); + FF_LIBRARY_LOAD_SYMBOL(libeet, eet_data_descriptor_file_new, false); + FF_LIBRARY_LOAD_SYMBOL(libeet, eet_data_read, false); + FF_LIBRARY_LOAD_SYMBOL(libeet, eet_close, false); + FF_LIBRARY_LOAD_SYMBOL(libeet, eet_data_descriptor_free, false); + FF_LIBRARY_LOAD_SYMBOL(libeet, eet_eina_file_data_descriptor_class_set, false); + FF_LIBRARY_LOAD_SYMBOL(libeet, eet_data_descriptor_element_add, false); + + if (ffeet_init() == 0) { + return false; + } + + FF_STRBUF_AUTO_DESTROY fileName = ffStrbufCreateCopy(&instance.state.platform.homeDir); + ffStrbufAppendS(&fileName, ".e/e/config/standard/e.cfg"); + + Eet_File* ef = ffeet_open(fileName.chars, EET_FILE_MODE_READ); + if (!ef) { + return false; + } + + Eet_Data_Descriptor_Class fontDdc; + FF_EET_EINA_FILE_DATA_DESCRIPTOR_CLASS_SET(&fontDdc, E_Font_Default); + Eet_Data_Descriptor* fontDdd = ffeet_data_descriptor_file_new(&fontDdc); + if (!fontDdd) { + ffeet_close(ef); + return false; + } + FF_EET_DATA_DESCRIPTOR_ADD_BASIC(fontDdd, E_Font_Default, text_class, EET_T_STRING); + FF_EET_DATA_DESCRIPTOR_ADD_BASIC(fontDdd, E_Font_Default, font, EET_T_STRING); + FF_EET_DATA_DESCRIPTOR_ADD_BASIC(fontDdd, E_Font_Default, size, EET_T_INT); + + Eet_Data_Descriptor_Class eddc; + FF_EET_EINA_FILE_DATA_DESCRIPTOR_CLASS_SET(&eddc, E_Config); + Eet_Data_Descriptor* edd = ffeet_data_descriptor_file_new(&eddc); + if (!edd) { + ffeet_data_descriptor_free(fontDdd); + ffeet_close(ef); + return false; + } + + FF_EET_DATA_DESCRIPTOR_ADD_BASIC(edd, E_Config, theme_default_border_style, EET_T_STRING); + FF_EET_DATA_DESCRIPTOR_ADD_BASIC(edd, E_Config, icon_theme, EET_T_STRING); + FF_EET_DATA_DESCRIPTOR_ADD_BASIC(edd, E_Config, use_e_cursor, EET_T_INT); + FF_EET_DATA_DESCRIPTOR_ADD_BASIC(edd, E_Config, cursor_size, EET_T_INT); + FF_EET_DATA_DESCRIPTOR_ADD_BASIC(edd, E_Config, desktop_default_background, EET_T_STRING); + FF_EET_DATA_DESCRIPTOR_ADD_LIST(edd, E_Config, font_defaults, fontDdd); + + E_Config* parsed = ffeet_data_read(ef, edd, "config"); + + if (parsed) { + // TODO: find a better method to get the main theme name + result->theme = parsed->theme_default_border_style; + result->icon_theme = parsed->icon_theme; + result->use_e_cursor = !!parsed->use_e_cursor; + result->cursor_size = parsed->cursor_size; + result->desktop_default_background = parsed->desktop_default_background; + + E_Font_Default* firstFont = eina_list_data_get(parsed->font_defaults); + if (firstFont) { + result->font = firstFont->font; + } + } + + ffeet_close(ef); + ffeet_data_descriptor_free(edd); + ffeet_data_descriptor_free(fontDdd); + + return !!parsed; +} +#else +bool ffSettingsGetEnlightenmentProperty(FF_A_UNUSED ffEnlightenmentSettings* result) { + return false; +} +#endif diff --git a/src/common/settings.h b/src/common/settings.h index 02658670eb..f2143cf46c 100644 --- a/src/common/settings.h +++ b/src/common/settings.h @@ -30,6 +30,16 @@ FFvariant ffSettingsGetXFConfFirstMatch(const char* channelName, const char* pro int ffSettingsGetSQLite3Int(const char* dbPath, const char* query); bool ffSettingsGetSQLite3String(const char* dbPath, const char* query, FFstrbuf* result); +typedef struct { + char* theme; + char* icon_theme; + bool use_e_cursor; + int cursor_size; + char* desktop_default_background; + char* font; +} ffEnlightenmentSettings; +bool ffSettingsGetEnlightenmentProperty(ffEnlightenmentSettings* result); + #ifdef __ANDROID__ bool ffSettingsGetAndroidProperty(const char* propName, FFstrbuf* result); #elif defined(__FreeBSD__) diff --git a/src/detection/displayserver/displayserver.h b/src/detection/displayserver/displayserver.h index 9dd41ec234..b33f2c84bd 100644 --- a/src/detection/displayserver/displayserver.h +++ b/src/detection/displayserver/displayserver.h @@ -42,6 +42,7 @@ #define FF_WM_PRETTY_FVWM "fvwm" #define FF_WM_PRETTY_CTWM "ctwm" #define FF_WM_PRETTY_RATPOISON "ratpoison" +#define FF_WM_PRETTY_ENLIGHTENMENT "Enlightenment" #define FF_WM_PROTOCOL_TTY "TTY" #define FF_WM_PROTOCOL_X11 "X11" diff --git a/src/detection/gtk_qt/gtk.c b/src/detection/gtk_qt/gtk.c index 41da10e70a..e2af5ef6d1 100644 --- a/src/detection/gtk_qt/gtk.c +++ b/src/detection/gtk_qt/gtk.c @@ -95,6 +95,17 @@ static void detectGTKFromSettings(FFGTKResult* result) { cursorTheme = ffSettingsGetGnome("/org/gnome/desktop/interface/cursor-theme", "org.gnome.desktop.interface", NULL, "cursor-theme", FF_VARIANT_TYPE_STRING).strValue; cursorSize = ffSettingsGetGnome("/org/gnome/desktop/interface/cursor-size", "org.gnome.desktop.interface", NULL, "cursor-size", FF_VARIANT_TYPE_INT).intValue; wallpaper = ffSettingsGetGnome("/org/gnome/desktop/background/picture-uri", "org.gnome.desktop.background", NULL, "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; + } } applyGTKSettings(result, themeName, iconsName, fontName, cursorTheme, cursorSize, wallpaper); diff --git a/src/detection/wmtheme/wmtheme_linux.c b/src/detection/wmtheme/wmtheme_linux.c index adc3cd5aa0..685ba5f5ef 100644 --- a/src/detection/wmtheme/wmtheme_linux.c +++ b/src/detection/wmtheme/wmtheme_linux.c @@ -220,6 +220,10 @@ bool ffDetectWmTheme(FFstrbuf* themeOrError) { return detectOpenbox(&wm->dePrettyName, themeOrError); } + if (ffStrbufIgnCaseEqualS(&wm->wmPrettyName, FF_WM_PRETTY_ENLIGHTENMENT)) { + return detectGTKThemeAsWMTheme(themeOrError); + } + ffStrbufAppendS(themeOrError, "Unknown WM: "); ffStrbufAppend(themeOrError, &wm->wmPrettyName); return false; From 1400291ae9eab02a3de276e101fb1cb85f1fe47a Mon Sep 17 00:00:00 2001 From: Carter Li Date: Tue, 12 May 2026 15:07:08 +0800 Subject: [PATCH 89/92] Media (macOS): adds length & position detection support --- .clang-format | 10 +- src/common/apple/cf_helpers.c | 41 ++++++++ src/common/apple/cf_helpers.h | 4 + src/detection/media/media_apple.m | 154 +++++++++++++++++++----------- 4 files changed, 150 insertions(+), 59 deletions(-) diff --git a/.clang-format b/.clang-format index 957f565d32..8584e37814 100644 --- a/.clang-format +++ b/.clang-format @@ -1,7 +1,6 @@ -# find . -type f \( -name "*.c" -o -name "*.h" -o -name "*.cpp" -o -name "*.hpp" \) -print0 | xargs -0 clang-format -i +# find . -type f \( -name "*.c" -o -name "*.h" -o -name "*.cpp" -o -name "*.hpp" -o -name "*.m" -o -name "*.mm" \) -print0 | xargs -0 clang-format -i BasedOnStyle: LLVM -Language: Cpp UseTab: Never IndentWidth: 4 @@ -81,3 +80,10 @@ AttributeMacros: MaxEmptyLinesToKeep: 1 KeepEmptyLinesAtTheStartOfBlocks: false + +--- +Language: ObjC +ObjCBlockIndentWidth: 4 +ObjCSpaceAfterProperty: true +ObjCSpaceBeforeProtocolList: true +ObjCBreakBeforeNestedBlockParam: true diff --git a/src/common/apple/cf_helpers.c b/src/common/apple/cf_helpers.c index e7a9c4fa10..60e1a1c592 100644 --- a/src/common/apple/cf_helpers.c +++ b/src/common/apple/cf_helpers.c @@ -34,6 +34,29 @@ const char* ffCfNumGetInt(CFTypeRef cf, int32_t* result) { return "TypeID is neither 'CFNumber' nor 'CFData'"; } +const char* ffCfNumGetDouble(CFTypeRef cf, double* result) { + if (CFGetTypeID(cf) == CFNumberGetTypeID()) { + if (!CFNumberGetValue((CFNumberRef) cf, kCFNumberDoubleType, result) && + !CFNumberGetValue((CFNumberRef) cf, kCFNumberFloatType, result)) { + return "Number type is not Double or Float"; + } + return NULL; + } + + return "TypeID is neither 'CFNumber'"; +} + +const char* ffCfDateGetEpoch(CFTypeRef cf, uint64_t* result) { + if (CFGetTypeID(cf) != CFDateGetTypeID()) { + return "TypeID is not 'CFDate'"; + } + + CFAbsoluteTime absTime = CFDateGetAbsoluteTime((CFDateRef) cf); + // Convert from seconds to milliseconds and add the difference between 1970 and 2001 in milliseconds + *result = (uint64_t) ((absTime + 978307200 /*kCFAbsoluteTimeIntervalSince1970*/) * 1000); + return NULL; +} + const char* ffCfStrGetString(CFTypeRef cf, FFstrbuf* result) { ffStrbufClear(result); if (!cf) { @@ -149,6 +172,15 @@ const char* ffCfDictGetInt64(CFDictionaryRef dict, CFStringRef key, int64_t* res return ffCfNumGetInt64(cf, result); } +const char* ffCfDictGetDouble(CFDictionaryRef dict, CFStringRef key, double* result) { + CFTypeRef cf = (CFTypeRef) CFDictionaryGetValue(dict, key); + if (cf == NULL) { + return "CFDictionaryGetValue() failed"; + } + + return ffCfNumGetDouble(cf, result); +} + const char* ffCfDictGetData(CFDictionaryRef dict, CFStringRef key, uint32_t offset, uint32_t size, uint8_t* result, uint32_t* length) { CFTypeRef cf = (CFTypeRef) CFDictionaryGetValue(dict, key); if (cf == NULL) { @@ -182,3 +214,12 @@ const char* ffCfDictGetDict(CFDictionaryRef dict, CFStringRef key, CFDictionaryR *result = cf; return NULL; } + +const char* ffCfDictGetDateAsEpoch(CFDictionaryRef dict, CFStringRef key, uint64_t* result) { + CFTypeRef cf = (CFTypeRef) CFDictionaryGetValue(dict, key); + if (cf == NULL) { + return "CFDictionaryGetValue() failed"; + } + + return ffCfDateGetEpoch(cf, result); +} diff --git a/src/common/apple/cf_helpers.h b/src/common/apple/cf_helpers.h index 8ce58ccc30..a929f030e8 100644 --- a/src/common/apple/cf_helpers.h +++ b/src/common/apple/cf_helpers.h @@ -8,14 +8,18 @@ const char* ffCfStrGetString(CFTypeRef cf, FFstrbuf* result); const char* ffCfNumGetInt(CFTypeRef cf, int32_t* result); const char* ffCfNumGetInt64(CFTypeRef cf, int64_t* result); +const char* ffCfNumGetDouble(CFTypeRef cf, double* result); +const char* ffCfDateGetEpoch(CFTypeRef cf, uint64_t* result); const char* ffCfDataGetDataAsString(CFTypeRef cf, FFstrbuf* result); const char* ffCfDictGetString(CFDictionaryRef dict, CFStringRef key, FFstrbuf* result); const char* ffCfDictGetBool(CFDictionaryRef dict, CFStringRef key, bool* result); const char* ffCfDictGetInt(CFDictionaryRef dict, CFStringRef key, int* result); const char* ffCfDictGetInt64(CFDictionaryRef dict, CFStringRef key, int64_t* result); +const char* ffCfDictGetDouble(CFDictionaryRef dict, CFStringRef key, double* result); const char* ffCfDictGetData(CFDictionaryRef dict, CFStringRef key, uint32_t offset, uint32_t size, uint8_t* result, uint32_t* length); const char* ffCfDictGetDataAsString(CFDictionaryRef dict, CFStringRef key, FFstrbuf* result); const char* ffCfDictGetDict(CFDictionaryRef dict, CFStringRef key, CFDictionaryRef* result); +const char* ffCfDictGetDateAsEpoch(CFDictionaryRef dict, CFStringRef key, uint64_t* result); static inline CFNumberRef ffCfCreateInt(int value) { return CFNumberCreate(kCFAllocatorDefault, kCFNumberIntType, &value); diff --git a/src/detection/media/media_apple.m b/src/detection/media/media_apple.m index b646ebcc62..e99196c865 100644 --- a/src/detection/media/media_apple.m +++ b/src/detection/media/media_apple.m @@ -1,6 +1,7 @@ #include "fastfetch.h" #include "common/processing.h" #include "common/apple/cf_helpers.h" +#include "common/time.h" #include "detection/media/media.h" #import @@ -8,17 +9,36 @@ #import // https://github.com/andrewwiik/iOS-Blocks/blob/master/Widgets/Music/MediaRemote.h -extern void MRMediaRemoteGetNowPlayingInfo(dispatch_queue_t dispatcher, void(^callback)(_Nullable CFDictionaryRef info)) FF_A_WEAK_IMPORT; +extern void MRMediaRemoteGetNowPlayingInfo(dispatch_queue_t dispatcher, void (^callback)(_Nullable CFDictionaryRef info)) FF_A_WEAK_IMPORT; extern void MRMediaRemoteGetNowPlayingApplicationIsPlaying(dispatch_queue_t queue, void (^callback)(BOOL playing)) FF_A_WEAK_IMPORT; extern void MRMediaRemoteGetNowPlayingApplicationDisplayID(dispatch_queue_t queue, void (^callback)(_Nullable CFStringRef displayID)) FF_A_WEAK_IMPORT; extern void MRMediaRemoteGetNowPlayingApplicationDisplayName(int unknown, dispatch_queue_t queue, void (^callback)(_Nullable CFStringRef name)) FF_A_WEAK_IMPORT; -static const char* getMediaByMediaRemote(FFMediaResult* result, bool saveCover) -{ - #define FF_TEST_FN_EXISTANCE(fn) if (!fn) return "MediaRemote function " #fn " is not available" - FF_TEST_FN_EXISTANCE(MRMediaRemoteGetNowPlayingInfo); - FF_TEST_FN_EXISTANCE(MRMediaRemoteGetNowPlayingApplicationIsPlaying); - #undef FF_TEST_FN_EXISTANCE +static uint32_t getTrueElapsedTime(CFDictionaryRef info) { + double elapsedTime; + if (ffCfDictGetDouble(info, CFSTR("kMRMediaRemoteNowPlayingInfoElapsedTime"), &elapsedTime) != NULL) { + return 0; + } + + elapsedTime *= 1000; + + double playbackRate; + uint64_t timestampEpoch; + if (ffCfDictGetDouble(info, CFSTR("kMRMediaRemoteNowPlayingInfoPlaybackRate"), &playbackRate) == NULL && + ffCfDictGetDateAsEpoch(info, CFSTR("kMRMediaRemoteNowPlayingInfoTimestamp"), ×tampEpoch) == NULL) { + uint64_t timeDiff = ffTimeGetNow() - timestampEpoch; + elapsedTime += (double) timeDiff * playbackRate; + } + + return (uint32_t) elapsedTime; +} + +static const char* getMediaByMediaRemote(FFMediaResult* result, bool saveCover) { +#define FF_TEST_FN_EXISTENCE(fn) \ + if (!fn) return "MediaRemote function " #fn " is not available" + FF_TEST_FN_EXISTENCE(MRMediaRemoteGetNowPlayingInfo); + FF_TEST_FN_EXISTENCE(MRMediaRemoteGetNowPlayingApplicationIsPlaying); +#undef FF_TEST_FN_EXISTENCE dispatch_group_t group = dispatch_group_create(); dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0); @@ -26,32 +46,33 @@ dispatch_group_enter(group); __block const char* error = NULL; MRMediaRemoteGetNowPlayingInfo(queue, ^(_Nullable CFDictionaryRef info) { - if(info != nil) - { + if (info != nil) { ffCfDictGetString(info, CFSTR("kMRMediaRemoteNowPlayingInfoTitle"), &result->song); ffCfDictGetString(info, CFSTR("kMRMediaRemoteNowPlayingInfoArtist"), &result->artist); ffCfDictGetString(info, CFSTR("kMRMediaRemoteNowPlayingInfoAlbum"), &result->album); + double value; + if (ffCfDictGetDouble(info, CFSTR("kMRMediaRemoteNowPlayingInfoDuration"), &value) == NULL) { + result->length = (uint32_t) (value * 1000); + result->position = getTrueElapsedTime(info); + } - if (saveCover) - { + if (saveCover) { NSData* artworkData = (__bridge NSData*) CFDictionaryGetValue(info, CFSTR("kMRMediaRemoteNowPlayingInfoArtworkData")); - if (artworkData) - { + if (artworkData) { CFStringRef mime = (CFStringRef) CFDictionaryGetValue(info, CFSTR("kMRMediaRemoteNowPlayingInfoArtworkMIMEType")); #pragma clang diagnostic push #pragma clang diagnostic ignored "-Wdeprecated-declarations" FF_CFTYPE_AUTO_RELEASE CFStringRef uti = UTTypeCreatePreferredIdentifierForTag(kUTTagClassMIMEType, mime, NULL); FF_CFTYPE_AUTO_RELEASE CFStringRef ext = UTTypeCopyPreferredTagWithClass(uti, kUTTagClassFilenameExtension); #pragma clang diagnostic pop - NSString *tmpDir = NSTemporaryDirectory(); - NSString *uuid = NSUUID.UUID.UUIDString; - NSString *path = [tmpDir stringByAppendingPathComponent:[NSString stringWithFormat:@"ff_%@.%@", uuid, ext ? (__bridge NSString *) ext : @"img"]]; + NSString* tmpDir = NSTemporaryDirectory(); + NSString* uuid = NSUUID.UUID.UUIDString; + NSString* path = [tmpDir stringByAppendingPathComponent:[NSString stringWithFormat:@"ff_%@.%@", uuid, ext ? (__bridge NSString*) ext : @"img"]]; if ([artworkData writeToFile:path atomically:NO]) ffStrbufSetS(&result->cover, path.UTF8String); } } - } - else + } else error = "MRMediaRemoteGetNowPlayingInfo() failed"; dispatch_group_leave(group); @@ -63,8 +84,7 @@ dispatch_group_leave(group); }); - if (MRMediaRemoteGetNowPlayingApplicationDisplayID) - { + if (MRMediaRemoteGetNowPlayingApplicationDisplayID) { dispatch_group_enter(group); MRMediaRemoteGetNowPlayingApplicationDisplayID(queue, ^(_Nullable CFStringRef displayID) { ffCfStrGetString(displayID, &result->playerId); @@ -72,8 +92,7 @@ }); } - if (MRMediaRemoteGetNowPlayingApplicationDisplayName) - { + if (MRMediaRemoteGetNowPlayingApplicationDisplayName) { dispatch_group_enter(group); MRMediaRemoteGetNowPlayingApplicationDisplayName(0, queue, ^(_Nullable CFStringRef name) { ffCfStrGetString(name, &result->player); @@ -84,15 +103,14 @@ dispatch_group_wait(group, DISPATCH_TIME_FOREVER); // Don't dispatch_release because we are using ARC - if(result->song.length > 0) + if (result->song.length > 0) { return NULL; + } return error; } -__attribute__((visibility("default"), used)) -int ffPrintMediaByMediaRemote(bool saveCover) -{ +__attribute__((visibility("default"), used)) int ffPrintMediaByMediaRemote(bool saveCover) { FFMediaResult media = { .status = ffStrbufCreate(), .song = ffStrbufCreate(), @@ -102,15 +120,28 @@ int ffPrintMediaByMediaRemote(bool saveCover) .player = ffStrbufCreate(), .cover = ffStrbufCreate(), }; - if (getMediaByMediaRemote(&media, saveCover) != NULL) + if (getMediaByMediaRemote(&media, saveCover) != NULL) { return 1; - ffStrbufPutTo(&media.status, stdout); - ffStrbufPutTo(&media.song, stdout); - ffStrbufPutTo(&media.artist, stdout); - ffStrbufPutTo(&media.album, stdout); - ffStrbufPutTo(&media.playerId, stdout); - ffStrbufPutTo(&media.player, stdout); - ffStrbufWriteTo(&media.cover, stdout); + } + ffStrbufAppendC(&media.status, '\n'); + ffStrbufAppend(&media.status, &media.song); + ffStrbufAppendC(&media.status, '\n'); + ffStrbufAppend(&media.status, &media.artist); + ffStrbufAppendC(&media.status, '\n'); + ffStrbufAppend(&media.status, &media.album); + ffStrbufAppendC(&media.status, '\n'); + ffStrbufAppend(&media.status, &media.playerId); + ffStrbufAppendC(&media.status, '\n'); + ffStrbufAppend(&media.status, &media.player); + ffStrbufAppendC(&media.status, '\n'); + if (saveCover) { + ffStrbufAppend(&media.status, &media.cover); + } + ffStrbufAppendC(&media.status, '\n'); + ffStrbufAppendUInt(&media.status, media.position); + ffStrbufAppendC(&media.status, '\n'); + ffStrbufAppendUInt(&media.status, media.length); + write(STDOUT_FILENO, media.status.chars, media.status.length); ffStrbufDestroy(&media.status); ffStrbufDestroy(&media.song); ffStrbufDestroy(&media.artist); @@ -121,46 +152,55 @@ int ffPrintMediaByMediaRemote(bool saveCover) return 0; } -static const char* getMediaByAuthorizedProcess(FFMediaResult* result, bool saveCover) -{ +static const char* getMediaByAuthorizedProcess(FFMediaResult* result, bool saveCover) { // #1737 FF_STRBUF_AUTO_DESTROY script = ffStrbufCreateF("import ctypes;ctypes.CDLL('%s').ffPrintMediaByMediaRemote(%s)", instance.state.platform.exePath.chars, saveCover ? "True" : "False"); FF_STRBUF_AUTO_DESTROY buffer = ffStrbufCreate(); - const char* error = ffProcessAppendStdOut(&buffer, (char* const[]) { - "/usr/bin/python3", // Must be signed by Apple. Homebrew python doesn't work - "-c", - script.chars, - nil - }); - if (error) return error; - if (buffer.length == 0) return "No media found"; + const char* error = ffProcessAppendStdOut( + &buffer, + (char* const[]) { "/usr/bin/python3", // Must be signed by Apple. Homebrew python doesn't work + "-c", + script.chars, + nil }); + if (error) { + return error; + } + if (buffer.length == 0) { + return "No media found"; + } - // status\ntitle\nartist\nalbum\nbundleName\nappName - FFstrbuf* const varList[] = { &result->status, &result->song, &result->artist, &result->album, &result->playerId, &result->player, &result->cover }; + // status\ntitle\nartist\nalbum\nbundleName\nappName\ncoverPath\nelapsedTimeMS\ndurationMS + FFstrbuf* const strList[] = { &result->status, &result->song, &result->artist, &result->album, &result->playerId, &result->player, &result->cover }; char* line = NULL; size_t len = 0; - for (uint32_t i = 0; i < ARRAY_SIZE(varList) && ffStrbufGetline(&line, &len, &buffer); ++i) - ffStrbufSetS(varList[i], line); + for (uint32_t i = 0; i < ARRAY_SIZE(strList) && ffStrbufGetline(&line, &len, &buffer); ++i) { + ffStrbufSetS(strList[i], line); + } + uint32_t* const numList[] = { &result->position, &result->length }; + for (uint32_t i = 0; i < ARRAY_SIZE(numList) && ffStrbufGetline(&line, &len, &buffer); ++i) { + *numList[i] = (uint32_t) strtoul(line, NULL, 10); + } return NULL; } -void ffDetectMediaImpl(FFMediaResult* media, bool saveCover) -{ +void ffDetectMediaImpl(FFMediaResult* media, bool saveCover) { const char* error; - if (@available(macOS 15.4, *)) + if (@available(macOS 15.4, *)) { error = getMediaByAuthorizedProcess(media, saveCover); - else + } else { error = getMediaByMediaRemote(media, saveCover); - if (error) + } + if (error) { ffStrbufAppendS(&media->error, error); - else if (media->player.length == 0 && media->playerId.length > 0) - { + } else if (media->player.length == 0 && media->playerId.length > 0) { ffStrbufSet(&media->player, &media->playerId); - if (ffStrbufStartsWithIgnCaseS(&media->player, "com.")) + if (ffStrbufStartsWithIgnCaseS(&media->player, "com.")) { ffStrbufSubstrAfter(&media->player, strlen("com.") - 1); + } ffStrbufReplaceAllC(&media->player, '.', ' '); - if (media->cover.length > 0) + if (media->cover.length > 0) { media->removeCoverAfterUse = true; + } } } From f71c0b0866ba85e447fc44e9f2789a18a8845677 Mon Sep 17 00:00:00 2001 From: Carter Li Date: Tue, 12 May 2026 16:47:43 +0800 Subject: [PATCH 90/92] Media: adds progress number & bar format support --- doc/json_schema.json | 3 ++ src/modules/media/media.c | 91 ++++++++++++++++++++++++++++++++++++-- src/modules/media/option.h | 2 + 3 files changed, 92 insertions(+), 4 deletions(-) diff --git a/doc/json_schema.json b/doc/json_schema.json index 95224cced8..37bf77114b 100644 --- a/doc/json_schema.json +++ b/doc/json_schema.json @@ -3254,6 +3254,9 @@ "const": "media", "description": "Print the name of the currently playing song" }, + "percent": { + "$ref": "#/$defs/percent" + }, "key": { "$ref": "#/$defs/key" }, diff --git a/src/modules/media/media.c b/src/modules/media/media.c index 59fab0831e..687c1dd3a8 100644 --- a/src/modules/media/media.c +++ b/src/modules/media/media.c @@ -1,6 +1,6 @@ #include "common/printing.h" #include "common/jsonconfig.h" -#include "common/stringUtils.h" +#include "common/percent.h" #include "detection/media/media.h" #include "modules/media/media.h" @@ -88,6 +88,8 @@ bool ffPrintMedia(FFMediaOptions* options) { ffStrbufAppend(&songPretty, &media->song); } + FFPercentageTypeFlags percentType = options->percent.type == 0 ? instance.config.display.percentType : options->percent.type; + if (options->moduleArgs.outputFormat.length == 0) { // We don't expose artistPretty to the format, as it might be empty (when the think that the artist is already in the song title) FF_STRBUF_AUTO_DESTROY artistPretty = ffStrbufCreateCopy(&media->artist); @@ -106,18 +108,85 @@ bool ffPrintMedia(FFMediaOptions* options) { fputs(" - ", stdout); } + if (media->length > 0) { + bool hasProgress = false; + ffStrbufAppendS(&songPretty, " ("); + if (!(percentType & FF_PERCENTAGE_TYPE_HIDE_OTHERS_BIT)) { + uint32_t sLen = media->length / 1000, sPos = media->position / 1000; + ffStrbufAppendF(&songPretty, "%02u:%02u / %02u:%02u", sPos / 60, sPos % 60, sLen / 60, sLen % 60); + hasProgress = true; + } + + if (percentType & FF_PERCENTAGE_TYPE_NUM_BIT) { + if (hasProgress) { + ffStrbufAppendS(&songPretty, " - "); + } + ffPercentAppendNum( + &songPretty, + media->position * 100.0 / media->length, + options->percent, + true, + &options->moduleArgs); + hasProgress = true; + } + if (percentType & FF_PERCENTAGE_TYPE_BAR_BIT) { + if (hasProgress) { + ffStrbufAppendS(&songPretty, " - "); + } + ffPercentAppendBar( + &songPretty, + media->position * 100.0 / media->length, + options->percent, + &options->moduleArgs); + hasProgress = true; + } + + if (hasProgress) { + ffStrbufAppendC(&songPretty, ')'); + } else { + ffStrbufSubstrBefore(&songPretty, songPretty.length - 2); + } + } + if (media->status.length > 0) { - ffStrbufAppendF(&songPretty, " (%s)", media->status.chars); + ffStrbufAppendF(&songPretty, " [%s]", media->status.chars); } ffStrbufPutTo(&songPretty, stdout); } else { - FF_PRINT_FORMAT_CHECKED(FF_MEDIA_MODULE_NAME, 0, &options->moduleArgs, FF_PRINT_TYPE_DEFAULT, ((FFformatarg[]) { + FF_STRBUF_AUTO_DESTROY progress = ffStrbufCreate(); + FF_STRBUF_AUTO_DESTROY percentageNum = ffStrbufCreate(); + FF_STRBUF_AUTO_DESTROY percentageBar = ffStrbufCreate(); + if (media->length > 0) { + if (!(percentType & FF_PERCENTAGE_TYPE_HIDE_OTHERS_BIT)) { + uint32_t sLen = media->length / 1000, sPos = media->position / 1000; + ffStrbufSetF(&progress, "%02u:%02u / %02u:%02u", sPos / 60, sPos % 60, sLen / 60, sLen % 60); + } + if (percentType & FF_PERCENTAGE_TYPE_NUM_BIT) { + ffPercentAppendNum( + &percentageNum, + media->position * 100.0 / media->length, + options->percent, + false, + &options->moduleArgs); + } + if (percentType & FF_PERCENTAGE_TYPE_BAR_BIT) { + ffPercentAppendBar( + &percentageBar, + media->position * 100.0 / media->length, + options->percent, + &options->moduleArgs); + } + } + FF_PRINT_FORMAT_CHECKED(FF_MEDIA_MODULE_NAME, 0, &options->moduleArgs, FF_PRINT_TYPE_DEFAULT, ((FFformatarg[]){ FF_ARG(songPretty, "combined"), FF_ARG(media->song, "title"), FF_ARG(media->artist, "artist"), FF_ARG(media->album, "album"), FF_ARG(media->status, "status"), + FF_ARG(progress, "progress"), + FF_ARG(percentageNum, "progress-num"), + FF_ARG(percentageBar, "progress-bar"), FF_ARG(media->player, "player-name"), FF_ARG(media->playerId, "player-id"), FF_ARG(media->url, "url"), @@ -135,12 +204,18 @@ void ffParseMediaJsonObject(FFMediaOptions* options, yyjson_val* module) { continue; } + if (ffPercentParseJsonObject(key, val, &options->percent)) { + continue; + } + ffPrintError(FF_MEDIA_MODULE_NAME, 0, &options->moduleArgs, FF_PRINT_TYPE_DEFAULT, "Unknown JSON key %s", unsafe_yyjson_get_str(key)); } } void ffGenerateMediaJsonConfig(FFMediaOptions* options, yyjson_mut_doc* doc, yyjson_mut_val* module) { ffJsonConfigGenerateModuleArgsConfig(doc, module, &options->moduleArgs); + + ffPercentGenerateJsonConfig(doc, module, options->percent); } bool ffGenerateMediaJsonResult(FF_A_UNUSED FFMediaOptions* options, yyjson_mut_doc* doc, yyjson_mut_val* module) { @@ -176,6 +251,8 @@ bool ffGenerateMediaJsonResult(FF_A_UNUSED FFMediaOptions* options, yyjson_mut_d void ffInitMediaOptions(FFMediaOptions* options) { ffOptionInitModuleArg(&options->moduleArgs, ""); + + options->percent = (FFPercentageModuleConfig){ 100, 100, 0 }; } void ffDestroyMediaOptions(FFMediaOptions* options) { @@ -191,11 +268,17 @@ FFModuleBaseInfo ffMediaModuleInfo = { .printModule = (void*) ffPrintMedia, .generateJsonResult = (void*) ffGenerateMediaJsonResult, .generateJsonConfig = (void*) ffGenerateMediaJsonConfig, - .formatArgs = FF_FORMAT_ARG_LIST(((FFModuleFormatArg[]) { + .formatArgs = FF_FORMAT_ARG_LIST(((FFModuleFormatArg[]){ { "Pretty media name", "combined" }, { "Media name", "title" }, { "Artist name", "artist" }, { "Album name", "album" }, { "Status", "status" }, + { "Progress in text", "progress" }, + { "Progress in percentage (number)", "progress-num" }, + { "Progress in percentage (bar)", "progress-bar" }, + { "Player name", "player-name" }, + { "Player ID", "player-id" }, + { "URL", "url" }, })) }; diff --git a/src/modules/media/option.h b/src/modules/media/option.h index a8841f96e8..0207832d5d 100644 --- a/src/modules/media/option.h +++ b/src/modules/media/option.h @@ -1,9 +1,11 @@ #pragma once #include "common/option.h" +#include "common/percent.h" typedef struct FFMediaOptions { FFModuleArgs moduleArgs; + FFPercentageModuleConfig percent; } FFMediaOptions; static_assert(sizeof(FFMediaOptions) <= FF_OPTION_MAX_SIZE, "FFMediaOptions size exceeds maximum allowed size"); From af7e2d47d193de5dbee6366ca222f874b843c75c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=9D=8E=E9=80=9A=E6=B4=B2?= Date: Tue, 12 May 2026 22:41:36 +0800 Subject: [PATCH 91/92] OS (Windows): adds support for Embedded variant in OS detection --- src/detection/os/os_windows.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/detection/os/os_windows.c b/src/detection/os/os_windows.c index f4eb113391..12e51bbeab 100644 --- a/src/detection/os/os_windows.c +++ b/src/detection/os/os_windows.c @@ -1,5 +1,4 @@ #include "os.h" -#include "common/library.h" #include "common/stringUtils.h" #include "common/windows/registry.h" #include "common/windows/unicode.h" @@ -56,6 +55,9 @@ void ffDetectOSImpl(FFOSResult* os) { if (ffStrbufStartsWithS(&os->variant, "Server ")) { ffStrbufAppendS(&os->name, " Server"); ffStrbufSubstrAfter(&os->variant, strlen(" Server") - 1); + } else if (ffStrbufStartsWithS(&os->variant, "Embedded ")) { + ffStrbufAppendS(&os->name, " Embedded"); + ffStrbufSubstrAfter(&os->variant, strlen(" Embedded") - 1); } if (ffStrbufStartsWithIgnCaseS(&os->variant, "(TM) ")) { From d6bd855733d362788ce2c589f2f0dbee739737c0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=9D=8E=E9=80=9A=E6=B4=B2?= Date: Tue, 12 May 2026 22:54:44 +0800 Subject: [PATCH 92/92] DateTime: adds AM/PM format support to date and time output Fixes #2325 --- src/modules/datetime/datetime.c | 63 +++++++++++++++++++-------------- 1 file changed, 36 insertions(+), 27 deletions(-) diff --git a/src/modules/datetime/datetime.c b/src/modules/datetime/datetime.c index 1776abc975..be1188a103 100644 --- a/src/modules/datetime/datetime.c +++ b/src/modules/datetime/datetime.c @@ -35,6 +35,7 @@ typedef struct FFDateTimeResult { char secondPretty[FASTFETCH_STRBUF_DEFAULT_ALLOC]; // 37 char offsetFromUtc[FASTFETCH_STRBUF_DEFAULT_ALLOC]; char timezoneName[FASTFETCH_STRBUF_DEFAULT_ALLOC]; + char amPm[FASTFETCH_STRBUF_DEFAULT_ALLOC]; } FFDateTimeResult; static void printDateTimeFormat(struct tm* tm, const FFModuleArgs* moduleArgs) { @@ -63,32 +64,39 @@ static void printDateTimeFormat(struct tm* tm, const FFModuleArgs* moduleArgs) { strftime(result.secondPretty, sizeof(result.secondPretty), "%S", tm); strftime(result.offsetFromUtc, sizeof(result.offsetFromUtc), "%z", tm); strftime(result.timezoneName, sizeof(result.timezoneName), "%Z", tm); - - FF_PRINT_FORMAT_CHECKED(FF_DATETIME_DISPLAY_NAME, 0, moduleArgs, FF_PRINT_TYPE_DEFAULT, ((FFformatarg[]) { - FF_ARG(result.year, "year"), // 1 - FF_ARG(result.yearShort, "year-short"), // 2 - FF_ARG(result.month, "month"), // 3 - FF_ARG(result.monthPretty, "month-pretty"), // 4 - FF_ARG(result.monthName, "month-name"), // 5 - FF_ARG(result.monthNameShort, "month-name-short"), // 6 - FF_ARG(result.week, "week"), // 7 - FF_ARG(result.weekday, "weekday"), // 8 - FF_ARG(result.weekdayShort, "weekday-short"), // 9 - FF_ARG(result.dayInYear, "day-in-year"), // 10 - FF_ARG(result.dayInMonth, "day-in-month"), // 11 - FF_ARG(result.dayInWeek, "day-in-week"), // 12 - FF_ARG(result.hour, "hour"), // 13 - FF_ARG(result.hourPretty, "hour-pretty"), // 14 - FF_ARG(result.hour12, "hour-12"), // 15 - FF_ARG(result.hour12Pretty, "hour-12-pretty"), // 16 - FF_ARG(result.minute, "minute"), // 17 - FF_ARG(result.minutePretty, "minute-pretty"), // 18 - FF_ARG(result.second, "second"), // 19 - FF_ARG(result.secondPretty, "second-pretty"), // 20 - FF_ARG(result.offsetFromUtc, "offset-from-utc"), // 21 - FF_ARG(result.timezoneName, "timezone-name"), // 22 - FF_ARG(result.dayPretty, "day-pretty"), // 23 - })); + strftime(result.amPm, sizeof(result.amPm), "%p", tm); + + FF_PRINT_FORMAT_CHECKED( + FF_DATETIME_DISPLAY_NAME, + 0, + moduleArgs, + FF_PRINT_TYPE_DEFAULT, + ((FFformatarg[]){ + FF_ARG(result.year, "year"), // 1 + FF_ARG(result.yearShort, "year-short"), // 2 + FF_ARG(result.month, "month"), // 3 + FF_ARG(result.monthPretty, "month-pretty"), // 4 + FF_ARG(result.monthName, "month-name"), // 5 + FF_ARG(result.monthNameShort, "month-name-short"), // 6 + FF_ARG(result.week, "week"), // 7 + FF_ARG(result.weekday, "weekday"), // 8 + FF_ARG(result.weekdayShort, "weekday-short"), // 9 + FF_ARG(result.dayInYear, "day-in-year"), // 10 + FF_ARG(result.dayInMonth, "day-in-month"), // 11 + FF_ARG(result.dayInWeek, "day-in-week"), // 12 + FF_ARG(result.hour, "hour"), // 13 + FF_ARG(result.hourPretty, "hour-pretty"), // 14 + FF_ARG(result.hour12, "hour-12"), // 15 + FF_ARG(result.hour12Pretty, "hour-12-pretty"), // 16 + FF_ARG(result.minute, "minute"), // 17 + FF_ARG(result.minutePretty, "minute-pretty"), // 18 + FF_ARG(result.second, "second"), // 19 + FF_ARG(result.secondPretty, "second-pretty"), // 20 + FF_ARG(result.offsetFromUtc, "offset-from-utc"), // 21 + FF_ARG(result.timezoneName, "timezone-name"), // 22 + FF_ARG(result.dayPretty, "day-pretty"), // 23 + FF_ARG(result.amPm, "am-pm"), // 24 + })); } bool ffPrintDateTime(FFDateTimeOptions* options) { @@ -152,7 +160,7 @@ FFModuleBaseInfo ffDateTimeModuleInfo = { .printModule = (void*) ffPrintDateTime, .generateJsonResult = (void*) ffGenerateDateTimeJsonResult, .generateJsonConfig = (void*) ffGenerateDateTimeJsonConfig, - .formatArgs = FF_FORMAT_ARG_LIST(((FFModuleFormatArg[]) { + .formatArgs = FF_FORMAT_ARG_LIST(((FFModuleFormatArg[]){ { "Year", "year" }, { "Last two digits of year", "year-short" }, { "Month", "month" }, @@ -176,5 +184,6 @@ FFModuleBaseInfo ffDateTimeModuleInfo = { { "Offset from UTC in the ISO 8601 format", "offset-from-utc" }, { "Locale-dependent timezone name or abbreviation", "timezone-name" }, { "Day in month with leading zero", "day-pretty" }, + { "AM or PM", "am-pm" }, })) };