Repository: fastfetch-cli/fastfetch Branch: dev Commit: 56eabbe2cb96 Files: 1400 Total size: 5.0 MB Directory structure: gitextract_aqn42s14/ ├── .codespellrc ├── .editorconfig ├── .gitattributes ├── .github/ │ ├── FUNDING.yml │ ├── ISSUE_TEMPLATE/ │ │ ├── bug_report_crash.yml │ │ ├── bug_report_general.yml │ │ ├── bug_report_logo.yml │ │ ├── feature_request.yml │ │ └── logo_request.yml │ ├── pull_request_template.md │ ├── stale.yml │ └── workflows/ │ └── ci.yml ├── .gitignore ├── CHANGELOG.md ├── CMakeLists.txt ├── CODE_OF_CONDUCT.md ├── LICENSE ├── README-cn.md ├── README.md ├── completions/ │ ├── fastfetch.bash │ ├── fastfetch.fish │ └── fastfetch.zsh ├── debian/ │ ├── changelog.tpl │ ├── compat │ ├── control │ ├── copyright │ ├── publish.sh │ ├── rules │ └── watch ├── doc/ │ ├── fastfetch.1.in │ └── json_schema.json ├── presets/ │ ├── all.jsonc │ ├── archey.jsonc │ ├── ci.jsonc │ ├── examples/ │ │ ├── 10.jsonc │ │ ├── 11.jsonc │ │ ├── 12.jsonc │ │ ├── 13.jsonc │ │ ├── 14.jsonc │ │ ├── 15.jsonc │ │ ├── 16.jsonc │ │ ├── 17.jsonc │ │ ├── 18.jsonc │ │ ├── 19.jsonc │ │ ├── 2.jsonc │ │ ├── 20.jsonc │ │ ├── 21.jsonc │ │ ├── 22.jsonc │ │ ├── 23.jsonc │ │ ├── 24.jsonc │ │ ├── 25.jsonc │ │ ├── 26.jsonc │ │ ├── 27.jsonc │ │ ├── 28.jsonc │ │ ├── 29.jsonc │ │ ├── 3.jsonc │ │ ├── 30.jsonc │ │ ├── 31.jsonc │ │ ├── 32.jsonc │ │ ├── 4.jsonc │ │ ├── 5.jsonc │ │ ├── 6.jsonc │ │ ├── 7.jsonc │ │ ├── 8.jsonc │ │ └── 9.jsonc │ ├── neofetch.jsonc │ ├── paleofetch.jsonc │ └── screenfetch.jsonc ├── run.sh ├── scripts/ │ ├── gen-amdgpuids.py │ ├── gen-man.py │ └── gen-pciids.py ├── src/ │ ├── 3rdparty/ │ │ ├── display-library/ │ │ │ ├── adl_defines.h │ │ │ ├── adl_sdk.h │ │ │ ├── adl_structures.h │ │ │ └── repo.json │ │ ├── widecharwidth/ │ │ │ ├── repo.json │ │ │ └── widechar_width_c.h │ │ └── yyjson/ │ │ ├── repo.json │ │ ├── yyjson.c │ │ └── yyjson.h │ ├── common/ │ │ ├── FFPlatform.h │ │ ├── FFcheckmacros.h │ │ ├── FFlist.h │ │ ├── FFstrbuf.h │ │ ├── apple/ │ │ │ ├── Info.plist.in │ │ │ ├── cf_helpers.c │ │ │ ├── cf_helpers.h │ │ │ ├── osascript.h │ │ │ ├── osascript.m │ │ │ ├── smc_temps.c │ │ │ └── smc_temps.h │ │ ├── argType.h │ │ ├── arrayUtils.h │ │ ├── base64.h │ │ ├── binary.h │ │ ├── color.h │ │ ├── commandoption.h │ │ ├── dbus.h │ │ ├── debug.h │ │ ├── duration.h │ │ ├── edidHelper.h │ │ ├── ffdata.h │ │ ├── font.h │ │ ├── format.h │ │ ├── frequency.h │ │ ├── haiku/ │ │ │ ├── version.cpp │ │ │ └── version.h │ │ ├── impl/ │ │ │ ├── FFPlatform.c │ │ │ ├── FFPlatform_private.h │ │ │ ├── FFPlatform_unix.c │ │ │ ├── FFPlatform_windows.c │ │ │ ├── FFlist.c │ │ │ ├── FFstrbuf.c │ │ │ ├── base64.c │ │ │ ├── binary_apple.c │ │ │ ├── binary_linux.c │ │ │ ├── binary_windows.c │ │ │ ├── commandoption.c │ │ │ ├── dbus.c │ │ │ ├── debug_windows.c │ │ │ ├── duration.c │ │ │ ├── edidHelper.c │ │ │ ├── font.c │ │ │ ├── format.c │ │ │ ├── frequency.c │ │ │ ├── init.c │ │ │ ├── io_unix.c │ │ │ ├── io_windows.c │ │ │ ├── jsonconfig.c │ │ │ ├── kmod_apple.c │ │ │ ├── kmod_bsd.c │ │ │ ├── kmod_linux.c │ │ │ ├── kmod_nbsd.c │ │ │ ├── kmod_nosupport.c │ │ │ ├── kmod_sunos.c │ │ │ ├── kmod_windows.c │ │ │ ├── library.c │ │ │ ├── memrchr.c │ │ │ ├── netif.c │ │ │ ├── netif_apple.c │ │ │ ├── netif_bsd.c │ │ │ ├── netif_gnu.c │ │ │ ├── netif_haiku.c │ │ │ ├── netif_linux.c │ │ │ ├── netif_windows.c │ │ │ ├── networking_common.c │ │ │ ├── networking_linux.c │ │ │ ├── networking_windows.c │ │ │ ├── option.c │ │ │ ├── parsing.c │ │ │ ├── path.c │ │ │ ├── percent.c │ │ │ ├── printing.c │ │ │ ├── processing_linux.c │ │ │ ├── processing_windows.c │ │ │ ├── properties.c │ │ │ ├── settings.c │ │ │ ├── size.c │ │ │ ├── smbiosHelper.c │ │ │ ├── sysctl.c │ │ │ ├── temps.c │ │ │ ├── time.c │ │ │ └── wcwidth.c │ │ ├── init.h │ │ ├── io.h │ │ ├── jsonconfig.h │ │ ├── kmod.h │ │ ├── library.h │ │ ├── mallocHelper.h │ │ ├── memrchr.h │ │ ├── netif.h │ │ ├── networking.h │ │ ├── option.h │ │ ├── parsing.h │ │ ├── path.h │ │ ├── percent.h │ │ ├── printing.h │ │ ├── processing.h │ │ ├── properties.h │ │ ├── settings.h │ │ ├── size.h │ │ ├── smbiosHelper.h │ │ ├── stringUtils.h │ │ ├── sysctl.h │ │ ├── temps.h │ │ ├── textModifier.h │ │ ├── thread.h │ │ ├── time.h │ │ ├── unused.h │ │ ├── wcwidth.h │ │ └── windows/ │ │ ├── c-logo.sh │ │ ├── com.cpp │ │ ├── com.hpp │ │ ├── getline.c │ │ ├── getline.h │ │ ├── manifest.xml │ │ ├── nt.h │ │ ├── perflib_.h │ │ ├── registry.c │ │ ├── registry.h │ │ ├── unicode.c │ │ ├── unicode.h │ │ ├── unicode.hpp │ │ ├── util.hpp │ │ ├── variant.cpp │ │ ├── variant.hpp │ │ ├── version.c │ │ ├── version.h │ │ ├── version.rc │ │ ├── wmi.cpp │ │ └── wmi.hpp │ ├── data/ │ │ ├── help.json │ │ └── structure.txt │ ├── detection/ │ │ ├── battery/ │ │ │ ├── battery.h │ │ │ ├── battery_android.c │ │ │ ├── battery_apple.c │ │ │ ├── battery_bsd.c │ │ │ ├── battery_haiku.c │ │ │ ├── battery_linux.c │ │ │ ├── battery_nbsd.c │ │ │ ├── battery_nosupport.c │ │ │ ├── battery_obsd.c │ │ │ └── battery_windows.c │ │ ├── bios/ │ │ │ ├── bios.h │ │ │ ├── bios_android.c │ │ │ ├── bios_apple.c │ │ │ ├── bios_bsd.c │ │ │ ├── bios_linux.c │ │ │ ├── bios_nbsd.c │ │ │ ├── bios_nosupport.c │ │ │ └── bios_windows.c │ │ ├── bluetooth/ │ │ │ ├── bluetooth.h │ │ │ ├── bluetooth_apple.m │ │ │ ├── bluetooth_bsd.c │ │ │ ├── bluetooth_haiku.cpp │ │ │ ├── bluetooth_linux.c │ │ │ ├── bluetooth_nosupport.c │ │ │ ├── bluetooth_windows.c │ │ │ └── bluetooth_windows.cpp │ │ ├── bluetoothradio/ │ │ │ ├── bluetoothradio.c │ │ │ ├── bluetoothradio.h │ │ │ ├── bluetoothradio_apple.m │ │ │ ├── bluetoothradio_linux.c │ │ │ ├── bluetoothradio_nosupport.c │ │ │ └── bluetoothradio_windows.c │ │ ├── board/ │ │ │ ├── board.h │ │ │ ├── board_android.c │ │ │ ├── board_apple.c │ │ │ ├── board_bsd.c │ │ │ ├── board_linux.c │ │ │ ├── board_nbsd.c │ │ │ ├── board_nosupport.c │ │ │ └── board_windows.c │ │ ├── bootmgr/ │ │ │ ├── bootmgr.c │ │ │ ├── bootmgr.h │ │ │ ├── bootmgr_apple.c │ │ │ ├── bootmgr_bsd.c │ │ │ ├── bootmgr_linux.c │ │ │ ├── bootmgr_nosupport.c │ │ │ ├── bootmgr_windows.c │ │ │ └── efi_helper.h │ │ ├── brightness/ │ │ │ ├── brightness.h │ │ │ ├── brightness_apple.c │ │ │ ├── brightness_bsd.c │ │ │ ├── brightness_linux.c │ │ │ ├── brightness_nbsd.c │ │ │ ├── brightness_nosupport.c │ │ │ ├── brightness_obsd.c │ │ │ └── brightness_windows.cpp │ │ ├── btrfs/ │ │ │ ├── btrfs.h │ │ │ ├── btrfs_linux.c │ │ │ └── btrfs_nosupport.c │ │ ├── camera/ │ │ │ ├── camera.h │ │ │ ├── camera_android.c │ │ │ ├── camera_apple.m │ │ │ ├── camera_linux.c │ │ │ ├── camera_nosupport.c │ │ │ └── camera_windows.cpp │ │ ├── chassis/ │ │ │ ├── chassis.c │ │ │ ├── chassis.h │ │ │ ├── chassis_apple.c │ │ │ ├── chassis_bsd.c │ │ │ ├── chassis_linux.c │ │ │ ├── chassis_nbsd.c │ │ │ ├── chassis_nosupport.c │ │ │ └── chassis_windows.c │ │ ├── command/ │ │ │ ├── command.c │ │ │ └── command.h │ │ ├── cpu/ │ │ │ ├── cpu.c │ │ │ ├── cpu.h │ │ │ ├── cpu_apple.c │ │ │ ├── cpu_arm.h │ │ │ ├── cpu_bsd.c │ │ │ ├── cpu_haiku.c │ │ │ ├── cpu_linux.c │ │ │ ├── cpu_nbsd.c │ │ │ ├── cpu_nosupport.c │ │ │ ├── cpu_obsd.c │ │ │ ├── cpu_sunos.c │ │ │ └── cpu_windows.c │ │ ├── cpucache/ │ │ │ ├── cpucache.h │ │ │ ├── cpucache_apple.c │ │ │ ├── cpucache_linux.c │ │ │ ├── cpucache_nosupport.c │ │ │ ├── cpucache_shared.c │ │ │ └── cpucache_windows.c │ │ ├── cpuusage/ │ │ │ ├── cpuusage.c │ │ │ ├── cpuusage.h │ │ │ ├── cpuusage_apple.c │ │ │ ├── cpuusage_bsd.c │ │ │ ├── cpuusage_haiku.c │ │ │ ├── cpuusage_linux.c │ │ │ ├── cpuusage_nosupport.c │ │ │ ├── cpuusage_sunos.c │ │ │ └── cpuusage_windows.c │ │ ├── cursor/ │ │ │ ├── cursor.h │ │ │ ├── cursor_apple.m │ │ │ ├── cursor_linux.c │ │ │ ├── cursor_nosupport.c │ │ │ └── cursor_windows.c │ │ ├── de/ │ │ │ ├── de.h │ │ │ ├── de_linux.c │ │ │ └── de_nosupport.c │ │ ├── disk/ │ │ │ ├── disk.c │ │ │ ├── disk.h │ │ │ ├── disk_bsd.c │ │ │ ├── disk_haiku.cpp │ │ │ ├── disk_linux.c │ │ │ ├── disk_nosupport.c │ │ │ ├── disk_sunos.c │ │ │ └── disk_windows.c │ │ ├── diskio/ │ │ │ ├── diskio.c │ │ │ ├── diskio.h │ │ │ ├── diskio_apple.c │ │ │ ├── diskio_bsd.c │ │ │ ├── diskio_linux.c │ │ │ ├── diskio_nbsd.c │ │ │ ├── diskio_nosupport.c │ │ │ ├── diskio_obsd.c │ │ │ ├── diskio_sunos.c │ │ │ └── diskio_windows.c │ │ ├── displayserver/ │ │ │ ├── displayserver.c │ │ │ ├── displayserver.h │ │ │ ├── displayserver_android.c │ │ │ ├── displayserver_apple.c │ │ │ ├── displayserver_haiku.cpp │ │ │ ├── displayserver_windows.c │ │ │ └── linux/ │ │ │ ├── common.c │ │ │ ├── displayserver_linux.c │ │ │ ├── displayserver_linux.h │ │ │ ├── drm.c │ │ │ ├── wayland/ │ │ │ │ ├── global-output.c │ │ │ │ ├── kde-output-device-v2-client-protocol.h │ │ │ │ ├── kde-output-device-v2-protocol.c │ │ │ │ ├── kde-output-order-v1-client-protocol.h │ │ │ │ ├── kde-output-order-v1-protocol.c │ │ │ │ ├── kde-output.c │ │ │ │ ├── wayland.c │ │ │ │ ├── wayland.h │ │ │ │ ├── wlr-output-management-unstable-v1-client-protocol.h │ │ │ │ ├── wlr-output-management-unstable-v1-protocol.c │ │ │ │ ├── xdg-output-unstable-v1-client-protocol.h │ │ │ │ ├── xdg-output-unstable-v1-protocol.c │ │ │ │ └── zwlr-output.c │ │ │ ├── wmde.c │ │ │ ├── xcb.c │ │ │ └── xlib.c │ │ ├── dns/ │ │ │ ├── dns.h │ │ │ ├── dns_apple.c │ │ │ ├── dns_linux.c │ │ │ └── dns_windows.c │ │ ├── editor/ │ │ │ ├── editor.c │ │ │ └── editor.h │ │ ├── font/ │ │ │ ├── font.c │ │ │ ├── font.h │ │ │ ├── font_apple.m │ │ │ ├── font_haiku.cpp │ │ │ ├── font_linux.c │ │ │ ├── font_nosupport.c │ │ │ └── font_windows.c │ │ ├── gamepad/ │ │ │ ├── gamepad.h │ │ │ ├── gamepad_apple.c │ │ │ ├── gamepad_bsd.c │ │ │ ├── gamepad_haiku.cpp │ │ │ ├── gamepad_linux.c │ │ │ ├── gamepad_nosupport.c │ │ │ └── gamepad_windows.c │ │ ├── gpu/ │ │ │ ├── adl.h │ │ │ ├── asahi_drm.h │ │ │ ├── gpu.c │ │ │ ├── gpu.h │ │ │ ├── gpu_amd.c │ │ │ ├── gpu_android.c │ │ │ ├── gpu_apple.c │ │ │ ├── gpu_apple.m │ │ │ ├── gpu_bsd.c │ │ │ ├── gpu_driver_specific.h │ │ │ ├── gpu_drm.c │ │ │ ├── gpu_gnu.c │ │ │ ├── gpu_haiku.c │ │ │ ├── gpu_intel.c │ │ │ ├── gpu_linux.c │ │ │ ├── gpu_mthreads.c │ │ │ ├── gpu_nbsd.c │ │ │ ├── gpu_nosupport.c │ │ │ ├── gpu_nvidia.c │ │ │ ├── gpu_obsd.c │ │ │ ├── gpu_pci.c │ │ │ ├── gpu_sunos.c │ │ │ ├── gpu_windows.c │ │ │ ├── gpu_wsl.cpp │ │ │ ├── igcl.h │ │ │ ├── intel_drm.h │ │ │ ├── mtml.h │ │ │ ├── nvapi.h │ │ │ └── nvml.h │ │ ├── gtk_qt/ │ │ │ ├── gtk.c │ │ │ ├── gtk_qt.h │ │ │ └── qt.c │ │ ├── host/ │ │ │ ├── host.h │ │ │ ├── host_android.c │ │ │ ├── host_apple.c │ │ │ ├── host_bsd.c │ │ │ ├── host_linux.c │ │ │ ├── host_mac.c │ │ │ ├── host_nbsd.c │ │ │ ├── host_nosupport.c │ │ │ ├── host_obsd.c │ │ │ └── host_windows.c │ │ ├── icons/ │ │ │ ├── icons.h │ │ │ ├── icons_linux.c │ │ │ ├── icons_nosupport.c │ │ │ └── icons_windows.c │ │ ├── initsystem/ │ │ │ ├── initsystem.h │ │ │ ├── initsystem_haiku.c │ │ │ ├── initsystem_linux.c │ │ │ └── initsystem_nosupport.c │ │ ├── keyboard/ │ │ │ ├── keyboard.h │ │ │ ├── keyboard_apple.c │ │ │ ├── keyboard_bsd.c │ │ │ ├── keyboard_haiku.cpp │ │ │ ├── keyboard_linux.c │ │ │ ├── keyboard_nosupport.c │ │ │ └── keyboard_windows.c │ │ ├── libc/ │ │ │ ├── libc.h │ │ │ ├── libc_android.c │ │ │ ├── libc_apple.c │ │ │ ├── libc_bsd.c │ │ │ ├── libc_linux.c │ │ │ ├── libc_nosupport.c │ │ │ └── libc_windows.cpp │ │ ├── lm/ │ │ │ ├── lm.h │ │ │ ├── lm_linux.c │ │ │ └── lm_nosupport.c │ │ ├── loadavg/ │ │ │ ├── loadavg.h │ │ │ ├── loadavg_bsd.c │ │ │ ├── loadavg_linux.c │ │ │ ├── loadavg_nosupport.c │ │ │ └── loadavg_sunos.c │ │ ├── locale/ │ │ │ ├── locale.h │ │ │ ├── locale_linux.c │ │ │ └── locale_windows.c │ │ ├── localip/ │ │ │ ├── localip.h │ │ │ ├── localip_linux.c │ │ │ └── localip_windows.c │ │ ├── media/ │ │ │ ├── media.c │ │ │ ├── media.h │ │ │ ├── media_apple.m │ │ │ ├── media_linux.c │ │ │ ├── media_nosupport.c │ │ │ ├── media_windows.c │ │ │ ├── media_windows.dll.cpp │ │ │ └── media_windows.dll.h │ │ ├── memory/ │ │ │ ├── memory.h │ │ │ ├── memory_apple.c │ │ │ ├── memory_bsd.c │ │ │ ├── memory_haiku.c │ │ │ ├── memory_linux.c │ │ │ ├── memory_nbsd.c │ │ │ ├── memory_nosupport.c │ │ │ ├── memory_obsd.c │ │ │ ├── memory_sunos.c │ │ │ └── memory_windows.c │ │ ├── mouse/ │ │ │ ├── mouse.h │ │ │ ├── mouse_apple.c │ │ │ ├── mouse_bsd.c │ │ │ ├── mouse_haiku.cpp │ │ │ ├── mouse_linux.c │ │ │ ├── mouse_nosupport.c │ │ │ └── mouse_windows.c │ │ ├── netio/ │ │ │ ├── netio.c │ │ │ ├── netio.h │ │ │ ├── netio_apple.c │ │ │ ├── netio_bsd.c │ │ │ ├── netio_haiku.cpp │ │ │ ├── netio_linux.c │ │ │ ├── netio_nosupport.c │ │ │ ├── netio_sunos.c │ │ │ └── netio_windows.c │ │ ├── opencl/ │ │ │ ├── opencl.c │ │ │ └── opencl.h │ │ ├── opengl/ │ │ │ ├── opengl.h │ │ │ ├── opengl_apple.c │ │ │ ├── opengl_haiku.cpp │ │ │ ├── opengl_linux.c │ │ │ ├── opengl_shared.c │ │ │ └── opengl_windows.c │ │ ├── os/ │ │ │ ├── os.c │ │ │ ├── os.h │ │ │ ├── os_android.c │ │ │ ├── os_apple.m │ │ │ ├── os_haiku.c │ │ │ ├── os_linux.c │ │ │ ├── os_nbsd.c │ │ │ ├── os_obsd.c │ │ │ ├── os_sunos.c │ │ │ └── os_windows.c │ │ ├── packages/ │ │ │ ├── packages.c │ │ │ ├── packages.h │ │ │ ├── packages_apple.c │ │ │ ├── packages_bsd.c │ │ │ ├── packages_haiku.c │ │ │ ├── packages_linux.c │ │ │ ├── packages_nbsd.c │ │ │ ├── packages_nix.c │ │ │ ├── packages_nosupport.c │ │ │ ├── packages_obsd.c │ │ │ ├── packages_sunos.c │ │ │ └── packages_windows.c │ │ ├── physicaldisk/ │ │ │ ├── physicaldisk.h │ │ │ ├── physicaldisk_apple.c │ │ │ ├── physicaldisk_bsd.c │ │ │ ├── physicaldisk_haiku.c │ │ │ ├── physicaldisk_linux.c │ │ │ ├── physicaldisk_nosupport.c │ │ │ ├── physicaldisk_sunos.c │ │ │ └── physicaldisk_windows.c │ │ ├── physicalmemory/ │ │ │ ├── physicalmemory.c │ │ │ ├── physicalmemory.h │ │ │ ├── physicalmemory_apple.m │ │ │ ├── physicalmemory_linux.c │ │ │ └── physicalmemory_nosupport.c │ │ ├── poweradapter/ │ │ │ ├── poweradapter.h │ │ │ ├── poweradapter_apple.c │ │ │ ├── poweradapter_linux.c │ │ │ └── poweradapter_nosupport.c │ │ ├── processes/ │ │ │ ├── processes.h │ │ │ ├── processes_bsd.c │ │ │ ├── processes_haiku.c │ │ │ ├── processes_linux.c │ │ │ ├── processes_nbsd.c │ │ │ ├── processes_nosupport.c │ │ │ ├── processes_obsd.c │ │ │ └── processes_windows.c │ │ ├── publicip/ │ │ │ ├── publicip.c │ │ │ └── publicip.h │ │ ├── sound/ │ │ │ ├── audio_oss_sunos.h │ │ │ ├── sound.h │ │ │ ├── sound_apple.c │ │ │ ├── sound_bsd.c │ │ │ ├── sound_haiku.cpp │ │ │ ├── sound_linux.c │ │ │ ├── sound_nbsd.c │ │ │ ├── sound_nosupport.c │ │ │ ├── sound_obsd.c │ │ │ ├── sound_sunos.c │ │ │ └── sound_windows.cpp │ │ ├── swap/ │ │ │ ├── swap.h │ │ │ ├── swap_apple.c │ │ │ ├── swap_bsd.c │ │ │ ├── swap_haiku.c │ │ │ ├── swap_linux.c │ │ │ ├── swap_nosupport.c │ │ │ ├── swap_obsd.c │ │ │ ├── swap_sunos.c │ │ │ └── swap_windows.c │ │ ├── terminalfont/ │ │ │ ├── terminalfont.c │ │ │ ├── terminalfont.h │ │ │ ├── terminalfont_android.c │ │ │ ├── terminalfont_apple.m │ │ │ ├── terminalfont_linux.c │ │ │ └── terminalfont_windows.c │ │ ├── terminalshell/ │ │ │ ├── terminalshell.c │ │ │ ├── terminalshell.h │ │ │ ├── terminalshell_linux.c │ │ │ └── terminalshell_windows.c │ │ ├── terminalsize/ │ │ │ ├── terminalsize.h │ │ │ ├── terminalsize_linux.c │ │ │ └── terminalsize_windows.c │ │ ├── terminaltheme/ │ │ │ ├── terminaltheme.c │ │ │ └── terminaltheme.h │ │ ├── theme/ │ │ │ ├── theme.h │ │ │ ├── theme_apple.c │ │ │ ├── theme_linux.c │ │ │ ├── theme_nosupport.c │ │ │ └── theme_windows.c │ │ ├── tpm/ │ │ │ ├── tpm.h │ │ │ ├── tpm_apple.c │ │ │ ├── tpm_bsd.c │ │ │ ├── tpm_linux.c │ │ │ ├── tpm_nosupport.c │ │ │ └── tpm_windows.c │ │ ├── uptime/ │ │ │ ├── uptime.h │ │ │ ├── uptime_bsd.c │ │ │ ├── uptime_haiku.c │ │ │ ├── uptime_linux.c │ │ │ ├── uptime_sunos.c │ │ │ └── uptime_windows.c │ │ ├── users/ │ │ │ ├── users.h │ │ │ ├── users_linux.c │ │ │ ├── users_nosupport.c │ │ │ ├── users_obsd.c │ │ │ └── users_windows.c │ │ ├── version/ │ │ │ ├── version.c │ │ │ └── version.h │ │ ├── vulkan/ │ │ │ ├── vulkan.c │ │ │ └── vulkan.h │ │ ├── wallpaper/ │ │ │ ├── wallpaper.h │ │ │ ├── wallpaper_apple.m │ │ │ ├── wallpaper_linux.c │ │ │ ├── wallpaper_nosupport.c │ │ │ └── wallpaper_windows.c │ │ ├── weather/ │ │ │ ├── weather.c │ │ │ └── weather.h │ │ ├── wifi/ │ │ │ ├── wifi.h │ │ │ ├── wifi_android.c │ │ │ ├── wifi_apple.m │ │ │ ├── wifi_bsd.c │ │ │ ├── wifi_linux.c │ │ │ ├── wifi_nbsd.c │ │ │ ├── wifi_nosupport.c │ │ │ ├── wifi_obsd.c │ │ │ └── wifi_windows.c │ │ ├── wm/ │ │ │ ├── wm.h │ │ │ ├── wm_apple.m │ │ │ ├── wm_linux.c │ │ │ ├── wm_nosupport.c │ │ │ └── wm_windows.c │ │ ├── wmtheme/ │ │ │ ├── wmtheme.h │ │ │ ├── wmtheme_apple.m │ │ │ ├── wmtheme_linux.c │ │ │ ├── wmtheme_nosupport.c │ │ │ └── wmtheme_windows.c │ │ └── zpool/ │ │ ├── libzfs_simplified.h │ │ ├── zpool.c │ │ └── zpool.h │ ├── fastfetch.c │ ├── fastfetch.h │ ├── fastfetch_config.h.in │ ├── fastfetch_datatext.h.in │ ├── flashfetch.c │ ├── logo/ │ │ ├── ascii/ │ │ │ ├── adelie.txt │ │ │ ├── aeon.txt │ │ │ ├── aeros.txt │ │ │ ├── aerynos.txt │ │ │ ├── afterglow.txt │ │ │ ├── aix.txt │ │ │ ├── almalinux.txt │ │ │ ├── alpine.txt │ │ │ ├── alpine2.txt │ │ │ ├── alpine2_small.txt │ │ │ ├── alpine3_small.txt │ │ │ ├── alpine_small.txt │ │ │ ├── alter.txt │ │ │ ├── altlinux.txt │ │ │ ├── amazon.txt │ │ │ ├── amazon_linux.txt │ │ │ ├── amiga.txt │ │ │ ├── amogos.txt │ │ │ ├── anarchy.txt │ │ │ ├── android.txt │ │ │ ├── android_small.txt │ │ │ ├── anduinos.txt │ │ │ ├── antergos.txt │ │ │ ├── antix.txt │ │ │ ├── anushos.txt │ │ │ ├── aoscos.txt │ │ │ ├── aoscos_old.txt │ │ │ ├── aoscosretro.txt │ │ │ ├── aoscosretro_small.txt │ │ │ ├── aperture.txt │ │ │ ├── apricity.txt │ │ │ ├── arch.txt │ │ │ ├── arch2.txt │ │ │ ├── arch3.txt │ │ │ ├── arch_old.txt │ │ │ ├── arch_small.txt │ │ │ ├── archbox.txt │ │ │ ├── archcraft.txt │ │ │ ├── archcraft2.txt │ │ │ ├── archlabs.txt │ │ │ ├── archstrike.txt │ │ │ ├── arco.txt │ │ │ ├── arco_small.txt │ │ │ ├── arkane.txt │ │ │ ├── armbian.txt │ │ │ ├── armbian2.txt │ │ │ ├── arselinux.txt │ │ │ ├── artix.txt │ │ │ ├── artix2.txt │ │ │ ├── artix2_small.txt │ │ │ ├── artix_small.txt │ │ │ ├── arya.txt │ │ │ ├── asahi.txt │ │ │ ├── asahi2.txt │ │ │ ├── aster.txt │ │ │ ├── asteroidos.txt │ │ │ ├── astos.txt │ │ │ ├── astra_linux.txt │ │ │ ├── athenaos.txt │ │ │ ├── athenaos_old.txt │ │ │ ├── aurora.txt │ │ │ ├── axos.txt │ │ │ ├── azos.txt │ │ │ ├── bedrock.txt │ │ │ ├── bedrock_small.txt │ │ │ ├── biglinux.txt │ │ │ ├── bitrig.txt │ │ │ ├── blackarch.txt │ │ │ ├── blackmesa.txt │ │ │ ├── blackpanther.txt │ │ │ ├── blag.txt │ │ │ ├── blankon.txt │ │ │ ├── bluelight.txt │ │ │ ├── bodhi.txt │ │ │ ├── bonsai.txt │ │ │ ├── bredos.txt │ │ │ ├── bsd.txt │ │ │ ├── bunsenlabs.txt │ │ │ ├── cachyos.txt │ │ │ ├── cachyos_small.txt │ │ │ ├── calculate.txt │ │ │ ├── calinixos.txt │ │ │ ├── calinixos_small.txt │ │ │ ├── carbs.txt │ │ │ ├── cbl_mariner.txt │ │ │ ├── celos.txt │ │ │ ├── center.txt │ │ │ ├── centos.txt │ │ │ ├── centos_small.txt │ │ │ ├── cereus.txt │ │ │ ├── chakra.txt │ │ │ ├── chaletos.txt │ │ │ ├── chapeau.txt │ │ │ ├── chimera_linux.txt │ │ │ ├── chonkysealos.txt │ │ │ ├── chrom.txt │ │ │ ├── cleanjaro.txt │ │ │ ├── cleanjaro_small.txt │ │ │ ├── clear_linux.txt │ │ │ ├── clearos.txt │ │ │ ├── clover.txt │ │ │ ├── cobalt.txt │ │ │ ├── codex.txt │ │ │ ├── condres.txt │ │ │ ├── cosmic.txt │ │ │ ├── crux.txt │ │ │ ├── crux_small.txt │ │ │ ├── crystal.txt │ │ │ ├── cucumber.txt │ │ │ ├── cuerdos.txt │ │ │ ├── cutefishos.txt │ │ │ ├── cuteos.txt │ │ │ ├── cyberos.txt │ │ │ ├── cycledream.txt │ │ │ ├── dahlia.txt │ │ │ ├── darkos.txt │ │ │ ├── debian.txt │ │ │ ├── debian_small.txt │ │ │ ├── deepin.txt │ │ │ ├── desaos.txt │ │ │ ├── devuan.txt │ │ │ ├── devuan_small.txt │ │ │ ├── dietpi.txt │ │ │ ├── dracos.txt │ │ │ ├── dragonfly.txt │ │ │ ├── dragonfly_old.txt │ │ │ ├── dragonfly_small.txt │ │ │ ├── drauger.txt │ │ │ ├── droidian.txt │ │ │ ├── elbrus.txt │ │ │ ├── elementary.txt │ │ │ ├── elementary_small.txt │ │ │ ├── elive.txt │ │ │ ├── emmabuntus.txt │ │ │ ├── emperoros.txt │ │ │ ├── encryptos.txt │ │ │ ├── endeavouros.txt │ │ │ ├── endeavouros_small.txt │ │ │ ├── endless.txt │ │ │ ├── enso.txt │ │ │ ├── eshanizedos.txt │ │ │ ├── eurolinux.txt │ │ │ ├── evolutionos.txt │ │ │ ├── evolutionos_old.txt │ │ │ ├── evolutionos_small.txt │ │ │ ├── eweos.txt │ │ │ ├── exherbo.txt │ │ │ ├── exodia_predator.txt │ │ │ ├── fastfetch.txt │ │ │ ├── fedora.txt │ │ │ ├── fedora2_small.txt │ │ │ ├── fedora_coreos.txt │ │ │ ├── fedora_kinoite.txt │ │ │ ├── fedora_old.txt │ │ │ ├── fedora_sericea.txt │ │ │ ├── fedora_silverblue.txt │ │ │ ├── fedora_small.txt │ │ │ ├── femboyos.txt │ │ │ ├── feren.txt │ │ │ ├── filotimo.txt │ │ │ ├── finnix.txt │ │ │ ├── floflis.txt │ │ │ ├── freebsd.txt │ │ │ ├── freebsd_small.txt │ │ │ ├── freemint.txt │ │ │ ├── frugalware.txt │ │ │ ├── funtoo.txt │ │ │ ├── furreto.txt │ │ │ ├── galliumos.txt │ │ │ ├── garuda.txt │ │ │ ├── garuda_dragon.txt │ │ │ ├── garuda_small.txt │ │ │ ├── gentoo.txt │ │ │ ├── gentoo_small.txt │ │ │ ├── ghostbsd.txt │ │ │ ├── ghostfreak.txt │ │ │ ├── glaucus.txt │ │ │ ├── gnewsense.txt │ │ │ ├── gnome.txt │ │ │ ├── gnu.txt │ │ │ ├── gobolinux.txt │ │ │ ├── goldendoglinux.txt │ │ │ ├── grapheneos.txt │ │ │ ├── grombyang.txt │ │ │ ├── guix.txt │ │ │ ├── guix_small.txt │ │ │ ├── gxde.txt │ │ │ ├── haiku.txt │ │ │ ├── haiku2.txt │ │ │ ├── haiku_small.txt │ │ │ ├── hamonikr.txt │ │ │ ├── hardclanz.txt │ │ │ ├── harmonyos.txt │ │ │ ├── hash.txt │ │ │ ├── hce.txt │ │ │ ├── heliumos.txt │ │ │ ├── huayra.txt │ │ │ ├── hybrid.txt │ │ │ ├── hydroos.txt │ │ │ ├── hyperbola.txt │ │ │ ├── hyperbola_small.txt │ │ │ ├── hypros.txt │ │ │ ├── iglunix.txt │ │ │ ├── instantos.txt │ │ │ ├── interix.txt │ │ │ ├── irix.txt │ │ │ ├── ironclad.txt │ │ │ ├── itc.txt │ │ │ ├── januslinux.txt │ │ │ ├── kaisen.txt │ │ │ ├── kali.txt │ │ │ ├── kali_small.txt │ │ │ ├── kalpa_desktop.txt │ │ │ ├── kaos.txt │ │ │ ├── kdelinux.txt │ │ │ ├── kdeneon.txt │ │ │ ├── kernelos.txt │ │ │ ├── kibojoe.txt │ │ │ ├── kiss.txt │ │ │ ├── kiss2.txt │ │ │ ├── kogaion.txt │ │ │ ├── korora.txt │ │ │ ├── krassos.txt │ │ │ ├── kslinux.txt │ │ │ ├── kubuntu.txt │ │ │ ├── kylin.txt │ │ │ ├── lainos.txt │ │ │ ├── langitketujuh.txt │ │ │ ├── laxeros.txt │ │ │ ├── lede.txt │ │ │ ├── lfs.txt │ │ │ ├── libreelec.txt │ │ │ ├── lilidog.txt │ │ │ ├── lingmo.txt │ │ │ ├── linspire.txt │ │ │ ├── linux.txt │ │ │ ├── linux_small.txt │ │ │ ├── linuxlite.txt │ │ │ ├── linuxlite_small.txt │ │ │ ├── linuxmint.txt │ │ │ ├── linuxmint2.txt │ │ │ ├── linuxmint_old.txt │ │ │ ├── linuxmint_small.txt │ │ │ ├── live_raizo.txt │ │ │ ├── lliurex.txt │ │ │ ├── lmde.txt │ │ │ ├── locos.txt │ │ │ ├── lubuntu.txt │ │ │ ├── lunar.txt │ │ │ ├── macaronios.txt │ │ │ ├── macos.txt │ │ │ ├── macos2.txt │ │ │ ├── macos2_small.txt │ │ │ ├── macos3.txt │ │ │ ├── macos_small.txt │ │ │ ├── mageia.txt │ │ │ ├── mageia_small.txt │ │ │ ├── magix.txt │ │ │ ├── magpieos.txt │ │ │ ├── mainsailos.txt │ │ │ ├── mainsailos_small.txt │ │ │ ├── mandriva.txt │ │ │ ├── manjaro.txt │ │ │ ├── manjaro_small.txt │ │ │ ├── massos.txt │ │ │ ├── matuusos.txt │ │ │ ├── maui.txt │ │ │ ├── mauna.txt │ │ │ ├── meowix.txt │ │ │ ├── mer.txt │ │ │ ├── midnightbsd.txt │ │ │ ├── midos.txt │ │ │ ├── midos_old.txt │ │ │ ├── minimal.txt │ │ │ ├── minix.txt │ │ │ ├── miracle_linux.txt │ │ │ ├── mos.txt │ │ │ ├── msys2.txt │ │ │ ├── mx.txt │ │ │ ├── mx2.txt │ │ │ ├── mx_small.txt │ │ │ ├── namib.txt │ │ │ ├── nekos.txt │ │ │ ├── neptune.txt │ │ │ ├── netbsd.txt │ │ │ ├── netbsd2.txt │ │ │ ├── netbsd_small.txt │ │ │ ├── nethydra.txt │ │ │ ├── netrunner.txt │ │ │ ├── nexalinux.txt │ │ │ ├── nitrux.txt │ │ │ ├── nixos.txt │ │ │ ├── nixos_old.txt │ │ │ ├── nixos_old_small.txt │ │ │ ├── nixos_small.txt │ │ │ ├── nobara.txt │ │ │ ├── nomadbsd.txt │ │ │ ├── nuros.txt │ │ │ ├── nurunner.txt │ │ │ ├── nutyx.txt │ │ │ ├── obarun.txt │ │ │ ├── obrevenge.txt │ │ │ ├── obsidianos.txt │ │ │ ├── omnios.txt │ │ │ ├── openbsd.txt │ │ │ ├── openbsd_small.txt │ │ │ ├── openeuler.txt │ │ │ ├── openindiana.txt │ │ │ ├── openkylin.txt │ │ │ ├── openmamba.txt │ │ │ ├── openmandriva.txt │ │ │ ├── openstage.txt │ │ │ ├── opensuse.txt │ │ │ ├── opensuse_leap.txt │ │ │ ├── opensuse_leap_old.txt │ │ │ ├── opensuse_microos.txt │ │ │ ├── opensuse_slowroll.txt │ │ │ ├── opensuse_small.txt │ │ │ ├── opensuse_tumbleweed.txt │ │ │ ├── opensuse_tumbleweed2.txt │ │ │ ├── opensuse_tumbleweed_old.txt │ │ │ ├── opensuse_tumbleweed_small.txt │ │ │ ├── openwrt.txt │ │ │ ├── opnsense.txt │ │ │ ├── oracle.txt │ │ │ ├── orchid.txt │ │ │ ├── orchid_small.txt │ │ │ ├── oreon.txt │ │ │ ├── os2warp.txt │ │ │ ├── os_elbrus.txt │ │ │ ├── osmc.txt │ │ │ ├── pacbsd.txt │ │ │ ├── panwah.txt │ │ │ ├── parabola.txt │ │ │ ├── parabola_small.txt │ │ │ ├── parch.txt │ │ │ ├── pardus.txt │ │ │ ├── parrot.txt │ │ │ ├── parsix.txt │ │ │ ├── pcbsd.txt │ │ │ ├── pclinuxos.txt │ │ │ ├── pearos.txt │ │ │ ├── pengwin.txt │ │ │ ├── pentoo.txt │ │ │ ├── peppermint.txt │ │ │ ├── peropesis.txt │ │ │ ├── phyos.txt │ │ │ ├── pikaos.txt │ │ │ ├── pisi.txt │ │ │ ├── pnm_linux.txt │ │ │ ├── pop.txt │ │ │ ├── pop_small.txt │ │ │ ├── porteus.txt │ │ │ ├── postmarketos.txt │ │ │ ├── postmarketos_small.txt │ │ │ ├── prismlinux.txt │ │ │ ├── prismlinux_small.txt │ │ │ ├── proxmox.txt │ │ │ ├── puffos.txt │ │ │ ├── puppy.txt │ │ │ ├── pureos.txt │ │ │ ├── pureos_small.txt │ │ │ ├── q4os.txt │ │ │ ├── qts.txt │ │ │ ├── qubes.txt │ │ │ ├── qubyt.txt │ │ │ ├── quibian.txt │ │ │ ├── quirinux.txt │ │ │ ├── radix.txt │ │ │ ├── raspbian.txt │ │ │ ├── raspbian_small.txt │ │ │ ├── ravynos.txt │ │ │ ├── rebornos.txt │ │ │ ├── rebornos_small.txt │ │ │ ├── redcore.txt │ │ │ ├── redos.txt │ │ │ ├── redos_small.txt │ │ │ ├── redstar.txt │ │ │ ├── refracta.txt │ │ │ ├── regata.txt │ │ │ ├── regolith.txt │ │ │ ├── rengeos.txt │ │ │ ├── rhaymos.txt │ │ │ ├── rhel.txt │ │ │ ├── rhel_old.txt │ │ │ ├── rhel_small.txt │ │ │ ├── rhino.txt │ │ │ ├── rocky.txt │ │ │ ├── rocky_small.txt │ │ │ ├── rosa.txt │ │ │ ├── sabayon.txt │ │ │ ├── sabotage.txt │ │ │ ├── sailfish.txt │ │ │ ├── salentos.txt │ │ │ ├── salientos.txt │ │ │ ├── salix.txt │ │ │ ├── sambabox.txt │ │ │ ├── sasanqua.txt │ │ │ ├── scientific.txt │ │ │ ├── secureblue.txt │ │ │ ├── semc.txt │ │ │ ├── septor.txt │ │ │ ├── serene.txt │ │ │ ├── serpent_os.txt │ │ │ ├── sharklinux.txt │ │ │ ├── shastraos.txt │ │ │ ├── shebang.txt │ │ │ ├── siduction.txt │ │ │ ├── skiffos.txt │ │ │ ├── slackel.txt │ │ │ ├── slackware.txt │ │ │ ├── slackware_small.txt │ │ │ ├── sleeperos.txt │ │ │ ├── sleeperos_small.txt │ │ │ ├── slitaz.txt │ │ │ ├── smartos.txt │ │ │ ├── snigdhaos.txt │ │ │ ├── soda.txt │ │ │ ├── solaris.txt │ │ │ ├── solaris_small.txt │ │ │ ├── solus.txt │ │ │ ├── source_mage.txt │ │ │ ├── sparky.txt │ │ │ ├── spoinkos.txt │ │ │ ├── star.txt │ │ │ ├── steamdeck.txt │ │ │ ├── steamdeck_small.txt │ │ │ ├── steamos.txt │ │ │ ├── stock_linux.txt │ │ │ ├── sulin.txt │ │ │ ├── summitos.txt │ │ │ ├── suse.txt │ │ │ ├── swagarch.txt │ │ │ ├── t2.txt │ │ │ ├── t2_small.txt │ │ │ ├── tails.txt │ │ │ ├── tatra.txt │ │ │ ├── tearch.txt │ │ │ ├── templeos.txt │ │ │ ├── tileos.txt │ │ │ ├── torizoncore.txt │ │ │ ├── trisquel.txt │ │ │ ├── truenas_scale.txt │ │ │ ├── tuxedo_os.txt │ │ │ ├── twister.txt │ │ │ ├── ublinux.txt │ │ │ ├── ublinux_small.txt │ │ │ ├── ubuntu.txt │ │ │ ├── ubuntu_budgie.txt │ │ │ ├── ubuntu_cinnamon.txt │ │ │ ├── ubuntu_gnome.txt │ │ │ ├── ubuntu_kylin.txt │ │ │ ├── ubuntu_mate.txt │ │ │ ├── ubuntu_old.txt │ │ │ ├── ubuntu_old2.txt │ │ │ ├── ubuntu_old2_small.txt │ │ │ ├── ubuntu_small.txt │ │ │ ├── ubuntu_studio.txt │ │ │ ├── ubuntu_sway.txt │ │ │ ├── ubuntu_touch.txt │ │ │ ├── ubuntu_unity.txt │ │ │ ├── ultramarine.txt │ │ │ ├── ultramarine_small.txt │ │ │ ├── unifi.txt │ │ │ ├── univalent.txt │ │ │ ├── univention.txt │ │ │ ├── unknown.txt │ │ │ ├── uos.txt │ │ │ ├── urukos.txt │ │ │ ├── uwuntu.txt │ │ │ ├── valhalla.txt │ │ │ ├── vanilla.txt │ │ │ ├── vanilla2.txt │ │ │ ├── vanilla_small.txt │ │ │ ├── venom.txt │ │ │ ├── venom_small.txt │ │ │ ├── vincentos.txt │ │ │ ├── vnux.txt │ │ │ ├── void.txt │ │ │ ├── void2.txt │ │ │ ├── void2_small.txt │ │ │ ├── void_small.txt │ │ │ ├── vzlinux.txt │ │ │ ├── wii_linux.txt │ │ │ ├── windows.txt │ │ │ ├── windows_11.txt │ │ │ ├── windows_11_small.txt │ │ │ ├── windows_2025.txt │ │ │ ├── windows_8.txt │ │ │ ├── windows_95.txt │ │ │ ├── wolfos.txt │ │ │ ├── xcp_ng.txt │ │ │ ├── xenia.txt │ │ │ ├── xenia_old.txt │ │ │ ├── xeroarch.txt │ │ │ ├── xferience.txt │ │ │ ├── xinux.txt │ │ │ ├── xray_os.txt │ │ │ ├── xubuntu.txt │ │ │ ├── yiffos.txt │ │ │ ├── zorin.txt │ │ │ ├── zos.txt │ │ │ └── zraxyl.txt │ │ ├── builtin.c │ │ ├── image/ │ │ │ ├── im6.c │ │ │ ├── im7.c │ │ │ ├── image.c │ │ │ └── image.h │ │ ├── logo.c │ │ └── logo.h │ ├── modules/ │ │ ├── battery/ │ │ │ ├── battery.c │ │ │ ├── battery.h │ │ │ └── option.h │ │ ├── bios/ │ │ │ ├── bios.c │ │ │ ├── bios.h │ │ │ └── option.h │ │ ├── bluetooth/ │ │ │ ├── bluetooth.c │ │ │ ├── bluetooth.h │ │ │ └── option.h │ │ ├── bluetoothradio/ │ │ │ ├── bluetoothradio.c │ │ │ ├── bluetoothradio.h │ │ │ └── option.h │ │ ├── board/ │ │ │ ├── board.c │ │ │ ├── board.h │ │ │ └── option.h │ │ ├── bootmgr/ │ │ │ ├── bootmgr.c │ │ │ ├── bootmgr.h │ │ │ └── option.h │ │ ├── break/ │ │ │ ├── break.c │ │ │ ├── break.h │ │ │ └── option.h │ │ ├── brightness/ │ │ │ ├── brightness.c │ │ │ ├── brightness.h │ │ │ └── option.h │ │ ├── btrfs/ │ │ │ ├── btrfs.c │ │ │ ├── btrfs.h │ │ │ └── option.h │ │ ├── camera/ │ │ │ ├── camera.c │ │ │ ├── camera.h │ │ │ └── option.h │ │ ├── chassis/ │ │ │ ├── chassis.c │ │ │ ├── chassis.h │ │ │ └── option.h │ │ ├── colors/ │ │ │ ├── colors.c │ │ │ ├── colors.h │ │ │ └── option.h │ │ ├── command/ │ │ │ ├── command.c │ │ │ ├── command.h │ │ │ └── option.h │ │ ├── cpu/ │ │ │ ├── cpu.c │ │ │ ├── cpu.h │ │ │ └── option.h │ │ ├── cpucache/ │ │ │ ├── cpucache.c │ │ │ ├── cpucache.h │ │ │ └── option.h │ │ ├── cpuusage/ │ │ │ ├── cpuusage.c │ │ │ ├── cpuusage.h │ │ │ └── option.h │ │ ├── cursor/ │ │ │ ├── cursor.c │ │ │ ├── cursor.h │ │ │ └── option.h │ │ ├── custom/ │ │ │ ├── custom.c │ │ │ ├── custom.h │ │ │ └── option.h │ │ ├── datetime/ │ │ │ ├── datetime.c │ │ │ ├── datetime.h │ │ │ └── option.h │ │ ├── de/ │ │ │ ├── de.c │ │ │ ├── de.h │ │ │ └── option.h │ │ ├── disk/ │ │ │ ├── disk.c │ │ │ ├── disk.h │ │ │ └── option.h │ │ ├── diskio/ │ │ │ ├── diskio.c │ │ │ ├── diskio.h │ │ │ └── option.h │ │ ├── display/ │ │ │ ├── display.c │ │ │ ├── display.h │ │ │ └── option.h │ │ ├── dns/ │ │ │ ├── dns.c │ │ │ ├── dns.h │ │ │ └── option.h │ │ ├── editor/ │ │ │ ├── editor.c │ │ │ ├── editor.h │ │ │ └── option.h │ │ ├── font/ │ │ │ ├── font.c │ │ │ ├── font.h │ │ │ └── option.h │ │ ├── gamepad/ │ │ │ ├── gamepad.c │ │ │ ├── gamepad.h │ │ │ └── option.h │ │ ├── gpu/ │ │ │ ├── gpu.c │ │ │ ├── gpu.h │ │ │ └── option.h │ │ ├── host/ │ │ │ ├── host.c │ │ │ ├── host.h │ │ │ └── option.h │ │ ├── icons/ │ │ │ ├── icons.c │ │ │ ├── icons.h │ │ │ └── option.h │ │ ├── initsystem/ │ │ │ ├── initsystem.c │ │ │ ├── initsystem.h │ │ │ └── option.h │ │ ├── kernel/ │ │ │ ├── kernel.c │ │ │ ├── kernel.h │ │ │ └── option.h │ │ ├── keyboard/ │ │ │ ├── keyboard.c │ │ │ ├── keyboard.h │ │ │ └── option.h │ │ ├── lm/ │ │ │ ├── lm.c │ │ │ ├── lm.h │ │ │ └── option.h │ │ ├── loadavg/ │ │ │ ├── loadavg.c │ │ │ ├── loadavg.h │ │ │ └── option.h │ │ ├── locale/ │ │ │ ├── locale.c │ │ │ ├── locale.h │ │ │ └── option.h │ │ ├── localip/ │ │ │ ├── localip.c │ │ │ ├── localip.h │ │ │ └── option.h │ │ ├── logo/ │ │ │ ├── logo.c │ │ │ ├── logo.h │ │ │ └── option.h │ │ ├── media/ │ │ │ ├── media.c │ │ │ ├── media.h │ │ │ └── option.h │ │ ├── memory/ │ │ │ ├── memory.c │ │ │ ├── memory.h │ │ │ └── option.h │ │ ├── modules.c │ │ ├── modules.h │ │ ├── monitor/ │ │ │ ├── monitor.c │ │ │ ├── monitor.h │ │ │ └── option.h │ │ ├── mouse/ │ │ │ ├── mouse.c │ │ │ ├── mouse.h │ │ │ └── option.h │ │ ├── netio/ │ │ │ ├── netio.c │ │ │ ├── netio.h │ │ │ └── option.h │ │ ├── opencl/ │ │ │ ├── opencl.c │ │ │ ├── opencl.h │ │ │ └── option.h │ │ ├── opengl/ │ │ │ ├── opengl.c │ │ │ ├── opengl.h │ │ │ └── option.h │ │ ├── options.h │ │ ├── os/ │ │ │ ├── option.h │ │ │ ├── os.c │ │ │ └── os.h │ │ ├── packages/ │ │ │ ├── option.h │ │ │ ├── packages.c │ │ │ └── packages.h │ │ ├── physicaldisk/ │ │ │ ├── option.h │ │ │ ├── physicaldisk.c │ │ │ └── physicaldisk.h │ │ ├── physicalmemory/ │ │ │ ├── option.h │ │ │ ├── physicalmemory.c │ │ │ └── physicalmemory.h │ │ ├── player/ │ │ │ ├── option.h │ │ │ ├── player.c │ │ │ └── player.h │ │ ├── poweradapter/ │ │ │ ├── option.h │ │ │ ├── poweradapter.c │ │ │ └── poweradapter.h │ │ ├── processes/ │ │ │ ├── option.h │ │ │ ├── processes.c │ │ │ └── processes.h │ │ ├── publicip/ │ │ │ ├── option.h │ │ │ ├── publicip.c │ │ │ └── publicip.h │ │ ├── separator/ │ │ │ ├── option.h │ │ │ ├── separator.c │ │ │ └── separator.h │ │ ├── shell/ │ │ │ ├── option.h │ │ │ ├── shell.c │ │ │ └── shell.h │ │ ├── sound/ │ │ │ ├── option.h │ │ │ ├── sound.c │ │ │ └── sound.h │ │ ├── swap/ │ │ │ ├── option.h │ │ │ ├── swap.c │ │ │ └── swap.h │ │ ├── terminal/ │ │ │ ├── option.h │ │ │ ├── terminal.c │ │ │ └── terminal.h │ │ ├── terminalfont/ │ │ │ ├── option.h │ │ │ ├── terminalfont.c │ │ │ └── terminalfont.h │ │ ├── terminalsize/ │ │ │ ├── option.h │ │ │ ├── terminalsize.c │ │ │ └── terminalsize.h │ │ ├── terminaltheme/ │ │ │ ├── option.h │ │ │ ├── terminaltheme.c │ │ │ └── terminaltheme.h │ │ ├── theme/ │ │ │ ├── option.h │ │ │ ├── theme.c │ │ │ └── theme.h │ │ ├── title/ │ │ │ ├── option.h │ │ │ ├── title.c │ │ │ └── title.h │ │ ├── tpm/ │ │ │ ├── option.h │ │ │ ├── tpm.c │ │ │ └── tpm.h │ │ ├── uptime/ │ │ │ ├── option.h │ │ │ ├── uptime.c │ │ │ └── uptime.h │ │ ├── users/ │ │ │ ├── option.h │ │ │ ├── users.c │ │ │ └── users.h │ │ ├── version/ │ │ │ ├── option.h │ │ │ ├── version.c │ │ │ └── version.h │ │ ├── vulkan/ │ │ │ ├── option.h │ │ │ ├── vulkan.c │ │ │ └── vulkan.h │ │ ├── wallpaper/ │ │ │ ├── option.h │ │ │ ├── wallpaper.c │ │ │ └── wallpaper.h │ │ ├── weather/ │ │ │ ├── option.h │ │ │ ├── weather.c │ │ │ └── weather.h │ │ ├── wifi/ │ │ │ ├── option.h │ │ │ ├── wifi.c │ │ │ └── wifi.h │ │ ├── wm/ │ │ │ ├── option.h │ │ │ ├── wm.c │ │ │ └── wm.h │ │ ├── wmtheme/ │ │ │ ├── option.h │ │ │ ├── wmtheme.c │ │ │ └── wmtheme.h │ │ └── zpool/ │ │ ├── option.h │ │ ├── zpool.c │ │ └── zpool.h │ └── options/ │ ├── display.c │ ├── display.h │ ├── general.c │ ├── general.h │ ├── logo.c │ └── logo.h └── tests/ ├── color.c ├── duration.c ├── format.c ├── list.c ├── strbuf.c ├── testlogo-hardcolors.fflogo └── testlogo-softcolors.fflogo ================================================ FILE CONTENTS ================================================ ================================================ FILE: .codespellrc ================================================ [codespell] check-filenames = builtin = clear,rare,usage,informal skip = */.git,*/cmake-build-*,*/.idea,*/completions,*/presets,*/screenshots,*/tests,*/3rdparty,*/logo/ascii,./src/detection/gpu/asahi_drm.h ignore-words-list = iterm,compiletime,unknwn,pengwin,siduction,master,slave,sur,doas,conexant,ags,bu ================================================ FILE: .editorconfig ================================================ root = true [*] end_of_line = lf indent_style = space tab_width = 4 insert_final_newline = true trim_trailing_whitespace = true [*.md] trim_trailing_whitespace = false [*.txt] insert_final_newline = false [*.yml] tab_width = 2 ================================================ FILE: .gitattributes ================================================ *.h linguist-language=c *.c linguist-language=c ================================================ FILE: .github/FUNDING.yml ================================================ github: LinusDierheimer ko_fi: carterli custom: https://paypal.me/zhangsongcui ================================================ FILE: .github/ISSUE_TEMPLATE/bug_report_crash.yml ================================================ name: Crash Bug Report description: If fastfetch crashes or freezes title: "[BUG] " labels: ["bug", "crash", "triage"] body: - type: markdown attributes: value: | Thanks for taking the time to fill out this bug report! We will try hard to solve the issue. However since platforms and hardwares vary greatly, it can be hard to find the root cause of an issue. Providing the following information may help us greatly. Thanks in advance! - type: checkboxes attributes: label: Read the FAQ first description: Please check if the issue is already covered in the FAQ. options: - label: I have checked the FAQ but the issue is not covered required: true - type: checkboxes attributes: label: Not a known issue description: Please check if the issue is already known and being worked on. options: - label: I have checked the existing issues but the issue is not covered required: true - label: My issue is not about crashing on Fedora and KDE 6.6 required: true - type: markdown attributes: value: "### General description of the bug" - type: textarea attributes: label: Description description: A clear and concise description of what the bug is. placeholder: I was trying to [...] but [...] validations: required: true - type: input attributes: label: Version used description: Fastfetch version used. Please use the latest version (found in the [releases](https://github.com/fastfetch-cli/fastfetch/releases)) if possible. placeholder: Result of `fastfetch --version` validations: required: true - type: dropdown attributes: label: Bug prevalence description: How often does the bug occur? options: - - Always - Sometimes - Rarely - Once - Other validations: required: true - type: dropdown attributes: label: Regression description: Did it work in an older version? options: - - Not sure - 'Yes' - 'No' validations: required: true - type: dropdown attributes: label: Installation description: Where did you install fastfetch from? options: - - GitHub Releases - GitHub Actions (nightly) - Built from source - Package manager validations: required: true - type: input attributes: label: Package manager description: Which package manager did you use if applicable? placeholder: e.g. `apt`, `pacman`, `brew`, `scoop` - type: markdown attributes: value: '### Often helpful information' - type: textarea attributes: label: Screenshots description: If applicable, add screenshots to help explain your problem. - type: textarea attributes: label: Configuration description: If applicable, paste your configuration file here. placeholder: cat ~/.config/fastfetch/config.jsonc render: jsonc - type: markdown attributes: value: | Paste the stacktrace here. You may get it with: ```shell # You may need Ctrl+C to stop the process if it freezes gdb -q -ex 'set confirm off' -ex run -ex 'bt full' -ex quit --args /path/to/fastfetch ``` If you are able to identify which module crashed, the strace can be helpful too ```shell strace /path/to/fastfetch --multithreading false -s {MODULE} --pipe ``` If you cannot do the instructions above, please upload the core dump file if available. - type: textarea attributes: label: Stacktrace description: Paste the stacktrace or core dump file here. render: text validations: required: true ================================================ FILE: .github/ISSUE_TEMPLATE/bug_report_general.yml ================================================ name: General Bug Report description: If something is not working as expected (wrong output, missing info, etc) title: "[BUG] " labels: ["bug", "triage"] body: - type: markdown attributes: value: | Thanks for taking the time to fill out this bug report! We will try hard to solve the issue. However since platforms and hardwares vary greatly, it can be hard to find the root cause of an issue. Providing the following information may help us greatly. Thanks in advance! - type: checkboxes attributes: label: Read the FAQ first description: Please check if the issue is already covered in the FAQ. options: - label: I have checked the FAQ but the issue is not covered required: true - type: markdown attributes: value: "### General description of the bug" - type: textarea attributes: label: Description description: A clear and concise description of what the bug is. placeholder: I was trying to [...] but [...] validations: required: true - type: input attributes: label: Version used description: Fastfetch version used. Please use the latest version (found in the [releases](https://github.com/fastfetch-cli/fastfetch/releases)) if possible. placeholder: Result of `fastfetch --version` validations: required: true - type: dropdown attributes: label: Bug prevalence description: How often does the bug occur? options: - - Always - Sometimes - Rarely - Once - Other validations: required: true - type: dropdown attributes: label: Regression description: Did it work in an older version? options: - - Not sure - 'Yes' - 'No' validations: required: true - type: dropdown attributes: label: Installation description: Where did you install fastfetch from? options: - - GitHub Releases - GitHub Actions (nightly) - Built from source - Package manager validations: required: true - type: input attributes: label: Package manager description: Which package manager did you use if applicable? placeholder: e.g. `apt`, `pacman`, `brew`, `scoop` - type: markdown attributes: value: '### Often helpful information' - type: textarea attributes: label: Screenshots description: If applicable, add screenshots to help explain your problem. - type: textarea attributes: label: Configuration description: If applicable, paste your configuration file here. placeholder: cat ~/.config/fastfetch/config.jsonc render: jsonc - type: textarea attributes: label: System information description: Output of `fastfetch -c all.jsonc --stat --format json` placeholder: | Note that this output will contain you public IP. If it is not relevant for the issue, feel free to remove it before uploading. render: jsonc validations: required: true - type: textarea attributes: label: Features built-in description: Output of `fastfetch --list-features` render: text validations: required: true ================================================ FILE: .github/ISSUE_TEMPLATE/bug_report_logo.yml ================================================ name: Logo Bug Report description: If my image logo is not displayed correctly title: "[BUG] " labels: ["bug", "logo", "triage"] body: - type: markdown attributes: value: | Thanks for taking the time to fill out this bug report! We will try hard to solve the issue. However since platforms and hardwares vary greatly, it can be hard to find the root cause of an issue. Providing the following information may help us greatly. Thanks in advance! - type: checkboxes attributes: label: Read the FAQ first description: Please check if the issue is already covered in the FAQ. options: - label: I have checked the FAQ but the issue is not covered required: true - type: markdown attributes: value: "### General description of the bug" - type: textarea attributes: label: Description description: A clear and concise description of what the bug is. placeholder: I was trying to [...] but [...] validations: required: true - type: input attributes: label: Version used description: Fastfetch version used. Please use the latest version (found in the [releases](https://github.com/fastfetch-cli/fastfetch/releases)) if possible. placeholder: Result of `fastfetch --version` validations: required: true - type: dropdown attributes: label: Bug prevalence description: How often does the bug occur? options: - - Always - Sometimes - Rarely - Once - Other validations: required: true - type: dropdown attributes: label: Regression description: Did it work in an older version? options: - - Not sure - 'Yes' - 'No' validations: required: true - type: dropdown attributes: label: Installation description: Where did you install fastfetch from? options: - - GitHub Releases - GitHub Actions (nightly) - Built from source - Package manager validations: required: true - type: input attributes: label: Package manager description: Which package manager did you use if applicable? placeholder: e.g. `apt`, `pacman`, `brew`, `scoop` - type: markdown attributes: value: '### Often helpful information' - type: textarea attributes: label: Screenshots description: If applicable, add screenshots to help explain your problem. - type: textarea attributes: label: Configuration description: If applicable, paste your configuration file here. placeholder: cat ~/.config/fastfetch/config.jsonc render: jsonc - type: markdown attributes: value: | #### If an image or logo didn't show Please make sure your terminal supports the image protocol you used. Note that GNOME Terminal doesn't support any image protocols as of now Some tips: 1. Try `fastfetch --show-errors` to see if there are any errors. 2. Try `fastfetch --logo-width {WIDTH} --logo-height {HEIGHT}`. Some protocols may require a image size being set. - type: input attributes: label: Image protocol description: The image protocol you used validations: required: true - type: input attributes: label: Terminal description: The terminal you used validations: required: true - type: textarea attributes: label: Image tried description: Upload the image file, or paste the image URL here validations: required: true - type: textarea attributes: label: Error message description: Error message printed by `fastfetch -s none --show-errors`, if any - type: textarea attributes: label: Features built-in description: Output of `fastfetch --list-features` render: text validations: required: true ================================================ FILE: .github/ISSUE_TEMPLATE/feature_request.yml ================================================ name: Feature Request description: Suggest an idea for this project title: "[FEAT] " labels: ["enhancement"] body: - type: markdown attributes: value: | Fastfetch is a system information tool. We only accept hardware or system level software feature requests. For most personal uses, I recommend using `Command` module to detect it yourself, which can be used to grab output from a custom shell script: ```jsonc // This module shows the git version { "modules": [ { "type": "command", "key": "Git", "text": "git version", "format": "{~12}" // cut the first 12 characters } ] } ``` - type: textarea attributes: label: Description description: A clear and concise description of what the feature is. validations: required: true - type: textarea attributes: label: Motivation description: Why do you want this feature? Why doesn't `Command` module suffice for you? validations: required: true - type: textarea attributes: label: Additional context description: Add any other context or screenshots about the feature request here. ================================================ FILE: .github/ISSUE_TEMPLATE/logo_request.yml ================================================ name: Logo Request description: Request a new ASCII logo for your favorite distro title: "[LOGO] " labels: ["logo request"] body: - type: markdown attributes: value: | Tip: You can display a logo in fastfetch without adding it to fastfetch's official repo. For highly customized, personal logos, we recommend keeping them locally. Please refer to https://github.com/fastfetch-cli/fastfetch/wiki/Migrate-Neofetch-Logo-To-Fastfetch - type: textarea attributes: label: OS description: Paste the contents of `/etc/os-release` and `/etc/lsb-release` here. If neither file exists, describe how to identify the distro. placeholder: cat /etc/os-release validations: required: true - type: input attributes: label: Distro Website description: To help prevent spam and verify the request, a distro website is required, and a downloadable ISO must be available on that site. placeholder: https://example.com validations: required: true - type: textarea attributes: label: ASCII Art description: The ASCII logo should not take up too much space (smaller than 50x20 characters, W x H). Please also include the color codes if they are not available in `os-release`. placeholder: Paste ASCII art here validations: required: true - type: input attributes: label: Original Image URL description: If the ASCII art is based on an image, please provide a link to the original image file. placeholder: Image URL from the distro website mentioned above - type: checkboxes attributes: label: Checklist options: - label: The ASCII art is smaller than 50x20 characters (W x H). required: true - label: The ASCII art includes color codes, or the color codes are available in `os-release`. required: true - label: The ASCII art has no internal padding (spaces at the start and/or end of lines). required: true ================================================ FILE: .github/pull_request_template.md ================================================ ## Summary ## Related issue (required for new logos for new distros) Closes # ## Changes - ## Checklist - [ ] I have tested my changes locally. ================================================ FILE: .github/stale.yml ================================================ # Number of days of inactivity before an issue becomes stale daysUntilStale: 60 # Number of days of inactivity before a stale issue is closed daysUntilClose: 7 # Issues with these labels will never be considered stale exemptLabels: - keepalive # Label to use when marking an issue as stale staleLabel: stale # Comment to post when marking an issue as stale. Set to `false` to disable markComment: > This issue has been automatically marked as stale because it has not had recent activity. It will be closed if no further activity occurs. Thank you for your contributions. # Comment to post when closing a stale issue. Set to `false` to disable closeComment: false ================================================ FILE: .github/workflows/ci.yml ================================================ name: CI on: - push - pull_request env: CMAKE_BUILD_TYPE: ${{ vars.CMAKE_BUILD_TYPE || 'RelWithDebInfo' }} jobs: spellcheck: runs-on: ubuntu-latest steps: - name: checkout repository uses: actions/checkout@v6 - name: Install codespell shell: bash run: | sudo apt-get update || true sudo apt-get install -y codespell - name: Run Spellchecker run: codespell no-features-test: name: No-features-test runs-on: ubuntu-latest permissions: security-events: write contents: read steps: - name: checkout repository uses: actions/checkout@v6 - name: uname -a run: uname -a - name: configure project run: cmake -DSET_TWEAK=Off -DBUILD_TESTS=On -DCMAKE_INSTALL_PREFIX=/usr . -DENABLE_VULKAN=OFF -DENABLE_WAYLAND=OFF -DENABLE_XCB_RANDR=OFF -DENABLE_XCB=OFF -DENABLE_XRANDR=OFF -DENABLE_X11=OFF -DENABLE_DRM=OFF -DENABLE_DRM_AMDGPU=OFF -DENABLE_GIO=OFF -DENABLE_DCONF=OFF -DENABLE_DBUS=OFF -DENABLE_SQLITE3=OFF -DENABLE_RPM=OFF -DENABLE_IMAGEMAGICK7=OFF -DENABLE_IMAGEMAGICK6=OFF -DENABLE_CHAFA=OFF -DENABLE_ZLIB=OFF -DENABLE_EGL=OFF -DENABLE_GLX=OFF -DENABLE_OPENCL=OFF -DENABLE_FREETYPE=OFF -DENABLE_PULSE=OFF -DENABLE_DDCUTIL=OFF -DENABLE_ELF=OFF -DENABLE_DIRECTX_HEADERS=OFF -DENABLE_THREADS=OFF - name: build project run: cmake --build . --target package --verbose -j4 - name: list features run: ./fastfetch --list-features - name: run fastfetch run: time ./fastfetch -c presets/ci.jsonc --stat false - name: run fastfetch --format json run: time ./fastfetch -c presets/ci.jsonc --format json - name: run flashfetch run: time ./flashfetch - name: print dependencies run: ldd fastfetch - name: run tests run: ctest --output-on-failure linux-hosts: name: Linux-${{ matrix.arch }} runs-on: ${{ matrix.runs-on }} permissions: security-events: write contents: read strategy: matrix: include: - arch: amd64 runs-on: ubuntu-22.04 - arch: aarch64 runs-on: ubuntu-22.04-arm outputs: ffversion: ${{ steps.ffversion.outputs.ffversion }} steps: - name: checkout repository uses: actions/checkout@v6 - name: uname -a run: uname -a - name: cat /etc/os-release run: cat /etc/os-release - name: cat /proc/cpuinfo run: cat /proc/cpuinfo - name: add gcc-13 repo run: sudo add-apt-repository -y ppa:ubuntu-toolchain-r/test - name: install required packages run: sudo apt-get update && sudo apt-get install -y gcc-13 g++-13 libvulkan-dev libwayland-dev libxrandr-dev libxcb-randr0-dev libdconf-dev libdbus-1-dev libmagickcore-dev libsqlite3-dev librpm-dev libegl-dev libglx-dev ocl-icd-opencl-dev libpulse-dev libdrm-dev libelf-dev libddcutil-dev directx-headers-dev rpm ninja-build - name: install linuxbrew packages run: | /bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)" /home/linuxbrew/.linuxbrew/bin/brew install imagemagick chafa --ignore-dependencies - name: Initialize CodeQL if: matrix.arch == 'amd64' uses: github/codeql-action/init@v4 with: languages: c - name: configure project run: CC=gcc-13 CXX=g++-13 PKG_CONFIG_PATH=/home/linuxbrew/.linuxbrew/lib/pkgconfig:$PKG_CONFIG_PATH cmake -GNinja -DSET_TWEAK=Off -DBUILD_TESTS=On -DENABLE_EMBEDDED_PCIIDS=On -DENABLE_EMBEDDED_AMDGPUIDS=On -DCMAKE_INSTALL_PREFIX=/usr . - name: build project run: cmake --build . --target package --verbose -j4 - name: perform CodeQL analysis if: matrix.arch == 'amd64' uses: github/codeql-action/analyze@v4 - name: list features run: ./fastfetch --list-features - name: run fastfetch run: time ./fastfetch -c presets/ci.jsonc --stat false - name: run fastfetch --format json run: time ./fastfetch -c presets/ci.jsonc --format json - name: run flashfetch run: time ./flashfetch - name: print dependencies run: ldd fastfetch - name: run tests run: ctest --output-on-failure - name: get fastfetch version id: ffversion run: echo "ffversion=$(./fastfetch --version-raw)" >> $GITHUB_OUTPUT - name: polyfill glibc run: | wget https://github.com/CarterLi/polyfill-glibc/releases/download/v0.0.1/polyfill-glibc-${{ matrix.arch }} -O polyfill-glibc chmod +x polyfill-glibc strip fastfetch && ./polyfill-glibc fastfetch --target-glibc=2.17 strip flashfetch && ./polyfill-glibc flashfetch --target-glibc=2.17 echo 'set(CPACK_PACKAGE_FILE_NAME "${CPACK_PACKAGE_FILE_NAME}-polyfilled")' >> CPackConfig.cmake echo 'set(CPACK_PACKAGE_RELOCATABLE OFF)' >> CPackConfig.cmake echo 'set(CPACK_DEBIAN_PACKAGE_DEPENDS "libc6 (>= 2.17)")' >> CPackConfig.cmake echo 'set(CPACK_RPM_SPEC_MORE_DEFINE "%global __os_install_post %{nil}")' >> CPackConfig.cmake cpack -V - name: upload artifacts uses: actions/upload-artifact@v7 with: name: fastfetch-linux-${{ matrix.arch }} path: ./fastfetch-*.* linux-i686: name: Linux-i686 runs-on: ubuntu-22.04 permissions: security-events: write contents: read steps: - name: checkout repository uses: actions/checkout@v6 - name: uname -a run: uname -a - name: cat /etc/os-release run: cat /etc/os-release - name: cat /proc/cpuinfo run: cat /proc/cpuinfo - name: add gcc-13 repo run: sudo add-apt-repository -y ppa:ubuntu-toolchain-r/test - name: install required packages run: sudo apt-get update && sudo apt-get install -y gcc-13 gcc-13-multilib libvulkan-dev libwayland-dev libxrandr-dev libxcb-randr0-dev libdconf-dev libdbus-1-dev libmagickcore-dev libsqlite3-dev librpm-dev libegl-dev libglx-dev ocl-icd-opencl-dev libpulse-dev libdrm-dev libelf-dev libddcutil-dev rpm ninja-build - name: install linuxbrew packages run: | /bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)" /home/linuxbrew/.linuxbrew/bin/brew install imagemagick chafa --ignore-dependencies - name: cmake version run: cmake --version - name: configure project run: CC=gcc-13 PKG_CONFIG_PATH=/home/linuxbrew/.linuxbrew/lib/pkgconfig:$PKG_CONFIG_PATH cmake -DCMAKE_C_FLAGS="-m32 -march=i686 -mtune=i686" -DCMAKE_SYSTEM_PROCESSOR_OVERRIDE=i686 -DCPACK_DEBIAN_PACKAGE_ARCHITECTURE=i386 -GNinja -DSET_TWEAK=Off -DBUILD_TESTS=On -DENABLE_EMBEDDED_PCIIDS=On -DENABLE_EMBEDDED_AMDGPUIDS=On -DCMAKE_INSTALL_PREFIX=/usr -DENABLE_DIRECTX_HEADERS=Off . - name: build project run: cmake --build . --target package --verbose -j4 - name: check deb package run: dpkg -I fastfetch-*.deb - name: list features run: ./fastfetch --list-features - name: run fastfetch run: time ./fastfetch -c presets/ci.jsonc --stat false - name: run fastfetch --format json run: time ./fastfetch -c presets/ci.jsonc --format json - name: run flashfetch run: time ./flashfetch - name: print dependencies run: ldd fastfetch - name: run tests run: ctest --output-on-failure - name: upload artifacts uses: actions/upload-artifact@v7 with: name: fastfetch-linux-i686 path: ./fastfetch-*.* linux-armv7l: name: Linux-armv7l runs-on: ubuntu-24.04 permissions: security-events: write contents: read steps: - name: checkout repository uses: actions/checkout@v6 - name: run VM uses: uraimo/run-on-arch-action@v3 id: runcmd with: arch: armv7 distro: ubuntu22.04 githubToken: ${{ github.token }} run: | uname -a apt-get update && apt-get install -y software-properties-common ca-certificates gpg curl add-apt-repository -y ppa:ubuntu-toolchain-r/test curl -L https://apt.kitware.com/keys/kitware-archive-latest.asc | gpg --dearmor - | tee /usr/share/keyrings/kitware-archive-keyring.gpg >/dev/null echo 'deb [signed-by=/usr/share/keyrings/kitware-archive-keyring.gpg] https://apt.kitware.com/ubuntu/ jammy main' | tee /etc/apt/sources.list.d/kitware.list >/dev/null echo -e 'Acquire::https::Verify-Peer "false";\nAcquire::https::Verify-Host "false";' >> /etc/apt/apt.conf.d/99ignore-certificates apt-get update && apt-get install -y cmake make gcc-13 libvulkan-dev libwayland-dev libxrandr-dev libxcb-randr0-dev libdconf-dev libdbus-1-dev libmagickcore-dev libsqlite3-dev librpm-dev libegl-dev libglx-dev ocl-icd-opencl-dev libpulse-dev libdrm-dev libelf-dev rpm CC=gcc-13 cmake -DSET_TWEAK=Off -DBUILD_TESTS=On -DENABLE_DIRECTX_HEADERS=Off -DCMAKE_INSTALL_PREFIX=/usr . cmake --build . --target package --verbose -j4 ./fastfetch --list-features time ./fastfetch -c presets/ci.jsonc --stat false time ./fastfetch -c presets/ci.jsonc --format json time ./flashfetch ldd fastfetch ctest --output-on-failure - name: upload artifacts uses: actions/upload-artifact@v7 with: name: fastfetch-linux-armv7l path: ./fastfetch-*.* linux-armv6l: name: Linux-armv6l runs-on: ubuntu-24.04 permissions: security-events: write contents: read steps: - name: checkout repository uses: actions/checkout@v6 - name: run VM uses: uraimo/run-on-arch-action@v3 id: runcmd with: arch: armv6 distro: bookworm githubToken: ${{ github.token }} run: | uname -a apt-get update && apt-get install -y wget apt-get install -y cmake make gcc libvulkan-dev libwayland-dev libxrandr-dev libxcb-randr0-dev libdconf-dev libdbus-1-dev libmagickcore-dev libsqlite3-dev librpm-dev libegl-dev libglx-dev ocl-icd-opencl-dev libpulse-dev libdrm-dev libelf-dev rpm cmake -DSET_TWEAK=Off -DBUILD_TESTS=On -DENABLE_DIRECTX_HEADERS=Off -DCMAKE_INSTALL_PREFIX=/usr . cmake --build . --target package --verbose -j4 ./fastfetch --list-features time ./fastfetch -c presets/ci.jsonc --stat false time ./fastfetch -c presets/ci.jsonc --format json time ./flashfetch ldd fastfetch ctest --output-on-failure - name: upload artifacts uses: actions/upload-artifact@v7 with: name: fastfetch-linux-armv6l path: ./fastfetch-*.* linux-vms: name: Linux-${{ matrix.arch }} runs-on: ubuntu-latest permissions: security-events: write contents: read strategy: matrix: include: - arch: riscv64 - arch: ppc64le - arch: s390x steps: - name: checkout repository uses: actions/checkout@v6 - name: run VM uses: uraimo/run-on-arch-action@v3 id: runcmd with: arch: ${{ matrix.arch }} distro: ubuntu22.04 githubToken: ${{ github.token }} run: | uname -a apt-get update && apt-get install -y software-properties-common add-apt-repository -y ppa:ubuntu-toolchain-r/test apt-get update && apt-get install -y cmake make gcc-13 libvulkan-dev libwayland-dev libxrandr-dev libxcb-randr0-dev libdconf-dev libdbus-1-dev libmagickcore-dev libsqlite3-dev librpm-dev libegl-dev libglx-dev ocl-icd-opencl-dev libpulse-dev libdrm-dev libchafa-dev libelf-dev rpm CC=gcc-13 cmake -DSET_TWEAK=Off -DBUILD_TESTS=On -DENABLE_DIRECTX_HEADERS=Off -DCMAKE_INSTALL_PREFIX=/usr . cmake --build . --target package --verbose -j4 ./fastfetch --list-features time ./fastfetch -c presets/ci.jsonc --stat false time ./fastfetch -c presets/ci.jsonc --format json time ./flashfetch ldd fastfetch ctest --output-on-failure - name: upload artifacts uses: actions/upload-artifact@v7 with: name: fastfetch-linux-${{ matrix.arch }} path: ./fastfetch-*.* musl-amd64: name: Musl-amd64 runs-on: ubuntu-latest steps: - uses: actions/checkout@v6 - name: setup alpine linux uses: jirutka/setup-alpine@master - name: install dependencies run: | cat /etc/alpine-release uname -a apk add cmake samurai vulkan-loader-dev libxcb-dev libxrandr-dev rpm-dev wayland-dev libdrm-dev dconf-dev imagemagick-dev chafa-dev zlib-dev dbus-dev mesa-dev opencl-dev sqlite-dev networkmanager-dev pulseaudio-dev ddcutil-dev elfutils-dev gcc g++ shell: alpine.sh --root {0} - name: build run: | cmake -DSET_TWEAK=Off -DBUILD_TESTS=On -DCMAKE_INSTALL_PREFIX=/usr -DIS_MUSL=ON -DENABLE_EMBEDDED_PCIIDS=On -DENABLE_EMBEDDED_AMDGPUIDS=On -GNinja . cmake --build . --target package --verbose -j4 shell: alpine.sh {0} - name: run run: | ./fastfetch --list-features time ./fastfetch -c presets/ci.jsonc --stat false time ./fastfetch -c presets/ci.jsonc --format json time ./flashfetch ldd fastfetch ctest --output-on-failure shell: alpine.sh {0} - name: upload artifacts uses: actions/upload-artifact@v7 with: name: fastfetch-musl-amd64 path: ./fastfetch-*.* macos-hosts: name: macOS-${{ matrix.arch }} runs-on: ${{ matrix.runs-on }} permissions: security-events: write contents: read strategy: matrix: include: - arch: amd64 runs-on: macos-15-intel - arch: aarch64 runs-on: macos-latest steps: - name: checkout repository uses: actions/checkout@v6 - name: uname -a run: uname -a - name: install required packages run: | HOMEBREW_NO_INSTALLED_DEPENDENTS_CHECK=1 brew install --overwrite vulkan-loader vulkan-headers molten-vk imagemagick chafa - name: configure project run: cmake -DSET_TWEAK=Off -DBUILD_TESTS=On . - name: build project run: cmake --build . --target package --verbose -j4 - name: list features run: ./fastfetch --list-features - name: run fastfetch run: time ./fastfetch -c presets/ci.jsonc --stat false - name: run fastfetch --format json run: time ./fastfetch -c presets/ci.jsonc --format json - name: run flashfetch run: time ./flashfetch - name: print dependencies run: otool -L fastfetch - name: run tests run: ctest --output-on-failure - name: upload artifacts uses: actions/upload-artifact@v7 with: name: fastfetch-macos-${{ matrix.arch }} path: ./fastfetch-*.* omnios-amd64: runs-on: ubuntu-latest name: OmniOS-amd64 steps: - name: checkout repository uses: actions/checkout@v6 - name: run VM uses: vmactions/omnios-vm@v1 with: usesh: true envs: 'CMAKE_BUILD_TYPE' prepare: | uname -a pkg update --accept pkg install gcc14 cmake git pkg-config glib2 dbus sqlite-3 imagemagick ninja run: | cmake -DSET_TWEAK=Off -DBUILD_TESTS=On -GNinja . cmake --build . --verbose -j4 ./fastfetch --list-features time ./fastfetch -c presets/ci.jsonc --stat false time ./fastfetch -c presets/ci.jsonc --format json time ./flashfetch ldd fastfetch ctest --output-on-failure echo 'set(CPACK_PACKAGE_FILE_NAME "fastfetch-omnios-amd64")' >> CPackConfig.cmake cpack -V - name: upload artifacts uses: actions/upload-artifact@v7 with: name: fastfetch-omnios-amd64 path: ./fastfetch-*.* solaris-amd64: runs-on: ubuntu-latest name: Solaris-amd64 steps: - name: checkout repository uses: actions/checkout@v6 - name: run VM uses: vmactions/solaris-vm@v1 with: usesh: true envs: 'CMAKE_BUILD_TYPE' release: "11.4-gcc-14" prepare: | uname -a pkg install cmake git pkg-config glib2 dbus sqlite-3 imagemagick ninja dconf mesa run: | export PKG_CONFIG_PATH=/usr/lib/64/pkgconfig cmake -DSET_TWEAK=Off -DBUILD_TESTS=On -GNinja . cmake --build . --verbose -j4 ./fastfetch --list-features time ./fastfetch -c presets/ci.jsonc --stat false time ./fastfetch -c presets/ci.jsonc --format json time ./flashfetch ldd fastfetch ctest --output-on-failure echo 'set(CPACK_PACKAGE_FILE_NAME "fastfetch-solaris-amd64")' >> CPackConfig.cmake cpack -V - name: upload artifacts uses: actions/upload-artifact@v7 with: name: fastfetch-solaris-amd64 path: ./fastfetch-*.* freebsd-amd64: name: FreeBSD-amd64 runs-on: ubuntu-latest permissions: security-events: write contents: read steps: - name: checkout repository uses: actions/checkout@v6 - name: run VM uses: cross-platform-actions/action@master with: operating_system: freebsd architecture: x86-64 cpu_count: 4 shell: bash version: '15.0' environment_variables: 'CMAKE_BUILD_TYPE' run: | uname -a sudo pkg update sudo pkg install -y cmake git pkgconf binutils wayland vulkan-headers vulkan-loader libxcb libXrandr libX11 libdrm glib dconf dbus sqlite3-tcl egl opencl ocl-icd v4l_compat chafa cmake -DSET_TWEAK=Off -DBUILD_TESTS=On -DENABLE_EMBEDDED_PCIIDS=On -DENABLE_EMBEDDED_AMDGPUIDS=On . cmake --build . --target package --verbose -j4 ./fastfetch --list-features time ./fastfetch -c presets/ci.jsonc --stat false time ./fastfetch -c presets/ci.jsonc --format json time ./flashfetch ldd fastfetch ctest --output-on-failure - name: upload artifacts uses: actions/upload-artifact@v7 with: name: fastfetch-freebsd-amd64 path: ./fastfetch-*.* openbsd-amd64: name: OpenBSD-amd64 runs-on: ubuntu-latest permissions: security-events: write contents: read steps: - name: checkout repository uses: actions/checkout@v6 - name: run VM uses: cross-platform-actions/action@master with: operating_system: openbsd architecture: x86-64 cpu_count: 4 shell: bash version: '7.8' environment_variables: 'CMAKE_BUILD_TYPE' run: | uname -a sudo pkg_add -u sudo pkg_add -r llvm-21.1.2p0 cmake git pkgconf wayland vulkan-headers vulkan-loader glib2 dconf dbus sqlite3 imagemagick chafa CC=clang-21 cmake -DSET_TWEAK=Off -DBUILD_TESTS=On -DENABLE_EMBEDDED_PCIIDS=On -DENABLE_EMBEDDED_AMDGPUIDS=On . cmake --build . --target package --verbose -j4 ./fastfetch --list-features time ./fastfetch -c presets/ci.jsonc --stat false time ./fastfetch -c presets/ci.jsonc --format json time ./flashfetch ldd fastfetch ctest --output-on-failure - name: upload artifacts uses: actions/upload-artifact@v7 with: name: fastfetch-openbsd-amd64 path: ./fastfetch-*.* netbsd-amd64: name: NetBSD-amd64 runs-on: ubuntu-latest permissions: security-events: write contents: read steps: - name: checkout repository uses: actions/checkout@v6 - name: run VM uses: cross-platform-actions/action@master with: operating_system: netbsd architecture: x86-64 cpu_count: 4 shell: bash version: '10.1' environment_variables: 'CMAKE_BUILD_TYPE' run: | uname -a sudo pkgin -y install clang cmake git pkgconf wayland vulkan-headers dconf dbus sqlite3 ImageMagick CC=clang cmake -DSET_TWEAK=Off -DBUILD_TESTS=On -DENABLE_EMBEDDED_PCIIDS=On -DENABLE_EMBEDDED_AMDGPUIDS=On . cmake --build . --target package --verbose -j4 ./fastfetch --list-features time ./fastfetch -c presets/ci.jsonc --stat false time ./fastfetch -c presets/ci.jsonc --format json time ./flashfetch ldd fastfetch ctest --output-on-failure - name: upload artifacts uses: actions/upload-artifact@v7 with: name: fastfetch-netbsd-amd64 path: ./fastfetch-*.* dragonfly-amd64: name: DragonFly-amd64 runs-on: ubuntu-latest permissions: security-events: write contents: read steps: - name: checkout repository uses: actions/checkout@v6 - name: run VM uses: vmactions/dragonflybsd-vm@v1 with: usesh: yes envs: 'CMAKE_BUILD_TYPE' prepare: | uname -a pkg update pkg install -y llvm cmake git pkgconf binutils wayland vulkan-headers vulkan-loader libxcb libXrandr libX11 libdrm glib dconf dbus sqlite3-tcl egl opencl ocl-icd v4l_compat chafa libelf run: | env CC=clang cmake -DSET_TWEAK=Off -DBUILD_TESTS=On -DENABLE_EMBEDDED_PCIIDS=On -DENABLE_EMBEDDED_AMDGPUIDS=On . cmake --build . --target package --verbose -j4 ./fastfetch --list-features time ./fastfetch -c presets/ci.jsonc --stat false time ./fastfetch -c presets/ci.jsonc --format json time ./flashfetch ldd fastfetch ctest --output-on-failure - name: upload artifacts uses: actions/upload-artifact@v7 with: name: fastfetch-dragonfly-amd64 path: ./fastfetch-*.* haiku-amd64: name: Haiku-amd64 runs-on: ubuntu-latest permissions: security-events: write contents: read steps: - name: checkout repository uses: actions/checkout@v6 - name: run VM uses: cross-platform-actions/action@master with: operating_system: haiku version: 'r1beta5' architecture: x86-64 cpu_count: 4 shell: bash environment_variables: 'CMAKE_BUILD_TYPE' run: | uname -a pkgman install -y git dbus_devel mesa_devel libelf_devel imagemagick_devel opencl_headers ocl_icd_devel vulkan_devel zlib_devel chafa_devel cmake gcc make pkgconfig python3.10 || pkgman install -y git dbus_devel mesa_devel libelf_devel imagemagick_devel opencl_headers ocl_icd_devel vulkan_devel zlib_devel chafa_devel cmake gcc make pkgconfig python3.10 cmake -DSET_TWEAK=Off -DBUILD_TESTS=On -DENABLE_EMBEDDED_PCIIDS=On -DENABLE_EMBEDDED_AMDGPUIDS=On . cmake --build . --target package --verbose -j4 ./fastfetch --list-features time ./fastfetch -c presets/ci.jsonc --stat false time ./fastfetch -c presets/ci.jsonc --format json time ./flashfetch ctest --output-on-failure - name: upload artifacts uses: actions/upload-artifact@v7 with: name: fastfetch-haiku-amd64 path: ./fastfetch-*.* windows-hosts: name: Windows-${{ matrix.arch }} runs-on: ${{ matrix.runs-on }} permissions: security-events: write contents: read strategy: matrix: include: - arch: amd64 runs-on: windows-latest msystem: CLANG64 msystem-lower: clang64 msys-arch: x86_64 - arch: aarch64 runs-on: windows-11-arm msystem: CLANGARM64 msystem-lower: clangarm64 msys-arch: aarch64 defaults: run: shell: msys2 {0} steps: - name: checkout repository uses: actions/checkout@v6 - name: setup-msys2 uses: msys2/setup-msys2@v2 with: msystem: ${{ matrix.msystem }} update: true install: git mingw-w64-clang-${{ matrix.msys-arch }}-7zip mingw-w64-clang-${{ matrix.msys-arch }}-cmake mingw-w64-clang-${{ matrix.msys-arch }}-clang mingw-w64-clang-${{ matrix.msys-arch }}-vulkan-loader mingw-w64-clang-${{ matrix.msys-arch }}-vulkan-headers mingw-w64-clang-${{ matrix.msys-arch }}-opencl-icd mingw-w64-clang-${{ matrix.msys-arch }}-opencl-headers mingw-w64-clang-${{ matrix.msys-arch }}-cppwinrt mingw-w64-clang-${{ matrix.msys-arch }}-imagemagick mingw-w64-clang-${{ matrix.msys-arch }}-chafa - name: print msys version run: uname -a - name: configure project run: env PKG_CONFIG_PATH=/${{ matrix.msystem-lower }}/lib/pkgconfig/:$PKG_CONFIG_PATH cmake -DSET_TWEAK=Off -DBUILD_TESTS=On . - name: build project run: cmake --build . --verbose -j4 - name: copy necessary dlls run: cp /${{ matrix.msystem-lower }}/bin/{OpenCL,vulkan-1}.dll . - name: list features run: ./fastfetch --list-features - name: run fastfetch run: time ./fastfetch -c presets/ci.jsonc --stat false - name: run fastfetch --format json run: time ./fastfetch -c presets/ci.jsonc --format json - name: run flashfetch run: time ./flashfetch - name: print dependencies run: ldd fastfetch - name: run tests run: ctest --output-on-failure - if: github.event_name == 'push' && github.repository == 'fastfetch-cli/fastfetch' id: upload-unsigned-artifact name: upload artifacts for signing uses: actions/upload-artifact@v7 with: name: fastfetch-windows-${{ matrix.arch }} path: | *.dll fastfetch.exe flashfetch.exe - if: github.event_name == 'push' && github.repository == 'fastfetch-cli/fastfetch' name: submit signing request uses: signpath/github-action-submit-signing-request@v1 with: api-token: '${{ secrets.SIGNPATH_API_TOKEN }}' organization-id: '${{ vars.SIGNPATH_ORG_ID }}' project-slug: 'fastfetch' signing-policy-slug: ${{ github.ref == 'refs/heads/master' && 'release-signing' || 'test-signing' }} github-artifact-id: '${{ steps.upload-unsigned-artifact.outputs.artifact-id }}' wait-for-completion: true output-artifact-directory: '.' - name: create zip archive run: 7z a -tzip -mx9 -bd -y fastfetch-windows-${{ matrix.arch }}.zip LICENSE *.dll fastfetch.exe flashfetch.exe presets - name: create 7z archive run: 7z a -t7z -mx9 -bd -y fastfetch-windows-${{ matrix.arch }}.7z LICENSE *.dll fastfetch.exe flashfetch.exe presets - name: upload true artifacts uses: actions/upload-artifact@v7 with: name: fastfetch-windows-${{ matrix.arch }} path: ./fastfetch-windows-${{ matrix.arch }}.* overwrite: true release: if: github.event_name == 'push' && github.ref == 'refs/heads/master' && github.repository == 'fastfetch-cli/fastfetch' name: Release runs-on: ubuntu-latest needs: - linux-hosts - linux-i686 - linux-armv7l - linux-armv6l - linux-vms - musl-amd64 - macos-hosts - freebsd-amd64 - openbsd-amd64 - netbsd-amd64 - dragonfly-amd64 - solaris-amd64 - omnios-amd64 - haiku-amd64 - windows-hosts permissions: contents: write steps: - name: get latest release version id: get_version_release uses: pozetroninc/github-action-get-latest-release@master with: repository: ${{ github.repository }} - name: download artifacts if: needs.linux-hosts.outputs.ffversion != steps.get_version_release.outputs.release uses: actions/download-artifact@v8 - name: create release if: needs.linux-hosts.outputs.ffversion != steps.get_version_release.outputs.release uses: ncipollo/release-action@v1 with: tag: ${{ needs.linux-hosts.outputs.ffversion }} commit: ${{ github.sha }} artifactErrorsFailBuild: true artifacts: fastfetch-*/fastfetch-* body: "Please refer to [CHANGELOG.md](https://github.com/${{ github.repository }}/blob/${{ needs.linux-hosts.outputs.ffversion }}/CHANGELOG.md) for details." - name: download source tarballs if: needs.linux-hosts.outputs.ffversion != steps.get_version_release.outputs.release run: | for i in 1 2 3 4 5; do curl -L --remote-name-all --output-dir fastfetch-source --create-dirs https://github.com/${{ github.repository }}/archive/refs/tags/${{ needs.linux-hosts.outputs.ffversion }}.{tar.gz,zip} && break || sleep 5; done ls fastfetch-*/* - name: generate release notes if: needs.linux-hosts.outputs.ffversion != steps.get_version_release.outputs.release run: | echo "Please refer to [CHANGELOG.md](https://github.com/${{ github.repository }}/blob/${{ needs.linux-hosts.outputs.ffversion }}/CHANGELOG.md) for details." > fastfetch-release-notes.md echo -e "\n---\n\n
SHA256SUMs
\n\n\`\`\`" >> fastfetch-release-notes.md sha256sum fastfetch-*/* >> fastfetch-release-notes.md echo -e "\`\`\`\n
" >> fastfetch-release-notes.md echo -e "\n
SHA512SUMs
\n\n\`\`\`" >> fastfetch-release-notes.md sha512sum fastfetch-*/* >> fastfetch-release-notes.md echo -e "\`\`\`\n
" >> fastfetch-release-notes.md - name: update release body if: needs.linux-hosts.outputs.ffversion != steps.get_version_release.outputs.release uses: ncipollo/release-action@v1 with: tag: ${{ needs.linux-hosts.outputs.ffversion }} commit: ${{ github.sha }} bodyFile: fastfetch-release-notes.md allowUpdates: true ================================================ FILE: .gitignore ================================================ build/ .vs/ .vscode/ .cache/ .kdev4/ .DS_Store cscope.* tags fastfetch.kdev4 *.user *.user.* *.swp ================================================ FILE: CHANGELOG.md ================================================ # 2.60.0 Changes: * The CMake option `ENABLE_WIN7_COMPAT:BOOLEAN` now defaults to `OFF` and will be removed in v2.61.0, effectively dropping support for Windows 7 in the next release. * This follows the Windows 7 deprecation notice introduced in v2.57.0. * `wm.detectPlugin` now defaults to `true` (WM) Features: * Adds `{cwd}` for custom title formatting, which displays the current working directory (Title) * Adds support for detecting the Zed version (#2200, Editor) * Adds support for detecting `moss` packages (Packages, Linux) * Adds support for detecting komorebi, FancyWM, and GlazeWM (WM, Windows) * Adds support for WM plugin version detection on macOS (WM, macOS) * Adds support for retrieving the executable path on OpenBSD (#2195, OpenBSD) Bugfixes: * Fixes a potential segmentation fault caused by dereferencing a negative index (#2198) * Fixes `tempSensor` parsing so that it accepts only string values (#2202, CPU) * Fixes an issue that unexpectedly caused fewer devices to be reported (Keyboard, Linux) * Improves WM detection on LXQt by querying WM settings only when no WM has already been detected (#2199, WM, Linux) * Fixes memory leaks in DBus connection handling and in the OpenGL EGL context lifecycle * Fixes niri version detection on Fedora (WM, Linux) * Includes various internal cleanups and optimizations Logos: * Adds `RengeOS` (#2170) * Adds `Emmabuntüs` (#2207) * Updates Artix Linux (#2157) * Updates Linux Mint (#2186) * Renames `Refracted Devuan` to `Refracta` * Renames `ExodiaPredator` to `ExodiaOS` # 2.59.0 Changes: * Fastfetch no longer relies on the unreliable environment variables `$USER` or `%USERPROFILE%` to determine the current username (Title) * People who set `$USER` to customize the Fastfetch title should use `{ "type": "title", "format": "your-custom-user-name" }` to achieve the same result. * Fastfetch no longer tries to probe inaccessible remote disk drives on Windows (Disk, Windows) * People who have remote drives may use `{ "type": "disk", "hideFolders": "X:\\" }` to ignore problematic ones. * This change removes some ugly hacks from the codebase and matches the behavior on `*nix`. Features: * Adds Oracle Solaris support (#2176, SunOS) * Adds UID / SID detection (Title) * In custom format: `{user-id}` * Switches to native GPU detection on GNU/Hurd and removes the `libpciaccess` dependency (GPU, Hurd) * Improves memory size detection on macOS (Memory, macOS) * Avoids relying on `hw.memsize_usable` by default, which may not be available on older macOS versions * Improves Windows disk detection accuracy and performance (Disk, Windows) * Adds more ARM CPU parts and removes duplicated cases (CPU, ARM) Logos: * Adds 6-color support to the NixOS logo (including the small variant) (#2180) # 2.58.0 An early release to fix compatibility issues with KDE Plasma 6.6. Breaking changes: * The `de.slowVersionDetection` option has been removed. Slow version detection is now always enabled, as required on non-FHS-compliant distros (e.g., NixOS). (#2149, DE, Linux) Features: * Adds the `--structure-disabled ` command-line flag to temporarily disable module structure printing. * For example: `fastfetch --structure-disabled colors` removes the color blocks from the default output. * Supports chassis type detection on Linux ARM devices when reported via the device tree (Chassis, Linux) * Supports Bedrock Linux version detection (#2155, OS, Linux) * Honors the `DBPath` and `RootDir` settings in `pacman.conf` when detecting Pacman packages (#2154, Packages, Linux) Bugfixes: * Fixes a crash issue on KDE Plasma 6.6 (Display, Linux) * Fixes the Command module not working with `--dynamic-interval` (#2152, Command) * Fixes Quartz Compositor version detection. It now correctly reports the version of `WindowServer` (`SkyLight`) instead of `WindowManager`. (WM, macOS) Logos: * Adds Kiss2 # 2.57.1 Features: * Tiny performance improvements (Windows) * Improves the reliability of hostname retrieval (Title, Windows) Bugfixes: * Fixes potential compilation issues on Linux (#2142, Linux) * Fixes compilation errors on macOS when building with older SDKs (#2140, macOS) * Fixes compilation issues when building with `-DENABLE_SYSTEM_YYJSON=ON` (#2143) Logos: * Updates PrismLinux and adds a small variant # 2.57.0 Deprecation notice: * Support for Windows 7 (and 8.x) is deprecated and will be removed in a future release. Extended support for Windows 7 (and 8.1) ended on January 10, 2023. These versions do not officially support ANSI escape codes (running fastfetch on them requires a third-party terminal such as ConEmu). In addition, Windows 7 lacks some APIs used by fastfetch. Fastfetch currently loads these APIs dynamically at runtime to maintain compatibility, but this adds complexity to the codebase and increases the maintenance burden. * A CMake flag `ENABLE_WIN7_COMPAT:BOOLEAN` has been introduced (defaults to `ON` for now). If set to `OFF`, Windows 7 compatibility code is excluded, and the resulting binaries will support only Windows 10 (version 1607 and later) and Windows 11. * The main prebuilt Windows binaries on the Release page (`fastfetch-windows-amd64.*`) are built with `ENABLE_WIN7_COMPAT=OFF`. These are the binaries used by `scoop` and `winget`. Users who need Windows 7 (or 8.x) support can download the `-win7` variant instead. * ~~The `ENABLE_WIN7_COMPAT` CMake option and the `-win7` variant binaries are planned to be removed in 2.60.0~~. Features: * Supports COSMIC DE version detection (DE, Linux) * Supports niri version detection (#2121, WM, Linux) * Supports cosmic-term version and terminal font detection (Terminal / TerminalFont, Linux) * Supports urxvt font detection (TerminalFont, Linux) (#2105) * Improves xterm font detection by checking `xterm.vt100.faceName` (TerminalFont, Linux) * Supports Secure Boot detection (Bootmgr, macOS) * Supports DPI scale factor detection on Windows 7 (Display, Windows) * Supports xterm 256-color codes in color configuration * In `display.color`: "`@`" (e.g., "`@34`" for color index `34`) * In `*.format` strings: "`#@`" (e.g., "`#@34`" for color index `34`) * Improves uptime accuracy on Windows 10+ (Uptime, Windows) * Adds a new module `Logo` to query built-in logo raw data in JSON output (Logo) * Usage: `fastfetch -s logo -l -j # Supported in JSON format only` * Supports shell version detection even if the binary has been deleted (#2136, Shell, Linux) * Overall code refinements and optimizations Bugfixes: * Skips local / loopback routes when detecting network interfaces (LocalIP, Linux) (#2127) * Fixes CPU speed detection on s390x (CPU, Linux) (#2129) * Fixes GPU detection error handling and supports case-insensitive PCI ID parsing (GPU, Windows) * Fixes some networking issues and memory leaks (Networking) * Fixes `exePath` reporting relative paths on macOS (Shell, macOS) Logos: * Adds openSUSE Tumbleweed braille logo * Adds Xinux * Renames HydraPWK to NetHydra * Fixes colors of deepin and UOS * Fixes colors of macOS and variants # 2.56.1 Features: * Improves compatibility with KDE Plasma 6.5 (#2093, Display) * Adds a `tempSensor` option to specify the sensor name used for CPU temperature detection (CPU) * Example: `{ "type": "cpu", "tempSensor": "hwmon0" /* Use /sys/class/hwmon/hwmon0 for temperature detection */ }` * Refines Memory usage detection on macOS to match Activity Monitor more closely (Memory, macOS) * Minor optimizations Bugfixes: * Fixes cache line size detection (CPU, macOS) Logos: * Removes Opak * Updates GXDE # 2.56.0 Features: * Enhances config file loading. `--config` and `-c` with relative path now also searches paths defined in `fastfetch --list-config-paths` (typically `~/.config/fastfetch/`) * This allows users to use `fastfetch -c my-config` without needing to specify the full path. * Adds NUMA node count detection (CPU) * Exposed via `{numa-nodes}` in custom format * Supported on Linux, FreeBSD and Windows * Supports the newest Alacritty config format (#2070, TerminalFont) * Detects driver specific info for Zhaoxin GPUs (GPU, Linux) * Detects Android OEM UI for certain OSes (DE, Android) * Improves users detection on Linux (#2064, Users, Linux) * Adds systemd fallback when utmp is unavailable * Fixes resource leaks * Always reports the newest session info * Adds kiss package manager support (#2072, Packages, Linux) * Reports `sshd` if `$SSH_TTY` is not available (Terminal) * Zpool module rewrite (#2051, Zpool) * Adds new Zpool properties: allocated, guid, readOnly * Zpool module now uses runtime lookup for properties to ensure portability * Adds NetBSD (requires `sudo`) and macOS support * Adds `splitLines` option for Command module, which splits the output into sub modules, each containing one line of the output (Command) ``` * Command output: Line 1 Line 2 Line 3 * Old behavior: Command: Line 1 Line 2 Line 3 * With `"splitLines": true`: Command 1: Line 1 Command 2: Line 2 Command 3: Line 3 ``` Bugfixes: * Fixes {m,o}ksh version detection on Linux (Shell) * Fixes Alacritty config parsing for TOML format (#2070, TerminalFont) * Improves builtin logo printing for piping and buffering (#2065, Logo) * Uses absolute path when detecting shell and terminal version if available (#2067, TerminalShell) Logos: * Updates Codex Linux logo (#2071) * Adds OS/2 Warp logo (#2062) * Adds Amiga logo (#2061) # 2.55.1 Bugfixes: * Fix parallel command execution breaks randomly (#2056 / #2058, Command) * Regression from v2.55.0 * Fix `dylib` searching path on macOS (macOS) * Regression from v2.55.0 * Fix an uninitialized field (#2057, Display) # 2.55.0 Changes: * Commands are now executed in parallel by default to improve performance (#2045, Command) * This behavior can be disabled in the config file with `"parallel": false` if it causes problems with certain scripts * Folder/filesystem hiding is moved to the detection stage; hidden entries are no longer probed, improving performance (#2043, Disk) Features: * Adds `command.parallel` and `command.useStdErr` config options (Command) * `parallel`: set to `false` to disable parallel execution (see Changes above) * `useStdErr`: set to `true` to use stderr output instead of stdout * Adds the command-line flag `--dynamic-interval ` to enable dynamic output auto-refresh (#2041) * Due to internal limitations, some modules do not support dynamic updates (notably Display and Media) * Adds support for using the current playing media's cover art as a logo source (Media / Logo) * Usage: `"logo": { "type": "", "source": "media-cover" }` in JSON config; or `-- media-cover` in command line * Supports local sources only * Adds native GPU detection support on OpenBSD and NetBSD (instead of depending on `libpciaccess`) (GPU) * No functional changes * Root privileges are required to access PCI config space on OpenBSD (as always) * Adds GPU detection support on GNU/Hurd (GPU) * Requires building with `libpciaccess` * Shows Debian point release on Raspberry Pi OS (#2032, OS, Linux) * Adds `Brush` shell version detection (Shell) * Improves Mac family detection via prefix matching (Host) Bugfixes: * Ignores `run-parts` during terminal/shell detection (#2048, Terminal / Shell, Linux) * Fixes fish version detection when `LC_ALL` is set (#2014, Shell, Linux) * Hides the module when no desktop icons are found (#2023, Icons, Windows) * Skips auxiliary display controllers to prevent the module from reporting duplicate entries (#2034, GPU, Linux) * Refines Apple rpath handling; fixes building for the Homebrew version on macOS (#1998, CMake) Logos: * Adds Vincent OS and MacaroniOS # 2.54.0 Windows binaries in Release page are now signed by SignPath. Changes: * Moves macOS and Windows design language detection from the DE module to the Theme module Features: * Adds `--json` and `-j` command line flags as a shortcut for `--format json` * Various improvements to the OS module (OS) * Displays point releases for Debian * Displays code names for Ubuntu * Displays build ID for macOS * Displays code names for Windows (previously shown in the Kernel module) * Adds basic support for Wine (Windows) * Adds basic support for hppa and sh architectures (CPU, Linux) * Improves T-Head SoC name detection from the device tree (#1997, CPU, Linux) * Supports glob patterns in `Disk.hideFolders` (Disk) * For example, `/boot/*` will match both `/boot/efi` and `/boot/firmware` * Adds brightness-level detection for external monitor support on Intel macOS (Brightness, macOS) * Adds configurable spacing between icon and text in keys * `display.key.type: "both-N"` where N is `0-4` * Useful for non-monospaced Nerd Fonts * Adds detection support for modern Samsung Exynos SoCs (CPU, Android) * Adds a new CMake option `-DENABLE_WORDEXP=` to enable or disable using `wordexp(3)` for acquiring logo file paths (`logo.source`) * Enabled by default for compatibility * Disabling this option reverts to using `glob(3)`, which is less functional but more secure Bugfixes: * Avoids integer overflow when calculating swap size (#1988, Swap, Windows) * Trims whitespace from full user name (Title, macOS) * Fixes default font size for Ghostty (#1986, TerminalFont, Linux) * Works around an issue that could report impossibly high memory usage in rare cases (#1988, Memory, Linux) * Fixes incorrect glibc dependency in polyfilled DEB packages (#1983, Linux) * Fixes corrupted binaries in polyfilled RPM packages (#1990, Linux) * Fixes crashes on ancient Android kernels (#1993, Disk, Android) * Fixes incorrect usage of `glob(3)` (OpenBSD) * Prefers resolutions reported by RandR mode info, fixing incorrect resolutions on XFCE when DPI scaling is enabled (Display, Linux) * Various code cleanups and minor fixes Logos: * Adds secureblue, PrismLinux, EmperorOS and Zraxyl * Updates T2 # 2.53.0 Changes: * JSON property `length` in `Separator` module has been renamed to `times` for clarity (Separator) Features: * Adds IPv6 type selection (#1459, LocalIP) * For example: `{ "type": "localip", "showIpv6": "ula" /* Show ULA only */ }` * Adds more ARM CPU part IDs (CPU, Linux) * Improves Ghostty font config parsing with fallback font detection (#1967, TerminalFont) * Replaces statx(2) call with syscall(2) for better compatibility (Disk, Linux) * Allows array input for disk folder and filesystem options (Disk) * For example: `{ "type": "disk", "folders": ["/", "/home"] }` * Adds support for ignoring input devices by name prefix (#1950, Keyboard / Mouse / Gamepad) * For example: `{ "type": "keyboard", "ignores": ["Apple ", "Corsair "] }` * Adds support for (B)SSID detection on macOS Tahoe (Wifi, macOS) * Please don't expect it to work on later macOS versions * Improves Ubuntu flavor detection (#1975, OS, Linux) * Refines ARMv8.4-A detection to require LSE2 (CPU, Windows) * Detects the latest Dimensity & Snapdragon SoC names (CPU, Android) Bugfixes: * Handles zero temperature data (#1960, CPU, Windows) * Fixes `dlopen libzfs.so failed` error on Proxmox 9 (#1973, Zpool, Linux) Logos: * Removes Starry Linux * Adds TempleOS * Updates ObsidianOS # 2.52.0 Changes: * New optional build dependencies on Android * main: chafa dbus glib imagemagick libelf libxcb libxrandr pulseaudio zlib * x11: dconf (Optional) * Dependency on `libxfconf` is removed. XFCE related detection now uses `libdbus` instead (Linux) * The default format of `Display` module is updated to `{width}x{height} @ {scale-factor}x in {inch}", {refresh-rate} Hz` * Replaced scaled resolution with scale factor for shorter texts and avoiding potential confusion. Bugfixes: * Fixes linking on 32-bit Android (#1939) * Skips network interfaces without IPs unless MAC address is requested (#1949, LocalIP) * Fixes unexpected padding when setting `logo.width` with chafa logos (#1947, Logo) * Regression from v2.51.0 * Improves Wallpaper detection on XFCE4 (Wallpaper, Linux) * Ignores process `Relay(xxx)` when detecting terminal on WSL2 (Terminal, Linux) Features: * Enables X11-related info (i.e., WM/DE) detection on Android (Global, Android) * This requires many dependencies. See above. * Adds scale factors detection for X11 (Display, Linux) * X11 doesn't natively report scale factor as Wayland does. Instead, Fastfetch tries to detect `Xft.dpi` (DPI used by X FreeType for scaling fonts), which is usually set by the WM when DPI scaling is enabled. * It's not always accurate. For example, XFCE4 has a separate config for text scaling, which is unaffected by the global DPI scaling setting. * Adds `display.fraction.trailingZeros: [always|never]` option for fraction formatting * The default value of `display.fraction.ndigits` is changed from `-1` (unlimited) to `2` for usability. * Used for displaying scale factor in Display module mentioned above, alongside other places for printing raw fraction numbers. * Informs users that module-specific CLI options are no longer supported and provide guidance for transitioning to JSON config * Adds CPU name detection support for IA64 (CPU, Linux) * Support Btrfs allocation profile detection (#1941, Btrfs, Linux) # 2.51.1 Bugfixes: * Fix building on macOS 14 or older; no functional changes (CPU, macOS) # 2.51.0 Changes: * Fastfetch now requires [yyjson 0.12](https://github.com/ibireme/yyjson/releases/tag/0.12.0) to build when using `-DENABLE_SYSTEM_YYJSON=ON`. * The Disk module no longer shows hyperlink mountpoints by default, which cause issues on some real consoles (Disk) * Instead, the custom key for the Disk module now supports `{mountpoint-link}` and `{name-link}` to show hyperlinks for mountpoints and names. For example, `{ "type": "disk", "key": "Disk ({mountpoint-link})" }` can be used to restore the old behavior. Features: * Adds `succeeded` module condition to JSONC config. When set to `false`, the module will only run if the last module failed (#1908) * Useful for displaying fallback placeholders when a module fails. For example: ```jsonc { "host", // If fastfetch fails to detect host info, display "DIY PC" instead { "type": "custom", "condition": { "succeeded": false }, "key": "Host", "format": "DIY PC" } } ``` * By upgrading to yyjson 0.12, fastfetch now adds [JSON5](https://json5.org/) format support for configuration files (#1907) * [JSON5](https://json5.org/) is a superset of JSONC that allows unquoted keys, single quotes, multi-line strings, etc., and is fully compatible with JSONC and strict JSON. * To use JSON5, simply name your config file with a `.json5` extension. The `.jsonc` extension is still supported and used as the default extension for better IDE syntax highlighting support. * Fastfetch has been ported to [`GNU/Hurd`](https://www.gnu.org/software/hurd/) (#1895) * Thanks to the efforts of @yelninei! * Built-in logos now honor `logo.width` (#1905) * When its value is larger than the actual logo width, the logo will be padded with spaces to the right * Adds Trinity DE version detection (#1917, DE, Linux) * Adds formatted free and available disk size fields (#1929, Disk) * `{size-free}`: free size of the disk * `{size-available}`: available size of the disk * See [askubuntu.com](https://askubuntu.com/questions/249387/df-h-used-space-avail-free-space-is-less-than-the-total-size-of-home) for the difference between free and available size * Adds [x86_64 micro-architecture level](https://en.wikipedia.org/wiki/X86-64#Microarchitecture_levels) detection (#1928, CPU) * Useful when installing software that requires or is optimized for specific CPU features. E.g., [CachyOS](https://wiki.cachyos.org/features/optimized_repos/) * Exposed via `{march}` in custom format * Adds [Aarch64 micro-architecture level](https://en.wikipedia.org/wiki/AArch64#Profiles) detection (CPU) * Supported on Linux (including Android), macOS and Windows * This is not fully accurate because there are many optional features across different levels, and not all levels are detectable. * Exposed via `{march}` in custom format. * Adds shepherd detection support (InitSystem, Linux) Bugfixes: * Refines GPU detection logic to correctly handle virtual devices (#1920, GPU, Windows) * Fixes possible default route detection failure when the route table is very large (#1919, LocalIP, Linux) * Fastfetch now correctly parses `hwdata/pci.ids` files alongside `pciids/pci.ids` on FreeBSD when detecting GPU names (#1924, GPU, FreeBSD) * Fixes twin WM detection (#1917, WM, Linux) * Various fixes for Android support * Corrects WM name for Android (WM, Android) * Fixes battery temperature detection when running in ADB (Battery, Android) * Adds CPU and GPU temperature detection support (CPU, Android) Logos: * Adds AerynOS # 2.50.2 Bugfixes: * Fixes linglong package detection V2 (#1903, Packages, Linux) * Fixes building with `-DENABLE_SYSTEM_YYJSON=ON` (#1904) * Fixes `showMac` does not honor `defaultRouteOnly` (#1902, LocalIP, Linux) * Fixes failing to acquire default route on Linux in certain cases (#1902, LocalIP, Linux) # 2.50.1 Bugfixes: * Fixes percentage bar not displaying correctly in certain cases * Fixes linglong package detection on Debian 13 (#1899, Packages, Linux) # 2.50.0 Changes: * Keys in JSON configuration files are now case-sensitive, as stated in v2.49.0. * This is a breaking change, but it should not affect most users as long as your config file passes JSON schema validation. * All module config flags have been removed, as stated in v2.49.0. * To configure modules via the command line, use: `echo '{"modules": [{"type":"custom","format":"Hello Fastfetch!"}]}' | fastfetch -c -`. * The percent bar config `display.bar.*` options have been replaced with a more organized, nested object structure. * `display.bar.charElapsed` has been renamed to `display.bar.char.elapsed`. * `display.bar.charTotal` has been renamed to `display.bar.char.total`. * `display.bar.borderLeft` has been renamed to `display.bar.border.left`. * `display.bar.borderRight` has been renamed to `display.bar.border.right`. * The undocumented flag `--load-config` has been removed. * Use `--config` or `-c` instead. * Flashfetch, a simplified fastfetch variant that used a hardcoded module list with direct function calls to reduce startup overhead, has been changed to a version that aims to match neofetch's behavior as closely as possible, for demonstration purposes. * Flashfetch is intended to be built from source (like [st](https://st.suckless.org/)). We do not provide prebuilt binaries in distributions. Features: * Added support for reading JSON config from stdin using `--config -` or `-c -`. * Added `display.bar.border.{leftElapsed,rightElapsed}` for using the border as part of the bar content. (#1875) * `display.bar.border: null` has been added as a shorthand to disable bar borders. * Added `display.bar.color.{elapsed,total,border}` to customize the color of the elapsed, total, and border sections of the percent bar. * `display.bar.color: null` has been added as a shorthand to disable bar colors. * Improved Bedrock Linux detection (#1881, OS / Disk, Linux) * Added the command flag `--gen-config-full`, which generates a JSON config file containing all optional module options. * Improved the default IP address display when `localip.showAllIPs` is not set (LocalIP) * For IPv4, the preferred source address (if detected) is shown. * For IPv6, the first GUA or ULA that is not deprecated or temporary is shown. * Added support for interface speed detection on SunOS (LocalIP, SunOS) * Added detection support for Xlibre (#1888, WM, Linux) * Improved the accuracy of color detection (Cursor, macOS) * Improved the proformance of `Nix` package manager detection on macOS by porting optimizations form Linux port (#1893, Packages, macOS) Bugfixes: * Fixed custom object inheriting a key from the previous custom object if the key is blank (#1477) * Fixed a possible segfault when parsing color strings in the JSON config (#1878) * Fixed GPU driver detection when DRM is used (GPU, FreeBSD) * Fixed default route detection on DragonFly BSD (LocalIP, DFBSD) * Fixed lliurex detection (#1882, OS, Linux) * Fixed compatibility with `-ffast-math` (#1894) * Fixed physical GPU sometimes being ignored (#1896, GPU, Windows) Logos: * Added ObsidianOS (#1890) # 2.49.0 Deprecation Notice: * In fastfetch v2, the JSONC configuration format has been introduced, while command line configuration flags are kept for compatibility. Although they have the same effects, they use two different code paths, and as the number of flags grows, the codebase is becoming increasingly difficult to maintain. * Removal of module config flags is planned for **v2.50.0**, which will also fix a long-standing issue #1477. * Removal of most other config flags is also planned for later versions. * Keys of JSON configuration files will be all case-sensitive. Currently they are inconsistent. Planned for **v2.50.0**. Changes: * Due to more restrictive permissions in macOS Tahoe, SSID detection on macOS 26+ requires root privileges. `` will be displayed otherwise. Features: * Improve `nouveau` driver support for `--gpu-driver-specific` (GPU, Linux) * VRAM size detection * GPU temperature detection * Core count detection (when available) * Improve Scoop package manager detection (Packages, Windows) * Support [`scoop-global`](https://github.com/ScoopInstaller/Install?tab=readme-ov-file#advanced-installation) * Read Scoop's config file to find the installation path of Scoop * Improve ARM SoC detection (CPU, Android) * Make SoC detection more lenient. Higher chance to match at the cost of accuracy. * Add more Snapdragon SoC names * Support labwc WM version detection, used for XFCE4 on Wayland (WM, Linux) * Improve accuracy of GPU temperature detection for Intel dedicated GPUs on Windows (GPU, Windows) * Parse unicode escaped strings generated by qt5ct (#1864, Font, Linux) * Add `--{duration,percent,size,freq,temp}-space-before-unit [always|never]` options to add a space before the unit when printing duration, percent, size, frequency and temperature values * Add `--duration-abbreviation` to abbreviate duration values in custom format * For example: `1 day, 2 hours, 3 mins` will be displayed as `1d 2h 3m` * Add `--percent-width` to pad the percent value with spaces to a fixed width * For example: `--percent-width 3` will display ` 50%` instead of `50%`; useful for aligning percent values in custom format Bugfixes: * Improve accuracy of Flatpak count detection (#1856, Packages, Linux) * Remove qi package manager support (#1858, Packages, Linux) * Fix LocalIP module on Windows (LocalIP, Windows) * Fix default route detection when multiple network interfaces are connected * Fix link speed calculation * Fix interface status when the interface is up but not connected (Wifi, Linux) * Fix variable names in custom format (#1861) * `full-path` to `path` (Editor) * `session` to `session-name` (Users) * `name` to `project-name` (Version) * Fix wrong /s assignment in custom format (#1871, DiskIO) Logos: * Add `Aeon` * Remove `Evolinx` # 2.48.1 Features: * Add support for detecting Openbox WM version (WM, Linux) * Improve reliability of child process spawning on Windows (Windows) * Add a new option `--packages-combined`, which combines related package managers into single counts (#1851, Packages) * For example: if you have both `flatpak-system` and `flatpak-user` packages installed, they will be combined into a single `flatpak` count with `--packages-combined` enabled. * Add `modules[n].condition` to conditionally enable modules on different platforms * Useful when sharing configuration files across platforms * For example: ```jsonc { "type": "custom", "format": "This string will be printed on Intel macOS only", "condition": { "system": "macOS", // Can be an array, optional "arch": "x86_64" // Can be an array, optional too } } ``` Bugfixes: * Revert the change of `posix_spawn` in v2.48.0 for Android and OpenBSD (Android / OpenBSD) * Fix completion for Android 7 (Required by Termux) # 2.48.0 Features: * Add support for detecting Fedora variants (#1830, OS, Linux) * Currently supported variants: CoreOS, Kinoite, Sericea, Silverblue * Optimize GPU detection on Windows when `--gpu-driver-specific` is not used (GPU, Windows) * Improve accuracy of GPU type detection. Previously it was guessed based on the dedicated vmem size, which causes issues on newer AMD integrated GPUs such as 9000 HX and AI 9 HX series. Supported on Windows 8.1 or later. * Add support for generic frequency detection of GPU 3D engine on Windows 11 22H2 or later. * Improve performance. GPU temperature detection is significantly improved when `--gpu-driver-specific` is not used. * Improve performance and security when spawning child processes by replacing `fork-exec` with `posix_spawn` (*nix) * Improve accuracy of sound device detection on macOS (Sound, macOS) * Trim leading and trailing whitespaces in disk serial numbers (PhysicalDisk) * Add `/etc/profiles/per-user` detection for Nix user packages (#1782, Packages, Linux) * Introduce `years` (whole years as integer), `days-of-year` (days since the last anniversary) and `years-fraction` (years as fraction, e.g. 1.5 means 1 year and 6 months) formatting placeholder to `Disk` (since disk creation), `Users` (since user login) and `Uptime` (since system boot) modules * For example: `fastfetch -s disk --disk-key 'OS Installation' --disk-format '{years} years {days-of-year} days'` * Add `--fraction-ndigits` option to specify the number of digits after the decimal point when displaying ordinary fractional values * Typically used with `{years-fraction}` above * This option does not affect percentage values, sizes, etc, which are controlled by individual options. Bugfixes: * Fix compilation issues when not using `-DBINARY_LINK_TYPE=dlopen` * Regression from v2.47.0 * Note: this option was added for debugging purposes only and is not recommended for production use * Replace `MTLDevice::hasUnifiedMemory` with `MTLDevice::location` for GPU type Detection (GPU, macOS) * This should resolve the issue where discrete GPUs were detected as integrated GPUs on Intel MacBooks with multi-GPU configurations. * Prevent text files from being loaded as image files (#1843, Logo) Logos: * Add Minimal System * Add AxOS * Rename Ada to Xray OS # 2.47.0 Features: * Various improvements for Solaris / OpenIndiana support * Support BIOS (UEFI or legacy) type detection (BIOS) * Support physical disk detection (PhysicalDisk) * Remove leading `-` from login shells (Shell) * Improve GPU detection performance (GPU) * Drop `libpciaccess` dependency * Use native API to detect sound devices (Sound) * Drop `PulseAudio` dependency * Improve DietPi OS and Raspberry Pi OS detection (#1816, OS, Linux) * Force reporting version 26 on macOS Tahoe (OS, macOS) * Append version string to Ubuntu variants (OS, Linux) * Improve performance of media detection for macOS 15.4+ (Media, macOS) * Increase `PROC_FILE_BUFFSIZ` to avoid possible short reads (Linux) * Fix potential bugs in `DiskIO`, `NetIO` and `CPUUsage` modules * Improve accuracy of CPU usage calculations by including interrupt and softirq times (CPUUsage, Linux / *BSD) * Ignore `init` and `systemd` processes when detecting terminals (Terminal, Linux) * Improve accuracy of CPU usage detection on Windows 10+ with perflib, which matches values reported by Task Manager (CPUUsage, Windows) Bugfixes: * Fix `pci.ids` file location (#1819, GPU, OpenBSD) * Fix compiling on FreeBSD when `libdrm` support is disabled (#1820, GPU, FreeBSD) Logos: * Improve visibility on white-background terminals for some logos by replacing white with the default foreground color * According to Wikipedia, the default foreground color is implementation-defined. It's usually black for white themes and white for dark themes. However, some terminals, notably Konsole with the default theme, use a different color, which may cause issues with some logos. * Add Xubuntu # 2.46.0 Features: * Support Rio terminal font detection (#1789, TerminalFont, Linux) * Support GPU detection by DRM on FreeBSD (GPU, FreeBSD) * Enable by `--gpu-detection-method auto` * Require proper DRM drivers installed and loaded * Support PowerPC CPU detection on NetBSD (#1802, CPU, NetBSD) * Support Aerospace WM detection (#1796, WM, macOS) * Improve Raspberry Pi OS for RPI5 detection (#1773, OS, Linux) * Support Linux Binary Compatibility detection on FreeBSD (#1786, Host, Linux) * Use `board-id` as board name if available (Board, macOS) * Intel only * Support shared VRAM usage detection for AMD GPUs (GPU, Linux) * Use `perflib.h` instead of `pdh.h` for CPU temperature querying to get rid of `pdh.dll` dependency (#1787, CPU, Windows) * Support GPU info detection for old ATI radeon driver (#1810, GPU, Linux) * Add macOS 26 Tahoe support (macOS) * Report macOS 26 code name (OS) * Report Liquid Glass DE on macOS 26+ (DE) * Detect Metal 4 support (GPU) Bugfixes: * Fix packages counting by ignoring hidden folders (Packages, OpenBSD) * Fix Hyprland version detection (WM, FreeBSD) * Don't show `Please insert a disk into drive D:` error dialogs (#1805, Disk, Windows) * Hide `/boot/firmware` by default (Disk, Linux) Logos: * Rename Hydra Framework to HydraPwk (#1812) * Add AnushOS (#1806) * Add HarmonyOS (#1804) * Add GhostFreak (#1801) * Add TrueNAS Scale (#1795) * Add Fedora2_small (#1785) * Add xenia_old; update colors of xenia (#1797) * Improve colors of bedrock_small (#1790) * Add Kalpa Desktop (#1807) # 2.45.0 Features: * Support OnePlus marketing name detection (#1768, Host, Android) * Recognize additional GPU vendors (GPU, Linux) * Support CTWM, FVWM and I3 window manager version detection (WM) * Support KDE version detection on *BSD (DE, FreeBSD) * Support `"logo": { "type": "command-raw" }` to run a command and display its output as logo (#1780, Logo) * Useful for displaying custom logos generated by other programs such as `pokeget`: `{ "type": "command-raw", "source": "pokeget random --hide-name" }` * Supported in JSONC config file only. `pokeget random --hide-name | fastfetch --file-raw -` should be used in shell. * Acquire SMBIOS information on DragonFly BSD from `/dev/mem`; legacy BIOS only (PhysicalMemory, DragonFly) * Support swap usage detection on DragonFly BSD (Swap, DragonFly) * Support `--swap-separate` to display detailed swap devices on separate lines (Swap) Bugfixes * Fix MacBook Air model name (#1779, Host, macOS) * Don't ignore sshfs mountpoints (#1776, Disk, Linux / FreeBSD) * Fix dnf package count detection (#1777, Packages, Linux) Logos: * Add Starry Linux (#1771) * Add rhel_small (#1774) * Update color palette of voidlinux (#1775) * Add void2 * Update Xenia Linux (#1783) # 2.44.0 Features: * Add option `--disk-hide-folders` and `--disk-hide-fs` to hide specific mountpoints and filesystems in Disk module (Disk) * `--disk-hide-folders` defaults to `/efi:/boot:/boot/efi` on Linux and *BSD. Previously these EFI-related folders were hardcoded to be hidden on Linux. Bugfixes: * Fix Apple Terminal compatibility with `--stat` (macOS, #1755) * Ignore `/usr/bin/script` when detecting shell and terminal (Terminal / Shell, #1761) * Fix compatibility with KDE Plasma 6.4 which is in beta currently (Display, Linux, #1765) Logos: * Add Kylin (#1760) * Add UBLinux (#1764) # 2.43.0 Features: * Support physical core count and package count detection on Solaris (CPU, SunOS) * Improve physical core count detection on FreeBSD (CPU, FreeBSD) * Add option to hide unknown GPUs (GPU) * Detect VRAM type of AMD GPUs on Linux (GPU, Linux) * Support playing media detection on macOS 15.4 (#1737, Media, macOS) * Whether it works on newer versions is unknown * Detect player name for Windows UMP apps (Media, Windows) Bugfixes: * Fix disk usage detection on 32-bit Linux (#1734, Disk, Linux) * Fix compiling on Asahi Linux (GPU, Linux) * Fix duplicated playback status (Media, Linux) * Don't show 255 in custom format when muted on macOS (#1750, Sound, macOS) * Remove shared memory detection for AMD GPUs, which doesn't work as expected (GPU, Windows) Logos: * new AthenaOS * add Hydra Framework # 2.42.0 Changes: * Normalize the module name `Bios` to `BIOS` (#1721) * No configuration file changes are required because fastfetch parses module names case-insensitively. Bugfixes: * Disable disk type detection for virtual disks (PhysicalDisk, Linux, #1669) * Fix incorrect CPU temperature reporting (CPU, OpenBSD) * Fix setting `logo.chafa.symbols` in JSON configuration (Logo, #1709) * Fix non-normalized time display (Uptime, #1720) * Miscellaneous minor fixes Features: * Add CPU temperature detection support (CPU, SunOS) * Improve CPU frequency detection (CPU, NetBSD) * Add Wi-Fi detection support (Wifi, NetBSD) * Add Webcam detection support (Camera, OpenBSD) * Requires root privileges Logos: * Remove GoralixOS logo (#1699) * Add Aurora logo (#1700) * Add Codex Linux logo (#1701) # 2.41.0 Changes: * Due to [the deprecation](https://github.com/actions/runner-images/issues/11101), Linux x86_64 binaries are now built with Ubuntu 22.04 (Glibc 2.35, Debian 12) * You can always build fastfetch yourself on your own. Please don't report bugs related to this change. Features: * Support physical core count detection on non-x86 platforms (CPU, Linux / FreeBSD) * Support CPU frequency detection on PPC64 (CPU, FreeBSD) * Support soar packages count detection (Packages, Linux) * Support `~` path expanding on Windows (Logo, Windows) * Support retrieving full user name (Title) * Exposed with `--title-format '{full-user-name}'` * Improve CPU (thermal zone) temperature detection on Windows (CPU, Windows) * Administrator privileges are no longer needed * Support base Wifi info detection on OpenBSD (Wifi, OpenBSD) * To be tested * Support GPU temperature detection for Intel dGPU on Linux (GPU, Linux) * To be tested * Add new ARM CPU part numbers (CPU, Linux) * Add base implementation of Bluetooth device detection (Bluetooth, NetBSD, #1690) * Some small improvements Logo: * Add anduinos * Add 2 more Alpine logos # 2.40.4 Bugfixes: * Fix loading presets config on Windows (Windows, #1682) * Regression of v2.40.0 * Remove the prefix `v` of Hyprland version on Arch Linux (WM, Linux) # 2.40.3 Bugfixes: * Fix loading example configs from presets directory (#1672) * Regression of v2.40.2 * Mark kitty image protocol support for warp terminal on macOS too (Logo) # 2.40.2 Changes: * Since v2.40.0, we've been loading config files from the directory where the fastfetch binary is located. However, this approach may lead to loading unexpected files. For example, `fastfetch -c groups` would attempt to load `/usr/bin/groups`. Therefore, we now enforce the `.jsonc` extension when loading config files. Examples: 1. `-c filename`: loads `filename.jsonc` 2. `-c filename.jsonc`: loads `filename.jsonc` 3. `-c filename.json`: loads `filename.json` and enforces strict JSON syntax (no comments or trailing commas) 4. `-c filename.ext`: loads `filename.ext.jsonc` (`.jsonc` extension is enforced) Features: * Mark kitty image protocol support for warp terminal (Logo) * Documentation improvements # 2.40.1 Bugfixes: * Fix compiling error on old intel platform (TPM, macOS) * Fix `--file-raw -` no longer working (Logo, #1659) * Regression of v2.40.0 # 2.40.0 Changes: * In `key-format` of `LocalIP` module, `{name}` has been renamed to `{ifname}` for consistency (LocalIP, #1639) Features: * Support Warp Terminal font detection (TerminalFont, Windows) * Support more AMD GPU information using ADL SDK, including memory type detection (GPU, Windows) * Support Intel dGPU memory type detection (GPU, Windows) * Support Nvidia VMEM type detection via NVAPI (GPU, Windows, #993) * Support Boot manager detection for OpenBSD and NetBSD (Bootmgr, OpenBSD / NetBSD) * Use `SystemConfiguration` for DNS entries detection (DNS, macOS) * Add `systemd-resolved` support for DNS module (DNS, Linux, #1646) * Improve performance and accuracy of Wifi detection on FreeBSD using ioctl (Wifi, FreeBSD) * Support remaining time reporting for batteries on NetBSD (Battery, NetBSD) * Add new Mac models support (Host, macOS) * Load config from fastfetch binary path with `--config` option (#1649) * Support TPM detection on macOS (TPM, macOS) * Support IPv6 client address report (Users, Linux / Windows) * Support default route detection for IPv6 (LocalIP, Linux) * Round seconds to the nearest minute to match the behavior of `uptime` command (Uptime) Bugfixes: * Fix `outputColor` not working when `length` is set in Separator module (#1644) * Fix CPU detection on PowerPC platforms (#1640, CPU, Linux) * Fix battery manufacture date detection (Battery, macOS) * Fix battery critical state detection (Battery, Linux) * Fix Warp Terminal PID detection (Terminal, macOS) * Remove disk creation time detection support on SunOS as ctim is file status change timestamp, not creation time (Disk, SunOS) * Fall back to KDGKBINFO if `usbhid` fails (Keyboard, FreeBSD) * Fix multiple paging file support (Swap, Windows) * Fix memleaks, code smells in multiple modules * Fix boot time calculation on NetBSD (Uptime, NetBSD) * Temporarily fix Hyprland version detection (WM, Linux, #1657) Logo: * Fix opensuse-tumbleweed_small (#1636) * Change WiiLinuxNgx to more generic name with aliases Wii-Linux and WiiLinux (#1633) * Change name of Xray-OS to Ada (#1651) * Change Nexa Linux logo (#1653) # 2.39.1 Bugfixes: * Fix a regression that PublicIP detection fails randomly (PublicIP, #1629) # 2.39.0 Changes: * OSMesa backend for OpenGL detection is removed (#1618) * Fastfetch no longer tries to use the private framework `Apple80211` to acquire SSID for Wifi module, which is only useful for macOS Sonoma (Wifi, macOS) Features: * Improve accuracy of HDR support on Windows 11 24H2 (Display, Windows) * Improve performance of SSID detection on macOS Sequoia (Wifi, macOS, #1597) * Support warp terminal version detection on Windows (Terminal, Windows) * Support default route detection on OpenBSD & DragonFly BSD (LocalIP, OpenBSD / DragonFly) * Improve bash completion script * Improve performance of networking (PublicIP / Weather) * Support pkgsrc package manager detection on Linux (Packages, Linux) Logo: * Add Common Torizon OS * Change FoxOS to WolfOS * Add Bredos * Add NetBSD2 # 2.38.0 Bugfixes: * Fix empty battery slots handling (Battery, Haiku, #1575) * Fix `{day-pretty}` output in custom format (DateTime, Windows) * Fix VanillaOS detection (OS, Linux) * Fix secure boot testing (Bootmgr, Linux, #1584) * Fix the SI unit "kB" in help message (#1589) * Fix segfault on macOS 10.15 when using the binary downloaded from Github Releases (Camera, macOS, #1594) Features: * Support Chassis module in macOS (Chassis, macOS) * Allow customize key format with kernel name and distro name (OS) * Add missing `{icon}` in custom key format (Battery) * Add missing `{mountpoint}` and `{mount-from}` in custom output format (Disk, #1577) * Support percentage num & bar in custom format (GPU, #1583) * Support `pisi` package manager detection (Packages, Linux) * Support termite terminal font detection (TerminalFont, Linux) * Report monitor type in Brightness module (Brightness) Logo: * Add `opensuse-tumbleweed_small` * Add `Bedrock_small` * Add `fastfetch` * Remove some unnecessary distro names # 2.37.0 Changes: * Option `--escape-bedrock` is removed. The function is always enabled now. Features: * Support for Haiku is greatly improved (Haiku) * CPU, GPU, Disk, Sound, Terminal, Terminal Font, Init System, Battery, Mouse, Keyboard, NetIO, CPU Usage, Physical Disk and OpenGL should work on Haiku now * SMBIOS related modules (Host, Bios, Board, Chassis, Physical Memory) should work in platforms with legacy BIOS system. * Support for Gamepad and Bluetooth are WIP. * Some bugs are found and fixed. * Remove `python-requests` dependency in `scripts/gen-*.py`. * Add cmake option `-DENABLE_EMBEDDED_AMDGPUIDS=BOOL` (disabled by default) * If enabled, fastfetch will embed the newest [`amdgpu.ids`](https://gitlab.freedesktop.org/mesa/drm/-/blob/main/data/amdgpu.ids?ref_type=heads) file into fastfetch binary. * Weather module now honors `display.temp.unit` option (#1560, Weather) * Support Physical Memory module in NetBSD (PhysicalMemory, NetBSD) * Requires root permission * Improve non-intel CPU detection in NetBSD (#1573, CPU, NetBSD) Bugfixes: * Fix building in macOS 10.13 (GPU, macOS) * Properly round percent values when detecting volume (#1558, Sound) * Fix Physical Memory module doesn't work in `--format json` mode * Add some missing variable inits (GPU, Linux) * Fix `--localip-default-route-only false` not working with `--gen-config` (#1570, LocalIP) Logo: * Update Rosa linux * Add Haiku2 # 2.36.1 Changes: * To use [the native arm64 runner of Github Action](https://github.blog/changelog/2025-01-16-linux-arm64-hosted-runners-now-available-for-free-in-public-repositories-public-preview/), Linux aarch64 binary is built with Ubuntu 22.04 (Glibc 2.35, Debian 12). Bugfixes: * Chimera Linux logo is now displayed correctly (#1554, Logo) * Regression of 2.36.0 * Fix building on Haiku Logo: * Fix ALT Linux # 2.36.0 Bugfixes: * Trim leading slash for login shells (Shell, OpenBSD) * Prefer SOC name if available over CPU name (CPU, Linux) Features: * Use kernel API to detect sound devices (Sound, NetBSD) * Use sndio for sound server detection on OpenBSD (Sound, OpenBSD) * Add minimal implementation for Haiku (#1538, Haiku) * Support CPU & GPU temperature detection for M4x (CPU / GPU, macOS) * Support VMEM size detection for old Nvidia cards (GPU, Linux) * Use [recommendedMaxWorkingSetSize](https://developer.apple.com/documentation/metal/mtldevice/recommendedmaxworkingsetsize) as total GPU mem size (GPU, macOS) * Support Physical core count and CPU package count detection for loongarch (CPU, Linux) * Split ID_LIKE when used for distro matching (#1540, Logo) * Capitalize `{type}`'s first letter in custom format (#1543, Display) * Support model name detection for s390x (CPU, Linux) * Support more Armbian variants detection (#1547, OS, Linux) * Support the syntax of `{$ENV_VAR}` in custom format, which will be replaced by the value of the environment variable `ENV_VAR` (#1541) * This is another way to pass 3rd-party data to fastfetch besides `Custom` module. * Improve performance of Tilix version detection (Terminal, Linux) Logo: * Update arch_old * Add Nexa Linux * Add filotimo * Update some distro names # 2.35.0 Bugfixes: * Suppress output of EGL again (#1513, GPU, Linux) * Regression of 2.34.0 Features: * Show SOC name reported in `cpuinfo` if available (#1510, CPU, Linux) * Change package manager name of NetBSD from `pkg` to `pkgsrc` (#1515, Packages, NetBSD) * Detect SOC name on RISCV (#1519, CPU, Linux) * Report marketing name of new QS8Es (CPU, Android) * Acquire acquire more os info from lsb-release if missing from os-release (#1521) * CMake: add option `-DCUSTOM_LSB_RELEASE_PATH` to specify the path of `lsb-release` file * `-DCUSTOM_OS_RELEASE_PATH` has been supported since `v2.11.4` * Report more SOC names on Android (CPU, Android) * Support duration printing in custom format (Disk / Users) * For example: ```jsonc { "modules": [ { "key": "OS Installation Date", // No longer need to write bash scripts "type": "disk", "folders": "/", // Different OSes may need to specify different folders "format": "{create-time:10} [{days} days]" // Reports the creation date of the root folder } ] } ``` Logo: * Add Arch_old * Update key color of NetBSD_small * Fix OpenBSD and many other ascii logos (#1522) # 2.34.1 An early release to fix KDE Plasma 6.3 compatibility. Hopefully it can be accepted by package managers before KDE 6.3 is officially released. To package managers: if you find fastfetch bugs, it's highly appreciated if you can report them to the upstream, so that all users can benefit from the fix, instead of maintaining out-of-tree patches. Thanks! Features: * Report vendor name when detecting GPUs by OpenGL * Note: the vendor name is actually the creator of the OpenGL driver (such as `Mesa`) and may not be the same as the GPU vendor. Bugfixes: * Fix Ghostty termfont detection (#1495, TerminalFont, macOS) * Fix compatibility with KDE Plasma 6.3 (#1504, Display, Linux) * Make memory usage detection logic consistent with other systems (Memory, OpenBSD / NetBSD) * Report media file name if media title is not available (Media) * Fix max frequency detection for CPUs with both performance and efficiency cores (CPU, FreeBSD) Logo: * Add HeliumOS * Add Oreon * Update SnigdhaOS # 2.34.0 Changes: * We now print distro pretty name if available (OS) * This is a long requested feature. However, it may break some distros. File a bug with the content of `/etc/os-release` if it breaks your distro. Bugfixes: * Fix thunderbolt version of new MBPs (#1465, Host, macOS) * Fix backlight name detection on FreeBSD (Brightness, FreeBSD) * Fix Terminal detection when running fastfetch in `pk-command-not-found` (#1467, Terminal, Linux) * Relax detection of terminals in NixOS (#1479, Terminal, Linux) * Should fix konsole, ghostty and maybe others * Fix core count output in multi-package platforms (CPU) * Don't suppress the output of `preRun` (#1489) * Fix battery percentage detection (Battery, NetBSD) Features: * Support ghostty terminal font detection (TerminalFont, Linux / macOS) * Support `kitty-icat` image protocol, which uses `kitten icat` to generate image data * Pros: support tmux; support gif animations; good performance * Cons: due to the limitation of `kitten icat`, we need to clear the screen before displaying the image logo * Support WM version detection (WM) * In Linux, Hyprland & sway are supported currently * Improve performance when stdout is redirected (TerminalSize) * Report thermal zone temp if CPU temp is not available (CPU, Linux) * Report sound server (Pipewire or PulseAudio) if available (#1454, Sound, Linux) * Enable OpenGL & OpenCL detection on Android (OpenGL / OpenCL, Android) * Detect & report MediaTek Dimensity 9000+ SOC name (CPU, Android) * Support appman (am-user) package manager detection (Packages, Linux) Logo: * Add Lubuntu * Update Xray_os * Add SnigdhaOS * Add Rhino Linux # 2.33.0 Changes: * Introduce a new CMake flag `-DBUILD_FLASHFETCH=OFF` to disable building flashfetch binaries * Package managers are encouraged to enable it. See for detail Bugfixes: * Fix interconnect type detection (#1453, PhysicalDisk, Linux) * Regression of v2.28 * Don't report `proot` as terminal (Terminal, Android) * Remove a debug output (DiskIO, OpenBSD) * Fix media detection for some players (#1461, Media, Linux) * Regression of v2.32 Features: * Use `$POWERSHELL_VERSION` as PowerShell version if available (Shell, Windows) * Fetching Windows PowerShell version can be very slow. Add `$env:POWERSHELL_VERSION = $PSVersionTable.PSVersion.ToString()` in `$PROFILE` before running `fastfetch` to improve the performance of `Shell` module * Add support for ubuntu-based armbian detection (#1447, OS, Linux) * Improve performance of Bluetooth detection (Bluetooth) * We no longer report disconnected bluetooth devices in `--format json` when `--bluetooth-show-disconnected` isn't specified * Support brightness level detection for builtin displays (Brightness, OpenBSD / NetBSD) * Requires root permission on OpenBSD * Support battery level detection (Battery, OpenBSD / NetBSD) * Support CPU temperature detection in NetBSD (CPU, NetBSD) * Hard code path of `libvulkan.so` for Android * So that users don't need to install the vulkan-loader wrapper of termux Logo: * Add NurOS * Add GoralixOS # 2.32.1 A hotfix for OpenBSD. No changes to other platforms. Bugfixes: * Fix package count detection on OpenBSD (Packages, OpenBSD) # 2.32.0 Bugfixes: * Fix `pci.ids` file location on OpenBSD (GPU, OpenBSD) * It's normally unused because enumerating PCI devices on OpenBSD requires root privileges * Fix bssid formatting (Wifi, Linux) * Fix Linux Lite distro detection (#1434, OS, Linux) * Suppress XE driver warnings from Mesa (#1435, OpenGL, Linux) * Fix format parameter name (#1443, Version) * Don't report useless information when Wifi is disabled (Wifi, FreeBSD) * Currently there are issues when the SSID contains whitespaces. More fixes are expected in the future. * Always use physical size reported by X11 server to avoid inconsistent results (#1444, Display, Linux) Features: * Randomly select one if the logo source expands to multiple files (#1426, Logo) * Report mac product name when running Linux in MacBook (Host, Linux / FreeBSD) * Use screen size reported in DTD if make sense (Display) * Detect Virtualized Apple Silicon CPUs (CPU, Linux) * Add detection support for fvwm and ctwm (WM, OpenBSD / NetBSD) * Add Armbian-unofficial detection (OS, Linux) * Prefer surfaceless display when connect EGL (OpenGL) * Improve accuracy of WM detection on FreeBSD (WM, FreeBSD) * Add ratpoison window manager (WM, Linux) Logo: * Update Linux Lite * Add Serpent OS * Add Ultramarine Small * Update Debian # 2.31.0 Bugfixes: * Improve performance of media detection; fix musikcube detection (Media, Linux) * After the change, `general.processingTimeout` will also control the timeout of dbus remote calls * Fix invalid variable names (#1408, Users) * Change physical size detection to use basic display parameters (#1406) * Fix possible sigfaults when detecting displays (#1393) * Fix Nvidia card type detection * Fix wl-restart parsing (#1422, WM, Linux) * Fix syntax error in completion file (#1421) * Fix hunging when using `ssh-agent` as command text (#1418, Command, macOS) Features: * Remove support of xcb & xlib and xrandr extension is always required (Display) * Support preferred resolution & refresh rate detection * On macOS there is no preferred resolution reported and maximum available resolution is reported instead. * `--display-format {preferred-width}x{preferred-height}@{preferred-refresh-rate}` * Report scale factor in custom format (Display) * `--display-format {scale-factor}` * Detect current Wi-Fi channel and maximum frequency (Wifi) * Report processor package count (#1413, CPU) * Remove duplicate whitespaces in CPU name * Support sakura terminal version & font detection (Terminal / TerminalFont, Linux) Logo: * Fix LMDE * Update MidOS * Add Windows Server 2025 # 2.30.1 Bugfixes: * Fix the destination where `fastfetch.1` is generated (#1403) # 2.30.0 Changes: * Percent: bar type must be enabled in `percent.type` before using percent bar in custom format Features: * Port to MidnightBSD; add mport package manager support * Support bluetooth battery detection for macOS and Windows (Bluetooth, macOS / Windows) * Support M4 model detection (Host, macOS) * Support CPU temperature detection on OpenBSD (CPU, OpenBSD) * Display Android icon in Android devices (OS, Android) * Support qi package manager detection (Packages, Linux) * Detect WM / DE by enumerating running processes (WM / DE, NetBSD) * Generate manual pages from `help.json` (Doc) * Detect marketing name of vivo smartphone (Host, Android) * Add txDrops detection if supported (NetIO, *BSD) * Support tilix version detection (Terminal, Linux) * Support percent type config in module level. Example: ```jsonc { "type": "memory", "percent": { "green": 20, // [0%, 20%) will be displayed in green "yellow": 40, // [20, 40) will be displayed in yellow and [40, 100] will be displayed in red "type": [ // Display percent value in monochrome bar, same as 10 "bar", "bar-monochrome" ] } } ``` Bugfixes: * Don't display `()` in key if display name is not available (Display) * Fix & normalize bluetooth mac address detection (Bluetooth, macOS / Windows) * Don't print index in multi-battery devices (Battery) * Fix segfault in macOS (#1388, macOS) * Fix `CFStringGetCString() failed` errors (#1394, Media, macOS) * Fix CPU frequency detection on Apple M4 (#1394, CPU, macOS) * Fix exe path detection on macOS (Shell / Terminal, macOS) * Fix logo fails to load from symlinked files on macOS (#1395, Logo, macOS) * Fix 32-bit truncation (NetIO, macOS) Logos: * Fix Lilidog * Add MidnightBSD * Add Unifi * Add Cosmic DE * Update openSUSE Tumbleweed # 2.29.0 Changes: * Due to [the upstream removal of MSYS2 CLANG32 environment](https://www.msys2.org/news/#2024-09-23-starting-to-drop-the-clang32-environment), we dropped fastfetch-windows-i686 support. v2.27.1 was the last version supporting it. * Note: fastfetch built with MSVCRT has known bug that DateTime module doesn't work because of its bad support of [strftime](https://en.cppreference.com/w/c/chrono/strftime). Don't use it. Features: * Port to NetBSD and DragonFly BSD * Fastfetch now supports all major BSD variants * Support DiskIO, NetIO, GPU and Users module on OpenBSD * Report SD8E SOC name (CPU, Android) * On Windows, try loading dlls from current exe path (Windows) * Fix Media module when installed with winget Bugfixes: * Fix the VIM version detection on Ubuntu (Editor, Linux) * Improve performance of OS version detection on Proxmox (#1370, OS, Linux) Logo: * Update OpenSuse Tumbleweed * Add XCP-ng * Add SummitOS * Add Lilidog * Update PikaOS * Update OpenSUSE Leap * Update aperture # 2.28.0 Features: * Add new module `Mouse` and `Keyboard` which display connected mice and keyboards * Support remaining time detection (Battery) * Report if AC is connected (Battery, Linux) * Report platform API used for display detection for debugging (Display) * Report Wine version when running in Wine (Kernel, Windows) * Add option `waitTime` in modules `CPUUsage`, `DiskIO` and `NetIO` Bugfixes: * Fix used memory size detection (Memory, OpenBSD) * Don't report invalid fragmentation percentage when fails to detect it (Zpool) * Fix unexpected errors when running fastfetch in parallel (#1346, Windows) * Don't report obviously invalid temperature values (PhysicalDisk, Linux) Logos: * Add eweOS * Add MidOS * Update XeroArch # 2.27.1 Bugfixes: * Fix invalid display name detection on GNOME, wayland (Display, Linux) # 2.27.0 Changes: * We now print `"` instead of `″` when displaying diagonal length in inches, so that the character can be correctly displayed in Linux console (Display) * All detection code of `monitor` module is merged into `display` module. Now `monitor` just prints the same information as `display` with different format. Notably: * The resolution reported by `monitor` module is now current resolution instead of native / maximum resolution. PPI is calculated based on current resolution too. * The refresh rate reported by `monitor` module is the current refresh rate. Features: * Add basic, highly experimental support of OpenBSD (OpenBSD) * Improve support for Raspberry pi (CPU / GPU, Linux) * Detect SOC name, instead of displaying components used in the SOC, if available (CPU, Linux) * Add option `--brightness-compact` to display multiple brightness values in one line (Brightness) * Add `day-pretty` (#1305, DateTime) * Support network interface adapter flag detection (#1315, LocalIP) * Enable it with `--localip-show-flags` Bugfixes: * Remove trailing newline in GPU name for Raspberry pi (#1303, GPU, Linux) * Fix a possible buffer overflow (GPU, Linux) * Fix CPU temp incorrectly reported as 0 celsius (#1308, CPU, Linux) * Correctly report `TPM device is not found` error (#1314, TPM, Windows) * Fix errors when triggering shell completion with python3 uninstalled (#1310) * To package managers: as shell completion scripts of fastfetch use python3, it should be added as an optional dependency of fastfetch * Fix possible crashes when detecting term font of kitty (#1321, TerminalFont, Linux) Logos: * Add XeroArch * Add ValhallaOS # 2.26.1 Features: * Allow to disable pacstall packager detection in CMake Bugfixes: * Fix uninitialized variables (GPU, Windows) # 2.26.0 Changes: * To be consistent to other platforms, CPU frequency detection on Linux no longer checks `bios_limit` Features: * Detect GPU index (#1267, GPU) * Count Flatpak runtime packages (#1085, Packages, Linux) * Support pacstall package manager (Packages, Linux) * Support CU core count, max frequency, VMEM usage detection for AMD cards on Linux (GPU, Linux) * Requires `--gpu-driver-specific` * Support EU core count, VMEM size detection Intel cards on Linux (GPU, Linux) * Requires `--gpu-driver-specific`. VMEM usage detection requires root permissions. * Add new module `TPM` to print TPM (Trusted Platform Module) version if available (TPM) * Support GPU driver version detection (GPU, macOS) * Add new CMake option `-DENABLE_EMBEDDED_PCIIDS=ON`. * If enabled, fastfetch will download the newest [`pci.ids`](https://pci-ids.ucw.cz/) file, [transform it into C code](https://github.com/fastfetch-cli/fastfetch/blob/dev/scripts/gen-pciids.py) and compile it into fastfetch binaries. Bugfixes: * Fix font size detecton of foot terminal (#1276, TerminalFont, Linux) * Ignore `su` and `sudo` when detecting terminal (#1283, Terminal, Linux) * Always print inches in integer (Display) * Fix Wifi connection protocol detection on macOS Sequoia (Wifi, macOS) * Fix hanging when font name is long when detecting kitty term font (#1289, TerminalFont) * Detect all enabled or connected connectors (#1301, Display, Linux) Logos: * Add FoxOS * Add GXDE OS # 2.25.0 Features: * Moore Threads GPU add support to query number of cores (#1259, GPU) * Cache detection result based on last modification time (Packages) * Add cmake options to disable certain package managers at compile time * Package managers are encouraged to disable some package managers by passing `-DPACKAGES_DISABLE_` when running `cmake`. For example, when building for Arch Linux, `-DPACKAGES_DISABLE_APK=ON -DPACKAGES_DISABLE_DPKG=ON -DPACKAGES_DISABLE_RPM=ON ...` should be specified. * See all available options by [running `cmake -L | grep PACKAGES_DISABLE_`](https://github.com/fastfetch-cli/fastfetch/blob/dev/CMakeLists.txt#L91) * This option does NOT remove the detection code. It just disables the detection at runtime. One can still use `--packages-disabled ""` to enable all package managers. * Add new option `--show-localip-{speed,mtu}` (LocalIP) * Add new module `Btrfs`, which prints all mounted Btrfs volumes, like `Zpool` module (#1262, Linux) * Improve Wifi module support for macOS Sequoia (Wifi, macOS) * Currently it uses `system_profiler` which costs about 2 seconds on my MBP. I suggest disabling it for now until a better solution is found. Bugfixes: * Fix invalid CPU temperature detection on FreeBSD (#1260, CPU, FreeBSD) * Remove `showPeCoreCount` support on FreeBSD (#1260, CPU, FreeBSD) * Don't use Wifi speed as Ethernet speed (LocalIP, FreeBSD) * Fix compiling with old linux headers (Camera, Linux) * Fix detecting public ipv6 address (PublicIP, Windows) Logo: * Fix parrot logo detection * Rename TorizonCore to Torizon OS # 2.24.0 Changes: * Support of `--lib-XXX` is removed * If fastfetch fails to load some `.so` `.dylib` libraries, `LD_LIBRARY_PATH` should be used. Features: * Support sixel image protocol on Windows (Logo, Windows) * Requires imagemagick7 to be installed. MSYS2 is recommended. * Improve terminal query on Windows (Windows) * TerminalSize, TerminalTheme * Detect more ARM microarchitectures and SOC names (CPU, Linux) * Detect the number of online cores (CPU, FreeBSD) * Support board name detection for Asahi Linux (Board, Linux) * Add new option `--command-param` to customize the parameters when running shell * Support syntax of sub string in `---format`: `{variable~startIndex,endIndex}` * See `fastfetch -h format` for detail Bugfixes: * Fix tests building when system yyjson is used (#1244) * Fix dinit detection; support dinit version detection (#1245, InitSystem, Linux) * Fix signal quality, refresh rate and maybe others in custom format (#1241) * Fix boot time calculation (#1249, Uptime, Linux) * Fix custom format for boolean values * `{?false-value}This should not print{?}{?true-value}This should print{?}` will print `This should print` * Fix possible hanging when running fastfetch in screen 5.0 (TerminalTheme, macOS) Logos: * Add Lliurex # 2.23.0 Features: * Support unity version detection (DE, Linux) * Print model name in Battery keys if available (Battery) * Add module `Zpool` * Improve performance (Shell / Terminal, Linux) * Support syntax of padded strings in `---format`. `{variablepadlength}` are supported. * If pad length is greater than the length of the variable, the variable will be padded with spaces. * `fastfetch -l none -s command --command-text 'echo 12345' --command-format 'output({1<20})'` prints `Command: output(12345 )` * `fastfetch -l none -s command --command-text 'echo 12345' --command-format 'output({1>20})'` prints `Command: output( 12345)` * If pad length is less than the length of the variable, the variable will be truncated. Bugfixes: * Fix broken `--list-presets` * Update zsh completion script * Don't print `*` if `defaultRouteOnly` is set (NetIO) * Fix Camera module incorrectly disabled on FreeBSD (Camera, FreeBSD) * Fix hanging on screen 5.0 (Terminal) * Improve image logo support on Windows (Logo, Windows) Logos: * Update AmogOS * Add Magix * Make ubuntu logo colorable * Add Steam Deck Logo * add Huawei Cloud EulerOS # 2.22.0 Features: * Small performance improvements (Terminal, Editor) * Improve arm32 and loongarch support (CPU, Linux) * Ignore the parent process if env `$FFTS_IGNORE_PARENT` is set to `1` (Shell) * Add code name of Apple M4 (CPU, Linux) * Add ethernet speed rate detection support (LocalIP) * Add zsh completion script * Add Linglong package manager detection support (Packages, Linux) Bugfixes: * Fix building on macOS 10.14 * Fix tmux in linux TTY (Colors) * Fix hang in WSL when custom format is used (Disk, Linux) * Fix `/proc/loadavg` parsing (Loadavg, Linux) * Disable use of `LC_NUMERIC` locale settings to fix parsing of decimal numbers * Fix possible segfault (DiskIO, Linux) * Honor `preciseRefreshRate` in custom format (Display) Logos: * Add Lingmo OS * Add Sleeper OS # 2.21.3 Bugfixes: * Fix bad Intel Arc GPU name detection, which was supposed to be fixed in the last version but the change was reverted accidentally (#1177, GPU, Linux) * Fix arm32 CPU name detection no longer work. Regression of 2.21.2 (CPU, Linux) # 2.21.2 Features: * Support `--stat ` to display long running modules in yellow or red Bugfixes: * Fix bad Intel Arc GPU name and type detection (GPU, Linux) * Fix uninited struct fields (GPU, Linux) * Skip cpu model smbios detection on ARM platforms (CPU, Linux) * Always use `CurrentControlSet` instead of `ControlSet001` when querying registry (Windows) * Fix NVIDIA GPUs are missing in GPU detection sometimes (GPU, Windows) * Fixing detection of `pthread_timedjoin_np` (Linux) Logos: * Add HyprOS * Add GoldenDog Linux # 2.21.1 Hotfix for a regression that breaks WM detection when running `startx` from TTY (Regression from 2.21.0, #1172 / #1162) Changes: * On Linux, FreeBSD and SunOS, a new recommended dependency `libelf` is introduced to extract strings in ELF binary, used for * st term font detection when the term font is compiled directly into the binary * fast path of systemd version detection Features: * Improve performance of * kitty version detection (Terminal, Linux) * st term font detection (TerminalFont, Linux) * systemd version detection (InitSystem, Linux) Bugfixes: * Fix building error without `linux/wireless.h` (Wifi, Linux) * Fix wrong GPU max frequency on Asahi Linux (GPU, Linux) * Don't rely `$XDG_SESSION_TYPE` for detecting wm protocol (#1172 / #1162, WM, Linux) * Fix light color doesn't work on Linux console (Colors, Linux) * `LC_ALL`, if set, overrides every other locale-related environment variable (Locale) * Increase timeout of DBus calls (Linux) Logos: * Add vanilla_small and vanilla2 * Add LFS (Linux From Scratch) # 2.21.0 Changes: * We no longer use `libnm` for Wifi detection on Linux. Instead, we use `libdbus` to communicate with NetworkManager directly * To package managers: libnm dependency should be removed Features: * Add module `BluetoothRadio` that prints bluetooth radios installed on the system * Don't confuse with module `Bluetooth` which lists connected bluetooth devices * Detect more information when `--gpu-driver-specific` is used (GPU) * Detect which type of nvidia driver (open source or proprietary) is used (GPU, Linux) * `--gpu-driver-specific` adds supports for Moore Threads GPU (#1142, GPU, Linux / Windows) * Use SetupAPI for detecting GPUs to support GPU detection when running fastfetch as a Windows Service (GPU, Windows) * See https://github.com/gpustack/gpustack/pull/97#issuecomment-2264699787 for detail * Detect playback status (Media, Linux) Bugfixes: * Don't try to connect display server in tty mode (Linux, #1110) * Improve ssh detection * Fix max frequency printing in custom format (CPU) * Fix displaying random characters when detecting kitty term font (#1136 / #1145, TerminalFont, Linux) * Make sure to detect all physical memory devices (#1137) * Don't detect `wl-restart` as WM (#1135, WM, Linux) * Use PCI bus ID to match Nvidia cards; fix multi-GPU detection (GPU) * Ignore invalid GPU (#1066, GPU, macOS) * Print error when invalid color code is found (#1138) * Fix invalid refresh rate detection on old macOS versions (Display, macOS) * Fix disk size detection on 32-bit systems (Disk, BSD) * Don't ignore disabled GPUs (#1140, GPU, Linux) * Fix GPU type detection on FreeBSD (GPU, FreeBSD) * Remove shell version detection for unknown shells (#1144, Shell) * Don't detect hyfetch as shell on NixOS (Shell, NixOS) Logos: * Update EndeavourOS_small * Add QTS # 2.20.0 This release fixes regression of `2.19.0` on M1 MacBook Air. It also introduces a new option `--key-type icon` to display predefined icons in keys (requires newest nerd font). See `fastfetch -h key-type` for detail. Changes: * JSON option `display.keyWidth` has been renamed to `display.key.width` * Previously: `{ "display": { "keyWidth": 3 } }` * Now: `{ "display": { "key": { "width": 3 } } }` * Windows Terminal font detection **in WSL** has been removed due to [issue #1113](https://github.com/fastfetch-cli/fastfetch/issues/1113) Features: * Add option `display.key.type: ` to print icons in keys * Supported value `string`, `icon` and `both`. Default to `string` (don't display icons) * Example: `{ "display": { "key": { "type": "icon" } } }` * Add option `display.key.paddingLeft: ` to print left padding (whitespaces) in keys * Example: `{ "display": { "key": { "paddingLeft": 2 } } }` * Add option `modules.keyIcon` to set icon for specified module * Example: `{ "modules": { "type": "command", "keyIcon": "🔑" } }` * Report system mono font name for Terminator if used (TerminalFont, Linux) * Don't require logo height to be set when using `--logo-position right` * Report Snapdragon SOC marketing name for newer Android phones (CPU, Android) * Detect MTK SOC part name (CPU, Android) Bugfixes: * Don't wake up suspended GPUs when using `--ds-force-drm` (Display, Linux) * Fix printing editor type in JSON result (Editor) * Fix `--logo-padding-*` not working correctly (#1121, Logo) * Fix possible segfault when detecting GPU frequency (#1121, macOS, GPU) # 2.19.1 Bugfixes * Fix frequency value printing when using custom format (#1111, CPU / GPU) * Fix display detection for XiaoMi Android phone (Display, Android) Features: * Display if HDR mode is enabled for screens (Display) * Supported in Windows and Linux (KDE) correctly # 2.19.0 Changes: * JSON option `modules.cpu.freqNdigits` has been renamed and moved to `display.freq.ndigits` * Previously: `{ "modules": { "type": "cpu", "freqNdigits": 2 } }` * Now: `{ "display": { "freq": { "ndigits": 2 } } }` * This option now affects GPU frequency too * By default, frequencies are displayed in *GHz*. Set `display.freq.ndigits` to `-1` to display them in *MHz* * JSON option `display.binaryPrefix` has been moved to `display.size.binaryPrefix` * Previously: `{ "display": { "binaryPrefix": "IEC" } }` * Now: `{ "display": { "size": { "binaryPrefix": "IEC" } } }` Features: * Print physical diagonal length if supported (Display) * Detect display type in X11 mode (Display) * Assume displays connected via DisplayPort are external monitors (Display, Linux) * Support GPU frequency detection for Intel XE driver (GPU, Linux) * Detect init system on Android (InitSystem, Android) * Use background to display color blocks (Colors) * To fix weird vertical black lines in some terminals and match the behavior of neofetch (#1094) * Can be reverted to old behavior with `--colors-symbol block` * Support Zed terminal version detection (Terminal) * Improve wezterm font detection (TerminalFont) * Add option `--separator-length` * Support GPU frequency detection for Apple Silicon (GPU, macOS) * Detect maximum refresh rate (#1101, Monitor) * Detect if HDR mode is supported and enabled (Windows, Display / Monitor) * Support physical monitor info detection for FreeBSD and SunOS (Monitor) * Support defining constant strings in JSON config file, which can be used to dedupe formattion strings ```jsonc { "display": { "constants": [ "Hello", // {$1} "world" // {$2} ] }, "modules": [ { "type": "custom", "format": "{$1} {$2}!" // print "Hello world!" }, { "type": "custom", "format": "{$2} {$1}" // print "world Hello" } ] } ``` Bugfixes: * Fix some presets * Better detection for XTerm terminal fonts (#1095, TerminalFont, Linux) * Remove debug output (#1097, Windows) * Fix flag `--gpu-hide-type` doesn't work (#1098, GPU) * Fix wrong date on Raspbian 10 (#1108, DateTime, Linux) * Use `brightness` instead of `actuall_brightness` when detecting current brightness level (Brightness, Linux) * Ref: https://bugzilla.kernel.org/show_bug.cgi?id=203905 * Fix buffer overflow with long font family names when detecting kitty term font (TerminalFont) * Fix some typos Logos: * Update void_small * Add ALT Linux # 2.18.1 Fix a regression introduced in v2.18.0 Changes: * `--ts-version` has been renamed to `--detect-version` * `general.detectVersion` in JSON config file Bugfixes: * Fix and improve GPU driver detection (#1084, GPU, Linux) # 2.18.0 Changes: * `yyjson 0.10.0` is required * Fastfetch no longer prints `*` (which means it's the default route) if `defaultRouteOnly` is set (LocalIP) Bugfixes: * Fix some memory leaks * Fix compatibility with old Python versions * Don't detect frequency for AMD cards (GPU, Linux) * Fix possible hang with discrete AMD cards (#1077) * Don't print colors in `--pipe` mode (Separator) * Don't print `(null)` in property `locator` (PhysicalMemory) * Ignore disabled PCI devices (GPU) * Fix flag `--opengl-library` doesn't work (OpenGL) Features: * Detect revision of USB drives (#1048, Disk) * Support fractional scale factor detection (Display, Linux) * Support primary display detection for KDE and GNOME (Display, Linux) * Support percent bar in custom formatting * Print signal quality by default (Wifi) * Detect used OpenGL library version (OpenGL) * Support detecting OpenGL version by `EGL` (ANGLE) on Windows (OpenGL) Logos: * Add Arkane Linux * Add Opak # 2.17.2 Changes: * Flatpak package count no longer takes runtime packages into account (Packages, Linux) Bugfixes: * Fix formattion with multiple batteries (Battery) * Fix incorrect size value for large memory sticks (PhysicalMemory) * Fix spelling of `Qt` and `LXQt` * Fix building on SunOS if imagemagick support is enabled (Logo, SunOS) * Fix typos Features: * Support Ptyxis terminal version and font detection (Terminal / TerminalFont, Linux) * Improve Cinnamon version detection (DE) * Support `cinnamon-wayland` (WMTheme) * `--ts-version false` will disable editor version detection (Editor) # 2.17.1 Hotfix for a regression that breaks Qt font detection Bugfixes: * Don't generate and install `libffwinrt.dll.a` on MinGW (Windows) * Fix building on Windows when imagemagick support is enabled (Logo, Windows) * Don't print GPU frequency with `--gpu-temp` for Nvidia cards (#1052, GPU) * `--gpu-driver-specific` needs to be specified * Print formatted size when `--gpu-format` is used (#1052, GPU) * Ignore QVariant format; fix unreadable Qt font (#1053, Theme, Linux) * Fix segfaults with `--show-errors` and an invalid module (#1055) # 2.17.0 Changes: * CMake option `ENABLE_PROPRIETARY_GPU_DRIVER_API` is removed. The GPU driver APIs are now enabled by default. * The option was introduced to reduce the license concerns. Since all non MIT proprietary code has been rewritten manually from scratch, it is no longer necessary. * See for detail * Option `--logo-separate true` is changed to `--logo-position top` for better readability * Builtin ascii logos can be positioned on the right side now with`--logo-position right` Features: * Add support for `--gpu-detection-method opencl` which uses OpenCL to detect GPUs. * Support detecting CPU cache size by using SMBIOS as fallback (CPUCache) * Support GPU detection (SunOS) * Support GPU type detection with AMD GPU driver (GPU, Windows) * Add fast path of version and font detection for kitty (Terminal / TerminalFont) * Make sure `stdin` and `stdout` are TTYs when querying terminal * So modules like `TerminalSize` should work when `stdin` or `stdout` is redirected * Support argument truncation in `---format` (#1043) * See `fastfetch --help format` for detail * Improve Qt theme detection (#1047, Theme, Linux) * Add new JSON config option `general.preRun`, which is executed before fastfetch prints output. * It can be used to generate a temp logo file. For example ```jsonc { "general": { "preRun": "kitten icat --align=left /path/to/image > /tmp/logo.kitty" }, "logo": { "source": "/tmp/logo.kitty", "type": "raw" } } ``` Bugfixes: * Fix invalid path (#1031, LM, Linux) * Fix VMEM detection for Nvidia GPU (requires `--gpu-driver-specific`) (GPU) * Fix AMD `--gpu-driver-specific` for AMD cards (#1032, GPU, Windows) * Use Coordinated Universal Time rather than timezone-varying local date (#1046) Logo: * Fix colors of Asahi Linux # 2.16.0 This release added basic support for SunOS (Solaris, illumos). The binaries provided in the release lack a few useful features (such as Display detection). People who use SunOS should consider building fastfetch themselves. Changes: * Fastfetch now prefers `/etc/os-release` over `/etc/lsb-release` when detecting distro info. * This may break some distros (notably some debian based distros). File a bug with the content of `os-release` and `lsb-release` if it breaks your distro. Features: * Support Media detection in Windows (Media / Player, Windows) * Requires Windows 10 and later * Add new option `--users-myself-only` to display current login user only (Users) * Add code name of macOS Sequoia (OS, macOS) * Add new module `DNS` to show active DNS servers (DNS) * Add new option `--loadavg-compact`. Defaults to true (Loadavg) * Use `--loadavg-compact false` to display load averages in different lines * Detect MTU size (LocalIP) * Support version detection of pluma, which is the default editor of OpenIndiana (Editor) * Print used OGL library, eg EGL, GLX or OSMesa (OpenGL) Bugfixes: * Report error if cache size is unavailable (CPUCache, Android) * Trim white spaces in device name (Sound, Linux, #1005) * Fix `display.bar.border{Left,Right}` doesn't work in JSON config files (Config) * Fix invalid call to `realpath(3)` (Platform, Linux) * Fix result calculation (CPUUsage, FreeBSD) Logos: * Add Mauna * Add Tuxdeo * Add Manjaro ARM * Add RedOS * Add Arch3 # 2.15.0 Changes: * `--bar-border ` has been changed to `--bar-border-left ` and `--bar-border-right `, which are used for customizing the style of bar border. * `--bar-border-left '' --bar-border-right ''` can be used to disable the border Features: * Add ability to skip installing license with INSTALL_LICENSE option (CMake) * Make it possible to shorten the theme and icons output (Theme / Icons) * Support `-l '?'` to show a question mark * Add new module `CPUCache` to display CPU cache sizes (CPUCache) * In `---format`, `{#keys}` and `{#title}` can be used to reference the color of keys and title * Improve speed of Guix package detection (Packages, Linux) * Assume wm plugins are daemon processes to improve performance (WM, macOS) Bugfixes: * Remove shebangs from completions (#980) * Fix while chars not visible in terminal of light theme (Logo) * Normalize bright colors to fix color display in Apple Terminal (#991, Colors) * Correctly capitalize GNOME (#997, DE, Linux) * Fix segfault on system using turkish language (#995, InitSystem, Linux) * Fix kubuntu detection (#1000, OS, Linux) * Don't display duplicate entries (OS, Linux) # 2.14.0 Features: * Support monochrome bar type (#960) * Support editor version detection on Windows (Editor, Windows) * Apply default color palettes in `--file` and `--data` (Logo) * Print all presets in `--list-presets` for better Windows support (Windows) * Support for guix package manager detection (Packages, Linux) * Support named variable placeholders in custom module formattion string (#796) * `--title-format '{user-name-colored}{at-symbol-colored}{host-name-colored}'` is now equivalent to `--title-format '{6}{7}{8}'` * Support named color placeholders in custom module formattion string * `---format '{#red}'` is now equivalent to `---format '{#31}'` * `'{#red}'` or `'{#31}'` is preferred over `\u001b[31m` because is more readable and `--pipe` aware (will be ignored in pipe mode) * Supported in `Custom` module too * See `fastfetch -h format` for detail * Add new module `InitSystem`, which detects the name of init system * i.e. process name of pid1. `init`, `systemd`, etc * Add option `--color-separator` to set the color of key-value separators * Support Guix package manager count (#792, Packages, Linux) * Improve python based shell detection (#977, Shell, macOS) * Print error reason when vulkan init fails (Vulkan) Bugfixes: * Don't detect `.conf` files in `--list-config-paths` * Don't try to detect terminals in MSYS shell with process backtracing (Windows) * Fix `outputColor` doesn't work if module keys are disabled Logos: * Add Cereus Linux * Re-add special handling of Loc-OS # 2.13.2 Another hotfix release :( Bugfixes: * Remove DRM driver version detection feature, which caused a performance regression for nouveau drivers (#956, Display, Linux) * Fix compatibility for old python versions. Regression of `2.13.0` * Don't use `*-unknown` as display name for Wayland protocol (Display, Linux) Features: * Add new module `Editor` which prints information of the default editor, i.e. $VISUAL or $EDITOR (#430, Editor) Logos: * Added CuerdOS * Remove special handling of Loc-OS # 2.13.1 Fix a regression introduced in v2.13.0 Bugfixes: * Fix CPU frequency not displayed if `bios_limit` is not available (CPU, Linux) Features: * Add `--cpu-show-pe-core-count` to detect and display core count for performance / efficiency cores (CPU, FreeBSD) # 2.13.0 Changes: * Option `--gpu-force-vulkan ` has been changed to `--gpu-detection-method ` * Use `--gpu-detection-method vulkan` to get the old behavior * See `fastfetch -h gpu-detection-method` for detail * In Linux, BIOS limited CPU frequency is printed by default to match the behavior of neofetch (CPU, Linux, #947) Features: * Add new module `Bootmgr` which prints information of stage 2 bootloader (grub, system-boot, etc) * Requires root permission to work on Windows and FreeBSD * Requires booting in UEFI mode * Add package manager lpkg and lpkg-build support (Packages, Linux) * Improve macOS 10.13 compatibility (macOS) * Detect core count for performance / efficiency cores (CPU) * Test it with `fastfetch -s cpu --cpu-format '{9}'` * Support min / max frequency and physical core count detection in FreeBSD, if kernel supports it (CPU, FreeBSD) * Detect DRM driver version if DRM detection method is used (GPU, Linux) Bugfixes: * Don't detect `clifm` and `valgrind` as a terminal (Terminal, Linux) * Improve stability (PhysicalMemory, FreeBSD) * Fix bssid & status detection (Wifi, FreeBSD) * Ensure createTime is correctly initialized (Disk, FreeBSD / macOS) * Fix `--cpu-freq-ndigits` not working if `--cpu-format` is used * Fix `nix-user` package count detection (Packages, Linux) * Fix some memory leaks Logos: * Fix Manjaro logo not displayed * Add SpoinkOS * Add Loc-OS * Add Furreto Linux * Fix TorizonCore logo colors * Fix KDE neon logo not displayed # 2.12.0 Changes: * The long deprecated flag based config files are removed. * They can still be used with `xargs fastfetch < /path/to/config.conf` * `--gen-config` can be used to migrate them to json based config files * The long deprecated options `--set` and `--set-keyless` are removed. * `Kernel` module now prints kernel name by default Features: * Support `st` terminal font detection for font configuration compiled in `st` binary (TerminalFont, Linux) * Add option `--color-output` to change output color of all modules except `Title`, `Separator` * `display.color.output` in JSONC config file * Add option `---output-color` to change output color of one specified module, which overrides the global option `--color-output` * Add option `--publicip-ipv6` to print IPv6 address (PublicIP) * Add new module `Loadavg` to print load averages (Loadavg) * Add new module `PhysicalMemory` to print information of physical memory devices (PhysicalMemory) * Requires root permission to work on Linux and FreeBSD * Support specifying `--logo-width` only for `--kitty-direct` and `--iterm` (Logo) * Add option `--localip-show-all-ips` to show all IPs assigned to the same interface (LocalIP) * Default to `false` * Improve compatibility with `(*term)` (#909, Terminal, macOS) * Support GPU core count and frequency detection for Asahi Linux (GPU, Linux) Bugfixes: * Rename option `--temperature-unit` to `--temp-unit` as documented in help messages * Fix alternate logo doesn't work with `{ "type": "builtin" }` (#914, Logo) Logos: * Fix DahliaOS detection * Add openSUSE Slowroll * Add macOS3 * Add Quirinux # 2.11.5 Bugfix: * Fix logo printing for OpenMandriva (#896) * Remove `--os-file` in help messages # 2.11.4 Changes: * Fastfetch will print a colorless ascii logo in `--pipe` mode for better `lolcat` compatibility. `fastfetch | lolcat` should work and no `--pipe false` needed. * Previously the logo would be disabled in `--pipe` mode. * Use `--pipe -l none` to get the old beheavior * `--os-file` was removed and CMake option `-DCUSTOM_OS_RELEASE_PATH=/path/to/os-release` was introduced for configuring at compile time by package managers if needed. This option should not used in most cases. Bugfixes: * Fix possible out-of-bound memory access (#868) * Fix Apple Terminal detection (#878, macOS, Terminal) * Fix deprecation warning for macOS 14.0 hopefully (#860, macOS, Camera) * Fix memory leaks when passing informative options (#888) * Fix JSON config `size.ndigits` doesn't work Features: * Enable `--pipe` mode if environment variable `$NO_COLOR` is set * Support Armbian and Proxmox distro detection (OS, Linux) Logo: * Add Armbian # 2.11.3 Hotfix for nix (https://github.com/NixOS/nixpkgs/issues/308849#issuecomment-2093962376) Features: * Add cmake option `CUSTOM_AMDGPU_IDS_PATH` for specifying custom path of `amdgpu.ids` Bugfixes: * Fix hanging when detecting disconnected network drive (Disk, Windows) * Ensure line ending is printed when printing image logo errors (Logo) * Revert image logo limitation change in 2.11.2; allow image logo in SSH session and tmux again (#861, Logo) * Fix doubled output in custom formation (PhysicalDisk, Windows) # 2.11.2 Hotfix for Debian 11 Changes: * Error messages when trying to print image logo will only be printed with `--show-errors` * When generating JSON output, fastfetch will generate an empty array when no result is detected, instead of an error. Bugfixes: * Fix segfault in Debian 11 and some old kernels. Regression introduced in 2.11.0 (#845, GPU, Linux) * Don't try detecting version of raw `sh` shell (#849, Shell, Linux) * Trim `\r` on Windows Features: * Check xdg state home for nix user packages (#837, Packages, Linux) * Disable image logos in ssh and tmux sessions (#839) * Support MX Linux distro detection (#847, OS, Linux) Logo: * Add KernelOS * Fix name of DraugerOS * Add missing `FF_LOGO_LINE_TYPE_SMALL_BIT` flags * Add MX2 # 2.11.1 Hotfix for Android Bugfixes: * Fix uninitialized variables which can cause crashes (#760 #838, Battery, Android) * Don't detect hyfetch as shell when used as backend of [hyfetch](https://github.com/hykilpikonna/hyfetch) * Fix incorrect information in man page (#828) Features: * Support sorcery package manager detection (Packages, Linux) * Make `--custom-format` optional (Custom) * Make `/` an alias of `C:\` for `--disk-folders` (Disk, Windows) * Build for Linux armv7 Logo: * Fix colors of Source Mage logo # 2.11.0 Changes: * Default `hideCursor` to false. It doesn't make much difference but makes user's terminal unusable if fastfetch is not exited correctly. * Linux amd64 binaries are built with Ubuntu 20.04 again (#808) Bugfixes: * Fix swap usage detection in x86-32 build (Windows, Swap) * Fix minimum cmake version support (#810) * Fix wifi detection on platforms that don't use NetworkManager (#811, Wifi, Linux) * Fix NixOS wrapped process name (#814, Terminal, Linux) * Fix GPU type detection for AMD cards (#816, GPU, Linux) * Silence system deprecation warnings (#822, Camera, macOS) Features: * Add basic support DE detection support for UKUI (DE, Linux) * Support printing total number of nix / flatpak / brew packages (Packages) * See `fastfetch -h packages-format` for detail * Better max CPU frequency detection support with `CPUID / 16H` instruction (CPU, Windows) * This requires Intel Core I Gen 6 or newer, and with `Virtual Machine Platform` Windows feature disabled. X86 only. * Improve performance of nix packages detection (Packages, Linux) * Make config specified in JSONC overridable by command line flags * Note this change only make global config overridable; module configs are still not * Suggest increasing `--processing-timeout` when child process timeouts * Only detect folders that specified by `--disk-folders` * Previously `--disk-folders` only omits unmatched disks from output * This option can be used to improve detection performance by ignoring slow network drives # 2.10.2 Bugfixes: * Fix a regression that detect x11 as wayland (#805, WM, Linux) # 2.10.1 Bugfixes: * Fix build with `-DENABLE_DBUS=OFF` (Linux) # 2.10.0 Changes: * We now always detect max frequency of GPUs for consistent, instead of current frequency Features: * Improve display detection for wlroots based WMs. Fastfetch now correctly reports fractional scale factors in hyprland (Display, Linux) * Improve GPU detection on Linux (GPU, Linux) * Support GPU memory usage detection for AMD GPUs * Support GPU frequency detection for Intel GPUs * Improve performance of GNOME version detection (DE, Linux) * Improve performance of kitty version detection (Terminal, Linux) * Detect refresh rate when using `--ds-force-drm sysfs-only` (Display, Linux) * Add option `--ts-version` to disable terminal and shell version detection. Mainly for benchmarking purposes * Improve performance of detecting WSL version (Host, Linux) Bugfixes: * Correctly detect `/bin/sh` as current shell if it's used as default shell (#798, Shell, Linux) * Work around an issue which CPU module reports incorrect CPU frequency that is too high (#800, CPU, Linux) * Don't print ANSI escape codes in `--pipe` mode # 2.9.2 Changes: * To make use of the newly introduced `yyjson` flag `YYJSON_WRITE_NEWLINE_AT_END`, fastfetch now requires `yyjson` 0.9.0 or later Features: * Always add a final new-line when generating JSON output * Detect partition create time, which can be used as OS installation time (Disk) * Print time string when generating JSON result instead of UNIX epoch time number, which is more human-readable Bugfixes: * Fix a memory leak * Better portable mode detection of Windows Terminal (TerminalFont, Windows) * Fix parsing of option `--packages-disabled` (Packages) * Don't use command `time` as a shell (Shell) Logos: * Add openSUSE MicroOS * Fix color of AOSC OS # 2.9.1 Features: * Support weston-terminal (missed commit in v2.9.0) (TerminalFont, Linux) * Support hyprcursor detection (#776, Cursor, Linux) Bugfixes: * Fix `fastfetch --gen-config` raises SIGSEGV when `~/.config/fastfetch` doesn't exist. Regression of `2.9.0` (#778) # 2.9.0 Features: * Support Lxterminal version detection (Terminal, Linux) * Support weston-terminal version detection (Terminal, Linux) * Support `am` package manager detection (#771, Packages, Linux) * Support network prefix length detection for IPv6 (LocalIP) * Display all IPs when multiple IPs are assigned to the same interface (LocalIP) * Add option `--localip-show-prefix-len` to show network prefix length for both IPv4 and IPv6. Defaults to `true` (LocalIP) Bugfixes: * Fix network prefix length detection when the value is greater than 24 (#773, LocalIP, Linux) * For xfce4-terminal, use system mono font if no config file is found (TerminalFont, Linux) # 2.8.10 Bugfixes: * Don't display 0.00 GHz (CPU, FreeBSD) * Don't detect manufactor of Qualcomm as ARM (CPU, Android) * Ignore `chezmoi` (Terminal, Linux) * Trim trailing possible whitespaces (PublicIP) * Fix detection compatibility for KDE 6 (Font, Linux) * Always use Metal API to detect vmem size (GPU, macOS) Features: * Improve stability; print more useful error message; avoid misuse (PublicIP / Weather) * Use MS-DOS device name as mountFrom result, instead of useless GUID volume name (Windows, Disk) * Some adjustments to Terminal detection (Terminal, Windows) * Don't pretty print CMD * Print conhost as Windows Console * Don't detect `wininit` as Terminal Logo: * Fix color of Arco Linux # 2.8.9 Bugfixes: * Don't detect `SessionLeader` as terminal, actually (Terminal, Linux) * Fix blurry chafa result when specifying both width and height (#757, Logo) Features: * Support new MacBook Air (Host, macOS) * Distinguish min frequency and base frequency (CPU) Logo: * Fix proxmox # 2.8.8 Bugfixes: * Fix old fish version compatibility (#744) * Fix truncated texts in `--help format` (#745) * Fix old vulkan-header and libdrm library compatibility (#748, Linux) * Fix possible segfaults in `--help *-format` (#749) * Fix invalid resolution detection when using libdrm (Linux, Display) * Fix segfault when `/sys/devices/system/cpu/cpufreq/` doesn't exist (#750, CPU, Linux) * Don't detect `SessionLeader` as terminal (Terminal, Linux) * Fix detection of client IP (Users, Linux) # 2.8.7 Bugfixes: * Fix max CPU frequency detection for some cases (CPU, Linux) * Fix some memory leaks * Fix ddcutil 2.1 compatibility (Brightness, Linux) * Workaround `permission denied` error when reading `/proc/uptime` (Uptime, Android) Features: * Support zellij version detection (Linux, Terminal) Logo: * Fix PostMarketOS # 2.8.6 Changes: * Due to newly introduced configs, JSONC option `{ "temperatureUnit": "C" }` has been changed to `{ "temp": { "unit": "C" } }` Bugfixes: * Fix incorrect GPU name detection for Intel iGPU on Linux (#736, GPU, Linux) Features: * Support additional temperature formatting options (#737) * `{ "temp": { "ndigits": 1 } }` * `{ "temp": { "color": { "green": "green", "yellow": "yellow", "red": "red" } } }` * Support specifying custom `pci.ids` path for Linux (GPU, Linux) * Support warp-linux terminal version & terminal font detection (Terminal, Linux) # 2.8.5 Bugfixes: * Fix uninitialized variables # 2.8.4 Bugfixes: * Fix segfault if we fail to find `Vendor ID` in `lscpu` (#718, CPU, Linux) * Fix multi-device bcachefs filesystem compatibility (#731, Disk, Linux) Features: * Support portable Windows Terminal settings (#720, Terminal, Windows) * Support `--color-block-width` and `--color-block-range` (#721, Colors) * Support `--diskio-detect-total` to show total bytes read/written (DiskIO) * Support `--netio-detect-total` to show total bytes received/sent (NetIO) * Support `--packages-disabled` to disable specified package manager (#729, Packages) * Support `--display-order` to sort multiple displays in a specific order (Display) * Support `--display-compact-type original-with-refresh-rate` to show refresh rates in compact (oneline) mode (Display) # 2.8.3 Bugfixes: * Fix GPU name detection for AMD graphic cards (GPU, Linux / FreeBSD) # 2.8.2 Changes: * The linux binaries are now built with glibc 2.35, which means they no longer support Debian 11 and Ubuntu 20.04. Users using these distros may download the artifacts `fastfetch-linux-old` from GitHub Actions. Features: * Rewrite GPU module, drop libpci dependency (GPU, Linux) * Detect marketing name of Apple Silicon CPUs for asahi linux (CPU, Linux) * Add new module `Camera`, which prints the name and resolution of connected cameras Bugfixes: * Fix compatibility with packages installed by flatpak (Terminal, Linux) * Don't show an empty battery if no battery is detected (macOS, Battery) * Don't show `not connected` if no power adapter is found (macOS / Linux, PowerAdapter) * Make format of battery status be consistent with other platforms (Linux, Battery) Logo: * Print Asahi logo in asahi linux (Logo, Linux) * Add Asahi2, z/OS, Tatra, PikaOS # 2.7.1 Features: * Config presets in app folder now work with symlinks Bugfixes: * Fix a possible segfault when detecting terminal (Terminal, Linux) # 2.7.0 Features: * Add new module `TerminalTheme`, which prints the foreground and background color of the current terminal window. Currently doesn't work on Windows. * Allow command substitution when expanding paths. For example, now it's possible to use `"source": "$(ls ~/path/to/images/*.png | shuf -n 1)"` in JSONC config file to randomly choose an image to display. (#698) * Use native methods instead of pciutils to detect GPUs in FreeBSD. (GPU, FreeBSD) Bugfixes: * Fix text formatting (Wifi, Linux) * Fix terminal detection in some cases (Terminal) * Remove trailing `\0` in JSON results (FreeBSD) * Fix uninitialized variables (GPU, Linux) * Fix a possible segfault (OpenCL) Logo: * Add ASCII logos for fedora immutable variants (#700) # 2.6.3 Bugfixes: * Fix module not working (Bluetooth) # 2.6.2 Bugfixes: * Fix building for GCC in Windows (Windows) # 2.6.1 Features: * Improve xonsh shell detection (Shell) * Support colored percentage values (Bluetooth / Gamepad / Sound) * Add `---percent-[green|yellow]` options to specify threshold of percentage colors * eg. `--disk-percent-green 20 --disk-percent-yellow 50` will show green if disk usage is less than 20%, yellow if disk usage is less then 50%, and red otherwise. * Add `--percent-color-[green|yellow|red]` options to specify color of different percent value states. * eg. `--percent-color-green blue` will show blue color if percent value falls in green state. * Improve Intel macbook support (macOS) Bugfixes: * Fix segfault in CPU module when running in aarch64 machine without `lscpu` installed (CPU, Linux) * Don't use `login` as terminal process (Terminal, Linux) * Silence warnings when building in 32bit machines. * Create sub folders when writing config file (#690) * Improve user specific locale detection; fix locale detection in Windows 7 (Locale) * Fix GPU type detection (GPU, macOS) # 2.6.0 Changes: * Remove support of option `--battery-dir`. We detect a lot of things in `/sys/class/*` and only module `Battery` supports specifying a custom directory for some reason, which is weird. * Remove `--chassis-use-wmi` which is no longer used. Features: * Add `ENABLE_PROPRIETARY_GPU_DRIVER_API` cmake option to disable using of proprietary GPU driver APIs (GPU) * Support wallpaper detection for macOS Sonoma (Wallpaper, macOS) * Support power adapter detection for Asahi Linux (PowerAdapter, Linux) * Support battery serial number and manufacturer date detection (Battery) * Support host serial number and UUID detection (Host) * Support battery level detection for gamepads where possible (Gamepad) * Support maximum CPU clock detection. Previously base clock was printed (CPU, Windows) * Support manufacture date and serial number detection for physical monitors (Monitor) * Support ash (default shell of BusyBox) version detection (Shell, Linux) * Sound module in FreeBSD now uses native `ioctl`s. Pulseaudio dependency is no longer used. * Locale module in Windows now prints the same format as in Linux and other posix systems. Bugfixes: * Fix overall memory leaks (macOS) * Remove trailing `\0` in JSON results (FreeBSD) * Fix physical monitor detection with Nvidia drivers (Monitor, Linux) * Don't print llvmpipe in vulkan module (Vulkan) * Fix system yyjson usage in `fastfetch.c`. Previously embedded `3rdparty/yyjson/yyjson.h` was used in `fastfetch.c` even if `ENABLE_SYSTEM_YYJSON` was set (CMake) * Fix locale module printing unexpected results in specific environments (Locale) * Fix battery temperature detection in Windows. Note only smart batteries report temperatures but few laptops uses smart battery (Battery, Windows) * Print device name if no backlight name is available, so we don't print empty parentheses (Brightness, FreeBSD) # 2.5.0 Changes: * `--gpu-use-nvml` has been renamed to `--gpu-driver-specific` due to using of `IGCL` and `AGS` * We now detect external partitions more conservatively in Linux. USB partitions will not be detected as external always ( eg. The Linux kernel itself is installed in a USB drive ) Features: * Support more authentication type detection for macOS Sonoma (Wifi, macOS) * Default preset names to `.jsonc`. For example, `fastfetch -c all` will load `presets/all.jsonc` (#666) * Use Intel Graphics Control Library (IGCL) to detect more GPU information. Windows only (GPU, Windows) * Improve support of Asahi Linux (Brightness / CPU / GPU / Disk, Linux) * Support more properties of physical disks (PhysicalDisk) * Support SSD temperature detection with `--physicaldisk-temp` (PhysicalDisk) * Support partition label detection (Disk, FreeBSD) * Support platform specific graphic API version detection (GPU, macOS / Windows) Bugfixes: * Fix Windows partition detection for WSL2 (Linux, Disk) * Fix Btrfs subvolumes being detected as external partitions some times (Linux, Disk) * Fix battery cycle counts in some places (Battery) * Fix CodeWhisperer compatibility (#676, Terminal, macOS) # 2.4.0 **We are deprecating flags based config files (will be removed in v3.0.0). We suggest you migrate to json based config files.** One may use `-c /path/to/config.conf --gen-config` to migrate existing flag based config files. Changes: * All flag based presets are removed Features: * Improve performance of detecting rpm and pkg package count (Packages, Linux / FreeBSD) * Support Apple M3X temperature detection (CPU / GPU, macOS) * `--ds-force-drm` support a new option `sysfs-only` * Improve xfce4 version detection * Detect WM and DE by enumerating running processes (WM / DE, FreeBSD) * Add a new module `Physical Disk`, which detects product name, full size, serial number and so on. Bugfixes: * Fix crashes sometimes when `--logo-padding-top` is not set (Logo) * Fix memory usage counting algorithm (Memory, macOS) * Fix the behavior of `--no-buffer` in Windows * Fix possible segfault in some devices (Display, Linux) * Fix segfaults on first use of new images with Sixel flag (Image) Logo: * Remove unnecessary escaping for Adelie logo * Add EshanizedOS # 2.3.4 Bugfixes: * Fix `--help` doesn't work when built without python Features: * Use `MemAvailable` if available (Memory, Linux) * Improve performance of detecting dpkg package count (Packages, Linux) # 2.3.3 Bugfixes: * Fix `--help` doesn't work in Windows and some other platforms # 2.3.2 Bugfixes: * Fix fish completion script, and install the script correctly Logo: * Fix Xray-OS logo name # 2.3.1 Bugfixes: * Fix man page install location # 2.3.0 **We are deprecating flags based config files (will be removed in v3.0.0). We suggest you migrate to json based config files.** Config related changes: * The deprecated flag `--gen-config conf` is removed * Flag `--gen-config` now does the same thing as `--migrate-config`, which can be used as config migration and default config file generation. Flag `--migrate-config` is removed * Fastfetch now searches for config files in the order of `fastfetch --list-config-paths`, and won't load other config if one is found. * The undocumented flag `--load-user-config` is removed. As an alternative, `--config none` can be used to disable loading config files. * `--config` (previously named `--load-config`) is now supported for command line arguments only. If specified, other config files won't be loaded, which works like other programs. * Config files will always be loaded before other command line flags being parsed. That is to say, command line flags will always override options defined in config files. * the value of GPUType `integrated` contained a typo and was fixed. Existing config files may need to be updated. Features: * Support Oils and elvish shell version detection (Shell) * Support Windows Server Core (Windows) * Better ddcutil 2.x compatibility (Brightness, Linux) * Add completion support for fish (natively) and nushell (via [carapace-bin](https://github.com/rsteube/carapace-bin)) * Support nix in macOS (Packages, macOS) * Print module description for `--list-modules` * Support `alacritty.toml` (TerminalFont) * Support board detection on macOS. It simplily prints machine model identifier as for now (Board, macOS) * Add general method to query product name (Host, macOS) * Use `libdrm` as a better fall back for detecting displays, which correctly detects current mode; supports refresh rate detection and maybe also faster than using `/sys/class/drm` (Display, Linux) * Support physical disk size detection (DiskIO) * Support physical disk name and type detection (DiskIO, FreeBSD) Bugfixes: * End `va_list` before returning (@VoltrexKeyva) * Don't use background color when printing blocks (Color) * Fix lots of typos * Fix compatibility with Linux containers (Linux) * Don't report disabled monitors when using DRM (Linux) * Fix bad performance in some cases when using X11 (Display, Linux) * Fix some memory leaks * Fix used swap space detection (Swap, FreeBSD) * Don't leak fds to child processes (Linux) * Fix possible issues when reading procfs (Linux, @apocelipes) Logos: * Add Adelie, Ironclad * Update parch # 2.2.3 Features: * Update the latest mac models (Host, macOS) Bugfixes: * Fix local ips detection on Android. Regression from `2.0.0` (LocalIP, Android) * Fix terminal detection on NixOS (Terminal) # 2.2.2 Changes: * `--percent-type` now defaults to 9 (colored percentage numbers) * `fastfetch` now prints LocalIp module by default Features: * LocalIP module now prints netmask in CIDR format for IPv4 (LocalIP) * Bios module now detects system firmware type (Bios) * Improve detection of `Battery` * Detect cycle count on supported platforms * Detect temperature on Linux when supported * Status detection on macOS has been adjusted to be consistent with other platforms * Linux binaries are built with imagemagick7 support Bugfixes: * Fix uninitialized variables (#609) * Fix spelling of `--preserve-aspect-ratio` (#614) Logos: * Update NixOS_small # 2.2.1 Hotfix release for #606 Bugfixes: * Fix broken presets due to the breaking changes introduced in 2.2.0 Features: * Pretty print `fastfetch --help` # 2.2.0 This release introduces a new option `--migrate-config`, which migrates old flag based config file to new JSONC format Changes: * `--pipe` and `--stat` are moved from `general` options to `display` options. This affects cjson configuration. * Display keys `percent*` and `size*` in JSON config are restructured. e.g. `{ "sizeNdigits": 1 }` is now `{ "size": { "ndigits": 1 } }` * With the introduction of `--migrate-config`, the old flag based config file is deprecated, and will be removed in 3.0.0 (next major version) * Support of `--gen-config conf` is deprecated accordingly, and will be removed in 2.3.0 (next minor version) * The global flag `--allow-slow-operations` is split into some explicit flags in different modules * `--packages-winget`: control whether `winget` packages count should be detected. Note it's a very slow operation, please enable it with caution. * `--chassis-use-wmi`: control whether `WMI` query should be used to detect chassis type, which detects more information, but slower. This flag only affects `--chassis-format` and `--format json`. * `--battery-use-setup-api`: control whether `SetupAPI` should be used on Windows to detect battery info, which supports multi batteries, but slower. * `--wm-detect-plugin`: control whether WM plugins should be detected. Note it's implemented with global processes enumeration and can report false results. * `--de-slow-version-detection`: control DE version should be detected with slow operations. It's usually not necessary and only provided as a backup. * `--localip-default-route-only` and `--netio-default-route-only` defaults to true to avoid large number of results Features: * Quirks for MIPS platforms (CPU, Linux) * Use devicetree path for OBP hosts (Host, Linux) * Detect `tmux: server` as tmux (Terminal, Linux) * Support urxvt version detection (Terminal, Linux) * Support st version detection (Terminal, Linux) * Support st terminal font detection (TerminalFont, Linux) * Support xfce4-terminal 1.1.0+ terminal font detection (TerminalFont, Linux) * Add option `--migrate-config ` * Support Nvidia GPU temp and cuda core count detection via nvml. Use `--gpu-use-nvml` to enable it (GPU) * Try supporting Wifi authentication type detection in macOS Sonoma. Please file a feature request if you get `to be supported (num)` with result of `/System/Library/PrivateFrameworks/Apple80211.framework/Resources/airport -I | grep auth` (Wifi, macOS) Bugfixes: * Better GPU memory and type detection (GPU, Windows) * Don't print display type twice (Display) * Detect BSSID instead of Wifi MAC address to align with other platforms (Wifi, macOS) * Remove support of used GPU memory detection, which is not reliable and only supported with `--gpu-force-vulkan`. (GPU) * Fix flag `--brightness-ddcci-sleep` (Brightness, Linux) * Fix hanging if a child process prints to both stdout and stderr (Linux) Logos: * Add Black Mesa * Add cycledream * Add Evolinx * Add azos * Add Interix # 2.1.2 Bugfixes: * Fix icon detection on Windows. It shows enabled system icons in desktop (`This PC`, `Recycle Bin`, etc) (Icon, Windows) * Fix compatibility with ddcutil 2.0 (Brightness, Linux) * Fix a compile warning (CPUUsage, FreeBSD) # 2.1.1 Features: * Support opkg (Packages, Linux) * Support GNOME Console terminal version and font detection (Terminal, Linux) * Add `--cpu-freq-ndigits` to set number of digits for CPU frequency (CPU) * New module to detect physical disk I/O usage (DiskIO) * Add `--cpuusage-separate` to display CPU usage per CPU logical core * Add `--brightness-ddcci-sleep` to set the sleep times (in ms) when sending DDC/CI requests (Brightness, #580) Bugfixes: * Fix possible crashes on Windows 7 (Disk, Windows) * Fix possible crashes caused by uninitialized strings (Users, Windows) * Improve support of `--help *-format` and several bugs are found and fixed * Don't incorrectly print `No active sound devices found` when using a non-controllable sound device (Sound, macOS) * Fix implementation processes counting (Processes, Linux) * Work around a issue that SSID cannot be detected on macOS Sonoma (Wifi, macOS) Logo: * Add Chimera Linux * Add EndeavourSmall * Add Xenia * Add MainsailOS * Fix phyOS # 2.1.0 This release introduces a new output format: JSON result Changes: * Users module detects and prints user login time by default. Specifying `--users-compact` to disable it * Fastfetch now requires yyjson 0.8.0 or later, which is embedded in fastfetch source tree. If you build fastfetch with `-DENABLE_SYSTEM_YYJSON` cmake option, you must upgrade your yyjson package * Reduced information supported by `--terminal-format`, `--shell-format` * Some config presets (`devinfo` and `verbose`) are obsolete and removed. They are barely maintained and can be replaced with `--format json` now. * Custom strings in `--module-key` and `--module-format` are no longer trimmed. * `/boot` is hidden by default (FreeBSD, Disk) Features: * Add `--format json`, which prints system information as JSON format * Add fast path for xfce4 version detection (DE, FreeBSD) * Support contour terminal version and font detection (Terminal / TerminalFont) * Support `kitty-direct` / `iterm` without specifying logo width / height. Note: in this case, the entre screen will be cleared. * Support new flag `--logo-separate`. If true, print modules at bottom of the logo * Support Apple Silicon CPU frequency detection (CPU, macOS) * Support user login time detection (Users) * Support winget package manager detection, guarded behind `--allow-slow-operations` (Packages, Windows) * Print monitor type (built-in or external) (Display) * Support full GPU detection in WSL (Linux, GPU) * Add `--module-key " "` as a special case for hiding keys * Support `--title-format`. See `fastfetch --help title-format` for detail * Support `--colors-key` (Colors) * Add `-c` as a shortcut of `--load-config`. Note it was used as the shortcut of `--color` before 2.0.5 * Support Windows Service Pack version detection (Kernel, Windows) * Support Debian point releases detection (OS, Linux) * Add new module `NetIO` which prints network throughput (usage) of specified interface. Note this module costs about 1 second to finish. * Use `lscpu` to detect CPU name for ARM CPUs (CPU, Linux) Bugfixes: * Fix fastfetch hanging in specific environment (#561) * Fix short read when reading from stdin (Logo) * Fix `poll() timeout or failed` error when image is very large (Logo) * Fix Termux Monet terminal version detection (Terminal) * Fix zpool volumes detection (Disk, Linux) * Fix external volumes detection (Disk, Linux) * Fix snap package number detection on systems other than Ubuntu (Packages, Linux) * Fix dpkg / apt package number detection (Packages, Linux) * Fix bluetooth mac address detection (Bluetooth, Windows) Logo: * Add Afterglow * Add Elbrus * Update EvolutionOS * Update AOSC OS * Update Ubuntu_old * Update Windows 11_small * Add Amazon Linux * Add LainOS * Fix colors of Slackware # 2.0.5 Bugfixes: * Fix segfault when using libxrandr (#544, Display, Linux) * Don't print 0px (#544, Cursor) Features: * Add option `--disk-use-available` (#543) * Add option `--disk-show-readonly` # 2.0.4 Bugfixes: * Fix building on 32-bit FreeBSD (Memory, FreeBSD) * Fix `--file-raw` doesn't work (Logo) Features: * Trait `-` as an alias for `/dev/stdin`. Available for `--file`, `--file-raw` and `--raw` (Logo) # 2.0.3 Bugfixes: * Fix typo in config parsing for --color-title (#534) * Fix percent formatting for `--*-format` (#535) * Fix loading presets for homebrew (macOS) Features: * Add option `--percent-ndigits` * Add command flag `--config` as an alias of `--load-config` * Windows packages now include presets (Windows) # 2.0.2 Bugfixes: * Workaround [a compiler bug of GCC](https://gcc.gnu.org/bugzilla/show_bug.cgi?id=85282) (Windows) * Fix presets not detected by file name (#529) Logo: * Add Tuxedo OS # 2.0.1 First stable release of Fastfetch V2 Changes: * Unescape strings only when parsing `.conf` files * Previously: `$ NO_CONFIG=1 fastfetch --os-key \\\\ -s os -l none` prints `\: *`. Note the backslashes are unescaped twice (once by shell and once by fastfetch). * Now: `$ NO_CONFIG=1 fastfetch --os-key \\\\ -s os -l none` prints `\\: *` * Remove option shortcut `-c` (alias of `--color`), which is more commonly used as alias of `--config` * Rename `--recache` to `--logo-recache` (which is used for regenerate image logo cache). Remove option shortcut `-r` (alias of `--recache`). * Detecting brightness of external displays with DDC/CI is no longer guarded behind `--allow-slow-operations` (Brightness) Features: * Add `--key-width` for aligning the left edge of values, supported both for global `--key-width` and specific module `--module-key-width` * Add `--bar-char-elapsed`, `--bar-char-total`, `--bar-width` and `--bar-border` options * Add CMake option `ENABLE_SYSTEM_YYJSON`, which allow building fastfetch with system-provided yyjson (for package managers) * Add new module `Version`, which prints fastfetch version (like `fastfetch --version`) Bugfixes: * Fix label detection. Use `--disk-key 'Disk ({2})'` to display it (Disk, Linux) * Fix some module options were not inited * Fix terminal version and font detection on NixOS (Terminal, Linux) # 2.0.0-beta Fastfetch v2 introduces a new configuration file format: JSON config. Please refer to for details. Changes: * Drop the dependency of cJSON. We now use [yyjson](https://ibireme.github.io/yyjson/doc/doxygen/html/index.html) to parse JSON documents. * Remove `--shell-version` and `--terminal-version`. They are always enabled (Terminal / Shell) * Remove `--*-error-format`, which seems to be useless * Remove `--display-detect-name`. Display name is always detected, and will be printed if multiple displays are detected * Deprecate `--set` and `--set-keyless`; they may be removed in future releases. Use JSON config with Custom module instead * Remove the special handling of Command module (it can be set once in the triditional `config.conf`). Use JSON config with Command module instead * Change `--wm-theme-*` to `--wmtheme-*`. Affect `key` and `format` (WMTheme) * Change `--terminal-font-*` to `--terminalfont-*`. Affect `key` and `format` (TerminalFont) * Module `Command` uses `/bin/sh` as the default shell on systems other than Windows (Command) * Fix M2 CPU temperature detection (CPU, macOS) * Detect monitor name when available, instead of using DRM connector name (Display / Brightness, Linux) Features: * FreeBSD support is improved greatly, and actually tested in a physical machine * Add `--no-buffer` option for easier debugging. CMake option `ENABLE_BUFFER` is removed and always enabled. * Support `--*-key-color` option to change the key color of specified module * Support `--colors-symbol` and `--colors-padding-left` (Colors) * Add LM (Login Manager) module. Currently requires systemd installed (thus Linux only) * Add `--wmi-timeout` option (Windows) * Add `--logo-type small` to search for small logos * Support detecting brightness of external displays with DDC/CI (guard behind `--allow-slow-operations`) (Brightness) * Add option `--size-ndigits` and `--size-max-prefix` (#494) * Add option `--processing-timeout` to the timeout when waiting for child processes. * Public IP module prints the IP location if `--publicip-url` is not set (PublicIP) * Add option `--localip-default-route-only` (LocalIP) * Add option `--weather-location` (Weather) * Support iTerm non-ascii font detection (Terminal, macOS) * Add option `--title-color-user`, `--title-color-at` and `--title-color-host` (Title) * Add Exherbo logo and package manager count (Packages, Linux, #503) * Add module `Terminal Size` which prints the number of terminal width and height in characters and pixels * Add new option `--temperature-unit` * Better CPU and Host detection for Android (Android) * Support yakuake terminal version & font detection (Terminal, Linux) * Add new option `--bright-color` which can be used to disable the default bright color of keys, title and ASCII logo. * Add module `Monitor` which prints physical parameters (native resolutions and dimensions) of connected monitors * Support path with environment variables for `--logo-source` and `--load-config`. Bugfixes: * Fix possible hanging (TerminalFont, #493) * Fix heap-buffer-overflow reading (DisplayServer, Linux) * Fix false errors when built without libnm support (Wifi, Linux) * Properly detect CPU on POWER (CPU, Linux) * Fix compatibility with Fig (Terminal, macOS) * Fix option `--title-fqdn` doesn't work (Title) * Fix used spaces calculation (Disk, Linux / BSD / macOS, #508) * Fix `--brightness-format` (Brightness) * Fix specifying `--set-keyless` with the same key second time won't override the value set before (#517) * Fix specifying `--color` second time won't clear the value set before (#517) Logo: * Change the special handling of `kitty` protocol with `.png` image file to a new image protocol `kitty-direct`. This is the fastest image protocol because fastfetch doesn't need to pre-encode the image to base64 or something and the image content doesn't need to be transmitted via tty. Note: 1. Although konsole was said to support `kitty` image protocol, it doesn't support `kitty-direct` 2. wezterm support more image formats other than `.png` (tested with `.jpg` and `.webp`) * Port all logos supported by neo(wo)fetch. Fastfetch now has 350 builtin logos in total. # 1.12.2 Features: * Support terminator terminal version detection (Linux, Terminal) * Support `pkgtool` package manager detection (Linux, Packages) * Support `Far` shell version detection (Windows, Shell) Bugfixes: * Fix ConEmu terminal detection in some special cases (Windows, Terminal, #488) * Fix incorrect Host on M2 Mac Studio with M2 Max CPU (macOS, Host, #490) # 1.12.1 Bugfixes: * Fix compiling error on Apple Slicon (Bios, macOS) # 1.12.0 This release backports some changes from dev branch, and fixes 2 crashing issues Features: * Support KDE / LXQt / MATE / Cinnamon wallpaper detection (Wallpaper, Linux) * Support QTerminal version & terminal font detection * Support MATE Terminal version & terminal font detection * Set `--pipe true` automatically if stdout is not a tty * Detect new macs released on WWDC 2023 (macOS, Host) * Count cached memory as free memory (FreeBSD, Memory) * Support sound detection (FreeBSD, Sound) Bugfixes: * Fix DE detection on Windows 8.1 (Windows, DE) * Fix `--logo-padding-left` doesn't work when `--logo-padding-top` is set (Logo) * Fix KDE version detection on Fedora (DE) * Include limits.h when needed (Linux, #472) * Fix Windows drives detection in WSL (Linux, Disk) * Fix CPU temp detection (FreeBSD, CPU) * Fix disk detection (Android, Disk) * Fix GNOME Terminal version and font detection (FreeBSD, TerminalFont) * Fix crash on newer wayland desktops (Linux, Display, #477) * Fix vendor detection for Intel GPU (macOS, GPU) * Fix possible crashes on Windows Server (Windows, GPU, #484) Logo: * Add bsd, freebsd_small, ghostbsd * Make Windows 11 logo colorable # 1.11.3 Bugfixes: * Fix a segfault bug, regression of `1.11.1` (Linux, wmtheme, #467) # 1.11.2 This release should be the last version of fastfetch 1.x (if no serious bugs found, hopefully) Features: * Support display name, type, rotation detection on Wayland (Linux, Display) * Print more useful display name instead of intel_backlight (Linux, Brightness) * Icons module supports Windows (Windows, Icons) * Add Wallpaper module which shows the current wallpaper image path * Add mac address detection `--localip-show-mac` (LocalIP, #451) Bugfixes: * Fix GNOME version detection on Fedora (DE) * Fix Windows drives detection in WSL (Disk) Changes: * In order to make Icons module consistent between different platforms, `--icons-format` no longer supports individual GTK / Qt icon params. * `--theme-format` no longer supports individual GTK / plasma theme params. * `--local-ip-*` and `--public-ip-*` have been changed to `--localip-*` and `--publicip-*` * `--localip-compact-type` is no longer supported. Fastfetch now display IPs as `--localip-compat-type multiline` by default, with `--local-compact true` can be set as an alias of `--localip-compact-type oneline` * `--localip-v6first` is no longer supported. # 1.11.1 Features: * Support xonsh detection (TerminalShell) * Support Tabby version / terminal font detection (TerminalFont) Bugfixes: * Fix name of Pro Controller (Gamepad, Windows) * Fix compile error with imagemagick enabled (Windows) * Fix copy-and-paste errors (Gamepad) * Flatpak: Fix user package count * Flatpak: Count runtime packages too (#441) * Fix flatpak package count (#441) * Don't print white color blocks with `--pipe` (#450) * Fix iTerm being detected as iTermServer-* sometimes * Fix sound device volume being incorrectly detected as muted sometimes (Sound) * Fix memleaks reported by LeakSanitizer (Linux) * Fix potential memory corruption bug in unicode.c (Windows) Logo: * Update Windows 11 ASCII logo to look more visually consistent (#445) * Add another font color index to arch icon (#446) * Add SteamOS * Add macOS small / small2 # 1.11.0 Features: * Support linuxbrew (Packages, Linux) * Support foot terminal (#431, Linux) * Support cursor size detection on Windows (Cursor, Windows) * Support cursor detection on macOS (Cursor, macOS) * Support display name, display type and decimal refresh rate detection (Display, macOS / Windows) * Support `--display-compact-type` to display multiple resolutions in one line (Display) * Support flatpak-user (Packages, Linux, #436) * Support `--gpu-force-vulkan` to force using vulkan to detect GPUs, which support video memory usage detection with `--allow-slow-operations` (GPU) Bugfixes: * Fix date time format * Fix compiling with musl (Wifi, Linux, #429) * Don't exit if libpci is failed to init (GPU, Linux, #433) * Names of most well-known gamepads are correctly printed instead of `Wireless Controller` on Windows Logo: * Small update for nobara logo (#435, @regulargvy13) # 1.10.3 Bugfixes: * Fix uninitialized variables (GPU, Windows) * Fix compiling errors (Windows) Improvements: * Improve performance (WmTheme amd Font, Windows and macOS) * Enable nonblocking public-ip / weather detection (Android) # 1.10.2 Bugfixes: * Handle `kAudioObjectPropertyElementMain` for macOS **SDK** < 12 (#425, @nandahkrishna) * Add missing `NULL` for `ffProcessAppendStdOut` (#421) # 1.10.1 New release for debugging #421 # 1.10.0 Notable Changes: * With the support of Win32 platform, original Windows 64bit artifact file is renamed to Win64 to avoid possible confusion Features: * Bluetooth module * Sound module * Gamepad module * Support colored percentage numbers output (#409) * Support `--localip-compact-type` option (#408) * Terminator terminal font detection (@Zerogiven, #415) * Windows 32bit compatibility * Support global configuration in MSYS2 environment (Windows) * Support GPU driver version detection on Windows 11 * Support scaled resolution detection for wayland (Linux) Bugfixes: * Fix build with older libnm versions * Fix a rare case that fails to detect terminal * Fix Muffin detection (@Zerogiven, #411) * Fix IPv6 detection (Windows) * Fix scoop package count detection when scoop is installed in non-default path (Windows, #417) * Fix UB reported by clang * Honor $SCOOP when detecting scoop packages (#417) Other: * Simplified wmtheme output format (Windows) * Improved GPU detection performance on Windows 11 * Latest Mac model identifier support (macOS) # 1.9.1 Bugfixes: * Fix builds on s390x (@jonathanspw, #402) * Fix zero refresh rate on some monitors (macOS) * Fix default formatting of Wifi module # 1.9.0 Notable Changes: * fastfetch no longer creates a sample config file silently. Use `--gen-config` to generate one. * fastfetch now search for user config file in the order of `fastfetch --list-config-paths` * Unknown disks are hidden by default. * `Resolution` module is renamed to `Display`. (#393) Features: * `--logo-padding-top` option (@CarterLi, #372) * Raw image file as logo support (@CarterLi) * Look for config files in `$APPDATA` ([RoamingAppData](https://superuser.com/questions/21458/why-are-there-directories-called-local-locallow-and-roaming-under-users-user#answer-21462)) (Windows) * Look for config files in `~/Library/Preferences` (macOS) * Add `--list-config-paths` option which list search paths of config files * Add `--list-data-paths` option which list search paths for presets and logos * Add `Brightness` module support * Add `Battery` module support for FreeBSD * Add `--disk-show-unknown` option for Disk module * Add `--disk-show-subvolumes` option for Disk module * Add `--gpu-hide-integrated` option (#379) * Add `--gpu-hide-discrete` option (#379) * Detect terminal version when available * Support `WezTerm` terminal font detection (requires [`wezterm` executable](https://wezfurlong.org/wezterm/cli/general.html) being available) * Add `--shell-version` and `--terminal-version` options to disable shell / terminal version detection * Enhance `--percent-type` to allow hiding other texts (#387) * Add Wifi module support for Linux * Detect scaled resolutions (Windows, macOS) * Optimise font module printing (Windows) * Detect pacman package count inside MSYS2 environment (Windows) * Add Wifi / Battery module support for Android * Disk name support for Linux Logos: * Raspbian (@IamNoRobot, #373) Bugfixes: * `--logo-type` now does accept `iterm` too (@CarterLi, #374) * Fix mintty terminal font detection (Windows) * Fix bug that line buffering doesn't work properly (Windows) * Fix rpm package count detection (Linux) * Fix cpu temp detection (Linux) Other: * Fixed a Typo in iterm error message (@jessebot, #376) * Don't try to load config file in `/etc` (Windows) # 1.8.2 Bugfixes: * Fix memleaks Users module (Windows) * Fix shell detection when installed with scoop (Windows) * Don't use libcJSON as wlanapi's dll name (Windows) * Align artifact names to other platforms (Windows) # 1.8.1 Notable Changes: * `Song` was used as an alias to `Media` module. It's removed to avoid confusion. All song related flags (`--song-key`, etc) should change to media (`--media-key`, etc). (@CarterLi) Bugfixes: * Mountpoint paths on linux get decoded correctly (#364) * Color parsing once again works (@IanManske, #365) * Using a custom key with a placeholder for the local ip module now does work correctly if multiple interfaces are present (#368) # 1.8.0 This release introduces Windows support! Fastfetch now fully support all major desktop OSes (Linux, macOS, Windows and FreeBSD) Notable Changes: * Bios / Board / Chassis modules are split against Host module for performance reasons * Caching is removed. Option `--nocache` is removed accordingly Features: * Windows (7 and newer) is officially and fully supported * FreeBSD support is improved greatly (Bios, Cpu Temp, Cpu Usage, Disk, Host, Processes, Swap, Terminal / Shell, Uptime) * Adds a new flag `--stat`, which prints time usage for individual modules * Adds Wifi module which supports Windows and macOS * Adds data source option for logo printing * Detects Homebrew Cellar and Cask separately * Detects WSL version * Detects disk based on mount point * Exposes more chafa configs * Improves performance for Cpu Usage, Public IP, Weather modules * Improves performance for Kitty image protocol when both image width / height specified * Improves performance for large file loading * Improves performance for macOS WM and Host detection * Improves shell and terminal detection on macOS * Supports Deepin Terminal terminal font * Supports GPU detection on Android * Supports Kitty Terminal terminal font * Supports bar output for percentage values * Supports Bios module on macOS * Supports eopkg package manager detection * Supports iTerm image logo protocol * Supports image logo printing on macOS * Supports tcsh version detection * Vulkan module on macOS no longer requires vulkan-loader to work Logos: * Alpine * CRUX * EndeavourOS * Enso * Garuda small * Nobara * OpenMandriva * Parabola GNU/Linux-libre * Rocky * Rosa * Solus * Univalent * Vanilla OS Bugfixes: * Fixes disk size detection on 32bit Linux (#337) * Fixes cpu freq detection in WSL * Fixes internal bug of FFstrbuf * Fixes some memory leaks * Fixes segfault if 0 is given as argument index * Lots of code refactors # 1.7.5 Fixes a crash on linux that could happen when getting zsh version (#285) # 1.7.4 The last element in the default structure (currently the color blocks) is now printed again (#283) # 1.7.3 A lot of small improvements for MacOS & BSD platforms. Features: * BSD is now officially supported (#228) * MacPorts package manager support (@SladeGetz, #234) * Battery support for MacOS (@CarterLi, #235) * Processes, swap & terminal font support for MacOS(@CarterLi, #237) * Media support for MacOS (@CarterLi, #242) * Player support for MacOS (@CarterLi, #245) * WM theme support for MacOS (@CarterLi, #246) * CPU usage support for MacOS (@CarterLi, #247) * Power Adapter module (@CarterLi, #249) * Windows terminal font for WSL (@CarterLi, #254) * Temps & Font support for MacOS (@CarterLi, #258) * Terminal font support for Termux (@CarterLi, #263) * Weather module (@CarterLi, #266) Logos * Crystal linux (@AloneER0, #239) * FreeBSD (@draumaz, #244) * New Ubuntu (@AloneER0, #259) Bugfixes: * Don't segfault in GPU code on Intel Macs (@CarterLi, #236) * Don't use hardcoded size units in presets (@dr460nf1r3, #255) * Don't crash with some format strings (#252) * --logo none keeps key color now (#264) # 1.7.2 Fixes the bash completions # 1.7.1 This release brings a lot of bug fixes and improvements for MacOS. Big thanks to @CarterLi for the help on this! Features: * The color of the title and the keys can now be configured individually, using `--color-keys` and `--color-title` respectively. Some distros have different defaults now, similar to neofetch * Swap module, similar to the Memory module, but for swap. Add `Swap` to your structure to enable it (#225) Logos: * Slackware (#227) Bugfixes: * Used disk space is now calculated much more accurately * On Linux, GPU names are no longer truncated, if they are longer than 32 characters (#224) * On Linux, NVIDIA GPUs once again have a proper name * On M1 platforms, showing the GPU name no longer crashes the program (#222) * Brew package count does now work on M1 platforms too * The Vulkan module now does work on MacOS too * The OpenGL and OpenCL modules now work on MacOS too (@CarterLi, #226) * The LocalIp module now works on MacOS too (@CarterLi, #232) * Detecting custom WMs on MacOS does now work Other: * GitHub actions now builds a dmg file for MacOS, as you can see in the release page # 1.7.0 This release brings support for MacOS! The basics things are working, but it is far from feature parity with Linux. I developed this in a VM, so bugs on real hardware are likely. If you have a Mac and no idea what to do with your free time, i am very happy to accept pull requests / work on issues. A lot of things were changed under the hood to make this possible, which should bring better performance and stability on all platforms. Besides that, the following things have changed: Features: * The binary prefix used can now be configured, and is used consistently across all modules. Set `--binary-prefix` to `iec` (default), `si` or `jedec`. * AMD GPUs now have a much better name, if the file `/usr/share/libdrm/amdgpu.ids` exists. For example my dedicated GPU, which was displayed as `AMD/ATI Radeon RX 5600 OEM/5600 XT / 5700/5700 XT`, is now `AMD Radeon RX 5600M`. Logos: * MacOS * CachyOS small (@sirlucjan, #220) * MSYS2 (#219) Bugfixes: * the `--file` option, which can be used to display the contents of a file as the logo, is now working again. # 1.6.5 Fixes parsing quoted values in config files # 1.6.4 Releasing this, so fedora can package fastfetch. Thanks to @jonathanspw for doing that! Features: * --set-keyless option (#215) * Replace `\n`, `\t`, `\e` and `\\` in user provided strings, just like c would do it (#215) * APK (Alpine Package Keeper) support (@mxkrsv, #216) Logos: * Alma Linux (@jonathanspw, #214) Bugfixes: * replace deprecated gethostbyname call with getaddrinfo (#217) # 1.6.3 Fixes installing presets in their own directory (@ceamac, #212) # 1.6.2 Releasing this, so void linux can package fastfetch. Logos: * Rosa linux (#206) * KISS linux (@draumaz, #207) * LangitKetujuh (@hervyqa, #208) Bugfixes: * Using musl as libc does work now (#210) * XBPS packages are once again printed (#209) * configured target dirs are applied to install directories too * empty XDG_* env vars don't cause a crash anymore # 1.6.1 Fixes build on android (#205) # 1.6.0 Features: * Detect Qt on more DEs than just KDE Plasma. The [Plasma] category was therefore renamed to [Qt] * Alacritty font detection * Load `/etc/fastfetch/config.conf` before user config * Disk: print one decimal point if size < 100GB * `--title-fqdn` option, to print fully qualified domain name instead of host name in title Logos: * updated old NixOS logo Bugfixes: * Correctly detect GTK on DEs that store their settings in dconf * Correctly detect NixOS packages * Mutter WM detected once again * Show full NixOS version in OS output * Don't segfault if an invalid structure is given * WSL doesn't output GPU anymore, as the name is always meaningless ================================================ FILE: CMakeLists.txt ================================================ cmake_minimum_required(VERSION 3.12.0) # target_link_libraries with OBJECT libs & project homepage url project(fastfetch VERSION 2.60.0 LANGUAGES C DESCRIPTION "Fast neofetch-like system information tool" HOMEPAGE_URL "https://github.com/fastfetch-cli/fastfetch" ) set(PROJECT_LICENSE "MIT license") if(DEFINED CMAKE_SYSTEM_PROCESSOR_OVERRIDE) # Used by github actions for i686 build set(CMAKE_SYSTEM_PROCESSOR ${CMAKE_SYSTEM_PROCESSOR_OVERRIDE} CACHE INTERNAL "") endif() string(TOLOWER "${CMAKE_SYSTEM_PROCESSOR}" CMAKE_SYSTEM_PROCESSOR) if(CMAKE_SYSTEM_PROCESSOR STREQUAL "x86_64") set(CMAKE_SYSTEM_PROCESSOR "amd64") elseif(CMAKE_SYSTEM_PROCESSOR STREQUAL "arm64") set(CMAKE_SYSTEM_PROCESSOR "aarch64") endif() message(STATUS "Build for system processor: ${CMAKE_SYSTEM_PROCESSOR}") ################### # Target Platform # ################### if(ANDROID) set(LINUX FALSE) elseif("${CMAKE_SYSTEM_NAME}" STREQUAL "Linux") set(LINUX TRUE CACHE BOOL "..." FORCE) # LINUX means GNU/Linux, not just the kernel elseif("${CMAKE_SYSTEM_NAME}" STREQUAL "FreeBSD") set(FreeBSD TRUE CACHE BOOL "..." FORCE) elseif("${CMAKE_SYSTEM_NAME}" STREQUAL "OpenBSD") set(OpenBSD TRUE CACHE BOOL "..." FORCE) elseif("${CMAKE_SYSTEM_NAME}" STREQUAL "MidnightBSD") set(FreeBSD TRUE CACHE BOOL "..." FORCE) set(MidnightBSD TRUE CACHE BOOL "..." FORCE) elseif("${CMAKE_SYSTEM_NAME}" STREQUAL "NetBSD") set(NetBSD TRUE CACHE BOOL "..." FORCE) elseif("${CMAKE_SYSTEM_NAME}" STREQUAL "DragonFly") set(FreeBSD TRUE CACHE BOOL "..." FORCE) set(DragonFly TRUE CACHE BOOL "..." FORCE) elseif("${CMAKE_SYSTEM_NAME}" STREQUAL "SunOS") set(SunOS TRUE CACHE BOOL "..." FORCE) elseif("${CMAKE_SYSTEM_NAME}" STREQUAL "Haiku") set(Haiku TRUE CACHE BOOL "..." FORCE) elseif("${CMAKE_SYSTEM_NAME}" STREQUAL "GNU") set(GNU TRUE CACHE BOOL "..." FORCE) elseif(NOT APPLE AND NOT WIN32) message(FATAL_ERROR "Unsupported platform: ${CMAKE_SYSTEM_NAME}") endif() ############################# # Compile time dependencies # ############################# set(THREADS_PREFER_PTHREAD_FLAG NOT WIN32) find_package(Threads) find_package(PkgConfig) if(NOT PKG_CONFIG_FOUND) message(WARNING "pkg-config not found, library detection might be limited") endif() include(CheckIncludeFile) ##################### # Configure options # ##################### include(CMakeDependentOption) cmake_dependent_option(ENABLE_VULKAN "Enable vulkan" ON "LINUX OR APPLE OR FreeBSD OR OpenBSD OR NetBSD OR WIN32 OR ANDROID OR SunOS OR Haiku OR GNU" OFF) cmake_dependent_option(ENABLE_WAYLAND "Enable wayland-client" ON "LINUX OR FreeBSD OR OpenBSD OR NetBSD OR GNU" OFF) cmake_dependent_option(ENABLE_XCB_RANDR "Enable xcb-randr" ON "LINUX OR FreeBSD OR OpenBSD OR NetBSD OR ANDROID OR SunOS OR GNU" OFF) cmake_dependent_option(ENABLE_XRANDR "Enable xrandr" ON "LINUX OR FreeBSD OR OpenBSD OR NetBSD OR ANDROID OR SunOS OR GNU" OFF) cmake_dependent_option(ENABLE_DRM "Enable libdrm" ON "LINUX OR FreeBSD OR OpenBSD OR NetBSD OR SunOS OR GNU" OFF) 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_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) cmake_dependent_option(ENABLE_IMAGEMAGICK7 "Enable imagemagick 7" ON "LINUX OR FreeBSD OR OpenBSD OR NetBSD OR APPLE OR ANDROID OR WIN32 OR SunOS OR Haiku OR GNU" OFF) cmake_dependent_option(ENABLE_IMAGEMAGICK6 "Enable imagemagick 6" ON "LINUX OR FreeBSD OR OpenBSD OR NetBSD OR APPLE OR SunOS OR GNU" OFF) cmake_dependent_option(ENABLE_CHAFA "Enable chafa" ON "ENABLE_IMAGEMAGICK6 OR ENABLE_IMAGEMAGICK7" OFF) cmake_dependent_option(ENABLE_EGL "Enable egl" ON "LINUX OR FreeBSD OR OpenBSD OR NetBSD OR ANDROID OR WIN32 OR SunOS OR Haiku OR GNU" OFF) cmake_dependent_option(ENABLE_GLX "Enable glx" ON "LINUX OR FreeBSD OR OpenBSD OR NetBSD OR ANDROID OR SunOS OR GNU" OFF) cmake_dependent_option(ENABLE_OPENCL "Enable opencl" ON "LINUX OR FreeBSD OR OpenBSD OR NetBSD OR WIN32 OR ANDROID OR SunOS OR Haiku OR GNU" OFF) cmake_dependent_option(ENABLE_FREETYPE "Enable freetype" ON "ANDROID" OFF) cmake_dependent_option(ENABLE_PULSE "Enable pulse" ON "LINUX OR ANDROID OR GNU" OFF) cmake_dependent_option(ENABLE_DDCUTIL "Enable ddcutil" ON "LINUX" OFF) cmake_dependent_option(ENABLE_DIRECTX_HEADERS "Enable DirectX headers for WSL" ON "LINUX" OFF) cmake_dependent_option(ENABLE_ELF "Enable libelf" ON "LINUX OR ANDROID OR DragonFly OR Haiku OR GNU" OFF) cmake_dependent_option(ENABLE_THREADS "Enable multithreading" ON "Threads_FOUND" OFF) option(ENABLE_ZLIB "Enable zlib" ON) option(ENABLE_SYSTEM_YYJSON "Use system provided (instead of fastfetch embedded) yyjson library" OFF) option(ENABLE_ASAN "Build fastfetch with ASAN (address sanitizer)" OFF) option(ENABLE_LTO "Enable link-time optimization in release mode if supported" ON) option(BUILD_FLASHFETCH "Build flashfetch" ON) # Also build the flashfetch binary option(BUILD_TESTS "Build tests" OFF) # Also create test executables option(SET_TWEAK "Add tweak to project version" ON) # This is set to off by github actions for release builds option(IS_MUSL "Build with musl libc" OFF) # Used by Github Actions option(INSTALL_LICENSE "Install license into /usr/share/licenses" ON) option(ENABLE_EMBEDDED_PCIIDS "Embed pci.ids into fastfetch, requires `python`" OFF) option(ENABLE_EMBEDDED_AMDGPUIDS "Embed amdgpu.ids into fastfetch, requires `python`" OFF) option(ENABLE_WORDEXP "Enable using of wordexp(3) if available, instead of glob(3)" ON) option(ENABLE_LIBZFS "Enable libzfs" ON) if(WIN32 AND NOT CMAKE_SYSTEM_PROCESSOR STREQUAL "aarch64") option(ENABLE_WIN81_COMPAT "Enable legacy Windows compatibility (Windows 8.1 and later; Windows 7 unsupported)" ON) endif() if(APPLE) option(ENABLE_APPLE_MEMSIZE_USABLE "Use usable memory size as total memory size in Memory module, to match other systems" OFF) endif() set(BINARY_LINK_TYPE_OPTIONS dlopen dynamic static) set(BINARY_LINK_TYPE dlopen CACHE STRING "How to link fastfetch") set_property(CACHE BINARY_LINK_TYPE PROPERTY STRINGS ${BINARY_LINK_TYPE_OPTIONS}) if(NOT BINARY_LINK_TYPE IN_LIST BINARY_LINK_TYPE_OPTIONS) message(FATAL_ERROR "BINARY_LINK_TYPE must be one of ${BINARY_LINK_TYPE_OPTIONS}") endif() set(PACKAGE_MANAGERS AM APK BREW CHOCO DPKG EMERGE EOPKG FLATPAK GUIX LINGLONG LPKG LPKGBUILD MACPORTS NIX OPKG PACMAN PACSTALL PALUDIS PISI PKG PKGTOOL RPM SCOOP SNAP SOAR SORCERY WINGET XBPS) foreach(package_manager ${PACKAGE_MANAGERS}) if(package_manager STREQUAL "WINGET") option(PACKAGES_DISABLE_${package_manager} "Disable ${package_manager} package manager detection by default" ON) else() option(PACKAGES_DISABLE_${package_manager} "Disable ${package_manager} package manager detection by default" OFF) endif() endforeach() if (LINUX) set(CUSTOM_PCI_IDS_PATH "" CACHE STRING "Custom path to file pci.ids, defaults to `/usr/share/hwdata/pci.ids`") set(CUSTOM_AMDGPU_IDS_PATH "" CACHE STRING "Custom path to file amdgpu.ids, defaults to `/usr/share/libdrm/amdgpu.ids`") set(CUSTOM_OS_RELEASE_PATH "" CACHE STRING "Custom path to file os-release, defaults to `/etc/os-release`") endif() #################### # Compiler options # #################### if(NOT CMAKE_BUILD_TYPE) set(CMAKE_BUILD_TYPE RelWithDebInfo) endif() message(STATUS "Build type: ${CMAKE_BUILD_TYPE}") if(ENABLE_THREADS) if(CMAKE_USE_WIN32_THREADS_INIT) message(STATUS "Threads type: Win32 thread") elseif(CMAKE_USE_PTHREADS_INIT) message(STATUS "Threads type: pthread") endif() else() message(STATUS "Threads type: disabled") endif() set(WARNING_FLAGS "-Wall -Wextra -Wconversion -Werror=uninitialized -Werror=return-type -Werror=vla") set(CMAKE_C_STANDARD 11) set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} ${WARNING_FLAGS} -Werror=incompatible-pointer-types -Werror=implicit-function-declaration -Werror=int-conversion") if(WIN32 OR Haiku OR ENABLE_DIRECTX_HEADERS) enable_language(CXX) set(CMAKE_CXX_STANDARD 17) set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} ${WARNING_FLAGS}") endif() if(WIN32) set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -Wl,--tsaware -Wl,--build-id") if(ENABLE_WIN81_COMPAT) set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -Wl,--subsystem,console,--major-os-version,6,--minor-os-version,3") else() set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -Wl,--subsystem,console,--major-os-version,10,--minor-os-version,0") endif() elseif(APPLE AND CMAKE_C_COMPILER_ID MATCHES "Clang") set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -fobjc-arc") endif() set(FASTFETCH_FLAGS_DEBUG "-fno-omit-frame-pointer") if(ENABLE_ASAN) message(STATUS "Address sanitizer enabled (DEBUG only)") set(FASTFETCH_FLAGS_DEBUG "${FASTFETCH_FLAGS_DEBUG} -fsanitize=address") endif() set(CMAKE_C_FLAGS_DEBUG "${CMAKE_C_FLAGS_DEBUG} ${FASTFETCH_FLAGS_DEBUG}") set(CMAKE_EXE_LINKER_FLAGS_DEBUG "${CMAKE_EXE_LINKER_FLAGS_DEBUG} ${FASTFETCH_FLAGS_DEBUG} -fstack-protector-all -fno-delete-null-pointer-checks") if(NOT WIN32) set(CMAKE_EXE_LINKER_FLAGS_DEBUG "${CMAKE_EXE_LINKER_FLAGS_DEBUG} -rdynamic") endif() if(ENABLE_LTO AND NOT CMAKE_BUILD_TYPE STREQUAL "Debug") message(STATUS "Enabling LTO") include(CheckIPOSupported) check_ipo_supported(RESULT IPO_SUPPORTED) if(IPO_SUPPORTED) set(CMAKE_INTERPROCEDURAL_OPTIMIZATION TRUE) endif() endif() ####################### # Target FS structure # ####################### if(NOT TARGET_DIR_ROOT) if(NOT ANDROID) set(TARGET_DIR_ROOT "") else() set(TARGET_DIR_ROOT "/data/data/com.termux/files/usr") endif() endif() if(NOT TARGET_DIR_USR) if(NOT ANDROID) set(TARGET_DIR_USR "${TARGET_DIR_ROOT}/usr") else() set(TARGET_DIR_USR "${TARGET_DIR_ROOT}") endif() endif() if(NOT TARGET_DIR_HOME) if(APPLE) set(TARGET_DIR_HOME "${TARGET_DIR_ROOT}/Users") elseif(ANDROID) set(TARGET_DIR_HOME "/data/data/com.termux/files/home") else() set(TARGET_DIR_HOME "${TARGET_DIR_ROOT}/home") endif() endif() if(NOT TARGET_DIR_ETC) set(TARGET_DIR_ETC "${TARGET_DIR_ROOT}/etc") endif() message(STATUS "Target dirs: ROOT=\"${TARGET_DIR_ROOT}\" USR=\"${TARGET_DIR_USR}\" HOME=\"${TARGET_DIR_HOME}\" ETC=\"${TARGET_DIR_ETC}\"") #https://cmake.org/cmake/help/latest/variable/CMAKE_INSTALL_PREFIX_INITIALIZED_TO_DEFAULT.html if(CMAKE_INSTALL_PREFIX_INITIALIZED_TO_DEFAULT) set(CMAKE_INSTALL_PREFIX "${TARGET_DIR_USR}" CACHE PATH "..." FORCE) endif() if(NOT CMAKE_INSTALL_SYSCONFDIR) set(CMAKE_INSTALL_SYSCONFDIR "${TARGET_DIR_ETC}" CACHE PATH "..." FORCE) endif() ################# # Tweak version # ################# if (SET_TWEAK AND EXISTS "${CMAKE_SOURCE_DIR}/.git") execute_process( COMMAND git describe --tags WORKING_DIRECTORY "${CMAKE_SOURCE_DIR}" OUTPUT_VARIABLE PROJECT_VERSION_GIT OUTPUT_STRIP_TRAILING_WHITESPACE ) string(REGEX MATCH "-[0-9]+" PROJECT_VERSION_TWEAK "${PROJECT_VERSION_GIT}") endif() if(PROJECT_VERSION_TWEAK) string(REGEX MATCH "[0-9]+" PROJECT_VERSION_TWEAK_NUM "${PROJECT_VERSION_TWEAK}") else() set(PROJECT_VERSION_TWEAK_NUM 0) endif() ############# # Text data # ############# function(fastfetch_encode_c_string STR OUTVAR) string(REGEX REPLACE "\n$" "" TEMP "${STR}") # Remove trailing newline string(REPLACE "\\" "\\\\" TEMP "${TEMP}") # Escape backslashes string(REPLACE "\n" "\\n" TEMP "${TEMP}") # Replace newlines with \n string(REPLACE "\"" "\\\"" TEMP "${TEMP}") # Replace quotes with \" set(${OUTVAR} "\"${TEMP}\"" PARENT_SCOPE) endfunction(fastfetch_encode_c_string) function(fastfetch_load_text FILENAME OUTVAR) file(READ "${FILENAME}" TEMP) fastfetch_encode_c_string("${TEMP}" TEMP) set(${OUTVAR} "${TEMP}" PARENT_SCOPE) endfunction(fastfetch_load_text) find_package(Python) if(Python_FOUND) message(STATUS "Minifying 'help.json'") execute_process(COMMAND ${Python_EXECUTABLE} -c "import json,sys;json.dump(json.load(sys.stdin),sys.stdout,separators=(',',':'))" INPUT_FILE "${CMAKE_CURRENT_SOURCE_DIR}/src/data/help.json" OUTPUT_VARIABLE DATATEXT_JSON_HELP) if(DATATEXT_JSON_HELP STREQUAL "") message(ERROR "DATATEXT_JSON_HELP is empty, which should not happen!") endif() else() message(WARNING "Python3 is not found, 'help.json' will not be minified") file(READ "src/data/help.json" DATATEXT_JSON_HELP) endif() if(ENABLE_EMBEDDED_PCIIDS AND NOT EXISTS "${PROJECT_BINARY_DIR}/fastfetch_pciids.c.inc") if(Python_FOUND) if(NOT EXISTS "${PROJECT_BINARY_DIR}/pci.ids") message(STATUS "'${PROJECT_BINARY_DIR}/pci.ids' is missing, downloading...") file(DOWNLOAD "https://pci-ids.ucw.cz/v2.2/pci.ids" "${PROJECT_BINARY_DIR}/pci.ids") endif() message(STATUS "Generating 'fastfetch_pciids.c.inc'") execute_process(COMMAND ${Python_EXECUTABLE} "${CMAKE_CURRENT_SOURCE_DIR}/scripts/gen-pciids.py" "${PROJECT_BINARY_DIR}/pci.ids" OUTPUT_FILE "${PROJECT_BINARY_DIR}/fastfetch_pciids.c.inc" RESULT_VARIABLE PYTHON_PCIIDS_RETCODE) if(NOT PYTHON_PCIIDS_RETCODE EQUAL 0) file(REMOVE "${PROJECT_BINARY_DIR}/fastfetch_pciids.c.inc") message(WARNING "Failed to generate 'fastfetch_pciids.c.inc'") set(ENABLE_EMBEDDED_PCIIDS OFF) endif() else() message(WARNING "Python3 is not found, 'fastfetch_pciids.c.inc' will not be generated") set(ENABLE_EMBEDDED_PCIIDS OFF) endif() endif() if(ENABLE_EMBEDDED_AMDGPUIDS AND NOT EXISTS "${PROJECT_BINARY_DIR}/fastfetch_amdgpuids.c.inc") if(Python_FOUND) if(NOT EXISTS "${PROJECT_BINARY_DIR}/amdgpu.ids") message(STATUS "'${PROJECT_BINARY_DIR}/amdgpu.ids' is missing, downloading...") file(DOWNLOAD "https://gitlab.freedesktop.org/mesa/drm/-/raw/main/data/amdgpu.ids" "${PROJECT_BINARY_DIR}/amdgpu.ids") endif() message(STATUS "Generating 'fastfetch_amdgpuids.c.inc'") execute_process(COMMAND ${Python_EXECUTABLE} "${CMAKE_CURRENT_SOURCE_DIR}/scripts/gen-amdgpuids.py" "${PROJECT_BINARY_DIR}/amdgpu.ids" OUTPUT_FILE "${PROJECT_BINARY_DIR}/fastfetch_amdgpuids.c.inc" RESULT_VARIABLE PYTHON_AMDGPUIDS_RETCODE) if(NOT PYTHON_AMDGPUIDS_RETCODE EQUAL 0) file(REMOVE "${PROJECT_BINARY_DIR}/fastfetch_amdgpuids.c.inc") message(WARNING "Failed to generate 'fastfetch_amdgpuids.c.inc'") set(ENABLE_EMBEDDED_AMDGPUIDS OFF) endif() else() message(WARNING "Python3 is not found, 'fastfetch_amdgpuids.c.inc' will not be generated") set(ENABLE_EMBEDDED_AMDGPUIDS OFF) endif() endif() if(Python_FOUND) message(STATUS "Generating 'fastfetch.1'") execute_process(COMMAND ${Python_EXECUTABLE} "${CMAKE_CURRENT_SOURCE_DIR}/scripts/gen-man.py" OUTPUT_FILE "${PROJECT_BINARY_DIR}/fastfetch.1" RESULT_VARIABLE PYTHON_MANPAGE_RETCODE) if(NOT PYTHON_MANPAGE_RETCODE EQUAL 0) message(FATAL_ERROR "Failed to generate 'fastfetch.1'") endif() else() message(WARNING "Python3 is not found, use basic 'fastfetch.1.in' instead") string(TIMESTAMP FASTFETCH_BUILD_DATE "%d %B %Y" UTC) configure_file(doc/fastfetch.1.in fastfetch.1 @ONLY) endif() fastfetch_encode_c_string("${DATATEXT_JSON_HELP}" DATATEXT_JSON_HELP) fastfetch_load_text(src/data/structure.txt DATATEXT_STRUCTURE) configure_file(src/fastfetch_config.h.in fastfetch_config.h @ONLY) configure_file(src/fastfetch_datatext.h.in fastfetch_datatext.h @ONLY) if(APPLE) configure_file(src/common/apple/Info.plist.in Info.plist @ONLY) endif() #################### # Ascii image data # #################### file(GLOB LOGO_FILES CONFIGURE_DEPENDS "src/logo/ascii/*.txt") set(LOGO_BUILTIN_H "#pragma once\n#pragma GCC diagnostic ignored \"-Wtrigraphs\"\n\n") foreach(file ${LOGO_FILES}) fastfetch_load_text("${file}" content) get_filename_component(file "${file}" NAME_WE) string(TOUPPER "${file}" file) set(LOGO_BUILTIN_H "${LOGO_BUILTIN_H}#define FASTFETCH_DATATEXT_LOGO_${file} ${content}\n") endforeach() file(GENERATE OUTPUT logo_builtin.h CONTENT "${LOGO_BUILTIN_H}") ####################### # libfastfetch target # ####################### set(LIBFASTFETCH_SRC src/common/impl/commandoption.c src/common/impl/duration.c src/common/impl/font.c src/common/impl/format.c src/common/impl/frequency.c src/common/impl/init.c src/common/impl/jsonconfig.c src/common/impl/library.c src/common/impl/netif.c src/common/impl/networking_common.c src/common/impl/option.c src/common/impl/parsing.c src/common/impl/percent.c src/common/impl/printing.c src/common/impl/properties.c src/common/impl/settings.c src/common/impl/size.c src/common/impl/temps.c src/common/impl/time.c src/common/impl/edidHelper.c src/common/impl/base64.c src/common/impl/FFlist.c src/common/impl/FFstrbuf.c src/common/impl/path.c src/common/impl/FFPlatform.c src/common/impl/smbiosHelper.c src/detection/bluetoothradio/bluetoothradio.c src/detection/bootmgr/bootmgr.c src/detection/chassis/chassis.c src/detection/cpu/cpu.c src/detection/cpuusage/cpuusage.c src/detection/command/command.c src/detection/disk/disk.c src/detection/diskio/diskio.c src/detection/displayserver/displayserver.c src/detection/editor/editor.c src/detection/font/font.c src/detection/gpu/gpu.c src/detection/media/media.c src/detection/netio/netio.c src/detection/opencl/opencl.c src/detection/opengl/opengl_shared.c src/detection/os/os.c src/detection/packages/packages.c src/detection/physicalmemory/physicalmemory.c src/detection/publicip/publicip.c src/detection/terminaltheme/terminaltheme.c src/detection/terminalfont/terminalfont.c src/detection/terminalshell/terminalshell.c src/detection/version/version.c src/detection/vulkan/vulkan.c src/detection/weather/weather.c src/detection/zpool/zpool.c src/logo/builtin.c src/logo/image/im6.c src/logo/image/im7.c src/logo/image/image.c src/logo/logo.c src/modules/battery/battery.c src/modules/bios/bios.c src/modules/bluetooth/bluetooth.c src/modules/bluetoothradio/bluetoothradio.c src/modules/board/board.c src/modules/bootmgr/bootmgr.c src/modules/brightness/brightness.c src/modules/break/break.c src/modules/btrfs/btrfs.c src/modules/camera/camera.c src/modules/chassis/chassis.c src/modules/colors/colors.c src/modules/cpu/cpu.c src/modules/cpucache/cpucache.c src/modules/cpuusage/cpuusage.c src/modules/cursor/cursor.c src/modules/custom/custom.c src/modules/command/command.c src/modules/datetime/datetime.c src/modules/de/de.c src/modules/disk/disk.c src/modules/diskio/diskio.c src/modules/dns/dns.c src/modules/editor/editor.c src/modules/font/font.c src/modules/gpu/gpu.c src/modules/host/host.c src/modules/icons/icons.c src/modules/initsystem/initsystem.c src/modules/gamepad/gamepad.c src/modules/kernel/kernel.c src/modules/keyboard/keyboard.c src/modules/lm/lm.c src/modules/loadavg/loadavg.c src/modules/locale/locale.c src/modules/localip/localip.c src/modules/logo/logo.c src/modules/memory/memory.c src/modules/monitor/monitor.c src/modules/netio/netio.c src/modules/opencl/opencl.c src/modules/opengl/opengl.c src/modules/os/os.c src/modules/packages/packages.c src/modules/physicaldisk/physicaldisk.c src/modules/physicalmemory/physicalmemory.c src/modules/processes/processes.c src/modules/player/player.c src/modules/poweradapter/poweradapter.c src/modules/publicip/publicip.c src/modules/display/display.c src/modules/separator/separator.c src/modules/shell/shell.c src/modules/sound/sound.c src/modules/swap/swap.c src/modules/media/media.c src/modules/mouse/mouse.c src/modules/terminal/terminal.c src/modules/terminaltheme/terminaltheme.c src/modules/terminalfont/terminalfont.c src/modules/terminalsize/terminalsize.c src/modules/theme/theme.c src/modules/title/title.c src/modules/tpm/tpm.c src/modules/uptime/uptime.c src/modules/users/users.c src/modules/version/version.c src/modules/vulkan/vulkan.c src/modules/wallpaper/wallpaper.c src/modules/weather/weather.c src/modules/wifi/wifi.c src/modules/wm/wm.c src/modules/wmtheme/wmtheme.c src/modules/zpool/zpool.c src/modules/modules.c src/options/display.c src/options/logo.c src/options/general.c ) if(LINUX) list(APPEND LIBFASTFETCH_SRC src/common/impl/dbus.c src/common/impl/io_unix.c src/common/impl/netif_linux.c src/common/impl/networking_linux.c src/common/impl/processing_linux.c src/common/impl/FFPlatform_unix.c src/common/impl/binary_linux.c src/common/impl/kmod_linux.c src/detection/battery/battery_linux.c src/detection/bios/bios_linux.c src/detection/board/board_linux.c src/detection/bootmgr/bootmgr_linux.c src/detection/brightness/brightness_linux.c src/detection/btrfs/btrfs_linux.c src/detection/chassis/chassis_linux.c src/detection/cpu/cpu_linux.c src/detection/cpucache/cpucache_linux.c src/detection/cpuusage/cpuusage_linux.c src/detection/cursor/cursor_linux.c src/detection/bluetooth/bluetooth_linux.c src/detection/bluetoothradio/bluetoothradio_linux.c src/detection/disk/disk_linux.c src/detection/dns/dns_linux.c src/detection/physicaldisk/physicaldisk_linux.c src/detection/physicalmemory/physicalmemory_linux.c src/detection/diskio/diskio_linux.c src/detection/displayserver/linux/displayserver_linux.c src/detection/displayserver/linux/common.c src/detection/displayserver/linux/drm.c src/detection/displayserver/linux/wayland/wayland.c src/detection/displayserver/linux/wayland/global-output.c src/detection/displayserver/linux/wayland/zwlr-output.c src/detection/displayserver/linux/wayland/kde-output.c src/detection/displayserver/linux/wayland/wlr-output-management-unstable-v1-protocol.c src/detection/displayserver/linux/wayland/kde-output-device-v2-protocol.c src/detection/displayserver/linux/wayland/kde-output-order-v1-protocol.c src/detection/displayserver/linux/wayland/xdg-output-unstable-v1-protocol.c src/detection/displayserver/linux/wmde.c src/detection/displayserver/linux/xcb.c src/detection/displayserver/linux/xlib.c src/detection/font/font_linux.c src/detection/gpu/gpu_linux.c src/detection/gpu/gpu_drm.c src/detection/gpu/gpu_pci.c src/detection/gtk_qt/gtk.c src/detection/host/host_linux.c src/detection/host/host_mac.c src/detection/icons/icons_linux.c src/detection/initsystem/initsystem_linux.c src/detection/keyboard/keyboard_linux.c src/detection/libc/libc_linux.c src/detection/lm/lm_linux.c src/detection/loadavg/loadavg_linux.c src/detection/locale/locale_linux.c src/detection/localip/localip_linux.c src/detection/gamepad/gamepad_linux.c src/detection/media/media_linux.c src/detection/memory/memory_linux.c src/detection/mouse/mouse_linux.c src/detection/netio/netio_linux.c src/detection/opengl/opengl_linux.c src/detection/os/os_linux.c src/detection/packages/packages_linux.c src/detection/packages/packages_nix.c src/detection/poweradapter/poweradapter_linux.c src/detection/processes/processes_linux.c src/detection/gtk_qt/qt.c src/detection/sound/sound_linux.c src/detection/swap/swap_linux.c src/detection/terminalfont/terminalfont_linux.c src/detection/terminalshell/terminalshell_linux.c src/detection/terminalsize/terminalsize_linux.c src/detection/theme/theme_linux.c src/detection/tpm/tpm_linux.c src/detection/uptime/uptime_linux.c src/detection/users/users_linux.c src/detection/wallpaper/wallpaper_linux.c src/detection/wifi/wifi_linux.c src/detection/wm/wm_linux.c src/detection/de/de_linux.c src/detection/wmtheme/wmtheme_linux.c src/detection/camera/camera_linux.c ) elseif(ANDROID) list(APPEND LIBFASTFETCH_SRC src/common/impl/dbus.c src/common/impl/io_unix.c src/common/impl/netif_linux.c src/common/impl/networking_linux.c src/common/impl/processing_linux.c src/common/impl/FFPlatform_unix.c src/common/impl/binary_linux.c src/common/impl/kmod_linux.c src/detection/battery/battery_android.c src/detection/bios/bios_android.c src/detection/bluetooth/bluetooth_nosupport.c src/detection/bluetoothradio/bluetoothradio_nosupport.c src/detection/board/board_android.c src/detection/bootmgr/bootmgr_nosupport.c src/detection/brightness/brightness_nosupport.c src/detection/btrfs/btrfs_nosupport.c src/detection/chassis/chassis_nosupport.c src/detection/cpu/cpu_linux.c src/detection/cpucache/cpucache_linux.c src/detection/cursor/cursor_linux.c src/detection/cpuusage/cpuusage_linux.c src/detection/disk/disk_linux.c src/detection/dns/dns_linux.c src/detection/physicaldisk/physicaldisk_linux.c src/detection/physicalmemory/physicalmemory_nosupport.c src/detection/diskio/diskio_linux.c src/detection/displayserver/displayserver_android.c src/detection/displayserver/linux/common.c src/detection/displayserver/linux/wmde.c src/detection/displayserver/linux/xcb.c src/detection/displayserver/linux/xlib.c src/detection/gtk_qt/gtk.c src/detection/gtk_qt/qt.c src/detection/font/font_linux.c src/detection/gpu/gpu_android.c src/detection/host/host_android.c src/detection/icons/icons_linux.c src/detection/initsystem/initsystem_linux.c src/detection/keyboard/keyboard_nosupport.c src/detection/libc/libc_android.c src/detection/lm/lm_nosupport.c src/detection/loadavg/loadavg_linux.c src/detection/locale/locale_linux.c src/detection/localip/localip_linux.c src/detection/gamepad/gamepad_nosupport.c src/detection/media/media_linux.c src/detection/memory/memory_linux.c src/detection/mouse/mouse_nosupport.c src/detection/netio/netio_linux.c src/detection/opengl/opengl_linux.c src/detection/os/os_android.c src/detection/packages/packages_linux.c src/detection/packages/packages_nix.c src/detection/poweradapter/poweradapter_nosupport.c src/detection/processes/processes_linux.c src/detection/sound/sound_linux.c src/detection/swap/swap_linux.c src/detection/terminalfont/terminalfont_android.c src/detection/terminalfont/terminalfont_linux.c src/detection/terminalshell/terminalshell_linux.c src/detection/terminalsize/terminalsize_linux.c src/detection/theme/theme_linux.c src/detection/tpm/tpm_nosupport.c src/detection/uptime/uptime_linux.c src/detection/users/users_linux.c src/detection/wallpaper/wallpaper_linux.c src/detection/wifi/wifi_android.c src/detection/wm/wm_linux.c src/detection/de/de_linux.c src/detection/wmtheme/wmtheme_linux.c src/detection/camera/camera_android.c ) elseif(FreeBSD) list(APPEND LIBFASTFETCH_SRC src/common/impl/dbus.c src/common/impl/io_unix.c src/common/impl/netif_bsd.c src/common/impl/networking_linux.c src/common/impl/processing_linux.c src/common/impl/sysctl.c src/common/impl/FFPlatform_unix.c src/common/impl/binary_linux.c src/common/impl/kmod_bsd.c src/detection/battery/battery_bsd.c src/detection/bios/bios_bsd.c src/detection/bluetoothradio/bluetoothradio_nosupport.c src/detection/board/board_bsd.c src/detection/bootmgr/bootmgr_bsd.c src/detection/brightness/brightness_bsd.c src/detection/btrfs/btrfs_nosupport.c src/detection/chassis/chassis_bsd.c src/detection/cpu/cpu_bsd.c src/detection/cpucache/cpucache_shared.c src/detection/cpuusage/cpuusage_bsd.c src/detection/cursor/cursor_linux.c src/detection/disk/disk_bsd.c src/detection/dns/dns_linux.c src/detection/physicaldisk/physicaldisk_bsd.c src/detection/physicalmemory/physicalmemory_linux.c src/detection/diskio/diskio_bsd.c src/detection/displayserver/linux/displayserver_linux.c src/detection/displayserver/linux/common.c src/detection/displayserver/linux/drm.c src/detection/displayserver/linux/wayland/wayland.c src/detection/displayserver/linux/wayland/global-output.c src/detection/displayserver/linux/wayland/zwlr-output.c src/detection/displayserver/linux/wayland/kde-output.c src/detection/displayserver/linux/wayland/wlr-output-management-unstable-v1-protocol.c src/detection/displayserver/linux/wayland/kde-output-device-v2-protocol.c src/detection/displayserver/linux/wayland/kde-output-order-v1-protocol.c src/detection/displayserver/linux/wayland/xdg-output-unstable-v1-protocol.c src/detection/displayserver/linux/wmde.c src/detection/displayserver/linux/xcb.c src/detection/displayserver/linux/xlib.c src/detection/font/font_linux.c src/detection/gpu/gpu_bsd.c src/detection/gpu/gpu_drm.c src/detection/gpu/gpu_pci.c src/detection/gtk_qt/gtk.c src/detection/host/host_bsd.c src/detection/host/host_mac.c src/detection/lm/lm_linux.c src/detection/icons/icons_linux.c src/detection/initsystem/initsystem_linux.c src/detection/keyboard/keyboard_bsd.c src/detection/libc/libc_bsd.c src/detection/loadavg/loadavg_bsd.c src/detection/locale/locale_linux.c src/detection/localip/localip_linux.c src/detection/gamepad/gamepad_bsd.c src/detection/media/media_linux.c src/detection/memory/memory_bsd.c src/detection/mouse/mouse_bsd.c src/detection/netio/netio_bsd.c src/detection/opengl/opengl_linux.c src/detection/os/os_linux.c src/detection/packages/packages_bsd.c src/detection/poweradapter/poweradapter_nosupport.c src/detection/processes/processes_bsd.c src/detection/gtk_qt/qt.c src/detection/sound/sound_bsd.c src/detection/swap/swap_bsd.c src/detection/terminalfont/terminalfont_linux.c src/detection/terminalshell/terminalshell_linux.c src/detection/terminalsize/terminalsize_linux.c src/detection/theme/theme_linux.c src/detection/tpm/tpm_bsd.c src/detection/uptime/uptime_bsd.c src/detection/users/users_linux.c src/detection/wallpaper/wallpaper_linux.c src/detection/wm/wm_linux.c src/detection/de/de_linux.c src/detection/wmtheme/wmtheme_linux.c src/detection/camera/camera_linux.c ) if(DragonFly) list(APPEND LIBFASTFETCH_SRC src/detection/bluetooth/bluetooth_nosupport.c src/detection/wifi/wifi_nosupport.c ) else() list(APPEND LIBFASTFETCH_SRC src/detection/bluetooth/bluetooth_bsd.c src/detection/wifi/wifi_bsd.c ) endif() elseif(NetBSD) list(APPEND LIBFASTFETCH_SRC src/common/impl/dbus.c src/common/impl/io_unix.c src/common/impl/netif_bsd.c src/common/impl/networking_linux.c src/common/impl/processing_linux.c src/common/impl/sysctl.c src/common/impl/FFPlatform_unix.c src/common/impl/binary_linux.c src/common/impl/kmod_nbsd.c src/detection/battery/battery_nbsd.c src/detection/bios/bios_nbsd.c src/detection/bluetooth/bluetooth_bsd.c src/detection/bluetoothradio/bluetoothradio_nosupport.c src/detection/board/board_nbsd.c src/detection/bootmgr/bootmgr_bsd.c src/detection/brightness/brightness_nbsd.c src/detection/btrfs/btrfs_nosupport.c src/detection/chassis/chassis_nbsd.c src/detection/cpu/cpu_nbsd.c src/detection/cpucache/cpucache_shared.c src/detection/cpuusage/cpuusage_bsd.c src/detection/cursor/cursor_linux.c src/detection/disk/disk_bsd.c src/detection/dns/dns_linux.c src/detection/physicaldisk/physicaldisk_nosupport.c src/detection/physicalmemory/physicalmemory_linux.c src/detection/diskio/diskio_nbsd.c src/detection/displayserver/linux/displayserver_linux.c src/detection/displayserver/linux/common.c src/detection/displayserver/linux/drm.c src/detection/displayserver/linux/wayland/wayland.c src/detection/displayserver/linux/wayland/global-output.c src/detection/displayserver/linux/wayland/zwlr-output.c src/detection/displayserver/linux/wayland/kde-output.c src/detection/displayserver/linux/wayland/wlr-output-management-unstable-v1-protocol.c src/detection/displayserver/linux/wayland/kde-output-device-v2-protocol.c src/detection/displayserver/linux/wayland/kde-output-order-v1-protocol.c src/detection/displayserver/linux/wayland/xdg-output-unstable-v1-protocol.c src/detection/displayserver/linux/wmde.c src/detection/displayserver/linux/xcb.c src/detection/displayserver/linux/xlib.c src/detection/font/font_linux.c src/detection/gpu/gpu_nbsd.c src/detection/gpu/gpu_pci.c src/detection/gtk_qt/gtk.c src/detection/host/host_nbsd.c src/detection/lm/lm_linux.c src/detection/icons/icons_linux.c src/detection/initsystem/initsystem_linux.c src/detection/keyboard/keyboard_nosupport.c src/detection/libc/libc_nosupport.c src/detection/loadavg/loadavg_bsd.c src/detection/locale/locale_linux.c src/detection/localip/localip_linux.c src/detection/gamepad/gamepad_nosupport.c src/detection/media/media_linux.c src/detection/memory/memory_nbsd.c src/detection/mouse/mouse_nosupport.c src/detection/netio/netio_bsd.c src/detection/opengl/opengl_linux.c src/detection/os/os_nbsd.c src/detection/packages/packages_nbsd.c src/detection/poweradapter/poweradapter_nosupport.c src/detection/processes/processes_nbsd.c src/detection/gtk_qt/qt.c src/detection/sound/sound_nbsd.c src/detection/swap/swap_obsd.c src/detection/terminalfont/terminalfont_linux.c src/detection/terminalshell/terminalshell_linux.c src/detection/terminalsize/terminalsize_linux.c src/detection/theme/theme_linux.c src/detection/tpm/tpm_nosupport.c src/detection/uptime/uptime_bsd.c src/detection/users/users_linux.c src/detection/wallpaper/wallpaper_linux.c src/detection/wifi/wifi_nbsd.c src/detection/wm/wm_linux.c src/detection/de/de_linux.c src/detection/wmtheme/wmtheme_linux.c src/detection/camera/camera_linux.c ) elseif(OpenBSD) list(APPEND LIBFASTFETCH_SRC src/common/impl/dbus.c src/common/impl/io_unix.c src/common/impl/netif_bsd.c src/common/impl/networking_linux.c src/common/impl/processing_linux.c src/common/impl/sysctl.c src/common/impl/FFPlatform_unix.c src/common/impl/binary_linux.c src/common/impl/smbiosHelper.c src/common/impl/kmod_nosupport.c src/detection/battery/battery_obsd.c src/detection/bios/bios_windows.c src/detection/bluetooth/bluetooth_nosupport.c src/detection/bluetoothradio/bluetoothradio_nosupport.c src/detection/board/board_windows.c src/detection/bootmgr/bootmgr_bsd.c src/detection/brightness/brightness_obsd.c src/detection/btrfs/btrfs_nosupport.c src/detection/chassis/chassis_windows.c src/detection/cpu/cpu_obsd.c src/detection/cpucache/cpucache_shared.c src/detection/cpuusage/cpuusage_bsd.c src/detection/cursor/cursor_linux.c src/detection/disk/disk_bsd.c src/detection/dns/dns_linux.c src/detection/physicaldisk/physicaldisk_nosupport.c src/detection/physicalmemory/physicalmemory_linux.c src/detection/diskio/diskio_obsd.c src/detection/displayserver/linux/displayserver_linux.c src/detection/displayserver/linux/common.c src/detection/displayserver/linux/drm.c src/detection/displayserver/linux/wayland/wayland.c src/detection/displayserver/linux/wayland/global-output.c src/detection/displayserver/linux/wayland/zwlr-output.c src/detection/displayserver/linux/wayland/kde-output.c src/detection/displayserver/linux/wayland/wlr-output-management-unstable-v1-protocol.c src/detection/displayserver/linux/wayland/kde-output-device-v2-protocol.c src/detection/displayserver/linux/wayland/kde-output-order-v1-protocol.c src/detection/displayserver/linux/wayland/xdg-output-unstable-v1-protocol.c src/detection/displayserver/linux/wmde.c src/detection/displayserver/linux/xcb.c src/detection/displayserver/linux/xlib.c src/detection/font/font_linux.c src/detection/gpu/gpu_pci.c src/detection/gpu/gpu_obsd.c src/detection/gtk_qt/gtk.c src/detection/host/host_obsd.c src/detection/lm/lm_nosupport.c src/detection/icons/icons_linux.c src/detection/initsystem/initsystem_linux.c src/detection/keyboard/keyboard_nosupport.c src/detection/libc/libc_nosupport.c src/detection/loadavg/loadavg_bsd.c src/detection/locale/locale_linux.c src/detection/localip/localip_linux.c src/detection/gamepad/gamepad_nosupport.c src/detection/media/media_linux.c src/detection/memory/memory_obsd.c src/detection/mouse/mouse_nosupport.c src/detection/netio/netio_bsd.c src/detection/opengl/opengl_linux.c src/detection/os/os_obsd.c src/detection/packages/packages_obsd.c src/detection/poweradapter/poweradapter_nosupport.c src/detection/processes/processes_obsd.c src/detection/gtk_qt/qt.c src/detection/sound/sound_obsd.c src/detection/swap/swap_obsd.c src/detection/terminalfont/terminalfont_linux.c src/detection/terminalshell/terminalshell_linux.c src/detection/terminalsize/terminalsize_linux.c src/detection/theme/theme_linux.c src/detection/tpm/tpm_nosupport.c src/detection/uptime/uptime_bsd.c src/detection/users/users_obsd.c src/detection/wallpaper/wallpaper_linux.c src/detection/wifi/wifi_obsd.c src/detection/wm/wm_linux.c src/detection/de/de_linux.c src/detection/wmtheme/wmtheme_linux.c src/detection/camera/camera_linux.c ) elseif(APPLE) list(APPEND LIBFASTFETCH_SRC src/common/impl/io_unix.c src/common/impl/netif_apple.c src/common/impl/networking_linux.c src/common/impl/processing_linux.c src/common/impl/sysctl.c src/common/impl/FFPlatform_unix.c src/common/impl/binary_apple.c src/common/impl/kmod_apple.c src/common/apple/cf_helpers.c src/common/apple/osascript.m src/common/apple/smc_temps.c src/detection/battery/battery_apple.c src/detection/bios/bios_apple.c src/detection/bluetooth/bluetooth_apple.m src/detection/bluetoothradio/bluetoothradio_apple.m src/detection/board/board_apple.c src/detection/bootmgr/bootmgr_apple.c src/detection/brightness/brightness_apple.c src/detection/btrfs/btrfs_nosupport.c src/detection/chassis/chassis_apple.c src/detection/cpu/cpu_apple.c src/detection/cpucache/cpucache_apple.c src/detection/cpuusage/cpuusage_apple.c src/detection/cursor/cursor_apple.m src/detection/disk/disk_bsd.c src/detection/dns/dns_apple.c src/detection/physicaldisk/physicaldisk_apple.c src/detection/physicalmemory/physicalmemory_apple.m src/detection/diskio/diskio_apple.c src/detection/displayserver/displayserver_apple.c src/detection/font/font_apple.m src/detection/gpu/gpu_apple.c src/detection/gpu/gpu_apple.m src/detection/host/host_apple.c src/detection/host/host_mac.c src/detection/icons/icons_nosupport.c src/detection/initsystem/initsystem_linux.c src/detection/keyboard/keyboard_apple.c src/detection/lm/lm_nosupport.c src/detection/loadavg/loadavg_bsd.c src/detection/libc/libc_apple.c src/detection/locale/locale_linux.c src/detection/localip/localip_linux.c src/detection/gamepad/gamepad_apple.c src/detection/media/media_apple.m src/detection/memory/memory_apple.c src/detection/mouse/mouse_apple.c src/detection/netio/netio_apple.c src/detection/opengl/opengl_apple.c src/detection/os/os_apple.m src/detection/packages/packages_apple.c src/detection/packages/packages_nix.c src/detection/poweradapter/poweradapter_apple.c src/detection/processes/processes_bsd.c src/detection/sound/sound_apple.c src/detection/swap/swap_apple.c src/detection/terminalfont/terminalfont_apple.m src/detection/terminalshell/terminalshell_linux.c src/detection/terminalsize/terminalsize_linux.c src/detection/theme/theme_apple.c src/detection/tpm/tpm_apple.c src/detection/uptime/uptime_bsd.c src/detection/users/users_linux.c src/detection/wallpaper/wallpaper_apple.m src/detection/wifi/wifi_apple.m src/detection/wm/wm_apple.m src/detection/de/de_nosupport.c src/detection/wmtheme/wmtheme_apple.m src/detection/camera/camera_apple.m ) elseif(WIN32) list(APPEND LIBFASTFETCH_SRC src/common/impl/io_windows.c src/common/impl/netif_windows.c src/common/impl/networking_windows.c src/common/impl/processing_windows.c src/common/impl/FFPlatform_windows.c src/common/impl/binary_windows.c 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/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 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 src/detection/brightness/brightness_windows.cpp src/detection/btrfs/btrfs_nosupport.c src/detection/chassis/chassis_windows.c src/detection/cpu/cpu_windows.c src/detection/cpucache/cpucache_windows.c src/detection/cpuusage/cpuusage_windows.c src/detection/cursor/cursor_windows.c src/detection/disk/disk_windows.c src/detection/physicaldisk/physicaldisk_windows.c src/detection/diskio/diskio_windows.c src/detection/displayserver/displayserver_windows.c src/detection/dns/dns_windows.c src/detection/font/font_windows.c src/detection/gpu/gpu_windows.c src/detection/host/host_mac.c src/detection/host/host_windows.c src/detection/icons/icons_windows.c src/detection/initsystem/initsystem_nosupport.c src/detection/keyboard/keyboard_windows.c src/detection/libc/libc_windows.cpp src/detection/lm/lm_nosupport.c src/detection/loadavg/loadavg_nosupport.c 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/memory/memory_windows.c src/detection/mouse/mouse_windows.c src/detection/physicalmemory/physicalmemory_linux.c src/detection/netio/netio_windows.c src/detection/opengl/opengl_windows.c src/detection/os/os_windows.c src/detection/packages/packages_windows.c src/detection/poweradapter/poweradapter_nosupport.c src/detection/processes/processes_windows.c src/detection/sound/sound_windows.cpp src/detection/swap/swap_windows.c src/detection/terminalfont/terminalfont_windows.c src/detection/terminalshell/terminalshell_windows.c src/detection/terminalsize/terminalsize_windows.c src/detection/theme/theme_windows.c src/detection/tpm/tpm_windows.c src/detection/uptime/uptime_windows.c src/detection/users/users_windows.c src/detection/wallpaper/wallpaper_windows.c src/detection/wifi/wifi_windows.c src/detection/wm/wm_windows.c src/detection/de/de_nosupport.c src/detection/wmtheme/wmtheme_windows.c src/detection/camera/camera_windows.cpp ) elseif(SunOS) list(APPEND LIBFASTFETCH_SRC src/common/impl/dbus.c src/common/impl/io_unix.c src/common/impl/netif_apple.c src/common/impl/networking_linux.c src/common/impl/processing_linux.c src/common/impl/FFPlatform_unix.c src/common/impl/binary_linux.c src/common/impl/kmod_sunos.c src/detection/battery/battery_nosupport.c src/detection/bios/bios_windows.c src/detection/board/board_windows.c src/detection/bootmgr/bootmgr_nosupport.c src/detection/brightness/brightness_nosupport.c src/detection/btrfs/btrfs_nosupport.c src/detection/chassis/chassis_windows.c src/detection/cpu/cpu_sunos.c src/detection/cpucache/cpucache_shared.c src/detection/cpuusage/cpuusage_sunos.c src/detection/cursor/cursor_linux.c src/detection/bluetooth/bluetooth_nosupport.c src/detection/bluetoothradio/bluetoothradio_nosupport.c src/detection/disk/disk_sunos.c src/detection/dns/dns_linux.c src/detection/physicaldisk/physicaldisk_sunos.c src/detection/physicalmemory/physicalmemory_linux.c src/detection/diskio/diskio_sunos.c src/detection/displayserver/linux/displayserver_linux.c src/detection/displayserver/linux/common.c src/detection/displayserver/linux/drm.c src/detection/displayserver/linux/wayland/wayland.c src/detection/displayserver/linux/wayland/global-output.c src/detection/displayserver/linux/wayland/zwlr-output.c src/detection/displayserver/linux/wayland/kde-output.c src/detection/displayserver/linux/wayland/wlr-output-management-unstable-v1-protocol.c src/detection/displayserver/linux/wayland/kde-output-device-v2-protocol.c src/detection/displayserver/linux/wayland/kde-output-order-v1-protocol.c src/detection/displayserver/linux/wayland/xdg-output-unstable-v1-protocol.c src/detection/displayserver/linux/wmde.c src/detection/displayserver/linux/xcb.c src/detection/displayserver/linux/xlib.c src/detection/font/font_linux.c src/detection/gpu/gpu_sunos.c src/detection/gpu/gpu_pci.c src/detection/gtk_qt/gtk.c src/detection/host/host_windows.c src/detection/icons/icons_linux.c src/detection/initsystem/initsystem_linux.c src/detection/keyboard/keyboard_nosupport.c src/detection/libc/libc_nosupport.c src/detection/lm/lm_nosupport.c src/detection/loadavg/loadavg_sunos.c src/detection/locale/locale_linux.c src/detection/localip/localip_linux.c src/detection/gamepad/gamepad_nosupport.c src/detection/media/media_linux.c src/detection/memory/memory_sunos.c src/detection/mouse/mouse_nosupport.c src/detection/netio/netio_sunos.c src/detection/opengl/opengl_linux.c src/detection/os/os_sunos.c src/detection/packages/packages_sunos.c src/detection/poweradapter/poweradapter_nosupport.c src/detection/processes/processes_linux.c src/detection/gtk_qt/qt.c src/detection/sound/sound_sunos.c src/detection/swap/swap_sunos.c src/detection/terminalfont/terminalfont_linux.c src/detection/terminalshell/terminalshell_linux.c src/detection/terminalsize/terminalsize_linux.c src/detection/theme/theme_linux.c src/detection/tpm/tpm_nosupport.c src/detection/uptime/uptime_sunos.c src/detection/users/users_linux.c src/detection/wallpaper/wallpaper_linux.c src/detection/wifi/wifi_nosupport.c src/detection/wm/wm_nosupport.c src/detection/de/de_linux.c src/detection/wmtheme/wmtheme_linux.c src/detection/camera/camera_nosupport.c ) elseif(Haiku) list(APPEND LIBFASTFETCH_SRC src/common/impl/dbus.c src/common/impl/io_unix.c src/common/impl/netif_haiku.c src/common/impl/networking_linux.c src/common/impl/processing_linux.c src/common/impl/FFPlatform_unix.c src/common/impl/binary_linux.c src/common/impl/kmod_nosupport.c src/common/haiku/version.cpp src/detection/battery/battery_haiku.c src/detection/bios/bios_windows.c src/detection/board/board_windows.c src/detection/bootmgr/bootmgr_nosupport.c src/detection/brightness/brightness_nosupport.c src/detection/btrfs/btrfs_nosupport.c src/detection/chassis/chassis_windows.c src/detection/cpu/cpu_haiku.c src/detection/cpucache/cpucache_shared.c src/detection/cpuusage/cpuusage_haiku.c src/detection/cursor/cursor_nosupport.c src/detection/bluetooth/bluetooth_haiku.cpp src/detection/bluetoothradio/bluetoothradio_nosupport.c src/detection/disk/disk_haiku.cpp src/detection/dns/dns_linux.c src/detection/physicaldisk/physicaldisk_haiku.c src/detection/physicalmemory/physicalmemory_linux.c src/detection/diskio/diskio_nosupport.c src/detection/displayserver/displayserver_haiku.cpp src/detection/font/font_haiku.cpp src/detection/gpu/gpu_haiku.c src/detection/gpu/gpu_pci.c src/detection/gtk_qt/gtk.c src/detection/host/host_windows.c src/detection/icons/icons_nosupport.c src/detection/initsystem/initsystem_haiku.c src/detection/keyboard/keyboard_haiku.cpp src/detection/libc/libc_nosupport.c src/detection/lm/lm_nosupport.c src/detection/loadavg/loadavg_nosupport.c src/detection/locale/locale_linux.c src/detection/localip/localip_linux.c src/detection/gamepad/gamepad_haiku.cpp src/detection/media/media_linux.c src/detection/memory/memory_haiku.c src/detection/mouse/mouse_haiku.cpp src/detection/netio/netio_haiku.cpp src/detection/opengl/opengl_haiku.cpp src/detection/os/os_haiku.c src/detection/packages/packages_haiku.c src/detection/poweradapter/poweradapter_nosupport.c src/detection/processes/processes_haiku.c src/detection/gtk_qt/qt.c src/detection/sound/sound_haiku.cpp src/detection/swap/swap_haiku.c src/detection/terminalfont/terminalfont_linux.c src/detection/terminalshell/terminalshell_linux.c src/detection/terminalsize/terminalsize_linux.c src/detection/theme/theme_nosupport.c 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/wifi/wifi_nosupport.c src/detection/wm/wm_nosupport.c src/detection/de/de_nosupport.c src/detection/wmtheme/wmtheme_nosupport.c src/detection/camera/camera_nosupport.c ) elseif(GNU) list(APPEND LIBFASTFETCH_SRC src/common/impl/dbus.c src/common/impl/io_unix.c src/common/impl/netif_gnu.c src/common/impl/networking_linux.c src/common/impl/processing_linux.c src/common/impl/FFPlatform_unix.c src/common/impl/binary_linux.c src/common/impl/kmod_nosupport.c src/detection/battery/battery_nosupport.c src/detection/bios/bios_nosupport.c src/detection/board/board_nosupport.c src/detection/bootmgr/bootmgr_nosupport.c src/detection/brightness/brightness_nosupport.c src/detection/btrfs/btrfs_nosupport.c src/detection/chassis/chassis_nosupport.c src/detection/cpu/cpu_linux.c src/detection/cpucache/cpucache_nosupport.c src/detection/cpuusage/cpuusage_linux.c src/detection/cursor/cursor_linux.c src/detection/bluetooth/bluetooth_linux.c src/detection/bluetoothradio/bluetoothradio_linux.c src/detection/disk/disk_linux.c src/detection/dns/dns_linux.c src/detection/physicaldisk/physicaldisk_nosupport.c src/detection/physicalmemory/physicalmemory_nosupport.c src/detection/diskio/diskio_nosupport.c src/detection/displayserver/linux/displayserver_linux.c src/detection/displayserver/linux/common.c src/detection/displayserver/linux/drm.c src/detection/displayserver/linux/wayland/wayland.c src/detection/displayserver/linux/wayland/global-output.c src/detection/displayserver/linux/wayland/zwlr-output.c src/detection/displayserver/linux/wayland/wlr-output-management-unstable-v1-protocol.c src/detection/displayserver/linux/wmde.c src/detection/displayserver/linux/xcb.c src/detection/displayserver/linux/xlib.c src/detection/font/font_linux.c src/detection/gpu/gpu_gnu.c src/detection/gpu/gpu_pci.c src/detection/gtk_qt/gtk.c src/detection/host/host_nosupport.c src/detection/icons/icons_linux.c src/detection/initsystem/initsystem_linux.c src/detection/keyboard/keyboard_nosupport.c src/detection/libc/libc_linux.c src/detection/lm/lm_linux.c src/detection/loadavg/loadavg_linux.c src/detection/locale/locale_linux.c src/detection/localip/localip_linux.c src/detection/gamepad/gamepad_nosupport.c src/detection/media/media_linux.c src/detection/memory/memory_linux.c src/detection/mouse/mouse_nosupport.c src/detection/netio/netio_nosupport.c src/detection/opengl/opengl_linux.c src/detection/os/os_linux.c src/detection/packages/packages_linux.c src/detection/packages/packages_nix.c src/detection/poweradapter/poweradapter_nosupport.c src/detection/processes/processes_linux.c src/detection/gtk_qt/qt.c src/detection/sound/sound_linux.c src/detection/swap/swap_linux.c src/detection/terminalfont/terminalfont_linux.c src/detection/terminalshell/terminalshell_linux.c src/detection/terminalsize/terminalsize_linux.c src/detection/theme/theme_linux.c src/detection/tpm/tpm_nosupport.c src/detection/uptime/uptime_linux.c src/detection/users/users_linux.c src/detection/wallpaper/wallpaper_linux.c src/detection/wifi/wifi_nosupport.c src/detection/wm/wm_linux.c src/detection/de/de_linux.c src/detection/wmtheme/wmtheme_linux.c src/detection/camera/camera_nosupport.c ) endif() if(ENABLE_DIRECTX_HEADERS) message(STATUS "Enabling DirectX headers for WSL") list(APPEND LIBFASTFETCH_SRC src/detection/gpu/gpu_wsl.cpp) endif() # Proprietary GPU driver APIs if(LINUX OR FreeBSD OR WIN32) list(APPEND LIBFASTFETCH_SRC src/detection/gpu/gpu_nvidia.c) list(APPEND LIBFASTFETCH_SRC src/detection/gpu/gpu_mthreads.c) endif() if(WIN32) list(APPEND LIBFASTFETCH_SRC src/detection/gpu/gpu_intel.c) list(APPEND LIBFASTFETCH_SRC src/detection/gpu/gpu_amd.c) endif() include(CheckFunctionExists) check_function_exists(wcwidth HAVE_WCWIDTH) if(NOT HAVE_WCWIDTH) list(APPEND LIBFASTFETCH_SRC src/common/impl/wcwidth.c) endif() if(NOT WIN32) check_function_exists(pipe2 HAVE_PIPE2) endif() check_function_exists(memrchr HAVE_MEMRCHR) if(NOT HAVE_MEMRCHR) list(APPEND LIBFASTFETCH_SRC src/common/impl/memrchr.c) endif() if(ENABLE_SYSTEM_YYJSON) find_package(yyjson) if(yyjson_FOUND) message(STATUS "System provided yyjson is used") else() message(FATAL_ERROR "ENABLE_SYSTEM_YYJSON is set but system provided yyjson is not found") endif() else() list(APPEND LIBFASTFETCH_SRC src/3rdparty/yyjson/yyjson.c ) endif() add_library(libfastfetch OBJECT ${LIBFASTFETCH_SRC} ) include(CheckCSourceCompiles) check_c_source_compiles("int main(void){int arr[1];return _Countof(arr);}" COMPILER_SUPPORTS_COUNT_OF) if(COMPILER_SUPPORTS_COUNT_OF) message(STATUS "_Countof is supported by the compiler") target_compile_definitions(libfastfetch PUBLIC FF_SUPPORTS_COUNT_OF=1) else() message(STATUS "_Countof is NOT supported by the compiler") endif() if(yyjson_FOUND) target_compile_definitions(libfastfetch PUBLIC FF_USE_SYSTEM_YYJSON=1) target_link_libraries(libfastfetch PUBLIC yyjson::yyjson) # `target_link_libraries(yyjson::yyjson)` sets rpath implicitly endif() # Used for dlopen finding dylibs installed by homebrew # `/opt/homebrew/lib` is not on in dlopen search path by default if(APPLE AND BINARY_LINK_TYPE STREQUAL "dlopen") set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -Wl,-rpath,/opt/homebrew/lib -Wl,-rpath,/usr/local/lib") endif() if(ANDROID) if(CMAKE_SIZEOF_VOID_P EQUAL 8) set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -Wl,-rpath,/vendor/lib64 -Wl,-rpath,/system/lib64") else() set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -Wl,-rpath,/vendor/lib -Wl,-rpath,/system/lib") endif() endif() if(LINUX AND EXISTS "/lib/ld-musl-${CMAKE_HOST_SYSTEM_PROCESSOR}.so.1") execute_process(COMMAND "/lib/ld-musl-${CMAKE_HOST_SYSTEM_PROCESSOR}.so.1" ERROR_VARIABLE LD_MUSL_RESULT) if("${LD_MUSL_RESULT}" MATCHES "Version ([0-9]+\\.[0-9]+\\.[0-9]+)") target_compile_definitions(libfastfetch PUBLIC FF_MUSL_VERSION="${CMAKE_MATCH_1}") endif() endif() if(APPLE AND EXISTS "/usr/bin/otool") execute_process(COMMAND /usr/bin/otool -L /usr/bin/otool OUTPUT_VARIABLE OTOOL_OTOOL_RESULT) if("${OTOOL_OTOOL_RESULT}" MATCHES "libSystem\\.B\\.dylib \\(.*current version ([0-9]+\\.[0-9]+\\.[0-9]+)\\)") target_compile_definitions(libfastfetch PUBLIC FF_LIBSYSTEM_VERSION="${CMAKE_MATCH_1}") endif() endif() if(MidnightBSD AND EXISTS "/usr/bin/objdump") execute_process(COMMAND /bin/sh -c "/usr/bin/objdump -T /lib/libc.so.* | grep 'FBSD_[0-9][0-9]*\\.[0-9][0-9]*' -o | sort -Vru | head -1" OUTPUT_VARIABLE OBJDUMP_T_RESULT) if("${OBJDUMP_T_RESULT}" MATCHES "FBSD_([0-9]+\\.[0-9]+)") message(STATUS "Found FBSD ${CMAKE_MATCH_1}") target_compile_definitions(libfastfetch PUBLIC FF_FBSD_VERSION="${CMAKE_MATCH_1}") endif() elseif(FreeBSD AND EXISTS "/usr/local/bin/objdump") execute_process(COMMAND /bin/sh -c "/usr/local/bin/objdump -T /lib/libc.so.* | grep 'FBSD_[0-9][0-9]*\\.[0-9][0-9]*' -o | sort -Vru | head -1" OUTPUT_VARIABLE OBJDUMP_T_RESULT) if("${OBJDUMP_T_RESULT}" MATCHES "FBSD_([0-9]+\\.[0-9]+)") message(STATUS "Found FBSD ${CMAKE_MATCH_1}") target_compile_definitions(libfastfetch PUBLIC FF_FBSD_VERSION="${CMAKE_MATCH_1}") endif() elseif(DragonFly AND EXISTS "/usr/local/bin/objdump") execute_process(COMMAND /bin/sh -c "/usr/local/bin/objdump -T /lib/libc.so.* | grep 'DF[0-9][0-9]*\\.[0-9][0-9]*' -o | sort -Vru | head -1" OUTPUT_VARIABLE OBJDUMP_T_RESULT) if("${OBJDUMP_T_RESULT}" MATCHES "DF([0-9]+\\.[0-9]+)") message(STATUS "Found DF ${CMAKE_MATCH_1}") target_compile_definitions(libfastfetch PUBLIC FF_DF_VERSION="${CMAKE_MATCH_1}") endif() endif() if(LINUX) target_compile_definitions(libfastfetch PUBLIC _GNU_SOURCE _XOPEN_SOURCE _ATFILE_SOURCE __STDC_WANT_LIB_EXT1__ _FILE_OFFSET_BITS=64 _TIME_BITS=64) # "$<$:_FORTIFY_SOURCE=3>" 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>" elseif(APPLE) target_compile_definitions(libfastfetch PUBLIC _GNU_SOURCE _XOPEN_SOURCE __STDC_WANT_LIB_EXT1__ _FILE_OFFSET_BITS=64 _DARWIN_C_SOURCE) elseif(OpenBSD) target_compile_definitions(libfastfetch PUBLIC _XOPEN_SOURCE=700 _FILE_OFFSET_BITS=64 _BSD_SOURCE) set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -Wl,-rpath,/usr/X11R6/lib") # detect x11 lib path automatically elseif(DragonFly) target_compile_definitions(libfastfetch PUBLIC __FreeBSD__) elseif(SunOS) target_compile_definitions(libfastfetch PUBLIC _GNU_SOURCE __STDC_WANT_LIB_EXT1__ _FILE_OFFSET_BITS=64 __EXTENSIONS__ _POSIX_C_SOURCE) elseif(NetBSD) target_compile_definitions(libfastfetch PUBLIC _GNU_SOURCE) set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -Wno-char-subscripts") set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -Wl,-rpath,/usr/X11R7/lib -Wl,-rpath,/usr/pkg/lib") # ditto elseif(Haiku) target_compile_definitions(libfastfetch PUBLIC _GNU_SOURCE) elseif(GNU) # On Hurd PATH_MAX is not defined. Set an arbitrary limit as workaround. target_compile_definitions(libfastfetch PUBLIC _GNU_SOURCE PATH_MAX=4096 O_PATH=0) endif() if(APPLE) if(ENABLE_APPLE_MEMSIZE_USABLE) target_compile_definitions(libfastfetch PUBLIC FF_APPLE_MEMSIZE_USABLE=1) else() target_compile_definitions(libfastfetch PUBLIC FF_APPLE_MEMSIZE_USABLE=0) endif() endif() if(WIN32) check_function_exists(_msize HAVE_MSVC_MSIZE) if(HAVE_MSVC_MSIZE) target_compile_definitions(libfastfetch PUBLIC FF_HAVE_MSVC_MSIZE=1) endif() if(ENABLE_WIN81_COMPAT) target_compile_definitions(libfastfetch PUBLIC FF_WIN81_COMPAT=1) else() target_compile_definitions(libfastfetch PUBLIC FF_WIN81_COMPAT=0) endif() else() check_function_exists(malloc_usable_size HAVE_MALLOC_USABLE_SIZE) if(HAVE_MALLOC_USABLE_SIZE) target_compile_definitions(libfastfetch PUBLIC FF_HAVE_MALLOC_USABLE_SIZE=1) else() check_function_exists(malloc_size HAVE_MALLOC_SIZE) if(HAVE_MALLOC_SIZE) target_compile_definitions(libfastfetch PUBLIC FF_HAVE_MALLOC_SIZE=1) endif() endif() endif() if(FreeBSD OR OpenBSD OR NetBSD) include(CheckStructHasMember) set(CMAKE_REQUIRED_DEFINITIONS "-D_IFI_OQDROPS=1") CHECK_STRUCT_HAS_MEMBER("struct if_data" ifi_oqdrops net/if.h HAVE_IFI_OQDROPS LANGUAGE C) if(HAVE_IFI_OQDROPS) target_compile_definitions(libfastfetch PUBLIC _IFI_OQDROPS FF_HAVE_IFI_OQDROPS) endif() unset(CMAKE_REQUIRED_DEFINITIONS) endif() if(HAVE_WCWIDTH) target_compile_definitions(libfastfetch PUBLIC FF_HAVE_WCWIDTH) endif() if(HAVE_PIPE2) target_compile_definitions(libfastfetch PUBLIC FF_HAVE_PIPE2) endif() if(NOT "${CUSTOM_PCI_IDS_PATH}" STREQUAL "") message(STATUS "Custom file path of pci.ids: ${CUSTOM_PCI_IDS_PATH}") target_compile_definitions(libfastfetch PRIVATE FF_CUSTOM_PCI_IDS_PATH=${CUSTOM_PCI_IDS_PATH}) endif() if(NOT "${CUSTOM_AMDGPU_IDS_PATH}" STREQUAL "") message(STATUS "Custom file path of amdgpu.ids: ${CUSTOM_AMDGPU_IDS_PATH}") target_compile_definitions(libfastfetch PRIVATE FF_CUSTOM_AMDGPU_IDS_PATH=${CUSTOM_AMDGPU_IDS_PATH}) endif() if(NOT "${CUSTOM_OS_RELEASE_PATH}" STREQUAL "") message(STATUS "Custom file path of os_release: ${CUSTOM_OS_RELEASE_PATH}") target_compile_definitions(libfastfetch PRIVATE FF_CUSTOM_OS_RELEASE_PATH=${CUSTOM_OS_RELEASE_PATH}) endif() if(NOT "${CUSTOM_LSB_RELEASE_PATH}" STREQUAL "") message(STATUS "Custom file path of lsb_release: ${CUSTOM_LSB_RELEASE_PATH}") target_compile_definitions(libfastfetch PRIVATE FF_CUSTOM_LSB_RELEASE_PATH=${CUSTOM_LSB_RELEASE_PATH}) endif() if(NOT BINARY_LINK_TYPE STREQUAL "dlopen") message(STATUS "Enabling custom link type: ${BINARY_LINK_TYPE}") target_compile_definitions(libfastfetch PRIVATE FF_DISABLE_DLOPEN=1) if(NOT WIN32) set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -Wl,--copy-dt-needed-entries") endif() endif() function(ff_lib_enable VARNAME PKGCONFIG_NAMES CMAKE_NAME) if(NOT ENABLE_${VARNAME}) return() endif() if(PKG_CONFIG_FOUND) pkg_search_module(${VARNAME} QUIET ${PKGCONFIG_NAMES}) endif() if(NOT ${VARNAME}_FOUND) find_package(${CMAKE_NAME} QUIET) set(${VARNAME}_FOUND ${${CMAKE_NAME}_FOUND}) set(${VARNAME}_INCLUDE_DIRS ${${CMAKE_NAME}_INCLUDE_DIRS}) set(${VARNAME}_LIBRARIES ${${CMAKE_NAME}_LIBRARIES}) set(${VARNAME}_CFLAGS_OTHER ${${CMAKE_NAME}_CFLAGS_OTHER}) endif() if(NOT ${VARNAME}_FOUND) message(STATUS "Library: missing: ${VARNAME}") return() endif() message(STATUS "Library: found ${VARNAME}") target_compile_definitions(libfastfetch PRIVATE FF_HAVE_${VARNAME}=1) target_include_directories(libfastfetch PRIVATE ${${VARNAME}_INCLUDE_DIRS}) if(NOT BINARY_LINK_TYPE STREQUAL "dlopen") target_link_directories(libfastfetch PUBLIC ${${VARNAME}_LIBRARY_DIRS}) target_link_libraries(libfastfetch PRIVATE ${${VARNAME}_LIBRARIES}) endif() foreach(FLAG ${${VARNAME}_CFLAGS_OTHER}) if(FLAG MATCHES "-D.*") string(SUBSTRING ${FLAG} 2 -1 FLAG) target_compile_definitions(libfastfetch PRIVATE ${FLAG}) endif() endforeach() endfunction() ff_lib_enable(VULKAN "vulkan" "Vulkan" ) ff_lib_enable(WAYLAND "wayland-client" "WaylandClient" ) ff_lib_enable(XCB_RANDR "xcb-randr" "XcbRandr" ) ff_lib_enable(XRANDR "xrandr" "XRandr" ) ff_lib_enable(DRM "libdrm" "Libdrm" ) ff_lib_enable(DRM_AMDGPU "libdrm_amdgpu" "Libdrm_Amdgpu" ) ff_lib_enable(GIO "gio-2.0" "GIO" ) ff_lib_enable(DCONF "dconf" "DConf" ) ff_lib_enable(DBUS "dbus-1" "DBus" ) ff_lib_enable(SQLITE3 "sqlite3" "SQLite3" ) ff_lib_enable(RPM "rpm" "RPM" ) ff_lib_enable(IMAGEMAGICK7 "MagickCore-7.Q16HDRI;MagickCore-7.Q16;MagickCore-7;/usr/lib/imagemagick7/pkgconfig/MagickCore-7.Q16HDRI.pc;/usr/lib/imagemagick7/pkgconfig/MagickCore-7.Q16.pc;/usr/lib/imagemagick7/pkgconfig/MagickCore-7.pc" "ImageMagick7" ) ff_lib_enable(IMAGEMAGICK6 "MagickCore-6.Q16HDRI;MagickCore-6.Q16;MagickCore-6;/usr/lib/imagemagick6/pkgconfig/MagickCore-6.Q16HDRI.pc;/usr/lib/imagemagick6/pkgconfig/MagickCore-6.Q16.pc;/usr/lib/imagemagick6/pkgconfig/MagickCore-6.pc" "ImageMagick6" ) ff_lib_enable(ZLIB "zlib" "ZLIB" ) ff_lib_enable(CHAFA "chafa>=1.10" "Chafa" ) ff_lib_enable(EGL "egl" "EGL" ) if(NOT OpenBSD AND NOT NetBSD) ff_lib_enable(GLX "glx" "GLX" ) else() ff_lib_enable(GLX "gl" "GL" ) endif() ff_lib_enable(OPENCL "OpenCL" "OpenCL" ) ff_lib_enable(FREETYPE "freetype2" "FreeType2" ) ff_lib_enable(PULSE "libpulse" "Pulse" ) ff_lib_enable(DDCUTIL "ddcutil" "Ddcutil" ) ff_lib_enable(ELF "libelf" "libelf" ) ff_lib_enable(DIRECTX_HEADERS "DirectX-Headers" "DirectX-Headers" ) if(ENABLE_THREADS) target_compile_definitions(libfastfetch PRIVATE FF_HAVE_THREADS=1) if(CMAKE_USE_PTHREADS_INIT) #Threads::Threads is not set for WIN32 target_link_libraries(libfastfetch PRIVATE Threads::Threads) endif() endif() if(ENABLE_EMBEDDED_PCIIDS) target_compile_definitions(libfastfetch PRIVATE FF_HAVE_EMBEDDED_PCIIDS=1) endif() if(ENABLE_EMBEDDED_AMDGPUIDS) target_compile_definitions(libfastfetch PRIVATE FF_HAVE_EMBEDDED_AMDGPUIDS=1) endif() if(ENABLE_LIBZFS) target_compile_definitions(libfastfetch PRIVATE FF_HAVE_LIBZFS=1) if(NOT BINARY_LINK_TYPE STREQUAL "dlopen") target_link_libraries(libfastfetch PRIVATE "zfs" ) endif() endif() if(NOT HAVE_MEMRCHR) target_compile_definitions(libfastfetch PRIVATE FF_HAVE_CUSTOM_MEMRCHR=1) endif() if(LINUX) target_link_libraries(libfastfetch PRIVATE "m" ) if(ENABLE_DIRECTX_HEADERS) if(NOT BINARY_LINK_TYPE STREQUAL "dlopen") target_link_libraries(libfastfetch PRIVATE "/usr/lib/wsl/lib/libdxcore.so" ) endif() endif() elseif(APPLE) target_link_libraries(libfastfetch PRIVATE "-framework AVFoundation" PRIVATE "-framework Cocoa" PRIVATE "-framework CoreFoundation" PRIVATE "-framework CoreAudio" PRIVATE "-framework CoreMedia" PRIVATE "-framework CoreVideo" PRIVATE "-framework CoreWLAN" PRIVATE "-framework IOBluetooth" PRIVATE "-framework IOKit" PRIVATE "-framework Metal" PRIVATE "-framework OpenGL" PRIVATE "-framework OpenCL" PRIVATE "-framework SystemConfiguration" PRIVATE "-weak_framework CoreDisplay" PRIVATE "-F /System/Library/PrivateFrameworks" PRIVATE "-weak_framework DisplayServices" PRIVATE "-weak_framework MediaRemote" ) elseif(WIN32) target_link_libraries(libfastfetch PRIVATE "gdi32" PRIVATE "iphlpapi" PRIVATE "ole32" PRIVATE "oleaut32" PRIVATE "ws2_32" PRIVATE "ntdll" PRIVATE "version" PRIVATE "hid" PRIVATE "wtsapi32" PRIVATE "cfgmgr32" PRIVATE "winbrand" PRIVATE "secur32" ) if(NOT ENABLE_WIN81_COMPAT) target_link_libraries(libfastfetch PRIVATE "mincore" ) endif() elseif(FreeBSD) target_link_libraries(libfastfetch PRIVATE "m" PRIVATE "usbhid" PRIVATE "bluetooth" ) if(NOT DragonFly) target_link_libraries(libfastfetch PRIVATE "geom" ) else() target_link_libraries(libfastfetch PRIVATE "devstat" ) endif() elseif(OpenBSD) target_link_libraries(libfastfetch PRIVATE "m" PRIVATE "kvm" PRIVATE "sndio" ) elseif(NetBSD) target_link_libraries(libfastfetch PRIVATE "bluetooth" PRIVATE "m" PRIVATE "prop" ) elseif(SunOS) target_link_libraries(libfastfetch PRIVATE "m" PRIVATE "socket" PRIVATE "kstat" PRIVATE "proc" PRIVATE "devinfo" ) elseif(GNU) target_link_libraries(libfastfetch PRIVATE "m" ) elseif(ANDROID) target_link_libraries(libfastfetch PRIVATE "m" ) if(ENABLE_WORDEXP) # https://github.com/termux/termux-packages/pull/7056 CHECK_LIBRARY_EXISTS(-l:libandroid-wordexp.a wordexp "" HAVE_LIBANDROID_WORDEXP_STATIC) if(HAVE_LIBANDROID_WORDEXP_STATIC) target_link_libraries(libfastfetch PRIVATE -l:libandroid-wordexp.a ) else() CHECK_LIBRARY_EXISTS(android-wordexp wordexp "" HAVE_LIBANDROID_WORDEXP) if(HAVE_LIBANDROID_WORDEXP) target_link_libraries(libfastfetch PRIVATE android-wordexp ) endif() endif() endif() elseif(Haiku) target_link_libraries(libfastfetch PRIVATE "network" PRIVATE "bnetapi" PRIVATE "media" PRIVATE "device" PRIVATE "bluetooth" PRIVATE "GL" PRIVATE "be" PRIVATE "gnu" ) endif() target_include_directories(libfastfetch PUBLIC ${PROJECT_BINARY_DIR} PUBLIC ${PROJECT_SOURCE_DIR}/src ) target_link_libraries(libfastfetch PRIVATE ${CMAKE_DL_LIBS} ) 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() if(LINUX OR FreeBSD OR OpenBSD OR NetBSD) CHECK_INCLUDE_FILE("linux/videodev2.h" HAVE_LINUX_VIDEODEV2) if(HAVE_LINUX_VIDEODEV2) 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) target_compile_definitions(libfastfetch PRIVATE FF_HAVE_UTMPX=1) endif() if(ENABLE_WORDEXP) CHECK_INCLUDE_FILE("wordexp.h" HAVE_WORDEXP) if(HAVE_WORDEXP) target_compile_definitions(libfastfetch PRIVATE FF_HAVE_WORDEXP=1) message(STATUS "wordexp.h found, wordexp support enabled") else() set(ENABLE_WORDEXP OFF) endif() endif() if(NOT ENABLE_WORDEXP) message(STATUS "wordexp.h not found or disabled, glob used instead") endif() if(ENABLE_THREADS AND CMAKE_USE_PTHREADS_INIT) CHECK_INCLUDE_FILE("pthread_np.h" HAVE_PTHREAD_NP) if(HAVE_PTHREAD_NP) target_compile_definitions(libfastfetch PRIVATE FF_HAVE_PTHREAD_NP=1) set(CMAKE_REQUIRED_INCLUDES ${CMAKE_REQUIRED_INCLUDES} pthread_np.h) endif() set(CMAKE_REQUIRED_LIBRARIES ${CMAKE_REQUIRED_LIBRARIES} Threads::Threads) check_function_exists("pthread_timedjoin_np" HAVE_TIMEDJOIN_NP) if(HAVE_TIMEDJOIN_NP) target_compile_definitions(libfastfetch PRIVATE FF_HAVE_TIMEDJOIN_NP=1) else() message(WARNING "pthread_timedjoin_np was not found; networking timeout will not work") endif() endif() endif() set(PACKAGES_DISABLE_LIST "") foreach(package_manager ${PACKAGE_MANAGERS}) if(PACKAGES_DISABLE_${package_manager}) list(APPEND PACKAGES_DISABLE_LIST "${package_manager}") endif() endforeach() if("${PACKAGES_DISABLE_LIST}" STREQUAL "") set(PACKAGES_DISABLE_LIST "FF_PACKAGES_FLAG_NONE") else() message(STATUS "Disabled package managers: ${PACKAGES_DISABLE_LIST}") list(TRANSFORM PACKAGES_DISABLE_LIST PREPEND "FF_PACKAGES_FLAG_") list(TRANSFORM PACKAGES_DISABLE_LIST APPEND "_BIT") list(JOIN PACKAGES_DISABLE_LIST " | " PACKAGES_DISABLE_LIST) endif() target_compile_definitions(libfastfetch PRIVATE FF_PACKAGES_DISABLE_LIST=${PACKAGES_DISABLE_LIST}) ###################### # Executable targets # ###################### add_executable(fastfetch src/fastfetch.c ) target_compile_definitions(fastfetch PRIVATE FASTFETCH_TARGET_BINARY_NAME=fastfetch ) target_link_libraries(fastfetch PRIVATE libfastfetch ) # Prevent fastfetch from linking to libstdc++ set(CMAKE_CXX_IMPLICIT_LINK_LIBRARIES "") set(CMAKE_CXX_IMPLICIT_LINK_DIRECTORIES "") set_target_properties(fastfetch PROPERTIES LINKER_LANGUAGE C) if(WIN32) target_sources(fastfetch PRIVATE src/common/windows/version.rc ) elseif(APPLE) target_link_options(fastfetch PRIVATE LINKER:-sectcreate,__TEXT,__info_plist,Info.plist ) endif() if(BINARY_LINK_TYPE STREQUAL "static") target_link_options(fastfetch PRIVATE "-static") endif() # Apply all above parameters to flashfetch if it is built if (BUILD_FLASHFETCH) add_executable(flashfetch src/flashfetch.c ) target_compile_definitions(flashfetch PRIVATE FASTFETCH_TARGET_BINARY_NAME=flashfetch ) target_link_libraries(flashfetch PRIVATE libfastfetch ) set_target_properties(flashfetch PROPERTIES LINKER_LANGUAGE C) if(WIN32) target_sources(flashfetch PRIVATE src/common/windows/version.rc ) elseif(APPLE) target_link_options(flashfetch PRIVATE LINKER:-sectcreate,__TEXT,__info_plist,Info.plist ) endif() if(BINARY_LINK_TYPE STREQUAL "static") target_link_options(flashfetch PRIVATE "-static") endif() endif() ################### # Testing targets # ################### if (BUILD_TESTS) add_executable(fastfetch-test-strbuf tests/strbuf.c ) target_link_libraries(fastfetch-test-strbuf PRIVATE libfastfetch ) add_executable(fastfetch-test-list tests/list.c ) target_link_libraries(fastfetch-test-list PRIVATE libfastfetch ) add_executable(fastfetch-test-format tests/format.c ) target_link_libraries(fastfetch-test-format PRIVATE libfastfetch ) add_executable(fastfetch-test-color tests/color.c ) target_link_libraries(fastfetch-test-color PRIVATE libfastfetch ) add_executable(fastfetch-test-duration tests/duration.c ) target_link_libraries(fastfetch-test-duration PRIVATE libfastfetch ) enable_testing() add_test(NAME test-strbuf COMMAND fastfetch-test-strbuf) add_test(NAME test-list COMMAND fastfetch-test-list) add_test(NAME test-format COMMAND fastfetch-test-format) add_test(NAME test-color COMMAND fastfetch-test-color) add_test(NAME test-duration COMMAND fastfetch-test-duration) endif() ################## # install target # ################## include(GNUInstallDirs) install( TARGETS fastfetch DESTINATION "${CMAKE_INSTALL_BINDIR}" ) if (TARGET flashfetch) install( TARGETS flashfetch DESTINATION "${CMAKE_INSTALL_BINDIR}" ) 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" RENAME "${CMAKE_PROJECT_NAME}" ) install( FILES "${CMAKE_SOURCE_DIR}/completions/${CMAKE_PROJECT_NAME}.zsh" DESTINATION "${CMAKE_INSTALL_DATAROOTDIR}/zsh/site-functions" RENAME "_${CMAKE_PROJECT_NAME}" ) install( FILES "${CMAKE_SOURCE_DIR}/completions/${CMAKE_PROJECT_NAME}.fish" DESTINATION "${CMAKE_INSTALL_DATAROOTDIR}/fish/vendor_completions.d" RENAME "${CMAKE_PROJECT_NAME}.fish" ) install( DIRECTORY "${CMAKE_SOURCE_DIR}/presets" DESTINATION "${CMAKE_INSTALL_DATAROOTDIR}/${CMAKE_PROJECT_NAME}" ) if(INSTALL_LICENSE) install( FILES "${CMAKE_SOURCE_DIR}/LICENSE" DESTINATION "${CMAKE_INSTALL_DATAROOTDIR}/licenses/${CMAKE_PROJECT_NAME}" ) endif() install( FILES "${PROJECT_BINARY_DIR}/fastfetch.1" DESTINATION "${CMAKE_INSTALL_MANDIR}/man1" ) ################## # package target # ################## set(CPACK_GENERATOR "TGZ;ZIP") if(APPLE) string(TOLOWER "${CMAKE_PROJECT_NAME}-macos-${CMAKE_SYSTEM_PROCESSOR}" CPACK_PACKAGE_FILE_NAME) # use macos instead of darwin elseif(IS_MUSL) string(TOLOWER "${CMAKE_PROJECT_NAME}-musl-${CMAKE_SYSTEM_PROCESSOR}" CPACK_PACKAGE_FILE_NAME) else() string(TOLOWER "${CMAKE_PROJECT_NAME}-${CMAKE_SYSTEM_NAME}-${CMAKE_SYSTEM_PROCESSOR}" CPACK_PACKAGE_FILE_NAME) endif() if(LINUX) find_program(HAVE_DPKG "dpkg") if(HAVE_DPKG) set(CPACK_GENERATOR "${CPACK_GENERATOR};DEB") set(CPACK_DEBIAN_PACKAGE_SECTION, "utils") set(CPACK_DEBIAN_PACKAGE_PRIORITY "optional") if(NOT IS_MUSL) EXECUTE_PROCESS( COMMAND getconf GNU_LIBC_VERSION OUTPUT_VARIABLE GLIBC_VERSION OUTPUT_STRIP_TRAILING_WHITESPACE) if(GLIBC_VERSION) STRING(REPLACE "glibc " "" GLIBC_VERSION ${GLIBC_VERSION}) set(CPACK_DEBIAN_PACKAGE_DEPENDS "libc6 (>= ${GLIBC_VERSION})") message(STATUS "found glibc ${GLIBC_VERSION}") else() message(WARNING "Could not determine glibc version. If `musl` is used, `-DIS_MUSL=ON` should be set") endif() endif() endif() if(NOT IS_MUSL) find_program(HAVE_RPMBUILD "rpmbuild") if(HAVE_RPMBUILD) set(CPACK_GENERATOR "${CPACK_GENERATOR};RPM") set(CPACK_RPM_PACKAGE_LICENSE "MIT") endif() endif() elseif(FreeBSD AND NOT DragonFly) set(CPACK_FREEBSD_PACKAGE_LICENSE "MIT") set(CPACK_GENERATOR "${CPACK_GENERATOR};FREEBSD") endif() set(CPACK_SET_DESTDIR ON) set(CPACK_PACKAGE_CONTACT "Carter Li ") set(CPACK_PACKAGE_DESCRIPTION "\ fastfetch is a neofetch-like tool for fetching system information and displaying them in a pretty way. \ It is written mostly in C to achieve much better performance.\ ") include(CPack) ================================================ FILE: CODE_OF_CONDUCT.md ================================================ # Contributor Covenant Code of Conduct ## Our Pledge We as members, contributors, and leaders pledge to make participation in our community a harassment-free experience for everyone, regardless of age, body size, visible or invisible disability, ethnicity, sex characteristics, gender identity and expression, level of experience, education, socio-economic status, nationality, personal appearance, race, caste, color, religion, or sexual identity and orientation. We pledge to act and interact in ways that contribute to an open, welcoming, diverse, inclusive, and healthy community. ## Our Standards Examples of behavior that contributes to a positive environment for our community include: * Demonstrating empathy and kindness toward other people * Being respectful of differing opinions, viewpoints, and experiences * Giving and gracefully accepting constructive feedback * Accepting responsibility and apologizing to those affected by our mistakes, and learning from the experience * Focusing on what is best not just for us as individuals, but for the overall community Examples of unacceptable behavior include: * The use of sexualized language or imagery, and sexual attention or advances of any kind * Trolling, insulting or derogatory comments, and personal or political attacks * Public or private harassment * Publishing others' private information, such as a physical or email address, without their explicit permission * Other conduct which could reasonably be considered inappropriate in a professional setting ## Enforcement Responsibilities Community leaders are responsible for clarifying and enforcing our standards of acceptable behavior and will take appropriate and fair corrective action in response to any behavior that they deem inappropriate, threatening, offensive, or harmful. Community leaders have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct, and will communicate reasons for moderation decisions when appropriate. ## Scope This Code of Conduct applies within all community spaces, and also applies when an individual is officially representing the community in public spaces. Examples of representing our community include using an official email address, posting via an official social media account, or acting as an appointed representative at an online or offline event. ## Enforcement Instances of abusive, harassing, or otherwise unacceptable behavior may be reported to the community leaders responsible for enforcement at [INSERT CONTACT METHOD]. All complaints will be reviewed and investigated promptly and fairly. All community leaders are obligated to respect the privacy and security of the reporter of any incident. ## Enforcement Guidelines Community leaders will follow these Community Impact Guidelines in determining the consequences for any action they deem in violation of this Code of Conduct: ### 1. Correction **Community Impact**: Use of inappropriate language or other behavior deemed unprofessional or unwelcome in the community. **Consequence**: A private, written warning from community leaders, providing clarity around the nature of the violation and an explanation of why the behavior was inappropriate. A public apology may be requested. ### 2. Warning **Community Impact**: A violation through a single incident or series of actions. **Consequence**: A warning with consequences for continued behavior. No interaction with the people involved, including unsolicited interaction with those enforcing the Code of Conduct, for a specified period of time. This includes avoiding interactions in community spaces as well as external channels like social media. Violating these terms may lead to a temporary or permanent ban. ### 3. Temporary Ban **Community Impact**: A serious violation of community standards, including sustained inappropriate behavior. **Consequence**: A temporary ban from any sort of interaction or public communication with the community for a specified period of time. No public or private interaction with the people involved, including unsolicited interaction with those enforcing the Code of Conduct, is allowed during this period. Violating these terms may lead to a permanent ban. ### 4. Permanent Ban **Community Impact**: Demonstrating a pattern of violation of community standards, including sustained inappropriate behavior, harassment of an individual, or aggression toward or disparagement of classes of individuals. **Consequence**: A permanent ban from any sort of public interaction within the community. ## Attribution This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 2.1, available at [https://www.contributor-covenant.org/version/2/1/code_of_conduct.html][v2.1]. Community Impact Guidelines were inspired by [Mozilla's code of conduct enforcement ladder][Mozilla CoC]. For answers to common questions about this code of conduct, see the FAQ at [https://www.contributor-covenant.org/faq][FAQ]. Translations are available at [https://www.contributor-covenant.org/translations][translations]. [homepage]: https://www.contributor-covenant.org [v2.1]: https://www.contributor-covenant.org/version/2/1/code_of_conduct.html [Mozilla CoC]: https://github.com/mozilla/diversity [FAQ]: https://www.contributor-covenant.org/faq [translations]: https://www.contributor-covenant.org/translations ================================================ FILE: LICENSE ================================================ MIT License Copyright (c) 2021-2023 Linus Dierheimer Copyright (c) 2022-2026 Carter Li Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ================================================ FILE: README-cn.md ================================================ # Fastfetch Fastfetch 是一款类似 neofetch 的系统信息展示工具,主要用 C 编写,强调性能和可定制性。支持 Linux、macOS、Windows 7+、Android、FreeBSD、OpenBSD、NetBSD、DragonFly、Haiku、SunOS。 示例配置见 presets/examples,更多截图与平台说明见 Wiki。 ## 安装 Linux(部分): - Debian 13+ / Ubuntu: apt install fastfetch - Arch: pacman -S fastfetch - Fedora: dnf install fastfetch - openSUSE: zypper install fastfetch - Linuxbrew:brew install fastfetch - 各发行版打包状态:https://repology.org/project/fastfetch/versions macOS: - Homebrew:brew install fastfetch - MacPorts:sudo port install fastfetch Windows: - scoop install fastfetch - choco install fastfetch - winget install fastfetch - MSYS2:pacman -S mingw-w64---fastfetch BSD: - FreeBSD:pkg install fastfetch - NetBSD:pkgin in fastfetch - OpenBSD:pkg_add fastfetch Android(Termux): - pkg install fastfetch Nightly 构建: - https://nightly.link/fastfetch-cli/fastfetch/workflows/ci/dev?preview ## 源码构建 基本上是 `cmake . && make`。详见 Wiki:https://github.com/fastfetch-cli/fastfetch/wiki/Building ## 使用 - 默认运行:`fastfetch` - 查看所有可用模块示例:`fastfetch -c all.jsonc` - 以 JSON 输出指定模块:`fastfetch -s [:] --format json` - 完整命令行帮助:`fastfetch --help` - 生成最小配置:`fastfetch --gen-config []` - 生成完整配置:`fastfetch --gen-config-full` - 请使用支持 JSON schema 的编辑器(如 VSCode)编辑配置文件! - 如果你连接 Github 有网络困难(智能提示不生效),可将配置文件中的 `$schema` 的值替换为 `https://gitee.com/carterl/fastfetch/raw/dev/doc/json_schema.json` ## 定制 - 配置使用 JSONC,语法与选项见 Wiki:https://github.com/fastfetch-cli/fastfetch/wiki/Configuration - 预设示例位于 presets,可用 `-c ` 加载 - Logo 选项与图像显示见文档:https://github.com/fastfetch-cli/fastfetch/wiki/Logo-options - 模块格式化(示例,仅显示 GPU 名称): ```jsonc { "modules": [ { "type": "gpu", "format": "{name}" } ] } ``` 详见:https://github.com/fastfetch-cli/fastfetch/wiki/Format-String-Guide ## 反馈与支持 - 使用问题:Discussions https://github.com/fastfetch-cli/fastfetch/discussions - 疑似缺陷:Issues https://github.com/fastfetch-cli/fastfetch/issues(请填写模版) ## 赞助 ================================================ FILE: README.md ================================================ # Fastfetch [![GitHub Workflow Status (with event)](https://img.shields.io/github/actions/workflow/status/fastfetch-cli/fastfetch/ci.yml)](https://github.com/fastfetch-cli/fastfetch/actions) [![GitHub license](https://img.shields.io/github/license/fastfetch-cli/fastfetch)](https://github.com/fastfetch-cli/fastfetch/blob/dev/LICENSE) [![GitHub contributors](https://img.shields.io/github/contributors/fastfetch-cli/fastfetch)](https://github.com/fastfetch-cli/fastfetch/graphs/contributors) [![GitHub top language](https://img.shields.io/github/languages/top/fastfetch-cli/fastfetch?logo=c&label=)](https://github.com/fastfetch-cli/fastfetch/blob/dev/CMakeLists.txt#L5) [![GitHub commit activity (branch)](https://img.shields.io/github/commit-activity/m/fastfetch-cli/fastfetch)](https://github.com/fastfetch-cli/fastfetch/commits) [![homebrew downloads](https://img.shields.io/homebrew/installs/dm/fastfetch?logo=homebrew)](https://formulae.brew.sh/formula/fastfetch#default) [![GitHub all releases](https://img.shields.io/github/downloads/fastfetch-cli/fastfetch/total?logo=github)](https://github.com/fastfetch-cli/fastfetch/releases) [![GitHub release (with filter)](https://img.shields.io/github/v/release/fastfetch-cli/fastfetch?logo=github)](https://github.com/fastfetch-cli/fastfetch/releases) [![latest packaged version(s)](https://repology.org/badge/latest-versions/fastfetch.svg)](https://repology.org/project/fastfetch/versions) [![Packaging status](https://repology.org/badge/tiny-repos/fastfetch.svg)](https://repology.org/project/fastfetch/versions) [![Ask DeepWiki](https://deepwiki.com/badge.svg)](https://deepwiki.com/fastfetch-cli/fastfetch) [![中文README](https://img.shields.io/badge/%E4%B8%AD%E6%96%87-README-red)](README-cn.md) Fastfetch is a [neofetch](https://github.com/dylanaraps/neofetch)-like tool for fetching system information and displaying it in a visually appealing way. It is written mainly in C, with a focus on performance and customizability. Currently, it supports Linux, macOS, Windows 8.1+, Android, FreeBSD, OpenBSD, NetBSD, DragonFly, Haiku and SunOS (illumos, Solaris). According configuration files for examples are located [here](https://github.com/fastfetch-cli/fastfetch/tree/dev/presets/examples). There are [screenshots on different platforms](https://github.com/fastfetch-cli/fastfetch/wiki). ## Installation ### Linux Some distributions package outdated versions of fastfetch. Older versions receive no support, so please always try to use the latest version. Packaging status * Ubuntu: [`ppa:zhangsongcui3371/fastfetch`](https://launchpad.net/~zhangsongcui3371/+archive/ubuntu/fastfetch) (Ubuntu 22.04 or newer; latest version) * Debian / Ubuntu: `apt install fastfetch` (Debian 13 or newer; Ubuntu 25.04 or newer) * Debian / Ubuntu: Download `fastfetch-linux-.deb` from [Github release page](https://github.com/fastfetch-cli/fastfetch/releases/latest) and double-click it (for Ubuntu 20.04 or newer and Debian 11 or newer). * Arch Linux: `pacman -S fastfetch` * Fedora: `dnf install fastfetch` * Gentoo: `emerge --ask app-misc/fastfetch` * Alpine: `apk add --upgrade fastfetch` * NixOS: `nix-shell -p fastfetch` * openSUSE: `zypper install fastfetch` * ALT Linux: `apt-get install fastfetch` * Exherbo: `cave resolve --execute app-misc/fastfetch` * Solus: `eopkg install fastfetch` * Slackware: `sbopkg -i fastfetch` * Void Linux: `xbps-install fastfetch` * Venom Linux: `scratch install fastfetch` You may need `sudo`, `doas`, or `sup` to run these commands. If fastfetch is not packaged for your distribution or an outdated version is packaged, [linuxbrew](https://brew.sh/) is a good alternative: `brew install fastfetch` ### macOS * [Homebrew](https://formulae.brew.sh/formula/fastfetch#default): `brew install fastfetch` * [MacPorts](https://ports.macports.org/port/fastfetch/): `sudo port install fastfetch` ### Windows * [scoop](https://scoop.sh/#/apps?q=fastfetch): `scoop install fastfetch` * [Chocolatey](https://community.chocolatey.org/packages/fastfetch): `choco install fastfetch` * [winget](https://github.com/microsoft/winget-pkgs/tree/master/manifests/f/Fastfetch-cli/Fastfetch): `winget install fastfetch` * [MSYS2 MinGW](https://packages.msys2.org/base/mingw-w64-fastfetch): `pacman -S mingw-w64---fastfetch` You may also download the program directly from [the GitHub releases page](https://github.com/fastfetch-cli/fastfetch/releases/latest) in the form of an archive file. ### BSD systems * FreeBSD: `pkg install fastfetch` * NetBSD: `pkgin in fastfetch` * OpenBSD: `pkg_add fastfetch` (Snapshots only) * DragonFly BSD: `pkg install fastfetch` (Snapshots only) ### Android (Termux) * `pkg install fastfetch` ### Nightly ## Build from source See the Wiki: https://github.com/fastfetch-cli/fastfetch/wiki/Building ## Usage * Run with default configuration: `fastfetch` * Run with [all supported modules](https://github.com/fastfetch-cli/fastfetch/wiki/Support+Status#available-modules) to find what interests you: `fastfetch -c all.jsonc` * View all data that fastfetch detects: `fastfetch -s [:][:] --format json` * Display help messages: `fastfetch --help` * Generate a minimal config file: `fastfetch [-s [:]] --gen-config []` * Use: `--gen-config-full` to generate a full config file with all optional options ## Customization Fastfetch uses JSONC (JSON with comments) for configuration. [See the Wiki for details](https://github.com/fastfetch-cli/fastfetch/wiki/Configuration). There are some premade config files in the [`presets`](presets) directory, including those used for the screenshots above. You can load them using `-c `. These files can serve as examples of the configuration syntax. Logos can also be heavily customized; see the [logo documentation](https://github.com/fastfetch-cli/fastfetch/wiki/Logo-options) for more information. ### WARNING Fastfetch supports a `Command` module that can run arbitrary shell commands. If you copy-paste a config file from an untrusted source, it may contain malicious commands that can harm your system or compromise your privacy. Please always review the config file before using it. ## FAQ ### Q: Neofetch is good enough. Why do I need fastfetch? 1. Fastfetch is actively maintained. 2. Fastfetch is faster, as the name suggests. 3. Fastfetch has a greater number of features, though by default it only has a few modules enabled; use `fastfetch -c all` to discover what you want. 4. Fastfetch is more configurable. You can find more information in the Wiki: . 5. Fastfetch is more polished. For example, neofetch prints `555 MiB` in the Memory module and `23 G` in the Disk module, whereas fastfetch prints `555.00 MiB` and `22.97 GiB` respectively. 6. Fastfetch is more accurate. For example, [neofetch never actually supports the Wayland protocol](https://github.com/dylanaraps/neofetch/pull/2395). ### Q: Fastfetch shows my local IP address. Does it leak my privacy? A local IP address (10.x.x.x, 172.x.x.x, 192.168.x.x) has nothing to do with privacy. It only has meaning if you are on the same network, for example, if you connect to the same Wi-Fi network. Actually, the `Local IP` module is the most useful module for me personally. I (@CarterLi) have several VMs installed to test fastfetch and often need to SSH into them. With fastfetch running on shell startup, I never need to type `ip addr` manually. If you really don't like it, you can disable the `Local IP` module in `config.jsonc`. ### Q: Where is the config file? I can't find it. Fastfetch does not generate a config file automatically. You can use `fastfetch --gen-config` to generate one. The config file will be saved in `~/.config/fastfetch/config.jsonc` by default. See the [Wiki for details](https://github.com/fastfetch-cli/fastfetch/wiki/Configuration). ### Q: The configuration is so complex. Where is the documentation? Fastfetch uses JSON (with comments) for configuration. I suggest using an IDE with JSON schema support (like VSCode) to edit it. Alternatively, you can refer to the presets in the [`presets` directory](https://github.com/fastfetch-cli/fastfetch/tree/dev/presets). The **correct** way to edit the configuration: This is an example that [changes size prefix from MiB / GiB to MB / GB](https://github.com/fastfetch-cli/fastfetch/discussions/1014). Editor used: [helix](https://github.com/helix-editor/helix) [![asciicast](https://asciinema.org/a/1uF6sTPGKrHKI1MVaFcikINSQ.svg)](https://asciinema.org/a/1uF6sTPGKrHKI1MVaFcikINSQ) ### Q: I WANT THE DOCUMENTATION! [Here is the documentation](https://github.com/fastfetch-cli/fastfetch/wiki/Json-Schema). It is generated from the [JSON schema](https://github.com/fastfetch-cli/fastfetch/blob/dev/doc/json_schema.json), but you might not find it very user-friendly. ### Q: How can I customize the module output? Fastfetch uses `format` to generate output. For example, to make the `GPU` module show only the GPU name (leaving other information undisplayed), you can use: ```jsonc { "modules": [ { "type": "gpu", "format": "{name}" // See `fastfetch -h gpu-format` for details } ] } ``` ...which is equivalent to `fastfetch -s gpu --gpu-format '{name}'` See `fastfetch -h format` for information on basic usage. For module-specific formatting, see `fastfetch -h -format` ### Q: I have my own ASCII art / image file. How can I show it with fastfetch? Try `fastfetch -l /path/to/logo`. See the [logo documentation](https://github.com/fastfetch-cli/fastfetch/wiki/Logo-options) for details. If you just want to display the distro name in [FIGlet text](https://github.com/pwaller/pyfiglet): ```bash # install pyfiglet and jq first pyfiglet -s -f small_slant $(fastfetch -s os --format json | jq -r '.[0].result.name') && fastfetch -l none ``` ![image](https://github.com/fastfetch-cli/fastfetch/assets/6134068/6466524e-ab8c-484f-848d-eec7ddeb7df2) ### Q: My image logo behaves strangely. How can I fix it? See the troubleshooting section: ### Q: Fastfetch runs in black and white on shell startup. Why? This issue usually occurs when using fastfetch with `p10k`. There are known incompatibilities between fastfetch and p10k instant prompt. The p10k documentation clearly states that you should NOT print anything to stdout after `p10k-instant-prompt` is initialized. You should put `fastfetch` before the initialization of `p10k-instant-prompt` (recommended). You can always use `fastfetch --pipe false` to force fastfetch to run in colorful mode. ### Q: Why do fastfetch and neofetch show different memory usage results? See [#1096](https://github.com/fastfetch-cli/fastfetch/issues/1096). ### Q: Fastfetch shows fewer dpkg packages than neofetch. Is it a bug? Neofetch incorrectly counts `rc` packages (packages that have been removed but still have configuration files remaining). See bug: https://github.com/dylanaraps/neofetch/issues/2278 ### Q: I use Debian / Ubuntu / Debian-derived distro. My GPU is detected as `XXXX Device XXXX (VGA compatible)`. Is this a bug? Try upgrading `pci.ids`: Download and overwrite the file `/usr/share/hwdata/pci.ids`. For AMD GPUs, you should also upgrade `amdgpu.ids`: Download and overwrite the file `/usr/share/libdrm/amdgpu.ids` Alternatively, you may try using `fastfetch --gpu-driver-specific`, which will make fastfetch attempt to ask the driver for the GPU name if supported. ### Q: I get the error `Authorization required, but no authorization protocol specified` when running fastfetch as root Try `export XAUTHORITY=$HOME/.Xauthority` ### Q: Fastfetch cannot detect my awesome 3rd-party macOS window manager! Try `fastfetch --wm-detect-plugin`. See also [#984](https://github.com/fastfetch-cli/fastfetch/issues/984) ### Q: How can I change the colors of my ASCII logo? Try `fastfetch --logo-color-[1-9] `, where `[1-9]` is the index of color placeholders. For example: `fastfetch --logo-color-1 red --logo-color-2 green`. In JSONC, you can use: ```jsonc { "logo": { "color": { "1": "red", "2": "green" } } } ``` ### Q: How do I hide a key? Set the key to a white space. ```jsonc { "key": " " } ``` ### Q: How can I display images on Windows? As of April 2025: #### mintty and Wezterm mintty (used by Bash on Windows and MSYS2) and Wezterm (nightly build only) support the iTerm image protocol on Windows. In `config.jsonc`: ```json { "logo": { "type": "iterm", "source": "C:/path/to/image.png", "width": } } ``` #### Windows Terminal Windows Terminal supports the sixel image protocol only. * If you installed fastfetch through MSYS2: 1. Install imagemagick: `pacman -S mingw-w64--x86_64-imagemagick` 2. In `config.jsonc`: ```jsonc { "logo": { "type": "sixel", // DO NOT USE "auto" "source": "C:/path/to/image.png", // Do NOT use `~` as fastfetch is a native Windows program and doesn't apply cygwin path conversion "width": , // Optional "height": // Optional } } ``` * If you installed fastfetch via scoop or downloaded the binary directly from the GitHub Releases page: 1. Convert your image manually to sixel format using [any online image conversion service](https://www.google.com/search?q=convert+image+to+sixel) 2. In `config.jsonc`: ```jsonc { "logo": { "type": "raw", // DO NOT USE "auto" "source": "C:/path/to/image.sixel", "width": , // Required "height": // Required } } ``` ### Q: I want feature A / B / C. Will fastfetch support it? Fastfetch is a system information tool. We only accept hardware or system-level software feature requests. For most personal uses, I recommend using the `Command` module to implement custom functionality, which can be used to grab output from a custom shell script: ```jsonc // This module shows the default editor { "modules": [ { "type": "command", "text": "$EDITOR --version | head -1", "key": "Editor" } ] } ``` Otherwise, please open a feature request in [GitHub Issues](https://github.com/fastfetch-cli/fastfetch/issues). ### Q: I have questions. Where can I get help? * For usage questions, please start a discussion in [GitHub Discussions](https://github.com/fastfetch-cli/fastfetch/discussions). * For possible bugs, please open an issue in [GitHub Issues](https://github.com/fastfetch-cli/fastfetch/issues). Be sure to fill out the bug report template carefully to help developers investigate. ## Donate If you find Fastfetch useful, please consider donating. * Current maintainer: [@CarterLi](https://paypal.me/zhangsongcui) * Original author: [@LinusDierheimer](https://github.com/sponsors/LinusDierheimer) ## Code signing * Free code signing provided by [SignPath.io](https://about.signpath.io/), certificate by [SignPath Foundation](https://signpath.org/) * This program will not transfer any information to other networked systems unless specifically requested by the user or the person installing or operating it ## Star History Give us a star to show your support! Star History Chart ================================================ FILE: completions/fastfetch.bash ================================================ #!/usr/bin/env bash _fastfetch() { # Use Bash built-in variables directly local cur="${COMP_WORDS[COMP_CWORD]}" local prev="${COMP_WORDS[COMP_CWORD-1]}" # Check if Python is available if ! command -v python3 &>/dev/null; then return fi # Handle standard completion cases case "$prev" in --color|--color-keys|--color-title|--color-output|--color-separator|--*-color|--*-key-color|--*-output-color|--logo-color-[1-9]|--percent-color-*|--temp-color-*) local -a colors=("black" "red" "green" "yellow" "blue" "magenta" "cyan" "white" "default") COMPREPLY=($(compgen -W "${colors[*]}" -- "$cur")) return ;; --logo|-l) local -a logos readarray -t logos < <(fastfetch --list-logos autocompletion 2>/dev/null) logos+=("none" "small") COMPREPLY=($(compgen -W "${logos[*]}" -- "$cur")) return ;; --config|-c) local -a presets readarray -t presets < <(fastfetch --list-presets autocompletion 2>/dev/null) presets+=("none") COMPREPLY=($(compgen -W "${presets[*]}" -- "$cur")) # Also allow file path completion if type _filedir &>/dev/null; then _filedir elif type compgen &>/dev/null; then COMPREPLY+=($(compgen -f -- "$cur")) fi return ;; --structure|-s) # Get all module names in lowercase only local -a structures readarray -t structures < <(fastfetch --list-modules autocompletion 2>/dev/null | cut -d':' -f1 | tr '[:upper:]' '[:lower:]') COMPREPLY=($(compgen -W "${structures[*]}" -- "$cur")) return ;; --help|-h) local -a modules readarray -t modules < <(fastfetch --list-modules autocompletion 2>/dev/null) # Convert to lowercase and keep only module names local -a module_names=() for module in "${modules[@]}"; do module_names+=($(echo "$module" | cut -d':' -f1 | tr '[:upper:]' '[:lower:]')-format) done module_names+=("format" "color") COMPREPLY=($(compgen -W "${module_names[*]}" -- "$cur")) return ;; --format) COMPREPLY=($(compgen -W "json default" -- "$cur")) return ;; --*-format) # Format string completion, handle spaces return ;; --*path*|--*file*|--gen-config*|--*data*) # File path completion if type _filedir &>/dev/null; then _filedir elif type compgen &>/dev/null; then COMPREPLY=($(compgen -f -- "$cur")) fi return ;; esac # If not a special option, generate all possible options if [[ "$cur" == -* ]]; then local -a opts readarray -t opts < <(python3 - "$cur" <<'EOF' import json import sys import subprocess def main(current): try: # Use fastfetch --help-raw to get option data output = subprocess.check_output(['fastfetch', '--help-raw'], stderr=subprocess.DEVNULL) data = json.loads(output) for category in data.values(): for flag in category: if flag.get("pseudo", False): continue if "short" in flag: print(f"-{flag['short']}") if "long" in flag: if flag["long"] == "logo-color-[1-9]": for i in range(1, 10): print(f"--logo-color-{i}") else: print(f"--{flag['long']}") except Exception: # If error occurs, return no options pass if __name__ == "__main__": main(sys.argv[1]) EOF ) COMPREPLY=($(compgen -W "${opts[*]}" -- "$cur")) fi return 0 } # Register completion complete -F _fastfetch fastfetch ================================================ FILE: completions/fastfetch.fish ================================================ if not type -q fastfetch exit end command -q python3 if test $status -ne 0 exit end complete -c fastfetch -f function __fastfetch_complete_bool echo -e "true\tBool" echo -e "false\tBool" end function __fastfetch_complete_color echo -e "black\tColor" echo -e "red\tColor" echo -e "green\tColor" echo -e "yellow\tColor" echo -e "blue\tColor" echo -e "magenta\tColor" echo -e "cyan\tColor" echo -e "white\tColor" echo -e "default\tColor" end function __fastfetch_complete_command for line in (fastfetch --list-modules autocompletion) set -l pair (string split -m 2 : $line) set -l module (string lower $pair[1]) echo -e "$module-format\tModule format" end echo -e "format\tCustom format" echo -e "color\tColor format" end function __fastfetch_complete_config for line in (fastfetch --list-presets autocompletion) echo -e "$line\tPreset" end echo -e "none\tDisable loading config file" end function __fastfetch_complete_logo for line in (fastfetch --list-logos autocompletion) echo -e "$line\tBuiltin logo" end echo -e "none\tDon't print logo" echo -e "small\tPrint small ascii logo if available" end function __fastfetch_complete_structure for line in (fastfetch --list-modules autocompletion) set -l pair (string split -m 2 : $line) echo -e "$pair[1]\t$pair[2]" end end echo ' import json, subprocess, sys def main(): data: dict[str, list[dict]] = json.loads(subprocess.check_output(["fastfetch", "--help-raw"])) for key in data: for flag in data[key]: if flag["long"] == "logo-color-[1-9]": for i in range(1, 10): print(f"""complete -c fastfetch -d "{flag["desc"]}" -l "logo-color-{i}" -x -a "(__fastfetch_complete_color)" """) continue if flag.get("pseudo", False): continue command_prefix = f"""complete -c fastfetch -d "{flag["desc"]}" -l "{flag["long"]}\"""" if "short" in flag: command_prefix += f""" -o {flag["short"]}""" if "arg" in flag: type: str = flag["arg"]["type"] if type == "bool": print(f"{command_prefix} -x -a \"(__fastfetch_complete_bool)\"") elif type == "color": print(f"{command_prefix} -x -a \"(__fastfetch_complete_color)\"") elif type == "command": print(f"{command_prefix} -x -a \"(__fastfetch_complete_command)\"") elif type == "config": print(f"{command_prefix} -x -a \"(__fastfetch_complete_config)\"") elif type == "enum": temp: str = " ".join(flag["arg"]["enum"]) print(f"{command_prefix} -x -a \"{temp}\"") elif type == "logo": print(f"{command_prefix} -x -a \"(__fastfetch_complete_logo)\"") elif type == "structure": print(f"{command_prefix} -x -a \"(__fish_complete_list : __fastfetch_complete_structure)\"") elif type == "path": print(f"{command_prefix} -r -F") else: print(f"{command_prefix} -x") else: print(f"{command_prefix} -f") if __name__ == "__main__": try: main() except: sys.exit(1) ' | python3 | source ================================================ FILE: completions/fastfetch.zsh ================================================ #compdef fastfetch function _fastfetch() { whence python3 &> /dev/null if [ $? -ne 0 ] then return fi local state local -a opts=("${(f)$( python3 <colors") elif type == "command": print(f"{command_prefix}::module:->modules") elif type == "config": print(f"{command_prefix}:preset:->presets") elif type == "enum": temp: str = " ".join(flag["arg"]["enum"]) print(f'{command_prefix}:type:({temp})') elif type == "logo": print(f"{command_prefix}:logo:->logos") elif type == "structure": print(f"{command_prefix}:structure:->structures") elif type == "path": print(f"{command_prefix}::path:_files") else: print(f"{command_prefix}:") else: print(f"{command_prefix}") if __name__ == "__main__": try: main() except Exception: sys.exit(1) EOF )}") _arguments "$opts[@]" case $state in colors) local -a colors=(black red green yellow blue magenta cyan white default) _describe 'color' colors ;; modules) local -a modules=("${(f)$(fastfetch --list-modules autocompletion)}") modules=(${(L)^modules[@]%%:*}-format format color) _describe 'module' modules ;; presets) local -a presets=( "${(f)$(fastfetch --list-presets autocompletion)}" "none:Disable loading config file" ) _describe 'preset' presets || _files ;; structures) local -a structures=("${(f)$(fastfetch --list-modules autocompletion)}") _describe 'structure' structures ;; logos) local -a logos=( "${(f)$(fastfetch --list-logos autocompletion)}" "none:Don't print logo" "small:Print small ascii logo if available" ) _describe 'logo' logos ;; esac } _fastfetch "$@" ================================================ FILE: debian/changelog.tpl ================================================ fastfetch (2.58.0~#UBUNTU_CODENAME#) #UBUNTU_CODENAME#; urgency=medium * Update to 2.58.0 -- Carter Li Thu, 22 Jan 2026 15:30:58 +0800 fastfetch (2.57.1~#UBUNTU_CODENAME#) #UBUNTU_CODENAME#; urgency=medium * Update to 2.57.1 -- Carter Li Wed, 14 Jan 2026 14:00:17 +0800 fastfetch (2.57.0~#UBUNTU_CODENAME#) #UBUNTU_CODENAME#; urgency=medium * Update to 2.57.0 -- Carter Li Mon, 12 Jan 2026 10:11:21 +0800 fastfetch (2.56.1~#UBUNTU_CODENAME#) #UBUNTU_CODENAME#; urgency=medium * Update to 2.56.1 -- Carter Li Thu, 18 Dec 2025 14:57:36 +0800 fastfetch (2.56.0~#UBUNTU_CODENAME#) #UBUNTU_CODENAME#; urgency=medium * Update to 2.56.0 -- Carter Li Mon, 08 Dec 2025 09:21:58 +0800 fastfetch (2.55.1~#UBUNTU_CODENAME#) #UBUNTU_CODENAME#; urgency=medium * Update to 2.55.1 -- Carter Li Mon, 17 Nov 2025 10:15:44 +0800 fastfetch (2.55.0~#UBUNTU_CODENAME#) #UBUNTU_CODENAME#; urgency=medium * Update to 2.55.0 -- Carter Li Wed, 12 Nov 2025 09:15:24 +0800 fastfetch (2.54.1~#UBUNTU_CODENAME#) #UBUNTU_CODENAME#; urgency=medium * Fix building on plucky -- Carter Li Mon, 20 Oct 2025 14:27:02 +0800 fastfetch (2.54.0~#UBUNTU_CODENAME#) #UBUNTU_CODENAME#; urgency=medium * Update to 2.54.0 * Test independent changelog entries for different Ubuntu releases -- Carter Li Mon, 20 Oct 2025 10:47:02 +0800 fastfetch (2.53.0) jammy; urgency=medium * Update to 2.53.0 -- Carter Li Tue, 23 Sep 2025 09:58:48 +0800 fastfetch (2.52.0) jammy; urgency=medium * Update to 2.52.0 -- Carter Li Fri, 05 Sep 2025 14:59:44 +0800 fastfetch (2.51.0) jammy; urgency=medium * Update to 2.51.0 -- Carter Li Fri, 29 Aug 2025 08:55:03 +0800 fastfetch (2.50.2) jammy; urgency=medium * Update to 2.50.2 -- Carter Li Thu, 21 Aug 2025 10:33:07 +0800 fastfetch (2.49.0) jammy; urgency=medium * Update to 2.49.0 -- Carter Li Thu, 31 Jul 2025 14:32:59 +0800 fastfetch (2.48.0) jammy; urgency=medium * Update to 2.48.0 -- Carter Li Thu, 17 Jul 2025 16:16:52 +0800 fastfetch (2.47.0) jammy; urgency=medium * Update to 2.47.0 -- Carter Li Thu, 03 Jul 2025 14:51:45 +0800 fastfetch (2.46.0) jammy; urgency=medium * Update to 2.46.0 -- Carter Li Fri, 20 Jun 2025 15:01:21 +0800 fastfetch (2.45.0) jammy; urgency=medium * Update to 2.45.0 -- Carter Li Thu, 05 Jun 2025 10:56:38 +0800 fastfetch (2.44.0) jammy; urgency=medium * Update to 2.44.0 -- Carter Li Mon, 26 May 2025 14:59:01 +0800 fastfetch (2.43.0) jammy; urgency=medium * Update to 2.43.0 -- Carter Li Wed, 14 May 2025 09:49:50 +0800 fastfetch (2.42.0) jammy; urgency=medium * Update to 2.42.0 -- Carter Li Wed, 30 Apr 2025 14:08:57 +0800 fastfetch (2.41.0) jammy; urgency=medium * Update to 2.41.0 -- Carter Li Wed, 16 Apr 2025 13:42:58 +0800 fastfetch (2.40.4) jammy; urgency=medium * Update to 2.40.4 -- Carter Li Thu, 10 Apr 2025 15:38:21 +0800 fastfetch (2.40.3) jammy; urgency=medium * Update to 2.40.3 -- Carter Li Mon, 07 Apr 2025 09:29:27 +0800 fastfetch (2.40.0) jammy; urgency=medium * Update to 2.40.0 -- Carter Li Thu, 03 Apr 2025 08:46:54 +0800 fastfetch (2.39.1) jammy; urgency=medium * Update to 2.39.1 -- Carter Li Fri, 21 Mar 2025 11:02:19 +0800 fastfetch (2.39.0ubuntu1) jammy; urgency=medium * Remove unwanted debugging code -- Carter Li Thu, 20 Mar 2025 10:39:22 +0800 fastfetch (2.39.0) jammy; urgency=medium * Update to 2.39.0 -- Carter Li Thu, 20 Mar 2025 10:35:18 +0800 fastfetch (2.38.0) jammy; urgency=medium * Update to 2.38.0 -- Carter Li Wed, 05 Mar 2025 14:56:24 +0800 fastfetch (2.37.0) jammy; urgency=medium * Update to 2.37.0 -- Carter Li Wed, 19 Feb 2025 15:43:42 +0800 fastfetch (2.36.1) jammy; urgency=medium * Update to 2.36.1 -- Carter Li Tue, 11 Feb 2025 13:39:55 +0800 fastfetch (2.36.0) jammy; urgency=medium * Update to 2.36.0 -- Carter Li Mon, 10 Feb 2025 10:13:53 +0800 fastfetch (2.35.0) jammy; urgency=medium * Update to 2.35.0 -- Carter Li Sun, 26 Jan 2025 10:15:22 +0800 fastfetch (2.34.1) jammy; urgency=medium * Update to 2.34.1 -- Carter Li Mon, 13 Jan 2025 16:06:22 +0800 fastfetch (2.34.0) jammy; urgency=medium * Update to 2.34.0 -- Carter Li Thu, 09 Jan 2025 09:03:17 +0800 fastfetch (2.33.0) jammy; urgency=medium * Update to 2.33.0 -- Carter Li Thu, 26 Dec 2024 09:42:27 +0800 fastfetch (2.32.0) jammy; urgency=medium * Update to 2.32.0 -- Carter Li Wed, 18 Dec 2024 10:39:06 +0800 fastfetch (2.31.0) jammy; urgency=medium * Update to 2.31.0 -- Carter Li Wed, 04 Dec 2024 08:41:40 +0800 fastfetch (2.30.1) jammy; urgency=medium * Update to 2.30.1 -- Carter Li Mon, 18 Nov 2024 15:40:48 +0800 fastfetch (2.30.0) jammy; urgency=medium * Update to 2.30.0 -- Carter Li Mon, 18 Nov 2024 09:30:58 +0800 fastfetch (2.29.0) jammy; urgency=medium * Update to 2.29.0 -- Carter Li Mon, 04 Nov 2024 15:05:02 +0800 fastfetch (2.28.0) jammy; urgency=medium * Update to 2.28.0 -- Carter Li Wed, 23 Oct 2024 10:18:59 +0800 fastfetch (2.27.1) jammy; urgency=medium * Update to 2.27.1 -- Carter Li Sun, 06 Oct 2024 12:55:18 +0800 fastfetch (2.26.1ubuntu1) jammy; urgency=medium * Update correct code -- Carter Li Mon, 30 Sep 2024 00:21:20 +0800 fastfetch (2.26.1) jammy; urgency=medium * Update to 2.26.1 -- Carter Li Sun, 29 Sep 2024 16:15:31 +0800 fastfetch (2.26.0) jammy; urgency=medium * Update to 2.26.0 -- Carter Li Sun, 29 Sep 2024 13:31:25 +0800 fastfetch (2.25.0) jammy; urgency=medium * Update to 2.25.0 -- Carter Li Thu, 19 Sep 2024 10:28:38 +0800 fastfetch (2.24.0) jammy; urgency=medium * Update to 2.24.0 -- Carter Li Wed, 11 Sep 2024 13:50:02 +0800 fastfetch (2.23.0) jammy; urgency=medium * Update to 2.23.0 -- Carter Li Tue, 03 Sep 2024 18:44:11 +0800 fastfetch (2.22.0) jammy; urgency=medium * Update to 2.22.0 -- Carter Li Mon, 26 Aug 2024 18:53:35 +0800 fastfetch (2.21.3) jammy; urgency=medium * Update to 2.21.3 -- Carter Li Thu, 15 Aug 2024 16:14:52 +0800 fastfetch (2.21.2) jammy; urgency=medium * Update to 2.21.2 -- Carter Li Wed, 14 Aug 2024 14:42:07 +0800 fastfetch (2.21.1) jammy; urgency=medium * Update to 2.21.1 -- Carter Li Fri, 09 Aug 2024 14:27:10 +0800 fastfetch (2.21.0) jammy; urgency=medium * Update to 2.21.0 -- Carter Li Mon, 05 Aug 2024 14:35:43 +0800 fastfetch (2.20.0) jammy; urgency=medium * Update to 2.20.0 -- Carter Li Fri, 26 Jul 2024 14:02:50 +0800 fastfetch (2.19.1) jammy; urgency=medium * Update to 2.19.1 -- Carter Li Tue, 23 Jul 2024 10:25:14 +0800 fastfetch (2.19.0) jammy; urgency=medium * Update to 2.19.0 -- Carter Li Mon, 22 Jul 2024 14:17:55 +0800 fastfetch (2.18.1) jammy; urgency=medium * Update to 2.18.1 -- Carter Li Thu, 11 Jul 2024 14:32:15 +0800 fastfetch (2.18.0) jammy; urgency=medium * Update to 2.18.0 -- Carter Li Wed, 10 Jul 2024 16:46:32 +0800 fastfetch (2.17.2) jammy; urgency=medium * Update to 2.17.2 -- Carter Li Thu, 04 Jul 2024 10:22:44 +0800 fastfetch (2.17.1) jammy; urgency=medium * Update to 2.17.1 -- Carter Li Mon, 01 Jul 2024 08:56:29 +0800 fastfetch (2.17.0) jammy; urgency=medium * Update to 2.17.0 -- Carter Li Fri, 28 Jun 2024 13:43:18 +0800 fastfetch (2.16.0) jammy; urgency=medium * Update to 2.16.0 -- Carter Li Wed, 19 Jun 2024 14:53:43 +0800 fastfetch (2.15.0) jammy; urgency=medium * Update to 2.15.0 -- Carter Li Fri, 07 Jun 2024 13:52:43 +0800 fastfetch (2.14.0) jammy; urgency=medium * Update to 2.14.0 -- Carter Li Thu, 30 May 2024 14:27:54 +0800 fastfetch (2.13.2) jammy; urgency=medium * Update to 2.13.2 -- Carter Li Fri, 24 May 2024 13:48:59 +0800 fastfetch (2.13.1) jammy; urgency=medium * Update to 2.13.1 -- Carter Li Tue, 21 May 2024 15:10:37 +0800 fastfetch (2.12.0) jammy; urgency=medium * Update to 2.12.0 -- Carter Li Tue, 14 May 2024 16:33:33 +0800 fastfetch (2.11.5) jammy; urgency=medium * Update to 2.11.5 -- Carter Li Tue, 07 May 2024 09:30:05 +0800 fastfetch (2.11.3) jammy; urgency=medium * Update to 2.11.3 -- Carter Li Mon, 06 May 2024 08:46:45 +0800 fastfetch (2.10.2) jammy; urgency=medium * Update to 2.10.2 -- Carter Li Tue, 23 Apr 2024 15:18:23 +0800 fastfetch (2.10.1) jammy; urgency=medium * Update to 2.10.1 -- Carter Li Tue, 23 Apr 2024 09:55:02 +0800 fastfetch (2.9.2) jammy; urgency=medium * Update to 2.9.2 -- Carter Li Tue, 16 Apr 2024 16:32:40 +0800 fastfetch (2.9.1) jammy; urgency=medium * Update to 2.9.1 -- Carter Li Mon, 08 Apr 2024 09:34:30 +0800 fastfetch (2.8.10) jammy; urgency=medium * Update to 2.8.10 -- Carter Li Mon, 25 Mar 2024 15:01:53 +0800 fastfetch (2.8.9) jammy; urgency=medium * Update to 2.8.9 -- Carter Li Fri, 15 Mar 2024 10:49:42 +0800 fastfetch (2.8.8) jammy; urgency=medium * Update to 2.8.8 -- Carter Li Fri, 08 Mar 2024 09:59:41 +0800 fastfetch (2.8.6) jammy; urgency=medium * Update to 2.8.6 -- Carter Li Wed, 28 Feb 2024 10:01:40 +0800 fastfetch (2.8.4) jammy; urgency=medium * Update to 2.8.4 -- Carter Li Fri, 23 Feb 2024 16:14:57 +0800 fastfetch (2.7.1ubuntu2) jammy; urgency=medium * Ignore .git -- Carter Li Wed, 07 Feb 2024 14:23:23 +0800 fastfetch (2.7.1ubuntu1) jammy; urgency=medium * Update build scripts -- Carter Li Wed, 07 Feb 2024 13:53:37 +0800 fastfetch (2.7.1) jammy; urgency=medium * Initial release. -- Carter Li Tue, 06 Feb 2024 15:01:11 +0800 ================================================ FILE: debian/compat ================================================ 12 ================================================ FILE: debian/control ================================================ Source: fastfetch Section: universe/utils Priority: optional Maintainer: Carter Li Build-Depends: libelf-dev, libvulkan-dev, libwayland-dev, libxrandr-dev, libxcb-randr0-dev, libdconf-dev, libdbus-1-dev, libmagickcore-dev, libsqlite3-dev, librpm-dev, libegl-dev, libglx-dev, ocl-icd-opencl-dev, libpulse-dev, libdrm-dev, libddcutil-dev, libchafa-dev, directx-headers-dev, pkgconf, cmake (>= 3.12), debhelper (>= 11.2), dh-cmake, dh-cmake-compat (= 1), dh-sequence-cmake, dh-sequence-ctest, ninja-build, python3-setuptools Standards-Version: 4.0.0 Homepage: https://github.com/fastfetch-cli/fastfetch Package: fastfetch Architecture: any Depends: ${shlibs:Depends}, ${misc:Depends} Description: Fastfetch is a neofetch-like tool for fetching system information and displaying them in a pretty way. It is written mainly in C, with performance and customizability in mind. ================================================ FILE: debian/copyright ================================================ Format: https://www.debian.org/doc/packaging-manuals/copyright-format/1.0/ Upstream-Name: fastfetch Source: https://github.com/fastfetch-cli/fastfetch Files: * Copyright: 2024 fastfetch-cli License: Expat Files: src/3rdparty/yyjson/* Copyright: 2020 YaoYuan License: Expat ================================================ FILE: debian/publish.sh ================================================ #!/usr/bin/env bash # WARNING: DO NOT RUN THIS SCRIPT UNLESS YOU KNOW WHAT YOU ARE DOING. set -euo pipefail command -v debuild >/dev/null 2>&1 || { echo "debuild not found. Install devscripts." >&2; exit 1; } command -v dput >/dev/null 2>&1 || { echo "dput not found." >&2; exit 1; } SCRIPT_DIR="$(cd -- "$(dirname "${BASH_SOURCE[0]}")" >/dev/null 2>&1 && pwd)" ROOT_DIR="$(cd -- "$SCRIPT_DIR/.." >/dev/null 2>&1 && pwd)" OUT_DIR="$(dirname "$ROOT_DIR")" PPA="ppa:zhangsongcui3371/fastfetch" CODENAMES=( jammy noble plucky questing resolute ) DRY_RUN=0 if [[ -d "$OUT_DIR/build" ]]; then echo "Output directory exists: $OUT_DIR/build" >&2 echo "Please move or delete it before proceeding." >&2 exit 1 fi TEMPLATE="$ROOT_DIR/debian/changelog.tpl" CHANGELOG_DEBIAN="$ROOT_DIR/debian/changelog" if [[ ! -f "$TEMPLATE" ]]; then echo "Template not found: $TEMPLATE" >&2 exit 1 fi if ! grep -q '#UBUNTU_CODENAME#' "$TEMPLATE"; then echo "Template missing placeholder: #UBUNTU_CODENAME#" >&2 exit 1 fi echo "IMPORTANT: Before proceeding, please ensure that 'debian/changelog.tpl' is up-to-date" echo " with the correct version number, release notes, and maintainer information." echo echo " The template must contain a placeholder like '#UBUNTU_CODENAME#' for the codename." echo read -p "Have you reviewed and updated 'debian/changelog.tpl'? (y/N): " -r echo if [[ ! $REPLY =~ ^[Yy]$ ]]; then exit 1 fi cleanup() { rm -f "$CHANGELOG_DEBIAN" rm -f "$ROOT_DIR/debian/files" } trap cleanup EXIT shopt -s nullglob for codename in "${CODENAMES[@]}"; do echo "==> Publishing distro: $codename" sed "s/#UBUNTU_CODENAME#/${codename}/g" "$TEMPLATE" > "$CHANGELOG_DEBIAN" ( cd "$ROOT_DIR" && debuild -S -i -I ) changes=( "$OUT_DIR"/fastfetch_*~${codename}_source.changes ) if [[ ${#changes[@]} -ne 1 ]]; then echo "Unable to uniquely identify .changes for '$codename' in: $OUT_DIR" >&2 printf 'Found:\n'; printf ' %s\n' "${changes[@]}" >&2 || true exit 1 fi if [[ $DRY_RUN -eq 1 ]]; then echo "[DRY-RUN] dput \"$PPA\" \"${changes[0]}\"" else dput "$PPA" "${changes[0]}" fi rm -f "$OUT_DIR"/fastfetch_*~${codename}_source.{changes,dsc,tar.*} echo "<== Done: $codename" done ================================================ FILE: debian/rules ================================================ #!/usr/bin/make -f %: dh $@ --buildsystem=cmake+ninja override_dh_ctest_configure: dh_ctest_configure -- -DSET_TWEAK=OFF -DBUILD_TESTS=ON -DCMAKE_BUILD_TYPE=RelWithDebInfo ================================================ FILE: debian/watch ================================================ version=4 opts="filenamemangle=s%.*/@ANY_VERSION@%@PACKAGE@-$1.tar.gz%,searchmode=plain" \ https://api.github.com/repos/fastfetch-cli/fastfetch/releases?per_page=100 \ https://api.github.com/repos/fastfetch-cli/fastfetch/tarball/@ANY_VERSION@ ================================================ FILE: doc/fastfetch.1.in ================================================ .TH FASTFETCH 1 "@FASTFETCH_BUILD_DATE@" "@CMAKE_PROJECT_NAME@ @CMAKE_PROJECT_VERSION@" .SH NAME fastfetch \- a fast and customizable system information tool similar to neofetch .SH SYNOPSIS .B fastfetch .I [options...] .SH DESCRIPTION Fastfetch is a tool for displaying system information in a visually appealing way. Written primarily in C, it focuses on performance and customizability while providing functionality similar to neofetch. It supports Linux, Android, *BSD, macOS, Haiku, and Windows 7 or newer. .SH "EXIT STATUS" Fastfetch returns zero on successful execution. Any errors result in a non-zero exit code. .SH OPTIONS .SS "Informative Options" .TP .B \-h, \-\-help \fI[command] Display help information for all available options or for a specific command .TP .B \-v, \-\-version Display the version of fastfetch .TP .B \-\-version\-raw Display the raw version string (major.minor.patch) .TP .B \-\-list\-config\-paths List search paths for configuration files .TP .B \-\-list\-data\-paths List search paths for presets and logos .TP .B \-\-list\-logos List available logos that can be loaded with \fI\-\-logo .TP .B \-\-list\-modules List all available modules .TP .B \-\-list\-presets List available presets that can be loaded with \fI\-\-config .TP .B \-\-list\-features List the features that fastfetch was compiled with (mainly for development) .TP .B \-\-print\-logos Display all available logos .TP .B \-\-print\-structure Display the default structure .TP .B \-\-format \fI Set the output format. Available options are: .RS .IP \(bu 2 \fIdefault\fR: Default human-readable format .IP \(bu 2 \fIjson\fR: JSON format for machine processing .RE .SS "Config Options" .TP .B \-c, \-\-config \fI Use the specified config file or preset. Specify \fInone\fR to disable further config loading. See the CONFIGURATION section for details on config files. .TP .B \-\-gen\-config \fI[path] Generate a config file with options specified on the command line. If \fIpath\fR is not specified, it defaults to \fB~/.config/fastfetch/config.jsonc\fR. If \fIpath\fR is "\-", the configuration will be written to stdout. .TP .B \-\-gen\-config\-force \fI[path] Same as \fB\-\-gen\-config\fR, but overwrites any existing file at the destination path. .TP .SS "Logo Options" .TP .B \-l, \-\-logo \fI Set the logo to display. Can be the name of a built-in logo or a path to an image file. Use \fInone\fR to disable the logo. .TP .B \-\-logo\-type \fI Set the type of the logo specified with \fI\-\-logo\fR. Available types include \fIauto\fR, \fIbuiltin\fR, \fIfile\fR, \fIsixel\fR, \fIkitty\fR, and others. See \fB\-\-help logo\-type\fR for details. .TP .B \-\-logo\-width \fI Set the width of the logo in characters (for image logos) .TP .B \-\-logo\-height \fI Set the height of the logo in characters (for image logos) .TP .B \-\-logo\-color\-[1\-9] \fI Override specific colors in the logo .SS "Display Options" .TP .B \-s, \-\-structure \fI Set the structure of the fetch (a colon-separated list of module names) .TP .B \-\-color \fI Set the color of keys and title. See \fB\-\-help color\fR for available colors. .TP .B \-\-color\-keys \fI Set the color of keys only .TP .B \-\-color\-title \fI Set the color of the title only .TP .B \-\-separator \fI Set the separator between key and value (default: ": ") .TP .B \-\-key\-width \fI Align the width of keys to \fI\fR characters .TP .B \-\-show\-errors Display errors when they occur (default: false) .TP .B \-\-pipe Disable colors (automatically detected based on whether stdout is a terminal) To list all available options including module-specific options, use \fB\-\-help\fR. .SH CONFIGURATION .SS "Fetch Structure" The structure defines which modules to display and in what order. It consists of module names separated by colons (:). For example: \fBtitle:separator:os:kernel:uptime\fR To list all available modules, use \fB\-\-list\-modules\fR .SS "Config Files" Fastfetch uses JSONC for configuration files. JSONC is JSON with support for comments (// and /* */). Configuration files must have the .jsonc extension. You can generate a default config file using \fB\-\-gen\-config\fR. By default, the config file is saved at \fB~/.config/fastfetch/config.jsonc\fR. The configuration/preset files are searched in the following locations (in order): .RS .IP 1. 4 Relative to the current working directory .IP 2. 4 Relative to ~/.local/share/fastfetch/presets/ .IP 3. 4 Relative to /usr/share/fastfetch/presets/ .RE For detailed information on logo options, module configuration, and formatting, visit: .RS \fIhttps://github.com/fastfetch-cli/fastfetch/wiki/Configuration\fR .RE Fastfetch provides several built-in presets. List them with \fB\-\-list\-presets\fR. .SS "JSON Schema" A JSON schema is available for editor intelligence when editing the configuration file. Add the following line at the beginning of your config file: .PP \fB"$schema": "https://github.com/fastfetch-cli/fastfetch/raw/dev/doc/json_schema.json"\fR .SH EXAMPLES .TP Basic usage: .RS \fBfastfetch\fR .RE .TP Use a specific logo: .RS \fBfastfetch \-\-logo arch\fR .RE .TP Custom structure: .RS \fBfastfetch \-\-structure title:os:kernel:uptime:memory\fR .RE .TP Generate a config file: .RS \fBfastfetch \-\-gen\-config\fR .RE .TP Use a preset: .RS \fBfastfetch \-\-config neofetch\fR .RE .SH "SEE ALSO" .BR neofetch (1) .SH BUGS Please report bugs to: \fIhttps://github.com/fastfetch\-cli/fastfetch/issues\fR .SH AUTHORS Fastfetch is developed by a team of contributors on GitHub. Visit \fIhttps://github.com/fastfetch-cli/fastfetch\fR for more information. ================================================ FILE: doc/json_schema.json ================================================ { "$schema": "https://json-schema.org/draft-07/schema", "$defs": { "colors": { "oneOf": [ { "type": "string", "$comment": "https://github.com/fastfetch-cli/fastfetch/wiki/Color-Format-Specification", "examples": [ "reset_", "bright_", "dim_", "italic_", "underline_", "blink_", "inverse_", "hidden_", "strike_", "light_", "black", "red", "green", "yellow", "blue", "magenta", "cyan", "white", "default" ] }, { "type": "null", "$comment": "Disable default color" } ] }, "key": { "description": "Key of the module\nOne whitespace character (` `) can be used to hide the key", "type": "string", "minLength": 1 }, "keyColor": { "description": "Color of the module key to override the global setting `display.color.key`", "$ref": "#/$defs/colors" }, "keyWidth": { "description": "Width of the module key to override the global setting `display.keyWidth`", "type": "integer", "minimum": 1 }, "keyIcon": { "description": "Set the icon to be displayed by `display.keyType: \"icon\"`", "type": "string" }, "outputColor": { "description": "Output color of the module to override the global setting `display.color.output`", "$ref": "#/$defs/colors" }, "percentType": { "description": "Set the percentage output type", "oneOf": [ { "type": "number", "description": "0 to use global setting, 1 for percentage number, 2 for multi-color bar, 3 for both, 6 for bar only, 9 for colored number, 10 for monochrome bar", "minimum": 0, "maximum": 255, "default": 9 }, { "type": "array", "description": "Array of string flags", "items": { "enum": [ "num", "bar", "hide-others", "num-color", "bar-monochrome" ] }, "uniqueItems": true, "default": [ "num", "num-color" ] } ] }, "percent": { "description": "Thresholds for percentage colors", "type": "object", "additionalProperties": false, "properties": { "green": { "type": "integer", "minimum": 0, "maximum": 100, "description": "Values less than green will be shown in green" }, "yellow": { "type": "integer", "minimum": 0, "maximum": 100, "description": "Values greater than green and less than yellow will be shown in yellow.\nValues greater than yellow will be shown in red" }, "type": { "$ref": "#/$defs/percentType" } } }, "temperature": { "description": "Detect and display temperature if supported", "oneOf": [ { "type": "boolean", "default": false }, { "type": "object", "additionalProperties": false, "properties": { "green": { "type": "integer", "minimum": 0, "maximum": 100, "description": "Values (in celsius) less than green will be shown in green" }, "yellow": { "type": "integer", "minimum": 0, "maximum": 100, "description": "Values (in celsius) greater than green and less than yellow will be shown in yellow.\nValues greater than yellow will be shown in red" } } } ] }, "systems": { "type": "string", "enum": [ "Android", "Linux", "DragonFly", "MidnightBSD", "FreeBSD", "macOS", "Windows", "SunOS", "OpenBSD", "NetBSD", "Haiku" ] }, "architectures": { "type": "string", "enum": [ "x86_64", "i386", "ia64", "aarch64", "arm", "mips", "powerpc", "riscv", "s390x", "loongarch", "sparc", "alpha", "hppa", "m68k" ] }, "conditions": { "description": "Only show the module if conditions are met", "type": "object", "additionalProperties": false, "properties": { "system": { "description": "System name to match", "oneOf": [ { "$ref": "#/$defs/systems" }, { "type": "array", "uniqueItems": true, "items": { "$ref": "#/$defs/systems" }, "description": "Array of system names to match" }, { "type": "null", "description": "Null to disable this condition" } ] }, "!system": { "description": "System name to not match", "oneOf": [ { "$ref": "#/$defs/systems" }, { "type": "array", "uniqueItems": true, "items": { "$ref": "#/$defs/systems" }, "description": "Array of system names to not match" }, { "type": "null", "description": "Null to disable this condition" } ] }, "arch": { "description": "Architecture to match", "oneOf": [ { "$ref": "#/$defs/architectures" }, { "type": "array", "uniqueItems": true, "items": { "$ref": "#/$defs/architectures" }, "description": "Array of architectures to match" }, { "type": "null", "description": "Null to disable this condition" } ] }, "!arch": { "description": "Architecture to not match", "oneOf": [ { "$ref": "#/$defs/architectures" }, { "type": "array", "uniqueItems": true, "items": { "$ref": "#/$defs/architectures" }, "description": "Array of architectures to not match" }, { "type": "null", "description": "Null to disable this condition" } ] }, "succeeded": { "description": "Whether the module succeeded in the last run", "oneOf": [ { "type": "boolean", "description": "True to only show the module if it succeeded, false to only show it if it failed" }, { "type": "null", "description": "Null to disable this condition" } ] } } }, "spaceBeforeUnit": { "type": "string", "description": "Whether to put a space before the unit", "oneOf": [ { "const": "default", "description": "Use the default behavior of the module" }, { "const": "always", "description": "Always add a space before the unit" }, { "const": "never", "description": "Never add a space before the unit" } ] }, "batteryFormat": { "description": "Output format of the module `Battery`. See Wiki for formatting syntax\n 1. {manufacturer}: Battery manufacturer\n 2. {model-name}: Battery model name\n 3. {technology}: Battery technology\n 4. {capacity}: Battery capacity (percentage num)\n 5. {status}: Battery status\n 6. {temperature}: Battery temperature (formatted)\n 7. {cycle-count}: Battery cycle count\n 8. {serial}: Battery serial number\n 9. {manufacture-date}: Battery manufactor date\n 10. {capacity-bar}: Battery capacity (percentage bar)\n 11. {time-days}: Battery time remaining days\n 12. {time-hours}: Battery time remaining hours\n 13. {time-minutes}: Battery time remaining minutes\n 14. {time-seconds}: Battery time remaining seconds\n 15. {time-formatted}: Battery time remaining (formatted)", "type": "string" }, "biosFormat": { "description": "Output format of the module `BIOS`. See Wiki for formatting syntax\n 1. {date}: Bios date\n 2. {release}: Bios release\n 3. {vendor}: Bios vendor\n 4. {version}: Bios version\n 5. {type}: Firmware type", "type": "string" }, "bluetoothFormat": { "description": "Output format of the module `Bluetooth`. See Wiki for formatting syntax\n 1. {name}: Name\n 2. {address}: Address\n 3. {type}: Type\n 4. {battery-percentage}: Battery percentage number\n 5. {connected}: Is connected\n 6. {battery-percentage-bar}: Battery percentage bar", "type": "string" }, "bluetoothradioFormat": { "description": "Output format of the module `BluetoothRadio`. See Wiki for formatting syntax\n 1. {name}: Radio name for discovering\n 2. {address}: Address\n 3. {lmp-version}: LMP version\n 4. {lmp-subversion}: LMP subversion\n 5. {version}: Bluetooth version\n 6. {vendor}: Vendor\n 7. {discoverable}: Discoverable\n 8. {connectable}: Connectable / Pairable", "type": "string" }, "boardFormat": { "description": "Output format of the module `Board`. See Wiki for formatting syntax\n 1. {name}: Board name\n 2. {vendor}: Board vendor\n 3. {version}: Board version\n 4. {serial}: Board serial number", "type": "string" }, "bootmgrFormat": { "description": "Output format of the module `Bootmgr`. See Wiki for formatting syntax\n 1. {name}: Name / description\n 2. {firmware-path}: Firmware file path\n 3. {firmware-name}: Firmware file name\n 4. {secure-boot}: Is secure boot enabled\n 5. {order}: Boot order", "type": "string" }, "brightnessFormat": { "description": "Output format of the module `Brightness`. See Wiki for formatting syntax\n 1. {percentage}: Screen brightness (percentage num)\n 2. {name}: Screen name\n 3. {max}: Maximum brightness value\n 4. {min}: Minimum brightness value\n 5. {current}: Current brightness value\n 6. {percentage-bar}: Screen brightness (percentage bar)\n 7. {is-builtin}: Is built-in screen", "type": "string" }, "btrfsFormat": { "description": "Output format of the module `Btrfs`. See Wiki for formatting syntax\n 1. {name}: Name / Label\n 2. {uuid}: UUID\n 3. {devices}: Associated devices\n 4. {features}: Enabled features\n 5. {used}: Size used\n 6. {allocated}: Size allocated\n 7. {total}: Size total\n 8. {used-percentage}: Used percentage num\n 9. {allocated-percentage}: Allocated percentage num\n 10. {used-percentage-bar}: Used percentage bar\n 11. {allocated-percentage-bar}: Allocated percentage bar\n 12. {node-size}: Node size\n 13. {sector-size}: Sector size", "type": "string" }, "cameraFormat": { "description": "Output format of the module `Camera`. See Wiki for formatting syntax\n 1. {name}: Device name\n 2. {vendor}: Vendor\n 3. {colorspace}: Color space\n 4. {id}: Identifier\n 5. {width}: Width (in px)\n 6. {height}: Height (in px)", "type": "string" }, "chassisFormat": { "description": "Output format of the module `Chassis`. See Wiki for formatting syntax\n 1. {type}: Chassis type\n 2. {vendor}: Chassis vendor\n 3. {version}: Chassis version\n 4. {serial}: Chassis serial number", "type": "string" }, "commandFormat": { "description": "Output format of the module `Command`. See Wiki for formatting syntax\n 1. {result}: Command result", "type": "string" }, "cpuFormat": { "description": "Output format of the module `CPU`. See Wiki for formatting syntax\n 1. {name}: Name\n 2. {vendor}: Vendor\n 3. {cores-physical}: Physical core count\n 4. {cores-logical}: Logical core count\n 5. {cores-online}: Online core count\n 6. {freq-base}: Base frequency (formatted)\n 7. {freq-max}: Max frequency (formatted)\n 8. {temperature}: Temperature (formatted)\n 9. {core-types}: Logical core count grouped by frequency\n 10. {packages}: Processor package count\n 11. {march}: CPU microarchitecture\n 12. {numa-nodes}: NUMA node count", "type": "string" }, "cpucacheFormat": { "description": "Output format of the module `CPUCache`. See Wiki for formatting syntax\n 1. {result}: Separate result\n 2. {sum}: Sum result", "type": "string" }, "cpuusageFormat": { "description": "Output format of the module `CPUUsage`. See Wiki for formatting syntax\n 1. {avg}: CPU usage (percentage num, average)\n 2. {max}: CPU usage (percentage num, maximum)\n 3. {max-index}: CPU core index of maximum usage\n 4. {min}: CPU usage (percentage num, minimum)\n 5. {min-index}: CPU core index of minimum usage\n 6. {avg-bar}: CPU usage (percentage bar, average)\n 7. {max-bar}: CPU usage (percentage bar, maximum)\n 8. {min-bar}: CPU usage (percentage bar, minimum)", "type": "string" }, "cursorFormat": { "description": "Output format of the module `Cursor`. See Wiki for formatting syntax\n 1. {theme}: Cursor theme\n 2. {size}: Cursor size", "type": "string" }, "datetimeFormat": { "description": "Output format of the module `DateTime`. See Wiki for formatting syntax\n 1. {year}: Year\n 2. {year-short}: Last two digits of year\n 3. {month}: Month\n 4. {month-pretty}: Month with leading zero\n 5. {month-name}: Month name\n 6. {month-name-short}: Month name short\n 7. {week}: Week number on year\n 8. {weekday}: Weekday\n 9. {weekday-short}: Weekday short\n 10. {day-in-year}: Day in year\n 11. {day-in-month}: Day in month\n 12. {day-in-week}: Day in week\n 13. {hour}: Hour\n 14. {hour-pretty}: Hour with leading zero\n 15. {hour-12}: Hour 12h format\n 16. {hour-12-pretty}: Hour 12h format with leading zero\n 17. {minute}: Minute\n 18. {minute-pretty}: Minute with leading zero\n 19. {second}: Second\n 20. {second-pretty}: Second with leading zero\n 21. {offset-from-utc}: Offset from UTC in the ISO 8601 format\n 22. {timezone-name}: Locale-dependent timezone name or abbreviation\n 23. {day-pretty}: Day in month with leading zero", "type": "string" }, "deFormat": { "description": "Output format of the module `DE`. See Wiki for formatting syntax\n 1. {process-name}: DE process name\n 2. {pretty-name}: DE pretty name\n 3. {version}: DE version", "type": "string" }, "displayFormat": { "description": "Output format of the module `Display`. See Wiki for formatting syntax\n 1. {width}: Screen configured width (in pixels)\n 2. {height}: Screen configured height (in pixels)\n 3. {refresh-rate}: Screen configured refresh rate (in Hz)\n 4. {scaled-width}: Screen scaled width (in pixels)\n 5. {scaled-height}: Screen scaled height (in pixels)\n 6. {name}: Screen name\n 7. {type}: Screen type (Built-in or External)\n 8. {rotation}: Screen rotation (in degrees)\n 9. {is-primary}: True if being the primary screen\n 10. {physical-width}: Screen physical width (in millimeters)\n 11. {physical-height}: Screen physical height (in millimeters)\n 12. {inch}: Physical diagonal length in inches\n 13. {ppi}: Pixels per inch (PPI)\n 14. {bit-depth}: Bits per color channel\n 15. {hdr-enabled}: True if high dynamic range (HDR) mode is enabled\n 16. {manufacture-year}: Year of manufacturing\n 17. {manufacture-week}: Nth week of manufacturing in the year\n 18. {serial}: Serial number\n 19. {platform-api}: The platform API used when detecting the display\n 20. {hdr-compatible}: True if the display is HDR compatible\n 21. {scale-factor}: HiDPI scale factor\n 22. {preferred-width}: Screen preferred width (in pixels)\n 23. {preferred-height}: Screen preferred height (in pixels)\n 24. {preferred-refresh-rate}: Screen preferred refresh rate (in Hz)", "type": "string" }, "diskFormat": { "description": "Output format of the module `Disk`. See Wiki for formatting syntax\n 1. {size-used}: Size used\n 2. {size-total}: Size total\n 3. {size-percentage}: Size percentage num\n 4. {files-used}: Files used\n 5. {files-total}: Files total\n 6. {files-percentage}: Files percentage num\n 7. {is-external}: True if external volume\n 8. {is-hidden}: True if hidden volume\n 9. {filesystem}: Filesystem\n 10. {name}: Label / name\n 11. {is-readonly}: True if read-only\n 12. {create-time}: Create time in local timezone\n 13. {size-percentage-bar}: Size percentage bar\n 14. {files-percentage-bar}: Files percentage bar\n 15. {days}: Days after creation\n 16. {hours}: Hours after creation\n 17. {minutes}: Minutes after creation\n 18. {seconds}: Seconds after creation\n 19. {milliseconds}: Milliseconds after creation\n 20. {mountpoint}: Mount point / drive letter\n 21. {mount-from}: Mount from (device path)\n 22. {years}: Years integer after creation\n 23. {days-of-year}: Days of year after creation\n 24. {years-fraction}: Years fraction after creation\n 25. {size-free}: Size free\n 26. {size-available}: Size available", "type": "string" }, "diskioFormat": { "description": "Output format of the module `DiskIO`. See Wiki for formatting syntax\n 1. {size-read}: Size of data read [per second] (formatted)\n 2. {size-written}: Size of data written [per second] (formatted)\n 3. {name}: Device name\n 4. {dev-path}: Device raw file path\n 5. {bytes-read}: Size of data read [per second] (in bytes)\n 6. {bytes-written}: Size of data written [per second] (in bytes)\n 7. {read-count}: Number of reads\n 8. {write-count}: Number of writes", "type": "string" }, "dnsFormat": { "description": "Output format of the module `DNS`. See Wiki for formatting syntax\n 1. {result}: DNS result", "type": "string" }, "editorFormat": { "description": "Output format of the module `Editor`. See Wiki for formatting syntax\n 1. {type}: Type (Visual / Editor)\n 2. {name}: Name\n 3. {exe-name}: Exe name of real path\n 4. {path}: Full path of real path\n 5. {version}: Version", "type": "string" }, "fontFormat": { "description": "Output format of the module `Font`. See Wiki for formatting syntax\n 1. {font1}: Font 1\n 2. {font2}: Font 2\n 3. {font3}: Font 3\n 4. {font4}: Font 4\n 5. {combined}: Combined fonts for display", "type": "string" }, "gamepadFormat": { "description": "Output format of the module `Gamepad`. See Wiki for formatting syntax\n 1. {name}: Name\n 2. {serial}: Serial number\n 3. {battery-percentage}: Battery percentage num\n 4. {battery-percentage-bar}: Battery percentage bar", "type": "string" }, "gpuFormat": { "description": "Output format of the module `GPU`. See Wiki for formatting syntax\n 1. {vendor}: GPU vendor\n 2. {name}: GPU name\n 3. {driver}: GPU driver\n 4. {temperature}: GPU temperature\n 5. {core-count}: GPU core count\n 6. {type}: GPU type\n 7. {dedicated-total}: GPU total dedicated memory\n 8. {dedicated-used}: GPU used dedicated memory\n 9. {shared-total}: GPU total shared memory\n 10. {shared-used}: GPU used shared memory\n 11. {platform-api}: The platform API used when detecting the GPU\n 12. {frequency}: Current frequency in GHz\n 13. {index}: GPU vendor specific index\n 14. {dedicated-percentage-num}: Dedicated memory usage percentage num\n 15. {dedicated-percentage-bar}: Dedicated memory usage percentage bar\n 16. {shared-percentage-num}: Shared memory usage percentage num\n 17. {shared-percentage-bar}: Shared memory usage percentage bar\n 18. {core-usage-num}: Core usage percentage num\n 19. {core-usage-bar}: Core usage percentage bar\n 20. {memory-type}: Memory type (Windows only)", "type": "string" }, "hostFormat": { "description": "Output format of the module `Host`. See Wiki for formatting syntax\n 1. {family}: Product family\n 2. {name}: Product name\n 3. {version}: Product version\n 4. {sku}: Product sku\n 5. {vendor}: Product vendor\n 6. {serial}: Product serial number\n 7. {uuid}: Product uuid", "type": "string" }, "iconsFormat": { "description": "Output format of the module `Icons`. See Wiki for formatting syntax\n 1. {icons1}: Icons part 1\n 2. {icons2}: Icons part 2", "type": "string" }, "initsystemFormat": { "description": "Output format of the module `InitSystem`. See Wiki for formatting syntax\n 1. {name}: Init system name\n 2. {exe}: Init system exe path\n 3. {version}: Init system version path\n 4. {pid}: Init system pid", "type": "string" }, "kernelFormat": { "description": "Output format of the module `Kernel`. See Wiki for formatting syntax\n 1. {sysname}: Sysname\n 2. {release}: Release\n 3. {version}: Version\n 4. {arch}: Architecture\n 5. {display-version}: Display version\n 6. {page-size}: Page size", "type": "string" }, "keyboardFormat": { "description": "Output format of the module `Keyboard`. See Wiki for formatting syntax\n 1. {name}: Name\n 2. {serial}: Serial number", "type": "string" }, "lmFormat": { "description": "Output format of the module `LM`. See Wiki for formatting syntax\n 1. {service}: LM service\n 2. {type}: LM type\n 3. {version}: LM version", "type": "string" }, "loadavgFormat": { "description": "Output format of the module `Loadavg`. See Wiki for formatting syntax\n 1. {loadavg1}: Load average over 1min\n 2. {loadavg2}: Load average over 5min\n 3. {loadavg3}: Load average over 15min", "type": "string" }, "localeFormat": { "description": "Output format of the module `Locale`. See Wiki for formatting syntax\n 1. {result}: Locale code", "type": "string" }, "localipFormat": { "description": "Output format of the module `LocalIp`. See Wiki for formatting syntax\n 1. {ipv4}: IPv4 address\n 2. {ipv6}: IPv6 address\n 3. {mac}: MAC address\n 4. {ifname}: Interface name\n 5. {is-default-route}: Is default route\n 6. {mtu}: MTU size in bytes\n 7. {speed}: Link speed (formatted)\n 8. {flags}: Interface flags", "type": "string" }, "mediaFormat": { "description": "Output format of the module `Media`. See Wiki for formatting syntax\n 1. {combined}: Pretty media name\n 2. {title}: Media name\n 3. {artist}: Artist name\n 4. {album}: Album name\n 5. {status}: Status", "type": "string" }, "memoryFormat": { "description": "Output format of the module `Memory`. See Wiki for formatting syntax\n 1. {used}: Used size\n 2. {total}: Total size\n 3. {percentage}: Percentage used (num)\n 4. {percentage-bar}: Percentage used (bar)", "type": "string" }, "monitorFormat": { "description": "Output format of the module `Monitor`. See Wiki for formatting syntax\n 1. {name}: Display name\n 2. {width}: Native resolution width in pixels\n 3. {height}: Native resolution height in pixels\n 4. {physical-width}: Physical width in millimeters\n 5. {physical-height}: Physical height in millimeters\n 6. {inch}: Physical diagonal length in inches\n 7. {ppi}: Pixels per inch (PPI)\n 8. {manufacture-year}: Year of manufacturing\n 9. {manufacture-week}: Nth week of manufacturing in the year\n 10. {serial}: Serial number\n 11. {refresh-rate}: Maximum refresh rate in Hz\n 12. {hdr-compatible}: True if the display is HDR compatible", "type": "string" }, "mouseFormat": { "description": "Output format of the module `Mouse`. See Wiki for formatting syntax\n 1. {name}: Mouse name\n 2. {serial}: Mouse serial number", "type": "string" }, "netioFormat": { "description": "Output format of the module `NetIO`. See Wiki for formatting syntax\n 1. {rx-size}: Size of data received [per second] (formatted)\n 2. {tx-size}: Size of data sent [per second] (formatted)\n 3. {ifname}: Interface name\n 4. {is-default-route}: Is default route\n 5. {rx-bytes}: Size of data received [per second] (in bytes)\n 6. {tx-bytes}: Size of data sent [per second] (in bytes)\n 7. {rx-packets}: Number of packets received [per second]\n 8. {tx-packets}: Number of packets sent [per second]\n 9. {rx-errors}: Number of errors received [per second]\n 10. {tx-errors}: Number of errors sent [per second]\n 11. {rx-drops}: Number of packets dropped when receiving [per second]\n 12. {tx-drops}: Number of packets dropped when sending [per second]", "type": "string" }, "openclFormat": { "description": "Output format of the module `OpenCL`. See Wiki for formatting syntax\n 1. {version}: Platform version\n 2. {name}: Platform name\n 3. {vendor}: Platform vendor", "type": "string" }, "openglFormat": { "description": "Output format of the module `OpenGL`. See Wiki for formatting syntax\n 1. {version}: OpenGL version\n 2. {renderer}: OpenGL renderer\n 3. {vendor}: OpenGL vendor\n 4. {slv}: OpenGL shading language version\n 5. {library}: OpenGL library used", "type": "string" }, "osFormat": { "description": "Output format of the module `OS`. See Wiki for formatting syntax\n 1. {sysname}: Name of the kernel\n 2. {name}: Name of the OS\n 3. {pretty-name}: Pretty name of the OS, if available\n 4. {id}: ID of the OS\n 5. {id-like}: ID like of the OS\n 6. {variant}: Variant of the OS\n 7. {variant-id}: Variant ID of the OS\n 8. {version}: Version of the OS\n 9. {version-id}: Version ID of the OS\n 10. {codename}: Version codename of the OS\n 11. {build-id}: Build ID of the OS\n 12. {arch}: Architecture of the OS", "type": "string" }, "packagesFormat": { "description": "Output format of the module `Packages`. See Wiki for formatting syntax\n 1. {all}: Number of all packages\n 2. {pacman}: Number of pacman packages\n 3. {pacman-branch}: Pacman branch on manjaro\n 4. {dpkg}: Number of dpkg packages\n 5. {rpm}: Number of rpm packages\n 6. {emerge}: Number of emerge packages\n 7. {eopkg}: Number of eopkg packages\n 8. {xbps}: Number of xbps packages\n 9. {nix-system}: Number of nix-system packages\n 10. {nix-user}: Number of nix-user packages\n 11. {nix-default}: Number of nix-default packages\n 12. {apk}: Number of apk packages\n 13. {pkg}: Number of pkg packages\n 14. {flatpak-system}: Number of flatpak-system app packages\n 15. {flatpak-user}: Number of flatpak-user app packages\n 16. {snap}: Number of snap packages\n 17. {brew}: Number of brew packages\n 18. {brew-cask}: Number of brew-cask packages\n 19. {macports}: Number of macports packages\n 20. {scoop-user}: Number of scoop-user packages\n 21. {scoop-global}: Number of scoop-global packages\n 22. {choco}: Number of choco packages\n 23. {pkgtool}: Number of pkgtool packages\n 24. {paludis}: Number of paludis packages\n 25. {winget}: Number of winget packages\n 26. {opkg}: Number of opkg packages\n 27. {am-system}: Number of am-system packages\n 28. {sorcery}: Number of sorcery packages\n 29. {lpkg}: Number of lpkg packages\n 30. {lpkgbuild}: Number of lpkgbuild packages\n 31. {guix-system}: Number of guix-system packages\n 32. {guix-user}: Number of guix-user packages\n 33. {guix-home}: Number of guix-home packages\n 34. {linglong}: Number of linglong packages\n 35. {pacstall}: Number of pacstall packages\n 36. {mport}: Number of mport packages\n 37. {am-user}: Number of am-user (aka appman) packages\n 38. {pkgsrc}: Number of pkgsrc packages\n 39. {hpkg-system}: Number of hpkg-system packages\n 40. {hpkg-user}: Number of hpkg-user packages\n 41. {pisi}: Number of pisi packages\n 42. {soar}: Number of soar packages\n 43. {kiss}: Number of kiss packages\n 44. {moss}: Number of moss packages\n 45. {nix-all}: Total number of all nix packages\n 46. {flatpak-all}: Total number of all flatpak app packages\n 47. {brew-all}: Total number of all brew packages\n 48. {guix-all}: Total number of all guix packages\n 49. {hpkg-all}: Total number of all hpkg packages", "type": "string" }, "physicaldiskFormat": { "description": "Output format of the module `PhysicalDisk`. See Wiki for formatting syntax\n 1. {size}: Device size (formatted)\n 2. {name}: Device name\n 3. {interconnect}: Device interconnect type\n 4. {dev-path}: Device raw file path\n 5. {serial}: Serial number\n 6. {physical-type}: Device kind (SSD or HDD)\n 7. {removable-type}: Device kind (Removable or Fixed)\n 8. {readonly-type}: Device kind (Read-only or Read-write)\n 9. {revision}: Product revision\n 10. {temperature}: Device temperature (formatted)", "type": "string" }, "physicalmemoryFormat": { "description": "Output format of the module `PhysicalMemory`. See Wiki for formatting syntax\n 1. {bytes}: Size (in bytes)\n 2. {size}: Size formatted\n 3. {max-speed}: Max speed (in MT/s)\n 4. {running-speed}: Running speed (in MT/s)\n 5. {type}: Type (DDR4, DDR5, etc.)\n 6. {form-factor}: Form factor (SODIMM, DIMM, etc.)\n 7. {locator}: Bank/Device Locator (BANK0/SIMM0, BANK0/SIMM1, etc.)\n 8. {vendor}: Vendor\n 9. {serial}: Serial number\n 10. {part-number}: Part number\n 11. {is-ecc-enabled}: True if ECC enabled\n 12. {is-installed}: True if a memory module is installed in the slot", "type": "string" }, "playerFormat": { "description": "Output format of the module `Player`. See Wiki for formatting syntax\n 1. {player}: Pretty player name\n 2. {name}: Player name\n 3. {id}: Player Identifier\n 4. {url}: URL name", "type": "string" }, "poweradapterFormat": { "description": "Output format of the module `PowerAdapter`. See Wiki for formatting syntax\n 1. {watts}: Power adapter watts\n 2. {name}: Power adapter name\n 3. {manufacturer}: Power adapter manufacturer\n 4. {model}: Power adapter model\n 5. {description}: Power adapter description\n 6. {serial}: Power adapter serial number", "type": "string" }, "processesFormat": { "description": "Output format of the module `Processes`. See Wiki for formatting syntax\n 1. {result}: Process count", "type": "string" }, "publicipFormat": { "description": "Output format of the module `PublicIp`. See Wiki for formatting syntax\n 1. {ip}: Public IP address\n 2. {location}: Location", "type": "string" }, "shellFormat": { "description": "Output format of the module `Shell`. See Wiki for formatting syntax\n 1. {process-name}: Shell process name\n 2. {exe}: The first argument of the command line when running the shell\n 3. {exe-name}: Shell base name of arg0\n 4. {version}: Shell version\n 5. {pid}: Shell pid\n 6. {pretty-name}: Shell pretty name\n 7. {exe-path}: Shell full exe path\n 8. {tty}: Shell tty used", "type": "string" }, "soundFormat": { "description": "Output format of the module `Sound`. See Wiki for formatting syntax\n 1. {is-main}: Is main sound device\n 2. {name}: Device name\n 3. {volume-percentage}: Volume (in percentage num)\n 4. {identifier}: Identifier\n 5. {volume-percentage-bar}: Volume (in percentage bar)\n 6. {platform-api}: Platform API used", "type": "string" }, "swapFormat": { "description": "Output format of the module `Swap`. See Wiki for formatting syntax\n 1. {used}: Used size\n 2. {total}: Total size\n 3. {percentage}: Percentage used (num)\n 4. {percentage-bar}: Percentage used (bar)\n 5. {name}: Name", "type": "string" }, "terminalFormat": { "description": "Output format of the module `Terminal`. See Wiki for formatting syntax\n 1. {process-name}: Terminal process name\n 2. {exe}: The first argument of the command line when running the terminal\n 3. {exe-name}: Terminal base name of arg0\n 4. {pid}: Terminal pid\n 5. {pretty-name}: Terminal pretty name\n 6. {version}: Terminal version\n 7. {exe-path}: Terminal full exe path\n 8. {tty}: Terminal tty / pts used", "type": "string" }, "terminalfontFormat": { "description": "Output format of the module `TerminalFont`. See Wiki for formatting syntax\n 1. {combined}: Terminal font combined\n 2. {name}: Terminal font name\n 3. {size}: Terminal font size\n 4. {styles}: Terminal font styles", "type": "string" }, "terminalsizeFormat": { "description": "Output format of the module `TerminalSize`. See Wiki for formatting syntax\n 1. {rows}: Terminal rows\n 2. {columns}: Terminal columns\n 3. {width}: Terminal width (in pixels)\n 4. {height}: Terminal height (in pixels)", "type": "string" }, "terminalthemeFormat": { "description": "Output format of the module `TerminalTheme`. See Wiki for formatting syntax\n 1. {fg-color}: Terminal foreground color\n 2. {fg-type}: Terminal foreground type (Dark / Light)\n 3. {bg-color}: Terminal background color\n 4. {bg-type}: Terminal background type (Dark / Light)", "type": "string" }, "titleFormat": { "description": "Output format of the module `Title`. See Wiki for formatting syntax\n 1. {user-name}: User name\n 2. {host-name}: Host name\n 3. {home-dir}: Home directory\n 4. {exe-path}: Executable path of current process\n 5. {user-shell}: User's default shell\n 6. {user-name-colored}: User name (colored)\n 7. {at-symbol-colored}: @ symbol (colored)\n 8. {host-name-colored}: Host name (colored)\n 9. {full-user-name}: Full user name\n 10. {user-id}: UID (*nix) / SID (Windows)\n 11. {pid}: PID of current process\n 12. {cwd}: CWD with home dir replaced by `~`", "type": "string" }, "themeFormat": { "description": "Output format of the module `Theme`. See Wiki for formatting syntax\n 1. {theme1}: Theme part 1\n 2. {theme2}: Theme part 2", "type": "string" }, "tpmFormat": { "description": "Output format of the module `TPM`. See Wiki for formatting syntax\n 1. {version}: TPM device version\n 2. {description}: TPM general description", "type": "string" }, "uptimeFormat": { "description": "Output format of the module `Uptime`. See Wiki for formatting syntax\n 1. {days}: Days after boot\n 2. {hours}: Hours after boot\n 3. {minutes}: Minutes after boot\n 4. {seconds}: Seconds after boot\n 5. {milliseconds}: Milliseconds after boot\n 6. {boot-time}: Boot time in local timezone\n 7. {years}: Years integer after boot\n 8. {days-of-year}: Days of year after boot\n 9. {years-fraction}: Years fraction after boot\n 10. {formatted}: Formatted uptime", "type": "string" }, "usersFormat": { "description": "Output format of the module `Users`. See Wiki for formatting syntax\n 1. {name}: User name\n 2. {host-name}: Host name\n 3. {session-name}: Session name\n 4. {client-ip}: Client IP\n 5. {login-time}: Login Time in local timezone\n 6. {days}: Days after login\n 7. {hours}: Hours after login\n 8. {minutes}: Minutes after login\n 9. {seconds}: Seconds after login\n 10. {milliseconds}: Milliseconds after login\n 11. {years}: Years integer after login\n 12. {days-of-year}: Days of year after login\n 13. {years-fraction}: Years fraction after login", "type": "string" }, "versionFormat": { "description": "Output format of the module `Version`. See Wiki for formatting syntax\n 1. {project-name}: Project name\n 2. {version}: Version\n 3. {version-tweak}: Version tweak\n 4. {build-type}: Build type (debug or release)\n 5. {sysname}: System name\n 6. {arch}: Architecture\n 7. {cmake-built-type}: CMake build type when compiling (Debug, Release, RelWithDebInfo, MinSizeRel)\n 8. {compile-time}: Date time when compiling\n 9. {compiler}: Compiler used when compiling\n 10. {libc}: Libc used when compiling", "type": "string" }, "vulkanFormat": { "description": "Output format of the module `Vulkan`. See Wiki for formatting syntax\n 1. {driver}: Driver name\n 2. {api-version}: API version\n 3. {conformance-version}: Conformance version\n 4. {instance-version}: Instance version", "type": "string" }, "wallpaperFormat": { "description": "Output format of the module `Wallpaper`. See Wiki for formatting syntax\n 1. {file-name}: File name\n 2. {full-path}: Full path", "type": "string" }, "weatherFormat": { "description": "Output format of the module `Weather`. See Wiki for formatting syntax\n 1. {result}: Weather result", "type": "string" }, "wmFormat": { "description": "Output format of the module `WM`. See Wiki for formatting syntax\n 1. {process-name}: WM process name\n 2. {pretty-name}: WM pretty name\n 3. {protocol-name}: WM protocol name\n 4. {plugin-name}: WM plugin name\n 5. {version}: WM version", "type": "string" }, "wifiFormat": { "description": "Output format of the module `Wifi`. See Wiki for formatting syntax\n 1. {inf-desc}: Interface description\n 2. {inf-status}: Interface status\n 3. {status}: Connection status\n 4. {ssid}: Connection SSID\n 5. {bssid}: Connection BSSID\n 6. {protocol}: Connection protocol\n 7. {signal-quality}: Connection signal quality (percentage num)\n 8. {rx-rate}: Connection RX rate\n 9. {tx-rate}: Connection TX rate\n 10. {security}: Connection Security algorithm\n 11. {signal-quality-bar}: Connection signal quality (percentage bar)\n 12. {channel}: Connection channel number\n 13. {band}: Connection channel band in GHz", "type": "string" }, "wmthemeFormat": { "description": "Output format of the module `WMTheme`. See Wiki for formatting syntax\n 1. {result}: WM theme", "type": "string" }, "zpoolFormat": { "description": "Output format of the module `Zpool`. See Wiki for formatting syntax\n 1. {name}: Zpool name\n 2. {guid}: Zpool guid\n 3. {state}: Zpool state\n 4. {used}: Size used\n 5. {allocated}: Size allocated\n 6. {total}: Size total\n 7. {used-percentage}: Size used percentage num\n 8. {allocated-percentage}: Size allocated percentage num\n 9. {fragmentation-percentage}: Fragmentation percentage num\n 10. {used-percentage-bar}: Size used percentage bar\n 11. {allocated-percentage-bar}: Size allocated percentage bar\n 12. {fragmentation-percentage-bar}: Fragmentation percentage bar\n 13. {is-readonly}: Is read-only", "type": "string" } }, "type": "object", "additionalProperties": false, "title": "JSON config", "description": "JSON config file for fastfetch. Usually located at `~/.config/fastfetch/config.jsonc`", "properties": { "$schema": { "type": "string", "description": "JSON schema URL, for JSON validation and IDE intelligence", "format": "uri", "default": "https://github.com/fastfetch-cli/fastfetch/raw/dev/doc/json_schema.json" }, "logo": { "description": "Fastfetch logo configurations\nSee also https://github.com/fastfetch-cli/fastfetch/wiki/Logo-options", "oneOf": [ { "description": "Disable logo", "type": "null", "const": null }, { "description": "Set the source file of the logo or built-in ASCII art name", "type": "string" }, { "description": "Fastfetch logo configurations", "type": "object", "additionalProperties": false, "properties": { "type": { "description": "Set the type of the logo", "oneOf": [ { "const": "auto", "description": "If something is given, first try built-in, then file. Otherwise detect logo" }, { "const": "builtin", "description": "Built-in ASCII art" }, { "const": "small", "description": "Built-in ASCII art, small version (not all logos support this option)" }, { "const": "file", "description": "Text file, printed with color code replacement" }, { "const": "file-raw", "description": "Text file, printed as is" }, { "const": "data", "description": "Text data, printed with color code replacement" }, { "const": "data-raw", "description": "Text data, printed as is" }, { "const": "sixel", "description": "Image file, printed as sixel codes" }, { "const": "kitty", "description": "Image file, printed using kitty graphics protocol" }, { "const": "kitty-direct", "description": "Image file, tells the terminal emulator to read image data from the specified file" }, { "const": "kitty-icat", "description": "Image file, uses `kitten icat` to display the image. Requires binary `kitten` to be installed" }, { "const": "iterm", "description": "Image file, uses `iTerm` image protocol" }, { "const": "chafa", "description": "Image file, prints `ASCII` art image generated by `libchafa`" }, { "const": "raw", "description": "Image file, printed as `raw` binary string image" }, { "const": "none", "description": "Disable logo printing" } ], "default": "auto" }, "source": { "type": "string", "description": "Set the source file of the logo" }, "color": { "type": "object", "additionalProperties": false, "description": "Override colors in the logo", "properties": { "1": { "description": "Color 1", "$ref": "#/$defs/colors" }, "2": { "description": "Color 2", "$ref": "#/$defs/colors" }, "3": { "description": "Color 3", "$ref": "#/$defs/colors" }, "4": { "description": "Color 4", "$ref": "#/$defs/colors" }, "5": { "description": "Color 5", "$ref": "#/$defs/colors" }, "6": { "description": "Color 6", "$ref": "#/$defs/colors" }, "7": { "description": "Color 7", "$ref": "#/$defs/colors" }, "8": { "description": "Color 8", "$ref": "#/$defs/colors" }, "9": { "description": "Color 9", "$ref": "#/$defs/colors" } } }, "width": { "oneOf": [ { "type": "null", "description": "Auto detect width (default)" }, { "type": "integer", "description": "Set the width of the logo (in characters). Required for some image protocols", "minimum": 1 } ] }, "height": { "oneOf": [ { "type": "null", "description": "Auto detect width (default)" }, { "type": "integer", "description": "Set the height of the logo (in characters). Required for some image protocols", "minimum": 1 } ] }, "padding": { "type": "object", "additionalProperties": false, "description": "Set the padding of the logo", "properties": { "top": { "type": "integer", "description": "Set the top padding of the logo", "minimum": 0 }, "left": { "type": "integer", "description": "Set the left padding of the logo", "minimum": 0 }, "right": { "type": "integer", "description": "Set the right padding of the logo", "minimum": 0 } } }, "printRemaining": { "type": "boolean", "description": "Whether to print the remaining logo if it has more lines than modules to display", "default": true }, "preserveAspectRatio": { "type": "boolean", "description": "Whether to preserve the aspect ratio of the logo. Supported by iTerm image protocol only", "default": false }, "recache": { "type": "boolean", "description": "If true, regenerate image logo cache", "default": false }, "position": { "type": "string", "description": "Set the position where the logo should be displayed", "enum": [ "left", "top", "right" ], "default": "left" }, "chafa": { "type": "object", "additionalProperties": false, "description": "Chafa configuration. See chafa documentation for details", "properties": { "fgOnly": { "type": "boolean", "description": "Produce character-cell output using foreground colors only", "default": false }, "symbols": { "type": "string", "description": "Specify character symbols to employ in final output" }, "canvasMode": { "type": "string", "description": "Determine how colors are used in the output. This value maps to enum ChafaCanvasMode.", "oneOf": [ { "const": "TRUECOLOR", "description": "Use 24-bit true colors" }, { "const": "INDEXED_256", "description": "Use 256 colors" }, { "const": "INDEXED_240", "description": "Use 240 colors, but avoid using the lower 16 whose values vary between terminal environments" }, { "const": "INDEXED_16", "description": "Use 16 colors using the aixterm ANSI extension" }, { "const": "FGBG_BGFG", "description": "Use default foreground and background colors, plus inversion" }, { "const": "FGBG", "description": "Use default foreground and background colors. No ANSI codes will be used" }, { "const": "INDEXED_8", "description": "Use 8 colors, compatible with original ANSI X3.64" }, { "const": "INDEXED_16_8", "description": "Use 16 FG colors (8 of which enabled with bold/bright) and 8 BG colors" } ] }, "colorSpace": { "type": "string", "description": "Set color space used for quantization. This value maps to enum ChafaColorSpace.", "oneOf": [ { "const": "RGB", "description": "RGB color space. Fast but imprecise" }, { "const": "DIN99D", "description": "DIN99d color space. Slower, but good perceptual color precision" } ] }, "ditherMode": { "type": "string", "description": "Set output dither mode (No effect with 24-bit color). This value maps to enum ChafaDitherMode.", "oneOf": [ { "const": "NONE", "description": "No dithering" }, { "const": "ORDERED", "description": "Ordered dithering (Bayer or similar)" }, { "const": "DIFFUSION", "description": "Error diffusion dithering (Floyd-Steinberg or similar)" } ] } } } } } ] }, "general": { "description": "Fastfetch general configurations", "type": "object", "additionalProperties": false, "properties": { "thread": { "type": "boolean", "description": "Use separate threads for HTTP requests", "default": true }, "escapeBedrock": { "type": "boolean", "description": "On Bedrock Linux, whether to escape the bedrock jail", "default": true }, "playerName": { "type": "string", "description": "The name of the player to use for Media and Player modules. Linux only" }, "dsForceDrm": { "description": "Force display detection to use DRM. Linux only", "oneOf": [ { "type": "boolean", "const": false, "description": "Try `wayland`, then `x11`, then `drm`" }, { "type": "string", "description": "Use `/sys/class/drm` only", "const": "sysfs-only" }, { "type": "boolean", "const": true, "description": "Try `libdrm` first, then `sysfs` if libdrm fails" } ], "default": false }, "wmiTimeout": { "type": "integer", "description": "Set the timeout (ms) for WMI queries, `-1` for no timeout. Windows only", "default": 5000 }, "processingTimeout": { "type": "integer", "description": "Set the timeout (ms) when waiting for child processes, `-1` for no timeout", "default": 5000 }, "preRun": { "type": "string", "description": "Set the command to be executed before printing logos", "default": "" }, "detectVersion": { "type": "boolean", "description": "Whether to detect and display component versions. Mainly for benchmarking", "default": true } } }, "display": { "description": "Configure how things should be displayed", "type": "object", "additionalProperties": false, "properties": { "stat": { "description": "Show time usage (in ms) for individual modules with optional threshold", "oneOf": [ { "type": "boolean", "default": false }, { "type": "integer", "minimum": 1 } ] }, "pipe": { "type": "boolean", "description": "Whether to disable colors (auto-detected based on isatty(1) by default)", "default": false }, "showErrors": { "type": "boolean", "description": "Print occurring errors to the console. False to ignore errored modules", "default": false }, "disableLinewrap": { "type": "boolean", "description": "Whether to disable line wrap during execution", "default": true }, "hideCursor": { "type": "boolean", "description": "Whether to hide the cursor during execution", "default": true }, "separator": { "type": "string", "description": "Set the separator between key and value", "default": ": " }, "color": { "description": "Set the color of the keys and title", "oneOf": [ { "description": "Set both the colors of keys and title", "$ref": "#/$defs/colors" }, { "type": "object", "additionalProperties": false, "properties": { "keys": { "description": "Set the color of the keys", "$ref": "#/$defs/colors" }, "title": { "description": "Set the color of the title", "$ref": "#/$defs/colors" }, "output": { "description": "Set the color of the module output", "$ref": "#/$defs/colors" }, "separator": { "description": "Set the color of the key-value separator", "$ref": "#/$defs/colors" } } } ] }, "brightColor": { "description": "Set if the keys, title and ASCII logo should be printed in bright color", "type": "boolean", "default": true }, "key": { "type": "object", "additionalProperties": false, "description": "Set how module keys should be displayed", "properties": { "width": { "description": "Align the width of keys to number of characters, 0 to disable", "type": "integer", "minimum": 0, "default": 0 }, "type": { "type": "string", "description": "Set whether to show builtin icon before string keys", "oneOf": [ { "const": "none", "description": "Disable keys" }, { "const": "string", "description": "Show string keys" }, { "const": "icon", "description": "Show builtin icon (requires newest nerd font)" }, { "const": "both", "description": "Show both icon and string keys (alias of `both-1`)" }, { "const": "both-0", "description": "Show both icon and string with no spaces between them" }, { "const": "both-1", "description": "Show both icon and string with a space between them" }, { "const": "both-2", "description": "Show both icon and string with 2 spaces between them" }, { "const": "both-3", "description": "Show both icon and string with 3 spaces between them" }, { "const": "both-4", "description": "Show both icon and string with 4 spaces between them" } ], "default": "string" }, "paddingLeft": { "type": "integer", "description": "Set the left padding of keys", "minimum": 0, "default": 0 } } }, "size": { "type": "object", "additionalProperties": false, "description": "Set how size values should be displayed", "properties": { "binaryPrefix": { "type": "string", "description": "Set the binary prefix to use when formatting sizes", "oneOf": [ { "const": "iec", "description": "1024 Bytes = 1 KiB, 1024 KiB = 1 MiB, ... (standard)" }, { "const": "si", "description": "1000 Bytes = 1 kB, 1000 kB = 1 MB, ..." }, { "const": "jedec", "description": "1024 Bytes = 1 KB, 1024 KB = 1 MB, ..." } ], "default": "iec" }, "maxPrefix": { "type": "string", "description": "Set the largest binary prefix to use when formatting sizes", "enum": ["B", "kB", "MB", "GB", "TB", "PB", "EB", "ZB", "YB"], "default": "YB" }, "ndigits": { "type": "integer", "description": "Set the number of digits to keep after the decimal point when formatting sizes", "minimum": 0, "maximum": 9, "default": 2 }, "spaceBeforeUnit": { "$ref": "#/$defs/spaceBeforeUnit" } } }, "temp": { "type": "object", "additionalProperties": false, "description": "Set how temperature values should be displayed", "properties": { "unit": { "type": "string", "description": "Set the unit of temperature", "oneOf": [ { "const": "C", "description": "Celsius" }, { "const": "F", "description": "Fahrenheit" }, { "const": "K", "description": "Kelvin" }, { "const": "D", "description": "Default (alias for Celsius)" } ], "default": "D" }, "ndigits": { "type": "integer", "description": "Set the number of digits to keep after the decimal point when formatting temperature values", "minimum": 0, "maximum": 9, "default": 1 }, "color": { "type": "object", "additionalProperties": false, "description": "Set colors used in different states of temperature values", "properties": { "green": { "description": "Color used in green state", "$ref": "#/$defs/colors", "default": "green" }, "yellow": { "description": "Color used in yellow state", "$ref": "#/$defs/colors", "default": "light_yellow" }, "red": { "description": "Color used in red state", "$ref": "#/$defs/colors", "default": "light_red" } } }, "spaceBeforeUnit": { "$ref": "#/$defs/spaceBeforeUnit" } } }, "bar": { "type": "object", "additionalProperties": false, "description": "Set the bar configuration", "properties": { "char": { "type": "object", "additionalProperties": false, "description": "Set the characters used in the bar", "properties": { "elapsed": { "type": "string", "description": "Set the character to use in elapsed part", "default": "■" }, "total": { "type": "string", "description": "Set the character to use in total part", "default": "-" } } }, "border": { "oneOf": [ { "type": "null", "description": "Disable bar borders" }, { "type": "object", "additionalProperties": false, "description": "Set the string to use of borders of percentage bars", "properties": { "left": { "type": "string", "description": "Set the string to use at left border", "default": "[ " }, "right": { "type": "string", "description": "Set the string to use at right border", "default": " ]" }, "leftElapsed": { "type": "string", "description": "If both leftElapsed and rightElapsed are set, the border will be used as parts of bar content", "default": "" }, "rightElapsed": { "type": "string", "description": "If both leftElapsed and rightElapsed are set, the border will be used as parts of bar content", "default": "" } } } ] }, "color": { "oneOf": [ { "type": "null", "description": "Disable color in percentage bars" }, { "type": "object", "additionalProperties": false, "description": "Set the color to use of percentage bars", "properties": { "elapsed": { "description": "Color to use in the elapsed part of percentage bars\nBy default, auto selected by percent.color.{green,yellow,red}", "$ref": "#/$defs/colors", "default": "auto" }, "total": { "description": "Color to use in the total part of percentage bars", "$ref": "#/$defs/colors", "default": "light_white" }, "border": { "description": "Color to use in the borders of percentage bars", "$ref": "#/$defs/colors", "default": "light_white" } } } ] }, "width": { "type": "integer", "description": "Set the width of the bar, in number of characters", "minimum": 1, "default": 10 } } }, "percent": { "type": "object", "additionalProperties": false, "description": "Set how percentage values should be displayed", "properties": { "type": { "$ref": "#/$defs/percentType" }, "ndigits": { "type": "number", "description": "Set the number of digits to keep after the decimal point when formatting percentage numbers", "minimum": 0, "maximum": 9, "default": 0 }, "color": { "type": "object", "additionalProperties": false, "description": "Set colors used in different states of percentage bars and numbers", "properties": { "green": { "description": "Color used in green state", "$ref": "#/$defs/colors", "default": "green" }, "yellow": { "description": "Color used in yellow state", "$ref": "#/$defs/colors", "default": "light_yellow" }, "red": { "description": "Color used in red state", "$ref": "#/$defs/colors", "default": "light_red" } } }, "spaceBeforeUnit": { "$ref": "#/$defs/spaceBeforeUnit" }, "width": { "type": "integer", "description": "Set the width of the percentage number, in number of characters", "minimum": 0, "default": 0 } } }, "freq": { "type": "object", "additionalProperties": false, "description": "Set how frequency values should be displayed", "properties": { "ndigits": { "description": "Set the number of decimal places to display when formatting frequency values", "oneOf": [ { "type": "integer", "minimum": 0, "maximum": 9, "description": "Integer value displays the frequency in GHz with specified decimal places" }, { "type": "null", "description": "Null value display the frequency as integer MHz" } ], "default": 2 }, "spaceBeforeUnit": { "$ref": "#/$defs/spaceBeforeUnit" } } }, "duration": { "type": "object", "description": "Set how duration values should be displayed", "properties": { "abbreviation": { "type": "boolean", "description": "Set whether to abbreviate duration values\nIf true, the output will be in the form of \"1h 2m\" instead of \"1 hour, 2 mins\"", "default": false }, "spaceBeforeUnit": { "$ref": "#/$defs/spaceBeforeUnit" } } }, "fraction": { "type": "object", "additionalProperties": false, "description": "Set how ordinary fraction numbers should be displayed", "properties": { "ndigits": { "oneOf": [ { "type": "number", "description": "Set the number of digits to keep after the decimal point when formatting ordinary fraction numbers", "minimum": 0, "maximum": 9 }, { "type": "null", "description": "The number of digits will be automatically determined based on the value" } ], "default": 2 }, "trailingZeros": { "description": "Set when to keep trailing zeros", "oneOf": [ { "type": "null", "description": "Same as `default`" }, { "const": "default", "description": "Use the behavior defined internally" }, { "const": "always", "description": "Always keep trailing zeros" }, { "const": "never", "description": "Never keep trailing zeros" } ], "default": null } } }, "noBuffer": { "type": "boolean", "description": "Whether to disable the stdout application buffer", "default": false }, "constants": { "type": "array", "description": "List of strings to be used in custom format of modules", "items": { "type": "string" } } } }, "modules": { "description": "Fastfetch modules to run", "type": "array", "items": { "anyOf": [ { "type": "string", "description": "Run module with default configurations", "enum": [ "battery", "bios", "bluetooth", "bluetoothradio", "board", "bootmgr", "break", "brightness", "btrfs", "camera", "chassis", "cpu", "cpucache", "cpuusage", "command", "colors", "cursor", "datetime", "display", "disk", "diskio", "de", "dns", "editor", "font", "gamepad", "gpu", "host", "icons", "initsystem", "keyboard", "kernel", "lm", "loadavg", "locale", "localip", "media", "memory", "monitor", "mouse", "netio", "opencl", "opengl", "os", "packages", "physicaldisk", "physicalmemory", "player", "poweradapter", "processes", "publicip", "separator", "shell", "sound", "swap", "terminal", "terminalfont", "terminalsize", "terminaltheme", "title", "theme", "tpm", "uptime", "users", "version", "vulkan", "wallpaper", "weather", "wm", "wifi", "wmtheme", "zpool" ] }, { "type": "object", "description": "Run module with custom configurations", "required": [ "type" ], "properties": { "type": { "type": "string" } }, "oneOf": [ { "title": "Break", "type": "object", "additionalProperties": false, "properties": { "type": { "const": "break", "description": "Print a empty line" }, "condition": { "$ref": "#/$defs/conditions" } } }, { "title": "Battery", "type": "object", "additionalProperties": false, "properties": { "type": { "const": "battery", "description": "Print battery capacity, status, etc" }, "useSetupApi": { "description": "Set if `CM API` should be used on Windows to detect battery info, which supports multi batteries, but slower. Windows only", "type": "boolean", "default": false }, "temp": { "$ref": "#/$defs/temperature" }, "percent": { "$ref": "#/$defs/percent" }, "key": { "$ref": "#/$defs/key" }, "keyColor": { "$ref": "#/$defs/keyColor" }, "keyIcon": { "$ref": "#/$defs/keyIcon" }, "keyWidth": { "$ref": "#/$defs/keyWidth" }, "outputColor": { "$ref": "#/$defs/outputColor" }, "format": { "$ref": "#/$defs/batteryFormat" }, "condition": { "$ref": "#/$defs/conditions" } } }, { "title": "BIOS", "type": "object", "additionalProperties": false, "properties": { "type": { "description": "Print information of 1st-stage bootloader (name, version, release date, etc)", "const": "bios" }, "key": { "$ref": "#/$defs/key" }, "keyColor": { "$ref": "#/$defs/keyColor" }, "keyIcon": { "$ref": "#/$defs/keyIcon" }, "keyWidth": { "$ref": "#/$defs/keyWidth" }, "outputColor": { "$ref": "#/$defs/outputColor" }, "format": { "$ref": "#/$defs/biosFormat" }, "condition": { "$ref": "#/$defs/conditions" } } }, { "title": "Bluetooth", "type": "object", "additionalProperties": false, "properties": { "type": { "description": "List (connected) bluetooth devices", "const": "bluetooth" }, "showDisconnected": { "description": "Set if disconnected bluetooth devices should be printed", "type": "boolean", "default": false }, "percent": { "$ref": "#/$defs/percent" }, "key": { "$ref": "#/$defs/key" }, "keyColor": { "$ref": "#/$defs/keyColor" }, "keyIcon": { "$ref": "#/$defs/keyIcon" }, "keyWidth": { "$ref": "#/$defs/keyWidth" }, "outputColor": { "$ref": "#/$defs/outputColor" }, "format": { "$ref": "#/$defs/bluetoothFormat" }, "condition": { "$ref": "#/$defs/conditions" } } }, { "title": "Bluetooth Radio", "type": "object", "additionalProperties": false, "properties": { "type": { "description": "List bluetooth radios width supported version and vendor", "const": "bluetoothradio" }, "key": { "$ref": "#/$defs/key" }, "keyColor": { "$ref": "#/$defs/keyColor" }, "keyIcon": { "$ref": "#/$defs/keyIcon" }, "keyWidth": { "$ref": "#/$defs/keyWidth" }, "outputColor": { "$ref": "#/$defs/outputColor" }, "format": { "$ref": "#/$defs/bluetoothradioFormat" }, "condition": { "$ref": "#/$defs/conditions" } } }, { "title": "Board", "type": "object", "additionalProperties": false, "properties": { "type": { "description": "Print motherboard name and other info", "const": "board" }, "key": { "$ref": "#/$defs/key" }, "keyColor": { "$ref": "#/$defs/keyColor" }, "keyIcon": { "$ref": "#/$defs/keyIcon" }, "keyWidth": { "$ref": "#/$defs/keyWidth" }, "outputColor": { "$ref": "#/$defs/outputColor" }, "format": { "$ref": "#/$defs/boardFormat" }, "condition": { "$ref": "#/$defs/conditions" } } }, { "title": "Boot Manager", "type": "object", "additionalProperties": false, "properties": { "type": { "description": "Print information of 2nd-stage bootloader (name, firmware, etc)", "const": "bootmgr" }, "key": { "$ref": "#/$defs/key" }, "keyColor": { "$ref": "#/$defs/keyColor" }, "keyIcon": { "$ref": "#/$defs/keyIcon" }, "keyWidth": { "$ref": "#/$defs/keyWidth" }, "outputColor": { "$ref": "#/$defs/outputColor" }, "format": { "$ref": "#/$defs/bootmgrFormat" }, "condition": { "$ref": "#/$defs/conditions" } } }, { "title": "Brightness", "type": "object", "additionalProperties": false, "properties": { "type": { "const": "brightness", "description": "Print current brightness level of your monitors" }, "percent": { "$ref": "#/$defs/percent" }, "ddcciSleep": { "type": "integer", "description": "Set the sleep times (in ms) when sending DDC/CI requests.\nSee for detail", "minimum": 0, "maximum": 400, "default": 10 }, "compact": { "description": "Set if multiple results should be printed in one line", "type": "boolean", "default": false }, "key": { "$ref": "#/$defs/key" }, "keyColor": { "$ref": "#/$defs/keyColor" }, "keyIcon": { "$ref": "#/$defs/keyIcon" }, "keyWidth": { "$ref": "#/$defs/keyWidth" }, "outputColor": { "$ref": "#/$defs/outputColor" }, "format": { "$ref": "#/$defs/brightnessFormat" }, "condition": { "$ref": "#/$defs/conditions" } } }, { "title": "BTRFS", "type": "object", "additionalProperties": false, "properties": { "type": { "const": "btrfs", "description": "Print Linux BTRFS volumes" }, "percent": { "$ref": "#/$defs/percent" }, "key": { "$ref": "#/$defs/key" }, "keyColor": { "$ref": "#/$defs/keyColor" }, "keyIcon": { "$ref": "#/$defs/keyIcon" }, "keyWidth": { "$ref": "#/$defs/keyWidth" }, "outputColor": { "$ref": "#/$defs/outputColor" }, "format": { "$ref": "#/$defs/btrfsFormat" }, "condition": { "$ref": "#/$defs/conditions" } } }, { "title": "Camera", "type": "object", "additionalProperties": false, "properties": { "type": { "description": "Print available cameras", "const": "camera" }, "key": { "$ref": "#/$defs/key" }, "keyColor": { "$ref": "#/$defs/keyColor" }, "keyIcon": { "$ref": "#/$defs/keyIcon" }, "keyWidth": { "$ref": "#/$defs/keyWidth" }, "outputColor": { "$ref": "#/$defs/outputColor" }, "format": { "$ref": "#/$defs/cameraFormat" }, "condition": { "$ref": "#/$defs/conditions" } } }, { "title": "Chassis", "type": "object", "additionalProperties": false, "properties": { "type": { "const": "chassis", "description": "Print chassis type (desktop, laptop, etc)" }, "key": { "$ref": "#/$defs/key" }, "keyColor": { "$ref": "#/$defs/keyColor" }, "keyIcon": { "$ref": "#/$defs/keyIcon" }, "keyWidth": { "$ref": "#/$defs/keyWidth" }, "outputColor": { "$ref": "#/$defs/outputColor" }, "format": { "$ref": "#/$defs/chassisFormat" }, "condition": { "$ref": "#/$defs/conditions" } } }, { "title": "CPU", "type": "object", "additionalProperties": false, "properties": { "type": { "description": "Print CPU name, frequency, etc", "const": "cpu" }, "temp": { "$ref": "#/$defs/temperature" }, "tempSensor": { "description": "Set the temperature sensor to use for CPU temperature detection\n* Linux: `hwmon` or `thermal` path name (eg. `hwmon0`, `thermal_zone0)`\n* macOS: SMC sensor key (eg. `Tp01`)\n* Windows: thermal zone key (eg. `\\_TZ.CPUZ`)\n* FreeBSD: sysctl key (eg. `dev.cpu.0.temperature`)\n* NetBSD: sysmon sensor key (eg. `coretemp0`)", "type": "string" }, "showPeCoreCount": { "description": "Detect and display CPU frequency of different core types (eg. Pcore and Ecore) if supported", "type": "boolean", "default": false }, "key": { "$ref": "#/$defs/key" }, "keyColor": { "$ref": "#/$defs/keyColor" }, "keyIcon": { "$ref": "#/$defs/keyIcon" }, "keyWidth": { "$ref": "#/$defs/keyWidth" }, "outputColor": { "$ref": "#/$defs/outputColor" }, "format": { "$ref": "#/$defs/cpuFormat" }, "condition": { "$ref": "#/$defs/conditions" } } }, { "title": "CPU Cache", "type": "object", "additionalProperties": false, "properties": { "type": { "const": "cpucache", "description": "Print CPU cache sizes" }, "percent": { "$ref": "#/$defs/percent" }, "key": { "$ref": "#/$defs/key" }, "keyColor": { "$ref": "#/$defs/keyColor" }, "keyIcon": { "$ref": "#/$defs/keyIcon" }, "keyWidth": { "$ref": "#/$defs/keyWidth" }, "outputColor": { "$ref": "#/$defs/outputColor" }, "format": { "$ref": "#/$defs/cpucacheFormat" }, "condition": { "$ref": "#/$defs/conditions" } } }, { "title": "CPU Usage", "type": "object", "additionalProperties": false, "properties": { "type": { "const": "cpuusage", "description": "Print CPU usage. Costs some time to collect data" }, "percent": { "$ref": "#/$defs/percent" }, "separate": { "type": "boolean", "description": "Display CPU usage per CPU logical core, instead of an average result", "default": false }, "waitTime": { "type": "integer", "description": "Wait time (in ms). CPU usage = (inUseEnd - inUseStart) / waitTime", "default": 200, "minimum": 1 }, "key": { "$ref": "#/$defs/key" }, "keyColor": { "$ref": "#/$defs/keyColor" }, "keyIcon": { "$ref": "#/$defs/keyIcon" }, "keyWidth": { "$ref": "#/$defs/keyWidth" }, "outputColor": { "$ref": "#/$defs/outputColor" }, "format": { "$ref": "#/$defs/cpuusageFormat" }, "condition": { "$ref": "#/$defs/conditions" } } }, { "title": "Colors", "type": "object", "additionalProperties": false, "properties": { "type": { "description": "Display the terminal's 16-color palette", "const": "colors" }, "symbol": { "description": "Set the symbol to use", "type": "string", "oneOf": [ { "const": "block", "description": "\u2588\u2588\u2588" }, { "const": "background", "description": "(whitespaces with background)" }, { "const": "circle", "description": "\u25cf" }, { "const": "diamond", "description": "\u25c6" }, { "const": "triangle", "description": "\u25b2" }, { "const": "square", "description": "\u25a0" }, { "const": "star", "description": "\u2605" } ], "default": "background" }, "paddingLeft": { "description": "Set the number of white spaces to print before the symbol", "type": "integer", "minimum": 0, "default": 0 }, "block": { "description": "Set behavior of block printing", "type": "object", "additionalProperties": false, "properties": { "width": { "description": "Set the block width in spaces", "type": "integer", "minimum": 1, "default": 3 }, "range": { "description": "Set the range of colors in the blocks to print", "type": "array", "items": { "type": "integer", "minimum": 0, "maximum": 15 }, "minItems": 2, "maxItems": 2 } } }, "key": { "$ref": "#/$defs/key" }, "keyColor": { "$ref": "#/$defs/keyColor" }, "keyIcon": { "$ref": "#/$defs/keyIcon" }, "keyWidth": { "$ref": "#/$defs/keyWidth" }, "condition": { "$ref": "#/$defs/conditions" } } }, { "title": "Command", "type": "object", "additionalProperties": false, "properties": { "type": { "description": "Running custom shell scripts", "const": "command" }, "shell": { "description": "Set the shell program to execute the command text\nDefault: cmd for Windows, /bin/sh for *nix", "type": "string" }, "param": { "description": "Set the parameter used when starting the shell\nIf set to an empty string, it will be ignored\nDefault: /c for Windows, -c for *nix", "type": "string" }, "text": { "description": "Set the command text to be executed", "type": "string" }, "useStdErr": { "description": "Set if stderr should be used instead of stdout for command output", "type": "boolean", "default": false }, "parallel": { "description": "Set if the command should be executed in parallel with other commands\nImprove performance when using multiple commands, but may cause issues with some commands", "type": "boolean", "default": true }, "splitLines": { "description": "Set if the command output should be split into multiple lines", "type": "boolean", "default": false }, "key": { "$ref": "#/$defs/key" }, "keyColor": { "$ref": "#/$defs/keyColor" }, "keyIcon": { "$ref": "#/$defs/keyIcon" }, "keyWidth": { "$ref": "#/$defs/keyWidth" }, "outputColor": { "$ref": "#/$defs/outputColor" }, "format": { "$ref": "#/$defs/commandFormat" }, "condition": { "$ref": "#/$defs/conditions" } } }, { "title": "Cursor", "type": "object", "additionalProperties": false, "properties": { "type": { "const": "cursor", "description": "Print cursor style name" }, "percent": { "$ref": "#/$defs/percent" }, "key": { "$ref": "#/$defs/key" }, "keyColor": { "$ref": "#/$defs/keyColor" }, "keyIcon": { "$ref": "#/$defs/keyIcon" }, "keyWidth": { "$ref": "#/$defs/keyWidth" }, "outputColor": { "$ref": "#/$defs/outputColor" }, "format": { "$ref": "#/$defs/cursorFormat" }, "condition": { "$ref": "#/$defs/conditions" } } }, { "title": "Custom", "type": "object", "additionalProperties": false, "properties": { "type": { "description": "Print a custom string, with or without key", "const": "custom" }, "key": { "description": "Leave empty not to print the key", "type": "string" }, "keyColor": { "$ref": "#/$defs/keyColor" }, "keyIcon": { "$ref": "#/$defs/keyIcon" }, "keyWidth": { "$ref": "#/$defs/keyWidth" }, "outputColor": { "$ref": "#/$defs/outputColor" }, "format": { "description": "Text to print", "type": "string" }, "condition": { "$ref": "#/$defs/conditions" } } }, { "title": "Date Time", "type": "object", "additionalProperties": false, "properties": { "type": { "const": "datetime", "description": "Print current date and time" }, "percent": { "$ref": "#/$defs/percent" }, "key": { "$ref": "#/$defs/key" }, "keyColor": { "$ref": "#/$defs/keyColor" }, "keyIcon": { "$ref": "#/$defs/keyIcon" }, "keyWidth": { "$ref": "#/$defs/keyWidth" }, "outputColor": { "$ref": "#/$defs/outputColor" }, "format": { "$ref": "#/$defs/datetimeFormat" }, "condition": { "$ref": "#/$defs/conditions" } } }, { "title": "Display", "type": "object", "additionalProperties": false, "properties": { "type": { "description": "Print resolutions, refresh rates, etc", "const": "display" }, "compactType": { "description": "Set if all displays should be printed in one line", "oneOf": [ { "const": null, "description": "Disable compact mode" }, { "const": "none", "description": "Disable compact mode (kept for compatibility)" }, { "const": "original", "description": "Print original resolutions" }, { "const": "scaled", "description": "Print scaled resolutions" }, { "const": "original-with-refresh-rate", "description": "Print original resolutions with refresh rates" }, { "const": "scaled-with-refresh-rate", "description": "Print scaled resolutions with refresh rates" } ], "default": null }, "preciseRefreshRate": { "description": "Set if decimal refresh rates should not be rounded into integers when printing", "type": "boolean", "default": false }, "order": { "description": "Set the order should be used when printing", "oneOf": [ { "const": null, "description": "Use the default order" }, { "const": "none", "description": "Use the detected order (kept for compatibility)" }, { "const": "asc", "description": "Sort by display name in ascending order" }, { "const": "desc", "description": "Sort by display name in descending order" } ], "default": null }, "key": { "$ref": "#/$defs/key" }, "keyColor": { "$ref": "#/$defs/keyColor" }, "keyIcon": { "$ref": "#/$defs/keyIcon" }, "keyWidth": { "$ref": "#/$defs/keyWidth" }, "outputColor": { "$ref": "#/$defs/outputColor" }, "format": { "$ref": "#/$defs/displayFormat" }, "condition": { "$ref": "#/$defs/conditions" } } }, { "title": "Disk", "type": "object", "additionalProperties": false, "properties": { "type": { "description": "Print partitions, space usage, disk type, etc", "const": "disk" }, "folders": { "description": "A list of folder paths for the disk output\nDefault: auto detection using mount-points\nThis option overrides other `show*` options", "oneOf": [ { "type": "string", "description": "A colon (semicolon on Windows) separated list of folder paths to get disk usage from", "default": "/" }, { "type": "array", "description": "An array of folder paths to get disk usage from", "items": { "type": "string" }, "minItems": 1, "uniqueItems": true } ] }, "hideFolders": { "description": "A list of folder paths (or glob patterns) to hide from the disk output", "oneOf": [ { "type": "string", "description": "A colon (semicolon on Windows) separated list of folder paths to hide from the disk output" }, { "type": "array", "description": "An array of folder paths to hide from the disk output", "items": { "type": "string" }, "minItems": 1, "uniqueItems": true } ], "default": "/efi:/boot:/boot/*" }, "hideFS": { "description": "A list of file systems to hide from the disk output", "oneOf": [ { "type": "string", "description": "A colon separated list of file systems to hide from the disk output" }, { "type": "array", "description": "An array of file systems to hide from the disk output", "items": { "type": "string" }, "minItems": 1, "uniqueItems": true } ] }, "showRegular": { "type": "boolean", "description": "Set if regular volume should be printed", "default": true }, "showExternal": { "type": "boolean", "description": "Set if external volume should be printed", "default": true }, "showHidden": { "type": "boolean", "description": "Set if hidden volumes should be printed", "default": false }, "showSubvolumes": { "type": "boolean", "description": "Set if subvolumes should be printed", "default": false }, "showReadOnly": { "type": "boolean", "description": "Set if read only volumes should be printed", "default": false }, "showUnknown": { "type": "boolean", "description": "Set if unknown (unable to detect sizes) volumes should be printed", "default": false }, "useAvailable": { "type": "boolean", "description": "Use f_bavail (lpFreeBytesAvailableToCaller for Windows) instead of f_bfree to calculate used bytes\nMay be required for macOS to display correct results", "default": false }, "percent": { "$ref": "#/$defs/percent" }, "key": { "$ref": "#/$defs/key" }, "keyColor": { "$ref": "#/$defs/keyColor" }, "keyIcon": { "$ref": "#/$defs/keyIcon" }, "keyWidth": { "$ref": "#/$defs/keyWidth" }, "outputColor": { "$ref": "#/$defs/outputColor" }, "format": { "$ref": "#/$defs/diskFormat" }, "condition": { "$ref": "#/$defs/conditions" } } }, { "title": "DiskIO", "type": "object", "additionalProperties": false, "properties": { "type": { "description": "Print physical disk I/O throughput", "const": "diskio" }, "namePrefix": { "description": "Show disks with given name prefix only", "type": "string" }, "detectTotal": { "description": "Detect total bytes instead of current rate", "type": "boolean", "default": false }, "waitTime": { "type": "integer", "description": "Wait time (in ms). Disk I/O = (totalBytesEnd - totalBytesStart) / waitTime", "default": 200, "minimum": 1 }, "key": { "$ref": "#/$defs/key" }, "keyColor": { "$ref": "#/$defs/keyColor" }, "keyIcon": { "$ref": "#/$defs/keyIcon" }, "keyWidth": { "$ref": "#/$defs/keyWidth" }, "outputColor": { "$ref": "#/$defs/outputColor" }, "format": { "$ref": "#/$defs/diskioFormat" }, "condition": { "$ref": "#/$defs/conditions" } } }, { "title": "Desktop Environment", "type": "object", "additionalProperties": false, "properties": { "type": { "const": "de", "description": "Print desktop environment name" }, "key": { "$ref": "#/$defs/key" }, "keyColor": { "$ref": "#/$defs/keyColor" }, "keyIcon": { "$ref": "#/$defs/keyIcon" }, "keyWidth": { "$ref": "#/$defs/keyWidth" }, "outputColor": { "$ref": "#/$defs/outputColor" }, "format": { "$ref": "#/$defs/deFormat" }, "condition": { "$ref": "#/$defs/conditions" } } }, { "title": "DNS", "type": "object", "additionalProperties": false, "properties": { "type": { "const": "dns", "description": "Print DNS servers" }, "showType": { "oneOf": [ { "const": "ipv4", "description": "Show IPv4 addresses only" }, { "const": "ipv6", "description": "Show IPv6 addresses only" }, { "const": "both", "description": "Show both IPv4 and IPv6 addresses" } ], "default": "both", "description": "Specify the type of DNS servers should be detected" }, "key": { "$ref": "#/$defs/key" }, "keyColor": { "$ref": "#/$defs/keyColor" }, "keyIcon": { "$ref": "#/$defs/keyIcon" }, "keyWidth": { "$ref": "#/$defs/keyWidth" }, "outputColor": { "$ref": "#/$defs/outputColor" }, "format": { "$ref": "#/$defs/dnsFormat" }, "condition": { "$ref": "#/$defs/conditions" } } }, { "title": "Editor", "type": "object", "additionalProperties": false, "properties": { "type": { "const": "editor", "description": "Print information of the default editor ($VISUAL or $EDITOR)" }, "percent": { "$ref": "#/$defs/percent" }, "key": { "$ref": "#/$defs/key" }, "keyColor": { "$ref": "#/$defs/keyColor" }, "keyIcon": { "$ref": "#/$defs/keyIcon" }, "keyWidth": { "$ref": "#/$defs/keyWidth" }, "outputColor": { "$ref": "#/$defs/outputColor" }, "format": { "$ref": "#/$defs/editorFormat" }, "condition": { "$ref": "#/$defs/conditions" } } }, { "title": "Font", "type": "object", "additionalProperties": false, "properties": { "type": { "const": "font", "description": "Print system font names" }, "percent": { "$ref": "#/$defs/percent" }, "key": { "$ref": "#/$defs/key" }, "keyColor": { "$ref": "#/$defs/keyColor" }, "keyIcon": { "$ref": "#/$defs/keyIcon" }, "keyWidth": { "$ref": "#/$defs/keyWidth" }, "outputColor": { "$ref": "#/$defs/outputColor" }, "format": { "$ref": "#/$defs/fontFormat" }, "condition": { "$ref": "#/$defs/conditions" } } }, { "title": "Gamepad", "type": "object", "additionalProperties": false, "properties": { "type": { "const": "gamepad", "description": "List connected gamepads" }, "ignores": { "type": "array", "description": "An array of case-insensitive device name prefixes to ignore", "items": { "type": "string" }, "minItems": 1, "uniqueItems": true }, "percent": { "$ref": "#/$defs/percent" }, "key": { "$ref": "#/$defs/key" }, "keyColor": { "$ref": "#/$defs/keyColor" }, "keyIcon": { "$ref": "#/$defs/keyIcon" }, "keyWidth": { "$ref": "#/$defs/keyWidth" }, "outputColor": { "$ref": "#/$defs/outputColor" }, "format": { "$ref": "#/$defs/gamepadFormat" }, "condition": { "$ref": "#/$defs/conditions" } } }, { "title": "GPU", "type": "object", "additionalProperties": false, "properties": { "type": { "description": "Print GPU names, graphic memory size, type, etc", "const": "gpu" }, "temp": { "$ref": "#/$defs/temperature" }, "driverSpecific": { "description": "Use driver specific method to detect more detailed GPU information (memory usage, core count, etc)\nRequires the latest GPU drivers to be installed.", "type": "boolean", "default": false }, "detectionMethod": { "description": "Force using a specified method to detect GPUs", "type": "string", "oneOf": [ { "const": "auto", "description": "Query platform-specific graphics APIs.\nRequires proper GPU drivers to be installed.\nSupported on Linux, FreeBSD, Windows and macOS" }, { "const": "pci", "description": "Search PCI devices, which does not require GPU drivers to be installed.\nNot supported on Windows and macOS" }, { "const": "vulkan", "description": "Use Vulkan API.\nSlow and requires proper Vulkan drivers to be installed.\nUsed for Android" }, { "const": "opencl", "description": "Use OpenCL API.\nSlow and requires proper OpenCL drivers to be installed" }, { "const": "opengl", "description": "Use OpenGL API.\nSlow and only detects one GPU.\nUsed for OpenBSD" } ], "default": "" }, "hideType": { "description": "Specify the type of GPUs should not be printed", "oneOf": [ { "const": null, "description": "Do not hide any GPUs" }, { "const": "none", "description": "Do not hide any GPUs (kept for compatibility)" }, { "const": "integrated", "description": "Hide integrated GPUs" }, { "const": "discrete", "description": "Hide discrete GPUs" }, { "const": "unknown", "description": "Hide unknown (unrecognized) GPUs" } ], "default": null }, "percent": { "$ref": "#/$defs/percent" }, "key": { "$ref": "#/$defs/key" }, "keyColor": { "$ref": "#/$defs/keyColor" }, "keyIcon": { "$ref": "#/$defs/keyIcon" }, "keyWidth": { "$ref": "#/$defs/keyWidth" }, "outputColor": { "$ref": "#/$defs/outputColor" }, "format": { "$ref": "#/$defs/gpuFormat" }, "condition": { "$ref": "#/$defs/conditions" } } }, { "title": "Host", "type": "object", "additionalProperties": false, "properties": { "type": { "const": "host", "description": "Print product name of your computer" }, "key": { "$ref": "#/$defs/key" }, "keyColor": { "$ref": "#/$defs/keyColor" }, "keyIcon": { "$ref": "#/$defs/keyIcon" }, "keyWidth": { "$ref": "#/$defs/keyWidth" }, "outputColor": { "$ref": "#/$defs/outputColor" }, "format": { "$ref": "#/$defs/hostFormat" }, "condition": { "$ref": "#/$defs/conditions" } } }, { "title": "Icons", "type": "object", "additionalProperties": false, "properties": { "type": { "const": "icons", "description": "Print icon style name" }, "key": { "$ref": "#/$defs/key" }, "keyColor": { "$ref": "#/$defs/keyColor" }, "keyIcon": { "$ref": "#/$defs/keyIcon" }, "keyWidth": { "$ref": "#/$defs/keyWidth" }, "outputColor": { "$ref": "#/$defs/outputColor" }, "format": { "$ref": "#/$defs/iconsFormat" }, "condition": { "$ref": "#/$defs/conditions" } } }, { "title": "Init System", "type": "object", "additionalProperties": false, "properties": { "type": { "const": "initsystem", "description": "Print init system (pid 1) name and version" }, "key": { "$ref": "#/$defs/key" }, "keyColor": { "$ref": "#/$defs/keyColor" }, "keyIcon": { "$ref": "#/$defs/keyIcon" }, "keyWidth": { "$ref": "#/$defs/keyWidth" }, "outputColor": { "$ref": "#/$defs/outputColor" }, "format": { "$ref": "#/$defs/initsystemFormat" }, "condition": { "$ref": "#/$defs/conditions" } } }, { "title": "Kernel", "type": "object", "additionalProperties": false, "properties": { "type": { "const": "kernel", "description": "Print system kernel version" }, "key": { "$ref": "#/$defs/key" }, "keyColor": { "$ref": "#/$defs/keyColor" }, "keyIcon": { "$ref": "#/$defs/keyIcon" }, "keyWidth": { "$ref": "#/$defs/keyWidth" }, "outputColor": { "$ref": "#/$defs/outputColor" }, "format": { "$ref": "#/$defs/kernelFormat" }, "condition": { "$ref": "#/$defs/conditions" } } }, { "title": "Keyboard", "type": "object", "additionalProperties": false, "properties": { "type": { "const": "keyboard", "description": "List (connected) keyboards" }, "ignores": { "type": "array", "description": "An array of case-insensitive device name prefixes to ignore", "items": { "type": "string" }, "minItems": 1, "uniqueItems": true }, "key": { "$ref": "#/$defs/key" }, "keyColor": { "$ref": "#/$defs/keyColor" }, "keyIcon": { "$ref": "#/$defs/keyIcon" }, "keyWidth": { "$ref": "#/$defs/keyWidth" }, "outputColor": { "$ref": "#/$defs/outputColor" }, "format": { "$ref": "#/$defs/keyboardFormat" }, "condition": { "$ref": "#/$defs/conditions" } } }, { "title": "Login Manager", "type": "object", "additionalProperties": false, "properties": { "type": { "const": "lm", "description": "Print login manager (desktop manager) name and version" }, "key": { "$ref": "#/$defs/key" }, "keyColor": { "$ref": "#/$defs/keyColor" }, "keyIcon": { "$ref": "#/$defs/keyIcon" }, "keyWidth": { "$ref": "#/$defs/keyWidth" }, "outputColor": { "$ref": "#/$defs/outputColor" }, "format": { "$ref": "#/$defs/lmFormat" }, "condition": { "$ref": "#/$defs/conditions" } } }, { "title": "Local IP", "type": "object", "additionalProperties": false, "properties": { "type": { "description": "List local IP addresses (v4 or v6), MAC addresses, etc", "const": "localip" }, "showIpv4": { "description": "Show IPv4 addresses", "type": "boolean", "default": true }, "showIpv6": { "description": "Show IPv6 addresses", "oneOf": [ { "const": true, "description": "Show the most useful IPv6 addresses" }, { "const": false, "description": "Do not show IPv6 addresses" }, { "const": "gua", "description": "Show only global unicast IPv6 addresses (2000::/3)" }, { "const": "ula", "description": "Show only unique local IPv6 addresses (fc00::/7)" }, { "const": "lla", "description": "Show only link-local IPv6 addresses (fe80::/10)" }, { "const": "unknown", "description": "Show only IPv6 addresses that are not gua, ula or lla" } ], "default": false }, "showSpeed": { "description": "Show ethernet rx speed", "type": "boolean", "default": false }, "showMtu": { "description": "Show MTU", "type": "boolean", "default": false }, "showMac": { "description": "Show MAC addresses", "type": "boolean", "default": false }, "showLoop": { "description": "Show loop back addresses (127.0.0.1)", "type": "boolean", "default": false }, "showPrefixLen": { "description": "Show network prefix length (/N)", "type": "boolean", "default": true }, "showAllIps": { "description": "Show all IPs bound to the same interface.\nBy default only the firstly detected IP is shown", "type": "boolean", "default": false }, "showFlags": { "description": "Show the interface's flags", "type": "boolean", "default": false }, "compact": { "description": "Show all IPs in one line", "type": "boolean", "default": false }, "namePrefix": { "description": "Show IPs with given name prefix only", "type": "string" }, "defaultRouteOnly": { "description": "Show ips that are used for default routing only\nDoesn't work on Android", "type": "boolean", "default": true }, "key": { "$ref": "#/$defs/key" }, "keyColor": { "$ref": "#/$defs/keyColor" }, "keyIcon": { "$ref": "#/$defs/keyIcon" }, "keyWidth": { "$ref": "#/$defs/keyWidth" }, "outputColor": { "$ref": "#/$defs/outputColor" }, "format": { "$ref": "#/$defs/localipFormat" }, "condition": { "$ref": "#/$defs/conditions" } } }, { "title": "Loadavg", "type": "object", "additionalProperties": false, "properties": { "type": { "const": "loadavg", "description": "Print system load averages" }, "ndigits": { "type": "integer", "description": "Set the number of digits to keep after the decimal point", "minimum": 0, "maximum": 9, "default": 2 }, "compact": { "type": "boolean", "description": "Show values in one line", "default": true }, "percent": { "$ref": "#/$defs/percent" }, "key": { "$ref": "#/$defs/key" }, "keyColor": { "$ref": "#/$defs/keyColor" }, "keyIcon": { "$ref": "#/$defs/keyIcon" }, "keyWidth": { "$ref": "#/$defs/keyWidth" }, "outputColor": { "$ref": "#/$defs/outputColor" }, "format": { "$ref": "#/$defs/loadavgFormat" }, "condition": { "$ref": "#/$defs/conditions" } } }, { "title": "Locale", "type": "object", "additionalProperties": false, "properties": { "type": { "const": "locale", "description": "Print system locale name" }, "key": { "$ref": "#/$defs/key" }, "keyColor": { "$ref": "#/$defs/keyColor" }, "keyIcon": { "$ref": "#/$defs/keyIcon" }, "keyWidth": { "$ref": "#/$defs/keyWidth" }, "outputColor": { "$ref": "#/$defs/outputColor" }, "format": { "$ref": "#/$defs/localeFormat" }, "condition": { "$ref": "#/$defs/conditions" } } }, { "title": "Logo", "type": "object", "additionalProperties": false, "properties": { "type": { "const": "logo", "description": "Query built-in logo for JSON output" }, "condition": { "$ref": "#/$defs/conditions" } } }, { "title": "Media", "type": "object", "additionalProperties": false, "properties": { "type": { "const": "media", "description": "Print song name of currently playing" }, "key": { "$ref": "#/$defs/key" }, "keyColor": { "$ref": "#/$defs/keyColor" }, "keyIcon": { "$ref": "#/$defs/keyIcon" }, "keyWidth": { "$ref": "#/$defs/keyWidth" }, "outputColor": { "$ref": "#/$defs/outputColor" }, "format": { "$ref": "#/$defs/mediaFormat" }, "condition": { "$ref": "#/$defs/conditions" } } }, { "title": "Memory", "type": "object", "additionalProperties": false, "properties": { "type": { "const": "memory", "description": "Print system memory usage info" }, "percent": { "$ref": "#/$defs/percent" }, "key": { "$ref": "#/$defs/key" }, "keyColor": { "$ref": "#/$defs/keyColor" }, "keyIcon": { "$ref": "#/$defs/keyIcon" }, "keyWidth": { "$ref": "#/$defs/keyWidth" }, "outputColor": { "$ref": "#/$defs/outputColor" }, "format": { "$ref": "#/$defs/memoryFormat" }, "condition": { "$ref": "#/$defs/conditions" } } }, { "title": "Mouse", "type": "object", "additionalProperties": false, "properties": { "type": { "const": "mouse", "description": "List connected mouses" }, "ignores": { "type": "array", "description": "An array of case-insensitive device name prefixes to ignore", "items": { "type": "string" }, "minItems": 1, "uniqueItems": true }, "key": { "$ref": "#/$defs/key" }, "keyColor": { "$ref": "#/$defs/keyColor" }, "keyIcon": { "$ref": "#/$defs/keyIcon" }, "keyWidth": { "$ref": "#/$defs/keyWidth" }, "outputColor": { "$ref": "#/$defs/outputColor" }, "format": { "$ref": "#/$defs/mouseFormat" }, "condition": { "$ref": "#/$defs/conditions" } } }, { "title": "Monitor", "type": "object", "additionalProperties": false, "properties": { "type": { "const": "monitor", "description": "Alias of Display module (for backwards compatibility, deprecated)" }, "key": { "$ref": "#/$defs/key" }, "keyColor": { "$ref": "#/$defs/keyColor" }, "keyIcon": { "$ref": "#/$defs/keyIcon" }, "keyWidth": { "$ref": "#/$defs/keyWidth" }, "outputColor": { "$ref": "#/$defs/outputColor" }, "format": { "$ref": "#/$defs/monitorFormat" }, "condition": { "$ref": "#/$defs/conditions" } } }, { "title": "NetIO", "type": "object", "additionalProperties": false, "properties": { "type": { "description": "Print network I/O throughput", "const": "netio" }, "namePrefix": { "description": "Show IPs with given name prefix only", "type": "string" }, "defaultRouteOnly": { "description": "Show ips that are used for default routing only\nDoesn't work on Android", "type": "boolean", "default": true }, "detectTotal": { "description": "Detect total bytes instead of current rate", "type": "boolean", "default": false }, "waitTime": { "type": "integer", "description": "Wait time (in ms). Net I/O = (totalBytesEnd - totalBytesStart) / waitTime", "default": 200, "minimum": 1 }, "key": { "$ref": "#/$defs/key" }, "keyColor": { "$ref": "#/$defs/keyColor" }, "keyIcon": { "$ref": "#/$defs/keyIcon" }, "keyWidth": { "$ref": "#/$defs/keyWidth" }, "outputColor": { "$ref": "#/$defs/outputColor" }, "format": { "$ref": "#/$defs/netioFormat" }, "condition": { "$ref": "#/$defs/conditions" } } }, { "title": "OpenCL", "type": "object", "additionalProperties": false, "properties": { "type": { "const": "opencl", "description": "Print highest OpenCL version supported by the GPU" }, "key": { "$ref": "#/$defs/key" }, "keyColor": { "$ref": "#/$defs/keyColor" }, "keyIcon": { "$ref": "#/$defs/keyIcon" }, "keyWidth": { "$ref": "#/$defs/keyWidth" }, "outputColor": { "$ref": "#/$defs/outputColor" }, "format": { "$ref": "#/$defs/openclFormat" }, "condition": { "$ref": "#/$defs/conditions" } } }, { "title": "OpenGL", "type": "object", "additionalProperties": false, "properties": { "type": { "description": "Print highest OpenGL version supported by the GPU", "const": "opengl" }, "library": { "description": "Set the OpenGL context creation library to use", "oneOf": [ { "const": "auto", "description": "Prefer EGL on *nix; prefer platform-specific implementation on others" }, { "const": "egl", "description": "Use EGL, which works on TTY" }, { "const": "glx", "description": "Use GLX, requires X session (*nix only)" } ], "default": "auto" }, "key": { "$ref": "#/$defs/key" }, "keyColor": { "$ref": "#/$defs/keyColor" }, "keyIcon": { "$ref": "#/$defs/keyIcon" }, "keyWidth": { "$ref": "#/$defs/keyWidth" }, "outputColor": { "$ref": "#/$defs/outputColor" }, "format": { "$ref": "#/$defs/openglFormat" }, "condition": { "$ref": "#/$defs/conditions" } } }, { "title": "Operating System", "type": "object", "additionalProperties": false, "properties": { "type": { "const": "os", "description": "Print OS / or Linux distro name and version" }, "key": { "$ref": "#/$defs/key" }, "keyColor": { "$ref": "#/$defs/keyColor" }, "keyIcon": { "$ref": "#/$defs/keyIcon" }, "keyWidth": { "$ref": "#/$defs/keyWidth" }, "outputColor": { "$ref": "#/$defs/outputColor" }, "format": { "$ref": "#/$defs/osFormat" }, "condition": { "$ref": "#/$defs/conditions" } } }, { "title": "Packages", "type": "object", "additionalProperties": false, "properties": { "type": { "const": "packages", "description": "List installed package managers and count of installed packages" }, "disabled": { "oneOf": [ { "description": "List of package managers to be disabled when detecting\nWarning: Some detection methods can be very slow.", "type": "array", "items": { "type": "string", "enum": [ "am", "apk", "brew", "choco", "dpkg", "emerge", "eopkg", "flatpak", "guix", "hpkg", "linglong", "lpkg", "lpkgbuild", "macports", "mport", "nix", "opkg", "pacman", "pacstall", "paludis", "pisi", "pkg", "pkgtool", "rpm", "scoop", "snap", "sorcery", "winget", "xbps" ], "uniqueItems": true } }, { "description": "Enable all package managers", "type": "null" } ], "default": ["winget"] }, "combined": { "description": "Whether to combine related package managers into single counts (e.g., nix-system + nix-user = nix)", "type": "boolean", "default": false }, "key": { "$ref": "#/$defs/key" }, "keyColor": { "$ref": "#/$defs/keyColor" }, "keyIcon": { "$ref": "#/$defs/keyIcon" }, "keyWidth": { "$ref": "#/$defs/keyWidth" }, "outputColor": { "$ref": "#/$defs/outputColor" }, "format": { "$ref": "#/$defs/packagesFormat" }, "condition": { "$ref": "#/$defs/conditions" } } }, { "title": "Physical Disk", "type": "object", "additionalProperties": false, "properties": { "type": { "description": "Print physical disk information", "const": "physicaldisk" }, "namePrefix": { "description": "Show disks with given name prefix only", "type": "string" }, "temp": { "$ref": "#/$defs/temperature" }, "key": { "$ref": "#/$defs/key" }, "keyColor": { "$ref": "#/$defs/keyColor" }, "keyIcon": { "$ref": "#/$defs/keyIcon" }, "keyWidth": { "$ref": "#/$defs/keyWidth" }, "outputColor": { "$ref": "#/$defs/outputColor" }, "format": { "$ref": "#/$defs/physicaldiskFormat" }, "condition": { "$ref": "#/$defs/conditions" } } }, { "title": "Physical Memory", "type": "object", "additionalProperties": false, "properties": { "type": { "const": "physicalmemory", "description": "Print system physical memory devices" }, "showEmptySlots": { "description": "Set if uninstalled memory slots should be printed", "type": "boolean", "default": false }, "key": { "$ref": "#/$defs/key" }, "keyColor": { "$ref": "#/$defs/keyColor" }, "keyIcon": { "$ref": "#/$defs/keyIcon" }, "keyWidth": { "$ref": "#/$defs/keyWidth" }, "outputColor": { "$ref": "#/$defs/outputColor" }, "format": { "$ref": "#/$defs/physicalmemoryFormat" }, "condition": { "$ref": "#/$defs/conditions" } } }, { "title": "Player", "type": "object", "additionalProperties": false, "properties": { "type": { "const": "player", "description": "Print music player name" }, "key": { "$ref": "#/$defs/key" }, "keyColor": { "$ref": "#/$defs/keyColor" }, "keyIcon": { "$ref": "#/$defs/keyIcon" }, "keyWidth": { "$ref": "#/$defs/keyWidth" }, "outputColor": { "$ref": "#/$defs/outputColor" }, "format": { "$ref": "#/$defs/playerFormat" }, "condition": { "$ref": "#/$defs/conditions" } } }, { "title": "Power Adapter", "type": "object", "additionalProperties": false, "properties": { "type": { "const": "poweradapter", "description": "Print power adapter name and charging watts" }, "key": { "$ref": "#/$defs/key" }, "keyColor": { "$ref": "#/$defs/keyColor" }, "keyIcon": { "$ref": "#/$defs/keyIcon" }, "keyWidth": { "$ref": "#/$defs/keyWidth" }, "outputColor": { "$ref": "#/$defs/outputColor" }, "format": { "$ref": "#/$defs/poweradapterFormat" }, "condition": { "$ref": "#/$defs/conditions" } } }, { "title": "Processes", "type": "object", "additionalProperties": false, "properties": { "type": { "const": "processes", "description": "Count running processes" }, "key": { "$ref": "#/$defs/key" }, "keyColor": { "$ref": "#/$defs/keyColor" }, "keyIcon": { "$ref": "#/$defs/keyIcon" }, "keyWidth": { "$ref": "#/$defs/keyWidth" }, "outputColor": { "$ref": "#/$defs/outputColor" }, "format": { "$ref": "#/$defs/processesFormat" }, "condition": { "$ref": "#/$defs/conditions" } } }, { "title": "Public IP", "type": "object", "additionalProperties": false, "properties": { "type": { "description": "Print your public IP address, etc", "const": "publicip" }, "url": { "description": "The URL of public IP detection server to be used. Only HTTP protocol is supported", "type": "string", "format": "url", "default": "http://ipinfo.io/ip" }, "timeout": { "description": "Time in milliseconds to wait for the public ip server to respond.\n0 to disable timeout", "type": "integer", "minimum": 0, "default": 0 }, "ipv6": { "description": "Whether to use IPv6 for public IP detection server", "type": "boolean", "default": false }, "key": { "$ref": "#/$defs/key" }, "keyColor": { "$ref": "#/$defs/keyColor" }, "keyIcon": { "$ref": "#/$defs/keyIcon" }, "keyWidth": { "$ref": "#/$defs/keyWidth" }, "outputColor": { "$ref": "#/$defs/outputColor" }, "format": { "$ref": "#/$defs/publicipFormat" }, "condition": { "$ref": "#/$defs/conditions" } } }, { "title": "Separator", "type": "object", "additionalProperties": false, "properties": { "type": { "description": "Print a separator line", "const": "separator" }, "string": { "description": "Set the string to be printed by the separator line", "type": "string", "default": "-" }, "outputColor": { "description": "Set the color of the separator line", "$ref": "#/$defs/outputColor" }, "times": { "description": "Set the times of separator string to repeat, or 0 to auto-detect", "type": "integer", "minimum": 0, "default": 0 }, "condition": { "$ref": "#/$defs/conditions" } } }, { "title": "Shell", "type": "object", "additionalProperties": false, "properties": { "type": { "const": "shell", "description": "Print current shell name and version" }, "key": { "$ref": "#/$defs/key" }, "keyColor": { "$ref": "#/$defs/keyColor" }, "keyIcon": { "$ref": "#/$defs/keyIcon" }, "keyWidth": { "$ref": "#/$defs/keyWidth" }, "outputColor": { "$ref": "#/$defs/outputColor" }, "format": { "$ref": "#/$defs/shellFormat" }, "condition": { "$ref": "#/$defs/conditions" } } }, { "title": "Sound", "type": "object", "additionalProperties": false, "properties": { "type": { "description": "Print sound devices, volume, etc", "const": "sound" }, "soundType": { "description": "Set what type of sound devices should be printed", "type": "string", "oneOf": [ { "const": "main", "description": "Print only main sound devices" }, { "const": "active", "description": "Print only active sound devices" }, { "const": "all", "description": "Print all sound devices" } ], "default": "main" }, "percent": { "$ref": "#/$defs/percent" }, "key": { "$ref": "#/$defs/key" }, "keyColor": { "$ref": "#/$defs/keyColor" }, "keyIcon": { "$ref": "#/$defs/keyIcon" }, "keyWidth": { "$ref": "#/$defs/keyWidth" }, "outputColor": { "$ref": "#/$defs/outputColor" }, "format": { "$ref": "#/$defs/soundFormat" }, "condition": { "$ref": "#/$defs/conditions" } } }, { "title": "Swap", "type": "object", "additionalProperties": false, "properties": { "type": { "const": "swap", "description": "Print swap (paging file) space usage" }, "separate": { "type": "boolean", "description": "Set if detailed swap devices should be reported on separate lines instead of a summary", "default": false }, "percent": { "$ref": "#/$defs/percent" }, "key": { "$ref": "#/$defs/key" }, "keyColor": { "$ref": "#/$defs/keyColor" }, "keyIcon": { "$ref": "#/$defs/keyIcon" }, "keyWidth": { "$ref": "#/$defs/keyWidth" }, "outputColor": { "$ref": "#/$defs/outputColor" }, "format": { "$ref": "#/$defs/swapFormat" }, "condition": { "$ref": "#/$defs/conditions" } } }, { "title": "Terminal", "type": "object", "additionalProperties": false, "properties": { "type": { "const": "terminal", "description": "Print current terminal name and version" }, "key": { "$ref": "#/$defs/key" }, "keyColor": { "$ref": "#/$defs/keyColor" }, "keyIcon": { "$ref": "#/$defs/keyIcon" }, "keyWidth": { "$ref": "#/$defs/keyWidth" }, "outputColor": { "$ref": "#/$defs/outputColor" }, "format": { "$ref": "#/$defs/terminalFormat" }, "condition": { "$ref": "#/$defs/conditions" } } }, { "title": "Terminal Font", "type": "object", "additionalProperties": false, "properties": { "type": { "const": "terminalfont", "description": "Print font name and size used by current terminal" }, "key": { "$ref": "#/$defs/key" }, "keyColor": { "$ref": "#/$defs/keyColor" }, "keyIcon": { "$ref": "#/$defs/keyIcon" }, "keyWidth": { "$ref": "#/$defs/keyWidth" }, "outputColor": { "$ref": "#/$defs/outputColor" }, "format": { "$ref": "#/$defs/terminalfontFormat" }, "condition": { "$ref": "#/$defs/conditions" } } }, { "title": "Terminal Size", "type": "object", "additionalProperties": false, "properties": { "type": { "const": "terminalsize", "description": "Print current terminal size" }, "key": { "$ref": "#/$defs/key" }, "keyColor": { "$ref": "#/$defs/keyColor" }, "keyIcon": { "$ref": "#/$defs/keyIcon" }, "keyWidth": { "$ref": "#/$defs/keyWidth" }, "outputColor": { "$ref": "#/$defs/outputColor" }, "format": { "$ref": "#/$defs/terminalsizeFormat" }, "condition": { "$ref": "#/$defs/conditions" } } }, { "title": "Terminal Theme", "type": "object", "additionalProperties": false, "properties": { "type": { "const": "terminaltheme", "description": "Print current terminal theme (foreground and background colors)" }, "key": { "$ref": "#/$defs/key" }, "keyColor": { "$ref": "#/$defs/keyColor" }, "keyIcon": { "$ref": "#/$defs/keyIcon" }, "keyWidth": { "$ref": "#/$defs/keyWidth" }, "outputColor": { "$ref": "#/$defs/outputColor" }, "format": { "$ref": "#/$defs/terminalthemeFormat" }, "condition": { "$ref": "#/$defs/conditions" } } }, { "title": "Theme", "type": "object", "additionalProperties": false, "properties": { "type": { "const": "theme", "description": "Print current theme of desktop environment" }, "key": { "$ref": "#/$defs/key" }, "keyColor": { "$ref": "#/$defs/keyColor" }, "keyIcon": { "$ref": "#/$defs/keyIcon" }, "keyWidth": { "$ref": "#/$defs/keyWidth" }, "outputColor": { "$ref": "#/$defs/outputColor" }, "format": { "$ref": "#/$defs/themeFormat" }, "condition": { "$ref": "#/$defs/conditions" } } }, { "title": "Title", "type": "object", "additionalProperties": false, "properties": { "type": { "description": "Print title, which contains your user name, hostname", "const": "title" }, "fqdn": { "type": "boolean", "description": "Set if the title should use fully qualified domain name", "default": false }, "color": { "description": "Set colors of the different part of title", "type": "object", "additionalProperties": false, "properties": { "user": { "description": "Set color of the user name (left part)", "$ref": "#/$defs/colors" }, "at": { "description": "Set color of the @ symbol (middle part)", "$ref": "#/$defs/colors" }, "host": { "description": "Set color of the host name (right part)", "$ref": "#/$defs/colors" } } }, "key": { "$ref": "#/$defs/key" }, "keyColor": { "$ref": "#/$defs/keyColor" }, "keyIcon": { "$ref": "#/$defs/keyIcon" }, "keyWidth": { "$ref": "#/$defs/keyWidth" }, "outputColor": { "$ref": "#/$defs/outputColor" }, "format": { "$ref": "#/$defs/titleFormat" }, "condition": { "$ref": "#/$defs/conditions" } } }, { "title": "TPM", "type": "object", "additionalProperties": false, "properties": { "type": { "const": "tpm", "description": "Print info of Trusted Platform Module (TPM) Security Device" }, "key": { "$ref": "#/$defs/key" }, "keyColor": { "$ref": "#/$defs/keyColor" }, "keyIcon": { "$ref": "#/$defs/keyIcon" }, "keyWidth": { "$ref": "#/$defs/keyWidth" }, "outputColor": { "$ref": "#/$defs/outputColor" }, "format": { "$ref": "#/$defs/tpmFormat" }, "condition": { "$ref": "#/$defs/conditions" } } }, { "title": "Users", "type": "object", "additionalProperties": false, "properties": { "type": { "const": "users", "description": "Print users currently logged in" }, "compact": { "type": "boolean", "description": "Show all active users in one line", "default": false }, "myselfOnly": { "type": "boolean", "description": "Show only the current user", "default": false }, "key": { "$ref": "#/$defs/key" }, "keyColor": { "$ref": "#/$defs/keyColor" }, "keyIcon": { "$ref": "#/$defs/keyIcon" }, "keyWidth": { "$ref": "#/$defs/keyWidth" }, "outputColor": { "$ref": "#/$defs/outputColor" }, "format": { "$ref": "#/$defs/usersFormat" }, "condition": { "$ref": "#/$defs/conditions" } } }, { "title": "Uptime", "type": "object", "additionalProperties": false, "properties": { "type": { "const": "uptime", "description": "Print how long system has been running" }, "key": { "$ref": "#/$defs/key" }, "keyColor": { "$ref": "#/$defs/keyColor" }, "keyIcon": { "$ref": "#/$defs/keyIcon" }, "keyWidth": { "$ref": "#/$defs/keyWidth" }, "outputColor": { "$ref": "#/$defs/outputColor" }, "format": { "$ref": "#/$defs/uptimeFormat" }, "condition": { "$ref": "#/$defs/conditions" } } }, { "title": "Version", "type": "object", "additionalProperties": false, "properties": { "type": { "const": "version", "description": "Print Fastfetch version" }, "key": { "$ref": "#/$defs/key" }, "keyColor": { "$ref": "#/$defs/keyColor" }, "keyIcon": { "$ref": "#/$defs/keyIcon" }, "keyWidth": { "$ref": "#/$defs/keyWidth" }, "outputColor": { "$ref": "#/$defs/outputColor" }, "format": { "$ref": "#/$defs/versionFormat" }, "condition": { "$ref": "#/$defs/conditions" } } }, { "title": "Vulkan", "type": "object", "additionalProperties": false, "properties": { "type": { "const": "vulkan", "description": "Print highest Vulkan version supported by the GPU" }, "key": { "$ref": "#/$defs/key" }, "keyColor": { "$ref": "#/$defs/keyColor" }, "keyIcon": { "$ref": "#/$defs/keyIcon" }, "keyWidth": { "$ref": "#/$defs/keyWidth" }, "outputColor": { "$ref": "#/$defs/outputColor" }, "format": { "$ref": "#/$defs/vulkanFormat" }, "condition": { "$ref": "#/$defs/conditions" } } }, { "title": "Wallpaper", "type": "object", "additionalProperties": false, "properties": { "type": { "const": "wallpaper", "description": "Print image file path of current wallpaper" }, "key": { "$ref": "#/$defs/key" }, "keyColor": { "$ref": "#/$defs/keyColor" }, "keyIcon": { "$ref": "#/$defs/keyIcon" }, "keyWidth": { "$ref": "#/$defs/keyWidth" }, "outputColor": { "$ref": "#/$defs/outputColor" }, "format": { "$ref": "#/$defs/wallpaperFormat" }, "condition": { "$ref": "#/$defs/conditions" } } }, { "title": "Weather", "type": "object", "additionalProperties": false, "properties": { "type": { "description": "Print weather information", "const": "weather" }, "location": { "description": "The location to display\nMust be URI encoded (e.g., a whitespace must be encoded as \"+\")", "type": "string" }, "timeout": { "description": "Time in milliseconds to wait for the weather server to respond.\n0 to disable timeout", "type": "integer", "minimum": 0, "default": 0 }, "outputFormat": { "description": "The output weather format to be used (must be URI encoded)", "type": "string", "default": "%t+-+%C+(%l)" }, "key": { "$ref": "#/$defs/key" }, "keyColor": { "$ref": "#/$defs/keyColor" }, "keyIcon": { "$ref": "#/$defs/keyIcon" }, "keyWidth": { "$ref": "#/$defs/keyWidth" }, "outputColor": { "$ref": "#/$defs/outputColor" }, "format": { "$ref": "#/$defs/weatherFormat" }, "condition": { "$ref": "#/$defs/conditions" } } }, { "title": "Wi-Fi", "type": "object", "additionalProperties": false, "properties": { "type": { "const": "wifi", "description": "Print connected Wi-Fi info (SSID, connection and security protocol)" }, "key": { "$ref": "#/$defs/key" }, "keyColor": { "$ref": "#/$defs/keyColor" }, "keyIcon": { "$ref": "#/$defs/keyIcon" }, "keyWidth": { "$ref": "#/$defs/keyWidth" }, "outputColor": { "$ref": "#/$defs/outputColor" }, "format": { "$ref": "#/$defs/wifiFormat" }, "condition": { "$ref": "#/$defs/conditions" } } }, { "title": "Window Manager", "type": "object", "additionalProperties": false, "properties": { "type": { "const": "wm", "description": "Print window manager name and version" }, "detectPlugin": { "description": "Set if window manager plugin should be detected on supported platforms", "type": "boolean", "default": true }, "key": { "$ref": "#/$defs/key" }, "keyColor": { "$ref": "#/$defs/keyColor" }, "keyIcon": { "$ref": "#/$defs/keyIcon" }, "keyWidth": { "$ref": "#/$defs/keyWidth" }, "outputColor": { "$ref": "#/$defs/outputColor" }, "format": { "$ref": "#/$defs/wmFormat" }, "condition": { "$ref": "#/$defs/conditions" } } }, { "title": "WM Theme", "type": "object", "additionalProperties": false, "properties": { "type": { "const": "wmtheme", "description": "Print current theme of window manager" }, "key": { "$ref": "#/$defs/key" }, "keyColor": { "$ref": "#/$defs/keyColor" }, "keyIcon": { "$ref": "#/$defs/keyIcon" }, "keyWidth": { "$ref": "#/$defs/keyWidth" }, "outputColor": { "$ref": "#/$defs/outputColor" }, "format": { "$ref": "#/$defs/wmthemeFormat" }, "condition": { "$ref": "#/$defs/conditions" } } }, { "title": "Zpool", "type": "object", "additionalProperties": false, "properties": { "type": { "const": "zpool", "description": "Print ZFS storage pools" }, "percent": { "$ref": "#/$defs/percent" }, "key": { "$ref": "#/$defs/key" }, "keyColor": { "$ref": "#/$defs/keyColor" }, "keyIcon": { "$ref": "#/$defs/keyIcon" }, "keyWidth": { "$ref": "#/$defs/keyWidth" }, "outputColor": { "$ref": "#/$defs/outputColor" }, "format": { "$ref": "#/$defs/zpoolFormat" }, "condition": { "$ref": "#/$defs/conditions" } } } ] } ] } } } } ================================================ FILE: presets/all.jsonc ================================================ { "$schema": "https://github.com/fastfetch-cli/fastfetch/raw/dev/doc/json_schema.json", "logo": { "padding": { "top": 2 } }, "modules": [ "title", "separator", "os", "host", "bios", "bootmgr", "board", "chassis", "kernel", "initsystem", "uptime", "loadavg", "processes", "packages", "shell", "editor", "display", "brightness", "monitor", "lm", "de", "wm", "wmtheme", "theme", "icons", "font", "cursor", "wallpaper", "terminal", "terminalfont", "terminalsize", "terminaltheme", { "type": "cpu", "showPeCoreCount": true, "temp": true }, "cpucache", "cpuusage", { "type": "gpu", "driverSpecific": true, "temp": true }, "memory", "physicalmemory", { "type": "swap", "separate": true }, "disk", "btrfs", "zpool", { "type": "battery", "temp": true }, "poweradapter", "player", "media", { "type": "publicip", "timeout": 1000 }, { "type": "localip", "showIpv6": true, "showMac": true, "showSpeed": true, "showMtu": true, "showLoop": true, "showFlags": true, "showAllIps": true }, "dns", "wifi", "datetime", "locale", "vulkan", "opengl", "opencl", "users", "bluetooth", "bluetoothradio", "sound", "camera", "gamepad", "mouse", "keyboard", { "type": "weather", "timeout": 1000 }, "netio", "diskio", { "type": "physicaldisk", "temp": true }, "tpm", "version", "break", "colors" ] } ================================================ FILE: presets/archey.jsonc ================================================ { "$schema": "https://github.com/fastfetch-cli/fastfetch/raw/dev/doc/json_schema.json", "modules": [ { "type": "title", "key": "User", "format": "{user-name}" }, { "type": "title", "key": "Hostname", "format": "{host-name}" }, { "type": "host", "key": "Model" }, { "type": "os", "format": "{pretty-name} {version-id} {arch}" }, "kernel", "uptime", { "type": "loadavg", "key": "Load Average" }, "processes", { "type": "wm", "key": "Window Manager" }, { "type": "de", "key": "Desktop Environment" }, "shell", { "type": "terminal", "format": "{pretty-name} {version} {#37}█{#97}█ {#36}█{#96}█ {#35}█{#95}█ {#34}█{#94}█ {#33}█{#93}█ {#32}█{#92}█ {#31}█{#91}█ {#30}█{#90}█" }, { "type": "packages", "format": "{all}" }, { "type": "cpu", "key": "Temperature", "temp": true, "format": "{temperature}" }, { "type": "cpu", "key": "CPU", "format": "{cores-logical} x {name}" }, { "type": "gpu", "format": "{name}" }, { "type": "memory", "key": "RAM" }, { "type": "disk", "key": "Disk", "folders": "/" }, { "type": "localip", "key": "LAN IP", "showIpv6": true, "showPrefixLen": false }, { "type": "publicip", "key": "WAN IP", "timeout": 1000 } ] } ================================================ FILE: presets/ci.jsonc ================================================ { "$schema": "https://github.com/fastfetch-cli/fastfetch/raw/dev/doc/json_schema.json", "display": { "stat": true, "pipe": true, "showErrors": true, "noBuffer": true }, "logo": null, "modules": [ "title", "separator", "os", "host", "bios", "bootmgr", "board", "chassis", "kernel", "initsystem", "uptime", "loadavg", "processes", "packages", "shell", "editor", "display", "brightness", "monitor", "lm", "de", "wm", "wmtheme", "theme", "icons", "font", "cursor", "wallpaper", "terminal", "terminalfont", "terminalsize", "terminaltheme", { "type": "cpu", "showPeCoreCount": true, "temp": true }, "cpucache", "cpuusage", { "type": "gpu", "driverSpecific": true, "temp": true }, "memory", "physicalmemory", { "type": "swap", "separate": true }, "disk", "btrfs", "zpool", { "type": "battery", "temp": true }, "poweradapter", "player", "media", { "type": "publicip", "timeout": 1000 }, { "type": "localip", "showIpv6": true, "showMac": true, "showSpeed": true, "showMtu": true, "showLoop": true, "showFlags": true, "showAllIps": true }, "dns", "wifi", "datetime", "locale", "vulkan", "opengl", "opencl", "users", // "bluetooth", // doesn't work on macOS because it requires bluetooth permissions // "bluetoothradio", "sound", "camera", "gamepad", "mouse", "keyboard", { "type": "weather", "timeout": 1000 }, "netio", "diskio", { "type": "physicaldisk", "temp": true }, "tpm", "version", "logo", "break", "colors" ] } ================================================ FILE: presets/examples/10.jsonc ================================================ // Load with --config examples/2.jsonc // Note that you must replace the image path to an existing image to display it. { "$schema": "https://github.com/fastfetch-cli/fastfetch/raw/dev/doc/json_schema.json", "logo": { "padding": { "top": 2 } }, "display": { "separator": " -> ", "constants": [ "──────────────────────────────" ] }, "modules": [ { "type": "custom", "format": "┌{$1}{$1}┐", "outputColor": "90" }, { "type": "title", "keyWidth": 10 }, { "type": "custom", "format": "└{$1}{$1}┘", "outputColor": "90" }, { "type": "custom", "format": " {#90} {#31} {#32} {#33} {#34} {#35} {#36} {#37} {#38} {#39}  {#38} {#37} {#36} {#35} {#34} {#33} {#32} {#31} {#90}" }, { "type": "custom", "format": "┌{$1}{$1}┐", "outputColor": "90" }, { "type": "os", "key": "{icon} OS", "keyColor": "yellow" }, { "type": "kernel", "key": "│ ├", "keyColor": "yellow" }, { "type": "packages", "key": "│ ├󰏖", "keyColor": "yellow" }, { "type": "shell", "key": "│ └", "keyColor": "yellow" }, { "type": "wm", "key": " DE/WM", "keyColor": "blue" }, { "type": "lm", "key": "│ ├󰧨", "keyColor": "blue" }, { "type": "wmtheme", "key": "│ ├󰉼", "keyColor": "blue" }, { "type": "icons", "key": "│ ├󰀻", "keyColor": "blue" }, { "type": "terminal", "key": "│ ├", "keyColor": "blue" }, { "type": "wallpaper", "key": "│ └󰸉", "keyColor": "blue" }, { "type": "host", "key": "󰌢 PC", "keyColor": "green" }, { "type": "cpu", "key": "│ ├󰻠", "keyColor": "green" }, { "type": "gpu", "key": "│ ├󰍛", "keyColor": "green" }, { "type": "disk", "key": "│ ├", "keyColor": "green" }, { "type": "memory", "key": "│ ├󰑭", "keyColor": "green" }, { "type": "swap", "key": "│ ├󰓡", "keyColor": "green" }, { "type": "uptime", "key": "│ ├󰅐", "keyColor": "green" }, { "type": "display", "key": "│ └󰍹", "keyColor": "green" }, { "type": "sound", "key": " SND", "keyColor": "cyan" }, { "type": "player", "key": "│ ├󰥠", "keyColor": "cyan" }, { "type": "media", "key": "│ └󰝚", "keyColor": "cyan" }, { "type": "custom", "format": "└{$1}{$1}┘", "outputColor": "90" }, "break", { "type": "custom", "format": " {#90} {#31} {#32} {#33} {#34} {#35} {#36} {#37} {#38} {#39}  {#38} {#37} {#36} {#35} {#34} {#33} {#32} {#31} {#90}" } ] } ================================================ FILE: presets/examples/11.jsonc ================================================ { "$schema": "https://github.com/fastfetch-cli/fastfetch/raw/dev/doc/json_schema.json", "logo": { "type": "small" }, "display": { "separator": "-> ", "color": { "separator": "red" } }, "modules": [ { "key": "Distro ", "type": "os" }, { "key": "Shell ", "type": "shell" }, { "key": "Terminal ", "type": "terminal" }, { "key": "Display ", "type": "display" }, { "key": "Backlight ", "type": "brightness" }, "break", { "type": "colors", "paddingLeft": 6, "symbol": "circle" } ] } ================================================ FILE: presets/examples/12.jsonc ================================================ { "$schema": "https://github.com/fastfetch-cli/fastfetch/raw/dev/doc/json_schema.json", "logo": { "type": "none" }, "display": { "separator": "-> ", "color": { "separator": "1" // Bold }, "constants": [ "───────────────────────────" ], "key": { "type": "both", "paddingLeft": 4 } }, "modules": [ { "type": "title", "format": " {user-name-colored}{at-symbol-colored}{host-name-colored}" }, "break", { "type": "custom", "format": "┌{$1} {#1}System Information{#} {$1}┐" }, "break", { "key": "OS ", "keyColor": "red", "type": "os" }, { "key": "Machine ", "keyColor": "green", "type": "host" }, { "key": "Kernel ", "keyColor": "magenta", "type": "kernel" }, { "key": "Uptime ", "keyColor": "red", "type": "uptime" }, { "key": "Resolution ", "keyColor": "yellow", "type": "display", "compactType": "original-with-refresh-rate" }, { "key": "WM ", "keyColor": "blue", "type": "wm" }, { "key": "DE ", "keyColor": "green", "type": "de" }, { "key": "Shell ", "keyColor": "cyan", "type": "shell" }, { "key": "Terminal ", "keyColor": "red", "type": "terminal" }, { "key": "CPU ", "keyColor": "yellow", "type": "cpu" }, { "key": "GPU ", "keyColor": "blue", "type": "gpu" }, { "key": "Memory ", "keyColor": "magenta", "type": "memory" }, { "key": "Local IP ", "keyColor": "red", "type": "localip", "compact": true }, { "key": "Public IP ", "keyColor": "cyan", "type": "publicip", "timeout": 1000 }, "break", { "type": "custom", "format": "└{$1}────────────────────{$1}┘" }, "break", { "type": "colors", "paddingLeft": 34, "symbol": "circle" } ] } ================================================ FILE: presets/examples/13.jsonc ================================================ // Inspired by Catnap { "$schema": "https://github.com/fastfetch-cli/fastfetch/raw/dev/doc/json_schema.json", "logo": { "type": "small", "padding": { "top": 1 } }, "display": { "separator": " " }, "modules": [ { "key": "╭───────────╮", "type": "custom" }, { "key": "│ {#31} user {#keys}│", "type": "title", "format": "{user-name}" }, { "key": "│ {#32}󰇅 hname {#keys}│", "type": "title", "format": "{host-name}" }, { "key": "│ {#33}󰅐 uptime {#keys}│", "type": "uptime" }, { "key": "│ {#34}{icon} distro {#keys}│", "type": "os" }, { "key": "│ {#35} kernel {#keys}│", "type": "kernel" }, { "key": "│ {#36}󰇄 desktop {#keys}│", "type": "de" }, { "key": "│ {#31} term {#keys}│", "type": "terminal" }, { "key": "│ {#32} shell {#keys}│", "type": "shell" }, { "key": "│ {#33}󰍛 cpu {#keys}│", "type": "cpu", "showPeCoreCount": true }, { "key": "│ {#34}󰉉 disk {#keys}│", "type": "disk", "folders": "/" }, { "key": "│ {#35} memory {#keys}│", "type": "memory" }, { "key": "│ {#36}󰩟 network {#keys}│", "type": "localip", "format": "{ipv4} ({ifname})" }, { "key": "├───────────┤", "type": "custom" }, { "key": "│ {#39} colors {#keys}│", "type": "colors", "symbol": "circle" }, { "key": "╰───────────╯", "type": "custom" } ] } ================================================ FILE: presets/examples/14.jsonc ================================================ // Inspired by Catnap { "$schema": "https://github.com/fastfetch-cli/fastfetch/raw/dev/doc/json_schema.json", "logo": { "type": "small" }, "display": { "separator": "", "key": { "width": 15 } }, "modules": [ { // draw borders first to make colors of left and right border consistant "key": " user", "type": "title", "format": "{user-name}", "keyColor": "31" }, { "key": "󰇅 hname", "type": "title", "format": "{host-name}", "keyColor": "32" }, { "key": "󰅐 uptime", "type": "uptime", "keyColor": "33" }, { "key": "{icon} distro", "type": "os", "keyColor": "34" }, { "key": " kernel", "type": "kernel", "keyColor": "35" }, { "key": "󰇄 desktop", "type": "de", "keyColor": "36" }, { "key": " term", "type": "terminal", "keyColor": "31" }, { "key": " shell", "type": "shell", "keyColor": "32" }, { "key": "󰍛 cpu", "type": "cpu", "showPeCoreCount": true, "keyColor": "33" }, { "key": "󰉉 disk", "type": "disk", "folders": "/", "keyColor": "34" }, { "key": " memory", "type": "memory", "keyColor": "35" }, { "key": "󰩟 network", "type": "localip", "format": "{ipv4} ({ifname})", "keyColor": "36" }, { "key": " colors", "type": "colors", "symbol": "circle", "keyColor": "39" } ] } ================================================ FILE: presets/examples/15.jsonc ================================================ // Inspired by Catnap { "$schema": "https://github.com/fastfetch-cli/fastfetch/raw/dev/doc/json_schema.json", "logo": { "type": "small", "padding": { "top": 1 } }, "display": { "separator": " " }, "modules": [ { "key": "•••••••••••••", "type": "custom" }, { "key": "• {#31} user {#keys}•", "type": "title", "format": "{user-name}" }, { "key": "• {#32}󰇅 hname {#keys}•", "type": "title", "format": "{host-name}" }, { "key": "• {#33}󰅐 uptime {#keys}•", "type": "uptime" }, { "key": "• {#34}{icon} distro {#keys}•", "type": "os" }, { "key": "• {#35} kernel {#keys}•", "type": "kernel" }, { "key": "• {#36}󰇄 desktop {#keys}•", "type": "de" }, { "key": "• {#31} term {#keys}•", "type": "terminal" }, { "key": "• {#32} shell {#keys}•", "type": "shell" }, { "key": "• {#33}󰍛 cpu {#keys}•", "type": "cpu", "showPeCoreCount": true }, { "key": "• {#34}󰉉 disk {#keys}•", "type": "disk", "folders": "/" }, { "key": "• {#35} memory {#keys}•", "type": "memory" }, { "key": "• {#36}󰩟 network {#keys}•", "type": "localip", "format": "{ipv4} ({ifname})" }, { "key": "•••••••••••••", "type": "custom" }, { "key": "• {#39} colors {#keys}•", "type": "colors", "symbol": "circle" }, { "key": "•••••••••••••", "type": "custom" } ] } ================================================ FILE: presets/examples/16.jsonc ================================================ // Inspired by Catnap { "$schema": "https://github.com/fastfetch-cli/fastfetch/raw/dev/doc/json_schema.json", "logo": { "type": "small", "padding": { "top": 1 } }, "display": { "separator": " " }, "modules": [ { "key": "╔═══════════╗", "type": "custom" }, { "key": "║ {#31} user {#keys}║", "type": "title", "format": "{user-name}" }, { "key": "║ {#32}󰇅 hname {#keys}║", "type": "title", "format": "{host-name}" }, { "key": "║ {#33}󰅐 uptime {#keys}║", "type": "uptime" }, { "key": "║ {#34}{icon} distro {#keys}║", "type": "os" }, { "key": "║ {#35} kernel {#keys}║", "type": "kernel" }, { "key": "║ {#36}󰇄 desktop {#keys}║", "type": "de" }, { "key": "║ {#31} term {#keys}║", "type": "terminal" }, { "key": "║ {#32} shell {#keys}║", "type": "shell" }, { "key": "║ {#33}󰍛 cpu {#keys}║", "type": "cpu", "showPeCoreCount": true }, { "key": "║ {#34}󰉉 disk {#keys}║", "type": "disk", "folders": "/" }, { "key": "║ {#35} memory {#keys}║", "type": "memory" }, { "key": "║ {#36}󰩟 network {#keys}║", "type": "localip", "format": "{ipv4} ({ifname})" }, { "key": "╠═══════════╣", "type": "custom" }, { "key": "║ {#39} colors {#keys}║", "type": "colors", "symbol": "circle" }, { "key": "╚═══════════╝", "type": "custom" } ] } ================================================ FILE: presets/examples/17.jsonc ================================================ { "$schema": "https://github.com/fastfetch-cli/fastfetch/raw/dev/doc/json_schema.json", "logo": { "type": "small", "padding": { "top": 1, "right": 2 } }, "display": { "separator": "> ", "color": { "separator": "red" }, "constants": [ "───────────────────────────────────────────────────────────────────────────", "│\u001b[75C│\u001b[75D" ] }, "modules": [ { "format": "{#1}{#keys}╭{$1}╮\u001b[76D {user-name-colored}{at-symbol-colored}{host-name-colored} 🖥 ", "type": "title" }, { "key": "{$2}{#31} kernel ", "type": "kernel" }, { "key": "{$2}{#32}󰅐 uptime ", "type": "uptime" }, { "key": "{$2}{#33}{icon} distro ", "type": "os" }, { "key": "{$2}{#34}󰇄 desktop ", "type": "de" }, { "key": "{$2}{#35} term ", "type": "terminal" }, { "key": "{$2}{#36} shell ", "type": "shell" }, { "key": "{$2}{#35}󰍛 cpu ", "type": "cpu", "showPeCoreCount": true, "temp": true }, { "key": "{$2}{#34}󰍛 gpu ", "type": "gpu" }, { "key": "{$2}{#33}󰉉 disk ", "type": "disk", "folders": "/" }, { "key": "{$2}{#32} memory ", "type": "memory" }, { "key": "{$2}{#31}󰩟 network ", "type": "localip", "format": "{ipv4} ({ifname})" }, { "format": "{#1}{#keys}├{$1}┤", "type": "custom" }, { "key": "{$2}{#39} colors ", "type": "colors", "symbol": "circle" }, { "format": "{#1}{#keys}╰{$1}╯", "type": "custom" } ] } ================================================ FILE: presets/examples/18.jsonc ================================================ { "$schema": "https://github.com/fastfetch-cli/fastfetch/raw/dev/doc/json_schema.json", "logo": { "type": "small", "padding": { "top": 1, "right": 2 } }, "display": { "separator": "> ", "color": { "separator": "red" }, "constants": [ "═══════════════════════════════════════════════════════════════════════════", "║\u001b[75C║\u001b[75D" ] }, "modules": [ { "format": "{#1}{#keys}╔{$1}╗\u001b[76D {user-name-colored}{at-symbol-colored}{host-name-colored} 💻 ", "type": "title" }, { "key": "{$2}{#31} kernel ", "type": "kernel" }, { "key": "{$2}{#32}󰅐 uptime ", "type": "uptime" }, { "key": "{$2}{#33}{icon} distro ", "type": "os" }, { "key": "{$2}{#34}󰇄 desktop ", "type": "de" }, { "key": "{$2}{#35} term ", "type": "terminal" }, { "key": "{$2}{#36} shell ", "type": "shell" }, { "key": "{$2}{#35}󰍛 cpu ", "type": "cpu", "showPeCoreCount": true, "temp": true }, { "key": "{$2}{#34}󰍛 gpu ", "type": "gpu" }, { "key": "{$2}{#33}󰉉 disk ", "type": "disk", "folders": "/" }, { "key": "{$2}{#32} memory ", "type": "memory" }, { "key": "{$2}{#31}󰩟 network ", "type": "localip", "format": "{ipv4} ({ifname})" }, { "format": "{#1}{#keys}╠{$1}╣", "type": "custom" }, { "key": "{$2}{#39} colors ", "type": "colors", "symbol": "circle" }, { "format": "{#1}{#keys}╚{$1}╝", "type": "custom" } ] } ================================================ FILE: presets/examples/19.jsonc ================================================ // _____ _____ _____ _____ _____ _____ _____ _____ _____ // | __| _ | __|_ _| __| __|_ _| | | | // | __| |__ | | | | __| __| | | | --| | // |__| |__|__|_____| |_| |__| |_____| |_| |_____|__|__| // // By CarterLi - https://github.com/CarterLi // Homepage - https://github.com/fastfetch-cli/fastfetch // config.jsonc - ニリ @niri-san // pokemon-colorscripts - https://gitlab.com/phoneybadger/pokemon-colorscripts { "$schema": "https://github.com/fastfetch-cli/fastfetch/raw/dev/doc/json_schema.json", "logo": { "source": " _____ _____ _____ _____ _____ _____ _____ _____ _____\n| __| _ | __|_ _| __| __|_ _| | | |\n| __| |__ | | | | __| __| | | | --| |\n|__| |__|__|_____| |_| |__| |_____| |_| |_____|__|__|", "type": "data", "position": "top", "padding": { "right": 2 } }, "display": { "separator": " - " }, "modules": [ { "type": "custom", // HardwareInfo "format": "• {#green}SYSTEM INFORMATION" }, { "type": "host", "key": "HOST", "format": "{name}{?vendor} ({vendor}){?}", "keyColor": "green" }, { "type": "chassis", "key": "COMPUTER TYPE", "keyColor": "green" }, { "type": "cpu", "key": "CPU", "keyColor": "green" }, { "type": "gpu", "key": "GPU", "keyColor": "green" }, { "type": "memory", "key": "MEMORY USED", "keyColor": "green" }, { "type": "swap", "key": "SWAP USED", "keyColor": "green" }, { "type": "disk", "key": "DISK", "folders": "/", "keyColor": "green" }, { "type": "custom", // SoftwareInfo "format": "• {#red}SOFTWARE INFORMATION" }, { "type": "os", "key": "DISTRO", "keyColor": "red" }, { "type": "disk", "folders": "/", // Use "/System/Volumes/VM" or something else on macOS "format": "{create-time}", "key": "INSTALLED DATE", "keyColor": "red" }, { "type": "kernel", "key": "KERNEL", "keyColor": "red" }, { "type": "packages", "key": "PACKAGES", "keyColor": "red" }, { "type": "uptime", "key": "UPTIME", "keyColor": "red" }, { "type": "custom", // DisplayInfo "format": "• {#blue}DISPLAY INFORMATION" }, { "type": "de", "key": "DESKTOP ENVIRONMENT", "keyColor": "blue" }, { "type": "lm", "key": "LOGIN MANAGER", "format": "{type}", "keyColor": "blue" }, { "type": "wm", "key": "WM", "keyColor": "blue" }, { "type": "wmtheme", "key": "WM THEME", "keyColor": "blue" }, { "type": "display", "key": "MONITOR ({name})", "keyColor": "blue", "format": "{width}x{height} @ {refresh-rate} Hz - {physical-width}x{physical-height} mm ({inch} inches, {ppi} ppi)" }, { "type": "custom", // DesignInfo "format": "• {#yellow}DESIGN INFORMATION" }, { "type": "wallpaper", "key": "WALLPAPER", "keyColor": "yellow" }, { "type": "theme", "key": "KDE THEME", "format": "{1}", "keyColor": "yellow" }, { "type": "icons", "key": "ICON THEME", "format": "{1}", "keyColor": "yellow" }, { "type": "font", "key": "FONT", "format": "{?1}{1} [Qt]{?}{/1}Unknown", // Remove "[Qt]" if not using Qt "keyColor": "yellow" }, { "type": "terminalfont", "key": "TERMINAL FONT", "keyColor": "yellow" }, { "type": "cursor", "key": "CURSOR", "keyColor": "yellow" }, { "type": "custom", // OtherInfo "format": "• {#cyan}VARIOUS INFORMATION" }, { "type": "media", "key": "NOW PLAYING", "format": "{?artist}{artist} - {?}{title}", "keyColor": "cyan" }, { "type": "weather", "key": "WEATHER", "timeout": 1000, "keyColor": "cyan" }, { "type": "version", "key": "INFO", "keyColor": "cyan" }, "break", "colors", "break" ] } ================================================ FILE: presets/examples/2.jsonc ================================================ // Load with --config examples/2.jsonc // Note that you must replace the image path to an existing image to display it. { "$schema": "https://github.com/fastfetch-cli/fastfetch/raw/dev/doc/json_schema.json", // "logo": { // "type": "iterm", // "source": "~/Desktop/apple1.png", // "width": 28, // "height": 12 // }, "display": { "separator": "  ", "constants": [ "─────────────────" // {$1}, used in Custom module ], "key": { "type": "icon", "paddingLeft": 2 } }, "modules": [ { "type": "custom", // HardwareStart // {#1} is equivalent to `\u001b[1m`. {#} is equivalent to `\u001b[m` "format": "┌{$1} {#1}Hardware Information{#} {$1}┐" }, "host", "cpu", "gpu", "disk", "memory", "swap", "display", "brightness", "battery", "poweradapter", "bluetooth", "sound", "gamepad", { "type": "custom", // SoftwareStart "format": "├{$1} {#1}Software Information{#} {$1}┤" }, { "type": "title", "keyIcon": "", "key": "Title", // Title module has no key by default, so that icon is not displayed "format": "{user-name}@{host-name}" }, "os", "kernel", "lm", "de", "wm", "shell", "terminal", "terminalfont", "theme", "icons", "wallpaper", "packages", "uptime", "media", { "type": "localip", "compact": true }, { "type": "publicip", "timeout": 1000 }, { "type": "wifi", "format": "{ssid}" }, "locale", { "type": "custom", // InformationEnd "format": "└{$1}──────────────────────{$1}┘" }, { "type": "colors", "paddingLeft": 2, "symbol": "circle" } ] } ================================================ FILE: presets/examples/20.jsonc ================================================ // Inspired by https://github.com/usgraphics/TR-100 { "$schema": "https://github.com/fastfetch-cli/fastfetch/raw/dev/doc/json_schema.json", "logo": null, "display": { "pipe": true, "key": { "width": 16 }, "separator": "│ ", "percent": { "type": ["bar", "hide-others"] }, "bar": { "border": null, "char": { "elapsed": "█", "total": "░" }, "width": 40 }, "constants": [ "\u001b[42C" ] }, "modules": [ { "type": "custom", "format": "┌┬┬┬┬┬┬┬┬┬┬┬┬┬┬┬┬┬┬┬┬┬┬┬┬┬┬┬┬┬┬┬┬┬┬┬┬┬┬┬┬┬┬┬┬┬┬┬┬┬┬┬┬┬┬┬┐" }, { "type": "custom", "format": "├┴┴┴┴┴┴┴┴┴┴┴┴┴┴┴┴┴┴┴┴┴┴┴┴┴┴┴┴┴┴┴┴┴┴┴┴┴┴┴┴┴┴┴┴┴┴┴┴┴┴┴┴┴┴┴┤" }, { "type": "version", "key": " ", "format": "│ FASTFETCH v{version} │" }, { "type": "custom", "format": "│ TR-100 MACHINE REPORT │" }, { "type": "custom", "format": "├────────────┬──────────────────────────────────────────┤" }, { "type": "os", "key": "│ OS │{$1}" }, { "type": "kernel", "key": "│ KERNEL │{$1}" }, { "type": "custom", "format": "├────────────┼──────────────────────────────────────────┤" }, { "type": "title", "key": "│ HOSTNAME │{$1}", "format": "{host-name}" }, { "type": "localip", "key": "│ CLIENT IP │{$1}", "format": "{ipv4}" }, { "type": "localip", "key": "│ MAC ADDR │{$1}", "format": "{mac} ({ifname})", "showIpv4": false, "showMac": true }, { "type": "dns", "key": "│ DNS │{$1}", "showType": "ipv4" }, { "type": "title", "key": "│ USER │{$1}", "format": "{user-name}" }, { "type": "host", "key": "│ MACHINE │{$1}", "format": "{name}" }, { "type": "custom", "format": "├────────────┼──────────────────────────────────────────┤" }, { "type": "cpu", "key": "│ PROCESSOR │{$1}", "format": "{name}" }, { "type": "cpu", "key": "│ CORES │{$1}", "format": "{cores-physical} PHYSICAL CORES / {cores-logical} THREADS", "showPeCoreCount": false }, { "type": "cpu", "key": "│ CPU FREQ │{$1}", "format": "{freq-max}{/freq-max}{freq-base}{/}" }, { "type": "loadavg", "compact": false, "key": "│ LOAD {duration>2}m │{$1}" // pad duration to 2 chars }, { "type": "custom", "format": "├────────────┼──────────────────────────────────────────┤" }, { "type": "memory", "key": "│ MEMORY │{$1}", "format": "{used} / {total} [{percentage}]", "percent": { "type": ["num"] } }, { "type": "memory", "key": "│ USAGE │{$1}", "format": "", "percent": { "type": ["bar", "hide-others"] } }, { "type": "custom", "format": "├────────────┼──────────────────────────────────────────┤" }, { "type": "disk", "key": "│ VOLUME │{$1}", "format": "{size-used} / {size-total} [{size-percentage}]", "folders": "/", "percent": { "type": ["num"] } }, { "type": "disk", "key": "│ DISK USAGE │{$1}", "format": "", "percent": { "type": ["bar", "hide-others"] } }, { "type": "custom", "format": "├────────────┼──────────────────────────────────────────┤" }, { "type": "users", "key": "│ LAST LOGIN │{$1}", "format": "{login-time}{?client-ip} ({client-ip})", "myselfOnly": true }, { "type": "uptime", "key": "│ UPTIME │{$1}" }, { "type": "custom", "format": "└────────────┴──────────────────────────────────────────┘" } ] } ================================================ FILE: presets/examples/21.jsonc ================================================ { "$schema": "https://github.com/fastfetch-cli/fastfetch/raw/dev/doc/json_schema.json", "logo": { "type": "small" }, "display": { "constants": [ "██ " ] }, "modules": [ { "key": "{$1}Distro", "keyColor": "38;5;210", "type": "os" }, { "key": "{$1}Kernel", "keyColor": "38;5;84", "type": "kernel" }, { "key": "{$1}Shell", "keyColor": "38;5;147", "type": "shell" }, { "key": "{$1}Packages", "keyColor": "38;5;200", "type": "packages" }, { "key": "{$1}WM", "keyColor": "38;5;44", "type": "wm" }, { "key": "{$1}CPU", "keyColor": "38;5;75", "type": "cpu" }, { "key": "{$1}Memory", "keyColor": "38;5;123", "type": "memory" } ] } ================================================ FILE: presets/examples/22.jsonc ================================================ // Designed for Arch Linux // Modified from: https://github.com/fastfetch-cli/fastfetch/pull/1025#issuecomment-2177566138 { "$schema": "https://github.com/fastfetch-cli/fastfetch/raw/dev/doc/json_schema.json", "logo": { "source": "arch3", "color": { "1": "red", "2": "yellow" } }, "display": { "color": { "separator": "blue" }, "separator": " | ", "constants": [ ">-----------<+>---------------------------------------------<" ] }, "modules": [ { "type": "kernel", "key": " /\\rch Linux", "keyColor": "magenta" }, { "type": "custom", "format": "{$1}", "outputColor": "separator" }, { "type": "uptime", "key": " Uptime ", "keyColor": "green" }, { "type": "shell", "key": " Shell ", "keyColor": "green" }, { "type": "terminal", "key": " Terminal ", "keyColor": "green" }, { "type": "terminalfont", "key": " Font ", "keyColor": "green" }, { "type": "packages", "key": " Packages ", "keyColor": "green" }, { "type": "localip", "key": " Local IP ", "keyColor": "green" }, { "type": "custom", "format": "{$1}", "outputColor": "separator" }, { "type": "display", "key": " Display ", "keyColor": "cyan" }, { "type": "cpu", "key": " CPU ", "keyColor": "cyan" }, { "type": "gpu", "key": " GPU ", "keyColor": "cyan" }, { "type": "memory", "key": " RAM ", "keyColor": "cyan" }, { "type": "swap", "key": " SWAP ", "keyColor": "cyan" }, { "type": "disk", "key": " Disk ", "keyColor": "cyan" }, { "type": "battery", "key": " Battery ", "keyColor": "cyan" }, { "type": "custom", "format": "{$1}", "outputColor": "separator" }, "break", { "type": "colors", "paddingLeft": 15 } ] } ================================================ FILE: presets/examples/23.jsonc ================================================ // designed for presenting Vanilla Linux // inspired from imstilllearnin's Vanilla Logo Ultra { "$schema": "https://github.com/fastfetch-cli/fastfetch/raw/dev/doc/json_schema.json", "logo": { "type": "small" }, "display": { "color": { "output": "cyan" }, "separator": "" }, "modules": [ { "type": "kernel", "key": "[_Kernel___> ", "keyColor": "blue" }, { "type": "packages", "outputColor": "white", "key": " [_Packages_> ", "keyColor": "green" }, { "type": "localip", "outputColor": "white", "key": " [_Local_IP_> ", "keyColor": "green" }, { "type": "memory", "format": "[{3}] {1} / {2}", "key": " [_RAM______> ", "keyColor": "magenta" }, { "type": "swap", "format": "[{3}] {1} / {2}", "key": " [_SWAP_____> ", "keyColor": "magenta" }, { "type": "disk", "format": "[{3}] {1} / {2} {9}", "key": " [_Disk_____> ", "keyColor": "magenta" }, { "type": "battery", "format": "[{4}] {5}", "key": " [_Battery__> ", "keyColor": "magenta" }, "break", { "type": "colors", "paddingLeft": 9, "symbol": "circle" } ] } ================================================ FILE: presets/examples/24.jsonc ================================================ // By jan-rex // Modified from: https://github.com/fastfetch-cli/fastfetch/discussions/1269 { "$schema": "https://github.com/fastfetch-cli/fastfetch/raw/dev/doc/json_schema.json", "logo": { "padding": { "top": 2 } }, "display": { "separator": "", "constants": [ // CONSTANT {$1} - COLOR BACKGROUND FOR KEY "\u001b[48;2;43;43;69m", // CONSTANT {$2} - COLOR BACKGROUND FOR OUTPUT "\u001b[48;2;56;59;78m", // CONSTANT {$3} - VERTICAL BARS AT START AND 75th CHARACTERS FORWARD AND BACKWARD "\u001b[90m│ │\u001b[60D\u001b[39m", ] }, "modules": [ // CUSTOM - Top UI bar { "type": "custom", "key": "{#90}{$1}╭─────────────╮", "format": "{#90}{$2}╭────────────────────────────────────────────────────────────╮", }, { "type": "title", "key": "{#90}{$1}│ {#92}User {#90}│", "format": "{$2}{$3}{user-name} {#2}[{home-dir}]" }, { "type": "users", "key": "{#90}{$1}│ {#92}Users {#90}│", "myselfOnly": false, "format": "{$2}{$3}{1}@{host-name}{/host-name}localhost{/}{?client-ip} {#2}[IP:{client-ip}]{?} [Login time: {login-time}]" }, { "type": "datetime", "key": "{#90}{$1}│ {#92}Datetime {#90}│", "format": "{$2}{$3}{year}-{month-pretty}-{day-in-month} {hour-pretty}:{minute-pretty}:{second-pretty} [{weekday}] [W{week}] [UTC{offset-from-utc}]" }, { "type": "title", "key": "{#90}{$1}│ {#93}Host {#90}│", "format": "{$2}{$3}{host-name}" }, { "type": "host", "key": "{#90}{$1}│ {#93}Machine {#90}│", "format": "{$2}{$3}{name} {#2}{version}" }, { "type": "os", "key": "{#90}{$1}│ {#93}OS {#90}│", "format": "{$2}{$3}{?pretty-name}{pretty-name}{?}{/pretty-name}{name}{/} {codename} {#2}[v{version}] [{arch}]" }, { "type": "kernel", "key": "{#90}{$1}│ {#93}Kernel {#90}│", "format": "{$2}{$3}{sysname} {#2}[v{release}]" }, { "type": "uptime", "key": "{#90}{$1}│ {#93}Uptime {#90}│", "format": "{$2}{$3}{?days}{days} Days + {?}{hours}:{minutes}:{seconds}" }, { "type": "cpu", "key": "{#90}{$1}│ {#91}CPU {#90}│", "showPeCoreCount": true, "temp": true, "format": "{$2}{$3}{name} {#2}[C:{core-types}] [{freq-max}]" }, { "type": "gpu", "key": "{#90}{$1}│ {#91}GPU {#90}│", "detectionMethod": "auto", "driverSpecific": true, "format": "{$2}{$3}{name} {#2}[C:{core-count}]{?frequency} [{frequency}]{?} [{type}]" }, { "type": "memory", "key": "{#90}{$1}│ {#91}Memory {#90}│", "format": "{$2}{$3}{used} / {total} ({percentage}{$2})" }, { "type": "disk", "key": "{#90}{$1}│ {#91}Disk {#90}│", "format": "{$2}{$3}{size-used} / {size-total} ({size-percentage}{$2})" }, { "type": "poweradapter", "key": "{#90}{$1}│ {#91}Power {#90}│", "format": "{$2}{$3}{name}" }, { "type": "terminal", "key": "{#90}{$1}│ {#95}Terminal {#90}│", "format": "{$2}{$3}{pretty-name} {#2}[{version}] [PID:{pid}]" }, { "type": "terminalfont", "key": "{#90}{$1}│ {#95}Font {#90}│", "format": "{$2}{$3}{name} {#2}[{size}]" }, { "type": "shell", "key": "{#90}{$1}│ {#95}Shell {#90}│", "format": "{$2}{$3}{pretty-name} {#2}[v{version}] [PID:{pid}]" }, { // localip IPv4 "type": "localip", "key": "{#90}{$1}│ {#94}Local IPv4 {#90}│", "showPrefixLen": true, "showIpv4": true, "showIpv6": false, "showMtu": true, "format": "{$2}{$3}{ifname}: {ipv4} {#2}[MTU:{mtu}]" }, { // localip IPv6 "type": "localip", "key": "{#90}{$1}│ {#94}Local IPv6 {#90}│", "showPrefixLen": true, "showIpv4": false, "showIpv6": true, "showMtu": true, "format": "{$2}{$3}{ifname}: {ipv6} {#2}[MTU:{mtu}]" }, { "type": "publicip", "key": "{#90}{$1}│ {#94}Public IPv4 {#90}│", "ipv6": false, "format": "{$2}{$3}{ip} {#2}[{location}]" }, { "type": "publicip", "key": "{#90}{$1}│ {#94}Public IPv6 {#90}│", "ipv6": true, "format": "{$2}{$3}{ip} {#2}[{location}]" }, // CUSTOM - Button UI bar { "type": "custom", "key": "{#90}{$1}╰─────────────╯", "format": "{#90}{$2}╰────────────────────────────────────────────────────────────╯", } ] } ================================================ FILE: presets/examples/25.jsonc ================================================ // Based on #1576 { "$schema": "https://github.com/fastfetch-cli/fastfetch/raw/dev/doc/json_schema.json", "display": { "color": { "keys": "blue" }, "separator": "", // Constants are reusable strings referenced by {$1}, {$2}, etc. // These contain ANSI escape codes for cursor positioning "constants": [ "──────────────────────────────────────────────", // {$1} - horizontal line for inner borders "\u001b[47D", // {$2} - move cursor left 47 columns "\u001b[47C", // {$3} - move cursor right 47 columns "\u001b[46C", // {$4} - move cursor right 46 columns "══════════════════════════════════════════════" // {$5} - horizontal line for outer borders ], "brightColor": false }, "modules": [ { "type": "version", "key": "╔═══════════════╦═{$5}╗\u001b[41D", "format": "\u001b[1m{#keys} {1} - {2} " }, { "type": "os", // Key format breakdown for OS module: // "║ {icon} \u001b[s{sysname}\u001b[u\u001b[10C│{$3}║{$2}" // // ║ - Left border of key block // {icon} - OS icon (defined internally by fastfetch) // \u001b[s - ANSI escape: save cursor position (ESC[s) // {sysname} - Format variable: system name (e.g., "Linux", "Darwin") // \u001b[u - ANSI escape: restore cursor to saved position (ESC[u) // Necessary because the length of `{sysname}` differs between different platforms // \u001b[10C - ANSI escape: move cursor right 10 columns (ESC[10C) // │ - Right border of key block (always 10 columns from left border) // {$3} - Reference to constants[2]: move cursor right 47 columns // ║ - Right border of value block // {$2} - Reference to constants[1]: move cursor left 47 columns // // This creates a fixed-width layout where the key block is exactly 10 columns wide, // regardless of the actual content length. The cursor manipulation ensures proper // alignment for the table-like structure. "key": "║ {icon} \u001b[s{sysname}\u001b[u\u001b[10C║{$3}║{$2}" }, { "type": "datetime", "key": "║ {icon} Fetched ║{$3}║{$2}", "format": "{year}-{month-pretty}-{day-pretty} {hour-pretty}:{minute-pretty}:{second-pretty} {timezone-name}" }, { "type": "locale", "key": "║ {icon} Locale ║{$3}║{$2}" }, // Hardware section with cyan color theme { "type": "custom", "key": "║{#cyan}┌──────────────┬{$1}┐{#keys}║\u001b[37D", "format": "{#bright_cyan} Hardware " }, { "type": "chassis", // Similar structure but with cyan color formatting: // │{#cyan}│ - Left border with cyan color // {icon} - Chassis icon // Chassis - Fixed label text // │{$4}│{#keys}║{$2} - Positioning and borders for value area "key": "║{#cyan}│ {icon} Chassis │{$4}│{#keys}║{$2}" }, { "type": "memory", "key": "║{#cyan}│ {icon} RAM │{$4}│{#keys}║{$2}" }, { "type": "swap", "key": "║{#cyan}│ {icon} SWAP │{$4}│{#keys}║{$2}" }, { "type": "cpu", "key": "║{#cyan}│ {icon} CPU │{$4}│{#keys}║{$2}", "showPeCoreCount": true }, { "type": "gpu", "key": "║{#cyan}│ {icon} GPU │{$4}│{#keys}║{$2}" }, { "type": "disk", "key": "║{#cyan}│ {icon} Disk │{$4}│{#keys}║{$2}", "format": "{size-used} \/ {size-total} ({size-percentage}) - {filesystem}", }, { "type": "battery", "key": "║{#cyan}│ {icon} Battery │{$4}│{#keys}║{$2}" }, { "type": "custom", "key": "║{#cyan}└──────────────┴{$1}┘{#keys}║", "format": "" }, // Desktop section with green color theme { "type": "custom", "key": "║{#green}┌──────────────┬{$1}┐{#keys}║\u001b[37D", "format": "{#bright_green} Desktop " }, { "type": "de", "key": "║{#green}│ {icon} Desktop │{$4}│{#keys}║{$2}" }, { "type": "wm", "key": "║{#green}│ {icon} Session │{$4}│{#keys}║{$2}" }, { "type": "display", "key": "║{#green}│ {icon} Display │{$4}│{#keys}║{$2}", "compactType": "original-with-refresh-rate" }, { "type": "gpu", "key": "║{#green}│ {icon} G-Driver │{$4}│{#keys}║{$2}", "format": "{driver}" }, { "type": "custom", "key": "║{#green}└──────────────┴{$1}┘{#keys}║", "format": "" }, // Terminal section with yellow color theme { "type": "custom", "key": "║{#yellow}┌──────────────┬{$1}┐{#keys}║\u001b[37D", "format": "{#bright_yellow} Terminal " }, { "type": "shell", "key": "║{#yellow}│ {icon} Shell │{$4}│{#keys}║{$2}" }, { "type": "terminal", "key": "║{#yellow}│ {icon} Terminal │{$4}│{#keys}║{$2}" }, { "type": "terminalfont", "key": "║{#yellow}│ {icon} Term Font │{$4}│{#keys}║{$2}" }, { "type": "terminaltheme", "key": "║{#yellow}│ {icon} Colors │{$4}│{#keys}║{$2}" }, { "type": "packages", "key": "║{#yellow}│ {icon} Packages │{$4}│{#keys}║{$2}" }, { "type": "custom", "key": "║{#yellow}└──────────────┴{$1}┘{#keys}║", "format": "" }, // Development section with red color theme { "type": "custom", "key": "║{#red}┌──────────────┬{$1}┐{#keys}║\u001b[39D", "format": "{#bright_red} Development " }, { "type": "command", "keyIcon": "", // Custom icon override "key": "║{#red}│ {icon} Rust │{$4}│{#keys}║{$2}", "text": "rustc --version", "format": "rustc {~6,13}" // Print 6th to 13th characters (version number) }, { "type": "command", "condition": { "!system": "Windows" // Posix version }, "keyIcon": "", "key": "║{#red}│ {icon} Clang │{$4}│{#keys}║{$2}", "text": "clang --version | sed -n 's/.*version \\([0-9][0-9.]*\\).*/\\1/p'", "format": "clang {}" }, { "type": "command", "condition": { "system": "Windows" // Windows version }, "keyIcon": "", "key": "║{#red}│ {icon} Clang │{$4}│{#keys}║{$2}", "text": "clang --version | findstr version", // Finds the line with "version" "format": "clang {~-6}" // Prints the last 6 characters (version number) }, { "type": "command", "keyIcon": "", "key": "║{#red}│ {icon} NodeJS │{$4}│{#keys}║{$2}", "text": "node --version", "format": "node {~1}" // {~1} removes first character (v) }, { "type": "command", "keyIcon": "", "key": "║{#red}│ {icon} Go │{$4}│{#keys}║{$2}", "text": "go version | cut -d' ' -f3", "format": "go {~2}" // {~2} removes first 2 characters (go) }, { "type": "command", "keyIcon": "", "key": "║{#red}│ {icon} Zig │{$4}│{#keys}║{$2}", "text": "zig version", "format": "zig {}" }, { "type": "editor", "key": "║{#red}│ {icon} Editor │{$4}│{#keys}║{$2}" }, { "type": "command", "keyIcon": "󰊢", "key": "║{#red}│ {icon} Git │{$4}│{#keys}║{$2}", "text": "git version", "format": "git {~12}" }, { "type": "font", "key": "║{#red}│ {icon} Interface │{$4}│{#keys}║{$2}" }, { "type": "custom", "key": "║{#red}└──────────────┴{$1}┘{#keys}║", "format": "" }, // Uptime section with magenta color theme { "type": "custom", "key": "║{#magenta}┌──────────────┬{$1}┐{#keys}║\u001b[36D", "format": "{#bright_magenta} Uptime " }, { "type": "uptime", "key": "║{#magenta}│ {icon} Uptime │{$4}│{#keys}║{$2}" }, { "type": "users", "myselfOnly": true, // Only show current user "keyIcon": "", "key": "║{#magenta}│ {icon} Login │{$4}│{#keys}║{$2}" }, { "condition": { // Conditional module: only show on non-macOS "!system": "macOS" }, "type": "disk", "keyIcon": "", "key": "║{#magenta}│ {icon} OS Age │{$4}│{#keys}║{$2}", "folders": "/", // Check root filesystem "format": "{create-time:10} [{days} days]" // Show creation time and age in days }, { "condition": { // Conditional module: only show on macOS "system": "macOS" }, "type": "disk", "keyIcon": "", "key": "║{#magenta}│ {icon} OS Age │{$4}│{#keys}║{$2}", "folders": "/System/Volumes/VM", // Work around for APFS on macOS "format": "{create-time:10} [{days} days]" }, { "type": "custom", "key": "║{#magenta}└──────────────┴{$1}┘{#keys}║", "format": "" }, { "type": "custom", "key": "╚═════════════════{$5}╝", // Bottom border of the entire layout "format": "" }, // End with color palette and line break "break", // Add a blank line "colors" // Display color palette ] } /* Key Format Structure Explanation: The key format uses a combination of: 1. Unicode box drawing characters (│ ┌ ┐ └ ┘ ┬ ┴) for borders 2. ANSI escape codes for cursor positioning (\u001b[...) 3. Format variables ({icon}, {sysname}, etc.) 4. Constant references ({$1}, {$2}, etc.) 5. Color formatting ({#color}) ANSI Escape Codes Used: - \u001b[s - Save cursor position (ESC[s) - \u001b[u - Restore cursor position (ESC[u) - \u001b[nC - Move cursor right n columns (ESC[nC) - \u001b[nD - Move cursor left n columns (ESC[nD) This creates a table-like layout with fixed column widths and proper alignment, regardless of the actual content length in each field. For more ANSI escape code reference, see: https://gist.github.com/fnky/458719343aabd01cfb17a3a4f7296797#cursor-controls */ ================================================ FILE: presets/examples/26.jsonc ================================================ // Modified from: 24.jsonc { "$schema": "https://github.com/fastfetch-cli/fastfetch/raw/dev/doc/json_schema.json", "logo": { "padding": { "top": 2 } }, "display": { "separator": "", "constants": [ // CONSTANT {$1} - VERTICAL BARS AT START AND 75th CHARACTERS FORWARD AND BACKWARD "\u001b[90m│ │\u001b[60D\u001b[39m" ] }, "modules": [ // CUSTOM - Top UI bar { "type": "custom", "key": "{#90}╭ Keys ───────╮", "format": "{#90}╭ Values ────────────────────────────────────────────────────╮", }, { "type": "title", "key": "{#90}│ {#92}User {#90}│", "format": "{$1}{user-name} {#2}[{home-dir}]" }, { "type": "users", "key": "{#90}│ {#92}Users {#90}│", "myselfOnly": false, "format": "{$1}{1}@{host-name}{/host-name}localhost{/}{?client-ip} {#2}[IP:{client-ip}]{?} [Login time: {login-time}]" }, { "type": "datetime", "key": "{#90}│ {#92}Datetime {#90}│", "format": "{$1}{year}-{month-pretty}-{day-in-month} {hour-pretty}:{minute-pretty}:{second-pretty} {#2}[{weekday}] [W{week}] [UTC{offset-from-utc}]" }, { "type": "title", "key": "{#90}│ {#93}Host {#90}│", "format": "{$1}{host-name}" }, { "type": "host", "key": "{#90}│ {#93}Machine {#90}│", "format": "{$1}{name} {#2}{version}" }, { "type": "os", "key": "{#90}│ {#93}OS {#90}│", "format": "{$1}{?pretty-name}{pretty-name}{?}{/pretty-name}{name}{/} {codename} {#2}[v{version}] [{arch}]" }, { "type": "kernel", "key": "{#90}│ {#93}Kernel {#90}│", "format": "{$1}{sysname} {#2}[v{release}]" }, { "type": "uptime", "key": "{#90}│ {#93}Uptime {#90}│", "format": "{$1}{?days}{days} Days + {?}{hours}:{minutes}:{seconds}" }, { "type": "cpu", "key": "{#90}│ {#91}CPU {#90}│", "showPeCoreCount": true, "temp": true, "format": "{$1}{name} {#2}[C:{core-types}] [{freq-max}]" }, { "type": "gpu", "key": "{#90}│ {#91}GPU {#90}│", "detectionMethod": "auto", "driverSpecific": true, "format": "{$1}{name} {#2}[C:{core-count}]{?frequency} [{frequency}]{?} {#2}[{type}]" }, { "type": "memory", "key": "{#90}│ {#91}Memory {#90}│", "format": "{$1}{used} / {total} ({percentage})" }, { "type": "disk", "key": "{#90}│ {#91}Disk {#90}│", "format": "{$1}{size-used} / {size-total} ({size-percentage})" }, { "type": "poweradapter", "key": "{#90}│ {#91}Power {#90}│", "format": "{$1}{name}" }, { "type": "terminal", "key": "{#90}│ {#95}Terminal {#90}│", "format": "{$1}{pretty-name} {#2}[{version}] [PID:{pid}]" }, { "type": "terminalfont", "key": "{#90}│ {#95}Font {#90}│", "format": "{$1}{name} {#2}[{size}]" }, { "type": "shell", "key": "{#90}│ {#95}Shell {#90}│", "format": "{$1}{pretty-name} {#2}[v{version}] [PID:{pid}]" }, { // localip IPv4 "type": "localip", "key": "{#90}│ {#94}Local IPv4 {#90}│", "showPrefixLen": true, "showIpv4": true, "showIpv6": false, "showMtu": true, "format": "{$1}{ifname}: {ipv4} {#2}[MTU:{mtu}]" }, { // localip IPv6 "type": "localip", "key": "{#90}│ {#94}Local IPv6 {#90}│", "showPrefixLen": true, "showIpv4": false, "showIpv6": true, "showMtu": true, "format": "{$1}{ifname}: {ipv6} {#2}[MTU:{mtu}]" }, { "type": "publicip", "key": "{#90}│ {#94}Public IPv4 {#90}│", "ipv6": false, "format": "{$1}{ip} {#2}[{location}]" }, { "type": "publicip", "key": "{#90}│ {#94}Public IPv6 {#90}│", "ipv6": true, "format": "{$1}{ip} {#2}[{location}]" }, // CUSTOM - Button UI bar { "type": "custom", "key": "{#90}╰─────────────╯", "format": "{#90}╰────────────────────────────────────────────────────────────╯", }, "break", { "type": "custom", "key": " ", "format": "{#90}╭ Colors ───────────────────────────────────────────────────────────────────╮", }, { "type": "custom", "format": "{#90}│ {#40} {#41} {#42} {#43} {#44} {#45} {#46} {#47} {#} {#90}│", }, { "type": "custom", "format": "{#90}│ {#100} {#101} {#102} {#103} {#104} {#105} {#106} {#107} {#} {#90}│", }, { "type": "custom", "format": "{#90}╰───────────────────────────────────────────────────────────────────────────╯", }, ] } ================================================ FILE: presets/examples/27.jsonc ================================================ { "$schema": "https://github.com/fastfetch-cli/fastfetch/raw/dev/doc/json_schema.json", "logo": { "type": "small", "padding": { "top": 1 } }, "display": { "separator": " " }, "modules": [ "break", "title", { "type": "os", "key": "os ", "keyColor": "red" }, { "type": "kernel", "key": "kernel", "keyColor": "green" }, { "type": "host", "format": "{vendor} {family}", "key": "host ", "keyColor": "yellow" }, { "type": "packages", "key": "pkgs ", "keyColor": "blue" }, { "type": "uptime", "format": "{?days}{days}d {?}{hours}h {minutes}m", "key": "uptime", "keyColor": "magenta" }, { "type": "memory", "key": "memory", "keyColor": "cyan" }, "break" ] } ================================================ FILE: presets/examples/28.jsonc ================================================ { "$schema": "https://github.com/fastfetch-cli/fastfetch/raw/dev/doc/json_schema.json", "logo": { "type": "small" }, "display": { "separator": " ", "key": { "type": "both" }, "bar": { "border": { "left": "\uee00", "leftElapsed": "\uee03", "right": "\uee02", "rightElapsed": "\uee05" }, "char": { "total": "\uee01", "elapsed": "\uee04" }, "color": { "total": null } }, "percent": { "type": [ "bar", "bar-monochrome" ] } }, "modules": [ "title", "separator", { "type": "memory", "key": "MEM" }, { "type": "swap", "key": "SWP" }, { "type": "disk", "folders": "/", "key": "DSK" }, { "type": "battery", "key": "BAT" }, { "type": "brightness", "key": "BGT" }, { "type": "colors", "paddingLeft": 6, "symbol": "circle" } ] } ================================================ FILE: presets/examples/29.jsonc ================================================ // #1887 { "$schema": "https://github.com/fastfetch-cli/fastfetch/raw/dev/doc/json_schema.json", "logo": null, "display": { "constants": [ "\u001b[s\u001b[33C│\u001b[u", " » " ], "separator": "", "percent": { "type": ["num", "bar"] }, "brightColor": false, "bar": { "border": { "left": "[", "leftElapsed": "[", "right": "]", "rightElapsed": "]" }, "char": { "elapsed": "─", "total": "─" }, "color": { "elapsed": "default", "total": "light_black" }, "width": 16 }, "color": { "separator": "default", "keys": "default", "output": "default" } }, "modules": [ "title", { "type": "custom", "format": "┌────「 {#1}OS{#} 」────────────────────────────┐" }, { "type": "os", "key": "│ {icon}{$2}{$1}" }, { "type": "disk", "folders": "/", "key": "│ {$1}", "format": "{size-percentage-bar} {size-percentage}" }, { "type": "disk", "folders": "/", "key": "│ {$1}", "format": "{size-used} / {size-total}" }, { "type": "custom", "format": "└────────────────────────────────────────┘" }, "break", { "type": "custom", "format": "┌────「 {#1}UI{#} 」────────────────────────────┐" }, { "type": "wm", "key": "│ {icon}{$2}{$1}" }, { "type": "wmtheme", "key": "│ {icon}{$2}{$1}" }, { "type": "custom", "key": "│ {$1}" }, { "type": "display", "key": "│ {icon}{$2}{$1}", "format": "{width}x{height} @ {refresh-rate} Hz" }, { "type": "custom", "key": "│ {$1}" }, { "type": "terminal", "key": "│ {icon}{$2}{$1}" }, { "type": "terminalfont", "key": "│ {icon}{$2}{$1}" }, { "type": "custom", "format": "└────────────────────────────────────────┘" }, "break", { "type": "custom", "format": "┌────「 {#1}HW{#} 」────────────────────────────┐" }, { "type": "cpu", "key": "│ {icon}{$2}{$1}", "format": "{name}" }, { "type": "gpu", "key": "│ {icon}{$2}{$1}", "format": "{name}" }, { "type": "custom", "key": "│ {$1}" }, { "type": "memory", "key": "│ {icon}{$2}{$1}", "format": "{percentage-bar} {percentage}" }, { "type": "memory", "key": "│ {$1}", "format": "{used} / {total}" }, { "type": "custom", "format": "└────────────────────────────────────────┘" } ] } ================================================ FILE: presets/examples/3.jsonc ================================================ // Load with --config examples/3.jsonc { "$schema": "https://github.com/fastfetch-cli/fastfetch/raw/dev/doc/json_schema.json", "logo": { "type": "small" }, "display": { "size": { "binaryPrefix": "si" } }, "modules": [ "vulkan", "opengl", "opencl", "memory", { "type": "disk", "folders": "/:/home:/boot:/efi" }, "localip" ] } ================================================ FILE: presets/examples/30.jsonc ================================================ { "$schema": "https://github.com/fastfetch-cli/fastfetch/raw/dev/doc/json_schema.json", "logo": null, "display": { "key": { "type": "both", "paddingLeft": 6, "width": 17 } }, "modules": [ { "type": "custom", "format": "|---------------------: {#1}Hardware{#} : ---------------------|" }, "break", { "keyColor": "green", "type": "host" }, { "keyColor": "green", "type": "cpu" }, { "keyColor": "yellow", "type": "memory" }, { "keyColor": "yellow", "type": "swap" }, { "type": "custom", "keyIcon": "", "key": "Disks" }, { "type": "disk", "key": " ", "format": " [{mountpoint}] - {size-used} / {size-total} ({size-percentage})" }, "break", { "type": "title", "format": "|-------------------------------------------------------|\u001b[40D: {#1}{user-name} @ {host-name}{#} :" }, "break", { "type": "os", "keyColor": "cyan" }, { "type": "kernel", "keyColor": "cyan" }, { "type": "packages", "keyColor": "red", "key": "Pkgs" }, { "type": "shell", "keyColor": "red" }, { "type": "terminal", "key": "Term", "keyColor": "red" }, { "type": "locale", "keyColor": "magenta" }, "break", { "type": "custom", "format": "|---------------------: {#1}Software{#} : ---------------------|" }, "break", { "type": "colors", "symbol": "circle", "paddingLeft": 8 } ] } ================================================ FILE: presets/examples/31.jsonc ================================================ // Modified from https://github.com/fastfetch-cli/fastfetch/discussions/2133 { "$schema": "https://github.com/fastfetch-cli/fastfetch/raw/dev/doc/json_schema.json", "logo": { "source": "\u001b[?25l\u001b[0m\u001b[38;2;0;0;0;48;2;23;23;23m▇\u001b[48;2;24;24;24m▇▇▇▇▇\u001b[38;2;6;2;4;48;2;39;20;29m▅\u001b[38;2;45;17;30;48;2;5;4;4m▁\u001b[38;2;57;23;43;48;2;5;3;4m▁\u001b[38;2;46;17;34;48;2;4;3;4m▁\u001b[38;2;49;19;37;48;2;7;5;6m▁\u001b[38;2;52;20;41;48;2;13;8;10m▄\u001b[38;2;96;79;131;48;2;52;23;42m▗\u001b[38;2;153;160;221;48;2;83;63;97m▅\u001b[38;2;162;178;235;48;2;95;103;133m▇\u001b[38;2;158;173;227;48;2;93;103;135m▇\u001b[38;2;165;183;244;48;2;94;104;138m▇\u001b[38;2;166;184;248;48;2;96;103;138m▇\u001b[38;2;164;180;243;48;2;94;102;137m▇\u001b[38;2;162;178;240m▇\u001b[38;2;164;180;247;48;2;93;101;137m▇\u001b[38;2;163;181;247;48;2;94;102;138m▇\u001b[38;2;163;180;245;48;2;94;101;139m▇\u001b[38;2;164;180;246;48;2;95;102;139m▇\u001b[38;2;165;179;248;48;2;94;102;139m▇\u001b[38;2;164;179;248;48;2;95;101;139m▇\u001b[38;2;163;179;247;48;2;94;101;139m▇\u001b[38;2;163;178;247;48;2;93;100;138m▇\u001b[38;2;163;179;246;48;2;95;100;136m▇\u001b[38;2;27;17;22;48;2;144;151;202m▝\u001b[38;2;94;74;108;48;2;50;21;40m▁\u001b[38;2;55;17;43;48;2;19;9;14m▄\u001b[38;2;51;16;41;48;2;9;5;7m▂\u001b[38;2;4;1;3;48;2;23;23;24m▇\u001b[38;2;4;1;2;48;2;24;24;24m▇\u001b[38;2;45;15;33;48;2;4;3;3m▁\u001b[38;2;48;17;36;48;2;10;5;6m▁\u001b[38;2;0;0;0;48;2;24;24;24m▇\u001b[48;2;23;23;23m▇\u001b[48;2;24;24;24m▇▇▇\u001b[0m\n\u001b[7m\u001b[38;2;0;0;0m \u001b[0m\u001b[38;2;0;0;0;48;2;0;0;0m \u001b[38;2;24;11;18;48;2;2;1;2m▁\u001b[38;2;44;18;32;48;2;6;3;4m▅\u001b[38;2;42;13;28;48;2;50;21;38m╴\u001b[38;2;55;25;45;48;2;53;23;42m╴\u001b[38;2;54;24;44;48;2;56;22;45m▄\u001b[38;2;56;23;45;48;2;55;24;48m▘\u001b[38;2;54;25;48;48;2;55;23;46m╴\u001b[38;2;90;82;131;48;2;66;41;72m▗\u001b[38;2;116;115;169;48;2;151;155;218m▚\u001b[38;2;107;106;151;48;2;150;158;205m╼\u001b[38;2;137;142;194;48;2;164;166;200m╹\u001b[38;2;128;121;155;48;2;186;187;218m▏\u001b[38;2;170;166;177;48;2;186;189;219m▁\u001b[38;2;158;158;196;48;2;162;177;234m╴\u001b[38;2;130;129;169;48;2;156;168;222m╱\u001b[38;2;163;182;247;48;2;163;180;244m┊\u001b[38;2;164;183;248;48;2;162;181;246m▆\u001b[38;2;164;183;249;48;2;162;181;247m▄\u001b[38;2;163;181;248;48;2;163;181;245m╴\u001b[38;2;148;152;200;48;2;156;168;225m╴\u001b[38;2;162;181;245;48;2;163;181;247m╼\u001b[38;2;155;156;191;48;2;162;179;242m╺\u001b[38;2;172;176;214;48;2;194;194;226m▏\u001b[38;2;145;143;163;48;2;187;190;225m╶\u001b[38;2;116;116;151;48;2;169;174;217m▏\u001b[38;2;171;172;203;48;2;161;176;238m▁\u001b[38;2;152;162;215;48;2;114;104;145m▇\u001b[38;2;133;126;167;48;2;52;18;45m▖\u001b[38;2;56;20;48;48;2;55;19;46m╴\u001b[38;2;55;20;46;48;2;56;20;46m╸\u001b[38;2;55;18;47;48;2;56;20;47m▝\u001b[38;2;55;18;44;48;2;55;19;45m╹\u001b[38;2;44;14;35;48;2;50;18;42m╵\u001b[38;2;12;4;9;48;2;46;16;36m▝\u001b[38;2;34;12;25;48;2;2;0;1m▖\u001b[48;2;0;0;0m \u001b[0m\n\u001b[7m\u001b[38;2;0;0;0m \u001b[0m\u001b[38;2;0;0;0;48;2;0;0;0m \u001b[38;2;109;75;91;48;2;15;6;11m▗\u001b[38;2;154;127;138;48;2;64;36;49m▖\u001b[38;2;57;26;45;48;2;42;17;32m▇\u001b[38;2;43;18;30;48;2;53;24;40m╶\u001b[38;2;57;27;44;48;2;55;25;42m╴\u001b[38;2;56;26;48;48;2;56;26;46m━\u001b[38;2;39;17;29;48;2;54;24;43m▁\u001b[38;2;51;24;43;48;2;89;75;111m▋\u001b[38;2;107;100;153;48;2;90;82;126m▝\u001b[38;2;234;232;241;48;2;140;125;150m▗\u001b[38;2;125;97;98;48;2;224;218;222m▗\u001b[38;2;219;190;204;48;2;190;175;186m╴\u001b[38;2;149;148;174;48;2;188;193;225m╴\u001b[38;2;118;118;152;48;2;162;170;215m╻\u001b[38;2;117;116;154;48;2;160;171;224m╻\u001b[38;2;119;119;160;48;2;163;179;238m▗\u001b[38;2;138;144;205;48;2;165;181;245m▏\u001b[38;2;165;184;246;48;2;165;183;249m▂\u001b[38;2;165;185;246m▂\u001b[38;2;166;185;244;48;2;165;184;248m▃\u001b[38;2;136;139;184;48;2;163;177;231m┃\u001b[38;2;147;157;202;48;2;161;176;233m╴\u001b[38;2;167;182;240;48;2;165;183;247m▁\u001b[38;2;164;180;239;48;2;169;176;210m▇\u001b[38;2;153;155;189;48;2;192;197;228m╾\u001b[38;2;148;123;123;48;2;194;182;195m┕\u001b[38;2;155;137;136;48;2;217;208;219m▏\u001b[38;2;207;204;230;48;2;156;156;192m▊\u001b[38;2;160;169;221;48;2;124;118;156m▇\u001b[38;2;112;96;128;48;2;61;30;56m▏\u001b[38;2;48;14;35;48;2;51;19;42m╴\u001b[38;2;57;22;48;48;2;57;22;47m╴\u001b[38;2;58;23;46m▁\u001b[38;2;45;16;36;48;2;54;21;43m┎\u001b[38;2;43;17;33;48;2;53;21;43m╵\u001b[38;2;151;120;136;48;2;69;37;56m▗\u001b[38;2;108;77;93;48;2;17;7;12m▖\u001b[48;2;0;0;0m \u001b[38;2;0;0;1m▗\u001b[0m\n\u001b[7m\u001b[38;2;0;0;0m \u001b[0m\u001b[38;2;0;0;0;48;2;0;0;0m \u001b[38;2;27;17;26m▝\u001b[38;2;49;25;40;48;2;150;122;134m▍\u001b[38;2;118;92;100;48;2;63;34;49m▘\u001b[38;2;59;31;47;48;2;57;29;46m▖\u001b[38;2;59;28;46;48;2;58;28;46m╵\u001b[38;2;37;15;26;48;2;55;25;42m╹\u001b[38;2;58;27;45;48;2;44;16;31m▉\u001b[38;2;39;16;30;48;2;49;23;39m┆\u001b[38;2;57;30;50;48;2;91;78;114m▋\u001b[38;2;86;74;115;48;2;95;86;131m┊\u001b[38;2;102;95;139;48;2;185;174;200m▂\u001b[38;2;170;163;199;48;2;101;84;108m▚\u001b[38;2;171;187;245;48;2;166;178;229m╴\u001b[38;2;147;158;201;48;2;167;183;238m╴\u001b[38;2;136;137;173;48;2;165;179;229m┃\u001b[38;2;105;97;144;48;2;155;166;216m┃\u001b[38;2;168;182;234;48;2;91;81;122m▋\u001b[38;2;114;112;170;48;2;162;175;235m▖\u001b[38;2;162;175;236;48;2;165;182;242m╻\u001b[38;2;160;176;234;48;2;166;184;243m╷\u001b[38;2;166;185;245m▉\u001b[38;2;95;91;129;48;2;161;170;224m┃\u001b[38;2;125;127;165;48;2;162;172;224m│\u001b[38;2;117;120;156;48;2;163;175;223m│\u001b[38;2;102;100;149;48;2;164;178;235m╷\u001b[38;2;167;185;244;48;2;166;184;245m╴\u001b[38;2;162;175;230;48;2;157;157;195m▇\u001b[38;2;127;110;128;48;2;173;175;215m▘\u001b[38;2;190;187;226;48;2;152;154;194m▊\u001b[38;2;174;186;239;48;2;167;187;246m▏\u001b[38;2;165;178;231;48;2;102;88;115m▊\u001b[38;2;41;14;33;48;2;49;20;40m╷\u001b[38;2;44;16;35;48;2;58;24;49m▎\u001b[38;2;58;23;48;48;2;37;10;24m▊\u001b[38;2;50;21;37;48;2;59;24;48m▏\u001b[38;2;59;25;48;48;2;58;24;50m▅\u001b[38;2;65;32;53;48;2;127;97;109m▊\u001b[38;2;149;121;137;48;2;84;57;70m▉\u001b[38;2;39;15;27;48;2;4;1;2m▏\u001b[48;2;0;0;0m \u001b[0m\n\u001b[7m\u001b[38;2;0;0;0m \u001b[0m\u001b[38;2;0;0;0;48;2;0;0;0m \u001b[38;2;0;0;1m╵\u001b[38;2;109;85;92;48;2;36;21;29m▝\u001b[38;2;58;33;47;48;2;61;30;47m▇\u001b[38;2;59;32;50;48;2;59;32;47m┒\u001b[38;2;47;21;35;48;2;55;28;43m╶\u001b[38;2;54;25;41;48;2;58;30;46m▏\u001b[38;2;57;28;46;48;2;71;46;67m▋\u001b[38;2;94;75;106;48;2;59;32;54m▆\u001b[38;2;51;31;53;48;2;97;88;130m▘\u001b[38;2;87;78;121;48;2;102;95;149m▏\u001b[38;2;103;96;151;48;2;90;81;126m▉\u001b[38;2;97;88;135;48;2;150;158;209m▍\u001b[38;2;124;124;160;48;2;156;167;213m╴\u001b[38;2;127;120;140;48;2;166;177;220m╻\u001b[38;2;121;102;114;48;2;169;156;177m╹\u001b[38;2;181;188;221;48;2;109;96;131m▎\u001b[38;2;161;172;220;48;2;107;89;112m▊\u001b[38;2;143;149;208;48;2;103;92;145m▝\u001b[38;2;104;103;158;48;2;155;166;224m╻\u001b[38;2;166;181;233;48;2;112;110;152m▋\u001b[38;2;140;146;202;48;2;166;182;239m▏\u001b[38;2;166;177;222;48;2;107;100;150m▎\u001b[38;2;165;170;211;48;2;111;102;142m▌\u001b[38;2;146;111;114;48;2;168;164;197m┍\u001b[38;2;164;161;194;48;2;107;101;141m▊\u001b[38;2;108;99;144;48;2;160;175;231m▖\u001b[38;2;117;119;161;48;2;159;172;223m┕\u001b[38;2;129;131;184;48;2;167;183;236m▖\u001b[38;2;146;152;193;48;2;162;176;225m╸\u001b[38;2;170;190;244;48;2;168;188;244m▂\u001b[38;2;158;167;207;48;2;76;62;82m▉\u001b[38;2;99;78;114;48;2;64;34;60m▅\u001b[38;2;80;59;88;48;2;59;28;52m▖\u001b[38;2;60;27;49;48;2;59;25;48m╴\u001b[38;2;52;19;38;48;2;55;22;44m╴\u001b[38;2;60;26;49;48;2;59;26;48m▃\u001b[38;2;59;26;48;48;2;60;26;49m╵\u001b[38;2;134;107;115;48;2;57;37;45m▘\u001b[38;2;0;0;1;48;2;0;0;0m╵ \u001b[0m\n\u001b[7m\u001b[38;2;0;0;0m \u001b[0m\u001b[38;2;0;0;1;48;2;0;0;0m╸ \u001b[38;2;48;30;39;48;2;5;3;4m▝\u001b[38;2;59;34;48;48;2;59;35;47m╴\u001b[38;2;59;36;48;48;2;59;34;48m╷\u001b[38;2;42;16;29;48;2;54;30;43m╹\u001b[38;2;60;33;49;48;2;60;32;49m▃\u001b[38;2;57;31;51;48;2;99;78;108m▍\u001b[38;2;102;81;116;48;2;101;81;114m╵\u001b[38;2;69;52;73;48;2;88;76;108m╻\u001b[38;2;105;100;152;48;2;83;75;115m▋\u001b[38;2;104;98;151;48;2;86;77;114m▋\u001b[38;2;102;94;135;48;2;75;65;95m▚\u001b[38;2;117;91;94;48;2;166;158;184m╻\u001b[38;2;174;150;157;48;2;157;125;125m┣\u001b[38;2;164;121;108;48;2;202;155;147m▃\u001b[38;2;92;62;55;48;2;157;122;119m▂\u001b[38;2;155;158;202;48;2;125;100;115m╹\u001b[38;2;187;145;138;48;2;121;98;127m▖\u001b[38;2;101;91;147;48;2;103;95;147m╵\u001b[38;2;152;159;207;48;2;125;99;109m▊\u001b[38;2;109;90;125;48;2;154;154;196m▍\u001b[38;2;169;143;149;48;2;110;90;120m▏\u001b[38;2;123;88;86;48;2;151;126;144m▂\u001b[38;2;101;74;62;48;2;206;176;176m▂\u001b[38;2;98;73;59;48;2;199;168;163m▁\u001b[38;2;141;96;81;48;2;147;114;119m╻\u001b[38;2;169;183;235;48;2;131;116;151m▝\u001b[38;2;112;110;152;48;2;142;149;197m▚\u001b[38;2;174;192;240;48;2;163;180;230m╵\u001b[38;2;171;190;240;48;2;171;190;243m╲\u001b[38;2;173;184;226;48;2;100;85;108m▎\u001b[38;2;100;80;111;48;2;99;80;115m▏\u001b[38;2;98;77;111;48;2;58;28;54m▊\u001b[38;2;60;28;51;48;2;60;27;50m▅\u001b[38;2;41;12;32;48;2;57;25;47m╹\u001b[38;2;60;27;50;48;2;61;28;48m▘\u001b[38;2;60;26;51;48;2;58;26;47m╵\u001b[38;2;33;16;26;48;2;2;0;1m▘\u001b[48;2;0;0;0m \u001b[0m\n\u001b[38;2;0;0;0;48;2;0;0;0m \u001b[48;2;0;0;1m▉\u001b[38;2;2;1;1;48;2;43;28;36m▖\u001b[38;2;49;26;37;48;2;55;32;45m┌\u001b[38;2;47;24;36;48;2;58;34;48m▏\u001b[38;2;59;35;49;48;2;55;32;46m▉\u001b[38;2;56;31;50;48;2;102;85;113m▎\u001b[38;2;99;82;112;48;2;99;81;111m▎\u001b[38;2;103;83;110;48;2;89;71;98m┊\u001b[38;2;89;75;108;48;2;94;88;128m▃\u001b[38;2;99;95;141;48;2;90;83;124m▇\u001b[38;2;85;73;107;48;2;94;85;125m┃\u001b[38;2;82;56;72;48;2;121;102;130m╹\u001b[38;2;170;129;124;48;2;194;148;138m▏\u001b[38;2;125;91;82;48;2;220;193;186m▘\u001b[38;2;198;176;170;48;2;145;83;80m▋\u001b[38;2;232;207;202;48;2;104;52;53m▗\u001b[38;2;234;208;202;48;2;148;102;99m▆\u001b[38;2;230;199;192;48;2;132;101;113m▄\u001b[38;2;132;111;138;48;2;220;176;169m▉\u001b[38;2;214;177;169;48;2;147;111;118m▅\u001b[38;2;235;205;199;48;2;143;100;98m▅\u001b[38;2;229;208;204;48;2;134;87;81m▇\u001b[38;2;215;180;178;48;2;112;34;37m▂\u001b[38;2;106;67;64;48;2;223;202;196m▘\u001b[38;2;237;209;204;48;2;139;95;88m▆\u001b[38;2;122;99;120;48;2;187;161;170m▝\u001b[38;2;142;146;195;48;2;98;89;129m▖\u001b[38;2;110;105;156;48;2;168;183;235m▎\u001b[38;2;134;130;157;48;2;161;173;211m▁\u001b[38;2;116;108;135;48;2;93;74;102m▏\u001b[38;2;100;81;114;48;2;100;80;113m╸\u001b[38;2;98;79;112;48;2;57;37;57m▉\u001b[38;2;49;16;39;48;2;62;28;51m▏\u001b[38;2;61;27;50;48;2;52;22;38m▉\u001b[38;2;48;20;33;48;2;56;27;44m╴\u001b[38;2;61;31;47;48;2;14;7;11m▘\u001b[48;2;0;0;0m \u001b[0m\n\u001b[38;2;0;0;0;48;2;0;0;0m \u001b[38;2;43;28;35;48;2;5;3;3m▝\u001b[38;2;57;39;48;48;2;54;34;45m╴\u001b[38;2;59;35;48;48;2;51;27;41m▉\u001b[38;2;35;10;26;48;2;100;83;110m▏\u001b[38;2;99;85;113;48;2;97;82;111m▍\u001b[38;2;85;68;93;48;2;93;76;104m╻\u001b[38;2;99;81;112;48;2;96;79;108m╵\u001b[38;2;83;66;91;48;2;96;86;123m╸\u001b[38;2;75;65;93;48;2;93;89;130m▖\u001b[38;2;101;100;149;48;2;100;98;147m┊\u001b[38;2;90;75;95;48;2;108;96;127m┕\u001b[38;2;101;77;88;48;2;213;184;173m▖\u001b[38;2;245;224;216;48;2;239;217;207m╵\u001b[38;2;245;223;214;48;2;243;222;212m▌\u001b[38;2;243;221;211;48;2;239;219;210m▃\u001b[38;2;243;220;211;48;2;241;219;210m╴\u001b[38;2;191;154;140;48;2;235;206;196m╻\u001b[38;2;243;219;210;48;2;243;220;212m▘\u001b[38;2;243;220;212;48;2;245;219;215m▇\u001b[38;2;243;219;211;48;2;245;219;213m▃\u001b[38;2;241;218;209;48;2;245;221;216m▁\u001b[38;2;166;140;133;48;2;241;218;212m▂\u001b[38;2;144;145;172;48;2;187;177;187m╶\u001b[38;2;144;154;201;48;2;171;186;231m▁\u001b[38;2;142;149;193;48;2;161;173;223m▁\u001b[38;2;87;73;103;48;2;157;159;182m▌\u001b[38;2;126;121;137;48;2;97;77;106m▘\u001b[38;2;100;80;112;48;2;88;66;94m▋\u001b[38;2;99;80;115;48;2;99;80;114m╹\u001b[38;2;97;77;109;48;2;50;22;46m▉\u001b[38;2;57;24;45;48;2;62;30;51m▏\u001b[38;2;14;6;9;48;2;56;29;45m▗\u001b[38;2;1;0;1;48;2;33;18;26m▇\u001b[48;2;0;0;0m \u001b[0m\n\u001b[7m\u001b[38;2;0;0;0m \u001b[0m\u001b[38;2;0;0;0;48;2;0;0;0m \u001b[48;2;2;2;2m╴\u001b[38;2;166;152;177;48;2;32;25;30m▗\u001b[38;2;160;149;172;48;2;10;5;6m▅\u001b[38;2;66;58;65;48;2;9;7;8m▏\u001b[38;2;37;27;32;48;2;1;0;0m▝\u001b[38;2;22;14;17;48;2;55;35;46m▂\u001b[38;2;98;83;109;48;2;56;36;51m▝\u001b[38;2;51;34;47;48;2;95;82;108m▁\u001b[38;2;67;55;70;48;2;91;76;102m▁\u001b[38;2;101;87;117;48;2;98;84;110m╴\u001b[38;2;100;85;114;48;2;97;82;109m╴\u001b[38;2;64;51;70;48;2;92;77;103m┖\u001b[38;2;94;81;111;48;2;74;65;89m▉\u001b[38;2;68;58;68;48;2;96;92;132m▂\u001b[38;2;116;100;123;48;2;110;81;85m▆\u001b[38;2;130;66;58;48;2;202;169;153m▂\u001b[38;2;219;188;169;48;2;240;218;205m▁\u001b[38;2;239;221;206;48;2;243;221;210m▂\u001b[38;2;244;222;212;48;2;240;218;207m╵\u001b[38;2;154;108;101;48;2;225;198;186m╹\u001b[38;2;227;203;193;48;2;238;215;205m╻\u001b[38;2;242;219;209;48;2;242;220;210m▄\u001b[38;2;241;218;207;48;2;242;219;210m▁\u001b[38;2;181;129;118;48;2;222;194;184m▁\u001b[38;2;145;74;66;48;2;166;132;133m▖\u001b[38;2;92;76;95;48;2;149;149;180m┎\u001b[38;2;61;53;72;48;2;123;122;169m▄\u001b[38;2;130;134;174;48;2;91;74;101m▘\u001b[38;2;87;69;85;48;2;98;79;106m╹\u001b[38;2;100;81;113;48;2;98;79;109m╵\u001b[38;2;69;49;65;48;2;95;75;103m▁\u001b[38;2;53;27;45;48;2;97;77;107m▁\u001b[38;2;99;78;109;48;2;59;32;51m▘\u001b[38;2;5;2;3;48;2;51;26;40m▗\u001b[38;2;0;0;0;48;2;3;1;2m╵\u001b[48;2;1;0;0m┊\u001b[38;2;128;108;125;48;2;2;0;1m▄\u001b[38;2;140;121;146;48;2;2;1;2m▃\u001b[38;2;0;0;0;48;2;4;3;4m╴\u001b[48;2;0;0;0m \u001b[0m\n\u001b[7m\u001b[38;2;0;0;0m \u001b[0m\u001b[38;2;0;0;0;48;2;2;1;2m╴\u001b[38;2;7;5;6;48;2;89;75;96m▘\u001b[38;2;100;88;115;48;2;115;104;128m▇\u001b[38;2;100;89;114;48;2;156;149;176m▅\u001b[38;2;141;128;144;48;2;25;17;20m▋\u001b[38;2;54;38;48;48;2;0;0;0m▂\u001b[38;2;56;39;51;48;2;1;0;0m▂\u001b[38;2;57;40;49;48;2;12;8;10m▂\u001b[38;2;57;37;50;48;2;18;12;15m▂\u001b[38;2;0;0;0;48;2;36;22;28m━\u001b[38;2;105;75;79;48;2;18;10;13m▁\u001b[38;2;192;174;183;48;2;27;17;21m▂\u001b[38;2;213;205;220;48;2;72;47;56m▃\u001b[38;2;188;176;181;48;2;64;19;20m▖\u001b[38;2;134;23;22;48;2;65;19;19m▆\u001b[38;2;92;57;57;48;2;177;164;175m▌\u001b[38;2;120;30;32;48;2;187;172;180m▝\u001b[38;2;193;163;176;48;2;132;53;49m▂\u001b[38;2;118;26;22;48;2;220;194;178m▄\u001b[38;2;166;93;81;48;2;235;215;200m▂\u001b[38;2;237;210;190;48;2;239;219;207m▁\u001b[38;2;239;220;211;48;2;236;216;203m╵\u001b[38;2;149;78;71;48;2;238;214;203m▃\u001b[38;2;113;12;12;48;2;201;156;154m╼\u001b[38;2;209;194;197;48;2;152;68;69m▄\u001b[38;2;224;216;213;48;2;184;144;144m▇\u001b[38;2;164;57;18;48;2;174;155;153m▆\u001b[38;2;234;70;2;48;2;142;55;30m╴\u001b[38;2;238;227;219;48;2;85;44;48m▂\u001b[38;2;240;230;232;48;2;44;26;29m▁\u001b[38;2;108;80;80;48;2;15;7;11m▁\u001b[38;2;10;4;6;48;2;42;22;32m▆\u001b[38;2;62;35;49;48;2;12;6;9m▁\u001b[38;2;64;37;51;48;2;7;3;4m▁\u001b[38;2;56;28;45;48;2;0;0;0m▁\u001b[38;2;29;10;20m▁\u001b[38;2;24;16;17;48;2;138;119;136m▍\u001b[38;2;89;70;94;48;2;158;142;171m▗\u001b[38;2;100;82;110;48;2;135;120;145m▆\u001b[38;2;12;7;11;48;2;94;75;101m▝\u001b[38;2;0;0;0;48;2;3;1;2m┊\u001b[0m\n\u001b[38;2;0;0;0;48;2;1;1;1m╴\u001b[38;2;6;5;6;48;2;87;76;97m▘\u001b[38;2;99;90;117;48;2;101;90;117m▇\u001b[38;2;99;89;117;48;2;99;89;115m▎\u001b[38;2;59;45;58;48;2;97;84;109m▗\u001b[38;2;52;37;47;48;2;56;39;50m▏\u001b[38;2;47;30;40;48;2;57;39;48m╴\u001b[38;2;211;205;201;48;2;62;42;52m▂\u001b[38;2;222;224;219;48;2;58;38;47m▄\u001b[38;2;235;241;236;48;2;77;52;60m▅\u001b[38;2;238;246;239;48;2;122;97;100m▆\u001b[38;2;239;244;236;48;2;212;195;194m▇\u001b[38;2;141;98;78;48;2;229;233;227m▃\u001b[38;2;181;62;13;48;2;170;162;160m▅\u001b[38;2;186;57;10;48;2;87;23;20m▆\u001b[38;2;200;183;178;48;2;122;34;30m▃\u001b[38;2;176;167;175;48;2;204;198;210m╶\u001b[38;2;109;95;97;48;2;197;193;205m▝\u001b[38;2;123;113;115;48;2;200;194;208m▘\u001b[38;2;213;209;228;48;2;163;101;111m▇\u001b[38;2;207;196;214;48;2;119;16;19m▆\u001b[38;2;120;32;34;48;2;195;151;128m▇\u001b[38;2;186;157;169;48;2;140;58;58m▅\u001b[38;2;220;215;227;48;2;172;102;110m▇\u001b[38;2;84;65;58;48;2;217;211;214m╺\u001b[38;2;119;97;91;48;2;204;198;190m┛\u001b[38;2;206;199;191;48;2;231;229;223m╸\u001b[38;2;114;45;24;48;2;216;212;208m▝\u001b[38;2;144;116;109;48;2;177;57;11m▖\u001b[38;2;197;72;22;48;2;208;194;182m▆\u001b[38;2;160;70;38;48;2;230;227;217m▃\u001b[38;2;183;168;151;48;2;239;236;229m▁\u001b[38;2;241;241;237;48;2;126;101;108m▆\u001b[38;2;236;235;231;48;2;79;50;61m▅\u001b[38;2;239;239;236;48;2;77;50;62m▃\u001b[38;2;212;202;203;48;2;64;37;50m▂\u001b[38;2;49;24;35;48;2;66;39;53m╴\u001b[38;2;62;36;51;48;2;93;74;86m▇\u001b[38;2;69;45;61;48;2;111;92;113m▅\u001b[38;2;100;81;112;48;2;100;82;111m▋\u001b[38;2;101;83;115m▝\u001b[38;2;6;4;6;48;2;84;68;89m▝\u001b[0m\n\u001b[38;2;5;3;5;48;2;82;74;92m▘\u001b[38;2;100;91;116;48;2;99;91;116m╴\u001b[38;2;99;90;117;48;2;97;88;112m╵\u001b[38;2;59;47;57;48;2;94;85;107m▃\u001b[38;2;56;39;49;48;2;65;48;57m╵\u001b[38;2;220;221;210;48;2;87;65;72m▄\u001b[38;2;236;244;235;48;2;159;142;141m▇\u001b[38;2;238;248;243;48;2;237;248;240m╍\u001b[38;2;238;248;242m╸\u001b[38;2;235;248;239;48;2;235;246;236m╵\u001b[38;2;122;70;48;48;2;221;228;215m▃\u001b[38;2;186;63;14;48;2;170;159;145m▆\u001b[38;2;125;39;11;48;2;218;69;4m▗\u001b[38;2;227;222;210;48;2;184;78;35m▃\u001b[38;2;236;241;232;48;2;161;118;106m▆\u001b[38;2;238;248;240;48;2;237;247;238m┊\u001b[38;2;225;229;233;48;2;236;245;238m▝\u001b[38;2;224;228;233;48;2;193;190;204m▇\u001b[38;2;184;182;192;48;2;210;210;222m╺\u001b[38;2;185;185;194;48;2;205;206;221m┐\u001b[38;2;209;211;231;48;2;212;214;228m╴\u001b[38;2;236;241;238;48;2;217;215;230m▃\u001b[38;2;236;241;236;48;2;222;221;230m▆\u001b[38;2;221;221;211;48;2;230;233;225m╷\u001b[38;2;196;193;184;48;2;231;234;228m╴\u001b[38;2;233;238;233;48;2;217;219;211m▇\u001b[38;2;236;242;235;48;2;237;243;237m╲\u001b[38;2;236;244;237;48;2;238;243;239m▃\u001b[38;2;239;244;240;48;2;233;236;231m╴\u001b[38;2;226;226;220;48;2;137;73;53m▄\u001b[38;2;96;25;7;48;2;193;69;20m╴\u001b[38;2;212;64;3;48;2;132;86;72m▇\u001b[38;2;181;73;30;48;2;234;229;216m▆\u001b[38;2;116;71;57;48;2;240;239;228m▖\u001b[38;2;239;241;237;48;2;241;245;240m▃\u001b[38;2;242;245;241;48;2;241;244;239m╺\u001b[38;2;241;243;239;48;2;196;182;182m▇\u001b[38;2;232;231;228;48;2;108;83;92m▅\u001b[38;2;164;147;148;48;2;65;39;51m▖\u001b[38;2;67;45;59;48;2;92;72;95m▃\u001b[38;2;101;80;110;48;2;98;79;106m╴\u001b[38;2;101;82;112;48;2;104;85;113m▉\u001b[0m\n\u001b[38;2;65;63;77;48;2;99;92;116m▏\u001b[38;2;55;42;50;48;2;90;82;101m▗\u001b[38;2;51;37;46;48;2;69;59;72m▇\u001b[38;2;53;36;45;48;2;61;45;52m┊\u001b[38;2;110;95;90;48;2;213;206;194m▚\u001b[38;2;187;185;173;48;2;236;244;232m▏\u001b[38;2;237;249;240;48;2;236;249;237m▝\u001b[38;2;236;249;238;48;2;237;248;241m▃\u001b[38;2;236;247;235;48;2;235;247;236m┊\u001b[38;2;199;200;187;48;2;115;70;51m▌\u001b[38;2;157;47;7;48;2;224;70;3m▏\u001b[38;2;233;73;0;48;2;230;73;2m╵\u001b[38;2;117;45;25;48;2;204;192;180m▋\u001b[48;2;236;246;236m \u001b[38;2;238;246;239;48;2;236;247;239m▝\u001b[38;2;233;247;235;48;2;236;247;238m▂\u001b[38;2;235;246;236;48;2;236;246;238m▅\u001b[38;2;234;245;234;48;2;235;246;237m╍\u001b[38;2;234;246;234;48;2;235;245;237m▅\u001b[38;2;234;244;235;48;2;229;233;232m▇\u001b[38;2;209;208;194;48;2;226;230;219m╵\u001b[38;2;195;194;179;48;2;233;239;228m─\u001b[38;2;204;202;187;48;2;230;234;223m╸\u001b[38;2;236;244;239;48;2;235;241;233m╴\u001b[38;2;235;243;234;48;2;235;243;236m▍\u001b[38;2;235;244;232m▅\u001b[38;2;235;243;233;48;2;237;243;236m▅\u001b[38;2;235;242;236m▅\u001b[38;2;236;242;234;48;2;238;243;237m▃\u001b[38;2;237;242;232;48;2;238;243;236m▅\u001b[38;2;229;224;215;48;2;129;45;20m▍\u001b[38;2;235;72;1;48;2;234;72;0m╴\u001b[38;2;236;71;0m╵\u001b[38;2;152;47;13;48;2;172;151;134m▊\u001b[38;2;237;240;235;48;2;237;236;228m╵\u001b[38;2;234;237;231;48;2;237;240;235m▃\u001b[38;2;232;235;231;48;2;236;239;235m▂\u001b[38;2;230;233;228;48;2;236;238;232m▃\u001b[38;2;122;101;93;48;2;210;204;196m▄\u001b[38;2;208;198;185;48;2;76;50;59m▖\u001b[38;2;61;36;50;48;2;75;54;71m▇\u001b[38;2;70;49;66;48;2;101;82;111m▖\u001b[0m\n\u001b[38;2;58;46;54;48;2;90;84;100m▗\u001b[38;2;41;27;33;48;2;53;40;46m┎\u001b[38;2;58;44;51;48;2;60;45;52m╴\u001b[38;2;236;223;202;48;2;104;87;79m╱\u001b[38;2;202;195;183;48;2;97;84;76m▗\u001b[38;2;234;246;233;48;2;232;241;228m╵\u001b[38;2;233;244;231;48;2;234;247;236m▄\u001b[38;2;132;139;141;48;2;224;236;224m▁\u001b[38;2;77;78;109;48;2;217;225;213m▅\u001b[38;2;52;53;100;48;2;95;43;29m▆\u001b[38;2;57;50;94;48;2;209;72;6m▆\u001b[38;2;55;52;98;48;2;168;55;9m▅\u001b[38;2;229;237;223;48;2;83;72;91m▝\u001b[38;2;242;243;231;48;2;234;246;234m▏\u001b[38;2;236;247;236;48;2;234;248;235m▘\u001b[38;2;234;248;234;48;2;234;247;234m╍\u001b[38;2;234;247;234;48;2;234;245;235m▄\u001b[38;2;234;244;234;48;2;234;246;235m┷\u001b[38;2;232;243;233;48;2;234;245;234m┬\u001b[38;2;233;243;233;48;2;241;248;236m▉\u001b[38;2;216;213;200;48;2;150;130;113m▏\u001b[38;2;219;207;188;48;2;143;120;104m┠\u001b[38;2;148;124;108;48;2;191;187;165m▇\u001b[38;2;229;217;200;48;2;235;242;231m▏\u001b[38;2;233;243;233;48;2;234;242;235m▌\u001b[38;2;233;241;232;48;2;234;242;233m┊\u001b[38;2;234;243;232m┳\u001b[38;2;233;241;233;48;2;234;241;235m▆\u001b[38;2;236;241;233;48;2;234;242;233m╴\u001b[38;2;234;241;234;48;2;236;241;232m▂\u001b[38;2;226;225;216;48;2;77;44;62m▌\u001b[38;2;71;48;87;48;2;224;75;7m▅\u001b[38;2;61;49;95;48;2;206;67;6m▅\u001b[38;2;62;49;97;48;2;154;50;15m▆\u001b[38;2;70;62;103;48;2;199;197;189m▆\u001b[38;2;79;72;103;48;2;216;218;210m▃\u001b[38;2;156;154;153;48;2;224;227;219m▁\u001b[38;2;219;223;213;48;2;225;229;221m▅\u001b[38;2;196;193;187;48;2;124;108;107m▌\u001b[38;2;174;156;142;48;2;87;67;63m▝\u001b[38;2;160;143;127;48;2;67;42;51m▖\u001b[38;2;79;58;76;48;2;56;33;46m▝\u001b[0m\n\u001b[38;2;75;67;76;48;2;47;38;41m▘\u001b[38;2;35;34;34;48;2;58;43;48m▂\u001b[38;2;80;66;68;48;2;175;168;153m▊\u001b[38;2;193;182;167;48;2;111;103;90m╶\u001b[38;2;60;62;59;48;2;223;230;212m▂\u001b[38;2;57;59;55;48;2;231;244;226m▂\u001b[38;2;52;51;65;48;2;190;196;189m▄\u001b[38;2;56;54;100;48;2;54;55;84m╴\u001b[38;2;34;34;38;48;2;51;55;101m▂\u001b[38;2;33;33;36;48;2;47;50;93m▂\u001b[38;2;49;52;94;48;2;20;21;36m▝\u001b[38;2;34;34;37;48;2;53;55;99m▂\u001b[38;2;56;50;76;48;2;170;159;151m▊\u001b[38;2;60;62;60;48;2;236;248;234m▂\u001b[38;2;60;63;60;48;2;236;249;236m▂\u001b[48;2;236;249;235m▂\u001b[48;2;235;248;235m▂\u001b[38;2;60;63;61;48;2;235;248;236m▂\u001b[38;2;60;62;60;48;2;235;248;235m▂\u001b[48;2;235;247;235m▂\u001b[38;2;60;62;61;48;2;236;247;236m▂\u001b[48;2;236;247;235m▂\u001b[38;2;60;62;60;48;2;237;244;235m▂\u001b[38;2;61;62;61;48;2;236;245;236m▂\u001b[38;2;60;62;60;48;2;236;244;237m▂\u001b[48;2;236;244;234m▂▂\u001b[48;2;235;244;236m▂\u001b[48;2;235;244;234m▂\u001b[38;2;60;61;60;48;2;235;243;235m▂\u001b[38;2;188;188;183;48;2;62;49;78m▌\u001b[38;2;35;34;39;48;2;59;51;104m▂\u001b[38;2;18;17;26;48;2;44;41;80m▗\u001b[38;2;21;19;31;48;2;48;45;84m▖\u001b[38;2;34;34;39;48;2;57;50;101m▂\u001b[38;2;34;34;38;48;2;56;48;95m▂\u001b[38;2;52;47;92;48;2;58;52;79m╴\u001b[38;2;45;44;44;48;2;175;172;168m▂\u001b[38;2;56;56;55;48;2;215;217;205m▂\u001b[38;2;206;203;196;48;2;120;113;108m╾\u001b[38;2;95;76;69;48;2;158;147;133m▋\u001b[38;2;145;123;118;48;2;68;50;55m╴\u001b[0m", "type": "data-raw" }, "display": { "separator": "▌", "key": { "width": 18 }, "bar": { "border": null, "char": { "elapsed": "█", "total": "░" }, "width": 35 } }, "modules": [ { "type": "custom", "format": "{#@196}╔════════════════════════════════════════════════════════╗" }, { "type": "custom", "format": "{#@196}║ {#@46}███╗ ██╗███████╗██████╗ ██╗ ██╗ {#@220}EVA-00 SYSTEM {#@196}║" }, { "type": "custom", "format": "{#@196}║ {#@46}████╗ ██║██╔════╝██╔══██╗██║ ██║ {#@220}MAGI INTERFACE {#@196} ║" }, { "type": "custom", "format": "{#@196}║ {#@46}██╔██╗ ██║█████╗ ██████╔╝██║ ██║ {#@196}║" }, { "type": "custom", "format": "{#@196}║ {#@46}██║╚██╗██║██╔══╝ ██╔══██╗╚██╗ ██╔╝ {#@208}CLASSIFIED {#@196} ║" }, { "type": "custom", "format": "{#@196}║ {#@46}██║ ╚████║███████╗██║ ██║ ╚████╔╝ {#@208}ACCESS ONLY {#@196} ║" }, { "type": "custom", "format": "{#@196}║ {#@46}╚═╝ ╚═══╝╚══════╝╚═╝ ╚═╝ ╚═══╝ {#@196} ║" }, { "type": "custom", "format": "{#@196}╚════════════════════════════════════════════════════════╝" }, { "type": "custom", "format": "{#@208}╔══●{#@166}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━●══╗" }, { "type": "custom", "format": "{#@208}║ {#@46}00101{#@208} ●{#@166}────{#@208}● NERVE CONNECTION INTERFACE ●{#@166}────{#@208}● {#@46}00102{#@196} ║" }, { "type": "custom", "format": "{#@208}╚══●{#@166}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━●══╝" }, { "type": "custom", "format": "{#@208}▰{#@166}▰{#@208}▰{#@166}▰{#@208} BORDER LINE {#@166}▰{#@208}▰{#@166}▰{#@208}▰ {#@46}[{#@220}SYSTEM CONFIG{#@46}]" }, { "type": "os", "key": "{#@46}│ OS " }, { "type": "kernel", "key": "{#@46}│ KERNEL " }, { "type": "uptime", "key": "{#@46}│ UPTIME " }, { "type": "custom", "format": "{#@208}▰{#@166}▰{#@208}▰{#@166}▰{#@208}▰ ABSOLUTE {#@166}▰{#@208}▰{#@166}▰{#@208}▰{#@166}▰ {#@196}[{#@220}FIRST CHILD{#@196}] {#@51}REI AYANAMI" }, { "type": "users", "key": "{#@46}│ DESIGNATION ", "format": "{name}, since {login-time}", "myselfOnly": true }, { "type": "localip", "key": "{#@46}│ NEURAL-LINK ", "format": "{ipv4}" }, { "type": "custom", "format": "{#@196}▰{#@208}▰{#@196}▰{#@208}▰{#@196}▰ DANGER {#@208}▰{#@196}▰{#@208}▰{#@196}▰{#@208}▰ {#@220}[{#@196}UNIT-00{#@220}] {#@51}CORE STATUS" }, { "type": "cpu", "key": "{#@220}│ CPU-CORE ", "format": "{name}" }, { "type": "cpu", "key": "{#@220}│ SYNC-RATE ", "format": "{cores-physical}C / {cores-logical}T" }, { "type": "custom", "format": "{#@196}▰{#@208}▰{#@196}▰{#@208}▰{#@196} WARNING {#@208}▰{#@196}▰{#@208}▰{#@196}▰ {#@220}[{#@51}LCL{#@220}] {#@208}[{#@51}A.T. FIELD{#@208}]" }, { "type": "memory", "key": "{#@208}{#@166}{#@208}{#@220}│ LCL-LEVEL ", "format": "{used} / {total} (RAM)" }, { "type": "memory", "key": "{#@208}{#@166}{#@208}{#@220}│ A.T.FIELD ", "percent": { "type": [ "bar", "hide-others" ] } }, { "type": "disk", "folders": "/", "key": "{#@208}{#@166}{#@208}{#@220}│ ENTRY-PLUG ", "format": "{size-used} / {size-total} (DISK)" }, { "type": "disk", "folders": "/", "key": "{#@208}{#@166}{#@208}{#@220}│ PLUG-DEPTH ", "percent": { "type": [ "bar", "hide-others" ] } }, { "type": "custom", "format": "{#@196}╔═════════════════════════════════════════════════════════╗" }, { "type": "custom", "format": "{#@196}║ {#@46}[SYSTEMS NOMINAL] {#@208}MAGI: {#@46} ONLINE {#@208} STATUS: {#@46} OK {#@196} ║" }, { "type": "custom", "format": "{#@196}╚═════════════════════════════════════════════════════════╝" }, { "type": "custom", "format": "{#@46} PILOT REI AYANAMI - READY FOR DEPLOYMENT" }, { "type": "custom", "format": "{#@208} 綾波レイ (I am not a doll. I am me.)" } ] } ================================================ FILE: presets/examples/32.jsonc ================================================ // Inspired by microfetch { "$schema": "https://github.com/fastfetch-cli/fastfetch/raw/dev/doc/json_schema.json", "logo": { "type": "small" }, "general": { "detectVersion": false }, "display": { "separator": "  ", "brightColor": false, "key": { "type": "both-2" } }, "modules": [ { "type": "title", "format": "{user-name-colored}{#light_red}@{host-name-colored} {#}{cwd}" }, { "type": "os", "key": "System " }, { "type": "kernel", "key": "Kernel " }, { "type": "shell", "key": "Shell " }, { "type": "uptime", "key": "Uptime " }, { "type": "wm", "key": "Desktop " }, { "type": "memory", "key": "Memory " }, { "type": "disk", "key": "Storage (/) ", "folders": "/" }, { "type": "colors", "key": "Colors ", "symbol": "circle" } ] } ================================================ FILE: presets/examples/4.jsonc ================================================ // Load with --config examples/4.jsonc { "$schema": "https://github.com/fastfetch-cli/fastfetch/raw/dev/doc/json_schema.json", "logo": { "type": "small", "padding": { "right": 1 } }, "display": { "size": { "binaryPrefix": "si" }, "color": "blue", "separator": "  " }, "modules": [ { "type": "datetime", "key": "Date", "format": "{1}-{3}-{11}" }, { "type": "datetime", "key": "Time", "format": "{14}:{17}:{20}" }, "break", "player", "media" ] } ================================================ FILE: presets/examples/5.jsonc ================================================ // Load with --config examples/5.jsonc { "$schema": "https://github.com/fastfetch-cli/fastfetch/raw/dev/doc/json_schema.json", "logo": null, "display": { "color": "magenta" }, "modules": [ { "type": "theme", "key": "T" }, { "type": "icons", "key": "I" }, { "type": "font", "key": "F" }, { "type": "cursor", "key": "C" } ] } ================================================ FILE: presets/examples/6.jsonc ================================================ // Load with --config examples/2.jsonc // Note that you must replace the image path to an existing image to display it. { "$schema": "https://github.com/fastfetch-cli/fastfetch/raw/dev/doc/json_schema.json", "display": { "separator": " " }, "modules": [ { "type": "host", "key": "╭─󰌢", "keyColor": "green" }, { "type": "cpu", "key": "├─󰻠", "keyColor": "green" }, { "type": "gpu", "key": "├─󰍛", "keyColor": "green" }, { "type": "disk", "key": "├─", "keyColor": "green" }, { "type": "memory", "key": "├─󰑭", "keyColor": "green" }, { "type": "swap", "key": "├─󰓡", "keyColor": "green" }, { "type": "display", "key": "├─󰍹", "keyColor": "green" }, { "type": "brightness", "key": "├─󰃞", "keyColor": "green" }, { "type": "battery", "key": "├─", "keyColor": "green" }, { "type": "poweradapter", "key": "├─", "keyColor": "green" }, { "type": "gamepad", "key": "├─", "keyColor": "green" }, { "type": "bluetooth", "key": "├─", "keyColor": "green" }, { "type": "sound", "key": "╰─", "keyColor": "green" }, "break", { "type": "shell", "key": "╭─", "keyColor": "yellow" }, { "type": "terminal", "key": "├─", "keyColor": "yellow" }, { "type": "terminalfont", "key": "├─", "keyColor": "yellow" }, { "type": "lm", "key": "├─󰧨", "keyColor": "yellow" }, { "type": "de", "key": "├─", "keyColor": "yellow" }, { "type": "wm", "key": "├─", "keyColor": "yellow" }, { "type": "theme", "key": "├─󰉼", "keyColor": "yellow" }, { "type": "icons", "key": "├─󰀻", "keyColor": "yellow" }, { "type": "wallpaper", "key": "╰─󰸉", "keyColor": "yellow" }, "break", { "type": "title", "key": "╭─", "format": "{user-name}@{host-name}", "keyColor": "blue" }, { "type": "os", "key": "├─{icon}", // Just get your distro's logo off nerdfonts.com "keyColor": "blue" }, { "type": "kernel", "key": "├─", "keyColor": "blue" }, { "type": "packages", "key": "├─󰏖", "keyColor": "blue" }, { "type": "uptime", "key": "├─󰅐", "keyColor": "blue" }, { "type": "media", "key": "├─󰝚", "keyColor": "blue" }, { "type": "localip", "key": "├─󰩟", "compact": true, "keyColor": "blue" }, { "type": "publicip", "key": "├─󰩠", "keyColor": "blue", "timeout": 1000 }, { "type": "wifi", "key": "├─", "format": "{ssid}", "keyColor": "blue" }, { "type": "locale", "key": "╰─", "keyColor": "blue" } ] } ================================================ FILE: presets/examples/7.jsonc ================================================ // Load with --config examples/2.jsonc // Note that you must replace the image path to an existing image to display it. { "$schema": "https://github.com/fastfetch-cli/fastfetch/raw/dev/doc/json_schema.json", "logo": { "padding": { "top": 2 } }, "display": { "separator": " -> " }, "modules": [ "title", "separator", { "type": "os", "key": " OS", "keyColor": "yellow", "format": "{2}" }, { "type": "os", "key": "├{icon}", // Just get your distro's logo off nerdfonts.com "keyColor": "yellow" }, { "type": "kernel", "key": "├", "keyColor": "yellow" }, { "type": "packages", "key": "├󰏖", "keyColor": "yellow" }, { "type": "shell", "key": "└", "keyColor": "yellow" }, "break", { "type": "wm", "key": " DE/WM", "keyColor": "blue" }, { "type": "lm", "key": "├󰧨", "keyColor": "blue" }, { "type": "wmtheme", "key": "├󰉼", "keyColor": "blue" }, { "type": "icons", "key": "├󰀻", "keyColor": "blue" }, { "type": "terminal", "key": "├", "keyColor": "blue" }, { "type": "wallpaper", "key": "└󰸉", "keyColor": "blue" }, "break", { "type": "host", "key": "󰌢 PC", "keyColor": "green" }, { "type": "cpu", "key": "├󰻠", "keyColor": "green" }, { "type": "gpu", "key": "├󰍛", "keyColor": "green" }, { "type": "disk", "key": "├", "keyColor": "green" }, { "type": "memory", "key": "├󰑭", "keyColor": "green" }, { "type": "swap", "key": "├󰓡", "keyColor": "green" }, { "type": "display", "key": "├󰍹", "keyColor": "green" }, { "type": "uptime", "key": "└󰅐", "keyColor": "green" }, "break", { "type": "sound", "key": " SOUND", "keyColor": "cyan" }, { "type": "player", "key": "├󰥠", "keyColor": "cyan" }, { "type": "media", "key": "└󰝚", "keyColor": "cyan" }, "break", "colors" ] } ================================================ FILE: presets/examples/8.jsonc ================================================ { "$schema": "https://github.com/fastfetch-cli/fastfetch/raw/dev/doc/json_schema.json", "logo": { "type": "small" }, "display": { "separator": " ", "color": { "keys": "magenta" }, "size": { "ndigits": 0, "maxPrefix": "MB" }, "key": { "type": "icon" } }, "modules": [ { "type": "title", "color": { "user": "green", "at": "red", "host": "blue" } }, "os", "kernel", "memory", "packages", "uptime", { "type": "colors", "key": "Colors", // For printing icon "block": { "range": [1, 6] } } ] } ================================================ FILE: presets/examples/9.jsonc ================================================ { "$schema": "https://github.com/fastfetch-cli/fastfetch/raw/dev/doc/json_schema.json", "logo": { "type": "small" }, "display": { "key": { "width": 11 }, "bar": { "char": { "elapsed": "=", "total": "-" }, "width": 13 }, "percent": { "type": 2 } }, "modules": [ "title", "separator", "memory", "swap", { "type": "disk", "folders": "/" }, { "type": "battery", "key": "Battery" }, { "type": "colors", "paddingLeft": 10, "symbol": "circle" } ] } ================================================ FILE: presets/neofetch.jsonc ================================================ { "$schema": "https://github.com/fastfetch-cli/fastfetch/raw/dev/doc/json_schema.json", "display": { "size": { "maxPrefix": "MB", "ndigits": 0, "spaceBeforeUnit": "never" }, "freq": { "ndigits": 3, "spaceBeforeUnit": "never" } }, "modules": [ "title", "separator", "os", "host", { "type": "kernel", "format": "{release}" }, "uptime", { "type": "packages", "combined": true }, "shell", { "type": "display", "compactType": "original", "key": "Resolution" }, "de", "wm", "wmtheme", "theme", "icons", "terminal", { "type": "terminalfont", "format": "{/name}{-}{/}{name}{?size} {size}{?}" }, "cpu", { "type": "gpu", "key": "GPU", "format": "{name}" }, { "type": "memory", "format": "{used} / {total}" }, "break", "colors" ] } ================================================ FILE: presets/paleofetch.jsonc ================================================ { "$schema": "https://github.com/fastfetch-cli/fastfetch/raw/dev/doc/json_schema.json", "modules": [ "title", "separator", "os", { "type": "host", "format": "{/2}{-}{/}{2}{?3} {3}{?}" }, "kernel", "uptime", { "type": "battery", "format": "{/4}{-}{/}{4}{?5} [{5}]{?}" }, "break", "packages", "shell", "display", "terminal", "break", "cpu", { "type": "gpu", "key": "GPU" }, "memory", "break", "colors" ] } ================================================ FILE: presets/screenfetch.jsonc ================================================ { "$schema": "https://github.com/fastfetch-cli/fastfetch/raw/dev/doc/json_schema.json", "modules": [ "title", "os", "kernel", "uptime", { "type": "packages", "format": "{all}" }, "shell", { "type": "display", "key": "Resolution", "compactType": "original" }, "de", "wm", "wmtheme", { "type": "terminalfont", "key": "font" }, { "type": "disk", "folders": "/", "key": "Disk" }, "cpu", "gpu", { "type": "memory", "key": "RAM" } ] } ================================================ FILE: run.sh ================================================ #!/usr/bin/env sh set -e mkdir -p build/ cd build/ cmake .. kernel_name="$(uname -s)" case "${kernel_name}" in "Linux" | "MINGW"*) cmake_build_args="-j$(nproc)" ;; "Darwin" | *"BSD" | "DragonFly") cmake_build_args="-j$(sysctl -n hw.ncpu)" ;; *) cmake_build_args="" ;; esac cmake --build . --target fastfetch "${cmake_build_args}" ./fastfetch "$@" ================================================ FILE: scripts/gen-amdgpuids.py ================================================ #!/usr/bin/env python3 import sys def main(amdgpu_ids_path: str): with open(amdgpu_ids_path, 'r') as f: full_text = f.read() if full_text == '': sys.exit('Error: pci.ids file is empty') products = [] for line in full_text.split('\n'): if not line or line[0] == '#' or not ',\t' in line: continue device, revision, name = line.split(',\t', maxsplit=2) products.append((device, revision, name)) code = """\ // SPDX-License-Identifier: MIT // https://opensource.org/license/mit // Generated from https://gitlab.freedesktop.org/mesa/drm/-/raw/main/data/amdgpu.ids #include #include typedef struct FFArmGpuProduct { const uint32_t id; // device << 8 | revision const char* name; } FFArmGpuProduct; const FFArmGpuProduct ffAmdGpuProducts[] = { """ for device, revision, name in products: code += f" {{ 0x{device} << 8 | 0x{revision}, \"{name}\" }},\n" code += "};\n" print(code) if __name__ == '__main__': len(sys.argv) == 2 or sys.exit('Usage: gen-amdgpuids.py ') main(sys.argv[1]) ================================================ FILE: scripts/gen-man.py ================================================ #!/usr/bin/env python3 """ Python script to generate a comprehensive man page for the command `fastfetch`. The generated man page content will be printed to stdout, so you will need to pipe it to a file if you want to save it. Example: python3 gen-man.py > fastfetch.1 The command options are generated using a JSON file. For the JSON file format, see: https://github.com/fastfetch-cli/fastfetch/blob/dev/src/data/help.json """ from json import load from datetime import datetime, timezone from time import time from re import search from os import environ, path ###### Text Decorations Tags ###### startUnderline = r"\fI" # start underline text tag endUnderline = r"\fR" # end underline text tag startBold = r"\fB" # start bold text tag endBold = r"\fR" # end bold text tag ###### Parameters ###### # path to the current directory pathToCurrentDir = path.dirname(__file__) # path to the JSON option file pathToHelpFile = path.join(pathToCurrentDir, "../src/data/help.json") # man page section manSection = 1 # title (center header) titlePage = "FASTFETCH" # date (center footer) # format : "Month (abbreviation) Day Year" todayDate = datetime.fromtimestamp( int(environ.get("SOURCE_DATE_EPOCH", time())), tz=timezone.utc, ).strftime("%b %d %Y") # file to fastfetch version (left footer) pathToVersionFile = path.join(pathToCurrentDir, "../CMakeLists.txt") ###### Sections Text ###### # text displayed in the "NAME" section nameSection = r"fastfetch \- A fast and feature-rich system information tool similar to neofetch" # text displayed in the "DESCRIPTION" section descriptionSection = r""" Fastfetch is a tool for displaying system information in a visually appealing way. Written primarily in C, it focuses on performance and customizability while providing functionality similar to neofetch. It supports Linux, Android, FreeBSD, macOS, and Windows 7 or newer. """ # text displayed at the beginning of the "OPTIONS" section optionSection = r""" Options are parsed in a case-insensitive manner. For example, \fB--logo-type\fR and \fB--LOGO-TYPE\fR are treated identically. Arguments in square brackets are optional. Optional boolean arguments default to 'true' when specified without a value. For more detailed information about a specific option, use: \fBfastfetch -h \fR Any combination of options can be made permanent by generating a configuration file: \fBfastfetch --gen-config\fR """ # text displayed in the "CONFIGURATION" configurationSection = f""" .SS Fetch Structure The structure defines which modules to display and in what order. It consists of module names separated by colons (:). For example: {startBold}title:separator:os:kernel:uptime{endBold} To list all available modules, use {startBold}--list-modules{endBold} .SS Config Files Fastfetch uses JSONC (JSON with Comments) for configuration files. These files must have the .jsonc extension. You can generate a default config file using {startBold}--gen-config{endBold}. By default, the config file is saved at {startBold}~/.config/fastfetch/config.jsonc{endBold}. The configuration/preset files are searched in the following locations (in order): {startBold}1.{endBold} Relative to the current working directory {startBold}2.{endBold} Relative to ~/.local/share/fastfetch/presets/ {startBold}3.{endBold} Relative to /usr/share/fastfetch/presets/ For detailed information on logo options, module configuration, and formatting, visit: {startBold}https://github.com/fastfetch-cli/fastfetch/wiki/Configuration{endBold} Fastfetch provides several built-in presets. List them with {startBold}--list-presets{endBold}. .SS JSON Schema A JSON schema is available for editor intelligence when editing the configuration file. Add the following line at the beginning of your config file: {startBold}"$schema": "https://github.com/fastfetch-cli/fastfetch/raw/dev/doc/json_schema.json"{endBold} """ # text displayed in the "EXAMPLE" section exampleSection = f""" .SS Basic Usage {startBold}fastfetch{endBold} .SS Use a specific logo {startBold}fastfetch --logo arch{endBold} .SS Custom structure {startBold}fastfetch --structure title:os:kernel:uptime:memory{endBold} .SS Generate a config file {startBold}fastfetch --gen-config{endBold} .SS Use a preset {startBold}fastfetch --config neofetch{endBold} .SS Config File Example .nf // ~/.config/fastfetch/config.jsonc {{ "$schema": "https://github.com/fastfetch-cli/fastfetch/raw/dev/doc/json_schema.json", "logo": {{ "type": "auto", "source": "arch" }}, "display": {{ "separator": ": ", "color": {{ "keys": "blue", "title": "red" }}, "key": {{ "width": 12 }} }}, "modules": [ "title", "separator", "os", "kernel", "uptime", {{ "type": "memory", "format": "{{used}}/{{total}} ({{used_percent}}%)" }} ] }} .fi """ # text displayed in the "BUGS" section bugSection = "Please report bugs to: https://github.com/fastfetch-cli/fastfetch/issues" # text displayed in the "AUTHORS" section authorsSection = "Fastfetch is developed by a team of contributors on GitHub.\nVisit https://github.com/fastfetch-cli/fastfetch for more information." ###### Argument decoration ###### ### optional arguments tags ### # if an optional argument is displayed as [?optArg] (with "optArg" underlined) # this value should be f"[?{startUnderline}" startOptionalArgument = f"[{startUnderline}" # if an optional argument is displayed as [?optArg] (with "optArg underlined") # this value should be f"{endUnderline}]" endOptionalArgument = f"{endUnderline}]" ### mandatory arguments tags ### startMandatoryArgument = f"{startUnderline}" endMandatoryArgument = f"{endUnderline}" def main(): # importing the JSON file with open(pathToHelpFile, 'r') as jsonFile: helpFileData = load(jsonFile) # json.load ######## Start printing the generated .1 file ######## ###### header, footer & config ##### print(f".TH FASTFETCH {manSection} ", end=" ") print(f"\"{todayDate}\"", end=" ") # version number with open(pathToVersionFile, 'r') as versionFile: # research version number in file with regex for line in versionFile: researchVersion = search(r"^\s*VERSION (\d+\.\d+\.\d+)$", line) if (researchVersion): print(f"\"Fastfetch {researchVersion.group(1)}\"", end=" ") break print(f"\"{titlePage}\"") ###### Name ###### print(".SH NAME") print(nameSection) ##### Synopsis ###### print(".SH SYNOPSIS") print(".B fastfetch") print(f"[{startUnderline}OPTIONS{endUnderline}...]") ###### Description ###### print(".SH DESCRIPTION") print(descriptionSection) ###### Configuration ###### print(".SH CONFIGURATION") print(configurationSection) ###### Options ###### print(".SH OPTIONS") print(optionSection) print() # loop through every options sections for key, value in helpFileData.items(): # print new subsection print(f".SS {key}") # loop through every option in a section for option in value: # list of existing keys for this option keyList = option.keys() # start a new "option" entry print(".TP") print(startBold, end="") # short option (-opt) if "short" in keyList: print(fr"\-{ option['short'] }", end="") # if also have a long option, print a comma if "long" in keyList: print(", ", end="") # long option (--option) if "long" in keyList: print(fr"\-\-{ option['long'] }", end="") print(endBold, end=" ") # arguments if "arg" in keyList: # if argument is optional, print "[arg]" if "optional" in option["arg"].keys() and option["arg"]["optional"]: print(startOptionalArgument + option['arg']['type'] + endOptionalArgument, end="") # if argument is mandatory, print "arg" else: print(startMandatoryArgument + option['arg']['type'] + endMandatoryArgument, end="") # description print() # If desc is a list, join with newlines and proper spacing if isinstance(option['desc'], list): desc_text = "\n ".join(option['desc']) print(f" {desc_text}") else: print(f" {option['desc']}") # Add remarks if available if "remark" in keyList: print() if isinstance(option['remark'], list): for remark in option['remark']: print(f" {remark}") else: print(f" {option['remark']}") print() ###### Examples ###### print(".SH EXAMPLES") print(exampleSection) ###### See Also ###### print(".SH \"SEE ALSO\"") print(".BR neofetch (1)") ###### Bugs ###### print(".SH BUGS") print(bugSection) ###### Authors ###### print(".SH AUTHORS") print(authorsSection) if __name__ == "__main__": main() ================================================ FILE: scripts/gen-pciids.py ================================================ #!/usr/bin/env python3 import sys class PciDeviceModel: def __init__(self, id: int, name: str): self.id = id self.name = name class PciVendorModel: def __init__(self, id: int, name: str): self.id = id self.name = name self.devices = [] def main(keep_vendor_list: set, pci_ids_path: str): vendors = [] with open(pci_ids_path, 'r') as f: full_text = f.read() if full_text == '': sys.exit('Error: pci.ids file is empty') dev_list_text = full_text[:full_text.rfind('\n\n\n')] # remove known classes for line in dev_list_text.split('\n'): if not line or line[0] == '#': continue if line[0] != '\t': id, name = line.split(' ', maxsplit=1) vendors.append(PciVendorModel(int(id, 16), name)) elif line[1] != '\t': id, name = line[1:].split(' ', maxsplit=1) vendors[-1].devices.append(PciDeviceModel(int(id, 16), name)) code = """\ // SPDX-License-Identifier: BSD-3-Clause // https://opensource.org/license/BSD-3-Clause // Generated from https://pci-ids.ucw.cz/v2.2/pci.ids #include #include typedef struct FFPciDevice { const uint32_t id; const char* name; } FFPciDevice; typedef struct FFPciVendor { const uint32_t id; const char* name; const FFPciDevice* devices; const uint32_t nDevices; } FFPciVendor; """ if keep_vendor_list: vendors = [vendor for vendor in vendors if vendor.id in keep_vendor_list] for vendor in vendors: if vendor.devices: piece = ',\n '.join('{{ 0x{:04X}, "{}" }}'.format(device.id, device.name.replace('"', '\\"')) for device in vendor.devices) code += f""" // {vendor.name} static const FFPciDevice pciDevices_{vendor.id:04X}[] = {{ {piece}, {{}}, }}; """ piece = ',\n '.join('{{ 0x{:04X}, "{}", {}, {} }}'.format(vendor.id, vendor.name.replace('"', '\\"'), vendor.devices and f"pciDevices_{vendor.id:04X}" or "NULL", len(vendor.devices)) for vendor in vendors) code += f""" const FFPciVendor ffPciVendors[] = {{ {piece}, {{}}, }};""" print(code) if __name__ == '__main__': len(sys.argv) == 2 or sys.exit('Usage: gen-pciids.py ') # From main({ 0x106b, # Apple 0x1002, 0x1022, # AMD 0x8086, 0x8087, 0x03e7, # Intel 0x0955, 0x10de, 0x12d2, # Nvidia 0x1ed5, # MThreads 0x5143, # Qualcomm 0x14c3, # MTK 0x15ad, # VMware 0x1af4, # RedHat 0x1ab8, # Parallel 0x1414, # Microsoft 0x108e, # Oracle }, sys.argv[1]) ================================================ FILE: src/3rdparty/display-library/adl_defines.h ================================================ // // Copyright (c) 2016 - 2022 Advanced Micro Devices, Inc. All rights reserved. // // MIT LICENSE: // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to deal // in the Software without restriction, including without limitation the rights // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell // copies of the Software, and to permit persons to whom the Software is // furnished to do so, subject to the following conditions: // // The above copyright notice and this permission notice shall be included in // all copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE // SOFTWARE. /// \file adl_defines.h /// \brief Contains all definitions exposed by ADL for \ALL platforms.\n Included in ADL SDK /// /// This file contains all definitions used by ADL. /// The ADL definitions include the following: /// \li ADL error codes /// \li Enumerations for the ADLDisplayInfo structure /// \li Maximum limits /// #ifndef ADL_DEFINES_H_ #define ADL_DEFINES_H_ /// \defgroup DEFINES Constants and Definitions /// @{ /// \defgroup define_misc Miscellaneous Constant Definitions /// @{ /// \name General Definitions /// @{ /// Defines ADL_TRUE #define ADL_TRUE 1 /// Defines ADL_FALSE #define ADL_FALSE 0 /// Defines the maximum string length #define ADL_MAX_CHAR 4096 /// Defines the maximum string length #define ADL_MAX_PATH 256 /// Defines the maximum number of supported adapters #define ADL_MAX_ADAPTERS 250 /// Defines the maxumum number of supported displays #define ADL_MAX_DISPLAYS 150 /// Defines the maxumum string length for device name #define ADL_MAX_DEVICENAME 32 /// Defines for all adapters #define ADL_ADAPTER_INDEX_ALL -1 /// Defines APIs with iOption none #define ADL_MAIN_API_OPTION_NONE 0 /// @} /// \name Definitions for iOption parameter used by /// ADL_Display_DDCBlockAccess_Get() /// @{ /// Switch to DDC line 2 before sending the command to the display. #define ADL_DDC_OPTION_SWITCHDDC2 0x00000001 /// Save command in the registry under a unique key, corresponding to parameter \b iCommandIndex #define ADL_DDC_OPTION_RESTORECOMMAND 0x00000002 /// Combine write-read DDC block access command. #define ADL_DDC_OPTION_COMBOWRITEREAD 0x00000010 /// Direct DDC access to the immediate device connected to graphics card. /// MST with this option set: DDC command is sent to first branch. /// MST with this option not set: DDC command is sent to the end node sink device. #define ADL_DDC_OPTION_SENDTOIMMEDIATEDEVICE 0x00000020 /// @} /// \name Values for /// ADLI2C.iAction used with ADL_Display_WriteAndReadI2C() /// @{ #define ADL_DL_I2C_ACTIONREAD 0x00000001 #define ADL_DL_I2C_ACTIONWRITE 0x00000002 #define ADL_DL_I2C_ACTIONREAD_REPEATEDSTART 0x00000003 #define ADL_DL_I2C_ACTIONIS_PRESENT 0x00000004 /// @} /// @} //Misc /// \defgroup define_adl_results Result Codes /// This group of definitions are the various results returned by all ADL functions \n /// @{ /// All OK, but need to wait #define ADL_OK_WAIT 4 /// All OK, but need restart #define ADL_OK_RESTART 3 /// All OK but need mode change #define ADL_OK_MODE_CHANGE 2 /// All OK, but with warning #define ADL_OK_WARNING 1 /// ADL function completed successfully #define ADL_OK 0 /// Generic Error. Most likely one or more of the Escape calls to the driver failed! #define ADL_ERR -1 /// ADL not initialized #define ADL_ERR_NOT_INIT -2 /// One of the parameter passed is invalid #define ADL_ERR_INVALID_PARAM -3 /// One of the parameter size is invalid #define ADL_ERR_INVALID_PARAM_SIZE -4 /// Invalid ADL index passed #define ADL_ERR_INVALID_ADL_IDX -5 /// Invalid controller index passed #define ADL_ERR_INVALID_CONTROLLER_IDX -6 /// Invalid display index passed #define ADL_ERR_INVALID_DIPLAY_IDX -7 /// Function not supported by the driver #define ADL_ERR_NOT_SUPPORTED -8 /// Null Pointer error #define ADL_ERR_NULL_POINTER -9 /// Call can't be made due to disabled adapter #define ADL_ERR_DISABLED_ADAPTER -10 /// Invalid Callback #define ADL_ERR_INVALID_CALLBACK -11 /// Display Resource conflict #define ADL_ERR_RESOURCE_CONFLICT -12 //Failed to update some of the values. Can be returned by set request that include multiple values if not all values were successfully committed. #define ADL_ERR_SET_INCOMPLETE -20 /// There's no Linux XDisplay in Linux Console environment #define ADL_ERR_NO_XDISPLAY -21 /// escape call failed becuse of incompatiable driver found in driver store #define ADL_ERR_CALL_TO_INCOMPATIABLE_DRIVER -22 /// not running as administrator #define ADL_ERR_NO_ADMINISTRATOR_PRIVILEGES -23 /// Feature Sync Start api is not called yet #define ADL_ERR_FEATURESYNC_NOT_STARTED -24 /// Adapter is in an invalid power state #define ADL_ERR_INVALID_POWER_STATE -25 /// @} /// /// \defgroup define_display_type Display Type /// Define Monitor/CRT display type /// @{ /// Define Monitor display type #define ADL_DT_MONITOR 0 /// Define TV display type #define ADL_DT_TELEVISION 1 /// Define LCD display type #define ADL_DT_LCD_PANEL 2 /// Define DFP display type #define ADL_DT_DIGITAL_FLAT_PANEL 3 /// Define Componment Video display type #define ADL_DT_COMPONENT_VIDEO 4 /// Define Projector display type #define ADL_DT_PROJECTOR 5 /// @} /// \defgroup define_display_connection_type Display Connection Type /// @{ /// Define unknown display output type #define ADL_DOT_UNKNOWN 0 /// Define composite display output type #define ADL_DOT_COMPOSITE 1 /// Define SVideo display output type #define ADL_DOT_SVIDEO 2 /// Define analog display output type #define ADL_DOT_ANALOG 3 /// Define digital display output type #define ADL_DOT_DIGITAL 4 /// @} /// \defgroup define_color_type Display Color Type and Source /// Define Display Color Type and Source /// @{ #define ADL_DISPLAY_COLOR_BRIGHTNESS (1 << 0) #define ADL_DISPLAY_COLOR_CONTRAST (1 << 1) #define ADL_DISPLAY_COLOR_SATURATION (1 << 2) #define ADL_DISPLAY_COLOR_HUE (1 << 3) #define ADL_DISPLAY_COLOR_TEMPERATURE (1 << 4) /// Color Temperature Source is EDID #define ADL_DISPLAY_COLOR_TEMPERATURE_SOURCE_EDID (1 << 5) /// Color Temperature Source is User #define ADL_DISPLAY_COLOR_TEMPERATURE_SOURCE_USER (1 << 6) /// @} /// \defgroup define_adjustment_capabilities Display Adjustment Capabilities /// Display adjustment capabilities values. Returned by ADL_Display_AdjustCaps_Get /// @{ #define ADL_DISPLAY_ADJUST_OVERSCAN (1 << 0) #define ADL_DISPLAY_ADJUST_VERT_POS (1 << 1) #define ADL_DISPLAY_ADJUST_HOR_POS (1 << 2) #define ADL_DISPLAY_ADJUST_VERT_SIZE (1 << 3) #define ADL_DISPLAY_ADJUST_HOR_SIZE (1 << 4) #define ADL_DISPLAY_ADJUST_SIZEPOS (ADL_DISPLAY_ADJUST_VERT_POS | ADL_DISPLAY_ADJUST_HOR_POS | ADL_DISPLAY_ADJUST_VERT_SIZE | ADL_DISPLAY_ADJUST_HOR_SIZE) #define ADL_DISPLAY_CUSTOMMODES (1<<5) #define ADL_DISPLAY_ADJUST_UNDERSCAN (1<<6) /// @} ///Down-scale support #define ADL_DISPLAY_CAPS_DOWNSCALE (1 << 0) /// Sharpness support #define ADL_DISPLAY_CAPS_SHARPNESS (1 << 0) /// \defgroup define_desktop_config Desktop Configuration Flags /// These flags are used by ADL_DesktopConfig_xxx /// \deprecated This API has been deprecated because it was only used for RandR 1.1 (Red Hat 5.x) distributions which is now not supported. /// @{ #define ADL_DESKTOPCONFIG_UNKNOWN 0 /* UNKNOWN desktop config */ #define ADL_DESKTOPCONFIG_SINGLE (1 << 0) /* Single */ #define ADL_DESKTOPCONFIG_CLONE (1 << 2) /* Clone */ #define ADL_DESKTOPCONFIG_BIGDESK_H (1 << 4) /* Big Desktop Horizontal */ #define ADL_DESKTOPCONFIG_BIGDESK_V (1 << 5) /* Big Desktop Vertical */ #define ADL_DESKTOPCONFIG_BIGDESK_HR (1 << 6) /* Big Desktop Reverse Horz */ #define ADL_DESKTOPCONFIG_BIGDESK_VR (1 << 7) /* Big Desktop Reverse Vert */ #define ADL_DESKTOPCONFIG_RANDR12 (1 << 8) /* RandR 1.2 Multi-display */ /// @} /// needed for ADLDDCInfo structure #define ADL_MAX_DISPLAY_NAME 256 /// \defgroup define_edid_flags Values for ulDDCInfoFlag /// defines for ulDDCInfoFlag EDID flag /// @{ #define ADL_DISPLAYDDCINFOEX_FLAG_PROJECTORDEVICE (1 << 0) #define ADL_DISPLAYDDCINFOEX_FLAG_EDIDEXTENSION (1 << 1) #define ADL_DISPLAYDDCINFOEX_FLAG_DIGITALDEVICE (1 << 2) #define ADL_DISPLAYDDCINFOEX_FLAG_HDMIAUDIODEVICE (1 << 3) #define ADL_DISPLAYDDCINFOEX_FLAG_SUPPORTS_AI (1 << 4) #define ADL_DISPLAYDDCINFOEX_FLAG_SUPPORT_xvYCC601 (1 << 5) #define ADL_DISPLAYDDCINFOEX_FLAG_SUPPORT_xvYCC709 (1 << 6) /// @} /// \defgroup define_displayinfo_connector Display Connector Type /// defines for ADLDisplayInfo.iDisplayConnector /// @{ #define ADL_DISPLAY_CONTYPE_UNKNOWN 0 #define ADL_DISPLAY_CONTYPE_VGA 1 #define ADL_DISPLAY_CONTYPE_DVI_D 2 #define ADL_DISPLAY_CONTYPE_DVI_I 3 #define ADL_DISPLAY_CONTYPE_ATICVDONGLE_NTSC 4 #define ADL_DISPLAY_CONTYPE_ATICVDONGLE_JPN 5 #define ADL_DISPLAY_CONTYPE_ATICVDONGLE_NONI2C_JPN 6 #define ADL_DISPLAY_CONTYPE_ATICVDONGLE_NONI2C_NTSC 7 #define ADL_DISPLAY_CONTYPE_PROPRIETARY 8 #define ADL_DISPLAY_CONTYPE_HDMI_TYPE_A 10 #define ADL_DISPLAY_CONTYPE_HDMI_TYPE_B 11 #define ADL_DISPLAY_CONTYPE_SVIDEO 12 #define ADL_DISPLAY_CONTYPE_COMPOSITE 13 #define ADL_DISPLAY_CONTYPE_RCA_3COMPONENT 14 #define ADL_DISPLAY_CONTYPE_DISPLAYPORT 15 #define ADL_DISPLAY_CONTYPE_EDP 16 #define ADL_DISPLAY_CONTYPE_WIRELESSDISPLAY 17 #define ADL_DISPLAY_CONTYPE_USB_TYPE_C 18 /// @} /// TV Capabilities and Standards /// \defgroup define_tv_caps TV Capabilities and Standards /// \deprecated Dropping support for TV displays /// @{ #define ADL_TV_STANDARDS (1 << 0) #define ADL_TV_SCART (1 << 1) /// TV Standards Definitions #define ADL_STANDARD_NTSC_M (1 << 0) #define ADL_STANDARD_NTSC_JPN (1 << 1) #define ADL_STANDARD_NTSC_N (1 << 2) #define ADL_STANDARD_PAL_B (1 << 3) #define ADL_STANDARD_PAL_COMB_N (1 << 4) #define ADL_STANDARD_PAL_D (1 << 5) #define ADL_STANDARD_PAL_G (1 << 6) #define ADL_STANDARD_PAL_H (1 << 7) #define ADL_STANDARD_PAL_I (1 << 8) #define ADL_STANDARD_PAL_K (1 << 9) #define ADL_STANDARD_PAL_K1 (1 << 10) #define ADL_STANDARD_PAL_L (1 << 11) #define ADL_STANDARD_PAL_M (1 << 12) #define ADL_STANDARD_PAL_N (1 << 13) #define ADL_STANDARD_PAL_SECAM_D (1 << 14) #define ADL_STANDARD_PAL_SECAM_K (1 << 15) #define ADL_STANDARD_PAL_SECAM_K1 (1 << 16) #define ADL_STANDARD_PAL_SECAM_L (1 << 17) /// @} /// \defgroup define_video_custom_mode Video Custom Mode flags /// Component Video Custom Mode flags. This is used by the iFlags parameter in ADLCustomMode /// @{ #define ADL_CUSTOMIZEDMODEFLAG_MODESUPPORTED (1 << 0) #define ADL_CUSTOMIZEDMODEFLAG_NOTDELETETABLE (1 << 1) #define ADL_CUSTOMIZEDMODEFLAG_INSERTBYDRIVER (1 << 2) #define ADL_CUSTOMIZEDMODEFLAG_INTERLACED (1 << 3) #define ADL_CUSTOMIZEDMODEFLAG_BASEMODE (1 << 4) /// @} /// \defgroup define_ddcinfoflag Values used for DDCInfoFlag /// ulDDCInfoFlag field values used by the ADLDDCInfo structure /// @{ #define ADL_DISPLAYDDCINFOEX_FLAG_PROJECTORDEVICE (1 << 0) #define ADL_DISPLAYDDCINFOEX_FLAG_EDIDEXTENSION (1 << 1) #define ADL_DISPLAYDDCINFOEX_FLAG_DIGITALDEVICE (1 << 2) #define ADL_DISPLAYDDCINFOEX_FLAG_HDMIAUDIODEVICE (1 << 3) #define ADL_DISPLAYDDCINFOEX_FLAG_SUPPORTS_AI (1 << 4) #define ADL_DISPLAYDDCINFOEX_FLAG_SUPPORT_xvYCC601 (1 << 5) #define ADL_DISPLAYDDCINFOEX_FLAG_SUPPORT_xvYCC709 (1 << 6) /// @} /// \defgroup define_cv_dongle Values used by ADL_CV_DongleSettings_xxx /// The following is applicable to ADL_DISPLAY_CONTYPE_ATICVDONGLE_JP and ADL_DISPLAY_CONTYPE_ATICVDONGLE_NONI2C_D only /// \deprecated Dropping support for Component Video displays /// @{ #define ADL_DISPLAY_CV_DONGLE_D1 (1 << 0) #define ADL_DISPLAY_CV_DONGLE_D2 (1 << 1) #define ADL_DISPLAY_CV_DONGLE_D3 (1 << 2) #define ADL_DISPLAY_CV_DONGLE_D4 (1 << 3) #define ADL_DISPLAY_CV_DONGLE_D5 (1 << 4) /// The following is applicable to ADL_DISPLAY_CONTYPE_ATICVDONGLE_NA and ADL_DISPLAY_CONTYPE_ATICVDONGLE_NONI2C only #define ADL_DISPLAY_CV_DONGLE_480I (1 << 0) #define ADL_DISPLAY_CV_DONGLE_480P (1 << 1) #define ADL_DISPLAY_CV_DONGLE_540P (1 << 2) #define ADL_DISPLAY_CV_DONGLE_720P (1 << 3) #define ADL_DISPLAY_CV_DONGLE_1080I (1 << 4) #define ADL_DISPLAY_CV_DONGLE_1080P (1 << 5) #define ADL_DISPLAY_CV_DONGLE_16_9 (1 << 6) #define ADL_DISPLAY_CV_DONGLE_720P50 (1 << 7) #define ADL_DISPLAY_CV_DONGLE_1080I25 (1 << 8) #define ADL_DISPLAY_CV_DONGLE_576I25 (1 << 9) #define ADL_DISPLAY_CV_DONGLE_576P50 (1 << 10) #define ADL_DISPLAY_CV_DONGLE_1080P24 (1 << 11) #define ADL_DISPLAY_CV_DONGLE_1080P25 (1 << 12) #define ADL_DISPLAY_CV_DONGLE_1080P30 (1 << 13) #define ADL_DISPLAY_CV_DONGLE_1080P50 (1 << 14) /// @} /// \defgroup define_formats_ovr Formats Override Settings /// Display force modes flags /// @{ /// #define ADL_DISPLAY_FORMAT_FORCE_720P 0x00000001 #define ADL_DISPLAY_FORMAT_FORCE_1080I 0x00000002 #define ADL_DISPLAY_FORMAT_FORCE_1080P 0x00000004 #define ADL_DISPLAY_FORMAT_FORCE_720P50 0x00000008 #define ADL_DISPLAY_FORMAT_FORCE_1080I25 0x00000010 #define ADL_DISPLAY_FORMAT_FORCE_576I25 0x00000020 #define ADL_DISPLAY_FORMAT_FORCE_576P50 0x00000040 #define ADL_DISPLAY_FORMAT_FORCE_1080P24 0x00000080 #define ADL_DISPLAY_FORMAT_FORCE_1080P25 0x00000100 #define ADL_DISPLAY_FORMAT_FORCE_1080P30 0x00000200 #define ADL_DISPLAY_FORMAT_FORCE_1080P50 0x00000400 ///< Below are \b EXTENDED display mode flags #define ADL_DISPLAY_FORMAT_CVDONGLEOVERIDE 0x00000001 #define ADL_DISPLAY_FORMAT_CVMODEUNDERSCAN 0x00000002 #define ADL_DISPLAY_FORMAT_FORCECONNECT_SUPPORTED 0x00000004 #define ADL_DISPLAY_FORMAT_RESTRICT_FORMAT_SELECTION 0x00000008 #define ADL_DISPLAY_FORMAT_SETASPECRATIO 0x00000010 #define ADL_DISPLAY_FORMAT_FORCEMODES 0x00000020 #define ADL_DISPLAY_FORMAT_LCDRTCCOEFF 0x00000040 /// @} /// Defines used by OD5 #define ADL_PM_PARAM_DONT_CHANGE 0 /// The following defines Bus types /// @{ #define ADL_BUSTYPE_PCI 0 /* PCI bus */ #define ADL_BUSTYPE_AGP 1 /* AGP bus */ #define ADL_BUSTYPE_PCIE 2 /* PCI Express bus */ #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 */ /// @} /// \defgroup define_ws_caps Workstation Capabilities /// Workstation values /// @{ /// This value indicates that the workstation card supports active stereo though stereo output connector #define ADL_STEREO_SUPPORTED (1 << 2) /// This value indicates that the workstation card supports active stereo via "blue-line" #define ADL_STEREO_BLUE_LINE (1 << 3) /// This value is used to turn off stereo mode. #define ADL_STEREO_OFF 0 /// This value indicates that the workstation card supports active stereo. This is also used to set the stereo mode to active though the stereo output connector #define ADL_STEREO_ACTIVE (1 << 1) /// This value indicates that the workstation card supports auto-stereo monitors with horizontal interleave. This is also used to set the stereo mode to use the auto-stereo monitor with horizontal interleave #define ADL_STEREO_AUTO_HORIZONTAL (1 << 30) /// This value indicates that the workstation card supports auto-stereo monitors with vertical interleave. This is also used to set the stereo mode to use the auto-stereo monitor with vertical interleave #define ADL_STEREO_AUTO_VERTICAL (1 << 31) /// This value indicates that the workstation card supports passive stereo, ie. non stereo sync #define ADL_STEREO_PASSIVE (1 << 6) /// This value indicates that the workstation card supports auto-stereo monitors with vertical interleave. This is also used to set the stereo mode to use the auto-stereo monitor with vertical interleave #define ADL_STEREO_PASSIVE_HORIZ (1 << 7) /// This value indicates that the workstation card supports auto-stereo monitors with vertical interleave. This is also used to set the stereo mode to use the auto-stereo monitor with vertical interleave #define ADL_STEREO_PASSIVE_VERT (1 << 8) /// This value indicates that the workstation card supports auto-stereo monitors with Samsung. #define ADL_STEREO_AUTO_SAMSUNG (1 << 11) /// This value indicates that the workstation card supports auto-stereo monitors with Tridility. #define ADL_STEREO_AUTO_TSL (1 << 12) /// This value indicates that the workstation card supports DeepBitDepth (10 bpp) #define ADL_DEEPBITDEPTH_10BPP_SUPPORTED (1 << 5) /// This value indicates that the workstation supports 8-Bit Grayscale #define ADL_8BIT_GREYSCALE_SUPPORTED (1 << 9) /// This value indicates that the workstation supports CUSTOM TIMING #define ADL_CUSTOM_TIMING_SUPPORTED (1 << 10) /// Load balancing is supported. #define ADL_WORKSTATION_LOADBALANCING_SUPPORTED 0x00000001 /// Load balancing is available. #define ADL_WORKSTATION_LOADBALANCING_AVAILABLE 0x00000002 /// Load balancing is disabled. #define ADL_WORKSTATION_LOADBALANCING_DISABLED 0x00000000 /// Load balancing is Enabled. #define ADL_WORKSTATION_LOADBALANCING_ENABLED 0x00000001 /// @} /// \defgroup define_adapterspeed speed setting from the adapter /// @{ #define ADL_CONTEXT_SPEED_UNFORCED 0 /* default asic running speed */ #define ADL_CONTEXT_SPEED_FORCEHIGH 1 /* asic running speed is forced to high */ #define ADL_CONTEXT_SPEED_FORCELOW 2 /* asic running speed is forced to low */ #define ADL_ADAPTER_SPEEDCAPS_SUPPORTED (1 << 0) /* change asic running speed setting is supported */ /// @} /// \defgroup define_glsync Genlock related values /// GL-Sync port types (unique values) /// @{ /// Unknown port of GL-Sync module #define ADL_GLSYNC_PORT_UNKNOWN 0 /// BNC port of of GL-Sync module #define ADL_GLSYNC_PORT_BNC 1 /// RJ45(1) port of of GL-Sync module #define ADL_GLSYNC_PORT_RJ45PORT1 2 /// RJ45(2) port of of GL-Sync module #define ADL_GLSYNC_PORT_RJ45PORT2 3 // GL-Sync Genlock settings mask (bit-vector) /// None of the ADLGLSyncGenlockConfig members are valid #define ADL_GLSYNC_CONFIGMASK_NONE 0 /// The ADLGLSyncGenlockConfig.lSignalSource member is valid #define ADL_GLSYNC_CONFIGMASK_SIGNALSOURCE (1 << 0) /// The ADLGLSyncGenlockConfig.iSyncField member is valid #define ADL_GLSYNC_CONFIGMASK_SYNCFIELD (1 << 1) /// The ADLGLSyncGenlockConfig.iSampleRate member is valid #define ADL_GLSYNC_CONFIGMASK_SAMPLERATE (1 << 2) /// The ADLGLSyncGenlockConfig.lSyncDelay member is valid #define ADL_GLSYNC_CONFIGMASK_SYNCDELAY (1 << 3) /// The ADLGLSyncGenlockConfig.iTriggerEdge member is valid #define ADL_GLSYNC_CONFIGMASK_TRIGGEREDGE (1 << 4) /// The ADLGLSyncGenlockConfig.iScanRateCoeff member is valid #define ADL_GLSYNC_CONFIGMASK_SCANRATECOEFF (1 << 5) /// The ADLGLSyncGenlockConfig.lFramelockCntlVector member is valid #define ADL_GLSYNC_CONFIGMASK_FRAMELOCKCNTL (1 << 6) // GL-Sync Framelock control mask (bit-vector) /// Framelock is disabled #define ADL_GLSYNC_FRAMELOCKCNTL_NONE 0 /// Framelock is enabled #define ADL_GLSYNC_FRAMELOCKCNTL_ENABLE ( 1 << 0) #define ADL_GLSYNC_FRAMELOCKCNTL_DISABLE ( 1 << 1) #define ADL_GLSYNC_FRAMELOCKCNTL_SWAP_COUNTER_RESET ( 1 << 2) #define ADL_GLSYNC_FRAMELOCKCNTL_SWAP_COUNTER_ACK ( 1 << 3) #define ADL_GLSYNC_FRAMELOCKCNTL_VERSION_KMD (1 << 4) #define ADL_GLSYNC_FRAMELOCKCNTL_STATE_ENABLE ( 1 << 0) #define ADL_GLSYNC_FRAMELOCKCNTL_STATE_KMD (1 << 4) // GL-Sync Framelock counters mask (bit-vector) #define ADL_GLSYNC_COUNTER_SWAP ( 1 << 0 ) // GL-Sync Signal Sources (unique values) /// GL-Sync signal source is undefined #define ADL_GLSYNC_SIGNALSOURCE_UNDEFINED 0x00000100 /// GL-Sync signal source is Free Run #define ADL_GLSYNC_SIGNALSOURCE_FREERUN 0x00000101 /// GL-Sync signal source is the BNC GL-Sync port #define ADL_GLSYNC_SIGNALSOURCE_BNCPORT 0x00000102 /// GL-Sync signal source is the RJ45(1) GL-Sync port #define ADL_GLSYNC_SIGNALSOURCE_RJ45PORT1 0x00000103 /// GL-Sync signal source is the RJ45(2) GL-Sync port #define ADL_GLSYNC_SIGNALSOURCE_RJ45PORT2 0x00000104 // GL-Sync Signal Types (unique values) /// GL-Sync signal type is unknown #define ADL_GLSYNC_SIGNALTYPE_UNDEFINED 0 /// GL-Sync signal type is 480I #define ADL_GLSYNC_SIGNALTYPE_480I 1 /// GL-Sync signal type is 576I #define ADL_GLSYNC_SIGNALTYPE_576I 2 /// GL-Sync signal type is 480P #define ADL_GLSYNC_SIGNALTYPE_480P 3 /// GL-Sync signal type is 576P #define ADL_GLSYNC_SIGNALTYPE_576P 4 /// GL-Sync signal type is 720P #define ADL_GLSYNC_SIGNALTYPE_720P 5 /// GL-Sync signal type is 1080P #define ADL_GLSYNC_SIGNALTYPE_1080P 6 /// GL-Sync signal type is 1080I #define ADL_GLSYNC_SIGNALTYPE_1080I 7 /// GL-Sync signal type is SDI #define ADL_GLSYNC_SIGNALTYPE_SDI 8 /// GL-Sync signal type is TTL #define ADL_GLSYNC_SIGNALTYPE_TTL 9 /// GL_Sync signal type is Analog #define ADL_GLSYNC_SIGNALTYPE_ANALOG 10 // GL-Sync Sync Field options (unique values) ///GL-Sync sync field option is undefined #define ADL_GLSYNC_SYNCFIELD_UNDEFINED 0 ///GL-Sync sync field option is Sync to Field 1 (used for Interlaced signal types) #define ADL_GLSYNC_SYNCFIELD_BOTH 1 ///GL-Sync sync field option is Sync to Both fields (used for Interlaced signal types) #define ADL_GLSYNC_SYNCFIELD_1 2 // GL-Sync trigger edge options (unique values) /// GL-Sync trigger edge is undefined #define ADL_GLSYNC_TRIGGEREDGE_UNDEFINED 0 /// GL-Sync trigger edge is the rising edge #define ADL_GLSYNC_TRIGGEREDGE_RISING 1 /// GL-Sync trigger edge is the falling edge #define ADL_GLSYNC_TRIGGEREDGE_FALLING 2 /// GL-Sync trigger edge is both the rising and the falling edge #define ADL_GLSYNC_TRIGGEREDGE_BOTH 3 // GL-Sync scan rate coefficient/multiplier options (unique values) /// GL-Sync scan rate coefficient/multiplier is undefined #define ADL_GLSYNC_SCANRATECOEFF_UNDEFINED 0 /// GL-Sync scan rate coefficient/multiplier is 5 #define ADL_GLSYNC_SCANRATECOEFF_x5 1 /// GL-Sync scan rate coefficient/multiplier is 4 #define ADL_GLSYNC_SCANRATECOEFF_x4 2 /// GL-Sync scan rate coefficient/multiplier is 3 #define ADL_GLSYNC_SCANRATECOEFF_x3 3 /// GL-Sync scan rate coefficient/multiplier is 5:2 (SMPTE) #define ADL_GLSYNC_SCANRATECOEFF_x5_DIV_2 4 /// GL-Sync scan rate coefficient/multiplier is 2 #define ADL_GLSYNC_SCANRATECOEFF_x2 5 /// GL-Sync scan rate coefficient/multiplier is 3 : 2 #define ADL_GLSYNC_SCANRATECOEFF_x3_DIV_2 6 /// GL-Sync scan rate coefficient/multiplier is 5 : 4 #define ADL_GLSYNC_SCANRATECOEFF_x5_DIV_4 7 /// GL-Sync scan rate coefficient/multiplier is 1 (default) #define ADL_GLSYNC_SCANRATECOEFF_x1 8 /// GL-Sync scan rate coefficient/multiplier is 4 : 5 #define ADL_GLSYNC_SCANRATECOEFF_x4_DIV_5 9 /// GL-Sync scan rate coefficient/multiplier is 2 : 3 #define ADL_GLSYNC_SCANRATECOEFF_x2_DIV_3 10 /// GL-Sync scan rate coefficient/multiplier is 1 : 2 #define ADL_GLSYNC_SCANRATECOEFF_x1_DIV_2 11 /// GL-Sync scan rate coefficient/multiplier is 2 : 5 (SMPTE) #define ADL_GLSYNC_SCANRATECOEFF_x2_DIV_5 12 /// GL-Sync scan rate coefficient/multiplier is 1 : 3 #define ADL_GLSYNC_SCANRATECOEFF_x1_DIV_3 13 /// GL-Sync scan rate coefficient/multiplier is 1 : 4 #define ADL_GLSYNC_SCANRATECOEFF_x1_DIV_4 14 /// GL-Sync scan rate coefficient/multiplier is 1 : 5 #define ADL_GLSYNC_SCANRATECOEFF_x1_DIV_5 15 // GL-Sync port (signal presence) states (unique values) /// GL-Sync port state is undefined #define ADL_GLSYNC_PORTSTATE_UNDEFINED 0 /// GL-Sync port is not connected #define ADL_GLSYNC_PORTSTATE_NOCABLE 1 /// GL-Sync port is Idle #define ADL_GLSYNC_PORTSTATE_IDLE 2 /// GL-Sync port has an Input signal #define ADL_GLSYNC_PORTSTATE_INPUT 3 /// GL-Sync port is Output #define ADL_GLSYNC_PORTSTATE_OUTPUT 4 // GL-Sync LED types (used index within ADL_Workstation_GLSyncPortState_Get returned ppGlSyncLEDs array) (unique values) /// Index into the ADL_Workstation_GLSyncPortState_Get returned ppGlSyncLEDs array for the one LED of the BNC port #define ADL_GLSYNC_LEDTYPE_BNC 0 /// Index into the ADL_Workstation_GLSyncPortState_Get returned ppGlSyncLEDs array for the Left LED of the RJ45(1) or RJ45(2) port #define ADL_GLSYNC_LEDTYPE_RJ45_LEFT 0 /// Index into the ADL_Workstation_GLSyncPortState_Get returned ppGlSyncLEDs array for the Right LED of the RJ45(1) or RJ45(2) port #define ADL_GLSYNC_LEDTYPE_RJ45_RIGHT 1 // GL-Sync LED colors (unique values) /// GL-Sync LED undefined color #define ADL_GLSYNC_LEDCOLOR_UNDEFINED 0 /// GL-Sync LED is unlit #define ADL_GLSYNC_LEDCOLOR_NOLIGHT 1 /// GL-Sync LED is yellow #define ADL_GLSYNC_LEDCOLOR_YELLOW 2 /// GL-Sync LED is red #define ADL_GLSYNC_LEDCOLOR_RED 3 /// GL-Sync LED is green #define ADL_GLSYNC_LEDCOLOR_GREEN 4 /// GL-Sync LED is flashing green #define ADL_GLSYNC_LEDCOLOR_FLASH_GREEN 5 // GL-Sync Port Control (refers one GL-Sync Port) (unique values) /// Used to configure the RJ54(1) or RJ42(2) port of GL-Sync is as Idle #define ADL_GLSYNC_PORTCNTL_NONE 0x00000000 /// Used to configure the RJ54(1) or RJ42(2) port of GL-Sync is as Output #define ADL_GLSYNC_PORTCNTL_OUTPUT 0x00000001 // GL-Sync Mode Control (refers one Display/Controller) (bitfields) /// Used to configure the display to use internal timing (not genlocked) #define ADL_GLSYNC_MODECNTL_NONE 0x00000000 /// Bitfield used to configure the display as genlocked (either as Timing Client or as Timing Server) #define ADL_GLSYNC_MODECNTL_GENLOCK 0x00000001 /// Bitfield used to configure the display as Timing Server #define ADL_GLSYNC_MODECNTL_TIMINGSERVER 0x00000002 // GL-Sync Mode Status /// Display is currently not genlocked #define ADL_GLSYNC_MODECNTL_STATUS_NONE 0x00000000 /// Display is currently genlocked #define ADL_GLSYNC_MODECNTL_STATUS_GENLOCK 0x00000001 /// Display requires a mode switch #define ADL_GLSYNC_MODECNTL_STATUS_SETMODE_REQUIRED 0x00000002 /// Display is capable of being genlocked #define ADL_GLSYNC_MODECNTL_STATUS_GENLOCK_ALLOWED 0x00000004 #define ADL_MAX_GLSYNC_PORTS 8 #define ADL_MAX_GLSYNC_PORT_LEDS 8 /// @} /// \defgroup define_crossfirestate CrossfireX state of a particular adapter CrossfireX combination /// @{ #define ADL_XFIREX_STATE_NOINTERCONNECT ( 1 << 0 ) /* Dongle / cable is missing */ #define ADL_XFIREX_STATE_DOWNGRADEPIPES ( 1 << 1 ) /* CrossfireX can be enabled if pipes are downgraded */ #define ADL_XFIREX_STATE_DOWNGRADEMEM ( 1 << 2 ) /* CrossfireX cannot be enabled unless mem downgraded */ #define ADL_XFIREX_STATE_REVERSERECOMMENDED ( 1 << 3 ) /* Card reversal recommended, CrossfireX cannot be enabled. */ #define ADL_XFIREX_STATE_3DACTIVE ( 1 << 4 ) /* 3D client is active - CrossfireX cannot be safely enabled */ #define ADL_XFIREX_STATE_MASTERONSLAVE ( 1 << 5 ) /* Dongle is OK but master is on slave */ #define ADL_XFIREX_STATE_NODISPLAYCONNECT ( 1 << 6 ) /* No (valid) display connected to master card. */ #define ADL_XFIREX_STATE_NOPRIMARYVIEW ( 1 << 7 ) /* CrossfireX is enabled but master is not current primary device */ #define ADL_XFIREX_STATE_DOWNGRADEVISMEM ( 1 << 8 ) /* CrossfireX cannot be enabled unless visible mem downgraded */ #define ADL_XFIREX_STATE_LESSTHAN8LANE_MASTER ( 1 << 9 ) /* CrossfireX can be enabled however performance not optimal due to <8 lanes */ #define ADL_XFIREX_STATE_LESSTHAN8LANE_SLAVE ( 1 << 10 ) /* CrossfireX can be enabled however performance not optimal due to <8 lanes */ #define ADL_XFIREX_STATE_PEERTOPEERFAILED ( 1 << 11 ) /* CrossfireX cannot be enabled due to failed peer to peer test */ #define ADL_XFIREX_STATE_MEMISDOWNGRADED ( 1 << 16 ) /* Notification that memory is currently downgraded */ #define ADL_XFIREX_STATE_PIPESDOWNGRADED ( 1 << 17 ) /* Notification that pipes are currently downgraded */ #define ADL_XFIREX_STATE_XFIREXACTIVE ( 1 << 18 ) /* CrossfireX is enabled on current device */ #define ADL_XFIREX_STATE_VISMEMISDOWNGRADED ( 1 << 19 ) /* Notification that visible FB memory is currently downgraded */ #define ADL_XFIREX_STATE_INVALIDINTERCONNECTION ( 1 << 20 ) /* Cannot support current inter-connection configuration */ #define ADL_XFIREX_STATE_NONP2PMODE ( 1 << 21 ) /* CrossfireX will only work with clients supporting non P2P mode */ #define ADL_XFIREX_STATE_DOWNGRADEMEMBANKS ( 1 << 22 ) /* CrossfireX cannot be enabled unless memory banks downgraded */ #define ADL_XFIREX_STATE_MEMBANKSDOWNGRADED ( 1 << 23 ) /* Notification that memory banks are currently downgraded */ #define ADL_XFIREX_STATE_DUALDISPLAYSALLOWED ( 1 << 24 ) /* Extended desktop or clone mode is allowed. */ #define ADL_XFIREX_STATE_P2P_APERTURE_MAPPING ( 1 << 25 ) /* P2P mapping was through peer aperture */ #define ADL_XFIREX_STATE_P2PFLUSH_REQUIRED ADL_XFIREX_STATE_P2P_APERTURE_MAPPING /* For back compatible */ #define ADL_XFIREX_STATE_XSP_CONNECTED ( 1 << 26 ) /* There is CrossfireX side port connection between GPUs */ #define ADL_XFIREX_STATE_ENABLE_CF_REBOOT_REQUIRED ( 1 << 27 ) /* System needs a reboot bofore enable CrossfireX */ #define ADL_XFIREX_STATE_DISABLE_CF_REBOOT_REQUIRED ( 1 << 28 ) /* System needs a reboot after disable CrossfireX */ #define ADL_XFIREX_STATE_DRV_HANDLE_DOWNGRADE_KEY ( 1 << 29 ) /* Indicate base driver handles the downgrade key updating */ #define ADL_XFIREX_STATE_CF_RECONFIG_REQUIRED ( 1 << 30 ) /* CrossfireX need to be reconfigured by CCC because of a LDA chain broken */ #define ADL_XFIREX_STATE_ERRORGETTINGSTATUS ( 1 << 31 ) /* Could not obtain current status */ /// @} /////////////////////////////////////////////////////////////////////////// // ADL_DISPLAY_ADJUSTMENT_PIXELFORMAT adjustment values // (bit-vector) /////////////////////////////////////////////////////////////////////////// /// \defgroup define_pixel_formats Pixel Formats values /// This group defines the various Pixel Formats that a particular digital display can support. \n /// Since a display can support multiple formats, these values can be bit-or'ed to indicate the various formats \n /// @{ #define ADL_DISPLAY_PIXELFORMAT_UNKNOWN 0 #define ADL_DISPLAY_PIXELFORMAT_RGB (1 << 0) #define ADL_DISPLAY_PIXELFORMAT_YCRCB444 (1 << 1) //Limited range #define ADL_DISPLAY_PIXELFORMAT_YCRCB422 (1 << 2) //Limited range #define ADL_DISPLAY_PIXELFORMAT_RGB_LIMITED_RANGE (1 << 3) #define ADL_DISPLAY_PIXELFORMAT_RGB_FULL_RANGE ADL_DISPLAY_PIXELFORMAT_RGB //Full range #define ADL_DISPLAY_PIXELFORMAT_YCRCB420 (1 << 4) /// @} /// \defgroup define_contype Connector Type Values /// ADLDisplayConfig.ulConnectorType defines /// @{ #define ADL_DL_DISPLAYCONFIG_CONTYPE_UNKNOWN 0 #define ADL_DL_DISPLAYCONFIG_CONTYPE_CV_NONI2C_JP 1 #define ADL_DL_DISPLAYCONFIG_CONTYPE_CV_JPN 2 #define ADL_DL_DISPLAYCONFIG_CONTYPE_CV_NA 3 #define ADL_DL_DISPLAYCONFIG_CONTYPE_CV_NONI2C_NA 4 #define ADL_DL_DISPLAYCONFIG_CONTYPE_VGA 5 #define ADL_DL_DISPLAYCONFIG_CONTYPE_DVI_D 6 #define ADL_DL_DISPLAYCONFIG_CONTYPE_DVI_I 7 #define ADL_DL_DISPLAYCONFIG_CONTYPE_HDMI_TYPE_A 8 #define ADL_DL_DISPLAYCONFIG_CONTYPE_HDMI_TYPE_B 9 #define ADL_DL_DISPLAYCONFIG_CONTYPE_DISPLAYPORT 10 /// @} /////////////////////////////////////////////////////////////////////////// // ADL_DISPLAY_DISPLAYINFO_ Definitions // for ADLDisplayInfo.iDisplayInfoMask and ADLDisplayInfo.iDisplayInfoValue // (bit-vector) /////////////////////////////////////////////////////////////////////////// /// \defgroup define_displayinfomask Display Info Mask Values /// @{ #define ADL_DISPLAY_DISPLAYINFO_DISPLAYCONNECTED 0x00000001 #define ADL_DISPLAY_DISPLAYINFO_DISPLAYMAPPED 0x00000002 #define ADL_DISPLAY_DISPLAYINFO_NONLOCAL 0x00000004 #define ADL_DISPLAY_DISPLAYINFO_FORCIBLESUPPORTED 0x00000008 #define ADL_DISPLAY_DISPLAYINFO_GENLOCKSUPPORTED 0x00000010 #define ADL_DISPLAY_DISPLAYINFO_MULTIVPU_SUPPORTED 0x00000020 #define ADL_DISPLAY_DISPLAYINFO_LDA_DISPLAY 0x00000040 #define ADL_DISPLAY_DISPLAYINFO_MODETIMING_OVERRIDESSUPPORTED 0x00000080 #define ADL_DISPLAY_DISPLAYINFO_MANNER_SUPPORTED_SINGLE 0x00000100 #define ADL_DISPLAY_DISPLAYINFO_MANNER_SUPPORTED_CLONE 0x00000200 /// Legacy support for XP #define ADL_DISPLAY_DISPLAYINFO_MANNER_SUPPORTED_2VSTRETCH 0x00000400 #define ADL_DISPLAY_DISPLAYINFO_MANNER_SUPPORTED_2HSTRETCH 0x00000800 #define ADL_DISPLAY_DISPLAYINFO_MANNER_SUPPORTED_EXTENDED 0x00001000 /// More support manners #define ADL_DISPLAY_DISPLAYINFO_MANNER_SUPPORTED_NSTRETCH1GPU 0x00010000 #define ADL_DISPLAY_DISPLAYINFO_MANNER_SUPPORTED_NSTRETCHNGPU 0x00020000 #define ADL_DISPLAY_DISPLAYINFO_MANNER_SUPPORTED_RESERVED2 0x00040000 #define ADL_DISPLAY_DISPLAYINFO_MANNER_SUPPORTED_RESERVED3 0x00080000 /// Projector display type #define ADL_DISPLAY_DISPLAYINFO_SHOWTYPE_PROJECTOR 0x00100000 /// @} /////////////////////////////////////////////////////////////////////////// // ADL_ADAPTER_DISPLAY_MANNER_SUPPORTED_ Definitions // for ADLAdapterDisplayCap of ADL_Adapter_Display_Cap() // (bit-vector) /////////////////////////////////////////////////////////////////////////// /// \defgroup define_adaptermanner Adapter Manner Support Values /// @{ #define ADL_ADAPTER_DISPLAYCAP_MANNER_SUPPORTED_NOTACTIVE 0x00000001 #define ADL_ADAPTER_DISPLAYCAP_MANNER_SUPPORTED_SINGLE 0x00000002 #define ADL_ADAPTER_DISPLAYCAP_MANNER_SUPPORTED_CLONE 0x00000004 #define ADL_ADAPTER_DISPLAYCAP_MANNER_SUPPORTED_NSTRETCH1GPU 0x00000008 #define ADL_ADAPTER_DISPLAYCAP_MANNER_SUPPORTED_NSTRETCHNGPU 0x00000010 /// Legacy support for XP #define ADL_ADAPTER_DISPLAYCAP_MANNER_SUPPORTED_2VSTRETCH 0x00000020 #define ADL_ADAPTER_DISPLAYCAP_MANNER_SUPPORTED_2HSTRETCH 0x00000040 #define ADL_ADAPTER_DISPLAYCAP_MANNER_SUPPORTED_EXTENDED 0x00000080 #define ADL_ADAPTER_DISPLAYCAP_PREFERDISPLAY_SUPPORTED 0x00000100 #define ADL_ADAPTER_DISPLAYCAP_BEZEL_SUPPORTED 0x00000200 /////////////////////////////////////////////////////////////////////////// // ADL_DISPLAY_DISPLAYMAP_MANNER_ Definitions // for ADLDisplayMap.iDisplayMapMask and ADLDisplayMap.iDisplayMapValue // (bit-vector) /////////////////////////////////////////////////////////////////////////// #define ADL_DISPLAY_DISPLAYMAP_MANNER_RESERVED 0x00000001 #define ADL_DISPLAY_DISPLAYMAP_MANNER_NOTACTIVE 0x00000002 #define ADL_DISPLAY_DISPLAYMAP_MANNER_SINGLE 0x00000004 #define ADL_DISPLAY_DISPLAYMAP_MANNER_CLONE 0x00000008 #define ADL_DISPLAY_DISPLAYMAP_MANNER_RESERVED1 0x00000010 // Removed NSTRETCH #define ADL_DISPLAY_DISPLAYMAP_MANNER_HSTRETCH 0x00000020 #define ADL_DISPLAY_DISPLAYMAP_MANNER_VSTRETCH 0x00000040 #define ADL_DISPLAY_DISPLAYMAP_MANNER_VLD 0x00000080 /// @} /////////////////////////////////////////////////////////////////////////// // ADL_DISPLAY_DISPLAYMAP_OPTION_ Definitions // for iOption in function ADL_Display_DisplayMapConfig_Get // (bit-vector) /////////////////////////////////////////////////////////////////////////// #define ADL_DISPLAY_DISPLAYMAP_OPTION_GPUINFO 0x00000001 /////////////////////////////////////////////////////////////////////////// // ADL_DISPLAY_DISPLAYTARGET_ Definitions // for ADLDisplayTarget.iDisplayTargetMask and ADLDisplayTarget.iDisplayTargetValue // (bit-vector) /////////////////////////////////////////////////////////////////////////// #define ADL_DISPLAY_DISPLAYTARGET_PREFERRED 0x00000001 /////////////////////////////////////////////////////////////////////////// // ADL_DISPLAY_POSSIBLEMAPRESULT_VALID Definitions // for ADLPossibleMapResult.iPossibleMapResultMask and ADLPossibleMapResult.iPossibleMapResultValue // (bit-vector) /////////////////////////////////////////////////////////////////////////// #define ADL_DISPLAY_POSSIBLEMAPRESULT_VALID 0x00000001 #define ADL_DISPLAY_POSSIBLEMAPRESULT_BEZELSUPPORTED 0x00000002 #define ADL_DISPLAY_POSSIBLEMAPRESULT_OVERLAPSUPPORTED 0x00000004 /////////////////////////////////////////////////////////////////////////// // ADL_DISPLAY_MODE_ Definitions // for ADLMode.iModeMask, ADLMode.iModeValue, and ADLMode.iModeFlag // (bit-vector) /////////////////////////////////////////////////////////////////////////// /// \defgroup define_displaymode Display Mode Values /// @{ #define ADL_DISPLAY_MODE_COLOURFORMAT_565 0x00000001 #define ADL_DISPLAY_MODE_COLOURFORMAT_8888 0x00000002 #define ADL_DISPLAY_MODE_ORIENTATION_SUPPORTED_000 0x00000004 #define ADL_DISPLAY_MODE_ORIENTATION_SUPPORTED_090 0x00000008 #define ADL_DISPLAY_MODE_ORIENTATION_SUPPORTED_180 0x00000010 #define ADL_DISPLAY_MODE_ORIENTATION_SUPPORTED_270 0x00000020 #define ADL_DISPLAY_MODE_REFRESHRATE_ROUNDED 0x00000040 #define ADL_DISPLAY_MODE_REFRESHRATE_ONLY 0x00000080 #define ADL_DISPLAY_MODE_PROGRESSIVE_FLAG 0 #define ADL_DISPLAY_MODE_INTERLACED_FLAG 2 /// @} /////////////////////////////////////////////////////////////////////////// // ADL_OSMODEINFO Definitions /////////////////////////////////////////////////////////////////////////// /// \defgroup define_osmode OS Mode Values /// @{ #define ADL_OSMODEINFOXPOS_DEFAULT -640 #define ADL_OSMODEINFOYPOS_DEFAULT 0 #define ADL_OSMODEINFOXRES_DEFAULT 640 #define ADL_OSMODEINFOYRES_DEFAULT 480 #define ADL_OSMODEINFOXRES_DEFAULT800 800 #define ADL_OSMODEINFOYRES_DEFAULT600 600 #define ADL_OSMODEINFOREFRESHRATE_DEFAULT 60 #define ADL_OSMODEINFOCOLOURDEPTH_DEFAULT 8 #define ADL_OSMODEINFOCOLOURDEPTH_DEFAULT16 16 #define ADL_OSMODEINFOCOLOURDEPTH_DEFAULT24 24 #define ADL_OSMODEINFOCOLOURDEPTH_DEFAULT32 32 #define ADL_OSMODEINFOORIENTATION_DEFAULT 0 #define ADL_OSMODEINFOORIENTATION_DEFAULT_WIN7 DISPLAYCONFIG_ROTATION_FORCE_UINT32 #define ADL_OSMODEFLAG_DEFAULT 0 /// @} /////////////////////////////////////////////////////////////////////////// // ADLThreadingModel Enumeration /////////////////////////////////////////////////////////////////////////// /// \defgroup thread_model /// Used with \ref ADL_Main_ControlX2_Create and \ref ADL2_Main_ControlX2_Create to specify how ADL handles API calls when executed by multiple threads concurrently. /// \brief Declares ADL threading behavior. /// @{ 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. */ }ADLThreadingModel; /// @} /////////////////////////////////////////////////////////////////////////// // ADLPurposeCode Enumeration /////////////////////////////////////////////////////////////////////////// enum ADLPurposeCode { ADL_PURPOSECODE_NORMAL = 0, ADL_PURPOSECODE_HIDE_MODE_SWITCH, ADL_PURPOSECODE_MODE_SWITCH, ADL_PURPOSECODE_ATTATCH_DEVICE, ADL_PURPOSECODE_DETACH_DEVICE, ADL_PURPOSECODE_SETPRIMARY_DEVICE, ADL_PURPOSECODE_GDI_ROTATION, ADL_PURPOSECODE_ATI_ROTATION }; /////////////////////////////////////////////////////////////////////////// // ADLAngle Enumeration /////////////////////////////////////////////////////////////////////////// enum ADLAngle { ADL_ANGLE_LANDSCAPE = 0, ADL_ANGLE_ROTATERIGHT = 90, ADL_ANGLE_ROTATE180 = 180, ADL_ANGLE_ROTATELEFT = 270, }; /////////////////////////////////////////////////////////////////////////// // ADLOrientationDataType Enumeration /////////////////////////////////////////////////////////////////////////// enum ADLOrientationDataType { ADL_ORIENTATIONTYPE_OSDATATYPE, ADL_ORIENTATIONTYPE_NONOSDATATYPE }; /////////////////////////////////////////////////////////////////////////// // ADLPanningMode Enumeration /////////////////////////////////////////////////////////////////////////// enum ADLPanningMode { ADL_PANNINGMODE_NO_PANNING = 0, ADL_PANNINGMODE_AT_LEAST_ONE_NO_PANNING = 1, ADL_PANNINGMODE_ALLOW_PANNING = 2, }; /////////////////////////////////////////////////////////////////////////// // ADLLARGEDESKTOPTYPE Enumeration /////////////////////////////////////////////////////////////////////////// enum ADLLARGEDESKTOPTYPE { ADL_LARGEDESKTOPTYPE_NORMALDESKTOP = 0, ADL_LARGEDESKTOPTYPE_PSEUDOLARGEDESKTOP = 1, ADL_LARGEDESKTOPTYPE_VERYLARGEDESKTOP = 2 }; /////////////////////////////////////////////////////////////////////////// // ADLPlatform Enumeration /////////////////////////////////////////////////////////////////////////// enum ADLPlatForm { GRAPHICS_PLATFORM_DESKTOP = 0, GRAPHICS_PLATFORM_MOBILE = 1 }; /////////////////////////////////////////////////////////////////////////// // ADLGraphicCoreGeneration Enumeration /////////////////////////////////////////////////////////////////////////// enum ADLGraphicCoreGeneration { ADL_GRAPHIC_CORE_GENERATION_UNDEFINED = 0, ADL_GRAPHIC_CORE_GENERATION_PRE_GCN = 1, ADL_GRAPHIC_CORE_GENERATION_GCN = 2, ADL_GRAPHIC_CORE_GENERATION_RDNA = 3 }; // Other Definitions for internal use // Values for ADL_Display_WriteAndReadI2CRev_Get() #define ADL_I2C_MAJOR_API_REV 0x00000001 #define ADL_I2C_MINOR_DEFAULT_API_REV 0x00000000 #define ADL_I2C_MINOR_OEM_API_REV 0x00000001 // Values for ADL_Display_WriteAndReadI2C() #define ADL_DL_I2C_LINE_OEM 0x00000001 #define ADL_DL_I2C_LINE_OD_CONTROL 0x00000002 #define ADL_DL_I2C_LINE_OEM2 0x00000003 #define ADL_DL_I2C_LINE_OEM3 0x00000004 #define ADL_DL_I2C_LINE_OEM4 0x00000005 #define ADL_DL_I2C_LINE_OEM5 0x00000006 #define ADL_DL_I2C_LINE_OEM6 0x00000007 #define ADL_DL_I2C_LINE_GPIO 0x00000008 // Max size of I2C data buffer #define ADL_DL_I2C_MAXDATASIZE 0x00000018 #define ADL_DL_I2C_MAXWRITEDATASIZE 0x0000000C #define ADL_DL_I2C_MAXADDRESSLENGTH 0x00000006 #define ADL_DL_I2C_MAXOFFSETLENGTH 0x00000004 // I2C clock speed in KHz #define ADL_DL_I2C_SPEED_50K 50 #define ADL_DL_I2C_SPEED_100K 100 #define ALD_DL_I2C_SPEED_400K 400 #define ADL_DL_I2C_SPEED_1M 1000 #define ADL_DL_I2C_SPEED_2M 2300 /// Values for ADLDisplayProperty.iPropertyType #define ADL_DL_DISPLAYPROPERTY_TYPE_UNKNOWN 0 #define ADL_DL_DISPLAYPROPERTY_TYPE_EXPANSIONMODE 1 #define ADL_DL_DISPLAYPROPERTY_TYPE_USEUNDERSCANSCALING 2 /// Enables ITC processing for HDMI panels that are capable of the feature #define ADL_DL_DISPLAYPROPERTY_TYPE_ITCFLAGENABLE 9 #define ADL_DL_DISPLAYPROPERTY_TYPE_DOWNSCALE 11 #define ADL_DL_DISPLAYPROPERTY_TYPE_INTEGER_SCALING 12 /// Values for ADLDisplayContent.iContentType /// Certain HDMI panels that support ITC have support for a feature such that, the display on the panel /// can be adjusted to optimize the view of the content being displayed, depending on the type of content. #define ADL_DL_DISPLAYCONTENT_TYPE_GRAPHICS 1 #define ADL_DL_DISPLAYCONTENT_TYPE_PHOTO 2 #define ADL_DL_DISPLAYCONTENT_TYPE_CINEMA 4 #define ADL_DL_DISPLAYCONTENT_TYPE_GAME 8 //values for ADLDisplayProperty.iExpansionMode #define ADL_DL_DISPLAYPROPERTY_EXPANSIONMODE_CENTER 0 #define ADL_DL_DISPLAYPROPERTY_EXPANSIONMODE_FULLSCREEN 1 #define ADL_DL_DISPLAYPROPERTY_EXPANSIONMODE_ASPECTRATIO 2 ///\defgroup define_dither_states Dithering options /// @{ /// Dithering disabled. #define ADL_DL_DISPLAY_DITHER_DISABLED 0 /// Use default driver settings for dithering. Note that the default setting could be dithering disabled. #define ADL_DL_DISPLAY_DITHER_DRIVER_DEFAULT 1 /// Temporal dithering to 6 bpc. Note that if the input is 12 bits, the two least significant bits will be truncated. #define ADL_DL_DISPLAY_DITHER_FM6 2 /// Temporal dithering to 8 bpc. #define ADL_DL_DISPLAY_DITHER_FM8 3 /// Temporal dithering to 10 bpc. #define ADL_DL_DISPLAY_DITHER_FM10 4 /// Spatial dithering to 6 bpc. Note that if the input is 12 bits, the two least significant bits will be truncated. #define ADL_DL_DISPLAY_DITHER_DITH6 5 /// Spatial dithering to 8 bpc. #define ADL_DL_DISPLAY_DITHER_DITH8 6 /// Spatial dithering to 10 bpc. #define ADL_DL_DISPLAY_DITHER_DITH10 7 /// Spatial dithering to 6 bpc. Random number generators are reset every frame, so the same input value of a certain pixel will always be dithered to the same output value. Note that if the input is 12 bits, the two least significant bits will be truncated. #define ADL_DL_DISPLAY_DITHER_DITH6_NO_FRAME_RAND 8 /// Spatial dithering to 8 bpc. Random number generators are reset every frame, so the same input value of a certain pixel will always be dithered to the same output value. #define ADL_DL_DISPLAY_DITHER_DITH8_NO_FRAME_RAND 9 /// Spatial dithering to 10 bpc. Random number generators are reset every frame, so the same input value of a certain pixel will always be dithered to the same output value. #define ADL_DL_DISPLAY_DITHER_DITH10_NO_FRAME_RAND 10 /// Truncation to 6 bpc. #define ADL_DL_DISPLAY_DITHER_TRUN6 11 /// Truncation to 8 bpc. #define ADL_DL_DISPLAY_DITHER_TRUN8 12 /// Truncation to 10 bpc. #define ADL_DL_DISPLAY_DITHER_TRUN10 13 /// Truncation to 10 bpc followed by spatial dithering to 8 bpc. #define ADL_DL_DISPLAY_DITHER_TRUN10_DITH8 14 /// Truncation to 10 bpc followed by spatial dithering to 6 bpc. #define ADL_DL_DISPLAY_DITHER_TRUN10_DITH6 15 /// Truncation to 10 bpc followed by temporal dithering to 8 bpc. #define ADL_DL_DISPLAY_DITHER_TRUN10_FM8 16 /// Truncation to 10 bpc followed by temporal dithering to 6 bpc. #define ADL_DL_DISPLAY_DITHER_TRUN10_FM6 17 /// Truncation to 10 bpc followed by spatial dithering to 8 bpc and temporal dithering to 6 bpc. #define ADL_DL_DISPLAY_DITHER_TRUN10_DITH8_FM6 18 /// Spatial dithering to 10 bpc followed by temporal dithering to 8 bpc. #define ADL_DL_DISPLAY_DITHER_DITH10_FM8 19 /// Spatial dithering to 10 bpc followed by temporal dithering to 6 bpc. #define ADL_DL_DISPLAY_DITHER_DITH10_FM6 20 /// Truncation to 8 bpc followed by spatial dithering to 6 bpc. #define ADL_DL_DISPLAY_DITHER_TRUN8_DITH6 21 /// Truncation to 8 bpc followed by temporal dithering to 6 bpc. #define ADL_DL_DISPLAY_DITHER_TRUN8_FM6 22 /// Spatial dithering to 8 bpc followed by temporal dithering to 6 bpc. #define ADL_DL_DISPLAY_DITHER_DITH8_FM6 23 #define ADL_DL_DISPLAY_DITHER_LAST ADL_DL_DISPLAY_DITHER_DITH8_FM6 /// @} /// Display Get Cached EDID flag #define ADL_MAX_EDIDDATA_SIZE 256 // number of UCHAR #define ADL_MAX_OVERRIDEEDID_SIZE 512 // number of UCHAR #define ADL_MAX_EDID_EXTENSION_BLOCKS 3 #define ADL_DL_CONTROLLER_OVERLAY_ALPHA 0 #define ADL_DL_CONTROLLER_OVERLAY_ALPHAPERPIX 1 #define ADL_DL_DISPLAY_DATA_PACKET__INFO_PACKET_RESET 0x00000000 #define ADL_DL_DISPLAY_DATA_PACKET__INFO_PACKET_SET 0x00000001 #define ADL_DL_DISPLAY_DATA_PACKET__INFO_PACKET_SCAN 0x00000002 ///\defgroup define_display_packet Display Data Packet Types /// @{ #define ADL_DL_DISPLAY_DATA_PACKET__TYPE__AVI 0x00000001 #define ADL_DL_DISPLAY_DATA_PACKET__TYPE__GAMMUT 0x00000002 #define ADL_DL_DISPLAY_DATA_PACKET__TYPE__VENDORINFO 0x00000004 #define ADL_DL_DISPLAY_DATA_PACKET__TYPE__HDR 0x00000008 #define ADL_DL_DISPLAY_DATA_PACKET__TYPE__SPD 0x00000010 /// @} // matrix types #define ADL_GAMUT_MATRIX_SD 1 // SD matrix i.e. BT601 #define ADL_GAMUT_MATRIX_HD 2 // HD matrix i.e. BT709 ///\defgroup define_clockinfo_flags Clock flags /// Used by ADLAdapterODClockInfo.iFlag /// @{ #define ADL_DL_CLOCKINFO_FLAG_FULLSCREEN3DONLY 0x00000001 #define ADL_DL_CLOCKINFO_FLAG_ALWAYSFULLSCREEN3D 0x00000002 #define ADL_DL_CLOCKINFO_FLAG_VPURECOVERYREDUCED 0x00000004 #define ADL_DL_CLOCKINFO_FLAG_THERMALPROTECTION 0x00000008 /// @} // Supported GPUs // ADL_Display_PowerXpressActiveGPU_Get() #define ADL_DL_POWERXPRESS_GPU_INTEGRATED 1 #define ADL_DL_POWERXPRESS_GPU_DISCRETE 2 // Possible values for lpOperationResult // ADL_Display_PowerXpressActiveGPU_Get() #define ADL_DL_POWERXPRESS_SWITCH_RESULT_STARTED 1 // Switch procedure has been started - Windows platform only #define ADL_DL_POWERXPRESS_SWITCH_RESULT_DECLINED 2 // Switch procedure cannot be started - All platforms #define ADL_DL_POWERXPRESS_SWITCH_RESULT_ALREADY 3 // System already has required status - All platforms #define ADL_DL_POWERXPRESS_SWITCH_RESULT_DEFERRED 5 // Switch was deferred and requires an X restart - Linux platform only // PowerXpress support version // ADL_Display_PowerXpressVersion_Get() #define ADL_DL_POWERXPRESS_VERSION_MAJOR 2 // Current PowerXpress support version 2.0 #define ADL_DL_POWERXPRESS_VERSION_MINOR 0 #define ADL_DL_POWERXPRESS_VERSION (((ADL_DL_POWERXPRESS_VERSION_MAJOR) << 16) | ADL_DL_POWERXPRESS_VERSION_MINOR) //values for ADLThermalControllerInfo.iThermalControllerDomain #define ADL_DL_THERMAL_DOMAIN_OTHER 0 #define ADL_DL_THERMAL_DOMAIN_GPU 1 //values for ADLThermalControllerInfo.iFlags #define ADL_DL_THERMAL_FLAG_INTERRUPT 1 #define ADL_DL_THERMAL_FLAG_FANCONTROL 2 ///\defgroup define_fanctrl Fan speed cotrol /// Values for ADLFanSpeedInfo.iFlags /// @{ #define ADL_DL_FANCTRL_SUPPORTS_PERCENT_READ 1 #define ADL_DL_FANCTRL_SUPPORTS_PERCENT_WRITE 2 #define ADL_DL_FANCTRL_SUPPORTS_RPM_READ 4 #define ADL_DL_FANCTRL_SUPPORTS_RPM_WRITE 8 /// @} //values for ADLFanSpeedValue.iSpeedType #define ADL_DL_FANCTRL_SPEED_TYPE_PERCENT 1 #define ADL_DL_FANCTRL_SPEED_TYPE_RPM 2 //values for ADLFanSpeedValue.iFlags #define ADL_DL_FANCTRL_FLAG_USER_DEFINED_SPEED 1 // MVPU interfaces #define ADL_DL_MAX_MVPU_ADAPTERS 4 #define MVPU_ADAPTER_0 0x00000001 #define MVPU_ADAPTER_1 0x00000002 #define MVPU_ADAPTER_2 0x00000004 #define MVPU_ADAPTER_3 0x00000008 #define ADL_DL_MAX_REGISTRY_PATH 256 //values for ADLMVPUStatus.iStatus #define ADL_DL_MVPU_STATUS_OFF 0 #define ADL_DL_MVPU_STATUS_ON 1 // values for ASIC family ///\defgroup define_Asic_type Detailed asic types /// Defines for Adapter ASIC family type /// @{ #define ADL_ASIC_UNDEFINED 0 #define ADL_ASIC_DISCRETE (1 << 0) #define ADL_ASIC_INTEGRATED (1 << 1) #define ADL_ASIC_WORKSTATION (1 << 2) #define ADL_ASIC_FIREMV (1 << 3) #define ADL_ASIC_XGP (1 << 4) #define ADL_ASIC_FUSION (1 << 5) #define ADL_ASIC_FIRESTREAM (1 << 6) #define ADL_ASIC_EMBEDDED (1 << 7) // Backward compatibility #define ADL_ASIC_FIREGL ADL_ASIC_WORKSTATION /// @} ///\defgroup define_detailed_timing_flags Detailed Timimg Flags /// Defines for ADLDetailedTiming.sTimingFlags field /// @{ #define ADL_DL_TIMINGFLAG_DOUBLE_SCAN 0x0001 //sTimingFlags is set when the mode is INTERLACED, if not PROGRESSIVE #define ADL_DL_TIMINGFLAG_INTERLACED 0x0002 //sTimingFlags is set when the Horizontal Sync is POSITIVE, if not NEGATIVE #define ADL_DL_TIMINGFLAG_H_SYNC_POLARITY 0x0004 //sTimingFlags is set when the Vertical Sync is POSITIVE, if not NEGATIVE #define ADL_DL_TIMINGFLAG_V_SYNC_POLARITY 0x0008 /// @} ///\defgroup define_modetiming_standard Timing Standards /// Defines for ADLDisplayModeInfo.iTimingStandard field /// @{ #define ADL_DL_MODETIMING_STANDARD_CVT 0x00000001 // CVT Standard #define ADL_DL_MODETIMING_STANDARD_GTF 0x00000002 // GFT Standard #define ADL_DL_MODETIMING_STANDARD_DMT 0x00000004 // DMT Standard #define ADL_DL_MODETIMING_STANDARD_CUSTOM 0x00000008 // User-defined standard #define ADL_DL_MODETIMING_STANDARD_DRIVER_DEFAULT 0x00000010 // Remove Mode from overriden list #define ADL_DL_MODETIMING_STANDARD_CVT_RB 0x00000020 // CVT-RB Standard /// @} // \defgroup define_xserverinfo driver x-server info /// These flags are used by ADL_XServerInfo_Get() // @ /// Xinerama is active in the x-server, Xinerama extension may report it to be active but it /// may not be active in x-server #define ADL_XSERVERINFO_XINERAMAACTIVE (1<<0) /// RandR 1.2 is supported by driver, RandR extension may report version 1.2 /// but driver may not support it #define ADL_XSERVERINFO_RANDR12SUPPORTED (1<<1) // @ ///\defgroup define_eyefinity_constants Eyefinity Definitions /// @{ #define ADL_CONTROLLERVECTOR_0 1 // ADL_CONTROLLERINDEX_0 = 0, (1 << ADL_CONTROLLERINDEX_0) #define ADL_CONTROLLERVECTOR_1 2 // ADL_CONTROLLERINDEX_1 = 1, (1 << ADL_CONTROLLERINDEX_1) #define ADL_DISPLAY_SLSGRID_ORIENTATION_000 0x00000001 #define ADL_DISPLAY_SLSGRID_ORIENTATION_090 0x00000002 #define ADL_DISPLAY_SLSGRID_ORIENTATION_180 0x00000004 #define ADL_DISPLAY_SLSGRID_ORIENTATION_270 0x00000008 #define ADL_DISPLAY_SLSGRID_CAP_OPTION_RELATIVETO_LANDSCAPE 0x00000001 #define ADL_DISPLAY_SLSGRID_CAP_OPTION_RELATIVETO_CURRENTANGLE 0x00000002 #define ADL_DISPLAY_SLSGRID_PORTAIT_MODE 0x00000004 #define ADL_DISPLAY_SLSGRID_KEEPTARGETROTATION 0x00000080 #define ADL_DISPLAY_SLSGRID_SAMEMODESLS_SUPPORT 0x00000010 #define ADL_DISPLAY_SLSGRID_MIXMODESLS_SUPPORT 0x00000020 #define ADL_DISPLAY_SLSGRID_DISPLAYROTATION_SUPPORT 0x00000040 #define ADL_DISPLAY_SLSGRID_DESKTOPROTATION_SUPPORT 0x00000080 #define ADL_DISPLAY_SLSMAP_SLSLAYOUTMODE_FIT 0x0100 #define ADL_DISPLAY_SLSMAP_SLSLAYOUTMODE_FILL 0x0200 #define ADL_DISPLAY_SLSMAP_SLSLAYOUTMODE_EXPAND 0x0400 #define ADL_DISPLAY_SLSMAP_IS_SLS 0x1000 #define ADL_DISPLAY_SLSMAP_IS_SLSBUILDER 0x2000 #define ADL_DISPLAY_SLSMAP_IS_CLONEVT 0x4000 #define ADL_DISPLAY_SLSMAPCONFIG_GET_OPTION_RELATIVETO_LANDSCAPE 0x00000001 #define ADL_DISPLAY_SLSMAPCONFIG_GET_OPTION_RELATIVETO_CURRENTANGLE 0x00000002 #define ADL_DISPLAY_SLSMAPCONFIG_CREATE_OPTION_RELATIVETO_LANDSCAPE 0x00000001 #define ADL_DISPLAY_SLSMAPCONFIG_CREATE_OPTION_RELATIVETO_CURRENTANGLE 0x00000002 #define ADL_DISPLAY_SLSMAPCONFIG_REARRANGE_OPTION_RELATIVETO_LANDSCAPE 0x00000001 #define ADL_DISPLAY_SLSMAPCONFIG_REARRANGE_OPTION_RELATIVETO_CURRENTANGLE 0x00000002 #define ADL_SLS_SAMEMODESLS_SUPPORT 0x0001 #define ADL_SLS_MIXMODESLS_SUPPORT 0x0002 #define ADL_SLS_DISPLAYROTATIONSLS_SUPPORT 0x0004 #define ADL_SLS_DESKTOPROTATIONSLS_SUPPORT 0x0008 #define ADL_SLS_TARGETS_INVALID 0x0001 #define ADL_SLS_MODES_INVALID 0x0002 #define ADL_SLS_ROTATIONS_INVALID 0x0004 #define ADL_SLS_POSITIONS_INVALID 0x0008 #define ADL_SLS_LAYOUTMODE_INVALID 0x0010 #define ADL_DISPLAY_SLSDISPLAYOFFSET_VALID 0x0002 #define ADL_DISPLAY_SLSGRID_RELATIVETO_LANDSCAPE 0x00000010 #define ADL_DISPLAY_SLSGRID_RELATIVETO_CURRENTANGLE 0x00000020 /// The bit mask identifies displays is currently in bezel mode. #define ADL_DISPLAY_SLSMAP_BEZELMODE 0x00000010 /// The bit mask identifies displays from this map is arranged. #define ADL_DISPLAY_SLSMAP_DISPLAYARRANGED 0x00000002 /// The bit mask identifies this map is currently in used for the current adapter. #define ADL_DISPLAY_SLSMAP_CURRENTCONFIG 0x00000004 ///For onlay active SLS map info #define ADL_DISPLAY_SLSMAPINDEXLIST_OPTION_ACTIVE 0x00000001 ///For Bezel #define ADL_DISPLAY_BEZELOFFSET_STEPBYSTEPSET 0x00000004 #define ADL_DISPLAY_BEZELOFFSET_COMMIT 0x00000008 typedef enum SLS_ImageCropType { Fit = 1, Fill = 2, Expand = 3 }SLS_ImageCropType; typedef enum DceSettingsType { DceSetting_HdmiLq, DceSetting_DpSettings, DceSetting_Protection } DceSettingsType; typedef enum DpLinkRate { DPLinkRate_Unknown, DPLinkRate_RBR, DPLinkRate_2_16Gbps, DPLinkRate_2_43Gbps, DPLinkRate_HBR, DPLinkRate_4_32Gbps, DPLinkRate_HBR2, DPLinkRate_HBR3, DPLinkRate_UHBR10, DPLinkRate_UHBR13D5, DPLinkRate_UHBR20 } DpLinkRate; /// @} ///\defgroup define_powerxpress_constants PowerXpress Definitions /// @{ /// The bit mask identifies PX caps for ADLPXConfigCaps.iPXConfigCapMask and ADLPXConfigCaps.iPXConfigCapValue #define ADL_PX_CONFIGCAPS_SPLASHSCREEN_SUPPORT 0x0001 #define ADL_PX_CONFIGCAPS_CF_SUPPORT 0x0002 #define ADL_PX_CONFIGCAPS_MUXLESS 0x0004 #define ADL_PX_CONFIGCAPS_PROFILE_COMPLIANT 0x0008 #define ADL_PX_CONFIGCAPS_NON_AMD_DRIVEN_DISPLAYS 0x0010 #define ADL_PX_CONFIGCAPS_FIXED_SUPPORT 0x0020 #define ADL_PX_CONFIGCAPS_DYNAMIC_SUPPORT 0x0040 #define ADL_PX_CONFIGCAPS_HIDE_AUTO_SWITCH 0x0080 /// The bit mask identifies PX schemes for ADLPXSchemeRange #define ADL_PX_SCHEMEMASK_FIXED 0x0001 #define ADL_PX_SCHEMEMASK_DYNAMIC 0x0002 /// PX Schemes typedef enum ADLPXScheme { ADL_PX_SCHEME_INVALID = 0, ADL_PX_SCHEME_FIXED = ADL_PX_SCHEMEMASK_FIXED, ADL_PX_SCHEME_DYNAMIC = ADL_PX_SCHEMEMASK_DYNAMIC }ADLPXScheme; /// Just keep the old definitions for compatibility, need to be removed later typedef enum PXScheme { PX_SCHEME_INVALID = 0, PX_SCHEME_FIXED = 1, PX_SCHEME_DYNAMIC = 2 } PXScheme; /// @} ///\defgroup define_appprofiles For Application Profiles /// @{ #define ADL_APP_PROFILE_FILENAME_LENGTH 256 #define ADL_APP_PROFILE_TIMESTAMP_LENGTH 32 #define ADL_APP_PROFILE_VERSION_LENGTH 32 #define ADL_APP_PROFILE_PROPERTY_LENGTH 64 enum ApplicationListType { ADL_PX40_MRU, ADL_PX40_MISSED, ADL_PX40_DISCRETE, ADL_PX40_INTEGRATED, ADL_MMD_PROFILED, ADL_PX40_TOTAL }; typedef enum ADLProfilePropertyType { ADL_PROFILEPROPERTY_TYPE_BINARY = 0, ADL_PROFILEPROPERTY_TYPE_BOOLEAN, ADL_PROFILEPROPERTY_TYPE_DWORD, ADL_PROFILEPROPERTY_TYPE_QWORD, ADL_PROFILEPROPERTY_TYPE_ENUMERATED, ADL_PROFILEPROPERTY_TYPE_STRING }ADLProfilePropertyType; //Virtual display type returning virtual display type and for request for creating a dummy target ID (xInput or remote play) typedef enum ADL_VIRTUALDISPLAY_TYPE { ADL_VIRTUALDISPLAY_NONE = 0, ADL_VIRTUALDISPLAY_XINPUT = 1, //Requested for xInput ADL_VIRTUALDISPLAY_REMOTEPLAY = 2, //Requested for emulated display during remote play ADL_VIRTUALDISPLAY_GENERIC = 10 //Generic virtual display, af a type different than any of the above special ones }ADL_VIRTUALDISPLAY_TYPE; /// @} ///\defgroup define_dp12 For Display Port 1.2 /// @{ /// Maximum Relative Address Link #define ADL_MAX_RAD_LINK_COUNT 15 /// @} ///\defgroup defines_gamutspace Driver Supported Gamut Space /// @{ /// The flags desribes that gamut is related to source or to destination and to overlay or to graphics #define ADL_GAMUT_REFERENCE_SOURCE (1 << 0) #define ADL_GAMUT_GAMUT_VIDEO_CONTENT (1 << 1) /// The flags are used to describe the source of gamut and how read information from struct ADLGamutData #define ADL_CUSTOM_WHITE_POINT (1 << 0) #define ADL_CUSTOM_GAMUT (1 << 1) #define ADL_GAMUT_REMAP_ONLY (1 << 2) /// The define means the predefined gamut values . ///Driver uses to find entry in the table and apply appropriate gamut space. #define ADL_GAMUT_SPACE_CCIR_709 (1 << 0) #define ADL_GAMUT_SPACE_CCIR_601 (1 << 1) #define ADL_GAMUT_SPACE_ADOBE_RGB (1 << 2) #define ADL_GAMUT_SPACE_CIE_RGB (1 << 3) #define ADL_GAMUT_SPACE_CUSTOM (1 << 4) #define ADL_GAMUT_SPACE_CCIR_2020 (1 << 5) #define ADL_GAMUT_SPACE_APPCTRL (1 << 6) /// Predefine white point values are structed similar to gamut . #define ADL_WHITE_POINT_5000K (1 << 0) #define ADL_WHITE_POINT_6500K (1 << 1) #define ADL_WHITE_POINT_7500K (1 << 2) #define ADL_WHITE_POINT_9300K (1 << 3) #define ADL_WHITE_POINT_CUSTOM (1 << 4) ///gamut and white point coordinates are from 0.0 -1.0 and divider is used to find the real value . /// X float = X int /divider #define ADL_GAMUT_WHITEPOINT_DIVIDER 10000 ///gamma a0 coefficient uses the following divider: #define ADL_REGAMMA_COEFFICIENT_A0_DIVIDER 10000000 ///gamma a1 ,a2,a3 coefficients use the following divider: #define ADL_REGAMMA_COEFFICIENT_A1A2A3_DIVIDER 1000 ///describes whether the coefficients are from EDID or custom user values. #define ADL_EDID_REGAMMA_COEFFICIENTS (1 << 0) ///Used for struct ADLRegamma. Feature if set use gamma ramp, if missing use regamma coefficents #define ADL_USE_GAMMA_RAMP (1 << 4) ///Used for struct ADLRegamma. If the gamma ramp flag is used then the driver could apply de gamma corretion to the supplied curve and this depends on this flag #define ADL_APPLY_DEGAMMA (1 << 5) ///specifies that standard SRGB gamma should be applied #define ADL_EDID_REGAMMA_PREDEFINED_SRGB (1 << 1) ///specifies that PQ gamma curve should be applied #define ADL_EDID_REGAMMA_PREDEFINED_PQ (1 << 2) ///specifies that PQ gamma curve should be applied, lower max nits #define ADL_EDID_REGAMMA_PREDEFINED_PQ_2084_INTERIM (1 << 3) ///specifies that 3.6 gamma should be applied #define ADL_EDID_REGAMMA_PREDEFINED_36 (1 << 6) ///specifies that BT709 gama should be applied #define ADL_EDID_REGAMMA_PREDEFINED_BT709 (1 << 7) ///specifies that regamma should be disabled, and application controls regamma content (of the whole screen) #define ADL_EDID_REGAMMA_PREDEFINED_APPCTRL (1 << 8) /// @} /// \defgroup define_ddcinfo_pixelformats DDCInfo Pixel Formats /// @{ /// defines for iPanelPixelFormat in struct ADLDDCInfo2 #define ADL_DISPLAY_DDCINFO_PIXEL_FORMAT_RGB656 0x00000001L #define ADL_DISPLAY_DDCINFO_PIXEL_FORMAT_RGB666 0x00000002L #define ADL_DISPLAY_DDCINFO_PIXEL_FORMAT_RGB888 0x00000004L #define ADL_DISPLAY_DDCINFO_PIXEL_FORMAT_RGB101010 0x00000008L #define ADL_DISPLAY_DDCINFO_PIXEL_FORMAT_RGB161616 0x00000010L #define ADL_DISPLAY_DDCINFO_PIXEL_FORMAT_RGB_RESERVED1 0x00000020L #define ADL_DISPLAY_DDCINFO_PIXEL_FORMAT_RGB_RESERVED2 0x00000040L #define ADL_DISPLAY_DDCINFO_PIXEL_FORMAT_RGB_RESERVED3 0x00000080L #define ADL_DISPLAY_DDCINFO_PIXEL_FORMAT_XRGB_BIAS101010 0x00000100L #define ADL_DISPLAY_DDCINFO_PIXEL_FORMAT_YCBCR444_8BPCC 0x00000200L #define ADL_DISPLAY_DDCINFO_PIXEL_FORMAT_YCBCR444_10BPCC 0x00000400L #define ADL_DISPLAY_DDCINFO_PIXEL_FORMAT_YCBCR444_12BPCC 0x00000800L #define ADL_DISPLAY_DDCINFO_PIXEL_FORMAT_YCBCR422_8BPCC 0x00001000L #define ADL_DISPLAY_DDCINFO_PIXEL_FORMAT_YCBCR422_10BPCC 0x00002000L #define ADL_DISPLAY_DDCINFO_PIXEL_FORMAT_YCBCR422_12BPCC 0x00004000L #define ADL_DISPLAY_DDCINFO_PIXEL_FORMAT_YCBCR420_8BPCC 0x00008000L #define ADL_DISPLAY_DDCINFO_PIXEL_FORMAT_YCBCR420_10BPCC 0x00010000L #define ADL_DISPLAY_DDCINFO_PIXEL_FORMAT_YCBCR420_12BPCC 0x00020000L /// @} /// \defgroup define_source_content_TF ADLSourceContentAttributes transfer functions (gamma) /// @{ /// defines for iTransferFunction in ADLSourceContentAttributes #define ADL_TF_sRGB 0x0001 ///< sRGB #define ADL_TF_BT709 0x0002 ///< BT.709 #define ADL_TF_PQ2084 0x0004 ///< PQ2084 #define ADL_TF_PQ2084_INTERIM 0x0008 ///< PQ2084-Interim #define ADL_TF_LINEAR_0_1 0x0010 ///< Linear 0 - 1 #define ADL_TF_LINEAR_0_125 0x0020 ///< Linear 0 - 125 #define ADL_TF_DOLBYVISION 0x0040 ///< DolbyVision #define ADL_TF_GAMMA_22 0x0080 ///< Plain 2.2 gamma curve /// @} /// \defgroup define_source_content_CS ADLSourceContentAttributes color spaces /// @{ /// defines for iColorSpace in ADLSourceContentAttributes #define ADL_CS_sRGB 0x0001 ///< sRGB #define ADL_CS_BT601 0x0002 ///< BT.601 #define ADL_CS_BT709 0x0004 ///< BT.709 #define ADL_CS_BT2020 0x0008 ///< BT.2020 #define ADL_CS_ADOBE 0x0010 ///< Adobe RGB #define ADL_CS_P3 0x0020 ///< DCI-P3 #define ADL_CS_scRGB_MS_REF 0x0040 ///< scRGB (MS Reference) #define ADL_CS_DISPLAY_NATIVE 0x0080 ///< Display Native #define ADL_CS_APP_CONTROL 0x0100 ///< Application Controlled #define ADL_CS_DOLBYVISION 0x0200 ///< DolbyVision /// @} /// \defgroup define_HDR_support ADLDDCInfo2 HDR support options /// @{ /// defines for iSupportedHDR in ADLDDCInfo2 #define ADL_HDR_CEA861_3 0x0001 ///< HDR10/CEA861.3 HDR supported #define ADL_HDR_DOLBYVISION 0x0002 ///< \deprecated DolbyVision HDR supported #define ADL_HDR_FREESYNC_HDR 0x0004 ///< FreeSync HDR supported /// @} /// \defgroup define_FreesyncFlags ADLDDCInfo2 Freesync HDR flags /// @{ /// defines for iFreesyncFlags in ADLDDCInfo2 #define ADL_HDR_FREESYNC_BACKLIGHT_SUPPORT 0x0001 ///< Global backlight control supported #define ADL_HDR_FREESYNC_LOCAL_DIMMING 0x0002 ///< Local dimming supported /// @} /// \defgroup define_source_content_flags ADLSourceContentAttributes flags /// @{ /// defines for iFlags in ADLSourceContentAttributes #define ADL_SCA_LOCAL_DIMMING_DISABLE 0x0001 ///< Disable local dimming /// @} /// \defgroup define_dbd_state Deep Bit Depth /// @{ /// defines for ADL_Workstation_DeepBitDepth_Get and ADL_Workstation_DeepBitDepth_Set functions // This value indicates that the deep bit depth state is forced off #define ADL_DEEPBITDEPTH_FORCEOFF 0 /// This value indicates that the deep bit depth state is set to auto, the driver will automatically enable the /// appropriate deep bit depth state depending on what connected display supports. #define ADL_DEEPBITDEPTH_10BPP_AUTO 1 /// This value indicates that the deep bit depth state is forced on to 10 bits per pixel, this is regardless if the display /// supports 10 bpp. #define ADL_DEEPBITDEPTH_10BPP_FORCEON 2 /// defines for ADLAdapterConfigMemory of ADL_Adapter_ConfigMemory_Get /// If this bit is set, it indicates that the Deep Bit Depth pixel is set on the display #define ADL_ADAPTER_CONFIGMEMORY_DBD (1 << 0) /// If this bit is set, it indicates that the display is rotated (90, 180 or 270) #define ADL_ADAPTER_CONFIGMEMORY_ROTATE (1 << 1) /// If this bit is set, it indicates that passive stereo is set on the display #define ADL_ADAPTER_CONFIGMEMORY_STEREO_PASSIVE (1 << 2) /// If this bit is set, it indicates that the active stereo is set on the display #define ADL_ADAPTER_CONFIGMEMORY_STEREO_ACTIVE (1 << 3) /// If this bit is set, it indicates that the tear free vsync is set on the display #define ADL_ADAPTER_CONFIGMEMORY_ENHANCEDVSYNC (1 << 4) #define ADL_ADAPTER_CONFIGMEMORY_TEARFREEVSYNC (1 << 4) /// @} /// \defgroup define_adl_validmemoryrequiredfields Memory Type /// @{ /// This group defines memory types in ADLMemoryRequired struct \n /// Indicates that this is the visible memory #define ADL_MEMORYREQTYPE_VISIBLE (1 << 0) /// Indicates that this is the invisible memory. #define ADL_MEMORYREQTYPE_INVISIBLE (1 << 1) /// Indicates that this is amount of visible memory per GPU that should be reserved for all other allocations. #define ADL_MEMORYREQTYPE_GPURESERVEDVISIBLE (1 << 2) /// @} /// \defgroup define_adapter_tear_free_status /// Used in ADL_Adapter_TEAR_FREE_Set and ADL_Adapter_TFD_Get functions to indicate the tear free /// desktop status. /// @{ /// Tear free desktop is enabled. #define ADL_ADAPTER_TEAR_FREE_ON 1 /// Tear free desktop can't be enabled due to a lack of graphic adapter memory. #define ADL_ADAPTER_TEAR_FREE_NOTENOUGHMEM -1 /// Tear free desktop can't be enabled due to quad buffer stereo being enabled. #define ADL_ADAPTER_TEAR_FREE_OFF_ERR_QUADBUFFERSTEREO -2 /// Tear free desktop can't be enabled due to MGPU-SLS being enabled. #define ADL_ADAPTER_TEAR_FREE_OFF_ERR_MGPUSLD -3 /// Tear free desktop is disabled. #define ADL_ADAPTER_TEAR_FREE_OFF 0 /// @} /// \defgroup define_adapter_crossdisplay_platforminfo /// Used in ADL_Adapter_CrossDisplayPlatformInfo_Get function to indicate the Crossdisplay platform info. /// @{ /// CROSSDISPLAY platform. #define ADL_CROSSDISPLAY_PLATFORM (1 << 0) /// CROSSDISPLAY platform for Lasso station. #define ADL_CROSSDISPLAY_PLATFORM_LASSO (1 << 1) /// CROSSDISPLAY platform for docking station. #define ADL_CROSSDISPLAY_PLATFORM_DOCKSTATION (1 << 2) /// @} /// \defgroup define_adapter_crossdisplay_option /// Used in ADL_Adapter_CrossdisplayInfoX2_Set function to indicate cross display options. /// @{ /// Checking if 3D application is runnning. If yes, not to do switch, return ADL_OK_WAIT; otherwise do switch. #define ADL_CROSSDISPLAY_OPTION_NONE 0 /// Force switching without checking for running 3D applications #define ADL_CROSSDISPLAY_OPTION_FORCESWITCH (1 << 0) /// @} /// \defgroup define_adapter_states Adapter Capabilities /// These defines the capabilities supported by an adapter. It is used by \ref ADL_Adapter_ConfigureState_Get /// @{ /// Indicates that the adapter is headless (i.e. no displays can be connected to it) #define ADL_ADAPTERCONFIGSTATE_HEADLESS ( 1 << 2 ) /// Indicates that the adapter is configured to define the main rendering capabilities. For example, adapters /// in Crossfire(TM) configuration, this bit would only be set on the adapter driving the display(s). #define ADL_ADAPTERCONFIGSTATE_REQUISITE_RENDER ( 1 << 0 ) /// Indicates that the adapter is configured to be used to unload some of the rendering work for a particular /// requisite rendering adapter. For eample, for adapters in a Crossfire configuration, this bit would be set /// on all adapters that are currently not driving the display(s) #define ADL_ADAPTERCONFIGSTATE_ANCILLARY_RENDER ( 1 << 1 ) /// Indicates that scatter gather feature enabled on the adapter #define ADL_ADAPTERCONFIGSTATE_SCATTERGATHER ( 1 << 4 ) /// @} /// \defgroup define_controllermode_ulModifiers /// These defines the detailed actions supported by set viewport. It is used by \ref ADL_Display_ViewPort_Set /// @{ /// Indicate that the viewport set will change the view position #define ADL_CONTROLLERMODE_CM_MODIFIER_VIEW_POSITION 0x00000001 /// Indicate that the viewport set will change the view PanLock #define ADL_CONTROLLERMODE_CM_MODIFIER_VIEW_PANLOCK 0x00000002 /// Indicate that the viewport set will change the view size #define ADL_CONTROLLERMODE_CM_MODIFIER_VIEW_SIZE 0x00000008 /// @} /// \defgroup defines for Mirabilis /// These defines are used for the Mirabilis feature /// @{ /// /// Indicates the maximum number of audio sample rates #define ADL_MAX_AUDIO_SAMPLE_RATE_COUNT 16 /// @} /////////////////////////////////////////////////////////////////////////// // ADLMultiChannelSplitStateFlag Enumeration /////////////////////////////////////////////////////////////////////////// enum ADLMultiChannelSplitStateFlag { ADLMultiChannelSplit_Unitialized = 0, ADLMultiChannelSplit_Disabled = 1, ADLMultiChannelSplit_Enabled = 2, ADLMultiChannelSplit_SaveProfile = 3 }; /////////////////////////////////////////////////////////////////////////// // ADLSampleRate Enumeration /////////////////////////////////////////////////////////////////////////// enum ADLSampleRate { ADLSampleRate_32KHz =0, ADLSampleRate_44P1KHz, ADLSampleRate_48KHz, ADLSampleRate_88P2KHz, ADLSampleRate_96KHz, ADLSampleRate_176P4KHz, ADLSampleRate_192KHz, ADLSampleRate_384KHz, //DP1.2 ADLSampleRate_768KHz, //DP1.2 ADLSampleRate_Undefined }; /// \defgroup define_overdrive6_capabilities /// These defines the capabilities supported by Overdrive 6. It is used by \ref ADL_Overdrive6_Capabilities_Get /// @{ /// Indicate that core (engine) clock can be changed. #define ADL_OD6_CAPABILITY_SCLK_CUSTOMIZATION 0x00000001 /// Indicate that memory clock can be changed. #define ADL_OD6_CAPABILITY_MCLK_CUSTOMIZATION 0x00000002 /// Indicate that graphics activity reporting is supported. #define ADL_OD6_CAPABILITY_GPU_ACTIVITY_MONITOR 0x00000004 /// Indicate that power limit can be customized. #define ADL_OD6_CAPABILITY_POWER_CONTROL 0x00000008 /// Indicate that SVI2 Voltage Control is supported. #define ADL_OD6_CAPABILITY_VOLTAGE_CONTROL 0x00000010 /// Indicate that OD6+ percentage adjustment is supported. #define ADL_OD6_CAPABILITY_PERCENT_ADJUSTMENT 0x00000020 /// Indicate that Thermal Limit Unlock is supported. #define ADL_OD6_CAPABILITY_THERMAL_LIMIT_UNLOCK 0x00000040 ///Indicate that Fan speed needs to be displayed in RPM #define ADL_OD6_CAPABILITY_FANSPEED_IN_RPM 0x00000080 /// @} /// \defgroup define_overdrive6_supported_states /// These defines the power states supported by Overdrive 6. It is used by \ref ADL_Overdrive6_Capabilities_Get /// @{ /// Indicate that overdrive is supported in the performance state. This is currently the only state supported. #define ADL_OD6_SUPPORTEDSTATE_PERFORMANCE 0x00000001 /// Do not use. Reserved for future use. #define ADL_OD6_SUPPORTEDSTATE_POWER_SAVING 0x00000002 /// @} /// \defgroup define_overdrive6_getstateinfo /// These defines the power states to get information about. It is used by \ref ADL_Overdrive6_StateInfo_Get /// @{ /// Get default clocks for the performance state. #define ADL_OD6_GETSTATEINFO_DEFAULT_PERFORMANCE 0x00000001 /// Do not use. Reserved for future use. #define ADL_OD6_GETSTATEINFO_DEFAULT_POWER_SAVING 0x00000002 /// Get clocks for current state. Currently this is the same as \ref ADL_OD6_GETSTATEINFO_CUSTOM_PERFORMANCE /// since only performance state is supported. #define ADL_OD6_GETSTATEINFO_CURRENT 0x00000003 /// Get the modified clocks (if any) for the performance state. If clocks were not modified /// through Overdrive 6, then this will return the same clocks as \ref ADL_OD6_GETSTATEINFO_DEFAULT_PERFORMANCE. #define ADL_OD6_GETSTATEINFO_CUSTOM_PERFORMANCE 0x00000004 /// Do not use. Reserved for future use. #define ADL_OD6_GETSTATEINFO_CUSTOM_POWER_SAVING 0x00000005 /// @} /// \defgroup define_overdrive6_getstate and define_overdrive6_getmaxclockadjust /// These defines the power states to get information about. It is used by \ref ADL_Overdrive6_StateEx_Get and \ref ADL_Overdrive6_MaxClockAdjust_Get /// @{ /// Get default clocks for the performance state. Only performance state is currently supported. #define ADL_OD6_STATE_PERFORMANCE 0x00000001 /// @} /// \defgroup define_overdrive6_setstate /// These define which power state to set customized clocks on. It is used by \ref ADL_Overdrive6_State_Set /// @{ /// Set customized clocks for the performance state. #define ADL_OD6_SETSTATE_PERFORMANCE 0x00000001 /// Do not use. Reserved for future use. #define ADL_OD6_SETSTATE_POWER_SAVING 0x00000002 /// @} /// \defgroup define_overdrive6_thermalcontroller_caps /// These defines the capabilities of the GPU thermal controller. It is used by \ref ADL_Overdrive6_ThermalController_Caps /// @{ /// GPU thermal controller is supported. #define ADL_OD6_TCCAPS_THERMAL_CONTROLLER 0x00000001 /// GPU fan speed control is supported. #define ADL_OD6_TCCAPS_FANSPEED_CONTROL 0x00000002 /// Fan speed percentage can be read. #define ADL_OD6_TCCAPS_FANSPEED_PERCENT_READ 0x00000100 /// Fan speed can be set by specifying a percentage value. #define ADL_OD6_TCCAPS_FANSPEED_PERCENT_WRITE 0x00000200 /// Fan speed RPM (revolutions-per-minute) can be read. #define ADL_OD6_TCCAPS_FANSPEED_RPM_READ 0x00000400 /// Fan speed can be set by specifying an RPM value. #define ADL_OD6_TCCAPS_FANSPEED_RPM_WRITE 0x00000800 /// @} /// \defgroup define_overdrive6_fanspeed_type /// These defines the fan speed type being reported. It is used by \ref ADL_Overdrive6_FanSpeed_Get /// @{ /// Fan speed reported in percentage. #define ADL_OD6_FANSPEED_TYPE_PERCENT 0x00000001 /// Fan speed reported in RPM. #define ADL_OD6_FANSPEED_TYPE_RPM 0x00000002 /// Fan speed has been customized by the user, and fan is not running in automatic mode. #define ADL_OD6_FANSPEED_USER_DEFINED 0x00000100 /// @} /// \defgroup define_overdrive_EventCounter_type /// These defines the EventCounter type being reported. It is used by \ref ADL2_OverdriveN_CountOfEvents_Get ,can be used on older OD version supported ASICs also. /// @{ #define ADL_ODN_EVENTCOUNTER_THERMAL 0 #define ADL_ODN_EVENTCOUNTER_VPURECOVERY 1 /// @} /////////////////////////////////////////////////////////////////////////// // ADLODNControlType Enumeration /////////////////////////////////////////////////////////////////////////// enum ADLODNControlType { ODNControlType_Current = 0, ODNControlType_Default, ODNControlType_Auto, ODNControlType_Manual }; enum ADLODNDPMMaskType { ADL_ODN_DPM_CLOCK = 1 << 0, ADL_ODN_DPM_VDDC = 1 << 1, ADL_ODN_DPM_MASK = 1 << 2, }; //ODN features Bits for ADLODNCapabilitiesX2 enum ADLODNFeatureControl { ADL_ODN_SCLK_DPM = 1 << 0, ADL_ODN_MCLK_DPM = 1 << 1, ADL_ODN_SCLK_VDD = 1 << 2, ADL_ODN_MCLK_VDD = 1 << 3, ADL_ODN_FAN_SPEED_MIN = 1 << 4, ADL_ODN_FAN_SPEED_TARGET = 1 << 5, ADL_ODN_ACOUSTIC_LIMIT_SCLK = 1 << 6, ADL_ODN_TEMPERATURE_FAN_MAX = 1 << 7, ADL_ODN_TEMPERATURE_SYSTEM = 1 << 8, ADL_ODN_POWER_LIMIT = 1 << 9, ADL_ODN_SCLK_AUTO_LIMIT = 1 << 10, ADL_ODN_MCLK_AUTO_LIMIT = 1 << 11, ADL_ODN_SCLK_DPM_MASK_ENABLE = 1 << 12, ADL_ODN_MCLK_DPM_MASK_ENABLE = 1 << 13, ADL_ODN_MCLK_UNDERCLOCK_ENABLE = 1 << 14, ADL_ODN_SCLK_DPM_THROTTLE_NOTIFY = 1 << 15, ADL_ODN_POWER_UTILIZATION = 1 << 16, ADL_ODN_PERF_TUNING_SLIDER = 1 << 17, ADL_ODN_REMOVE_WATTMAN_PAGE = 1 << 31 // Internal Only }; //If any new feature is added, PPLIB only needs to add ext feature ID and Item ID(Seeting ID). These IDs should match the drive defined in CWDDEPM.h enum ADLODNExtFeatureControl { ADL_ODN_EXT_FEATURE_MEMORY_TIMING_TUNE = 1 << 0, ADL_ODN_EXT_FEATURE_FAN_ZERO_RPM_CONTROL = 1 << 1, ADL_ODN_EXT_FEATURE_AUTO_UV_ENGINE = 1 << 2, //Auto under voltage ADL_ODN_EXT_FEATURE_AUTO_OC_ENGINE = 1 << 3, //Auto OC Enine ADL_ODN_EXT_FEATURE_AUTO_OC_MEMORY = 1 << 4, //Auto OC memory ADL_ODN_EXT_FEATURE_FAN_CURVE = 1 << 5 //Fan curve }; //If any new feature is added, PPLIB only needs to add ext feature ID and Item ID(Seeting ID).These IDs should match the drive defined in CWDDEPM.h enum ADLODNExtSettingId { ADL_ODN_PARAMETER_AC_TIMING = 0, ADL_ODN_PARAMETER_FAN_ZERO_RPM_CONTROL, ADL_ODN_PARAMETER_AUTO_UV_ENGINE, ADL_ODN_PARAMETER_AUTO_OC_ENGINE, ADL_ODN_PARAMETER_AUTO_OC_MEMORY, ADL_ODN_PARAMETER_FAN_CURVE_TEMPERATURE_1, ADL_ODN_PARAMETER_FAN_CURVE_SPEED_1, ADL_ODN_PARAMETER_FAN_CURVE_TEMPERATURE_2, ADL_ODN_PARAMETER_FAN_CURVE_SPEED_2, ADL_ODN_PARAMETER_FAN_CURVE_TEMPERATURE_3, ADL_ODN_PARAMETER_FAN_CURVE_SPEED_3, ADL_ODN_PARAMETER_FAN_CURVE_TEMPERATURE_4, ADL_ODN_PARAMETER_FAN_CURVE_SPEED_4, ADL_ODN_PARAMETER_FAN_CURVE_TEMPERATURE_5, ADL_ODN_PARAMETER_FAN_CURVE_SPEED_5, ADL_ODN_POWERGAUGE, ODN_COUNT } ; //OD8 Capability features bits enum ADLOD8FeatureControl { ADL_OD8_GFXCLK_LIMITS = 1 << 0, ADL_OD8_GFXCLK_CURVE = 1 << 1, ADL_OD8_UCLK_MAX = 1 << 2, ADL_OD8_POWER_LIMIT = 1 << 3, ADL_OD8_ACOUSTIC_LIMIT_SCLK = 1 << 4, //FanMaximumRpm ADL_OD8_FAN_SPEED_MIN = 1 << 5, //FanMinimumPwm 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_GFXCLK_QUADRATIC_CURVE = 1 << 15, ADL_OD8_OPTIMIZED_GPU_POWER_MODE = 1 << 16, ADL_OD8_ODVOLTAGE_LIMIT = 1 << 17, ADL_OD8_ADV_OC_LIMITS = 1 << 18, //Advanced OC limits. ADL_OD8_PER_ZONE_GFX_VOLTAGE_OFFSET = 1 << 19, //Per Zone gfx voltage offset feature ADL_OD8_AUTO_CURVE_OPTIMIZER = 1 << 20, //Auto per zone tuning. ADL_OD8_GFX_VOLTAGE_LIMIT = 1 << 21, //Voltage limit slider 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 }; 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_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, OD8_UCLK_FMIN, OD8_FAN_ZERO_RPM_STOP_TEMPERATURE, OD8_OPTIMZED_POWER_MODE, 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_3, OD8_PER_ZONE_GFX_VOLTAGE_OFFSET_POINT_4, OD8_PER_ZONE_GFX_VOLTAGE_OFFSET_POINT_5, OD8_PER_ZONE_GFX_VOLTAGE_OFFSET_POINT_6, OD8_AUTO_CURVE_OPTIMIZER_SETTING, OD8_GFX_VOLTAGE_LIMIT_SETTING, OD8_TDC_PERCENTAGE, OD8_FULL_CONTROL_MODE_SETTING, OD8_IDLE_POWER_SAVING_FEATURE_CONTROL, OD8_RUNTIME_POWER_SAVING_FEATURE_CONTROL, OD8_POWER_GAUGE, OD8_COUNT } ADLOD8SettingId; //Define Performance Metrics Log max sensors number #define ADL_PMLOG_MAX_SENSORS 256 /// \deprecated Replaced with ADL_PMLOG_SENSORS typedef enum ADLSensorType { SENSOR_MAXTYPES = 0, PMLOG_CLK_GFXCLK = 1, // Current graphic clock value in MHz PMLOG_CLK_MEMCLK = 2, // Current memory clock value in MHz PMLOG_CLK_SOCCLK = 3, PMLOG_CLK_UVDCLK1 = 4, PMLOG_CLK_UVDCLK2 = 5, PMLOG_CLK_VCECLK = 6, PMLOG_CLK_VCNCLK = 7, PMLOG_TEMPERATURE_EDGE = 8, // Current edge of the die temperature value in C PMLOG_TEMPERATURE_MEM = 9, PMLOG_TEMPERATURE_VRVDDC = 10, PMLOG_TEMPERATURE_VRMVDD = 11, PMLOG_TEMPERATURE_LIQUID = 12, PMLOG_TEMPERATURE_PLX = 13, PMLOG_FAN_RPM = 14, // Current fan RPM value PMLOG_FAN_PERCENTAGE = 15, // Current ratio of fan RPM and max RPM PMLOG_SOC_VOLTAGE = 16, PMLOG_SOC_POWER = 17, PMLOG_SOC_CURRENT = 18, PMLOG_INFO_ACTIVITY_GFX = 19, // Current graphic activity level in percentage PMLOG_INFO_ACTIVITY_MEM = 20, // Current memory activity level in percentage PMLOG_GFX_VOLTAGE = 21, // Current graphic voltage in mV PMLOG_MEM_VOLTAGE = 22, PMLOG_ASIC_POWER = 23, // Current ASIC power draw in Watt PMLOG_TEMPERATURE_VRSOC = 24, PMLOG_TEMPERATURE_VRMVDD0 = 25, PMLOG_TEMPERATURE_VRMVDD1 = 26, PMLOG_TEMPERATURE_HOTSPOT = 27, // Current center of the die temperature value in C PMLOG_TEMPERATURE_GFX = 28, PMLOG_TEMPERATURE_SOC = 29, PMLOG_GFX_POWER = 30, PMLOG_GFX_CURRENT = 31, PMLOG_TEMPERATURE_CPU = 32, PMLOG_CPU_POWER = 33, PMLOG_CLK_CPUCLK = 34, PMLOG_THROTTLER_STATUS = 35, // A bit map of GPU throttle information. If a bit is set, the bit represented type of thorttling occurred in the last metrics sampling period PMLOG_CLK_VCN1CLK1 = 36, PMLOG_CLK_VCN1CLK2 = 37, PMLOG_SMART_POWERSHIFT_CPU = 38, PMLOG_SMART_POWERSHIFT_DGPU = 39, PMLOG_BUS_SPEED = 40, // Current PCIE bus speed running PMLOG_BUS_LANES = 41, // Current PCIE bus lanes using PMLOG_TEMPERATURE_LIQUID0 = 42, PMLOG_TEMPERATURE_LIQUID1 = 43, PMLOG_CLK_FCLK = 44, PMLOG_THROTTLER_STATUS_CPU = 45, PMLOG_SSPAIRED_ASICPOWER = 46, // apuPower PMLOG_SSTOTAL_POWERLIMIT = 47, // Total Power limit PMLOG_SSAPU_POWERLIMIT = 48, // APU Power limit 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_MAX_SENSORS_REAL } ADLSensorType; //Throttle Status typedef enum ADL_THROTTLE_NOTIFICATION { ADL_PMLOG_THROTTLE_POWER = 1 << 0, ADL_PMLOG_THROTTLE_THERMAL = 1 << 1, ADL_PMLOG_THROTTLE_CURRENT = 1 << 2, } ADL_THROTTLE_NOTIFICATION; typedef enum ADL_PMLOG_SENSORS { ADL_SENSOR_MAXTYPES = 0, ADL_PMLOG_CLK_GFXCLK = 1, ADL_PMLOG_CLK_MEMCLK = 2, ADL_PMLOG_CLK_SOCCLK = 3, ADL_PMLOG_CLK_UVDCLK1 = 4, ADL_PMLOG_CLK_UVDCLK2 = 5, ADL_PMLOG_CLK_VCECLK = 6, ADL_PMLOG_CLK_VCNCLK = 7, ADL_PMLOG_TEMPERATURE_EDGE = 8, ADL_PMLOG_TEMPERATURE_MEM = 9, ADL_PMLOG_TEMPERATURE_VRVDDC = 10, ADL_PMLOG_TEMPERATURE_VRMVDD = 11, ADL_PMLOG_TEMPERATURE_LIQUID = 12, ADL_PMLOG_TEMPERATURE_PLX = 13, ADL_PMLOG_FAN_RPM = 14, ADL_PMLOG_FAN_PERCENTAGE = 15, ADL_PMLOG_SOC_VOLTAGE = 16, ADL_PMLOG_SOC_POWER = 17, ADL_PMLOG_SOC_CURRENT = 18, ADL_PMLOG_INFO_ACTIVITY_GFX = 19, ADL_PMLOG_INFO_ACTIVITY_MEM = 20, ADL_PMLOG_GFX_VOLTAGE = 21, ADL_PMLOG_MEM_VOLTAGE = 22, ADL_PMLOG_ASIC_POWER = 23, ADL_PMLOG_TEMPERATURE_VRSOC = 24, ADL_PMLOG_TEMPERATURE_VRMVDD0 = 25, ADL_PMLOG_TEMPERATURE_VRMVDD1 = 26, ADL_PMLOG_TEMPERATURE_HOTSPOT = 27, ADL_PMLOG_TEMPERATURE_GFX = 28, ADL_PMLOG_TEMPERATURE_SOC = 29, ADL_PMLOG_GFX_POWER = 30, ADL_PMLOG_GFX_CURRENT = 31, ADL_PMLOG_TEMPERATURE_CPU = 32, ADL_PMLOG_CPU_POWER = 33, ADL_PMLOG_CLK_CPUCLK = 34, ADL_PMLOG_THROTTLER_STATUS = 35, // GFX ADL_PMLOG_CLK_VCN1CLK1 = 36, ADL_PMLOG_CLK_VCN1CLK2 = 37, ADL_PMLOG_SMART_POWERSHIFT_CPU = 38, ADL_PMLOG_SMART_POWERSHIFT_DGPU = 39, ADL_PMLOG_BUS_SPEED = 40, ADL_PMLOG_BUS_LANES = 41, ADL_PMLOG_TEMPERATURE_LIQUID0 = 42, ADL_PMLOG_TEMPERATURE_LIQUID1 = 43, ADL_PMLOG_CLK_FCLK = 44, ADL_PMLOG_THROTTLER_STATUS_CPU = 45, ADL_PMLOG_SSPAIRED_ASICPOWER = 46, // apuPower ADL_PMLOG_SSTOTAL_POWERLIMIT = 47, // Total Power limit ADL_PMLOG_SSAPU_POWERLIMIT = 48, // APU Power limit 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_MAX_SENSORS_REAL } ADL_PMLOG_SENSORS; /// \defgroup define_ecc_mode_states /// These defines the ECC(Error Correction Code) state. It is used by \ref ADL_Workstation_ECC_Get,ADL_Workstation_ECC_Set /// @{ /// Error Correction is OFF. #define ECC_MODE_OFF 0 /// Error Correction is ECCV2. #define ECC_MODE_ON 2 /// Error Correction is HBM. #define ECC_MODE_HBM 3 /// @} /// \defgroup define_board_layout_flags /// These defines are the board layout flags state which indicates what are the valid properties of \ref ADLBoardLayoutInfo . It is used by \ref ADL_Adapter_BoardLayout_Get /// @{ /// Indicates the number of slots is valid. #define ADL_BLAYOUT_VALID_NUMBER_OF_SLOTS 0x1 /// Indicates the slot sizes are valid. Size of the slot consists of the length and width. #define ADL_BLAYOUT_VALID_SLOT_SIZES 0x2 /// Indicates the connector offsets are valid. #define ADL_BLAYOUT_VALID_CONNECTOR_OFFSETS 0x4 /// Indicates the connector lengths is valid. #define ADL_BLAYOUT_VALID_CONNECTOR_LENGTHS 0x8 /// @} /// \defgroup define_max_constants /// These defines are the maximum value constants. /// @{ /// Indicates the Maximum supported slots on board. #define ADL_ADAPTER_MAX_SLOTS 4 /// Indicates the Maximum supported connectors on slot. #define ADL_ADAPTER_MAX_CONNECTORS 10 /// Indicates the Maximum supported properties of connection #define ADL_MAX_CONNECTION_TYPES 32 /// Indicates the Maximum relative address link count. #define ADL_MAX_RELATIVE_ADDRESS_LINK_COUNT 15 /// Indicates the Maximum size of EDID data block size #define ADL_MAX_DISPLAY_EDID_DATA_SIZE 1024 /// Indicates the Maximum count of Error Records. #define ADL_MAX_ERROR_RECORDS_COUNT 256 /// Indicates the maximum number of power states supported #define ADL_MAX_POWER_POLICY 6 /// @} /// \defgroup define_connection_types /// These defines are the connection types constants which indicates what are the valid connection type of given connector. It is used by \ref ADL_Adapter_SupportedConnections_Get /// @{ /// Indicates the VGA connection type is valid. #define ADL_CONNECTION_TYPE_VGA 0 /// Indicates the DVI_I connection type is valid. #define ADL_CONNECTION_TYPE_DVI 1 /// Indicates the DVI_SL connection type is valid. #define ADL_CONNECTION_TYPE_DVI_SL 2 /// Indicates the HDMI connection type is valid. #define ADL_CONNECTION_TYPE_HDMI 3 /// Indicates the DISPLAY PORT connection type is valid. #define ADL_CONNECTION_TYPE_DISPLAY_PORT 4 /// Indicates the Active dongle DP->DVI(single link) connection type is valid. #define ADL_CONNECTION_TYPE_ACTIVE_DONGLE_DP_DVI_SL 5 /// Indicates the Active dongle DP->DVI(double link) connection type is valid. #define ADL_CONNECTION_TYPE_ACTIVE_DONGLE_DP_DVI_DL 6 /// Indicates the Active dongle DP->HDMI connection type is valid. #define ADL_CONNECTION_TYPE_ACTIVE_DONGLE_DP_HDMI 7 /// Indicates the Active dongle DP->VGA connection type is valid. #define ADL_CONNECTION_TYPE_ACTIVE_DONGLE_DP_VGA 8 /// Indicates the Passive dongle DP->HDMI connection type is valid. #define ADL_CONNECTION_TYPE_PASSIVE_DONGLE_DP_HDMI 9 /// Indicates the Active dongle DP->VGA connection type is valid. #define ADL_CONNECTION_TYPE_PASSIVE_DONGLE_DP_DVI 10 /// Indicates the MST type is valid. #define ADL_CONNECTION_TYPE_MST 11 /// Indicates the active dongle, all types. #define ADL_CONNECTION_TYPE_ACTIVE_DONGLE 12 /// Indicates the Virtual Connection Type. #define ADL_CONNECTION_TYPE_VIRTUAL 13 /// Macros for generating bitmask from index. #define ADL_CONNECTION_BITMAST_FROM_INDEX(index) (1 << index) /// @} /// \defgroup define_connection_properties /// These defines are the connection properties which indicates what are the valid properties of given connection type. It is used by \ref ADL_Adapter_SupportedConnections_Get /// @{ /// Indicates the property Bitrate is valid. #define ADL_CONNECTION_PROPERTY_BITRATE 0x1 /// Indicates the property number of lanes is valid. #define ADL_CONNECTION_PROPERTY_NUMBER_OF_LANES 0x2 /// Indicates the property 3D caps is valid. #define ADL_CONNECTION_PROPERTY_3DCAPS 0x4 /// Indicates the property output bandwidth is valid. #define ADL_CONNECTION_PROPERTY_OUTPUT_BANDWIDTH 0x8 /// Indicates the property colordepth is valid. #define ADL_CONNECTION_PROPERTY_COLORDEPTH 0x10 /// @} /// \defgroup define_lanecount_constants /// These defines are the Lane count constants which will be used in DP & etc. /// @{ /// Indicates if lane count is unknown #define ADL_LANECOUNT_UNKNOWN 0 /// Indicates if lane count is 1 #define ADL_LANECOUNT_ONE 1 /// Indicates if lane count is 2 #define ADL_LANECOUNT_TWO 2 /// Indicates if lane count is 4 #define ADL_LANECOUNT_FOUR 4 /// Indicates if lane count is 8 #define ADL_LANECOUNT_EIGHT 8 /// Indicates default value of lane count #define ADL_LANECOUNT_DEF ADL_LANECOUNT_FOUR /// @} /// \defgroup define_linkrate_constants /// These defines are the link rate constants which will be used in DP & etc. /// @{ /// Indicates if link rate is unknown #define ADL_LINK_BITRATE_UNKNOWN 0 /// Indicates if link rate is 1.62Ghz #define ADL_LINK_BITRATE_1_62_GHZ 0x06 /// Indicates if link rate is 2.7Ghz #define ADL_LINK_BITRATE_2_7_GHZ 0x0A /// Indicates if link rate is 5.4Ghz #define ADL_LINK_BITRATE_5_4_GHZ 0x14 /// Indicates if link rate is 8.1Ghz #define ADL_LINK_BITRATE_8_1_GHZ 0x1E /// Indicates default value of link rate #define ADL_LINK_BITRATE_DEF ADL_LINK_BITRATE_2_7_GHZ /// @} /// \defgroup define_colordepth_constants /// These defines are the color depth constants which will be used in DP & etc. /// @{ #define ADL_CONNPROP_S3D_ALTERNATE_TO_FRAME_PACK 0x00000001 /// @} /// \defgroup define_colordepth_constants /// These defines are the color depth constants which will be used in DP & etc. /// @{ /// Indicates if color depth is unknown #define ADL_COLORDEPTH_UNKNOWN 0 /// Indicates if color depth is 666 #define ADL_COLORDEPTH_666 1 /// Indicates if color depth is 888 #define ADL_COLORDEPTH_888 2 /// Indicates if color depth is 101010 #define ADL_COLORDEPTH_101010 3 /// Indicates if color depth is 121212 #define ADL_COLORDEPTH_121212 4 /// Indicates if color depth is 141414 #define ADL_COLORDEPTH_141414 5 /// Indicates if color depth is 161616 #define ADL_COLORDEPTH_161616 6 /// Indicates default value of color depth #define ADL_COLOR_DEPTH_DEF ADL_COLORDEPTH_888 /// @} /// \defgroup define_emulation_status /// These defines are the status of emulation /// @{ /// Indicates if real device is connected. #define ADL_EMUL_STATUS_REAL_DEVICE_CONNECTED 0x1 /// Indicates if emulated device is presented. #define ADL_EMUL_STATUS_EMULATED_DEVICE_PRESENT 0x2 /// Indicates if emulated device is used. #define ADL_EMUL_STATUS_EMULATED_DEVICE_USED 0x4 /// In case when last active real/emulated device used (when persistence is enabled but no emulation enforced then persistence will use last connected/emulated device). #define ADL_EMUL_STATUS_LAST_ACTIVE_DEVICE_USED 0x8 /// @} /// \defgroup define_emulation_mode /// These defines are the modes of emulation /// @{ /// Indicates if no emulation is used #define ADL_EMUL_MODE_OFF 0 /// Indicates if emulation is used when display connected #define ADL_EMUL_MODE_ON_CONNECTED 1 /// Indicates if emulation is used when display dis connected #define ADL_EMUL_MODE_ON_DISCONNECTED 2 /// Indicates if emulation is used always #define ADL_EMUL_MODE_ALWAYS 3 /// @} /// \defgroup define_emulation_query /// These defines are the modes of emulation /// @{ /// Indicates Data from real device #define ADL_QUERY_REAL_DATA 0 /// Indicates Emulated data #define ADL_QUERY_EMULATED_DATA 1 /// Indicates Data currently in use #define ADL_QUERY_CURRENT_DATA 2 /// @} /// \defgroup define_persistence_state /// These defines are the states of persistence /// @{ /// Indicates persistence is disabled #define ADL_EDID_PERSISTANCE_DISABLED 0 /// Indicates persistence is enabled #define ADL_EDID_PERSISTANCE_ENABLED 1 /// @} /// \defgroup define_connector_types Connector Type /// defines for ADLConnectorInfo.iType /// @{ /// Indicates unknown Connector type #define ADL_CONNECTOR_TYPE_UNKNOWN 0 /// Indicates VGA Connector type #define ADL_CONNECTOR_TYPE_VGA 1 /// Indicates DVI-D Connector type #define ADL_CONNECTOR_TYPE_DVI_D 2 /// Indicates DVI-I Connector type #define ADL_CONNECTOR_TYPE_DVI_I 3 /// Indicates Active Dongle-NA Connector type #define ADL_CONNECTOR_TYPE_ATICVDONGLE_NA 4 /// Indicates Active Dongle-JP Connector type #define ADL_CONNECTOR_TYPE_ATICVDONGLE_JP 5 /// Indicates Active Dongle-NONI2C Connector type #define ADL_CONNECTOR_TYPE_ATICVDONGLE_NONI2C 6 /// Indicates Active Dongle-NONI2C-D Connector type #define ADL_CONNECTOR_TYPE_ATICVDONGLE_NONI2C_D 7 /// Indicates HDMI-Type A Connector type #define ADL_CONNECTOR_TYPE_HDMI_TYPE_A 8 /// Indicates HDMI-Type B Connector type #define ADL_CONNECTOR_TYPE_HDMI_TYPE_B 9 /// Indicates Display port Connector type #define ADL_CONNECTOR_TYPE_DISPLAYPORT 10 /// Indicates EDP Connector type #define ADL_CONNECTOR_TYPE_EDP 11 /// Indicates MiniDP Connector type #define ADL_CONNECTOR_TYPE_MINI_DISPLAYPORT 12 /// Indicates Virtual Connector type #define ADL_CONNECTOR_TYPE_VIRTUAL 13 /// Indicates USB type C Connector type #define ADL_CONNECTOR_TYPE_USB_TYPE_C 14 /// @} /// \defgroup define_freesync_usecase /// These defines are to specify use cases in which FreeSync should be enabled /// They are a bit mask. To specify FreeSync for more than one use case, the input value /// should be set to include multiple bits set /// @{ /// Indicates FreeSync is enabled for Static Screen case #define ADL_FREESYNC_USECASE_STATIC 0x1 /// Indicates FreeSync is enabled for Video use case #define ADL_FREESYNC_USECASE_VIDEO 0x2 /// Indicates FreeSync is enabled for Gaming use case #define ADL_FREESYNC_USECASE_GAMING 0x4 /// @} /// \defgroup define_freesync_caps /// These defines are used to retrieve FreeSync display capabilities. /// GPU support flag also indicates whether the display is /// connected to a GPU that actually supports FreeSync /// @{ #define ADL_FREESYNC_CAP_SUPPORTED (1 << 0) #define ADL_FREESYNC_CAP_GPUSUPPORTED (1 << 1) #define ADL_FREESYNC_CAP_DISPLAYSUPPORTED (1 << 2) #define ADL_FREESYNC_CAP_CURRENTMODESUPPORTED (1 << 3) #define ADL_FREESYNC_CAP_NOCFXORCFXSUPPORTED (1 << 4) #define ADL_FREESYNC_CAP_NOGENLOCKORGENLOCKSUPPORTED (1 << 5) #define ADL_FREESYNC_CAP_BORDERLESSWINDOWSUPPORTED (1 << 6) /// @} /// \defgroup define_freesync_labelIndex /// These defines are used to retrieve which FreeSync label to use /// @{ #define ADL_FREESYNC_LABEL_UNSUPPORTED 0 #define ADL_FREESYNC_LABEL_FREESYNC 1 #define ADL_FREESYNC_LABEL_ADAPTIVE_SYNC 2 #define ADL_FREESYNC_LABEL_VRR 3 #define ADL_FREESYNC_LABEL_FREESYNC_PREMIUM 4 #define ADL_FREESYNC_LABEL_FREESYNC_PREMIUM_PRO 5 /// @} /// Freesync Power optimization masks /// @{ #define ADL_FREESYNC_POWEROPTIMIZATION_SUPPORTED_MASK (1 << 0) #define ADL_FREESYNC_POWEROPTIMIZATION_ENABLED_MASK (1 << 1) #define ADL_FREESYNC_POWEROPTIMIZATION_DEFAULT_VALUE_MASK (1 << 2) /// @} /// \defgroup define_MST_CommandLine_execute /// @{ /// Indicates the MST command line for branch message if the bit is set. Otherwise, it is display message #define ADL_MST_COMMANDLINE_PATH_MSG 0x1 /// Indicates the MST command line to send message in broadcast way it the bit is set #define ADL_MST_COMMANDLINE_BROADCAST 0x2 /// @} /// \defgroup define_Adapter_CloneTypes_Get /// @{ /// Indicates there is crossGPU clone with non-AMD dispalys #define ADL_CROSSGPUDISPLAYCLONE_AMD_WITH_NONAMD 0x1 /// Indicates there is crossGPU clone #define ADL_CROSSGPUDISPLAYCLONE 0x2 /// @} /// \defgroup define_D3DKMT_HANDLE /// @{ /// Handle can be used to create Device Handle when using CreateDevice() typedef unsigned int ADL_D3DKMT_HANDLE; /// @} // End Bracket for Constants and Definitions. Add new groups ABOVE this line! /// @} typedef enum ADL_RAS_ERROR_INJECTION_MODE { ADL_RAS_ERROR_INJECTION_MODE_SINGLE = 1, ADL_RAS_ERROR_INJECTION_MODE_MULTIPLE = 2 }ADL_RAS_ERROR_INJECTION_MODE; typedef enum ADL_RAS_BLOCK_ID { ADL_RAS_BLOCK_ID_UMC = 0, ADL_RAS_BLOCK_ID_SDMA, ADL_RAS_BLOCK_ID_GFX_HUB, ADL_RAS_BLOCK_ID_MMHUB, ADL_RAS_BLOCK_ID_ATHUB, ADL_RAS_BLOCK_ID_PCIE_BIF, ADL_RAS_BLOCK_ID_HDP, ADL_RAS_BLOCK_ID_XGMI_WAFL, ADL_RAS_BLOCK_ID_DF, ADL_RAS_BLOCK_ID_SMN, ADL_RAS_BLOCK_ID_SEM, ADL_RAS_BLOCK_ID_MP0, ADL_RAS_BLOCK_ID_MP1, ADL_RAS_BLOCK_ID_FUSE }ADL_RAS_BLOCK_ID; typedef enum ADL_MEM_SUB_BLOCK_ID { ADL_RAS__UMC_HBM = 0, ADL_RAS__UMC_SRAM = 1 }ADL_MEM_SUB_BLOCK_ID; typedef enum _ADL_RAS_ERROR_TYPE { ADL_RAS_ERROR__NONE = 0, ADL_RAS_ERROR__PARITY = 1, ADL_RAS_ERROR__SINGLE_CORRECTABLE = 2, ADL_RAS_ERROR__PARITY_SINGLE_CORRECTABLE = 3, ADL_RAS_ERROR__MULTI_UNCORRECTABLE = 4, ADL_RAS_ERROR__PARITY_MULTI_UNCORRECTABLE = 5, ADL_RAS_ERROR__SINGLE_CORRECTABLE_MULTI_UNCORRECTABLE = 6, ADL_RAS_ERROR__PARITY_SINGLE_CORRECTABLE_MULTI_UNCORRECTABLE = 7, ADL_RAS_ERROR__POISON = 8, ADL_RAS_ERROR__PARITY_POISON = 9, ADL_RAS_ERROR__SINGLE_CORRECTABLE_POISON = 10, ADL_RAS_ERROR__PARITY_SINGLE_CORRECTABLE_POISON = 11, ADL_RAS_ERROR__MULTI_UNCORRECTABLE_POISON = 12, ADL_RAS_ERROR__PARITY_MULTI_UNCORRECTABLE_POISON = 13, ADL_RAS_ERROR__SINGLE_CORRECTABLE_MULTI_UNCORRECTABLE_POISON = 14, ADL_RAS_ERROR__PARITY_SINGLE_CORRECTABLE_MULTI_UNCORRECTABLE_POISON = 15 }ADL_RAS_ERROR_TYPE; typedef enum ADL_RAS_INJECTION_METHOD { ADL_RAS_ERROR__UMC_METH_COHERENT = 0, ADL_RAS_ERROR__UMC_METH_SINGLE_SHOT = 1, ADL_RAS_ERROR__UMC_METH_PERSISTENT = 2, ADL_RAS_ERROR__UMC_METH_PERSISTENT_DISABLE = 3 }ADL_RAS_INJECTION_METHOD; // Driver event types typedef enum ADL_DRIVER_EVENT_TYPE { ADL_EVENT_ID_AUTO_FEATURE_COMPLETED = 30, ADL_EVENT_ID_FEATURE_AVAILABILITY = 31, } ADL_DRIVER_EVENT_TYPE; //UIFeature Ids typedef enum ADL_UIFEATURES_GROUP { ADL_UIFEATURES_GROUP_DVR = 0, ADL_UIFEATURES_GROUP_TURBOSYNC = 1, ADL_UIFEATURES_GROUP_FRAMEMETRICSMONITOR = 2, ADL_UIFEATURES_GROUP_FRTC = 3, ADL_UIFEATURES_GROUP_XVISION = 4, ADL_UIFEATURES_GROUP_BLOCKCHAIN = 5, ADL_UIFEATURES_GROUP_GAMEINTELLIGENCE = 6, ADL_UIFEATURES_GROUP_CHILL = 7, ADL_UIFEATURES_GROUP_DELAG = 8, ADL_UIFEATURES_GROUP_BOOST = 9, ADL_UIFEATURES_GROUP_USU = 10, ADL_UIFEATURES_GROUP_XGMI = 11, ADL_UIFEATURES_GROUP_PROVSR = 12, ADL_UIFEATURES_GROUP_SMA = 13, ADL_UIFEATURES_GROUP_CAMERA = 14, ADL_UIFEATURES_GROUP_FRTCPRO = 15 } ADL_UIFEATURES_GROUP; /// Maximum brightness supported by Radeon LED interface #define ADL_RADEON_LED_MAX_BRIGHTNESS 2 /// Maximum speed supported by Radeon LED interface #define ADL_RADEON_LED_MAX_SPEED 4 /// Maximum RGB supported by Radeon LED interface #define ADL_RADEON_LED_MAX_RGB 255 /// Maximum MORSE code supported string #define ADL_RADEON_LED_MAX_MORSE_CODE 260 /// Maximum LED ROW ON GRID #define ADL_RADEON_LED_MAX_LED_ROW_ON_GRID 7 /// Maximum LED COLUMN ON GRID #define ADL_RADEON_LED_MAX_LED_COLUMN_ON_GRID 24 ///////////////////////////////////////////////////////////////////////////////////////////// ///\brief /// /// /// /// /// \nosubgrouping //////////////////////////////////////////////////////////////////////////////////////////// typedef enum ADL_RADEON_USB_LED_BAR_CONTROLS { RadeonLEDBarControl_OFF = 0, RadeonLEDBarControl_Static, RadeonLEDBarControl_Rainbow, RadeonLEDBarControl_Swirl, RadeonLEDBarControl_Chase, RadeonLEDBarControl_Bounce, RadeonLEDBarControl_MorseCode, RadeonLEDBarControl_ColorCycle, RadeonLEDBarControl_Breathing, RadeonLEDBarControl_CustomPattern, RadeonLEDBarControl_MAX }ADL_RADEON_USB_LED_BAR_CONTROLS; ///////////////////////////////////////////////////////////////////////////////////////////// ///\brief /// /// /// /// /// \nosubgrouping //////////////////////////////////////////////////////////////////////////////////////////// typedef unsigned int RadeonLEDBARSupportedControl; ///////////////////////////////////////////////////////////////////////////////////////////// ///\brief /// /// /// /// /// \nosubgrouping //////////////////////////////////////////////////////////////////////////////////////////// typedef enum ADL_RADEON_USB_LED_CONTROL_CONFIGS { RadeonLEDPattern_Speed = 0, RadeonLEDPattern_Brightness, RadeonLEDPattern_Direction, RadeonLEDPattern_Color, RadeonLEDPattern_MAX }ADL_RADEON_USB_LED_CONTROL_CONFIGS; ///////////////////////////////////////////////////////////////////////////////////////////// ///\brief /// /// /// /// /// \nosubgrouping //////////////////////////////////////////////////////////////////////////////////////////// typedef unsigned int RadeonLEDBARSupportedConfig; //User blob feature settings typedef enum ADL_USER_SETTINGS { ADL_USER_SETTINGS_ENHANCEDSYNC = 1 << 0, //notify Enhanced Sync settings change ADL_USER_SETTINGS_CHILL_PROFILE = 1 << 1, //notify Chill settings change ADL_USER_SETTINGS_DELAG_PROFILE = 1 << 2, //notify Delag settings change ADL_USER_SETTINGS_BOOST_PROFILE = 1 << 3, //notify Boost settings change 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; #define ADL_REG_DEVICE_FUNCTION_1 0x00000001 #endif /* ADL_DEFINES_H_ */ ================================================ FILE: src/3rdparty/display-library/adl_sdk.h ================================================ // // Copyright (c) 2016 - 2022 Advanced Micro Devices, Inc. All rights reserved. // // MIT LICENSE: // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to deal // in the Software without restriction, including without limitation the rights // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell // copies of the Software, and to permit persons to whom the Software is // furnished to do so, subject to the following conditions: // // The above copyright notice and this permission notice shall be included in // all copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE // SOFTWARE. /// \file adl_sdk.h /// \brief Contains the definition of the Memory Allocation Callback.\n Included in ADL SDK /// /// \n\n /// This file contains the definition of the Memory Allocation Callback.\n /// It also includes definitions of the respective structures and constants.\n /// This is the only header file to be included in a C/C++ project using ADL #ifndef ADL_SDK_H_ #define ADL_SDK_H_ #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 #endif /* ADL_SDK_H_ */ ================================================ FILE: src/3rdparty/display-library/adl_structures.h ================================================ // // Copyright (c) 2016 - 2022 Advanced Micro Devices, Inc. All rights reserved. // // MIT LICENSE: // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to deal // in the Software without restriction, including without limitation the rights // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell // copies of the Software, and to permit persons to whom the Software is // furnished to do so, subject to the following conditions: // // The above copyright notice and this permission notice shall be included in // all copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE // 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 /// /// All data structures used in AMD Display Library (ADL) public interfaces should be defined in this header file. /// #ifndef ADL_STRUCTURES_H_ #define ADL_STRUCTURES_H_ #include "adl_defines.h" #include ///////////////////////////////////////////////////////////////////////////////////////////// ///\brief Structure containing information about the graphics adapter. /// /// This structure is used to store various information about the graphics adapter. This /// information can be returned to the user. Alternatively, it can be used to access various driver calls to set /// or fetch various settings upon the user's request. /// \nosubgrouping //////////////////////////////////////////////////////////////////////////////////////////// typedef struct AdapterInfo { /// \ALL_STRUCT_MEM /// Size of the structure. int iSize; /// The ADL index handle. One GPU may be associated with one or two index handles int iAdapterIndex; /// The unique device ID associated with this adapter. char strUDID[ADL_MAX_PATH]; /// The BUS number associated with this adapter. int iBusNumber; /// The driver number associated with this adapter. int iDeviceNumber; /// The function number. int iFunctionNumber; /// The vendor ID associated with this adapter. int iVendorID; /// Adapter name. char strAdapterName[ADL_MAX_PATH]; /// Display name. For example, "\\\\Display0" for Windows or ":0:0" for Linux. 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; #if defined (_WIN32) || defined (_WIN64) /// \WIN_STRUCT_MEM /// Exist or not; 1 is exist and 0 is not present. int iExist; /// Driver registry path. char strDriverPath[ADL_MAX_PATH]; /// Driver registry path Ext for. char strDriverPathExt[ADL_MAX_PATH]; /// PNP string from Windows. char strPNPString[ADL_MAX_PATH]; /// It is generated from EnumDisplayDevices. int iOSDisplayIndex; #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 /// /// This structure is used to store information of an controller mode /// \nosubgrouping //////////////////////////////////////////////////////////////////////////////////////////// typedef struct ADLAdapterCaps { /// AdapterID for this adapter int iAdapterID; /// Number of controllers for this adapter int iNumControllers; /// Number of displays for this adapter int iNumDisplays; /// Number of overlays for this adapter int iNumOverlays; /// Number of GLSyncConnectors int iNumOfGLSyncConnectors; /// The bit mask identifies the adapter caps int iCapsMask; /// The bit identifies the adapter caps \ref define_adapter_caps int iCapsValue; }ADLAdapterCaps; ///////////////////////////////////////////////////////////////////////////////////////////// ///\brief Structure containing additional information about the ASIC memory /// /// This structure is used to store additional information about the ASIC memory. This /// information can be returned to the user. /// \nosubgrouping //////////////////////////////////////////////////////////////////////////////////////////// typedef struct ADLMemoryInfo2 { /// Memory size in bytes. long long iMemorySize; /// Memory type in string. char strMemoryType[ADL_MAX_PATH]; /// Highest default performance level Memory bandwidth in Mbytes/s long long iMemoryBandwidth; /// HyperMemory size in bytes. long long iHyperMemorySize; /// Invisible Memory size in bytes. long long iInvisibleMemorySize; /// Visible Memory size in bytes. long long iVisibleMemorySize; } ADLMemoryInfo2, *LPADLMemoryInfo2; ///////////////////////////////////////////////////////////////////////////////////////////// ///\brief Structure containing additional information about the ASIC memory /// /// This structure is used to store additional information about the ASIC memory. This /// information can be returned to the user. /// \nosubgrouping //////////////////////////////////////////////////////////////////////////////////////////// typedef struct ADLMemoryInfo3 { /// Memory size in bytes. long long iMemorySize; /// Memory type in string. char strMemoryType[ADL_MAX_PATH]; /// Highest default performance level Memory bandwidth in Mbytes/s long long iMemoryBandwidth; /// HyperMemory size in bytes. long long iHyperMemorySize; /// Invisible Memory size in bytes. long long iInvisibleMemorySize; /// Visible Memory size in bytes. long long iVisibleMemorySize; /// Vram vendor ID long long iVramVendorRevId; } ADLMemoryInfo3, *LPADLMemoryInfo3; ///////////////////////////////////////////////////////////////////////////////////////////// ///\brief Structure containing additional information about the ASIC memory /// /// This structure is used to store additional information about the ASIC memory. This /// information can be returned to the user. /// \nosubgrouping //////////////////////////////////////////////////////////////////////////////////////////// typedef struct ADLMemoryInfoX4 { /// Memory size in bytes. long long iMemorySize; /// Memory type in string. char strMemoryType[ADL_MAX_PATH]; /// Highest default performance level Memory bandwidth in Mbytes/s long long iMemoryBandwidth; /// HyperMemory size in bytes. long long iHyperMemorySize; /// Invisible Memory size in bytes. long long iInvisibleMemorySize; /// Visible Memory size in bytes. long long iVisibleMemorySize; /// Vram vendor ID long long iVramVendorRevId; /// Memory Bandiwidth that is calculated and finalized on the driver side, grab and go. long long iMemoryBandwidthX2; /// Memory Bit Rate that is calculated and finalized on the driver side, grab and go. long long iMemoryBitRateX2; } ADLMemoryInfoX4, *LPADLMemoryInfoX4; /////////////////////////////////////////////////////////////////////////// // ADLvRamVendor Enumeration /////////////////////////////////////////////////////////////////////////// enum ADLvRamVendors { ADLvRamVendor_Unsupported = 0x0, ADLvRamVendor_SAMSUNG, ADLvRamVendor_INFINEON, ADLvRamVendor_ELPIDA, ADLvRamVendor_ETRON, ADLvRamVendor_NANYA, ADLvRamVendor_HYNIX, ADLvRamVendor_MOSEL, ADLvRamVendor_WINBOND, ADLvRamVendor_ESMT, ADLvRamVendor_MICRON = 0xF, ADLvRamVendor_Undefined }; ///////////////////////////////////////////////////////////////////////////////////////////// ///\brief Structure containing information about components of ASIC GCN architecture /// /// Elements of GCN info are compute units, number of Tex (Texture filtering units) , number of ROPs (render back-ends). /// \nosubgrouping //////////////////////////////////////////////////////////////////////////////////////////// typedef struct ADLGcnInfo { int CuCount; //Number of compute units on the ASIC. int TexCount; //Number of texture mapping units. int RopCount; //Number of Render backend Units. int ASICFamilyId; //Such SI, VI. See /inc/asic_reg/atiid.h for family ids int ASICRevisionId; //Such as Ellesmere, Fiji. For example - VI family revision ids are stored in /inc/asic_reg/vi_id.h }ADLGcnInfo; ///////////////////////////////////////////////////////////////////////////////////////////// ///\brief Structure containing information related virtual segment config information. /// /// This structure is used to store information related virtual segment config /// \nosubgrouping //////////////////////////////////////////////////////////////////////////////////////////// typedef struct ADLVirtualSegmentSettingsOutput { int virtualSegmentSupported; // 1 - subsequent values are valid int virtualSegmentDefault; //virtual segment default, 1: enable, 0: disable int virtualSegmentCurrent; //virtual segment current, 1: enable, 0: disable int iMinSizeInMB; //minimum value int iMaxSizeInMB; //maximum value int icurrentSizeInMB; //last configured otherwise same as factory default int idefaultSizeInMB; //factory default int iMask; //fileds for extension in the future int iValue; //fileds for extension in the future } ADLVirtualSegmentSettingsOutput; ///////////////////////////////////////////////////////////////////////////////////////////// /// \brief Structure containing information about the Chipset. /// /// This structure is used to store various information about the Chipset. This /// information can be returned to the user. /// \nosubgrouping //////////////////////////////////////////////////////////////////////////////////////////// typedef struct ADLChipSetInfo { int iBusType; ///< Bus type. int iBusSpeedType; ///Maximum Bus Speed of the current platform int iMaxPCIELaneWidth; ///< Number of PCIE lanes. int iCurrentPCIELaneWidth; ///< Current PCIE Lane Width int iSupportedAGPSpeeds; ///< Bit mask or AGP transfer speed. int iCurrentAGPSpeed; ///< Current AGP speed } ADLChipSetInfo, *LPADLChipSetInfo; ///////////////////////////////////////////////////////////////////////////////////////////// ///\brief Structure containing information about the ASIC memory. /// /// This structure is used to store various information about the ASIC memory. This /// information can be returned to the user. /// \nosubgrouping //////////////////////////////////////////////////////////////////////////////////////////// typedef struct ADLMemoryInfo { /// Memory size in bytes. long long iMemorySize; /// Memory type in string. char strMemoryType[ADL_MAX_PATH]; /// Memory bandwidth in Mbytes/s. long long iMemoryBandwidth; } ADLMemoryInfo, *LPADLMemoryInfo; ///////////////////////////////////////////////////////////////////////////////////////////// ///\brief Structure containing information about memory required by type /// /// This structure is returned by ADL_Adapter_ConfigMemory_Get, which given a desktop and display configuration /// will return the Memory used. /// \nosubgrouping //////////////////////////////////////////////////////////////////////////////////////////// typedef struct ADLMemoryRequired { long long iMemoryReq; /// Memory in bytes required int iType; /// Type of Memory \ref define_adl_validmemoryrequiredfields int iDisplayFeatureValue; /// Display features \ref define_adl_visiblememoryfeatures that are using this type of memory } ADLMemoryRequired, *LPADLMemoryRequired; ///////////////////////////////////////////////////////////////////////////////////////////// ///\brief Structure containing information about the features associated with a display /// /// This structure is a parameter to ADL_Adapter_ConfigMemory_Get, which given a desktop and display configuration /// will return the Memory used. /// \nosubgrouping //////////////////////////////////////////////////////////////////////////////////////////// typedef struct ADLMemoryDisplayFeatures { int iDisplayIndex; /// ADL Display index int iDisplayFeatureValue; /// features that the display is using \ref define_adl_visiblememoryfeatures } ADLMemoryDisplayFeatures, *LPADLMemoryDisplayFeatures; ///////////////////////////////////////////////////////////////////////////////////////////// ///\brief Structure containing DDC information. /// /// This structure is used to store various DDC information that can be returned to the user. /// Note that all fields of type int are actually defined as unsigned int types within the driver. /// \nosubgrouping //////////////////////////////////////////////////////////////////////////////////////////// typedef struct ADLDDCInfo { /// Size of the structure int ulSize; /// Indicates whether the attached display supports DDC. If this field is zero on return, no other DDC information fields will be used. int ulSupportsDDC; /// Returns the manufacturer ID of the display device. Should be zeroed if this information is not available. int ulManufacturerID; /// Returns the product ID of the display device. Should be zeroed if this information is not available. int ulProductID; /// Returns the name of the display device. Should be zeroed if this information is not available. char cDisplayName[ADL_MAX_DISPLAY_NAME]; /// Returns the maximum Horizontal supported resolution. Should be zeroed if this information is not available. int ulMaxHResolution; /// Returns the maximum Vertical supported resolution. Should be zeroed if this information is not available. int ulMaxVResolution; /// Returns the maximum supported refresh rate. Should be zeroed if this information is not available. int ulMaxRefresh; /// Returns the display device preferred timing mode's horizontal resolution. int ulPTMCx; /// Returns the display device preferred timing mode's vertical resolution. int ulPTMCy; /// Returns the display device preferred timing mode's refresh rate. int ulPTMRefreshRate; /// Return EDID flags. int ulDDCInfoFlag; } ADLDDCInfo, *LPADLDDCInfo; ///////////////////////////////////////////////////////////////////////////////////////////// ///\brief Structure containing DDC information. /// /// This structure is used to store various DDC information that can be returned to the user. /// Note that all fields of type int are actually defined as unsigned int types within the driver. /// \nosubgrouping //////////////////////////////////////////////////////////////////////////////////////////// typedef struct ADLDDCInfo2 { /// Size of the structure int ulSize; /// Indicates whether the attached display supports DDC. If this field is zero on return, no other DDC /// information fields will be used. int ulSupportsDDC; /// Returns the manufacturer ID of the display device. Should be zeroed if this information is not available. int ulManufacturerID; /// Returns the product ID of the display device. Should be zeroed if this information is not available. int ulProductID; /// Returns the name of the display device. Should be zeroed if this information is not available. char cDisplayName[ADL_MAX_DISPLAY_NAME]; /// Returns the maximum Horizontal supported resolution. Should be zeroed if this information is not available. int ulMaxHResolution; /// Returns the maximum Vertical supported resolution. Should be zeroed if this information is not available. int ulMaxVResolution; /// Returns the maximum supported refresh rate. Should be zeroed if this information is not available. int ulMaxRefresh; /// Returns the display device preferred timing mode's horizontal resolution. int ulPTMCx; /// Returns the display device preferred timing mode's vertical resolution. int ulPTMCy; /// Returns the display device preferred timing mode's refresh rate. int ulPTMRefreshRate; /// Return EDID flags. int ulDDCInfoFlag; /// Returns 1 if the display supported packed pixel, 0 otherwise int bPackedPixelSupported; /// Returns the Pixel formats the display supports \ref define_ddcinfo_pixelformats int iPanelPixelFormat; /// Return EDID serial ID. int ulSerialID; /// Return minimum monitor luminance data int ulMinLuminanceData; /// Return average monitor luminance data int ulAvgLuminanceData; /// Return maximum monitor luminance data int ulMaxLuminanceData; /// Bit vector of supported transfer functions \ref define_source_content_TF int iSupportedTransferFunction; /// Bit vector of supported color spaces \ref define_source_content_CS int iSupportedColorSpace; /// Display Red Chromaticity X coordinate multiplied by 10000 int iNativeDisplayChromaticityRedX; /// Display Red Chromaticity Y coordinate multiplied by 10000 int iNativeDisplayChromaticityRedY; /// Display Green Chromaticity X coordinate multiplied by 10000 int iNativeDisplayChromaticityGreenX; /// Display Green Chromaticity Y coordinate multiplied by 10000 int iNativeDisplayChromaticityGreenY; /// Display Blue Chromaticity X coordinate multiplied by 10000 int iNativeDisplayChromaticityBlueX; /// Display Blue Chromaticity Y coordinate multiplied by 10000 int iNativeDisplayChromaticityBlueY; /// Display White Point X coordinate multiplied by 10000 int iNativeDisplayChromaticityWhitePointX; /// Display White Point Y coordinate multiplied by 10000 int iNativeDisplayChromaticityWhitePointY; /// Display diffuse screen reflectance 0-1 (100%) in units of 0.01 int iDiffuseScreenReflectance; /// Display specular screen reflectance 0-1 (100%) in units of 0.01 int iSpecularScreenReflectance; /// Bit vector of supported color spaces \ref define_HDR_support int iSupportedHDR; /// Bit vector for freesync flags int iFreesyncFlags; /// Return minimum monitor luminance without dimming data int ulMinLuminanceNoDimmingData; int ulMaxBacklightMaxLuminanceData; int ulMinBacklightMaxLuminanceData; int ulMaxBacklightMinLuminanceData; int ulMinBacklightMinLuminanceData; // Reserved for future use int iReserved[4]; } ADLDDCInfo2, *LPADLDDCInfo2; ///////////////////////////////////////////////////////////////////////////////////////////// ///\brief Structure containing information controller Gamma settings. /// /// This structure is used to store the red, green and blue color channel information for the. /// controller gamma setting. This information is returned by ADL, and it can also be used to /// set the controller gamma setting. /// \nosubgrouping //////////////////////////////////////////////////////////////////////////////////////////// typedef struct ADLGamma { /// Red color channel gamma value. float fRed; /// Green color channel gamma value. float fGreen; /// Blue color channel gamma value. float fBlue; } ADLGamma, *LPADLGamma; ///////////////////////////////////////////////////////////////////////////////////////////// ///\brief Structure containing information about component video custom modes. /// /// This structure is used to store the component video custom mode. /// \nosubgrouping //////////////////////////////////////////////////////////////////////////////////////////// typedef struct ADLCustomMode { /// Custom mode flags. They are returned by the ADL driver. int iFlags; /// Custom mode width. int iModeWidth; /// Custom mode height. int iModeHeight; /// Custom mode base width. int iBaseModeWidth; /// Custom mode base height. int iBaseModeHeight; /// Custom mode refresh rate. int iRefreshRate; } ADLCustomMode, *LPADLCustomMode; ///////////////////////////////////////////////////////////////////////////////////////////// ///\brief Structure containing Clock information for OD5 calls. /// /// This structure is used to retrieve clock information for OD5 calls. /// \nosubgrouping //////////////////////////////////////////////////////////////////////////////////////////// typedef struct ADLGetClocksOUT { long ulHighCoreClock; long ulHighMemoryClock; long ulHighVddc; long ulCoreMin; long ulCoreMax; long ulMemoryMin; long ulMemoryMax; long ulActivityPercent; long ulCurrentCoreClock; long ulCurrentMemoryClock; long ulReserved; } ADLGetClocksOUT; ///////////////////////////////////////////////////////////////////////////////////////////// ///\brief Structure containing HDTV information for display calls. /// /// This structure is used to retrieve HDTV information information for display calls. /// \nosubgrouping //////////////////////////////////////////////////////////////////////////////////////////// typedef struct ADLDisplayConfig { /// Size of the structure long ulSize; /// HDTV connector type. long ulConnectorType; /// HDTV capabilities. long ulDeviceData; /// Overridden HDTV capabilities. long ulOverridedDeviceData; /// Reserved field long ulReserved; } ADLDisplayConfig; ///////////////////////////////////////////////////////////////////////////////////////////// ///\brief Structure containing information about the display device. /// /// This structure is used to store display device information /// such as display index, type, name, connection status, mapped adapter and controller indexes, /// whether or not multiple VPUs are supported, local display connections or not (through Lasso), etc. /// This information can be returned to the user. Alternatively, it can be used to access various driver calls to set /// or fetch various display device related settings upon the user's request. /// \nosubgrouping //////////////////////////////////////////////////////////////////////////////////////////// typedef struct ADLDisplayID { /// The logical display index belonging to this adapter. int iDisplayLogicalIndex; ///\brief The physical display index. /// For example, display index 2 from adapter 2 can be used by current adapter 1.\n /// So current adapter may enumerate this adapter as logical display 7 but the physical display /// index is still 2. int iDisplayPhysicalIndex; /// The persistent logical adapter index for the display. int iDisplayLogicalAdapterIndex; ///\brief The persistent physical adapter index for the display. /// It can be the current adapter or a non-local adapter. \n /// If this adapter index is different than the current adapter, /// the Display Non Local flag is set inside DisplayInfoValue. int iDisplayPhysicalAdapterIndex; } ADLDisplayID, *LPADLDisplayID; ///////////////////////////////////////////////////////////////////////////////////////////// ///\brief Structure containing information about the display device. /// /// This structure is used to store various information about the display device. This /// information can be returned to the user, or used to access various driver calls to set /// or fetch various display-device-related settings upon the user's request /// \nosubgrouping //////////////////////////////////////////////////////////////////////////////////////////// typedef struct ADLDisplayInfo { /// The DisplayID structure ADLDisplayID displayID; ///\deprecated The controller index to which the display is mapped.\n Will not be used in the future\n int iDisplayControllerIndex; /// The display's EDID name. char strDisplayName[ADL_MAX_PATH]; /// The display's manufacturer name. char strDisplayManufacturerName[ADL_MAX_PATH]; /// The Display type. For example: CRT, TV, CV, DFP. int iDisplayType; /// The display output type. For example: HDMI, SVIDEO, COMPONMNET VIDEO. int iDisplayOutputType; /// The connector type for the device. int iDisplayConnector; ///\brief The bit mask identifies the number of bits ADLDisplayInfo is currently using. \n /// It will be the sum all the bit definitions in ADL_DISPLAY_DISPLAYINFO_xxx. int iDisplayInfoMask; /// The bit mask identifies the display status. \ref define_displayinfomask int iDisplayInfoValue; } ADLDisplayInfo, *LPADLDisplayInfo; ///////////////////////////////////////////////////////////////////////////////////////////// ///\brief Structure containing information about the display port MST device. /// /// This structure is used to store various MST information about the display port device. This /// information can be returned to the user, or used to access various driver calls to /// fetch various display-device-related settings upon the user's request /// \nosubgrouping //////////////////////////////////////////////////////////////////////////////////////////// typedef struct ADLDisplayDPMSTInfo { /// The ADLDisplayID structure ADLDisplayID displayID; /// total bandwidth available on the DP connector int iTotalAvailableBandwidthInMpbs; /// bandwidth allocated to this display int iAllocatedBandwidthInMbps; // info from DAL DpMstSinkInfo /// string identifier for the display char strGlobalUniqueIdentifier[ADL_MAX_PATH]; /// The link count of relative address, rad[0] upto rad[linkCount] are valid int radLinkCount; /// The physical connector ID, used to identify the physical DP port int iPhysicalConnectorID; /// Relative address, address scheme starts from source side char rad[ADL_MAX_RAD_LINK_COUNT]; } ADLDisplayDPMSTInfo, *LPADLDisplayDPMSTInfo; ///////////////////////////////////////////////////////////////////////////////////////////// ///\brief Structure containing the display mode definition used per controller. /// /// This structure is used to store the display mode definition used per controller. /// \nosubgrouping //////////////////////////////////////////////////////////////////////////////////////////// typedef struct ADLDisplayMode { /// Vertical resolution (in pixels). int iPelsHeight; /// Horizontal resolution (in pixels). int iPelsWidth; /// Color depth. int iBitsPerPel; /// Refresh rate. int iDisplayFrequency; } ADLDisplayMode; ///////////////////////////////////////////////////////////////////////////////////////////// ///\brief Structure containing detailed timing parameters. /// /// This structure is used to store the detailed timing parameters. /// \nosubgrouping //////////////////////////////////////////////////////////////////////////////////////////// typedef struct ADLDetailedTiming { /// Size of the structure. int iSize; /// Timing flags. \ref define_detailed_timing_flags short sTimingFlags; /// Total width (columns). short sHTotal; /// Displayed width. short sHDisplay; /// Horizontal sync signal offset. short sHSyncStart; /// Horizontal sync signal width. short sHSyncWidth; /// Total height (rows). short sVTotal; /// Displayed height. short sVDisplay; /// Vertical sync signal offset. short sVSyncStart; /// Vertical sync signal width. short sVSyncWidth; /// Pixel clock value. short sPixelClock; /// Overscan right. short sHOverscanRight; /// Overscan left. short sHOverscanLeft; /// Overscan bottom. short sVOverscanBottom; /// Overscan top. short sVOverscanTop; short sOverscan8B; short sOverscanGR; } ADLDetailedTiming; ///////////////////////////////////////////////////////////////////////////////////////////// ///\brief Structure containing display mode information. /// /// This structure is used to store the display mode information. /// \nosubgrouping //////////////////////////////////////////////////////////////////////////////////////////// typedef struct ADLDisplayModeInfo { /// Timing standard of the current mode. \ref define_modetiming_standard int iTimingStandard; /// Applicable timing standards for the current mode. int iPossibleStandard; /// Refresh rate factor. int iRefreshRate; /// Num of pixels in a row. int iPelsWidth; /// Num of pixels in a column. int iPelsHeight; /// Detailed timing parameters. ADLDetailedTiming sDetailedTiming; } ADLDisplayModeInfo; ///////////////////////////////////////////////////////////////////////////////////////////// /// \brief Structure containing information about display property. /// /// This structure is used to store the display property for the current adapter. /// \nosubgrouping //////////////////////////////////////////////////////////////////////////////////////////// typedef struct ADLDisplayProperty { /// Must be set to sizeof the structure int iSize; /// Must be set to \ref ADL_DL_DISPLAYPROPERTY_TYPE_EXPANSIONMODE or \ref ADL_DL_DISPLAYPROPERTY_TYPE_USEUNDERSCANSCALING int iPropertyType; /// Get or Set \ref ADL_DL_DISPLAYPROPERTY_EXPANSIONMODE_CENTER or \ref ADL_DL_DISPLAYPROPERTY_EXPANSIONMODE_FULLSCREEN or \ref ADL_DL_DISPLAYPROPERTY_EXPANSIONMODE_ASPECTRATIO or \ref ADL_DL_DISPLAYPROPERTY_TYPE_ITCFLAGENABLE int iExpansionMode; /// Display Property supported? 1: Supported, 0: Not supported int iSupport; /// Display Property current value int iCurrent; /// Display Property Default value int iDefault; } ADLDisplayProperty; ///////////////////////////////////////////////////////////////////////////////////////////// ///\brief Structure containing information about Clock. /// /// This structure is used to store the clock information for the current adapter /// such as core clock and memory clock info. ///\nosubgrouping //////////////////////////////////////////////////////////////////////////////////////////// typedef struct ADLClockInfo { /// Core clock in 10 KHz. int iCoreClock; /// Memory clock in 10 KHz. int iMemoryClock; } ADLClockInfo, *LPADLClockInfo; ///////////////////////////////////////////////////////////////////////////////////////////// ///\brief Structure containing information about I2C. /// /// This structure is used to store the I2C information for the current adapter. /// This structure is used by the ADL_Display_WriteAndReadI2C() function. /// \nosubgrouping //////////////////////////////////////////////////////////////////////////////////////////// typedef struct ADLI2C { /// Size of the structure int iSize; /// Numerical value representing hardware I2C. int iLine; /// The 7-bit I2C slave device address, shifted one bit to the left. int iAddress; /// The offset of the data from the address. int iOffset; /// Read from or write to slave device. \ref ADL_DL_I2C_ACTIONREAD or \ref ADL_DL_I2C_ACTIONWRITE or \ref ADL_DL_I2C_ACTIONREAD_REPEATEDSTART int iAction; /// I2C clock speed in KHz. int iSpeed; /// A numerical value representing the number of bytes to be sent or received on the I2C bus. int iDataSize; /// Address of the characters which are to be sent or received on the I2C bus. char *pcData; } ADLI2C; ///////////////////////////////////////////////////////////////////////////////////////////// ///\brief Structure containing information about EDID data. /// /// This structure is used to store the information about EDID data for the adapter. /// This structure is used by the ADL_Display_EdidData_Get() and ADL_Display_EdidData_Set() functions. /// \nosubgrouping //////////////////////////////////////////////////////////////////////////////////////////// typedef struct ADLDisplayEDIDData { /// Size of the structure int iSize; /// Set to 0 int iFlag; /// Size of cEDIDData. Set by ADL_Display_EdidData_Get() upon return int iEDIDSize; /// 0, 1 or 2. If set to 3 or above an error ADL_ERR_INVALID_PARAM is generated int iBlockIndex; /// EDID data char cEDIDData[ADL_MAX_EDIDDATA_SIZE]; /// Reserved int iReserved[4]; }ADLDisplayEDIDData; ///////////////////////////////////////////////////////////////////////////////////////////// ///\brief Structure containing information about input of controller overlay adjustment. /// /// This structure is used to store the information about input of controller overlay adjustment for the adapter. /// This structure is used by the ADL_Display_ControllerOverlayAdjustmentCaps_Get, ADL_Display_ControllerOverlayAdjustmentData_Get, and /// ADL_Display_ControllerOverlayAdjustmentData_Set() functions. /// \nosubgrouping //////////////////////////////////////////////////////////////////////////////////////////// typedef struct ADLControllerOverlayInput { /// Should be set to the sizeof the structure int iSize; ///\ref ADL_DL_CONTROLLER_OVERLAY_ALPHA or \ref ADL_DL_CONTROLLER_OVERLAY_ALPHAPERPIX int iOverlayAdjust; /// Data. int iValue; /// Should be 0. int iReserved; } ADLControllerOverlayInput; ///////////////////////////////////////////////////////////////////////////////////////////// ///\brief Structure containing information about overlay adjustment. /// /// This structure is used to store the information about overlay adjustment for the adapter. /// This structure is used by the ADLControllerOverlayInfo() function. /// \nosubgrouping //////////////////////////////////////////////////////////////////////////////////////////// typedef struct ADLAdjustmentinfo { /// Default value int iDefault; /// Minimum value int iMin; /// Maximum Value int iMax; /// Step value int iStep; } ADLAdjustmentinfo; ///////////////////////////////////////////////////////////////////////////////////////////// ///\brief Structure containing information about controller overlay information. /// /// This structure is used to store information about controller overlay info for the adapter. /// This structure is used by the ADL_Display_ControllerOverlayAdjustmentCaps_Get() function. /// \nosubgrouping //////////////////////////////////////////////////////////////////////////////////////////// typedef struct ADLControllerOverlayInfo { /// Should be set to the sizeof the structure int iSize; /// Data. ADLAdjustmentinfo sOverlayInfo; /// Should be 0. int iReserved[3]; } ADLControllerOverlayInfo; ///////////////////////////////////////////////////////////////////////////////////////////// ///\brief Structure containing GL-Sync module information. /// /// This structure is used to retrieve GL-Sync module information for /// Workstation Framelock/Genlock. /// \nosubgrouping //////////////////////////////////////////////////////////////////////////////////////////// typedef struct ADLGLSyncModuleID { /// Unique GL-Sync module ID. int iModuleID; /// GL-Sync GPU port index (to be passed into ADLGLSyncGenlockConfig.lSignalSource and ADLGlSyncPortControl.lSignalSource). int iGlSyncGPUPort; /// GL-Sync module firmware version of Boot Sector. int iFWBootSectorVersion; /// GL-Sync module firmware version of User Sector. int iFWUserSectorVersion; } ADLGLSyncModuleID , *LPADLGLSyncModuleID; ///////////////////////////////////////////////////////////////////////////////////////////// ///\brief Structure containing GL-Sync ports capabilities. /// /// This structure is used to retrieve hardware capabilities for the ports of the GL-Sync module /// for Workstation Framelock/Genlock (such as port type and number of associated LEDs). /// \nosubgrouping //////////////////////////////////////////////////////////////////////////////////////////// typedef struct ADLGLSyncPortCaps { /// Port type. Bitfield of ADL_GLSYNC_PORTTYPE_* \ref define_glsync int iPortType; /// Number of LEDs associated for this port. int iNumOfLEDs; }ADLGLSyncPortCaps, *LPADLGLSyncPortCaps; ///////////////////////////////////////////////////////////////////////////////////////////// ///\brief Structure containing GL-Sync Genlock settings. /// /// This structure is used to get and set genlock settings for the GPU ports of the GL-Sync module /// for Workstation Framelock/Genlock.\n /// \see define_glsync /// \nosubgrouping //////////////////////////////////////////////////////////////////////////////////////////// typedef struct ADLGLSyncGenlockConfig { /// Specifies what fields in this structure are valid \ref define_glsync int iValidMask; /// Delay (ms) generating a sync signal. int iSyncDelay; /// Vector of framelock control bits. Bitfield of ADL_GLSYNC_FRAMELOCKCNTL_* \ref define_glsync int iFramelockCntlVector; /// Source of the sync signal. Either GL_Sync GPU Port index or ADL_GLSYNC_SIGNALSOURCE_* \ref define_glsync int iSignalSource; /// Use sampled sync signal. A value of 0 specifies no sampling. int iSampleRate; /// For interlaced sync signals, the value can be ADL_GLSYNC_SYNCFIELD_1 or *_BOTH \ref define_glsync int iSyncField; /// The signal edge that should trigger synchronization. ADL_GLSYNC_TRIGGEREDGE_* \ref define_glsync int iTriggerEdge; /// Scan rate multiplier applied to the sync signal. ADL_GLSYNC_SCANRATECOEFF_* \ref define_glsync int iScanRateCoeff; }ADLGLSyncGenlockConfig, *LPADLGLSyncGenlockConfig; ///////////////////////////////////////////////////////////////////////////////////////////// ///\brief Structure containing GL-Sync port information. /// /// This structure is used to get status of the GL-Sync ports (BNC or RJ45s) /// for Workstation Framelock/Genlock. /// \see define_glsync /// \nosubgrouping //////////////////////////////////////////////////////////////////////////////////////////// typedef struct ADLGlSyncPortInfo { /// Type of GL-Sync port (ADL_GLSYNC_PORT_*). int iPortType; /// The number of LEDs for this port. It's also filled within ADLGLSyncPortCaps. int iNumOfLEDs; /// Port state ADL_GLSYNC_PORTSTATE_* \ref define_glsync int iPortState; /// Scanned frequency for this port (vertical refresh rate in milliHz; 60000 means 60 Hz). int iFrequency; /// Used for ADL_GLSYNC_PORT_BNC. It is ADL_GLSYNC_SIGNALTYPE_* \ref define_glsync int iSignalType; /// Used for ADL_GLSYNC_PORT_RJ45PORT*. It is GL_Sync GPU Port index or ADL_GLSYNC_SIGNALSOURCE_*. \ref define_glsync int iSignalSource; } ADLGlSyncPortInfo, *LPADLGlSyncPortInfo; ///////////////////////////////////////////////////////////////////////////////////////////// ///\brief Structure containing GL-Sync port control settings. /// /// This structure is used to configure the GL-Sync ports (RJ45s only) /// for Workstation Framelock/Genlock. /// \see define_glsync /// \nosubgrouping //////////////////////////////////////////////////////////////////////////////////////////// typedef struct ADLGlSyncPortControl { /// Port to control ADL_GLSYNC_PORT_RJ45PORT1 or ADL_GLSYNC_PORT_RJ45PORT2 \ref define_glsync int iPortType; /// Port control data ADL_GLSYNC_PORTCNTL_* \ref define_glsync int iControlVector; /// Source of the sync signal. Either GL_Sync GPU Port index or ADL_GLSYNC_SIGNALSOURCE_* \ref define_glsync int iSignalSource; } ADLGlSyncPortControl; ///////////////////////////////////////////////////////////////////////////////////////////// ///\brief Structure containing GL-Sync mode of a display. /// /// This structure is used to get and set GL-Sync mode settings for a display connected to /// an adapter attached to a GL-Sync module for Workstation Framelock/Genlock. /// \see define_glsync /// \nosubgrouping //////////////////////////////////////////////////////////////////////////////////////////// typedef struct ADLGlSyncMode { /// Mode control vector. Bitfield of ADL_GLSYNC_MODECNTL_* \ref define_glsync int iControlVector; /// Mode status vector. Bitfield of ADL_GLSYNC_MODECNTL_STATUS_* \ref define_glsync int iStatusVector; /// Index of GL-Sync connector used to genlock the display/controller. int iGLSyncConnectorIndex; } ADLGlSyncMode, *LPADLGlSyncMode; ///////////////////////////////////////////////////////////////////////////////////////////// ///\brief Structure containing GL-Sync mode of a display. /// /// This structure is used to get and set GL-Sync mode settings for a display connected to /// an adapter attached to a GL-Sync module for Workstation Framelock/Genlock. /// \see define_glsync /// \nosubgrouping //////////////////////////////////////////////////////////////////////////////////////////// typedef struct ADLGlSyncMode2 { /// Mode control vector. Bitfield of ADL_GLSYNC_MODECNTL_* \ref define_glsync int iControlVector; /// Mode status vector. Bitfield of ADL_GLSYNC_MODECNTL_STATUS_* \ref define_glsync int iStatusVector; /// Index of GL-Sync connector used to genlock the display/controller. int iGLSyncConnectorIndex; /// Index of the display to which this GLSync applies to. int iDisplayIndex; } ADLGlSyncMode2, *LPADLGlSyncMode2; ///////////////////////////////////////////////////////////////////////////////////////////// ///\brief Structure containing the packet info of a display. /// /// This structure is used to get and set the packet information of a display. /// This structure is used by ADLDisplayDataPacket. /// \nosubgrouping //////////////////////////////////////////////////////////////////////////////////////////// typedef struct ADLInfoPacket { char hb0; char hb1; char hb2; /// sb0~sb27 char sb[28]; }ADLInfoPacket; ///////////////////////////////////////////////////////////////////////////////////////////// ///\brief Structure containing the AVI packet info of a display. /// /// This structure is used to get and set AVI the packet info of a display. /// This structure is used by ADLDisplayDataPacket. /// \nosubgrouping //////////////////////////////////////////////////////////////////////////////////////////// typedef struct ADLAVIInfoPacket //Valid user defined data/ { /// byte 3, bit 7 char bPB3_ITC; /// byte 5, bit [7:4]. char bPB5; }ADLAVIInfoPacket; // Overdrive clock setting structure definition. ///////////////////////////////////////////////////////////////////////////////////////////// ///\brief Structure containing the Overdrive clock setting. /// /// This structure is used to get the Overdrive clock setting. /// This structure is used by ADLAdapterODClockInfo. /// \nosubgrouping //////////////////////////////////////////////////////////////////////////////////////////// typedef struct ADLODClockSetting { /// Deafult clock int iDefaultClock; /// Current clock int iCurrentClock; /// Maximum clcok int iMaxClock; /// Minimum clock int iMinClock; /// Requested clcock int iRequestedClock; /// Step int iStepClock; } ADLODClockSetting; ///////////////////////////////////////////////////////////////////////////////////////////// ///\brief Structure containing the Overdrive clock information. /// /// This structure is used to get the Overdrive clock information. /// This structure is used by the ADL_Display_ODClockInfo_Get() function. /// \nosubgrouping //////////////////////////////////////////////////////////////////////////////////////////// typedef struct ADLAdapterODClockInfo { /// Size of the structure int iSize; /// Flag \ref define_clockinfo_flags int iFlags; /// Memory Clock ADLODClockSetting sMemoryClock; /// Engine Clock ADLODClockSetting sEngineClock; } ADLAdapterODClockInfo; ///////////////////////////////////////////////////////////////////////////////////////////// ///\brief Structure containing the Overdrive clock configuration. /// /// This structure is used to set the Overdrive clock configuration. /// This structure is used by the ADL_Display_ODClockConfig_Set() function. /// \nosubgrouping //////////////////////////////////////////////////////////////////////////////////////////// typedef struct ADLAdapterODClockConfig { /// Size of the structure int iSize; /// Flag \ref define_clockinfo_flags int iFlags; /// Memory Clock int iMemoryClock; /// Engine Clock int iEngineClock; } ADLAdapterODClockConfig; ///////////////////////////////////////////////////////////////////////////////////////////// ///\brief Structure containing information about current power management related activity. /// /// This structure is used to store information about current power management related activity. /// This structure (Overdrive 5 interfaces) is used by the ADL_PM_CurrentActivity_Get() function. /// \nosubgrouping //////////////////////////////////////////////////////////////////////////////////////////// typedef struct ADLPMActivity { /// Must be set to the size of the structure int iSize; /// Current engine clock. int iEngineClock; /// Current memory clock. int iMemoryClock; /// Current core voltage. int iVddc; /// GPU utilization. int iActivityPercent; /// Performance level index. int iCurrentPerformanceLevel; /// Current PCIE bus speed. int iCurrentBusSpeed; /// Number of PCIE bus lanes. int iCurrentBusLanes; /// Maximum number of PCIE bus lanes. int iMaximumBusLanes; /// Reserved for future purposes. int iReserved; } ADLPMActivity; ///////////////////////////////////////////////////////////////////////////////////////////// ///\brief Structure containing information about thermal controller. /// /// This structure is used to store information about thermal controller. /// This structure is used by ADL_PM_ThermalDevices_Enum. /// \nosubgrouping //////////////////////////////////////////////////////////////////////////////////////////// typedef struct ADLThermalControllerInfo { /// Must be set to the size of the structure int iSize; /// Possible valies: \ref ADL_DL_THERMAL_DOMAIN_OTHER or \ref ADL_DL_THERMAL_DOMAIN_GPU. int iThermalDomain; /// GPU 0, 1, etc. int iDomainIndex; /// Possible valies: \ref ADL_DL_THERMAL_FLAG_INTERRUPT or \ref ADL_DL_THERMAL_FLAG_FANCONTROL int iFlags; } ADLThermalControllerInfo; ///////////////////////////////////////////////////////////////////////////////////////////// ///\brief Structure containing information about thermal controller temperature. /// /// This structure is used to store information about thermal controller temperature. /// This structure is used by the ADL_PM_Temperature_Get() function. /// \nosubgrouping //////////////////////////////////////////////////////////////////////////////////////////// typedef struct ADLTemperature { /// Must be set to the size of the structure int iSize; /// Temperature in millidegrees Celsius. int iTemperature; } ADLTemperature; ///////////////////////////////////////////////////////////////////////////////////////////// ///\brief Structure containing information about thermal controller fan speed. /// /// This structure is used to store information about thermal controller fan speed. /// This structure is used by the ADL_PM_FanSpeedInfo_Get() function. /// \nosubgrouping //////////////////////////////////////////////////////////////////////////////////////////// typedef struct ADLFanSpeedInfo { /// Must be set to the size of the structure int iSize; /// \ref define_fanctrl int iFlags; /// Minimum possible fan speed value in percents. int iMinPercent; /// Maximum possible fan speed value in percents. int iMaxPercent; /// Minimum possible fan speed value in RPM. int iMinRPM; /// Maximum possible fan speed value in RPM. int iMaxRPM; } ADLFanSpeedInfo; ///////////////////////////////////////////////////////////////////////////////////////////// ///\brief Structure containing information about fan speed reported by thermal controller. /// /// This structure is used to store information about fan speed reported by thermal controller. /// This structure is used by the ADL_Overdrive5_FanSpeed_Get() and ADL_Overdrive5_FanSpeed_Set() functions. /// \nosubgrouping //////////////////////////////////////////////////////////////////////////////////////////// typedef struct ADLFanSpeedValue { /// Must be set to the size of the structure int iSize; /// Possible valies: \ref ADL_DL_FANCTRL_SPEED_TYPE_PERCENT or \ref ADL_DL_FANCTRL_SPEED_TYPE_RPM int iSpeedType; /// Fan speed value int iFanSpeed; /// The only flag for now is: \ref ADL_DL_FANCTRL_FLAG_USER_DEFINED_SPEED int iFlags; } ADLFanSpeedValue; //////////////////////////////////////////////////////////////////////////////////////////// ///\brief Structure containing the range of Overdrive parameter. /// /// This structure is used to store information about the range of Overdrive parameter. /// This structure is used by ADLODParameters. /// \nosubgrouping //////////////////////////////////////////////////////////////////////////////////////////// typedef struct ADLODParameterRange { /// Minimum parameter value. int iMin; /// Maximum parameter value. int iMax; /// Parameter step value. int iStep; } ADLODParameterRange; ///////////////////////////////////////////////////////////////////////////////////////////// ///\brief Structure containing information about Overdrive parameters. /// /// This structure is used to store information about Overdrive parameters. /// This structure is used by the ADL_Overdrive5_ODParameters_Get() function. /// \nosubgrouping //////////////////////////////////////////////////////////////////////////////////////////// typedef struct ADLODParameters { /// Must be set to the size of the structure int iSize; /// Number of standard performance states. int iNumberOfPerformanceLevels; /// Indicates whether the GPU is capable to measure its activity. int iActivityReportingSupported; /// Indicates whether the GPU supports discrete performance levels or performance range. int iDiscretePerformanceLevels; /// Reserved for future use. int iReserved; /// Engine clock range. ADLODParameterRange sEngineClock; /// Memory clock range. ADLODParameterRange sMemoryClock; /// Core voltage range. ADLODParameterRange sVddc; } ADLODParameters; ///////////////////////////////////////////////////////////////////////////////////////////// ///\brief Structure containing information about Overdrive level. /// /// This structure is used to store information about Overdrive level. /// This structure is used by ADLODPerformanceLevels. /// \nosubgrouping //////////////////////////////////////////////////////////////////////////////////////////// typedef struct ADLODPerformanceLevel { /// Engine clock. int iEngineClock; /// Memory clock. int iMemoryClock; /// Core voltage. int iVddc; } ADLODPerformanceLevel; ///////////////////////////////////////////////////////////////////////////////////////////// ///\brief Structure containing information about Overdrive performance levels. /// /// This structure is used to store information about Overdrive performance levels. /// This structure is used by the ADL_Overdrive5_ODPerformanceLevels_Get() and ADL_Overdrive5_ODPerformanceLevels_Set() functions. /// \nosubgrouping //////////////////////////////////////////////////////////////////////////////////////////// typedef struct ADLODPerformanceLevels { /// Must be set to sizeof( \ref ADLODPerformanceLevels ) + sizeof( \ref ADLODPerformanceLevel ) * (ADLODParameters.iNumberOfPerformanceLevels - 1) int iSize; int iReserved; /// Array of performance state descriptors. Must have ADLODParameters.iNumberOfPerformanceLevels elements. ADLODPerformanceLevel aLevels [1]; } ADLODPerformanceLevels; ///////////////////////////////////////////////////////////////////////////////////////////// ///\brief Structure containing information about the proper CrossfireX chains combinations. /// /// This structure is used to store information about the CrossfireX chains combination for a particular adapter. /// This structure is used by the ADL_Adapter_Crossfire_Caps(), ADL_Adapter_Crossfire_Get(), and ADL_Adapter_Crossfire_Set() functions. /// \nosubgrouping //////////////////////////////////////////////////////////////////////////////////////////// typedef struct ADLCrossfireComb { /// Number of adapters in this combination. int iNumLinkAdapter; /// A list of ADL indexes of the linked adapters in this combination. int iAdaptLink[3]; } ADLCrossfireComb; ///////////////////////////////////////////////////////////////////////////////////////////// ///\brief Structure containing CrossfireX state and error information. /// /// This structure is used to store state and error information about a particular adapter CrossfireX combination. /// This structure is used by the ADL_Adapter_Crossfire_Get() function. /// \nosubgrouping //////////////////////////////////////////////////////////////////////////////////////////// typedef struct ADLCrossfireInfo { /// Current error code of this CrossfireX combination. int iErrorCode; /// Current \ref define_crossfirestate int iState; /// If CrossfireX is supported by this combination. The value is either \ref ADL_TRUE or \ref ADL_FALSE. int iSupported; } ADLCrossfireInfo; ///////////////////////////////////////////////////////////////////////////////////////////// /// \brief Structure containing information about the BIOS. /// /// This structure is used to store various information about the Chipset. This /// information can be returned to the user. /// \nosubgrouping //////////////////////////////////////////////////////////////////////////////////////////// typedef struct ADLBiosInfo { char strPartNumber[ADL_MAX_PATH]; ///< Part number. char strVersion[ADL_MAX_PATH]; ///< Version number. char strDate[ADL_MAX_PATH]; ///< BIOS date in yyyy/mm/dd hh:mm format. } ADLBiosInfo, *LPADLBiosInfo; ///////////////////////////////////////////////////////////////////////////////////////////// /// \brief Structure containing information about adapter location. /// /// This structure is used to store information about adapter location. /// This structure is used by ADLMVPUStatus. /// \nosubgrouping //////////////////////////////////////////////////////////////////////////////////////////// typedef struct ADLAdapterLocation { /// PCI Bus number : 8 bits int iBus; /// Device number : 5 bits int iDevice; /// Function number : 3 bits int iFunction; } ADLAdapterLocation,ADLBdf; ///////////////////////////////////////////////////////////////////////////////////////////// /// \brief Structure containing version information /// /// This structure is used to store software version information, description of the display device and a web link to the latest installed Catalyst drivers. /// \nosubgrouping //////////////////////////////////////////////////////////////////////////////////////////// typedef struct ADLVersionsInfo { /// Driver Release (Packaging) Version (e.g. 8.71-100128n-094835E-ATI) char strDriverVer[ADL_MAX_PATH]; /// Catalyst Version(e.g. "10.1"). char strCatalystVersion[ADL_MAX_PATH]; /// Web link to an XML file with information about the latest AMD drivers and locations (e.g. "http://www.amd.com/us/driverxml" ) char strCatalystWebLink[ADL_MAX_PATH]; } ADLVersionsInfo, *LPADLVersionsInfo; ///////////////////////////////////////////////////////////////////////////////////////////// /// \brief Structure containing version information /// /// This structure is used to store software version information, description of the display device and a web link to the latest installed Catalyst drivers. /// \nosubgrouping //////////////////////////////////////////////////////////////////////////////////////////// typedef struct ADLVersionsInfoX2 { /// Driver Release (Packaging) Version (e.g. "16.20.1035-160621a-303814C") char strDriverVer[ADL_MAX_PATH]; /// Catalyst Version(e.g. "15.8"). char strCatalystVersion[ADL_MAX_PATH]; /// Crimson Version(e.g. "16.6.2"). char strCrimsonVersion[ADL_MAX_PATH]; /// Web link to an XML file with information about the latest AMD drivers and locations (e.g. "http://support.amd.com/drivers/xml/driver_09_us.xml" ) char strCatalystWebLink[ADL_MAX_PATH]; } ADLVersionsInfoX2, *LPADLVersionsInfoX2; ///////////////////////////////////////////////////////////////////////////////////////////// /// \brief Structure containing information about MultiVPU capabilities. /// /// This structure is used to store information about MultiVPU capabilities. /// This structure is used by the ADL_Display_MVPUCaps_Get() function. /// \nosubgrouping //////////////////////////////////////////////////////////////////////////////////////////// typedef struct ADLMVPUCaps { /// Must be set to sizeof( ADLMVPUCaps ). int iSize; /// Number of adapters. int iAdapterCount; /// Bits set for all possible MVPU masters. \ref MVPU_ADAPTER_0 .. \ref MVPU_ADAPTER_3 int iPossibleMVPUMasters; /// Bits set for all possible MVPU slaves. \ref MVPU_ADAPTER_0 .. \ref MVPU_ADAPTER_3 int iPossibleMVPUSlaves; /// Registry path for each adapter. char cAdapterPath[ADL_DL_MAX_MVPU_ADAPTERS][ADL_DL_MAX_REGISTRY_PATH]; } ADLMVPUCaps; ///////////////////////////////////////////////////////////////////////////////////////////// /// \brief Structure containing information about MultiVPU status. /// /// This structure is used to store information about MultiVPU status. /// Ths structure is used by the ADL_Display_MVPUStatus_Get() function. /// \nosubgrouping //////////////////////////////////////////////////////////////////////////////////////////// typedef struct ADLMVPUStatus { /// Must be set to sizeof( ADLMVPUStatus ). int iSize; /// Number of active adapters. int iActiveAdapterCount; /// MVPU status. int iStatus; /// PCI Bus/Device/Function for each active adapter participating in MVPU. ADLAdapterLocation aAdapterLocation[ADL_DL_MAX_MVPU_ADAPTERS]; } ADLMVPUStatus; // Displays Manager structures /////////////////////////////////////////////////////////////////////////// /// \brief Structure containing information about the activatable source. /// /// This structure is used to store activatable source information /// This information can be returned to the user. /// \nosubgrouping //////////////////////////////////////////////////////////////////////////////////////////// typedef struct ADLActivatableSource { /// The Persistent logical Adapter Index. int iAdapterIndex; /// The number of Activatable Sources. int iNumActivatableSources; /// The bit mask identifies the number of bits ActivatableSourceValue is using. (Not currnetly used) int iActivatableSourceMask; /// The bit mask identifies the status. (Not currnetly used) int iActivatableSourceValue; } ADLActivatableSource, *LPADLActivatableSource; ///////////////////////////////////////////////////////////////////////////////////////////// /// \brief Structure containing information about display mode. /// /// This structure is used to store the display mode for the current adapter /// such as X, Y positions, screen resolutions, orientation, /// color depth, refresh rate, progressive or interlace mode, etc. /// \nosubgrouping //////////////////////////////////////////////////////////////////////////////////////////// typedef struct ADLMode { /// Adapter index. int iAdapterIndex; /// Display IDs. ADLDisplayID displayID; /// Screen position X coordinate. int iXPos; /// Screen position Y coordinate. int iYPos; /// Screen resolution Width. int iXRes; /// Screen resolution Height. int iYRes; /// Screen Color Depth. E.g., 16, 32. int iColourDepth; /// Screen refresh rate. Could be fractional E.g. 59.97 float fRefreshRate; /// Screen orientation. E.g., 0, 90, 180, 270. int iOrientation; /// Vista mode flag indicating Progressive or Interlaced mode. int iModeFlag; /// The bit mask identifying the number of bits this Mode is currently using. It is the sum of all the bit definitions defined in \ref define_displaymode int iModeMask; /// The bit mask identifying the display status. The detailed definition is in \ref define_displaymode int iModeValue; } ADLMode, *LPADLMode; ///////////////////////////////////////////////////////////////////////////////////////////// /// \brief Structure containing information about display target information. /// /// This structure is used to store the display target information. /// \nosubgrouping //////////////////////////////////////////////////////////////////////////////////////////// typedef struct ADLDisplayTarget { /// The Display ID. ADLDisplayID displayID; /// The display map index identify this manner and the desktop surface. int iDisplayMapIndex; /// The bit mask identifies the number of bits DisplayTarget is currently using. It is the sum of all the bit definitions defined in \ref ADL_DISPLAY_DISPLAYTARGET_PREFERRED. int iDisplayTargetMask; /// The bit mask identifies the display status. The detailed definition is in \ref ADL_DISPLAY_DISPLAYTARGET_PREFERRED. int iDisplayTargetValue; } ADLDisplayTarget, *LPADLDisplayTarget; ///////////////////////////////////////////////////////////////////////////////////////////// ///\brief Structure containing information about the display SLS bezel Mode information. /// /// This structure is used to store the display SLS bezel Mode information. /// \nosubgrouping //////////////////////////////////////////////////////////////////////////////////////////// typedef struct tagADLBezelTransientMode { /// Adapter Index int iAdapterIndex; /// SLS Map Index int iSLSMapIndex; /// The mode index int iSLSModeIndex; /// The mode ADLMode displayMode; /// The number of bezel offsets belongs to this map int iNumBezelOffset; /// The first bezel offset array index in the native mode array int iFirstBezelOffsetArrayIndex; /// The bit mask identifies the bits this structure is currently using. It will be the total OR of all the bit definitions. int iSLSBezelTransientModeMask; /// The bit mask identifies the display status. The detail definition is defined below. int iSLSBezelTransientModeValue; } ADLBezelTransientMode, *LPADLBezelTransientMode; ///////////////////////////////////////////////////////////////////////////////////////////// /// \brief Structure containing information about the adapter display manner. /// /// This structure is used to store adapter display manner information /// This information can be returned to the user. Alternatively, it can be used to access various driver calls to /// fetch various display device related display manner settings upon the user's request. /// \nosubgrouping //////////////////////////////////////////////////////////////////////////////////////////// typedef struct ADLAdapterDisplayCap { /// The Persistent logical Adapter Index. int iAdapterIndex; /// The bit mask identifies the number of bits AdapterDisplayCap is currently using. Sum all the bits defined in ADL_ADAPTER_DISPLAYCAP_XXX int iAdapterDisplayCapMask; /// The bit mask identifies the status. Refer to ADL_ADAPTER_DISPLAYCAP_XXX int iAdapterDisplayCapValue; } ADLAdapterDisplayCap, *LPADLAdapterDisplayCap; ///////////////////////////////////////////////////////////////////////////////////////////// ///\brief Structure containing information about display mapping. /// /// This structure is used to store the display mapping data such as display manner. /// For displays with horizontal or vertical stretch manner, /// this structure also stores the display order, display row, and column data. /// \nosubgrouping //////////////////////////////////////////////////////////////////////////////////////////// typedef struct ADLDisplayMap { /// The current display map index. It is the OS desktop index. For example, if the OS index 1 is showing clone mode, the display map will be 1. int iDisplayMapIndex; /// The Display Mode for the current map ADLMode displayMode; /// The number of display targets belongs to this map\n int iNumDisplayTarget; /// The first target array index in the Target array\n int iFirstDisplayTargetArrayIndex; /// The bit mask identifies the number of bits DisplayMap is currently using. It is the sum of all the bit definitions defined in ADL_DISPLAY_DISPLAYMAP_MANNER_xxx. int iDisplayMapMask; ///The bit mask identifies the display status. The detailed definition is in ADL_DISPLAY_DISPLAYMAP_MANNER_xxx. int iDisplayMapValue; } ADLDisplayMap, *LPADLDisplayMap; ///////////////////////////////////////////////////////////////////////////////////////////// /// \brief Structure containing information about the display device possible map for one GPU /// /// This structure is used to store the display device possible map /// This information can be returned to the user. /// \nosubgrouping //////////////////////////////////////////////////////////////////////////////////////////// typedef struct ADLPossibleMap { /// The current PossibleMap index. Each PossibleMap is assigned an index int iIndex; /// The adapter index identifying the GPU for which to validate these Maps & Targets int iAdapterIndex; /// Number of display Maps for this GPU to be validated int iNumDisplayMap; /// The display Maps list to validate ADLDisplayMap* displayMap; /// the number of display Targets for these display Maps int iNumDisplayTarget; /// The display Targets list for these display Maps to be validated. ADLDisplayTarget* displayTarget; } ADLPossibleMap, *LPADLPossibleMap; ///////////////////////////////////////////////////////////////////////////////////////////// /// \brief Structure containing information about display possible mapping. /// /// This structure is used to store the display possible mapping's controller index for the current display. /// \nosubgrouping //////////////////////////////////////////////////////////////////////////////////////////// typedef struct ADLPossibleMapping { int iDisplayIndex; ///< The display index. Each display is assigned an index. int iDisplayControllerIndex; ///< The controller index to which display is mapped. int iDisplayMannerSupported; ///< The supported display manner. } ADLPossibleMapping, *LPADLPossibleMapping; ///////////////////////////////////////////////////////////////////////////////////////////// /// \brief Structure containing information about the validated display device possible map result. /// /// This structure is used to store the validated display device possible map result /// This information can be returned to the user. /// \nosubgrouping //////////////////////////////////////////////////////////////////////////////////////////// typedef struct ADLPossibleMapResult { /// The current display map index. It is the OS Desktop index. For example, OS Index 1 showing clone mode. The Display Map will be 1. int iIndex; // The bit mask identifies the number of bits PossibleMapResult is currently using. It will be the sum all the bit definitions defined in ADL_DISPLAY_POSSIBLEMAPRESULT_VALID. int iPossibleMapResultMask; /// The bit mask identifies the possible map result. The detail definition is defined in ADL_DISPLAY_POSSIBLEMAPRESULT_XXX. int iPossibleMapResultValue; } ADLPossibleMapResult, *LPADLPossibleMapResult; ///////////////////////////////////////////////////////////////////////////////////////////// ///\brief Structure containing information about the display SLS Grid information. /// /// This structure is used to store the display SLS Grid information. /// \nosubgrouping //////////////////////////////////////////////////////////////////////////////////////////// typedef struct ADLSLSGrid { /// The Adapter index. int iAdapterIndex; /// The grid index. int iSLSGridIndex; /// The grid row. int iSLSGridRow; /// The grid column. int iSLSGridColumn; /// The grid bit mask identifies the number of bits DisplayMap is currently using. Sum of all bits defined in ADL_DISPLAY_SLSGRID_ORIENTATION_XXX int iSLSGridMask; /// The grid bit value identifies the display status. Refer to ADL_DISPLAY_SLSGRID_ORIENTATION_XXX int iSLSGridValue; } ADLSLSGrid, *LPADLSLSGrid; ///////////////////////////////////////////////////////////////////////////////////////////// ///\brief Structure containing information about the display SLS Map information. /// /// This structure is used to store the display SLS Map information. /// \nosubgrouping //////////////////////////////////////////////////////////////////////////////////////////// typedef struct ADLSLSMap { /// The Adapter Index int iAdapterIndex; /// The current display map index. It is the OS Desktop index. For example, OS Index 1 showing clone mode. The Display Map will be 1. int iSLSMapIndex; /// Indicate the current grid ADLSLSGrid grid; /// OS surface index int iSurfaceMapIndex; /// Screen orientation. E.g., 0, 90, 180, 270 int iOrientation; /// The number of display targets belongs to this map int iNumSLSTarget; /// The first target array index in the Target array int iFirstSLSTargetArrayIndex; /// The number of native modes belongs to this map int iNumNativeMode; /// The first native mode array index in the native mode array int iFirstNativeModeArrayIndex; /// The number of bezel modes belongs to this map int iNumBezelMode; /// The first bezel mode array index in the native mode array int iFirstBezelModeArrayIndex; /// The number of bezel offsets belongs to this map int iNumBezelOffset; /// The first bezel offset array index in the int iFirstBezelOffsetArrayIndex; /// The bit mask identifies the number of bits DisplayMap is currently using. Sum all the bit definitions defined in ADL_DISPLAY_SLSMAP_XXX. int iSLSMapMask; /// The bit mask identifies the display map status. Refer to ADL_DISPLAY_SLSMAP_XXX int iSLSMapValue; } ADLSLSMap, *LPADLSLSMap; ///////////////////////////////////////////////////////////////////////////////////////////// ///\brief Structure containing information about the display SLS Offset information. /// /// This structure is used to store the display SLS Offset information. /// \nosubgrouping //////////////////////////////////////////////////////////////////////////////////////////// typedef struct ADLSLSOffset { /// The Adapter Index int iAdapterIndex; /// The current display map index. It is the OS Desktop index. For example, OS Index 1 showing clone mode. The Display Map will be 1. int iSLSMapIndex; /// The Display ID. ADLDisplayID displayID; /// SLS Bezel Mode Index int iBezelModeIndex; /// SLS Bezel Offset X int iBezelOffsetX; /// SLS Bezel Offset Y int iBezelOffsetY; /// SLS Display Width int iDisplayWidth; /// SLS Display Height int iDisplayHeight; /// The bit mask identifies the number of bits Offset is currently using. int iBezelOffsetMask; /// The bit mask identifies the display status. int iBezelffsetValue; } ADLSLSOffset, *LPADLSLSOffset; ///////////////////////////////////////////////////////////////////////////////////////////// ///\brief Structure containing information about the display SLS Mode information. /// /// This structure is used to store the display SLS Mode information. /// \nosubgrouping //////////////////////////////////////////////////////////////////////////////////////////// typedef struct ADLSLSMode { /// The Adapter Index int iAdapterIndex; /// The current display map index. It is the OS Desktop index. For example, OS Index 1 showing clone mode. The Display Map will be 1. int iSLSMapIndex; /// The mode index int iSLSModeIndex; /// The mode for this map. ADLMode displayMode; /// The bit mask identifies the number of bits Mode is currently using. int iSLSNativeModeMask; /// The bit mask identifies the display status. int iSLSNativeModeValue; } ADLSLSMode, *LPADLSLSMode; ///////////////////////////////////////////////////////////////////////////////////////////// ///\brief Structure containing information about the display Possible SLS Map information. /// /// This structure is used to store the display Possible SLS Map information. /// \nosubgrouping //////////////////////////////////////////////////////////////////////////////////////////// typedef struct ADLPossibleSLSMap { /// The current display map index. It is the OS Desktop index. /// For example, OS Index 1 showing clone mode. The Display Map will be 1. int iSLSMapIndex; /// Number of display map to be validated. int iNumSLSMap; /// The display map list for validation ADLSLSMap* lpSLSMap; /// the number of display map config to be validated. int iNumSLSTarget; /// The display target list for validation. ADLDisplayTarget* lpDisplayTarget; } ADLPossibleSLSMap, *LPADLPossibleSLSMap; ///////////////////////////////////////////////////////////////////////////////////////////// ///\brief Structure containing information about the SLS targets. /// /// This structure is used to store the SLS targets information. /// \nosubgrouping //////////////////////////////////////////////////////////////////////////////////////////// typedef struct ADLSLSTarget { /// the logic adapter index int iAdapterIndex; /// The SLS map index int iSLSMapIndex; /// The target ID ADLDisplayTarget displayTarget; /// Target postion X in SLS grid int iSLSGridPositionX; /// Target postion Y in SLS grid int iSLSGridPositionY; /// The view size width, height and rotation angle per SLS Target ADLMode viewSize; /// The bit mask identifies the bits in iSLSTargetValue are currently used int iSLSTargetMask; /// The bit mask identifies status info. It is for function extension purpose int iSLSTargetValue; } ADLSLSTarget, *LPADLSLSTarget; ///////////////////////////////////////////////////////////////////////////////////////////// ///\brief Structure containing information about the Adapter offset stepping size. /// /// This structure is used to store the Adapter offset stepping size information. /// \nosubgrouping //////////////////////////////////////////////////////////////////////////////////////////// typedef struct ADLBezelOffsetSteppingSize { /// the logic adapter index int iAdapterIndex; /// The SLS map index int iSLSMapIndex; /// Bezel X stepping size offset int iBezelOffsetSteppingSizeX; /// Bezel Y stepping size offset int iBezelOffsetSteppingSizeY; /// Identifies the bits this structure is currently using. It will be the total OR of all the bit definitions. int iBezelOffsetSteppingSizeMask; /// Bit mask identifies the display status. int iBezelOffsetSteppingSizeValue; } ADLBezelOffsetSteppingSize, *LPADLBezelOffsetSteppingSize; ///////////////////////////////////////////////////////////////////////////////////////////// ///\brief Structure containing information about the overlap offset info for all the displays for each SLS mode. /// /// This structure is used to store the no. of overlapped modes for each SLS Mode once user finishes the configuration from Overlap Widget /// \nosubgrouping //////////////////////////////////////////////////////////////////////////////////////////// typedef struct ADLSLSOverlappedMode { /// the SLS mode for which the overlap is configured ADLMode SLSMode; /// the number of target displays in SLS. int iNumSLSTarget; /// the first target array index in the target array int iFirstTargetArrayIndex; }ADLSLSTargetOverlap, *LPADLSLSTargetOverlap; ///////////////////////////////////////////////////////////////////////////////////////////// ///\brief Structure containing information about driver supported PowerExpress Config Caps /// /// This structure is used to store the driver supported PowerExpress Config Caps /// \nosubgrouping //////////////////////////////////////////////////////////////////////////////////////////// typedef struct ADLPXConfigCaps { /// The Persistent logical Adapter Index. int iAdapterIndex; /// The bit mask identifies the number of bits PowerExpress Config Caps is currently using. It is the sum of all the bit definitions defined in ADL_PX_CONFIGCAPS_XXXX /ref define_powerxpress_constants. int iPXConfigCapMask; /// The bit mask identifies the PowerExpress Config Caps value. The detailed definition is in ADL_PX_CONFIGCAPS_XXXX /ref define_powerxpress_constants. int iPXConfigCapValue; } ADLPXConfigCaps, *LPADLPXConfigCaps; ///////////////////////////////////////////////////////////////////////////////////////// ///\brief Enum containing PX or HG type /// /// This enum is used to get PX or hG type /// /// \nosubgrouping ////////////////////////////////////////////////////////////////////////////////////////// typedef enum ADLPxType { //Not AMD related PX/HG or not PX or HG at all ADL_PX_NONE = 0, //A+A PX ADL_SWITCHABLE_AMDAMD = 1, // A+A HG ADL_HG_AMDAMD = 2, //A+I PX ADL_SWITCHABLE_AMDOTHER = 3, //A+I HG ADL_HG_AMDOTHER = 4, }ADLPxType; ///////////////////////////////////////////////////////////////////////////////////////////// ///\brief Structure containing information about an application /// /// This structure is used to store basic information of an application /// \nosubgrouping //////////////////////////////////////////////////////////////////////////////////////////// typedef struct ADLApplicationData { /// Path Name char strPathName[ADL_MAX_PATH]; /// File Name char strFileName[ADL_APP_PROFILE_FILENAME_LENGTH]; /// Creation timestamp char strTimeStamp[ADL_APP_PROFILE_TIMESTAMP_LENGTH]; /// Version char strVersion[ADL_APP_PROFILE_VERSION_LENGTH]; }ADLApplicationData; ///////////////////////////////////////////////////////////////////////////////////////////// ///\brief Structure containing information about an application /// /// This structure is used to store basic information of an application /// \nosubgrouping //////////////////////////////////////////////////////////////////////////////////////////// typedef struct ADLApplicationDataX2 { /// Path Name wchar_t strPathName[ADL_MAX_PATH]; /// File Name wchar_t strFileName[ADL_APP_PROFILE_FILENAME_LENGTH]; /// Creation timestamp wchar_t strTimeStamp[ADL_APP_PROFILE_TIMESTAMP_LENGTH]; /// Version wchar_t strVersion[ADL_APP_PROFILE_VERSION_LENGTH]; }ADLApplicationDataX2; ///////////////////////////////////////////////////////////////////////////////////////////// ///\brief Structure containing information about an application /// /// This structure is used to store basic information of an application including process id /// \nosubgrouping //////////////////////////////////////////////////////////////////////////////////////////// typedef struct ADLApplicationDataX3 { /// Path Name wchar_t strPathName[ADL_MAX_PATH]; /// File Name wchar_t strFileName[ADL_APP_PROFILE_FILENAME_LENGTH]; /// Creation timestamp wchar_t strTimeStamp[ADL_APP_PROFILE_TIMESTAMP_LENGTH]; /// Version wchar_t strVersion[ADL_APP_PROFILE_VERSION_LENGTH]; //Application Process id unsigned int iProcessId; }ADLApplicationDataX3; ///////////////////////////////////////////////////////////////////////////////////////////// ///\brief Structure containing information of a property of an application profile /// /// This structure is used to store property information of an application profile /// \nosubgrouping //////////////////////////////////////////////////////////////////////////////////////////// typedef struct PropertyRecord { /// Property Name char strName [ADL_APP_PROFILE_PROPERTY_LENGTH]; /// Property Type ADLProfilePropertyType eType; /// Data Size in bytes int iDataSize; /// Property Value, can be any data type unsigned char uData[1]; }PropertyRecord; ///////////////////////////////////////////////////////////////////////////////////////////// ///\brief Structure containing information about an application profile /// /// This structure is used to store information of an application profile /// \nosubgrouping //////////////////////////////////////////////////////////////////////////////////////////// typedef struct ADLApplicationProfile { /// Number of properties int iCount; /// Buffer to store all property records PropertyRecord record[1]; }ADLApplicationProfile; ///////////////////////////////////////////////////////////////////////////////////////////// ///\brief Structure containing information about an OD5 Power Control feature /// /// This structure is used to store information of an Power Control feature /// \nosubgrouping //////////////////////////////////////////////////////////////////////////////////////////// typedef struct ADLPowerControlInfo { /// Minimum value. int iMinValue; /// Maximum value. int iMaxValue; /// The minimum change in between minValue and maxValue. int iStepValue; } ADLPowerControlInfo; ///////////////////////////////////////////////////////////////////////////////////////////// ///\brief Structure containing information about an controller mode /// /// This structure is used to store information of an controller mode /// \nosubgrouping //////////////////////////////////////////////////////////////////////////////////////////// typedef struct ADLControllerMode { /// This falg indicates actions that will be applied by set viewport /// The value can be a combination of ADL_CONTROLLERMODE_CM_MODIFIER_VIEW_POSITION, /// ADL_CONTROLLERMODE_CM_MODIFIER_VIEW_PANLOCK and ADL_CONTROLLERMODE_CM_MODIFIER_VIEW_SIZE int iModifiers; /// Horizontal view starting position int iViewPositionCx; /// Vertical view starting position int iViewPositionCy; /// Horizontal left panlock position int iViewPanLockLeft; /// Horizontal right panlock position int iViewPanLockRight; /// Vertical top panlock position int iViewPanLockTop; /// Vertical bottom panlock position int iViewPanLockBottom; /// View resolution in pixels (width) int iViewResolutionCx; /// View resolution in pixels (hight) int iViewResolutionCy; }ADLControllerMode; ///////////////////////////////////////////////////////////////////////////////////////////// ///\brief Structure containing information about a display /// /// This structure is used to store information about a display /// \nosubgrouping //////////////////////////////////////////////////////////////////////////////////////////// typedef struct ADLDisplayIdentifier { /// ADL display index long ulDisplayIndex; /// manufacturer ID of the display long ulManufacturerId; /// product ID of the display long ulProductId; /// serial number of the display long ulSerialNo; } ADLDisplayIdentifier; ///////////////////////////////////////////////////////////////////////////////////////////// ///\brief Structure containing information about Overdrive 6 clock range /// /// This structure is used to store information about Overdrive 6 clock range /// \nosubgrouping //////////////////////////////////////////////////////////////////////////////////////////// typedef struct ADLOD6ParameterRange { /// The starting value of the clock range int iMin; /// The ending value of the clock range int iMax; /// The minimum increment between clock values int iStep; } ADLOD6ParameterRange; ///////////////////////////////////////////////////////////////////////////////////////////// ///\brief Structure containing information about Overdrive 6 capabilities /// /// This structure is used to store information about Overdrive 6 capabilities /// \nosubgrouping //////////////////////////////////////////////////////////////////////////////////////////// typedef struct ADLOD6Capabilities { /// Contains a bitmap of the OD6 capability flags. Possible values: \ref ADL_OD6_CAPABILITY_SCLK_CUSTOMIZATION, /// \ref ADL_OD6_CAPABILITY_MCLK_CUSTOMIZATION, \ref ADL_OD6_CAPABILITY_GPU_ACTIVITY_MONITOR int iCapabilities; /// Contains a bitmap indicating the power states /// supported by OD6. Currently only the performance state /// is supported. Possible Values: \ref ADL_OD6_SUPPORTEDSTATE_PERFORMANCE int iSupportedStates; /// Number of levels. OD6 will always use 2 levels, which describe /// the minimum to maximum clock ranges. /// The 1st level indicates the minimum clocks, and the 2nd level /// indicates the maximum clocks. int iNumberOfPerformanceLevels; /// Contains the hard limits of the sclk range. Overdrive /// clocks cannot be set outside this range. ADLOD6ParameterRange sEngineClockRange; /// Contains the hard limits of the mclk range. Overdrive /// clocks cannot be set outside this range. ADLOD6ParameterRange sMemoryClockRange; /// Value for future extension int iExtValue; /// Mask for future extension int iExtMask; } ADLOD6Capabilities; ///////////////////////////////////////////////////////////////////////////////////////////// ///\brief Structure containing information about Overdrive 6 clock values. /// /// This structure is used to store information about Overdrive 6 clock values. /// \nosubgrouping //////////////////////////////////////////////////////////////////////////////////////////// typedef struct ADLOD6PerformanceLevel { /// Engine (core) clock. int iEngineClock; /// Memory clock. int iMemoryClock; } ADLOD6PerformanceLevel; ///////////////////////////////////////////////////////////////////////////////////////////// ///\brief Structure containing information about Overdrive 6 clocks. /// /// This structure is used to store information about Overdrive 6 clocks. This is a /// variable-sized structure. iNumberOfPerformanceLevels indicate how many elements /// are contained in the aLevels array. /// \nosubgrouping //////////////////////////////////////////////////////////////////////////////////////////// typedef struct ADLOD6StateInfo { /// Number of levels. OD6 uses clock ranges instead of discrete performance levels. /// iNumberOfPerformanceLevels is always 2. The 1st level indicates the minimum clocks /// in the range. The 2nd level indicates the maximum clocks in the range. int iNumberOfPerformanceLevels; /// Value for future extension int iExtValue; /// Mask for future extension int iExtMask; /// Variable-sized array of levels. /// The number of elements in the array is specified by iNumberofPerformanceLevels. ADLOD6PerformanceLevel aLevels [1]; } ADLOD6StateInfo; ///////////////////////////////////////////////////////////////////////////////////////////// ///\brief Structure containing information about current Overdrive 6 performance status. /// /// This structure is used to store information about current Overdrive 6 performance status. /// \nosubgrouping //////////////////////////////////////////////////////////////////////////////////////////// typedef struct ADLOD6CurrentStatus { /// Current engine clock in 10 KHz. int iEngineClock; /// Current memory clock in 10 KHz. int iMemoryClock; /// Current GPU activity in percent. This /// indicates how "busy" the GPU is. int iActivityPercent; /// Not used. Reserved for future use. int iCurrentPerformanceLevel; /// Current PCI-E bus speed int iCurrentBusSpeed; /// Current PCI-E bus # of lanes int iCurrentBusLanes; /// Maximum possible PCI-E bus # of lanes int iMaximumBusLanes; /// Value for future extension int iExtValue; /// Mask for future extension int iExtMask; } ADLOD6CurrentStatus; ///////////////////////////////////////////////////////////////////////////////////////////// ///\brief Structure containing information about Overdrive 6 thermal contoller capabilities /// /// This structure is used to store information about Overdrive 6 thermal controller capabilities /// \nosubgrouping //////////////////////////////////////////////////////////////////////////////////////////// typedef struct ADLOD6ThermalControllerCaps { /// Contains a bitmap of thermal controller capability flags. Possible values: \ref ADL_OD6_TCCAPS_THERMAL_CONTROLLER, \ref ADL_OD6_TCCAPS_FANSPEED_CONTROL, /// \ref ADL_OD6_TCCAPS_FANSPEED_PERCENT_READ, \ref ADL_OD6_TCCAPS_FANSPEED_PERCENT_WRITE, \ref ADL_OD6_TCCAPS_FANSPEED_RPM_READ, \ref ADL_OD6_TCCAPS_FANSPEED_RPM_WRITE int iCapabilities; /// Minimum fan speed expressed as a percentage int iFanMinPercent; /// Maximum fan speed expressed as a percentage int iFanMaxPercent; /// Minimum fan speed expressed in revolutions-per-minute int iFanMinRPM; /// Maximum fan speed expressed in revolutions-per-minute int iFanMaxRPM; /// Value for future extension int iExtValue; /// Mask for future extension int iExtMask; } ADLOD6ThermalControllerCaps; ///////////////////////////////////////////////////////////////////////////////////////////// ///\brief Structure containing information about Overdrive 6 fan speed information /// /// This structure is used to store information about Overdrive 6 fan speed information /// \nosubgrouping //////////////////////////////////////////////////////////////////////////////////////////// typedef struct ADLOD6FanSpeedInfo { /// Contains a bitmap of the valid fan speed type flags. Possible values: \ref ADL_OD6_FANSPEED_TYPE_PERCENT, \ref ADL_OD6_FANSPEED_TYPE_RPM, \ref ADL_OD6_FANSPEED_USER_DEFINED int iSpeedType; /// Contains current fan speed in percent (if valid flag exists in iSpeedType) int iFanSpeedPercent; /// Contains current fan speed in RPM (if valid flag exists in iSpeedType) int iFanSpeedRPM; /// Value for future extension int iExtValue; /// Mask for future extension int iExtMask; } ADLOD6FanSpeedInfo; ///////////////////////////////////////////////////////////////////////////////////////////// ///\brief Structure containing information about Overdrive 6 fan speed value /// /// This structure is used to store information about Overdrive 6 fan speed value /// \nosubgrouping //////////////////////////////////////////////////////////////////////////////////////////// typedef struct ADLOD6FanSpeedValue { /// Indicates the units of the fan speed. Possible values: \ref ADL_OD6_FANSPEED_TYPE_PERCENT, \ref ADL_OD6_FANSPEED_TYPE_RPM int iSpeedType; /// Fan speed value (units as indicated above) int iFanSpeed; /// Value for future extension int iExtValue; /// Mask for future extension int iExtMask; } ADLOD6FanSpeedValue; ///////////////////////////////////////////////////////////////////////////////////////////// ///\brief Structure containing information about Overdrive 6 PowerControl settings. /// /// This structure is used to store information about Overdrive 6 PowerControl settings. /// PowerControl is the feature which allows the performance characteristics of the GPU /// to be adjusted by changing the PowerTune power limits. /// \nosubgrouping //////////////////////////////////////////////////////////////////////////////////////////// typedef struct ADLOD6PowerControlInfo { /// The minimum PowerControl adjustment value int iMinValue; /// The maximum PowerControl adjustment value int iMaxValue; /// The minimum difference between PowerControl adjustment values int iStepValue; /// Value for future extension int iExtValue; /// Mask for future extension int iExtMask; } ADLOD6PowerControlInfo; ///////////////////////////////////////////////////////////////////////////////////////////// ///\brief Structure containing information about Overdrive 6 PowerControl settings. /// /// This structure is used to store information about Overdrive 6 PowerControl settings. /// PowerControl is the feature which allows the performance characteristics of the GPU /// to be adjusted by changing the PowerTune power limits. /// \nosubgrouping //////////////////////////////////////////////////////////////////////////////////////////// typedef struct ADLOD6VoltageControlInfo { /// The minimum VoltageControl adjustment value int iMinValue; /// The maximum VoltageControl adjustment value int iMaxValue; /// The minimum difference between VoltageControl adjustment values int iStepValue; /// Value for future extension int iExtValue; /// Mask for future extension int iExtMask; } ADLOD6VoltageControlInfo; //////////////////////////////////////////////////////////////////////////////////////////// ///\brief Structure containing ECC statistics namely SEC counts and DED counts /// Single error count - count of errors that can be corrected /// Doubt Error Detect - count of errors that cannot be corrected /// \nosubgrouping //////////////////////////////////////////////////////////////////////////////////////////// typedef struct ADLECCData { // Single error count - count of errors that can be corrected int iSec; // Double error detect - count of errors that cannot be corrected int iDed; } ADLECCData; /// \brief Handle to ADL client context. /// /// ADL clients obtain context handle from initial call to \ref ADL2_Main_Control_Create. /// Clients have to pass the handle to each subsequent ADL call and finally destroy /// the context with call to \ref ADL2_Main_Control_Destroy /// \nosubgrouping typedef void *ADL_CONTEXT_HANDLE; /// \brief Handle to ADL Frame Monitor Token. /// /// Frame Monitor clients obtain handle from initial call to \ref ADL2_Adapter_FrameMetrics_FrameDuration_Enable /// Clients have to pass the handle to each subsequent ADL call to \ref ADL2_Adapter_FrameMetrics_FrameDuration_Get /// and finally destroy the token with call to \ref ADL2_Adapter_FrameMetrics_FrameDuration_Disable /// \nosubgrouping typedef void *ADL_FRAME_DURATION_HANDLE; ///////////////////////////////////////////////////////////////////////////////////////////// ///\brief Structure containing the display mode definition used per controller. /// /// This structure is used to store the display mode definition used per controller. /// \nosubgrouping //////////////////////////////////////////////////////////////////////////////////////////// typedef struct ADLDisplayModeX2 { /// Horizontal resolution (in pixels). int iWidth; /// Vertical resolution (in lines). int iHeight; /// Interlaced/Progressive. The value will be set for Interlaced as ADL_DL_TIMINGFLAG_INTERLACED. If not set it is progressive. Refer define_detailed_timing_flags. int iScanType; /// Refresh rate. int iRefreshRate; /// Timing Standard. Refer define_modetiming_standard. int iTimingStandard; } ADLDisplayModeX2; typedef enum ADLAppProcessState { APP_PROC_INVALID = 0, // Invalid Application APP_PROC_PREMPTION = 1, // The Application is being set up for Process Creation APP_PROC_CREATION = 2, // The Application's Main Process is created by the OS APP_PROC_READ = 3, // The Application's Data is ready to be read APP_PROC_WAIT = 4, // The Application is waiting for Timeout or Notification to Resume APP_PROC_RUNNING = 5, // The Application is running APP_PROC_TERMINATE = 6 // The Application is about to terminate }ADLAppProcessState; typedef enum ADLAppInterceptionListType { ADL_INVALID_FORMAT = 0, ADL_IMAGEFILEFORMAT = 1, ADL_ENVVAR = 2 }ADLAppInterceptionListType; typedef struct ADLAppInterceptionInfo { wchar_t AppName[ADL_MAX_PATH]; // the file name of the application or env var unsigned int ProcessId; ADLAppInterceptionListType AppFormat; ADLAppProcessState AppState; } ADLAppInterceptionInfo; typedef enum ADL_AP_DATABASE // same as _SHARED_AP_DATABASE in "inc/shared/shared_escape.h" { ADL_AP_DATABASE__SYSTEM, ADL_AP_DATABASE__USER, ADL_AP_DATABASE__OEM } ADL_AP_DATABASE; typedef struct ADLAppInterceptionInfoX2 { wchar_t AppName[ADL_MAX_PATH]; // the file name of the application or env var unsigned int ProcessId; unsigned int WaitForResumeNeeded; wchar_t CommandLine[ADL_MAX_PATH]; // The command line on app start/stop event ADLAppInterceptionListType AppFormat; ADLAppProcessState AppState; } ADLAppInterceptionInfoX2; typedef struct ADLAppInterceptionInfoX3 { wchar_t AppName[ADL_MAX_PATH]; // the file name of the application or env var unsigned int ProcessId; unsigned int WaitForResumeNeeded; unsigned int RayTracingStatus; // returns the Ray Tracing status if it is enabled atleast once in session. wchar_t CommandLine[ADL_MAX_PATH]; // The command line on app start/stop event ADLAppInterceptionListType AppFormat; ADLAppProcessState AppState; } ADLAppInterceptionInfoX3; ///////////////////////////////////////////////////////////////////////////////////////////// ///\brief Structure containing information info for a property record in a profile /// /// This structure is used to store info for a property record in a profile /// \nosubgrouping //////////////////////////////////////////////////////////////////////////////////////////// typedef struct ADLPropertyRecordCreate { /// Name of the property wchar_t * strPropertyName; /// Data type of the property ADLProfilePropertyType eType; // Value of the property wchar_t * strPropertyValue; } ADLPropertyRecordCreate; ///////////////////////////////////////////////////////////////////////////////////////////// ///\brief Structure containing information info for an application record /// /// This structure is used to store info for an application record /// \nosubgrouping //////////////////////////////////////////////////////////////////////////////////////////// typedef struct ADLApplicationRecord { /// Title of the application wchar_t * strTitle; /// File path of the application wchar_t * strPathName; /// File name of the application wchar_t * strFileName; /// File versin the application wchar_t * strVersion; /// Nostes on the application wchar_t * strNotes; /// Driver area which the application uses wchar_t * strArea; /// Name of profile assigned to the application wchar_t * strProfileName; // Source where this application record come from ADL_AP_DATABASE recordSource; } ADLApplicationRecord; ///////////////////////////////////////////////////////////////////////////////////////////// ///\brief Structure containing information about Overdrive 6 extension capabilities /// /// This structure is used to store information about Overdrive 6 extension capabilities /// \nosubgrouping //////////////////////////////////////////////////////////////////////////////////////////// typedef struct ADLOD6CapabilitiesEx { /// Contains a bitmap of the OD6 extension capability flags. Possible values: \ref ADL_OD6_CAPABILITY_SCLK_CUSTOMIZATION, /// \ref ADL_OD6_CAPABILITY_MCLK_CUSTOMIZATION, \ref ADL_OD6_CAPABILITY_GPU_ACTIVITY_MONITOR, /// \ref ADL_OD6_CAPABILITY_POWER_CONTROL, \ref ADL_OD6_CAPABILITY_VOLTAGE_CONTROL, \ref ADL_OD6_CAPABILITY_PERCENT_ADJUSTMENT, //// \ref ADL_OD6_CAPABILITY_THERMAL_LIMIT_UNLOCK int iCapabilities; /// The Power states that support clock and power customization. Only performance state is currently supported. /// Possible Values: \ref ADL_OD6_SUPPORTEDSTATE_PERFORMANCE int iSupportedStates; /// Returns the hard limits of the SCLK overdrive adjustment range. Overdrive clocks should not be adjusted outside of this range. The values are specified as +/- percentages. ADLOD6ParameterRange sEngineClockPercent; /// Returns the hard limits of the MCLK overdrive adjustment range. Overdrive clocks should not be adjusted outside of this range. The values are specified as +/- percentages. ADLOD6ParameterRange sMemoryClockPercent; /// Returns the hard limits of the Power Limit adjustment range. Power limit should not be adjusted outside this range. The values are specified as +/- percentages. ADLOD6ParameterRange sPowerControlPercent; /// Reserved for future expansion of the structure. int iExtValue; /// Reserved for future expansion of the structure. int iExtMask; } ADLOD6CapabilitiesEx; ///////////////////////////////////////////////////////////////////////////////////////////// ///\brief Structure containing information about Overdrive 6 extension state information /// /// This structure is used to store information about Overdrive 6 extension state information /// \nosubgrouping //////////////////////////////////////////////////////////////////////////////////////////// typedef struct ADLOD6StateEx { /// The current engine clock adjustment value, specified as a +/- percent. int iEngineClockPercent; /// The current memory clock adjustment value, specified as a +/- percent. int iMemoryClockPercent; /// The current power control adjustment value, specified as a +/- percent. int iPowerControlPercent; /// Reserved for future expansion of the structure. int iExtValue; /// Reserved for future expansion of the structure. int iExtMask; } ADLOD6StateEx; ///////////////////////////////////////////////////////////////////////////////////////////// ///\brief Structure containing information about Overdrive 6 extension recommended maximum clock adjustment values /// /// This structure is used to store information about Overdrive 6 extension recommended maximum clock adjustment values /// \nosubgrouping //////////////////////////////////////////////////////////////////////////////////////////// typedef struct ADLOD6MaxClockAdjust { /// The recommended maximum engine clock adjustment in percent, for the specified power limit value. int iEngineClockMax; /// The recommended maximum memory clock adjustment in percent, for the specified power limit value. /// Currently the memory is independent of the Power Limit setting, so iMemoryClockMax will always return the maximum /// possible adjustment value. This field is here for future enhancement in case we add a dependency between Memory Clock /// adjustment and Power Limit setting. int iMemoryClockMax; /// Reserved for future expansion of the structure. int iExtValue; /// Reserved for future expansion of the structure. int iExtMask; } ADLOD6MaxClockAdjust; //////////////////////////////////////////////////////////////////////////////////////////// ///\brief Structure containing the Connector information /// /// this structure is used to get the connector information like length, positions & etc. /// \nosubgrouping //////////////////////////////////////////////////////////////////////////////////////////// typedef struct ADLConnectorInfo { ///index of the connector(0-based) int iConnectorIndex; ///used for disply identification/ordering int iConnectorId; ///index of the slot, 0-based index. int iSlotIndex; ///Type of the connector. \ref define_connector_types int iType; ///Position of the connector(in millimeters), from the right side of the slot. int iOffset; ///Length of the connector(in millimeters). int iLength; } ADLConnectorInfo; //////////////////////////////////////////////////////////////////////////////////////////// ///\brief Structure containing the slot information /// /// this structure is used to get the slot information like length of the slot, no of connectors on the slot & etc. /// \nosubgrouping //////////////////////////////////////////////////////////////////////////////////////////// typedef struct ADLBracketSlotInfo { ///index of the slot, 0-based index. int iSlotIndex; ///length of the slot(in millimeters). int iLength; ///width of the slot(in millimeters). int iWidth; } ADLBracketSlotInfo; //////////////////////////////////////////////////////////////////////////////////////////// ///\brief Structure containing MST branch information /// /// this structure is used to store the MST branch information /// \nosubgrouping //////////////////////////////////////////////////////////////////////////////////////////// typedef struct ADLMSTRad { ///depth of the link. int iLinkNumber; /// Relative address, address scheme starts from source side char rad[ADL_MAX_RAD_LINK_COUNT]; } ADLMSTRad; //////////////////////////////////////////////////////////////////////////////////////////// ///\brief Structure containing port information /// /// this structure is used to get the display or MST branch information /// \nosubgrouping //////////////////////////////////////////////////////////////////////////////////////////// typedef struct ADLDevicePort { ///index of the connector. int iConnectorIndex; ///Relative MST address. If MST RAD contains 0 it means DP or Root of the MST topology. For non DP connectors MST RAD is ignored. ADLMSTRad aMSTRad; } ADLDevicePort; //////////////////////////////////////////////////////////////////////////////////////////// ///\brief Structure containing supported connection types and properties /// /// this structure is used to get the supported connection types and supported properties of given connector /// \nosubgrouping //////////////////////////////////////////////////////////////////////////////////////////// typedef struct ADLSupportedConnections { ///Bit vector of supported connections. Bitmask is defined in constants section. \ref define_connection_types int iSupportedConnections; ///Array of bitvectors. Each bit vector represents supported properties for one connection type. Index of this array is connection type (bit number in mask). int iSupportedProperties[ADL_MAX_CONNECTION_TYPES]; } ADLSupportedConnections; //////////////////////////////////////////////////////////////////////////////////////////// ///\brief Structure containing connection state of the connector /// /// this structure is used to get the current Emulation status and mode of the given connector /// \nosubgrouping //////////////////////////////////////////////////////////////////////////////////////////// typedef struct ADLConnectionState { ///The value is bit vector. Each bit represents status. See masks constants for details. \ref define_emulation_status int iEmulationStatus; ///It contains information about current emulation mode. See constants for details. \ref define_emulation_mode int iEmulationMode; ///If connection is active it will contain display id, otherwise CWDDEDI_INVALID_DISPLAY_INDEX int iDisplayIndex; } ADLConnectionState; //////////////////////////////////////////////////////////////////////////////////////////// ///\brief Structure containing connection properties information /// /// this structure is used to retrieve the properties of connection type /// \nosubgrouping //////////////////////////////////////////////////////////////////////////////////////////// typedef struct ADLConnectionProperties { //Bit vector. Represents actual properties. Supported properties for specific connection type. \ref define_connection_properties int iValidProperties; //Bitrate(in MHz). Could be used for MST branch, DP or DP active dongle. \ref define_linkrate_constants int iBitrate; //Number of lanes in DP connection. \ref define_lanecount_constants int iNumberOfLanes; //Color depth(in bits). \ref define_colordepth_constants int iColorDepth; //3D capabilities. It could be used for some dongles. For instance: alternate framepack. Value of this property is bit vector. int iStereo3DCaps; ///Output Bandwidth. Could be used for MST branch, DP or DP Active dongle. \ref define_linkrate_constants int iOutputBandwidth; } ADLConnectionProperties; //////////////////////////////////////////////////////////////////////////////////////////// ///\brief Structure containing connection information /// /// this structure is used to retrieve the data from driver which includes /// \nosubgrouping //////////////////////////////////////////////////////////////////////////////////////////// typedef struct ADLConnectionData { ///Connection type. based on the connection type either iNumberofPorts or IDataSize,EDIDdata is valid, \ref define_connection_types int iConnectionType; ///Specifies the connection properties. ADLConnectionProperties aConnectionProperties; ///Number of ports int iNumberofPorts; ///Number of Active Connections int iActiveConnections; ///actual size of EDID data block size. int iDataSize; ///EDID Data char EdidData[ADL_MAX_DISPLAY_EDID_DATA_SIZE]; } ADLConnectionData; ///////////////////////////////////////////////////////////////////////////////////////////// ///\brief Structure containing information about an controller mode including Number of Connectors /// /// This structure is used to store information of an controller mode /// \nosubgrouping //////////////////////////////////////////////////////////////////////////////////////////// typedef struct ADLAdapterCapsX2 { /// AdapterID for this adapter int iAdapterID; /// Number of controllers for this adapter int iNumControllers; /// Number of displays for this adapter int iNumDisplays; /// Number of overlays for this adapter int iNumOverlays; /// Number of GLSyncConnectors int iNumOfGLSyncConnectors; /// The bit mask identifies the adapter caps int iCapsMask; /// The bit identifies the adapter caps \ref define_adapter_caps int iCapsValue; /// Number of Connectors for this adapter int iNumConnectors; }ADLAdapterCapsX2; typedef enum ADL_ERROR_RECORD_SEVERITY { ADL_GLOBALLY_UNCORRECTED = 1, ADL_LOCALLY_UNCORRECTED = 2, ADL_DEFFERRED = 3, ADL_CORRECTED = 4 }ADL_ERROR_RECORD_SEVERITY; typedef union _ADL_ECC_EDC_FLAG { struct { unsigned int isEccAccessing : 1; unsigned int reserved : 31; }bits; unsigned int u32All; }ADL_ECC_EDC_FLAG; ///////////////////////////////////////////////////////////////////////////////////////////// ///\brief Structure containing information about EDC Error Record /// /// This structure is used to store EDC Error Record /// \nosubgrouping //////////////////////////////////////////////////////////////////////////////////////////// typedef struct ADLErrorRecord { // Severity of error ADL_ERROR_RECORD_SEVERITY Severity; // Is the counter valid? int countValid; // Counter value, if valid unsigned int count; // Is the location information valid? int locationValid; // Physical location of error unsigned int CU; // CU number on which error occurred, if known char StructureName[32]; // e.g. LDS, TCC, etc. // Time of error record creation (e.g. time of query, or time of poison interrupt) char tiestamp[32]; unsigned int padding[3]; }ADLErrorRecord; typedef enum ADL_EDC_BLOCK_ID { ADL_EDC_BLOCK_ID_SQCIS = 1, ADL_EDC_BLOCK_ID_SQCDS = 2, ADL_EDC_BLOCK_ID_SGPR = 3, ADL_EDC_BLOCK_ID_VGPR = 4, ADL_EDC_BLOCK_ID_LDS = 5, ADL_EDC_BLOCK_ID_GDS = 6, ADL_EDC_BLOCK_ID_TCL1 = 7, ADL_EDC_BLOCK_ID_TCL2 = 8 }ADL_EDC_BLOCK_ID; typedef enum ADL_ERROR_INJECTION_MODE { ADL_ERROR_INJECTION_MODE_SINGLE = 1, ADL_ERROR_INJECTION_MODE_MULTIPLE = 2, ADL_ERROR_INJECTION_MODE_ADDRESS = 3 }ADL_ERROR_INJECTION_MODE; typedef union _ADL_ERROR_PATTERN { struct { unsigned long EccInjVector : 16; unsigned long EccInjEn : 9; unsigned long EccBeatEn : 4; unsigned long EccChEn : 4; unsigned long reserved : 31; } bits; unsigned long long u64Value; } ADL_ERROR_PATTERN; typedef struct ADL_ERROR_INJECTION_DATA { unsigned long long errorAddress; ADL_ERROR_PATTERN errorPattern; }ADL_ERROR_INJECTION_DATA; ///////////////////////////////////////////////////////////////////////////////////////////// ///\brief Structure containing information about EDC Error Injection /// /// This structure is used to store EDC Error Injection /// \nosubgrouping //////////////////////////////////////////////////////////////////////////////////////////// typedef struct ADLErrorInjection { ADL_EDC_BLOCK_ID blockId; ADL_ERROR_INJECTION_MODE errorInjectionMode; }ADLErrorInjection; typedef struct ADLErrorInjectionX2 { ADL_EDC_BLOCK_ID blockId; ADL_ERROR_INJECTION_MODE errorInjectionMode; ADL_ERROR_INJECTION_DATA errorInjectionData; }ADLErrorInjectionX2; ///////////////////////////////////////////////////////////////////////////////////////////// ///\brief Structure containing per display FreeSync capability information. /// /// This structure is used to store the FreeSync capability of both the display and /// the GPU the display is connected to. /// \nosubgrouping //////////////////////////////////////////////////////////////////////////////////////////// typedef struct ADLFreeSyncCap { /// FreeSync capability flags. \ref define_freesync_caps int iCaps; /// Reports minimum FreeSync refresh rate supported by the display in micro hertz int iMinRefreshRateInMicroHz; /// Reports maximum FreeSync refresh rate supported by the display in micro hertz int iMaxRefreshRateInMicroHz; /// Index of FreeSync Label to use: ADL_FREESYNC_LABEL_* unsigned char ucLabelIndex; /// Reserved char cReserved[3]; int iReserved[4]; } ADLFreeSyncCap; ///////////////////////////////////////////////////////////////////////////////////////////// ///\brief Structure containing per display Display Connectivty Experience Settings /// /// This structure is used to store the Display Connectivity Experience settings of a /// display /// \nosubgrouping //////////////////////////////////////////////////////////////////////////////////////////// typedef struct ADLDceSettings { DceSettingsType type; // Defines which structure is in the union below union { struct { bool qualityDetectionEnabled; } HdmiLq; struct { DpLinkRate linkRate; // Read-only unsigned int numberOfActiveLanes; // Read-only unsigned int numberofTotalLanes; // Read-only int relativePreEmphasis; // Allowable values are -2 to +2 int relativeVoltageSwing; // Allowable values are -2 to +2 int persistFlag; } DpLink; struct { bool linkProtectionEnabled; // Read-only } Protection; } Settings; int iReserved[15]; } ADLDceSettings; ///////////////////////////////////////////////////////////////////////////////////////////// ///\brief Structure containing information about Graphic Core /// /// This structure is used to get Graphic Core Info /// \nosubgrouping //////////////////////////////////////////////////////////////////////////////////////////// typedef struct ADLGraphicCoreInfo { /// indicate the graphic core generation int iGCGen; union { /// Total number of CUs. Valid for GCN (iGCGen == GCN) int iNumCUs; /// Total number of WGPs. Valid for RDNA (iGCGen == RDNA) int iNumWGPs; }; union { /// Number of processing elements per CU. Valid for GCN (iGCGen == GCN) int iNumPEsPerCU; /// Number of processing elements per WGP. Valid for RDNA (iGCGen == RDNA) int iNumPEsPerWGP; }; /// Total number of SIMDs. Valid for Pre GCN (iGCGen == Pre-GCN) int iNumSIMDs; /// Total number of ROPs. Valid for both GCN and Pre GCN int iNumROPs; /// reserved for future use int iReserved[11]; }ADLGraphicCoreInfo; ///////////////////////////////////////////////////////////////////////////////////////////// ///\brief Structure containing information about Overdrive N clock range /// /// This structure is used to store information about Overdrive N clock range /// \nosubgrouping //////////////////////////////////////////////////////////////////////////////////////////// typedef struct ADLODNParameterRange { /// The starting value of the clock range int iMode; /// The starting value of the clock range int iMin; /// The ending value of the clock range int iMax; /// The minimum increment between clock values int iStep; /// The default clock values int iDefault; } ADLODNParameterRange; ///////////////////////////////////////////////////////////////////////////////////////////// ///\brief Structure containing information about Overdrive N capabilities /// /// This structure is used to store information about Overdrive N capabilities /// \nosubgrouping //////////////////////////////////////////////////////////////////////////////////////////// typedef struct ADLODNCapabilities { /// Number of levels which describe the minimum to maximum clock ranges. /// The 1st level indicates the minimum clocks, and the 2nd level /// indicates the maximum clocks. int iMaximumNumberOfPerformanceLevels; /// Contains the hard limits of the sclk range. Overdrive /// clocks cannot be set outside this range. ADLODNParameterRange sEngineClockRange; /// Contains the hard limits of the mclk range. Overdrive /// clocks cannot be set outside this range. ADLODNParameterRange sMemoryClockRange; /// Contains the hard limits of the vddc range. Overdrive /// clocks cannot be set outside this range. ADLODNParameterRange svddcRange; /// Contains the hard limits of the power range. Overdrive /// clocks cannot be set outside this range. ADLODNParameterRange power; /// Contains the hard limits of the power range. Overdrive /// clocks cannot be set outside this range. ADLODNParameterRange powerTuneTemperature; /// Contains the hard limits of the Temperature range. Overdrive /// clocks cannot be set outside this range. ADLODNParameterRange fanTemperature; /// Contains the hard limits of the Fan range. Overdrive /// clocks cannot be set outside this range. ADLODNParameterRange fanSpeed; /// Contains the hard limits of the Fan range. Overdrive /// clocks cannot be set outside this range. ADLODNParameterRange minimumPerformanceClock; } ADLODNCapabilities; ///////////////////////////////////////////////////////////////////////////////////////////// ///\brief Structure containing information about Overdrive N capabilities /// /// This structure is used to store information about Overdrive N capabilities /// \nosubgrouping //////////////////////////////////////////////////////////////////////////////////////////// typedef struct ADLODNCapabilitiesX2 { /// Number of levels which describe the minimum to maximum clock ranges. /// The 1st level indicates the minimum clocks, and the 2nd level /// indicates the maximum clocks. int iMaximumNumberOfPerformanceLevels; /// bit vector, which tells what are the features are supported. /// \ref: ADLODNFEATURECONTROL int iFlags; /// Contains the hard limits of the sclk range. Overdrive /// clocks cannot be set outside this range. ADLODNParameterRange sEngineClockRange; /// Contains the hard limits of the mclk range. Overdrive /// clocks cannot be set outside this range. ADLODNParameterRange sMemoryClockRange; /// Contains the hard limits of the vddc range. Overdrive /// clocks cannot be set outside this range. ADLODNParameterRange svddcRange; /// Contains the hard limits of the power range. Overdrive /// clocks cannot be set outside this range. ADLODNParameterRange power; /// Contains the hard limits of the power range. Overdrive /// clocks cannot be set outside this range. ADLODNParameterRange powerTuneTemperature; /// Contains the hard limits of the Temperature range. Overdrive /// clocks cannot be set outside this range. ADLODNParameterRange fanTemperature; /// Contains the hard limits of the Fan range. Overdrive /// clocks cannot be set outside this range. ADLODNParameterRange fanSpeed; /// Contains the hard limits of the Fan range. Overdrive /// clocks cannot be set outside this range. ADLODNParameterRange minimumPerformanceClock; /// Contains the hard limits of the throttleNotification ADLODNParameterRange throttleNotificaion; /// Contains the hard limits of the Auto Systemclock ADLODNParameterRange autoSystemClock; } ADLODNCapabilitiesX2; ///////////////////////////////////////////////////////////////////////////////////////////// ///\brief Structure containing information about Overdrive level. /// /// This structure is used to store information about Overdrive level. /// This structure is used by ADLODPerformanceLevels. /// \nosubgrouping //////////////////////////////////////////////////////////////////////////////////////////// typedef struct ADLODNPerformanceLevel { /// clock. int iClock; /// VDCC. int iVddc; /// enabled int iEnabled; } ADLODNPerformanceLevel; ///////////////////////////////////////////////////////////////////////////////////////////// ///\brief Structure containing information about Overdrive N performance levels. /// /// This structure is used to store information about Overdrive performance levels. /// This structure is used by the ADL_OverdriveN_ODPerformanceLevels_Get() and ADL_OverdriveN_ODPerformanceLevels_Set() functions. /// \nosubgrouping //////////////////////////////////////////////////////////////////////////////////////////// typedef struct ADLODNPerformanceLevels { int iSize; //Automatic/manual int iMode; /// Must be set to sizeof( \ref ADLODPerformanceLevels ) + sizeof( \ref ADLODPerformanceLevel ) * (ADLODParameters.iNumberOfPerformanceLevels - 1) int iNumberOfPerformanceLevels; /// Array of performance state descriptors. Must have ADLODParameters.iNumberOfPerformanceLevels elements. ADLODNPerformanceLevel aLevels[1]; } ADLODNPerformanceLevels; ///////////////////////////////////////////////////////////////////////////////////////////// ///\brief Structure containing information about Overdrive N Fan Speed. /// /// This structure is used to store information about Overdrive Fan control . /// This structure is used by the ADL_OverdriveN_ODPerformanceLevels_Get() and ADL_OverdriveN_ODPerformanceLevels_Set() functions. /// \nosubgrouping //////////////////////////////////////////////////////////////////////////////////////////// typedef struct ADLODNFanControl { int iMode; int iFanControlMode; int iCurrentFanSpeedMode; int iCurrentFanSpeed; int iTargetFanSpeed; int iTargetTemperature; int iMinPerformanceClock; int iMinFanLimit; } ADLODNFanControl; ///////////////////////////////////////////////////////////////////////////////////////////// ///\brief Structure containing information about Overdrive N power limit. /// /// This structure is used to store information about Overdrive power limit. /// This structure is used by the ADL_OverdriveN_ODPerformanceLevels_Get() and ADL_OverdriveN_ODPerformanceLevels_Set() functions. /// \nosubgrouping //////////////////////////////////////////////////////////////////////////////////////////// typedef struct ADLODNPowerLimitSetting { int iMode; int iTDPLimit; int iMaxOperatingTemperature; } ADLODNPowerLimitSetting; typedef struct ADLODNPerformanceStatus { int iCoreClock; int iMemoryClock; int iDCEFClock; int iGFXClock; int iUVDClock; int iVCEClock; int iGPUActivityPercent; int iCurrentCorePerformanceLevel; int iCurrentMemoryPerformanceLevel; int iCurrentDCEFPerformanceLevel; int iCurrentGFXPerformanceLevel; int iUVDPerformanceLevel; int iVCEPerformanceLevel; int iCurrentBusSpeed; int iCurrentBusLanes; int iMaximumBusLanes; int iVDDC; int iVDDCI; } ADLODNPerformanceStatus; ///\brief Structure containing information about Overdrive level. /// /// This structure is used to store information about Overdrive level. /// This structure is used by ADLODPerformanceLevels. /// \nosubgrouping //////////////////////////////////////////////////////////////////////////////////////////// typedef struct ADLODNPerformanceLevelX2 { /// clock. int iClock; /// VDCC. int iVddc; /// enabled int iEnabled; /// MASK int iControl; } ADLODNPerformanceLevelX2; ///////////////////////////////////////////////////////////////////////////////////////////// ///\brief Structure containing information about Overdrive N performance levels. /// /// This structure is used to store information about Overdrive performance levels. /// This structure is used by the ADL_OverdriveN_ODPerformanceLevels_Get() and ADL_OverdriveN_ODPerformanceLevels_Set() functions. /// \nosubgrouping //////////////////////////////////////////////////////////////////////////////////////////// typedef struct ADLODNPerformanceLevelsX2 { int iSize; //Automatic/manual int iMode; /// Must be set to sizeof( \ref ADLODPerformanceLevels ) + sizeof( \ref ADLODPerformanceLevel ) * (ADLODParameters.iNumberOfPerformanceLevels - 1) int iNumberOfPerformanceLevels; /// Array of performance state descriptors. Must have ADLODParameters.iNumberOfPerformanceLevels elements. ADLODNPerformanceLevelX2 aLevels[1]; } ADLODNPerformanceLevelsX2; typedef enum ADLODNCurrentPowerType { ODN_GPU_TOTAL_POWER = 0, ODN_GPU_PPT_POWER, ODN_GPU_SOCKET_POWER, ODN_GPU_CHIP_POWER } ADLODNCurrentPowerType; // in/out: CWDDEPM_CURRENTPOWERPARAMETERS typedef struct ADLODNCurrentPowerParameters { int size; ADLODNCurrentPowerType powerType; int currentPower; } ADLODNCurrentPowerParameters; //ODN Ext range data structure typedef struct ADLODNExtSingleInitSetting { int mode; int minValue; int maxValue; int step; int defaultValue; } ADLODNExtSingleInitSetting; //OD8 Ext range data structure typedef struct ADLOD8SingleInitSetting { int featureID; int minValue; int maxValue; int defaultValue; } ADLOD8SingleInitSetting; ///////////////////////////////////////////////////////////////////////////////////////////// ///\brief Structure containing information about Overdrive8 initial setting /// /// This structure is used to store information about Overdrive8 initial setting /// \nosubgrouping //////////////////////////////////////////////////////////////////////////////////////////// typedef struct ADLOD8InitSetting { int count; int overdrive8Capabilities; ADLOD8SingleInitSetting od8SettingTable[OD8_COUNT]; } ADLOD8InitSetting; ///////////////////////////////////////////////////////////////////////////////////////////// ///\brief Structure containing information about Overdrive8 current setting /// /// This structure is used to store information about Overdrive8 current setting /// \nosubgrouping //////////////////////////////////////////////////////////////////////////////////////////// typedef struct ADLOD8CurrentSetting { int count; int Od8SettingTable[OD8_COUNT]; } ADLOD8CurrentSetting; ///////////////////////////////////////////////////////////////////////////////////////////// ///\brief Structure containing information about Overdrive8 set setting /// /// This structure is used to store information about Overdrive8 set setting /// \nosubgrouping //////////////////////////////////////////////////////////////////////////////////////////// typedef struct ADLOD8SingleSetSetting { int value; int requested; // 0 - default , 1 - requested int reset; // 0 - do not reset , 1 - reset setting back to default } ADLOD8SingleSetSetting; typedef struct ADLOD8SetSetting { int count; ADLOD8SingleSetSetting od8SettingTable[OD8_COUNT]; } ADLOD8SetSetting; ///////////////////////////////////////////////////////////////////////////////////////////// ///\brief Structure containing information about Performance Metrics data /// /// This structure is used to store information about Performance Metrics data output /// \nosubgrouping //////////////////////////////////////////////////////////////////////////////////////////// typedef struct ADLSingleSensorData { int supported; int value; } ADLSingleSensorData; typedef struct ADLPMLogDataOutput { int size; ADLSingleSensorData sensors[ADL_PMLOG_MAX_SENSORS]; }ADLPMLogDataOutput; ///////////////////////////////////////////////////////////////////////////////////////////// ///\brief Structure containing information about PPLog settings. /// /// This structure is used to store information about PPLog settings. /// This structure is used by the ADL2_PPLogSettings_Set() and ADL2_PPLogSettings_Get() functions. /// \nosubgrouping //////////////////////////////////////////////////////////////////////////////////////////// typedef struct ADLPPLogSettings { int BreakOnAssert; int BreakOnWarn; int LogEnabled; int LogFieldMask; int LogDestinations; int LogSeverityEnabled; int LogSourceMask; int PowerProfilingEnabled; int PowerProfilingTimeInterval; }ADLPPLogSettings; ///////////////////////////////////////////////////////////////////////////////////////////// ///\brief Structure containing information related Frames Per Second for AC and DC. /// /// This structure is used to store information related AC and DC Frames Per Second settings /// \nosubgrouping //////////////////////////////////////////////////////////////////////////////////////////// typedef struct ADLFPSSettingsOutput { /// size int ulSize; /// FPS Monitor is enabled in the AC state if 1 int bACFPSEnabled; /// FPS Monitor is enabled in the DC state if 1 int bDCFPSEnabled; /// Current Value of FPS Monitor in AC state int ulACFPSCurrent; /// Current Value of FPS Monitor in DC state int ulDCFPSCurrent; /// Maximum FPS Threshold allowed in PPLib for AC int ulACFPSMaximum; /// Minimum FPS Threshold allowed in PPLib for AC int ulACFPSMinimum; /// Maximum FPS Threshold allowed in PPLib for DC int ulDCFPSMaximum; /// Minimum FPS Threshold allowed in PPLib for DC int ulDCFPSMinimum; } ADLFPSSettingsOutput; ///////////////////////////////////////////////////////////////////////////////////////////// ///\brief Structure containing information related Frames Per Second for AC and DC. /// /// This structure is used to store information related AC and DC Frames Per Second settings /// \nosubgrouping //////////////////////////////////////////////////////////////////////////////////////////// typedef struct ADLFPSSettingsInput { /// size int ulSize; /// Settings are for Global FPS (used by CCC) int bGlobalSettings; /// Current Value of FPS Monitor in AC state int ulACFPSCurrent; /// Current Value of FPS Monitor in DC state int ulDCFPSCurrent; /// Reserved int ulReserved[6]; } ADLFPSSettingsInput; ///////////////////////////////////////////////////////////////////////////////////////////// ///\brief Structure containing information related power management logging. /// /// This structure is used to store support information for power management logging. /// \nosubgrouping //////////////////////////////////////////////////////////////////////////////////////////// enum { ADL_PMLOG_MAX_SUPPORTED_SENSORS = 256 }; typedef struct ADLPMLogSupportInfo { /// list of sensors defined by ADL_PMLOG_SENSORS unsigned short usSensors[ADL_PMLOG_MAX_SUPPORTED_SENSORS]; /// Reserved int ulReserved[16]; } ADLPMLogSupportInfo; ///////////////////////////////////////////////////////////////////////////////////////////// ///\brief Structure containing information to start power management logging. /// /// This structure is used as input to ADL2_Adapter_PMLog_Start /// \nosubgrouping //////////////////////////////////////////////////////////////////////////////////////////// typedef struct ADLPMLogStartInput { /// list of sensors defined by ADL_PMLOG_SENSORS unsigned short usSensors[ADL_PMLOG_MAX_SUPPORTED_SENSORS]; /// Sample rate in milliseconds unsigned long ulSampleRate; /// Reserved int ulReserved[15]; } ADLPMLogStartInput; typedef struct ADLPMLogData { /// Structure version unsigned int ulVersion; /// Current driver sample rate unsigned int ulActiveSampleRate; /// Timestamp of last update unsigned long long ulLastUpdated; /// 2D array of senesor and values unsigned int ulValues[ADL_PMLOG_MAX_SUPPORTED_SENSORS][2]; /// Reserved unsigned int ulReserved[256]; } ADLPMLogData; ///////////////////////////////////////////////////////////////////////////////////////////// ///\brief Structure containing information to start power management logging. /// /// This structure is returned as output from ADL2_Adapter_PMLog_Start /// \nosubgrouping //////////////////////////////////////////////////////////////////////////////////////////// typedef struct ADLPMLogStartOutput { /// Pointer to memory address containing logging data union { void* pLoggingAddress; unsigned long long ptr_LoggingAddress; }; /// Reserved int ulReserved[14]; } ADLPMLogStartOutput; ///////////////////////////////////////////////////////////////////////////////////////////// ///\brief Structure containing information to query limts of power management logging. /// /// This structure is returned as output from ADL2_Adapter_PMLog_SensorLimits_Get /// \nosubgrouping //////////////////////////////////////////////////////////////////////////////////////////// typedef struct ADLPMLogSensorLimits { int SensorLimits[ADL_PMLOG_MAX_SENSORS][2]; //index 0: min, 1: max } ADLPMLogSensorLimits; ///////////////////////////////////////////////////////////////////////////////////////////// ///\brief Structure containing information related RAS Get Error Counts Information /// /// This structure is used to store RAS Error Counts Get Input Information /// \nosubgrouping //////////////////////////////////////////////////////////////////////////////////////////// typedef struct ADLRASGetErrorCountsInput { unsigned int Reserved[16]; } ADLRASGetErrorCountsInput; ///////////////////////////////////////////////////////////////////////////////////////////// ///\brief Structure containing information related RAS Get Error Counts Information /// /// This structure is used to store RAS Error Counts Get Output Information /// \nosubgrouping //////////////////////////////////////////////////////////////////////////////////////////// typedef struct ADLRASGetErrorCountsOutput { unsigned int CorrectedErrors; // includes both DRAM and SRAM ECC unsigned int UnCorrectedErrors; // includes both DRAM and SRAM ECC unsigned int Reserved[14]; } ADLRASGetErrorCountsOutput; ///////////////////////////////////////////////////////////////////////////////////////////// ///\brief Structure containing information related RAS Get Error Counts Information /// /// This structure is used to store RAS Error Counts Get Information /// \nosubgrouping //////////////////////////////////////////////////////////////////////////////////////////// typedef struct ADLRASGetErrorCounts { unsigned int InputSize; ADLRASGetErrorCountsInput Input; unsigned int OutputSize; ADLRASGetErrorCountsOutput Output; } ADLRASGetErrorCounts; ///////////////////////////////////////////////////////////////////////////////////////////// ///\brief Structure containing information related RAS Error Counts Reset Information /// /// This structure is used to store RAS Error Counts Reset Input Information /// \nosubgrouping //////////////////////////////////////////////////////////////////////////////////////////// typedef struct ADLRASResetErrorCountsInput { unsigned int Reserved[8]; } ADLRASResetErrorCountsInput; ///////////////////////////////////////////////////////////////////////////////////////////// ///\brief Structure containing information related RAS Error Counts Reset Information /// /// This structure is used to store RAS Error Counts Reset Output Information /// \nosubgrouping //////////////////////////////////////////////////////////////////////////////////////////// typedef struct ADLRASResetErrorCountsOutput { unsigned int Reserved[8]; } ADLRASResetErrorCountsOutput; ///////////////////////////////////////////////////////////////////////////////////////////// ///\brief Structure containing information related RAS Error Counts Reset Information /// /// This structure is used to store RAS Error Counts Reset Information /// \nosubgrouping //////////////////////////////////////////////////////////////////////////////////////////// typedef struct ADLRASResetErrorCounts { unsigned int InputSize; ADLRASResetErrorCountsInput Input; unsigned int OutputSize; ADLRASResetErrorCountsOutput Output; } ADLRASResetErrorCounts; ///////////////////////////////////////////////////////////////////////////////////////////// ///\brief Structure containing information related RAS Error Injection information /// /// This structure is used to store RAS Error Injection input information /// \nosubgrouping //////////////////////////////////////////////////////////////////////////////////////////// typedef struct ADLRASErrorInjectonInput { unsigned long long Address; ADL_RAS_INJECTION_METHOD Value; ADL_RAS_BLOCK_ID BlockId; ADL_RAS_ERROR_TYPE InjectErrorType; ADL_MEM_SUB_BLOCK_ID SubBlockIndex; unsigned int padding[9]; } ADLRASErrorInjectonInput; ///////////////////////////////////////////////////////////////////////////////////////////// ///\brief Structure containing information related RAS Error Injection information /// /// This structure is used to store RAS Error Injection output information /// \nosubgrouping //////////////////////////////////////////////////////////////////////////////////////////// typedef struct ADLRASErrorInjectionOutput { unsigned int ErrorInjectionStatus; unsigned int padding[15]; } ADLRASErrorInjectionOutput; ///////////////////////////////////////////////////////////////////////////////////////////// ///\brief Structure containing information related RAS Error Injection information /// /// This structure is used to store RAS Error Injection information /// \nosubgrouping //////////////////////////////////////////////////////////////////////////////////////////// typedef struct ADLRASErrorInjection { unsigned int InputSize; ADLRASErrorInjectonInput Input; unsigned int OutputSize; ADLRASErrorInjectionOutput Output; } ADLRASErrorInjection; ///////////////////////////////////////////////////////////////////////////////////////////// ///\brief Structure containing information about an application /// /// This structure is used to store basic information of a recently ran or currently running application /// \nosubgrouping //////////////////////////////////////////////////////////////////////////////////////////// typedef struct ADLSGApplicationInfo { /// Application file name wchar_t strFileName[ADL_MAX_PATH]; /// Application file path wchar_t strFilePath[ADL_MAX_PATH]; /// Application version wchar_t strVersion[ADL_MAX_PATH]; /// Timestamp at which application has run long long int timeStamp; /// Holds whether the applicaition profile exists or not unsigned int iProfileExists; /// The GPU on which application runs unsigned int iGPUAffinity; /// The BDF of the GPU on which application runs ADLBdf GPUBdf; } ADLSGApplicationInfo; ///////////////////////////////////////////////////////////////////////////////////////////// ///\brief Structure containing information related Frames Per Second for AC and DC. /// /// This structure is used to store information related AC and DC Frames Per Second settings /// \nosubgrouping //////////////////////////////////////////////////////////////////////////////////////////// enum { ADLPreFlipPostProcessingInfoInvalidLUTIndex = 0xFFFFFFFF }; enum ADLPreFlipPostProcessingLUTAlgorithm { ADLPreFlipPostProcessingLUTAlgorithm_Default = 0, ADLPreFlipPostProcessingLUTAlgorithm_Full, ADLPreFlipPostProcessingLUTAlgorithm_Approximation }; typedef struct ADLPreFlipPostProcessingInfo { /// size int ulSize; /// Current active state int bEnabled; /// Current selected LUT index. 0xFFFFFFF returned if nothing selected. int ulSelectedLUTIndex; /// Current selected LUT Algorithm int ulSelectedLUTAlgorithm; /// Reserved int ulReserved[12]; } ADLPreFlipPostProcessingInfo; typedef struct ADL_ERROR_REASON { int boost; //ON, when boost is Enabled int delag; //ON, when delag is Enabled int chill; //ON, when chill is Enabled int proVsr; //ON, when proVsr is Enabled }ADL_ERROR_REASON; typedef struct ADL_ERROR_REASON2 { int boost; //ON, when boost is Enabled int delag; //ON, when delag is Enabled int chill; //ON, when chill is Enabled int proVsr; //ON, when proVsr is Enabled int upscale; //ON, when RSR is Enabled }ADL_ERROR_REASON2; ///////////////////////////////////////////////////////////////////////////////////////////// ///\brief Structure containing information about DELAG Settings change reason /// /// Elements of DELAG settings changed reason. /// \nosubgrouping //////////////////////////////////////////////////////////////////////////////////////////// typedef struct ADL_DELAG_NOTFICATION_REASON { int HotkeyChanged; //Set when Hotkey value is changed int GlobalEnableChanged; //Set when Global enable value is changed int GlobalLimitFPSChanged; //Set when Global enable value is changed }ADL_DELAG_NOTFICATION_REASON; ///////////////////////////////////////////////////////////////////////////////////////////// ///\brief Structure containing information about DELAG Settings /// /// Elements of DELAG settings. /// \nosubgrouping //////////////////////////////////////////////////////////////////////////////////////////// typedef struct ADL_DELAG_SETTINGS { int Hotkey; // Hotkey value int GlobalEnable; //Global enable value int GlobalLimitFPS; //Global Limit FPS int GlobalLimitFPS_MinLimit; //Gloabl Limit FPS slider min limit value int GlobalLimitFPS_MaxLimit; //Gloabl Limit FPS slider max limit value int GlobalLimitFPS_Step; //Gloabl Limit FPS step value }ADL_DELAG_SETTINGS; ///////////////////////////////////////////////////////////////////////////////////////////// ///\brief Structure containing information about BOOST Settings change reason /// /// Elements of BOOST settings changed reason. /// \nosubgrouping //////////////////////////////////////////////////////////////////////////////////////////// typedef struct ADL_BOOST_NOTFICATION_REASON { int HotkeyChanged; //Set when Hotkey value is changed int GlobalEnableChanged; //Set when Global enable value is changed int GlobalMinResChanged; //Set when Global min resolution value is changed }ADL_BOOST_NOTFICATION_REASON; ///////////////////////////////////////////////////////////////////////////////////////////// ///\brief Structure containing information about BOOST Settings /// /// Elements of BOOST settings. /// \nosubgrouping //////////////////////////////////////////////////////////////////////////////////////////// typedef struct ADL_BOOST_SETTINGS { 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 }ADL_BOOST_SETTINGS; ///////////////////////////////////////////////////////////////////////////////////////////// ///\brief Structure containing information about ProVSR Settings change reason /// /// Elements of ProVSR settings changed reason. /// \nosubgrouping //////////////////////////////////////////////////////////////////////////////////////////// typedef struct ADL_PROVSR_NOTFICATION_REASON { int HotkeyChanged; //Set when Hotkey value is changed int GlobalEnableChanged; //Set when Global enable value is changed }ADL_PROVSR_NOTFICATION_REASON; ///////////////////////////////////////////////////////////////////////////////////////////// ///\brief Structure containing information about Pro VSR Settings /// /// Elements of ProVSR settings. /// \nosubgrouping //////////////////////////////////////////////////////////////////////////////////////////// typedef struct ADL_PROVSR_SETTINGS { int Hotkey; // Hotkey value int GlobalEnable; //Global enable value }ADL_PROVSR_SETTINGS; ///////////////////////////////////////////////////////////////////////////////////////////// ///\brief Structure containing information about Image Boost(OGL) Settings change reason /// /// Elements of Image Boost settings changed reason. /// \nosubgrouping //////////////////////////////////////////////////////////////////////////////////////////// typedef struct ADL_IMAGE_BOOST_NOTFICATION_REASON { int HotkeyChanged; //Set when Hotkey value is changed int GlobalEnableChanged; //Set when Global enable value is changed }ADL_IMAGE_BOOST_NOTFICATION_REASON; ///////////////////////////////////////////////////////////////////////////////////////////// ///\brief Structure containing information about OGL IMAGE BOOST Settings /// /// Elements of OGL IMAGE BOOST settings. /// \nosubgrouping //////////////////////////////////////////////////////////////////////////////////////////// typedef struct ADL_IMAGE_BOOST_SETTINGS { int Hotkey; // Hotkey value int GlobalEnable; //Global enable value }ADL_IMAGE_BOOST_SETTINGS; ///////////////////////////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////////////////////// ///\brief Structure containing information about RIS Settings change reason /// /// Elements of RIS settings changed reason. /// \nosubgrouping //////////////////////////////////////////////////////////////////////////////////////////// typedef struct ADL_RIS_NOTFICATION_REASON { unsigned int GlobalEnableChanged; //Set when Global enable value is changed unsigned int GlobalSharpeningDegreeChanged; //Set when Global sharpening Degree value is changed }ADL_RIS_NOTFICATION_REASON; ///////////////////////////////////////////////////////////////////////////////////////////// ///\brief Structure containing information about RIS Settings /// /// Elements of RIS settings. /// \nosubgrouping //////////////////////////////////////////////////////////////////////////////////////////// typedef struct ADL_RIS_SETTINGS { int GlobalEnable; //Global enable value int GlobalSharpeningDegree; //Global sharpening value int GlobalSharpeningDegree_MinLimit; //Gloabl sharpening slider min limit value int GlobalSharpeningDegree_MaxLimit; //Gloabl sharpening slider max limit value int GlobalSharpeningDegree_Step; //Gloabl sharpening step value }ADL_RIS_SETTINGS; ///////////////////////////////////////////////////////////////////////////////////////////// ///\brief Structure containing information about CHILL Settings change reason /// /// Elements of Chiil settings changed reason. /// \nosubgrouping //////////////////////////////////////////////////////////////////////////////////////////// typedef struct ADL_CHILL_NOTFICATION_REASON { int HotkeyChanged; //Set when Hotkey value is changed int GlobalEnableChanged; //Set when Global enable value is changed int GlobalMinFPSChanged; //Set when Global min FPS value is changed int GlobalMaxFPSChanged; //Set when Global max FPS value is changed }ADL_CHILL_NOTFICATION_REASON; ///////////////////////////////////////////////////////////////////////////////////////////// ///\brief Structure containing information about CHILL Settings /// /// Elements of Chill settings. /// \nosubgrouping //////////////////////////////////////////////////////////////////////////////////////////// typedef struct ADL_CHILL_SETTINGS { int Hotkey; // Hotkey value int GlobalEnable; //Global enable value int GlobalMinFPS; //Global Min FPS value int GlobalMaxFPS; //Global Max FPS value int GlobalFPS_MinLimit; //Gloabl FPS slider min limit value int GlobalFPS_MaxLimit; //Gloabl FPS slider max limit value int GlobalFPS_Step; //Gloabl FPS Slider step value }ADL_CHILL_SETTINGS; ///////////////////////////////////////////////////////////////////////////////////////////// ///\brief Structure containing information about DRIVERUPSCALE Settings change reason /// /// Elements of DRIVERUPSCALE settings changed reason. /// \nosubgrouping //////////////////////////////////////////////////////////////////////////////////////////// typedef struct ADL_DRIVERUPSCALE_NOTFICATION_REASON { int ModeOverrideEnabledChanged; //Set when Global min resolution value is changed int GlobalEnabledChanged; //Set when Global enable value is changed }ADL_DRIVERUPSCALE_NOTFICATION_REASON; ///////////////////////////////////////////////////////////////////////////////////////////// ///\brief Structure containing information about DRIVERUPSCALE Settings /// /// Elements of DRIVERUPSCALE settings. /// \nosubgrouping //////////////////////////////////////////////////////////////////////////////////////////// typedef struct ADL_DRIVERUPSCALE_SETTINGS { int ModeOverrideEnabled; int GlobalEnabled; }ADL_DRIVERUPSCALE_SETTINGS; ///////////////////////////////////////////////////////////////////////////////////////////// ///\brief Structure Containing R G B values for Radeon USB LED Bar /// /// Elements of RGB Values. /// \nosubgrouping //////////////////////////////////////////////////////////////////////////////////////////// typedef struct ADL_RADEON_LED_COLOR_CONFIG { unsigned short R : 8; // Red Value unsigned short G : 8; // Green Value unsigned short B : 8; // Blue Value }ADL_RADEON_LED_COLOR_CONFIG; ///////////////////////////////////////////////////////////////////////////////////////////// ///\brief Structure Containing All Generic LED configuration for user requested LED pattern. The driver will apply the confgiuration as requested /// /// Elements of Radeon USB LED configuration. /// \nosubgrouping //////////////////////////////////////////////////////////////////////////////////////////// typedef struct ADL_RADEON_LED_PATTERN_CONFIG_GENERIC { short brightness : 8; // Brightness of LED short speed : 8; // Speed of LED pattern bool directionCounterClockWise; //Direction of LED Pattern ADL_RADEON_LED_COLOR_CONFIG colorConfig; // RGB value of LED pattern char morseCodeText[ADL_RADEON_LED_MAX_MORSE_CODE]; // Morse Code user input for Morse Code LED pattern char morseCodeTextOutPut[ADL_RADEON_LED_MAX_MORSE_CODE]; // Driver set output representation of Morse Code int morseCodeTextOutPutLen; // Length of Morse Code output }ADL_RADEON_LED_PATTERN_CONFIG_GENERIC; ///////////////////////////////////////////////////////////////////////////////////////////// ///\brief Structure Containing All custom grid pattern LED configuration for user requested LED grid pattern. The driver will apply the confgiuration as requested /// /// Elements of Radeon USB LED custom grid configuration. /// \nosubgrouping //////////////////////////////////////////////////////////////////////////////////////////// typedef struct ADL_RADEON_LED_CUSTOM_LED_CONFIG { short brightness : 8; // Brightness of LED ADL_RADEON_LED_COLOR_CONFIG colorConfig[ADL_RADEON_LED_MAX_LED_ROW_ON_GRID][ADL_RADEON_LED_MAX_LED_COLUMN_ON_GRID]; // Full grid array representation of Radeon LED to be populated by user }ADL_RADEON_LED_CUSTOM_GRID_LED_CONFIG; ///////////////////////////////////////////////////////////////////////////////////////////// ///\brief Structure Containing All Radeon USB LED requests and controls. /// /// Elements of Radeon USB LED Controls. /// \nosubgrouping //////////////////////////////////////////////////////////////////////////////////////////// typedef struct ADL_RADEON_LED_PATTERN_CONFIG { ADL_RADEON_USB_LED_BAR_CONTROLS control; //Requested LED pattern union { ADL_RADEON_LED_PATTERN_CONFIG_GENERIC genericPararmeters; //Requested pattern configuration settings ADL_RADEON_LED_CUSTOM_GRID_LED_CONFIG customGridConfig; //Requested custom grid configuration settings }; }ADL_RADEON_LED_PATTERN_CONFIG; ///////////////////////////////////////////////////////////////////////////////////////////// ///\brief Structure containing information about the graphics adapter with extended caps /// /// This structure is used to store various information about the graphics adapter. This /// information can be returned to the user. Alternatively, it can be used to access various driver calls to set /// or fetch various settings upon the user's request. /// This AdapterInfoX2 struct extends the AdapterInfo struct in adl_structures.h /// \nosubgrouping //////////////////////////////////////////////////////////////////////////////////////////// typedef struct AdapterInfoX2 { /// \ALL_STRUCT_MEM /// Size of the structure. int iSize; /// The ADL index handle. One GPU may be associated with one or two index handles int iAdapterIndex; /// The unique device ID associated with this adapter. char strUDID[ADL_MAX_PATH]; /// The BUS number associated with this adapter. int iBusNumber; /// The driver number associated with this adapter. int iDeviceNumber; /// The function number. int iFunctionNumber; /// The vendor ID associated with this adapter. int iVendorID; /// Adapter name. char strAdapterName[ADL_MAX_PATH]; /// Display name. For example, "\\\\Display0" 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; /// Exist or not; 1 is exist and 0 is not present. int iExist; /// Driver registry path. char strDriverPath[ADL_MAX_PATH]; /// Driver registry path Ext for. char strDriverPathExt[ADL_MAX_PATH]; /// PNP string from Windows. char strPNPString[ADL_MAX_PATH]; /// It is generated from EnumDisplayDevices. int iOSDisplayIndex; /// The bit mask identifies the adapter info int iInfoMask; /// The bit identifies the adapter info \ref define_adapter_info int iInfoValue; } AdapterInfoX2, *LPAdapterInfoX2; ///////////////////////////////////////////////////////////////////////////////////////////// ///\brief Structure containing information about driver gamut space , whether it is related to source or to destination, overlay or graphics /// \nosubgrouping //////////////////////////////////////////////////////////////////////////////////////////// typedef struct ADLGamutReference { /// mask whether it is related to source or to destination, overlay or graphics int iGamutRef; }ADLGamutReference; ///////////////////////////////////////////////////////////////////////////////////////////// ///\brief Structure containing information about driver supported gamut spaces , capability method /// /// This structure is used to get driver all supported gamut spaces /// \nosubgrouping //////////////////////////////////////////////////////////////////////////////////////////// typedef struct ADLGamutInfo { ///Any combination of following ADL_GAMUT_SPACE_CCIR_709 - ADL_GAMUT_SPACE_CUSTOM int SupportedGamutSpace; ///Any combination of following ADL_WHITE_POINT_5000K - ADL_WHITE_POINT_CUSTOM int SupportedWhitePoint; } ADLGamutInfo; ///////////////////////////////////////////////////////////////////////////////////////////// ///\brief Structure containing information about driver point coordinates /// /// This structure is used to store the driver point coodinates for gamut and white point /// \nosubgrouping //////////////////////////////////////////////////////////////////////////////////////////// typedef struct ADLPoint { /// x coordinate int iX; /// y coordinate int iY; } ADLPoint; ///////////////////////////////////////////////////////////////////////////////////////////// ///\brief Structure containing information about driver supported gamut coordinates /// /// This structure is used to store the driver supported supported gamut coordinates /// \nosubgrouping //////////////////////////////////////////////////////////////////////////////////////////// typedef struct ADLGamutCoordinates { /// red channel chromasity coordinate ADLPoint Red; /// green channel chromasity coordinate ADLPoint Green; /// blue channel chromasity coordinate ADLPoint Blue; } ADLGamutCoordinates; ///////////////////////////////////////////////////////////////////////////////////////////// ///\brief Structure containing information about driver current gamut space , parent struct for ADLGamutCoordinates and ADLWhitePoint /// This structure is used to get/set driver supported gamut space /// \nosubgrouping //////////////////////////////////////////////////////////////////////////////////////////// typedef struct ADLGamutData { ///used as mask and could be 4 options ///BIT_0 If flag ADL_GAMUT_REFERENCE_SOURCE is asserted set operation is related to gamut source , ///if not gamut destination ///BIT_1 If flag ADL_GAMUT_GAMUT_VIDEO_CONTENT is asserted ///BIT_2,BIT_3 used as mask and could be 4 options custom (2) + predefined (2) ///0. Gamut predefined, white point predefined -> 0 | 0 ///1. Gamut predefined, white point custom -> 0 | ADL_CUSTOM_WHITE_POINT ///2. White point predefined, gamut custom -> 0 | ADL_CUSTOM_GAMUT ///3. White point custom, gamut custom -> ADL_CUSTOM_GAMUT | ADL_CUSTOM_WHITE_POINT int iFeature; ///one of ADL_GAMUT_SPACE_CCIR_709 - ADL_GAMUT_SPACE_CIE_RGB int iPredefinedGamut; ///one of ADL_WHITE_POINT_5000K - ADL_WHITE_POINT_9300K int iPredefinedWhitePoint; ///valid when in mask avails ADL_CUSTOM_WHITE_POINT ADLPoint CustomWhitePoint; ///valid when in mask avails ADL_CUSTOM_GAMUT ADLGamutCoordinates CustomGamut; } ADLGamutData; ///////////////////////////////////////////////////////////////////////////////////////////// ///\brief Structure containing detailed timing parameters. /// /// This structure is used to store the detailed timing parameters. /// \nosubgrouping //////////////////////////////////////////////////////////////////////////////////////////// typedef struct ADLDetailedTimingX2 { /// Size of the structure. int iSize; /// Timing flags. \ref define_detailed_timing_flags int sTimingFlags; /// Total width (columns). int sHTotal; /// Displayed width. int sHDisplay; /// Horizontal sync signal offset. int sHSyncStart; /// Horizontal sync signal width. int sHSyncWidth; /// Total height (rows). int sVTotal; /// Displayed height. int sVDisplay; /// Vertical sync signal offset. int sVSyncStart; /// Vertical sync signal width. int sVSyncWidth; /// Pixel clock value. int sPixelClock; /// Overscan right. short sHOverscanRight; /// Overscan left. short sHOverscanLeft; /// Overscan bottom. short sVOverscanBottom; /// Overscan top. short sVOverscanTop; short sOverscan8B; short sOverscanGR; } ADLDetailedTimingX2; ///////////////////////////////////////////////////////////////////////////////////////////// ///\brief Structure containing display mode information. /// /// This structure is used to store the display mode information. /// \nosubgrouping //////////////////////////////////////////////////////////////////////////////////////////// typedef struct ADLDisplayModeInfoX2 { /// Timing standard of the current mode. \ref define_modetiming_standard int iTimingStandard; /// Applicable timing standards for the current mode. int iPossibleStandard; /// Refresh rate factor. int iRefreshRate; /// Num of pixels in a row. int iPelsWidth; /// Num of pixels in a column. int iPelsHeight; /// Detailed timing parameters. ADLDetailedTimingX2 sDetailedTiming; } ADLDisplayModeInfoX2; ///////////////////////////////////////////////////////////////////////////////////////////// ///\brief Structure containing information about I2C. /// /// This structure is used to store the I2C information for the current adapter. /// This structure is used by \ref ADL_Display_WriteAndReadI2CLargePayload /// \nosubgrouping //////////////////////////////////////////////////////////////////////////////////////////// typedef struct ADLI2CLargePayload { /// Size of the structure int iSize; /// Numerical value representing hardware I2C. int iLine; /// The 7-bit I2C slave device address. int iAddress; /// The offset of the data from the address. int iOffset; /// Read from or write to slave device. \ref ADL_DL_I2C_ACTIONREAD or \ref ADL_DL_I2C_ACTIONWRITE int iAction; /// I2C clock speed in KHz. int iSpeed; /// I2C option flags. \ref define_ADLI2CLargePayload int iFlags; /// A numerical value representing the number of bytes to be sent or received on the I2C bus. int iDataSize; /// Address of the characters which are to be sent or received on the I2C bus. char *pcData; } ADLI2CLargePayload; /// Size in bytes of the Feature Name #define ADL_FEATURE_NAME_LENGTH 16 ///////////////////////////////////////////////////////////////////////////////////////////// ///\brief Structure containing the Multimedia Feature Name /// //////////////////////////////////////////////////////////////////////////////////////////// typedef struct ADLFeatureName { /// The Feature Name char FeatureName[ADL_FEATURE_NAME_LENGTH]; } ADLFeatureName, *LPADLFeatureName; ///////////////////////////////////////////////////////////////////////////////////////////// ///\brief Structure containing information about MM Feature Capabilities. /// //////////////////////////////////////////////////////////////////////////////////////////// typedef struct ADLFeatureCaps { /// The Feature Name ADLFeatureName Name; // char strFeatureName[ADL_FEATURE_NAME_LENGTH]; /// Group ID. All Features in the same group are shown sequentially in the same UI Page. int iGroupID; /// Visual ID. Places one or more features in a Group Box. If zero, no Group Box is added. int iVisualID; /// Page ID. All Features with the same Page ID value are shown together on the same UI page. int iPageID; /// Feature Property Mask. Indicates which are the valid bits for iFeatureProperties. int iFeatureMask; /// Feature Property Values. See definitions for ADL_FEATURE_PROPERTIES_XXX int iFeatureProperties; /// Apperance of the User-Controlled Boolean. int iControlType; /// Style of the User-Controlled Boolean. int iControlStyle; /// Apperance of the Adjustment Controls. int iAdjustmentType; /// Style of the Adjustment Controls. int iAdjustmentStyle; /// Default user-controlled boolean value. Valid only if ADLFeatureCaps supports user-controlled boolean. int bDefault; /// Minimum integer value. Valid only if ADLFeatureCaps indicates support for integers. int iMin; /// Maximum integer value. Valid only if ADLFeatureCaps indicates support for integers. int iMax; /// Step integer value. Valid only if ADLFeatureCaps indicates support for integers. int iStep; /// Default integer value. Valid only if ADLFeatureCaps indicates support for integers. int iDefault; /// Minimum float value. Valid only if ADLFeatureCaps indicates support for floats. float fMin; /// Maximum float value. Valid only if ADLFeatureCaps indicates support for floats. float fMax; /// Step float value. Valid only if ADLFeatureCaps indicates support for floats. float fStep; /// Default float value. Valid only if ADLFeatureCaps indicates support for floats. float fDefault; /// The Mask for available bits for enumerated values.(If ADLFeatureCaps supports ENUM values) int EnumMask; } ADLFeatureCaps, *LPADLFeatureCaps; ///////////////////////////////////////////////////////////////////////////////////////////// ///\brief Structure containing information about MM Feature Values. /// //////////////////////////////////////////////////////////////////////////////////////////// typedef struct ADLFeatureValues { /// The Feature Name ADLFeatureName Name; // char strFeatureName[ADL_FEATURE_NAME_LENGTH]; /// User controlled Boolean current value. Valid only if ADLFeatureCaps supports Boolean. int bCurrent; /// Current integer value. Valid only if ADLFeatureCaps indicates support for integers. int iCurrent; /// Current float value. Valid only if ADLFeatureCaps indicates support for floats. float fCurrent; /// The States for the available bits for enumerated values. int EnumStates; } ADLFeatureValues, *LPADLFeatureValues; ///////////////////////////////////////////////////////////////////////////////////////////// ///\brief Structure containing HDCP Settings info /// /// This structure is used to store the HDCP settings of a /// display /// \nosubgrouping //////////////////////////////////////////////////////////////////////////////////////////// typedef struct ADLHDCPSettings { int iHDCPProtectionVersion; // Version, starting from 1 int iHDCPCaps; //Caps used to ensure at least one protection scheme is supported, 1 is HDCP1X and 2 is HDCP22 int iAllowAll; //Allow all is true, disable all is false int iHDCPVale; int iHDCPMask; } ADLHDCPSettings; ///////////////////////////////////////////////////////////////////////////////////////////// ///\brief Structure containing Mantle App info /// /// This structure is used to store the Mantle Driver information /// \nosubgrouping //////////////////////////////////////////////////////////////////////////////////////////// typedef struct ADLMantleAppInfo { /// mantle api version int apiVersion; /// mantle driver version long driverVersion; /// mantle vendroe id long vendorId; /// mantle device id long deviceId; /// mantle gpu type; int gpuType; /// gpu name char gpuName[256]; /// mem size int maxMemRefsPerSubmission; /// virtual mem size long long virtualMemPageSize; /// mem update long long maxInlineMemoryUpdateSize; /// bound descriptot long maxBoundDescriptorSets; /// thread group size long maxThreadGroupSize; /// time stamp frequency long long timestampFrequency; /// color target long multiColorTargetClears; }ADLMantleAppInfo, *LPADLMantleAppInfo; //////////////////////////////////////////////////////////////////////////////////////////// ///\brief Structure containing information about SDIData ///This structure is used to store information about the state of the SDI whether it is on ///or off and the current size of the segment or aperture size. //////////////////////////////////////////////////////////////////////////////////////////// typedef struct ADLSDIData { /// The SDI state, ADL_SDI_ON or ADL_SDI_OFF, for the current SDI mode int iSDIState; /// Size of the memory segment for SDI (in MB). int iSizeofSDISegment; } ADLSDIData, *LPADLSDIData; ///////////////////////////////////////////////////////////////////////////////////////////// ///\brief Structure containing information about FRTCPRO Settings /// /// Elements of FRTCPRO settings. /// \nosubgrouping //////////////////////////////////////////////////////////////////////////////////////////// typedef struct ADL_FRTCPRO_Settings { int DefaultState; //The default status for FRTC pro int CurrentState; //The current enable/disable status for FRTC pro unsigned int DefaultValue; //The default FPS value for FRTC pro. unsigned int CurrentValue; //The current FPS value for FRTC pro. unsigned int maxSupportedFps; //The max value for FRTC pro. unsigned int minSupportedFps; //The min value for FRTC pro. }ADL_FRTCPRO_Settings, *LPADLFRTCProSettings; ///////////////////////////////////////////////////////////////////////////////////////////// ///\brief Structure containing information about FRTCPRO Settings changed reason /// /// Reason of FRTCPRO changed. /// \nosubgrouping //////////////////////////////////////////////////////////////////////////////////////////// typedef struct ADL_FRTCPRO_CHANGED_REASON { int StateChanged; // FRTCPro state changed int ValueChanged; // FRTCPro value changed }ADL_FRTCPRO_CHANGED_REASON; ///////////////////////////////////////////////////////////////////////////////////////////// /// \brief Structure containing the display mode definition used per controller. /// /// This structure is used to store the display mode definition used per controller. /// \nosubgrouping //////////////////////////////////////////////////////////////////////////////////////////// typedef struct ADL_DL_DISPLAY_MODE { int iPelsHeight; // Vertical resolution (in pixels). int iPelsWidth; // Horizontal resolution (in pixels). int iBitsPerPel; // Color depth. int iDisplayFrequency; // Refresh rate. } ADL_DL_DISPLAY_MODE; ///////////////////////////////////////////////////////////////////////////////////////////// ///\brief Structure containing information related DCE support /// /// This structure is used to store a bit vector of possible DCE support /// /// \nosubgrouping //////////////////////////////////////////////////////////////////////////////////////////// typedef union _ADLDCESupport { struct { unsigned int PrePhasis : 1; unsigned int voltageSwing : 1; unsigned int reserved : 30; }bits; unsigned int u32All; }ADLDCESupport; ///////////////////////////////////////////////////////////////////////////////////////////// /// \brief Structure for Smart shift 2.0 settings /// /// This structure is used to return the smart shift settings /// \nosubgrouping //////////////////////////////////////////////////////////////////////////////////////////// typedef struct ADLSmartShiftSettings { int iMinRange; int iMaxRange; int iDefaultMode; //Refer to CWDDEPM_ODN_CONTROL_TYPE int iDefaultValue; int iCurrentMode; int iCurrentValue; int iFlags; //refer to define_smartshift_bits }ADLSmartShiftSettings, *LPADLSmartShiftSettings; #endif /* ADL_STRUCTURES_H_ */ ================================================ FILE: src/3rdparty/display-library/repo.json ================================================ { "home": "https://github.com/GPUOpen-LibrariesAndSDKs/display-library", "license": "MIT (embeded in source)", "version": "ADL SDK 17.1", "author": "Advanced Micro Devices, Inc" } ================================================ FILE: src/3rdparty/widecharwidth/repo.json ================================================ { "home": "https://github.com/ridiculousfish/widecharwidth", "license": "Public domain", "version": "Unicode 17", "author": "ridiculousfish" } ================================================ FILE: src/3rdparty/widecharwidth/widechar_width_c.h ================================================ /** * widechar_width_c.h for Unicode 17.0.0 * See https://github.com/ridiculousfish/widecharwidth/ * * SHA1 file hashes: * ( * the hashes for generate.py and the template are git object hashes, * use `git log --all --find-object=` in the widecharwidth repository * to see which commit they correspond to, * or run `git hash-object` on the file to compare. * The other hashes are simple `sha1sum` style hashes. * ) * * generate.py: b35da43f176cc0d5880c67356ebb064048c5bac4 * template.js: 1985fb56796d6d9627f9c5290d5dee9f9364bcf4 * UnicodeData.txt: 50dffef1b7d1f97b72e4c2adceb9b2245f0f34ba * EastAsianWidth.txt: 2cadc5034b6206ad84b75898a1d4186bb38fc12b * emoji-data.txt: 3d123e12f70f63e609c4281ce83dfdd9ac7443d2 */ #ifndef WIDECHAR_WIDTH_H #define WIDECHAR_WIDTH_H #include #include #include #ifndef widechar_ARRAY_SIZE #define widechar_ARRAY_SIZE(array) (sizeof(array) / sizeof(array[0])) #endif /* Special width values */ enum { widechar_nonprint = -1, // The character is not printable. widechar_combining = -2, // The character is a zero-width combiner. widechar_ambiguous = -3, // The character is East-Asian ambiguous width. widechar_private_use = -4, // The character is for private use. widechar_unassigned = -5, // The character is unassigned. widechar_widened_in_9 = -6, // Width is 1 in Unicode 8, 2 in Unicode 9+. widechar_non_character = -7 // The character is a noncharacter. }; /* An inclusive range of characters. */ struct widechar_range { uint32_t lo; uint32_t hi; }; /* Simple ASCII characters - used a lot, so we check them first. */ static const struct widechar_range widechar_ascii_table[] = { {0x00020, 0x0007E} }; /* Private usage range. */ static const struct widechar_range widechar_private_table[] = { {0x0E000, 0x0F8FF}, {0xF0000, 0xFFFFD}, {0x100000, 0x10FFFD} }; /* Nonprinting characters. */ static const struct widechar_range widechar_nonprint_table[] = { {0x00000, 0x0001F}, {0x0007F, 0x0009F}, {0x000AD, 0x000AD}, {0x00600, 0x00605}, {0x0061C, 0x0061C}, {0x006DD, 0x006DD}, {0x0070F, 0x0070F}, {0x00890, 0x00891}, {0x008E2, 0x008E2}, {0x0180E, 0x0180E}, {0x0200B, 0x0200F}, {0x02028, 0x0202E}, {0x02060, 0x02064}, {0x02066, 0x0206F}, {0x0D800, 0x0DFFF}, {0x0FEFF, 0x0FEFF}, {0x0FFF9, 0x0FFFB}, {0x110BD, 0x110BD}, {0x110CD, 0x110CD}, {0x13430, 0x1343F}, {0x1BCA0, 0x1BCA3}, {0x1D173, 0x1D17A}, {0xE0001, 0xE0001}, {0xE0020, 0xE007F} }; /* Width 0 combining marks. */ static const struct widechar_range widechar_combining_table[] = { {0x00300, 0x0036F}, {0x00483, 0x00489}, {0x00591, 0x005BD}, {0x005BF, 0x005BF}, {0x005C1, 0x005C2}, {0x005C4, 0x005C5}, {0x005C7, 0x005C7}, {0x00610, 0x0061A}, {0x0064B, 0x0065F}, {0x00670, 0x00670}, {0x006D6, 0x006DC}, {0x006DF, 0x006E4}, {0x006E7, 0x006E8}, {0x006EA, 0x006ED}, {0x00711, 0x00711}, {0x00730, 0x0074A}, {0x007A6, 0x007B0}, {0x007EB, 0x007F3}, {0x007FD, 0x007FD}, {0x00816, 0x00819}, {0x0081B, 0x00823}, {0x00825, 0x00827}, {0x00829, 0x0082D}, {0x00859, 0x0085B}, {0x00897, 0x0089F}, {0x008CA, 0x008E1}, {0x008E3, 0x00903}, {0x0093A, 0x0093C}, {0x0093E, 0x0094F}, {0x00951, 0x00957}, {0x00962, 0x00963}, {0x00981, 0x00983}, {0x009BC, 0x009BC}, {0x009BE, 0x009C4}, {0x009C7, 0x009C8}, {0x009CB, 0x009CD}, {0x009D7, 0x009D7}, {0x009E2, 0x009E3}, {0x009FE, 0x009FE}, {0x00A01, 0x00A03}, {0x00A3C, 0x00A3C}, {0x00A3E, 0x00A42}, {0x00A47, 0x00A48}, {0x00A4B, 0x00A4D}, {0x00A51, 0x00A51}, {0x00A70, 0x00A71}, {0x00A75, 0x00A75}, {0x00A81, 0x00A83}, {0x00ABC, 0x00ABC}, {0x00ABE, 0x00AC5}, {0x00AC7, 0x00AC9}, {0x00ACB, 0x00ACD}, {0x00AE2, 0x00AE3}, {0x00AFA, 0x00AFF}, {0x00B01, 0x00B03}, {0x00B3C, 0x00B3C}, {0x00B3E, 0x00B44}, {0x00B47, 0x00B48}, {0x00B4B, 0x00B4D}, {0x00B55, 0x00B57}, {0x00B62, 0x00B63}, {0x00B82, 0x00B82}, {0x00BBE, 0x00BC2}, {0x00BC6, 0x00BC8}, {0x00BCA, 0x00BCD}, {0x00BD7, 0x00BD7}, {0x00C00, 0x00C04}, {0x00C3C, 0x00C3C}, {0x00C3E, 0x00C44}, {0x00C46, 0x00C48}, {0x00C4A, 0x00C4D}, {0x00C55, 0x00C56}, {0x00C62, 0x00C63}, {0x00C81, 0x00C83}, {0x00CBC, 0x00CBC}, {0x00CBE, 0x00CC4}, {0x00CC6, 0x00CC8}, {0x00CCA, 0x00CCD}, {0x00CD5, 0x00CD6}, {0x00CE2, 0x00CE3}, {0x00CF3, 0x00CF3}, {0x00D00, 0x00D03}, {0x00D3B, 0x00D3C}, {0x00D3E, 0x00D44}, {0x00D46, 0x00D48}, {0x00D4A, 0x00D4D}, {0x00D57, 0x00D57}, {0x00D62, 0x00D63}, {0x00D81, 0x00D83}, {0x00DCA, 0x00DCA}, {0x00DCF, 0x00DD4}, {0x00DD6, 0x00DD6}, {0x00DD8, 0x00DDF}, {0x00DF2, 0x00DF3}, {0x00E31, 0x00E31}, {0x00E34, 0x00E3A}, {0x00E47, 0x00E4E}, {0x00EB1, 0x00EB1}, {0x00EB4, 0x00EBC}, {0x00EC8, 0x00ECE}, {0x00F18, 0x00F19}, {0x00F35, 0x00F35}, {0x00F37, 0x00F37}, {0x00F39, 0x00F39}, {0x00F3E, 0x00F3F}, {0x00F71, 0x00F84}, {0x00F86, 0x00F87}, {0x00F8D, 0x00F97}, {0x00F99, 0x00FBC}, {0x00FC6, 0x00FC6}, {0x0102B, 0x0103E}, {0x01056, 0x01059}, {0x0105E, 0x01060}, {0x01062, 0x01064}, {0x01067, 0x0106D}, {0x01071, 0x01074}, {0x01082, 0x0108D}, {0x0108F, 0x0108F}, {0x0109A, 0x0109D}, {0x0135D, 0x0135F}, {0x01712, 0x01715}, {0x01732, 0x01734}, {0x01752, 0x01753}, {0x01772, 0x01773}, {0x017B4, 0x017D3}, {0x017DD, 0x017DD}, {0x0180B, 0x0180D}, {0x0180F, 0x0180F}, {0x01885, 0x01886}, {0x018A9, 0x018A9}, {0x01920, 0x0192B}, {0x01930, 0x0193B}, {0x01A17, 0x01A1B}, {0x01A55, 0x01A5E}, {0x01A60, 0x01A7C}, {0x01A7F, 0x01A7F}, {0x01AB0, 0x01ADD}, {0x01AE0, 0x01AEB}, {0x01B00, 0x01B04}, {0x01B34, 0x01B44}, {0x01B6B, 0x01B73}, {0x01B80, 0x01B82}, {0x01BA1, 0x01BAD}, {0x01BE6, 0x01BF3}, {0x01C24, 0x01C37}, {0x01CD0, 0x01CD2}, {0x01CD4, 0x01CE8}, {0x01CED, 0x01CED}, {0x01CF4, 0x01CF4}, {0x01CF7, 0x01CF9}, {0x01DC0, 0x01DFF}, {0x020D0, 0x020F0}, {0x02CEF, 0x02CF1}, {0x02D7F, 0x02D7F}, {0x02DE0, 0x02DFF}, {0x0302A, 0x0302F}, {0x03099, 0x0309A}, {0x0A66F, 0x0A672}, {0x0A674, 0x0A67D}, {0x0A69E, 0x0A69F}, {0x0A6F0, 0x0A6F1}, {0x0A802, 0x0A802}, {0x0A806, 0x0A806}, {0x0A80B, 0x0A80B}, {0x0A823, 0x0A827}, {0x0A82C, 0x0A82C}, {0x0A880, 0x0A881}, {0x0A8B4, 0x0A8C5}, {0x0A8E0, 0x0A8F1}, {0x0A8FF, 0x0A8FF}, {0x0A926, 0x0A92D}, {0x0A947, 0x0A953}, {0x0A980, 0x0A983}, {0x0A9B3, 0x0A9C0}, {0x0A9E5, 0x0A9E5}, {0x0AA29, 0x0AA36}, {0x0AA43, 0x0AA43}, {0x0AA4C, 0x0AA4D}, {0x0AA7B, 0x0AA7D}, {0x0AAB0, 0x0AAB0}, {0x0AAB2, 0x0AAB4}, {0x0AAB7, 0x0AAB8}, {0x0AABE, 0x0AABF}, {0x0AAC1, 0x0AAC1}, {0x0AAEB, 0x0AAEF}, {0x0AAF5, 0x0AAF6}, {0x0ABE3, 0x0ABEA}, {0x0ABEC, 0x0ABED}, {0x0FB1E, 0x0FB1E}, {0x0FE00, 0x0FE0F}, {0x0FE20, 0x0FE2F}, {0x101FD, 0x101FD}, {0x102E0, 0x102E0}, {0x10376, 0x1037A}, {0x10A01, 0x10A03}, {0x10A05, 0x10A06}, {0x10A0C, 0x10A0F}, {0x10A38, 0x10A3A}, {0x10A3F, 0x10A3F}, {0x10AE5, 0x10AE6}, {0x10D24, 0x10D27}, {0x10D69, 0x10D6D}, {0x10EAB, 0x10EAC}, {0x10EFA, 0x10EFF}, {0x10F46, 0x10F50}, {0x10F82, 0x10F85}, {0x11000, 0x11002}, {0x11038, 0x11046}, {0x11070, 0x11070}, {0x11073, 0x11074}, {0x1107F, 0x11082}, {0x110B0, 0x110BA}, {0x110C2, 0x110C2}, {0x11100, 0x11102}, {0x11127, 0x11134}, {0x11145, 0x11146}, {0x11173, 0x11173}, {0x11180, 0x11182}, {0x111B3, 0x111C0}, {0x111C9, 0x111CC}, {0x111CE, 0x111CF}, {0x1122C, 0x11237}, {0x1123E, 0x1123E}, {0x11241, 0x11241}, {0x112DF, 0x112EA}, {0x11300, 0x11303}, {0x1133B, 0x1133C}, {0x1133E, 0x11344}, {0x11347, 0x11348}, {0x1134B, 0x1134D}, {0x11357, 0x11357}, {0x11362, 0x11363}, {0x11366, 0x1136C}, {0x11370, 0x11374}, {0x113B8, 0x113C0}, {0x113C2, 0x113C2}, {0x113C5, 0x113C5}, {0x113C7, 0x113CA}, {0x113CC, 0x113D0}, {0x113D2, 0x113D2}, {0x113E1, 0x113E2}, {0x11435, 0x11446}, {0x1145E, 0x1145E}, {0x114B0, 0x114C3}, {0x115AF, 0x115B5}, {0x115B8, 0x115C0}, {0x115DC, 0x115DD}, {0x11630, 0x11640}, {0x116AB, 0x116B7}, {0x1171D, 0x1172B}, {0x1182C, 0x1183A}, {0x11930, 0x11935}, {0x11937, 0x11938}, {0x1193B, 0x1193E}, {0x11940, 0x11940}, {0x11942, 0x11943}, {0x119D1, 0x119D7}, {0x119DA, 0x119E0}, {0x119E4, 0x119E4}, {0x11A01, 0x11A0A}, {0x11A33, 0x11A39}, {0x11A3B, 0x11A3E}, {0x11A47, 0x11A47}, {0x11A51, 0x11A5B}, {0x11A8A, 0x11A99}, {0x11B60, 0x11B67}, {0x11C2F, 0x11C36}, {0x11C38, 0x11C3F}, {0x11C92, 0x11CA7}, {0x11CA9, 0x11CB6}, {0x11D31, 0x11D36}, {0x11D3A, 0x11D3A}, {0x11D3C, 0x11D3D}, {0x11D3F, 0x11D45}, {0x11D47, 0x11D47}, {0x11D8A, 0x11D8E}, {0x11D90, 0x11D91}, {0x11D93, 0x11D97}, {0x11EF3, 0x11EF6}, {0x11F00, 0x11F01}, {0x11F03, 0x11F03}, {0x11F34, 0x11F3A}, {0x11F3E, 0x11F42}, {0x11F5A, 0x11F5A}, {0x13440, 0x13440}, {0x13447, 0x13455}, {0x1611E, 0x1612F}, {0x16AF0, 0x16AF4}, {0x16B30, 0x16B36}, {0x16F4F, 0x16F4F}, {0x16F51, 0x16F87}, {0x16F8F, 0x16F92}, {0x16FE4, 0x16FE4}, {0x16FF0, 0x16FF1}, {0x1BC9D, 0x1BC9E}, {0x1CF00, 0x1CF2D}, {0x1CF30, 0x1CF46}, {0x1D165, 0x1D169}, {0x1D16D, 0x1D172}, {0x1D17B, 0x1D182}, {0x1D185, 0x1D18B}, {0x1D1AA, 0x1D1AD}, {0x1D242, 0x1D244}, {0x1DA00, 0x1DA36}, {0x1DA3B, 0x1DA6C}, {0x1DA75, 0x1DA75}, {0x1DA84, 0x1DA84}, {0x1DA9B, 0x1DA9F}, {0x1DAA1, 0x1DAAF}, {0x1E000, 0x1E006}, {0x1E008, 0x1E018}, {0x1E01B, 0x1E021}, {0x1E023, 0x1E024}, {0x1E026, 0x1E02A}, {0x1E08F, 0x1E08F}, {0x1E130, 0x1E136}, {0x1E2AE, 0x1E2AE}, {0x1E2EC, 0x1E2EF}, {0x1E4EC, 0x1E4EF}, {0x1E5EE, 0x1E5EF}, {0x1E6E3, 0x1E6E3}, {0x1E6E6, 0x1E6E6}, {0x1E6EE, 0x1E6EF}, {0x1E6F5, 0x1E6F5}, {0x1E8D0, 0x1E8D6}, {0x1E944, 0x1E94A}, {0xE0100, 0xE01EF} }; /* Width 0 combining letters. */ static const struct widechar_range widechar_combiningletters_table[] = { {0x01160, 0x011FF}, {0x0D7B0, 0x0D7FF} }; /* Width 2 characters. */ static const struct widechar_range widechar_doublewide_table[] = { {0x01100, 0x0115F}, {0x02329, 0x0232A}, {0x02630, 0x02637}, {0x0268A, 0x0268F}, {0x02E80, 0x02E99}, {0x02E9B, 0x02EF3}, {0x02F00, 0x02FD5}, {0x02FF0, 0x0303E}, {0x03041, 0x03096}, {0x03099, 0x030FF}, {0x03105, 0x0312F}, {0x03131, 0x0318E}, {0x03190, 0x031E5}, {0x031EF, 0x0321E}, {0x03220, 0x03247}, {0x03250, 0x0A48C}, {0x0A490, 0x0A4C6}, {0x0A960, 0x0A97C}, {0x0AC00, 0x0D7A3}, {0x0F900, 0x0FAFF}, {0x0FE10, 0x0FE19}, {0x0FE30, 0x0FE52}, {0x0FE54, 0x0FE66}, {0x0FE68, 0x0FE6B}, {0x0FF01, 0x0FF60}, {0x0FFE0, 0x0FFE6}, {0x16FE0, 0x16FE4}, {0x16FF0, 0x16FF6}, {0x17000, 0x18CD5}, {0x18CFF, 0x18D1E}, {0x18D80, 0x18DF2}, {0x1AFF0, 0x1AFF3}, {0x1AFF5, 0x1AFFB}, {0x1AFFD, 0x1AFFE}, {0x1B000, 0x1B122}, {0x1B132, 0x1B132}, {0x1B150, 0x1B152}, {0x1B155, 0x1B155}, {0x1B164, 0x1B167}, {0x1B170, 0x1B2FB}, {0x1D300, 0x1D356}, {0x1D360, 0x1D376}, {0x1F200, 0x1F200}, {0x1F202, 0x1F202}, {0x1F210, 0x1F219}, {0x1F21B, 0x1F22E}, {0x1F230, 0x1F231}, {0x1F237, 0x1F237}, {0x1F23B, 0x1F23B}, {0x1F240, 0x1F248}, {0x1F260, 0x1F265}, {0x1F57A, 0x1F57A}, {0x1F5A4, 0x1F5A4}, {0x1F6D1, 0x1F6D2}, {0x1F6D5, 0x1F6D8}, {0x1F6DC, 0x1F6DF}, {0x1F6F4, 0x1F6FC}, {0x1F7E0, 0x1F7EB}, {0x1F7F0, 0x1F7F0}, {0x1F90C, 0x1F90F}, {0x1F919, 0x1F93A}, {0x1F93C, 0x1F945}, {0x1F947, 0x1F97F}, {0x1F985, 0x1F9BF}, {0x1F9C1, 0x1F9FF}, {0x1FA70, 0x1FA7C}, {0x1FA80, 0x1FA8A}, {0x1FA8E, 0x1FAC6}, {0x1FAC8, 0x1FAC8}, {0x1FACD, 0x1FADC}, {0x1FADF, 0x1FAEA}, {0x1FAEF, 0x1FAF8}, {0x20000, 0x2FFFD}, {0x30000, 0x3FFFD} }; /* Ambiguous-width characters. */ static const struct widechar_range widechar_ambiguous_table[] = { {0x000A1, 0x000A1}, {0x000A4, 0x000A4}, {0x000A7, 0x000A8}, {0x000AA, 0x000AA}, {0x000AD, 0x000AE}, {0x000B0, 0x000B4}, {0x000B6, 0x000BA}, {0x000BC, 0x000BF}, {0x000C6, 0x000C6}, {0x000D0, 0x000D0}, {0x000D7, 0x000D8}, {0x000DE, 0x000E1}, {0x000E6, 0x000E6}, {0x000E8, 0x000EA}, {0x000EC, 0x000ED}, {0x000F0, 0x000F0}, {0x000F2, 0x000F3}, {0x000F7, 0x000FA}, {0x000FC, 0x000FC}, {0x000FE, 0x000FE}, {0x00101, 0x00101}, {0x00111, 0x00111}, {0x00113, 0x00113}, {0x0011B, 0x0011B}, {0x00126, 0x00127}, {0x0012B, 0x0012B}, {0x00131, 0x00133}, {0x00138, 0x00138}, {0x0013F, 0x00142}, {0x00144, 0x00144}, {0x00148, 0x0014B}, {0x0014D, 0x0014D}, {0x00152, 0x00153}, {0x00166, 0x00167}, {0x0016B, 0x0016B}, {0x001CE, 0x001CE}, {0x001D0, 0x001D0}, {0x001D2, 0x001D2}, {0x001D4, 0x001D4}, {0x001D6, 0x001D6}, {0x001D8, 0x001D8}, {0x001DA, 0x001DA}, {0x001DC, 0x001DC}, {0x00251, 0x00251}, {0x00261, 0x00261}, {0x002C4, 0x002C4}, {0x002C7, 0x002C7}, {0x002C9, 0x002CB}, {0x002CD, 0x002CD}, {0x002D0, 0x002D0}, {0x002D8, 0x002DB}, {0x002DD, 0x002DD}, {0x002DF, 0x002DF}, {0x00300, 0x0036F}, {0x00391, 0x003A1}, {0x003A3, 0x003A9}, {0x003B1, 0x003C1}, {0x003C3, 0x003C9}, {0x00401, 0x00401}, {0x00410, 0x0044F}, {0x00451, 0x00451}, {0x02010, 0x02010}, {0x02013, 0x02016}, {0x02018, 0x02019}, {0x0201C, 0x0201D}, {0x02020, 0x02022}, {0x02024, 0x02027}, {0x02030, 0x02030}, {0x02032, 0x02033}, {0x02035, 0x02035}, {0x0203B, 0x0203B}, {0x0203E, 0x0203E}, {0x02074, 0x02074}, {0x0207F, 0x0207F}, {0x02081, 0x02084}, {0x020AC, 0x020AC}, {0x02103, 0x02103}, {0x02105, 0x02105}, {0x02109, 0x02109}, {0x02113, 0x02113}, {0x02116, 0x02116}, {0x02121, 0x02122}, {0x02126, 0x02126}, {0x0212B, 0x0212B}, {0x02153, 0x02154}, {0x0215B, 0x0215E}, {0x02160, 0x0216B}, {0x02170, 0x02179}, {0x02189, 0x02189}, {0x02190, 0x02199}, {0x021B8, 0x021B9}, {0x021D2, 0x021D2}, {0x021D4, 0x021D4}, {0x021E7, 0x021E7}, {0x02200, 0x02200}, {0x02202, 0x02203}, {0x02207, 0x02208}, {0x0220B, 0x0220B}, {0x0220F, 0x0220F}, {0x02211, 0x02211}, {0x02215, 0x02215}, {0x0221A, 0x0221A}, {0x0221D, 0x02220}, {0x02223, 0x02223}, {0x02225, 0x02225}, {0x02227, 0x0222C}, {0x0222E, 0x0222E}, {0x02234, 0x02237}, {0x0223C, 0x0223D}, {0x02248, 0x02248}, {0x0224C, 0x0224C}, {0x02252, 0x02252}, {0x02260, 0x02261}, {0x02264, 0x02267}, {0x0226A, 0x0226B}, {0x0226E, 0x0226F}, {0x02282, 0x02283}, {0x02286, 0x02287}, {0x02295, 0x02295}, {0x02299, 0x02299}, {0x022A5, 0x022A5}, {0x022BF, 0x022BF}, {0x02312, 0x02312}, {0x02460, 0x024E9}, {0x024EB, 0x0254B}, {0x02550, 0x02573}, {0x02580, 0x0258F}, {0x02592, 0x02595}, {0x025A0, 0x025A1}, {0x025A3, 0x025A9}, {0x025B2, 0x025B3}, {0x025B6, 0x025B7}, {0x025BC, 0x025BD}, {0x025C0, 0x025C1}, {0x025C6, 0x025C8}, {0x025CB, 0x025CB}, {0x025CE, 0x025D1}, {0x025E2, 0x025E5}, {0x025EF, 0x025EF}, {0x02605, 0x02606}, {0x02609, 0x02609}, {0x0260E, 0x0260F}, {0x0261C, 0x0261C}, {0x0261E, 0x0261E}, {0x02640, 0x02640}, {0x02642, 0x02642}, {0x02660, 0x02661}, {0x02663, 0x02665}, {0x02667, 0x0266A}, {0x0266C, 0x0266D}, {0x0266F, 0x0266F}, {0x0269E, 0x0269F}, {0x026BF, 0x026BF}, {0x026C6, 0x026CD}, {0x026CF, 0x026D3}, {0x026D5, 0x026E1}, {0x026E3, 0x026E3}, {0x026E8, 0x026E9}, {0x026EB, 0x026F1}, {0x026F4, 0x026F4}, {0x026F6, 0x026F9}, {0x026FB, 0x026FC}, {0x026FE, 0x026FF}, {0x0273D, 0x0273D}, {0x02776, 0x0277F}, {0x02B56, 0x02B59}, {0x03248, 0x0324F}, {0x0E000, 0x0F8FF}, {0x0FE00, 0x0FE0F}, {0x0FFFD, 0x0FFFD}, {0x1F100, 0x1F10A}, {0x1F110, 0x1F12D}, {0x1F130, 0x1F169}, {0x1F170, 0x1F18D}, {0x1F18F, 0x1F190}, {0x1F19B, 0x1F1AC}, {0xE0100, 0xE01EF}, {0xF0000, 0xFFFFD}, {0x100000, 0x10FFFD} }; /* Unassigned characters. */ static const struct widechar_range widechar_unassigned_table[] = { {0x00378, 0x00379}, {0x00380, 0x00383}, {0x0038B, 0x0038B}, {0x0038D, 0x0038D}, {0x003A2, 0x003A2}, {0x00530, 0x00530}, {0x00557, 0x00558}, {0x0058B, 0x0058C}, {0x00590, 0x00590}, {0x005C8, 0x005CF}, {0x005EB, 0x005EE}, {0x005F5, 0x005FF}, {0x0070E, 0x0070E}, {0x0074B, 0x0074C}, {0x007B2, 0x007BF}, {0x007FB, 0x007FC}, {0x0082E, 0x0082F}, {0x0083F, 0x0083F}, {0x0085C, 0x0085D}, {0x0085F, 0x0085F}, {0x0086B, 0x0086F}, {0x00892, 0x00896}, {0x00984, 0x00984}, {0x0098D, 0x0098E}, {0x00991, 0x00992}, {0x009A9, 0x009A9}, {0x009B1, 0x009B1}, {0x009B3, 0x009B5}, {0x009BA, 0x009BB}, {0x009C5, 0x009C6}, {0x009C9, 0x009CA}, {0x009CF, 0x009D6}, {0x009D8, 0x009DB}, {0x009DE, 0x009DE}, {0x009E4, 0x009E5}, {0x009FF, 0x00A00}, {0x00A04, 0x00A04}, {0x00A0B, 0x00A0E}, {0x00A11, 0x00A12}, {0x00A29, 0x00A29}, {0x00A31, 0x00A31}, {0x00A34, 0x00A34}, {0x00A37, 0x00A37}, {0x00A3A, 0x00A3B}, {0x00A3D, 0x00A3D}, {0x00A43, 0x00A46}, {0x00A49, 0x00A4A}, {0x00A4E, 0x00A50}, {0x00A52, 0x00A58}, {0x00A5D, 0x00A5D}, {0x00A5F, 0x00A65}, {0x00A77, 0x00A80}, {0x00A84, 0x00A84}, {0x00A8E, 0x00A8E}, {0x00A92, 0x00A92}, {0x00AA9, 0x00AA9}, {0x00AB1, 0x00AB1}, {0x00AB4, 0x00AB4}, {0x00ABA, 0x00ABB}, {0x00AC6, 0x00AC6}, {0x00ACA, 0x00ACA}, {0x00ACE, 0x00ACF}, {0x00AD1, 0x00ADF}, {0x00AE4, 0x00AE5}, {0x00AF2, 0x00AF8}, {0x00B00, 0x00B00}, {0x00B04, 0x00B04}, {0x00B0D, 0x00B0E}, {0x00B11, 0x00B12}, {0x00B29, 0x00B29}, {0x00B31, 0x00B31}, {0x00B34, 0x00B34}, {0x00B3A, 0x00B3B}, {0x00B45, 0x00B46}, {0x00B49, 0x00B4A}, {0x00B4E, 0x00B54}, {0x00B58, 0x00B5B}, {0x00B5E, 0x00B5E}, {0x00B64, 0x00B65}, {0x00B78, 0x00B81}, {0x00B84, 0x00B84}, {0x00B8B, 0x00B8D}, {0x00B91, 0x00B91}, {0x00B96, 0x00B98}, {0x00B9B, 0x00B9B}, {0x00B9D, 0x00B9D}, {0x00BA0, 0x00BA2}, {0x00BA5, 0x00BA7}, {0x00BAB, 0x00BAD}, {0x00BBA, 0x00BBD}, {0x00BC3, 0x00BC5}, {0x00BC9, 0x00BC9}, {0x00BCE, 0x00BCF}, {0x00BD1, 0x00BD6}, {0x00BD8, 0x00BE5}, {0x00BFB, 0x00BFF}, {0x00C0D, 0x00C0D}, {0x00C11, 0x00C11}, {0x00C29, 0x00C29}, {0x00C3A, 0x00C3B}, {0x00C45, 0x00C45}, {0x00C49, 0x00C49}, {0x00C4E, 0x00C54}, {0x00C57, 0x00C57}, {0x00C5B, 0x00C5B}, {0x00C5E, 0x00C5F}, {0x00C64, 0x00C65}, {0x00C70, 0x00C76}, {0x00C8D, 0x00C8D}, {0x00C91, 0x00C91}, {0x00CA9, 0x00CA9}, {0x00CB4, 0x00CB4}, {0x00CBA, 0x00CBB}, {0x00CC5, 0x00CC5}, {0x00CC9, 0x00CC9}, {0x00CCE, 0x00CD4}, {0x00CD7, 0x00CDB}, {0x00CDF, 0x00CDF}, {0x00CE4, 0x00CE5}, {0x00CF0, 0x00CF0}, {0x00CF4, 0x00CFF}, {0x00D0D, 0x00D0D}, {0x00D11, 0x00D11}, {0x00D45, 0x00D45}, {0x00D49, 0x00D49}, {0x00D50, 0x00D53}, {0x00D64, 0x00D65}, {0x00D80, 0x00D80}, {0x00D84, 0x00D84}, {0x00D97, 0x00D99}, {0x00DB2, 0x00DB2}, {0x00DBC, 0x00DBC}, {0x00DBE, 0x00DBF}, {0x00DC7, 0x00DC9}, {0x00DCB, 0x00DCE}, {0x00DD5, 0x00DD5}, {0x00DD7, 0x00DD7}, {0x00DE0, 0x00DE5}, {0x00DF0, 0x00DF1}, {0x00DF5, 0x00E00}, {0x00E3B, 0x00E3E}, {0x00E5C, 0x00E80}, {0x00E83, 0x00E83}, {0x00E85, 0x00E85}, {0x00E8B, 0x00E8B}, {0x00EA4, 0x00EA4}, {0x00EA6, 0x00EA6}, {0x00EBE, 0x00EBF}, {0x00EC5, 0x00EC5}, {0x00EC7, 0x00EC7}, {0x00ECF, 0x00ECF}, {0x00EDA, 0x00EDB}, {0x00EE0, 0x00EFF}, {0x00F48, 0x00F48}, {0x00F6D, 0x00F70}, {0x00F98, 0x00F98}, {0x00FBD, 0x00FBD}, {0x00FCD, 0x00FCD}, {0x00FDB, 0x00FFF}, {0x010C6, 0x010C6}, {0x010C8, 0x010CC}, {0x010CE, 0x010CF}, {0x01249, 0x01249}, {0x0124E, 0x0124F}, {0x01257, 0x01257}, {0x01259, 0x01259}, {0x0125E, 0x0125F}, {0x01289, 0x01289}, {0x0128E, 0x0128F}, {0x012B1, 0x012B1}, {0x012B6, 0x012B7}, {0x012BF, 0x012BF}, {0x012C1, 0x012C1}, {0x012C6, 0x012C7}, {0x012D7, 0x012D7}, {0x01311, 0x01311}, {0x01316, 0x01317}, {0x0135B, 0x0135C}, {0x0137D, 0x0137F}, {0x0139A, 0x0139F}, {0x013F6, 0x013F7}, {0x013FE, 0x013FF}, {0x0169D, 0x0169F}, {0x016F9, 0x016FF}, {0x01716, 0x0171E}, {0x01737, 0x0173F}, {0x01754, 0x0175F}, {0x0176D, 0x0176D}, {0x01771, 0x01771}, {0x01774, 0x0177F}, {0x017DE, 0x017DF}, {0x017EA, 0x017EF}, {0x017FA, 0x017FF}, {0x0181A, 0x0181F}, {0x01879, 0x0187F}, {0x018AB, 0x018AF}, {0x018F6, 0x018FF}, {0x0191F, 0x0191F}, {0x0192C, 0x0192F}, {0x0193C, 0x0193F}, {0x01941, 0x01943}, {0x0196E, 0x0196F}, {0x01975, 0x0197F}, {0x019AC, 0x019AF}, {0x019CA, 0x019CF}, {0x019DB, 0x019DD}, {0x01A1C, 0x01A1D}, {0x01A5F, 0x01A5F}, {0x01A7D, 0x01A7E}, {0x01A8A, 0x01A8F}, {0x01A9A, 0x01A9F}, {0x01AAE, 0x01AAF}, {0x01ADE, 0x01ADF}, {0x01AEC, 0x01AFF}, {0x01B4D, 0x01B4D}, {0x01BF4, 0x01BFB}, {0x01C38, 0x01C3A}, {0x01C4A, 0x01C4C}, {0x01C8B, 0x01C8F}, {0x01CBB, 0x01CBC}, {0x01CC8, 0x01CCF}, {0x01CFB, 0x01CFF}, {0x01F16, 0x01F17}, {0x01F1E, 0x01F1F}, {0x01F46, 0x01F47}, {0x01F4E, 0x01F4F}, {0x01F58, 0x01F58}, {0x01F5A, 0x01F5A}, {0x01F5C, 0x01F5C}, {0x01F5E, 0x01F5E}, {0x01F7E, 0x01F7F}, {0x01FB5, 0x01FB5}, {0x01FC5, 0x01FC5}, {0x01FD4, 0x01FD5}, {0x01FDC, 0x01FDC}, {0x01FF0, 0x01FF1}, {0x01FF5, 0x01FF5}, {0x01FFF, 0x01FFF}, {0x02065, 0x02065}, {0x02072, 0x02073}, {0x0208F, 0x0208F}, {0x0209D, 0x0209F}, {0x020C2, 0x020CF}, {0x020F1, 0x020FF}, {0x0218C, 0x0218F}, {0x0242A, 0x0243F}, {0x0244B, 0x0245F}, {0x02B74, 0x02B75}, {0x02CF4, 0x02CF8}, {0x02D26, 0x02D26}, {0x02D28, 0x02D2C}, {0x02D2E, 0x02D2F}, {0x02D68, 0x02D6E}, {0x02D71, 0x02D7E}, {0x02D97, 0x02D9F}, {0x02DA7, 0x02DA7}, {0x02DAF, 0x02DAF}, {0x02DB7, 0x02DB7}, {0x02DBF, 0x02DBF}, {0x02DC7, 0x02DC7}, {0x02DCF, 0x02DCF}, {0x02DD7, 0x02DD7}, {0x02DDF, 0x02DDF}, {0x02E5E, 0x02E7F}, {0x02E9A, 0x02E9A}, {0x02EF4, 0x02EFF}, {0x02FD6, 0x02FEF}, {0x03040, 0x03040}, {0x03097, 0x03098}, {0x03100, 0x03104}, {0x03130, 0x03130}, {0x0318F, 0x0318F}, {0x031E6, 0x031EE}, {0x0321F, 0x0321F}, {0x03401, 0x04DBE}, {0x04E01, 0x09FFE}, {0x0A48D, 0x0A48F}, {0x0A4C7, 0x0A4CF}, {0x0A62C, 0x0A63F}, {0x0A6F8, 0x0A6FF}, {0x0A7DD, 0x0A7F0}, {0x0A82D, 0x0A82F}, {0x0A83A, 0x0A83F}, {0x0A878, 0x0A87F}, {0x0A8C6, 0x0A8CD}, {0x0A8DA, 0x0A8DF}, {0x0A954, 0x0A95E}, {0x0A97D, 0x0A97F}, {0x0A9CE, 0x0A9CE}, {0x0A9DA, 0x0A9DD}, {0x0A9FF, 0x0A9FF}, {0x0AA37, 0x0AA3F}, {0x0AA4E, 0x0AA4F}, {0x0AA5A, 0x0AA5B}, {0x0AAC3, 0x0AADA}, {0x0AAF7, 0x0AB00}, {0x0AB07, 0x0AB08}, {0x0AB0F, 0x0AB10}, {0x0AB17, 0x0AB1F}, {0x0AB27, 0x0AB27}, {0x0AB2F, 0x0AB2F}, {0x0AB6C, 0x0AB6F}, {0x0ABEE, 0x0ABEF}, {0x0ABFA, 0x0ABFF}, {0x0AC01, 0x0D7A2}, {0x0D7A4, 0x0D7AF}, {0x0D7C7, 0x0D7CA}, {0x0D7FC, 0x0D7FF}, {0x0FA6E, 0x0FA6F}, {0x0FADA, 0x0FAFF}, {0x0FB07, 0x0FB12}, {0x0FB18, 0x0FB1C}, {0x0FB37, 0x0FB37}, {0x0FB3D, 0x0FB3D}, {0x0FB3F, 0x0FB3F}, {0x0FB42, 0x0FB42}, {0x0FB45, 0x0FB45}, {0x0FE1A, 0x0FE1F}, {0x0FE53, 0x0FE53}, {0x0FE67, 0x0FE67}, {0x0FE6C, 0x0FE6F}, {0x0FE75, 0x0FE75}, {0x0FEFD, 0x0FEFE}, {0x0FF00, 0x0FF00}, {0x0FFBF, 0x0FFC1}, {0x0FFC8, 0x0FFC9}, {0x0FFD0, 0x0FFD1}, {0x0FFD8, 0x0FFD9}, {0x0FFDD, 0x0FFDF}, {0x0FFE7, 0x0FFE7}, {0x0FFEF, 0x0FFF8}, {0x1000C, 0x1000C}, {0x10027, 0x10027}, {0x1003B, 0x1003B}, {0x1003E, 0x1003E}, {0x1004E, 0x1004F}, {0x1005E, 0x1007F}, {0x100FB, 0x100FF}, {0x10103, 0x10106}, {0x10134, 0x10136}, {0x1018F, 0x1018F}, {0x1019D, 0x1019F}, {0x101A1, 0x101CF}, {0x101FE, 0x1027F}, {0x1029D, 0x1029F}, {0x102D1, 0x102DF}, {0x102FC, 0x102FF}, {0x10324, 0x1032C}, {0x1034B, 0x1034F}, {0x1037B, 0x1037F}, {0x1039E, 0x1039E}, {0x103C4, 0x103C7}, {0x103D6, 0x103FF}, {0x1049E, 0x1049F}, {0x104AA, 0x104AF}, {0x104D4, 0x104D7}, {0x104FC, 0x104FF}, {0x10528, 0x1052F}, {0x10564, 0x1056E}, {0x1057B, 0x1057B}, {0x1058B, 0x1058B}, {0x10593, 0x10593}, {0x10596, 0x10596}, {0x105A2, 0x105A2}, {0x105B2, 0x105B2}, {0x105BA, 0x105BA}, {0x105BD, 0x105BF}, {0x105F4, 0x105FF}, {0x10737, 0x1073F}, {0x10756, 0x1075F}, {0x10768, 0x1077F}, {0x10786, 0x10786}, {0x107B1, 0x107B1}, {0x107BB, 0x107FF}, {0x10806, 0x10807}, {0x10809, 0x10809}, {0x10836, 0x10836}, {0x10839, 0x1083B}, {0x1083D, 0x1083E}, {0x10856, 0x10856}, {0x1089F, 0x108A6}, {0x108B0, 0x108DF}, {0x108F3, 0x108F3}, {0x108F6, 0x108FA}, {0x1091C, 0x1091E}, {0x1093A, 0x1093E}, {0x1095A, 0x1097F}, {0x109B8, 0x109BB}, {0x109D0, 0x109D1}, {0x10A04, 0x10A04}, {0x10A07, 0x10A0B}, {0x10A14, 0x10A14}, {0x10A18, 0x10A18}, {0x10A36, 0x10A37}, {0x10A3B, 0x10A3E}, {0x10A49, 0x10A4F}, {0x10A59, 0x10A5F}, {0x10AA0, 0x10ABF}, {0x10AE7, 0x10AEA}, {0x10AF7, 0x10AFF}, {0x10B36, 0x10B38}, {0x10B56, 0x10B57}, {0x10B73, 0x10B77}, {0x10B92, 0x10B98}, {0x10B9D, 0x10BA8}, {0x10BB0, 0x10BFF}, {0x10C49, 0x10C7F}, {0x10CB3, 0x10CBF}, {0x10CF3, 0x10CF9}, {0x10D28, 0x10D2F}, {0x10D3A, 0x10D3F}, {0x10D66, 0x10D68}, {0x10D86, 0x10D8D}, {0x10D90, 0x10E5F}, {0x10E7F, 0x10E7F}, {0x10EAA, 0x10EAA}, {0x10EAE, 0x10EAF}, {0x10EB2, 0x10EC1}, {0x10EC8, 0x10ECF}, {0x10ED9, 0x10EF9}, {0x10F28, 0x10F2F}, {0x10F5A, 0x10F6F}, {0x10F8A, 0x10FAF}, {0x10FCC, 0x10FDF}, {0x10FF7, 0x10FFF}, {0x1104E, 0x11051}, {0x11076, 0x1107E}, {0x110C3, 0x110CC}, {0x110CE, 0x110CF}, {0x110E9, 0x110EF}, {0x110FA, 0x110FF}, {0x11135, 0x11135}, {0x11148, 0x1114F}, {0x11177, 0x1117F}, {0x111E0, 0x111E0}, {0x111F5, 0x111FF}, {0x11212, 0x11212}, {0x11242, 0x1127F}, {0x11287, 0x11287}, {0x11289, 0x11289}, {0x1128E, 0x1128E}, {0x1129E, 0x1129E}, {0x112AA, 0x112AF}, {0x112EB, 0x112EF}, {0x112FA, 0x112FF}, {0x11304, 0x11304}, {0x1130D, 0x1130E}, {0x11311, 0x11312}, {0x11329, 0x11329}, {0x11331, 0x11331}, {0x11334, 0x11334}, {0x1133A, 0x1133A}, {0x11345, 0x11346}, {0x11349, 0x1134A}, {0x1134E, 0x1134F}, {0x11351, 0x11356}, {0x11358, 0x1135C}, {0x11364, 0x11365}, {0x1136D, 0x1136F}, {0x11375, 0x1137F}, {0x1138A, 0x1138A}, {0x1138C, 0x1138D}, {0x1138F, 0x1138F}, {0x113B6, 0x113B6}, {0x113C1, 0x113C1}, {0x113C3, 0x113C4}, {0x113C6, 0x113C6}, {0x113CB, 0x113CB}, {0x113D6, 0x113D6}, {0x113D9, 0x113E0}, {0x113E3, 0x113FF}, {0x1145C, 0x1145C}, {0x11462, 0x1147F}, {0x114C8, 0x114CF}, {0x114DA, 0x1157F}, {0x115B6, 0x115B7}, {0x115DE, 0x115FF}, {0x11645, 0x1164F}, {0x1165A, 0x1165F}, {0x1166D, 0x1167F}, {0x116BA, 0x116BF}, {0x116CA, 0x116CF}, {0x116E4, 0x116FF}, {0x1171B, 0x1171C}, {0x1172C, 0x1172F}, {0x11747, 0x117FF}, {0x1183C, 0x1189F}, {0x118F3, 0x118FE}, {0x11907, 0x11908}, {0x1190A, 0x1190B}, {0x11914, 0x11914}, {0x11917, 0x11917}, {0x11936, 0x11936}, {0x11939, 0x1193A}, {0x11947, 0x1194F}, {0x1195A, 0x1199F}, {0x119A8, 0x119A9}, {0x119D8, 0x119D9}, {0x119E5, 0x119FF}, {0x11A48, 0x11A4F}, {0x11AA3, 0x11AAF}, {0x11AF9, 0x11AFF}, {0x11B0A, 0x11B5F}, {0x11B68, 0x11BBF}, {0x11BE2, 0x11BEF}, {0x11BFA, 0x11BFF}, {0x11C09, 0x11C09}, {0x11C37, 0x11C37}, {0x11C46, 0x11C4F}, {0x11C6D, 0x11C6F}, {0x11C90, 0x11C91}, {0x11CA8, 0x11CA8}, {0x11CB7, 0x11CFF}, {0x11D07, 0x11D07}, {0x11D0A, 0x11D0A}, {0x11D37, 0x11D39}, {0x11D3B, 0x11D3B}, {0x11D3E, 0x11D3E}, {0x11D48, 0x11D4F}, {0x11D5A, 0x11D5F}, {0x11D66, 0x11D66}, {0x11D69, 0x11D69}, {0x11D8F, 0x11D8F}, {0x11D92, 0x11D92}, {0x11D99, 0x11D9F}, {0x11DAA, 0x11DAF}, {0x11DDC, 0x11DDF}, {0x11DEA, 0x11EDF}, {0x11EF9, 0x11EFF}, {0x11F11, 0x11F11}, {0x11F3B, 0x11F3D}, {0x11F5B, 0x11FAF}, {0x11FB1, 0x11FBF}, {0x11FF2, 0x11FFE}, {0x1239A, 0x123FF}, {0x1246F, 0x1246F}, {0x12475, 0x1247F}, {0x12544, 0x12F8F}, {0x12FF3, 0x12FFF}, {0x13456, 0x1345F}, {0x143FB, 0x143FF}, {0x14647, 0x160FF}, {0x1613A, 0x167FF}, {0x16A39, 0x16A3F}, {0x16A5F, 0x16A5F}, {0x16A6A, 0x16A6D}, {0x16ABF, 0x16ABF}, {0x16ACA, 0x16ACF}, {0x16AEE, 0x16AEF}, {0x16AF6, 0x16AFF}, {0x16B46, 0x16B4F}, {0x16B5A, 0x16B5A}, {0x16B62, 0x16B62}, {0x16B78, 0x16B7C}, {0x16B90, 0x16D3F}, {0x16D7A, 0x16E3F}, {0x16E9B, 0x16E9F}, {0x16EB9, 0x16EBA}, {0x16ED4, 0x16EFF}, {0x16F4B, 0x16F4E}, {0x16F88, 0x16F8E}, {0x16FA0, 0x16FDF}, {0x16FE5, 0x16FEF}, {0x16FF7, 0x16FFF}, {0x17001, 0x187FE}, {0x18CD6, 0x18CFE}, {0x18D01, 0x18D1D}, {0x18D1F, 0x18D7F}, {0x18DF3, 0x1AFEF}, {0x1AFF4, 0x1AFF4}, {0x1AFFC, 0x1AFFC}, {0x1AFFF, 0x1AFFF}, {0x1B123, 0x1B131}, {0x1B133, 0x1B14F}, {0x1B153, 0x1B154}, {0x1B156, 0x1B163}, {0x1B168, 0x1B16F}, {0x1B2FC, 0x1BBFF}, {0x1BC6B, 0x1BC6F}, {0x1BC7D, 0x1BC7F}, {0x1BC89, 0x1BC8F}, {0x1BC9A, 0x1BC9B}, {0x1BCA4, 0x1CBFF}, {0x1CCFD, 0x1CCFF}, {0x1CEB4, 0x1CEB9}, {0x1CED1, 0x1CEDF}, {0x1CEF1, 0x1CEFF}, {0x1CF2E, 0x1CF2F}, {0x1CF47, 0x1CF4F}, {0x1CFC4, 0x1CFFF}, {0x1D0F6, 0x1D0FF}, {0x1D127, 0x1D128}, {0x1D1EB, 0x1D1FF}, {0x1D246, 0x1D2BF}, {0x1D2D4, 0x1D2DF}, {0x1D2F4, 0x1D2FF}, {0x1D357, 0x1D35F}, {0x1D379, 0x1D3FF}, {0x1D455, 0x1D455}, {0x1D49D, 0x1D49D}, {0x1D4A0, 0x1D4A1}, {0x1D4A3, 0x1D4A4}, {0x1D4A7, 0x1D4A8}, {0x1D4AD, 0x1D4AD}, {0x1D4BA, 0x1D4BA}, {0x1D4BC, 0x1D4BC}, {0x1D4C4, 0x1D4C4}, {0x1D506, 0x1D506}, {0x1D50B, 0x1D50C}, {0x1D515, 0x1D515}, {0x1D51D, 0x1D51D}, {0x1D53A, 0x1D53A}, {0x1D53F, 0x1D53F}, {0x1D545, 0x1D545}, {0x1D547, 0x1D549}, {0x1D551, 0x1D551}, {0x1D6A6, 0x1D6A7}, {0x1D7CC, 0x1D7CD}, {0x1DA8C, 0x1DA9A}, {0x1DAA0, 0x1DAA0}, {0x1DAB0, 0x1DEFF}, {0x1DF1F, 0x1DF24}, {0x1DF2B, 0x1DFFF}, {0x1E007, 0x1E007}, {0x1E019, 0x1E01A}, {0x1E022, 0x1E022}, {0x1E025, 0x1E025}, {0x1E02B, 0x1E02F}, {0x1E06E, 0x1E08E}, {0x1E090, 0x1E0FF}, {0x1E12D, 0x1E12F}, {0x1E13E, 0x1E13F}, {0x1E14A, 0x1E14D}, {0x1E150, 0x1E28F}, {0x1E2AF, 0x1E2BF}, {0x1E2FA, 0x1E2FE}, {0x1E300, 0x1E4CF}, {0x1E4FA, 0x1E5CF}, {0x1E5FB, 0x1E5FE}, {0x1E600, 0x1E6BF}, {0x1E6DF, 0x1E6DF}, {0x1E6F6, 0x1E6FD}, {0x1E700, 0x1E7DF}, {0x1E7E7, 0x1E7E7}, {0x1E7EC, 0x1E7EC}, {0x1E7EF, 0x1E7EF}, {0x1E7FF, 0x1E7FF}, {0x1E8C5, 0x1E8C6}, {0x1E8D7, 0x1E8FF}, {0x1E94C, 0x1E94F}, {0x1E95A, 0x1E95D}, {0x1E960, 0x1EC70}, {0x1ECB5, 0x1ED00}, {0x1ED3E, 0x1EDFF}, {0x1EE04, 0x1EE04}, {0x1EE20, 0x1EE20}, {0x1EE23, 0x1EE23}, {0x1EE25, 0x1EE26}, {0x1EE28, 0x1EE28}, {0x1EE33, 0x1EE33}, {0x1EE38, 0x1EE38}, {0x1EE3A, 0x1EE3A}, {0x1EE3C, 0x1EE41}, {0x1EE43, 0x1EE46}, {0x1EE48, 0x1EE48}, {0x1EE4A, 0x1EE4A}, {0x1EE4C, 0x1EE4C}, {0x1EE50, 0x1EE50}, {0x1EE53, 0x1EE53}, {0x1EE55, 0x1EE56}, {0x1EE58, 0x1EE58}, {0x1EE5A, 0x1EE5A}, {0x1EE5C, 0x1EE5C}, {0x1EE5E, 0x1EE5E}, {0x1EE60, 0x1EE60}, {0x1EE63, 0x1EE63}, {0x1EE65, 0x1EE66}, {0x1EE6B, 0x1EE6B}, {0x1EE73, 0x1EE73}, {0x1EE78, 0x1EE78}, {0x1EE7D, 0x1EE7D}, {0x1EE7F, 0x1EE7F}, {0x1EE8A, 0x1EE8A}, {0x1EE9C, 0x1EEA0}, {0x1EEA4, 0x1EEA4}, {0x1EEAA, 0x1EEAA}, {0x1EEBC, 0x1EEEF}, {0x1EEF2, 0x1EFFF}, {0x1F02C, 0x1F02F}, {0x1F094, 0x1F09F}, {0x1F0AF, 0x1F0B0}, {0x1F0C0, 0x1F0C0}, {0x1F0D0, 0x1F0D0}, {0x1F0F6, 0x1F0FF}, {0x1F1AE, 0x1F1E5}, {0x1F203, 0x1F20F}, {0x1F23C, 0x1F23F}, {0x1F249, 0x1F24F}, {0x1F252, 0x1F25F}, {0x1F266, 0x1F2FF}, {0x1F6D9, 0x1F6DB}, {0x1F6ED, 0x1F6EF}, {0x1F6FD, 0x1F6FF}, {0x1F7DA, 0x1F7DF}, {0x1F7EC, 0x1F7EF}, {0x1F7F1, 0x1F7FF}, {0x1F80C, 0x1F80F}, {0x1F848, 0x1F84F}, {0x1F85A, 0x1F85F}, {0x1F888, 0x1F88F}, {0x1F8AE, 0x1F8AF}, {0x1F8BC, 0x1F8BF}, {0x1F8C2, 0x1F8CF}, {0x1F8D9, 0x1F8FF}, {0x1FA58, 0x1FA5F}, {0x1FA6E, 0x1FA6F}, {0x1FA7D, 0x1FA7F}, {0x1FA8B, 0x1FA8D}, {0x1FAC7, 0x1FAC7}, {0x1FAC9, 0x1FACC}, {0x1FADD, 0x1FADE}, {0x1FAEB, 0x1FAEE}, {0x1FAF9, 0x1FAFF}, {0x1FB93, 0x1FB93}, {0x1FBFB, 0x1FFFD}, {0x20001, 0x2A6DE}, {0x2A6E0, 0x2A6FF}, {0x2A701, 0x2B73E}, {0x2B741, 0x2B81C}, {0x2B81E, 0x2B81F}, {0x2B821, 0x2CEAC}, {0x2CEAE, 0x2CEAF}, {0x2CEB1, 0x2EBDF}, {0x2EBE1, 0x2EBEF}, {0x2EBF1, 0x2EE5C}, {0x2EE5E, 0x2F7FF}, {0x2FA1E, 0x2FFFD}, {0x30001, 0x31349}, {0x3134B, 0x3134F}, {0x31351, 0x323AE}, {0x323B1, 0x33478}, {0x3347A, 0x3FFFD}, {0x40000, 0x4FFFD}, {0x50000, 0x5FFFD}, {0x60000, 0x6FFFD}, {0x70000, 0x7FFFD}, {0x80000, 0x8FFFD}, {0x90000, 0x9FFFD}, {0xA0000, 0xAFFFD}, {0xB0000, 0xBFFFD}, {0xC0000, 0xCFFFD}, {0xD0000, 0xDFFFD}, {0xE0000, 0xE0000}, {0xE0002, 0xE001F}, {0xE0080, 0xE00FF}, {0xE01F0, 0xEFFFD} }; /* Non-characters. */ static const struct widechar_range widechar_nonchar_table[] = { {0x0FDD0, 0x0FDEF}, {0x0FFFE, 0x0FFFF}, {0x1FFFE, 0x1FFFF}, {0x2FFFE, 0x2FFFF}, {0x3FFFE, 0x3FFFF}, {0x4FFFE, 0x4FFFF}, {0x5FFFE, 0x5FFFF}, {0x6FFFE, 0x6FFFF}, {0x7FFFE, 0x7FFFF}, {0x8FFFE, 0x8FFFF}, {0x9FFFE, 0x9FFFF}, {0xAFFFE, 0xAFFFF}, {0xBFFFE, 0xBFFFF}, {0xCFFFE, 0xCFFFF}, {0xDFFFE, 0xDFFFF}, {0xEFFFE, 0xEFFFF}, {0xFFFFE, 0xFFFFF}, {0x10FFFE, 0x10FFFF} }; /* Characters that were widened from width 1 to 2 in Unicode 9. */ static const struct widechar_range widechar_widened_table[] = { {0x0231A, 0x0231B}, {0x023E9, 0x023EC}, {0x023F0, 0x023F0}, {0x023F3, 0x023F3}, {0x025FD, 0x025FE}, {0x02614, 0x02615}, {0x02648, 0x02653}, {0x0267F, 0x0267F}, {0x02693, 0x02693}, {0x026A1, 0x026A1}, {0x026AA, 0x026AB}, {0x026BD, 0x026BE}, {0x026C4, 0x026C5}, {0x026CE, 0x026CE}, {0x026D4, 0x026D4}, {0x026EA, 0x026EA}, {0x026F2, 0x026F3}, {0x026F5, 0x026F5}, {0x026FA, 0x026FA}, {0x026FD, 0x026FD}, {0x02705, 0x02705}, {0x0270A, 0x0270B}, {0x02728, 0x02728}, {0x0274C, 0x0274C}, {0x0274E, 0x0274E}, {0x02753, 0x02755}, {0x02757, 0x02757}, {0x02795, 0x02797}, {0x027B0, 0x027B0}, {0x027BF, 0x027BF}, {0x02B1B, 0x02B1C}, {0x02B50, 0x02B50}, {0x02B55, 0x02B55}, {0x1F004, 0x1F004}, {0x1F0CF, 0x1F0CF}, {0x1F18E, 0x1F18E}, {0x1F191, 0x1F19A}, {0x1F201, 0x1F201}, {0x1F21A, 0x1F21A}, {0x1F22F, 0x1F22F}, {0x1F232, 0x1F236}, {0x1F238, 0x1F23A}, {0x1F250, 0x1F251}, {0x1F300, 0x1F320}, {0x1F32D, 0x1F335}, {0x1F337, 0x1F37C}, {0x1F37E, 0x1F393}, {0x1F3A0, 0x1F3CA}, {0x1F3CF, 0x1F3D3}, {0x1F3E0, 0x1F3F0}, {0x1F3F4, 0x1F3F4}, {0x1F3F8, 0x1F43E}, {0x1F440, 0x1F440}, {0x1F442, 0x1F4FC}, {0x1F4FF, 0x1F53D}, {0x1F54B, 0x1F54E}, {0x1F550, 0x1F567}, {0x1F595, 0x1F596}, {0x1F5FB, 0x1F64F}, {0x1F680, 0x1F6C5}, {0x1F6CC, 0x1F6CC}, {0x1F6D0, 0x1F6D0}, {0x1F6EB, 0x1F6EC}, {0x1F910, 0x1F918}, {0x1F980, 0x1F984}, {0x1F9C0, 0x1F9C0} }; static inline bool widechar_in_table(const struct widechar_range* arr, size_t len, uint32_t c) { size_t lo=0; size_t hi=len; if(c < arr[0].lo) return(0); if(c > arr[len-1].hi) return(0); while(1) { size_t mid = ((hi-lo)/2)+lo; if( (c >= arr[mid].lo) && (c <= arr[mid].hi)) { return(1); } if(mid == lo) return(0); if (c < arr[mid].lo) { hi = mid; } else { lo = mid; } } return(0); } /* Return the width of character c, or a special negative value. */ int widechar_wcwidth(uint32_t c) { if (widechar_in_table(widechar_ascii_table, widechar_ARRAY_SIZE(widechar_ascii_table), c)) return 1; if (widechar_in_table(widechar_private_table, widechar_ARRAY_SIZE(widechar_private_table), c)) return widechar_private_use; if (widechar_in_table(widechar_nonprint_table, widechar_ARRAY_SIZE(widechar_nonprint_table), c)) return widechar_nonprint; if (widechar_in_table(widechar_nonchar_table, widechar_ARRAY_SIZE(widechar_nonchar_table), c)) return widechar_non_character; if (widechar_in_table(widechar_combining_table, widechar_ARRAY_SIZE(widechar_combining_table), c)) return widechar_combining; if (widechar_in_table(widechar_combiningletters_table, widechar_ARRAY_SIZE(widechar_combiningletters_table), c)) return widechar_combining; if (widechar_in_table(widechar_doublewide_table, widechar_ARRAY_SIZE(widechar_doublewide_table), c)) return 2; if (widechar_in_table(widechar_ambiguous_table, widechar_ARRAY_SIZE(widechar_ambiguous_table), c)) return widechar_ambiguous; if (widechar_in_table(widechar_unassigned_table, widechar_ARRAY_SIZE(widechar_unassigned_table), c)) return widechar_unassigned; if (widechar_in_table(widechar_widened_table, widechar_ARRAY_SIZE(widechar_widened_table), c)) return widechar_widened_in_9; return 1; } #endif // WIDECHAR_WIDTH_H ================================================ FILE: src/3rdparty/yyjson/repo.json ================================================ { "home": "https://github.com/ibireme/yyjson", "license": "MIT ( embed in source )", "version": "0.12.0", "author": "ibireme" } ================================================ FILE: src/3rdparty/yyjson/yyjson.c ================================================ /*============================================================================== Copyright (c) 2020 YaoYuan Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. *============================================================================*/ #include "yyjson.h" #include /* for `HUGE_VAL/INFINIY/NAN` macros, no libm required */ /*============================================================================== * MARK: - Warning Suppress (Private) *============================================================================*/ #if defined(__clang__) # pragma clang diagnostic ignored "-Wunused-function" # pragma clang diagnostic ignored "-Wunused-parameter" # pragma clang diagnostic ignored "-Wunused-label" # pragma clang diagnostic ignored "-Wunused-macros" # pragma clang diagnostic ignored "-Wunused-variable" #elif defined(__GNUC__) # pragma GCC diagnostic ignored "-Wunused-function" # pragma GCC diagnostic ignored "-Wunused-parameter" # pragma GCC diagnostic ignored "-Wunused-label" # pragma GCC diagnostic ignored "-Wunused-macros" # pragma GCC diagnostic ignored "-Wunused-variable" #elif defined(_MSC_VER) # pragma warning(disable:4100) /* unreferenced formal parameter */ # pragma warning(disable:4101) /* unreferenced variable */ # pragma warning(disable:4102) /* unreferenced label */ # pragma warning(disable:4127) /* conditional expression is constant */ # pragma warning(disable:4706) /* assignment within conditional expression */ #endif /*============================================================================== * MARK: - Version (Public) *============================================================================*/ uint32_t yyjson_version(void) { return YYJSON_VERSION_HEX; } /*============================================================================== * MARK: - Flags (Private) *============================================================================*/ /* msvc intrinsic */ #if YYJSON_MSC_VER >= 1400 # include # if defined(_M_AMD64) || defined(_M_ARM64) # define MSC_HAS_BIT_SCAN_64 1 # pragma intrinsic(_BitScanForward64) # pragma intrinsic(_BitScanReverse64) # else # define MSC_HAS_BIT_SCAN_64 0 # endif # if defined(_M_AMD64) || defined(_M_ARM64) || \ defined(_M_IX86) || defined(_M_ARM) # define MSC_HAS_BIT_SCAN 1 # pragma intrinsic(_BitScanForward) # pragma intrinsic(_BitScanReverse) # else # define MSC_HAS_BIT_SCAN 0 # endif # if defined(_M_AMD64) # define MSC_HAS_UMUL128 1 # pragma intrinsic(_umul128) # else # define MSC_HAS_UMUL128 0 # endif #else # define MSC_HAS_BIT_SCAN_64 0 # define MSC_HAS_BIT_SCAN 0 # define MSC_HAS_UMUL128 0 #endif /* gcc builtin */ #if yyjson_has_builtin(__builtin_clzll) || yyjson_gcc_available(3, 4, 0) # define GCC_HAS_CLZLL 1 #else # define GCC_HAS_CLZLL 0 #endif #if yyjson_has_builtin(__builtin_ctzll) || yyjson_gcc_available(3, 4, 0) # define GCC_HAS_CTZLL 1 #else # define GCC_HAS_CTZLL 0 #endif /* int128 type */ #if defined(__SIZEOF_INT128__) && (__SIZEOF_INT128__ == 16) && \ (defined(__GNUC__) || defined(__clang__) || defined(__INTEL_COMPILER)) # define YYJSON_HAS_INT128 1 #else # define YYJSON_HAS_INT128 0 #endif /* IEEE 754 floating-point binary representation */ #if defined(__STDC_IEC_559__) || defined(__STDC_IEC_60559_BFP__) # define YYJSON_HAS_IEEE_754 1 #elif FLT_RADIX == 2 && \ FLT_MANT_DIG == 24 && FLT_DIG == 6 && \ FLT_MIN_EXP == -125 && FLT_MAX_EXP == 128 && \ FLT_MIN_10_EXP == -37 && FLT_MAX_10_EXP == 38 && \ DBL_MANT_DIG == 53 && DBL_DIG == 15 && \ DBL_MIN_EXP == -1021 && DBL_MAX_EXP == 1024 && \ DBL_MIN_10_EXP == -307 && DBL_MAX_10_EXP == 308 # define YYJSON_HAS_IEEE_754 1 #else # define YYJSON_HAS_IEEE_754 0 # undef YYJSON_DISABLE_FAST_FP_CONV # define YYJSON_DISABLE_FAST_FP_CONV 1 #endif /* Correct rounding in double number computations. On the x86 architecture, some compilers may use x87 FPU instructions for floating-point arithmetic. The x87 FPU loads all floating point number as 80-bit double-extended precision internally, then rounds the result to original precision, which may produce inaccurate results. For a more detailed explanation, see the paper: https://arxiv.org/abs/cs/0701192 Here are some examples of double precision calculation error: 2877.0 / 1e6 == 0.002877, but x87 returns 0.0028770000000000002 43683.0 * 1e21 == 4.3683e25, but x87 returns 4.3683000000000004e25 Here are some examples of compiler flags to generate x87 instructions on x86: clang -m32 -mno-sse gcc/icc -m32 -mfpmath=387 msvc /arch:SSE or /arch:IA32 If we are sure that there's no similar error described above, we can define the YYJSON_DOUBLE_MATH_CORRECT as 1 to enable the fast path calculation. This is not an accurate detection, it's just try to avoid the error at compile-time. An accurate detection can be done at run-time: bool is_double_math_correct(void) { volatile double r = 43683.0; r *= 1e21; return r == 4.3683e25; } See also: utils.h in https://github.com/google/double-conversion/ */ #if !defined(FLT_EVAL_METHOD) && defined(__FLT_EVAL_METHOD__) # define FLT_EVAL_METHOD __FLT_EVAL_METHOD__ #endif #if defined(FLT_EVAL_METHOD) && FLT_EVAL_METHOD != 0 && FLT_EVAL_METHOD != 1 # define YYJSON_DOUBLE_MATH_CORRECT 0 #elif defined(i386) || defined(__i386) || defined(__i386__) || \ defined(_X86_) || defined(__X86__) || defined(_M_IX86) || \ defined(__I86__) || defined(__IA32__) || defined(__THW_INTEL) # if (defined(_MSC_VER) && defined(_M_IX86_FP) && _M_IX86_FP == 2) || \ (defined(__SSE2_MATH__) && __SSE2_MATH__) # define YYJSON_DOUBLE_MATH_CORRECT 1 # else # define YYJSON_DOUBLE_MATH_CORRECT 0 # endif #elif defined(__mc68000__) || defined(__pnacl__) || defined(__native_client__) # define YYJSON_DOUBLE_MATH_CORRECT 0 #else # define YYJSON_DOUBLE_MATH_CORRECT 1 #endif /* Detect the endianness at compile-time. YYJSON_ENDIAN == YYJSON_BIG_ENDIAN YYJSON_ENDIAN == YYJSON_LITTLE_ENDIAN */ #define YYJSON_BIG_ENDIAN 4321 #define YYJSON_LITTLE_ENDIAN 1234 #if yyjson_has_include() # include /* POSIX */ #endif #if yyjson_has_include() # include /* Linux */ #elif yyjson_has_include() # include /* BSD, Android */ #elif yyjson_has_include() # include /* BSD, Darwin */ #endif #if defined(BYTE_ORDER) && BYTE_ORDER # if defined(BIG_ENDIAN) && (BYTE_ORDER == BIG_ENDIAN) # define YYJSON_ENDIAN YYJSON_BIG_ENDIAN # elif defined(LITTLE_ENDIAN) && (BYTE_ORDER == LITTLE_ENDIAN) # define YYJSON_ENDIAN YYJSON_LITTLE_ENDIAN # endif #elif defined(__BYTE_ORDER) && __BYTE_ORDER # if defined(__BIG_ENDIAN) && (__BYTE_ORDER == __BIG_ENDIAN) # define YYJSON_ENDIAN YYJSON_BIG_ENDIAN # elif defined(__LITTLE_ENDIAN) && (__BYTE_ORDER == __LITTLE_ENDIAN) # define YYJSON_ENDIAN YYJSON_LITTLE_ENDIAN # endif #elif defined(__BYTE_ORDER__) && __BYTE_ORDER__ # if defined(__ORDER_BIG_ENDIAN__) && \ (__BYTE_ORDER__ == __ORDER_BIG_ENDIAN__) # define YYJSON_ENDIAN YYJSON_BIG_ENDIAN # elif defined(__ORDER_LITTLE_ENDIAN__) && \ (__BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__) # define YYJSON_ENDIAN YYJSON_LITTLE_ENDIAN # endif #elif (defined(__LITTLE_ENDIAN__) && __LITTLE_ENDIAN__ == 1) || \ defined(__i386) || defined(__i386__) || \ defined(_X86_) || defined(__X86__) || \ defined(_M_IX86) || defined(__THW_INTEL__) || \ defined(__x86_64) || defined(__x86_64__) || \ defined(__amd64) || defined(__amd64__) || \ defined(_M_AMD64) || defined(_M_X64) || \ defined(_M_ARM) || defined(_M_ARM64) || \ defined(__ARMEL__) || defined(__THUMBEL__) || defined(__AARCH64EL__) || \ defined(_MIPSEL) || defined(__MIPSEL) || defined(__MIPSEL__) || \ defined(__EMSCRIPTEN__) || defined(__wasm__) || \ defined(__loongarch__) # define YYJSON_ENDIAN YYJSON_LITTLE_ENDIAN #elif (defined(__BIG_ENDIAN__) && __BIG_ENDIAN__ == 1) || \ defined(__ARMEB__) || defined(__THUMBEB__) || defined(__AARCH64EB__) || \ defined(_MIPSEB) || defined(__MIPSEB) || defined(__MIPSEB__) || \ defined(__or1k__) || defined(__OR1K__) # define YYJSON_ENDIAN YYJSON_BIG_ENDIAN #else # define YYJSON_ENDIAN 0 /* unknown endian, detect at run-time */ #endif /* This macro controls how yyjson handles unaligned memory accesses. By default, yyjson uses `memcpy()` for memory copying. This allows the compiler to optimize the code and emit unaligned memory access instructions when supported by the target architecture. However, on some older compilers or architectures where `memcpy()` is not well-optimized and may result in unnecessary function calls, defining this macro as 1 may help. In such cases, yyjson switches to manual byte-by-byte access, which can potentially improve performance. An example of the generated assembly code for ARM can be found here: https://godbolt.org/z/334jjhxPT This flag is already enabled for common architectures in the following code, so manual configuration is usually unnecessary. If unsure, you can check the generated assembly or run benchmarks to make an informed decision. */ #ifndef YYJSON_DISABLE_UNALIGNED_MEMORY_ACCESS # if defined(__ia64) || defined(_IA64) || defined(__IA64__) || \ defined(__ia64__) || defined(_M_IA64) || defined(__itanium__) # define YYJSON_DISABLE_UNALIGNED_MEMORY_ACCESS 1 /* Itanium */ # elif (defined(__arm__) || defined(__arm64__) || defined(__aarch64__)) && \ (defined(__GNUC__) || defined(__clang__)) && \ (!defined(__ARM_FEATURE_UNALIGNED) || !__ARM_FEATURE_UNALIGNED) # define YYJSON_DISABLE_UNALIGNED_MEMORY_ACCESS 1 /* ARM */ # elif defined(__sparc) || defined(__sparc__) # define YYJSON_DISABLE_UNALIGNED_MEMORY_ACCESS 1 /* SPARC */ # elif defined(__mips) || defined(__mips__) || defined(__MIPS__) # define YYJSON_DISABLE_UNALIGNED_MEMORY_ACCESS 1 /* MIPS */ # elif defined(__m68k__) || defined(M68000) # define YYJSON_DISABLE_UNALIGNED_MEMORY_ACCESS 1 /* M68K */ # else # define YYJSON_DISABLE_UNALIGNED_MEMORY_ACCESS 0 # endif #endif /* Estimated initial ratio of the JSON data (data_size / value_count). For example: data: {"id":12345678,"name":"Harry"} data_size: 30 value_count: 5 ratio: 6 yyjson uses dynamic memory with a growth factor of 1.5 when reading and writing JSON, the ratios below are used to determine the initial memory size. A too large ratio will waste memory, and a too small ratio will cause multiple memory growths and degrade performance. Currently, these ratios are generated with some commonly used JSON datasets. */ #define YYJSON_READER_ESTIMATED_PRETTY_RATIO 16 #define YYJSON_READER_ESTIMATED_MINIFY_RATIO 6 #define YYJSON_WRITER_ESTIMATED_PRETTY_RATIO 32 #define YYJSON_WRITER_ESTIMATED_MINIFY_RATIO 18 /* The initial and maximum size of the memory pool's chunk in yyjson_mut_doc. */ #define YYJSON_MUT_DOC_STR_POOL_INIT_SIZE 0x100 #define YYJSON_MUT_DOC_STR_POOL_MAX_SIZE 0x10000000 #define YYJSON_MUT_DOC_VAL_POOL_INIT_SIZE (0x10 * sizeof(yyjson_mut_val)) #define YYJSON_MUT_DOC_VAL_POOL_MAX_SIZE (0x1000000 * sizeof(yyjson_mut_val)) /* The minimum size of the dynamic allocator's chunk. */ #define YYJSON_ALC_DYN_MIN_SIZE 0x1000 /* Default value for compile-time options. */ #ifndef YYJSON_DISABLE_READER #define YYJSON_DISABLE_READER 0 #endif #ifndef YYJSON_DISABLE_WRITER #define YYJSON_DISABLE_WRITER 0 #endif #ifndef YYJSON_DISABLE_INCR_READER #define YYJSON_DISABLE_INCR_READER 0 #endif #ifndef YYJSON_DISABLE_UTILS #define YYJSON_DISABLE_UTILS 0 #endif #ifndef YYJSON_DISABLE_FAST_FP_CONV #define YYJSON_DISABLE_FAST_FP_CONV 0 #endif #ifndef YYJSON_DISABLE_NON_STANDARD #define YYJSON_DISABLE_NON_STANDARD 0 #endif #ifndef YYJSON_DISABLE_UTF8_VALIDATION #define YYJSON_DISABLE_UTF8_VALIDATION 0 #endif /*============================================================================== * MARK: - Macros (Private) *============================================================================*/ /* Macros used for loop unrolling and other purpose. */ #define repeat2(x) { x x } #define repeat4(x) { x x x x } #define repeat8(x) { x x x x x x x x } #define repeat16(x) { x x x x x x x x x x x x x x x x } #define repeat2_incr(x) { x(0) x(1) } #define repeat4_incr(x) { x(0) x(1) x(2) x(3) } #define repeat8_incr(x) { x(0) x(1) x(2) x(3) x(4) x(5) x(6) x(7) } #define repeat16_incr(x) { x(0) x(1) x(2) x(3) x(4) x(5) x(6) x(7) \ x(8) x(9) x(10) x(11) x(12) x(13) x(14) x(15) } #define repeat_in_1_18(x) { x(1) x(2) x(3) x(4) x(5) x(6) x(7) x(8) \ x(9) x(10) x(11) x(12) x(13) x(14) x(15) x(16) \ x(17) x(18) } /* Macros used to provide branch prediction information for compiler. */ #undef likely #define likely(x) yyjson_likely(x) #undef unlikely #define unlikely(x) yyjson_unlikely(x) /* Macros used to provide inline information for compiler. */ #undef static_inline #define static_inline static yyjson_inline #undef static_noinline #define static_noinline static yyjson_noinline /* Macros for min and max. */ #undef yyjson_min #define yyjson_min(x, y) ((x) < (y) ? (x) : (y)) #undef yyjson_max #define yyjson_max(x, y) ((x) > (y) ? (x) : (y)) /* Used to write u64 literal for C89 which doesn't support "ULL" suffix. */ #undef U64 #define U64(hi, lo) ((((u64)hi##UL) << 32U) + lo##UL) #undef U32 #define U32(hi) ((u32)(hi##UL)) /* Used to cast away (remove) const qualifier. */ #define constcast(type) (type)(void *)(size_t)(const void *) /* Compiler barriers for single variables. These macros inform GCC that a read or write access to the given memory location will occur, preventing certain compiler optimizations or reordering around the access to 'val'. They do not emit any actual instructions. This is useful when GCC's default optimization strategies are suboptimal and precise control over memory access patterns is required. These barriers are not needed when using Clang or MSVC. */ #if YYJSON_IS_REAL_GCC # define gcc_load_barrier(val) __asm__ volatile(""::"m"(val)) # define gcc_store_barrier(val) __asm__ volatile("":"=m"(val)) # define gcc_full_barrier(val) __asm__ volatile("":"=m"(val):"m"(val)) #else # define gcc_load_barrier(val) # define gcc_store_barrier(val) # define gcc_full_barrier(val) #endif /*============================================================================== * MARK: - Constants (Private) *============================================================================*/ /* Common error messages. */ #define MSG_FOPEN "failed to open file" #define MSG_FREAD "failed to read file" #define MSG_FWRITE "failed to write file" #define MSG_FCLOSE "failed to close file" #define MSG_MALLOC "failed to allocate memory" #define MSG_CHAR_T "invalid literal, expected 'true'" #define MSG_CHAR_F "invalid literal, expected 'false'" #define MSG_CHAR_N "invalid literal, expected 'null'" #define MSG_CHAR "unexpected character, expected a JSON value" #define MSG_ARR_END "unexpected character, expected ',' or ']'" #define MSG_OBJ_KEY "unexpected character, expected a string key" #define MSG_OBJ_SEP "unexpected character, expected ':' after key" #define MSG_OBJ_END "unexpected character, expected ',' or '}'" #define MSG_GARBAGE "unexpected content after document" #define MSG_NOT_END "unexpected end of data" #define MSG_COMMENT "unclosed multiline comment" #define MSG_COMMA "trailing comma is not allowed" #define MSG_NAN_INF "nan or inf number is not allowed" #define MSG_ERR_TYPE "invalid JSON value type" #define MSG_ERR_BOM "UTF-8 byte order mark (BOM) is not supported" #define MSG_ERR_UTF8 "invalid utf-8 encoding in string" #define MSG_ERR_UTF16 "UTF-16 encoding is not supported" #define MSG_ERR_UTF32 "UTF-32 encoding is not supported" /* U64 constant values */ #undef U64_MAX #define U64_MAX U64(0xFFFFFFFF, 0xFFFFFFFF) #undef I64_MAX #define I64_MAX U64(0x7FFFFFFF, 0xFFFFFFFF) #undef USIZE_MAX #define USIZE_MAX ((usize)(~(usize)0)) /* Maximum number of digits for reading u32/u64/usize safety (not overflow). */ #undef U32_SAFE_DIG #define U32_SAFE_DIG 9 /* u32 max is 4294967295, 10 digits */ #undef U64_SAFE_DIG #define U64_SAFE_DIG 19 /* u64 max is 18446744073709551615, 20 digits */ #undef USIZE_SAFE_DIG #define USIZE_SAFE_DIG (sizeof(usize) == 8 ? U64_SAFE_DIG : U32_SAFE_DIG) /* Inf bits (positive) */ #define F64_BITS_INF U64(0x7FF00000, 0x00000000) /* NaN bits (quiet NaN, no payload, no sign) */ #if defined(__hppa__) || (defined(__mips__) && !defined(__mips_nan2008)) #define F64_BITS_NAN U64(0x7FF7FFFF, 0xFFFFFFFF) #else #define F64_BITS_NAN U64(0x7FF80000, 0x00000000) #endif /* maximum significant digits count in decimal when reading double number */ #define F64_MAX_DEC_DIG 768 /* maximum decimal power of double number (1.7976931348623157e308) */ #define F64_MAX_DEC_EXP 308 /* minimum decimal power of double number (4.9406564584124654e-324) */ #define F64_MIN_DEC_EXP (-324) /* maximum binary power of double number */ #define F64_MAX_BIN_EXP 1024 /* minimum binary power of double number */ #define F64_MIN_BIN_EXP (-1021) /* float/double number bits */ #define F32_BITS 32 #define F64_BITS 64 /* float/double number exponent part bits */ #define F32_EXP_BITS 8 #define F64_EXP_BITS 11 /* float/double number significand part bits */ #define F32_SIG_BITS 23 #define F64_SIG_BITS 52 /* float/double number significand part bits (with 1 hidden bit) */ #define F32_SIG_FULL_BITS 24 #define F64_SIG_FULL_BITS 53 /* float/double number significand bit mask */ #define F32_SIG_MASK U32(0x007FFFFF) #define F64_SIG_MASK U64(0x000FFFFF, 0xFFFFFFFF) /* float/double number exponent bit mask */ #define F32_EXP_MASK U32(0x7F800000) #define F64_EXP_MASK U64(0x7FF00000, 0x00000000) /* float/double number exponent bias */ #define F32_EXP_BIAS 127 #define F64_EXP_BIAS 1023 /* float/double number significant digits count in decimal */ #define F32_DEC_DIG 9 #define F64_DEC_DIG 17 /* buffer length required for float/double number writer */ #define FP_BUF_LEN 40 /* maximum length of a number in incremental parsing */ #define INCR_NUM_MAX_LEN 1024 /*============================================================================== * MARK: - Types (Private) *============================================================================*/ /** Type define for primitive types. */ typedef float f32; typedef double f64; typedef int8_t i8; typedef uint8_t u8; typedef int16_t i16; typedef uint16_t u16; typedef int32_t i32; typedef uint32_t u32; typedef int64_t i64; typedef uint64_t u64; typedef size_t usize; /** 128-bit integer, used by floating-point number reader and writer. */ #if YYJSON_HAS_INT128 __extension__ typedef __int128 i128; __extension__ typedef unsigned __int128 u128; #endif /** 16/32/64-bit vector */ typedef struct v16 { char c[2]; } v16; typedef struct v32 { char c[4]; } v32; typedef struct v64 { char c[8]; } v64; /** 16/32/64-bit vector union */ typedef union v16_uni { v16 v; u16 u; } v16_uni; typedef union v32_uni { v32 v; u32 u; } v32_uni; typedef union v64_uni { v64 v; u64 u; } v64_uni; /*============================================================================== * MARK: - Load/Store Utils (Private) *============================================================================*/ #define byte_move_idx(x) ((char *)dst)[x] = ((const char *)src)[x]; #define byte_move_src(x) ((char *)tmp)[x] = ((const char *)src)[x]; #define byte_move_dst(x) ((char *)dst)[x] = ((const char *)tmp)[x]; /** Same as `memcpy(dst, src, 2)`, no overlap. */ static_inline void byte_copy_2(void *dst, const void *src) { #if !YYJSON_DISABLE_UNALIGNED_MEMORY_ACCESS memcpy(dst, src, 2); #else repeat2_incr(byte_move_idx) #endif } /** Same as `memcpy(dst, src, 4)`, no overlap. */ static_inline void byte_copy_4(void *dst, const void *src) { #if !YYJSON_DISABLE_UNALIGNED_MEMORY_ACCESS memcpy(dst, src, 4); #else repeat4_incr(byte_move_idx) #endif } /** Same as `memcpy(dst, src, 8)`, no overlap. */ static_inline void byte_copy_8(void *dst, const void *src) { #if !YYJSON_DISABLE_UNALIGNED_MEMORY_ACCESS memcpy(dst, src, 8); #else repeat8_incr(byte_move_idx) #endif } /** Same as `memcpy(dst, src, 16)`, no overlap. */ static_inline void byte_copy_16(void *dst, const void *src) { #if !YYJSON_DISABLE_UNALIGNED_MEMORY_ACCESS memcpy(dst, src, 16); #else repeat16_incr(byte_move_idx) #endif } /** Same as `memmove(dst, src, 2)`, allows overlap. */ static_inline void byte_move_2(void *dst, const void *src) { #if !YYJSON_DISABLE_UNALIGNED_MEMORY_ACCESS u16 tmp; memcpy(&tmp, src, 2); memcpy(dst, &tmp, 2); #else char tmp[2]; repeat2_incr(byte_move_src) repeat2_incr(byte_move_dst) #endif } /** Same as `memmove(dst, src, 4)`, allows overlap. */ static_inline void byte_move_4(void *dst, const void *src) { #if !YYJSON_DISABLE_UNALIGNED_MEMORY_ACCESS u32 tmp; memcpy(&tmp, src, 4); memcpy(dst, &tmp, 4); #else char tmp[4]; repeat4_incr(byte_move_src) repeat4_incr(byte_move_dst) #endif } /** Same as `memmove(dst, src, 8)`, allows overlap. */ static_inline void byte_move_8(void *dst, const void *src) { #if !YYJSON_DISABLE_UNALIGNED_MEMORY_ACCESS u64 tmp; memcpy(&tmp, src, 8); memcpy(dst, &tmp, 8); #else char tmp[8]; repeat8_incr(byte_move_src) repeat8_incr(byte_move_dst) #endif } /** Same as `memmove(dst, src, 16)`, allows overlap. */ static_inline void byte_move_16(void *dst, const void *src) { #if !YYJSON_DISABLE_UNALIGNED_MEMORY_ACCESS char *pdst = (char *)dst; const char *psrc = (const char *)src; u64 tmp1, tmp2; memcpy(&tmp1, psrc, 8); memcpy(&tmp2, psrc + 8, 8); memcpy(pdst, &tmp1, 8); memcpy(pdst + 8, &tmp2, 8); #else char tmp[16]; repeat16_incr(byte_move_src) repeat16_incr(byte_move_dst) #endif } /** Same as `memmove(dst, src, n)`, but only `dst <= src` and `n <= 16`. */ static_inline void byte_move_forward(void *dst, void *src, usize n) { char *d = (char *)dst, *s = (char *)src; n += (n % 2); /* round up to even */ if (n == 16) { byte_move_16(d, s); return; } if (n >= 8) { byte_move_8(d, s); n -= 8; d += 8; s += 8; } if (n >= 4) { byte_move_4(d, s); n -= 4; d += 4; s += 4; } if (n >= 2) { byte_move_2(d, s); } } /** Same as `memcmp(buf, pat, 2) == 0`. */ static_inline bool byte_match_2(void *buf, const char *pat) { #if !YYJSON_DISABLE_UNALIGNED_MEMORY_ACCESS v16_uni u1, u2; memcpy(&u1, buf, 2); memcpy(&u2, pat, 2); return u1.u == u2.u; #else return ((char *)buf)[0] == ((const char *)pat)[0] && ((char *)buf)[1] == ((const char *)pat)[1]; #endif } /** Same as `memcmp(buf, pat, 4) == 0`. */ static_inline bool byte_match_4(void *buf, const char *pat) { #if !YYJSON_DISABLE_UNALIGNED_MEMORY_ACCESS v32_uni u1, u2; memcpy(&u1, buf, 4); memcpy(&u2, pat, 4); return u1.u == u2.u; #else return ((char *)buf)[0] == ((const char *)pat)[0] && ((char *)buf)[1] == ((const char *)pat)[1] && ((char *)buf)[2] == ((const char *)pat)[2] && ((char *)buf)[3] == ((const char *)pat)[3]; #endif } /** Loads 2 bytes from `src` as a u16 (native-endian). */ static_inline u16 byte_load_2(const void *src) { v16_uni uni; #if !YYJSON_DISABLE_UNALIGNED_MEMORY_ACCESS memcpy(&uni, src, 2); #else uni.v.c[0] = ((const char *)src)[0]; uni.v.c[1] = ((const char *)src)[1]; #endif return uni.u; } /** Loads 3 bytes from `src` as a u32 (native-endian). */ static_inline u32 byte_load_3(const void *src) { v32_uni uni; #if !YYJSON_DISABLE_UNALIGNED_MEMORY_ACCESS memcpy(&uni, src, 2); uni.v.c[2] = ((const char *)src)[2]; uni.v.c[3] = 0; #else uni.v.c[0] = ((const char *)src)[0]; uni.v.c[1] = ((const char *)src)[1]; uni.v.c[2] = ((const char *)src)[2]; uni.v.c[3] = 0; #endif return uni.u; } /** Loads 4 bytes from `src` as a u32 (native-endian). */ static_inline u32 byte_load_4(const void *src) { v32_uni uni; #if !YYJSON_DISABLE_UNALIGNED_MEMORY_ACCESS memcpy(&uni, src, 4); #else uni.v.c[0] = ((const char *)src)[0]; uni.v.c[1] = ((const char *)src)[1]; uni.v.c[2] = ((const char *)src)[2]; uni.v.c[3] = ((const char *)src)[3]; #endif return uni.u; } /*============================================================================== * MARK: - Character Utils (Private) * These lookup tables were generated by `misc/make_tables.c`. *============================================================================*/ /* char_table1 */ #define CHAR_TYPE_ASCII (1 << 0) /* Except: ["\], [0x00-0x1F, 0x80-0xFF] */ #define CHAR_TYPE_ASCII_SQ (1 << 1) /* Except: ['\], [0x00-0x1F, 0x80-0xFF] */ #define CHAR_TYPE_SPACE (1 << 2) /* Whitespace: [ \t\n\r] */ #define CHAR_TYPE_SPACE_EXT (1 << 3) /* Whitespace: [ \t\n\r\v\f], JSON5 */ #define CHAR_TYPE_NUM (1 << 4) /* Number: [.-+0-9] */ #define CHAR_TYPE_COMMENT (1 << 5) /* Comment: [/] */ /* char_table2 */ #define CHAR_TYPE_EOL (1 << 0) /* End of line: [\r\n] */ #define CHAR_TYPE_EOL_EXT (1 << 1) /* End of line: [\r\n], JSON5 */ #define CHAR_TYPE_ID_START (1 << 2) /* ID start: [_$A-Za-z\], U+0080+ */ #define CHAR_TYPE_ID_NEXT (1 << 3) /* ID next: [_$A-Za-z0-9\], U+0080+ */ #define CHAR_TYPE_ID_ASCII (1 << 4) /* ID next ASCII: [_$A-Za-z0-9] */ /* char_table3 */ #define CHAR_TYPE_SIGN (1 << 0) /* [-+] */ #define CHAR_TYPE_DIGIT (1 << 1) /* [0-9] */ #define CHAR_TYPE_NONZERO (1 << 2) /* [1-9] */ #define CHAR_TYPE_EXP (1 << 3) /* [eE] */ #define CHAR_TYPE_DOT (1 << 4) /* [.] */ static const u8 char_table1[256] = { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0C, 0x0C, 0x08, 0x08, 0x0C, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0F, 0x03, 0x02, 0x03, 0x03, 0x03, 0x03, 0x01, 0x03, 0x03, 0x03, 0x13, 0x03, 0x13, 0x13, 0x23, 0x13, 0x13, 0x13, 0x13, 0x13, 0x13, 0x13, 0x13, 0x13, 0x13, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x00, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x08, 0x08, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }; static const u8 char_table2[256] = { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1C, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1C, 0x1C, 0x1C, 0x1C, 0x1C, 0x1C, 0x1C, 0x1C, 0x1C, 0x1C, 0x1C, 0x1C, 0x1C, 0x1C, 0x1C, 0x1C, 0x1C, 0x1C, 0x1C, 0x1C, 0x1C, 0x1C, 0x1C, 0x1C, 0x1C, 0x1C, 0x00, 0x0C, 0x00, 0x00, 0x1C, 0x00, 0x1C, 0x1C, 0x1C, 0x1C, 0x1C, 0x1C, 0x1C, 0x1C, 0x1C, 0x1C, 0x1C, 0x1C, 0x1C, 0x1C, 0x1C, 0x1C, 0x1C, 0x1C, 0x1C, 0x1C, 0x1C, 0x1C, 0x1C, 0x1C, 0x1C, 0x1C, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0C, 0x0C, 0x0C, 0x0C, 0x0C, 0x0C, 0x0C, 0x0C, 0x0C, 0x0C, 0x0C, 0x0C, 0x0C, 0x0C, 0x0C, 0x0C, 0x0C, 0x0C, 0x0C, 0x0C, 0x0C, 0x0C, 0x0C, 0x0C, 0x0C, 0x0C, 0x0C, 0x0C, 0x0C, 0x0C, 0x0C, 0x0C, 0x0C, 0x0C, 0x0C, 0x0C, 0x0C, 0x0C, 0x0C, 0x0C, 0x0C, 0x0C, 0x0C, 0x0C, 0x0C, 0x0C, 0x0C, 0x0C, 0x0C, 0x0C, 0x0C, 0x0C, 0x0C, 0x0C, 0x0C, 0x0C, 0x0C, 0x0C, 0x0C, 0x0C, 0x0C, 0x0C, 0x0C, 0x0C, 0x0C, 0x0C, 0x0C, 0x0C, 0x0C, 0x0C, 0x0C, 0x0C, 0x0C, 0x0C, 0x0C, 0x0C, 0x0C, 0x0C, 0x0C, 0x0C, 0x0C, 0x0C, 0x0C, 0x0C, 0x0C, 0x0C, 0x0C, 0x0C, 0x0C, 0x0C, 0x0C, 0x0C, 0x0C, 0x0C, 0x0C, 0x0C, 0x0C, 0x0C, 0x0E, 0x0C, 0x0C, 0x0C, 0x0C, 0x0C, 0x0C, 0x0C, 0x0C, 0x0C, 0x0C, 0x0C, 0x0C, 0x0C, 0x0C, 0x0C, 0x0C, 0x0C, 0x0C, 0x0C, 0x0C, 0x0C, 0x0C, 0x0C, 0x0C, 0x0C, 0x0C, 0x0C, 0x0C, 0x0C }; static const u8 char_table3[256] = { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x01, 0x10, 0x00, 0x02, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }; /** Match a whitespace: [ \t\n\r]. */ static_inline bool char_is_space(u8 c) { return !!(char_table1[c] & CHAR_TYPE_SPACE); } /** Match an extended whitespace: [ \t\n\r\\v\\f], JSON5 whitespace. */ static_inline bool char_is_space_ext(u8 c) { return !!(char_table1[c] & CHAR_TYPE_SPACE_EXT); } /** Match a JSON number: [.-+0-9]. */ static_inline bool char_is_num(u8 c) { return !!(char_table1[c] & CHAR_TYPE_NUM); } /** Match an ASCII character in string: ["\], [0x00-0x1F, 0x80-0xFF]. */ static_inline bool char_is_ascii_skip(u8 c) { return !!(char_table1[c] & CHAR_TYPE_ASCII); } /** Match an ASCII character single-quoted: ['\], [0x00-0x1F, 0x80-0xFF]. */ static_inline bool char_is_ascii_skip_sq(u8 c) { return !!(char_table1[c] & CHAR_TYPE_ASCII_SQ); } /** Match a trivia character: extended whitespace or comment. */ static_inline bool char_is_trivia(u8 c) { return !!(char_table1[c] & (CHAR_TYPE_SPACE_EXT | CHAR_TYPE_COMMENT)); } /** Match a line end character: [\r\n]. */ static_inline bool char_is_eol(u8 c) { return !!(char_table2[c] & CHAR_TYPE_EOL); } /** Match an extended line end character: [\r\n], JSON5 line terminator. */ static_inline bool char_is_eol_ext(u8 c) { return !!(char_table2[c] & CHAR_TYPE_EOL_EXT); } /** Match an identifier name start: [_$A-Za-z\], U+0080+. */ static_inline bool char_is_id_start(u8 c) { return !!(char_table2[c] & CHAR_TYPE_ID_START); } /** Match an identifier name next: [_$A-Za-z0-9\], U+0080+. */ static_inline bool char_is_id_next(u8 c) { return !!(char_table2[c] & CHAR_TYPE_ID_NEXT); } /** Match an identifier name ASCII: [_$A-Za-z0-9]. */ static_inline bool char_is_id_ascii(u8 c) { return !!(char_table2[c] & CHAR_TYPE_ID_ASCII); } /** Match a sign: [+-] */ static_inline bool char_is_sign(u8 d) { return !!(char_table3[d] & CHAR_TYPE_SIGN); } /** Match a none-zero digit: [1-9] */ static_inline bool char_is_nonzero(u8 d) { return !!(char_table3[d] & CHAR_TYPE_NONZERO); } /** Match a digit: [0-9] */ static_inline bool char_is_digit(u8 d) { return !!(char_table3[d] & CHAR_TYPE_DIGIT); } /** Match an exponent sign: [eE]. */ static_inline bool char_is_exp(u8 d) { return !!(char_table3[d] & CHAR_TYPE_EXP); } /** Match a floating point indicator: [.eE]. */ static_inline bool char_is_fp(u8 d) { return !!(char_table3[d] & (CHAR_TYPE_DOT | CHAR_TYPE_EXP)); } /** Match a digit or floating point indicator: [0-9.eE]. */ static_inline bool char_is_digit_or_fp(u8 d) { return !!(char_table3[d] & (CHAR_TYPE_DIGIT | CHAR_TYPE_DOT | CHAR_TYPE_EXP)); } /** Match a JSON container: `{` or `[`. */ static_inline bool char_is_ctn(u8 c) { return (c & 0xDF) == 0x5B; /* '[': 0x5B, '{': 0x7B */ } /** Convert ASCII letter to lowercase; valid only for [A-Za-z]. */ static_inline u8 char_to_lower(u8 c) { return c | 0x20; } /** Match UTF-8 byte order mask. */ static_inline bool is_utf8_bom(const u8 *cur) { return byte_load_3(cur) == byte_load_3("\xEF\xBB\xBF"); } /** Match UTF-16 byte order mask. */ static_inline bool is_utf16_bom(const u8 *cur) { return byte_load_2(cur) == byte_load_2("\xFE\xFF") || byte_load_2(cur) == byte_load_2("\xFF\xFE"); } /** Match UTF-32 byte order mask, need length check to avoid zero padding. */ static_inline bool is_utf32_bom(const u8 *cur) { return byte_load_4(cur) == byte_load_4("\x00\x00\xFE\xFF") || byte_load_4(cur) == byte_load_4("\xFF\xFE\x00\x00"); } /** Get the extended line end length. Used with `char_is_eol_ext`. */ static_inline usize ext_eol_len(const u8 *cur) { if (cur[0] < 0x80) return 1; if (cur[1] == 0x80 && (cur[2] == 0xA8 || cur[2] == 0xA9)) return 3; return 0; } /** Get the extended whitespace length. Used with `char_is_space_ext`. */ static_inline usize ext_space_len(const u8 *cur) { if (cur[0] < 0x80) { return 1; } else if (byte_load_2(cur) == byte_load_2("\xC2\xA0")) { return 2; } else if (byte_load_2(cur) == byte_load_2("\xE2\x80")) { if (cur[2] >= 0x80 && cur[2] <= 0x8A) return 3; if (cur[2] == 0xA8 || cur[2] == 0xA9 || cur[2] == 0xAF) return 3; } else { u32 uni = byte_load_3(cur); if (uni == byte_load_3("\xE1\x9A\x80") || uni == byte_load_3("\xE2\x81\x9F") || uni == byte_load_3("\xE3\x80\x80") || uni == byte_load_3("\xEF\xBB\xBF")) return 3; } return 0; } /*============================================================================== * MARK: - Hex Character Reader (Private) * This function is used by JSON reader to read escaped characters. *============================================================================*/ /** This table is used to convert 4 hex character sequence to a number. A valid hex character [0-9A-Fa-f] will mapped to it's raw number [0x00, 0x0F], an invalid hex character will mapped to [0xF0]. (generate with misc/make_tables.c) */ static const u8 hex_conv_table[256] = { 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0 }; /** Load 4 hex characters to `u16`, return true on valid input. */ static_inline bool hex_load_4(const u8 *src, u16 *dst) { u16 c0 = hex_conv_table[src[0]]; u16 c1 = hex_conv_table[src[1]]; u16 c2 = hex_conv_table[src[2]]; u16 c3 = hex_conv_table[src[3]]; u16 t0 = (u16)((c0 << 8) | c2); u16 t1 = (u16)((c1 << 8) | c3); *dst = (u16)((t0 << 4) | t1); return ((t0 | t1) & (u16)0xF0F0) == 0; } /** Load 2 hex characters to `u8`, return true on valid input. */ static_inline bool hex_load_2(const u8 *src, u8 *dst) { u8 c0 = hex_conv_table[src[0]]; u8 c1 = hex_conv_table[src[1]]; *dst = (u8)((c0 << 4) | c1); return ((c0 | c1) & 0xF0) == 0; } /** Match a hexadecimal numeric character: [0-9a-fA-F]. */ static_inline bool char_is_hex(u8 c) { return hex_conv_table[c] != 0xF0; } /*============================================================================== * MARK: - UTF8 Validation (Private) * Each Unicode code point is encoded using 1 to 4 bytes in UTF-8. * Validation is performed using a 4-byte mask and pattern-based approach, * which requires the input data to be padded with four zero bytes at the end. *============================================================================*/ /* Macro for concatenating four u8 into a u32 and keeping the byte order. */ #if YYJSON_ENDIAN == YYJSON_LITTLE_ENDIAN # define utf8_seq_def(name, a, b, c, d) \ static const u32 utf8_seq_##name = 0x##d##c##b##a##UL; # define utf8_seq(name) utf8_seq_##name #elif YYJSON_ENDIAN == YYJSON_BIG_ENDIAN # define utf8_seq_def(name, a, b, c, d) \ static const u32 utf8_seq_##name = 0x##a##b##c##d##UL; # define utf8_seq(name) utf8_seq_##name #else # define utf8_seq_def(name, a, b, c, d) \ static const v32_uni utf8_uni_##name = {{ 0x##a, 0x##b, 0x##c, 0x##d }}; # define utf8_seq(name) utf8_uni_##name.u #endif /* 1-byte sequence (U+0000 to U+007F) bit min [.......0] (U+0000) bit max [.1111111] (U+007F) bit mask [x.......] (80) bit pattern [0.......] (00) */ utf8_seq_def(b1_mask, 80, 00, 00, 00) utf8_seq_def(b1_patt, 00, 00, 00, 00) #define is_utf8_seq1(uni) ( \ ((uni & utf8_seq(b1_mask)) == utf8_seq(b1_patt)) ) /* 2-byte sequence (U+0080 to U+07FF) bit min [......10 ..000000] (U+0080) bit max [...11111 ..111111] (U+07FF) bit mask [xxx..... xx......] (E0 C0) bit pattern [110..... 10......] (C0 80) bit require [...xxxx. ........] (1E 00) */ utf8_seq_def(b2_mask, E0, C0, 00, 00) utf8_seq_def(b2_patt, C0, 80, 00, 00) utf8_seq_def(b2_requ, 1E, 00, 00, 00) #define is_utf8_seq2(uni) ( \ ((uni & utf8_seq(b2_mask)) == utf8_seq(b2_patt)) && \ ((uni & utf8_seq(b2_requ))) ) /* 3-byte sequence (U+0800 to U+FFFF) bit min [........ ..100000 ..000000] (U+0800) bit max [....1111 ..111111 ..111111] (U+FFFF) bit mask [xxxx.... xx...... xx......] (F0 C0 C0) bit pattern [1110.... 10...... 10......] (E0 80 80) bit require [....xxxx ..x..... ........] (0F 20 00) 3-byte invalid sequence, reserved for surrogate halves (U+D800 to U+DFFF) bit min [....1101 ..100000 ..000000] (U+D800) bit max [....1101 ..111111 ..111111] (U+DFFF) bit mask [....xxxx ..x..... ........] (0F 20 00) bit pattern [....1101 ..1..... ........] (0D 20 00) */ utf8_seq_def(b3_mask, F0, C0, C0, 00) utf8_seq_def(b3_patt, E0, 80, 80, 00) utf8_seq_def(b3_requ, 0F, 20, 00, 00) utf8_seq_def(b3_erro, 0D, 20, 00, 00) #define is_utf8_seq3(uni) ( \ ((uni & utf8_seq(b3_mask)) == utf8_seq(b3_patt)) && \ ((tmp = (uni & utf8_seq(b3_requ)))) && \ ((tmp != utf8_seq(b3_erro))) ) /* 4-byte sequence (U+10000 to U+10FFFF) bit min [........ ...10000 ..000000 ..000000] (U+10000) bit max [.....100 ..001111 ..111111 ..111111] (U+10FFFF) bit mask [xxxxx... xx...... xx...... xx......] (F8 C0 C0 C0) bit pattern [11110... 10...... 10...... 10......] (F0 80 80 80) bit require [.....xxx ..xx.... ........ ........] (07 30 00 00) bit require 1 [.....x.. ........ ........ ........] (04 00 00 00) bit require 2 [......xx ..xx.... ........ ........] (03 30 00 00) */ utf8_seq_def(b4_mask, F8, C0, C0, C0) utf8_seq_def(b4_patt, F0, 80, 80, 80) utf8_seq_def(b4_requ, 07, 30, 00, 00) utf8_seq_def(b4_req1, 04, 00, 00, 00) utf8_seq_def(b4_req2, 03, 30, 00, 00) #define is_utf8_seq4(uni) ( \ ((uni & utf8_seq(b4_mask)) == utf8_seq(b4_patt)) && \ ((tmp = (uni & utf8_seq(b4_requ)))) && \ ((tmp & utf8_seq(b4_req1)) == 0 || (tmp & utf8_seq(b4_req2)) == 0) ) /*============================================================================== * MARK: - Power10 Lookup Table (Private) * These data are used by the floating-point number reader and writer. *============================================================================*/ #if !YYJSON_DISABLE_FAST_FP_CONV /** Maximum pow10 exponent that can be represented exactly as a float64. */ #define F64_POW10_MAX_EXACT_EXP 22 /** Cached pow10 table. */ static const f64 f64_pow10_table[F64_POW10_MAX_EXACT_EXP + 1] = { 1e0, 1e1, 1e2, 1e3, 1e4, 1e5, 1e6, 1e7, 1e8, 1e9, 1e10, 1e11, 1e12, 1e13, 1e14, 1e15, 1e16, 1e17, 1e18, 1e19, 1e20, 1e21, 1e22 }; /** Maximum pow10 exponent that can be represented exactly as a uint64. */ #define U64_POW10_MAX_EXACT_EXP 19 /** Table: [ 10^0, ..., 10^19 ] (generate with misc/make_tables.c) */ static const u64 u64_pow10_table[U64_POW10_MAX_EXACT_EXP + 1] = { U64(0x00000000, 0x00000001), U64(0x00000000, 0x0000000A), U64(0x00000000, 0x00000064), U64(0x00000000, 0x000003E8), U64(0x00000000, 0x00002710), U64(0x00000000, 0x000186A0), U64(0x00000000, 0x000F4240), U64(0x00000000, 0x00989680), U64(0x00000000, 0x05F5E100), U64(0x00000000, 0x3B9ACA00), U64(0x00000002, 0x540BE400), U64(0x00000017, 0x4876E800), U64(0x000000E8, 0xD4A51000), U64(0x00000918, 0x4E72A000), U64(0x00005AF3, 0x107A4000), U64(0x00038D7E, 0xA4C68000), U64(0x002386F2, 0x6FC10000), U64(0x01634578, 0x5D8A0000), U64(0x0DE0B6B3, 0xA7640000), U64(0x8AC72304, 0x89E80000) }; /** Minimum decimal exponent in pow10_sig_table. */ #define POW10_SIG_TABLE_MIN_EXP -343 /** Maximum decimal exponent in pow10_sig_table. */ #define POW10_SIG_TABLE_MAX_EXP 324 /** Minimum exact decimal exponent in pow10_sig_table */ #define POW10_SIG_TABLE_MIN_EXACT_EXP 0 /** Maximum exact decimal exponent in pow10_sig_table */ #define POW10_SIG_TABLE_MAX_EXACT_EXP 55 /** Normalized significant 128 bits of pow10, no rounded up (size: 10.4KB). This lookup table is used by both the double number reader and writer. (generate with misc/make_tables.c) */ static const u64 pow10_sig_table[] = { U64(0xBF29DCAB, 0xA82FDEAE), U64(0x7432EE87, 0x3880FC33), /* ~= 10^-343 */ U64(0xEEF453D6, 0x923BD65A), U64(0x113FAA29, 0x06A13B3F), /* ~= 10^-342 */ U64(0x9558B466, 0x1B6565F8), U64(0x4AC7CA59, 0xA424C507), /* ~= 10^-341 */ U64(0xBAAEE17F, 0xA23EBF76), U64(0x5D79BCF0, 0x0D2DF649), /* ~= 10^-340 */ U64(0xE95A99DF, 0x8ACE6F53), U64(0xF4D82C2C, 0x107973DC), /* ~= 10^-339 */ U64(0x91D8A02B, 0xB6C10594), U64(0x79071B9B, 0x8A4BE869), /* ~= 10^-338 */ U64(0xB64EC836, 0xA47146F9), U64(0x9748E282, 0x6CDEE284), /* ~= 10^-337 */ U64(0xE3E27A44, 0x4D8D98B7), U64(0xFD1B1B23, 0x08169B25), /* ~= 10^-336 */ U64(0x8E6D8C6A, 0xB0787F72), U64(0xFE30F0F5, 0xE50E20F7), /* ~= 10^-335 */ U64(0xB208EF85, 0x5C969F4F), U64(0xBDBD2D33, 0x5E51A935), /* ~= 10^-334 */ U64(0xDE8B2B66, 0xB3BC4723), U64(0xAD2C7880, 0x35E61382), /* ~= 10^-333 */ U64(0x8B16FB20, 0x3055AC76), U64(0x4C3BCB50, 0x21AFCC31), /* ~= 10^-332 */ U64(0xADDCB9E8, 0x3C6B1793), U64(0xDF4ABE24, 0x2A1BBF3D), /* ~= 10^-331 */ U64(0xD953E862, 0x4B85DD78), U64(0xD71D6DAD, 0x34A2AF0D), /* ~= 10^-330 */ U64(0x87D4713D, 0x6F33AA6B), U64(0x8672648C, 0x40E5AD68), /* ~= 10^-329 */ U64(0xA9C98D8C, 0xCB009506), U64(0x680EFDAF, 0x511F18C2), /* ~= 10^-328 */ U64(0xD43BF0EF, 0xFDC0BA48), U64(0x0212BD1B, 0x2566DEF2), /* ~= 10^-327 */ U64(0x84A57695, 0xFE98746D), U64(0x014BB630, 0xF7604B57), /* ~= 10^-326 */ U64(0xA5CED43B, 0x7E3E9188), U64(0x419EA3BD, 0x35385E2D), /* ~= 10^-325 */ U64(0xCF42894A, 0x5DCE35EA), U64(0x52064CAC, 0x828675B9), /* ~= 10^-324 */ U64(0x818995CE, 0x7AA0E1B2), U64(0x7343EFEB, 0xD1940993), /* ~= 10^-323 */ U64(0xA1EBFB42, 0x19491A1F), U64(0x1014EBE6, 0xC5F90BF8), /* ~= 10^-322 */ U64(0xCA66FA12, 0x9F9B60A6), U64(0xD41A26E0, 0x77774EF6), /* ~= 10^-321 */ U64(0xFD00B897, 0x478238D0), U64(0x8920B098, 0x955522B4), /* ~= 10^-320 */ U64(0x9E20735E, 0x8CB16382), U64(0x55B46E5F, 0x5D5535B0), /* ~= 10^-319 */ U64(0xC5A89036, 0x2FDDBC62), U64(0xEB2189F7, 0x34AA831D), /* ~= 10^-318 */ U64(0xF712B443, 0xBBD52B7B), U64(0xA5E9EC75, 0x01D523E4), /* ~= 10^-317 */ U64(0x9A6BB0AA, 0x55653B2D), U64(0x47B233C9, 0x2125366E), /* ~= 10^-316 */ U64(0xC1069CD4, 0xEABE89F8), U64(0x999EC0BB, 0x696E840A), /* ~= 10^-315 */ U64(0xF148440A, 0x256E2C76), U64(0xC00670EA, 0x43CA250D), /* ~= 10^-314 */ U64(0x96CD2A86, 0x5764DBCA), U64(0x38040692, 0x6A5E5728), /* ~= 10^-313 */ U64(0xBC807527, 0xED3E12BC), U64(0xC6050837, 0x04F5ECF2), /* ~= 10^-312 */ U64(0xEBA09271, 0xE88D976B), U64(0xF7864A44, 0xC633682E), /* ~= 10^-311 */ U64(0x93445B87, 0x31587EA3), U64(0x7AB3EE6A, 0xFBE0211D), /* ~= 10^-310 */ U64(0xB8157268, 0xFDAE9E4C), U64(0x5960EA05, 0xBAD82964), /* ~= 10^-309 */ U64(0xE61ACF03, 0x3D1A45DF), U64(0x6FB92487, 0x298E33BD), /* ~= 10^-308 */ U64(0x8FD0C162, 0x06306BAB), U64(0xA5D3B6D4, 0x79F8E056), /* ~= 10^-307 */ U64(0xB3C4F1BA, 0x87BC8696), U64(0x8F48A489, 0x9877186C), /* ~= 10^-306 */ U64(0xE0B62E29, 0x29ABA83C), U64(0x331ACDAB, 0xFE94DE87), /* ~= 10^-305 */ U64(0x8C71DCD9, 0xBA0B4925), U64(0x9FF0C08B, 0x7F1D0B14), /* ~= 10^-304 */ U64(0xAF8E5410, 0x288E1B6F), U64(0x07ECF0AE, 0x5EE44DD9), /* ~= 10^-303 */ U64(0xDB71E914, 0x32B1A24A), U64(0xC9E82CD9, 0xF69D6150), /* ~= 10^-302 */ U64(0x892731AC, 0x9FAF056E), U64(0xBE311C08, 0x3A225CD2), /* ~= 10^-301 */ U64(0xAB70FE17, 0xC79AC6CA), U64(0x6DBD630A, 0x48AAF406), /* ~= 10^-300 */ U64(0xD64D3D9D, 0xB981787D), U64(0x092CBBCC, 0xDAD5B108), /* ~= 10^-299 */ U64(0x85F04682, 0x93F0EB4E), U64(0x25BBF560, 0x08C58EA5), /* ~= 10^-298 */ U64(0xA76C5823, 0x38ED2621), U64(0xAF2AF2B8, 0x0AF6F24E), /* ~= 10^-297 */ U64(0xD1476E2C, 0x07286FAA), U64(0x1AF5AF66, 0x0DB4AEE1), /* ~= 10^-296 */ U64(0x82CCA4DB, 0x847945CA), U64(0x50D98D9F, 0xC890ED4D), /* ~= 10^-295 */ U64(0xA37FCE12, 0x6597973C), U64(0xE50FF107, 0xBAB528A0), /* ~= 10^-294 */ U64(0xCC5FC196, 0xFEFD7D0C), U64(0x1E53ED49, 0xA96272C8), /* ~= 10^-293 */ U64(0xFF77B1FC, 0xBEBCDC4F), U64(0x25E8E89C, 0x13BB0F7A), /* ~= 10^-292 */ U64(0x9FAACF3D, 0xF73609B1), U64(0x77B19161, 0x8C54E9AC), /* ~= 10^-291 */ U64(0xC795830D, 0x75038C1D), U64(0xD59DF5B9, 0xEF6A2417), /* ~= 10^-290 */ U64(0xF97AE3D0, 0xD2446F25), U64(0x4B057328, 0x6B44AD1D), /* ~= 10^-289 */ U64(0x9BECCE62, 0x836AC577), U64(0x4EE367F9, 0x430AEC32), /* ~= 10^-288 */ U64(0xC2E801FB, 0x244576D5), U64(0x229C41F7, 0x93CDA73F), /* ~= 10^-287 */ U64(0xF3A20279, 0xED56D48A), U64(0x6B435275, 0x78C1110F), /* ~= 10^-286 */ U64(0x9845418C, 0x345644D6), U64(0x830A1389, 0x6B78AAA9), /* ~= 10^-285 */ U64(0xBE5691EF, 0x416BD60C), U64(0x23CC986B, 0xC656D553), /* ~= 10^-284 */ U64(0xEDEC366B, 0x11C6CB8F), U64(0x2CBFBE86, 0xB7EC8AA8), /* ~= 10^-283 */ U64(0x94B3A202, 0xEB1C3F39), U64(0x7BF7D714, 0x32F3D6A9), /* ~= 10^-282 */ U64(0xB9E08A83, 0xA5E34F07), U64(0xDAF5CCD9, 0x3FB0CC53), /* ~= 10^-281 */ U64(0xE858AD24, 0x8F5C22C9), U64(0xD1B3400F, 0x8F9CFF68), /* ~= 10^-280 */ U64(0x91376C36, 0xD99995BE), U64(0x23100809, 0xB9C21FA1), /* ~= 10^-279 */ U64(0xB5854744, 0x8FFFFB2D), U64(0xABD40A0C, 0x2832A78A), /* ~= 10^-278 */ U64(0xE2E69915, 0xB3FFF9F9), U64(0x16C90C8F, 0x323F516C), /* ~= 10^-277 */ U64(0x8DD01FAD, 0x907FFC3B), U64(0xAE3DA7D9, 0x7F6792E3), /* ~= 10^-276 */ U64(0xB1442798, 0xF49FFB4A), U64(0x99CD11CF, 0xDF41779C), /* ~= 10^-275 */ U64(0xDD95317F, 0x31C7FA1D), U64(0x40405643, 0xD711D583), /* ~= 10^-274 */ U64(0x8A7D3EEF, 0x7F1CFC52), U64(0x482835EA, 0x666B2572), /* ~= 10^-273 */ U64(0xAD1C8EAB, 0x5EE43B66), U64(0xDA324365, 0x0005EECF), /* ~= 10^-272 */ U64(0xD863B256, 0x369D4A40), U64(0x90BED43E, 0x40076A82), /* ~= 10^-271 */ U64(0x873E4F75, 0xE2224E68), U64(0x5A7744A6, 0xE804A291), /* ~= 10^-270 */ U64(0xA90DE353, 0x5AAAE202), U64(0x711515D0, 0xA205CB36), /* ~= 10^-269 */ U64(0xD3515C28, 0x31559A83), U64(0x0D5A5B44, 0xCA873E03), /* ~= 10^-268 */ U64(0x8412D999, 0x1ED58091), U64(0xE858790A, 0xFE9486C2), /* ~= 10^-267 */ U64(0xA5178FFF, 0x668AE0B6), U64(0x626E974D, 0xBE39A872), /* ~= 10^-266 */ U64(0xCE5D73FF, 0x402D98E3), U64(0xFB0A3D21, 0x2DC8128F), /* ~= 10^-265 */ U64(0x80FA687F, 0x881C7F8E), U64(0x7CE66634, 0xBC9D0B99), /* ~= 10^-264 */ U64(0xA139029F, 0x6A239F72), U64(0x1C1FFFC1, 0xEBC44E80), /* ~= 10^-263 */ U64(0xC9874347, 0x44AC874E), U64(0xA327FFB2, 0x66B56220), /* ~= 10^-262 */ U64(0xFBE91419, 0x15D7A922), U64(0x4BF1FF9F, 0x0062BAA8), /* ~= 10^-261 */ U64(0x9D71AC8F, 0xADA6C9B5), U64(0x6F773FC3, 0x603DB4A9), /* ~= 10^-260 */ U64(0xC4CE17B3, 0x99107C22), U64(0xCB550FB4, 0x384D21D3), /* ~= 10^-259 */ U64(0xF6019DA0, 0x7F549B2B), U64(0x7E2A53A1, 0x46606A48), /* ~= 10^-258 */ U64(0x99C10284, 0x4F94E0FB), U64(0x2EDA7444, 0xCBFC426D), /* ~= 10^-257 */ U64(0xC0314325, 0x637A1939), U64(0xFA911155, 0xFEFB5308), /* ~= 10^-256 */ U64(0xF03D93EE, 0xBC589F88), U64(0x793555AB, 0x7EBA27CA), /* ~= 10^-255 */ U64(0x96267C75, 0x35B763B5), U64(0x4BC1558B, 0x2F3458DE), /* ~= 10^-254 */ U64(0xBBB01B92, 0x83253CA2), U64(0x9EB1AAED, 0xFB016F16), /* ~= 10^-253 */ U64(0xEA9C2277, 0x23EE8BCB), U64(0x465E15A9, 0x79C1CADC), /* ~= 10^-252 */ U64(0x92A1958A, 0x7675175F), U64(0x0BFACD89, 0xEC191EC9), /* ~= 10^-251 */ U64(0xB749FAED, 0x14125D36), U64(0xCEF980EC, 0x671F667B), /* ~= 10^-250 */ U64(0xE51C79A8, 0x5916F484), U64(0x82B7E127, 0x80E7401A), /* ~= 10^-249 */ U64(0x8F31CC09, 0x37AE58D2), U64(0xD1B2ECB8, 0xB0908810), /* ~= 10^-248 */ U64(0xB2FE3F0B, 0x8599EF07), U64(0x861FA7E6, 0xDCB4AA15), /* ~= 10^-247 */ U64(0xDFBDCECE, 0x67006AC9), U64(0x67A791E0, 0x93E1D49A), /* ~= 10^-246 */ U64(0x8BD6A141, 0x006042BD), U64(0xE0C8BB2C, 0x5C6D24E0), /* ~= 10^-245 */ U64(0xAECC4991, 0x4078536D), U64(0x58FAE9F7, 0x73886E18), /* ~= 10^-244 */ U64(0xDA7F5BF5, 0x90966848), U64(0xAF39A475, 0x506A899E), /* ~= 10^-243 */ U64(0x888F9979, 0x7A5E012D), U64(0x6D8406C9, 0x52429603), /* ~= 10^-242 */ U64(0xAAB37FD7, 0xD8F58178), U64(0xC8E5087B, 0xA6D33B83), /* ~= 10^-241 */ U64(0xD5605FCD, 0xCF32E1D6), U64(0xFB1E4A9A, 0x90880A64), /* ~= 10^-240 */ U64(0x855C3BE0, 0xA17FCD26), U64(0x5CF2EEA0, 0x9A55067F), /* ~= 10^-239 */ U64(0xA6B34AD8, 0xC9DFC06F), U64(0xF42FAA48, 0xC0EA481E), /* ~= 10^-238 */ U64(0xD0601D8E, 0xFC57B08B), U64(0xF13B94DA, 0xF124DA26), /* ~= 10^-237 */ U64(0x823C1279, 0x5DB6CE57), U64(0x76C53D08, 0xD6B70858), /* ~= 10^-236 */ U64(0xA2CB1717, 0xB52481ED), U64(0x54768C4B, 0x0C64CA6E), /* ~= 10^-235 */ U64(0xCB7DDCDD, 0xA26DA268), U64(0xA9942F5D, 0xCF7DFD09), /* ~= 10^-234 */ U64(0xFE5D5415, 0x0B090B02), U64(0xD3F93B35, 0x435D7C4C), /* ~= 10^-233 */ U64(0x9EFA548D, 0x26E5A6E1), U64(0xC47BC501, 0x4A1A6DAF), /* ~= 10^-232 */ U64(0xC6B8E9B0, 0x709F109A), U64(0x359AB641, 0x9CA1091B), /* ~= 10^-231 */ U64(0xF867241C, 0x8CC6D4C0), U64(0xC30163D2, 0x03C94B62), /* ~= 10^-230 */ U64(0x9B407691, 0xD7FC44F8), U64(0x79E0DE63, 0x425DCF1D), /* ~= 10^-229 */ U64(0xC2109436, 0x4DFB5636), U64(0x985915FC, 0x12F542E4), /* ~= 10^-228 */ U64(0xF294B943, 0xE17A2BC4), U64(0x3E6F5B7B, 0x17B2939D), /* ~= 10^-227 */ U64(0x979CF3CA, 0x6CEC5B5A), U64(0xA705992C, 0xEECF9C42), /* ~= 10^-226 */ U64(0xBD8430BD, 0x08277231), U64(0x50C6FF78, 0x2A838353), /* ~= 10^-225 */ U64(0xECE53CEC, 0x4A314EBD), U64(0xA4F8BF56, 0x35246428), /* ~= 10^-224 */ U64(0x940F4613, 0xAE5ED136), U64(0x871B7795, 0xE136BE99), /* ~= 10^-223 */ U64(0xB9131798, 0x99F68584), U64(0x28E2557B, 0x59846E3F), /* ~= 10^-222 */ U64(0xE757DD7E, 0xC07426E5), U64(0x331AEADA, 0x2FE589CF), /* ~= 10^-221 */ U64(0x9096EA6F, 0x3848984F), U64(0x3FF0D2C8, 0x5DEF7621), /* ~= 10^-220 */ U64(0xB4BCA50B, 0x065ABE63), U64(0x0FED077A, 0x756B53A9), /* ~= 10^-219 */ U64(0xE1EBCE4D, 0xC7F16DFB), U64(0xD3E84959, 0x12C62894), /* ~= 10^-218 */ U64(0x8D3360F0, 0x9CF6E4BD), U64(0x64712DD7, 0xABBBD95C), /* ~= 10^-217 */ U64(0xB080392C, 0xC4349DEC), U64(0xBD8D794D, 0x96AACFB3), /* ~= 10^-216 */ U64(0xDCA04777, 0xF541C567), U64(0xECF0D7A0, 0xFC5583A0), /* ~= 10^-215 */ U64(0x89E42CAA, 0xF9491B60), U64(0xF41686C4, 0x9DB57244), /* ~= 10^-214 */ U64(0xAC5D37D5, 0xB79B6239), U64(0x311C2875, 0xC522CED5), /* ~= 10^-213 */ U64(0xD77485CB, 0x25823AC7), U64(0x7D633293, 0x366B828B), /* ~= 10^-212 */ U64(0x86A8D39E, 0xF77164BC), U64(0xAE5DFF9C, 0x02033197), /* ~= 10^-211 */ U64(0xA8530886, 0xB54DBDEB), U64(0xD9F57F83, 0x0283FDFC), /* ~= 10^-210 */ U64(0xD267CAA8, 0x62A12D66), U64(0xD072DF63, 0xC324FD7B), /* ~= 10^-209 */ U64(0x8380DEA9, 0x3DA4BC60), U64(0x4247CB9E, 0x59F71E6D), /* ~= 10^-208 */ U64(0xA4611653, 0x8D0DEB78), U64(0x52D9BE85, 0xF074E608), /* ~= 10^-207 */ U64(0xCD795BE8, 0x70516656), U64(0x67902E27, 0x6C921F8B), /* ~= 10^-206 */ U64(0x806BD971, 0x4632DFF6), U64(0x00BA1CD8, 0xA3DB53B6), /* ~= 10^-205 */ U64(0xA086CFCD, 0x97BF97F3), U64(0x80E8A40E, 0xCCD228A4), /* ~= 10^-204 */ U64(0xC8A883C0, 0xFDAF7DF0), U64(0x6122CD12, 0x8006B2CD), /* ~= 10^-203 */ U64(0xFAD2A4B1, 0x3D1B5D6C), U64(0x796B8057, 0x20085F81), /* ~= 10^-202 */ U64(0x9CC3A6EE, 0xC6311A63), U64(0xCBE33036, 0x74053BB0), /* ~= 10^-201 */ U64(0xC3F490AA, 0x77BD60FC), U64(0xBEDBFC44, 0x11068A9C), /* ~= 10^-200 */ U64(0xF4F1B4D5, 0x15ACB93B), U64(0xEE92FB55, 0x15482D44), /* ~= 10^-199 */ U64(0x99171105, 0x2D8BF3C5), U64(0x751BDD15, 0x2D4D1C4A), /* ~= 10^-198 */ U64(0xBF5CD546, 0x78EEF0B6), U64(0xD262D45A, 0x78A0635D), /* ~= 10^-197 */ U64(0xEF340A98, 0x172AACE4), U64(0x86FB8971, 0x16C87C34), /* ~= 10^-196 */ U64(0x9580869F, 0x0E7AAC0E), U64(0xD45D35E6, 0xAE3D4DA0), /* ~= 10^-195 */ U64(0xBAE0A846, 0xD2195712), U64(0x89748360, 0x59CCA109), /* ~= 10^-194 */ U64(0xE998D258, 0x869FACD7), U64(0x2BD1A438, 0x703FC94B), /* ~= 10^-193 */ U64(0x91FF8377, 0x5423CC06), U64(0x7B6306A3, 0x4627DDCF), /* ~= 10^-192 */ U64(0xB67F6455, 0x292CBF08), U64(0x1A3BC84C, 0x17B1D542), /* ~= 10^-191 */ U64(0xE41F3D6A, 0x7377EECA), U64(0x20CABA5F, 0x1D9E4A93), /* ~= 10^-190 */ U64(0x8E938662, 0x882AF53E), U64(0x547EB47B, 0x7282EE9C), /* ~= 10^-189 */ U64(0xB23867FB, 0x2A35B28D), U64(0xE99E619A, 0x4F23AA43), /* ~= 10^-188 */ U64(0xDEC681F9, 0xF4C31F31), U64(0x6405FA00, 0xE2EC94D4), /* ~= 10^-187 */ U64(0x8B3C113C, 0x38F9F37E), U64(0xDE83BC40, 0x8DD3DD04), /* ~= 10^-186 */ U64(0xAE0B158B, 0x4738705E), U64(0x9624AB50, 0xB148D445), /* ~= 10^-185 */ U64(0xD98DDAEE, 0x19068C76), U64(0x3BADD624, 0xDD9B0957), /* ~= 10^-184 */ U64(0x87F8A8D4, 0xCFA417C9), U64(0xE54CA5D7, 0x0A80E5D6), /* ~= 10^-183 */ U64(0xA9F6D30A, 0x038D1DBC), U64(0x5E9FCF4C, 0xCD211F4C), /* ~= 10^-182 */ U64(0xD47487CC, 0x8470652B), U64(0x7647C320, 0x0069671F), /* ~= 10^-181 */ U64(0x84C8D4DF, 0xD2C63F3B), U64(0x29ECD9F4, 0x0041E073), /* ~= 10^-180 */ U64(0xA5FB0A17, 0xC777CF09), U64(0xF4681071, 0x00525890), /* ~= 10^-179 */ U64(0xCF79CC9D, 0xB955C2CC), U64(0x7182148D, 0x4066EEB4), /* ~= 10^-178 */ U64(0x81AC1FE2, 0x93D599BF), U64(0xC6F14CD8, 0x48405530), /* ~= 10^-177 */ U64(0xA21727DB, 0x38CB002F), U64(0xB8ADA00E, 0x5A506A7C), /* ~= 10^-176 */ U64(0xCA9CF1D2, 0x06FDC03B), U64(0xA6D90811, 0xF0E4851C), /* ~= 10^-175 */ U64(0xFD442E46, 0x88BD304A), U64(0x908F4A16, 0x6D1DA663), /* ~= 10^-174 */ U64(0x9E4A9CEC, 0x15763E2E), U64(0x9A598E4E, 0x043287FE), /* ~= 10^-173 */ U64(0xC5DD4427, 0x1AD3CDBA), U64(0x40EFF1E1, 0x853F29FD), /* ~= 10^-172 */ U64(0xF7549530, 0xE188C128), U64(0xD12BEE59, 0xE68EF47C), /* ~= 10^-171 */ U64(0x9A94DD3E, 0x8CF578B9), U64(0x82BB74F8, 0x301958CE), /* ~= 10^-170 */ U64(0xC13A148E, 0x3032D6E7), U64(0xE36A5236, 0x3C1FAF01), /* ~= 10^-169 */ U64(0xF18899B1, 0xBC3F8CA1), U64(0xDC44E6C3, 0xCB279AC1), /* ~= 10^-168 */ U64(0x96F5600F, 0x15A7B7E5), U64(0x29AB103A, 0x5EF8C0B9), /* ~= 10^-167 */ U64(0xBCB2B812, 0xDB11A5DE), U64(0x7415D448, 0xF6B6F0E7), /* ~= 10^-166 */ U64(0xEBDF6617, 0x91D60F56), U64(0x111B495B, 0x3464AD21), /* ~= 10^-165 */ U64(0x936B9FCE, 0xBB25C995), U64(0xCAB10DD9, 0x00BEEC34), /* ~= 10^-164 */ U64(0xB84687C2, 0x69EF3BFB), U64(0x3D5D514F, 0x40EEA742), /* ~= 10^-163 */ U64(0xE65829B3, 0x046B0AFA), U64(0x0CB4A5A3, 0x112A5112), /* ~= 10^-162 */ U64(0x8FF71A0F, 0xE2C2E6DC), U64(0x47F0E785, 0xEABA72AB), /* ~= 10^-161 */ U64(0xB3F4E093, 0xDB73A093), U64(0x59ED2167, 0x65690F56), /* ~= 10^-160 */ U64(0xE0F218B8, 0xD25088B8), U64(0x306869C1, 0x3EC3532C), /* ~= 10^-159 */ U64(0x8C974F73, 0x83725573), U64(0x1E414218, 0xC73A13FB), /* ~= 10^-158 */ U64(0xAFBD2350, 0x644EEACF), U64(0xE5D1929E, 0xF90898FA), /* ~= 10^-157 */ U64(0xDBAC6C24, 0x7D62A583), U64(0xDF45F746, 0xB74ABF39), /* ~= 10^-156 */ U64(0x894BC396, 0xCE5DA772), U64(0x6B8BBA8C, 0x328EB783), /* ~= 10^-155 */ U64(0xAB9EB47C, 0x81F5114F), U64(0x066EA92F, 0x3F326564), /* ~= 10^-154 */ U64(0xD686619B, 0xA27255A2), U64(0xC80A537B, 0x0EFEFEBD), /* ~= 10^-153 */ U64(0x8613FD01, 0x45877585), U64(0xBD06742C, 0xE95F5F36), /* ~= 10^-152 */ U64(0xA798FC41, 0x96E952E7), U64(0x2C481138, 0x23B73704), /* ~= 10^-151 */ U64(0xD17F3B51, 0xFCA3A7A0), U64(0xF75A1586, 0x2CA504C5), /* ~= 10^-150 */ U64(0x82EF8513, 0x3DE648C4), U64(0x9A984D73, 0xDBE722FB), /* ~= 10^-149 */ U64(0xA3AB6658, 0x0D5FDAF5), U64(0xC13E60D0, 0xD2E0EBBA), /* ~= 10^-148 */ U64(0xCC963FEE, 0x10B7D1B3), U64(0x318DF905, 0x079926A8), /* ~= 10^-147 */ U64(0xFFBBCFE9, 0x94E5C61F), U64(0xFDF17746, 0x497F7052), /* ~= 10^-146 */ U64(0x9FD561F1, 0xFD0F9BD3), U64(0xFEB6EA8B, 0xEDEFA633), /* ~= 10^-145 */ U64(0xC7CABA6E, 0x7C5382C8), U64(0xFE64A52E, 0xE96B8FC0), /* ~= 10^-144 */ U64(0xF9BD690A, 0x1B68637B), U64(0x3DFDCE7A, 0xA3C673B0), /* ~= 10^-143 */ U64(0x9C1661A6, 0x51213E2D), U64(0x06BEA10C, 0xA65C084E), /* ~= 10^-142 */ U64(0xC31BFA0F, 0xE5698DB8), U64(0x486E494F, 0xCFF30A62), /* ~= 10^-141 */ U64(0xF3E2F893, 0xDEC3F126), U64(0x5A89DBA3, 0xC3EFCCFA), /* ~= 10^-140 */ U64(0x986DDB5C, 0x6B3A76B7), U64(0xF8962946, 0x5A75E01C), /* ~= 10^-139 */ U64(0xBE895233, 0x86091465), U64(0xF6BBB397, 0xF1135823), /* ~= 10^-138 */ U64(0xEE2BA6C0, 0x678B597F), U64(0x746AA07D, 0xED582E2C), /* ~= 10^-137 */ U64(0x94DB4838, 0x40B717EF), U64(0xA8C2A44E, 0xB4571CDC), /* ~= 10^-136 */ U64(0xBA121A46, 0x50E4DDEB), U64(0x92F34D62, 0x616CE413), /* ~= 10^-135 */ U64(0xE896A0D7, 0xE51E1566), U64(0x77B020BA, 0xF9C81D17), /* ~= 10^-134 */ U64(0x915E2486, 0xEF32CD60), U64(0x0ACE1474, 0xDC1D122E), /* ~= 10^-133 */ U64(0xB5B5ADA8, 0xAAFF80B8), U64(0x0D819992, 0x132456BA), /* ~= 10^-132 */ U64(0xE3231912, 0xD5BF60E6), U64(0x10E1FFF6, 0x97ED6C69), /* ~= 10^-131 */ U64(0x8DF5EFAB, 0xC5979C8F), U64(0xCA8D3FFA, 0x1EF463C1), /* ~= 10^-130 */ U64(0xB1736B96, 0xB6FD83B3), U64(0xBD308FF8, 0xA6B17CB2), /* ~= 10^-129 */ U64(0xDDD0467C, 0x64BCE4A0), U64(0xAC7CB3F6, 0xD05DDBDE), /* ~= 10^-128 */ U64(0x8AA22C0D, 0xBEF60EE4), U64(0x6BCDF07A, 0x423AA96B), /* ~= 10^-127 */ U64(0xAD4AB711, 0x2EB3929D), U64(0x86C16C98, 0xD2C953C6), /* ~= 10^-126 */ U64(0xD89D64D5, 0x7A607744), U64(0xE871C7BF, 0x077BA8B7), /* ~= 10^-125 */ U64(0x87625F05, 0x6C7C4A8B), U64(0x11471CD7, 0x64AD4972), /* ~= 10^-124 */ U64(0xA93AF6C6, 0xC79B5D2D), U64(0xD598E40D, 0x3DD89BCF), /* ~= 10^-123 */ U64(0xD389B478, 0x79823479), U64(0x4AFF1D10, 0x8D4EC2C3), /* ~= 10^-122 */ U64(0x843610CB, 0x4BF160CB), U64(0xCEDF722A, 0x585139BA), /* ~= 10^-121 */ U64(0xA54394FE, 0x1EEDB8FE), U64(0xC2974EB4, 0xEE658828), /* ~= 10^-120 */ U64(0xCE947A3D, 0xA6A9273E), U64(0x733D2262, 0x29FEEA32), /* ~= 10^-119 */ U64(0x811CCC66, 0x8829B887), U64(0x0806357D, 0x5A3F525F), /* ~= 10^-118 */ U64(0xA163FF80, 0x2A3426A8), U64(0xCA07C2DC, 0xB0CF26F7), /* ~= 10^-117 */ U64(0xC9BCFF60, 0x34C13052), U64(0xFC89B393, 0xDD02F0B5), /* ~= 10^-116 */ U64(0xFC2C3F38, 0x41F17C67), U64(0xBBAC2078, 0xD443ACE2), /* ~= 10^-115 */ U64(0x9D9BA783, 0x2936EDC0), U64(0xD54B944B, 0x84AA4C0D), /* ~= 10^-114 */ U64(0xC5029163, 0xF384A931), U64(0x0A9E795E, 0x65D4DF11), /* ~= 10^-113 */ U64(0xF64335BC, 0xF065D37D), U64(0x4D4617B5, 0xFF4A16D5), /* ~= 10^-112 */ U64(0x99EA0196, 0x163FA42E), U64(0x504BCED1, 0xBF8E4E45), /* ~= 10^-111 */ U64(0xC06481FB, 0x9BCF8D39), U64(0xE45EC286, 0x2F71E1D6), /* ~= 10^-110 */ U64(0xF07DA27A, 0x82C37088), U64(0x5D767327, 0xBB4E5A4C), /* ~= 10^-109 */ U64(0x964E858C, 0x91BA2655), U64(0x3A6A07F8, 0xD510F86F), /* ~= 10^-108 */ U64(0xBBE226EF, 0xB628AFEA), U64(0x890489F7, 0x0A55368B), /* ~= 10^-107 */ U64(0xEADAB0AB, 0xA3B2DBE5), U64(0x2B45AC74, 0xCCEA842E), /* ~= 10^-106 */ U64(0x92C8AE6B, 0x464FC96F), U64(0x3B0B8BC9, 0x0012929D), /* ~= 10^-105 */ U64(0xB77ADA06, 0x17E3BBCB), U64(0x09CE6EBB, 0x40173744), /* ~= 10^-104 */ U64(0xE5599087, 0x9DDCAABD), U64(0xCC420A6A, 0x101D0515), /* ~= 10^-103 */ U64(0x8F57FA54, 0xC2A9EAB6), U64(0x9FA94682, 0x4A12232D), /* ~= 10^-102 */ U64(0xB32DF8E9, 0xF3546564), U64(0x47939822, 0xDC96ABF9), /* ~= 10^-101 */ U64(0xDFF97724, 0x70297EBD), U64(0x59787E2B, 0x93BC56F7), /* ~= 10^-100 */ U64(0x8BFBEA76, 0xC619EF36), U64(0x57EB4EDB, 0x3C55B65A), /* ~= 10^-99 */ U64(0xAEFAE514, 0x77A06B03), U64(0xEDE62292, 0x0B6B23F1), /* ~= 10^-98 */ U64(0xDAB99E59, 0x958885C4), U64(0xE95FAB36, 0x8E45ECED), /* ~= 10^-97 */ U64(0x88B402F7, 0xFD75539B), U64(0x11DBCB02, 0x18EBB414), /* ~= 10^-96 */ U64(0xAAE103B5, 0xFCD2A881), U64(0xD652BDC2, 0x9F26A119), /* ~= 10^-95 */ U64(0xD59944A3, 0x7C0752A2), U64(0x4BE76D33, 0x46F0495F), /* ~= 10^-94 */ U64(0x857FCAE6, 0x2D8493A5), U64(0x6F70A440, 0x0C562DDB), /* ~= 10^-93 */ U64(0xA6DFBD9F, 0xB8E5B88E), U64(0xCB4CCD50, 0x0F6BB952), /* ~= 10^-92 */ U64(0xD097AD07, 0xA71F26B2), U64(0x7E2000A4, 0x1346A7A7), /* ~= 10^-91 */ U64(0x825ECC24, 0xC873782F), U64(0x8ED40066, 0x8C0C28C8), /* ~= 10^-90 */ U64(0xA2F67F2D, 0xFA90563B), U64(0x72890080, 0x2F0F32FA), /* ~= 10^-89 */ U64(0xCBB41EF9, 0x79346BCA), U64(0x4F2B40A0, 0x3AD2FFB9), /* ~= 10^-88 */ U64(0xFEA126B7, 0xD78186BC), U64(0xE2F610C8, 0x4987BFA8), /* ~= 10^-87 */ U64(0x9F24B832, 0xE6B0F436), U64(0x0DD9CA7D, 0x2DF4D7C9), /* ~= 10^-86 */ U64(0xC6EDE63F, 0xA05D3143), U64(0x91503D1C, 0x79720DBB), /* ~= 10^-85 */ U64(0xF8A95FCF, 0x88747D94), U64(0x75A44C63, 0x97CE912A), /* ~= 10^-84 */ U64(0x9B69DBE1, 0xB548CE7C), U64(0xC986AFBE, 0x3EE11ABA), /* ~= 10^-83 */ U64(0xC24452DA, 0x229B021B), U64(0xFBE85BAD, 0xCE996168), /* ~= 10^-82 */ U64(0xF2D56790, 0xAB41C2A2), U64(0xFAE27299, 0x423FB9C3), /* ~= 10^-81 */ U64(0x97C560BA, 0x6B0919A5), U64(0xDCCD879F, 0xC967D41A), /* ~= 10^-80 */ U64(0xBDB6B8E9, 0x05CB600F), U64(0x5400E987, 0xBBC1C920), /* ~= 10^-79 */ U64(0xED246723, 0x473E3813), U64(0x290123E9, 0xAAB23B68), /* ~= 10^-78 */ U64(0x9436C076, 0x0C86E30B), U64(0xF9A0B672, 0x0AAF6521), /* ~= 10^-77 */ U64(0xB9447093, 0x8FA89BCE), U64(0xF808E40E, 0x8D5B3E69), /* ~= 10^-76 */ U64(0xE7958CB8, 0x7392C2C2), U64(0xB60B1D12, 0x30B20E04), /* ~= 10^-75 */ U64(0x90BD77F3, 0x483BB9B9), U64(0xB1C6F22B, 0x5E6F48C2), /* ~= 10^-74 */ U64(0xB4ECD5F0, 0x1A4AA828), U64(0x1E38AEB6, 0x360B1AF3), /* ~= 10^-73 */ U64(0xE2280B6C, 0x20DD5232), U64(0x25C6DA63, 0xC38DE1B0), /* ~= 10^-72 */ U64(0x8D590723, 0x948A535F), U64(0x579C487E, 0x5A38AD0E), /* ~= 10^-71 */ U64(0xB0AF48EC, 0x79ACE837), U64(0x2D835A9D, 0xF0C6D851), /* ~= 10^-70 */ U64(0xDCDB1B27, 0x98182244), U64(0xF8E43145, 0x6CF88E65), /* ~= 10^-69 */ U64(0x8A08F0F8, 0xBF0F156B), U64(0x1B8E9ECB, 0x641B58FF), /* ~= 10^-68 */ U64(0xAC8B2D36, 0xEED2DAC5), U64(0xE272467E, 0x3D222F3F), /* ~= 10^-67 */ U64(0xD7ADF884, 0xAA879177), U64(0x5B0ED81D, 0xCC6ABB0F), /* ~= 10^-66 */ U64(0x86CCBB52, 0xEA94BAEA), U64(0x98E94712, 0x9FC2B4E9), /* ~= 10^-65 */ U64(0xA87FEA27, 0xA539E9A5), U64(0x3F2398D7, 0x47B36224), /* ~= 10^-64 */ U64(0xD29FE4B1, 0x8E88640E), U64(0x8EEC7F0D, 0x19A03AAD), /* ~= 10^-63 */ U64(0x83A3EEEE, 0xF9153E89), U64(0x1953CF68, 0x300424AC), /* ~= 10^-62 */ U64(0xA48CEAAA, 0xB75A8E2B), U64(0x5FA8C342, 0x3C052DD7), /* ~= 10^-61 */ U64(0xCDB02555, 0x653131B6), U64(0x3792F412, 0xCB06794D), /* ~= 10^-60 */ U64(0x808E1755, 0x5F3EBF11), U64(0xE2BBD88B, 0xBEE40BD0), /* ~= 10^-59 */ U64(0xA0B19D2A, 0xB70E6ED6), U64(0x5B6ACEAE, 0xAE9D0EC4), /* ~= 10^-58 */ U64(0xC8DE0475, 0x64D20A8B), U64(0xF245825A, 0x5A445275), /* ~= 10^-57 */ U64(0xFB158592, 0xBE068D2E), U64(0xEED6E2F0, 0xF0D56712), /* ~= 10^-56 */ U64(0x9CED737B, 0xB6C4183D), U64(0x55464DD6, 0x9685606B), /* ~= 10^-55 */ U64(0xC428D05A, 0xA4751E4C), U64(0xAA97E14C, 0x3C26B886), /* ~= 10^-54 */ U64(0xF5330471, 0x4D9265DF), U64(0xD53DD99F, 0x4B3066A8), /* ~= 10^-53 */ U64(0x993FE2C6, 0xD07B7FAB), U64(0xE546A803, 0x8EFE4029), /* ~= 10^-52 */ U64(0xBF8FDB78, 0x849A5F96), U64(0xDE985204, 0x72BDD033), /* ~= 10^-51 */ U64(0xEF73D256, 0xA5C0F77C), U64(0x963E6685, 0x8F6D4440), /* ~= 10^-50 */ U64(0x95A86376, 0x27989AAD), U64(0xDDE70013, 0x79A44AA8), /* ~= 10^-49 */ U64(0xBB127C53, 0xB17EC159), U64(0x5560C018, 0x580D5D52), /* ~= 10^-48 */ U64(0xE9D71B68, 0x9DDE71AF), U64(0xAAB8F01E, 0x6E10B4A6), /* ~= 10^-47 */ U64(0x92267121, 0x62AB070D), U64(0xCAB39613, 0x04CA70E8), /* ~= 10^-46 */ U64(0xB6B00D69, 0xBB55C8D1), U64(0x3D607B97, 0xC5FD0D22), /* ~= 10^-45 */ U64(0xE45C10C4, 0x2A2B3B05), U64(0x8CB89A7D, 0xB77C506A), /* ~= 10^-44 */ U64(0x8EB98A7A, 0x9A5B04E3), U64(0x77F3608E, 0x92ADB242), /* ~= 10^-43 */ U64(0xB267ED19, 0x40F1C61C), U64(0x55F038B2, 0x37591ED3), /* ~= 10^-42 */ U64(0xDF01E85F, 0x912E37A3), U64(0x6B6C46DE, 0xC52F6688), /* ~= 10^-41 */ U64(0x8B61313B, 0xBABCE2C6), U64(0x2323AC4B, 0x3B3DA015), /* ~= 10^-40 */ U64(0xAE397D8A, 0xA96C1B77), U64(0xABEC975E, 0x0A0D081A), /* ~= 10^-39 */ U64(0xD9C7DCED, 0x53C72255), U64(0x96E7BD35, 0x8C904A21), /* ~= 10^-38 */ U64(0x881CEA14, 0x545C7575), U64(0x7E50D641, 0x77DA2E54), /* ~= 10^-37 */ U64(0xAA242499, 0x697392D2), U64(0xDDE50BD1, 0xD5D0B9E9), /* ~= 10^-36 */ U64(0xD4AD2DBF, 0xC3D07787), U64(0x955E4EC6, 0x4B44E864), /* ~= 10^-35 */ U64(0x84EC3C97, 0xDA624AB4), U64(0xBD5AF13B, 0xEF0B113E), /* ~= 10^-34 */ U64(0xA6274BBD, 0xD0FADD61), U64(0xECB1AD8A, 0xEACDD58E), /* ~= 10^-33 */ U64(0xCFB11EAD, 0x453994BA), U64(0x67DE18ED, 0xA5814AF2), /* ~= 10^-32 */ U64(0x81CEB32C, 0x4B43FCF4), U64(0x80EACF94, 0x8770CED7), /* ~= 10^-31 */ U64(0xA2425FF7, 0x5E14FC31), U64(0xA1258379, 0xA94D028D), /* ~= 10^-30 */ U64(0xCAD2F7F5, 0x359A3B3E), U64(0x096EE458, 0x13A04330), /* ~= 10^-29 */ U64(0xFD87B5F2, 0x8300CA0D), U64(0x8BCA9D6E, 0x188853FC), /* ~= 10^-28 */ U64(0x9E74D1B7, 0x91E07E48), U64(0x775EA264, 0xCF55347D), /* ~= 10^-27 */ U64(0xC6120625, 0x76589DDA), U64(0x95364AFE, 0x032A819D), /* ~= 10^-26 */ U64(0xF79687AE, 0xD3EEC551), U64(0x3A83DDBD, 0x83F52204), /* ~= 10^-25 */ U64(0x9ABE14CD, 0x44753B52), U64(0xC4926A96, 0x72793542), /* ~= 10^-24 */ U64(0xC16D9A00, 0x95928A27), U64(0x75B7053C, 0x0F178293), /* ~= 10^-23 */ U64(0xF1C90080, 0xBAF72CB1), U64(0x5324C68B, 0x12DD6338), /* ~= 10^-22 */ U64(0x971DA050, 0x74DA7BEE), U64(0xD3F6FC16, 0xEBCA5E03), /* ~= 10^-21 */ U64(0xBCE50864, 0x92111AEA), U64(0x88F4BB1C, 0xA6BCF584), /* ~= 10^-20 */ U64(0xEC1E4A7D, 0xB69561A5), U64(0x2B31E9E3, 0xD06C32E5), /* ~= 10^-19 */ U64(0x9392EE8E, 0x921D5D07), U64(0x3AFF322E, 0x62439FCF), /* ~= 10^-18 */ U64(0xB877AA32, 0x36A4B449), U64(0x09BEFEB9, 0xFAD487C2), /* ~= 10^-17 */ U64(0xE69594BE, 0xC44DE15B), U64(0x4C2EBE68, 0x7989A9B3), /* ~= 10^-16 */ U64(0x901D7CF7, 0x3AB0ACD9), U64(0x0F9D3701, 0x4BF60A10), /* ~= 10^-15 */ U64(0xB424DC35, 0x095CD80F), U64(0x538484C1, 0x9EF38C94), /* ~= 10^-14 */ U64(0xE12E1342, 0x4BB40E13), U64(0x2865A5F2, 0x06B06FB9), /* ~= 10^-13 */ U64(0x8CBCCC09, 0x6F5088CB), U64(0xF93F87B7, 0x442E45D3), /* ~= 10^-12 */ U64(0xAFEBFF0B, 0xCB24AAFE), U64(0xF78F69A5, 0x1539D748), /* ~= 10^-11 */ U64(0xDBE6FECE, 0xBDEDD5BE), U64(0xB573440E, 0x5A884D1B), /* ~= 10^-10 */ U64(0x89705F41, 0x36B4A597), U64(0x31680A88, 0xF8953030), /* ~= 10^-9 */ U64(0xABCC7711, 0x8461CEFC), U64(0xFDC20D2B, 0x36BA7C3D), /* ~= 10^-8 */ U64(0xD6BF94D5, 0xE57A42BC), U64(0x3D329076, 0x04691B4C), /* ~= 10^-7 */ U64(0x8637BD05, 0xAF6C69B5), U64(0xA63F9A49, 0xC2C1B10F), /* ~= 10^-6 */ U64(0xA7C5AC47, 0x1B478423), U64(0x0FCF80DC, 0x33721D53), /* ~= 10^-5 */ U64(0xD1B71758, 0xE219652B), U64(0xD3C36113, 0x404EA4A8), /* ~= 10^-4 */ U64(0x83126E97, 0x8D4FDF3B), U64(0x645A1CAC, 0x083126E9), /* ~= 10^-3 */ U64(0xA3D70A3D, 0x70A3D70A), U64(0x3D70A3D7, 0x0A3D70A3), /* ~= 10^-2 */ U64(0xCCCCCCCC, 0xCCCCCCCC), U64(0xCCCCCCCC, 0xCCCCCCCC), /* ~= 10^-1 */ U64(0x80000000, 0x00000000), U64(0x00000000, 0x00000000), /* == 10^0 */ U64(0xA0000000, 0x00000000), U64(0x00000000, 0x00000000), /* == 10^1 */ U64(0xC8000000, 0x00000000), U64(0x00000000, 0x00000000), /* == 10^2 */ U64(0xFA000000, 0x00000000), U64(0x00000000, 0x00000000), /* == 10^3 */ U64(0x9C400000, 0x00000000), U64(0x00000000, 0x00000000), /* == 10^4 */ U64(0xC3500000, 0x00000000), U64(0x00000000, 0x00000000), /* == 10^5 */ U64(0xF4240000, 0x00000000), U64(0x00000000, 0x00000000), /* == 10^6 */ U64(0x98968000, 0x00000000), U64(0x00000000, 0x00000000), /* == 10^7 */ U64(0xBEBC2000, 0x00000000), U64(0x00000000, 0x00000000), /* == 10^8 */ U64(0xEE6B2800, 0x00000000), U64(0x00000000, 0x00000000), /* == 10^9 */ U64(0x9502F900, 0x00000000), U64(0x00000000, 0x00000000), /* == 10^10 */ U64(0xBA43B740, 0x00000000), U64(0x00000000, 0x00000000), /* == 10^11 */ U64(0xE8D4A510, 0x00000000), U64(0x00000000, 0x00000000), /* == 10^12 */ U64(0x9184E72A, 0x00000000), U64(0x00000000, 0x00000000), /* == 10^13 */ U64(0xB5E620F4, 0x80000000), U64(0x00000000, 0x00000000), /* == 10^14 */ U64(0xE35FA931, 0xA0000000), U64(0x00000000, 0x00000000), /* == 10^15 */ U64(0x8E1BC9BF, 0x04000000), U64(0x00000000, 0x00000000), /* == 10^16 */ U64(0xB1A2BC2E, 0xC5000000), U64(0x00000000, 0x00000000), /* == 10^17 */ U64(0xDE0B6B3A, 0x76400000), U64(0x00000000, 0x00000000), /* == 10^18 */ U64(0x8AC72304, 0x89E80000), U64(0x00000000, 0x00000000), /* == 10^19 */ U64(0xAD78EBC5, 0xAC620000), U64(0x00000000, 0x00000000), /* == 10^20 */ U64(0xD8D726B7, 0x177A8000), U64(0x00000000, 0x00000000), /* == 10^21 */ U64(0x87867832, 0x6EAC9000), U64(0x00000000, 0x00000000), /* == 10^22 */ U64(0xA968163F, 0x0A57B400), U64(0x00000000, 0x00000000), /* == 10^23 */ U64(0xD3C21BCE, 0xCCEDA100), U64(0x00000000, 0x00000000), /* == 10^24 */ U64(0x84595161, 0x401484A0), U64(0x00000000, 0x00000000), /* == 10^25 */ U64(0xA56FA5B9, 0x9019A5C8), U64(0x00000000, 0x00000000), /* == 10^26 */ U64(0xCECB8F27, 0xF4200F3A), U64(0x00000000, 0x00000000), /* == 10^27 */ U64(0x813F3978, 0xF8940984), U64(0x40000000, 0x00000000), /* == 10^28 */ U64(0xA18F07D7, 0x36B90BE5), U64(0x50000000, 0x00000000), /* == 10^29 */ U64(0xC9F2C9CD, 0x04674EDE), U64(0xA4000000, 0x00000000), /* == 10^30 */ U64(0xFC6F7C40, 0x45812296), U64(0x4D000000, 0x00000000), /* == 10^31 */ U64(0x9DC5ADA8, 0x2B70B59D), U64(0xF0200000, 0x00000000), /* == 10^32 */ U64(0xC5371912, 0x364CE305), U64(0x6C280000, 0x00000000), /* == 10^33 */ U64(0xF684DF56, 0xC3E01BC6), U64(0xC7320000, 0x00000000), /* == 10^34 */ U64(0x9A130B96, 0x3A6C115C), U64(0x3C7F4000, 0x00000000), /* == 10^35 */ U64(0xC097CE7B, 0xC90715B3), U64(0x4B9F1000, 0x00000000), /* == 10^36 */ U64(0xF0BDC21A, 0xBB48DB20), U64(0x1E86D400, 0x00000000), /* == 10^37 */ U64(0x96769950, 0xB50D88F4), U64(0x13144480, 0x00000000), /* == 10^38 */ U64(0xBC143FA4, 0xE250EB31), U64(0x17D955A0, 0x00000000), /* == 10^39 */ U64(0xEB194F8E, 0x1AE525FD), U64(0x5DCFAB08, 0x00000000), /* == 10^40 */ U64(0x92EFD1B8, 0xD0CF37BE), U64(0x5AA1CAE5, 0x00000000), /* == 10^41 */ U64(0xB7ABC627, 0x050305AD), U64(0xF14A3D9E, 0x40000000), /* == 10^42 */ U64(0xE596B7B0, 0xC643C719), U64(0x6D9CCD05, 0xD0000000), /* == 10^43 */ U64(0x8F7E32CE, 0x7BEA5C6F), U64(0xE4820023, 0xA2000000), /* == 10^44 */ U64(0xB35DBF82, 0x1AE4F38B), U64(0xDDA2802C, 0x8A800000), /* == 10^45 */ U64(0xE0352F62, 0xA19E306E), U64(0xD50B2037, 0xAD200000), /* == 10^46 */ U64(0x8C213D9D, 0xA502DE45), U64(0x4526F422, 0xCC340000), /* == 10^47 */ U64(0xAF298D05, 0x0E4395D6), U64(0x9670B12B, 0x7F410000), /* == 10^48 */ U64(0xDAF3F046, 0x51D47B4C), U64(0x3C0CDD76, 0x5F114000), /* == 10^49 */ U64(0x88D8762B, 0xF324CD0F), U64(0xA5880A69, 0xFB6AC800), /* == 10^50 */ U64(0xAB0E93B6, 0xEFEE0053), U64(0x8EEA0D04, 0x7A457A00), /* == 10^51 */ U64(0xD5D238A4, 0xABE98068), U64(0x72A49045, 0x98D6D880), /* == 10^52 */ U64(0x85A36366, 0xEB71F041), U64(0x47A6DA2B, 0x7F864750), /* == 10^53 */ U64(0xA70C3C40, 0xA64E6C51), U64(0x999090B6, 0x5F67D924), /* == 10^54 */ U64(0xD0CF4B50, 0xCFE20765), U64(0xFFF4B4E3, 0xF741CF6D), /* == 10^55 */ U64(0x82818F12, 0x81ED449F), U64(0xBFF8F10E, 0x7A8921A4), /* ~= 10^56 */ U64(0xA321F2D7, 0x226895C7), U64(0xAFF72D52, 0x192B6A0D), /* ~= 10^57 */ U64(0xCBEA6F8C, 0xEB02BB39), U64(0x9BF4F8A6, 0x9F764490), /* ~= 10^58 */ U64(0xFEE50B70, 0x25C36A08), U64(0x02F236D0, 0x4753D5B4), /* ~= 10^59 */ U64(0x9F4F2726, 0x179A2245), U64(0x01D76242, 0x2C946590), /* ~= 10^60 */ U64(0xC722F0EF, 0x9D80AAD6), U64(0x424D3AD2, 0xB7B97EF5), /* ~= 10^61 */ U64(0xF8EBAD2B, 0x84E0D58B), U64(0xD2E08987, 0x65A7DEB2), /* ~= 10^62 */ U64(0x9B934C3B, 0x330C8577), U64(0x63CC55F4, 0x9F88EB2F), /* ~= 10^63 */ U64(0xC2781F49, 0xFFCFA6D5), U64(0x3CBF6B71, 0xC76B25FB), /* ~= 10^64 */ U64(0xF316271C, 0x7FC3908A), U64(0x8BEF464E, 0x3945EF7A), /* ~= 10^65 */ U64(0x97EDD871, 0xCFDA3A56), U64(0x97758BF0, 0xE3CBB5AC), /* ~= 10^66 */ U64(0xBDE94E8E, 0x43D0C8EC), U64(0x3D52EEED, 0x1CBEA317), /* ~= 10^67 */ U64(0xED63A231, 0xD4C4FB27), U64(0x4CA7AAA8, 0x63EE4BDD), /* ~= 10^68 */ U64(0x945E455F, 0x24FB1CF8), U64(0x8FE8CAA9, 0x3E74EF6A), /* ~= 10^69 */ U64(0xB975D6B6, 0xEE39E436), U64(0xB3E2FD53, 0x8E122B44), /* ~= 10^70 */ U64(0xE7D34C64, 0xA9C85D44), U64(0x60DBBCA8, 0x7196B616), /* ~= 10^71 */ U64(0x90E40FBE, 0xEA1D3A4A), U64(0xBC8955E9, 0x46FE31CD), /* ~= 10^72 */ U64(0xB51D13AE, 0xA4A488DD), U64(0x6BABAB63, 0x98BDBE41), /* ~= 10^73 */ U64(0xE264589A, 0x4DCDAB14), U64(0xC696963C, 0x7EED2DD1), /* ~= 10^74 */ U64(0x8D7EB760, 0x70A08AEC), U64(0xFC1E1DE5, 0xCF543CA2), /* ~= 10^75 */ U64(0xB0DE6538, 0x8CC8ADA8), U64(0x3B25A55F, 0x43294BCB), /* ~= 10^76 */ U64(0xDD15FE86, 0xAFFAD912), U64(0x49EF0EB7, 0x13F39EBE), /* ~= 10^77 */ U64(0x8A2DBF14, 0x2DFCC7AB), U64(0x6E356932, 0x6C784337), /* ~= 10^78 */ U64(0xACB92ED9, 0x397BF996), U64(0x49C2C37F, 0x07965404), /* ~= 10^79 */ U64(0xD7E77A8F, 0x87DAF7FB), U64(0xDC33745E, 0xC97BE906), /* ~= 10^80 */ U64(0x86F0AC99, 0xB4E8DAFD), U64(0x69A028BB, 0x3DED71A3), /* ~= 10^81 */ U64(0xA8ACD7C0, 0x222311BC), U64(0xC40832EA, 0x0D68CE0C), /* ~= 10^82 */ U64(0xD2D80DB0, 0x2AABD62B), U64(0xF50A3FA4, 0x90C30190), /* ~= 10^83 */ U64(0x83C7088E, 0x1AAB65DB), U64(0x792667C6, 0xDA79E0FA), /* ~= 10^84 */ U64(0xA4B8CAB1, 0xA1563F52), U64(0x577001B8, 0x91185938), /* ~= 10^85 */ U64(0xCDE6FD5E, 0x09ABCF26), U64(0xED4C0226, 0xB55E6F86), /* ~= 10^86 */ U64(0x80B05E5A, 0xC60B6178), U64(0x544F8158, 0x315B05B4), /* ~= 10^87 */ U64(0xA0DC75F1, 0x778E39D6), U64(0x696361AE, 0x3DB1C721), /* ~= 10^88 */ U64(0xC913936D, 0xD571C84C), U64(0x03BC3A19, 0xCD1E38E9), /* ~= 10^89 */ U64(0xFB587849, 0x4ACE3A5F), U64(0x04AB48A0, 0x4065C723), /* ~= 10^90 */ U64(0x9D174B2D, 0xCEC0E47B), U64(0x62EB0D64, 0x283F9C76), /* ~= 10^91 */ U64(0xC45D1DF9, 0x42711D9A), U64(0x3BA5D0BD, 0x324F8394), /* ~= 10^92 */ U64(0xF5746577, 0x930D6500), U64(0xCA8F44EC, 0x7EE36479), /* ~= 10^93 */ U64(0x9968BF6A, 0xBBE85F20), U64(0x7E998B13, 0xCF4E1ECB), /* ~= 10^94 */ U64(0xBFC2EF45, 0x6AE276E8), U64(0x9E3FEDD8, 0xC321A67E), /* ~= 10^95 */ U64(0xEFB3AB16, 0xC59B14A2), U64(0xC5CFE94E, 0xF3EA101E), /* ~= 10^96 */ U64(0x95D04AEE, 0x3B80ECE5), U64(0xBBA1F1D1, 0x58724A12), /* ~= 10^97 */ U64(0xBB445DA9, 0xCA61281F), U64(0x2A8A6E45, 0xAE8EDC97), /* ~= 10^98 */ U64(0xEA157514, 0x3CF97226), U64(0xF52D09D7, 0x1A3293BD), /* ~= 10^99 */ U64(0x924D692C, 0xA61BE758), U64(0x593C2626, 0x705F9C56), /* ~= 10^100 */ U64(0xB6E0C377, 0xCFA2E12E), U64(0x6F8B2FB0, 0x0C77836C), /* ~= 10^101 */ U64(0xE498F455, 0xC38B997A), U64(0x0B6DFB9C, 0x0F956447), /* ~= 10^102 */ U64(0x8EDF98B5, 0x9A373FEC), U64(0x4724BD41, 0x89BD5EAC), /* ~= 10^103 */ U64(0xB2977EE3, 0x00C50FE7), U64(0x58EDEC91, 0xEC2CB657), /* ~= 10^104 */ U64(0xDF3D5E9B, 0xC0F653E1), U64(0x2F2967B6, 0x6737E3ED), /* ~= 10^105 */ U64(0x8B865B21, 0x5899F46C), U64(0xBD79E0D2, 0x0082EE74), /* ~= 10^106 */ U64(0xAE67F1E9, 0xAEC07187), U64(0xECD85906, 0x80A3AA11), /* ~= 10^107 */ U64(0xDA01EE64, 0x1A708DE9), U64(0xE80E6F48, 0x20CC9495), /* ~= 10^108 */ U64(0x884134FE, 0x908658B2), U64(0x3109058D, 0x147FDCDD), /* ~= 10^109 */ U64(0xAA51823E, 0x34A7EEDE), U64(0xBD4B46F0, 0x599FD415), /* ~= 10^110 */ U64(0xD4E5E2CD, 0xC1D1EA96), U64(0x6C9E18AC, 0x7007C91A), /* ~= 10^111 */ U64(0x850FADC0, 0x9923329E), U64(0x03E2CF6B, 0xC604DDB0), /* ~= 10^112 */ U64(0xA6539930, 0xBF6BFF45), U64(0x84DB8346, 0xB786151C), /* ~= 10^113 */ U64(0xCFE87F7C, 0xEF46FF16), U64(0xE6126418, 0x65679A63), /* ~= 10^114 */ U64(0x81F14FAE, 0x158C5F6E), U64(0x4FCB7E8F, 0x3F60C07E), /* ~= 10^115 */ U64(0xA26DA399, 0x9AEF7749), U64(0xE3BE5E33, 0x0F38F09D), /* ~= 10^116 */ U64(0xCB090C80, 0x01AB551C), U64(0x5CADF5BF, 0xD3072CC5), /* ~= 10^117 */ U64(0xFDCB4FA0, 0x02162A63), U64(0x73D9732F, 0xC7C8F7F6), /* ~= 10^118 */ U64(0x9E9F11C4, 0x014DDA7E), U64(0x2867E7FD, 0xDCDD9AFA), /* ~= 10^119 */ U64(0xC646D635, 0x01A1511D), U64(0xB281E1FD, 0x541501B8), /* ~= 10^120 */ U64(0xF7D88BC2, 0x4209A565), U64(0x1F225A7C, 0xA91A4226), /* ~= 10^121 */ U64(0x9AE75759, 0x6946075F), U64(0x3375788D, 0xE9B06958), /* ~= 10^122 */ U64(0xC1A12D2F, 0xC3978937), U64(0x0052D6B1, 0x641C83AE), /* ~= 10^123 */ U64(0xF209787B, 0xB47D6B84), U64(0xC0678C5D, 0xBD23A49A), /* ~= 10^124 */ U64(0x9745EB4D, 0x50CE6332), U64(0xF840B7BA, 0x963646E0), /* ~= 10^125 */ U64(0xBD176620, 0xA501FBFF), U64(0xB650E5A9, 0x3BC3D898), /* ~= 10^126 */ U64(0xEC5D3FA8, 0xCE427AFF), U64(0xA3E51F13, 0x8AB4CEBE), /* ~= 10^127 */ U64(0x93BA47C9, 0x80E98CDF), U64(0xC66F336C, 0x36B10137), /* ~= 10^128 */ U64(0xB8A8D9BB, 0xE123F017), U64(0xB80B0047, 0x445D4184), /* ~= 10^129 */ U64(0xE6D3102A, 0xD96CEC1D), U64(0xA60DC059, 0x157491E5), /* ~= 10^130 */ U64(0x9043EA1A, 0xC7E41392), U64(0x87C89837, 0xAD68DB2F), /* ~= 10^131 */ U64(0xB454E4A1, 0x79DD1877), U64(0x29BABE45, 0x98C311FB), /* ~= 10^132 */ U64(0xE16A1DC9, 0xD8545E94), U64(0xF4296DD6, 0xFEF3D67A), /* ~= 10^133 */ U64(0x8CE2529E, 0x2734BB1D), U64(0x1899E4A6, 0x5F58660C), /* ~= 10^134 */ U64(0xB01AE745, 0xB101E9E4), U64(0x5EC05DCF, 0xF72E7F8F), /* ~= 10^135 */ U64(0xDC21A117, 0x1D42645D), U64(0x76707543, 0xF4FA1F73), /* ~= 10^136 */ U64(0x899504AE, 0x72497EBA), U64(0x6A06494A, 0x791C53A8), /* ~= 10^137 */ U64(0xABFA45DA, 0x0EDBDE69), U64(0x0487DB9D, 0x17636892), /* ~= 10^138 */ U64(0xD6F8D750, 0x9292D603), U64(0x45A9D284, 0x5D3C42B6), /* ~= 10^139 */ U64(0x865B8692, 0x5B9BC5C2), U64(0x0B8A2392, 0xBA45A9B2), /* ~= 10^140 */ U64(0xA7F26836, 0xF282B732), U64(0x8E6CAC77, 0x68D7141E), /* ~= 10^141 */ U64(0xD1EF0244, 0xAF2364FF), U64(0x3207D795, 0x430CD926), /* ~= 10^142 */ U64(0x8335616A, 0xED761F1F), U64(0x7F44E6BD, 0x49E807B8), /* ~= 10^143 */ U64(0xA402B9C5, 0xA8D3A6E7), U64(0x5F16206C, 0x9C6209A6), /* ~= 10^144 */ U64(0xCD036837, 0x130890A1), U64(0x36DBA887, 0xC37A8C0F), /* ~= 10^145 */ U64(0x80222122, 0x6BE55A64), U64(0xC2494954, 0xDA2C9789), /* ~= 10^146 */ U64(0xA02AA96B, 0x06DEB0FD), U64(0xF2DB9BAA, 0x10B7BD6C), /* ~= 10^147 */ U64(0xC83553C5, 0xC8965D3D), U64(0x6F928294, 0x94E5ACC7), /* ~= 10^148 */ U64(0xFA42A8B7, 0x3ABBF48C), U64(0xCB772339, 0xBA1F17F9), /* ~= 10^149 */ U64(0x9C69A972, 0x84B578D7), U64(0xFF2A7604, 0x14536EFB), /* ~= 10^150 */ U64(0xC38413CF, 0x25E2D70D), U64(0xFEF51385, 0x19684ABA), /* ~= 10^151 */ U64(0xF46518C2, 0xEF5B8CD1), U64(0x7EB25866, 0x5FC25D69), /* ~= 10^152 */ U64(0x98BF2F79, 0xD5993802), U64(0xEF2F773F, 0xFBD97A61), /* ~= 10^153 */ U64(0xBEEEFB58, 0x4AFF8603), U64(0xAAFB550F, 0xFACFD8FA), /* ~= 10^154 */ U64(0xEEAABA2E, 0x5DBF6784), U64(0x95BA2A53, 0xF983CF38), /* ~= 10^155 */ U64(0x952AB45C, 0xFA97A0B2), U64(0xDD945A74, 0x7BF26183), /* ~= 10^156 */ U64(0xBA756174, 0x393D88DF), U64(0x94F97111, 0x9AEEF9E4), /* ~= 10^157 */ U64(0xE912B9D1, 0x478CEB17), U64(0x7A37CD56, 0x01AAB85D), /* ~= 10^158 */ U64(0x91ABB422, 0xCCB812EE), U64(0xAC62E055, 0xC10AB33A), /* ~= 10^159 */ U64(0xB616A12B, 0x7FE617AA), U64(0x577B986B, 0x314D6009), /* ~= 10^160 */ U64(0xE39C4976, 0x5FDF9D94), U64(0xED5A7E85, 0xFDA0B80B), /* ~= 10^161 */ U64(0x8E41ADE9, 0xFBEBC27D), U64(0x14588F13, 0xBE847307), /* ~= 10^162 */ U64(0xB1D21964, 0x7AE6B31C), U64(0x596EB2D8, 0xAE258FC8), /* ~= 10^163 */ U64(0xDE469FBD, 0x99A05FE3), U64(0x6FCA5F8E, 0xD9AEF3BB), /* ~= 10^164 */ U64(0x8AEC23D6, 0x80043BEE), U64(0x25DE7BB9, 0x480D5854), /* ~= 10^165 */ U64(0xADA72CCC, 0x20054AE9), U64(0xAF561AA7, 0x9A10AE6A), /* ~= 10^166 */ U64(0xD910F7FF, 0x28069DA4), U64(0x1B2BA151, 0x8094DA04), /* ~= 10^167 */ U64(0x87AA9AFF, 0x79042286), U64(0x90FB44D2, 0xF05D0842), /* ~= 10^168 */ U64(0xA99541BF, 0x57452B28), U64(0x353A1607, 0xAC744A53), /* ~= 10^169 */ U64(0xD3FA922F, 0x2D1675F2), U64(0x42889B89, 0x97915CE8), /* ~= 10^170 */ U64(0x847C9B5D, 0x7C2E09B7), U64(0x69956135, 0xFEBADA11), /* ~= 10^171 */ U64(0xA59BC234, 0xDB398C25), U64(0x43FAB983, 0x7E699095), /* ~= 10^172 */ U64(0xCF02B2C2, 0x1207EF2E), U64(0x94F967E4, 0x5E03F4BB), /* ~= 10^173 */ U64(0x8161AFB9, 0x4B44F57D), U64(0x1D1BE0EE, 0xBAC278F5), /* ~= 10^174 */ U64(0xA1BA1BA7, 0x9E1632DC), U64(0x6462D92A, 0x69731732), /* ~= 10^175 */ U64(0xCA28A291, 0x859BBF93), U64(0x7D7B8F75, 0x03CFDCFE), /* ~= 10^176 */ U64(0xFCB2CB35, 0xE702AF78), U64(0x5CDA7352, 0x44C3D43E), /* ~= 10^177 */ U64(0x9DEFBF01, 0xB061ADAB), U64(0x3A088813, 0x6AFA64A7), /* ~= 10^178 */ U64(0xC56BAEC2, 0x1C7A1916), U64(0x088AAA18, 0x45B8FDD0), /* ~= 10^179 */ U64(0xF6C69A72, 0xA3989F5B), U64(0x8AAD549E, 0x57273D45), /* ~= 10^180 */ U64(0x9A3C2087, 0xA63F6399), U64(0x36AC54E2, 0xF678864B), /* ~= 10^181 */ U64(0xC0CB28A9, 0x8FCF3C7F), U64(0x84576A1B, 0xB416A7DD), /* ~= 10^182 */ U64(0xF0FDF2D3, 0xF3C30B9F), U64(0x656D44A2, 0xA11C51D5), /* ~= 10^183 */ U64(0x969EB7C4, 0x7859E743), U64(0x9F644AE5, 0xA4B1B325), /* ~= 10^184 */ U64(0xBC4665B5, 0x96706114), U64(0x873D5D9F, 0x0DDE1FEE), /* ~= 10^185 */ U64(0xEB57FF22, 0xFC0C7959), U64(0xA90CB506, 0xD155A7EA), /* ~= 10^186 */ U64(0x9316FF75, 0xDD87CBD8), U64(0x09A7F124, 0x42D588F2), /* ~= 10^187 */ U64(0xB7DCBF53, 0x54E9BECE), U64(0x0C11ED6D, 0x538AEB2F), /* ~= 10^188 */ U64(0xE5D3EF28, 0x2A242E81), U64(0x8F1668C8, 0xA86DA5FA), /* ~= 10^189 */ U64(0x8FA47579, 0x1A569D10), U64(0xF96E017D, 0x694487BC), /* ~= 10^190 */ U64(0xB38D92D7, 0x60EC4455), U64(0x37C981DC, 0xC395A9AC), /* ~= 10^191 */ U64(0xE070F78D, 0x3927556A), U64(0x85BBE253, 0xF47B1417), /* ~= 10^192 */ U64(0x8C469AB8, 0x43B89562), U64(0x93956D74, 0x78CCEC8E), /* ~= 10^193 */ U64(0xAF584166, 0x54A6BABB), U64(0x387AC8D1, 0x970027B2), /* ~= 10^194 */ U64(0xDB2E51BF, 0xE9D0696A), U64(0x06997B05, 0xFCC0319E), /* ~= 10^195 */ U64(0x88FCF317, 0xF22241E2), U64(0x441FECE3, 0xBDF81F03), /* ~= 10^196 */ U64(0xAB3C2FDD, 0xEEAAD25A), U64(0xD527E81C, 0xAD7626C3), /* ~= 10^197 */ U64(0xD60B3BD5, 0x6A5586F1), U64(0x8A71E223, 0xD8D3B074), /* ~= 10^198 */ U64(0x85C70565, 0x62757456), U64(0xF6872D56, 0x67844E49), /* ~= 10^199 */ U64(0xA738C6BE, 0xBB12D16C), U64(0xB428F8AC, 0x016561DB), /* ~= 10^200 */ U64(0xD106F86E, 0x69D785C7), U64(0xE13336D7, 0x01BEBA52), /* ~= 10^201 */ U64(0x82A45B45, 0x0226B39C), U64(0xECC00246, 0x61173473), /* ~= 10^202 */ U64(0xA34D7216, 0x42B06084), U64(0x27F002D7, 0xF95D0190), /* ~= 10^203 */ U64(0xCC20CE9B, 0xD35C78A5), U64(0x31EC038D, 0xF7B441F4), /* ~= 10^204 */ U64(0xFF290242, 0xC83396CE), U64(0x7E670471, 0x75A15271), /* ~= 10^205 */ U64(0x9F79A169, 0xBD203E41), U64(0x0F0062C6, 0xE984D386), /* ~= 10^206 */ U64(0xC75809C4, 0x2C684DD1), U64(0x52C07B78, 0xA3E60868), /* ~= 10^207 */ U64(0xF92E0C35, 0x37826145), U64(0xA7709A56, 0xCCDF8A82), /* ~= 10^208 */ U64(0x9BBCC7A1, 0x42B17CCB), U64(0x88A66076, 0x400BB691), /* ~= 10^209 */ U64(0xC2ABF989, 0x935DDBFE), U64(0x6ACFF893, 0xD00EA435), /* ~= 10^210 */ U64(0xF356F7EB, 0xF83552FE), U64(0x0583F6B8, 0xC4124D43), /* ~= 10^211 */ U64(0x98165AF3, 0x7B2153DE), U64(0xC3727A33, 0x7A8B704A), /* ~= 10^212 */ U64(0xBE1BF1B0, 0x59E9A8D6), U64(0x744F18C0, 0x592E4C5C), /* ~= 10^213 */ U64(0xEDA2EE1C, 0x7064130C), U64(0x1162DEF0, 0x6F79DF73), /* ~= 10^214 */ U64(0x9485D4D1, 0xC63E8BE7), U64(0x8ADDCB56, 0x45AC2BA8), /* ~= 10^215 */ U64(0xB9A74A06, 0x37CE2EE1), U64(0x6D953E2B, 0xD7173692), /* ~= 10^216 */ U64(0xE8111C87, 0xC5C1BA99), U64(0xC8FA8DB6, 0xCCDD0437), /* ~= 10^217 */ U64(0x910AB1D4, 0xDB9914A0), U64(0x1D9C9892, 0x400A22A2), /* ~= 10^218 */ U64(0xB54D5E4A, 0x127F59C8), U64(0x2503BEB6, 0xD00CAB4B), /* ~= 10^219 */ U64(0xE2A0B5DC, 0x971F303A), U64(0x2E44AE64, 0x840FD61D), /* ~= 10^220 */ U64(0x8DA471A9, 0xDE737E24), U64(0x5CEAECFE, 0xD289E5D2), /* ~= 10^221 */ U64(0xB10D8E14, 0x56105DAD), U64(0x7425A83E, 0x872C5F47), /* ~= 10^222 */ U64(0xDD50F199, 0x6B947518), U64(0xD12F124E, 0x28F77719), /* ~= 10^223 */ U64(0x8A5296FF, 0xE33CC92F), U64(0x82BD6B70, 0xD99AAA6F), /* ~= 10^224 */ U64(0xACE73CBF, 0xDC0BFB7B), U64(0x636CC64D, 0x1001550B), /* ~= 10^225 */ U64(0xD8210BEF, 0xD30EFA5A), U64(0x3C47F7E0, 0x5401AA4E), /* ~= 10^226 */ U64(0x8714A775, 0xE3E95C78), U64(0x65ACFAEC, 0x34810A71), /* ~= 10^227 */ U64(0xA8D9D153, 0x5CE3B396), U64(0x7F1839A7, 0x41A14D0D), /* ~= 10^228 */ U64(0xD31045A8, 0x341CA07C), U64(0x1EDE4811, 0x1209A050), /* ~= 10^229 */ U64(0x83EA2B89, 0x2091E44D), U64(0x934AED0A, 0xAB460432), /* ~= 10^230 */ U64(0xA4E4B66B, 0x68B65D60), U64(0xF81DA84D, 0x5617853F), /* ~= 10^231 */ U64(0xCE1DE406, 0x42E3F4B9), U64(0x36251260, 0xAB9D668E), /* ~= 10^232 */ U64(0x80D2AE83, 0xE9CE78F3), U64(0xC1D72B7C, 0x6B426019), /* ~= 10^233 */ U64(0xA1075A24, 0xE4421730), U64(0xB24CF65B, 0x8612F81F), /* ~= 10^234 */ U64(0xC94930AE, 0x1D529CFC), U64(0xDEE033F2, 0x6797B627), /* ~= 10^235 */ U64(0xFB9B7CD9, 0xA4A7443C), U64(0x169840EF, 0x017DA3B1), /* ~= 10^236 */ U64(0x9D412E08, 0x06E88AA5), U64(0x8E1F2895, 0x60EE864E), /* ~= 10^237 */ U64(0xC491798A, 0x08A2AD4E), U64(0xF1A6F2BA, 0xB92A27E2), /* ~= 10^238 */ U64(0xF5B5D7EC, 0x8ACB58A2), U64(0xAE10AF69, 0x6774B1DB), /* ~= 10^239 */ U64(0x9991A6F3, 0xD6BF1765), U64(0xACCA6DA1, 0xE0A8EF29), /* ~= 10^240 */ U64(0xBFF610B0, 0xCC6EDD3F), U64(0x17FD090A, 0x58D32AF3), /* ~= 10^241 */ U64(0xEFF394DC, 0xFF8A948E), U64(0xDDFC4B4C, 0xEF07F5B0), /* ~= 10^242 */ U64(0x95F83D0A, 0x1FB69CD9), U64(0x4ABDAF10, 0x1564F98E), /* ~= 10^243 */ U64(0xBB764C4C, 0xA7A4440F), U64(0x9D6D1AD4, 0x1ABE37F1), /* ~= 10^244 */ U64(0xEA53DF5F, 0xD18D5513), U64(0x84C86189, 0x216DC5ED), /* ~= 10^245 */ U64(0x92746B9B, 0xE2F8552C), U64(0x32FD3CF5, 0xB4E49BB4), /* ~= 10^246 */ U64(0xB7118682, 0xDBB66A77), U64(0x3FBC8C33, 0x221DC2A1), /* ~= 10^247 */ U64(0xE4D5E823, 0x92A40515), U64(0x0FABAF3F, 0xEAA5334A), /* ~= 10^248 */ U64(0x8F05B116, 0x3BA6832D), U64(0x29CB4D87, 0xF2A7400E), /* ~= 10^249 */ U64(0xB2C71D5B, 0xCA9023F8), U64(0x743E20E9, 0xEF511012), /* ~= 10^250 */ U64(0xDF78E4B2, 0xBD342CF6), U64(0x914DA924, 0x6B255416), /* ~= 10^251 */ U64(0x8BAB8EEF, 0xB6409C1A), U64(0x1AD089B6, 0xC2F7548E), /* ~= 10^252 */ U64(0xAE9672AB, 0xA3D0C320), U64(0xA184AC24, 0x73B529B1), /* ~= 10^253 */ U64(0xDA3C0F56, 0x8CC4F3E8), U64(0xC9E5D72D, 0x90A2741E), /* ~= 10^254 */ U64(0x88658996, 0x17FB1871), U64(0x7E2FA67C, 0x7A658892), /* ~= 10^255 */ U64(0xAA7EEBFB, 0x9DF9DE8D), U64(0xDDBB901B, 0x98FEEAB7), /* ~= 10^256 */ U64(0xD51EA6FA, 0x85785631), U64(0x552A7422, 0x7F3EA565), /* ~= 10^257 */ U64(0x8533285C, 0x936B35DE), U64(0xD53A8895, 0x8F87275F), /* ~= 10^258 */ U64(0xA67FF273, 0xB8460356), U64(0x8A892ABA, 0xF368F137), /* ~= 10^259 */ U64(0xD01FEF10, 0xA657842C), U64(0x2D2B7569, 0xB0432D85), /* ~= 10^260 */ U64(0x8213F56A, 0x67F6B29B), U64(0x9C3B2962, 0x0E29FC73), /* ~= 10^261 */ U64(0xA298F2C5, 0x01F45F42), U64(0x8349F3BA, 0x91B47B8F), /* ~= 10^262 */ U64(0xCB3F2F76, 0x42717713), U64(0x241C70A9, 0x36219A73), /* ~= 10^263 */ U64(0xFE0EFB53, 0xD30DD4D7), U64(0xED238CD3, 0x83AA0110), /* ~= 10^264 */ U64(0x9EC95D14, 0x63E8A506), U64(0xF4363804, 0x324A40AA), /* ~= 10^265 */ U64(0xC67BB459, 0x7CE2CE48), U64(0xB143C605, 0x3EDCD0D5), /* ~= 10^266 */ U64(0xF81AA16F, 0xDC1B81DA), U64(0xDD94B786, 0x8E94050A), /* ~= 10^267 */ U64(0x9B10A4E5, 0xE9913128), U64(0xCA7CF2B4, 0x191C8326), /* ~= 10^268 */ U64(0xC1D4CE1F, 0x63F57D72), U64(0xFD1C2F61, 0x1F63A3F0), /* ~= 10^269 */ U64(0xF24A01A7, 0x3CF2DCCF), U64(0xBC633B39, 0x673C8CEC), /* ~= 10^270 */ U64(0x976E4108, 0x8617CA01), U64(0xD5BE0503, 0xE085D813), /* ~= 10^271 */ U64(0xBD49D14A, 0xA79DBC82), U64(0x4B2D8644, 0xD8A74E18), /* ~= 10^272 */ U64(0xEC9C459D, 0x51852BA2), U64(0xDDF8E7D6, 0x0ED1219E), /* ~= 10^273 */ U64(0x93E1AB82, 0x52F33B45), U64(0xCABB90E5, 0xC942B503), /* ~= 10^274 */ U64(0xB8DA1662, 0xE7B00A17), U64(0x3D6A751F, 0x3B936243), /* ~= 10^275 */ U64(0xE7109BFB, 0xA19C0C9D), U64(0x0CC51267, 0x0A783AD4), /* ~= 10^276 */ U64(0x906A617D, 0x450187E2), U64(0x27FB2B80, 0x668B24C5), /* ~= 10^277 */ U64(0xB484F9DC, 0x9641E9DA), U64(0xB1F9F660, 0x802DEDF6), /* ~= 10^278 */ U64(0xE1A63853, 0xBBD26451), U64(0x5E7873F8, 0xA0396973), /* ~= 10^279 */ U64(0x8D07E334, 0x55637EB2), U64(0xDB0B487B, 0x6423E1E8), /* ~= 10^280 */ U64(0xB049DC01, 0x6ABC5E5F), U64(0x91CE1A9A, 0x3D2CDA62), /* ~= 10^281 */ U64(0xDC5C5301, 0xC56B75F7), U64(0x7641A140, 0xCC7810FB), /* ~= 10^282 */ U64(0x89B9B3E1, 0x1B6329BA), U64(0xA9E904C8, 0x7FCB0A9D), /* ~= 10^283 */ U64(0xAC2820D9, 0x623BF429), U64(0x546345FA, 0x9FBDCD44), /* ~= 10^284 */ U64(0xD732290F, 0xBACAF133), U64(0xA97C1779, 0x47AD4095), /* ~= 10^285 */ U64(0x867F59A9, 0xD4BED6C0), U64(0x49ED8EAB, 0xCCCC485D), /* ~= 10^286 */ U64(0xA81F3014, 0x49EE8C70), U64(0x5C68F256, 0xBFFF5A74), /* ~= 10^287 */ U64(0xD226FC19, 0x5C6A2F8C), U64(0x73832EEC, 0x6FFF3111), /* ~= 10^288 */ U64(0x83585D8F, 0xD9C25DB7), U64(0xC831FD53, 0xC5FF7EAB), /* ~= 10^289 */ U64(0xA42E74F3, 0xD032F525), U64(0xBA3E7CA8, 0xB77F5E55), /* ~= 10^290 */ U64(0xCD3A1230, 0xC43FB26F), U64(0x28CE1BD2, 0xE55F35EB), /* ~= 10^291 */ U64(0x80444B5E, 0x7AA7CF85), U64(0x7980D163, 0xCF5B81B3), /* ~= 10^292 */ U64(0xA0555E36, 0x1951C366), U64(0xD7E105BC, 0xC332621F), /* ~= 10^293 */ U64(0xC86AB5C3, 0x9FA63440), U64(0x8DD9472B, 0xF3FEFAA7), /* ~= 10^294 */ U64(0xFA856334, 0x878FC150), U64(0xB14F98F6, 0xF0FEB951), /* ~= 10^295 */ U64(0x9C935E00, 0xD4B9D8D2), U64(0x6ED1BF9A, 0x569F33D3), /* ~= 10^296 */ U64(0xC3B83581, 0x09E84F07), U64(0x0A862F80, 0xEC4700C8), /* ~= 10^297 */ U64(0xF4A642E1, 0x4C6262C8), U64(0xCD27BB61, 0x2758C0FA), /* ~= 10^298 */ U64(0x98E7E9CC, 0xCFBD7DBD), U64(0x8038D51C, 0xB897789C), /* ~= 10^299 */ U64(0xBF21E440, 0x03ACDD2C), U64(0xE0470A63, 0xE6BD56C3), /* ~= 10^300 */ U64(0xEEEA5D50, 0x04981478), U64(0x1858CCFC, 0xE06CAC74), /* ~= 10^301 */ U64(0x95527A52, 0x02DF0CCB), U64(0x0F37801E, 0x0C43EBC8), /* ~= 10^302 */ U64(0xBAA718E6, 0x8396CFFD), U64(0xD3056025, 0x8F54E6BA), /* ~= 10^303 */ U64(0xE950DF20, 0x247C83FD), U64(0x47C6B82E, 0xF32A2069), /* ~= 10^304 */ U64(0x91D28B74, 0x16CDD27E), U64(0x4CDC331D, 0x57FA5441), /* ~= 10^305 */ U64(0xB6472E51, 0x1C81471D), U64(0xE0133FE4, 0xADF8E952), /* ~= 10^306 */ U64(0xE3D8F9E5, 0x63A198E5), U64(0x58180FDD, 0xD97723A6), /* ~= 10^307 */ U64(0x8E679C2F, 0x5E44FF8F), U64(0x570F09EA, 0xA7EA7648), /* ~= 10^308 */ U64(0xB201833B, 0x35D63F73), U64(0x2CD2CC65, 0x51E513DA), /* ~= 10^309 */ U64(0xDE81E40A, 0x034BCF4F), U64(0xF8077F7E, 0xA65E58D1), /* ~= 10^310 */ U64(0x8B112E86, 0x420F6191), U64(0xFB04AFAF, 0x27FAF782), /* ~= 10^311 */ U64(0xADD57A27, 0xD29339F6), U64(0x79C5DB9A, 0xF1F9B563), /* ~= 10^312 */ U64(0xD94AD8B1, 0xC7380874), U64(0x18375281, 0xAE7822BC), /* ~= 10^313 */ U64(0x87CEC76F, 0x1C830548), U64(0x8F229391, 0x0D0B15B5), /* ~= 10^314 */ U64(0xA9C2794A, 0xE3A3C69A), U64(0xB2EB3875, 0x504DDB22), /* ~= 10^315 */ U64(0xD433179D, 0x9C8CB841), U64(0x5FA60692, 0xA46151EB), /* ~= 10^316 */ U64(0x849FEEC2, 0x81D7F328), U64(0xDBC7C41B, 0xA6BCD333), /* ~= 10^317 */ U64(0xA5C7EA73, 0x224DEFF3), U64(0x12B9B522, 0x906C0800), /* ~= 10^318 */ U64(0xCF39E50F, 0xEAE16BEF), U64(0xD768226B, 0x34870A00), /* ~= 10^319 */ U64(0x81842F29, 0xF2CCE375), U64(0xE6A11583, 0x00D46640), /* ~= 10^320 */ U64(0xA1E53AF4, 0x6F801C53), U64(0x60495AE3, 0xC1097FD0), /* ~= 10^321 */ U64(0xCA5E89B1, 0x8B602368), U64(0x385BB19C, 0xB14BDFC4), /* ~= 10^322 */ U64(0xFCF62C1D, 0xEE382C42), U64(0x46729E03, 0xDD9ED7B5), /* ~= 10^323 */ U64(0x9E19DB92, 0xB4E31BA9), U64(0x6C07A2C2, 0x6A8346D1) /* ~= 10^324 */ }; /** Get the cached pow10 value from `pow10_sig_table`. @param exp10 The exponent of pow(10, e). This value must in range `POW10_SIG_TABLE_MIN_EXP` to `POW10_SIG_TABLE_MAX_EXP`. @param hi The highest 64 bits of pow(10, e). @param lo The lower 64 bits after `hi`. */ static_inline void pow10_table_get_sig(i32 exp10, u64 *hi, u64 *lo) { i32 idx = exp10 - (POW10_SIG_TABLE_MIN_EXP); *hi = pow10_sig_table[idx * 2]; *lo = pow10_sig_table[idx * 2 + 1]; } /** Get the exponent (base 2) for highest 64 bits significand in `pow10_sig_table`. */ static_inline void pow10_table_get_exp(i32 exp10, i32 *exp2) { /* e2 = floor(log2(pow(10, e))) - 64 + 1 */ /* = floor(e * log2(10) - 63) */ *exp2 = (exp10 * 217706 - 4128768) >> 16; } #endif /*============================================================================== * MARK: - Number and Bit Utils (Private) *============================================================================*/ /** Convert bits to double. */ static_inline f64 f64_from_bits(u64 u) { f64 f; memcpy(&f, &u, sizeof(u)); return f; } /** Convert double to bits. */ static_inline u64 f64_to_bits(f64 f) { u64 u; memcpy(&u, &f, sizeof(u)); return u; } /** Convert double to bits. */ static_inline u32 f32_to_bits(f32 f) { u32 u; memcpy(&u, &f, sizeof(u)); return u; } /** Get 'infinity' bits with sign. */ static_inline u64 f64_bits_inf(bool sign) { #if YYJSON_HAS_IEEE_754 return F64_BITS_INF | ((u64)sign << 63); #elif defined(INFINITY) return f64_to_bits(sign ? -INFINITY : INFINITY); #else return f64_to_bits(sign ? -HUGE_VAL : HUGE_VAL); #endif } /** Get 'nan' bits with sign. */ static_inline u64 f64_bits_nan(bool sign) { #if YYJSON_HAS_IEEE_754 return F64_BITS_NAN | ((u64)sign << 63); #elif defined(NAN) return f64_to_bits(sign ? (f64)-NAN : (f64)NAN); #else return f64_to_bits((sign ? -0.0 : 0.0) / 0.0); #endif } /** Casting double to float, allow overflow. */ #if yyjson_has_attribute(no_sanitize) __attribute__((no_sanitize("undefined"))) #elif yyjson_gcc_available(4, 9, 0) __attribute__((__no_sanitize_undefined__)) #endif static_inline f32 f64_to_f32(f64 val) { return (f32)val; } /** Returns the number of leading 0-bits in value (input should not be 0). */ static_inline u32 u64_lz_bits(u64 v) { #if GCC_HAS_CLZLL return (u32)__builtin_clzll(v); #elif MSC_HAS_BIT_SCAN_64 unsigned long r; _BitScanReverse64(&r, v); return (u32)63 - (u32)r; #elif MSC_HAS_BIT_SCAN unsigned long hi, lo; bool hi_set = _BitScanReverse(&hi, (u32)(v >> 32)) != 0; _BitScanReverse(&lo, (u32)v); hi |= 32; return (u32)63 - (u32)(hi_set ? hi : lo); #else /* branchless, use De Bruijn sequence */ /* see: https://www.chessprogramming.org/BitScan */ const u8 table[64] = { 63, 16, 62, 7, 15, 36, 61, 3, 6, 14, 22, 26, 35, 47, 60, 2, 9, 5, 28, 11, 13, 21, 42, 19, 25, 31, 34, 40, 46, 52, 59, 1, 17, 8, 37, 4, 23, 27, 48, 10, 29, 12, 43, 20, 32, 41, 53, 18, 38, 24, 49, 30, 44, 33, 54, 39, 50, 45, 55, 51, 56, 57, 58, 0 }; v |= v >> 1; v |= v >> 2; v |= v >> 4; v |= v >> 8; v |= v >> 16; v |= v >> 32; return table[(v * U64(0x03F79D71, 0xB4CB0A89)) >> 58]; #endif } /** Returns the number of trailing 0-bits in value (input should not be 0). */ static_inline u32 u64_tz_bits(u64 v) { #if GCC_HAS_CTZLL return (u32)__builtin_ctzll(v); #elif MSC_HAS_BIT_SCAN_64 unsigned long r; _BitScanForward64(&r, v); return (u32)r; #elif MSC_HAS_BIT_SCAN unsigned long lo, hi; bool lo_set = _BitScanForward(&lo, (u32)(v)) != 0; _BitScanForward(&hi, (u32)(v >> 32)); hi += 32; return lo_set ? lo : hi; #else /* branchless, use De Bruijn sequence */ /* see: https://www.chessprogramming.org/BitScan */ const u8 table[64] = { 0, 1, 2, 53, 3, 7, 54, 27, 4, 38, 41, 8, 34, 55, 48, 28, 62, 5, 39, 46, 44, 42, 22, 9, 24, 35, 59, 56, 49, 18, 29, 11, 63, 52, 6, 26, 37, 40, 33, 47, 61, 45, 43, 21, 23, 58, 17, 10, 51, 25, 36, 32, 60, 20, 57, 16, 50, 31, 19, 15, 30, 14, 13, 12 }; return table[((v & (~v + 1)) * U64(0x022FDD63, 0xCC95386D)) >> 58]; #endif } /** Multiplies two 64-bit unsigned integers (a * b), returns the 128-bit result as 'hi' and 'lo'. */ static_inline void u128_mul(u64 a, u64 b, u64 *hi, u64 *lo) { #if YYJSON_HAS_INT128 u128 m = (u128)a * b; *hi = (u64)(m >> 64); *lo = (u64)(m); #elif MSC_HAS_UMUL128 *lo = _umul128(a, b, hi); #else u32 a0 = (u32)(a), a1 = (u32)(a >> 32); u32 b0 = (u32)(b), b1 = (u32)(b >> 32); u64 p00 = (u64)a0 * b0, p01 = (u64)a0 * b1; u64 p10 = (u64)a1 * b0, p11 = (u64)a1 * b1; u64 m0 = p01 + (p00 >> 32); u32 m00 = (u32)(m0), m01 = (u32)(m0 >> 32); u64 m1 = p10 + m00; u32 m10 = (u32)(m1), m11 = (u32)(m1 >> 32); *hi = p11 + m01 + m11; *lo = ((u64)m10 << 32) | (u32)p00; #endif } /** Multiplies two 64-bit unsigned integers and add a value (a * b + c), returns the 128-bit result as 'hi' and 'lo'. */ static_inline void u128_mul_add(u64 a, u64 b, u64 c, u64 *hi, u64 *lo) { #if YYJSON_HAS_INT128 u128 m = (u128)a * b + c; *hi = (u64)(m >> 64); *lo = (u64)(m); #else u64 h, l, t; u128_mul(a, b, &h, &l); t = l + c; h += (u64)(((t < l) | (t < c))); *hi = h; *lo = t; #endif } /*============================================================================== * MARK: - File Utils (Private) * These functions are used to read and write JSON files. *============================================================================*/ #define YYJSON_FOPEN_E #if !defined(_MSC_VER) && defined(__GLIBC__) && defined(__GLIBC_PREREQ) # if __GLIBC_PREREQ(2, 7) # undef YYJSON_FOPEN_E # define YYJSON_FOPEN_E "e" /* glibc extension to enable O_CLOEXEC */ # endif #endif static_inline FILE *fopen_safe(const char *path, const char *mode) { #if YYJSON_MSC_VER >= 1400 FILE *file = NULL; if (fopen_s(&file, path, mode) != 0) return NULL; return file; #else return fopen(path, mode); #endif } static_inline FILE *fopen_readonly(const char *path) { return fopen_safe(path, "rb" YYJSON_FOPEN_E); } static_inline FILE *fopen_writeonly(const char *path) { return fopen_safe(path, "wb" YYJSON_FOPEN_E); } static_inline usize fread_safe(void *buf, usize size, FILE *file) { #if YYJSON_MSC_VER >= 1400 return fread_s(buf, size, 1, size, file); #else return fread(buf, 1, size, file); #endif } /*============================================================================== * MARK: - Size Utils (Private) * These functions are used for memory allocation. *============================================================================*/ /** Returns whether the size is overflow after increment. */ static_inline bool size_add_is_overflow(usize size, usize add) { return size > (size + add); } /** Returns whether the size is power of 2 (size should not be 0). */ static_inline bool size_is_pow2(usize size) { return (size & (size - 1)) == 0; } /** Align size upwards (may overflow). */ static_inline usize size_align_up(usize size, usize align) { if (size_is_pow2(align)) { return (size + (align - 1)) & ~(align - 1); } else { return size + align - (size + align - 1) % align - 1; } } /** Align size downwards. */ static_inline usize size_align_down(usize size, usize align) { if (size_is_pow2(align)) { return size & ~(align - 1); } else { return size - (size % align); } } /** Align address upwards (may overflow). */ static_inline void *mem_align_up(void *mem, usize align) { usize size; memcpy(&size, &mem, sizeof(usize)); size = size_align_up(size, align); memcpy(&mem, &size, sizeof(usize)); return mem; } /*============================================================================== * MARK: - Default Memory Allocator (Private) * This is a simple libc memory allocator wrapper. *============================================================================*/ static void *default_malloc(void *ctx, usize size) { return malloc(size); } static void *default_realloc(void *ctx, void *ptr, usize old_size, usize size) { return realloc(ptr, size); } static void default_free(void *ctx, void *ptr) { free(ptr); } static const yyjson_alc YYJSON_DEFAULT_ALC = { default_malloc, default_realloc, default_free, NULL }; /*============================================================================== * MARK: - Null Memory Allocator (Private) * This allocator is just a placeholder to ensure that the internal * malloc/realloc/free function pointers are not null. *============================================================================*/ static void *null_malloc(void *ctx, usize size) { return NULL; } static void *null_realloc(void *ctx, void *ptr, usize old_size, usize size) { return NULL; } static void null_free(void *ctx, void *ptr) { return; } static const yyjson_alc YYJSON_NULL_ALC = { null_malloc, null_realloc, null_free, NULL }; /*============================================================================== * MARK: - Pool Memory Allocator (Public) * This allocator is initialized with a fixed-size buffer. * The buffer is split into multiple memory chunks for memory allocation. *============================================================================*/ /** memory chunk header */ typedef struct pool_chunk { usize size; /* chunk memory size, include chunk header */ struct pool_chunk *next; /* linked list, nullable */ /* char mem[]; flexible array member */ } pool_chunk; /** allocator ctx header */ typedef struct pool_ctx { usize size; /* total memory size, include ctx header */ pool_chunk *free_list; /* linked list, nullable */ /* pool_chunk chunks[]; flexible array member */ } pool_ctx; /** align up the input size to chunk size */ static_inline void pool_size_align(usize *size) { *size = size_align_up(*size, sizeof(pool_chunk)) + sizeof(pool_chunk); } static void *pool_malloc(void *ctx_ptr, usize size) { /* assert(size != 0) */ pool_ctx *ctx = (pool_ctx *)ctx_ptr; pool_chunk *next, *prev = NULL, *cur = ctx->free_list; if (unlikely(size >= ctx->size)) return NULL; pool_size_align(&size); while (cur) { if (cur->size < size) { /* not enough space, try next chunk */ prev = cur; cur = cur->next; continue; } if (cur->size >= size + sizeof(pool_chunk) * 2) { /* too much space, split this chunk */ next = (pool_chunk *)(void *)((u8 *)cur + size); next->size = cur->size - size; next->next = cur->next; cur->size = size; } else { /* just enough space, use whole chunk */ next = cur->next; } if (prev) prev->next = next; else ctx->free_list = next; return (void *)(cur + 1); } return NULL; } static void pool_free(void *ctx_ptr, void *ptr) { /* assert(ptr != NULL) */ pool_ctx *ctx = (pool_ctx *)ctx_ptr; pool_chunk *cur = ((pool_chunk *)ptr) - 1; pool_chunk *prev = NULL, *next = ctx->free_list; while (next && next < cur) { prev = next; next = next->next; } if (prev) prev->next = cur; else ctx->free_list = cur; cur->next = next; if (next && ((u8 *)cur + cur->size) == (u8 *)next) { /* merge cur to higher chunk */ cur->size += next->size; cur->next = next->next; } if (prev && ((u8 *)prev + prev->size) == (u8 *)cur) { /* merge cur to lower chunk */ prev->size += cur->size; prev->next = cur->next; } } static void *pool_realloc(void *ctx_ptr, void *ptr, usize old_size, usize size) { /* assert(ptr != NULL && size != 0 && old_size < size) */ pool_ctx *ctx = (pool_ctx *)ctx_ptr; pool_chunk *cur = ((pool_chunk *)ptr) - 1, *prev, *next, *tmp; /* check size */ if (unlikely(size >= ctx->size)) return NULL; pool_size_align(&old_size); pool_size_align(&size); if (unlikely(old_size == size)) return ptr; /* find next and prev chunk */ prev = NULL; next = ctx->free_list; while (next && next < cur) { prev = next; next = next->next; } if ((u8 *)cur + cur->size == (u8 *)next && cur->size + next->size >= size) { /* merge to higher chunk if they are contiguous */ usize free_size = cur->size + next->size - size; if (free_size > sizeof(pool_chunk) * 2) { tmp = (pool_chunk *)(void *)((u8 *)cur + size); if (prev) prev->next = tmp; else ctx->free_list = tmp; tmp->next = next->next; tmp->size = free_size; cur->size = size; } else { if (prev) prev->next = next->next; else ctx->free_list = next->next; cur->size += next->size; } return ptr; } else { /* fallback to malloc and memcpy */ void *new_ptr = pool_malloc(ctx_ptr, size - sizeof(pool_chunk)); if (new_ptr) { memcpy(new_ptr, ptr, cur->size - sizeof(pool_chunk)); pool_free(ctx_ptr, ptr); } return new_ptr; } } bool yyjson_alc_pool_init(yyjson_alc *alc, void *buf, usize size) { pool_chunk *chunk; pool_ctx *ctx; if (unlikely(!alc)) return false; *alc = YYJSON_NULL_ALC; if (size < sizeof(pool_ctx) * 4) return false; ctx = (pool_ctx *)mem_align_up(buf, sizeof(pool_ctx)); if (unlikely(!ctx)) return false; size -= (usize)((u8 *)ctx - (u8 *)buf); size = size_align_down(size, sizeof(pool_ctx)); chunk = (pool_chunk *)(ctx + 1); chunk->size = size - sizeof(pool_ctx); chunk->next = NULL; ctx->size = size; ctx->free_list = chunk; alc->malloc = pool_malloc; alc->realloc = pool_realloc; alc->free = pool_free; alc->ctx = (void *)ctx; return true; } /*============================================================================== * MARK: - Dynamic Memory Allocator (Public) * This allocator allocates memory on demand and does not immediately release * unused memory. Instead, it places the unused memory into a freelist for * potential reuse in the future. It is only when the entire allocator is * destroyed that all previously allocated memory is released at once. *============================================================================*/ /** memory chunk header */ typedef struct dyn_chunk { usize size; /* chunk size, include header */ struct dyn_chunk *next; /* char mem[]; flexible array member */ } dyn_chunk; /** allocator ctx header */ typedef struct { dyn_chunk free_list; /* dummy header, sorted from small to large */ dyn_chunk used_list; /* dummy header */ } dyn_ctx; /** align up the input size to chunk size */ static_inline bool dyn_size_align(usize *size) { usize alc_size = *size + sizeof(dyn_chunk); alc_size = size_align_up(alc_size, YYJSON_ALC_DYN_MIN_SIZE); if (unlikely(alc_size < *size)) return false; /* overflow */ *size = alc_size; return true; } /** remove a chunk from list (the chunk must already be in the list) */ static_inline void dyn_chunk_list_remove(dyn_chunk *list, dyn_chunk *chunk) { dyn_chunk *prev = list, *cur; for (cur = prev->next; cur; cur = cur->next) { if (cur == chunk) { prev->next = cur->next; cur->next = NULL; return; } prev = cur; } } /** add a chunk to list header (the chunk must not be in the list) */ static_inline void dyn_chunk_list_add(dyn_chunk *list, dyn_chunk *chunk) { chunk->next = list->next; list->next = chunk; } static void *dyn_malloc(void *ctx_ptr, usize size) { /* assert(size != 0) */ const yyjson_alc def = YYJSON_DEFAULT_ALC; dyn_ctx *ctx = (dyn_ctx *)ctx_ptr; dyn_chunk *chunk, *prev; if (unlikely(!dyn_size_align(&size))) return NULL; /* freelist is empty, create new chunk */ if (!ctx->free_list.next) { chunk = (dyn_chunk *)def.malloc(def.ctx, size); if (unlikely(!chunk)) return NULL; chunk->size = size; chunk->next = NULL; dyn_chunk_list_add(&ctx->used_list, chunk); return (void *)(chunk + 1); } /* find a large enough chunk, or resize the largest chunk */ prev = &ctx->free_list; while (true) { chunk = prev->next; if (chunk->size >= size) { /* enough size, reuse this chunk */ prev->next = chunk->next; dyn_chunk_list_add(&ctx->used_list, chunk); return (void *)(chunk + 1); } if (!chunk->next) { /* resize the largest chunk */ chunk = (dyn_chunk *)def.realloc(def.ctx, chunk, chunk->size, size); if (unlikely(!chunk)) return NULL; prev->next = NULL; chunk->size = size; dyn_chunk_list_add(&ctx->used_list, chunk); return (void *)(chunk + 1); } prev = chunk; } } static void *dyn_realloc(void *ctx_ptr, void *ptr, usize old_size, usize size) { /* assert(ptr != NULL && size != 0 && old_size < size) */ const yyjson_alc def = YYJSON_DEFAULT_ALC; dyn_ctx *ctx = (dyn_ctx *)ctx_ptr; dyn_chunk *new_chunk, *chunk = (dyn_chunk *)ptr - 1; if (unlikely(!dyn_size_align(&size))) return NULL; if (chunk->size >= size) return ptr; dyn_chunk_list_remove(&ctx->used_list, chunk); new_chunk = (dyn_chunk *)def.realloc(def.ctx, chunk, chunk->size, size); if (likely(new_chunk)) { new_chunk->size = size; chunk = new_chunk; } dyn_chunk_list_add(&ctx->used_list, chunk); return new_chunk ? (void *)(new_chunk + 1) : NULL; } static void dyn_free(void *ctx_ptr, void *ptr) { /* assert(ptr != NULL) */ dyn_ctx *ctx = (dyn_ctx *)ctx_ptr; dyn_chunk *chunk = (dyn_chunk *)ptr - 1, *prev; dyn_chunk_list_remove(&ctx->used_list, chunk); for (prev = &ctx->free_list; prev; prev = prev->next) { if (!prev->next || prev->next->size >= chunk->size) { chunk->next = prev->next; prev->next = chunk; break; } } } yyjson_alc *yyjson_alc_dyn_new(void) { const yyjson_alc def = YYJSON_DEFAULT_ALC; usize hdr_len = sizeof(yyjson_alc) + sizeof(dyn_ctx); yyjson_alc *alc = (yyjson_alc *)def.malloc(def.ctx, hdr_len); dyn_ctx *ctx = (dyn_ctx *)(void *)(alc + 1); if (unlikely(!alc)) return NULL; alc->malloc = dyn_malloc; alc->realloc = dyn_realloc; alc->free = dyn_free; alc->ctx = alc + 1; memset(ctx, 0, sizeof(*ctx)); return alc; } void yyjson_alc_dyn_free(yyjson_alc *alc) { const yyjson_alc def = YYJSON_DEFAULT_ALC; dyn_ctx *ctx = (dyn_ctx *)(void *)(alc + 1); dyn_chunk *chunk, *next; if (unlikely(!alc)) return; for (chunk = ctx->free_list.next; chunk; chunk = next) { next = chunk->next; def.free(def.ctx, chunk); } for (chunk = ctx->used_list.next; chunk; chunk = next) { next = chunk->next; def.free(def.ctx, chunk); } def.free(def.ctx, alc); } /*============================================================================== * MARK: - JSON Struct Utils (Public) * These functions are used for creating, copying, releasing, and comparing * JSON documents and values. They are widely used throughout this library. *============================================================================*/ static_inline void unsafe_yyjson_str_pool_release(yyjson_str_pool *pool, yyjson_alc *alc) { yyjson_str_chunk *chunk = pool->chunks, *next; while (chunk) { next = chunk->next; alc->free(alc->ctx, chunk); chunk = next; } } static_inline void unsafe_yyjson_val_pool_release(yyjson_val_pool *pool, yyjson_alc *alc) { yyjson_val_chunk *chunk = pool->chunks, *next; while (chunk) { next = chunk->next; alc->free(alc->ctx, chunk); chunk = next; } } bool unsafe_yyjson_str_pool_grow(yyjson_str_pool *pool, const yyjson_alc *alc, usize len) { yyjson_str_chunk *chunk; usize size, max_len; /* create a new chunk */ max_len = USIZE_MAX - sizeof(yyjson_str_chunk); if (unlikely(len > max_len)) return false; size = len + sizeof(yyjson_str_chunk); size = yyjson_max(pool->chunk_size, size); chunk = (yyjson_str_chunk *)alc->malloc(alc->ctx, size); if (unlikely(!chunk)) return false; /* insert the new chunk as the head of the linked list */ chunk->next = pool->chunks; chunk->chunk_size = size; pool->chunks = chunk; pool->cur = (char *)chunk + sizeof(yyjson_str_chunk); pool->end = (char *)chunk + size; /* the next chunk is twice the size of the current one */ size = yyjson_min(pool->chunk_size * 2, pool->chunk_size_max); if (size < pool->chunk_size) size = pool->chunk_size_max; /* overflow */ pool->chunk_size = size; return true; } bool unsafe_yyjson_val_pool_grow(yyjson_val_pool *pool, const yyjson_alc *alc, usize count) { yyjson_val_chunk *chunk; usize size, max_count; /* create a new chunk */ max_count = USIZE_MAX / sizeof(yyjson_mut_val) - 1; if (unlikely(count > max_count)) return false; size = (count + 1) * sizeof(yyjson_mut_val); size = yyjson_max(pool->chunk_size, size); chunk = (yyjson_val_chunk *)alc->malloc(alc->ctx, size); if (unlikely(!chunk)) return false; /* insert the new chunk as the head of the linked list */ chunk->next = pool->chunks; chunk->chunk_size = size; pool->chunks = chunk; pool->cur = (yyjson_mut_val *)(void *)((u8 *)chunk) + 1; pool->end = (yyjson_mut_val *)(void *)((u8 *)chunk + size); /* the next chunk is twice the size of the current one */ size = yyjson_min(pool->chunk_size * 2, pool->chunk_size_max); if (size < pool->chunk_size) size = pool->chunk_size_max; /* overflow */ pool->chunk_size = size; return true; } bool yyjson_mut_doc_set_str_pool_size(yyjson_mut_doc *doc, size_t len) { usize max_size = USIZE_MAX - sizeof(yyjson_str_chunk); if (!doc || !len || len > max_size) return false; doc->str_pool.chunk_size = len + sizeof(yyjson_str_chunk); return true; } bool yyjson_mut_doc_set_val_pool_size(yyjson_mut_doc *doc, size_t count) { usize max_count = USIZE_MAX / sizeof(yyjson_mut_val) - 1; if (!doc || !count || count > max_count) return false; doc->val_pool.chunk_size = (count + 1) * sizeof(yyjson_mut_val); return true; } void yyjson_mut_doc_free(yyjson_mut_doc *doc) { if (doc) { yyjson_alc alc = doc->alc; memset(&doc->alc, 0, sizeof(alc)); unsafe_yyjson_str_pool_release(&doc->str_pool, &alc); unsafe_yyjson_val_pool_release(&doc->val_pool, &alc); alc.free(alc.ctx, doc); } } yyjson_mut_doc *yyjson_mut_doc_new(const yyjson_alc *alc) { yyjson_mut_doc *doc; if (!alc) alc = &YYJSON_DEFAULT_ALC; doc = (yyjson_mut_doc *)alc->malloc(alc->ctx, sizeof(yyjson_mut_doc)); if (!doc) return NULL; memset(doc, 0, sizeof(yyjson_mut_doc)); doc->alc = *alc; doc->str_pool.chunk_size = YYJSON_MUT_DOC_STR_POOL_INIT_SIZE; doc->str_pool.chunk_size_max = YYJSON_MUT_DOC_STR_POOL_MAX_SIZE; doc->val_pool.chunk_size = YYJSON_MUT_DOC_VAL_POOL_INIT_SIZE; doc->val_pool.chunk_size_max = YYJSON_MUT_DOC_VAL_POOL_MAX_SIZE; return doc; } yyjson_mut_doc *yyjson_doc_mut_copy(yyjson_doc *doc, const yyjson_alc *alc) { yyjson_mut_doc *m_doc; yyjson_mut_val *m_val; if (!doc || !doc->root) return NULL; m_doc = yyjson_mut_doc_new(alc); if (!m_doc) return NULL; m_val = yyjson_val_mut_copy(m_doc, doc->root); if (!m_val) { yyjson_mut_doc_free(m_doc); return NULL; } yyjson_mut_doc_set_root(m_doc, m_val); return m_doc; } yyjson_mut_doc *yyjson_mut_doc_mut_copy(yyjson_mut_doc *doc, const yyjson_alc *alc) { yyjson_mut_doc *m_doc; yyjson_mut_val *m_val; if (!doc) return NULL; if (!doc->root) return yyjson_mut_doc_new(alc); m_doc = yyjson_mut_doc_new(alc); if (!m_doc) return NULL; m_val = yyjson_mut_val_mut_copy(m_doc, doc->root); if (!m_val) { yyjson_mut_doc_free(m_doc); return NULL; } yyjson_mut_doc_set_root(m_doc, m_val); return m_doc; } yyjson_mut_val *yyjson_val_mut_copy(yyjson_mut_doc *m_doc, yyjson_val *i_vals) { /* The immutable object or array stores all sub-values in a contiguous memory, We copy them to another contiguous memory as mutable values, then reconnect the mutable values with the original relationship. */ usize i_vals_len; yyjson_mut_val *m_vals, *m_val; yyjson_val *i_val, *i_end; if (!m_doc || !i_vals) return NULL; i_end = unsafe_yyjson_get_next(i_vals); i_vals_len = (usize)(unsafe_yyjson_get_next(i_vals) - i_vals); m_vals = unsafe_yyjson_mut_val(m_doc, i_vals_len); if (!m_vals) return NULL; i_val = i_vals; m_val = m_vals; for (; i_val < i_end; i_val++, m_val++) { yyjson_type type = unsafe_yyjson_get_type(i_val); m_val->tag = i_val->tag; m_val->uni.u64 = i_val->uni.u64; if (type == YYJSON_TYPE_STR || type == YYJSON_TYPE_RAW) { const char *str = i_val->uni.str; usize str_len = unsafe_yyjson_get_len(i_val); m_val->uni.str = unsafe_yyjson_mut_strncpy(m_doc, str, str_len); if (!m_val->uni.str) return NULL; } else if (type == YYJSON_TYPE_ARR) { usize len = unsafe_yyjson_get_len(i_val); if (len > 0) { yyjson_val *ii_val = i_val + 1, *ii_next; yyjson_mut_val *mm_val = m_val + 1, *mm_ctn = m_val, *mm_next; while (len-- > 1) { ii_next = unsafe_yyjson_get_next(ii_val); mm_next = mm_val + (ii_next - ii_val); mm_val->next = mm_next; ii_val = ii_next; mm_val = mm_next; } mm_val->next = mm_ctn + 1; mm_ctn->uni.ptr = mm_val; } } else if (type == YYJSON_TYPE_OBJ) { usize len = unsafe_yyjson_get_len(i_val); if (len > 0) { yyjson_val *ii_key = i_val + 1, *ii_nextkey; yyjson_mut_val *mm_key = m_val + 1, *mm_ctn = m_val; yyjson_mut_val *mm_nextkey; while (len-- > 1) { ii_nextkey = unsafe_yyjson_get_next(ii_key + 1); mm_nextkey = mm_key + (ii_nextkey - ii_key); mm_key->next = mm_key + 1; mm_key->next->next = mm_nextkey; ii_key = ii_nextkey; mm_key = mm_nextkey; } mm_key->next = mm_key + 1; mm_key->next->next = mm_ctn + 1; mm_ctn->uni.ptr = mm_key; } } } return m_vals; } static yyjson_mut_val *unsafe_yyjson_mut_val_mut_copy(yyjson_mut_doc *m_doc, yyjson_mut_val *m_vals) { /* The mutable object or array stores all sub-values in a circular linked list, so we can traverse them in the same loop. The traversal starts from the last item, continues with the first item in a list, and ends with the second to last item, which needs to be linked to the last item to close the circle. */ yyjson_mut_val *m_val = unsafe_yyjson_mut_val(m_doc, 1); if (unlikely(!m_val)) return NULL; m_val->tag = m_vals->tag; switch (unsafe_yyjson_get_type(m_vals)) { case YYJSON_TYPE_OBJ: case YYJSON_TYPE_ARR: if (unsafe_yyjson_get_len(m_vals) > 0) { yyjson_mut_val *last = (yyjson_mut_val *)m_vals->uni.ptr; yyjson_mut_val *next = last->next, *prev; prev = unsafe_yyjson_mut_val_mut_copy(m_doc, last); if (!prev) return NULL; m_val->uni.ptr = (void *)prev; while (next != last) { prev->next = unsafe_yyjson_mut_val_mut_copy(m_doc, next); if (!prev->next) return NULL; prev = prev->next; next = next->next; } prev->next = (yyjson_mut_val *)m_val->uni.ptr; } break; case YYJSON_TYPE_RAW: case YYJSON_TYPE_STR: { const char *str = m_vals->uni.str; usize str_len = unsafe_yyjson_get_len(m_vals); m_val->uni.str = unsafe_yyjson_mut_strncpy(m_doc, str, str_len); if (!m_val->uni.str) return NULL; break; } default: m_val->uni = m_vals->uni; break; } return m_val; } yyjson_mut_val *yyjson_mut_val_mut_copy(yyjson_mut_doc *doc, yyjson_mut_val *val) { if (doc && val) return unsafe_yyjson_mut_val_mut_copy(doc, val); return NULL; } /* Count the number of values and the total length of the strings. */ static void yyjson_mut_stat(yyjson_mut_val *val, usize *val_sum, usize *str_sum) { yyjson_type type = unsafe_yyjson_get_type(val); *val_sum += 1; if (type == YYJSON_TYPE_ARR || type == YYJSON_TYPE_OBJ) { yyjson_mut_val *child = (yyjson_mut_val *)val->uni.ptr; usize len = unsafe_yyjson_get_len(val), i; len <<= (u8)(type == YYJSON_TYPE_OBJ); *val_sum += len; for (i = 0; i < len; i++) { yyjson_type stype = unsafe_yyjson_get_type(child); if (stype == YYJSON_TYPE_STR || stype == YYJSON_TYPE_RAW) { *str_sum += unsafe_yyjson_get_len(child) + 1; } else if (stype == YYJSON_TYPE_ARR || stype == YYJSON_TYPE_OBJ) { yyjson_mut_stat(child, val_sum, str_sum); *val_sum -= 1; } child = child->next; } } else if (type == YYJSON_TYPE_STR || type == YYJSON_TYPE_RAW) { *str_sum += unsafe_yyjson_get_len(val) + 1; } } /* Copy mutable values to immutable value pool. */ static usize yyjson_imut_copy(yyjson_val **val_ptr, char **buf_ptr, yyjson_mut_val *mval) { yyjson_val *val = *val_ptr; yyjson_type type = unsafe_yyjson_get_type(mval); if (type == YYJSON_TYPE_ARR || type == YYJSON_TYPE_OBJ) { yyjson_mut_val *child = (yyjson_mut_val *)mval->uni.ptr; usize len = unsafe_yyjson_get_len(mval), i; usize val_sum = 1; if (type == YYJSON_TYPE_OBJ) { if (len) child = child->next->next; len <<= 1; } else { if (len) child = child->next; } *val_ptr = val + 1; for (i = 0; i < len; i++) { val_sum += yyjson_imut_copy(val_ptr, buf_ptr, child); child = child->next; } val->tag = mval->tag; val->uni.ofs = val_sum * sizeof(yyjson_val); return val_sum; } else if (type == YYJSON_TYPE_STR || type == YYJSON_TYPE_RAW) { char *buf = *buf_ptr; usize len = unsafe_yyjson_get_len(mval); memcpy((void *)buf, (const void *)mval->uni.str, len); buf[len] = '\0'; val->tag = mval->tag; val->uni.str = buf; *val_ptr = val + 1; *buf_ptr = buf + len + 1; return 1; } else { val->tag = mval->tag; val->uni = mval->uni; *val_ptr = val + 1; return 1; } } yyjson_doc *yyjson_mut_doc_imut_copy(yyjson_mut_doc *mdoc, const yyjson_alc *alc) { if (!mdoc) return NULL; return yyjson_mut_val_imut_copy(mdoc->root, alc); } yyjson_doc *yyjson_mut_val_imut_copy(yyjson_mut_val *mval, const yyjson_alc *alc) { usize val_num = 0, str_sum = 0, hdr_size, buf_size; yyjson_doc *doc = NULL; yyjson_val *val_hdr = NULL; /* This value should be NULL here. Setting a non-null value suppresses warning from the clang analyzer. */ char *str_hdr = (char *)(void *)&str_sum; if (!mval) return NULL; if (!alc) alc = &YYJSON_DEFAULT_ALC; /* traverse the input value to get pool size */ yyjson_mut_stat(mval, &val_num, &str_sum); /* create doc and val pool */ hdr_size = size_align_up(sizeof(yyjson_doc), sizeof(yyjson_val)); buf_size = hdr_size + val_num * sizeof(yyjson_val); doc = (yyjson_doc *)alc->malloc(alc->ctx, buf_size); if (!doc) return NULL; memset(doc, 0, sizeof(yyjson_doc)); val_hdr = (yyjson_val *)(void *)((char *)(void *)doc + hdr_size); doc->root = val_hdr; doc->alc = *alc; /* create str pool */ if (str_sum > 0) { str_hdr = (char *)alc->malloc(alc->ctx, str_sum); doc->str_pool = str_hdr; if (!str_hdr) { alc->free(alc->ctx, (void *)doc); return NULL; } } /* copy vals and strs */ doc->val_read = yyjson_imut_copy(&val_hdr, &str_hdr, mval); doc->dat_read = str_sum + 1; return doc; } static_inline bool unsafe_yyjson_num_equals(void *lhs, void *rhs) { yyjson_val_uni *luni = &((yyjson_val *)lhs)->uni; yyjson_val_uni *runi = &((yyjson_val *)rhs)->uni; yyjson_subtype lt = unsafe_yyjson_get_subtype(lhs); yyjson_subtype rt = unsafe_yyjson_get_subtype(rhs); if (lt == rt) return luni->u64 == runi->u64; if (lt == YYJSON_SUBTYPE_SINT && rt == YYJSON_SUBTYPE_UINT) { return luni->i64 >= 0 && luni->u64 == runi->u64; } if (lt == YYJSON_SUBTYPE_UINT && rt == YYJSON_SUBTYPE_SINT) { return runi->i64 >= 0 && luni->u64 == runi->u64; } return false; } static_inline bool unsafe_yyjson_str_equals(void *lhs, void *rhs) { usize len = unsafe_yyjson_get_len(lhs); if (len != unsafe_yyjson_get_len(rhs)) return false; return !memcmp(unsafe_yyjson_get_str(lhs), unsafe_yyjson_get_str(rhs), len); } bool unsafe_yyjson_equals(yyjson_val *lhs, yyjson_val *rhs) { yyjson_type type = unsafe_yyjson_get_type(lhs); if (type != unsafe_yyjson_get_type(rhs)) return false; switch (type) { case YYJSON_TYPE_OBJ: { usize len = unsafe_yyjson_get_len(lhs); if (len != unsafe_yyjson_get_len(rhs)) return false; if (len > 0) { yyjson_obj_iter iter; yyjson_obj_iter_init(rhs, &iter); lhs = unsafe_yyjson_get_first(lhs); while (len-- > 0) { rhs = yyjson_obj_iter_getn(&iter, lhs->uni.str, unsafe_yyjson_get_len(lhs)); if (!rhs) return false; if (!unsafe_yyjson_equals(lhs + 1, rhs)) return false; lhs = unsafe_yyjson_get_next(lhs + 1); } } /* yyjson allows duplicate keys, so the check may be inaccurate */ return true; } case YYJSON_TYPE_ARR: { usize len = unsafe_yyjson_get_len(lhs); if (len != unsafe_yyjson_get_len(rhs)) return false; if (len > 0) { lhs = unsafe_yyjson_get_first(lhs); rhs = unsafe_yyjson_get_first(rhs); while (len-- > 0) { if (!unsafe_yyjson_equals(lhs, rhs)) return false; lhs = unsafe_yyjson_get_next(lhs); rhs = unsafe_yyjson_get_next(rhs); } } return true; } case YYJSON_TYPE_NUM: return unsafe_yyjson_num_equals(lhs, rhs); case YYJSON_TYPE_RAW: case YYJSON_TYPE_STR: return unsafe_yyjson_str_equals(lhs, rhs); case YYJSON_TYPE_NULL: case YYJSON_TYPE_BOOL: return lhs->tag == rhs->tag; default: return false; } } bool unsafe_yyjson_mut_equals(yyjson_mut_val *lhs, yyjson_mut_val *rhs) { yyjson_type type = unsafe_yyjson_get_type(lhs); if (type != unsafe_yyjson_get_type(rhs)) return false; switch (type) { case YYJSON_TYPE_OBJ: { usize len = unsafe_yyjson_get_len(lhs); if (len != unsafe_yyjson_get_len(rhs)) return false; if (len > 0) { yyjson_mut_obj_iter iter; yyjson_mut_obj_iter_init(rhs, &iter); lhs = (yyjson_mut_val *)lhs->uni.ptr; while (len-- > 0) { rhs = yyjson_mut_obj_iter_getn(&iter, lhs->uni.str, unsafe_yyjson_get_len(lhs)); if (!rhs) return false; if (!unsafe_yyjson_mut_equals(lhs->next, rhs)) return false; lhs = lhs->next->next; } } /* yyjson allows duplicate keys, so the check may be inaccurate */ return true; } case YYJSON_TYPE_ARR: { usize len = unsafe_yyjson_get_len(lhs); if (len != unsafe_yyjson_get_len(rhs)) return false; if (len > 0) { lhs = (yyjson_mut_val *)lhs->uni.ptr; rhs = (yyjson_mut_val *)rhs->uni.ptr; while (len-- > 0) { if (!unsafe_yyjson_mut_equals(lhs, rhs)) return false; lhs = lhs->next; rhs = rhs->next; } } return true; } case YYJSON_TYPE_NUM: return unsafe_yyjson_num_equals(lhs, rhs); case YYJSON_TYPE_RAW: case YYJSON_TYPE_STR: return unsafe_yyjson_str_equals(lhs, rhs); case YYJSON_TYPE_NULL: case YYJSON_TYPE_BOOL: return lhs->tag == rhs->tag; default: return false; } } bool yyjson_locate_pos(const char *str, size_t len, size_t pos, size_t *line, size_t *col, size_t *chr) { usize line_sum = 0, line_pos = 0, chr_sum = 0; const u8 *cur = (const u8 *)str; const u8 *end = cur + pos; if (!str || pos > len) { if (line) *line = 0; if (col) *col = 0; if (chr) *chr = 0; return false; } if (pos >= 3 && is_utf8_bom(cur)) cur += 3; /* don't count BOM */ while (cur < end) { u8 c = *cur; chr_sum += 1; if (likely(c < 0x80)) { /* 0xxxxxxx (0x00-0x7F) ASCII */ if (c == '\n') { line_sum += 1; line_pos = chr_sum; } cur += 1; } else if (c < 0xC0) cur += 1; /* 10xxxxxx (0x80-0xBF) Invalid */ else if (c < 0xE0) cur += 2; /* 110xxxxx (0xC0-0xDF) 2-byte UTF-8 */ else if (c < 0xF0) cur += 3; /* 1110xxxx (0xE0-0xEF) 3-byte UTF-8 */ else if (c < 0xF8) cur += 4; /* 11110xxx (0xF0-0xF7) 4-byte UTF-8 */ else cur += 1; /* 11111xxx (0xF8-0xFF) Invalid */ } if (line) *line = line_sum + 1; if (col) *col = chr_sum - line_pos + 1; if (chr) *chr = chr_sum; return true; } #if !YYJSON_DISABLE_READER /* reader begin */ /* Check read flag, avoids `always false` warning when disabled. */ #define has_flg(_flg) unlikely(has_rflag(flg, YYJSON_READ_##_flg, 0)) #define has_allow(_flg) unlikely(has_rflag(flg, YYJSON_READ_ALLOW_##_flg, 1)) #define YYJSON_READ_ALLOW_TRIVIA (YYJSON_READ_ALLOW_COMMENTS | \ YYJSON_READ_ALLOW_EXT_WHITESPACE) static_inline bool has_rflag(yyjson_read_flag flg, yyjson_read_flag chk, bool non_standard) { #if YYJSON_DISABLE_NON_STANDARD if (non_standard) return false; #endif return (flg & chk) != 0; } /*============================================================================== * MARK: - JSON Reader Utils (Private) * These functions are used by JSON reader to read literals and comments. *============================================================================*/ /** Read `true` literal, `*ptr[0]` should be `t`. */ static_inline bool read_true(u8 **ptr, yyjson_val *val) { u8 *cur = *ptr; if (likely(byte_match_4(cur, "true"))) { val->tag = YYJSON_TYPE_BOOL | YYJSON_SUBTYPE_TRUE; *ptr = cur + 4; return true; } return false; } /** Read `false` literal, `*ptr[0]` should be `f`. */ static_inline bool read_false(u8 **ptr, yyjson_val *val) { u8 *cur = *ptr; if (likely(byte_match_4(cur + 1, "alse"))) { val->tag = YYJSON_TYPE_BOOL | YYJSON_SUBTYPE_FALSE; *ptr = cur + 5; return true; } return false; } /** Read `null` literal, `*ptr[0]` should be `n`. */ static_inline bool read_null(u8 **ptr, yyjson_val *val) { u8 *cur = *ptr; if (likely(byte_match_4(cur, "null"))) { val->tag = YYJSON_TYPE_NULL; *ptr = cur + 4; return true; } return false; } /** Read `Inf` or `Infinity` literal (ignoring case). */ static_inline bool read_inf(u8 **ptr, u8 **pre, yyjson_read_flag flg, yyjson_val *val) { u8 *hdr = *ptr; u8 *cur = *ptr; u8 **end = ptr; bool sign = (*cur == '-'); if (*cur == '+' && !has_allow(EXT_NUMBER)) return false; cur += char_is_sign(*cur); if (char_to_lower(cur[0]) == 'i' && char_to_lower(cur[1]) == 'n' && char_to_lower(cur[2]) == 'f') { if (char_to_lower(cur[3]) == 'i') { if (char_to_lower(cur[4]) == 'n' && char_to_lower(cur[5]) == 'i' && char_to_lower(cur[6]) == 't' && char_to_lower(cur[7]) == 'y') { cur += 8; } else { return false; } } else { cur += 3; } *end = cur; if (has_flg(NUMBER_AS_RAW)) { **pre = '\0'; /* add null-terminator for previous raw string */ *pre = cur; /* save end position for current raw string */ val->tag = ((u64)(cur - hdr) << YYJSON_TAG_BIT) | YYJSON_TYPE_RAW; val->uni.str = (const char *)hdr; } else { val->tag = YYJSON_TYPE_NUM | YYJSON_SUBTYPE_REAL; val->uni.u64 = f64_bits_inf(sign); } return true; } return false; } /** Read `NaN` literal (ignoring case). */ static_inline bool read_nan(u8 **ptr, u8 **pre, yyjson_read_flag flg, yyjson_val *val) { u8 *hdr = *ptr; u8 *cur = *ptr; u8 **end = ptr; bool sign = (*cur == '-'); if (*cur == '+' && !has_allow(EXT_NUMBER)) return false; cur += char_is_sign(*cur); if (char_to_lower(cur[0]) == 'n' && char_to_lower(cur[1]) == 'a' && char_to_lower(cur[2]) == 'n') { cur += 3; *end = cur; if (has_flg(NUMBER_AS_RAW)) { **pre = '\0'; /* add null-terminator for previous raw string */ *pre = cur; /* save end position for current raw string */ val->tag = ((u64)(cur - hdr) << YYJSON_TAG_BIT) | YYJSON_TYPE_RAW; val->uni.str = (const char *)hdr; } else { val->tag = YYJSON_TYPE_NUM | YYJSON_SUBTYPE_REAL; val->uni.u64 = f64_bits_nan(sign); } return true; } return false; } /** Read `Inf`, `Infinity` or `NaN` literal (ignoring case). */ static_inline bool read_inf_or_nan(u8 **ptr, u8 **pre, yyjson_read_flag flg, yyjson_val *val) { if (read_inf(ptr, pre, flg, val)) return true; if (read_nan(ptr, pre, flg, val)) return true; return false; } /** Read a JSON number as raw string. */ static_noinline bool read_num_raw(u8 **ptr, u8 **pre, yyjson_read_flag flg, yyjson_val *val, const char **msg) { #define return_err(_pos, _msg) do { \ *msg = _msg; *end = _pos; return false; \ } while (false) #define return_raw() do { \ val->tag = ((u64)(cur - hdr) << YYJSON_TAG_BIT) | YYJSON_TYPE_RAW; \ val->uni.str = (const char *)hdr; \ **pre = '\0'; *pre = cur; *end = cur; return true; \ } while (false) u8 *hdr = *ptr; u8 *cur = *ptr; u8 **end = ptr; /* skip sign */ cur += (*cur == '-'); /* read first digit, check leading zero */ while (unlikely(!char_is_digit(*cur))) { if (has_allow(EXT_NUMBER)) { if (*cur == '+' && cur == hdr) { /* leading `+` sign */ cur++; continue; } if (*cur == '.' && char_is_digit(cur[1])) { /* e.g. '.123' */ goto read_double; } } if (has_allow(INF_AND_NAN)) { if (read_inf_or_nan(ptr, pre, flg, val)) return true; } return_err(cur, "no digit after sign"); } /* read integral part */ if (*cur == '0') { cur++; if (unlikely(char_is_digit(*cur))) { return_err(cur - 1, "number with leading zero is not allowed"); } if (!char_is_fp(*cur)) { if (has_allow(EXT_NUMBER) && char_to_lower(*cur) == 'x') { /* hex */ if (!char_is_hex(*++cur)) return_err(cur, "invalid hex number"); while(char_is_hex(*cur)) cur++; } return_raw(); } } else { while (char_is_digit(*cur)) cur++; if (!char_is_fp(*cur)) return_raw(); } read_double: /* read fraction part */ if (*cur == '.') { cur++; if (!char_is_digit(*cur)) { if (has_allow(EXT_NUMBER)) { if (!char_is_exp(*cur)) return_raw(); } else { return_err(cur, "no digit after decimal point"); } } while (char_is_digit(*cur)) cur++; } /* read exponent part */ if (char_is_exp(*cur)) { cur += 1 + char_is_sign(cur[1]); if (!char_is_digit(*cur++)) { return_err(cur, "no digit after exponent sign"); } while (char_is_digit(*cur)) cur++; } return_raw(); #undef return_err #undef return_raw } /** Read a hex number. */ static_noinline bool read_num_hex(u8 **ptr, u8 **pre, yyjson_read_flag flg, yyjson_val *val, const char **msg) { u8 *hdr = *ptr; u8 *cur = *ptr; u8 **end = ptr; u64 sig = 0, i = 0; bool sign; /* skip sign and '0x' */ sign = (*cur == '-'); cur += (*cur == '-' || *cur == '+') + 2; /* read hex */ for(; i < 16; i++) { u8 c = hex_conv_table[cur[i]]; if (c == 0xF0) break; sig <<= 4; sig |= c; } /* check error */ if (unlikely(i == 0)) { *msg = "invalid hex number"; return false; } /* check overflow */ if (unlikely(i == 16)) { if (char_is_hex(cur[16]) || (sign && sig > ((u64)1 << 63))) { if (!has_flg(BIGNUM_AS_RAW)) { *msg = "hex number overflow"; return false; } cur += 16; while (char_is_hex(*cur)) cur++; **pre = '\0'; val->tag = ((u64)(cur - hdr) << YYJSON_TAG_BIT) | YYJSON_TYPE_RAW; val->uni.str = (const char *)hdr; *pre = cur; *end = cur; return true; } } val->tag = YYJSON_TYPE_NUM | (u64)((u8)sign << 3); val->uni.u64 = (u64)(sign ? (u64)(~(sig) + 1) : (u64)(sig)); *end = cur + i; return true; } /** Skip trivia (whitespace and comments). This function should be used only when `char_is_trivia()` returns true. @param ptr (inout) Input current position, output end position. @param eof JSON end position. @param flg JSON read flags. @return true if at least one character was skipped. false if no characters were skipped, or if a multi-line comment is unterminated; in the latter case, `ptr` will be set to `eof`. */ static_noinline bool skip_trivia(u8 **ptr, u8 *eof, yyjson_read_flag flg) { u8 *hdr = *ptr, *cur = *ptr; usize len; while (cur < eof) { u8 *loop_begin = cur; /* skip standard whitespace */ while(char_is_space(*cur)) cur++; /* skip extended whitespace */ if (has_allow(EXT_WHITESPACE)) { while (char_is_space_ext(*cur)) { cur += (len = ext_space_len(cur)); if (!len) break; } } /* skip comment, do not validate encoding */ if (has_allow(COMMENTS) && cur[0] == '/') { if (cur[1] == '/') { /* single-line comment */ cur += 2; if (has_allow(EXT_WHITESPACE)) { while (cur < eof) { if (char_is_eol_ext(*cur)) { cur += (len = ext_eol_len(cur)); if (len) break; } cur++; } } else { while (cur < eof && !char_is_eol(*cur)) cur++; } } else if (cur[1] == '*') { /* multi-line comment */ cur += 2; while (!byte_match_2(cur, "*/") && cur < eof) cur++; if (cur == eof) { *ptr = eof; return false; /* unclosed comment */ } cur += 2; } } if (cur == loop_begin) break; } *ptr = cur; return cur > hdr; } /** Check truncated UTF-8 character. Return true if `cur` starts a valid UTF-8 sequence that is truncated. */ static bool is_truncated_utf8(u8 *cur, u8 *eof) { u8 c0, c1, c2; usize len = (usize)(eof - cur); if (cur >= eof || len >= 4) return false; c0 = cur[0]; c1 = cur[1]; c2 = cur[2]; /* 1-byte UTF-8, not truncated */ if (c0 < 0x80) return false; if (len == 1) { /* 2-byte UTF-8, truncated */ if ((c0 & 0xE0) == 0xC0 && (c0 & 0x1E) != 0x00) return true; /* 3-byte UTF-8, truncated */ if ((c0 & 0xF0) == 0xE0) return true; /* 4-byte UTF-8, truncated */ if ((c0 & 0xF8) == 0xF0 && (c0 & 0x07) <= 0x04) return true; } else if (len == 2) { /* 3-byte UTF-8, truncated */ if ((c0 & 0xF0) == 0xE0 && (c1 & 0xC0) == 0x80) { u8 t = (u8)(((c0 & 0x0F) << 1) | ((c1 & 0x20) >> 5)); return 0x01 <= t && t != 0x1B; } /* 4-byte UTF-8, truncated */ if ((c0 & 0xF8) == 0xF0 && (c1 & 0xC0) == 0x80) { u8 t = (u8)(((c0 & 0x07) << 2) | ((c1 & 0x30) >> 4)); return 0x01 <= t && t <= 0x10; } } else if (len == 3) { /* 4 bytes UTF-8, truncated */ if ((c0 & 0xF8) == 0xF0 && (c1 & 0xC0) == 0x80 && (c2 & 0xC0) == 0x80) { u8 t = (u8)(((c0 & 0x07) << 2) | ((c1 & 0x30) >> 4)); return 0x01 <= t && t <= 0x10; } } return false; } /** Check truncated string. Returns true if `cur` match `str` but is truncated. The `str` should be lowercase ASCII letters. */ static bool is_truncated_str(u8 *cur, u8 *eof, const char *str, bool case_sensitive) { usize len = strlen(str); if (cur + len <= eof || eof <= cur) return false; if (case_sensitive) { return memcmp(cur, str, (usize)(eof - cur)) == 0; } for (; cur < eof; cur++, str++) { if (char_to_lower(*cur) != *(const u8 *)str) return false; } return true; } /** Check truncated JSON on parsing errors. Returns true if the input is valid but truncated. */ static_noinline bool is_truncated_end(u8 *hdr, u8 *cur, u8 *eof, yyjson_read_code code, yyjson_read_flag flg) { if (cur >= eof) return true; if (code == YYJSON_READ_ERROR_LITERAL) { if (is_truncated_str(cur, eof, "true", true) || is_truncated_str(cur, eof, "false", true) || is_truncated_str(cur, eof, "null", true)) { return true; } } if (code == YYJSON_READ_ERROR_UNEXPECTED_CHARACTER || code == YYJSON_READ_ERROR_INVALID_NUMBER || code == YYJSON_READ_ERROR_LITERAL) { if (has_allow(INF_AND_NAN)) { if (*cur == '-') cur++; if (is_truncated_str(cur, eof, "infinity", false) || is_truncated_str(cur, eof, "nan", false)) { return true; } } } if (code == YYJSON_READ_ERROR_UNEXPECTED_CONTENT) { if (has_allow(INF_AND_NAN)) { if (hdr + 3 <= cur && is_truncated_str(cur - 3, eof, "infinity", false)) { return true; /* e.g. infin would be read as inf + in */ } } } if (code == YYJSON_READ_ERROR_INVALID_STRING) { usize len = (usize)(eof - cur); /* unicode escape sequence */ if (*cur == '\\') { if (len == 1) return true; if (len <= 5) { if (*++cur != 'u') return false; for (++cur; cur < eof; cur++) { if (!char_is_hex(*cur)) return false; } return true; } else if (len <= 11) { /* incomplete surrogate pair? */ u16 hi; if (*++cur != 'u') return false; if (!hex_load_4(++cur, &hi)) return false; if ((hi & 0xF800) != 0xD800) return false; cur += 4; if (cur >= eof) return true; /* valid low surrogate is DC00...DFFF */ if (*cur != '\\') return false; if (++cur >= eof) return true; if (*cur != 'u') return false; if (++cur >= eof) return true; if (*cur != 'd' && *cur != 'D') return false; if (++cur >= eof) return true; if ((*cur < 'c' || *cur > 'f') && (*cur < 'C' || *cur > 'F')) return false; if (++cur >= eof) return true; if (!char_is_hex(*cur)) return false; return true; } return false; } /* 2 to 4 bytes UTF-8 */ if (is_truncated_utf8(cur, eof)) { return true; } } if (has_allow(COMMENTS)) { if (code == YYJSON_READ_ERROR_INVALID_COMMENT) { /* unclosed multiline comment */ return true; } if (code == YYJSON_READ_ERROR_UNEXPECTED_CHARACTER && *cur == '/' && cur + 1 == eof) { /* truncated beginning of comment */ return true; } } if (code == YYJSON_READ_ERROR_UNEXPECTED_CHARACTER && has_allow(BOM)) { /* truncated UTF-8 BOM */ usize len = (usize)(eof - cur); if (cur == hdr && len < 3 && !memcmp(hdr, "\xEF\xBB\xBF", len)) { return true; } } return false; } #if !YYJSON_DISABLE_FAST_FP_CONV /* FP_READER */ /*============================================================================== * MARK: - BigInt For Floating Point Number Reader (Private) * * The bigint algorithm is used by floating-point number reader to get correctly * rounded result for numbers with lots of digits. This part of code is rarely * used for common numbers. *============================================================================*/ /** Unsigned arbitrarily large integer */ typedef struct bigint { u32 used; /* used chunks count, should not be 0 */ u64 bits[64]; /* chunks (58 is enough here) */ } bigint; /** Evaluate 'big += val'. @param big A big number (can be 0). @param val An unsigned integer (can be 0). */ static_inline void bigint_add_u64(bigint *big, u64 val) { u32 idx, max; u64 num = big->bits[0]; u64 add = num + val; big->bits[0] = add; if (likely((add >= num) || (add >= val))) return; for ((void)(idx = 1), max = big->used; idx < max; idx++) { if (likely(big->bits[idx] != U64_MAX)) { big->bits[idx] += 1; return; } big->bits[idx] = 0; } big->bits[big->used++] = 1; } /** Evaluate 'big *= val'. @param big A big number (can be 0). @param val An unsigned integer (cannot be 0). */ static_inline void bigint_mul_u64(bigint *big, u64 val) { u32 idx = 0, max = big->used; u64 hi, lo, carry = 0; for (; idx < max; idx++) { if (big->bits[idx]) break; } for (; idx < max; idx++) { u128_mul_add(big->bits[idx], val, carry, &hi, &lo); big->bits[idx] = lo; carry = hi; } if (carry) big->bits[big->used++] = carry; } /** Evaluate 'big *= 2^exp'. @param big A big number (can be 0). @param exp An exponent integer (can be 0). */ static_inline void bigint_mul_pow2(bigint *big, u32 exp) { u32 shft = exp % 64; u32 move = exp / 64; u32 idx = big->used; if (unlikely(shft == 0)) { for (; idx > 0; idx--) { big->bits[idx + move - 1] = big->bits[idx - 1]; } big->used += move; while (move) big->bits[--move] = 0; } else { big->bits[idx] = 0; for (; idx > 0; idx--) { u64 num = big->bits[idx] << shft; num |= big->bits[idx - 1] >> (64 - shft); big->bits[idx + move] = num; } big->bits[move] = big->bits[0] << shft; big->used += move + (big->bits[big->used + move] > 0); while (move) big->bits[--move] = 0; } } /** Evaluate 'big *= 10^exp'. @param big A big number (can be 0). @param exp An exponent integer (cannot be 0). */ static_inline void bigint_mul_pow10(bigint *big, i32 exp) { for (; exp >= U64_POW10_MAX_EXACT_EXP; exp -= U64_POW10_MAX_EXACT_EXP) { bigint_mul_u64(big, u64_pow10_table[U64_POW10_MAX_EXACT_EXP]); } if (exp) { bigint_mul_u64(big, u64_pow10_table[exp]); } } /** Compare two bigint. @return -1 if 'a < b', +1 if 'a > b', 0 if 'a == b'. */ static_inline i32 bigint_cmp(bigint *a, bigint *b) { u32 idx = a->used; if (a->used < b->used) return -1; if (a->used > b->used) return +1; while (idx-- > 0) { u64 av = a->bits[idx]; u64 bv = b->bits[idx]; if (av < bv) return -1; if (av > bv) return +1; } return 0; } /** Evaluate 'big = val'. @param big A big number (can be 0). @param val An unsigned integer (can be 0). */ static_inline void bigint_set_u64(bigint *big, u64 val) { big->used = 1; big->bits[0] = val; } /** Set a bigint with floating point number string. */ static_noinline void bigint_set_buf(bigint *big, u64 sig, i32 *exp, u8 *sig_cut, u8 *sig_end, u8 *dot_pos) { if (unlikely(!sig_cut)) { /* no digit cut, set significant part only */ bigint_set_u64(big, sig); return; } else { /* some digits were cut, read them from 'sig_cut' to 'sig_end' */ u8 *hdr = sig_cut; u8 *cur = hdr; u32 len = 0; u64 val = 0; bool dig_big_cut = false; bool has_dot = (hdr < dot_pos) & (dot_pos < sig_end); u32 dig_len_total = U64_SAFE_DIG + (u32)(sig_end - hdr) - has_dot; sig -= (*sig_cut >= '5'); /* sig was rounded before */ if (dig_len_total > F64_MAX_DEC_DIG) { dig_big_cut = true; sig_end -= dig_len_total - (F64_MAX_DEC_DIG + 1); sig_end -= (dot_pos + 1 == sig_end); dig_len_total = (F64_MAX_DEC_DIG + 1); } *exp -= (i32)dig_len_total - U64_SAFE_DIG; big->used = 1; big->bits[0] = sig; while (cur < sig_end) { if (likely(cur != dot_pos)) { val = val * 10 + (u8)(*cur++ - '0'); len++; if (unlikely(cur == sig_end && dig_big_cut)) { /* The last digit must be non-zero, */ /* set it to '1' for correct rounding. */ val = val - (val % 10) + 1; } if (len == U64_SAFE_DIG || cur == sig_end) { bigint_mul_pow10(big, (i32)len); bigint_add_u64(big, val); val = 0; len = 0; } } else { cur++; } } } } /*============================================================================== * MARK: - Diy Floating Point (Private) *============================================================================*/ /** "Do It Yourself Floating Point" struct. */ typedef struct diy_fp { u64 sig; /* significand */ i32 exp; /* exponent, base 2 */ i32 pad; /* padding, useless */ } diy_fp; /** Get cached rounded diy_fp with pow(10, e) The input value must in range [POW10_SIG_TABLE_MIN_EXP, POW10_SIG_TABLE_MAX_EXP]. */ static_inline diy_fp diy_fp_get_cached_pow10(i32 exp10) { diy_fp fp; u64 sig_ext; pow10_table_get_sig(exp10, &fp.sig, &sig_ext); pow10_table_get_exp(exp10, &fp.exp); fp.sig += (sig_ext >> 63); return fp; } /** Returns fp * fp2. */ static_inline diy_fp diy_fp_mul(diy_fp fp, diy_fp fp2) { u64 hi, lo; u128_mul(fp.sig, fp2.sig, &hi, &lo); fp.sig = hi + (lo >> 63); fp.exp += fp2.exp + 64; return fp; } /** Convert diy_fp to IEEE-754 raw value. */ static_inline u64 diy_fp_to_ieee_raw(diy_fp fp) { u64 sig = fp.sig; i32 exp = fp.exp; u32 lz_bits; if (unlikely(fp.sig == 0)) return 0; lz_bits = u64_lz_bits(sig); sig <<= lz_bits; sig >>= F64_BITS - F64_SIG_FULL_BITS; exp -= (i32)lz_bits; exp += F64_BITS - F64_SIG_FULL_BITS; exp += F64_SIG_BITS; if (unlikely(exp >= F64_MAX_BIN_EXP)) { /* overflow */ return F64_BITS_INF; } else if (likely(exp >= F64_MIN_BIN_EXP - 1)) { /* normal */ exp += F64_EXP_BIAS; return ((u64)exp << F64_SIG_BITS) | (sig & F64_SIG_MASK); } else if (likely(exp >= F64_MIN_BIN_EXP - F64_SIG_FULL_BITS)) { /* subnormal */ return sig >> (F64_MIN_BIN_EXP - exp - 1); } else { /* underflow */ return 0; } } /*============================================================================== * MARK: - Number Reader (Private) *============================================================================*/ /** Read a JSON number. 1. This function assume that the floating-point number is in IEEE-754 format. 2. This function support uint64/int64/double number. If an integer number cannot fit in uint64/int64, it will returns as a double number. If a double number is infinite, the return value is based on flag. 3. This function (with inline attribute) may generate a lot of instructions. */ static_inline bool read_num(u8 **ptr, u8 **pre, yyjson_read_flag flg, yyjson_val *val, const char **msg) { #define return_err(_pos, _msg) do { \ *msg = _msg; \ *end = _pos; \ return false; \ } while (false) #define return_0() do { \ val->tag = YYJSON_TYPE_NUM | (u8)((u8)sign << 3); \ val->uni.u64 = 0; \ *end = cur; return true; \ } while (false) #define return_i64(_v) do { \ val->tag = YYJSON_TYPE_NUM | (u8)((u8)sign << 3); \ val->uni.u64 = (u64)(sign ? (u64)(~(_v) + 1) : (u64)(_v)); \ *end = cur; return true; \ } while (false) #define return_f64(_v) do { \ val->tag = YYJSON_TYPE_NUM | YYJSON_SUBTYPE_REAL; \ val->uni.f64 = sign ? -(f64)(_v) : (f64)(_v); \ *end = cur; return true; \ } while (false) #define return_f64_bin(_v) do { \ val->tag = YYJSON_TYPE_NUM | YYJSON_SUBTYPE_REAL; \ val->uni.u64 = ((u64)sign << 63) | (u64)(_v); \ *end = cur; return true; \ } while (false) #define return_inf() do { \ if (has_flg(BIGNUM_AS_RAW)) return_raw(); \ if (has_allow(INF_AND_NAN)) return_f64_bin(F64_BITS_INF); \ else return_err(hdr, "number is infinity when parsed as double"); \ } while (false) #define return_raw() do { \ **pre = '\0'; /* add null-terminator for previous raw string */ \ val->tag = ((u64)(cur - hdr) << YYJSON_TAG_BIT) | YYJSON_TYPE_RAW; \ val->uni.str = (const char *)hdr; \ *pre = cur; *end = cur; return true; \ } while (false) u8 *sig_cut = NULL; /* significant part cutting position for long number */ u8 *sig_end = NULL; /* significant part ending position */ u8 *dot_pos = NULL; /* decimal point position */ u64 sig = 0; /* significant part of the number */ i32 exp = 0; /* exponent part of the number */ bool exp_sign; /* temporary exponent sign from literal part */ i64 exp_sig = 0; /* temporary exponent number from significant part */ i64 exp_lit = 0; /* temporary exponent number from exponent literal part */ u64 num; /* temporary number for reading */ u8 *tmp; /* temporary cursor for reading */ u8 *hdr = *ptr; u8 *cur = *ptr; u8 **end = ptr; bool sign; /* read number as raw string if has `YYJSON_READ_NUMBER_AS_RAW` flag */ if (has_flg(NUMBER_AS_RAW)) { return read_num_raw(ptr, pre, flg, val, msg); } sign = (*hdr == '-'); cur += sign; /* begin with a leading zero or non-digit */ while (unlikely(!char_is_nonzero(*cur))) { /* 0 or non-digit char */ if (unlikely(*cur != '0')) { /* non-digit char */ if (has_allow(EXT_NUMBER)) { if (*cur == '+' && cur == hdr) { /* leading `+` sign */ cur++; continue; } if (*cur == '.' && char_is_digit(cur[1])) { /* e.g. '.123' */ goto leading_dot; } } if (has_allow(INF_AND_NAN)) { if (read_inf_or_nan(ptr, pre, flg, val)) return true; } return_err(cur, "no digit after sign"); } /* begin with 0 */ if (likely(!char_is_digit_or_fp(*++cur))) { if (has_allow(EXT_NUMBER) && char_to_lower(*cur) == 'x') { /* hex */ return read_num_hex(ptr, pre, flg, val, msg); } return_0(); } if (likely(*cur == '.')) { leading_dot: dot_pos = cur++; if (unlikely(!char_is_digit(*cur))) { if (has_allow(EXT_NUMBER)) { if (char_is_exp(*cur)) { goto digi_exp_more; } else { return_f64_bin(0); } } return_err(cur, "no digit after decimal point"); } while (unlikely(*cur == '0')) cur++; if (likely(char_is_digit(*cur))) { /* first non-zero digit after decimal point */ sig = (u64)(*cur - '0'); /* read first digit */ cur--; goto digi_frac_1; /* continue read fraction part */ } } if (unlikely(char_is_digit(*cur))) { return_err(cur - 1, "number with leading zero is not allowed"); } if (unlikely(char_is_exp(*cur))) { /* 0 with any exponent is still 0 */ cur += (usize)1 + char_is_sign(cur[1]); if (unlikely(!char_is_digit(*cur))) { return_err(cur, "no digit after exponent sign"); } while (char_is_digit(*++cur)); } return_f64_bin(0); } /* begin with non-zero digit */ sig = (u64)(*cur - '0'); /* Read integral part, same as the following code. for (int i = 1; i <= 18; i++) { num = cur[i] - '0'; if (num <= 9) sig = num + sig * 10; else goto digi_sepr_i; } */ #define expr_intg(i) \ if (likely((num = (u64)(cur[i] - (u8)'0')) <= 9)) sig = num + sig * 10; \ else { goto digi_sepr_##i; } repeat_in_1_18(expr_intg) #undef expr_intg cur += 19; /* skip continuous 19 digits */ if (!char_is_digit_or_fp(*cur)) { /* this number is an integer consisting of 19 digits */ if (sign && (sig > ((u64)1 << 63))) { /* overflow */ if (has_flg(BIGNUM_AS_RAW)) return_raw(); return_f64(unsafe_yyjson_u64_to_f64(sig)); } return_i64(sig); } goto digi_intg_more; /* read more digits in integral part */ /* process first non-digit character */ #define expr_sepr(i) \ digi_sepr_##i: \ if (likely(!char_is_fp(cur[i]))) { cur += i; return_i64(sig); } \ dot_pos = cur + i; \ if (likely(cur[i] == '.')) goto digi_frac_##i; \ cur += i; sig_end = cur; goto digi_exp_more; repeat_in_1_18(expr_sepr) #undef expr_sepr /* read fraction part */ #define expr_frac(i) \ digi_frac_##i: \ if (likely((num = (u64)(cur[i + 1] - (u8)'0')) <= 9)) \ sig = num + sig * 10; \ else { goto digi_stop_##i; } repeat_in_1_18(expr_frac) #undef expr_frac cur += 20; /* skip 19 digits and 1 decimal point */ if (!char_is_digit(*cur)) goto digi_frac_end; /* fraction part end */ goto digi_frac_more; /* read more digits in fraction part */ /* significant part end */ #define expr_stop(i) \ digi_stop_##i: \ cur += i + 1; \ goto digi_frac_end; repeat_in_1_18(expr_stop) #undef expr_stop /* read more digits in integral part */ digi_intg_more: if (char_is_digit(*cur)) { if (!char_is_digit_or_fp(cur[1])) { /* this number is an integer consisting of 20 digits */ num = (u64)(*cur - '0'); if ((sig < (U64_MAX / 10)) || (sig == (U64_MAX / 10) && num <= (U64_MAX % 10))) { sig = num + sig * 10; cur++; /* convert to double if overflow */ if (sign) { if (has_flg(BIGNUM_AS_RAW)) return_raw(); return_f64(unsafe_yyjson_u64_to_f64(sig)); } return_i64(sig); } } } if (char_is_exp(*cur)) { dot_pos = cur; goto digi_exp_more; } if (*cur == '.') { dot_pos = cur++; if (unlikely(!char_is_digit(*cur))) { if (has_allow(EXT_NUMBER)) { goto digi_frac_end; } return_err(cur, "no digit after decimal point"); } } /* read more digits in fraction part */ digi_frac_more: sig_cut = cur; /* too large to fit in u64, excess digits need to be cut */ sig += (*cur >= '5'); /* round */ while (char_is_digit(*++cur)); if (!dot_pos) { if (!char_is_fp(*cur) && has_flg(BIGNUM_AS_RAW)) { return_raw(); /* it's a large integer */ } dot_pos = cur; if (*cur == '.') { if (unlikely(!char_is_digit(*++cur))) { if (!has_allow(EXT_NUMBER)) { return_err(cur, "no digit after decimal point"); } } while (char_is_digit(*cur)) cur++; } } exp_sig = (i64)(dot_pos - sig_cut); exp_sig += (dot_pos < sig_cut); /* ignore trailing zeros */ tmp = cur - 1; while ((*tmp == '0' || *tmp == '.') && tmp > hdr) tmp--; if (tmp < sig_cut) { sig_cut = NULL; } else { sig_end = cur; } if (char_is_exp(*cur)) goto digi_exp_more; goto digi_exp_finish; /* fraction part end */ digi_frac_end: if (unlikely(dot_pos + 1 == cur)) { if (!has_allow(EXT_NUMBER)) { return_err(cur, "no digit after decimal point"); } } sig_end = cur; exp_sig = -(i64)((u64)(cur - dot_pos) - 1); if (likely(!char_is_exp(*cur))) { if (unlikely(exp_sig < F64_MIN_DEC_EXP - 19)) { return_f64_bin(0); /* underflow */ } exp = (i32)exp_sig; goto digi_finish; } else { goto digi_exp_more; } /* read exponent part */ digi_exp_more: exp_sign = (*++cur == '-'); cur += char_is_sign(*cur); if (unlikely(!char_is_digit(*cur))) { return_err(cur, "no digit after exponent sign"); } while (*cur == '0') cur++; /* read exponent literal */ tmp = cur; while (char_is_digit(*cur)) { exp_lit = (i64)((u8)(*cur++ - '0') + (u64)exp_lit * 10); } if (unlikely(cur - tmp >= U64_SAFE_DIG)) { if (exp_sign) { return_f64_bin(0); /* underflow */ } else { return_inf(); /* overflow */ } } exp_sig += exp_sign ? -exp_lit : exp_lit; /* validate exponent value */ digi_exp_finish: if (unlikely(exp_sig < F64_MIN_DEC_EXP - 19)) { return_f64_bin(0); /* underflow */ } if (unlikely(exp_sig > F64_MAX_DEC_EXP)) { return_inf(); /* overflow */ } exp = (i32)exp_sig; /* all digit read finished */ digi_finish: /* Fast path 1: 1. The floating-point number calculation should be accurate, see the comments of macro `YYJSON_DOUBLE_MATH_CORRECT`. 2. Correct rounding should be performed (fegetround() == FE_TONEAREST). 3. The input of floating point number calculation does not lose precision, which means: 64 - leading_zero(input) - trailing_zero(input) < 53. We don't check all available inputs here, because that would make the code more complicated, and not friendly to branch predictor. */ #if YYJSON_DOUBLE_MATH_CORRECT if (sig < ((u64)1 << 53) && exp >= -F64_POW10_MAX_EXACT_EXP && exp <= +F64_POW10_MAX_EXACT_EXP) { f64 dbl = (f64)sig; if (exp < 0) { dbl /= f64_pow10_table[-exp]; } else { dbl *= f64_pow10_table[+exp]; } return_f64(dbl); } #endif /* Fast path 2: To keep it simple, we only accept normal number here, let the slow path to handle subnormal and infinity number. */ if (likely(!sig_cut && exp > -F64_MAX_DEC_EXP + 1 && exp < +F64_MAX_DEC_EXP - 20)) { /* The result value is exactly equal to (sig * 10^exp), the exponent part (10^exp) can be converted to (sig2 * 2^exp2). The sig2 can be an infinite length number, only the highest 128 bits is cached in the pow10_sig_table. Now we have these bits: sig1 (normalized 64bit) : aaaaaaaa sig2 (higher 64bit) : bbbbbbbb sig2_ext (lower 64bit) : cccccccc sig2_cut (extra unknown bits) : dddddddddddd.... And the calculation process is: ---------------------------------------- aaaaaaaa * bbbbbbbbccccccccdddddddddddd.... ---------------------------------------- abababababababab + acacacacacacacac + adadadadadadadadadad.... ---------------------------------------- [hi____][lo____] + [hi2___][lo2___] + [unknown___________....] ---------------------------------------- The addition with carry may affect higher bits, but if there is a 0 in higher bits, the bits higher than 0 will not be affected. `lo2` + `unknown` may get a carry bit and may affect `hi2`, the max value of `hi2` is 0xFFFFFFFFFFFFFFFE, so `hi2` will not overflow. `lo` + `hi2` may also get a carry bit and may affect `hi`, but only the highest significant 53 bits of `hi` is needed. If there is a 0 in the lower bits of `hi`, then all the following bits can be dropped. To convert the result to IEEE-754 double number, we need to perform correct rounding: 1. if bit 54 is 0, round down, 2. if bit 54 is 1 and any bit beyond bit 54 is 1, round up, 3. if bit 54 is 1 and all bits beyond bit 54 are 0, round to even, as the extra bits is unknown, this case will not be handled here. */ u64 raw; u64 sig1, sig2, sig2_ext, hi, lo, hi2, lo2, add, bits; i32 exp2; u32 lz; bool exact = false, carry, round_up; /* convert (10^exp) to (sig2 * 2^exp2) */ pow10_table_get_sig(exp, &sig2, &sig2_ext); pow10_table_get_exp(exp, &exp2); /* normalize and multiply */ lz = u64_lz_bits(sig); sig1 = sig << lz; exp2 -= (i32)lz; u128_mul(sig1, sig2, &hi, &lo); /* The `hi` is in range [0x4000000000000000, 0xFFFFFFFFFFFFFFFE], To get normalized value, `hi` should be shifted to the left by 0 or 1. The highest significant 53 bits is used by IEEE-754 double number, and the bit 54 is used to detect rounding direction. The lowest (64 - 54 - 1) bits is used to check whether it contains 0. */ bits = hi & (((u64)1 << (64 - 54 - 1)) - 1); if (bits - 1 < (((u64)1 << (64 - 54 - 1)) - 2)) { /* (bits != 0 && bits != 0x1FF) => (bits - 1 < 0x1FF - 1) The `bits` is not zero, so we don't need to check `round to even` case. The `bits` contains bit `0`, so we can drop the extra bits after `0`. */ exact = true; } else { /* (bits == 0 || bits == 0x1FF) The `bits` is filled with all `0` or all `1`, so we need to check lower bits with another 64-bit multiplication. */ u128_mul(sig1, sig2_ext, &hi2, &lo2); add = lo + hi2; if (add + 1 > (u64)1) { /* (add != 0 && add != U64_MAX) => (add + 1 > 1) The `add` is not zero, so we don't need to check `round to even` case. The `add` contains bit `0`, so we can drop the extra bits after `0`. The `hi` cannot be U64_MAX, so it will not overflow. */ carry = add < lo || add < hi2; hi += carry; exact = true; } } if (exact) { /* normalize */ lz = hi < ((u64)1 << 63); hi <<= lz; exp2 -= (i32)lz; exp2 += 64; /* test the bit 54 and get rounding direction */ round_up = (hi & ((u64)1 << (64 - 54))) > (u64)0; hi += (round_up ? ((u64)1 << (64 - 54)) : (u64)0); /* test overflow */ if (hi < ((u64)1 << (64 - 54))) { hi = ((u64)1 << 63); exp2 += 1; } /* This is a normal number, convert it to IEEE-754 format. */ hi >>= F64_BITS - F64_SIG_FULL_BITS; exp2 += F64_BITS - F64_SIG_FULL_BITS + F64_SIG_BITS; exp2 += F64_EXP_BIAS; raw = ((u64)exp2 << F64_SIG_BITS) | (hi & F64_SIG_MASK); return_f64_bin(raw); } } /* Slow path: read double number exactly with diyfp. 1. Use cached diyfp to get an approximation value. 2. Use bigcomp to check the approximation value if needed. This algorithm refers to google's double-conversion project: https://github.com/google/double-conversion */ { const i32 ERR_ULP_LOG = 3; const i32 ERR_ULP = 1 << ERR_ULP_LOG; const i32 ERR_CACHED_POW = ERR_ULP / 2; const i32 ERR_MUL_FIXED = ERR_ULP / 2; const i32 DIY_SIG_BITS = 64; const i32 EXP_BIAS = F64_EXP_BIAS + F64_SIG_BITS; const i32 EXP_SUBNORMAL = -EXP_BIAS + 1; u64 fp_err; u32 bits; i32 order_of_magnitude; i32 effective_significand_size; i32 precision_digits_count; u64 precision_bits; u64 half_way; u64 raw; diy_fp fp, fp_upper; bigint big_full, big_comp; i32 cmp; fp.sig = sig; fp.exp = 0; fp_err = sig_cut ? (u64)(ERR_ULP / 2) : (u64)0; /* normalize */ bits = u64_lz_bits(fp.sig); fp.sig <<= bits; fp.exp -= (i32)bits; fp_err <<= bits; /* multiply and add error */ fp = diy_fp_mul(fp, diy_fp_get_cached_pow10(exp)); fp_err += (u64)ERR_CACHED_POW + (fp_err != 0) + (u64)ERR_MUL_FIXED; /* normalize */ bits = u64_lz_bits(fp.sig); fp.sig <<= bits; fp.exp -= (i32)bits; fp_err <<= bits; /* effective significand */ order_of_magnitude = DIY_SIG_BITS + fp.exp; if (likely(order_of_magnitude >= EXP_SUBNORMAL + F64_SIG_FULL_BITS)) { effective_significand_size = F64_SIG_FULL_BITS; } else if (order_of_magnitude <= EXP_SUBNORMAL) { effective_significand_size = 0; } else { effective_significand_size = order_of_magnitude - EXP_SUBNORMAL; } /* precision digits count */ precision_digits_count = DIY_SIG_BITS - effective_significand_size; if (unlikely(precision_digits_count + ERR_ULP_LOG >= DIY_SIG_BITS)) { i32 shr = (precision_digits_count + ERR_ULP_LOG) - DIY_SIG_BITS + 1; fp.sig >>= shr; fp.exp += shr; fp_err = (fp_err >> shr) + 1 + (u32)ERR_ULP; precision_digits_count -= shr; } /* half way */ precision_bits = fp.sig & (((u64)1 << precision_digits_count) - 1); precision_bits *= (u32)ERR_ULP; half_way = (u64)1 << (precision_digits_count - 1); half_way *= (u32)ERR_ULP; /* rounding */ fp.sig >>= precision_digits_count; fp.sig += (precision_bits >= half_way + fp_err); fp.exp += precision_digits_count; /* get IEEE double raw value */ raw = diy_fp_to_ieee_raw(fp); if (unlikely(raw == F64_BITS_INF)) return_inf(); if (likely(precision_bits <= half_way - fp_err || precision_bits >= half_way + fp_err)) { return_f64_bin(raw); /* number is accurate */ } /* now the number is the correct value, or the next lower value */ /* upper boundary */ if (raw & F64_EXP_MASK) { fp_upper.sig = (raw & F64_SIG_MASK) + ((u64)1 << F64_SIG_BITS); fp_upper.exp = (i32)((raw & F64_EXP_MASK) >> F64_SIG_BITS); } else { fp_upper.sig = (raw & F64_SIG_MASK); fp_upper.exp = 1; } fp_upper.exp -= F64_EXP_BIAS + F64_SIG_BITS; fp_upper.sig <<= 1; fp_upper.exp -= 1; fp_upper.sig += 1; /* add half ulp */ /* compare with bigint */ bigint_set_buf(&big_full, sig, &exp, sig_cut, sig_end, dot_pos); bigint_set_u64(&big_comp, fp_upper.sig); if (exp >= 0) { bigint_mul_pow10(&big_full, +exp); } else { bigint_mul_pow10(&big_comp, -exp); } if (fp_upper.exp > 0) { bigint_mul_pow2(&big_comp, (u32)+fp_upper.exp); } else { bigint_mul_pow2(&big_full, (u32)-fp_upper.exp); } cmp = bigint_cmp(&big_full, &big_comp); if (likely(cmp != 0)) { /* round down or round up */ raw += (cmp > 0); } else { /* falls midway, round to even */ raw += (raw & 1); } if (unlikely(raw == F64_BITS_INF)) return_inf(); return_f64_bin(raw); } #undef return_err #undef return_inf #undef return_0 #undef return_i64 #undef return_f64 #undef return_f64_bin #undef return_raw } #else /* FP_READER */ /** Read a JSON number. This is a fallback function if the custom number reader is disabled. This function use libc's strtod() to read floating-point number. */ static_inline bool read_num(u8 **ptr, u8 **pre, yyjson_read_flag flg, yyjson_val *val, const char **msg) { #define return_err(_pos, _msg) do { \ *msg = _msg; \ *end = _pos; \ return false; \ } while (false) #define return_0() do { \ val->tag = YYJSON_TYPE_NUM | (u64)((u8)sign << 3); \ val->uni.u64 = 0; \ *end = cur; return true; \ } while (false) #define return_i64(_v) do { \ val->tag = YYJSON_TYPE_NUM | (u64)((u8)sign << 3); \ val->uni.u64 = (u64)(sign ? (u64)(~(_v) + 1) : (u64)(_v)); \ *end = cur; return true; \ } while (false) #define return_f64(_v) do { \ val->tag = YYJSON_TYPE_NUM | YYJSON_SUBTYPE_REAL; \ val->uni.f64 = sign ? -(f64)(_v) : (f64)(_v); \ *end = cur; return true; \ } while (false) #define return_f64_bin(_v) do { \ val->tag = YYJSON_TYPE_NUM | YYJSON_SUBTYPE_REAL; \ val->uni.u64 = ((u64)sign << 63) | (u64)(_v); \ *end = cur; return true; \ } while (false) #define return_inf() do { \ if (has_flg(BIGNUM_AS_RAW)) return_raw(); \ if (has_allow(INF_AND_NAN)) return_f64_bin(F64_BITS_INF); \ else return_err(hdr, "number is infinity when parsed as double"); \ } while (false) #define return_raw() do { \ val->tag = ((u64)(cur - hdr) << YYJSON_TAG_BIT) | YYJSON_TYPE_RAW; \ val->uni.str = (const char *)hdr; \ **pre = '\0'; *pre = cur; *end = cur; return true; \ } while (false) u64 sig, num; u8 *hdr = *ptr; u8 *cur = *ptr; u8 **end = ptr; u8 *dot = NULL; u8 *f64_end = NULL; bool sign; /* read number as raw string if has `YYJSON_READ_NUMBER_AS_RAW` flag */ if (has_flg(NUMBER_AS_RAW)) { return read_num_raw(ptr, pre, flg, val, msg); } sign = (*hdr == '-'); cur += sign; sig = (u8)(*cur - '0'); /* read first digit, check leading zero */ while (unlikely(!char_is_digit(*cur))) { if (has_allow(EXT_NUMBER)) { if (*cur == '+' && cur == hdr) { /* leading `+` sign */ cur++; sig = (u8)(*cur - '0'); continue; } if (*cur == '.' && char_is_num(cur[1])) { /* no integer part */ goto read_double; /* e.g. '.123' */ } } if (has_allow(INF_AND_NAN)) { if (read_inf_or_nan(ptr, pre, flg, val)) return true; } return_err(cur, "no digit after sign"); } if (*cur == '0') { cur++; if (unlikely(char_is_digit(*cur))) { return_err(cur - 1, "number with leading zero is not allowed"); } if (!char_is_fp(*cur)) { if (has_allow(EXT_NUMBER) && (*cur == 'x' || *cur == 'X')) { /* hex integer */ return read_num_hex(ptr, pre, flg, val, msg); } return_0(); } goto read_double; } /* read continuous digits, up to 19 characters */ #define expr_intg(i) \ if (likely((num = (u64)(cur[i] - (u8)'0')) <= 9)) sig = num + sig * 10; \ else { cur += i; goto intg_end; } repeat_in_1_18(expr_intg) #undef expr_intg /* here are 19 continuous digits, skip them */ cur += 19; if (char_is_digit(cur[0]) && !char_is_digit_or_fp(cur[1])) { /* this number is an integer consisting of 20 digits */ num = (u8)(*cur - '0'); if ((sig < (U64_MAX / 10)) || (sig == (U64_MAX / 10) && num <= (U64_MAX % 10))) { sig = num + sig * 10; cur++; if (sign) { if (has_flg(BIGNUM_AS_RAW)) return_raw(); return_f64(unsafe_yyjson_u64_to_f64(sig)); } return_i64(sig); } } intg_end: /* continuous digits ended */ if (!char_is_digit_or_fp(*cur)) { /* this number is an integer consisting of 1 to 19 digits */ if (sign && (sig > ((u64)1 << 63))) { if (has_flg(BIGNUM_AS_RAW)) return_raw(); return_f64(unsafe_yyjson_u64_to_f64(sig)); } return_i64(sig); } read_double: /* this number should be read as double */ while (char_is_digit(*cur)) cur++; if (!char_is_fp(*cur) && has_flg(BIGNUM_AS_RAW)) { return_raw(); /* it's a large integer */ } while (*cur == '.') { /* skip fraction part */ dot = cur; cur++; if (!char_is_digit(*cur)) { if (has_allow(EXT_NUMBER)) { break; } else { return_err(cur, "no digit after decimal point"); } } cur++; while (char_is_digit(*cur)) cur++; break; } if (char_is_exp(*cur)) { /* skip exponent part */ cur += 1 + char_is_sign(cur[1]); if (!char_is_digit(*cur)) { return_err(cur, "no digit after exponent sign"); } cur++; while (char_is_digit(*cur)) cur++; } /* libc's strtod() is used to parse the floating-point number. Note that the decimal point character used by strtod() is locale-dependent, and the rounding direction may affected by fesetround(). For currently known locales, (en, zh, ja, ko, am, he, hi) use '.' as the decimal point, while other locales use ',' as the decimal point. Here strtod() is called twice for different locales, but if another thread happens calls setlocale() between two strtod(), parsing may still fail. */ val->uni.f64 = strtod((const char *)hdr, (char **)&f64_end); if (unlikely(f64_end != cur)) { /* replace '.' with ',' for locale */ bool cut = (*cur == ','); if (cut) *cur = ' '; if (dot) *dot = ','; val->uni.f64 = strtod((const char *)hdr, (char **)&f64_end); /* restore ',' to '.' */ if (cut) *cur = ','; if (dot) *dot = '.'; if (unlikely(f64_end != cur)) { return_err(hdr, "strtod() failed to parse the number"); } } if (unlikely(val->uni.f64 >= HUGE_VAL || val->uni.f64 <= -HUGE_VAL)) { return_inf(); } val->tag = YYJSON_TYPE_NUM | YYJSON_SUBTYPE_REAL; *end = cur; return true; #undef return_err #undef return_0 #undef return_i64 #undef return_f64 #undef return_f64_bin #undef return_inf #undef return_raw } #endif /* FP_READER */ /*============================================================================== * MARK: - String Reader (Private) *============================================================================*/ /** Read unicode escape sequence. */ static_inline bool read_uni_esc(u8 **src_ptr, u8 **dst_ptr, const char **msg) { #define return_err(_end, _msg) *msg = _msg; *src_ptr = _end; return false u8 *src = *src_ptr; u8 *dst = *dst_ptr; u16 hi, lo; u32 uni; src += 2; /* skip `\u` */ if (unlikely(!hex_load_4(src, &hi))) { return_err(src - 2, "invalid escaped sequence in string"); } src += 4; /* skip hex */ if (likely((hi & 0xF800) != 0xD800)) { /* a BMP character */ if (hi >= 0x800) { *dst++ = (u8)(0xE0 | (hi >> 12)); *dst++ = (u8)(0x80 | ((hi >> 6) & 0x3F)); *dst++ = (u8)(0x80 | (hi & 0x3F)); } else if (hi >= 0x80) { *dst++ = (u8)(0xC0 | (hi >> 6)); *dst++ = (u8)(0x80 | (hi & 0x3F)); } else { *dst++ = (u8)hi; } } else { /* a non-BMP character, represented as a surrogate pair */ if (unlikely((hi & 0xFC00) != 0xD800)) { return_err(src - 6, "invalid high surrogate in string"); } if (unlikely(!byte_match_2(src, "\\u"))) { return_err(src - 6, "no low surrogate in string"); } if (unlikely(!hex_load_4(src + 2, &lo))) { return_err(src - 6, "invalid escape in string"); } if (unlikely((lo & 0xFC00) != 0xDC00)) { return_err(src - 6, "invalid low surrogate in string"); } uni = ((((u32)hi - 0xD800) << 10) | ((u32)lo - 0xDC00)) + 0x10000; *dst++ = (u8)(0xF0 | (uni >> 18)); *dst++ = (u8)(0x80 | ((uni >> 12) & 0x3F)); *dst++ = (u8)(0x80 | ((uni >> 6) & 0x3F)); *dst++ = (u8)(0x80 | (uni & 0x3F)); src += 6; } *src_ptr = src; *dst_ptr = dst; return true; #undef return_err } /** Read a JSON string. @param quo The quote character (single quote or double quote). @param ptr The head pointer of string before quote (inout). @param eof JSON end position. @param flg JSON read flag. @param val The string value to be written. @param msg The error message pointer. @param con Continuation for incremental parsing. @return Whether success. */ static_inline bool read_str_opt(u8 quo, u8 **ptr, u8 *eof, yyjson_read_flag flg, yyjson_val *val, const char **msg, u8 *con[2]) { /* GCC may sometimes load variables into registers too early, causing unnecessary instructions and performance degradation. This inline assembly serves as a hint to GCC: 'This variable will be modified, so avoid loading it too early.' Other compilers like MSVC, Clang, and ICC can generate the expected instructions without needing this hint. Check out this example: https://godbolt.org/z/YG6a5W5Ec */ #define return_err(_end, _msg) do { \ *msg = _msg; \ *end = _end; \ if (con) { con[0] = _end; con[1] = dst; } \ return false; \ } while (false) u8 *hdr = *ptr + 1; u8 **end = ptr; u8 *src = hdr, *dst = NULL, *pos; u16 hi, lo; u32 uni, tmp; /* Resume incremental parsing. */ if (con && unlikely(con[0])) { src = con[0]; dst = con[1]; if (dst) goto copy_ascii; } skip_ascii: /* Most strings have no escaped characters, so we can jump them quickly. We want to make loop unrolling, as shown in the following code. Some compiler may not generate instructions as expected, so we rewrite it with explicit goto statements. We hope the compiler can generate instructions like this: https://godbolt.org/z/8vjsYq while (true) repeat16({ if (likely((char_is_ascii_skip(*src)))) src++; else break; }) */ if (quo == '"') { #define expr_jump(i) \ if (likely(char_is_ascii_skip(src[i]))) {} \ else goto skip_ascii_stop##i; #define expr_stop(i) \ skip_ascii_stop##i: \ src += i; \ goto skip_ascii_end; repeat16_incr(expr_jump) src += 16; goto skip_ascii; repeat16_incr(expr_stop) #undef expr_jump #undef expr_stop } else { #define expr_jump(i) \ if (likely(char_is_ascii_skip_sq(src[i]))) {} \ else goto skip_ascii_stop_sq##i; #define expr_stop(i) \ skip_ascii_stop_sq##i: \ src += i; \ goto skip_ascii_end; repeat16_incr(expr_jump) src += 16; goto skip_ascii; repeat16_incr(expr_stop) #undef expr_jump #undef expr_stop } skip_ascii_end: gcc_store_barrier(*src); if (likely(*src == quo)) { val->tag = ((u64)(src - hdr) << YYJSON_TAG_BIT) | YYJSON_TYPE_STR | (quo == '"' ? YYJSON_SUBTYPE_NOESC : 0); val->uni.str = (const char *)hdr; *src = '\0'; *end = src + 1; if (con) con[0] = con[1] = NULL; return true; } skip_utf8: if (*src & 0x80) { /* non-ASCII character */ /* Non-ASCII character appears here, which means that the text is likely to be written in non-English or emoticons. According to some common data set statistics, byte sequences of the same length may appear consecutively. We process the byte sequences of the same length in each loop, which is more friendly to branch prediction. */ pos = src; #if YYJSON_DISABLE_UTF8_VALIDATION while (true) repeat8({ if (likely((*src & 0xF0) == 0xE0)) src += 3; else break; }) if (*src < 0x80) goto skip_ascii; while (true) repeat8({ if (likely((*src & 0xE0) == 0xC0)) src += 2; else break; }) while (true) repeat8({ if (likely((*src & 0xF8) == 0xF0)) src += 4; else break; }) #else uni = byte_load_4(src); while (is_utf8_seq3(uni)) { src += 3; uni = byte_load_4(src); } if (is_utf8_seq1(uni)) goto skip_ascii; while (is_utf8_seq2(uni)) { src += 2; uni = byte_load_4(src); } while (is_utf8_seq4(uni)) { src += 4; uni = byte_load_4(src); } #endif if (unlikely(pos == src)) { if (has_allow(INVALID_UNICODE)) ++src; else return_err(src, "invalid UTF-8 encoding in string"); } goto skip_ascii; } /* The escape character appears, we need to copy it. */ dst = src; copy_escape: if (likely(*src == '\\')) { switch (*++src) { case '"': *dst++ = '"'; src++; break; case '\\': *dst++ = '\\'; src++; break; case '/': *dst++ = '/'; src++; break; case 'b': *dst++ = '\b'; src++; break; case 'f': *dst++ = '\f'; src++; break; case 'n': *dst++ = '\n'; src++; break; case 'r': *dst++ = '\r'; src++; break; case 't': *dst++ = '\t'; src++; break; case 'u': src--; if (!read_uni_esc(&src, &dst, msg)) return_err(src, *msg); break; default: { if (has_allow(EXT_ESCAPE)) { /* read extended escape (non-standard) */ switch (*src) { case '\'': *dst++ = '\''; src++; break; case 'a': *dst++ = '\a'; src++; break; case 'v': *dst++ = '\v'; src++; break; case '?': *dst++ = '\?'; src++; break; case 'e': *dst++ = 0x1B; src++; break; case '0': if (!char_is_digit(src[1])) { *dst++ = '\0'; src++; break; } return_err(src - 1, "octal escape is not allowed"); case '1': case '2': case '3': case '4': case '5': case '6': case '7': case '8': case '9': return_err(src - 1, "invalid number escape"); case 'x': { u8 c; if (hex_load_2(src + 1, &c)) { src += 3; if (c <= 0x7F) { /* 1-byte ASCII */ *dst++ = c; } else { /* 2-byte UTF-8 */ *dst++ = (u8)(0xC0 | (c >> 6)); *dst++ = (u8)(0x80 | (c & 0x3F)); } break; } return_err(src - 1, "invalid hex escape"); } case '\n': src++; break; case '\r': src++; src += (*src == '\n'); break; case 0xE2: /* Line terminator: U+2028, U+2029 */ if ((src[1] == 0x80 && src[2] == 0xA8) || (src[1] == 0x80 && src[2] == 0xA9)) { src += 3; } break; default: break; /* skip */ } } else if (quo == '\'' && *src == '\'') { *dst++ = '\''; src++; break; } else { return_err(src - 1, "invalid escaped sequence in string"); } } } } else if (likely(*src == quo)) { val->tag = ((u64)(dst - hdr) << YYJSON_TAG_BIT) | YYJSON_TYPE_STR; val->uni.str = (const char *)hdr; *dst = '\0'; *end = src + 1; if (con) con[0] = con[1] = NULL; return true; } else { if (!has_allow(INVALID_UNICODE)) { return_err(src, "unexpected control character in string"); } if (src >= eof) return_err(src, "unclosed string"); *dst++ = *src++; } copy_ascii: /* Copy continuous ASCII, loop unrolling, same as the following code: while (true) repeat16({ if (char_is_ascii_skip(*src)) *dst++ = *src++; else break; }) */ if (quo == '"') { #define expr_jump(i) \ if (likely((char_is_ascii_skip(src[i])))) {} \ else { gcc_store_barrier(src[i]); goto copy_ascii_stop_##i; } repeat16_incr(expr_jump) #undef expr_jump } else { #define expr_jump(i) \ if (likely((char_is_ascii_skip_sq(src[i])))) {} \ else { gcc_store_barrier(src[i]); goto copy_ascii_stop_##i; } repeat16_incr(expr_jump) #undef expr_jump } byte_move_16(dst, src); dst += 16; src += 16; goto copy_ascii; /* The memory is copied forward since `dst < src`. So it's safe to move one extra byte to reduce instruction count. */ #define expr_jump(i) \ copy_ascii_stop_##i: \ byte_move_forward(dst, src, i); \ dst += i; src += i; \ goto copy_utf8; repeat16_incr(expr_jump) #undef expr_jump copy_utf8: if (*src & 0x80) { /* non-ASCII character */ pos = src; uni = byte_load_4(src); #if YYJSON_DISABLE_UTF8_VALIDATION while (true) repeat4({ if ((uni & utf8_seq(b3_mask)) == utf8_seq(b3_patt)) { byte_copy_4(dst, &uni); dst += 3; src += 3; uni = byte_load_4(src); } else break; }) if ((uni & utf8_seq(b1_mask)) == utf8_seq(b1_patt)) goto copy_ascii; while (true) repeat4({ if ((uni & utf8_seq(b2_mask)) == utf8_seq(b2_patt)) { byte_copy_2(dst, &uni); dst += 2; src += 2; uni = byte_load_4(src); } else break; }) while (true) repeat4({ if ((uni & utf8_seq(b4_mask)) == utf8_seq(b4_patt)) { byte_copy_4(dst, &uni); dst += 4; src += 4; uni = byte_load_4(src); } else break; }) #else while (is_utf8_seq3(uni)) { byte_copy_4(dst, &uni); dst += 3; src += 3; uni = byte_load_4(src); } if (is_utf8_seq1(uni)) goto copy_ascii; while (is_utf8_seq2(uni)) { byte_copy_2(dst, &uni); dst += 2; src += 2; uni = byte_load_4(src); } while (is_utf8_seq4(uni)) { byte_copy_4(dst, &uni); dst += 4; src += 4; uni = byte_load_4(src); } #endif if (unlikely(pos == src)) { if (!has_allow(INVALID_UNICODE)) { return_err(src, MSG_ERR_UTF8); } goto copy_ascii_stop_1; } goto copy_ascii; } goto copy_escape; #undef return_err } static_inline bool read_str(u8 **ptr, u8 *eof, yyjson_read_flag flg, yyjson_val *val, const char **msg) { return read_str_opt('\"', ptr, eof, flg, val, msg, NULL); } static_inline bool read_str_con(u8 **ptr, u8 *eof, yyjson_read_flag flg, yyjson_val *val, const char **msg, u8 **con) { return read_str_opt('\"', ptr, eof, flg, val, msg, con); } static_noinline bool read_str_sq(u8 **ptr, u8 *eof, yyjson_read_flag flg, yyjson_val *val, const char **msg) { return read_str_opt('\'', ptr, eof, flg, val, msg, NULL); } /** Read unquoted key (identifier name). */ static_noinline bool read_str_id(u8 **ptr, u8 *eof, yyjson_read_flag flg, u8 **pre, yyjson_val *val, const char **msg) { #define return_err(_end, _msg) do { \ *msg = _msg; \ *end = _end; \ return false; \ } while (false) #define return_suc(_str_end, _cur_end) do { \ val->tag = ((u64)(_str_end - hdr) << YYJSON_TAG_BIT) | \ (u64)(YYJSON_TYPE_STR); \ val->uni.str = (const char *)hdr; \ *pre = _str_end; *end = _cur_end; \ return true; \ } while (false) u8 *hdr = *ptr; u8 **end = ptr; u8 *src = hdr, *dst = NULL; u16 hi, lo; u32 uni, tmp; /* add null-terminator for previous raw string */ **pre = '\0'; skip_ascii: #define expr_jump(i) \ if (likely(char_is_id_ascii(src[i]))) {} \ else goto skip_ascii_stop##i; #define expr_stop(i) \ skip_ascii_stop##i: \ src += i; \ goto skip_ascii_end; repeat16_incr(expr_jump) src += 16; goto skip_ascii; repeat16_incr(expr_stop) #undef expr_jump #undef expr_stop skip_ascii_end: gcc_store_barrier(*src); if (likely(!char_is_id_next(*src))) { return_suc(src, src); } skip_utf8: while (*src >= 0x80) { if (has_allow(EXT_WHITESPACE)) { if (char_is_space_ext(*src) && ext_space_len(src)) { return_suc(src, src); } } uni = byte_load_4(src); if (is_utf8_seq2(uni)) { src += 2; } else if (is_utf8_seq3(uni)) { src += 3; } else if (is_utf8_seq4(uni)) { src += 4; } else { #if !YYJSON_DISABLE_UTF8_VALIDATION if (!has_allow(INVALID_UNICODE)) return_err(src, MSG_ERR_UTF8); #endif src += 1; } } if (char_is_id_ascii(*src)) goto skip_ascii; /* The escape character appears, we need to copy it. */ dst = src; copy_escape: if (byte_match_2(src, "\\u")) { if (!read_uni_esc(&src, &dst, msg)) return_err(src, *msg); } else { if (!char_is_id_next(*src)) return_suc(dst, src); return_err(src, "unexpected character in key"); } copy_ascii: /* Copy continuous ASCII, loop unrolling, same as the following code: while (true) repeat16({ if (char_is_ascii_skip(*src)) *dst++ = *src++; else break; }) */ #define expr_jump(i) \ if (likely((char_is_id_ascii(src[i])))) {} \ else { gcc_store_barrier(src[i]); goto copy_ascii_stop_##i; } repeat16_incr(expr_jump) #undef expr_jump byte_move_16(dst, src); dst += 16; src += 16; goto copy_ascii; #define expr_jump(i) \ copy_ascii_stop_##i: \ byte_move_forward(dst, src, i); \ dst += i; src += i; \ goto copy_utf8; repeat16_incr(expr_jump) #undef expr_jump copy_utf8: while (*src >= 0x80) { /* non-ASCII character */ if (has_allow(EXT_WHITESPACE)) { if (char_is_space_ext(*src) && ext_space_len(src)) { return_suc(dst, src); } } uni = byte_load_4(src); if (is_utf8_seq2(uni)) { byte_copy_2(dst, &uni); dst += 2; src += 2; } else if (is_utf8_seq3(uni)) { byte_copy_4(dst, &uni); dst += 3; src += 3; } else if (is_utf8_seq4(uni)) { byte_copy_4(dst, &uni); dst += 4; src += 4; } else { #if !YYJSON_DISABLE_UTF8_VALIDATION if (!has_allow(INVALID_UNICODE)) return_err(src, MSG_ERR_UTF8); #endif *dst = *src; dst += 1; src += 1; } } if (char_is_id_ascii(*src)) goto copy_ascii; goto copy_escape; #undef return_err #undef return_suc } /*============================================================================== * MARK: - JSON Reader Implementation (Private) * * We use goto statements to build the finite state machine (FSM). * The FSM's state was held by program counter (PC) and the 'goto' make the * state transitions. *============================================================================*/ /** Read single value JSON document. */ static_noinline yyjson_doc *read_root_single(u8 *hdr, u8 *cur, u8 *eof, yyjson_alc alc, yyjson_read_flag flg, yyjson_read_err *err) { #define return_err(_pos, _code, _msg) do { \ if (is_truncated_end(hdr, _pos, eof, YYJSON_READ_ERROR_##_code, flg)) { \ err->pos = (usize)(eof - hdr); \ err->code = YYJSON_READ_ERROR_UNEXPECTED_END; \ err->msg = MSG_NOT_END; \ } else { \ err->pos = (usize)(_pos - hdr); \ err->code = YYJSON_READ_ERROR_##_code; \ err->msg = _msg; \ } \ if (val_hdr) alc.free(alc.ctx, val_hdr); \ return NULL; \ } while (false) usize hdr_len; /* value count used by doc */ usize alc_num; /* value count capacity */ yyjson_val *val_hdr; /* the head of allocated values */ yyjson_val *val; /* current value */ yyjson_doc *doc; /* the JSON document, equals to val_hdr */ const char *msg; /* error message */ u8 raw_end[1]; /* raw end for null-terminator */ u8 *raw_ptr = raw_end; u8 **pre = &raw_ptr; /* previous raw end pointer */ hdr_len = sizeof(yyjson_doc) / sizeof(yyjson_val); hdr_len += (sizeof(yyjson_doc) % sizeof(yyjson_val)) > 0; alc_num = hdr_len + 1; /* single value */ val_hdr = (yyjson_val *)alc.malloc(alc.ctx, alc_num * sizeof(yyjson_val)); if (unlikely(!val_hdr)) goto fail_alloc; val = val_hdr + hdr_len; if (char_is_num(*cur)) { if (likely(read_num(&cur, pre, flg, val, &msg))) goto doc_end; goto fail_number; } if (*cur == '"') { if (likely(read_str(&cur, eof, flg, val, &msg))) goto doc_end; goto fail_string; } if (*cur == 't') { if (likely(read_true(&cur, val))) goto doc_end; goto fail_literal_true; } if (*cur == 'f') { if (likely(read_false(&cur, val))) goto doc_end; goto fail_literal_false; } if (*cur == 'n') { if (likely(read_null(&cur, val))) goto doc_end; if (has_allow(INF_AND_NAN)) { if (read_nan(&cur, pre, flg, val)) goto doc_end; } goto fail_literal_null; } if (has_allow(INF_AND_NAN)) { if (read_inf_or_nan(&cur, pre, flg, val)) goto doc_end; } if (has_allow(SINGLE_QUOTED_STR) && *cur == '\'') { if (likely(read_str_sq(&cur, eof, flg, val, &msg))) goto doc_end; goto fail_string; } goto fail_character; doc_end: /* check invalid contents after json document */ if (unlikely(cur < eof) && !has_flg(STOP_WHEN_DONE)) { while (char_is_space(*cur)) cur++; if (has_allow(TRIVIA) && char_is_trivia(*cur)) { if (!skip_trivia(&cur, eof, flg) && cur == eof) { goto fail_comment; } } if (unlikely(cur < eof)) goto fail_garbage; } **pre = '\0'; doc = (yyjson_doc *)val_hdr; doc->root = val_hdr + hdr_len; doc->alc = alc; doc->dat_read = (usize)(cur - hdr); doc->val_read = 1; doc->str_pool = has_flg(INSITU) ? NULL : (char *)hdr; return doc; fail_string: return_err(cur, INVALID_STRING, msg); fail_number: return_err(cur, INVALID_NUMBER, msg); fail_alloc: return_err(cur, MEMORY_ALLOCATION, MSG_MALLOC); fail_literal_true: return_err(cur, LITERAL, MSG_CHAR_T); fail_literal_false: return_err(cur, LITERAL, MSG_CHAR_F); fail_literal_null: return_err(cur, LITERAL, MSG_CHAR_N); fail_character: return_err(cur, UNEXPECTED_CHARACTER, MSG_CHAR); fail_comment: return_err(cur, INVALID_COMMENT, MSG_COMMENT); fail_garbage: return_err(cur, UNEXPECTED_CONTENT, MSG_GARBAGE); #undef return_err } /** Read JSON document (accept all style, but optimized for minify). */ static_inline yyjson_doc *read_root_minify(u8 *hdr, u8 *cur, u8 *eof, yyjson_alc alc, yyjson_read_flag flg, yyjson_read_err *err) { #define return_err(_pos, _code, _msg) do { \ if (is_truncated_end(hdr, _pos, eof, YYJSON_READ_ERROR_##_code, flg)) { \ err->pos = (usize)(eof - hdr); \ err->code = YYJSON_READ_ERROR_UNEXPECTED_END; \ err->msg = MSG_NOT_END; \ } else { \ err->pos = (usize)(_pos - hdr); \ err->code = YYJSON_READ_ERROR_##_code; \ err->msg = _msg; \ } \ if (val_hdr) alc.free(alc.ctx, val_hdr); \ return NULL; \ } while (false) #define val_incr() do { \ val++; \ if (unlikely(val >= val_end)) { \ usize alc_old = alc_len; \ usize val_ofs = (usize)(val - val_hdr); \ usize ctn_ofs = (usize)(ctn - val_hdr); \ alc_len += alc_len / 2; \ if ((sizeof(usize) < 8) && (alc_len >= alc_max)) goto fail_alloc; \ val_tmp = (yyjson_val *)alc.realloc(alc.ctx, (void *)val_hdr, \ alc_old * sizeof(yyjson_val), \ alc_len * sizeof(yyjson_val)); \ if ((!val_tmp)) goto fail_alloc; \ val = val_tmp + val_ofs; \ ctn = val_tmp + ctn_ofs; \ val_hdr = val_tmp; \ val_end = val_tmp + (alc_len - 2); \ } \ } while (false) usize dat_len; /* data length in bytes, hint for allocator */ usize hdr_len; /* value count used by yyjson_doc */ usize alc_len; /* value count allocated */ usize alc_max; /* maximum value count for allocator */ usize ctn_len; /* the number of elements in current container */ yyjson_val *val_hdr; /* the head of allocated values */ yyjson_val *val_end; /* the end of allocated values */ yyjson_val *val_tmp; /* temporary pointer for realloc */ yyjson_val *val; /* current JSON value */ yyjson_val *ctn; /* current container */ yyjson_val *ctn_parent; /* parent of current container */ yyjson_doc *doc; /* the JSON document, equals to val_hdr */ const char *msg; /* error message */ u8 raw_end[1]; /* raw end for null-terminator */ u8 *raw_ptr = raw_end; u8 **pre = &raw_ptr; /* previous raw end pointer */ dat_len = has_flg(STOP_WHEN_DONE) ? 256 : (usize)(eof - cur); hdr_len = sizeof(yyjson_doc) / sizeof(yyjson_val); hdr_len += (sizeof(yyjson_doc) % sizeof(yyjson_val)) > 0; alc_max = USIZE_MAX / sizeof(yyjson_val); alc_len = hdr_len + (dat_len / YYJSON_READER_ESTIMATED_MINIFY_RATIO) + 4; alc_len = yyjson_min(alc_len, alc_max); val_hdr = (yyjson_val *)alc.malloc(alc.ctx, alc_len * sizeof(yyjson_val)); if (unlikely(!val_hdr)) goto fail_alloc; val_end = val_hdr + (alc_len - 2); /* padding for key-value pair reading */ val = val_hdr + hdr_len; ctn = val; ctn_len = 0; if (*cur++ == '{') { ctn->tag = YYJSON_TYPE_OBJ; ctn->uni.ofs = 0; goto obj_key_begin; } else { ctn->tag = YYJSON_TYPE_ARR; ctn->uni.ofs = 0; goto arr_val_begin; } arr_begin: /* save current container */ ctn->tag = (((u64)ctn_len + 1) << YYJSON_TAG_BIT) | (ctn->tag & YYJSON_TAG_MASK); /* create a new array value, save parent container offset */ val_incr(); val->tag = YYJSON_TYPE_ARR; val->uni.ofs = (usize)((u8 *)val - (u8 *)ctn); /* push the new array value as current container */ ctn = val; ctn_len = 0; arr_val_begin: if (*cur == '{') { cur++; goto obj_begin; } if (*cur == '[') { cur++; goto arr_begin; } if (char_is_num(*cur)) { val_incr(); ctn_len++; if (likely(read_num(&cur, pre, flg, val, &msg))) goto arr_val_end; goto fail_number; } if (*cur == '"') { val_incr(); ctn_len++; if (likely(read_str(&cur, eof, flg, val, &msg))) goto arr_val_end; goto fail_string; } if (*cur == 't') { val_incr(); ctn_len++; if (likely(read_true(&cur, val))) goto arr_val_end; goto fail_literal_true; } if (*cur == 'f') { val_incr(); ctn_len++; if (likely(read_false(&cur, val))) goto arr_val_end; goto fail_literal_false; } if (*cur == 'n') { val_incr(); ctn_len++; if (likely(read_null(&cur, val))) goto arr_val_end; if (has_allow(INF_AND_NAN)) { if (read_nan(&cur, pre, flg, val)) goto arr_val_end; } goto fail_literal_null; } if (*cur == ']') { cur++; if (likely(ctn_len == 0)) goto arr_end; if (has_allow(TRAILING_COMMAS)) goto arr_end; while (*cur != ',') cur--; goto fail_trailing_comma; } if (char_is_space(*cur)) { while (char_is_space(*++cur)); goto arr_val_begin; } if (has_allow(INF_AND_NAN) && (*cur == 'i' || *cur == 'I' || *cur == 'N')) { val_incr(); ctn_len++; if (read_inf_or_nan(&cur, pre, flg, val)) goto arr_val_end; goto fail_character_val; } if (has_allow(SINGLE_QUOTED_STR) && *cur == '\'') { val_incr(); ctn_len++; if (likely(read_str_sq(&cur, eof, flg, val, &msg))) goto arr_val_end; goto fail_string; } if (has_allow(TRIVIA) && char_is_trivia(*cur)) { if (skip_trivia(&cur, eof, flg)) goto arr_val_begin; if (cur == eof) goto fail_comment; } goto fail_character_val; arr_val_end: if (*cur == ',') { cur++; goto arr_val_begin; } if (*cur == ']') { cur++; goto arr_end; } if (char_is_space(*cur)) { while (char_is_space(*++cur)); goto arr_val_end; } if (has_allow(TRIVIA) && char_is_trivia(*cur)) { if (skip_trivia(&cur, eof, flg)) goto arr_val_end; if (cur == eof) goto fail_comment; } goto fail_character_arr_end; arr_end: /* get parent container */ ctn_parent = (yyjson_val *)(void *)((u8 *)ctn - ctn->uni.ofs); /* save the next sibling value offset */ ctn->uni.ofs = (usize)((u8 *)val - (u8 *)ctn) + sizeof(yyjson_val); ctn->tag = ((ctn_len) << YYJSON_TAG_BIT) | YYJSON_TYPE_ARR; if (unlikely(ctn == ctn_parent)) goto doc_end; /* pop parent as current container */ ctn = ctn_parent; ctn_len = (usize)(ctn->tag >> YYJSON_TAG_BIT); if ((ctn->tag & YYJSON_TYPE_MASK) == YYJSON_TYPE_OBJ) { goto obj_val_end; } else { goto arr_val_end; } obj_begin: /* push container */ ctn->tag = (((u64)ctn_len + 1) << YYJSON_TAG_BIT) | (ctn->tag & YYJSON_TAG_MASK); val_incr(); val->tag = YYJSON_TYPE_OBJ; /* offset to the parent */ val->uni.ofs = (usize)((u8 *)val - (u8 *)ctn); ctn = val; ctn_len = 0; obj_key_begin: if (likely(*cur == '"')) { val_incr(); ctn_len++; if (likely(read_str(&cur, eof, flg, val, &msg))) goto obj_key_end; goto fail_string; } if (likely(*cur == '}')) { cur++; if (likely(ctn_len == 0)) goto obj_end; if (has_allow(TRAILING_COMMAS)) goto obj_end; while (*cur != ',') cur--; goto fail_trailing_comma; } if (char_is_space(*cur)) { while (char_is_space(*++cur)); goto obj_key_begin; } if (has_allow(SINGLE_QUOTED_STR) && *cur == '\'') { val_incr(); ctn_len++; if (likely(read_str_sq(&cur, eof, flg, val, &msg))) goto obj_key_end; goto fail_string; } if (has_allow(UNQUOTED_KEY) && char_is_id_start(*cur)) { val_incr(); ctn_len++; if (read_str_id(&cur, eof, flg, pre, val, &msg)) goto obj_key_end; goto fail_string; } if (has_allow(TRIVIA) && char_is_trivia(*cur)) { if (skip_trivia(&cur, eof, flg)) goto obj_key_begin; if (cur == eof) goto fail_comment; } goto fail_character_obj_key; obj_key_end: if (*cur == ':') { cur++; goto obj_val_begin; } if (char_is_space(*cur)) { while (char_is_space(*++cur)); goto obj_key_end; } if (has_allow(TRIVIA) && char_is_trivia(*cur)) { if (skip_trivia(&cur, eof, flg)) goto obj_key_end; if (cur == eof) goto fail_comment; } goto fail_character_obj_sep; obj_val_begin: if (*cur == '"') { val++; ctn_len++; if (likely(read_str(&cur, eof, flg, val, &msg))) goto obj_val_end; goto fail_string; } if (char_is_num(*cur)) { val++; ctn_len++; if (likely(read_num(&cur, pre, flg, val, &msg))) goto obj_val_end; goto fail_number; } if (*cur == '{') { cur++; goto obj_begin; } if (*cur == '[') { cur++; goto arr_begin; } if (*cur == 't') { val++; ctn_len++; if (likely(read_true(&cur, val))) goto obj_val_end; goto fail_literal_true; } if (*cur == 'f') { val++; ctn_len++; if (likely(read_false(&cur, val))) goto obj_val_end; goto fail_literal_false; } if (*cur == 'n') { val++; ctn_len++; if (likely(read_null(&cur, val))) goto obj_val_end; if (has_allow(INF_AND_NAN)) { if (read_nan(&cur, pre, flg, val)) goto obj_val_end; } goto fail_literal_null; } if (char_is_space(*cur)) { while (char_is_space(*++cur)); goto obj_val_begin; } if (has_allow(INF_AND_NAN) && (*cur == 'i' || *cur == 'I' || *cur == 'N')) { val++; ctn_len++; if (read_inf_or_nan(&cur, pre, flg, val)) goto obj_val_end; goto fail_character_val; } if (has_allow(SINGLE_QUOTED_STR) && *cur == '\'') { val++; ctn_len++; if (likely(read_str_sq(&cur, eof, flg, val, &msg))) goto obj_val_end; goto fail_string; } if (has_allow(TRIVIA) && char_is_trivia(*cur)) { if (skip_trivia(&cur, eof, flg)) goto obj_val_begin; if (cur == eof) goto fail_comment; } goto fail_character_val; obj_val_end: if (likely(*cur == ',')) { cur++; goto obj_key_begin; } if (likely(*cur == '}')) { cur++; goto obj_end; } if (char_is_space(*cur)) { while (char_is_space(*++cur)); goto obj_val_end; } if (has_allow(TRIVIA) && char_is_trivia(*cur)) { if (skip_trivia(&cur, eof, flg)) goto obj_val_end; if (cur == eof) goto fail_comment; } goto fail_character_obj_end; obj_end: /* pop container */ ctn_parent = (yyjson_val *)(void *)((u8 *)ctn - ctn->uni.ofs); /* point to the next value */ ctn->uni.ofs = (usize)((u8 *)val - (u8 *)ctn) + sizeof(yyjson_val); ctn->tag = (ctn_len << (YYJSON_TAG_BIT - 1)) | YYJSON_TYPE_OBJ; if (unlikely(ctn == ctn_parent)) goto doc_end; ctn = ctn_parent; ctn_len = (usize)(ctn->tag >> YYJSON_TAG_BIT); if ((ctn->tag & YYJSON_TYPE_MASK) == YYJSON_TYPE_OBJ) { goto obj_val_end; } else { goto arr_val_end; } doc_end: /* check invalid contents after json document */ if (unlikely(cur < eof) && !has_flg(STOP_WHEN_DONE)) { while (char_is_space(*cur)) cur++; if (has_allow(TRIVIA) && char_is_trivia(*cur)) { if (!skip_trivia(&cur, eof, flg) && cur == eof) { goto fail_comment; } } if (unlikely(cur < eof)) goto fail_garbage; } **pre = '\0'; doc = (yyjson_doc *)val_hdr; doc->root = val_hdr + hdr_len; doc->alc = alc; doc->dat_read = (usize)(cur - hdr); doc->val_read = (usize)((val - doc->root) + 1); doc->str_pool = has_flg(INSITU) ? NULL : (char *)hdr; return doc; fail_string: return_err(cur, INVALID_STRING, msg); fail_number: return_err(cur, INVALID_NUMBER, msg); fail_alloc: return_err(cur, MEMORY_ALLOCATION, MSG_MALLOC); fail_trailing_comma: return_err(cur, JSON_STRUCTURE, MSG_COMMA); fail_literal_true: return_err(cur, LITERAL, MSG_CHAR_T); fail_literal_false: return_err(cur, LITERAL, MSG_CHAR_F); fail_literal_null: return_err(cur, LITERAL, MSG_CHAR_N); fail_character_val: return_err(cur, UNEXPECTED_CHARACTER, MSG_CHAR); fail_character_arr_end: return_err(cur, UNEXPECTED_CHARACTER, MSG_ARR_END); fail_character_obj_key: return_err(cur, UNEXPECTED_CHARACTER, MSG_OBJ_KEY); fail_character_obj_sep: return_err(cur, UNEXPECTED_CHARACTER, MSG_OBJ_SEP); fail_character_obj_end: return_err(cur, UNEXPECTED_CHARACTER, MSG_OBJ_END); fail_comment: return_err(cur, INVALID_COMMENT, MSG_COMMENT); fail_garbage: return_err(cur, UNEXPECTED_CONTENT, MSG_GARBAGE); #undef val_incr #undef return_err } /** Read JSON document (accept all style, but optimized for pretty). */ static_inline yyjson_doc *read_root_pretty(u8 *hdr, u8 *cur, u8 *eof, yyjson_alc alc, yyjson_read_flag flg, yyjson_read_err *err) { #define return_err(_pos, _code, _msg) do { \ if (is_truncated_end(hdr, _pos, eof, YYJSON_READ_ERROR_##_code, flg)) { \ err->pos = (usize)(eof - hdr); \ err->code = YYJSON_READ_ERROR_UNEXPECTED_END; \ err->msg = MSG_NOT_END; \ } else { \ err->pos = (usize)(_pos - hdr); \ err->code = YYJSON_READ_ERROR_##_code; \ err->msg = _msg; \ } \ if (val_hdr) alc.free(alc.ctx, val_hdr); \ return NULL; \ } while (false) #define val_incr() do { \ val++; \ if (unlikely(val >= val_end)) { \ usize alc_old = alc_len; \ usize val_ofs = (usize)(val - val_hdr); \ usize ctn_ofs = (usize)(ctn - val_hdr); \ alc_len += alc_len / 2; \ if ((sizeof(usize) < 8) && (alc_len >= alc_max)) goto fail_alloc; \ val_tmp = (yyjson_val *)alc.realloc(alc.ctx, (void *)val_hdr, \ alc_old * sizeof(yyjson_val), \ alc_len * sizeof(yyjson_val)); \ if ((!val_tmp)) goto fail_alloc; \ val = val_tmp + val_ofs; \ ctn = val_tmp + ctn_ofs; \ val_hdr = val_tmp; \ val_end = val_tmp + (alc_len - 2); \ } \ } while (false) usize dat_len; /* data length in bytes, hint for allocator */ usize hdr_len; /* value count used by yyjson_doc */ usize alc_len; /* value count allocated */ usize alc_max; /* maximum value count for allocator */ usize ctn_len; /* the number of elements in current container */ yyjson_val *val_hdr; /* the head of allocated values */ yyjson_val *val_end; /* the end of allocated values */ yyjson_val *val_tmp; /* temporary pointer for realloc */ yyjson_val *val; /* current JSON value */ yyjson_val *ctn; /* current container */ yyjson_val *ctn_parent; /* parent of current container */ yyjson_doc *doc; /* the JSON document, equals to val_hdr */ const char *msg; /* error message */ u8 raw_end[1]; /* raw end for null-terminator */ u8 *raw_ptr = raw_end; u8 **pre = &raw_ptr; /* previous raw end pointer */ dat_len = has_flg(STOP_WHEN_DONE) ? 256 : (usize)(eof - cur); hdr_len = sizeof(yyjson_doc) / sizeof(yyjson_val); hdr_len += (sizeof(yyjson_doc) % sizeof(yyjson_val)) > 0; alc_max = USIZE_MAX / sizeof(yyjson_val); alc_len = hdr_len + (dat_len / YYJSON_READER_ESTIMATED_PRETTY_RATIO) + 4; alc_len = yyjson_min(alc_len, alc_max); val_hdr = (yyjson_val *)alc.malloc(alc.ctx, alc_len * sizeof(yyjson_val)); if (unlikely(!val_hdr)) goto fail_alloc; val_end = val_hdr + (alc_len - 2); /* padding for key-value pair reading */ val = val_hdr + hdr_len; ctn = val; ctn_len = 0; if (*cur++ == '{') { ctn->tag = YYJSON_TYPE_OBJ; ctn->uni.ofs = 0; if (*cur == '\n') cur++; goto obj_key_begin; } else { ctn->tag = YYJSON_TYPE_ARR; ctn->uni.ofs = 0; if (*cur == '\n') cur++; goto arr_val_begin; } arr_begin: /* save current container */ ctn->tag = (((u64)ctn_len + 1) << YYJSON_TAG_BIT) | (ctn->tag & YYJSON_TAG_MASK); /* create a new array value, save parent container offset */ val_incr(); val->tag = YYJSON_TYPE_ARR; val->uni.ofs = (usize)((u8 *)val - (u8 *)ctn); /* push the new array value as current container */ ctn = val; ctn_len = 0; if (*cur == '\n') cur++; arr_val_begin: #if YYJSON_IS_REAL_GCC while (true) repeat16({ if (byte_match_2(cur, " ")) cur += 2; else break; }) #else while (true) repeat16({ if (likely(byte_match_2(cur, " "))) cur += 2; else break; }) #endif if (*cur == '{') { cur++; goto obj_begin; } if (*cur == '[') { cur++; goto arr_begin; } if (char_is_num(*cur)) { val_incr(); ctn_len++; if (likely(read_num(&cur, pre, flg, val, &msg))) goto arr_val_end; goto fail_number; } if (*cur == '"') { val_incr(); ctn_len++; if (likely(read_str(&cur, eof, flg, val, &msg))) goto arr_val_end; goto fail_string; } if (*cur == 't') { val_incr(); ctn_len++; if (likely(read_true(&cur, val))) goto arr_val_end; goto fail_literal_true; } if (*cur == 'f') { val_incr(); ctn_len++; if (likely(read_false(&cur, val))) goto arr_val_end; goto fail_literal_false; } if (*cur == 'n') { val_incr(); ctn_len++; if (likely(read_null(&cur, val))) goto arr_val_end; if (has_allow(INF_AND_NAN)) { if (read_nan(&cur, pre, flg, val)) goto arr_val_end; } goto fail_literal_null; } if (*cur == ']') { cur++; if (likely(ctn_len == 0)) goto arr_end; if (has_allow(TRAILING_COMMAS)) goto arr_end; while (*cur != ',') cur--; goto fail_trailing_comma; } if (char_is_space(*cur)) { while (char_is_space(*++cur)); goto arr_val_begin; } if (has_allow(INF_AND_NAN) && (*cur == 'i' || *cur == 'I' || *cur == 'N')) { val_incr(); ctn_len++; if (read_inf_or_nan(&cur, pre, flg, val)) goto arr_val_end; goto fail_character_val; } if (has_allow(SINGLE_QUOTED_STR) && *cur == '\'') { val_incr(); ctn_len++; if (likely(read_str_sq(&cur, eof, flg, val, &msg))) goto arr_val_end; goto fail_string; } if (has_allow(TRIVIA) && char_is_trivia(*cur)) { if (skip_trivia(&cur, eof, flg)) goto arr_val_begin; if (cur == eof) goto fail_comment; } goto fail_character_val; arr_val_end: if (byte_match_2(cur, ",\n")) { cur += 2; goto arr_val_begin; } if (*cur == ',') { cur++; goto arr_val_begin; } if (*cur == ']') { cur++; goto arr_end; } if (char_is_space(*cur)) { while (char_is_space(*++cur)); goto arr_val_end; } if (has_allow(TRIVIA) && char_is_trivia(*cur)) { if (skip_trivia(&cur, eof, flg)) goto arr_val_end; if (cur == eof) goto fail_comment; } goto fail_character_arr_end; arr_end: /* get parent container */ ctn_parent = (yyjson_val *)(void *)((u8 *)ctn - ctn->uni.ofs); /* save the next sibling value offset */ ctn->uni.ofs = (usize)((u8 *)val - (u8 *)ctn) + sizeof(yyjson_val); ctn->tag = ((ctn_len) << YYJSON_TAG_BIT) | YYJSON_TYPE_ARR; if (unlikely(ctn == ctn_parent)) goto doc_end; /* pop parent as current container */ ctn = ctn_parent; ctn_len = (usize)(ctn->tag >> YYJSON_TAG_BIT); if (*cur == '\n') cur++; if ((ctn->tag & YYJSON_TYPE_MASK) == YYJSON_TYPE_OBJ) { goto obj_val_end; } else { goto arr_val_end; } obj_begin: /* push container */ ctn->tag = (((u64)ctn_len + 1) << YYJSON_TAG_BIT) | (ctn->tag & YYJSON_TAG_MASK); val_incr(); val->tag = YYJSON_TYPE_OBJ; /* offset to the parent */ val->uni.ofs = (usize)((u8 *)val - (u8 *)ctn); ctn = val; ctn_len = 0; if (*cur == '\n') cur++; obj_key_begin: #if YYJSON_IS_REAL_GCC while (true) repeat16({ if (byte_match_2(cur, " ")) cur += 2; else break; }) #else while (true) repeat16({ if (likely(byte_match_2(cur, " "))) cur += 2; else break; }) #endif if (likely(*cur == '"')) { val_incr(); ctn_len++; if (likely(read_str(&cur, eof, flg, val, &msg))) goto obj_key_end; goto fail_string; } if (likely(*cur == '}')) { cur++; if (likely(ctn_len == 0)) goto obj_end; if (has_allow(TRAILING_COMMAS)) goto obj_end; while (*cur != ',') cur--; goto fail_trailing_comma; } if (char_is_space(*cur)) { while (char_is_space(*++cur)); goto obj_key_begin; } if (has_allow(SINGLE_QUOTED_STR) && *cur == '\'') { val_incr(); ctn_len++; if (likely(read_str_sq(&cur, eof, flg, val, &msg))) goto obj_key_end; goto fail_string; } if (has_allow(UNQUOTED_KEY) && char_is_id_start(*cur)) { val_incr(); ctn_len++; if (read_str_id(&cur, eof, flg, pre, val, &msg)) goto obj_key_end; goto fail_string; } if (has_allow(TRIVIA) && char_is_trivia(*cur)) { if (skip_trivia(&cur, eof, flg)) goto obj_key_begin; if (cur == eof) goto fail_comment; } goto fail_character_obj_key; obj_key_end: if (byte_match_2(cur, ": ")) { cur += 2; goto obj_val_begin; } if (*cur == ':') { cur++; goto obj_val_begin; } if (char_is_space(*cur)) { while (char_is_space(*++cur)); goto obj_key_end; } if (has_allow(TRIVIA) && char_is_trivia(*cur)) { if (skip_trivia(&cur, eof, flg)) goto obj_key_end; if (cur == eof) goto fail_comment; } goto fail_character_obj_sep; obj_val_begin: if (*cur == '"') { val++; ctn_len++; if (likely(read_str(&cur, eof, flg, val, &msg))) goto obj_val_end; goto fail_string; } if (char_is_num(*cur)) { val++; ctn_len++; if (likely(read_num(&cur, pre, flg, val, &msg))) goto obj_val_end; goto fail_number; } if (*cur == '{') { cur++; goto obj_begin; } if (*cur == '[') { cur++; goto arr_begin; } if (*cur == 't') { val++; ctn_len++; if (likely(read_true(&cur, val))) goto obj_val_end; goto fail_literal_true; } if (*cur == 'f') { val++; ctn_len++; if (likely(read_false(&cur, val))) goto obj_val_end; goto fail_literal_false; } if (*cur == 'n') { val++; ctn_len++; if (likely(read_null(&cur, val))) goto obj_val_end; if (has_allow(INF_AND_NAN)) { if (read_nan(&cur, pre, flg, val)) goto obj_val_end; } goto fail_literal_null; } if (char_is_space(*cur)) { while (char_is_space(*++cur)); goto obj_val_begin; } if (has_allow(INF_AND_NAN) && (*cur == 'i' || *cur == 'I' || *cur == 'N')) { val++; ctn_len++; if (read_inf_or_nan(&cur, pre, flg, val)) goto obj_val_end; goto fail_character_val; } if (has_allow(SINGLE_QUOTED_STR) && *cur == '\'') { val++; ctn_len++; if (likely(read_str_sq(&cur, eof, flg, val, &msg))) goto obj_val_end; goto fail_string; } if (has_allow(TRIVIA) && char_is_trivia(*cur)) { if (skip_trivia(&cur, eof, flg)) goto obj_val_begin; if (cur == eof) goto fail_comment; } goto fail_character_val; obj_val_end: if (byte_match_2(cur, ",\n")) { cur += 2; goto obj_key_begin; } if (likely(*cur == ',')) { cur++; goto obj_key_begin; } if (likely(*cur == '}')) { cur++; goto obj_end; } if (char_is_space(*cur)) { while (char_is_space(*++cur)); goto obj_val_end; } if (has_allow(TRIVIA) && char_is_trivia(*cur)) { if (skip_trivia(&cur, eof, flg)) goto obj_val_end; if (cur == eof) goto fail_comment; } goto fail_character_obj_end; obj_end: /* pop container */ ctn_parent = (yyjson_val *)(void *)((u8 *)ctn - ctn->uni.ofs); /* point to the next value */ ctn->uni.ofs = (usize)((u8 *)val - (u8 *)ctn) + sizeof(yyjson_val); ctn->tag = (ctn_len << (YYJSON_TAG_BIT - 1)) | YYJSON_TYPE_OBJ; if (unlikely(ctn == ctn_parent)) goto doc_end; ctn = ctn_parent; ctn_len = (usize)(ctn->tag >> YYJSON_TAG_BIT); if (*cur == '\n') cur++; if ((ctn->tag & YYJSON_TYPE_MASK) == YYJSON_TYPE_OBJ) { goto obj_val_end; } else { goto arr_val_end; } doc_end: /* check invalid contents after json document */ if (unlikely(cur < eof) && !has_flg(STOP_WHEN_DONE)) { while (char_is_space(*cur)) cur++; if (has_allow(TRIVIA) && char_is_trivia(*cur)) { if (!skip_trivia(&cur, eof, flg) && cur == eof) { goto fail_comment; } } if (unlikely(cur < eof)) goto fail_garbage; } **pre = '\0'; doc = (yyjson_doc *)val_hdr; doc->root = val_hdr + hdr_len; doc->alc = alc; doc->dat_read = (usize)(cur - hdr); doc->val_read = (usize)((val - doc->root) + 1); doc->str_pool = has_flg(INSITU) ? NULL : (char *)hdr; return doc; fail_string: return_err(cur, INVALID_STRING, msg); fail_number: return_err(cur, INVALID_NUMBER, msg); fail_alloc: return_err(cur, MEMORY_ALLOCATION, MSG_MALLOC); fail_trailing_comma: return_err(cur, JSON_STRUCTURE, MSG_COMMA); fail_literal_true: return_err(cur, LITERAL, MSG_CHAR_T); fail_literal_false: return_err(cur, LITERAL, MSG_CHAR_F); fail_literal_null: return_err(cur, LITERAL, MSG_CHAR_N); fail_character_val: return_err(cur, UNEXPECTED_CHARACTER, MSG_CHAR); fail_character_arr_end: return_err(cur, UNEXPECTED_CHARACTER, MSG_ARR_END); fail_character_obj_key: return_err(cur, UNEXPECTED_CHARACTER, MSG_OBJ_KEY); fail_character_obj_sep: return_err(cur, UNEXPECTED_CHARACTER, MSG_OBJ_SEP); fail_character_obj_end: return_err(cur, UNEXPECTED_CHARACTER, MSG_OBJ_END); fail_comment: return_err(cur, INVALID_COMMENT, MSG_COMMENT); fail_garbage: return_err(cur, UNEXPECTED_CONTENT, MSG_GARBAGE); #undef val_incr #undef return_err } /*============================================================================== * MARK: - JSON Reader (Public) *============================================================================*/ yyjson_doc *yyjson_read_opts(char *dat, usize len, yyjson_read_flag flg, const yyjson_alc *alc_ptr, yyjson_read_err *err) { #define return_err(_pos, _code, _msg) do { \ err->pos = (usize)(_pos); \ err->msg = _msg; \ err->code = YYJSON_READ_ERROR_##_code; \ if (!has_flg(INSITU) && hdr) alc.free(alc.ctx, (void *)hdr); \ return NULL; \ } while (false) yyjson_read_err tmp_err; yyjson_alc alc = alc_ptr ? *alc_ptr : YYJSON_DEFAULT_ALC; yyjson_doc *doc; u8 *hdr = NULL, *eof, *cur; /* validate input parameters */ if (!err) err = &tmp_err; if (unlikely(!dat)) return_err(0, INVALID_PARAMETER, "input data is NULL"); if (unlikely(!len)) return_err(0, INVALID_PARAMETER, "input length is 0"); /* add 4-byte zero padding for input data if necessary */ if (has_flg(INSITU)) { hdr = (u8 *)dat; eof = (u8 *)dat + len; cur = (u8 *)dat; } else { if (unlikely(len >= USIZE_MAX - YYJSON_PADDING_SIZE)) { return_err(0, MEMORY_ALLOCATION, MSG_MALLOC); } hdr = (u8 *)alc.malloc(alc.ctx, len + YYJSON_PADDING_SIZE); if (unlikely(!hdr)) { return_err(0, MEMORY_ALLOCATION, MSG_MALLOC); } eof = hdr + len; cur = hdr; memcpy(hdr, dat, len); } memset(eof, 0, YYJSON_PADDING_SIZE); if (has_allow(BOM)) { if (len >= 3 && is_utf8_bom(cur)) cur += 3; } /* skip empty contents before json document */ if (unlikely(!char_is_ctn(*cur))) { while (char_is_space(*cur)) cur++; if (unlikely(!char_is_ctn(*cur))) { if (has_allow(TRIVIA) && char_is_trivia(*cur)) { if (!skip_trivia(&cur, eof, flg) && cur == eof) { return_err(cur - hdr, INVALID_COMMENT, MSG_COMMENT); } } } if (unlikely(cur >= eof)) { return_err(0, EMPTY_CONTENT, "input data is empty"); } } /* read json document */ if (likely(char_is_ctn(*cur))) { if (char_is_space(cur[1]) && char_is_space(cur[2])) { doc = read_root_pretty(hdr, cur, eof, alc, flg, err); } else { doc = read_root_minify(hdr, cur, eof, alc, flg, err); } } else { doc = read_root_single(hdr, cur, eof, alc, flg, err); } /* check result */ if (likely(doc)) { memset(err, 0, sizeof(yyjson_read_err)); } else { /* RFC 8259: JSON text MUST be encoded using UTF-8 */ if (err->pos == 0 && err->code != YYJSON_READ_ERROR_MEMORY_ALLOCATION) { if (is_utf8_bom(hdr)) err->msg = MSG_ERR_BOM; else if (len >= 4 && is_utf32_bom(hdr)) err->msg = MSG_ERR_UTF32; else if (len >= 2 && is_utf16_bom(hdr)) err->msg = MSG_ERR_UTF16; } if (!has_flg(INSITU)) alc.free(alc.ctx, hdr); } return doc; #undef return_err } yyjson_doc *yyjson_read_file(const char *path, yyjson_read_flag flg, const yyjson_alc *alc_ptr, yyjson_read_err *err) { #define return_err(_code, _msg) do { \ err->pos = 0; \ err->msg = _msg; \ err->code = YYJSON_READ_ERROR_##_code; \ return NULL; \ } while (false) yyjson_read_err tmp_err; yyjson_doc *doc; FILE *file; if (!err) err = &tmp_err; if (unlikely(!path)) return_err(INVALID_PARAMETER, "input path is NULL"); file = fopen_readonly(path); if (unlikely(!file)) return_err(FILE_OPEN, MSG_FREAD); doc = yyjson_read_fp(file, flg, alc_ptr, err); fclose(file); return doc; #undef return_err } yyjson_doc *yyjson_read_fp(FILE *file, yyjson_read_flag flg, const yyjson_alc *alc_ptr, yyjson_read_err *err) { #define return_err(_code, _msg) do { \ err->pos = 0; \ err->msg = _msg; \ err->code = YYJSON_READ_ERROR_##_code; \ if (buf) alc.free(alc.ctx, buf); \ return NULL; \ } while (false) yyjson_read_err tmp_err; yyjson_alc alc = alc_ptr ? *alc_ptr : YYJSON_DEFAULT_ALC; yyjson_doc *doc; long file_size = 0, file_pos; void *buf = NULL; usize buf_size = 0; /* validate input parameters */ if (!err) err = &tmp_err; if (unlikely(!file)) return_err(INVALID_PARAMETER, "input file is NULL"); /* get current position */ file_pos = ftell(file); if (file_pos != -1) { /* get total file size, may fail */ if (fseek(file, 0, SEEK_END) == 0) file_size = ftell(file); /* reset to original position, may fail */ if (fseek(file, file_pos, SEEK_SET) != 0) file_size = 0; /* get file size from current postion to end */ if (file_size > 0) file_size -= file_pos; } /* read file */ if (file_size > 0) { /* read the entire file in one call */ buf_size = (usize)file_size + YYJSON_PADDING_SIZE; buf = alc.malloc(alc.ctx, buf_size); if (buf == NULL) { return_err(MEMORY_ALLOCATION, MSG_MALLOC); } if (fread_safe(buf, (usize)file_size, file) != (usize)file_size) { return_err(FILE_READ, MSG_FREAD); } } else { /* failed to get file size, read it as a stream */ usize chunk_min = (usize)64; usize chunk_max = (usize)512 * 1024 * 1024; usize chunk_now = chunk_min; usize read_size; void *tmp; buf_size = YYJSON_PADDING_SIZE; while (true) { if (buf_size + chunk_now < buf_size) { /* overflow */ return_err(MEMORY_ALLOCATION, MSG_MALLOC); } buf_size += chunk_now; if (!buf) { buf = alc.malloc(alc.ctx, buf_size); if (!buf) return_err(MEMORY_ALLOCATION, MSG_MALLOC); } else { tmp = alc.realloc(alc.ctx, buf, buf_size - chunk_now, buf_size); if (!tmp) return_err(MEMORY_ALLOCATION, MSG_MALLOC); buf = tmp; } tmp = ((u8 *)buf) + buf_size - YYJSON_PADDING_SIZE - chunk_now; read_size = fread_safe(tmp, chunk_now, file); file_size += (long)read_size; if (read_size != chunk_now) break; chunk_now *= 2; if (chunk_now > chunk_max) chunk_now = chunk_max; } } /* read JSON */ memset((u8 *)buf + file_size, 0, YYJSON_PADDING_SIZE); flg |= YYJSON_READ_INSITU; doc = yyjson_read_opts((char *)buf, (usize)file_size, flg, &alc, err); if (doc) { doc->str_pool = (char *)buf; return doc; } else { alc.free(alc.ctx, buf); return NULL; } #undef return_err } const char *yyjson_read_number(const char *dat, yyjson_val *val, yyjson_read_flag flg, const yyjson_alc *alc, yyjson_read_err *err) { #define return_err(_pos, _code, _msg) do { \ err->pos = _pos > hdr ? (usize)(_pos - hdr) : 0; \ err->msg = _msg; \ err->code = YYJSON_READ_ERROR_##_code; \ return NULL; \ } while (false) u8 *hdr = constcast(u8 *)dat, *cur = hdr; u8 raw_end[1]; /* raw end for null-terminator */ u8 *raw_ptr = raw_end; u8 **pre = &raw_ptr; /* previous raw end pointer */ const char *msg; yyjson_read_err tmp_err; #if YYJSON_DISABLE_FAST_FP_CONV u8 buf[128]; usize dat_len; #endif if (!err) err = &tmp_err; if (unlikely(!dat)) { return_err(cur, INVALID_PARAMETER, "input data is NULL"); } if (unlikely(!val)) { return_err(cur, INVALID_PARAMETER, "output value is NULL"); } #if YYJSON_DISABLE_FAST_FP_CONV if (!alc) alc = &YYJSON_DEFAULT_ALC; dat_len = strlen(dat); if (dat_len < sizeof(buf)) { memcpy(buf, dat, dat_len + 1); hdr = buf; cur = hdr; } else { hdr = (u8 *)alc->malloc(alc->ctx, dat_len + 1); cur = hdr; if (unlikely(!hdr)) { return_err(cur, MEMORY_ALLOCATION, MSG_MALLOC); } memcpy(hdr, dat, dat_len + 1); } hdr[dat_len] = 0; #endif #if YYJSON_DISABLE_FAST_FP_CONV if (!read_num(&cur, pre, flg, val, &msg)) { if (dat_len >= sizeof(buf)) alc->free(alc->ctx, hdr); return_err(cur, INVALID_NUMBER, msg); } if (dat_len >= sizeof(buf)) alc->free(alc->ctx, hdr); if (yyjson_is_raw(val)) val->uni.str = dat; return dat + (cur - hdr); #else if (!read_num(&cur, pre, flg, val, &msg)) { return_err(cur, INVALID_NUMBER, msg); } return (const char *)cur; #endif #undef return_err } /*============================================================================== * MARK: - Incremental JSON Reader (Public) *============================================================================*/ #if !YYJSON_DISABLE_INCR_READER /* labels within yyjson_incr_read() to resume incremental parsing */ #define LABEL_doc_begin 0 #define LABEL_arr_val_begin 1 #define LABEL_arr_val_end 2 #define LABEL_obj_key_begin 3 #define LABEL_obj_key_end 4 #define LABEL_obj_val_begin 5 #define LABEL_obj_val_end 6 #define LABEL_doc_end 7 /** State for incremental JSON reader, opaque in the API. */ struct yyjson_incr_state { u32 label; /* current parser goto label */ yyjson_alc alc; /* allocator */ yyjson_read_flag flg; /* read flags */ u8 *hdr; /* JSON data header */ u8 *cur; /* current position in JSON data */ usize buf_len; /* total buffer length (without padding) */ usize hdr_len; /* value count used by yyjson_doc */ usize alc_len; /* value count allocated */ usize ctn_len; /* the number of elements in current container */ yyjson_val *val_hdr; /* the head of allocated values */ yyjson_val *val_end; /* the end of allocated values */ yyjson_val *val; /* current JSON value */ yyjson_val *ctn; /* current container */ u8 *str_con[2]; /* string parser incremental state */ }; yyjson_incr_state *yyjson_incr_new(char *buf, size_t buf_len, yyjson_read_flag flg, const yyjson_alc *alc_ptr) { yyjson_incr_state *state = NULL; yyjson_alc alc = alc_ptr ? *alc_ptr : YYJSON_DEFAULT_ALC; /* remove non-standard flags */ flg &= ~YYJSON_READ_JSON5; flg &= ~YYJSON_READ_ALLOW_BOM; flg &= ~YYJSON_READ_ALLOW_INVALID_UNICODE; if (unlikely(!buf)) return NULL; if (unlikely(buf_len >= USIZE_MAX - YYJSON_PADDING_SIZE)) return NULL; state = (yyjson_incr_state *)alc.malloc(alc.ctx, sizeof(*state)); if (!state) return NULL; memset(state, 0, sizeof(yyjson_incr_state)); state->alc = alc; state->flg = flg; state->buf_len = buf_len; /* add 4-byte zero padding for input data if necessary */ if (has_flg(INSITU)) { state->hdr = (u8 *)buf; } else { state->hdr = (u8 *)alc.malloc(alc.ctx, buf_len + YYJSON_PADDING_SIZE); if (unlikely(!state->hdr)) { alc.free(alc.ctx, state); return NULL; } memcpy(state->hdr, buf, buf_len); } memset(state->hdr + buf_len, 0, YYJSON_PADDING_SIZE); state->cur = state->hdr; state->label = LABEL_doc_begin; return state; } void yyjson_incr_free(yyjson_incr_state *state) { if (state) { yyjson_alc alc = state->alc; memset(&state->alc, 0, sizeof(alc)); if (state->val_hdr) { alc.free(alc.ctx, (void *)state->val_hdr); } if (state->hdr && !(state->flg & YYJSON_READ_INSITU)) { alc.free(alc.ctx, state->hdr); } alc.free(alc.ctx, state); } } yyjson_doc *yyjson_incr_read(yyjson_incr_state *state, size_t len, yyjson_read_err *err) { #define return_err_inv_param(_msg) do { \ err->pos = 0; \ err->msg = _msg; \ err->code = YYJSON_READ_ERROR_INVALID_PARAMETER; \ return NULL; \ } while (false) #define return_err(_pos, _code, _msg) do { \ if (is_truncated_end(hdr, _pos, end, YYJSON_READ_ERROR_##_code, flg)) { \ goto unexpected_end; \ } else { \ err->pos = (usize)(_pos - hdr); \ err->code = YYJSON_READ_ERROR_##_code; \ err->msg = _msg; \ } \ return NULL; \ } while (false) #define val_incr() do { \ val++; \ if (unlikely(val >= val_end)) { \ usize alc_old = alc_len; \ alc_len += alc_len / 2; \ if ((sizeof(usize) < 8) && (alc_len >= alc_max)) goto fail_alloc; \ val_tmp = (yyjson_val *)alc.realloc(alc.ctx, (void *)val_hdr, \ alc_old * sizeof(yyjson_val), \ alc_len * sizeof(yyjson_val)); \ if ((!val_tmp)) goto fail_alloc; \ val = val_tmp + (usize)(val - val_hdr); \ ctn = val_tmp + (usize)(ctn - val_hdr); \ state->val = val_tmp + (usize)(state->val - val_hdr); \ state->val_hdr = val_hdr = val_tmp; \ val_end = val_tmp + (alc_len - 2); \ state->val_end = val_end; \ } \ } while (false) /* save position where it's possible to resume incremental parsing */ #define save_incr_state(_label) do { \ state->label = LABEL_##_label; \ state->cur = cur; \ state->val = val; \ state->ctn_len = ctn_len; \ state->hdr_len = hdr_len; \ if (unlikely(cur >= end)) goto unexpected_end; \ } while (false) #define check_maybe_truncated_number() do { \ if (unlikely(cur >= end)) { \ if (unlikely(cur > state->cur + INCR_NUM_MAX_LEN)) { \ msg = "number too long"; \ goto fail_number; \ } \ goto unexpected_end; \ } \ } while (false) u8 *hdr = NULL, *end = NULL, *cur = NULL; yyjson_read_flag flg; yyjson_alc alc; usize dat_len; /* data length in bytes, hint for allocator */ usize hdr_len; /* value count used by yyjson_doc */ usize alc_len; /* value count allocated */ usize alc_max; /* maximum value count for allocator */ usize ctn_len; /* the number of elements in current container */ yyjson_val *val_hdr; /* the head of allocated values */ yyjson_val *val_end; /* the end of allocated values */ yyjson_val *val_tmp; /* temporary pointer for realloc */ yyjson_val *val; /* current JSON value */ yyjson_val *ctn; /* current container */ yyjson_val *ctn_parent; /* parent of current container */ yyjson_doc *doc; /* the JSON document, equals to val_hdr */ const char *msg; /* error message */ yyjson_read_err tmp_err; u8 raw_end[1]; /* raw end for null-terminator */ u8 *raw_ptr = raw_end; u8 **pre = &raw_ptr; /* previous raw end pointer */ u8 **con = NULL; /* for incremental string parsing */ u8 saved_end = '\0'; /* saved end char */ /* validate input parameters */ if (!err) err = &tmp_err; if (unlikely(!state)) { return_err_inv_param("input state is NULL"); } if (unlikely(!len)) { return_err_inv_param("input length is 0"); } if (unlikely(len > state->buf_len)) { return_err_inv_param("length is greater than total input length"); } /* restore state saved from the previous call */ hdr = state->hdr; end = state->hdr + len; cur = state->cur; flg = state->flg; alc = state->alc; ctn_len = state->ctn_len; hdr_len = state->hdr_len; alc_len = state->alc_len; val = state->val; val_hdr = state->val_hdr; val_end = state->val_end; ctn = state->ctn; con = state->str_con; alc_max = USIZE_MAX / sizeof(yyjson_val); /* insert null terminator to make us stop at the specified end, even if the data contains more valid JSON */ saved_end = *end; *end = '\0'; /* resume parsing from the last save point */ switch (state->label) { case LABEL_doc_begin: goto doc_begin; case LABEL_arr_val_begin: goto arr_val_begin; case LABEL_arr_val_end: goto arr_val_end; case LABEL_obj_key_begin: goto obj_key_begin; case LABEL_obj_key_end: goto obj_key_end; case LABEL_obj_val_begin: goto obj_val_begin; case LABEL_obj_val_end: goto obj_val_end; case LABEL_doc_end: goto doc_end; default: return_err_inv_param("invalid incremental state"); } doc_begin: /* skip empty contents before json document */ if (unlikely(!char_is_ctn(*cur))) { while (char_is_space(*cur)) cur++; if (unlikely(cur >= end)) goto unexpected_end; /* input data is empty */ } /* allocate memory for document */ if (!val_hdr) { hdr_len = sizeof(yyjson_doc) / sizeof(yyjson_val); hdr_len += (sizeof(yyjson_doc) % sizeof(yyjson_val)) > 0; if (likely(char_is_ctn(*cur))) { dat_len = has_flg(STOP_WHEN_DONE) ? 256 : state->buf_len; alc_len = hdr_len + (dat_len / YYJSON_READER_ESTIMATED_MINIFY_RATIO) + 4; alc_len = yyjson_min(alc_len, alc_max); } else { alc_len = hdr_len + 1; /* single value */ } val_hdr = (yyjson_val *)alc.malloc(alc.ctx, alc_len * sizeof(yyjson_val)); if (unlikely(!val_hdr)) goto fail_alloc; val_end = val_hdr + (alc_len - 2); /* padding for kv pair reading */ val = val_hdr + hdr_len; ctn = val; ctn_len = 0; state->val_hdr = val_hdr; state->val_end = val_end; save_incr_state(doc_begin); } /* read json document */ if (*cur == '{') { cur++; ctn->tag = YYJSON_TYPE_OBJ; ctn->uni.ofs = 0; goto obj_key_begin; } if (*cur == '[') { cur++; ctn->tag = YYJSON_TYPE_ARR; ctn->uni.ofs = 0; goto arr_val_begin; } if (char_is_num(*cur)) { if (likely(read_num(&cur, pre, flg, val, &msg))) goto doc_end; goto fail_number; } if (*cur == '"') { if (likely(read_str_con(&cur, end, flg, val, &msg, con))) goto doc_end; goto fail_string; } if (*cur == 't') { if (likely(read_true(&cur, val))) goto doc_end; goto fail_literal_true; } if (*cur == 'f') { if (likely(read_false(&cur, val))) goto doc_end; goto fail_literal_false; } if (*cur == 'n') { if (likely(read_null(&cur, val))) goto doc_end; goto fail_literal_null; } msg = "unexpected character, expected a valid root value"; if (cur == hdr) { /* RFC 8259: JSON text MUST be encoded using UTF-8 */ if (is_utf8_bom(hdr)) msg = MSG_ERR_BOM; else if (len >= 4 && is_utf32_bom(hdr)) msg = MSG_ERR_UTF32; else if (len >= 2 && is_utf16_bom(hdr)) msg = MSG_ERR_UTF16; } return_err(cur, UNEXPECTED_CHARACTER, msg); arr_begin: /* save current container */ ctn->tag = (((u64)ctn_len + 1) << YYJSON_TAG_BIT) | (ctn->tag & YYJSON_TAG_MASK); /* create a new array value, save parent container offset */ val_incr(); val->tag = YYJSON_TYPE_ARR; val->uni.ofs = (usize)((u8 *)val - (u8 *)ctn); /* push the new array value as current container */ ctn = val; ctn_len = 0; arr_val_begin: save_incr_state(arr_val_begin); arr_val_continue: if (*cur == '{') { cur++; goto obj_begin; } if (*cur == '[') { cur++; goto arr_begin; } if (char_is_num(*cur)) { val_incr(); ctn_len++; if (likely(read_num(&cur, pre, flg, val, &msg))) goto arr_val_maybe_end; goto fail_number; } if (*cur == '"') { val_incr(); ctn_len++; if (likely(read_str_con(&cur, end, flg, val, &msg, con))) goto arr_val_end; goto fail_string; } if (*cur == 't') { val_incr(); ctn_len++; if (likely(read_true(&cur, val))) goto arr_val_end; goto fail_literal_true; } if (*cur == 'f') { val_incr(); ctn_len++; if (likely(read_false(&cur, val))) goto arr_val_end; goto fail_literal_false; } if (*cur == 'n') { val_incr(); ctn_len++; if (likely(read_null(&cur, val))) goto arr_val_end; goto fail_literal_null; } if (*cur == ']') { cur++; if (likely(ctn_len == 0)) goto arr_end; while (*cur != ',') cur--; goto fail_trailing_comma; } if (char_is_space(*cur)) { while (char_is_space(*++cur)); goto arr_val_continue; } goto fail_character_val; arr_val_maybe_end: /* if incremental parsing stops in the middle of a number, it may continue with more digits, so arr val maybe didn't end yet */ check_maybe_truncated_number(); arr_val_end: save_incr_state(arr_val_end); if (*cur == ',') { cur++; goto arr_val_begin; } if (*cur == ']') { cur++; goto arr_end; } if (char_is_space(*cur)) { while (char_is_space(*++cur)); goto arr_val_end; } goto fail_character_arr_end; arr_end: /* get parent container */ ctn_parent = (yyjson_val *)(void *)((u8 *)ctn - ctn->uni.ofs); /* save the next sibling value offset */ ctn->uni.ofs = (usize)((u8 *)val - (u8 *)ctn) + sizeof(yyjson_val); ctn->tag = ((ctn_len) << YYJSON_TAG_BIT) | YYJSON_TYPE_ARR; if (unlikely(ctn == ctn_parent)) goto doc_end; /* pop parent as current container */ ctn = ctn_parent; ctn_len = (usize)(ctn->tag >> YYJSON_TAG_BIT); if ((ctn->tag & YYJSON_TYPE_MASK) == YYJSON_TYPE_OBJ) { goto obj_val_end; } else { goto arr_val_end; } obj_begin: /* push container */ ctn->tag = (((u64)ctn_len + 1) << YYJSON_TAG_BIT) | (ctn->tag & YYJSON_TAG_MASK); val_incr(); val->tag = YYJSON_TYPE_OBJ; /* offset to the parent */ val->uni.ofs = (usize)((u8 *)val - (u8 *)ctn); ctn = val; ctn_len = 0; obj_key_begin: save_incr_state(obj_key_begin); obj_key_continue: if (likely(*cur == '"')) { val_incr(); ctn_len++; if (likely(read_str_con(&cur, end, flg, val, &msg, con))) goto obj_key_end; goto fail_string; } if (likely(*cur == '}')) { cur++; if (likely(ctn_len == 0)) goto obj_end; while (*cur != ',') cur--; goto fail_trailing_comma; } if (char_is_space(*cur)) { while (char_is_space(*++cur)); goto obj_key_continue; } goto fail_character_obj_key; obj_key_end: save_incr_state(obj_key_end); if (*cur == ':') { cur++; goto obj_val_begin; } if (char_is_space(*cur)) { while (char_is_space(*++cur)); goto obj_key_end; } goto fail_character_obj_sep; obj_val_begin: save_incr_state(obj_val_begin); obj_val_continue: if (*cur == '"') { val++; ctn_len++; if (likely(read_str_con(&cur, end, flg, val, &msg, con))) goto obj_val_end; goto fail_string; } if (char_is_num(*cur)) { val++; ctn_len++; if (likely(read_num(&cur, pre, flg, val, &msg))) goto obj_val_maybe_end; goto fail_number; } if (*cur == '{') { cur++; goto obj_begin; } if (*cur == '[') { cur++; goto arr_begin; } if (*cur == 't') { val++; ctn_len++; if (likely(read_true(&cur, val))) goto obj_val_end; goto fail_literal_true; } if (*cur == 'f') { val++; ctn_len++; if (likely(read_false(&cur, val))) goto obj_val_end; goto fail_literal_false; } if (*cur == 'n') { val++; ctn_len++; if (likely(read_null(&cur, val))) goto obj_val_end; goto fail_literal_null; } if (char_is_space(*cur)) { while (char_is_space(*++cur)); goto obj_val_continue; } goto fail_character_val; obj_val_maybe_end: /* if incremental parsing stops in the middle of a number, it may continue with more digits, so obj val maybe didn't end yet */ check_maybe_truncated_number(); obj_val_end: save_incr_state(obj_val_end); if (likely(*cur == ',')) { cur++; goto obj_key_begin; } if (likely(*cur == '}')) { cur++; goto obj_end; } if (char_is_space(*cur)) { while (char_is_space(*++cur)); goto obj_val_end; } goto fail_character_obj_end; obj_end: /* pop container */ ctn_parent = (yyjson_val *)(void *)((u8 *)ctn - ctn->uni.ofs); /* point to the next value */ ctn->uni.ofs = (usize)((u8 *)val - (u8 *)ctn) + sizeof(yyjson_val); ctn->tag = (ctn_len << (YYJSON_TAG_BIT - 1)) | YYJSON_TYPE_OBJ; if (unlikely(ctn == ctn_parent)) goto doc_end; ctn = ctn_parent; ctn_len = (usize)(ctn->tag >> YYJSON_TAG_BIT); if ((ctn->tag & YYJSON_TYPE_MASK) == YYJSON_TYPE_OBJ) { goto obj_val_end; } else { goto arr_val_end; } doc_end: /* check invalid contents after json document */ if (unlikely(cur < end) && !has_flg(STOP_WHEN_DONE)) { save_incr_state(doc_end); while (char_is_space(*cur)) cur++; if (unlikely(cur < end)) goto fail_garbage; } **pre = '\0'; doc = (yyjson_doc *)val_hdr; doc->root = val_hdr + hdr_len; doc->alc = alc; doc->dat_read = (usize)(cur - hdr); doc->val_read = (usize)((val - doc->root) + 1); doc->str_pool = has_flg(INSITU) ? NULL : (char *)hdr; state->hdr = NULL; state->val_hdr = NULL; memset(err, 0, sizeof(yyjson_read_err)); return doc; unexpected_end: err->pos = len; /* if no nore data, stop the incr read */ if (unlikely(len >= state->buf_len)) { err->code = YYJSON_READ_ERROR_UNEXPECTED_END; err->msg = MSG_NOT_END; return NULL; } /* save parser state in extended error struct, in addition to what was * stored in the last save_incr_state */ err->code = YYJSON_READ_ERROR_MORE; err->msg = "need more data"; state->val_end = val_end; state->ctn = ctn; state->alc_len = alc_len; /* restore the end where we've inserted a null terminator */ *end = saved_end; return NULL; fail_string: return_err(cur, INVALID_STRING, msg); fail_number: return_err(cur, INVALID_NUMBER, msg); fail_alloc: return_err(cur, MEMORY_ALLOCATION, MSG_MALLOC); fail_trailing_comma: return_err(cur, JSON_STRUCTURE, MSG_COMMA); fail_literal_true: return_err(cur, LITERAL, MSG_CHAR_T); fail_literal_false: return_err(cur, LITERAL, MSG_CHAR_F); fail_literal_null: return_err(cur, LITERAL, MSG_CHAR_N); fail_character_val: return_err(cur, UNEXPECTED_CHARACTER, MSG_CHAR); fail_character_arr_end: return_err(cur, UNEXPECTED_CHARACTER, MSG_ARR_END); fail_character_obj_key: return_err(cur, UNEXPECTED_CHARACTER, MSG_OBJ_KEY); fail_character_obj_sep: return_err(cur, UNEXPECTED_CHARACTER, MSG_OBJ_SEP); fail_character_obj_end: return_err(cur, UNEXPECTED_CHARACTER, MSG_OBJ_END); fail_garbage: return_err(cur, UNEXPECTED_CONTENT, MSG_GARBAGE); #undef val_incr #undef return_err #undef return_err_inv_param #undef save_incr_state #undef check_maybe_truncated_number } #endif /* YYJSON_DISABLE_INCR_READER */ #undef has_flg #undef has_allow #endif /* YYJSON_DISABLE_READER */ #if !YYJSON_DISABLE_WRITER /* writer begin */ /* Check write flag, avoids `always false` warning when disabled. */ #define has_flg(_flg) unlikely(has_wflag(flg, YYJSON_WRITE_##_flg, 0)) #define has_allow(_flg) unlikely(has_wflag(flg, YYJSON_WRITE_ALLOW_##_flg, 1)) static_inline bool has_wflag(yyjson_write_flag flg, yyjson_write_flag chk, bool non_standard) { #if YYJSON_DISABLE_NON_STANDARD if (non_standard) return false; #endif return (flg & chk) != 0; } /*============================================================================== * MARK: - Integer Writer (Private) * * The maximum value of uint32_t is 4294967295 (10 digits), * these digits are named as 'aabbccddee' here. * * Although most compilers may convert the "division by constant value" into * "multiply and shift", manual conversion can still help some compilers * generate fewer and better instructions. * * Reference: * Division by Invariant Integers using Multiplication, 1994. * https://gmplib.org/~tege/divcnst-pldi94.pdf * Improved division by invariant integers, 2011. * https://gmplib.org/~tege/division-paper.pdf *============================================================================*/ /** Digit table from 00 to 99. */ yyjson_align(2) static const char digit_table[200] = { '0', '0', '0', '1', '0', '2', '0', '3', '0', '4', '0', '5', '0', '6', '0', '7', '0', '8', '0', '9', '1', '0', '1', '1', '1', '2', '1', '3', '1', '4', '1', '5', '1', '6', '1', '7', '1', '8', '1', '9', '2', '0', '2', '1', '2', '2', '2', '3', '2', '4', '2', '5', '2', '6', '2', '7', '2', '8', '2', '9', '3', '0', '3', '1', '3', '2', '3', '3', '3', '4', '3', '5', '3', '6', '3', '7', '3', '8', '3', '9', '4', '0', '4', '1', '4', '2', '4', '3', '4', '4', '4', '5', '4', '6', '4', '7', '4', '8', '4', '9', '5', '0', '5', '1', '5', '2', '5', '3', '5', '4', '5', '5', '5', '6', '5', '7', '5', '8', '5', '9', '6', '0', '6', '1', '6', '2', '6', '3', '6', '4', '6', '5', '6', '6', '6', '7', '6', '8', '6', '9', '7', '0', '7', '1', '7', '2', '7', '3', '7', '4', '7', '5', '7', '6', '7', '7', '7', '8', '7', '9', '8', '0', '8', '1', '8', '2', '8', '3', '8', '4', '8', '5', '8', '6', '8', '7', '8', '8', '8', '9', '9', '0', '9', '1', '9', '2', '9', '3', '9', '4', '9', '5', '9', '6', '9', '7', '9', '8', '9', '9' }; static_inline u8 *write_u32_len_8(u32 val, u8 *buf) { u32 aa, bb, cc, dd, aabb, ccdd; /* 8 digits: aabbccdd */ aabb = (u32)(((u64)val * 109951163) >> 40); /* (val / 10000) */ ccdd = val - aabb * 10000; /* (val % 10000) */ aa = (aabb * 5243) >> 19; /* (aabb / 100) */ cc = (ccdd * 5243) >> 19; /* (ccdd / 100) */ bb = aabb - aa * 100; /* (aabb % 100) */ dd = ccdd - cc * 100; /* (ccdd % 100) */ byte_copy_2(buf + 0, digit_table + aa * 2); byte_copy_2(buf + 2, digit_table + bb * 2); byte_copy_2(buf + 4, digit_table + cc * 2); byte_copy_2(buf + 6, digit_table + dd * 2); return buf + 8; } static_inline u8 *write_u32_len_4(u32 val, u8 *buf) { u32 aa, bb; /* 4 digits: aabb */ aa = (val * 5243) >> 19; /* (val / 100) */ bb = val - aa * 100; /* (val % 100) */ byte_copy_2(buf + 0, digit_table + aa * 2); byte_copy_2(buf + 2, digit_table + bb * 2); return buf + 4; } static_inline u8 *write_u32_len_1_to_8(u32 val, u8 *buf) { u32 aa, bb, cc, dd, aabb, bbcc, ccdd, lz; if (val < 100) { /* 1-2 digits: aa */ lz = val < 10; /* leading zero: 0 or 1 */ byte_copy_2(buf + 0, digit_table + val * 2 + lz); buf -= lz; return buf + 2; } else if (val < 10000) { /* 3-4 digits: aabb */ aa = (val * 5243) >> 19; /* (val / 100) */ bb = val - aa * 100; /* (val % 100) */ lz = aa < 10; /* leading zero: 0 or 1 */ byte_copy_2(buf + 0, digit_table + aa * 2 + lz); buf -= lz; byte_copy_2(buf + 2, digit_table + bb * 2); return buf + 4; } else if (val < 1000000) { /* 5-6 digits: aabbcc */ aa = (u32)(((u64)val * 429497) >> 32); /* (val / 10000) */ bbcc = val - aa * 10000; /* (val % 10000) */ bb = (bbcc * 5243) >> 19; /* (bbcc / 100) */ cc = bbcc - bb * 100; /* (bbcc % 100) */ lz = aa < 10; /* leading zero: 0 or 1 */ byte_copy_2(buf + 0, digit_table + aa * 2 + lz); buf -= lz; byte_copy_2(buf + 2, digit_table + bb * 2); byte_copy_2(buf + 4, digit_table + cc * 2); return buf + 6; } else { /* 7-8 digits: aabbccdd */ aabb = (u32)(((u64)val * 109951163) >> 40); /* (val / 10000) */ ccdd = val - aabb * 10000; /* (val % 10000) */ aa = (aabb * 5243) >> 19; /* (aabb / 100) */ cc = (ccdd * 5243) >> 19; /* (ccdd / 100) */ bb = aabb - aa * 100; /* (aabb % 100) */ dd = ccdd - cc * 100; /* (ccdd % 100) */ lz = aa < 10; /* leading zero: 0 or 1 */ byte_copy_2(buf + 0, digit_table + aa * 2 + lz); buf -= lz; byte_copy_2(buf + 2, digit_table + bb * 2); byte_copy_2(buf + 4, digit_table + cc * 2); byte_copy_2(buf + 6, digit_table + dd * 2); return buf + 8; } } static_inline u8 *write_u32_len_5_to_8(u32 val, u8 *buf) { u32 aa, bb, cc, dd, aabb, bbcc, ccdd, lz; if (val < 1000000) { /* 5-6 digits: aabbcc */ aa = (u32)(((u64)val * 429497) >> 32); /* (val / 10000) */ bbcc = val - aa * 10000; /* (val % 10000) */ bb = (bbcc * 5243) >> 19; /* (bbcc / 100) */ cc = bbcc - bb * 100; /* (bbcc % 100) */ lz = aa < 10; /* leading zero: 0 or 1 */ byte_copy_2(buf + 0, digit_table + aa * 2 + lz); buf -= lz; byte_copy_2(buf + 2, digit_table + bb * 2); byte_copy_2(buf + 4, digit_table + cc * 2); return buf + 6; } else { /* 7-8 digits: aabbccdd */ aabb = (u32)(((u64)val * 109951163) >> 40); /* (val / 10000) */ ccdd = val - aabb * 10000; /* (val % 10000) */ aa = (aabb * 5243) >> 19; /* (aabb / 100) */ cc = (ccdd * 5243) >> 19; /* (ccdd / 100) */ bb = aabb - aa * 100; /* (aabb % 100) */ dd = ccdd - cc * 100; /* (ccdd % 100) */ lz = aa < 10; /* leading zero: 0 or 1 */ byte_copy_2(buf + 0, digit_table + aa * 2 + lz); buf -= lz; byte_copy_2(buf + 2, digit_table + bb * 2); byte_copy_2(buf + 4, digit_table + cc * 2); byte_copy_2(buf + 6, digit_table + dd * 2); return buf + 8; } } static_inline u8 *write_u64(u64 val, u8 *buf) { u64 tmp, hgh; u32 mid, low; if (val < 100000000) { /* 1-8 digits */ buf = write_u32_len_1_to_8((u32)val, buf); return buf; } else if (val < (u64)100000000 * 100000000) { /* 9-16 digits */ hgh = val / 100000000; /* (val / 100000000) */ low = (u32)(val - hgh * 100000000); /* (val % 100000000) */ buf = write_u32_len_1_to_8((u32)hgh, buf); buf = write_u32_len_8(low, buf); return buf; } else { /* 17-20 digits */ tmp = val / 100000000; /* (val / 100000000) */ low = (u32)(val - tmp * 100000000); /* (val % 100000000) */ hgh = (u32)(tmp / 10000); /* (tmp / 10000) */ mid = (u32)(tmp - hgh * 10000); /* (tmp % 10000) */ buf = write_u32_len_5_to_8((u32)hgh, buf); buf = write_u32_len_4(mid, buf); buf = write_u32_len_8(low, buf); return buf; } } /*============================================================================== * MARK: - Number Writer (Private) *============================================================================*/ #if !YYJSON_DISABLE_FAST_FP_CONV /* FP_WRITER */ /** Trailing zero count table for number 0 to 99. (generate with misc/make_tables.c) */ static const u8 dec_trailing_zero_table[] = { 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0 }; static_inline u8 *write_u64_len_1_to_16(u64 val, u8 *buf) { u64 hgh; u32 low; if (val < 100000000) { /* 1-8 digits */ buf = write_u32_len_1_to_8((u32)val, buf); return buf; } else { /* 9-16 digits */ hgh = val / 100000000; /* (val / 100000000) */ low = (u32)(val - hgh * 100000000); /* (val % 100000000) */ buf = write_u32_len_1_to_8((u32)hgh, buf); buf = write_u32_len_8(low, buf); return buf; } } static_inline u8 *write_u64_len_1_to_17(u64 val, u8 *buf) { u64 hgh; u32 mid, low, one; if (val >= (u64)100000000 * 10000000) { /* len: 16 to 17 */ hgh = val / 100000000; /* (val / 100000000) */ low = (u32)(val - hgh * 100000000); /* (val % 100000000) */ one = (u32)(hgh / 100000000); /* (hgh / 100000000) */ mid = (u32)(hgh - (u64)one * 100000000); /* (hgh % 100000000) */ *buf = (u8)((u8)one + (u8)'0'); buf += one > 0; buf = write_u32_len_8(mid, buf); buf = write_u32_len_8(low, buf); return buf; } else if (val >= (u64)100000000){ /* len: 9 to 15 */ hgh = val / 100000000; /* (val / 100000000) */ low = (u32)(val - hgh * 100000000); /* (val % 100000000) */ buf = write_u32_len_1_to_8((u32)hgh, buf); buf = write_u32_len_8(low, buf); return buf; } else { /* len: 1 to 8 */ buf = write_u32_len_1_to_8((u32)val, buf); return buf; } } /** Write an unsigned integer with a length of 7 to 9 with trailing zero trimmed. These digits are named as "abbccddee" here. For example, input 123456000, output "123456". */ static_inline u8 *write_u32_len_7_to_9_trim(u32 val, u8 *buf) { bool lz; u32 tz, tz1, tz2; u32 abbcc = val / 10000; /* (abbccddee / 10000) */ u32 ddee = val - abbcc * 10000; /* (abbccddee % 10000) */ u32 abb = (u32)(((u64)abbcc * 167773) >> 24); /* (abbcc / 100) */ u32 a = (abb * 41) >> 12; /* (abb / 100) */ u32 bb = abb - a * 100; /* (abb % 100) */ u32 cc = abbcc - abb * 100; /* (abbcc % 100) */ /* write abbcc */ buf[0] = (u8)(a + '0'); buf += a > 0; lz = bb < 10 && a == 0; byte_copy_2(buf + 0, digit_table + bb * 2 + lz); buf -= lz; byte_copy_2(buf + 2, digit_table + cc * 2); if (ddee) { u32 dd = (ddee * 5243) >> 19; /* (ddee / 100) */ u32 ee = ddee - dd * 100; /* (ddee % 100) */ byte_copy_2(buf + 4, digit_table + dd * 2); byte_copy_2(buf + 6, digit_table + ee * 2); tz1 = dec_trailing_zero_table[dd]; tz2 = dec_trailing_zero_table[ee]; tz = ee ? tz2 : (tz1 + 2); buf += 8 - tz; return buf; } else { tz1 = dec_trailing_zero_table[bb]; tz2 = dec_trailing_zero_table[cc]; tz = cc ? tz2 : (tz1 + tz2); buf += 4 - tz; return buf; } } /** Write an unsigned integer with a length of 16 or 17 with trailing zero trimmed. These digits are named as "abbccddeeffgghhii" here. For example, input 1234567890123000, output "1234567890123". */ static_inline u8 *write_u64_len_16_to_17_trim(u64 val, u8 *buf) { u32 tz, tz1, tz2; u32 abbccddee = (u32)(val / 100000000); u32 ffgghhii = (u32)(val - (u64)abbccddee * 100000000); u32 abbcc = abbccddee / 10000; u32 ddee = abbccddee - abbcc * 10000; u32 abb = (u32)(((u64)abbcc * 167773) >> 24); /* (abbcc / 100) */ u32 a = (abb * 41) >> 12; /* (abb / 100) */ u32 bb = abb - a * 100; /* (abb % 100) */ u32 cc = abbcc - abb * 100; /* (abbcc % 100) */ buf[0] = (u8)(a + '0'); buf += a > 0; byte_copy_2(buf + 0, digit_table + bb * 2); byte_copy_2(buf + 2, digit_table + cc * 2); if (ffgghhii) { u32 dd = (ddee * 5243) >> 19; /* (ddee / 100) */ u32 ee = ddee - dd * 100; /* (ddee % 100) */ u32 ffgg = (u32)(((u64)ffgghhii * 109951163) >> 40); /* (val / 10000) */ u32 hhii = ffgghhii - ffgg * 10000; /* (val % 10000) */ u32 ff = (ffgg * 5243) >> 19; /* (aabb / 100) */ u32 gg = ffgg - ff * 100; /* (aabb % 100) */ byte_copy_2(buf + 4, digit_table + dd * 2); byte_copy_2(buf + 6, digit_table + ee * 2); byte_copy_2(buf + 8, digit_table + ff * 2); byte_copy_2(buf + 10, digit_table + gg * 2); if (hhii) { u32 hh = (hhii * 5243) >> 19; /* (ccdd / 100) */ u32 ii = hhii - hh * 100; /* (ccdd % 100) */ byte_copy_2(buf + 12, digit_table + hh * 2); byte_copy_2(buf + 14, digit_table + ii * 2); tz1 = dec_trailing_zero_table[hh]; tz2 = dec_trailing_zero_table[ii]; tz = ii ? tz2 : (tz1 + 2); return buf + 16 - tz; } else { tz1 = dec_trailing_zero_table[ff]; tz2 = dec_trailing_zero_table[gg]; tz = gg ? tz2 : (tz1 + 2); return buf + 12 - tz; } } else { if (ddee) { u32 dd = (ddee * 5243) >> 19; /* (ddee / 100) */ u32 ee = ddee - dd * 100; /* (ddee % 100) */ byte_copy_2(buf + 4, digit_table + dd * 2); byte_copy_2(buf + 6, digit_table + ee * 2); tz1 = dec_trailing_zero_table[dd]; tz2 = dec_trailing_zero_table[ee]; tz = ee ? tz2 : (tz1 + 2); return buf + 8 - tz; } else { tz1 = dec_trailing_zero_table[bb]; tz2 = dec_trailing_zero_table[cc]; tz = cc ? tz2 : (tz1 + tz2); return buf + 4 - tz; } } } /** Write exponent part in range `e-45` to `e38`. */ static_inline u8 *write_f32_exp(i32 exp, u8 *buf) { bool lz; byte_copy_2(buf, "e-"); buf += 2 - (exp >= 0); exp = exp < 0 ? -exp : exp; lz = exp < 10; byte_copy_2(buf + 0, digit_table + (u32)exp * 2 + lz); return buf + 2 - lz; } /** Write exponent part in range `e-324` to `e308`. */ static_inline u8 *write_f64_exp(i32 exp, u8 *buf) { byte_copy_2(buf, "e-"); buf += 2 - (exp >= 0); exp = exp < 0 ? -exp : exp; if (exp < 100) { bool lz = exp < 10; byte_copy_2(buf + 0, digit_table + (u32)exp * 2 + lz); return buf + 2 - lz; } else { u32 hi = ((u32)exp * 656) >> 16; /* exp / 100 */ u32 lo = (u32)exp - hi * 100; /* exp % 100 */ buf[0] = (u8)((u8)hi + (u8)'0'); byte_copy_2(buf + 1, digit_table + lo * 2); return buf + 3; } } /** Magic number for fast `divide by power of 10`. */ typedef struct { u64 p10, mul; u32 shr1, shr2; } div_pow10_magic; /** Generated with llvm, see https://github.com/llvm/llvm-project/ blob/main/llvm/lib/Support/DivisionByConstantInfo.cpp */ static const div_pow10_magic div_pow10_table[] = { { U64(0x00000000, 0x00000001), U64(0x00000000, 0x00000000), 0, 0 }, { U64(0x00000000, 0x0000000A), U64(0xCCCCCCCC, 0xCCCCCCCD), 0, 3 }, { U64(0x00000000, 0x00000064), U64(0x28F5C28F, 0x5C28F5C3), 2, 2 }, { U64(0x00000000, 0x000003E8), U64(0x20C49BA5, 0xE353F7CF), 3, 4 }, { U64(0x00000000, 0x00002710), U64(0x346DC5D6, 0x3886594B), 0, 11 }, { U64(0x00000000, 0x000186A0), U64(0x0A7C5AC4, 0x71B47843), 5, 7 }, { U64(0x00000000, 0x000F4240), U64(0x431BDE82, 0xD7B634DB), 0, 18 }, { U64(0x00000000, 0x00989680), U64(0xD6BF94D5, 0xE57A42BD), 0, 23 }, { U64(0x00000000, 0x05F5E100), U64(0xABCC7711, 0x8461CEFD), 0, 26 }, { U64(0x00000000, 0x3B9ACA00), U64(0x0044B82F, 0xA09B5A53), 9, 11 }, { U64(0x00000002, 0x540BE400), U64(0xDBE6FECE, 0xBDEDD5BF), 0, 33 }, { U64(0x00000017, 0x4876E800), U64(0xAFEBFF0B, 0xCB24AAFF), 0, 36 }, { U64(0x000000E8, 0xD4A51000), U64(0x232F3302, 0x5BD42233), 0, 37 }, { U64(0x00000918, 0x4E72A000), U64(0x384B84D0, 0x92ED0385), 0, 41 }, { U64(0x00005AF3, 0x107A4000), U64(0x0B424DC3, 0x5095CD81), 0, 42 }, { U64(0x00038D7E, 0xA4C68000), U64(0x00024075, 0xF3DCEAC3), 15, 20 }, { U64(0x002386F2, 0x6FC10000), U64(0x39A5652F, 0xB1137857), 0, 51 }, { U64(0x01634578, 0x5D8A0000), U64(0x00005C3B, 0xD5191B53), 17, 22 }, { U64(0x0DE0B6B3, 0xA7640000), U64(0x000049C9, 0x7747490F), 18, 24 }, { U64(0x8AC72304, 0x89E80000), U64(0x760F253E, 0xDB4AB0d3), 0, 62 }, }; /** Divide a number by power of 10. */ static_inline void div_pow10(u64 num, u32 exp, u64 *div, u64 *mod, u64 *p10) { u64 hi, lo; div_pow10_magic m = div_pow10_table[exp]; u128_mul(num >> m.shr1, m.mul, &hi, &lo); *div = hi >> m.shr2; *mod = num - (*div * m.p10); *p10 = m.p10; } /** Multiplies 64-bit integer and returns highest 64-bit rounded value. */ static_inline u32 u64_round_to_odd(u64 u, u32 cp) { u64 hi, lo; u32 y_hi, y_lo; u128_mul(cp, u, &hi, &lo); y_hi = (u32)hi; y_lo = (u32)(lo >> 32); return y_hi | (y_lo > 1); } /** Multiplies 128-bit integer and returns highest 64-bit rounded value. */ static_inline u64 u128_round_to_odd(u64 hi, u64 lo, u64 cp) { u64 x_hi, x_lo, y_hi, y_lo; u128_mul(cp, lo, &x_hi, &x_lo); u128_mul_add(cp, hi, x_hi, &y_hi, &y_lo); return y_hi | (y_lo > 1); } /** Convert f32 from binary to decimal (shortest but may have trailing zeros). The input should not be 0, inf or nan. */ static_inline void f32_bin_to_dec(u32 sig_raw, u32 exp_raw, u32 sig_bin, i32 exp_bin, u32 *sig_dec, i32 *exp_dec) { bool is_even, irregular, round_up, trim; bool u0_inside, u1_inside, w0_inside, w1_inside; u64 p10_hi, p10_lo, hi, lo; u32 s, sp, cb, cbl, cbr, vb, vbl, vbr, upper, lower, mid; i32 k, h; /* Fast path, see f64_bin_to_dec(). */ while (likely(sig_raw)) { u32 mod, dec, add_1, add_10, s_hi, s_lo; u32 c, half_ulp, t0, t1; /* k = floor(exp_bin * log10(2)); */ /* h = exp_bin + floor(log2(10) * -k); (h = 0/1/2/3) */ k = (i32)(exp_bin * 315653) >> 20; h = exp_bin + ((-k * 217707) >> 16); pow10_table_get_sig(-k, &p10_hi, &p10_lo); /* sig_bin << (1/2/3/4) */ cb = sig_bin << (h + 1); u128_mul(cb, p10_hi, &hi, &lo); s_hi = (u32)(hi); s_lo = (u32)(lo >> 32); mod = s_hi % 10; dec = s_hi - mod; /* right shift 4 to fit in u32 */ c = (mod << (32 - 4)) | (s_lo >> 4); half_ulp = (u32)(p10_hi >> (32 + 4 - h)); /* check w1, u0, w0 range */ w1_inside = (s_lo >= ((u32)1 << 31)); if (unlikely(s_lo == ((u32)1 << 31))) break; u0_inside = (half_ulp >= c); if (unlikely(half_ulp == c)) break; t0 = (u32)10 << (32 - 4); t1 = c + half_ulp; w0_inside = (t1 >= t0); if (unlikely(t0 - t1 <= (u32)1)) break; trim = (u0_inside | w0_inside); add_10 = (w0_inside ? 10 : 0); add_1 = mod + w1_inside; *sig_dec = dec + (trim ? add_10 : add_1); *exp_dec = k; return; } /* Schubfach algorithm, see f64_bin_to_dec(). */ irregular = (sig_raw == 0 && exp_raw > 1); is_even = !(sig_bin & 1); cbl = 4 * sig_bin - 2 + irregular; cb = 4 * sig_bin; cbr = 4 * sig_bin + 2; /* k = floor(exp_bin * log10(2) + (irregular ? log10(3.0 / 4.0) : 0)); */ /* h = exp_bin + floor(log2(10) * -k) + 1; (h = 1/2/3/4) */ k = (i32)(exp_bin * 315653 - (irregular ? 131237 : 0)) >> 20; h = exp_bin + ((-k * 217707) >> 16) + 1; pow10_table_get_sig(-k, &p10_hi, &p10_lo); p10_hi += 1; vbl = u64_round_to_odd(p10_hi, cbl << h); vb = u64_round_to_odd(p10_hi, cb << h); vbr = u64_round_to_odd(p10_hi, cbr << h); lower = vbl + !is_even; upper = vbr - !is_even; s = vb / 4; if (s >= 10) { sp = s / 10; u0_inside = (lower <= 40 * sp); w0_inside = (upper >= 40 * sp + 40); if (u0_inside != w0_inside) { *sig_dec = sp * 10 + (w0_inside ? 10 : 0); *exp_dec = k; return; } } u1_inside = (lower <= 4 * s); w1_inside = (upper >= 4 * s + 4); mid = 4 * s + 2; round_up = (vb > mid) || (vb == mid && (s & 1) != 0); *sig_dec = s + ((u1_inside != w1_inside) ? w1_inside : round_up); *exp_dec = k; } /** Convert f64 from binary to decimal (shortest but may have trailing zeros). The input should not be 0, inf or nan. */ static_inline void f64_bin_to_dec(u64 sig_raw, u32 exp_raw, u64 sig_bin, i32 exp_bin, u64 *sig_dec, i32 *exp_dec) { bool is_even, irregular, round_up, trim; bool u0_inside, u1_inside, w0_inside, w1_inside; u64 s, sp, cb, cbl, cbr, vb, vbl, vbr, p10_hi, p10_lo, upper, lower, mid; i32 k, h; /* Fast path: For regular spacing significand 'c', there are 4 candidates: u0 u1 c w1 w0 ----|----|----|----|----|-*--|----|----|----|----|----|----|----|---- 9 0 1 2 3 4 5 6 7 8 9 0 1 |___________________|___________________| 1ulp The `1ulp` is in the range [1.0, 10.0). If (c - 0.5ulp < u0), trim the last digit and round down. If (c + 0.5ulp > w0), trim the last digit and round up. If (c - 0.5ulp < u1), round down. If (c + 0.5ulp > w1), round up. */ while (likely(sig_raw)) { u64 mod, dec, add_1, add_10, s_hi, s_lo; u64 c, half_ulp, t0, t1; /* k = floor(exp_bin * log10(2)); */ /* h = exp_bin + floor(log2(10) * -k); (h = 0/1/2/3) */ k = (i32)(exp_bin * 315653) >> 20; h = exp_bin + ((-k * 217707) >> 16); pow10_table_get_sig(-k, &p10_hi, &p10_lo); /* sig_bin << (1/2/3/4) */ cb = sig_bin << (h + 1); u128_mul(cb, p10_lo, &s_hi, &s_lo); u128_mul_add(cb, p10_hi, s_hi, &s_hi, &s_lo); mod = s_hi % 10; dec = s_hi - mod; /* right shift 4 to fit in u64 */ c = (mod << (64 - 4)) | (s_lo >> 4); half_ulp = p10_hi >> (4 - h); /* check w1, u0, w0 range */ w1_inside = (s_lo >= ((u64)1 << 63)); if (unlikely(s_lo == ((u64)1 << 63))) break; u0_inside = (half_ulp >= c); if (unlikely(half_ulp == c)) break; t0 = ((u64)10 << (64 - 4)); t1 = c + half_ulp; w0_inside = (t1 >= t0); if (unlikely(t0 - t1 <= (u64)1)) break; trim = (u0_inside | w0_inside); add_10 = (w0_inside ? 10 : 0); add_1 = mod + w1_inside; *sig_dec = dec + (trim ? add_10 : add_1); *exp_dec = k; return; } /* Schubfach algorithm: Raffaello Giulietti, The Schubfach way to render doubles, 2022. https://drive.google.com/file/d/1gp5xv4CAa78SVgCeWfGqqI4FfYYYuNFb (Paper) https://github.com/openjdk/jdk/pull/3402 (Java implementation) https://github.com/abolz/Drachennest (C++ implementation) */ irregular = (sig_raw == 0 && exp_raw > 1); is_even = !(sig_bin & 1); cbl = 4 * sig_bin - 2 + irregular; cb = 4 * sig_bin; cbr = 4 * sig_bin + 2; /* k = floor(exp_bin * log10(2) + (irregular ? log10(3.0 / 4.0) : 0)); */ /* h = exp_bin + floor(log2(10) * -k) + 1; (h = 1/2/3/4) */ k = (i32)(exp_bin * 315653 - (irregular ? 131237 : 0)) >> 20; h = exp_bin + ((-k * 217707) >> 16) + 1; pow10_table_get_sig(-k, &p10_hi, &p10_lo); p10_lo += 1; vbl = u128_round_to_odd(p10_hi, p10_lo, cbl << h); vb = u128_round_to_odd(p10_hi, p10_lo, cb << h); vbr = u128_round_to_odd(p10_hi, p10_lo, cbr << h); lower = vbl + !is_even; upper = vbr - !is_even; s = vb / 4; if (s >= 10) { sp = s / 10; u0_inside = (lower <= 40 * sp); w0_inside = (upper >= 40 * sp + 40); if (u0_inside != w0_inside) { *sig_dec = sp * 10 + (w0_inside ? 10 : 0); *exp_dec = k; return; } } u1_inside = (lower <= 4 * s); w1_inside = (upper >= 4 * s + 4); mid = 4 * s + 2; round_up = (vb > mid) || (vb == mid && (s & 1) != 0); *sig_dec = s + ((u1_inside != w1_inside) ? w1_inside : round_up); *exp_dec = k; } /** Convert f64 from binary to decimal (fast but not the shortest). The input should not be 0, inf, nan. */ static_inline void f64_bin_to_dec_fast(u64 sig_raw, u32 exp_raw, u64 sig_bin, i32 exp_bin, u64 *sig_dec, i32 *exp_dec, bool *round_up) { u64 cb, p10_hi, p10_lo, s_hi, s_lo; i32 k, h; bool irregular, u; irregular = (sig_raw == 0 && exp_raw > 1); /* k = floor(exp_bin * log10(2) + (irregular ? log10(3.0 / 4.0) : 0)); */ /* h = exp_bin + floor(log2(10) * -k) + 1; (h = 1/2/3/4) */ k = (i32)(exp_bin * 315653 - (irregular ? 131237 : 0)) >> 20; h = exp_bin + ((-k * 217707) >> 16); pow10_table_get_sig(-k, &p10_hi, &p10_lo); /* sig_bin << (1/2/3/4) */ cb = sig_bin << (h + 1); u128_mul(cb, p10_lo, &s_hi, &s_lo); u128_mul_add(cb, p10_hi, s_hi, &s_hi, &s_lo); /* round up */ u = s_lo >= (irregular ? U64(0x55555555, 0x55555555) : ((u64)1 << 63)); *sig_dec = s_hi + u; *exp_dec = k; *round_up = u; return; } /** Write inf/nan if allowed. */ static_inline u8 *write_inf_or_nan(u8 *buf, yyjson_write_flag flg, u64 sig_raw, bool sign) { if (has_flg(INF_AND_NAN_AS_NULL)) { byte_copy_4(buf, "null"); return buf + 4; } if (has_allow(INF_AND_NAN)) { if (sig_raw == 0) { buf[0] = '-'; buf += sign; byte_copy_8(buf, "Infinity"); return buf + 8; } else { byte_copy_4(buf, "NaN"); return buf + 3; } } return NULL; } /** Write a float number (requires 40 bytes buffer). We follow the ECMAScript specification for printing floating-point numbers, similar to `Number.prototype.toString()`, but with the following changes: 1. Keep the negative sign of `-0.0` to preserve input information. 2. Keep decimal point to indicate the number is floating point. 3. Remove positive sign in the exponent part. */ static_noinline u8 *write_f32_raw(u8 *buf, u64 raw_f64, yyjson_write_flag flg) { u32 sig_bin, sig_dec, sig_raw; i32 exp_bin, exp_dec, sig_len, dot_ofs; u32 exp_raw, raw; u8 *end; bool sign; /* cast double to float */ raw = f32_to_bits(f64_to_f32(f64_from_bits(raw_f64))); /* decode raw bytes from IEEE-754 double format. */ sign = (bool)(raw >> (F32_BITS - 1)); sig_raw = raw & F32_SIG_MASK; exp_raw = (raw & F32_EXP_MASK) >> F32_SIG_BITS; /* return inf or nan */ if (unlikely(exp_raw == ((u32)1 << F32_EXP_BITS) - 1)) { return write_inf_or_nan(buf, flg, sig_raw, sign); } /* add sign for all finite number */ buf[0] = '-'; buf += sign; /* return zero */ if ((raw << 1) == 0) { byte_copy_4(buf, "0.0"); return buf + 3; } if (likely(exp_raw != 0)) { /* normal number */ sig_bin = sig_raw | ((u32)1 << F32_SIG_BITS); exp_bin = (i32)exp_raw - F32_EXP_BIAS - F32_SIG_BITS; /* fast path for small integer number without fraction */ if ((-F32_SIG_BITS <= exp_bin && exp_bin <= 0) && (u64_tz_bits(sig_bin) >= (u32)-exp_bin)) { sig_dec = sig_bin >> -exp_bin; /* range: [1, 0xFFFFFF] */ buf = write_u32_len_1_to_8(sig_dec, buf); byte_copy_2(buf, ".0"); return buf + 2; } /* binary to decimal */ f32_bin_to_dec(sig_raw, exp_raw, sig_bin, exp_bin, &sig_dec, &exp_dec); /* the sig length is 7 or 9 */ sig_len = 7 + (sig_dec >= (u32)10000000) + (sig_dec >= (u32)100000000); /* the decimal point offset relative to the first digit */ dot_ofs = sig_len + exp_dec; if (-6 < dot_ofs && dot_ofs <= 21) { i32 num_sep_pos, dot_set_pos, pre_ofs; u8 *num_hdr, *num_end, *num_sep, *dot_end; bool no_pre_zero; /* fill zeros */ memset(buf, '0', 32); /* not prefixed with zero, e.g. 1.234, 1234.0 */ no_pre_zero = (dot_ofs > 0); /* write the number as digits */ pre_ofs = no_pre_zero ? 0 : (2 - dot_ofs); num_hdr = buf + pre_ofs; num_end = write_u32_len_7_to_9_trim(sig_dec, num_hdr); /* seperate these digits to leave a space for dot */ num_sep_pos = no_pre_zero ? dot_ofs : 0; num_sep = num_hdr + num_sep_pos; byte_move_8(num_sep + no_pre_zero, num_sep); num_end += no_pre_zero; /* write the dot */ dot_set_pos = yyjson_max(dot_ofs, 1); buf[dot_set_pos] = '.'; /* return the ending */ dot_end = buf + dot_ofs + 2; return yyjson_max(dot_end, num_end); } else { /* write with scientific notation, e.g. 1.234e56 */ end = write_u32_len_7_to_9_trim(sig_dec, buf + 1); end -= (end == buf + 2); /* remove '.0', e.g. 2.0e34 -> 2e34 */ exp_dec += sig_len - 1; buf[0] = buf[1]; buf[1] = '.'; return write_f32_exp(exp_dec, end); } } else { /* subnormal number */ sig_bin = sig_raw; exp_bin = 1 - F32_EXP_BIAS - F32_SIG_BITS; /* binary to decimal */ f32_bin_to_dec(sig_raw, exp_raw, sig_bin, exp_bin, &sig_dec, &exp_dec); /* write significand part */ end = write_u32_len_1_to_8(sig_dec, buf + 1); buf[0] = buf[1]; buf[1] = '.'; exp_dec += (i32)(end - buf) - 2; /* trim trailing zeros */ end -= *(end - 1) == '0'; /* branchless for last zero */ end -= *(end - 1) == '0'; /* branchless for second last zero */ while (*(end - 1) == '0') end--; /* for unlikely more zeros */ end -= *(end - 1) == '.'; /* remove dot, e.g. 2.e-321 -> 2e-321 */ /* write exponent part */ return write_f32_exp(exp_dec, end); } } /** Write a double number (requires 40 bytes buffer). We follow the ECMAScript specification for printing floating-point numbers, similar to `Number.prototype.toString()`, but with the following changes: 1. Keep the negative sign of `-0.0` to preserve input information. 2. Keep decimal point to indicate the number is floating point. 3. Remove positive sign in the exponent part. */ static_noinline u8 *write_f64_raw(u8 *buf, u64 raw, yyjson_write_flag flg) { u64 sig_bin, sig_dec, sig_raw; i32 exp_bin, exp_dec, sig_len, dot_ofs; u32 exp_raw; u8 *end; bool sign; /* decode raw bytes from IEEE-754 double format. */ sign = (bool)(raw >> (F64_BITS - 1)); sig_raw = raw & F64_SIG_MASK; exp_raw = (u32)((raw & F64_EXP_MASK) >> F64_SIG_BITS); /* return inf or nan */ if (unlikely(exp_raw == ((u32)1 << F64_EXP_BITS) - 1)) { return write_inf_or_nan(buf, flg, sig_raw, sign); } /* add sign for all finite number */ buf[0] = '-'; buf += sign; /* return zero */ if ((raw << 1) == 0) { byte_copy_4(buf, "0.0"); return buf + 3; } if (likely(exp_raw != 0)) { /* normal number */ sig_bin = sig_raw | ((u64)1 << F64_SIG_BITS); exp_bin = (i32)exp_raw - F64_EXP_BIAS - F64_SIG_BITS; /* fast path for small integer number without fraction */ if ((-F64_SIG_BITS <= exp_bin && exp_bin <= 0) && (u64_tz_bits(sig_bin) >= (u32)-exp_bin)) { sig_dec = sig_bin >> -exp_bin; /* range: [1, 0x1FFFFFFFFFFFFF] */ buf = write_u64_len_1_to_16(sig_dec, buf); byte_copy_2(buf, ".0"); return buf + 2; } /* binary to decimal */ f64_bin_to_dec(sig_raw, exp_raw, sig_bin, exp_bin, &sig_dec, &exp_dec); /* the sig length is 16 or 17 */ sig_len = 16 + (sig_dec >= (u64)100000000 * 100000000); /* the decimal point offset relative to the first digit */ dot_ofs = sig_len + exp_dec; if (-6 < dot_ofs && dot_ofs <= 21) { i32 num_sep_pos, dot_set_pos, pre_ofs; u8 *num_hdr, *num_end, *num_sep, *dot_end; bool no_pre_zero; /* fill zeros */ memset(buf, '0', 32); /* not prefixed with zero, e.g. 1.234, 1234.0 */ no_pre_zero = (dot_ofs > 0); /* write the number as digits */ pre_ofs = no_pre_zero ? 0 : (2 - dot_ofs); num_hdr = buf + pre_ofs; num_end = write_u64_len_16_to_17_trim(sig_dec, num_hdr); /* seperate these digits to leave a space for dot */ num_sep_pos = no_pre_zero ? dot_ofs : 0; num_sep = num_hdr + num_sep_pos; byte_move_16(num_sep + no_pre_zero, num_sep); num_end += no_pre_zero; /* write the dot */ dot_set_pos = yyjson_max(dot_ofs, 1); buf[dot_set_pos] = '.'; /* return the ending */ dot_end = buf + dot_ofs + 2; return yyjson_max(dot_end, num_end); } else { /* write with scientific notation, e.g. 1.234e56 */ end = write_u64_len_16_to_17_trim(sig_dec, buf + 1); end -= (end == buf + 2); /* remove '.0', e.g. 2.0e34 -> 2e34 */ exp_dec += sig_len - 1; buf[0] = buf[1]; buf[1] = '.'; return write_f64_exp(exp_dec, end); } } else { /* subnormal number */ sig_bin = sig_raw; exp_bin = 1 - F64_EXP_BIAS - F64_SIG_BITS; /* binary to decimal */ f64_bin_to_dec(sig_raw, exp_raw, sig_bin, exp_bin, &sig_dec, &exp_dec); /* write significand part */ end = write_u64_len_1_to_17(sig_dec, buf + 1); buf[0] = buf[1]; buf[1] = '.'; exp_dec += (i32)(end - buf) - 2; /* trim trailing zeros */ end -= *(end - 1) == '0'; /* branchless for last zero */ end -= *(end - 1) == '0'; /* branchless for second last zero */ while (*(end - 1) == '0') end--; /* for unlikely more zeros */ end -= *(end - 1) == '.'; /* remove dot, e.g. 2.e-321 -> 2e-321 */ /* write exponent part */ return write_f64_exp(exp_dec, end); } } /** Write a double number using fixed-point notation (requires 40 bytes buffer). We follow the ECMAScript specification for printing floating-point numbers, similar to `Number.prototype.toFixed(prec)`, but with the following changes: 1. Keep the negative sign of `-0.0` to preserve input information. 2. Keep decimal point to indicate the number is floating point. 3. Remove positive sign in the exponent part. 4. Remove trailing zeros and reduce unnecessary precision. */ static_noinline u8 *write_f64_raw_fixed(u8 *buf, u64 raw, yyjson_write_flag flg, u32 prec) { u64 sig_bin, sig_dec, sig_raw; i32 exp_bin, exp_dec, sig_len, dot_ofs; u32 exp_raw; u8 *end; bool sign; /* decode raw bytes from IEEE-754 double format. */ sign = (bool)(raw >> (F64_BITS - 1)); sig_raw = raw & F64_SIG_MASK; exp_raw = (u32)((raw & F64_EXP_MASK) >> F64_SIG_BITS); /* return inf or nan */ if (unlikely(exp_raw == ((u32)1 << F64_EXP_BITS) - 1)) { return write_inf_or_nan(buf, flg, sig_raw, sign); } /* add sign for all finite number */ buf[0] = '-'; buf += sign; /* return zero */ if ((raw << 1) == 0) { byte_copy_4(buf, "0.0"); return buf + 3; } if (likely(exp_raw != 0)) { /* normal number */ sig_bin = sig_raw | ((u64)1 << F64_SIG_BITS); exp_bin = (i32)exp_raw - F64_EXP_BIAS - F64_SIG_BITS; /* fast path for small integer number without fraction */ if ((-F64_SIG_BITS <= exp_bin && exp_bin <= 0) && (u64_tz_bits(sig_bin) >= (u32)-exp_bin)) { sig_dec = sig_bin >> -exp_bin; /* range: [1, 0x1FFFFFFFFFFFFF] */ buf = write_u64_len_1_to_16(sig_dec, buf); byte_copy_2(buf, ".0"); return buf + 2; } /* only `fabs(num) < 1e21` are processed here. */ if ((raw << 1) < (U64(0x444B1AE4, 0xD6E2EF50) << 1)) { i32 num_sep_pos, dot_set_pos, pre_ofs; u8 *num_hdr, *num_end, *num_sep; bool round_up, no_pre_zero; /* binary to decimal */ f64_bin_to_dec_fast(sig_raw, exp_raw, sig_bin, exp_bin, &sig_dec, &exp_dec, &round_up); /* the sig length is 16 or 17 */ sig_len = 16 + (sig_dec >= (u64)100000000 * 100000000); /* limit the length of digits after the decimal point */ if (exp_dec < -1) { i32 sig_len_cut = -exp_dec - (i32)prec; if (sig_len_cut > sig_len) { byte_copy_4(buf, "0.0"); return buf + 3; } if (sig_len_cut > 0) { u64 div, mod, p10; /* remove round up */ sig_dec -= round_up; sig_len = 16 + (sig_dec >= (u64)100000000 * 100000000); /* cut off some digits */ div_pow10(sig_dec, (u32)sig_len_cut, &div, &mod, &p10); /* add round up */ sig_dec = div + (mod >= p10 / 2); /* update exp and sig length */ exp_dec += sig_len_cut; sig_len -= sig_len_cut; sig_len += (sig_len >= 0) && (sig_dec >= div_pow10_table[sig_len].p10); } if (sig_len <= 0) { byte_copy_4(buf, "0.0"); return buf + 3; } } /* fill zeros */ memset(buf, '0', 32); /* the decimal point offset relative to the first digit */ dot_ofs = sig_len + exp_dec; /* not prefixed with zero, e.g. 1.234, 1234.0 */ no_pre_zero = (dot_ofs > 0); /* write the number as digits */ pre_ofs = no_pre_zero ? 0 : (1 - dot_ofs); num_hdr = buf + pre_ofs; num_end = write_u64_len_1_to_17(sig_dec, num_hdr); /* seperate these digits to leave a space for dot */ num_sep_pos = no_pre_zero ? dot_ofs : -dot_ofs; num_sep = buf + num_sep_pos; byte_move_16(num_sep + 1, num_sep); num_end += (exp_dec < 0); /* write the dot */ dot_set_pos = yyjson_max(dot_ofs, 1); buf[dot_set_pos] = '.'; /* remove trailing zeros */ buf += dot_set_pos + 2; buf = yyjson_max(buf, num_end); buf -= *(buf - 1) == '0'; /* branchless for last zero */ buf -= *(buf - 1) == '0'; /* branchless for second last zero */ while (*(buf - 1) == '0') buf--; /* for unlikely more zeros */ buf += *(buf - 1) == '.'; /* keep a zero after dot */ return buf; } else { /* binary to decimal */ f64_bin_to_dec(sig_raw, exp_raw, sig_bin, exp_bin, &sig_dec, &exp_dec); /* the sig length is 16 or 17 */ sig_len = 16 + (sig_dec >= (u64)100000000 * 100000000); /* write with scientific notation, e.g. 1.234e56 */ end = write_u64_len_16_to_17_trim(sig_dec, buf + 1); end -= (end == buf + 2); /* remove '.0', e.g. 2.0e34 -> 2e34 */ exp_dec += sig_len - 1; buf[0] = buf[1]; buf[1] = '.'; return write_f64_exp(exp_dec, end); } } else { /* subnormal number */ byte_copy_4(buf, "0.0"); return buf + 3; } } #else /* FP_WRITER */ #if YYJSON_MSC_VER >= 1400 #define snprintf_num(buf, len, fmt, dig, val) \ sprintf_s((char *)buf, len, fmt, dig, val) #elif defined(snprintf) || (YYJSON_STDC_VER >= 199901L) #define snprintf_num(buf, len, fmt, dig, val) \ snprintf((char *)buf, len, fmt, dig, val) #else #define snprintf_num(buf, len, fmt, dig, val) \ sprintf((char *)buf, fmt, dig, val) #endif static_noinline u8 *write_fp_reformat(u8 *buf, int len, yyjson_write_flag flg, bool fixed) { u8 *cur = buf; if (unlikely(len < 1)) return NULL; cur += (*cur == '-'); if (unlikely(!char_is_digit(*cur))) { /* nan, inf, or bad output */ if (has_flg(INF_AND_NAN_AS_NULL)) { byte_copy_4(buf, "null"); return buf + 4; } else if (has_allow(INF_AND_NAN)) { if (*cur == 'i') { byte_copy_8(cur, "Infinity"); return cur + 8; } else if (*cur == 'n') { byte_copy_4(buf, "NaN"); return buf + 3; } } return NULL; } else { /* finite number */ u8 *end = buf + len, *dot = NULL, *exp = NULL; /* The snprintf() function is locale-dependent. For currently known locales, (en, zh, ja, ko, am, he, hi) use '.' as the decimal point, while other locales use ',' as the decimal point. we need to replace ',' with '.' to avoid the locale setting. */ for (; cur < end; cur++) { switch (*cur) { case ',': *cur = '.'; /* fallthrough */ case '.': dot = cur; break; case 'e': exp = cur; break; default: break; } } if (fixed) { /* remove trailing zeros */ while (*(end - 1) == '0') end--; end += *(end - 1) == '.'; } else { if (!dot && !exp) { /* add decimal point, e.g. 123 -> 123.0 */ byte_copy_2(end, ".0"); end += 2; } else if (exp) { cur = exp + 1; /* remove positive sign in the exponent part */ if (*cur == '+') { memmove(cur, cur + 1, (usize)(end - cur - 1)); end--; } cur += (*cur == '-'); /* remove leading zeros in the exponent part */ if (*cur == '0') { u8 *hdr = cur++; while (*cur == '0') cur++; memmove(hdr, cur, (usize)(end - cur)); end -= (usize)(cur - hdr); } } } return end; } } /** Write a double number (requires 40 bytes buffer). */ static_noinline u8 *write_f64_raw(u8 *buf, u64 raw, yyjson_write_flag flg) { #if defined(DBL_DECIMAL_DIG) && DBL_DECIMAL_DIG < F64_DEC_DIG int dig = DBL_DECIMAL_DIG; #else int dig = F64_DEC_DIG; #endif f64 val = f64_from_bits(raw); int len = snprintf_num(buf, FP_BUF_LEN, "%.*g", dig, val); return write_fp_reformat(buf, len, flg, false); } /** Write a double number (requires 40 bytes buffer). */ static_noinline u8 *write_f32_raw(u8 *buf, u64 raw, yyjson_write_flag flg) { #if defined(FLT_DECIMAL_DIG) && FLT_DECIMAL_DIG < F32_DEC_DIG int dig = FLT_DECIMAL_DIG; #else int dig = F32_DEC_DIG; #endif f64 val = (f64)f64_to_f32(f64_from_bits(raw)); int len = snprintf_num(buf, FP_BUF_LEN, "%.*g", dig, val); return write_fp_reformat(buf, len, flg, false); } /** Write a double number (requires 40 bytes buffer). */ static_noinline u8 *write_f64_raw_fixed(u8 *buf, u64 raw, yyjson_write_flag flg, u32 prec) { f64 val = (f64)f64_from_bits(raw); if (-1e21 < val && val < 1e21) { int len = snprintf_num(buf, FP_BUF_LEN, "%.*f", (int)prec, val); return write_fp_reformat(buf, len, flg, true); } else { return write_f64_raw(buf, raw, flg); } } #endif /* FP_WRITER */ /** Write a JSON number (requires 40 bytes buffer). */ static_inline u8 *write_num(u8 *cur, yyjson_val *val, yyjson_write_flag flg) { if (!(val->tag & YYJSON_SUBTYPE_REAL)) { u64 pos = val->uni.u64; u64 neg = ~pos + 1; usize sign = ((val->tag & YYJSON_SUBTYPE_SINT) > 0) & ((i64)pos < 0); *cur = '-'; return write_u64(sign ? neg : pos, cur + sign); } else { u64 raw = val->uni.u64; u32 val_fmt = (u32)(val->tag >> 32); u32 all_fmt = flg; u32 fmt = val_fmt | all_fmt; if (likely(!(fmt >> (32 - YYJSON_WRITE_FP_FLAG_BITS)))) { /* double to shortest */ return write_f64_raw(cur, raw, flg); } else if (fmt >> (32 - YYJSON_WRITE_FP_PREC_BITS)) { /* double to fixed */ u32 val_prec = val_fmt >> (32 - YYJSON_WRITE_FP_PREC_BITS); u32 all_prec = all_fmt >> (32 - YYJSON_WRITE_FP_PREC_BITS); u32 prec = val_prec ? val_prec : all_prec; return write_f64_raw_fixed(cur, raw, flg, prec); } else { if (fmt & YYJSON_WRITE_FP_TO_FLOAT) { /* float to shortest */ return write_f32_raw(cur, raw, flg); } else { /* double to shortest */ return write_f64_raw(cur, raw, flg); } } } } char *yyjson_write_number(const yyjson_val *val, char *buf) { if (unlikely(!val || !buf)) return NULL; switch (val->tag & YYJSON_TAG_MASK) { case YYJSON_TYPE_NUM | YYJSON_SUBTYPE_UINT: { buf = (char *)write_u64(val->uni.u64, (u8 *)buf); *buf = '\0'; return buf; } case YYJSON_TYPE_NUM | YYJSON_SUBTYPE_SINT: { u64 pos = val->uni.u64; u64 neg = ~pos + 1; usize sign = ((i64)pos < 0); *buf = '-'; buf = (char *)write_u64(sign ? neg : pos, (u8 *)buf + sign); *buf = '\0'; return buf; } case YYJSON_TYPE_NUM | YYJSON_SUBTYPE_REAL: { u64 raw = val->uni.u64; u32 fmt = (u32)(val->tag >> 32); u32 flg = YYJSON_WRITE_ALLOW_INF_AND_NAN; if (likely(!(fmt >> (32 - YYJSON_WRITE_FP_FLAG_BITS)))) { buf = (char *)write_f64_raw((u8 *)buf, raw, flg); } else if (fmt >> (32 - YYJSON_WRITE_FP_PREC_BITS)) { u32 prec = fmt >> (32 - YYJSON_WRITE_FP_PREC_BITS); buf = (char *)write_f64_raw_fixed((u8 *)buf, raw, flg, prec); } else { if (fmt & YYJSON_WRITE_FP_TO_FLOAT) { buf = (char *)write_f32_raw((u8 *)buf, raw, flg); } else { buf = (char *)write_f64_raw((u8 *)buf, raw, flg); } } if (buf) *buf = '\0'; return buf; } default: return NULL; } } /*============================================================================== * MARK: - String Writer (Private) *============================================================================*/ /** Character encode type, if (type > CHAR_ENC_ERR_1) bytes = type / 2; */ typedef u8 char_enc_type; #define CHAR_ENC_CPY_1 0 /* 1-byte UTF-8, copy. */ #define CHAR_ENC_ERR_1 1 /* 1-byte UTF-8, error. */ #define CHAR_ENC_ESC_A 2 /* 1-byte ASCII, escaped as '\x'. */ #define CHAR_ENC_ESC_1 3 /* 1-byte UTF-8, escaped as '\uXXXX'. */ #define CHAR_ENC_CPY_2 4 /* 2-byte UTF-8, copy. */ #define CHAR_ENC_ESC_2 5 /* 2-byte UTF-8, escaped as '\uXXXX'. */ #define CHAR_ENC_CPY_3 6 /* 3-byte UTF-8, copy. */ #define CHAR_ENC_ESC_3 7 /* 3-byte UTF-8, escaped as '\uXXXX'. */ #define CHAR_ENC_CPY_4 8 /* 4-byte UTF-8, copy. */ #define CHAR_ENC_ESC_4 9 /* 4-byte UTF-8, escaped as '\uXXXX\uXXXX'. */ /** Character encode type table: don't escape unicode, don't escape '/'. (generate with misc/make_tables.c) */ static const char_enc_type enc_table_cpy[256] = { 3, 3, 3, 3, 3, 3, 3, 3, 2, 2, 2, 3, 2, 2, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 0, 0, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 8, 8, 8, 8, 8, 8, 8, 8, 1, 1, 1, 1, 1, 1, 1, 1 }; /** Character encode type table: don't escape unicode, escape '/'. (generate with misc/make_tables.c) */ static const char_enc_type enc_table_cpy_slash[256] = { 3, 3, 3, 3, 3, 3, 3, 3, 2, 2, 2, 3, 2, 2, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 0, 0, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 8, 8, 8, 8, 8, 8, 8, 8, 1, 1, 1, 1, 1, 1, 1, 1 }; /** Character encode type table: escape unicode, don't escape '/'. (generate with misc/make_tables.c) */ static const char_enc_type enc_table_esc[256] = { 3, 3, 3, 3, 3, 3, 3, 3, 2, 2, 2, 3, 2, 2, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 0, 0, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 9, 9, 9, 9, 9, 9, 9, 9, 1, 1, 1, 1, 1, 1, 1, 1 }; /** Character encode type table: escape unicode, escape '/'. (generate with misc/make_tables.c) */ static const char_enc_type enc_table_esc_slash[256] = { 3, 3, 3, 3, 3, 3, 3, 3, 2, 2, 2, 3, 2, 2, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 0, 0, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 9, 9, 9, 9, 9, 9, 9, 9, 1, 1, 1, 1, 1, 1, 1, 1 }; /** Escaped hex character table: ["00" "01" "02" ... "FD" "FE" "FF"]. (generate with misc/make_tables.c) */ yyjson_align(2) static const u8 esc_hex_char_table[512] = { '0', '0', '0', '1', '0', '2', '0', '3', '0', '4', '0', '5', '0', '6', '0', '7', '0', '8', '0', '9', '0', 'A', '0', 'B', '0', 'C', '0', 'D', '0', 'E', '0', 'F', '1', '0', '1', '1', '1', '2', '1', '3', '1', '4', '1', '5', '1', '6', '1', '7', '1', '8', '1', '9', '1', 'A', '1', 'B', '1', 'C', '1', 'D', '1', 'E', '1', 'F', '2', '0', '2', '1', '2', '2', '2', '3', '2', '4', '2', '5', '2', '6', '2', '7', '2', '8', '2', '9', '2', 'A', '2', 'B', '2', 'C', '2', 'D', '2', 'E', '2', 'F', '3', '0', '3', '1', '3', '2', '3', '3', '3', '4', '3', '5', '3', '6', '3', '7', '3', '8', '3', '9', '3', 'A', '3', 'B', '3', 'C', '3', 'D', '3', 'E', '3', 'F', '4', '0', '4', '1', '4', '2', '4', '3', '4', '4', '4', '5', '4', '6', '4', '7', '4', '8', '4', '9', '4', 'A', '4', 'B', '4', 'C', '4', 'D', '4', 'E', '4', 'F', '5', '0', '5', '1', '5', '2', '5', '3', '5', '4', '5', '5', '5', '6', '5', '7', '5', '8', '5', '9', '5', 'A', '5', 'B', '5', 'C', '5', 'D', '5', 'E', '5', 'F', '6', '0', '6', '1', '6', '2', '6', '3', '6', '4', '6', '5', '6', '6', '6', '7', '6', '8', '6', '9', '6', 'A', '6', 'B', '6', 'C', '6', 'D', '6', 'E', '6', 'F', '7', '0', '7', '1', '7', '2', '7', '3', '7', '4', '7', '5', '7', '6', '7', '7', '7', '8', '7', '9', '7', 'A', '7', 'B', '7', 'C', '7', 'D', '7', 'E', '7', 'F', '8', '0', '8', '1', '8', '2', '8', '3', '8', '4', '8', '5', '8', '6', '8', '7', '8', '8', '8', '9', '8', 'A', '8', 'B', '8', 'C', '8', 'D', '8', 'E', '8', 'F', '9', '0', '9', '1', '9', '2', '9', '3', '9', '4', '9', '5', '9', '6', '9', '7', '9', '8', '9', '9', '9', 'A', '9', 'B', '9', 'C', '9', 'D', '9', 'E', '9', 'F', 'A', '0', 'A', '1', 'A', '2', 'A', '3', 'A', '4', 'A', '5', 'A', '6', 'A', '7', 'A', '8', 'A', '9', 'A', 'A', 'A', 'B', 'A', 'C', 'A', 'D', 'A', 'E', 'A', 'F', 'B', '0', 'B', '1', 'B', '2', 'B', '3', 'B', '4', 'B', '5', 'B', '6', 'B', '7', 'B', '8', 'B', '9', 'B', 'A', 'B', 'B', 'B', 'C', 'B', 'D', 'B', 'E', 'B', 'F', 'C', '0', 'C', '1', 'C', '2', 'C', '3', 'C', '4', 'C', '5', 'C', '6', 'C', '7', 'C', '8', 'C', '9', 'C', 'A', 'C', 'B', 'C', 'C', 'C', 'D', 'C', 'E', 'C', 'F', 'D', '0', 'D', '1', 'D', '2', 'D', '3', 'D', '4', 'D', '5', 'D', '6', 'D', '7', 'D', '8', 'D', '9', 'D', 'A', 'D', 'B', 'D', 'C', 'D', 'D', 'D', 'E', 'D', 'F', 'E', '0', 'E', '1', 'E', '2', 'E', '3', 'E', '4', 'E', '5', 'E', '6', 'E', '7', 'E', '8', 'E', '9', 'E', 'A', 'E', 'B', 'E', 'C', 'E', 'D', 'E', 'E', 'E', 'F', 'F', '0', 'F', '1', 'F', '2', 'F', '3', 'F', '4', 'F', '5', 'F', '6', 'F', '7', 'F', '8', 'F', '9', 'F', 'A', 'F', 'B', 'F', 'C', 'F', 'D', 'F', 'E', 'F', 'F' }; /** Escaped single character table. (generate with misc/make_tables.c) */ yyjson_align(2) static const u8 esc_single_char_table[512] = { ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', '\\', 'b', '\\', 't', '\\', 'n', ' ', ' ', '\\', 'f', '\\', 'r', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', '\\', '"', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', '\\', '/', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', '\\', '\\', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ' }; /** Returns the encode table with options. */ static_inline const char_enc_type *get_enc_table_with_flag( yyjson_write_flag flg) { if (has_flg(ESCAPE_UNICODE)) { if (has_flg(ESCAPE_SLASHES)) { return enc_table_esc_slash; } else { return enc_table_esc; } } else { if (has_flg(ESCAPE_SLASHES)) { return enc_table_cpy_slash; } else { return enc_table_cpy; } } } /** Write raw string. */ static_inline u8 *write_raw(u8 *cur, const u8 *raw, usize raw_len) { memcpy(cur, raw, raw_len); return cur + raw_len; } /** Write string no-escape. @param cur Buffer cursor. @param str A UTF-8 string, null-terminator is not required. @param str_len Length of string in bytes. @return The buffer cursor after string. */ static_inline u8 *write_str_noesc(u8 *cur, const u8 *str, usize str_len) { *cur++ = '"'; while (str_len >= 16) { byte_copy_16(cur, str); cur += 16; str += 16; str_len -= 16; } while (str_len >= 4) { byte_copy_4(cur, str); cur += 4; str += 4; str_len -= 4; } while (str_len) { *cur++ = *str++; str_len -= 1; } *cur++ = '"'; return cur; } /** Write UTF-8 string (requires len * 6 + 2 bytes buffer). @param cur Buffer cursor. @param esc Escape unicode. @param inv Allow invalid unicode. @param str A UTF-8 string, null-terminator is not required. @param str_len Length of string in bytes. @param enc_table Encode type table for character. @return The buffer cursor after string, or NULL on invalid unicode. */ static_inline u8 *write_str(u8 *cur, bool esc, bool inv, const u8 *str, usize str_len, const char_enc_type *enc_table) { /* The replacement character U+FFFD, used to indicate invalid character. */ const v32 rep = {{ 'F', 'F', 'F', 'D' }}; const v32 pre = {{ '\\', 'u', '0', '0' }}; const u8 *src = str; const u8 *end = str + str_len; *cur++ = '"'; copy_ascii: /* Copy continuous ASCII, loop unrolling, same as the following code: while (end > src) ( if (unlikely(enc_table[*src])) break; *cur++ = *src++; ); */ #define expr_jump(i) \ if (unlikely(enc_table[src[i]])) goto stop_char_##i; #define expr_stop(i) \ stop_char_##i: \ memcpy(cur, src, i); \ cur += i; src += i; goto copy_utf8; while (end - src >= 16) { repeat16_incr(expr_jump) byte_copy_16(cur, src); cur += 16; src += 16; } while (end - src >= 4) { repeat4_incr(expr_jump) byte_copy_4(cur, src); cur += 4; src += 4; } while (end > src) { expr_jump(0) *cur++ = *src++; } *cur++ = '"'; return cur; repeat16_incr(expr_stop) #undef expr_jump #undef expr_stop copy_utf8: if (unlikely(src + 4 > end)) { if (end == src) goto copy_end; if (end - src < enc_table[*src] / 2) goto err_one; } switch (enc_table[*src]) { case CHAR_ENC_CPY_1: { *cur++ = *src++; goto copy_ascii; } case CHAR_ENC_CPY_2: { #if YYJSON_DISABLE_UTF8_VALIDATION byte_copy_2(cur, src); #else u32 uni = 0; byte_copy_2(&uni, src); if (unlikely(!is_utf8_seq2(uni))) goto err_cpy; byte_copy_2(cur, &uni); #endif cur += 2; src += 2; goto copy_utf8; } case CHAR_ENC_CPY_3: { #if YYJSON_DISABLE_UTF8_VALIDATION if (likely(src + 4 <= end)) { byte_copy_4(cur, src); } else { byte_copy_2(cur, src); cur[2] = src[2]; } #else u32 uni, tmp; if (likely(src + 4 <= end)) { uni = byte_load_4(src); if (unlikely(!is_utf8_seq3(uni))) goto err_cpy; byte_copy_4(cur, src); } else { uni = byte_load_3(src); if (unlikely(!is_utf8_seq3(uni))) goto err_cpy; byte_copy_4(cur, &uni); } #endif cur += 3; src += 3; goto copy_utf8; } case CHAR_ENC_CPY_4: { #if YYJSON_DISABLE_UTF8_VALIDATION byte_copy_4(cur, src); #else u32 uni, tmp; uni = byte_load_4(src); if (unlikely(!is_utf8_seq4(uni))) goto err_cpy; byte_copy_4(cur, src); #endif cur += 4; src += 4; goto copy_utf8; } case CHAR_ENC_ESC_A: { byte_copy_2(cur, &esc_single_char_table[*src * 2]); cur += 2; src += 1; goto copy_utf8; } case CHAR_ENC_ESC_1: { byte_copy_4(cur + 0, &pre); byte_copy_2(cur + 4, &esc_hex_char_table[*src * 2]); cur += 6; src += 1; goto copy_utf8; } case CHAR_ENC_ESC_2: { u16 u; #if !YYJSON_DISABLE_UTF8_VALIDATION u32 v4 = 0; u16 v2 = byte_load_2(src); byte_copy_2(&v4, &v2); if (unlikely(!is_utf8_seq2(v4))) goto err_esc; #endif u = (u16)(((u16)(src[0] & 0x1F) << 6) | ((u16)(src[1] & 0x3F) << 0)); byte_copy_2(cur + 0, &pre); byte_copy_2(cur + 2, &esc_hex_char_table[(u >> 8) * 2]); byte_copy_2(cur + 4, &esc_hex_char_table[(u & 0xFF) * 2]); cur += 6; src += 2; goto copy_utf8; } case CHAR_ENC_ESC_3: { u16 u; u32 v, tmp; #if !YYJSON_DISABLE_UTF8_VALIDATION v = byte_load_3(src); if (unlikely(!is_utf8_seq3(v))) goto err_esc; #endif u = (u16)(((u16)(src[0] & 0x0F) << 12) | ((u16)(src[1] & 0x3F) << 6) | ((u16)(src[2] & 0x3F) << 0)); byte_copy_2(cur + 0, &pre); byte_copy_2(cur + 2, &esc_hex_char_table[(u >> 8) * 2]); byte_copy_2(cur + 4, &esc_hex_char_table[(u & 0xFF) * 2]); cur += 6; src += 3; goto copy_utf8; } case CHAR_ENC_ESC_4: { u32 hi, lo, u, v, tmp; #if !YYJSON_DISABLE_UTF8_VALIDATION v = byte_load_4(src); if (unlikely(!is_utf8_seq4(v))) goto err_esc; #endif u = ((u32)(src[0] & 0x07) << 18) | ((u32)(src[1] & 0x3F) << 12) | ((u32)(src[2] & 0x3F) << 6) | ((u32)(src[3] & 0x3F) << 0); u -= 0x10000; hi = (u >> 10) + 0xD800; lo = (u & 0x3FF) + 0xDC00; byte_copy_2(cur + 0, &pre); byte_copy_2(cur + 2, &esc_hex_char_table[(hi >> 8) * 2]); byte_copy_2(cur + 4, &esc_hex_char_table[(hi & 0xFF) * 2]); byte_copy_2(cur + 6, &pre); byte_copy_2(cur + 8, &esc_hex_char_table[(lo >> 8) * 2]); byte_copy_2(cur + 10, &esc_hex_char_table[(lo & 0xFF) * 2]); cur += 12; src += 4; goto copy_utf8; } case CHAR_ENC_ERR_1: { goto err_one; } default: break; /* unreachable */ } copy_end: *cur++ = '"'; return cur; err_one: if (esc) goto err_esc; else goto err_cpy; err_cpy: if (!inv) return NULL; *cur++ = *src++; goto copy_utf8; err_esc: if (!inv) return NULL; byte_copy_2(cur + 0, &pre); byte_copy_4(cur + 2, &rep); cur += 6; src += 1; goto copy_utf8; } /*============================================================================== * MARK: - JSON Writer Utilities (Private) *============================================================================*/ /** Write null (requires 8 bytes buffer). */ static_inline u8 *write_null(u8 *cur) { v64 v = {{ 'n', 'u', 'l', 'l', ',', '\n', 0, 0 }}; byte_copy_8(cur, &v); return cur + 4; } /** Write bool (requires 8 bytes buffer). */ static_inline u8 *write_bool(u8 *cur, bool val) { v64 v0 = {{ 'f', 'a', 'l', 's', 'e', ',', '\n', 0 }}; v64 v1 = {{ 't', 'r', 'u', 'e', ',', '\n', 0, 0 }}; if (val) { byte_copy_8(cur, &v1); } else { byte_copy_8(cur, &v0); } return cur + 5 - val; } /** Write indent (requires level x 4 bytes buffer). Param spaces should not larger than 4. */ static_inline u8 *write_indent(u8 *cur, usize level, usize spaces) { while (level-- > 0) { byte_copy_4(cur, " "); cur += spaces; } return cur; } /** Write data to file pointer. */ static bool write_dat_to_fp(FILE *fp, u8 *dat, usize len, yyjson_write_err *err) { if (fwrite(dat, len, 1, fp) != 1) { err->msg = "file writing failed"; err->code = YYJSON_WRITE_ERROR_FILE_WRITE; return false; } return true; } /** Write data to file. */ static bool write_dat_to_file(const char *path, u8 *dat, usize len, yyjson_write_err *err) { #define return_err(_code, _msg) do { \ err->msg = _msg; \ err->code = YYJSON_WRITE_ERROR_##_code; \ if (file) fclose(file); \ return false; \ } while (false) FILE *file = fopen_writeonly(path); if (file == NULL) { return_err(FILE_OPEN, MSG_FOPEN); } if (fwrite(dat, len, 1, file) != 1) { return_err(FILE_WRITE, MSG_FWRITE); } if (fclose(file) != 0) { file = NULL; return_err(FILE_WRITE, MSG_FCLOSE); } return true; #undef return_err } /*============================================================================== * MARK: - JSON Writer Implementation (Private) *============================================================================*/ typedef struct yyjson_write_ctx { usize tag; } yyjson_write_ctx; static_inline void yyjson_write_ctx_set(yyjson_write_ctx *ctx, usize size, bool is_obj) { ctx->tag = (size << 1) | (usize)is_obj; } static_inline void yyjson_write_ctx_get(yyjson_write_ctx *ctx, usize *size, bool *is_obj) { usize tag = ctx->tag; *size = tag >> 1; *is_obj = (bool)(tag & 1); } /** Write single JSON value. */ static_inline u8 *yyjson_write_single(yyjson_val *val, yyjson_write_flag flg, yyjson_alc alc, usize *dat_len, yyjson_write_err *err) { #define return_err(_code, _msg) do { \ if (hdr) alc.free(alc.ctx, (void *)hdr); \ *dat_len = 0; \ err->code = YYJSON_WRITE_ERROR_##_code; \ err->msg = _msg; \ return NULL; \ } while (false) #define incr_len(_len) do { \ hdr = (u8 *)alc.malloc(alc.ctx, _len); \ if (!hdr) goto fail_alloc; \ cur = hdr; \ } while (false) #define check_str_len(_len) do { \ if ((sizeof(usize) < 8) && (_len >= (USIZE_MAX - 16) / 6)) \ goto fail_alloc; \ } while (false) u8 *hdr = NULL, *cur; usize str_len; const u8 *str_ptr; const char_enc_type *enc_table = get_enc_table_with_flag(flg); bool cpy = (enc_table == enc_table_cpy); bool esc = has_flg(ESCAPE_UNICODE) != 0; bool inv = has_allow(INVALID_UNICODE) != 0; bool newline = has_flg(NEWLINE_AT_END) != 0; const usize end_len = 2; /* '\n' and '\0' */ switch (unsafe_yyjson_get_type(val)) { case YYJSON_TYPE_RAW: str_len = unsafe_yyjson_get_len(val); str_ptr = (const u8 *)unsafe_yyjson_get_str(val); check_str_len(str_len); incr_len(str_len + end_len); cur = write_raw(cur, str_ptr, str_len); break; case YYJSON_TYPE_STR: str_len = unsafe_yyjson_get_len(val); str_ptr = (const u8 *)unsafe_yyjson_get_str(val); check_str_len(str_len); incr_len(str_len * 6 + 2 + end_len); if (likely(cpy) && unsafe_yyjson_get_subtype(val)) { cur = write_str_noesc(cur, str_ptr, str_len); } else { cur = write_str(cur, esc, inv, str_ptr, str_len, enc_table); if (unlikely(!cur)) goto fail_str; } break; case YYJSON_TYPE_NUM: incr_len(FP_BUF_LEN + end_len); cur = write_num(cur, val, flg); if (unlikely(!cur)) goto fail_num; break; case YYJSON_TYPE_BOOL: incr_len(8); cur = write_bool(cur, unsafe_yyjson_get_bool(val)); break; case YYJSON_TYPE_NULL: incr_len(8); cur = write_null(cur); break; case YYJSON_TYPE_ARR: incr_len(2 + end_len); byte_copy_2(cur, "[]"); cur += 2; break; case YYJSON_TYPE_OBJ: incr_len(2 + end_len); byte_copy_2(cur, "{}"); cur += 2; break; default: goto fail_type; } if (newline) *cur++ = '\n'; *cur = '\0'; *dat_len = (usize)(cur - hdr); memset(err, 0, sizeof(yyjson_write_err)); return hdr; fail_alloc: return_err(MEMORY_ALLOCATION, MSG_MALLOC); fail_type: return_err(INVALID_VALUE_TYPE, MSG_ERR_TYPE); fail_num: return_err(NAN_OR_INF, MSG_NAN_INF); fail_str: return_err(INVALID_STRING, MSG_ERR_UTF8); #undef return_err #undef check_str_len #undef incr_len } /** Write JSON document minify. The root of this document should be a non-empty container. */ static_inline u8 *yyjson_write_minify(const yyjson_val *root, const yyjson_write_flag flg, const yyjson_alc alc, usize *dat_len, yyjson_write_err *err) { #define return_err(_code, _msg) do { \ *dat_len = 0; \ err->code = YYJSON_WRITE_ERROR_##_code; \ err->msg = _msg; \ if (hdr) alc.free(alc.ctx, hdr); \ return NULL; \ } while (false) #define incr_len(_len) do { \ ext_len = (usize)(_len); \ if (unlikely((u8 *)(cur + ext_len) >= (u8 *)ctx)) { \ usize ctx_pos = (usize)((u8 *)ctx - hdr); \ usize cur_pos = (usize)(cur - hdr); \ ctx_len = (usize)(end - (u8 *)ctx); \ alc_inc = yyjson_max(alc_len / 2, ext_len); \ alc_inc = size_align_up(alc_inc, sizeof(yyjson_write_ctx)); \ if ((sizeof(usize) < 8) && size_add_is_overflow(alc_len, alc_inc)) \ goto fail_alloc; \ alc_len += alc_inc; \ tmp = (u8 *)alc.realloc(alc.ctx, hdr, alc_len - alc_inc, alc_len); \ if (unlikely(!tmp)) goto fail_alloc; \ ctx_tmp = (yyjson_write_ctx *)(void *)(tmp + (alc_len - ctx_len)); \ memmove((void *)ctx_tmp, (void *)(tmp + ctx_pos), ctx_len); \ ctx = ctx_tmp; \ cur = tmp + cur_pos; \ end = tmp + alc_len; \ hdr = tmp; \ } \ } while (false) #define check_str_len(_len) do { \ if ((sizeof(usize) < 8) && (_len >= (USIZE_MAX - 16) / 6)) \ goto fail_alloc; \ } while (false) yyjson_val *val; yyjson_type val_type; usize ctn_len, ctn_len_tmp; bool ctn_obj, ctn_obj_tmp, is_key; u8 *hdr, *cur, *end, *tmp; yyjson_write_ctx *ctx, *ctx_tmp; usize alc_len, alc_inc, ctx_len, ext_len, str_len; const u8 *str_ptr; const char_enc_type *enc_table = get_enc_table_with_flag(flg); bool cpy = (enc_table == enc_table_cpy); bool esc = has_flg(ESCAPE_UNICODE) != 0; bool inv = has_allow(INVALID_UNICODE) != 0; bool newline = has_flg(NEWLINE_AT_END) != 0; alc_len = root->uni.ofs / sizeof(yyjson_val); alc_len = alc_len * YYJSON_WRITER_ESTIMATED_MINIFY_RATIO + 64; alc_len = size_align_up(alc_len, sizeof(yyjson_write_ctx)); hdr = (u8 *)alc.malloc(alc.ctx, alc_len); if (!hdr) goto fail_alloc; cur = hdr; end = hdr + alc_len; ctx = (yyjson_write_ctx *)(void *)end; doc_begin: val = constcast(yyjson_val *)root; val_type = unsafe_yyjson_get_type(val); ctn_obj = (val_type == YYJSON_TYPE_OBJ); ctn_len = unsafe_yyjson_get_len(val) << (u8)ctn_obj; *cur++ = (u8)('[' | ((u8)ctn_obj << 5)); val++; val_begin: val_type = unsafe_yyjson_get_type(val); if (val_type == YYJSON_TYPE_STR) { is_key = ((u8)ctn_obj & (u8)~ctn_len); str_len = unsafe_yyjson_get_len(val); str_ptr = (const u8 *)unsafe_yyjson_get_str(val); check_str_len(str_len); incr_len(str_len * 6 + 16); if (likely(cpy) && unsafe_yyjson_get_subtype(val)) { cur = write_str_noesc(cur, str_ptr, str_len); } else { cur = write_str(cur, esc, inv, str_ptr, str_len, enc_table); if (unlikely(!cur)) goto fail_str; } *cur++ = is_key ? ':' : ','; goto val_end; } if (val_type == YYJSON_TYPE_NUM) { incr_len(FP_BUF_LEN); cur = write_num(cur, val, flg); if (unlikely(!cur)) goto fail_num; *cur++ = ','; goto val_end; } if ((val_type & (YYJSON_TYPE_ARR & YYJSON_TYPE_OBJ)) == (YYJSON_TYPE_ARR & YYJSON_TYPE_OBJ)) { ctn_len_tmp = unsafe_yyjson_get_len(val); ctn_obj_tmp = (val_type == YYJSON_TYPE_OBJ); incr_len(16); if (unlikely(ctn_len_tmp == 0)) { /* write empty container */ *cur++ = (u8)('[' | ((u8)ctn_obj_tmp << 5)); *cur++ = (u8)(']' | ((u8)ctn_obj_tmp << 5)); *cur++ = ','; goto val_end; } else { /* push context, setup new container */ yyjson_write_ctx_set(--ctx, ctn_len, ctn_obj); ctn_len = ctn_len_tmp << (u8)ctn_obj_tmp; ctn_obj = ctn_obj_tmp; *cur++ = (u8)('[' | ((u8)ctn_obj << 5)); val++; goto val_begin; } } if (val_type == YYJSON_TYPE_BOOL) { incr_len(16); cur = write_bool(cur, unsafe_yyjson_get_bool(val)); cur++; goto val_end; } if (val_type == YYJSON_TYPE_NULL) { incr_len(16); cur = write_null(cur); cur++; goto val_end; } if (val_type == YYJSON_TYPE_RAW) { str_len = unsafe_yyjson_get_len(val); str_ptr = (const u8 *)unsafe_yyjson_get_str(val); check_str_len(str_len); incr_len(str_len + 2); cur = write_raw(cur, str_ptr, str_len); *cur++ = ','; goto val_end; } goto fail_type; val_end: val++; ctn_len--; if (unlikely(ctn_len == 0)) goto ctn_end; goto val_begin; ctn_end: cur--; *cur++ = (u8)(']' | ((u8)ctn_obj << 5)); *cur++ = ','; if (unlikely((u8 *)ctx >= end)) goto doc_end; yyjson_write_ctx_get(ctx++, &ctn_len, &ctn_obj); ctn_len--; if (likely(ctn_len > 0)) { goto val_begin; } else { goto ctn_end; } doc_end: if (newline) { incr_len(2); *(cur - 1) = '\n'; cur++; } *--cur = '\0'; *dat_len = (usize)(cur - hdr); memset(err, 0, sizeof(yyjson_write_err)); return hdr; fail_alloc: return_err(MEMORY_ALLOCATION, MSG_MALLOC); fail_type: return_err(INVALID_VALUE_TYPE, MSG_ERR_TYPE); fail_num: return_err(NAN_OR_INF, MSG_NAN_INF); fail_str: return_err(INVALID_STRING, MSG_ERR_UTF8); #undef return_err #undef incr_len #undef check_str_len } /** Write JSON document pretty. The root of this document should be a non-empty container. */ static_inline u8 *yyjson_write_pretty(const yyjson_val *root, const yyjson_write_flag flg, const yyjson_alc alc, usize *dat_len, yyjson_write_err *err) { #define return_err(_code, _msg) do { \ *dat_len = 0; \ err->code = YYJSON_WRITE_ERROR_##_code; \ err->msg = _msg; \ if (hdr) alc.free(alc.ctx, hdr); \ return NULL; \ } while (false) #define incr_len(_len) do { \ ext_len = (usize)(_len); \ if (unlikely((u8 *)(cur + ext_len) >= (u8 *)ctx)) { \ usize ctx_pos = (usize)((u8 *)ctx - hdr); \ usize cur_pos = (usize)(cur - hdr); \ ctx_len = (usize)(end - (u8 *)ctx); \ alc_inc = yyjson_max(alc_len / 2, ext_len); \ alc_inc = size_align_up(alc_inc, sizeof(yyjson_write_ctx)); \ if ((sizeof(usize) < 8) && size_add_is_overflow(alc_len, alc_inc)) \ goto fail_alloc; \ alc_len += alc_inc; \ tmp = (u8 *)alc.realloc(alc.ctx, hdr, alc_len - alc_inc, alc_len); \ if (unlikely(!tmp)) goto fail_alloc; \ ctx_tmp = (yyjson_write_ctx *)(void *)(tmp + (alc_len - ctx_len)); \ memmove((void *)ctx_tmp, (void *)(tmp + ctx_pos), ctx_len); \ ctx = ctx_tmp; \ cur = tmp + cur_pos; \ end = tmp + alc_len; \ hdr = tmp; \ } \ } while (false) #define check_str_len(_len) do { \ if ((sizeof(usize) < 8) && (_len >= (USIZE_MAX - 16) / 6)) \ goto fail_alloc; \ } while (false) yyjson_val *val; yyjson_type val_type; usize ctn_len, ctn_len_tmp; bool ctn_obj, ctn_obj_tmp, is_key, no_indent; u8 *hdr, *cur, *end, *tmp; yyjson_write_ctx *ctx, *ctx_tmp; usize alc_len, alc_inc, ctx_len, ext_len, str_len, level; const u8 *str_ptr; const char_enc_type *enc_table = get_enc_table_with_flag(flg); bool cpy = (enc_table == enc_table_cpy); bool esc = has_flg(ESCAPE_UNICODE) != 0; bool inv = has_allow(INVALID_UNICODE) != 0; usize spaces = has_flg(PRETTY_TWO_SPACES) ? 2 : 4; bool newline = has_flg(NEWLINE_AT_END) != 0; alc_len = root->uni.ofs / sizeof(yyjson_val); alc_len = alc_len * YYJSON_WRITER_ESTIMATED_PRETTY_RATIO + 64; alc_len = size_align_up(alc_len, sizeof(yyjson_write_ctx)); hdr = (u8 *)alc.malloc(alc.ctx, alc_len); if (!hdr) goto fail_alloc; cur = hdr; end = hdr + alc_len; ctx = (yyjson_write_ctx *)(void *)end; doc_begin: val = constcast(yyjson_val *)root; val_type = unsafe_yyjson_get_type(val); ctn_obj = (val_type == YYJSON_TYPE_OBJ); ctn_len = unsafe_yyjson_get_len(val) << (u8)ctn_obj; *cur++ = (u8)('[' | ((u8)ctn_obj << 5)); *cur++ = '\n'; val++; level = 1; val_begin: val_type = unsafe_yyjson_get_type(val); if (val_type == YYJSON_TYPE_STR) { is_key = (bool)((u8)ctn_obj & (u8)~ctn_len); no_indent = (bool)((u8)ctn_obj & (u8)ctn_len); str_len = unsafe_yyjson_get_len(val); str_ptr = (const u8 *)unsafe_yyjson_get_str(val); check_str_len(str_len); incr_len(str_len * 6 + 16 + (no_indent ? 0 : level * 4)); cur = write_indent(cur, no_indent ? 0 : level, spaces); if (likely(cpy) && unsafe_yyjson_get_subtype(val)) { cur = write_str_noesc(cur, str_ptr, str_len); } else { cur = write_str(cur, esc, inv, str_ptr, str_len, enc_table); if (unlikely(!cur)) goto fail_str; } *cur++ = is_key ? ':' : ','; *cur++ = is_key ? ' ' : '\n'; goto val_end; } if (val_type == YYJSON_TYPE_NUM) { no_indent = (bool)((u8)ctn_obj & (u8)ctn_len); incr_len(FP_BUF_LEN + (no_indent ? 0 : level * 4)); cur = write_indent(cur, no_indent ? 0 : level, spaces); cur = write_num(cur, val, flg); if (unlikely(!cur)) goto fail_num; *cur++ = ','; *cur++ = '\n'; goto val_end; } if ((val_type & (YYJSON_TYPE_ARR & YYJSON_TYPE_OBJ)) == (YYJSON_TYPE_ARR & YYJSON_TYPE_OBJ)) { no_indent = (bool)((u8)ctn_obj & (u8)ctn_len); ctn_len_tmp = unsafe_yyjson_get_len(val); ctn_obj_tmp = (val_type == YYJSON_TYPE_OBJ); if (unlikely(ctn_len_tmp == 0)) { /* write empty container */ incr_len(16 + (no_indent ? 0 : level * 4)); cur = write_indent(cur, no_indent ? 0 : level, spaces); *cur++ = (u8)('[' | ((u8)ctn_obj_tmp << 5)); *cur++ = (u8)(']' | ((u8)ctn_obj_tmp << 5)); *cur++ = ','; *cur++ = '\n'; goto val_end; } else { /* push context, setup new container */ incr_len(32 + (no_indent ? 0 : level * 4)); yyjson_write_ctx_set(--ctx, ctn_len, ctn_obj); ctn_len = ctn_len_tmp << (u8)ctn_obj_tmp; ctn_obj = ctn_obj_tmp; cur = write_indent(cur, no_indent ? 0 : level, spaces); level++; *cur++ = (u8)('[' | ((u8)ctn_obj << 5)); *cur++ = '\n'; val++; goto val_begin; } } if (val_type == YYJSON_TYPE_BOOL) { no_indent = (bool)((u8)ctn_obj & (u8)ctn_len); incr_len(16 + (no_indent ? 0 : level * 4)); cur = write_indent(cur, no_indent ? 0 : level, spaces); cur = write_bool(cur, unsafe_yyjson_get_bool(val)); cur += 2; goto val_end; } if (val_type == YYJSON_TYPE_NULL) { no_indent = (bool)((u8)ctn_obj & (u8)ctn_len); incr_len(16 + (no_indent ? 0 : level * 4)); cur = write_indent(cur, no_indent ? 0 : level, spaces); cur = write_null(cur); cur += 2; goto val_end; } if (val_type == YYJSON_TYPE_RAW) { no_indent = (bool)((u8)ctn_obj & (u8)ctn_len); str_len = unsafe_yyjson_get_len(val); str_ptr = (const u8 *)unsafe_yyjson_get_str(val); check_str_len(str_len); incr_len(str_len + 3 + (no_indent ? 0 : level * 4)); cur = write_indent(cur, no_indent ? 0 : level, spaces); cur = write_raw(cur, str_ptr, str_len); *cur++ = ','; *cur++ = '\n'; goto val_end; } goto fail_type; val_end: val++; ctn_len--; if (unlikely(ctn_len == 0)) goto ctn_end; goto val_begin; ctn_end: cur -= 2; *cur++ = '\n'; incr_len(level * 4); cur = write_indent(cur, --level, spaces); *cur++ = (u8)(']' | ((u8)ctn_obj << 5)); if (unlikely((u8 *)ctx >= end)) goto doc_end; yyjson_write_ctx_get(ctx++, &ctn_len, &ctn_obj); ctn_len--; *cur++ = ','; *cur++ = '\n'; if (likely(ctn_len > 0)) { goto val_begin; } else { goto ctn_end; } doc_end: if (newline) { incr_len(2); *cur++ = '\n'; } *cur = '\0'; *dat_len = (usize)(cur - hdr); memset(err, 0, sizeof(yyjson_write_err)); return hdr; fail_alloc: return_err(MEMORY_ALLOCATION, MSG_MALLOC); fail_type: return_err(INVALID_VALUE_TYPE, MSG_ERR_TYPE); fail_num: return_err(NAN_OR_INF, MSG_NAN_INF); fail_str: return_err(INVALID_STRING, MSG_ERR_UTF8); #undef return_err #undef incr_len #undef check_str_len } /*============================================================================== * MARK: - JSON Writer (Public) *============================================================================*/ char *yyjson_val_write_opts(const yyjson_val *val, yyjson_write_flag flg, const yyjson_alc *alc_ptr, usize *dat_len, yyjson_write_err *err) { yyjson_write_err tmp_err; usize tmp_dat_len; yyjson_alc alc = alc_ptr ? *alc_ptr : YYJSON_DEFAULT_ALC; yyjson_val *root = constcast(yyjson_val *)val; if (!err) err = &tmp_err; if (!dat_len) dat_len = &tmp_dat_len; if (unlikely(!root)) { *dat_len = 0; err->msg = "input JSON is NULL"; err->code = YYJSON_READ_ERROR_INVALID_PARAMETER; return NULL; } if (!unsafe_yyjson_is_ctn(root) || unsafe_yyjson_get_len(root) == 0) { return (char *)yyjson_write_single(root, flg, alc, dat_len, err); } else if (flg & (YYJSON_WRITE_PRETTY | YYJSON_WRITE_PRETTY_TWO_SPACES)) { return (char *)yyjson_write_pretty(root, flg, alc, dat_len, err); } else { return (char *)yyjson_write_minify(root, flg, alc, dat_len, err); } } char *yyjson_write_opts(const yyjson_doc *doc, yyjson_write_flag flg, const yyjson_alc *alc_ptr, usize *dat_len, yyjson_write_err *err) { yyjson_val *root = doc ? doc->root : NULL; return yyjson_val_write_opts(root, flg, alc_ptr, dat_len, err); } bool yyjson_val_write_file(const char *path, const yyjson_val *val, yyjson_write_flag flg, const yyjson_alc *alc_ptr, yyjson_write_err *err) { yyjson_write_err tmp_err; yyjson_alc alc = alc_ptr ? *alc_ptr : YYJSON_DEFAULT_ALC; u8 *dat; usize dat_len = 0; yyjson_val *root = constcast(yyjson_val *)val; bool suc; if (!err) err = &tmp_err; if (unlikely(!path || !*path)) { err->msg = "input path is invalid"; err->code = YYJSON_READ_ERROR_INVALID_PARAMETER; return false; } dat = (u8 *)yyjson_val_write_opts(root, flg, &alc, &dat_len, err); if (unlikely(!dat)) return false; suc = write_dat_to_file(path, dat, dat_len, err); alc.free(alc.ctx, dat); return suc; } bool yyjson_val_write_fp(FILE *fp, const yyjson_val *val, yyjson_write_flag flg, const yyjson_alc *alc_ptr, yyjson_write_err *err) { yyjson_write_err tmp_err; yyjson_alc alc = alc_ptr ? *alc_ptr : YYJSON_DEFAULT_ALC; u8 *dat; usize dat_len = 0; yyjson_val *root = constcast(yyjson_val *)val; bool suc; if (!err) err = &tmp_err; if (unlikely(!fp)) { err->msg = "input fp is invalid"; err->code = YYJSON_READ_ERROR_INVALID_PARAMETER; return false; } dat = (u8 *)yyjson_val_write_opts(root, flg, &alc, &dat_len, err); if (unlikely(!dat)) return false; suc = write_dat_to_fp(fp, dat, dat_len, err); alc.free(alc.ctx, dat); return suc; } bool yyjson_write_file(const char *path, const yyjson_doc *doc, yyjson_write_flag flg, const yyjson_alc *alc_ptr, yyjson_write_err *err) { yyjson_val *root = doc ? doc->root : NULL; return yyjson_val_write_file(path, root, flg, alc_ptr, err); } bool yyjson_write_fp(FILE *fp, const yyjson_doc *doc, yyjson_write_flag flg, const yyjson_alc *alc_ptr, yyjson_write_err *err) { yyjson_val *root = doc ? doc->root : NULL; return yyjson_val_write_fp(fp, root, flg, alc_ptr, err); } /*============================================================================== * MARK: - Mutable JSON Writer Implementation (Private) *============================================================================*/ typedef struct yyjson_mut_write_ctx { usize tag; yyjson_mut_val *ctn; } yyjson_mut_write_ctx; static_inline void yyjson_mut_write_ctx_set(yyjson_mut_write_ctx *ctx, yyjson_mut_val *ctn, usize size, bool is_obj) { ctx->tag = (size << 1) | (usize)is_obj; ctx->ctn = ctn; } static_inline void yyjson_mut_write_ctx_get(yyjson_mut_write_ctx *ctx, yyjson_mut_val **ctn, usize *size, bool *is_obj) { usize tag = ctx->tag; *size = tag >> 1; *is_obj = (bool)(tag & 1); *ctn = ctx->ctn; } /** Get the estimated number of values for the mutable JSON document. */ static_inline usize yyjson_mut_doc_estimated_val_num( const yyjson_mut_doc *doc) { usize sum = 0; yyjson_val_chunk *chunk = doc->val_pool.chunks; while (chunk) { sum += chunk->chunk_size / sizeof(yyjson_mut_val) - 1; if (chunk == doc->val_pool.chunks) { sum -= (usize)(doc->val_pool.end - doc->val_pool.cur); } chunk = chunk->next; } return sum; } /** Write single JSON value. */ static_inline u8 *yyjson_mut_write_single(yyjson_mut_val *val, yyjson_write_flag flg, yyjson_alc alc, usize *dat_len, yyjson_write_err *err) { return yyjson_write_single((yyjson_val *)val, flg, alc, dat_len, err); } /** Write JSON document minify. The root of this document should be a non-empty container. */ static_inline u8 *yyjson_mut_write_minify(const yyjson_mut_val *root, usize estimated_val_num, yyjson_write_flag flg, yyjson_alc alc, usize *dat_len, yyjson_write_err *err) { #define return_err(_code, _msg) do { \ *dat_len = 0; \ err->code = YYJSON_WRITE_ERROR_##_code; \ err->msg = _msg; \ if (hdr) alc.free(alc.ctx, hdr); \ return NULL; \ } while (false) #define incr_len(_len) do { \ ext_len = (usize)(_len); \ if (unlikely((u8 *)(cur + ext_len) >= (u8 *)ctx)) { \ usize ctx_pos = (usize)((u8 *)ctx - hdr); \ usize cur_pos = (usize)(cur - hdr); \ ctx_len = (usize)(end - (u8 *)ctx); \ alc_inc = yyjson_max(alc_len / 2, ext_len); \ alc_inc = size_align_up(alc_inc, sizeof(yyjson_mut_write_ctx)); \ if ((sizeof(usize) < 8) && size_add_is_overflow(alc_len, alc_inc)) \ goto fail_alloc; \ alc_len += alc_inc; \ tmp = (u8 *)alc.realloc(alc.ctx, hdr, alc_len - alc_inc, alc_len); \ if (unlikely(!tmp)) goto fail_alloc; \ ctx_tmp = (yyjson_mut_write_ctx *)(void *)(tmp + (alc_len - ctx_len)); \ memmove((void *)ctx_tmp, (void *)(tmp + ctx_pos), ctx_len); \ ctx = ctx_tmp; \ cur = tmp + cur_pos; \ end = tmp + alc_len; \ hdr = tmp; \ } \ } while (false) #define check_str_len(_len) do { \ if ((sizeof(usize) < 8) && (_len >= (USIZE_MAX - 16) / 6)) \ goto fail_alloc; \ } while (false) yyjson_mut_val *val, *ctn; yyjson_type val_type; usize ctn_len, ctn_len_tmp; bool ctn_obj, ctn_obj_tmp, is_key; u8 *hdr, *cur, *end, *tmp; yyjson_mut_write_ctx *ctx, *ctx_tmp; usize alc_len, alc_inc, ctx_len, ext_len, str_len; const u8 *str_ptr; const char_enc_type *enc_table = get_enc_table_with_flag(flg); bool cpy = (enc_table == enc_table_cpy); bool esc = has_flg(ESCAPE_UNICODE) != 0; bool inv = has_allow(INVALID_UNICODE) != 0; bool newline = has_flg(NEWLINE_AT_END) != 0; alc_len = estimated_val_num * YYJSON_WRITER_ESTIMATED_MINIFY_RATIO + 64; alc_len = size_align_up(alc_len, sizeof(yyjson_mut_write_ctx)); hdr = (u8 *)alc.malloc(alc.ctx, alc_len); if (!hdr) goto fail_alloc; cur = hdr; end = hdr + alc_len; ctx = (yyjson_mut_write_ctx *)(void *)end; doc_begin: val = constcast(yyjson_mut_val *)root; val_type = unsafe_yyjson_get_type(val); ctn_obj = (val_type == YYJSON_TYPE_OBJ); ctn_len = unsafe_yyjson_get_len(val) << (u8)ctn_obj; *cur++ = (u8)('[' | ((u8)ctn_obj << 5)); ctn = val; val = (yyjson_mut_val *)val->uni.ptr; /* tail */ val = ctn_obj ? val->next->next : val->next; val_begin: val_type = unsafe_yyjson_get_type(val); if (val_type == YYJSON_TYPE_STR) { is_key = ((u8)ctn_obj & (u8)~ctn_len); str_len = unsafe_yyjson_get_len(val); str_ptr = (const u8 *)unsafe_yyjson_get_str(val); check_str_len(str_len); incr_len(str_len * 6 + 16); if (likely(cpy) && unsafe_yyjson_get_subtype(val)) { cur = write_str_noesc(cur, str_ptr, str_len); } else { cur = write_str(cur, esc, inv, str_ptr, str_len, enc_table); if (unlikely(!cur)) goto fail_str; } *cur++ = is_key ? ':' : ','; goto val_end; } if (val_type == YYJSON_TYPE_NUM) { incr_len(FP_BUF_LEN); cur = write_num(cur, (yyjson_val *)val, flg); if (unlikely(!cur)) goto fail_num; *cur++ = ','; goto val_end; } if ((val_type & (YYJSON_TYPE_ARR & YYJSON_TYPE_OBJ)) == (YYJSON_TYPE_ARR & YYJSON_TYPE_OBJ)) { ctn_len_tmp = unsafe_yyjson_get_len(val); ctn_obj_tmp = (val_type == YYJSON_TYPE_OBJ); incr_len(16); if (unlikely(ctn_len_tmp == 0)) { /* write empty container */ *cur++ = (u8)('[' | ((u8)ctn_obj_tmp << 5)); *cur++ = (u8)(']' | ((u8)ctn_obj_tmp << 5)); *cur++ = ','; goto val_end; } else { /* push context, setup new container */ yyjson_mut_write_ctx_set(--ctx, ctn, ctn_len, ctn_obj); ctn_len = ctn_len_tmp << (u8)ctn_obj_tmp; ctn_obj = ctn_obj_tmp; *cur++ = (u8)('[' | ((u8)ctn_obj << 5)); ctn = val; val = (yyjson_mut_val *)ctn->uni.ptr; /* tail */ val = ctn_obj ? val->next->next : val->next; goto val_begin; } } if (val_type == YYJSON_TYPE_BOOL) { incr_len(16); cur = write_bool(cur, unsafe_yyjson_get_bool(val)); cur++; goto val_end; } if (val_type == YYJSON_TYPE_NULL) { incr_len(16); cur = write_null(cur); cur++; goto val_end; } if (val_type == YYJSON_TYPE_RAW) { str_len = unsafe_yyjson_get_len(val); str_ptr = (const u8 *)unsafe_yyjson_get_str(val); check_str_len(str_len); incr_len(str_len + 2); cur = write_raw(cur, str_ptr, str_len); *cur++ = ','; goto val_end; } goto fail_type; val_end: ctn_len--; if (unlikely(ctn_len == 0)) goto ctn_end; val = val->next; goto val_begin; ctn_end: cur--; *cur++ = (u8)(']' | ((u8)ctn_obj << 5)); *cur++ = ','; if (unlikely((u8 *)ctx >= end)) goto doc_end; val = ctn->next; yyjson_mut_write_ctx_get(ctx++, &ctn, &ctn_len, &ctn_obj); ctn_len--; if (likely(ctn_len > 0)) { goto val_begin; } else { goto ctn_end; } doc_end: if (newline) { incr_len(2); *(cur - 1) = '\n'; cur++; } *--cur = '\0'; *dat_len = (usize)(cur - hdr); err->code = YYJSON_WRITE_SUCCESS; err->msg = NULL; return hdr; fail_alloc: return_err(MEMORY_ALLOCATION, MSG_MALLOC); fail_type: return_err(INVALID_VALUE_TYPE, MSG_ERR_TYPE); fail_num: return_err(NAN_OR_INF, MSG_NAN_INF); fail_str: return_err(INVALID_STRING, MSG_ERR_UTF8); #undef return_err #undef incr_len #undef check_str_len } /** Write JSON document pretty. The root of this document should be a non-empty container. */ static_inline u8 *yyjson_mut_write_pretty(const yyjson_mut_val *root, usize estimated_val_num, yyjson_write_flag flg, yyjson_alc alc, usize *dat_len, yyjson_write_err *err) { #define return_err(_code, _msg) do { \ *dat_len = 0; \ err->code = YYJSON_WRITE_ERROR_##_code; \ err->msg = _msg; \ if (hdr) alc.free(alc.ctx, hdr); \ return NULL; \ } while (false) #define incr_len(_len) do { \ ext_len = (usize)(_len); \ if (unlikely((u8 *)(cur + ext_len) >= (u8 *)ctx)) { \ usize ctx_pos = (usize)((u8 *)ctx - hdr); \ usize cur_pos = (usize)(cur - hdr); \ ctx_len = (usize)(end - (u8 *)ctx); \ alc_inc = yyjson_max(alc_len / 2, ext_len); \ alc_inc = size_align_up(alc_inc, sizeof(yyjson_mut_write_ctx)); \ if ((sizeof(usize) < 8) && size_add_is_overflow(alc_len, alc_inc)) \ goto fail_alloc; \ alc_len += alc_inc; \ tmp = (u8 *)alc.realloc(alc.ctx, hdr, alc_len - alc_inc, alc_len); \ if (unlikely(!tmp)) goto fail_alloc; \ ctx_tmp = (yyjson_mut_write_ctx *)(void *)(tmp + (alc_len - ctx_len)); \ memmove((void *)ctx_tmp, (void *)(tmp + ctx_pos), ctx_len); \ ctx = ctx_tmp; \ cur = tmp + cur_pos; \ end = tmp + alc_len; \ hdr = tmp; \ } \ } while (false) #define check_str_len(_len) do { \ if ((sizeof(usize) < 8) && (_len >= (USIZE_MAX - 16) / 6)) \ goto fail_alloc; \ } while (false) yyjson_mut_val *val, *ctn; yyjson_type val_type; usize ctn_len, ctn_len_tmp; bool ctn_obj, ctn_obj_tmp, is_key, no_indent; u8 *hdr, *cur, *end, *tmp; yyjson_mut_write_ctx *ctx, *ctx_tmp; usize alc_len, alc_inc, ctx_len, ext_len, str_len, level; const u8 *str_ptr; const char_enc_type *enc_table = get_enc_table_with_flag(flg); bool cpy = (enc_table == enc_table_cpy); bool esc = has_flg(ESCAPE_UNICODE) != 0; bool inv = has_allow(INVALID_UNICODE) != 0; usize spaces = has_flg(PRETTY_TWO_SPACES) ? 2 : 4; bool newline = has_flg(NEWLINE_AT_END) != 0; alc_len = estimated_val_num * YYJSON_WRITER_ESTIMATED_PRETTY_RATIO + 64; alc_len = size_align_up(alc_len, sizeof(yyjson_mut_write_ctx)); hdr = (u8 *)alc.malloc(alc.ctx, alc_len); if (!hdr) goto fail_alloc; cur = hdr; end = hdr + alc_len; ctx = (yyjson_mut_write_ctx *)(void *)end; doc_begin: val = constcast(yyjson_mut_val *)root; val_type = unsafe_yyjson_get_type(val); ctn_obj = (val_type == YYJSON_TYPE_OBJ); ctn_len = unsafe_yyjson_get_len(val) << (u8)ctn_obj; *cur++ = (u8)('[' | ((u8)ctn_obj << 5)); *cur++ = '\n'; ctn = val; val = (yyjson_mut_val *)val->uni.ptr; /* tail */ val = ctn_obj ? val->next->next : val->next; level = 1; val_begin: val_type = unsafe_yyjson_get_type(val); if (val_type == YYJSON_TYPE_STR) { is_key = (bool)((u8)ctn_obj & (u8)~ctn_len); no_indent = (bool)((u8)ctn_obj & (u8)ctn_len); str_len = unsafe_yyjson_get_len(val); str_ptr = (const u8 *)unsafe_yyjson_get_str(val); check_str_len(str_len); incr_len(str_len * 6 + 16 + (no_indent ? 0 : level * 4)); cur = write_indent(cur, no_indent ? 0 : level, spaces); if (likely(cpy) && unsafe_yyjson_get_subtype(val)) { cur = write_str_noesc(cur, str_ptr, str_len); } else { cur = write_str(cur, esc, inv, str_ptr, str_len, enc_table); if (unlikely(!cur)) goto fail_str; } *cur++ = is_key ? ':' : ','; *cur++ = is_key ? ' ' : '\n'; goto val_end; } if (val_type == YYJSON_TYPE_NUM) { no_indent = (bool)((u8)ctn_obj & (u8)ctn_len); incr_len(FP_BUF_LEN + (no_indent ? 0 : level * 4)); cur = write_indent(cur, no_indent ? 0 : level, spaces); cur = write_num(cur, (yyjson_val *)val, flg); if (unlikely(!cur)) goto fail_num; *cur++ = ','; *cur++ = '\n'; goto val_end; } if ((val_type & (YYJSON_TYPE_ARR & YYJSON_TYPE_OBJ)) == (YYJSON_TYPE_ARR & YYJSON_TYPE_OBJ)) { no_indent = (bool)((u8)ctn_obj & (u8)ctn_len); ctn_len_tmp = unsafe_yyjson_get_len(val); ctn_obj_tmp = (val_type == YYJSON_TYPE_OBJ); if (unlikely(ctn_len_tmp == 0)) { /* write empty container */ incr_len(16 + (no_indent ? 0 : level * 4)); cur = write_indent(cur, no_indent ? 0 : level, spaces); *cur++ = (u8)('[' | ((u8)ctn_obj_tmp << 5)); *cur++ = (u8)(']' | ((u8)ctn_obj_tmp << 5)); *cur++ = ','; *cur++ = '\n'; goto val_end; } else { /* push context, setup new container */ incr_len(32 + (no_indent ? 0 : level * 4)); yyjson_mut_write_ctx_set(--ctx, ctn, ctn_len, ctn_obj); ctn_len = ctn_len_tmp << (u8)ctn_obj_tmp; ctn_obj = ctn_obj_tmp; cur = write_indent(cur, no_indent ? 0 : level, spaces); level++; *cur++ = (u8)('[' | ((u8)ctn_obj << 5)); *cur++ = '\n'; ctn = val; val = (yyjson_mut_val *)ctn->uni.ptr; /* tail */ val = ctn_obj ? val->next->next : val->next; goto val_begin; } } if (val_type == YYJSON_TYPE_BOOL) { no_indent = (bool)((u8)ctn_obj & (u8)ctn_len); incr_len(16 + (no_indent ? 0 : level * 4)); cur = write_indent(cur, no_indent ? 0 : level, spaces); cur = write_bool(cur, unsafe_yyjson_get_bool(val)); cur += 2; goto val_end; } if (val_type == YYJSON_TYPE_NULL) { no_indent = (bool)((u8)ctn_obj & (u8)ctn_len); incr_len(16 + (no_indent ? 0 : level * 4)); cur = write_indent(cur, no_indent ? 0 : level, spaces); cur = write_null(cur); cur += 2; goto val_end; } if (val_type == YYJSON_TYPE_RAW) { no_indent = (bool)((u8)ctn_obj & (u8)ctn_len); str_len = unsafe_yyjson_get_len(val); str_ptr = (const u8 *)unsafe_yyjson_get_str(val); check_str_len(str_len); incr_len(str_len + 3 + (no_indent ? 0 : level * 4)); cur = write_indent(cur, no_indent ? 0 : level, spaces); cur = write_raw(cur, str_ptr, str_len); *cur++ = ','; *cur++ = '\n'; goto val_end; } goto fail_type; val_end: ctn_len--; if (unlikely(ctn_len == 0)) goto ctn_end; val = val->next; goto val_begin; ctn_end: cur -= 2; *cur++ = '\n'; incr_len(level * 4); cur = write_indent(cur, --level, spaces); *cur++ = (u8)(']' | ((u8)ctn_obj << 5)); if (unlikely((u8 *)ctx >= end)) goto doc_end; val = ctn->next; yyjson_mut_write_ctx_get(ctx++, &ctn, &ctn_len, &ctn_obj); ctn_len--; *cur++ = ','; *cur++ = '\n'; if (likely(ctn_len > 0)) { goto val_begin; } else { goto ctn_end; } doc_end: if (newline) { incr_len(2); *cur++ = '\n'; } *cur = '\0'; *dat_len = (usize)(cur - hdr); err->code = YYJSON_WRITE_SUCCESS; err->msg = NULL; return hdr; fail_alloc: return_err(MEMORY_ALLOCATION, MSG_MALLOC); fail_type: return_err(INVALID_VALUE_TYPE, MSG_ERR_TYPE); fail_num: return_err(NAN_OR_INF, MSG_NAN_INF); fail_str: return_err(INVALID_STRING, MSG_ERR_UTF8); #undef return_err #undef incr_len #undef check_str_len } static char *yyjson_mut_write_opts_impl(const yyjson_mut_val *val, usize estimated_val_num, yyjson_write_flag flg, const yyjson_alc *alc_ptr, usize *dat_len, yyjson_write_err *err) { yyjson_write_err tmp_err; usize tmp_dat_len; yyjson_alc alc = alc_ptr ? *alc_ptr : YYJSON_DEFAULT_ALC; yyjson_mut_val *root = constcast(yyjson_mut_val *)val; if (!err) err = &tmp_err; if (!dat_len) dat_len = &tmp_dat_len; if (unlikely(!root)) { *dat_len = 0; err->msg = "input JSON is NULL"; err->code = YYJSON_WRITE_ERROR_INVALID_PARAMETER; return NULL; } if (!unsafe_yyjson_is_ctn(root) || unsafe_yyjson_get_len(root) == 0) { return (char *)yyjson_mut_write_single(root, flg, alc, dat_len, err); } else if (flg & (YYJSON_WRITE_PRETTY | YYJSON_WRITE_PRETTY_TWO_SPACES)) { return (char *)yyjson_mut_write_pretty(root, estimated_val_num, flg, alc, dat_len, err); } else { return (char *)yyjson_mut_write_minify(root, estimated_val_num, flg, alc, dat_len, err); } } /*============================================================================== * MARK: - Mutable JSON Writer (Public) *============================================================================*/ char *yyjson_mut_val_write_opts(const yyjson_mut_val *val, yyjson_write_flag flg, const yyjson_alc *alc_ptr, usize *dat_len, yyjson_write_err *err) { return yyjson_mut_write_opts_impl(val, 0, flg, alc_ptr, dat_len, err); } char *yyjson_mut_write_opts(const yyjson_mut_doc *doc, yyjson_write_flag flg, const yyjson_alc *alc_ptr, usize *dat_len, yyjson_write_err *err) { yyjson_mut_val *root; usize estimated_val_num; if (likely(doc)) { root = doc->root; estimated_val_num = yyjson_mut_doc_estimated_val_num(doc); } else { root = NULL; estimated_val_num = 0; } return yyjson_mut_write_opts_impl(root, estimated_val_num, flg, alc_ptr, dat_len, err); } bool yyjson_mut_val_write_file(const char *path, const yyjson_mut_val *val, yyjson_write_flag flg, const yyjson_alc *alc_ptr, yyjson_write_err *err) { yyjson_write_err tmp_err; yyjson_alc alc = alc_ptr ? *alc_ptr : YYJSON_DEFAULT_ALC; u8 *dat; usize dat_len = 0; yyjson_mut_val *root = constcast(yyjson_mut_val *)val; bool suc; if (!err) err = &tmp_err; if (unlikely(!path || !*path)) { err->msg = "input path is invalid"; err->code = YYJSON_WRITE_ERROR_INVALID_PARAMETER; return false; } dat = (u8 *)yyjson_mut_val_write_opts(root, flg, &alc, &dat_len, err); if (unlikely(!dat)) return false; suc = write_dat_to_file(path, dat, dat_len, err); alc.free(alc.ctx, dat); return suc; } bool yyjson_mut_val_write_fp(FILE *fp, const yyjson_mut_val *val, yyjson_write_flag flg, const yyjson_alc *alc_ptr, yyjson_write_err *err) { yyjson_write_err tmp_err; yyjson_alc alc = alc_ptr ? *alc_ptr : YYJSON_DEFAULT_ALC; u8 *dat; usize dat_len = 0; yyjson_mut_val *root = constcast(yyjson_mut_val *)val; bool suc; if (!err) err = &tmp_err; if (unlikely(!fp)) { err->msg = "input fp is invalid"; err->code = YYJSON_WRITE_ERROR_INVALID_PARAMETER; return false; } dat = (u8 *)yyjson_mut_val_write_opts(root, flg, &alc, &dat_len, err); if (unlikely(!dat)) return false; suc = write_dat_to_fp(fp, dat, dat_len, err); alc.free(alc.ctx, dat); return suc; } bool yyjson_mut_write_file(const char *path, const yyjson_mut_doc *doc, yyjson_write_flag flg, const yyjson_alc *alc_ptr, yyjson_write_err *err) { yyjson_mut_val *root = doc ? doc->root : NULL; return yyjson_mut_val_write_file(path, root, flg, alc_ptr, err); } bool yyjson_mut_write_fp(FILE *fp, const yyjson_mut_doc *doc, yyjson_write_flag flg, const yyjson_alc *alc_ptr, yyjson_write_err *err) { yyjson_mut_val *root = doc ? doc->root : NULL; return yyjson_mut_val_write_fp(fp, root, flg, alc_ptr, err); } #undef has_flg #undef has_allow #endif /* YYJSON_DISABLE_WRITER */ #if !YYJSON_DISABLE_UTILS /*============================================================================== * MARK: - JSON Pointer API (RFC 6901) (Public) *============================================================================*/ /** Get a token from JSON pointer string. @param ptr [in] string that points to current token prefix `/` [out] string that points to next token prefix `/`, or string end @param end [in] end of the entire JSON Pointer string @param len [out] unescaped token length @param esc [out] number of escaped characters in this token @return head of the token, or NULL if syntax error */ static_inline const char *ptr_next_token(const char **ptr, const char *end, usize *len, usize *esc) { const char *hdr = *ptr + 1; const char *cur = hdr; /* skip unescaped characters */ while (cur < end && *cur != '/' && *cur != '~') cur++; if (likely(cur == end || *cur != '~')) { /* no escaped characters, return */ *ptr = cur; *len = (usize)(cur - hdr); *esc = 0; return hdr; } else { /* handle escaped characters */ usize esc_num = 0; while (cur < end && *cur != '/') { if (*cur++ == '~') { if (cur == end || (*cur != '0' && *cur != '1')) { *ptr = cur - 1; return NULL; } esc_num++; } } *ptr = cur; *len = (usize)(cur - hdr) - esc_num; *esc = esc_num; return hdr; } } /** Convert token string to index. @param cur [in] token head @param len [in] token length @param idx [out] the index number, or USIZE_MAX if token is '-' @return true if token is a valid array index */ static_inline bool ptr_token_to_idx(const char *cur, usize len, usize *idx) { const char *end = cur + len; usize num = 0, add; if (unlikely(len == 0 || len > USIZE_SAFE_DIG)) return false; if (*cur == '0') { if (unlikely(len > 1)) return false; *idx = 0; return true; } if (*cur == '-') { if (unlikely(len > 1)) return false; *idx = USIZE_MAX; return true; } for (; cur < end && (add = (usize)((u8)*cur - (u8)'0')) <= 9; cur++) { num = num * 10 + add; } if (unlikely(num == 0 || cur < end)) return false; *idx = num; return true; } /** Compare JSON key with token. @param key a string key (yyjson_val or yyjson_mut_val) @param token a JSON pointer token @param len unescaped token length @param esc number of escaped characters in this token @return true if `str` is equals to `token` */ static_inline bool ptr_token_eq(void *key, const char *token, usize len, usize esc) { yyjson_val *val = (yyjson_val *)key; if (unsafe_yyjson_get_len(val) != len) return false; if (likely(!esc)) { return memcmp(val->uni.str, token, len) == 0; } else { const char *str = val->uni.str; for (; len-- > 0; token++, str++) { if (*token == '~') { if (*str != (*++token == '0' ? '~' : '/')) return false; } else { if (*str != *token) return false; } } return true; } } /** Get a value from array by token. @param arr an array, should not be NULL or non-array type @param token a JSON pointer token @param len unescaped token length @param esc number of escaped characters in this token @return value at index, or NULL if token is not index or index is out of range */ static_inline yyjson_val *ptr_arr_get(yyjson_val *arr, const char *token, usize len, usize esc) { yyjson_val *val = unsafe_yyjson_get_first(arr); usize num = unsafe_yyjson_get_len(arr), idx = 0; if (unlikely(num == 0)) return NULL; if (unlikely(!ptr_token_to_idx(token, len, &idx))) return NULL; if (unlikely(idx >= num)) return NULL; if (unsafe_yyjson_arr_is_flat(arr)) { return val + idx; } else { while (idx-- > 0) val = unsafe_yyjson_get_next(val); return val; } } /** Get a value from object by token. @param obj [in] an object, should not be NULL or non-object type @param token [in] a JSON pointer token @param len [in] unescaped token length @param esc [in] number of escaped characters in this token @return value associated with the token, or NULL if no value */ static_inline yyjson_val *ptr_obj_get(yyjson_val *obj, const char *token, usize len, usize esc) { yyjson_val *key = unsafe_yyjson_get_first(obj); usize num = unsafe_yyjson_get_len(obj); if (unlikely(num == 0)) return NULL; for (; num > 0; num--, key = unsafe_yyjson_get_next(key + 1)) { if (ptr_token_eq(key, token, len, esc)) return key + 1; } return NULL; } /** Get a value from array by token. @param arr [in] an array, should not be NULL or non-array type @param token [in] a JSON pointer token @param len [in] unescaped token length @param esc [in] number of escaped characters in this token @param pre [out] previous (sibling) value of the returned value @param last [out] whether index is last @return value at index, or NULL if token is not index or index is out of range */ static_inline yyjson_mut_val *ptr_mut_arr_get(yyjson_mut_val *arr, const char *token, usize len, usize esc, yyjson_mut_val **pre, bool *last) { yyjson_mut_val *val = (yyjson_mut_val *)arr->uni.ptr; /* last (tail) */ usize num = unsafe_yyjson_get_len(arr), idx; if (last) *last = false; if (pre) *pre = NULL; if (unlikely(num == 0)) { if (last && len == 1 && (*token == '0' || *token == '-')) *last = true; return NULL; } if (unlikely(!ptr_token_to_idx(token, len, &idx))) return NULL; if (last) *last = (idx == num || idx == USIZE_MAX); if (unlikely(idx >= num)) return NULL; while (idx-- > 0) val = val->next; if (pre) *pre = val; return val->next; } /** Get a value from object by token. @param obj [in] an object, should not be NULL or non-object type @param token [in] a JSON pointer token @param len [in] unescaped token length @param esc [in] number of escaped characters in this token @param pre [out] previous (sibling) key of the returned value's key @return value associated with the token, or NULL if no value */ static_inline yyjson_mut_val *ptr_mut_obj_get(yyjson_mut_val *obj, const char *token, usize len, usize esc, yyjson_mut_val **pre) { yyjson_mut_val *pre_key = (yyjson_mut_val *)obj->uni.ptr, *key; usize num = unsafe_yyjson_get_len(obj); if (pre) *pre = NULL; if (unlikely(num == 0)) return NULL; for (; num > 0; num--, pre_key = key) { key = pre_key->next->next; if (ptr_token_eq(key, token, len, esc)) { if (pre) *pre = pre_key; return key->next; } } return NULL; } /** Create a string value with JSON pointer token. @param token [in] a JSON pointer token @param len [in] unescaped token length @param esc [in] number of escaped characters in this token @param doc [in] used for memory allocation when creating value @return new string value, or NULL if memory allocation failed */ static_inline yyjson_mut_val *ptr_new_key(const char *token, usize len, usize esc, yyjson_mut_doc *doc) { const char *src = token; if (likely(!esc)) { return yyjson_mut_strncpy(doc, src, len); } else { const char *end = src + len + esc; char *dst = unsafe_yyjson_mut_str_alc(doc, len + esc); char *str = dst; if (unlikely(!dst)) return NULL; for (; src < end; src++, dst++) { if (*src != '~') *dst = *src; else *dst = (*++src == '0' ? '~' : '/'); } *dst = '\0'; return yyjson_mut_strn(doc, str, len); } } /* macros for yyjson_ptr */ #define return_err(_ret, _code, _pos, _msg) do { \ if (err) { \ err->code = YYJSON_PTR_ERR_##_code; \ err->msg = _msg; \ err->pos = (usize)(_pos); \ } \ return _ret; \ } while (false) #define return_err_resolve(_ret, _pos) \ return_err(_ret, RESOLVE, _pos, "JSON pointer cannot be resolved") #define return_err_syntax(_ret, _pos) \ return_err(_ret, SYNTAX, _pos, "invalid escaped character") #define return_err_alloc(_ret) \ return_err(_ret, MEMORY_ALLOCATION, 0, "failed to create value") yyjson_val *unsafe_yyjson_ptr_getx(yyjson_val *val, const char *ptr, size_t ptr_len, yyjson_ptr_err *err) { const char *hdr = ptr, *end = ptr + ptr_len, *token; usize len, esc; yyjson_type type; while (true) { token = ptr_next_token(&ptr, end, &len, &esc); if (unlikely(!token)) return_err_syntax(NULL, ptr - hdr); type = unsafe_yyjson_get_type(val); if (type == YYJSON_TYPE_OBJ) { val = ptr_obj_get(val, token, len, esc); } else if (type == YYJSON_TYPE_ARR) { val = ptr_arr_get(val, token, len, esc); } else { val = NULL; } if (!val) return_err_resolve(NULL, token - hdr); if (ptr == end) return val; } } yyjson_mut_val *unsafe_yyjson_mut_ptr_getx( yyjson_mut_val *val, const char *ptr, size_t ptr_len, yyjson_ptr_ctx *ctx, yyjson_ptr_err *err) { const char *hdr = ptr, *end = ptr + ptr_len, *token; usize len, esc; yyjson_mut_val *ctn, *pre = NULL; yyjson_type type; bool idx_is_last = false; while (true) { token = ptr_next_token(&ptr, end, &len, &esc); if (unlikely(!token)) return_err_syntax(NULL, ptr - hdr); ctn = val; type = unsafe_yyjson_get_type(val); if (type == YYJSON_TYPE_OBJ) { val = ptr_mut_obj_get(val, token, len, esc, &pre); } else if (type == YYJSON_TYPE_ARR) { val = ptr_mut_arr_get(val, token, len, esc, &pre, &idx_is_last); } else { val = NULL; } if (ctx && (ptr == end)) { if (type == YYJSON_TYPE_OBJ || (type == YYJSON_TYPE_ARR && (val || idx_is_last))) { ctx->ctn = ctn; ctx->pre = pre; } } if (!val) return_err_resolve(NULL, token - hdr); if (ptr == end) return val; } } bool unsafe_yyjson_mut_ptr_putx( yyjson_mut_val *val, const char *ptr, size_t ptr_len, yyjson_mut_val *new_val, yyjson_mut_doc *doc, bool create_parent, bool insert_new, yyjson_ptr_ctx *ctx, yyjson_ptr_err *err) { const char *hdr = ptr, *end = ptr + ptr_len, *token; usize token_len, esc, ctn_len; yyjson_mut_val *ctn, *key, *pre = NULL; yyjson_mut_val *sep_ctn = NULL, *sep_key = NULL, *sep_val = NULL; yyjson_type ctn_type; bool idx_is_last = false; /* skip exist parent nodes */ while (true) { token = ptr_next_token(&ptr, end, &token_len, &esc); if (unlikely(!token)) return_err_syntax(false, ptr - hdr); ctn = val; ctn_type = unsafe_yyjson_get_type(ctn); if (ctn_type == YYJSON_TYPE_OBJ) { val = ptr_mut_obj_get(ctn, token, token_len, esc, &pre); } else if (ctn_type == YYJSON_TYPE_ARR) { val = ptr_mut_arr_get(ctn, token, token_len, esc, &pre, &idx_is_last); } else return_err_resolve(false, token - hdr); if (!val) break; if (ptr == end) break; /* is last token */ } /* create parent nodes if not exist */ if (unlikely(ptr != end)) { /* not last token */ if (!create_parent) return_err_resolve(false, token - hdr); /* add value at last index if container is array */ if (ctn_type == YYJSON_TYPE_ARR) { if (!idx_is_last || !insert_new) { return_err_resolve(false, token - hdr); } val = yyjson_mut_obj(doc); if (!val) return_err_alloc(false); /* delay attaching until all operations are completed */ sep_ctn = ctn; sep_key = NULL; sep_val = val; /* move to next token */ ctn = val; val = NULL; ctn_type = YYJSON_TYPE_OBJ; token = ptr_next_token(&ptr, end, &token_len, &esc); if (unlikely(!token)) return_err_resolve(false, token - hdr); } /* container is object, create parent nodes */ while (ptr != end) { /* not last token */ key = ptr_new_key(token, token_len, esc, doc); if (!key) return_err_alloc(false); val = yyjson_mut_obj(doc); if (!val) return_err_alloc(false); /* delay attaching until all operations are completed */ if (!sep_ctn) { sep_ctn = ctn; sep_key = key; sep_val = val; } else { yyjson_mut_obj_add(ctn, key, val); } /* move to next token */ ctn = val; val = NULL; token = ptr_next_token(&ptr, end, &token_len, &esc); if (unlikely(!token)) return_err_syntax(false, ptr - hdr); } } /* JSON pointer is resolved, insert or replace target value */ ctn_len = unsafe_yyjson_get_len(ctn); if (ctn_type == YYJSON_TYPE_OBJ) { if (ctx) ctx->ctn = ctn; if (!val || insert_new) { /* insert new key-value pair */ key = ptr_new_key(token, token_len, esc, doc); if (unlikely(!key)) return_err_alloc(false); if (ctx) ctx->pre = ctn_len ? (yyjson_mut_val *)ctn->uni.ptr : key; unsafe_yyjson_mut_obj_add(ctn, key, new_val, ctn_len); } else { /* replace exist value */ key = pre->next->next; if (ctx) ctx->pre = pre; if (ctx) ctx->old = val; yyjson_mut_obj_put(ctn, key, new_val); } } else { /* array */ if (ctx && (val || idx_is_last)) ctx->ctn = ctn; if (insert_new) { /* append new value */ if (val) { pre->next = new_val; new_val->next = val; if (ctx) ctx->pre = pre; unsafe_yyjson_set_len(ctn, ctn_len + 1); } else if (idx_is_last) { if (ctx) ctx->pre = ctn_len ? (yyjson_mut_val *)ctn->uni.ptr : new_val; yyjson_mut_arr_append(ctn, new_val); } else { return_err_resolve(false, token - hdr); } } else { /* replace exist value */ if (!val) return_err_resolve(false, token - hdr); if (ctn_len > 1) { new_val->next = val->next; pre->next = new_val; if (ctn->uni.ptr == val) ctn->uni.ptr = new_val; } else { new_val->next = new_val; ctn->uni.ptr = new_val; pre = new_val; } if (ctx) ctx->pre = pre; if (ctx) ctx->old = val; } } /* all operations are completed, attach the new components to the target */ if (unlikely(sep_ctn)) { if (sep_key) yyjson_mut_obj_add(sep_ctn, sep_key, sep_val); else yyjson_mut_arr_append(sep_ctn, sep_val); } return true; } yyjson_mut_val *unsafe_yyjson_mut_ptr_replacex( yyjson_mut_val *val, const char *ptr, size_t len, yyjson_mut_val *new_val, yyjson_ptr_ctx *ctx, yyjson_ptr_err *err) { yyjson_mut_val *cur_val; yyjson_ptr_ctx cur_ctx; memset(&cur_ctx, 0, sizeof(cur_ctx)); if (!ctx) ctx = &cur_ctx; cur_val = unsafe_yyjson_mut_ptr_getx(val, ptr, len, ctx, err); if (!cur_val) return NULL; if (yyjson_mut_is_obj(ctx->ctn)) { yyjson_mut_val *key = ctx->pre->next->next; yyjson_mut_obj_put(ctx->ctn, key, new_val); } else { yyjson_ptr_ctx_replace(ctx, new_val); } ctx->old = cur_val; return cur_val; } yyjson_mut_val *unsafe_yyjson_mut_ptr_removex( yyjson_mut_val *val, const char *ptr, size_t len, yyjson_ptr_ctx *ctx, yyjson_ptr_err *err) { yyjson_mut_val *cur_val; yyjson_ptr_ctx cur_ctx; memset(&cur_ctx, 0, sizeof(cur_ctx)); if (!ctx) ctx = &cur_ctx; cur_val = unsafe_yyjson_mut_ptr_getx(val, ptr, len, ctx, err); if (cur_val) { if (yyjson_mut_is_obj(ctx->ctn)) { yyjson_mut_val *key = ctx->pre->next->next; yyjson_mut_obj_put(ctx->ctn, key, NULL); } else { yyjson_ptr_ctx_remove(ctx); } ctx->pre = NULL; ctx->old = cur_val; } return cur_val; } /* macros for yyjson_ptr */ #undef return_err #undef return_err_resolve #undef return_err_syntax #undef return_err_alloc /*============================================================================== * MARK: - JSON Patch API (RFC 6902) (Public) *============================================================================*/ /* JSON Patch operation */ typedef enum patch_op { PATCH_OP_ADD, /* path, value */ PATCH_OP_REMOVE, /* path */ PATCH_OP_REPLACE, /* path, value */ PATCH_OP_MOVE, /* from, path */ PATCH_OP_COPY, /* from, path */ PATCH_OP_TEST, /* path, value */ PATCH_OP_NONE /* invalid */ } patch_op; static patch_op patch_op_get(yyjson_val *op) { const char *str = op->uni.str; switch (unsafe_yyjson_get_len(op)) { case 3: if (!memcmp(str, "add", 3)) return PATCH_OP_ADD; return PATCH_OP_NONE; case 4: if (!memcmp(str, "move", 4)) return PATCH_OP_MOVE; if (!memcmp(str, "copy", 4)) return PATCH_OP_COPY; if (!memcmp(str, "test", 4)) return PATCH_OP_TEST; return PATCH_OP_NONE; case 6: if (!memcmp(str, "remove", 6)) return PATCH_OP_REMOVE; return PATCH_OP_NONE; case 7: if (!memcmp(str, "replace", 7)) return PATCH_OP_REPLACE; return PATCH_OP_NONE; default: return PATCH_OP_NONE; } } /* macros for yyjson_patch */ #define return_err(_code, _msg) do { \ if (err->ptr.code == YYJSON_PTR_ERR_MEMORY_ALLOCATION) { \ err->code = YYJSON_PATCH_ERROR_MEMORY_ALLOCATION; \ err->msg = _msg; \ memset(&err->ptr, 0, sizeof(yyjson_ptr_err)); \ } else { \ err->code = YYJSON_PATCH_ERROR_##_code; \ err->msg = _msg; \ err->idx = iter.idx ? iter.idx - 1 : 0; \ } \ return NULL; \ } while (false) #define return_err_copy() \ return_err(MEMORY_ALLOCATION, "failed to copy value") #define return_err_key(_key) \ return_err(MISSING_KEY, "missing key " _key) #define return_err_val(_key) \ return_err(INVALID_MEMBER, "invalid member " _key) #define ptr_get(_ptr) yyjson_mut_ptr_getx( \ root, _ptr->uni.str, _ptr##_len, NULL, &err->ptr) #define ptr_add(_ptr, _val) yyjson_mut_ptr_addx( \ root, _ptr->uni.str, _ptr##_len, _val, doc, false, NULL, &err->ptr) #define ptr_remove(_ptr) yyjson_mut_ptr_removex( \ root, _ptr->uni.str, _ptr##_len, NULL, &err->ptr) #define ptr_replace(_ptr, _val)yyjson_mut_ptr_replacex( \ root, _ptr->uni.str, _ptr##_len, _val, NULL, &err->ptr) yyjson_mut_val *yyjson_patch(yyjson_mut_doc *doc, yyjson_val *orig, yyjson_val *patch, yyjson_patch_err *err) { yyjson_mut_val *root; yyjson_val *obj; yyjson_arr_iter iter; yyjson_patch_err err_tmp; if (!err) err = &err_tmp; memset(err, 0, sizeof(*err)); memset(&iter, 0, sizeof(iter)); if (unlikely(!doc || !orig || !patch)) { return_err(INVALID_PARAMETER, "input parameter is NULL"); } if (unlikely(!yyjson_is_arr(patch))) { return_err(INVALID_PARAMETER, "input patch is not array"); } root = yyjson_val_mut_copy(doc, orig); if (unlikely(!root)) return_err_copy(); /* iterate through the patch array */ yyjson_arr_iter_init(patch, &iter); while ((obj = yyjson_arr_iter_next(&iter))) { patch_op op_enum; yyjson_val *op, *path, *from = NULL, *value; yyjson_mut_val *val = NULL, *test; usize path_len, from_len = 0; if (unlikely(!unsafe_yyjson_is_obj(obj))) { return_err(INVALID_OPERATION, "JSON patch operation is not object"); } /* get required member: op */ op = yyjson_obj_get(obj, "op"); if (unlikely(!op)) return_err_key("`op`"); if (unlikely(!yyjson_is_str(op))) return_err_val("`op`"); op_enum = patch_op_get(op); /* get required member: path */ path = yyjson_obj_get(obj, "path"); if (unlikely(!path)) return_err_key("`path`"); if (unlikely(!yyjson_is_str(path))) return_err_val("`path`"); path_len = unsafe_yyjson_get_len(path); /* get required member: value, from */ switch ((int)op_enum) { case PATCH_OP_ADD: case PATCH_OP_REPLACE: case PATCH_OP_TEST: value = yyjson_obj_get(obj, "value"); if (unlikely(!value)) return_err_key("`value`"); val = yyjson_val_mut_copy(doc, value); if (unlikely(!val)) return_err_copy(); break; case PATCH_OP_MOVE: case PATCH_OP_COPY: from = yyjson_obj_get(obj, "from"); if (unlikely(!from)) return_err_key("`from`"); if (unlikely(!yyjson_is_str(from))) return_err_val("`from`"); from_len = unsafe_yyjson_get_len(from); break; default: break; } /* perform an operation */ switch ((int)op_enum) { case PATCH_OP_ADD: /* add(path, val) */ if (unlikely(path_len == 0)) { root = val; break; } if (unlikely(!ptr_add(path, val))) { return_err(POINTER, "failed to add `path`"); } break; case PATCH_OP_REMOVE: /* remove(path) */ if (unlikely(!ptr_remove(path))) { return_err(POINTER, "failed to remove `path`"); } break; case PATCH_OP_REPLACE: /* replace(path, val) */ if (unlikely(path_len == 0)) { root = val; break; } if (unlikely(!ptr_replace(path, val))) { return_err(POINTER, "failed to replace `path`"); } break; case PATCH_OP_MOVE: /* val = remove(from), add(path, val) */ if (unlikely(from_len == 0 && path_len == 0)) break; val = ptr_remove(from); if (unlikely(!val)) { return_err(POINTER, "failed to remove `from`"); } if (unlikely(path_len == 0)) { root = val; break; } if (unlikely(!ptr_add(path, val))) { return_err(POINTER, "failed to add `path`"); } break; case PATCH_OP_COPY: /* val = get(from).copy, add(path, val) */ val = ptr_get(from); if (unlikely(!val)) { return_err(POINTER, "failed to get `from`"); } if (unlikely(path_len == 0)) { root = val; break; } val = yyjson_mut_val_mut_copy(doc, val); if (unlikely(!val)) return_err_copy(); if (unlikely(!ptr_add(path, val))) { return_err(POINTER, "failed to add `path`"); } break; case PATCH_OP_TEST: /* test = get(path), test.eq(val) */ test = ptr_get(path); if (unlikely(!test)) { return_err(POINTER, "failed to get `path`"); } if (unlikely(!yyjson_mut_equals(val, test))) { return_err(EQUAL, "failed to test equal"); } break; default: return_err(INVALID_MEMBER, "unsupported `op`"); } } return root; } yyjson_mut_val *yyjson_mut_patch(yyjson_mut_doc *doc, yyjson_mut_val *orig, yyjson_mut_val *patch, yyjson_patch_err *err) { yyjson_mut_val *root, *obj; yyjson_mut_arr_iter iter; yyjson_patch_err err_tmp; if (!err) err = &err_tmp; memset(err, 0, sizeof(*err)); memset(&iter, 0, sizeof(iter)); if (unlikely(!doc || !orig || !patch)) { return_err(INVALID_PARAMETER, "input parameter is NULL"); } if (unlikely(!yyjson_mut_is_arr(patch))) { return_err(INVALID_PARAMETER, "input patch is not array"); } root = yyjson_mut_val_mut_copy(doc, orig); if (unlikely(!root)) return_err_copy(); /* iterate through the patch array */ yyjson_mut_arr_iter_init(patch, &iter); while ((obj = yyjson_mut_arr_iter_next(&iter))) { patch_op op_enum; yyjson_mut_val *op, *path, *from = NULL, *value; yyjson_mut_val *val = NULL, *test; usize path_len, from_len = 0; if (!unsafe_yyjson_is_obj(obj)) { return_err(INVALID_OPERATION, "JSON patch operation is not object"); } /* get required member: op */ op = yyjson_mut_obj_get(obj, "op"); if (unlikely(!op)) return_err_key("`op`"); if (unlikely(!yyjson_mut_is_str(op))) return_err_val("`op`"); op_enum = patch_op_get((yyjson_val *)(void *)op); /* get required member: path */ path = yyjson_mut_obj_get(obj, "path"); if (unlikely(!path)) return_err_key("`path`"); if (unlikely(!yyjson_mut_is_str(path))) return_err_val("`path`"); path_len = unsafe_yyjson_get_len(path); /* get required member: value, from */ switch ((int)op_enum) { case PATCH_OP_ADD: case PATCH_OP_REPLACE: case PATCH_OP_TEST: value = yyjson_mut_obj_get(obj, "value"); if (unlikely(!value)) return_err_key("`value`"); val = yyjson_mut_val_mut_copy(doc, value); if (unlikely(!val)) return_err_copy(); break; case PATCH_OP_MOVE: case PATCH_OP_COPY: from = yyjson_mut_obj_get(obj, "from"); if (unlikely(!from)) return_err_key("`from`"); if (unlikely(!yyjson_mut_is_str(from))) { return_err_val("`from`"); } from_len = unsafe_yyjson_get_len(from); break; default: break; } /* perform an operation */ switch ((int)op_enum) { case PATCH_OP_ADD: /* add(path, val) */ if (unlikely(path_len == 0)) { root = val; break; } if (unlikely(!ptr_add(path, val))) { return_err(POINTER, "failed to add `path`"); } break; case PATCH_OP_REMOVE: /* remove(path) */ if (unlikely(!ptr_remove(path))) { return_err(POINTER, "failed to remove `path`"); } break; case PATCH_OP_REPLACE: /* replace(path, val) */ if (unlikely(path_len == 0)) { root = val; break; } if (unlikely(!ptr_replace(path, val))) { return_err(POINTER, "failed to replace `path`"); } break; case PATCH_OP_MOVE: /* val = remove(from), add(path, val) */ if (unlikely(from_len == 0 && path_len == 0)) break; val = ptr_remove(from); if (unlikely(!val)) { return_err(POINTER, "failed to remove `from`"); } if (unlikely(path_len == 0)) { root = val; break; } if (unlikely(!ptr_add(path, val))) { return_err(POINTER, "failed to add `path`"); } break; case PATCH_OP_COPY: /* val = get(from).copy, add(path, val) */ val = ptr_get(from); if (unlikely(!val)) { return_err(POINTER, "failed to get `from`"); } if (unlikely(path_len == 0)) { root = val; break; } val = yyjson_mut_val_mut_copy(doc, val); if (unlikely(!val)) return_err_copy(); if (unlikely(!ptr_add(path, val))) { return_err(POINTER, "failed to add `path`"); } break; case PATCH_OP_TEST: /* test = get(path), test.eq(val) */ test = ptr_get(path); if (unlikely(!test)) { return_err(POINTER, "failed to get `path`"); } if (unlikely(!yyjson_mut_equals(val, test))) { return_err(EQUAL, "failed to test equal"); } break; default: return_err(INVALID_MEMBER, "unsupported `op`"); } } return root; } /* macros for yyjson_patch */ #undef return_err #undef return_err_copy #undef return_err_key #undef return_err_val #undef ptr_get #undef ptr_add #undef ptr_remove #undef ptr_replace /*============================================================================== * MARK: - JSON Merge-Patch API (RFC 7386) (Public) *============================================================================*/ yyjson_mut_val *yyjson_merge_patch(yyjson_mut_doc *doc, yyjson_val *orig, yyjson_val *patch) { usize idx, max; yyjson_val *key, *orig_val, *patch_val, local_orig; yyjson_mut_val *builder, *mut_key, *mut_val, *merged_val; if (unlikely(!yyjson_is_obj(patch))) { return yyjson_val_mut_copy(doc, patch); } builder = yyjson_mut_obj(doc); if (unlikely(!builder)) return NULL; memset(&local_orig, 0, sizeof(local_orig)); if (!yyjson_is_obj(orig)) { orig = &local_orig; orig->tag = builder->tag; orig->uni = builder->uni; } /* If orig is contributing, copy any items not modified by the patch */ if (orig != &local_orig) { yyjson_obj_foreach(orig, idx, max, key, orig_val) { patch_val = yyjson_obj_getn(patch, unsafe_yyjson_get_str(key), unsafe_yyjson_get_len(key)); if (!patch_val) { mut_key = yyjson_val_mut_copy(doc, key); mut_val = yyjson_val_mut_copy(doc, orig_val); if (!yyjson_mut_obj_add(builder, mut_key, mut_val)) return NULL; } } } /* Merge items modified by the patch. */ yyjson_obj_foreach(patch, idx, max, key, patch_val) { /* null indicates the field is removed. */ if (unsafe_yyjson_is_null(patch_val)) { continue; } mut_key = yyjson_val_mut_copy(doc, key); orig_val = yyjson_obj_getn(orig, unsafe_yyjson_get_str(key), unsafe_yyjson_get_len(key)); merged_val = yyjson_merge_patch(doc, orig_val, patch_val); if (!yyjson_mut_obj_add(builder, mut_key, merged_val)) return NULL; } return builder; } yyjson_mut_val *yyjson_mut_merge_patch(yyjson_mut_doc *doc, yyjson_mut_val *orig, yyjson_mut_val *patch) { usize idx, max; yyjson_mut_val *key, *orig_val, *patch_val, local_orig; yyjson_mut_val *builder, *mut_key, *mut_val, *merged_val; if (unlikely(!yyjson_mut_is_obj(patch))) { return yyjson_mut_val_mut_copy(doc, patch); } builder = yyjson_mut_obj(doc); if (unlikely(!builder)) return NULL; memset(&local_orig, 0, sizeof(local_orig)); if (!yyjson_mut_is_obj(orig)) { orig = &local_orig; orig->tag = builder->tag; orig->uni = builder->uni; } /* If orig is contributing, copy any items not modified by the patch */ if (orig != &local_orig) { yyjson_mut_obj_foreach(orig, idx, max, key, orig_val) { patch_val = yyjson_mut_obj_getn(patch, unsafe_yyjson_get_str(key), unsafe_yyjson_get_len(key)); if (!patch_val) { mut_key = yyjson_mut_val_mut_copy(doc, key); mut_val = yyjson_mut_val_mut_copy(doc, orig_val); if (!yyjson_mut_obj_add(builder, mut_key, mut_val)) return NULL; } } } /* Merge items modified by the patch. */ yyjson_mut_obj_foreach(patch, idx, max, key, patch_val) { /* null indicates the field is removed. */ if (unsafe_yyjson_is_null(patch_val)) { continue; } mut_key = yyjson_mut_val_mut_copy(doc, key); orig_val = yyjson_mut_obj_getn(orig, unsafe_yyjson_get_str(key), unsafe_yyjson_get_len(key)); merged_val = yyjson_mut_merge_patch(doc, orig_val, patch_val); if (!yyjson_mut_obj_add(builder, mut_key, merged_val)) return NULL; } return builder; } #endif /* YYJSON_DISABLE_UTILS */ ================================================ FILE: src/3rdparty/yyjson/yyjson.h ================================================ /*============================================================================== Copyright (c) 2020 YaoYuan Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. *============================================================================*/ /** @file yyjson.h @date 2019-03-09 @author YaoYuan */ #ifndef YYJSON_H #define YYJSON_H /*============================================================================== * MARK: - Header Files *============================================================================*/ #include #include #include #include #include #include /*============================================================================== * MARK: - Compile-time Options *============================================================================*/ /* Define as 1 to disable JSON reader at compile-time. This disables functions with "read" in their name. Reduces binary size by about 60%. */ #ifndef YYJSON_DISABLE_READER #endif /* Define as 1 to disable JSON writer at compile-time. This disables functions with "write" in their name. Reduces binary size by about 30%. */ #ifndef YYJSON_DISABLE_WRITER #endif /* Define as 1 to disable JSON incremental reader at compile-time. This disables functions with "incr" in their name. */ #ifndef YYJSON_DISABLE_INCR_READER #endif /* Define as 1 to disable JSON Pointer, JSON Patch and JSON Merge Patch supports. This disables functions with "ptr" or "patch" in their name. */ #ifndef YYJSON_DISABLE_UTILS #endif /* Define as 1 to disable the fast floating-point number conversion in yyjson. Libc's `strtod/snprintf` will be used instead. This reduces binary size by about 30%, but significantly slows down the floating-point read/write speed. */ #ifndef YYJSON_DISABLE_FAST_FP_CONV #endif /* Define as 1 to disable non-standard JSON features support at compile-time, such as YYJSON_READ_ALLOW_XXX and YYJSON_WRITE_ALLOW_XXX. This reduces binary size by about 10%, and slightly improves performance. */ #ifndef YYJSON_DISABLE_NON_STANDARD #endif /* Define as 1 to disable UTF-8 validation at compile-time. Use this if all input strings are guaranteed to be valid UTF-8 (e.g. language-level String types are already validated). Disabling UTF-8 validation improves performance for non-ASCII strings by about 3% to 7%. Note: If this flag is enabled while passing illegal UTF-8 strings, the following errors may occur: - Escaped characters may be ignored when parsing JSON strings. - Ending quotes may be ignored when parsing JSON strings, causing the string to merge with the next value. - When serializing with `yyjson_mut_val`, the string's end may be accessed out of bounds, potentially causing a segmentation fault. */ #ifndef YYJSON_DISABLE_UTF8_VALIDATION #endif /* Define as 1 to improve performance on architectures that do not support unaligned memory access. Normally, this does not need to be set manually. See the C file for details. */ #ifndef YYJSON_DISABLE_UNALIGNED_MEMORY_ACCESS #endif /* Define as 1 to export symbols when building this library as a Windows DLL. */ #ifndef YYJSON_EXPORTS #endif /* Define as 1 to import symbols when using this library as a Windows DLL. */ #ifndef YYJSON_IMPORTS #endif /* Define as 1 to include for compilers without C99 support. */ #ifndef YYJSON_HAS_STDINT_H #endif /* Define as 1 to include for compilers without C99 support. */ #ifndef YYJSON_HAS_STDBOOL_H #endif /*============================================================================== * MARK: - Compiler Macros *============================================================================*/ /** compiler version (MSVC) */ #ifdef _MSC_VER # define YYJSON_MSC_VER _MSC_VER #else # define YYJSON_MSC_VER 0 #endif /** compiler version (GCC) */ #ifdef __GNUC__ # define YYJSON_GCC_VER __GNUC__ # if defined(__GNUC_PATCHLEVEL__) # define yyjson_gcc_available(major, minor, patch) \ ((__GNUC__ * 10000 + __GNUC_MINOR__ * 100 + __GNUC_PATCHLEVEL__) \ >= (major * 10000 + minor * 100 + patch)) # else # define yyjson_gcc_available(major, minor, patch) \ ((__GNUC__ * 10000 + __GNUC_MINOR__ * 100) \ >= (major * 10000 + minor * 100 + patch)) # endif #else # define YYJSON_GCC_VER 0 # define yyjson_gcc_available(major, minor, patch) 0 #endif /** real gcc check */ #if defined(__GNUC__) && defined(__GNUC_MINOR__) && \ !defined(__clang__) && !defined(__llvm__) && \ !defined(__INTEL_COMPILER) && !defined(__ICC) && \ !defined(__NVCC__) && !defined(__PGI) && !defined(__TINYC__) # define YYJSON_IS_REAL_GCC 1 #else # define YYJSON_IS_REAL_GCC 0 #endif /** C version (STDC) */ #if defined(__STDC__) && (__STDC__ >= 1) && defined(__STDC_VERSION__) # define YYJSON_STDC_VER __STDC_VERSION__ #else # define YYJSON_STDC_VER 0 #endif /** C++ version */ #if defined(__cplusplus) # define YYJSON_CPP_VER __cplusplus #else # define YYJSON_CPP_VER 0 #endif /** compiler builtin check (since gcc 10.0, clang 2.6, icc 2021) */ #ifndef yyjson_has_builtin # ifdef __has_builtin # define yyjson_has_builtin(x) __has_builtin(x) # else # define yyjson_has_builtin(x) 0 # endif #endif /** compiler attribute check (since gcc 5.0, clang 2.9, icc 17) */ #ifndef yyjson_has_attribute # ifdef __has_attribute # define yyjson_has_attribute(x) __has_attribute(x) # else # define yyjson_has_attribute(x) 0 # endif #endif /** compiler feature check (since clang 2.6, icc 17) */ #ifndef yyjson_has_feature # ifdef __has_feature # define yyjson_has_feature(x) __has_feature(x) # else # define yyjson_has_feature(x) 0 # endif #endif /** include check (since gcc 5.0, clang 2.7, icc 16, msvc 2017 15.3) */ #ifndef yyjson_has_include # ifdef __has_include # define yyjson_has_include(x) __has_include(x) # else # define yyjson_has_include(x) 0 # endif #endif /** inline for compiler */ #ifndef yyjson_inline # if YYJSON_MSC_VER >= 1200 # define yyjson_inline __forceinline # elif defined(_MSC_VER) # define yyjson_inline __inline # elif yyjson_has_attribute(always_inline) || YYJSON_GCC_VER >= 4 # define yyjson_inline __inline__ __attribute__((always_inline)) # elif defined(__clang__) || defined(__GNUC__) # define yyjson_inline __inline__ # elif defined(__cplusplus) || YYJSON_STDC_VER >= 199901L # define yyjson_inline inline # else # define yyjson_inline # endif #endif /** noinline for compiler */ #ifndef yyjson_noinline # if YYJSON_MSC_VER >= 1400 # define yyjson_noinline __declspec(noinline) # elif yyjson_has_attribute(noinline) || YYJSON_GCC_VER >= 4 # define yyjson_noinline __attribute__((noinline)) # else # define yyjson_noinline # endif #endif /** align for compiler */ #ifndef yyjson_align # if YYJSON_MSC_VER >= 1300 # define yyjson_align(x) __declspec(align(x)) # elif yyjson_has_attribute(aligned) || defined(__GNUC__) # define yyjson_align(x) __attribute__((aligned(x))) # elif YYJSON_CPP_VER >= 201103L # define yyjson_align(x) alignas(x) # else # define yyjson_align(x) # endif #endif /** likely for compiler */ #ifndef yyjson_likely # if yyjson_has_builtin(__builtin_expect) || \ (YYJSON_GCC_VER >= 4 && YYJSON_GCC_VER != 5) # define yyjson_likely(expr) __builtin_expect(!!(expr), 1) # else # define yyjson_likely(expr) (expr) # endif #endif /** unlikely for compiler */ #ifndef yyjson_unlikely # if yyjson_has_builtin(__builtin_expect) || \ (YYJSON_GCC_VER >= 4 && YYJSON_GCC_VER != 5) # define yyjson_unlikely(expr) __builtin_expect(!!(expr), 0) # else # define yyjson_unlikely(expr) (expr) # endif #endif /** compile-time constant check for compiler */ #ifndef yyjson_constant_p # if yyjson_has_builtin(__builtin_constant_p) || (YYJSON_GCC_VER >= 3) # define YYJSON_HAS_CONSTANT_P 1 # define yyjson_constant_p(value) __builtin_constant_p(value) # else # define YYJSON_HAS_CONSTANT_P 0 # define yyjson_constant_p(value) 0 # endif #endif /** deprecate warning */ #ifndef yyjson_deprecated # if YYJSON_MSC_VER >= 1400 # define yyjson_deprecated(msg) __declspec(deprecated(msg)) # elif yyjson_has_feature(attribute_deprecated_with_message) || \ (YYJSON_GCC_VER > 4 || (YYJSON_GCC_VER == 4 && __GNUC_MINOR__ >= 5)) # define yyjson_deprecated(msg) __attribute__((deprecated(msg))) # elif YYJSON_GCC_VER >= 3 # define yyjson_deprecated(msg) __attribute__((deprecated)) # else # define yyjson_deprecated(msg) # endif #endif /** function export */ #ifndef yyjson_api # if defined(_WIN32) # if defined(YYJSON_EXPORTS) && YYJSON_EXPORTS # define yyjson_api __declspec(dllexport) # elif defined(YYJSON_IMPORTS) && YYJSON_IMPORTS # define yyjson_api __declspec(dllimport) # else # define yyjson_api # endif # elif yyjson_has_attribute(visibility) || YYJSON_GCC_VER >= 4 # define yyjson_api __attribute__((visibility("default"))) # else # define yyjson_api # endif #endif /** inline function export */ #ifndef yyjson_api_inline # define yyjson_api_inline static yyjson_inline #endif /** stdint (C89 compatible) */ #if (defined(YYJSON_HAS_STDINT_H) && YYJSON_HAS_STDINT_H) || \ YYJSON_MSC_VER >= 1600 || YYJSON_STDC_VER >= 199901L || \ defined(_STDINT_H) || defined(_STDINT_H_) || \ defined(__CLANG_STDINT_H) || defined(_STDINT_H_INCLUDED) || \ yyjson_has_include() # include #elif defined(_MSC_VER) # if _MSC_VER < 1300 typedef signed char int8_t; typedef signed short int16_t; typedef signed int int32_t; typedef unsigned char uint8_t; typedef unsigned short uint16_t; typedef unsigned int uint32_t; typedef signed __int64 int64_t; typedef unsigned __int64 uint64_t; # else typedef signed __int8 int8_t; typedef signed __int16 int16_t; typedef signed __int32 int32_t; typedef unsigned __int8 uint8_t; typedef unsigned __int16 uint16_t; typedef unsigned __int32 uint32_t; typedef signed __int64 int64_t; typedef unsigned __int64 uint64_t; # endif #else # if UCHAR_MAX == 0xFFU typedef signed char int8_t; typedef unsigned char uint8_t; # else # error cannot find 8-bit integer type # endif # if USHRT_MAX == 0xFFFFU typedef unsigned short uint16_t; typedef signed short int16_t; # elif UINT_MAX == 0xFFFFU typedef unsigned int uint16_t; typedef signed int int16_t; # else # error cannot find 16-bit integer type # endif # if UINT_MAX == 0xFFFFFFFFUL typedef unsigned int uint32_t; typedef signed int int32_t; # elif ULONG_MAX == 0xFFFFFFFFUL typedef unsigned long uint32_t; typedef signed long int32_t; # elif USHRT_MAX == 0xFFFFFFFFUL typedef unsigned short uint32_t; typedef signed short int32_t; # else # error cannot find 32-bit integer type # endif # if defined(__INT64_TYPE__) && defined(__UINT64_TYPE__) typedef __INT64_TYPE__ int64_t; typedef __UINT64_TYPE__ uint64_t; # elif defined(__GNUC__) || defined(__clang__) # if !defined(_SYS_TYPES_H) && !defined(__int8_t_defined) __extension__ typedef long long int64_t; # endif __extension__ typedef unsigned long long uint64_t; # elif defined(_LONG_LONG) || defined(__MWERKS__) || defined(_CRAYC) || \ defined(__SUNPRO_C) || defined(__SUNPRO_CC) typedef long long int64_t; typedef unsigned long long uint64_t; # elif (defined(__BORLANDC__) && __BORLANDC__ > 0x460) || \ defined(__WATCOM_INT64__) || defined (__alpha) || defined (__DECC) typedef __int64 int64_t; typedef unsigned __int64 uint64_t; # else # error cannot find 64-bit integer type # endif #endif /** stdbool (C89 compatible) */ #if (defined(YYJSON_HAS_STDBOOL_H) && YYJSON_HAS_STDBOOL_H) || \ (yyjson_has_include() && !defined(__STRICT_ANSI__)) || \ YYJSON_MSC_VER >= 1800 || YYJSON_STDC_VER >= 199901L # include #elif !defined(__bool_true_false_are_defined) # define __bool_true_false_are_defined 1 # if defined(__cplusplus) # if defined(__GNUC__) && !defined(__STRICT_ANSI__) # define _Bool bool # if __cplusplus < 201103L # define bool bool # define false false # define true true # endif # endif # else # define bool unsigned char # define true 1 # define false 0 # endif #endif /** char bit check */ #if defined(CHAR_BIT) # if CHAR_BIT != 8 # error non 8-bit char is not supported # endif #endif /** Microsoft Visual C++ 6.0 doesn't support converting number from u64 to f64: error C2520: conversion from unsigned __int64 to double not implemented. */ #ifndef YYJSON_U64_TO_F64_NO_IMPL # if (0 < YYJSON_MSC_VER) && (YYJSON_MSC_VER <= 1200) # define YYJSON_U64_TO_F64_NO_IMPL 1 # else # define YYJSON_U64_TO_F64_NO_IMPL 0 # endif #endif /*============================================================================== * MARK: - Compile Hint Begin *============================================================================*/ /* extern "C" begin */ #ifdef __cplusplus extern "C" { #endif /* warning suppress begin */ #if defined(__clang__) # pragma clang diagnostic push # pragma clang diagnostic ignored "-Wunused-function" # pragma clang diagnostic ignored "-Wunused-parameter" #elif defined(__GNUC__) # if (__GNUC__ > 4) || (__GNUC__ == 4 && __GNUC_MINOR__ >= 6) # pragma GCC diagnostic push # endif # pragma GCC diagnostic ignored "-Wunused-function" # pragma GCC diagnostic ignored "-Wunused-parameter" #elif defined(_MSC_VER) # pragma warning(push) # pragma warning(disable:4800) /* 'int': forcing value to 'true' or 'false' */ #endif /*============================================================================== * MARK: - Version *============================================================================*/ /** The major version of yyjson. */ #define YYJSON_VERSION_MAJOR 0 /** The minor version of yyjson. */ #define YYJSON_VERSION_MINOR 12 /** The patch version of yyjson. */ #define YYJSON_VERSION_PATCH 0 /** The version of yyjson in hex: `(major << 16) | (minor << 8) | (patch)`. */ #define YYJSON_VERSION_HEX 0x000C00 /** The version string of yyjson. */ #define YYJSON_VERSION_STRING "0.12.0" /** The version of yyjson in hex, same as `YYJSON_VERSION_HEX`. */ yyjson_api uint32_t yyjson_version(void); /*============================================================================== * MARK: - JSON Types *============================================================================*/ /** Type of a JSON value (3 bit). */ typedef uint8_t yyjson_type; /** No type, invalid. */ #define YYJSON_TYPE_NONE ((uint8_t)0) /* _____000 */ /** Raw string type, no subtype. */ #define YYJSON_TYPE_RAW ((uint8_t)1) /* _____001 */ /** Null type: `null` literal, no subtype. */ #define YYJSON_TYPE_NULL ((uint8_t)2) /* _____010 */ /** Boolean type, subtype: TRUE, FALSE. */ #define YYJSON_TYPE_BOOL ((uint8_t)3) /* _____011 */ /** Number type, subtype: UINT, SINT, REAL. */ #define YYJSON_TYPE_NUM ((uint8_t)4) /* _____100 */ /** String type, subtype: NONE, NOESC. */ #define YYJSON_TYPE_STR ((uint8_t)5) /* _____101 */ /** Array type, no subtype. */ #define YYJSON_TYPE_ARR ((uint8_t)6) /* _____110 */ /** Object type, no subtype. */ #define YYJSON_TYPE_OBJ ((uint8_t)7) /* _____111 */ /** Subtype of a JSON value (2 bit). */ typedef uint8_t yyjson_subtype; /** No subtype. */ #define YYJSON_SUBTYPE_NONE ((uint8_t)(0 << 3)) /* ___00___ */ /** False subtype: `false` literal. */ #define YYJSON_SUBTYPE_FALSE ((uint8_t)(0 << 3)) /* ___00___ */ /** True subtype: `true` literal. */ #define YYJSON_SUBTYPE_TRUE ((uint8_t)(1 << 3)) /* ___01___ */ /** Unsigned integer subtype: `uint64_t`. */ #define YYJSON_SUBTYPE_UINT ((uint8_t)(0 << 3)) /* ___00___ */ /** Signed integer subtype: `int64_t`. */ #define YYJSON_SUBTYPE_SINT ((uint8_t)(1 << 3)) /* ___01___ */ /** Real number subtype: `double`. */ #define YYJSON_SUBTYPE_REAL ((uint8_t)(2 << 3)) /* ___10___ */ /** String that do not need to be escaped for writing (internal use). */ #define YYJSON_SUBTYPE_NOESC ((uint8_t)(1 << 3)) /* ___01___ */ /** The mask used to extract the type of a JSON value. */ #define YYJSON_TYPE_MASK ((uint8_t)0x07) /* _____111 */ /** The number of bits used by the type. */ #define YYJSON_TYPE_BIT ((uint8_t)3) /** The mask used to extract the subtype of a JSON value. */ #define YYJSON_SUBTYPE_MASK ((uint8_t)0x18) /* ___11___ */ /** The number of bits used by the subtype. */ #define YYJSON_SUBTYPE_BIT ((uint8_t)2) /** The mask used to extract the reserved bits of a JSON value. */ #define YYJSON_RESERVED_MASK ((uint8_t)0xE0) /* 111_____ */ /** The number of reserved bits. */ #define YYJSON_RESERVED_BIT ((uint8_t)3) /** The mask used to extract the tag of a JSON value. */ #define YYJSON_TAG_MASK ((uint8_t)0xFF) /* 11111111 */ /** The number of bits used by the tag. */ #define YYJSON_TAG_BIT ((uint8_t)8) /** Padding size for JSON reader. */ #define YYJSON_PADDING_SIZE 4 /*============================================================================== * MARK: - Allocator *============================================================================*/ /** A memory allocator. Typically you don't need to use it, unless you want to customize your own memory allocator. */ typedef struct yyjson_alc { /** Same as libc's malloc(size), should not be NULL. */ void *(*malloc)(void *ctx, size_t size); /** Same as libc's realloc(ptr, size), should not be NULL. */ void *(*realloc)(void *ctx, void *ptr, size_t old_size, size_t size); /** Same as libc's free(ptr), should not be NULL. */ void (*free)(void *ctx, void *ptr); /** A context for malloc/realloc/free, can be NULL. */ void *ctx; } yyjson_alc; /** A pool allocator uses fixed length pre-allocated memory. This allocator may be used to avoid malloc/realloc calls. The pre-allocated memory should be held by the caller. The maximum amount of memory required to read a JSON can be calculated using the `yyjson_read_max_memory_usage()` function, but the amount of memory required to write a JSON cannot be directly calculated. This is not a general-purpose allocator. It is designed to handle a single JSON data at a time. If it is used for overly complex memory tasks, such as parsing multiple JSON documents using the same allocator but releasing only a few of them, it may cause memory fragmentation, resulting in performance degradation and memory waste. @param alc The allocator to be initialized. If this parameter is NULL, the function will fail and return false. If `buf` or `size` is invalid, this will be set to an empty allocator. @param buf The buffer memory for this allocator. If this parameter is NULL, the function will fail and return false. @param size The size of `buf`, in bytes. If this parameter is less than 8 words (32/64 bytes on 32/64-bit OS), the function will fail and return false. @return true if the `alc` has been successfully initialized. @b Example @code // parse JSON with stack memory char buf[1024]; yyjson_alc alc; yyjson_alc_pool_init(&alc, buf, 1024); const char *json = "{\"name\":\"Helvetica\",\"size\":16}" yyjson_doc *doc = yyjson_read_opts(json, strlen(json), 0, &alc, NULL); // the memory of `doc` is on the stack @endcode @warning This Allocator is not thread-safe. */ yyjson_api bool yyjson_alc_pool_init(yyjson_alc *alc, void *buf, size_t size); /** A dynamic allocator. This allocator has a similar usage to the pool allocator above. However, when there is not enough memory, this allocator will dynamically request more memory using libc's `malloc` function, and frees it all at once when it is destroyed. @return A new dynamic allocator, or NULL if memory allocation failed. @note The returned value should be freed with `yyjson_alc_dyn_free()`. @warning This Allocator is not thread-safe. */ yyjson_api yyjson_alc *yyjson_alc_dyn_new(void); /** Free a dynamic allocator which is created by `yyjson_alc_dyn_new()`. @param alc The dynamic allocator to be destroyed. */ yyjson_api void yyjson_alc_dyn_free(yyjson_alc *alc); /*============================================================================== * MARK: - Text Locating *============================================================================*/ /** Locate the line and column number for a byte position in a string. This can be used to get better description for error position. @param str The input string. @param len The byte length of the input string. @param pos The byte position within the input string. @param line A pointer to receive the line number, starting from 1. @param col A pointer to receive the column number, starting from 1. @param chr A pointer to receive the character index, starting from 0. @return true on success, false if `str` is NULL or `pos` is out of bounds. @note Line/column/character are calculated based on Unicode characters for compatibility with text editors. For multi-byte UTF-8 characters, the returned value may not directly correspond to the byte position. */ yyjson_api bool yyjson_locate_pos(const char *str, size_t len, size_t pos, size_t *line, size_t *col, size_t *chr); /*============================================================================== * MARK: - JSON Structure *============================================================================*/ /** An immutable document for reading JSON. This document holds memory for all its JSON values and strings. When it is no longer used, the user should call `yyjson_doc_free()` to free its memory. */ typedef struct yyjson_doc yyjson_doc; /** An immutable value for reading JSON. A JSON Value has the same lifetime as its document. The memory is held by its document and and cannot be freed alone. */ typedef struct yyjson_val yyjson_val; /** A mutable document for building JSON. This document holds memory for all its JSON values and strings. When it is no longer used, the user should call `yyjson_mut_doc_free()` to free its memory. */ typedef struct yyjson_mut_doc yyjson_mut_doc; /** A mutable value for building JSON. A JSON Value has the same lifetime as its document. The memory is held by its document and and cannot be freed alone. */ typedef struct yyjson_mut_val yyjson_mut_val; /*============================================================================== * MARK: - JSON Reader API *============================================================================*/ /** Run-time options for JSON reader. */ typedef uint32_t yyjson_read_flag; /** Default option (RFC 8259 compliant): - Read positive integer as uint64_t. - Read negative integer as int64_t. - Read floating-point number as double with round-to-nearest mode. - Read integer which cannot fit in uint64_t or int64_t as double. - Report error if double number is infinity. - Report error if string contains invalid UTF-8 character or BOM. - Report error on trailing commas, comments, inf and nan literals. */ static const yyjson_read_flag YYJSON_READ_NOFLAG = 0; /** Read the input data in-situ. This option allows the reader to modify and use input data to store string values, which can increase reading speed slightly. The caller should hold the input data before free the document. The input data must be padded by at least `YYJSON_PADDING_SIZE` bytes. For example: `[1,2]` should be `[1,2]\0\0\0\0`, input length should be 5. */ static const yyjson_read_flag YYJSON_READ_INSITU = 1 << 0; /** Stop when done instead of issuing an error if there's additional content after a JSON document. This option may be used to parse small pieces of JSON in larger data, such as `NDJSON`. */ static const yyjson_read_flag YYJSON_READ_STOP_WHEN_DONE = 1 << 1; /** Allow single trailing comma at the end of an object or array, such as `[1,2,3,]`, `{"a":1,"b":2,}` (non-standard). */ static const yyjson_read_flag YYJSON_READ_ALLOW_TRAILING_COMMAS = 1 << 2; /** Allow C-style single-line and mult-line comments (non-standard). */ static const yyjson_read_flag YYJSON_READ_ALLOW_COMMENTS = 1 << 3; /** Allow inf/nan number and literal, case-insensitive, such as 1e999, NaN, inf, -Infinity (non-standard). */ static const yyjson_read_flag YYJSON_READ_ALLOW_INF_AND_NAN = 1 << 4; /** Read all numbers as raw strings (value with `YYJSON_TYPE_RAW` type), inf/nan literal is also read as raw with `ALLOW_INF_AND_NAN` flag. */ static const yyjson_read_flag YYJSON_READ_NUMBER_AS_RAW = 1 << 5; /** Allow reading invalid unicode when parsing string values (non-standard). Invalid characters will be allowed to appear in the string values, but invalid escape sequences will still be reported as errors. This flag does not affect the performance of correctly encoded strings. @warning Strings in JSON values may contain incorrect encoding when this option is used, you need to handle these strings carefully to avoid security risks. */ static const yyjson_read_flag YYJSON_READ_ALLOW_INVALID_UNICODE = 1 << 6; /** Read big numbers as raw strings. These big numbers include integers that cannot be represented by `int64_t` and `uint64_t`, and floating-point numbers that cannot be represented by finite `double`. The flag will be overridden by `YYJSON_READ_NUMBER_AS_RAW` flag. */ static const yyjson_read_flag YYJSON_READ_BIGNUM_AS_RAW = 1 << 7; /** Allow UTF-8 BOM and skip it before parsing if any (non-standard). */ static const yyjson_read_flag YYJSON_READ_ALLOW_BOM = 1 << 8; /** Allow extended number formats (non-standard): - Hexadecimal numbers, such as `0x7B`. - Numbers with leading or trailing decimal point, such as `.123`, `123.`. - Numbers with a leading plus sign, such as `+123`. */ static const yyjson_read_flag YYJSON_READ_ALLOW_EXT_NUMBER = 1 << 9; /** Allow extended escape sequences in strings (non-standard): - Additional escapes: `\a`, `\e`, `\v`, ``\'``, `\?`, `\0`. - Hex escapes: `\xNN`, such as `\x7B`. - Line continuation: backslash followed by line terminator sequences. - Unknown escape: if backslash is followed by an unsupported character, the backslash will be removed and the character will be kept as-is. However, `\1`-`\9` will still trigger an error. */ static const yyjson_read_flag YYJSON_READ_ALLOW_EXT_ESCAPE = 1 << 10; /** Allow extended whitespace characters (non-standard): - Vertical tab `\v` and form feed `\f`. - Line separator `\u2028` and paragraph separator `\u2029`. - Non-breaking space `\xA0`. - Byte order mark: `\uFEFF`. - Other Unicode characters in the Zs (Separator, space) category. */ static const yyjson_read_flag YYJSON_READ_ALLOW_EXT_WHITESPACE = 1 << 11; /** Allow strings enclosed in single quotes (non-standard), such as ``'ab'``. */ static const yyjson_read_flag YYJSON_READ_ALLOW_SINGLE_QUOTED_STR = 1 << 12; /** Allow object keys without quotes (non-standard), such as `{a:1,b:2}`. This extends the ECMAScript IdentifierName rule by allowing any non-whitespace character with code point above `U+007F`. */ static const yyjson_read_flag YYJSON_READ_ALLOW_UNQUOTED_KEY = 1 << 13; /** Allow JSON5 format, see: [https://json5.org]. This flag supports all JSON5 features with some additional extensions: - Accepts more escape sequences than JSON5 (e.g. `\a`, `\e`). - Unquoted keys are not limited to ECMAScript IdentifierName. - Allow case-insensitive `NaN`, `Inf` and `Infinity` literals. */ static const yyjson_read_flag YYJSON_READ_JSON5 = (1 << 2) | /* YYJSON_READ_ALLOW_TRAILING_COMMAS */ (1 << 3) | /* YYJSON_READ_ALLOW_COMMENTS */ (1 << 4) | /* YYJSON_READ_ALLOW_INF_AND_NAN */ (1 << 9) | /* YYJSON_READ_ALLOW_EXT_NUMBER */ (1 << 10) | /* YYJSON_READ_ALLOW_EXT_ESCAPE */ (1 << 11) | /* YYJSON_READ_ALLOW_EXT_WHITESPACE */ (1 << 12) | /* YYJSON_READ_ALLOW_SINGLE_QUOTED_STR */ (1 << 13); /* YYJSON_READ_ALLOW_UNQUOTED_KEY */ /** Result code for JSON reader. */ typedef uint32_t yyjson_read_code; /** Success, no error. */ static const yyjson_read_code YYJSON_READ_SUCCESS = 0; /** Invalid parameter, such as NULL input string or 0 input length. */ static const yyjson_read_code YYJSON_READ_ERROR_INVALID_PARAMETER = 1; /** Memory allocation failed. */ static const yyjson_read_code YYJSON_READ_ERROR_MEMORY_ALLOCATION = 2; /** Input JSON string is empty. */ static const yyjson_read_code YYJSON_READ_ERROR_EMPTY_CONTENT = 3; /** Unexpected content after document, such as `[123]abc`. */ static const yyjson_read_code YYJSON_READ_ERROR_UNEXPECTED_CONTENT = 4; /** Unexpected end of input, the parsed part is valid, such as `[123`. */ static const yyjson_read_code YYJSON_READ_ERROR_UNEXPECTED_END = 5; /** Unexpected character inside the document, such as `[abc]`. */ static const yyjson_read_code YYJSON_READ_ERROR_UNEXPECTED_CHARACTER = 6; /** Invalid JSON structure, such as `[1,]`. */ static const yyjson_read_code YYJSON_READ_ERROR_JSON_STRUCTURE = 7; /** Invalid comment, deprecated, use `UNEXPECTED_END` for unclosed comment. */ static const yyjson_read_code YYJSON_READ_ERROR_INVALID_COMMENT = 8; /** Invalid number, such as `123.e12`, `000`. */ static const yyjson_read_code YYJSON_READ_ERROR_INVALID_NUMBER = 9; /** Invalid string, such as invalid escaped character inside a string. */ static const yyjson_read_code YYJSON_READ_ERROR_INVALID_STRING = 10; /** Invalid JSON literal, such as `truu`. */ static const yyjson_read_code YYJSON_READ_ERROR_LITERAL = 11; /** Failed to open a file. */ static const yyjson_read_code YYJSON_READ_ERROR_FILE_OPEN = 12; /** Failed to read a file. */ static const yyjson_read_code YYJSON_READ_ERROR_FILE_READ = 13; /** Incomplete input during incremental parsing; parsing state is preserved. */ static const yyjson_read_code YYJSON_READ_ERROR_MORE = 14; /** Error information for JSON reader. */ typedef struct yyjson_read_err { /** Error code, see `yyjson_read_code` for all possible values. */ yyjson_read_code code; /** Error message, constant, no need to free (NULL if success). */ const char *msg; /** Error byte position for input data (0 if success). */ size_t pos; } yyjson_read_err; #if !defined(YYJSON_DISABLE_READER) || !YYJSON_DISABLE_READER /** Read JSON with options. This function is thread-safe when: 1. The `dat` is not modified by other threads. 2. The `alc` is thread-safe or NULL. @param dat The JSON data (UTF-8 without BOM), null-terminator is not required. If this parameter is NULL, the function will fail and return NULL. The `dat` will not be modified without the flag `YYJSON_READ_INSITU`, so you can pass a `const char *` string and case it to `char *` if you don't use the `YYJSON_READ_INSITU` flag. @param len The length of JSON data in bytes. If this parameter is 0, the function will fail and return NULL. @param flg The JSON read options. Multiple options can be combined with `|` operator. 0 means no options. @param alc The memory allocator used by JSON reader. Pass NULL to use the libc's default allocator. @param err A pointer to receive error information. Pass NULL if you don't need error information. @return A new JSON document, or NULL if an error occurs. When it's no longer needed, it should be freed with `yyjson_doc_free()`. */ yyjson_api yyjson_doc *yyjson_read_opts(char *dat, size_t len, yyjson_read_flag flg, const yyjson_alc *alc, yyjson_read_err *err); /** Read a JSON file. This function is thread-safe when: 1. The file is not modified by other threads. 2. The `alc` is thread-safe or NULL. @param path The JSON file's path. This should be a null-terminated string using the system's native encoding. If this path is NULL or invalid, the function will fail and return NULL. @param flg The JSON read options. Multiple options can be combined with `|` operator. 0 means no options. @param alc The memory allocator used by JSON reader. Pass NULL to use the libc's default allocator. @param err A pointer to receive error information. Pass NULL if you don't need error information. @return A new JSON document, or NULL if an error occurs. When it's no longer needed, it should be freed with `yyjson_doc_free()`. @warning On 32-bit operating system, files larger than 2GB may fail to read. */ yyjson_api yyjson_doc *yyjson_read_file(const char *path, yyjson_read_flag flg, const yyjson_alc *alc, yyjson_read_err *err); /** Read JSON from a file pointer. @param fp The file pointer. The data will be read from the current position of the FILE to the end. If this fp is NULL or invalid, the function will fail and return NULL. @param flg The JSON read options. Multiple options can be combined with `|` operator. 0 means no options. @param alc The memory allocator used by JSON reader. Pass NULL to use the libc's default allocator. @param err A pointer to receive error information. Pass NULL if you don't need error information. @return A new JSON document, or NULL if an error occurs. When it's no longer needed, it should be freed with `yyjson_doc_free()`. @warning On 32-bit operating system, files larger than 2GB may fail to read. */ yyjson_api yyjson_doc *yyjson_read_fp(FILE *fp, yyjson_read_flag flg, const yyjson_alc *alc, yyjson_read_err *err); /** Read a JSON string. This function is thread-safe. @param dat The JSON data (UTF-8 without BOM), null-terminator is not required. If this parameter is NULL, the function will fail and return NULL. @param len The length of JSON data in bytes. If this parameter is 0, the function will fail and return NULL. @param flg The JSON read options. Multiple options can be combined with `|` operator. 0 means no options. @return A new JSON document, or NULL if an error occurs. When it's no longer needed, it should be freed with `yyjson_doc_free()`. */ yyjson_api_inline yyjson_doc *yyjson_read(const char *dat, size_t len, yyjson_read_flag flg) { flg &= ~YYJSON_READ_INSITU; /* const string cannot be modified */ return yyjson_read_opts((char *)(void *)(size_t)(const void *)dat, len, flg, NULL, NULL); } #if !defined(YYJSON_DISABLE_INCR_READER) || !YYJSON_DISABLE_INCR_READER /** Opaque state for incremental JSON reader. */ typedef struct yyjson_incr_state yyjson_incr_state; /** Initialize state for incremental read. To read a large JSON document incrementally: 1. Call `yyjson_incr_new()` to create the state for incremental reading. 2. Call `yyjson_incr_read()` repeatedly. 3. Call `yyjson_incr_free()` to free the state. Note: The incremental JSON reader only supports standard JSON. Flags for non-standard features (e.g. comments, trailing commas) are ignored. @param buf The JSON data, null-terminator is not required. If this parameter is NULL, the function will fail and return NULL. @param buf_len The length of the JSON data in `buf`. If use `YYJSON_READ_INSITU`, `buf_len` should not include the padding size. @param flg The JSON read options. Multiple options can be combined with `|` operator. @param alc The memory allocator used by JSON reader. Pass NULL to use the libc's default allocator. @return A state for incremental reading. It should be freed with `yyjson_incr_free()`. NULL is returned if memory allocation fails. */ yyjson_api yyjson_incr_state *yyjson_incr_new(char *buf, size_t buf_len, yyjson_read_flag flg, const yyjson_alc *alc); /** Performs incremental read of up to `len` bytes. If NULL is returned and `err->code` is set to `YYJSON_READ_ERROR_MORE`, it indicates that more data is required to continue parsing. Then, call this function again with incremented `len`. Continue until a document is returned or an error other than `YYJSON_READ_ERROR_MORE` is returned. Note: Parsing in very small increments is not efficient. An increment of several kilobytes or megabytes is recommended. @param state The state for incremental reading, created using `yyjson_incr_new()`. @param len The number of bytes of JSON data available to parse. If this parameter is 0, the function will fail and return NULL. @param err A pointer to receive error information. @return A new JSON document, or NULL if an error occurs. When the document is no longer needed, it should be freed with `yyjson_doc_free()`. */ yyjson_api yyjson_doc *yyjson_incr_read(yyjson_incr_state *state, size_t len, yyjson_read_err *err); /** Release the incremental read state and free the memory. */ yyjson_api void yyjson_incr_free(yyjson_incr_state *state); #endif /* YYJSON_DISABLE_INCR_READER */ /** Returns the size of maximum memory usage to read a JSON data. You may use this value to avoid malloc() or calloc() call inside the reader to get better performance, or read multiple JSON with one piece of memory. @param len The length of JSON data in bytes. @param flg The JSON read options. @return The maximum memory size to read this JSON, or 0 if overflow. @b Example @code // read multiple JSON with same pre-allocated memory char *dat1, *dat2, *dat3; // JSON data size_t len1, len2, len3; // JSON length size_t max_len = MAX(len1, MAX(len2, len3)); yyjson_doc *doc; // use one allocator for multiple JSON size_t size = yyjson_read_max_memory_usage(max_len, 0); void *buf = malloc(size); yyjson_alc alc; yyjson_alc_pool_init(&alc, buf, size); // no more alloc() or realloc() call during reading doc = yyjson_read_opts(dat1, len1, 0, &alc, NULL); yyjson_doc_free(doc); doc = yyjson_read_opts(dat2, len2, 0, &alc, NULL); yyjson_doc_free(doc); doc = yyjson_read_opts(dat3, len3, 0, &alc, NULL); yyjson_doc_free(doc); free(buf); @endcode @see yyjson_alc_pool_init() */ yyjson_api_inline size_t yyjson_read_max_memory_usage(size_t len, yyjson_read_flag flg) { /* 1. The max value count is (json_size / 2 + 1), for example: "[1,2,3,4]" size is 9, value count is 5. 2. Some broken JSON may cost more memory during reading, but fail at end, for example: "[[[[[[[[". 3. yyjson use 16 bytes per value, see struct yyjson_val. 4. yyjson use dynamic memory with a growth factor of 1.5. The max memory size is (json_size / 2 * 16 * 1.5 + padding). */ size_t mul = (size_t)12 + !(flg & YYJSON_READ_INSITU); size_t pad = 256; size_t max = (size_t)(~(size_t)0); if (flg & YYJSON_READ_STOP_WHEN_DONE) len = len < 256 ? 256 : len; if (len >= (max - pad - mul) / mul) return 0; return len * mul + pad; } /** Read a JSON number. This function is thread-safe when data is not modified by other threads. @param dat The JSON data (UTF-8 without BOM), null-terminator is required. If this parameter is NULL, the function will fail and return NULL. @param val The output value where result is stored. If this parameter is NULL, the function will fail and return NULL. The value will hold either UINT or SINT or REAL number; @param flg The JSON read options. Multiple options can be combined with `|` operator. 0 means no options. Supports `YYJSON_READ_NUMBER_AS_RAW` and `YYJSON_READ_ALLOW_INF_AND_NAN`. @param alc The memory allocator used for long number. It is only used when the built-in floating point reader is disabled. Pass NULL to use the libc's default allocator. @param err A pointer to receive error information. Pass NULL if you don't need error information. @return If successful, a pointer to the character after the last character used in the conversion, NULL if an error occurs. */ yyjson_api const char *yyjson_read_number(const char *dat, yyjson_val *val, yyjson_read_flag flg, const yyjson_alc *alc, yyjson_read_err *err); /** Same as `yyjson_read_number()`. */ yyjson_api_inline const char *yyjson_mut_read_number(const char *dat, yyjson_mut_val *val, yyjson_read_flag flg, const yyjson_alc *alc, yyjson_read_err *err) { return yyjson_read_number(dat, (yyjson_val *)val, flg, alc, err); } #endif /* YYJSON_DISABLE_READER) */ /*============================================================================== * MARK: - JSON Writer API *============================================================================*/ /** Run-time options for JSON writer. */ typedef uint32_t yyjson_write_flag; /** Default option: - Write JSON minify. - Report error on inf or nan number. - Report error on invalid UTF-8 string. - Do not escape unicode or slash. */ static const yyjson_write_flag YYJSON_WRITE_NOFLAG = 0; /** Write JSON pretty with 4 space indent. */ static const yyjson_write_flag YYJSON_WRITE_PRETTY = 1 << 0; /** Escape unicode as `uXXXX`, make the output ASCII only. */ static const yyjson_write_flag YYJSON_WRITE_ESCAPE_UNICODE = 1 << 1; /** Escape '/' as '\/'. */ static const yyjson_write_flag YYJSON_WRITE_ESCAPE_SLASHES = 1 << 2; /** Write inf and nan number as 'Infinity' and 'NaN' literal (non-standard). */ static const yyjson_write_flag YYJSON_WRITE_ALLOW_INF_AND_NAN = 1 << 3; /** Write inf and nan number as null literal. This flag will override `YYJSON_WRITE_ALLOW_INF_AND_NAN` flag. */ static const yyjson_write_flag YYJSON_WRITE_INF_AND_NAN_AS_NULL = 1 << 4; /** Allow invalid unicode when encoding string values (non-standard). Invalid characters in string value will be copied byte by byte. If `YYJSON_WRITE_ESCAPE_UNICODE` flag is also set, invalid character will be escaped as `U+FFFD` (replacement character). This flag does not affect the performance of correctly encoded strings. */ static const yyjson_write_flag YYJSON_WRITE_ALLOW_INVALID_UNICODE = 1 << 5; /** Write JSON pretty with 2 space indent. This flag will override `YYJSON_WRITE_PRETTY` flag. */ static const yyjson_write_flag YYJSON_WRITE_PRETTY_TWO_SPACES = 1 << 6; /** Adds a newline character `\n` at the end of the JSON. This can be helpful for text editors or NDJSON. */ static const yyjson_write_flag YYJSON_WRITE_NEWLINE_AT_END = 1 << 7; /** The highest 8 bits of `yyjson_write_flag` and real number value's `tag` are reserved for controlling the output format of floating-point numbers. */ #define YYJSON_WRITE_FP_FLAG_BITS 8 /** The highest 4 bits of flag are reserved for precision value. */ #define YYJSON_WRITE_FP_PREC_BITS 4 /** Write floating-point number using fixed-point notation. - This is similar to ECMAScript `Number.prototype.toFixed(prec)`, but with trailing zeros removed. The `prec` ranges from 1 to 15. - This will produce shorter output but may lose some precision. */ #define YYJSON_WRITE_FP_TO_FIXED(prec) ((yyjson_write_flag)( \ (uint32_t)((uint32_t)(prec)) << (32 - 4) )) /** Write floating-point numbers using single-precision (float). - This casts `double` to `float` before serialization. - This will produce shorter output, but may lose some precision. - This flag is ignored if `YYJSON_WRITE_FP_TO_FIXED(prec)` is also used. */ #define YYJSON_WRITE_FP_TO_FLOAT ((yyjson_write_flag)(1 << (32 - 5))) /** Result code for JSON writer */ typedef uint32_t yyjson_write_code; /** Success, no error. */ static const yyjson_write_code YYJSON_WRITE_SUCCESS = 0; /** Invalid parameter, such as NULL document. */ static const yyjson_write_code YYJSON_WRITE_ERROR_INVALID_PARAMETER = 1; /** Memory allocation failure occurs. */ static const yyjson_write_code YYJSON_WRITE_ERROR_MEMORY_ALLOCATION = 2; /** Invalid value type in JSON document. */ static const yyjson_write_code YYJSON_WRITE_ERROR_INVALID_VALUE_TYPE = 3; /** NaN or Infinity number occurs. */ static const yyjson_write_code YYJSON_WRITE_ERROR_NAN_OR_INF = 4; /** Failed to open a file. */ static const yyjson_write_code YYJSON_WRITE_ERROR_FILE_OPEN = 5; /** Failed to write a file. */ static const yyjson_write_code YYJSON_WRITE_ERROR_FILE_WRITE = 6; /** Invalid unicode in string. */ static const yyjson_write_code YYJSON_WRITE_ERROR_INVALID_STRING = 7; /** Error information for JSON writer. */ typedef struct yyjson_write_err { /** Error code, see `yyjson_write_code` for all possible values. */ yyjson_write_code code; /** Error message, constant, no need to free (NULL if success). */ const char *msg; } yyjson_write_err; #if !defined(YYJSON_DISABLE_WRITER) || !YYJSON_DISABLE_WRITER /*============================================================================== * MARK: - JSON Document Writer API *============================================================================*/ /** Write a document to JSON string with options. This function is thread-safe when: The `alc` is thread-safe or NULL. @param doc The JSON document. If this doc is NULL or has no root, the function will fail and return false. @param flg The JSON write options. Multiple options can be combined with `|` operator. 0 means no options. @param alc The memory allocator used by JSON writer. Pass NULL to use the libc's default allocator. @param len A pointer to receive output length in bytes (not including the null-terminator). Pass NULL if you don't need length information. @param err A pointer to receive error information. Pass NULL if you don't need error information. @return A new JSON string, or NULL if an error occurs. This string is encoded as UTF-8 with a null-terminator. When it's no longer needed, it should be freed with free() or alc->free(). */ yyjson_api char *yyjson_write_opts(const yyjson_doc *doc, yyjson_write_flag flg, const yyjson_alc *alc, size_t *len, yyjson_write_err *err); /** Write a document to JSON file with options. This function is thread-safe when: 1. The file is not accessed by other threads. 2. The `alc` is thread-safe or NULL. @param path The JSON file's path. This should be a null-terminated string using the system's native encoding. If this path is NULL or invalid, the function will fail and return false. If this file is not empty, the content will be discarded. @param doc The JSON document. If this doc is NULL or has no root, the function will fail and return false. @param flg The JSON write options. Multiple options can be combined with `|` operator. 0 means no options. @param alc The memory allocator used by JSON writer. Pass NULL to use the libc's default allocator. @param err A pointer to receive error information. Pass NULL if you don't need error information. @return true if successful, false if an error occurs. @warning On 32-bit operating system, files larger than 2GB may fail to write. */ yyjson_api bool yyjson_write_file(const char *path, const yyjson_doc *doc, yyjson_write_flag flg, const yyjson_alc *alc, yyjson_write_err *err); /** Write a document to file pointer with options. @param fp The file pointer. The data will be written to the current position of the file. If this fp is NULL or invalid, the function will fail and return false. @param doc The JSON document. If this doc is NULL or has no root, the function will fail and return false. @param flg The JSON write options. Multiple options can be combined with `|` operator. 0 means no options. @param alc The memory allocator used by JSON writer. Pass NULL to use the libc's default allocator. @param err A pointer to receive error information. Pass NULL if you don't need error information. @return true if successful, false if an error occurs. @warning On 32-bit operating system, files larger than 2GB may fail to write. */ yyjson_api bool yyjson_write_fp(FILE *fp, const yyjson_doc *doc, yyjson_write_flag flg, const yyjson_alc *alc, yyjson_write_err *err); /** Write a document to JSON string. This function is thread-safe. @param doc The JSON document. If this doc is NULL or has no root, the function will fail and return false. @param flg The JSON write options. Multiple options can be combined with `|` operator. 0 means no options. @param len A pointer to receive output length in bytes (not including the null-terminator). Pass NULL if you don't need length information. @return A new JSON string, or NULL if an error occurs. This string is encoded as UTF-8 with a null-terminator. When it's no longer needed, it should be freed with free(). */ yyjson_api_inline char *yyjson_write(const yyjson_doc *doc, yyjson_write_flag flg, size_t *len) { return yyjson_write_opts(doc, flg, NULL, len, NULL); } /** Write a document to JSON string with options. This function is thread-safe when: 1. The `doc` is not modified by other threads. 2. The `alc` is thread-safe or NULL. @param doc The mutable JSON document. If this doc is NULL or has no root, the function will fail and return false. @param flg The JSON write options. Multiple options can be combined with `|` operator. 0 means no options. @param alc The memory allocator used by JSON writer. Pass NULL to use the libc's default allocator. @param len A pointer to receive output length in bytes (not including the null-terminator). Pass NULL if you don't need length information. @param err A pointer to receive error information. Pass NULL if you don't need error information. @return A new JSON string, or NULL if an error occurs. This string is encoded as UTF-8 with a null-terminator. When it's no longer needed, it should be freed with free() or alc->free(). */ yyjson_api char *yyjson_mut_write_opts(const yyjson_mut_doc *doc, yyjson_write_flag flg, const yyjson_alc *alc, size_t *len, yyjson_write_err *err); /** Write a document to JSON file with options. This function is thread-safe when: 1. The file is not accessed by other threads. 2. The `doc` is not modified by other threads. 3. The `alc` is thread-safe or NULL. @param path The JSON file's path. This should be a null-terminated string using the system's native encoding. If this path is NULL or invalid, the function will fail and return false. If this file is not empty, the content will be discarded. @param doc The mutable JSON document. If this doc is NULL or has no root, the function will fail and return false. @param flg The JSON write options. Multiple options can be combined with `|` operator. 0 means no options. @param alc The memory allocator used by JSON writer. Pass NULL to use the libc's default allocator. @param err A pointer to receive error information. Pass NULL if you don't need error information. @return true if successful, false if an error occurs. @warning On 32-bit operating system, files larger than 2GB may fail to write. */ yyjson_api bool yyjson_mut_write_file(const char *path, const yyjson_mut_doc *doc, yyjson_write_flag flg, const yyjson_alc *alc, yyjson_write_err *err); /** Write a document to file pointer with options. @param fp The file pointer. The data will be written to the current position of the file. If this fp is NULL or invalid, the function will fail and return false. @param doc The mutable JSON document. If this doc is NULL or has no root, the function will fail and return false. @param flg The JSON write options. Multiple options can be combined with `|` operator. 0 means no options. @param alc The memory allocator used by JSON writer. Pass NULL to use the libc's default allocator. @param err A pointer to receive error information. Pass NULL if you don't need error information. @return true if successful, false if an error occurs. @warning On 32-bit operating system, files larger than 2GB may fail to write. */ yyjson_api bool yyjson_mut_write_fp(FILE *fp, const yyjson_mut_doc *doc, yyjson_write_flag flg, const yyjson_alc *alc, yyjson_write_err *err); /** Write a document to JSON string. This function is thread-safe when: The `doc` is not modified by other threads. @param doc The JSON document. If this doc is NULL or has no root, the function will fail and return false. @param flg The JSON write options. Multiple options can be combined with `|` operator. 0 means no options. @param len A pointer to receive output length in bytes (not including the null-terminator). Pass NULL if you don't need length information. @return A new JSON string, or NULL if an error occurs. This string is encoded as UTF-8 with a null-terminator. When it's no longer needed, it should be freed with free(). */ yyjson_api_inline char *yyjson_mut_write(const yyjson_mut_doc *doc, yyjson_write_flag flg, size_t *len) { return yyjson_mut_write_opts(doc, flg, NULL, len, NULL); } /*============================================================================== * MARK: - JSON Value Writer API *============================================================================*/ /** Write a value to JSON string with options. This function is thread-safe when: The `alc` is thread-safe or NULL. @param val The JSON root value. If this parameter is NULL, the function will fail and return NULL. @param flg The JSON write options. Multiple options can be combined with `|` operator. 0 means no options. @param alc The memory allocator used by JSON writer. Pass NULL to use the libc's default allocator. @param len A pointer to receive output length in bytes (not including the null-terminator). Pass NULL if you don't need length information. @param err A pointer to receive error information. Pass NULL if you don't need error information. @return A new JSON string, or NULL if an error occurs. This string is encoded as UTF-8 with a null-terminator. When it's no longer needed, it should be freed with free() or alc->free(). */ yyjson_api char *yyjson_val_write_opts(const yyjson_val *val, yyjson_write_flag flg, const yyjson_alc *alc, size_t *len, yyjson_write_err *err); /** Write a value to JSON file with options. This function is thread-safe when: 1. The file is not accessed by other threads. 2. The `alc` is thread-safe or NULL. @param path The JSON file's path. This should be a null-terminated string using the system's native encoding. If this path is NULL or invalid, the function will fail and return false. If this file is not empty, the content will be discarded. @param val The JSON root value. If this parameter is NULL, the function will fail and return NULL. @param flg The JSON write options. Multiple options can be combined with `|` operator. 0 means no options. @param alc The memory allocator used by JSON writer. Pass NULL to use the libc's default allocator. @param err A pointer to receive error information. Pass NULL if you don't need error information. @return true if successful, false if an error occurs. @warning On 32-bit operating system, files larger than 2GB may fail to write. */ yyjson_api bool yyjson_val_write_file(const char *path, const yyjson_val *val, yyjson_write_flag flg, const yyjson_alc *alc, yyjson_write_err *err); /** Write a value to file pointer with options. @param fp The file pointer. The data will be written to the current position of the file. If this path is NULL or invalid, the function will fail and return false. @param val The JSON root value. If this parameter is NULL, the function will fail and return NULL. @param flg The JSON write options. Multiple options can be combined with `|` operator. 0 means no options. @param alc The memory allocator used by JSON writer. Pass NULL to use the libc's default allocator. @param err A pointer to receive error information. Pass NULL if you don't need error information. @return true if successful, false if an error occurs. @warning On 32-bit operating system, files larger than 2GB may fail to write. */ yyjson_api bool yyjson_val_write_fp(FILE *fp, const yyjson_val *val, yyjson_write_flag flg, const yyjson_alc *alc, yyjson_write_err *err); /** Write a value to JSON string. This function is thread-safe. @param val The JSON root value. If this parameter is NULL, the function will fail and return NULL. @param flg The JSON write options. Multiple options can be combined with `|` operator. 0 means no options. @param len A pointer to receive output length in bytes (not including the null-terminator). Pass NULL if you don't need length information. @return A new JSON string, or NULL if an error occurs. This string is encoded as UTF-8 with a null-terminator. When it's no longer needed, it should be freed with free(). */ yyjson_api_inline char *yyjson_val_write(const yyjson_val *val, yyjson_write_flag flg, size_t *len) { return yyjson_val_write_opts(val, flg, NULL, len, NULL); } /** Write a value to JSON string with options. This function is thread-safe when: 1. The `val` is not modified by other threads. 2. The `alc` is thread-safe or NULL. @param val The mutable JSON root value. If this parameter is NULL, the function will fail and return NULL. @param flg The JSON write options. Multiple options can be combined with `|` operator. 0 means no options. @param alc The memory allocator used by JSON writer. Pass NULL to use the libc's default allocator. @param len A pointer to receive output length in bytes (not including the null-terminator). Pass NULL if you don't need length information. @param err A pointer to receive error information. Pass NULL if you don't need error information. @return A new JSON string, or NULL if an error occurs. This string is encoded as UTF-8 with a null-terminator. When it's no longer needed, it should be freed with free() or alc->free(). */ yyjson_api char *yyjson_mut_val_write_opts(const yyjson_mut_val *val, yyjson_write_flag flg, const yyjson_alc *alc, size_t *len, yyjson_write_err *err); /** Write a value to JSON file with options. This function is thread-safe when: 1. The file is not accessed by other threads. 2. The `val` is not modified by other threads. 3. The `alc` is thread-safe or NULL. @param path The JSON file's path. This should be a null-terminated string using the system's native encoding. If this path is NULL or invalid, the function will fail and return false. If this file is not empty, the content will be discarded. @param val The mutable JSON root value. If this parameter is NULL, the function will fail and return NULL. @param flg The JSON write options. Multiple options can be combined with `|` operator. 0 means no options. @param alc The memory allocator used by JSON writer. Pass NULL to use the libc's default allocator. @param err A pointer to receive error information. Pass NULL if you don't need error information. @return true if successful, false if an error occurs. @warning On 32-bit operating system, files larger than 2GB may fail to write. */ yyjson_api bool yyjson_mut_val_write_file(const char *path, const yyjson_mut_val *val, yyjson_write_flag flg, const yyjson_alc *alc, yyjson_write_err *err); /** Write a value to JSON file with options. @param fp The file pointer. The data will be written to the current position of the file. If this path is NULL or invalid, the function will fail and return false. @param val The mutable JSON root value. If this parameter is NULL, the function will fail and return NULL. @param flg The JSON write options. Multiple options can be combined with `|` operator. 0 means no options. @param alc The memory allocator used by JSON writer. Pass NULL to use the libc's default allocator. @param err A pointer to receive error information. Pass NULL if you don't need error information. @return true if successful, false if an error occurs. @warning On 32-bit operating system, files larger than 2GB may fail to write. */ yyjson_api bool yyjson_mut_val_write_fp(FILE *fp, const yyjson_mut_val *val, yyjson_write_flag flg, const yyjson_alc *alc, yyjson_write_err *err); /** Write a value to JSON string. This function is thread-safe when: The `val` is not modified by other threads. @param val The JSON root value. If this parameter is NULL, the function will fail and return NULL. @param flg The JSON write options. Multiple options can be combined with `|` operator. 0 means no options. @param len A pointer to receive output length in bytes (not including the null-terminator). Pass NULL if you don't need length information. @return A new JSON string, or NULL if an error occurs. This string is encoded as UTF-8 with a null-terminator. When it's no longer needed, it should be freed with free(). */ yyjson_api_inline char *yyjson_mut_val_write(const yyjson_mut_val *val, yyjson_write_flag flg, size_t *len) { return yyjson_mut_val_write_opts(val, flg, NULL, len, NULL); } /** Write a JSON number. @param val A JSON number value to be converted to a string. If this parameter is invalid, the function will fail and return NULL. @param buf A buffer to store the resulting null-terminated string. If this parameter is NULL, the function will fail and return NULL. For integer values, the buffer must be at least 21 bytes. For floating-point values, the buffer must be at least 40 bytes. @return On success, returns a pointer to the character after the last written character. On failure, returns NULL. @note - This function is thread-safe and does not allocate memory (when `YYJSON_DISABLE_FAST_FP_CONV` is not defined). - This function will fail and return NULL only in the following cases: 1) `val` or `buf` is NULL; 2) `val` is not a number type; 3) `val` is `inf` or `nan`, and non-standard JSON is explicitly disabled via the `YYJSON_DISABLE_NON_STANDARD` flag. */ yyjson_api char *yyjson_write_number(const yyjson_val *val, char *buf); /** Same as `yyjson_write_number()`. */ yyjson_api_inline char *yyjson_mut_write_number(const yyjson_mut_val *val, char *buf) { return yyjson_write_number((const yyjson_val *)val, buf); } #endif /* YYJSON_DISABLE_WRITER */ /*============================================================================== * MARK: - JSON Document API *============================================================================*/ /** Returns the root value of this JSON document. Returns NULL if `doc` is NULL. */ yyjson_api_inline yyjson_val *yyjson_doc_get_root(yyjson_doc *doc); /** Returns read size of input JSON data. Returns 0 if `doc` is NULL. For example: the read size of `[1,2,3]` is 7 bytes. */ yyjson_api_inline size_t yyjson_doc_get_read_size(yyjson_doc *doc); /** Returns total value count in this JSON document. Returns 0 if `doc` is NULL. For example: the value count of `[1,2,3]` is 4. */ yyjson_api_inline size_t yyjson_doc_get_val_count(yyjson_doc *doc); /** Release the JSON document and free the memory. After calling this function, the `doc` and all values from the `doc` are no longer available. This function will do nothing if the `doc` is NULL. */ yyjson_api_inline void yyjson_doc_free(yyjson_doc *doc); /*============================================================================== * MARK: - JSON Value Type API *============================================================================*/ /** Returns whether the JSON value is raw. Returns false if `val` is NULL. */ yyjson_api_inline bool yyjson_is_raw(yyjson_val *val); /** Returns whether the JSON value is `null`. Returns false if `val` is NULL. */ yyjson_api_inline bool yyjson_is_null(yyjson_val *val); /** Returns whether the JSON value is `true`. Returns false if `val` is NULL. */ yyjson_api_inline bool yyjson_is_true(yyjson_val *val); /** Returns whether the JSON value is `false`. Returns false if `val` is NULL. */ yyjson_api_inline bool yyjson_is_false(yyjson_val *val); /** Returns whether the JSON value is bool (true/false). Returns false if `val` is NULL. */ yyjson_api_inline bool yyjson_is_bool(yyjson_val *val); /** Returns whether the JSON value is unsigned integer (uint64_t). Returns false if `val` is NULL. */ yyjson_api_inline bool yyjson_is_uint(yyjson_val *val); /** Returns whether the JSON value is signed integer (int64_t). Returns false if `val` is NULL. */ yyjson_api_inline bool yyjson_is_sint(yyjson_val *val); /** Returns whether the JSON value is integer (uint64_t/int64_t). Returns false if `val` is NULL. */ yyjson_api_inline bool yyjson_is_int(yyjson_val *val); /** Returns whether the JSON value is real number (double). Returns false if `val` is NULL. */ yyjson_api_inline bool yyjson_is_real(yyjson_val *val); /** Returns whether the JSON value is number (uint64_t/int64_t/double). Returns false if `val` is NULL. */ yyjson_api_inline bool yyjson_is_num(yyjson_val *val); /** Returns whether the JSON value is string. Returns false if `val` is NULL. */ yyjson_api_inline bool yyjson_is_str(yyjson_val *val); /** Returns whether the JSON value is array. Returns false if `val` is NULL. */ yyjson_api_inline bool yyjson_is_arr(yyjson_val *val); /** Returns whether the JSON value is object. Returns false if `val` is NULL. */ yyjson_api_inline bool yyjson_is_obj(yyjson_val *val); /** Returns whether the JSON value is container (array/object). Returns false if `val` is NULL. */ yyjson_api_inline bool yyjson_is_ctn(yyjson_val *val); /*============================================================================== * MARK: - JSON Value Content API *============================================================================*/ /** Returns the JSON value's type. Returns YYJSON_TYPE_NONE if `val` is NULL. */ yyjson_api_inline yyjson_type yyjson_get_type(yyjson_val *val); /** Returns the JSON value's subtype. Returns YYJSON_SUBTYPE_NONE if `val` is NULL. */ yyjson_api_inline yyjson_subtype yyjson_get_subtype(yyjson_val *val); /** Returns the JSON value's tag. Returns 0 if `val` is NULL. */ yyjson_api_inline uint8_t yyjson_get_tag(yyjson_val *val); /** Returns the JSON value's type description. The return value should be one of these strings: "raw", "null", "string", "array", "object", "true", "false", "uint", "sint", "real", "unknown". */ yyjson_api_inline const char *yyjson_get_type_desc(yyjson_val *val); /** Returns the content if the value is raw. Returns NULL if `val` is NULL or type is not raw. */ yyjson_api_inline const char *yyjson_get_raw(yyjson_val *val); /** Returns the content if the value is bool. Returns false if `val` is NULL or type is not bool. */ yyjson_api_inline bool yyjson_get_bool(yyjson_val *val); /** Returns the content and cast to uint64_t. Returns 0 if `val` is NULL or type is not integer(sint/uint). */ yyjson_api_inline uint64_t yyjson_get_uint(yyjson_val *val); /** Returns the content and cast to int64_t. Returns 0 if `val` is NULL or type is not integer(sint/uint). */ yyjson_api_inline int64_t yyjson_get_sint(yyjson_val *val); /** Returns the content and cast to int. Returns 0 if `val` is NULL or type is not integer(sint/uint). */ yyjson_api_inline int yyjson_get_int(yyjson_val *val); /** Returns the content if the value is real number, or 0.0 on error. Returns 0.0 if `val` is NULL or type is not real(double). */ yyjson_api_inline double yyjson_get_real(yyjson_val *val); /** Returns the content and typecast to `double` if the value is number. Returns 0.0 if `val` is NULL or type is not number(uint/sint/real). */ yyjson_api_inline double yyjson_get_num(yyjson_val *val); /** Returns the content if the value is string. Returns NULL if `val` is NULL or type is not string. */ yyjson_api_inline const char *yyjson_get_str(yyjson_val *val); /** Returns the content length (string length, array size, object size. Returns 0 if `val` is NULL or type is not string/array/object. */ yyjson_api_inline size_t yyjson_get_len(yyjson_val *val); /** Returns whether the JSON value is equals to a string. Returns false if input is NULL or type is not string. */ yyjson_api_inline bool yyjson_equals_str(yyjson_val *val, const char *str); /** Returns whether the JSON value is equals to a string. The `str` should be a UTF-8 string, null-terminator is not required. Returns false if input is NULL or type is not string. */ yyjson_api_inline bool yyjson_equals_strn(yyjson_val *val, const char *str, size_t len); /** Returns whether two JSON values are equal (deep compare). Returns false if input is NULL. @note the result may be inaccurate if object has duplicate keys. @warning This function is recursive and may cause a stack overflow if the object level is too deep. */ yyjson_api_inline bool yyjson_equals(yyjson_val *lhs, yyjson_val *rhs); /** Set the value to raw. Returns false if input is NULL or `val` is object or array. @warning This will modify the `immutable` value, use with caution. */ yyjson_api_inline bool yyjson_set_raw(yyjson_val *val, const char *raw, size_t len); /** Set the value to null. Returns false if input is NULL or `val` is object or array. @warning This will modify the `immutable` value, use with caution. */ yyjson_api_inline bool yyjson_set_null(yyjson_val *val); /** Set the value to bool. Returns false if input is NULL or `val` is object or array. @warning This will modify the `immutable` value, use with caution. */ yyjson_api_inline bool yyjson_set_bool(yyjson_val *val, bool num); /** Set the value to uint. Returns false if input is NULL or `val` is object or array. @warning This will modify the `immutable` value, use with caution. */ yyjson_api_inline bool yyjson_set_uint(yyjson_val *val, uint64_t num); /** Set the value to sint. Returns false if input is NULL or `val` is object or array. @warning This will modify the `immutable` value, use with caution. */ yyjson_api_inline bool yyjson_set_sint(yyjson_val *val, int64_t num); /** Set the value to int. Returns false if input is NULL or `val` is object or array. @warning This will modify the `immutable` value, use with caution. */ yyjson_api_inline bool yyjson_set_int(yyjson_val *val, int num); /** Set the value to float. Returns false if input is NULL or `val` is object or array. @warning This will modify the `immutable` value, use with caution. */ yyjson_api_inline bool yyjson_set_float(yyjson_val *val, float num); /** Set the value to double. Returns false if input is NULL or `val` is object or array. @warning This will modify the `immutable` value, use with caution. */ yyjson_api_inline bool yyjson_set_double(yyjson_val *val, double num); /** Set the value to real. Returns false if input is NULL or `val` is object or array. @warning This will modify the `immutable` value, use with caution. */ yyjson_api_inline bool yyjson_set_real(yyjson_val *val, double num); /** Set the floating-point number's output format to fixed-point notation. Returns false if input is NULL or `val` is not real type. @see YYJSON_WRITE_FP_TO_FIXED flag. @warning This will modify the `immutable` value, use with caution. */ yyjson_api_inline bool yyjson_set_fp_to_fixed(yyjson_val *val, int prec); /** Set the floating-point number's output format to single-precision. Returns false if input is NULL or `val` is not real type. @see YYJSON_WRITE_FP_TO_FLOAT flag. @warning This will modify the `immutable` value, use with caution. */ yyjson_api_inline bool yyjson_set_fp_to_float(yyjson_val *val, bool flt); /** Set the value to string (null-terminated). Returns false if input is NULL or `val` is object or array. @warning This will modify the `immutable` value, use with caution. */ yyjson_api_inline bool yyjson_set_str(yyjson_val *val, const char *str); /** Set the value to string (with length). Returns false if input is NULL or `val` is object or array. @warning This will modify the `immutable` value, use with caution. */ yyjson_api_inline bool yyjson_set_strn(yyjson_val *val, const char *str, size_t len); /** Marks this string as not needing to be escaped during JSON writing. This can be used to avoid the overhead of escaping if the string contains only characters that do not require escaping. Returns false if input is NULL or `val` is not string. @see YYJSON_SUBTYPE_NOESC subtype. @warning This will modify the `immutable` value, use with caution. */ yyjson_api_inline bool yyjson_set_str_noesc(yyjson_val *val, bool noesc); /*============================================================================== * MARK: - JSON Array API *============================================================================*/ /** Returns the number of elements in this array. Returns 0 if `arr` is NULL or type is not array. */ yyjson_api_inline size_t yyjson_arr_size(yyjson_val *arr); /** Returns the element at the specified position in this array. Returns NULL if array is NULL/empty or the index is out of bounds. @warning This function takes a linear search time if array is not flat. For example: `[1,{},3]` is flat, `[1,[2],3]` is not flat. */ yyjson_api_inline yyjson_val *yyjson_arr_get(yyjson_val *arr, size_t idx); /** Returns the first element of this array. Returns NULL if `arr` is NULL/empty or type is not array. */ yyjson_api_inline yyjson_val *yyjson_arr_get_first(yyjson_val *arr); /** Returns the last element of this array. Returns NULL if `arr` is NULL/empty or type is not array. @warning This function takes a linear search time if array is not flat. For example: `[1,{},3]` is flat, `[1,[2],3]` is not flat.*/ yyjson_api_inline yyjson_val *yyjson_arr_get_last(yyjson_val *arr); /*============================================================================== * MARK: - JSON Array Iterator API *============================================================================*/ /** A JSON array iterator. @b Example @code yyjson_val *val; yyjson_arr_iter iter = yyjson_arr_iter_with(arr); while ((val = yyjson_arr_iter_next(&iter))) { your_func(val); } @endcode */ typedef struct yyjson_arr_iter { size_t idx; /**< next value's index */ size_t max; /**< maximum index (arr.size) */ yyjson_val *cur; /**< next value */ } yyjson_arr_iter; /** Initialize an iterator for this array. @param arr The array to be iterated over. If this parameter is NULL or not an array, `iter` will be set to empty. @param iter The iterator to be initialized. If this parameter is NULL, the function will fail and return false. @return true if the `iter` has been successfully initialized. @note The iterator does not need to be destroyed. */ yyjson_api_inline bool yyjson_arr_iter_init(yyjson_val *arr, yyjson_arr_iter *iter); /** Create an iterator with an array , same as `yyjson_arr_iter_init()`. @param arr The array to be iterated over. If this parameter is NULL or not an array, an empty iterator will returned. @return A new iterator for the array. @note The iterator does not need to be destroyed. */ yyjson_api_inline yyjson_arr_iter yyjson_arr_iter_with(yyjson_val *arr); /** Returns whether the iteration has more elements. If `iter` is NULL, this function will return false. */ yyjson_api_inline bool yyjson_arr_iter_has_next(yyjson_arr_iter *iter); /** Returns the next element in the iteration, or NULL on end. If `iter` is NULL, this function will return NULL. */ yyjson_api_inline yyjson_val *yyjson_arr_iter_next(yyjson_arr_iter *iter); /** Macro for iterating over an array. It works like iterator, but with a more intuitive API. @b Example @code size_t idx, max; yyjson_val *val; yyjson_arr_foreach(arr, idx, max, val) { your_func(idx, val); } @endcode */ #define yyjson_arr_foreach(arr, idx, max, val) \ for ((idx) = 0, \ (max) = yyjson_arr_size(arr), \ (val) = yyjson_arr_get_first(arr); \ (idx) < (max); \ (idx)++, \ (val) = unsafe_yyjson_get_next(val)) /*============================================================================== * MARK: - JSON Object API *============================================================================*/ /** Returns the number of key-value pairs in this object. Returns 0 if `obj` is NULL or type is not object. */ yyjson_api_inline size_t yyjson_obj_size(yyjson_val *obj); /** Returns the value to which the specified key is mapped. Returns NULL if this object contains no mapping for the key. Returns NULL if `obj/key` is NULL, or type is not object. The `key` should be a null-terminated UTF-8 string. @warning This function takes a linear search time. */ yyjson_api_inline yyjson_val *yyjson_obj_get(yyjson_val *obj, const char *key); /** Returns the value to which the specified key is mapped. Returns NULL if this object contains no mapping for the key. Returns NULL if `obj/key` is NULL, or type is not object. The `key` should be a UTF-8 string, null-terminator is not required. The `key_len` should be the length of the key, in bytes. @warning This function takes a linear search time. */ yyjson_api_inline yyjson_val *yyjson_obj_getn(yyjson_val *obj, const char *key, size_t key_len); /*============================================================================== * MARK: - JSON Object Iterator API *============================================================================*/ /** A JSON object iterator. @b Example @code yyjson_val *key, *val; yyjson_obj_iter iter = yyjson_obj_iter_with(obj); while ((key = yyjson_obj_iter_next(&iter))) { val = yyjson_obj_iter_get_val(key); your_func(key, val); } @endcode If the ordering of the keys is known at compile-time, you can use this method to speed up value lookups: @code // {"k1":1, "k2": 3, "k3": 3} yyjson_val *key, *val; yyjson_obj_iter iter = yyjson_obj_iter_with(obj); yyjson_val *v1 = yyjson_obj_iter_get(&iter, "k1"); yyjson_val *v3 = yyjson_obj_iter_get(&iter, "k3"); @endcode @see yyjson_obj_iter_get() and yyjson_obj_iter_getn() */ typedef struct yyjson_obj_iter { size_t idx; /**< next key's index */ size_t max; /**< maximum key index (obj.size) */ yyjson_val *cur; /**< next key */ yyjson_val *obj; /**< the object being iterated */ } yyjson_obj_iter; /** Initialize an iterator for this object. @param obj The object to be iterated over. If this parameter is NULL or not an object, `iter` will be set to empty. @param iter The iterator to be initialized. If this parameter is NULL, the function will fail and return false. @return true if the `iter` has been successfully initialized. @note The iterator does not need to be destroyed. */ yyjson_api_inline bool yyjson_obj_iter_init(yyjson_val *obj, yyjson_obj_iter *iter); /** Create an iterator with an object, same as `yyjson_obj_iter_init()`. @param obj The object to be iterated over. If this parameter is NULL or not an object, an empty iterator will returned. @return A new iterator for the object. @note The iterator does not need to be destroyed. */ yyjson_api_inline yyjson_obj_iter yyjson_obj_iter_with(yyjson_val *obj); /** Returns whether the iteration has more elements. If `iter` is NULL, this function will return false. */ yyjson_api_inline bool yyjson_obj_iter_has_next(yyjson_obj_iter *iter); /** Returns the next key in the iteration, or NULL on end. If `iter` is NULL, this function will return NULL. */ yyjson_api_inline yyjson_val *yyjson_obj_iter_next(yyjson_obj_iter *iter); /** Returns the value for key inside the iteration. If `iter` is NULL, this function will return NULL. */ yyjson_api_inline yyjson_val *yyjson_obj_iter_get_val(yyjson_val *key); /** Iterates to a specified key and returns the value. This function does the same thing as `yyjson_obj_get()`, but is much faster if the ordering of the keys is known at compile-time and you are using the same order to look up the values. If the key exists in this object, then the iterator will stop at the next key, otherwise the iterator will not change and NULL is returned. @param iter The object iterator, should not be NULL. @param key The key, should be a UTF-8 string with null-terminator. @return The value to which the specified key is mapped. NULL if this object contains no mapping for the key or input is invalid. @warning This function takes a linear search time if the key is not nearby. */ yyjson_api_inline yyjson_val *yyjson_obj_iter_get(yyjson_obj_iter *iter, const char *key); /** Iterates to a specified key and returns the value. This function does the same thing as `yyjson_obj_getn()`, but is much faster if the ordering of the keys is known at compile-time and you are using the same order to look up the values. If the key exists in this object, then the iterator will stop at the next key, otherwise the iterator will not change and NULL is returned. @param iter The object iterator, should not be NULL. @param key The key, should be a UTF-8 string, null-terminator is not required. @param key_len The the length of `key`, in bytes. @return The value to which the specified key is mapped. NULL if this object contains no mapping for the key or input is invalid. @warning This function takes a linear search time if the key is not nearby. */ yyjson_api_inline yyjson_val *yyjson_obj_iter_getn(yyjson_obj_iter *iter, const char *key, size_t key_len); /** Macro for iterating over an object. It works like iterator, but with a more intuitive API. @b Example @code size_t idx, max; yyjson_val *key, *val; yyjson_obj_foreach(obj, idx, max, key, val) { your_func(key, val); } @endcode */ #define yyjson_obj_foreach(obj, idx, max, key, val) \ for ((idx) = 0, \ (max) = yyjson_obj_size(obj), \ (key) = (obj) ? unsafe_yyjson_get_first(obj) : NULL, \ (val) = (key) + 1; \ (idx) < (max); \ (idx)++, \ (key) = unsafe_yyjson_get_next(val), \ (val) = (key) + 1) /*============================================================================== * MARK: - Mutable JSON Document API *============================================================================*/ /** Returns the root value of this JSON document. Returns NULL if `doc` is NULL. */ yyjson_api_inline yyjson_mut_val *yyjson_mut_doc_get_root(yyjson_mut_doc *doc); /** Sets the root value of this JSON document. Pass NULL to clear root value of the document. */ yyjson_api_inline void yyjson_mut_doc_set_root(yyjson_mut_doc *doc, yyjson_mut_val *root); /** Set the string pool size for a mutable document. This function does not allocate memory immediately, but uses the size when the next memory allocation is needed. If the caller knows the approximate bytes of strings that the document needs to store (e.g. copy string with `yyjson_mut_strcpy` function), setting a larger size can avoid multiple memory allocations and improve performance. @param doc The mutable document. @param len The desired string pool size in bytes (total string length). @return true if successful, false if size is 0 or overflow. */ yyjson_api bool yyjson_mut_doc_set_str_pool_size(yyjson_mut_doc *doc, size_t len); /** Set the value pool size for a mutable document. This function does not allocate memory immediately, but uses the size when the next memory allocation is needed. If the caller knows the approximate number of values that the document needs to store (e.g. create new value with `yyjson_mut_xxx` functions), setting a larger size can avoid multiple memory allocations and improve performance. @param doc The mutable document. @param count The desired value pool size (number of `yyjson_mut_val`). @return true if successful, false if size is 0 or overflow. */ yyjson_api bool yyjson_mut_doc_set_val_pool_size(yyjson_mut_doc *doc, size_t count); /** Release the JSON document and free the memory. After calling this function, the `doc` and all values from the `doc` are no longer available. This function will do nothing if the `doc` is NULL. */ yyjson_api void yyjson_mut_doc_free(yyjson_mut_doc *doc); /** Creates and returns a new mutable JSON document, returns NULL on error. If allocator is NULL, the default allocator will be used. */ yyjson_api yyjson_mut_doc *yyjson_mut_doc_new(const yyjson_alc *alc); /** Copies and returns a new mutable document from input, returns NULL on error. This makes a `deep-copy` on the immutable document. If allocator is NULL, the default allocator will be used. @note `imut_doc` -> `mut_doc`. */ yyjson_api yyjson_mut_doc *yyjson_doc_mut_copy(yyjson_doc *doc, const yyjson_alc *alc); /** Copies and returns a new mutable document from input, returns NULL on error. This makes a `deep-copy` on the mutable document. If allocator is NULL, the default allocator will be used. @note `mut_doc` -> `mut_doc`. */ yyjson_api yyjson_mut_doc *yyjson_mut_doc_mut_copy(yyjson_mut_doc *doc, const yyjson_alc *alc); /** Copies and returns a new mutable value from input, returns NULL on error. This makes a `deep-copy` on the immutable value. The memory was managed by mutable document. @note `imut_val` -> `mut_val`. */ yyjson_api yyjson_mut_val *yyjson_val_mut_copy(yyjson_mut_doc *doc, yyjson_val *val); /** Copies and returns a new mutable value from input, returns NULL on error. This makes a `deep-copy` on the mutable value. The memory was managed by mutable document. @note `mut_val` -> `mut_val`. @warning This function is recursive and may cause a stack overflow if the object level is too deep. */ yyjson_api yyjson_mut_val *yyjson_mut_val_mut_copy(yyjson_mut_doc *doc, yyjson_mut_val *val); /** Copies and returns a new immutable document from input, returns NULL on error. This makes a `deep-copy` on the mutable document. The returned document should be freed with `yyjson_doc_free()`. @note `mut_doc` -> `imut_doc`. @warning This function is recursive and may cause a stack overflow if the object level is too deep. */ yyjson_api yyjson_doc *yyjson_mut_doc_imut_copy(yyjson_mut_doc *doc, const yyjson_alc *alc); /** Copies and returns a new immutable document from input, returns NULL on error. This makes a `deep-copy` on the mutable value. The returned document should be freed with `yyjson_doc_free()`. @note `mut_val` -> `imut_doc`. @warning This function is recursive and may cause a stack overflow if the object level is too deep. */ yyjson_api yyjson_doc *yyjson_mut_val_imut_copy(yyjson_mut_val *val, const yyjson_alc *alc); /*============================================================================== * MARK: - Mutable JSON Value Type API *============================================================================*/ /** Returns whether the JSON value is raw. Returns false if `val` is NULL. */ yyjson_api_inline bool yyjson_mut_is_raw(yyjson_mut_val *val); /** Returns whether the JSON value is `null`. Returns false if `val` is NULL. */ yyjson_api_inline bool yyjson_mut_is_null(yyjson_mut_val *val); /** Returns whether the JSON value is `true`. Returns false if `val` is NULL. */ yyjson_api_inline bool yyjson_mut_is_true(yyjson_mut_val *val); /** Returns whether the JSON value is `false`. Returns false if `val` is NULL. */ yyjson_api_inline bool yyjson_mut_is_false(yyjson_mut_val *val); /** Returns whether the JSON value is bool (true/false). Returns false if `val` is NULL. */ yyjson_api_inline bool yyjson_mut_is_bool(yyjson_mut_val *val); /** Returns whether the JSON value is unsigned integer (uint64_t). Returns false if `val` is NULL. */ yyjson_api_inline bool yyjson_mut_is_uint(yyjson_mut_val *val); /** Returns whether the JSON value is signed integer (int64_t). Returns false if `val` is NULL. */ yyjson_api_inline bool yyjson_mut_is_sint(yyjson_mut_val *val); /** Returns whether the JSON value is integer (uint64_t/int64_t). Returns false if `val` is NULL. */ yyjson_api_inline bool yyjson_mut_is_int(yyjson_mut_val *val); /** Returns whether the JSON value is real number (double). Returns false if `val` is NULL. */ yyjson_api_inline bool yyjson_mut_is_real(yyjson_mut_val *val); /** Returns whether the JSON value is number (uint/sint/real). Returns false if `val` is NULL. */ yyjson_api_inline bool yyjson_mut_is_num(yyjson_mut_val *val); /** Returns whether the JSON value is string. Returns false if `val` is NULL. */ yyjson_api_inline bool yyjson_mut_is_str(yyjson_mut_val *val); /** Returns whether the JSON value is array. Returns false if `val` is NULL. */ yyjson_api_inline bool yyjson_mut_is_arr(yyjson_mut_val *val); /** Returns whether the JSON value is object. Returns false if `val` is NULL. */ yyjson_api_inline bool yyjson_mut_is_obj(yyjson_mut_val *val); /** Returns whether the JSON value is container (array/object). Returns false if `val` is NULL. */ yyjson_api_inline bool yyjson_mut_is_ctn(yyjson_mut_val *val); /*============================================================================== * MARK: - Mutable JSON Value Content API *============================================================================*/ /** Returns the JSON value's type. Returns `YYJSON_TYPE_NONE` if `val` is NULL. */ yyjson_api_inline yyjson_type yyjson_mut_get_type(yyjson_mut_val *val); /** Returns the JSON value's subtype. Returns `YYJSON_SUBTYPE_NONE` if `val` is NULL. */ yyjson_api_inline yyjson_subtype yyjson_mut_get_subtype(yyjson_mut_val *val); /** Returns the JSON value's tag. Returns 0 if `val` is NULL. */ yyjson_api_inline uint8_t yyjson_mut_get_tag(yyjson_mut_val *val); /** Returns the JSON value's type description. The return value should be one of these strings: "raw", "null", "string", "array", "object", "true", "false", "uint", "sint", "real", "unknown". */ yyjson_api_inline const char *yyjson_mut_get_type_desc(yyjson_mut_val *val); /** Returns the content if the value is raw. Returns NULL if `val` is NULL or type is not raw. */ yyjson_api_inline const char *yyjson_mut_get_raw(yyjson_mut_val *val); /** Returns the content if the value is bool. Returns NULL if `val` is NULL or type is not bool. */ yyjson_api_inline bool yyjson_mut_get_bool(yyjson_mut_val *val); /** Returns the content and cast to uint64_t. Returns 0 if `val` is NULL or type is not integer(sint/uint). */ yyjson_api_inline uint64_t yyjson_mut_get_uint(yyjson_mut_val *val); /** Returns the content and cast to int64_t. Returns 0 if `val` is NULL or type is not integer(sint/uint). */ yyjson_api_inline int64_t yyjson_mut_get_sint(yyjson_mut_val *val); /** Returns the content and cast to int. Returns 0 if `val` is NULL or type is not integer(sint/uint). */ yyjson_api_inline int yyjson_mut_get_int(yyjson_mut_val *val); /** Returns the content if the value is real number. Returns 0.0 if `val` is NULL or type is not real(double). */ yyjson_api_inline double yyjson_mut_get_real(yyjson_mut_val *val); /** Returns the content and typecast to `double` if the value is number. Returns 0.0 if `val` is NULL or type is not number(uint/sint/real). */ yyjson_api_inline double yyjson_mut_get_num(yyjson_mut_val *val); /** Returns the content if the value is string. Returns NULL if `val` is NULL or type is not string. */ yyjson_api_inline const char *yyjson_mut_get_str(yyjson_mut_val *val); /** Returns the content length (string length, array size, object size. Returns 0 if `val` is NULL or type is not string/array/object. */ yyjson_api_inline size_t yyjson_mut_get_len(yyjson_mut_val *val); /** Returns whether the JSON value is equals to a string. The `str` should be a null-terminated UTF-8 string. Returns false if input is NULL or type is not string. */ yyjson_api_inline bool yyjson_mut_equals_str(yyjson_mut_val *val, const char *str); /** Returns whether the JSON value is equals to a string. The `str` should be a UTF-8 string, null-terminator is not required. Returns false if input is NULL or type is not string. */ yyjson_api_inline bool yyjson_mut_equals_strn(yyjson_mut_val *val, const char *str, size_t len); /** Returns whether two JSON values are equal (deep compare). Returns false if input is NULL. @note the result may be inaccurate if object has duplicate keys. @warning This function is recursive and may cause a stack overflow if the object level is too deep. */ yyjson_api_inline bool yyjson_mut_equals(yyjson_mut_val *lhs, yyjson_mut_val *rhs); /** Set the value to raw. Returns false if input is NULL. @warning This function should not be used on an existing object or array. */ yyjson_api_inline bool yyjson_mut_set_raw(yyjson_mut_val *val, const char *raw, size_t len); /** Set the value to null. Returns false if input is NULL. @warning This function should not be used on an existing object or array. */ yyjson_api_inline bool yyjson_mut_set_null(yyjson_mut_val *val); /** Set the value to bool. Returns false if input is NULL. @warning This function should not be used on an existing object or array. */ yyjson_api_inline bool yyjson_mut_set_bool(yyjson_mut_val *val, bool num); /** Set the value to uint. Returns false if input is NULL. @warning This function should not be used on an existing object or array. */ yyjson_api_inline bool yyjson_mut_set_uint(yyjson_mut_val *val, uint64_t num); /** Set the value to sint. Returns false if input is NULL. @warning This function should not be used on an existing object or array. */ yyjson_api_inline bool yyjson_mut_set_sint(yyjson_mut_val *val, int64_t num); /** Set the value to int. Returns false if input is NULL. @warning This function should not be used on an existing object or array. */ yyjson_api_inline bool yyjson_mut_set_int(yyjson_mut_val *val, int num); /** Set the value to float. Returns false if input is NULL. @warning This function should not be used on an existing object or array. */ yyjson_api_inline bool yyjson_mut_set_float(yyjson_mut_val *val, float num); /** Set the value to double. Returns false if input is NULL. @warning This function should not be used on an existing object or array. */ yyjson_api_inline bool yyjson_mut_set_double(yyjson_mut_val *val, double num); /** Set the value to real. Returns false if input is NULL. @warning This function should not be used on an existing object or array. */ yyjson_api_inline bool yyjson_mut_set_real(yyjson_mut_val *val, double num); /** Set the floating-point number's output format to fixed-point notation. Returns false if input is NULL or `val` is not real type. @see YYJSON_WRITE_FP_TO_FIXED flag. @warning This will modify the `immutable` value, use with caution. */ yyjson_api_inline bool yyjson_mut_set_fp_to_fixed(yyjson_mut_val *val, int prec); /** Set the floating-point number's output format to single-precision. Returns false if input is NULL or `val` is not real type. @see YYJSON_WRITE_FP_TO_FLOAT flag. @warning This will modify the `immutable` value, use with caution. */ yyjson_api_inline bool yyjson_mut_set_fp_to_float(yyjson_mut_val *val, bool flt); /** Set the value to string (null-terminated). Returns false if input is NULL. @warning This function should not be used on an existing object or array. */ yyjson_api_inline bool yyjson_mut_set_str(yyjson_mut_val *val, const char *str); /** Set the value to string (with length). Returns false if input is NULL. @warning This function should not be used on an existing object or array. */ yyjson_api_inline bool yyjson_mut_set_strn(yyjson_mut_val *val, const char *str, size_t len); /** Marks this string as not needing to be escaped during JSON writing. This can be used to avoid the overhead of escaping if the string contains only characters that do not require escaping. Returns false if input is NULL or `val` is not string. @see YYJSON_SUBTYPE_NOESC subtype. @warning This will modify the `immutable` value, use with caution. */ yyjson_api_inline bool yyjson_mut_set_str_noesc(yyjson_mut_val *val, bool noesc); /** Set the value to array. Returns false if input is NULL. @warning This function should not be used on an existing object or array. */ yyjson_api_inline bool yyjson_mut_set_arr(yyjson_mut_val *val); /** Set the value to array. Returns false if input is NULL. @warning This function should not be used on an existing object or array. */ yyjson_api_inline bool yyjson_mut_set_obj(yyjson_mut_val *val); /*============================================================================== * MARK: - Mutable JSON Value Creation API *============================================================================*/ /** Creates and returns a raw value, returns NULL on error. The `str` should be a null-terminated UTF-8 string. @warning The input string is not copied, you should keep this string unmodified for the lifetime of this JSON document. */ yyjson_api_inline yyjson_mut_val *yyjson_mut_raw(yyjson_mut_doc *doc, const char *str); /** Creates and returns a raw value, returns NULL on error. The `str` should be a UTF-8 string, null-terminator is not required. @warning The input string is not copied, you should keep this string unmodified for the lifetime of this JSON document. */ yyjson_api_inline yyjson_mut_val *yyjson_mut_rawn(yyjson_mut_doc *doc, const char *str, size_t len); /** Creates and returns a raw value, returns NULL on error. The `str` should be a null-terminated UTF-8 string. The input string is copied and held by the document. */ yyjson_api_inline yyjson_mut_val *yyjson_mut_rawcpy(yyjson_mut_doc *doc, const char *str); /** Creates and returns a raw value, returns NULL on error. The `str` should be a UTF-8 string, null-terminator is not required. The input string is copied and held by the document. */ yyjson_api_inline yyjson_mut_val *yyjson_mut_rawncpy(yyjson_mut_doc *doc, const char *str, size_t len); /** Creates and returns a null value, returns NULL on error. */ yyjson_api_inline yyjson_mut_val *yyjson_mut_null(yyjson_mut_doc *doc); /** Creates and returns a true value, returns NULL on error. */ yyjson_api_inline yyjson_mut_val *yyjson_mut_true(yyjson_mut_doc *doc); /** Creates and returns a false value, returns NULL on error. */ yyjson_api_inline yyjson_mut_val *yyjson_mut_false(yyjson_mut_doc *doc); /** Creates and returns a bool value, returns NULL on error. */ yyjson_api_inline yyjson_mut_val *yyjson_mut_bool(yyjson_mut_doc *doc, bool val); /** Creates and returns an unsigned integer value, returns NULL on error. */ yyjson_api_inline yyjson_mut_val *yyjson_mut_uint(yyjson_mut_doc *doc, uint64_t num); /** Creates and returns a signed integer value, returns NULL on error. */ yyjson_api_inline yyjson_mut_val *yyjson_mut_sint(yyjson_mut_doc *doc, int64_t num); /** Creates and returns a signed integer value, returns NULL on error. */ yyjson_api_inline yyjson_mut_val *yyjson_mut_int(yyjson_mut_doc *doc, int64_t num); /** Creates and returns a float number value, returns NULL on error. */ yyjson_api_inline yyjson_mut_val *yyjson_mut_float(yyjson_mut_doc *doc, float num); /** Creates and returns a double number value, returns NULL on error. */ yyjson_api_inline yyjson_mut_val *yyjson_mut_double(yyjson_mut_doc *doc, double num); /** Creates and returns a real number value, returns NULL on error. */ yyjson_api_inline yyjson_mut_val *yyjson_mut_real(yyjson_mut_doc *doc, double num); /** Creates and returns a string value, returns NULL on error. The `str` should be a null-terminated UTF-8 string. @warning The input string is not copied, you should keep this string unmodified for the lifetime of this JSON document. */ yyjson_api_inline yyjson_mut_val *yyjson_mut_str(yyjson_mut_doc *doc, const char *str); /** Creates and returns a string value, returns NULL on error. The `str` should be a UTF-8 string, null-terminator is not required. @warning The input string is not copied, you should keep this string unmodified for the lifetime of this JSON document. */ yyjson_api_inline yyjson_mut_val *yyjson_mut_strn(yyjson_mut_doc *doc, const char *str, size_t len); /** Creates and returns a string value, returns NULL on error. The `str` should be a null-terminated UTF-8 string. The input string is copied and held by the document. */ yyjson_api_inline yyjson_mut_val *yyjson_mut_strcpy(yyjson_mut_doc *doc, const char *str); /** Creates and returns a string value, returns NULL on error. The `str` should be a UTF-8 string, null-terminator is not required. The input string is copied and held by the document. */ yyjson_api_inline yyjson_mut_val *yyjson_mut_strncpy(yyjson_mut_doc *doc, const char *str, size_t len); /*============================================================================== * MARK: - Mutable JSON Array API *============================================================================*/ /** Returns the number of elements in this array. Returns 0 if `arr` is NULL or type is not array. */ yyjson_api_inline size_t yyjson_mut_arr_size(yyjson_mut_val *arr); /** Returns the element at the specified position in this array. Returns NULL if array is NULL/empty or the index is out of bounds. @warning This function takes a linear search time. */ yyjson_api_inline yyjson_mut_val *yyjson_mut_arr_get(yyjson_mut_val *arr, size_t idx); /** Returns the first element of this array. Returns NULL if `arr` is NULL/empty or type is not array. */ yyjson_api_inline yyjson_mut_val *yyjson_mut_arr_get_first(yyjson_mut_val *arr); /** Returns the last element of this array. Returns NULL if `arr` is NULL/empty or type is not array. */ yyjson_api_inline yyjson_mut_val *yyjson_mut_arr_get_last(yyjson_mut_val *arr); /*============================================================================== * MARK: - Mutable JSON Array Iterator API *============================================================================*/ /** A mutable JSON array iterator. @warning You should not modify the array while iterating over it, but you can use `yyjson_mut_arr_iter_remove()` to remove current value. @b Example @code yyjson_mut_val *val; yyjson_mut_arr_iter iter = yyjson_mut_arr_iter_with(arr); while ((val = yyjson_mut_arr_iter_next(&iter))) { your_func(val); if (your_val_is_unused(val)) { yyjson_mut_arr_iter_remove(&iter); } } @endcode */ typedef struct yyjson_mut_arr_iter { size_t idx; /**< next value's index */ size_t max; /**< maximum index (arr.size) */ yyjson_mut_val *cur; /**< current value */ yyjson_mut_val *pre; /**< previous value */ yyjson_mut_val *arr; /**< the array being iterated */ } yyjson_mut_arr_iter; /** Initialize an iterator for this array. @param arr The array to be iterated over. If this parameter is NULL or not an array, `iter` will be set to empty. @param iter The iterator to be initialized. If this parameter is NULL, the function will fail and return false. @return true if the `iter` has been successfully initialized. @note The iterator does not need to be destroyed. */ yyjson_api_inline bool yyjson_mut_arr_iter_init(yyjson_mut_val *arr, yyjson_mut_arr_iter *iter); /** Create an iterator with an array , same as `yyjson_mut_arr_iter_init()`. @param arr The array to be iterated over. If this parameter is NULL or not an array, an empty iterator will returned. @return A new iterator for the array. @note The iterator does not need to be destroyed. */ yyjson_api_inline yyjson_mut_arr_iter yyjson_mut_arr_iter_with( yyjson_mut_val *arr); /** Returns whether the iteration has more elements. If `iter` is NULL, this function will return false. */ yyjson_api_inline bool yyjson_mut_arr_iter_has_next( yyjson_mut_arr_iter *iter); /** Returns the next element in the iteration, or NULL on end. If `iter` is NULL, this function will return NULL. */ yyjson_api_inline yyjson_mut_val *yyjson_mut_arr_iter_next( yyjson_mut_arr_iter *iter); /** Removes and returns current element in the iteration. If `iter` is NULL, this function will return NULL. */ yyjson_api_inline yyjson_mut_val *yyjson_mut_arr_iter_remove( yyjson_mut_arr_iter *iter); /** Macro for iterating over an array. It works like iterator, but with a more intuitive API. @warning You should not modify the array while iterating over it. @b Example @code size_t idx, max; yyjson_mut_val *val; yyjson_mut_arr_foreach(arr, idx, max, val) { your_func(idx, val); } @endcode */ #define yyjson_mut_arr_foreach(arr, idx, max, val) \ for ((idx) = 0, \ (max) = yyjson_mut_arr_size(arr), \ (val) = yyjson_mut_arr_get_first(arr); \ (idx) < (max); \ (idx)++, \ (val) = (val)->next) /*============================================================================== * MARK: - Mutable JSON Array Creation API *============================================================================*/ /** Creates and returns an empty mutable array. @param doc A mutable document, used for memory allocation only. @return The new array. NULL if input is NULL or memory allocation failed. */ yyjson_api_inline yyjson_mut_val *yyjson_mut_arr(yyjson_mut_doc *doc); /** Creates and returns a new mutable array with the given boolean values. @param doc A mutable document, used for memory allocation only. If this parameter is NULL, the function will fail and return NULL. @param vals A C array of boolean values. @param count The value count. If this value is 0, an empty array will return. @return The new array. NULL if input is invalid or memory allocation failed. @b Example @code const bool vals[3] = { true, false, true }; yyjson_mut_val *arr = yyjson_mut_arr_with_bool(doc, vals, 3); @endcode */ yyjson_api_inline yyjson_mut_val *yyjson_mut_arr_with_bool( yyjson_mut_doc *doc, const bool *vals, size_t count); /** Creates and returns a new mutable array with the given sint numbers. @param doc A mutable document, used for memory allocation only. If this parameter is NULL, the function will fail and return NULL. @param vals A C array of sint numbers. @param count The number count. If this value is 0, an empty array will return. @return The new array. NULL if input is invalid or memory allocation failed. @b Example @code const int64_t vals[3] = { -1, 0, 1 }; yyjson_mut_val *arr = yyjson_mut_arr_with_sint64(doc, vals, 3); @endcode */ yyjson_api_inline yyjson_mut_val *yyjson_mut_arr_with_sint( yyjson_mut_doc *doc, const int64_t *vals, size_t count); /** Creates and returns a new mutable array with the given uint numbers. @param doc A mutable document, used for memory allocation only. If this parameter is NULL, the function will fail and return NULL. @param vals A C array of uint numbers. @param count The number count. If this value is 0, an empty array will return. @return The new array. NULL if input is invalid or memory allocation failed. @b Example @code const uint64_t vals[3] = { 0, 1, 0 }; yyjson_mut_val *arr = yyjson_mut_arr_with_uint(doc, vals, 3); @endcode */ yyjson_api_inline yyjson_mut_val *yyjson_mut_arr_with_uint( yyjson_mut_doc *doc, const uint64_t *vals, size_t count); /** Creates and returns a new mutable array with the given real numbers. @param doc A mutable document, used for memory allocation only. If this parameter is NULL, the function will fail and return NULL. @param vals A C array of real numbers. @param count The number count. If this value is 0, an empty array will return. @return The new array. NULL if input is invalid or memory allocation failed. @b Example @code const double vals[3] = { 0.1, 0.2, 0.3 }; yyjson_mut_val *arr = yyjson_mut_arr_with_real(doc, vals, 3); @endcode */ yyjson_api_inline yyjson_mut_val *yyjson_mut_arr_with_real( yyjson_mut_doc *doc, const double *vals, size_t count); /** Creates and returns a new mutable array with the given int8 numbers. @param doc A mutable document, used for memory allocation only. If this parameter is NULL, the function will fail and return NULL. @param vals A C array of int8 numbers. @param count The number count. If this value is 0, an empty array will return. @return The new array. NULL if input is invalid or memory allocation failed. @b Example @code const int8_t vals[3] = { -1, 0, 1 }; yyjson_mut_val *arr = yyjson_mut_arr_with_sint8(doc, vals, 3); @endcode */ yyjson_api_inline yyjson_mut_val *yyjson_mut_arr_with_sint8( yyjson_mut_doc *doc, const int8_t *vals, size_t count); /** Creates and returns a new mutable array with the given int16 numbers. @param doc A mutable document, used for memory allocation only. If this parameter is NULL, the function will fail and return NULL. @param vals A C array of int16 numbers. @param count The number count. If this value is 0, an empty array will return. @return The new array. NULL if input is invalid or memory allocation failed. @b Example @code const int16_t vals[3] = { -1, 0, 1 }; yyjson_mut_val *arr = yyjson_mut_arr_with_sint16(doc, vals, 3); @endcode */ yyjson_api_inline yyjson_mut_val *yyjson_mut_arr_with_sint16( yyjson_mut_doc *doc, const int16_t *vals, size_t count); /** Creates and returns a new mutable array with the given int32 numbers. @param doc A mutable document, used for memory allocation only. If this parameter is NULL, the function will fail and return NULL. @param vals A C array of int32 numbers. @param count The number count. If this value is 0, an empty array will return. @return The new array. NULL if input is invalid or memory allocation failed. @b Example @code const int32_t vals[3] = { -1, 0, 1 }; yyjson_mut_val *arr = yyjson_mut_arr_with_sint32(doc, vals, 3); @endcode */ yyjson_api_inline yyjson_mut_val *yyjson_mut_arr_with_sint32( yyjson_mut_doc *doc, const int32_t *vals, size_t count); /** Creates and returns a new mutable array with the given int64 numbers. @param doc A mutable document, used for memory allocation only. If this parameter is NULL, the function will fail and return NULL. @param vals A C array of int64 numbers. @param count The number count. If this value is 0, an empty array will return. @return The new array. NULL if input is invalid or memory allocation failed. @b Example @code const int64_t vals[3] = { -1, 0, 1 }; yyjson_mut_val *arr = yyjson_mut_arr_with_sint64(doc, vals, 3); @endcode */ yyjson_api_inline yyjson_mut_val *yyjson_mut_arr_with_sint64( yyjson_mut_doc *doc, const int64_t *vals, size_t count); /** Creates and returns a new mutable array with the given uint8 numbers. @param doc A mutable document, used for memory allocation only. If this parameter is NULL, the function will fail and return NULL. @param vals A C array of uint8 numbers. @param count The number count. If this value is 0, an empty array will return. @return The new array. NULL if input is invalid or memory allocation failed. @b Example @code const uint8_t vals[3] = { 0, 1, 0 }; yyjson_mut_val *arr = yyjson_mut_arr_with_uint8(doc, vals, 3); @endcode */ yyjson_api_inline yyjson_mut_val *yyjson_mut_arr_with_uint8( yyjson_mut_doc *doc, const uint8_t *vals, size_t count); /** Creates and returns a new mutable array with the given uint16 numbers. @param doc A mutable document, used for memory allocation only. If this parameter is NULL, the function will fail and return NULL. @param vals A C array of uint16 numbers. @param count The number count. If this value is 0, an empty array will return. @return The new array. NULL if input is invalid or memory allocation failed. @b Example @code const uint16_t vals[3] = { 0, 1, 0 }; yyjson_mut_val *arr = yyjson_mut_arr_with_uint16(doc, vals, 3); @endcode */ yyjson_api_inline yyjson_mut_val *yyjson_mut_arr_with_uint16( yyjson_mut_doc *doc, const uint16_t *vals, size_t count); /** Creates and returns a new mutable array with the given uint32 numbers. @param doc A mutable document, used for memory allocation only. If this parameter is NULL, the function will fail and return NULL. @param vals A C array of uint32 numbers. @param count The number count. If this value is 0, an empty array will return. @return The new array. NULL if input is invalid or memory allocation failed. @b Example @code const uint32_t vals[3] = { 0, 1, 0 }; yyjson_mut_val *arr = yyjson_mut_arr_with_uint32(doc, vals, 3); @endcode */ yyjson_api_inline yyjson_mut_val *yyjson_mut_arr_with_uint32( yyjson_mut_doc *doc, const uint32_t *vals, size_t count); /** Creates and returns a new mutable array with the given uint64 numbers. @param doc A mutable document, used for memory allocation only. If this parameter is NULL, the function will fail and return NULL. @param vals A C array of uint64 numbers. @param count The number count. If this value is 0, an empty array will return. @return The new array. NULL if input is invalid or memory allocation failed. @b Example @code const uint64_t vals[3] = { 0, 1, 0 }; yyjson_mut_val *arr = yyjson_mut_arr_with_uint64(doc, vals, 3); @endcode */ yyjson_api_inline yyjson_mut_val *yyjson_mut_arr_with_uint64( yyjson_mut_doc *doc, const uint64_t *vals, size_t count); /** Creates and returns a new mutable array with the given float numbers. @param doc A mutable document, used for memory allocation only. If this parameter is NULL, the function will fail and return NULL. @param vals A C array of float numbers. @param count The number count. If this value is 0, an empty array will return. @return The new array. NULL if input is invalid or memory allocation failed. @b Example @code const float vals[3] = { -1.0f, 0.0f, 1.0f }; yyjson_mut_val *arr = yyjson_mut_arr_with_float(doc, vals, 3); @endcode */ yyjson_api_inline yyjson_mut_val *yyjson_mut_arr_with_float( yyjson_mut_doc *doc, const float *vals, size_t count); /** Creates and returns a new mutable array with the given double numbers. @param doc A mutable document, used for memory allocation only. If this parameter is NULL, the function will fail and return NULL. @param vals A C array of double numbers. @param count The number count. If this value is 0, an empty array will return. @return The new array. NULL if input is invalid or memory allocation failed. @b Example @code const double vals[3] = { -1.0, 0.0, 1.0 }; yyjson_mut_val *arr = yyjson_mut_arr_with_double(doc, vals, 3); @endcode */ yyjson_api_inline yyjson_mut_val *yyjson_mut_arr_with_double( yyjson_mut_doc *doc, const double *vals, size_t count); /** Creates and returns a new mutable array with the given strings, these strings will not be copied. @param doc A mutable document, used for memory allocation only. If this parameter is NULL, the function will fail and return NULL. @param vals A C array of UTF-8 null-terminator strings. If this array contains NULL, the function will fail and return NULL. @param count The number of values in `vals`. If this value is 0, an empty array will return. @return The new array. NULL if input is invalid or memory allocation failed. @warning The input strings are not copied, you should keep these strings unmodified for the lifetime of this JSON document. If these strings will be modified, you should use `yyjson_mut_arr_with_strcpy()` instead. @b Example @code const char *vals[3] = { "a", "b", "c" }; yyjson_mut_val *arr = yyjson_mut_arr_with_str(doc, vals, 3); @endcode */ yyjson_api_inline yyjson_mut_val *yyjson_mut_arr_with_str( yyjson_mut_doc *doc, const char **vals, size_t count); /** Creates and returns a new mutable array with the given strings and string lengths, these strings will not be copied. @param doc A mutable document, used for memory allocation only. If this parameter is NULL, the function will fail and return NULL. @param vals A C array of UTF-8 strings, null-terminator is not required. If this array contains NULL, the function will fail and return NULL. @param lens A C array of string lengths, in bytes. @param count The number of strings in `vals`. If this value is 0, an empty array will return. @return The new array. NULL if input is invalid or memory allocation failed. @warning The input strings are not copied, you should keep these strings unmodified for the lifetime of this JSON document. If these strings will be modified, you should use `yyjson_mut_arr_with_strncpy()` instead. @b Example @code const char *vals[3] = { "a", "bb", "c" }; const size_t lens[3] = { 1, 2, 1 }; yyjson_mut_val *arr = yyjson_mut_arr_with_strn(doc, vals, lens, 3); @endcode */ yyjson_api_inline yyjson_mut_val *yyjson_mut_arr_with_strn( yyjson_mut_doc *doc, const char **vals, const size_t *lens, size_t count); /** Creates and returns a new mutable array with the given strings, these strings will be copied. @param doc A mutable document, used for memory allocation only. If this parameter is NULL, the function will fail and return NULL. @param vals A C array of UTF-8 null-terminator strings. If this array contains NULL, the function will fail and return NULL. @param count The number of values in `vals`. If this value is 0, an empty array will return. @return The new array. NULL if input is invalid or memory allocation failed. @b Example @code const char *vals[3] = { "a", "b", "c" }; yyjson_mut_val *arr = yyjson_mut_arr_with_strcpy(doc, vals, 3); @endcode */ yyjson_api_inline yyjson_mut_val *yyjson_mut_arr_with_strcpy( yyjson_mut_doc *doc, const char **vals, size_t count); /** Creates and returns a new mutable array with the given strings and string lengths, these strings will be copied. @param doc A mutable document, used for memory allocation only. If this parameter is NULL, the function will fail and return NULL. @param vals A C array of UTF-8 strings, null-terminator is not required. If this array contains NULL, the function will fail and return NULL. @param lens A C array of string lengths, in bytes. @param count The number of strings in `vals`. If this value is 0, an empty array will return. @return The new array. NULL if input is invalid or memory allocation failed. @b Example @code const char *vals[3] = { "a", "bb", "c" }; const size_t lens[3] = { 1, 2, 1 }; yyjson_mut_val *arr = yyjson_mut_arr_with_strn(doc, vals, lens, 3); @endcode */ yyjson_api_inline yyjson_mut_val *yyjson_mut_arr_with_strncpy( yyjson_mut_doc *doc, const char **vals, const size_t *lens, size_t count); /*============================================================================== * MARK: - Mutable JSON Array Modification API *============================================================================*/ /** Inserts a value into an array at a given index. @param arr The array to which the value is to be inserted. Returns false if it is NULL or not an array. @param val The value to be inserted. Returns false if it is NULL. @param idx The index to which to insert the new value. Returns false if the index is out of range. @return Whether successful. @warning This function takes a linear search time. */ yyjson_api_inline bool yyjson_mut_arr_insert(yyjson_mut_val *arr, yyjson_mut_val *val, size_t idx); /** Inserts a value at the end of the array. @param arr The array to which the value is to be inserted. Returns false if it is NULL or not an array. @param val The value to be inserted. Returns false if it is NULL. @return Whether successful. */ yyjson_api_inline bool yyjson_mut_arr_append(yyjson_mut_val *arr, yyjson_mut_val *val); /** Inserts a value at the head of the array. @param arr The array to which the value is to be inserted. Returns false if it is NULL or not an array. @param val The value to be inserted. Returns false if it is NULL. @return Whether successful. */ yyjson_api_inline bool yyjson_mut_arr_prepend(yyjson_mut_val *arr, yyjson_mut_val *val); /** Replaces a value at index and returns old value. @param arr The array to which the value is to be replaced. Returns false if it is NULL or not an array. @param idx The index to which to replace the value. Returns false if the index is out of range. @param val The new value to replace. Returns false if it is NULL. @return Old value, or NULL on error. @warning This function takes a linear search time. */ yyjson_api_inline yyjson_mut_val *yyjson_mut_arr_replace(yyjson_mut_val *arr, size_t idx, yyjson_mut_val *val); /** Removes and returns a value at index. @param arr The array from which the value is to be removed. Returns false if it is NULL or not an array. @param idx The index from which to remove the value. Returns false if the index is out of range. @return Old value, or NULL on error. @warning This function takes a linear search time. */ yyjson_api_inline yyjson_mut_val *yyjson_mut_arr_remove(yyjson_mut_val *arr, size_t idx); /** Removes and returns the first value in this array. @param arr The array from which the value is to be removed. Returns false if it is NULL or not an array. @return The first value, or NULL on error. */ yyjson_api_inline yyjson_mut_val *yyjson_mut_arr_remove_first( yyjson_mut_val *arr); /** Removes and returns the last value in this array. @param arr The array from which the value is to be removed. Returns false if it is NULL or not an array. @return The last value, or NULL on error. */ yyjson_api_inline yyjson_mut_val *yyjson_mut_arr_remove_last( yyjson_mut_val *arr); /** Removes all values within a specified range in the array. @param arr The array from which the value is to be removed. Returns false if it is NULL or not an array. @param idx The start index of the range (0 is the first). @param len The number of items in the range (can be 0). @return Whether successful. @warning This function takes a linear search time. */ yyjson_api_inline bool yyjson_mut_arr_remove_range(yyjson_mut_val *arr, size_t idx, size_t len); /** Removes all values in this array. @param arr The array from which all of the values are to be removed. Returns false if it is NULL or not an array. @return Whether successful. */ yyjson_api_inline bool yyjson_mut_arr_clear(yyjson_mut_val *arr); /** Rotates values in this array for the given number of times. For example: `[1,2,3,4,5]` rotate 2 is `[3,4,5,1,2]`. @param arr The array to be rotated. @param idx Index (or times) to rotate. @warning This function takes a linear search time. */ yyjson_api_inline bool yyjson_mut_arr_rotate(yyjson_mut_val *arr, size_t idx); /*============================================================================== * MARK: - Mutable JSON Array Modification Convenience API *============================================================================*/ /** Adds a value at the end of the array. @param arr The array to which the value is to be inserted. Returns false if it is NULL or not an array. @param val The value to be inserted. Returns false if it is NULL. @return Whether successful. */ yyjson_api_inline bool yyjson_mut_arr_add_val(yyjson_mut_val *arr, yyjson_mut_val *val); /** Adds a `null` value at the end of the array. @param doc The `doc` is only used for memory allocation. @param arr The array to which the value is to be inserted. Returns false if it is NULL or not an array. @return Whether successful. */ yyjson_api_inline bool yyjson_mut_arr_add_null(yyjson_mut_doc *doc, yyjson_mut_val *arr); /** Adds a `true` value at the end of the array. @param doc The `doc` is only used for memory allocation. @param arr The array to which the value is to be inserted. Returns false if it is NULL or not an array. @return Whether successful. */ yyjson_api_inline bool yyjson_mut_arr_add_true(yyjson_mut_doc *doc, yyjson_mut_val *arr); /** Adds a `false` value at the end of the array. @param doc The `doc` is only used for memory allocation. @param arr The array to which the value is to be inserted. Returns false if it is NULL or not an array. @return Whether successful. */ yyjson_api_inline bool yyjson_mut_arr_add_false(yyjson_mut_doc *doc, yyjson_mut_val *arr); /** Adds a bool value at the end of the array. @param doc The `doc` is only used for memory allocation. @param arr The array to which the value is to be inserted. Returns false if it is NULL or not an array. @param val The bool value to be added. @return Whether successful. */ yyjson_api_inline bool yyjson_mut_arr_add_bool(yyjson_mut_doc *doc, yyjson_mut_val *arr, bool val); /** Adds an unsigned integer value at the end of the array. @param doc The `doc` is only used for memory allocation. @param arr The array to which the value is to be inserted. Returns false if it is NULL or not an array. @param num The number to be added. @return Whether successful. */ yyjson_api_inline bool yyjson_mut_arr_add_uint(yyjson_mut_doc *doc, yyjson_mut_val *arr, uint64_t num); /** Adds a signed integer value at the end of the array. @param doc The `doc` is only used for memory allocation. @param arr The array to which the value is to be inserted. Returns false if it is NULL or not an array. @param num The number to be added. @return Whether successful. */ yyjson_api_inline bool yyjson_mut_arr_add_sint(yyjson_mut_doc *doc, yyjson_mut_val *arr, int64_t num); /** Adds an integer value at the end of the array. @param doc The `doc` is only used for memory allocation. @param arr The array to which the value is to be inserted. Returns false if it is NULL or not an array. @param num The number to be added. @return Whether successful. */ yyjson_api_inline bool yyjson_mut_arr_add_int(yyjson_mut_doc *doc, yyjson_mut_val *arr, int64_t num); /** Adds a float value at the end of the array. @param doc The `doc` is only used for memory allocation. @param arr The array to which the value is to be inserted. Returns false if it is NULL or not an array. @param num The number to be added. @return Whether successful. */ yyjson_api_inline bool yyjson_mut_arr_add_float(yyjson_mut_doc *doc, yyjson_mut_val *arr, float num); /** Adds a double value at the end of the array. @param doc The `doc` is only used for memory allocation. @param arr The array to which the value is to be inserted. Returns false if it is NULL or not an array. @param num The number to be added. @return Whether successful. */ yyjson_api_inline bool yyjson_mut_arr_add_double(yyjson_mut_doc *doc, yyjson_mut_val *arr, double num); /** Adds a double value at the end of the array. @param doc The `doc` is only used for memory allocation. @param arr The array to which the value is to be inserted. Returns false if it is NULL or not an array. @param num The number to be added. @return Whether successful. */ yyjson_api_inline bool yyjson_mut_arr_add_real(yyjson_mut_doc *doc, yyjson_mut_val *arr, double num); /** Adds a string value at the end of the array (no copy). @param doc The `doc` is only used for memory allocation. @param arr The array to which the value is to be inserted. Returns false if it is NULL or not an array. @param str A null-terminated UTF-8 string. @return Whether successful. @warning The input string is not copied, you should keep this string unmodified for the lifetime of this JSON document. */ yyjson_api_inline bool yyjson_mut_arr_add_str(yyjson_mut_doc *doc, yyjson_mut_val *arr, const char *str); /** Adds a string value at the end of the array (no copy). @param doc The `doc` is only used for memory allocation. @param arr The array to which the value is to be inserted. Returns false if it is NULL or not an array. @param str A UTF-8 string, null-terminator is not required. @param len The length of the string, in bytes. @return Whether successful. @warning The input string is not copied, you should keep this string unmodified for the lifetime of this JSON document. */ yyjson_api_inline bool yyjson_mut_arr_add_strn(yyjson_mut_doc *doc, yyjson_mut_val *arr, const char *str, size_t len); /** Adds a string value at the end of the array (copied). @param doc The `doc` is only used for memory allocation. @param arr The array to which the value is to be inserted. Returns false if it is NULL or not an array. @param str A null-terminated UTF-8 string. @return Whether successful. */ yyjson_api_inline bool yyjson_mut_arr_add_strcpy(yyjson_mut_doc *doc, yyjson_mut_val *arr, const char *str); /** Adds a string value at the end of the array (copied). @param doc The `doc` is only used for memory allocation. @param arr The array to which the value is to be inserted. Returns false if it is NULL or not an array. @param str A UTF-8 string, null-terminator is not required. @param len The length of the string, in bytes. @return Whether successful. */ yyjson_api_inline bool yyjson_mut_arr_add_strncpy(yyjson_mut_doc *doc, yyjson_mut_val *arr, const char *str, size_t len); /** Creates and adds a new array at the end of the array. @param doc The `doc` is only used for memory allocation. @param arr The array to which the value is to be inserted. Returns false if it is NULL or not an array. @return The new array, or NULL on error. */ yyjson_api_inline yyjson_mut_val *yyjson_mut_arr_add_arr(yyjson_mut_doc *doc, yyjson_mut_val *arr); /** Creates and adds a new object at the end of the array. @param doc The `doc` is only used for memory allocation. @param arr The array to which the value is to be inserted. Returns false if it is NULL or not an array. @return The new object, or NULL on error. */ yyjson_api_inline yyjson_mut_val *yyjson_mut_arr_add_obj(yyjson_mut_doc *doc, yyjson_mut_val *arr); /*============================================================================== * MARK: - Mutable JSON Object API *============================================================================*/ /** Returns the number of key-value pairs in this object. Returns 0 if `obj` is NULL or type is not object. */ yyjson_api_inline size_t yyjson_mut_obj_size(yyjson_mut_val *obj); /** Returns the value to which the specified key is mapped. Returns NULL if this object contains no mapping for the key. Returns NULL if `obj/key` is NULL, or type is not object. The `key` should be a null-terminated UTF-8 string. @warning This function takes a linear search time. */ yyjson_api_inline yyjson_mut_val *yyjson_mut_obj_get(yyjson_mut_val *obj, const char *key); /** Returns the value to which the specified key is mapped. Returns NULL if this object contains no mapping for the key. Returns NULL if `obj/key` is NULL, or type is not object. The `key` should be a UTF-8 string, null-terminator is not required. The `key_len` should be the length of the key, in bytes. @warning This function takes a linear search time. */ yyjson_api_inline yyjson_mut_val *yyjson_mut_obj_getn(yyjson_mut_val *obj, const char *key, size_t key_len); /*============================================================================== * MARK: - Mutable JSON Object Iterator API *============================================================================*/ /** A mutable JSON object iterator. @warning You should not modify the object while iterating over it, but you can use `yyjson_mut_obj_iter_remove()` to remove current value. @b Example @code yyjson_mut_val *key, *val; yyjson_mut_obj_iter iter = yyjson_mut_obj_iter_with(obj); while ((key = yyjson_mut_obj_iter_next(&iter))) { val = yyjson_mut_obj_iter_get_val(key); your_func(key, val); if (your_val_is_unused(key, val)) { yyjson_mut_obj_iter_remove(&iter); } } @endcode If the ordering of the keys is known at compile-time, you can use this method to speed up value lookups: @code // {"k1":1, "k2": 3, "k3": 3} yyjson_mut_val *key, *val; yyjson_mut_obj_iter iter = yyjson_mut_obj_iter_with(obj); yyjson_mut_val *v1 = yyjson_mut_obj_iter_get(&iter, "k1"); yyjson_mut_val *v3 = yyjson_mut_obj_iter_get(&iter, "k3"); @endcode @see `yyjson_mut_obj_iter_get()` and `yyjson_mut_obj_iter_getn()` */ typedef struct yyjson_mut_obj_iter { size_t idx; /**< next key's index */ size_t max; /**< maximum key index (obj.size) */ yyjson_mut_val *cur; /**< current key */ yyjson_mut_val *pre; /**< previous key */ yyjson_mut_val *obj; /**< the object being iterated */ } yyjson_mut_obj_iter; /** Initialize an iterator for this object. @param obj The object to be iterated over. If this parameter is NULL or not an array, `iter` will be set to empty. @param iter The iterator to be initialized. If this parameter is NULL, the function will fail and return false. @return true if the `iter` has been successfully initialized. @note The iterator does not need to be destroyed. */ yyjson_api_inline bool yyjson_mut_obj_iter_init(yyjson_mut_val *obj, yyjson_mut_obj_iter *iter); /** Create an iterator with an object, same as `yyjson_obj_iter_init()`. @param obj The object to be iterated over. If this parameter is NULL or not an object, an empty iterator will returned. @return A new iterator for the object. @note The iterator does not need to be destroyed. */ yyjson_api_inline yyjson_mut_obj_iter yyjson_mut_obj_iter_with( yyjson_mut_val *obj); /** Returns whether the iteration has more elements. If `iter` is NULL, this function will return false. */ yyjson_api_inline bool yyjson_mut_obj_iter_has_next( yyjson_mut_obj_iter *iter); /** Returns the next key in the iteration, or NULL on end. If `iter` is NULL, this function will return NULL. */ yyjson_api_inline yyjson_mut_val *yyjson_mut_obj_iter_next( yyjson_mut_obj_iter *iter); /** Returns the value for key inside the iteration. If `iter` is NULL, this function will return NULL. */ yyjson_api_inline yyjson_mut_val *yyjson_mut_obj_iter_get_val( yyjson_mut_val *key); /** Removes current key-value pair in the iteration, returns the removed value. If `iter` is NULL, this function will return NULL. */ yyjson_api_inline yyjson_mut_val *yyjson_mut_obj_iter_remove( yyjson_mut_obj_iter *iter); /** Iterates to a specified key and returns the value. This function does the same thing as `yyjson_mut_obj_get()`, but is much faster if the ordering of the keys is known at compile-time and you are using the same order to look up the values. If the key exists in this object, then the iterator will stop at the next key, otherwise the iterator will not change and NULL is returned. @param iter The object iterator, should not be NULL. @param key The key, should be a UTF-8 string with null-terminator. @return The value to which the specified key is mapped. NULL if this object contains no mapping for the key or input is invalid. @warning This function takes a linear search time if the key is not nearby. */ yyjson_api_inline yyjson_mut_val *yyjson_mut_obj_iter_get( yyjson_mut_obj_iter *iter, const char *key); /** Iterates to a specified key and returns the value. This function does the same thing as `yyjson_mut_obj_getn()` but is much faster if the ordering of the keys is known at compile-time and you are using the same order to look up the values. If the key exists in this object, then the iterator will stop at the next key, otherwise the iterator will not change and NULL is returned. @param iter The object iterator, should not be NULL. @param key The key, should be a UTF-8 string, null-terminator is not required. @param key_len The the length of `key`, in bytes. @return The value to which the specified key is mapped. NULL if this object contains no mapping for the key or input is invalid. @warning This function takes a linear search time if the key is not nearby. */ yyjson_api_inline yyjson_mut_val *yyjson_mut_obj_iter_getn( yyjson_mut_obj_iter *iter, const char *key, size_t key_len); /** Macro for iterating over an object. It works like iterator, but with a more intuitive API. @warning You should not modify the object while iterating over it. @b Example @code size_t idx, max; yyjson_mut_val *key, *val; yyjson_mut_obj_foreach(obj, idx, max, key, val) { your_func(key, val); } @endcode */ #define yyjson_mut_obj_foreach(obj, idx, max, key, val) \ for ((idx) = 0, \ (max) = yyjson_mut_obj_size(obj), \ (key) = (max) ? ((yyjson_mut_val *)(obj)->uni.ptr)->next->next : NULL, \ (val) = (key) ? (key)->next : NULL; \ (idx) < (max); \ (idx)++, \ (key) = (val)->next, \ (val) = (key)->next) /*============================================================================== * MARK: - Mutable JSON Object Creation API *============================================================================*/ /** Creates and returns a mutable object, returns NULL on error. */ yyjson_api_inline yyjson_mut_val *yyjson_mut_obj(yyjson_mut_doc *doc); /** Creates and returns a mutable object with keys and values, returns NULL on error. The keys and values are not copied. The strings should be a null-terminated UTF-8 string. @warning The input string is not copied, you should keep this string unmodified for the lifetime of this JSON document. @b Example @code const char *keys[2] = { "id", "name" }; const char *vals[2] = { "01", "Harry" }; yyjson_mut_val *obj = yyjson_mut_obj_with_str(doc, keys, vals, 2); @endcode */ yyjson_api_inline yyjson_mut_val *yyjson_mut_obj_with_str(yyjson_mut_doc *doc, const char **keys, const char **vals, size_t count); /** Creates and returns a mutable object with key-value pairs and pair count, returns NULL on error. The keys and values are not copied. The strings should be a null-terminated UTF-8 string. @warning The input string is not copied, you should keep this string unmodified for the lifetime of this JSON document. @b Example @code const char *kv_pairs[4] = { "id", "01", "name", "Harry" }; yyjson_mut_val *obj = yyjson_mut_obj_with_kv(doc, kv_pairs, 2); @endcode */ yyjson_api_inline yyjson_mut_val *yyjson_mut_obj_with_kv(yyjson_mut_doc *doc, const char **kv_pairs, size_t pair_count); /*============================================================================== * MARK: - Mutable JSON Object Modification API *============================================================================*/ /** Adds a key-value pair at the end of the object. This function allows duplicated key in one object. @param obj The object to which the new key-value pair is to be added. @param key The key, should be a string which is created by `yyjson_mut_str()`, `yyjson_mut_strn()`, `yyjson_mut_strcpy()` or `yyjson_mut_strncpy()`. @param val The value to add to the object. @return Whether successful. */ yyjson_api_inline bool yyjson_mut_obj_add(yyjson_mut_val *obj, yyjson_mut_val *key, yyjson_mut_val *val); /** Sets a key-value pair at the end of the object. This function may remove all key-value pairs for the given key before add. @param obj The object to which the new key-value pair is to be added. @param key The key, should be a string which is created by `yyjson_mut_str()`, `yyjson_mut_strn()`, `yyjson_mut_strcpy()` or `yyjson_mut_strncpy()`. @param val The value to add to the object. If this value is null, the behavior is same as `yyjson_mut_obj_remove()`. @return Whether successful. */ yyjson_api_inline bool yyjson_mut_obj_put(yyjson_mut_val *obj, yyjson_mut_val *key, yyjson_mut_val *val); /** Inserts a key-value pair to the object at the given position. This function allows duplicated key in one object. @param obj The object to which the new key-value pair is to be added. @param key The key, should be a string which is created by `yyjson_mut_str()`, `yyjson_mut_strn()`, `yyjson_mut_strcpy()` or `yyjson_mut_strncpy()`. @param val The value to add to the object. @param idx The index to which to insert the new pair. @return Whether successful. */ yyjson_api_inline bool yyjson_mut_obj_insert(yyjson_mut_val *obj, yyjson_mut_val *key, yyjson_mut_val *val, size_t idx); /** Removes all key-value pair from the object with given key. @param obj The object from which the key-value pair is to be removed. @param key The key, should be a string value. @return The first matched value, or NULL if no matched value. @warning This function takes a linear search time. */ yyjson_api_inline yyjson_mut_val *yyjson_mut_obj_remove(yyjson_mut_val *obj, yyjson_mut_val *key); /** Removes all key-value pair from the object with given key. @param obj The object from which the key-value pair is to be removed. @param key The key, should be a UTF-8 string with null-terminator. @return The first matched value, or NULL if no matched value. @warning This function takes a linear search time. */ yyjson_api_inline yyjson_mut_val *yyjson_mut_obj_remove_key( yyjson_mut_val *obj, const char *key); /** Removes all key-value pair from the object with given key. @param obj The object from which the key-value pair is to be removed. @param key The key, should be a UTF-8 string, null-terminator is not required. @param key_len The length of the key. @return The first matched value, or NULL if no matched value. @warning This function takes a linear search time. */ yyjson_api_inline yyjson_mut_val *yyjson_mut_obj_remove_keyn( yyjson_mut_val *obj, const char *key, size_t key_len); /** Removes all key-value pairs in this object. @param obj The object from which all of the values are to be removed. @return Whether successful. */ yyjson_api_inline bool yyjson_mut_obj_clear(yyjson_mut_val *obj); /** Replaces value from the object with given key. If the key is not exist, or the value is NULL, it will fail. @param obj The object to which the value is to be replaced. @param key The key, should be a string value. @param val The value to replace into the object. @return Whether successful. @warning This function takes a linear search time. */ yyjson_api_inline bool yyjson_mut_obj_replace(yyjson_mut_val *obj, yyjson_mut_val *key, yyjson_mut_val *val); /** Rotates key-value pairs in the object for the given number of times. For example: `{"a":1,"b":2,"c":3,"d":4}` rotate 1 is `{"b":2,"c":3,"d":4,"a":1}`. @param obj The object to be rotated. @param idx Index (or times) to rotate. @return Whether successful. @warning This function takes a linear search time. */ yyjson_api_inline bool yyjson_mut_obj_rotate(yyjson_mut_val *obj, size_t idx); /*============================================================================== * MARK: - Mutable JSON Object Modification Convenience API *============================================================================*/ /** Adds a `null` value at the end of the object. The `key` should be a null-terminated UTF-8 string. This function allows duplicated key in one object. @warning The key string is not copied, you should keep the string unmodified for the lifetime of this JSON document. */ yyjson_api_inline bool yyjson_mut_obj_add_null(yyjson_mut_doc *doc, yyjson_mut_val *obj, const char *key); /** Adds a `true` value at the end of the object. The `key` should be a null-terminated UTF-8 string. This function allows duplicated key in one object. @warning The key string is not copied, you should keep the string unmodified for the lifetime of this JSON document. */ yyjson_api_inline bool yyjson_mut_obj_add_true(yyjson_mut_doc *doc, yyjson_mut_val *obj, const char *key); /** Adds a `false` value at the end of the object. The `key` should be a null-terminated UTF-8 string. This function allows duplicated key in one object. @warning The key string is not copied, you should keep the string unmodified for the lifetime of this JSON document. */ yyjson_api_inline bool yyjson_mut_obj_add_false(yyjson_mut_doc *doc, yyjson_mut_val *obj, const char *key); /** Adds a bool value at the end of the object. The `key` should be a null-terminated UTF-8 string. This function allows duplicated key in one object. @warning The key string is not copied, you should keep the string unmodified for the lifetime of this JSON document. */ yyjson_api_inline bool yyjson_mut_obj_add_bool(yyjson_mut_doc *doc, yyjson_mut_val *obj, const char *key, bool val); /** Adds an unsigned integer value at the end of the object. The `key` should be a null-terminated UTF-8 string. This function allows duplicated key in one object. @warning The key string is not copied, you should keep the string unmodified for the lifetime of this JSON document. */ yyjson_api_inline bool yyjson_mut_obj_add_uint(yyjson_mut_doc *doc, yyjson_mut_val *obj, const char *key, uint64_t val); /** Adds a signed integer value at the end of the object. The `key` should be a null-terminated UTF-8 string. This function allows duplicated key in one object. @warning The key string is not copied, you should keep the string unmodified for the lifetime of this JSON document. */ yyjson_api_inline bool yyjson_mut_obj_add_sint(yyjson_mut_doc *doc, yyjson_mut_val *obj, const char *key, int64_t val); /** Adds an int value at the end of the object. The `key` should be a null-terminated UTF-8 string. This function allows duplicated key in one object. @warning The key string is not copied, you should keep the string unmodified for the lifetime of this JSON document. */ yyjson_api_inline bool yyjson_mut_obj_add_int(yyjson_mut_doc *doc, yyjson_mut_val *obj, const char *key, int64_t val); /** Adds a float value at the end of the object. The `key` should be a null-terminated UTF-8 string. This function allows duplicated key in one object. @warning The key string is not copied, you should keep the string unmodified for the lifetime of this JSON document. */ yyjson_api_inline bool yyjson_mut_obj_add_float(yyjson_mut_doc *doc, yyjson_mut_val *obj, const char *key, float val); /** Adds a double value at the end of the object. The `key` should be a null-terminated UTF-8 string. This function allows duplicated key in one object. @warning The key string is not copied, you should keep the string unmodified for the lifetime of this JSON document. */ yyjson_api_inline bool yyjson_mut_obj_add_double(yyjson_mut_doc *doc, yyjson_mut_val *obj, const char *key, double val); /** Adds a real value at the end of the object. The `key` should be a null-terminated UTF-8 string. This function allows duplicated key in one object. @warning The key string is not copied, you should keep the string unmodified for the lifetime of this JSON document. */ yyjson_api_inline bool yyjson_mut_obj_add_real(yyjson_mut_doc *doc, yyjson_mut_val *obj, const char *key, double val); /** Adds a string value at the end of the object. The `key` and `val` should be null-terminated UTF-8 strings. This function allows duplicated key in one object. @warning The key/value strings are not copied, you should keep these strings unmodified for the lifetime of this JSON document. */ yyjson_api_inline bool yyjson_mut_obj_add_str(yyjson_mut_doc *doc, yyjson_mut_val *obj, const char *key, const char *val); /** Adds a string value at the end of the object. The `key` should be a null-terminated UTF-8 string. The `val` should be a UTF-8 string, null-terminator is not required. The `len` should be the length of the `val`, in bytes. This function allows duplicated key in one object. @warning The key/value strings are not copied, you should keep these strings unmodified for the lifetime of this JSON document. */ yyjson_api_inline bool yyjson_mut_obj_add_strn(yyjson_mut_doc *doc, yyjson_mut_val *obj, const char *key, const char *val, size_t len); /** Adds a string value at the end of the object. The `key` and `val` should be null-terminated UTF-8 strings. The value string is copied. This function allows duplicated key in one object. @warning The key string is not copied, you should keep the string unmodified for the lifetime of this JSON document. */ yyjson_api_inline bool yyjson_mut_obj_add_strcpy(yyjson_mut_doc *doc, yyjson_mut_val *obj, const char *key, const char *val); /** Adds a string value at the end of the object. The `key` should be a null-terminated UTF-8 string. The `val` should be a UTF-8 string, null-terminator is not required. The `len` should be the length of the `val`, in bytes. This function allows duplicated key in one object. @warning The key strings are not copied, you should keep these strings unmodified for the lifetime of this JSON document. */ yyjson_api_inline bool yyjson_mut_obj_add_strncpy(yyjson_mut_doc *doc, yyjson_mut_val *obj, const char *key, const char *val, size_t len); /** Creates and adds a new array to the target object. The `key` should be a null-terminated UTF-8 string. This function allows duplicated key in one object. @warning The key string is not copied, you should keep these strings unmodified for the lifetime of this JSON document. @return The new array, or NULL on error. */ yyjson_api_inline yyjson_mut_val *yyjson_mut_obj_add_arr(yyjson_mut_doc *doc, yyjson_mut_val *obj, const char *key); /** Creates and adds a new object to the target object. The `key` should be a null-terminated UTF-8 string. This function allows duplicated key in one object. @warning The key string is not copied, you should keep these strings unmodified for the lifetime of this JSON document. @return The new object, or NULL on error. */ yyjson_api_inline yyjson_mut_val *yyjson_mut_obj_add_obj(yyjson_mut_doc *doc, yyjson_mut_val *obj, const char *key); /** Adds a JSON value at the end of the object. The `key` should be a null-terminated UTF-8 string. This function allows duplicated key in one object. @warning The key string is not copied, you should keep the string unmodified for the lifetime of this JSON document. */ yyjson_api_inline bool yyjson_mut_obj_add_val(yyjson_mut_doc *doc, yyjson_mut_val *obj, const char *key, yyjson_mut_val *val); /** Removes all key-value pairs for the given key. Returns the first value to which the specified key is mapped or NULL if this object contains no mapping for the key. The `key` should be a null-terminated UTF-8 string. @warning This function takes a linear search time. */ yyjson_api_inline yyjson_mut_val *yyjson_mut_obj_remove_str( yyjson_mut_val *obj, const char *key); /** Removes all key-value pairs for the given key. Returns the first value to which the specified key is mapped or NULL if this object contains no mapping for the key. The `key` should be a UTF-8 string, null-terminator is not required. The `len` should be the length of the key, in bytes. @warning This function takes a linear search time. */ yyjson_api_inline yyjson_mut_val *yyjson_mut_obj_remove_strn( yyjson_mut_val *obj, const char *key, size_t len); /** Replaces all matching keys with the new key. Returns true if at least one key was renamed. The `key` and `new_key` should be a null-terminated UTF-8 string. The `new_key` is copied and held by doc. @warning This function takes a linear search time. If `new_key` already exists, it will cause duplicate keys. */ yyjson_api_inline bool yyjson_mut_obj_rename_key(yyjson_mut_doc *doc, yyjson_mut_val *obj, const char *key, const char *new_key); /** Replaces all matching keys with the new key. Returns true if at least one key was renamed. The `key` and `new_key` should be a UTF-8 string, null-terminator is not required. The `new_key` is copied and held by doc. @warning This function takes a linear search time. If `new_key` already exists, it will cause duplicate keys. */ yyjson_api_inline bool yyjson_mut_obj_rename_keyn(yyjson_mut_doc *doc, yyjson_mut_val *obj, const char *key, size_t len, const char *new_key, size_t new_len); #if !defined(YYJSON_DISABLE_UTILS) || !YYJSON_DISABLE_UTILS /*============================================================================== * MARK: - JSON Pointer API (RFC 6901) * https://tools.ietf.org/html/rfc6901 *============================================================================*/ /** JSON Pointer error code. */ typedef uint32_t yyjson_ptr_code; /** No JSON pointer error. */ static const yyjson_ptr_code YYJSON_PTR_ERR_NONE = 0; /** Invalid input parameter, such as NULL input. */ static const yyjson_ptr_code YYJSON_PTR_ERR_PARAMETER = 1; /** JSON pointer syntax error, such as invalid escape, token no prefix. */ static const yyjson_ptr_code YYJSON_PTR_ERR_SYNTAX = 2; /** JSON pointer resolve failed, such as index out of range, key not found. */ static const yyjson_ptr_code YYJSON_PTR_ERR_RESOLVE = 3; /** Document's root is NULL, but it is required for the function call. */ static const yyjson_ptr_code YYJSON_PTR_ERR_NULL_ROOT = 4; /** Cannot set root as the target is not a document. */ static const yyjson_ptr_code YYJSON_PTR_ERR_SET_ROOT = 5; /** The memory allocation failed and a new value could not be created. */ static const yyjson_ptr_code YYJSON_PTR_ERR_MEMORY_ALLOCATION = 6; /** Error information for JSON pointer. */ typedef struct yyjson_ptr_err { /** Error code, see `yyjson_ptr_code` for all possible values. */ yyjson_ptr_code code; /** Error message, constant, no need to free (NULL if no error). */ const char *msg; /** Error byte position for input JSON pointer (0 if no error). */ size_t pos; } yyjson_ptr_err; /** A context for JSON pointer operation. This struct stores the context of JSON Pointer operation result. The struct can be used with three helper functions: `ctx_append()`, `ctx_replace()`, and `ctx_remove()`, which perform the corresponding operations on the container without re-parsing the JSON Pointer. For example: @code // doc before: {"a":[0,1,null]} // ptr: "/a/2" val = yyjson_mut_doc_ptr_getx(doc, ptr, strlen(ptr), &ctx, &err); if (yyjson_is_null(val)) { yyjson_ptr_ctx_remove(&ctx); } // doc after: {"a":[0,1]} @endcode */ typedef struct yyjson_ptr_ctx { /** The container (parent) of the target value. It can be either an array or an object. If the target location has no value, but all its parent containers exist, and the target location can be used to insert a new value, then `ctn` is the parent container of the target location. Otherwise, `ctn` is NULL. */ yyjson_mut_val *ctn; /** The previous sibling of the target value. It can be either a value in an array or a key in an object. As the container is a `circular linked list` of elements, `pre` is the previous node of the target value. If the operation is `add` or `set`, then `pre` is the previous node of the new value, not the original target value. If the target value does not exist, `pre` is NULL. */ yyjson_mut_val *pre; /** The removed value if the operation is `set`, `replace` or `remove`. It can be used to restore the original state of the document if needed. */ yyjson_mut_val *old; } yyjson_ptr_ctx; /** Get value by a JSON Pointer. @param doc The JSON document to be queried. @param ptr The JSON pointer string (UTF-8 with null-terminator). @return The value referenced by the JSON pointer. NULL if `doc` or `ptr` is NULL, or the JSON pointer cannot be resolved. */ yyjson_api_inline yyjson_val *yyjson_doc_ptr_get(yyjson_doc *doc, const char *ptr); /** Get value by a JSON Pointer. @param doc The JSON document to be queried. @param ptr The JSON pointer string (UTF-8, null-terminator is not required). @param len The length of `ptr` in bytes. @return The value referenced by the JSON pointer. NULL if `doc` or `ptr` is NULL, or the JSON pointer cannot be resolved. */ yyjson_api_inline yyjson_val *yyjson_doc_ptr_getn(yyjson_doc *doc, const char *ptr, size_t len); /** Get value by a JSON Pointer. @param doc The JSON document to be queried. @param ptr The JSON pointer string (UTF-8, null-terminator is not required). @param len The length of `ptr` in bytes. @param err A pointer to store the error information, or NULL if not needed. @return The value referenced by the JSON pointer. NULL if `doc` or `ptr` is NULL, or the JSON pointer cannot be resolved. */ yyjson_api_inline yyjson_val *yyjson_doc_ptr_getx(yyjson_doc *doc, const char *ptr, size_t len, yyjson_ptr_err *err); /** Get value by a JSON Pointer. @param val The JSON value to be queried. @param ptr The JSON pointer string (UTF-8 with null-terminator). @return The value referenced by the JSON pointer. NULL if `val` or `ptr` is NULL, or the JSON pointer cannot be resolved. */ yyjson_api_inline yyjson_val *yyjson_ptr_get(yyjson_val *val, const char *ptr); /** Get value by a JSON Pointer. @param val The JSON value to be queried. @param ptr The JSON pointer string (UTF-8, null-terminator is not required). @param len The length of `ptr` in bytes. @return The value referenced by the JSON pointer. NULL if `val` or `ptr` is NULL, or the JSON pointer cannot be resolved. */ yyjson_api_inline yyjson_val *yyjson_ptr_getn(yyjson_val *val, const char *ptr, size_t len); /** Get value by a JSON Pointer. @param val The JSON value to be queried. @param ptr The JSON pointer string (UTF-8, null-terminator is not required). @param len The length of `ptr` in bytes. @param err A pointer to store the error information, or NULL if not needed. @return The value referenced by the JSON pointer. NULL if `val` or `ptr` is NULL, or the JSON pointer cannot be resolved. */ yyjson_api_inline yyjson_val *yyjson_ptr_getx(yyjson_val *val, const char *ptr, size_t len, yyjson_ptr_err *err); /** Get value by a JSON Pointer. @param doc The JSON document to be queried. @param ptr The JSON pointer string (UTF-8 with null-terminator). @return The value referenced by the JSON pointer. NULL if `doc` or `ptr` is NULL, or the JSON pointer cannot be resolved. */ yyjson_api_inline yyjson_mut_val *yyjson_mut_doc_ptr_get(yyjson_mut_doc *doc, const char *ptr); /** Get value by a JSON Pointer. @param doc The JSON document to be queried. @param ptr The JSON pointer string (UTF-8, null-terminator is not required). @param len The length of `ptr` in bytes. @return The value referenced by the JSON pointer. NULL if `doc` or `ptr` is NULL, or the JSON pointer cannot be resolved. */ yyjson_api_inline yyjson_mut_val *yyjson_mut_doc_ptr_getn(yyjson_mut_doc *doc, const char *ptr, size_t len); /** Get value by a JSON Pointer. @param doc The JSON document to be queried. @param ptr The JSON pointer string (UTF-8, null-terminator is not required). @param len The length of `ptr` in bytes. @param ctx A pointer to store the result context, or NULL if not needed. @param err A pointer to store the error information, or NULL if not needed. @return The value referenced by the JSON pointer. NULL if `doc` or `ptr` is NULL, or the JSON pointer cannot be resolved. */ yyjson_api_inline yyjson_mut_val *yyjson_mut_doc_ptr_getx(yyjson_mut_doc *doc, const char *ptr, size_t len, yyjson_ptr_ctx *ctx, yyjson_ptr_err *err); /** Get value by a JSON Pointer. @param val The JSON value to be queried. @param ptr The JSON pointer string (UTF-8 with null-terminator). @return The value referenced by the JSON pointer. NULL if `val` or `ptr` is NULL, or the JSON pointer cannot be resolved. */ yyjson_api_inline yyjson_mut_val *yyjson_mut_ptr_get(yyjson_mut_val *val, const char *ptr); /** Get value by a JSON Pointer. @param val The JSON value to be queried. @param ptr The JSON pointer string (UTF-8, null-terminator is not required). @param len The length of `ptr` in bytes. @return The value referenced by the JSON pointer. NULL if `val` or `ptr` is NULL, or the JSON pointer cannot be resolved. */ yyjson_api_inline yyjson_mut_val *yyjson_mut_ptr_getn(yyjson_mut_val *val, const char *ptr, size_t len); /** Get value by a JSON Pointer. @param val The JSON value to be queried. @param ptr The JSON pointer string (UTF-8, null-terminator is not required). @param len The length of `ptr` in bytes. @param ctx A pointer to store the result context, or NULL if not needed. @param err A pointer to store the error information, or NULL if not needed. @return The value referenced by the JSON pointer. NULL if `val` or `ptr` is NULL, or the JSON pointer cannot be resolved. */ yyjson_api_inline yyjson_mut_val *yyjson_mut_ptr_getx(yyjson_mut_val *val, const char *ptr, size_t len, yyjson_ptr_ctx *ctx, yyjson_ptr_err *err); /** Add (insert) value by a JSON pointer. @param doc The target JSON document. @param ptr The JSON pointer string (UTF-8 with null-terminator). @param new_val The value to be added. @return true if JSON pointer is valid and new value is added, false otherwise. @note The parent nodes will be created if they do not exist. */ yyjson_api_inline bool yyjson_mut_doc_ptr_add(yyjson_mut_doc *doc, const char *ptr, yyjson_mut_val *new_val); /** Add (insert) value by a JSON pointer. @param doc The target JSON document. @param ptr The JSON pointer string (UTF-8, null-terminator is not required). @param len The length of `ptr` in bytes. @param new_val The value to be added. @return true if JSON pointer is valid and new value is added, false otherwise. @note The parent nodes will be created if they do not exist. */ yyjson_api_inline bool yyjson_mut_doc_ptr_addn(yyjson_mut_doc *doc, const char *ptr, size_t len, yyjson_mut_val *new_val); /** Add (insert) value by a JSON pointer. @param doc The target JSON document. @param ptr The JSON pointer string (UTF-8, null-terminator is not required). @param len The length of `ptr` in bytes. @param new_val The value to be added. @param create_parent Whether to create parent nodes if not exist. @param ctx A pointer to store the result context, or NULL if not needed. @param err A pointer to store the error information, or NULL if not needed. @return true if JSON pointer is valid and new value is added, false otherwise. */ yyjson_api_inline bool yyjson_mut_doc_ptr_addx(yyjson_mut_doc *doc, const char *ptr, size_t len, yyjson_mut_val *new_val, bool create_parent, yyjson_ptr_ctx *ctx, yyjson_ptr_err *err); /** Add (insert) value by a JSON pointer. @param val The target JSON value. @param ptr The JSON pointer string (UTF-8 with null-terminator). @param doc Only used to create new values when needed. @param new_val The value to be added. @return true if JSON pointer is valid and new value is added, false otherwise. @note The parent nodes will be created if they do not exist. */ yyjson_api_inline bool yyjson_mut_ptr_add(yyjson_mut_val *val, const char *ptr, yyjson_mut_val *new_val, yyjson_mut_doc *doc); /** Add (insert) value by a JSON pointer. @param val The target JSON value. @param ptr The JSON pointer string (UTF-8, null-terminator is not required). @param len The length of `ptr` in bytes. @param doc Only used to create new values when needed. @param new_val The value to be added. @return true if JSON pointer is valid and new value is added, false otherwise. @note The parent nodes will be created if they do not exist. */ yyjson_api_inline bool yyjson_mut_ptr_addn(yyjson_mut_val *val, const char *ptr, size_t len, yyjson_mut_val *new_val, yyjson_mut_doc *doc); /** Add (insert) value by a JSON pointer. @param val The target JSON value. @param ptr The JSON pointer string (UTF-8, null-terminator is not required). @param len The length of `ptr` in bytes. @param doc Only used to create new values when needed. @param new_val The value to be added. @param create_parent Whether to create parent nodes if not exist. @param ctx A pointer to store the result context, or NULL if not needed. @param err A pointer to store the error information, or NULL if not needed. @return true if JSON pointer is valid and new value is added, false otherwise. */ yyjson_api_inline bool yyjson_mut_ptr_addx(yyjson_mut_val *val, const char *ptr, size_t len, yyjson_mut_val *new_val, yyjson_mut_doc *doc, bool create_parent, yyjson_ptr_ctx *ctx, yyjson_ptr_err *err); /** Set value by a JSON pointer. @param doc The target JSON document. @param ptr The JSON pointer string (UTF-8 with null-terminator). @param new_val The value to be set, pass NULL to remove. @return true if JSON pointer is valid and new value is set, false otherwise. @note The parent nodes will be created if they do not exist. If the target value already exists, it will be replaced by the new value. */ yyjson_api_inline bool yyjson_mut_doc_ptr_set(yyjson_mut_doc *doc, const char *ptr, yyjson_mut_val *new_val); /** Set value by a JSON pointer. @param doc The target JSON document. @param ptr The JSON pointer string (UTF-8, null-terminator is not required). @param len The length of `ptr` in bytes. @param new_val The value to be set, pass NULL to remove. @return true if JSON pointer is valid and new value is set, false otherwise. @note The parent nodes will be created if they do not exist. If the target value already exists, it will be replaced by the new value. */ yyjson_api_inline bool yyjson_mut_doc_ptr_setn(yyjson_mut_doc *doc, const char *ptr, size_t len, yyjson_mut_val *new_val); /** Set value by a JSON pointer. @param doc The target JSON document. @param ptr The JSON pointer string (UTF-8, null-terminator is not required). @param len The length of `ptr` in bytes. @param new_val The value to be set, pass NULL to remove. @param create_parent Whether to create parent nodes if not exist. @param ctx A pointer to store the result context, or NULL if not needed. @param err A pointer to store the error information, or NULL if not needed. @return true if JSON pointer is valid and new value is set, false otherwise. @note If the target value already exists, it will be replaced by the new value. */ yyjson_api_inline bool yyjson_mut_doc_ptr_setx(yyjson_mut_doc *doc, const char *ptr, size_t len, yyjson_mut_val *new_val, bool create_parent, yyjson_ptr_ctx *ctx, yyjson_ptr_err *err); /** Set value by a JSON pointer. @param val The target JSON value. @param ptr The JSON pointer string (UTF-8 with null-terminator). @param new_val The value to be set, pass NULL to remove. @param doc Only used to create new values when needed. @return true if JSON pointer is valid and new value is set, false otherwise. @note The parent nodes will be created if they do not exist. If the target value already exists, it will be replaced by the new value. */ yyjson_api_inline bool yyjson_mut_ptr_set(yyjson_mut_val *val, const char *ptr, yyjson_mut_val *new_val, yyjson_mut_doc *doc); /** Set value by a JSON pointer. @param val The target JSON value. @param ptr The JSON pointer string (UTF-8, null-terminator is not required). @param len The length of `ptr` in bytes. @param new_val The value to be set, pass NULL to remove. @param doc Only used to create new values when needed. @return true if JSON pointer is valid and new value is set, false otherwise. @note The parent nodes will be created if they do not exist. If the target value already exists, it will be replaced by the new value. */ yyjson_api_inline bool yyjson_mut_ptr_setn(yyjson_mut_val *val, const char *ptr, size_t len, yyjson_mut_val *new_val, yyjson_mut_doc *doc); /** Set value by a JSON pointer. @param val The target JSON value. @param ptr The JSON pointer string (UTF-8, null-terminator is not required). @param len The length of `ptr` in bytes. @param new_val The value to be set, pass NULL to remove. @param doc Only used to create new values when needed. @param create_parent Whether to create parent nodes if not exist. @param ctx A pointer to store the result context, or NULL if not needed. @param err A pointer to store the error information, or NULL if not needed. @return true if JSON pointer is valid and new value is set, false otherwise. @note If the target value already exists, it will be replaced by the new value. */ yyjson_api_inline bool yyjson_mut_ptr_setx(yyjson_mut_val *val, const char *ptr, size_t len, yyjson_mut_val *new_val, yyjson_mut_doc *doc, bool create_parent, yyjson_ptr_ctx *ctx, yyjson_ptr_err *err); /** Replace value by a JSON pointer. @param doc The target JSON document. @param ptr The JSON pointer string (UTF-8 with null-terminator). @param new_val The new value to replace the old one. @return The old value that was replaced, or NULL if not found. */ yyjson_api_inline yyjson_mut_val *yyjson_mut_doc_ptr_replace( yyjson_mut_doc *doc, const char *ptr, yyjson_mut_val *new_val); /** Replace value by a JSON pointer. @param doc The target JSON document. @param ptr The JSON pointer string (UTF-8, null-terminator is not required). @param len The length of `ptr` in bytes. @param new_val The new value to replace the old one. @return The old value that was replaced, or NULL if not found. */ yyjson_api_inline yyjson_mut_val *yyjson_mut_doc_ptr_replacen( yyjson_mut_doc *doc, const char *ptr, size_t len, yyjson_mut_val *new_val); /** Replace value by a JSON pointer. @param doc The target JSON document. @param ptr The JSON pointer string (UTF-8, null-terminator is not required). @param len The length of `ptr` in bytes. @param new_val The new value to replace the old one. @param ctx A pointer to store the result context, or NULL if not needed. @param err A pointer to store the error information, or NULL if not needed. @return The old value that was replaced, or NULL if not found. */ yyjson_api_inline yyjson_mut_val *yyjson_mut_doc_ptr_replacex( yyjson_mut_doc *doc, const char *ptr, size_t len, yyjson_mut_val *new_val, yyjson_ptr_ctx *ctx, yyjson_ptr_err *err); /** Replace value by a JSON pointer. @param val The target JSON value. @param ptr The JSON pointer string (UTF-8 with null-terminator). @param new_val The new value to replace the old one. @return The old value that was replaced, or NULL if not found. */ yyjson_api_inline yyjson_mut_val *yyjson_mut_ptr_replace( yyjson_mut_val *val, const char *ptr, yyjson_mut_val *new_val); /** Replace value by a JSON pointer. @param val The target JSON value. @param ptr The JSON pointer string (UTF-8, null-terminator is not required). @param len The length of `ptr` in bytes. @param new_val The new value to replace the old one. @return The old value that was replaced, or NULL if not found. */ yyjson_api_inline yyjson_mut_val *yyjson_mut_ptr_replacen( yyjson_mut_val *val, const char *ptr, size_t len, yyjson_mut_val *new_val); /** Replace value by a JSON pointer. @param val The target JSON value. @param ptr The JSON pointer string (UTF-8, null-terminator is not required). @param len The length of `ptr` in bytes. @param new_val The new value to replace the old one. @param ctx A pointer to store the result context, or NULL if not needed. @param err A pointer to store the error information, or NULL if not needed. @return The old value that was replaced, or NULL if not found. */ yyjson_api_inline yyjson_mut_val *yyjson_mut_ptr_replacex( yyjson_mut_val *val, const char *ptr, size_t len, yyjson_mut_val *new_val, yyjson_ptr_ctx *ctx, yyjson_ptr_err *err); /** Remove value by a JSON pointer. @param doc The target JSON document. @param ptr The JSON pointer string (UTF-8 with null-terminator). @return The removed value, or NULL on error. */ yyjson_api_inline yyjson_mut_val *yyjson_mut_doc_ptr_remove( yyjson_mut_doc *doc, const char *ptr); /** Remove value by a JSON pointer. @param doc The target JSON document. @param ptr The JSON pointer string (UTF-8, null-terminator is not required). @param len The length of `ptr` in bytes. @return The removed value, or NULL on error. */ yyjson_api_inline yyjson_mut_val *yyjson_mut_doc_ptr_removen( yyjson_mut_doc *doc, const char *ptr, size_t len); /** Remove value by a JSON pointer. @param doc The target JSON document. @param ptr The JSON pointer string (UTF-8, null-terminator is not required). @param len The length of `ptr` in bytes. @param ctx A pointer to store the result context, or NULL if not needed. @param err A pointer to store the error information, or NULL if not needed. @return The removed value, or NULL on error. */ yyjson_api_inline yyjson_mut_val *yyjson_mut_doc_ptr_removex( yyjson_mut_doc *doc, const char *ptr, size_t len, yyjson_ptr_ctx *ctx, yyjson_ptr_err *err); /** Remove value by a JSON pointer. @param val The target JSON value. @param ptr The JSON pointer string (UTF-8 with null-terminator). @return The removed value, or NULL on error. */ yyjson_api_inline yyjson_mut_val *yyjson_mut_ptr_remove(yyjson_mut_val *val, const char *ptr); /** Remove value by a JSON pointer. @param val The target JSON value. @param ptr The JSON pointer string (UTF-8, null-terminator is not required). @param len The length of `ptr` in bytes. @return The removed value, or NULL on error. */ yyjson_api_inline yyjson_mut_val *yyjson_mut_ptr_removen(yyjson_mut_val *val, const char *ptr, size_t len); /** Remove value by a JSON pointer. @param val The target JSON value. @param ptr The JSON pointer string (UTF-8, null-terminator is not required). @param len The length of `ptr` in bytes. @param ctx A pointer to store the result context, or NULL if not needed. @param err A pointer to store the error information, or NULL if not needed. @return The removed value, or NULL on error. */ yyjson_api_inline yyjson_mut_val *yyjson_mut_ptr_removex(yyjson_mut_val *val, const char *ptr, size_t len, yyjson_ptr_ctx *ctx, yyjson_ptr_err *err); /** Append value by JSON pointer context. @param ctx The context from the `yyjson_mut_ptr_xxx()` calls. @param key New key if `ctx->ctn` is object, or NULL if `ctx->ctn` is array. @param val New value to be added. @return true on success or false on fail. */ yyjson_api_inline bool yyjson_ptr_ctx_append(yyjson_ptr_ctx *ctx, yyjson_mut_val *key, yyjson_mut_val *val); /** Replace value by JSON pointer context. @param ctx The context from the `yyjson_mut_ptr_xxx()` calls. @param val New value to be replaced. @return true on success or false on fail. @note If success, the old value will be returned via `ctx->old`. */ yyjson_api_inline bool yyjson_ptr_ctx_replace(yyjson_ptr_ctx *ctx, yyjson_mut_val *val); /** Remove value by JSON pointer context. @param ctx The context from the `yyjson_mut_ptr_xxx()` calls. @return true on success or false on fail. @note If success, the old value will be returned via `ctx->old`. */ yyjson_api_inline bool yyjson_ptr_ctx_remove(yyjson_ptr_ctx *ctx); /*============================================================================== * MARK: - JSON Patch API (RFC 6902) * https://tools.ietf.org/html/rfc6902 *============================================================================*/ /** Result code for JSON patch. */ typedef uint32_t yyjson_patch_code; /** Success, no error. */ static const yyjson_patch_code YYJSON_PATCH_SUCCESS = 0; /** Invalid parameter, such as NULL input or non-array patch. */ static const yyjson_patch_code YYJSON_PATCH_ERROR_INVALID_PARAMETER = 1; /** Memory allocation failure occurs. */ static const yyjson_patch_code YYJSON_PATCH_ERROR_MEMORY_ALLOCATION = 2; /** JSON patch operation is not object type. */ static const yyjson_patch_code YYJSON_PATCH_ERROR_INVALID_OPERATION = 3; /** JSON patch operation is missing a required key. */ static const yyjson_patch_code YYJSON_PATCH_ERROR_MISSING_KEY = 4; /** JSON patch operation member is invalid. */ static const yyjson_patch_code YYJSON_PATCH_ERROR_INVALID_MEMBER = 5; /** JSON patch operation `test` not equal. */ static const yyjson_patch_code YYJSON_PATCH_ERROR_EQUAL = 6; /** JSON patch operation failed on JSON pointer. */ static const yyjson_patch_code YYJSON_PATCH_ERROR_POINTER = 7; /** Error information for JSON patch. */ typedef struct yyjson_patch_err { /** Error code, see `yyjson_patch_code` for all possible values. */ yyjson_patch_code code; /** Index of the error operation (0 if no error). */ size_t idx; /** Error message, constant, no need to free (NULL if no error). */ const char *msg; /** JSON pointer error if `code == YYJSON_PATCH_ERROR_POINTER`. */ yyjson_ptr_err ptr; } yyjson_patch_err; /** Creates and returns a patched JSON value (RFC 6902). The memory of the returned value is allocated by the `doc`. The `err` is used to receive error information, pass NULL if not needed. Returns NULL if the patch could not be applied. */ yyjson_api yyjson_mut_val *yyjson_patch(yyjson_mut_doc *doc, yyjson_val *orig, yyjson_val *patch, yyjson_patch_err *err); /** Creates and returns a patched JSON value (RFC 6902). The memory of the returned value is allocated by the `doc`. The `err` is used to receive error information, pass NULL if not needed. Returns NULL if the patch could not be applied. */ yyjson_api yyjson_mut_val *yyjson_mut_patch(yyjson_mut_doc *doc, yyjson_mut_val *orig, yyjson_mut_val *patch, yyjson_patch_err *err); /*============================================================================== * MARK: - JSON Merge-Patch API (RFC 7386) * https://tools.ietf.org/html/rfc7386 *============================================================================*/ /** Creates and returns a merge-patched JSON value (RFC 7386). The memory of the returned value is allocated by the `doc`. Returns NULL if the patch could not be applied. @warning This function is recursive and may cause a stack overflow if the object level is too deep. */ yyjson_api yyjson_mut_val *yyjson_merge_patch(yyjson_mut_doc *doc, yyjson_val *orig, yyjson_val *patch); /** Creates and returns a merge-patched JSON value (RFC 7386). The memory of the returned value is allocated by the `doc`. Returns NULL if the patch could not be applied. @warning This function is recursive and may cause a stack overflow if the object level is too deep. */ yyjson_api yyjson_mut_val *yyjson_mut_merge_patch(yyjson_mut_doc *doc, yyjson_mut_val *orig, yyjson_mut_val *patch); #endif /* YYJSON_DISABLE_UTILS */ /*============================================================================== * MARK: - JSON Structure (Implementation) *============================================================================*/ /** Payload of a JSON value (8 bytes). */ typedef union yyjson_val_uni { uint64_t u64; int64_t i64; double f64; const char *str; void *ptr; size_t ofs; } yyjson_val_uni; /** Immutable JSON value, 16 bytes. */ struct yyjson_val { uint64_t tag; /**< type, subtype and length */ yyjson_val_uni uni; /**< payload */ }; struct yyjson_doc { /** Root value of the document (nonnull). */ yyjson_val *root; /** Allocator used by document (nonnull). */ yyjson_alc alc; /** The total number of bytes read when parsing JSON (nonzero). */ size_t dat_read; /** The total number of value read when parsing JSON (nonzero). */ size_t val_read; /** The string pool used by JSON values (nullable). */ char *str_pool; }; /*============================================================================== * MARK: - Unsafe JSON Value API (Implementation) *============================================================================*/ /* Whether the string does not need to be escaped for serialization. This function is used to optimize the writing speed of small constant strings. This function works only if the compiler can evaluate it at compile time. Clang supports it since v8.0, earlier versions do not support constant_p(strlen) and return false. GCC supports it since at least v4.4, earlier versions may compile it as run-time instructions. ICC supports it since at least v16, earlier versions are uncertain. @param str The C string. @param len The returnd value from strlen(str). */ yyjson_api_inline bool unsafe_yyjson_is_str_noesc(const char *str, size_t len) { #if YYJSON_HAS_CONSTANT_P && \ (!YYJSON_IS_REAL_GCC || yyjson_gcc_available(4, 4, 0)) if (yyjson_constant_p(len) && len <= 32) { /* Same as the following loop: for (size_t i = 0; i < len; i++) { char c = str[i]; if (c < ' ' || c > '~' || c == '"' || c == '\\') return false; } GCC evaluates it at compile time only if the string length is within 17 and -O3 (which turns on the -fpeel-loops flag) is used. So the loop is unrolled for GCC. */ # define yyjson_repeat32_incr(x) \ x(0) x(1) x(2) x(3) x(4) x(5) x(6) x(7) \ x(8) x(9) x(10) x(11) x(12) x(13) x(14) x(15) \ x(16) x(17) x(18) x(19) x(20) x(21) x(22) x(23) \ x(24) x(25) x(26) x(27) x(28) x(29) x(30) x(31) # define yyjson_check_char_noesc(i) \ if (i < len) { \ char c = str[i]; \ if (c < ' ' || c > '~' || c == '"' || c == '\\') return false; } yyjson_repeat32_incr(yyjson_check_char_noesc) # undef yyjson_repeat32_incr # undef yyjson_check_char_noesc return true; } #else (void)str; (void)len; #endif return false; } yyjson_api_inline double unsafe_yyjson_u64_to_f64(uint64_t num) { #if YYJSON_U64_TO_F64_NO_IMPL uint64_t msb = ((uint64_t)1) << 63; if ((num & msb) == 0) { return (double)(int64_t)num; } else { return ((double)(int64_t)((num >> 1) | (num & 1))) * (double)2.0; } #else return (double)num; #endif } yyjson_api_inline yyjson_type unsafe_yyjson_get_type(void *val) { uint8_t tag = (uint8_t)((yyjson_val *)val)->tag; return (yyjson_type)(tag & YYJSON_TYPE_MASK); } yyjson_api_inline yyjson_subtype unsafe_yyjson_get_subtype(void *val) { uint8_t tag = (uint8_t)((yyjson_val *)val)->tag; return (yyjson_subtype)(tag & YYJSON_SUBTYPE_MASK); } yyjson_api_inline uint8_t unsafe_yyjson_get_tag(void *val) { uint8_t tag = (uint8_t)((yyjson_val *)val)->tag; return (uint8_t)(tag & YYJSON_TAG_MASK); } yyjson_api_inline bool unsafe_yyjson_is_raw(void *val) { return unsafe_yyjson_get_type(val) == YYJSON_TYPE_RAW; } yyjson_api_inline bool unsafe_yyjson_is_null(void *val) { return unsafe_yyjson_get_type(val) == YYJSON_TYPE_NULL; } yyjson_api_inline bool unsafe_yyjson_is_bool(void *val) { return unsafe_yyjson_get_type(val) == YYJSON_TYPE_BOOL; } yyjson_api_inline bool unsafe_yyjson_is_num(void *val) { return unsafe_yyjson_get_type(val) == YYJSON_TYPE_NUM; } yyjson_api_inline bool unsafe_yyjson_is_str(void *val) { return unsafe_yyjson_get_type(val) == YYJSON_TYPE_STR; } yyjson_api_inline bool unsafe_yyjson_is_arr(void *val) { return unsafe_yyjson_get_type(val) == YYJSON_TYPE_ARR; } yyjson_api_inline bool unsafe_yyjson_is_obj(void *val) { return unsafe_yyjson_get_type(val) == YYJSON_TYPE_OBJ; } yyjson_api_inline bool unsafe_yyjson_is_ctn(void *val) { uint8_t mask = YYJSON_TYPE_ARR & YYJSON_TYPE_OBJ; return (unsafe_yyjson_get_tag(val) & mask) == mask; } yyjson_api_inline bool unsafe_yyjson_is_uint(void *val) { const uint8_t patt = YYJSON_TYPE_NUM | YYJSON_SUBTYPE_UINT; return unsafe_yyjson_get_tag(val) == patt; } yyjson_api_inline bool unsafe_yyjson_is_sint(void *val) { const uint8_t patt = YYJSON_TYPE_NUM | YYJSON_SUBTYPE_SINT; return unsafe_yyjson_get_tag(val) == patt; } yyjson_api_inline bool unsafe_yyjson_is_int(void *val) { const uint8_t mask = YYJSON_TAG_MASK & (~YYJSON_SUBTYPE_SINT); const uint8_t patt = YYJSON_TYPE_NUM | YYJSON_SUBTYPE_UINT; return (unsafe_yyjson_get_tag(val) & mask) == patt; } yyjson_api_inline bool unsafe_yyjson_is_real(void *val) { const uint8_t patt = YYJSON_TYPE_NUM | YYJSON_SUBTYPE_REAL; return unsafe_yyjson_get_tag(val) == patt; } yyjson_api_inline bool unsafe_yyjson_is_true(void *val) { const uint8_t patt = YYJSON_TYPE_BOOL | YYJSON_SUBTYPE_TRUE; return unsafe_yyjson_get_tag(val) == patt; } yyjson_api_inline bool unsafe_yyjson_is_false(void *val) { const uint8_t patt = YYJSON_TYPE_BOOL | YYJSON_SUBTYPE_FALSE; return unsafe_yyjson_get_tag(val) == patt; } yyjson_api_inline bool unsafe_yyjson_arr_is_flat(yyjson_val *val) { size_t ofs = val->uni.ofs; size_t len = (size_t)(val->tag >> YYJSON_TAG_BIT); return len * sizeof(yyjson_val) + sizeof(yyjson_val) == ofs; } yyjson_api_inline const char *unsafe_yyjson_get_raw(void *val) { return ((yyjson_val *)val)->uni.str; } yyjson_api_inline bool unsafe_yyjson_get_bool(void *val) { uint8_t tag = unsafe_yyjson_get_tag(val); return (bool)((tag & YYJSON_SUBTYPE_MASK) >> YYJSON_TYPE_BIT); } yyjson_api_inline uint64_t unsafe_yyjson_get_uint(void *val) { return ((yyjson_val *)val)->uni.u64; } yyjson_api_inline int64_t unsafe_yyjson_get_sint(void *val) { return ((yyjson_val *)val)->uni.i64; } yyjson_api_inline int unsafe_yyjson_get_int(void *val) { return (int)((yyjson_val *)val)->uni.i64; } yyjson_api_inline double unsafe_yyjson_get_real(void *val) { return ((yyjson_val *)val)->uni.f64; } yyjson_api_inline double unsafe_yyjson_get_num(void *val) { uint8_t tag = unsafe_yyjson_get_tag(val); if (tag == (YYJSON_TYPE_NUM | YYJSON_SUBTYPE_REAL)) { return ((yyjson_val *)val)->uni.f64; } else if (tag == (YYJSON_TYPE_NUM | YYJSON_SUBTYPE_SINT)) { return (double)((yyjson_val *)val)->uni.i64; } else if (tag == (YYJSON_TYPE_NUM | YYJSON_SUBTYPE_UINT)) { return unsafe_yyjson_u64_to_f64(((yyjson_val *)val)->uni.u64); } return 0.0; } yyjson_api_inline const char *unsafe_yyjson_get_str(void *val) { return ((yyjson_val *)val)->uni.str; } yyjson_api_inline size_t unsafe_yyjson_get_len(void *val) { return (size_t)(((yyjson_val *)val)->tag >> YYJSON_TAG_BIT); } yyjson_api_inline yyjson_val *unsafe_yyjson_get_first(yyjson_val *ctn) { return ctn + 1; } yyjson_api_inline yyjson_val *unsafe_yyjson_get_next(yyjson_val *val) { bool is_ctn = unsafe_yyjson_is_ctn(val); size_t ctn_ofs = val->uni.ofs; size_t ofs = (is_ctn ? ctn_ofs : sizeof(yyjson_val)); return (yyjson_val *)(void *)((uint8_t *)val + ofs); } yyjson_api_inline bool unsafe_yyjson_equals_strn(void *val, const char *str, size_t len) { return unsafe_yyjson_get_len(val) == len && memcmp(((yyjson_val *)val)->uni.str, str, len) == 0; } yyjson_api_inline bool unsafe_yyjson_equals_str(void *val, const char *str) { return unsafe_yyjson_equals_strn(val, str, strlen(str)); } yyjson_api_inline void unsafe_yyjson_set_type(void *val, yyjson_type type, yyjson_subtype subtype) { uint8_t tag = (type | subtype); uint64_t new_tag = ((yyjson_val *)val)->tag; new_tag = (new_tag & (~(uint64_t)YYJSON_TAG_MASK)) | (uint64_t)tag; ((yyjson_val *)val)->tag = new_tag; } yyjson_api_inline void unsafe_yyjson_set_len(void *val, size_t len) { uint64_t tag = ((yyjson_val *)val)->tag & YYJSON_TAG_MASK; tag |= (uint64_t)len << YYJSON_TAG_BIT; ((yyjson_val *)val)->tag = tag; } yyjson_api_inline void unsafe_yyjson_set_tag(void *val, yyjson_type type, yyjson_subtype subtype, size_t len) { uint64_t tag = (uint64_t)len << YYJSON_TAG_BIT; tag |= (type | subtype); ((yyjson_val *)val)->tag = tag; } yyjson_api_inline void unsafe_yyjson_inc_len(void *val) { uint64_t tag = ((yyjson_val *)val)->tag; tag += (uint64_t)(1 << YYJSON_TAG_BIT); ((yyjson_val *)val)->tag = tag; } yyjson_api_inline void unsafe_yyjson_set_raw(void *val, const char *raw, size_t len) { unsafe_yyjson_set_tag(val, YYJSON_TYPE_RAW, YYJSON_SUBTYPE_NONE, len); ((yyjson_val *)val)->uni.str = raw; } yyjson_api_inline void unsafe_yyjson_set_null(void *val) { unsafe_yyjson_set_tag(val, YYJSON_TYPE_NULL, YYJSON_SUBTYPE_NONE, 0); } yyjson_api_inline void unsafe_yyjson_set_bool(void *val, bool num) { yyjson_subtype subtype = num ? YYJSON_SUBTYPE_TRUE : YYJSON_SUBTYPE_FALSE; unsafe_yyjson_set_tag(val, YYJSON_TYPE_BOOL, subtype, 0); } yyjson_api_inline void unsafe_yyjson_set_uint(void *val, uint64_t num) { unsafe_yyjson_set_tag(val, YYJSON_TYPE_NUM, YYJSON_SUBTYPE_UINT, 0); ((yyjson_val *)val)->uni.u64 = num; } yyjson_api_inline void unsafe_yyjson_set_sint(void *val, int64_t num) { unsafe_yyjson_set_tag(val, YYJSON_TYPE_NUM, YYJSON_SUBTYPE_SINT, 0); ((yyjson_val *)val)->uni.i64 = num; } yyjson_api_inline void unsafe_yyjson_set_fp_to_fixed(void *val, int prec) { ((yyjson_val *)val)->tag &= ~((uint64_t)YYJSON_WRITE_FP_TO_FIXED(15) << 32); ((yyjson_val *)val)->tag |= (uint64_t)YYJSON_WRITE_FP_TO_FIXED(prec) << 32; } yyjson_api_inline void unsafe_yyjson_set_fp_to_float(void *val, bool flt) { uint64_t flag = (uint64_t)YYJSON_WRITE_FP_TO_FLOAT << 32; if (flt) ((yyjson_val *)val)->tag |= flag; else ((yyjson_val *)val)->tag &= ~flag; } yyjson_api_inline void unsafe_yyjson_set_float(void *val, float num) { unsafe_yyjson_set_tag(val, YYJSON_TYPE_NUM, YYJSON_SUBTYPE_REAL, 0); ((yyjson_val *)val)->tag |= (uint64_t)YYJSON_WRITE_FP_TO_FLOAT << 32; ((yyjson_val *)val)->uni.f64 = (double)num; } yyjson_api_inline void unsafe_yyjson_set_double(void *val, double num) { unsafe_yyjson_set_tag(val, YYJSON_TYPE_NUM, YYJSON_SUBTYPE_REAL, 0); ((yyjson_val *)val)->uni.f64 = num; } yyjson_api_inline void unsafe_yyjson_set_real(void *val, double num) { unsafe_yyjson_set_tag(val, YYJSON_TYPE_NUM, YYJSON_SUBTYPE_REAL, 0); ((yyjson_val *)val)->uni.f64 = num; } yyjson_api_inline void unsafe_yyjson_set_str_noesc(void *val, bool noesc) { ((yyjson_val *)val)->tag &= ~(uint64_t)YYJSON_SUBTYPE_MASK; if (noesc) ((yyjson_val *)val)->tag |= (uint64_t)YYJSON_SUBTYPE_NOESC; } yyjson_api_inline void unsafe_yyjson_set_strn(void *val, const char *str, size_t len) { unsafe_yyjson_set_tag(val, YYJSON_TYPE_STR, YYJSON_SUBTYPE_NONE, len); ((yyjson_val *)val)->uni.str = str; } yyjson_api_inline void unsafe_yyjson_set_str(void *val, const char *str) { size_t len = strlen(str); bool noesc = unsafe_yyjson_is_str_noesc(str, len); yyjson_subtype subtype = noesc ? YYJSON_SUBTYPE_NOESC : YYJSON_SUBTYPE_NONE; unsafe_yyjson_set_tag(val, YYJSON_TYPE_STR, subtype, len); ((yyjson_val *)val)->uni.str = str; } yyjson_api_inline void unsafe_yyjson_set_arr(void *val, size_t size) { unsafe_yyjson_set_tag(val, YYJSON_TYPE_ARR, YYJSON_SUBTYPE_NONE, size); } yyjson_api_inline void unsafe_yyjson_set_obj(void *val, size_t size) { unsafe_yyjson_set_tag(val, YYJSON_TYPE_OBJ, YYJSON_SUBTYPE_NONE, size); } /*============================================================================== * MARK: - JSON Document API (Implementation) *============================================================================*/ yyjson_api_inline yyjson_val *yyjson_doc_get_root(yyjson_doc *doc) { return doc ? doc->root : NULL; } yyjson_api_inline size_t yyjson_doc_get_read_size(yyjson_doc *doc) { return doc ? doc->dat_read : 0; } yyjson_api_inline size_t yyjson_doc_get_val_count(yyjson_doc *doc) { return doc ? doc->val_read : 0; } yyjson_api_inline void yyjson_doc_free(yyjson_doc *doc) { if (doc) { yyjson_alc alc = doc->alc; memset(&doc->alc, 0, sizeof(alc)); if (doc->str_pool) alc.free(alc.ctx, doc->str_pool); alc.free(alc.ctx, doc); } } /*============================================================================== * MARK: - JSON Value Type API (Implementation) *============================================================================*/ yyjson_api_inline bool yyjson_is_raw(yyjson_val *val) { return val ? unsafe_yyjson_is_raw(val) : false; } yyjson_api_inline bool yyjson_is_null(yyjson_val *val) { return val ? unsafe_yyjson_is_null(val) : false; } yyjson_api_inline bool yyjson_is_true(yyjson_val *val) { return val ? unsafe_yyjson_is_true(val) : false; } yyjson_api_inline bool yyjson_is_false(yyjson_val *val) { return val ? unsafe_yyjson_is_false(val) : false; } yyjson_api_inline bool yyjson_is_bool(yyjson_val *val) { return val ? unsafe_yyjson_is_bool(val) : false; } yyjson_api_inline bool yyjson_is_uint(yyjson_val *val) { return val ? unsafe_yyjson_is_uint(val) : false; } yyjson_api_inline bool yyjson_is_sint(yyjson_val *val) { return val ? unsafe_yyjson_is_sint(val) : false; } yyjson_api_inline bool yyjson_is_int(yyjson_val *val) { return val ? unsafe_yyjson_is_int(val) : false; } yyjson_api_inline bool yyjson_is_real(yyjson_val *val) { return val ? unsafe_yyjson_is_real(val) : false; } yyjson_api_inline bool yyjson_is_num(yyjson_val *val) { return val ? unsafe_yyjson_is_num(val) : false; } yyjson_api_inline bool yyjson_is_str(yyjson_val *val) { return val ? unsafe_yyjson_is_str(val) : false; } yyjson_api_inline bool yyjson_is_arr(yyjson_val *val) { return val ? unsafe_yyjson_is_arr(val) : false; } yyjson_api_inline bool yyjson_is_obj(yyjson_val *val) { return val ? unsafe_yyjson_is_obj(val) : false; } yyjson_api_inline bool yyjson_is_ctn(yyjson_val *val) { return val ? unsafe_yyjson_is_ctn(val) : false; } /*============================================================================== * MARK: - JSON Value Content API (Implementation) *============================================================================*/ yyjson_api_inline yyjson_type yyjson_get_type(yyjson_val *val) { return val ? unsafe_yyjson_get_type(val) : YYJSON_TYPE_NONE; } yyjson_api_inline yyjson_subtype yyjson_get_subtype(yyjson_val *val) { return val ? unsafe_yyjson_get_subtype(val) : YYJSON_SUBTYPE_NONE; } yyjson_api_inline uint8_t yyjson_get_tag(yyjson_val *val) { return val ? unsafe_yyjson_get_tag(val) : 0; } yyjson_api_inline const char *yyjson_get_type_desc(yyjson_val *val) { switch (yyjson_get_tag(val)) { case YYJSON_TYPE_RAW | YYJSON_SUBTYPE_NONE: return "raw"; case YYJSON_TYPE_NULL | YYJSON_SUBTYPE_NONE: return "null"; case YYJSON_TYPE_STR | YYJSON_SUBTYPE_NONE: return "string"; case YYJSON_TYPE_STR | YYJSON_SUBTYPE_NOESC: return "string"; case YYJSON_TYPE_ARR | YYJSON_SUBTYPE_NONE: return "array"; case YYJSON_TYPE_OBJ | YYJSON_SUBTYPE_NONE: return "object"; case YYJSON_TYPE_BOOL | YYJSON_SUBTYPE_TRUE: return "true"; case YYJSON_TYPE_BOOL | YYJSON_SUBTYPE_FALSE: return "false"; case YYJSON_TYPE_NUM | YYJSON_SUBTYPE_UINT: return "uint"; case YYJSON_TYPE_NUM | YYJSON_SUBTYPE_SINT: return "sint"; case YYJSON_TYPE_NUM | YYJSON_SUBTYPE_REAL: return "real"; default: return "unknown"; } } yyjson_api_inline const char *yyjson_get_raw(yyjson_val *val) { return yyjson_is_raw(val) ? unsafe_yyjson_get_raw(val) : NULL; } yyjson_api_inline bool yyjson_get_bool(yyjson_val *val) { return yyjson_is_bool(val) ? unsafe_yyjson_get_bool(val) : false; } yyjson_api_inline uint64_t yyjson_get_uint(yyjson_val *val) { return yyjson_is_int(val) ? unsafe_yyjson_get_uint(val) : 0; } yyjson_api_inline int64_t yyjson_get_sint(yyjson_val *val) { return yyjson_is_int(val) ? unsafe_yyjson_get_sint(val) : 0; } yyjson_api_inline int yyjson_get_int(yyjson_val *val) { return yyjson_is_int(val) ? unsafe_yyjson_get_int(val) : 0; } yyjson_api_inline double yyjson_get_real(yyjson_val *val) { return yyjson_is_real(val) ? unsafe_yyjson_get_real(val) : 0.0; } yyjson_api_inline double yyjson_get_num(yyjson_val *val) { return val ? unsafe_yyjson_get_num(val) : 0.0; } yyjson_api_inline const char *yyjson_get_str(yyjson_val *val) { return yyjson_is_str(val) ? unsafe_yyjson_get_str(val) : NULL; } yyjson_api_inline size_t yyjson_get_len(yyjson_val *val) { return val ? unsafe_yyjson_get_len(val) : 0; } yyjson_api_inline bool yyjson_equals_str(yyjson_val *val, const char *str) { if (yyjson_likely(val && str)) { return unsafe_yyjson_is_str(val) && unsafe_yyjson_equals_str(val, str); } return false; } yyjson_api_inline bool yyjson_equals_strn(yyjson_val *val, const char *str, size_t len) { if (yyjson_likely(val && str)) { return unsafe_yyjson_is_str(val) && unsafe_yyjson_equals_strn(val, str, len); } return false; } yyjson_api bool unsafe_yyjson_equals(yyjson_val *lhs, yyjson_val *rhs); yyjson_api_inline bool yyjson_equals(yyjson_val *lhs, yyjson_val *rhs) { if (yyjson_unlikely(!lhs || !rhs)) return false; return unsafe_yyjson_equals(lhs, rhs); } yyjson_api_inline bool yyjson_set_raw(yyjson_val *val, const char *raw, size_t len) { if (yyjson_unlikely(!val || unsafe_yyjson_is_ctn(val))) return false; unsafe_yyjson_set_raw(val, raw, len); return true; } yyjson_api_inline bool yyjson_set_null(yyjson_val *val) { if (yyjson_unlikely(!val || unsafe_yyjson_is_ctn(val))) return false; unsafe_yyjson_set_null(val); return true; } yyjson_api_inline bool yyjson_set_bool(yyjson_val *val, bool num) { if (yyjson_unlikely(!val || unsafe_yyjson_is_ctn(val))) return false; unsafe_yyjson_set_bool(val, num); return true; } yyjson_api_inline bool yyjson_set_uint(yyjson_val *val, uint64_t num) { if (yyjson_unlikely(!val || unsafe_yyjson_is_ctn(val))) return false; unsafe_yyjson_set_uint(val, num); return true; } yyjson_api_inline bool yyjson_set_sint(yyjson_val *val, int64_t num) { if (yyjson_unlikely(!val || unsafe_yyjson_is_ctn(val))) return false; unsafe_yyjson_set_sint(val, num); return true; } yyjson_api_inline bool yyjson_set_int(yyjson_val *val, int num) { if (yyjson_unlikely(!val || unsafe_yyjson_is_ctn(val))) return false; unsafe_yyjson_set_sint(val, (int64_t)num); return true; } yyjson_api_inline bool yyjson_set_float(yyjson_val *val, float num) { if (yyjson_unlikely(!val || unsafe_yyjson_is_ctn(val))) return false; unsafe_yyjson_set_float(val, num); return true; } yyjson_api_inline bool yyjson_set_double(yyjson_val *val, double num) { if (yyjson_unlikely(!val || unsafe_yyjson_is_ctn(val))) return false; unsafe_yyjson_set_double(val, num); return true; } yyjson_api_inline bool yyjson_set_real(yyjson_val *val, double num) { if (yyjson_unlikely(!val || unsafe_yyjson_is_ctn(val))) return false; unsafe_yyjson_set_real(val, num); return true; } yyjson_api_inline bool yyjson_set_fp_to_fixed(yyjson_val *val, int prec) { if (yyjson_unlikely(!yyjson_is_real(val))) return false; unsafe_yyjson_set_fp_to_fixed(val, prec); return true; } yyjson_api_inline bool yyjson_set_fp_to_float(yyjson_val *val, bool flt) { if (yyjson_unlikely(!yyjson_is_real(val))) return false; unsafe_yyjson_set_fp_to_float(val, flt); return true; } yyjson_api_inline bool yyjson_set_str(yyjson_val *val, const char *str) { if (yyjson_unlikely(!val || unsafe_yyjson_is_ctn(val))) return false; if (yyjson_unlikely(!str)) return false; unsafe_yyjson_set_str(val, str); return true; } yyjson_api_inline bool yyjson_set_strn(yyjson_val *val, const char *str, size_t len) { if (yyjson_unlikely(!val || unsafe_yyjson_is_ctn(val))) return false; if (yyjson_unlikely(!str)) return false; unsafe_yyjson_set_strn(val, str, len); return true; } yyjson_api_inline bool yyjson_set_str_noesc(yyjson_val *val, bool noesc) { if (yyjson_unlikely(!yyjson_is_str(val))) return false; unsafe_yyjson_set_str_noesc(val, noesc); return true; } /*============================================================================== * MARK: - JSON Array API (Implementation) *============================================================================*/ yyjson_api_inline size_t yyjson_arr_size(yyjson_val *arr) { return yyjson_is_arr(arr) ? unsafe_yyjson_get_len(arr) : 0; } yyjson_api_inline yyjson_val *yyjson_arr_get(yyjson_val *arr, size_t idx) { if (yyjson_likely(yyjson_is_arr(arr))) { if (yyjson_likely(unsafe_yyjson_get_len(arr) > idx)) { yyjson_val *val = unsafe_yyjson_get_first(arr); if (unsafe_yyjson_arr_is_flat(arr)) { return val + idx; } else { while (idx-- > 0) val = unsafe_yyjson_get_next(val); return val; } } } return NULL; } yyjson_api_inline yyjson_val *yyjson_arr_get_first(yyjson_val *arr) { if (yyjson_likely(yyjson_is_arr(arr))) { if (yyjson_likely(unsafe_yyjson_get_len(arr) > 0)) { return unsafe_yyjson_get_first(arr); } } return NULL; } yyjson_api_inline yyjson_val *yyjson_arr_get_last(yyjson_val *arr) { if (yyjson_likely(yyjson_is_arr(arr))) { size_t len = unsafe_yyjson_get_len(arr); if (yyjson_likely(len > 0)) { yyjson_val *val = unsafe_yyjson_get_first(arr); if (unsafe_yyjson_arr_is_flat(arr)) { return val + (len - 1); } else { while (len-- > 1) val = unsafe_yyjson_get_next(val); return val; } } } return NULL; } /*============================================================================== * MARK: - JSON Array Iterator API (Implementation) *============================================================================*/ yyjson_api_inline bool yyjson_arr_iter_init(yyjson_val *arr, yyjson_arr_iter *iter) { if (yyjson_likely(yyjson_is_arr(arr) && iter)) { iter->idx = 0; iter->max = unsafe_yyjson_get_len(arr); iter->cur = unsafe_yyjson_get_first(arr); return true; } if (iter) memset(iter, 0, sizeof(yyjson_arr_iter)); return false; } yyjson_api_inline yyjson_arr_iter yyjson_arr_iter_with(yyjson_val *arr) { yyjson_arr_iter iter; yyjson_arr_iter_init(arr, &iter); return iter; } yyjson_api_inline bool yyjson_arr_iter_has_next(yyjson_arr_iter *iter) { return iter ? iter->idx < iter->max : false; } yyjson_api_inline yyjson_val *yyjson_arr_iter_next(yyjson_arr_iter *iter) { yyjson_val *val; if (iter && iter->idx < iter->max) { val = iter->cur; iter->cur = unsafe_yyjson_get_next(val); iter->idx++; return val; } return NULL; } /*============================================================================== * MARK: - JSON Object API (Implementation) *============================================================================*/ yyjson_api_inline size_t yyjson_obj_size(yyjson_val *obj) { return yyjson_is_obj(obj) ? unsafe_yyjson_get_len(obj) : 0; } yyjson_api_inline yyjson_val *yyjson_obj_get(yyjson_val *obj, const char *key) { return yyjson_obj_getn(obj, key, key ? strlen(key) : 0); } yyjson_api_inline yyjson_val *yyjson_obj_getn(yyjson_val *obj, const char *_key, size_t key_len) { if (yyjson_likely(yyjson_is_obj(obj) && _key)) { size_t len = unsafe_yyjson_get_len(obj); yyjson_val *key = unsafe_yyjson_get_first(obj); while (len-- > 0) { if (unsafe_yyjson_equals_strn(key, _key, key_len)) return key + 1; key = unsafe_yyjson_get_next(key + 1); } } return NULL; } /*============================================================================== * MARK: - JSON Object Iterator API (Implementation) *============================================================================*/ yyjson_api_inline bool yyjson_obj_iter_init(yyjson_val *obj, yyjson_obj_iter *iter) { if (yyjson_likely(yyjson_is_obj(obj) && iter)) { iter->idx = 0; iter->max = unsafe_yyjson_get_len(obj); iter->cur = unsafe_yyjson_get_first(obj); iter->obj = obj; return true; } if (iter) memset(iter, 0, sizeof(yyjson_obj_iter)); return false; } yyjson_api_inline yyjson_obj_iter yyjson_obj_iter_with(yyjson_val *obj) { yyjson_obj_iter iter; yyjson_obj_iter_init(obj, &iter); return iter; } yyjson_api_inline bool yyjson_obj_iter_has_next(yyjson_obj_iter *iter) { return iter ? iter->idx < iter->max : false; } yyjson_api_inline yyjson_val *yyjson_obj_iter_next(yyjson_obj_iter *iter) { if (iter && iter->idx < iter->max) { yyjson_val *key = iter->cur; iter->idx++; iter->cur = unsafe_yyjson_get_next(key + 1); return key; } return NULL; } yyjson_api_inline yyjson_val *yyjson_obj_iter_get_val(yyjson_val *key) { return key ? key + 1 : NULL; } yyjson_api_inline yyjson_val *yyjson_obj_iter_get(yyjson_obj_iter *iter, const char *key) { return yyjson_obj_iter_getn(iter, key, key ? strlen(key) : 0); } yyjson_api_inline yyjson_val *yyjson_obj_iter_getn(yyjson_obj_iter *iter, const char *key, size_t key_len) { if (iter && key) { size_t idx = iter->idx; size_t max = iter->max; yyjson_val *cur = iter->cur; if (yyjson_unlikely(idx == max)) { idx = 0; cur = unsafe_yyjson_get_first(iter->obj); } while (idx++ < max) { yyjson_val *next = unsafe_yyjson_get_next(cur + 1); if (unsafe_yyjson_equals_strn(cur, key, key_len)) { iter->idx = idx; iter->cur = next; return cur + 1; } cur = next; if (idx == iter->max && iter->idx < iter->max) { idx = 0; max = iter->idx; cur = unsafe_yyjson_get_first(iter->obj); } } } return NULL; } /*============================================================================== * MARK: - Mutable JSON Structure (Implementation) *============================================================================*/ /** Mutable JSON value, 24 bytes. The 'tag' and 'uni' field is same as immutable value. The 'next' field links all elements inside the container to be a cycle. */ struct yyjson_mut_val { uint64_t tag; /**< type, subtype and length */ yyjson_val_uni uni; /**< payload */ yyjson_mut_val *next; /**< the next value in circular linked list */ }; /** A memory chunk in string memory pool. */ typedef struct yyjson_str_chunk { struct yyjson_str_chunk *next; /* next chunk linked list */ size_t chunk_size; /* chunk size in bytes */ /* char str[]; flexible array member */ } yyjson_str_chunk; /** A memory pool to hold all strings in a mutable document. */ typedef struct yyjson_str_pool { char *cur; /* cursor inside current chunk */ char *end; /* the end of current chunk */ size_t chunk_size; /* chunk size in bytes while creating new chunk */ size_t chunk_size_max; /* maximum chunk size in bytes */ yyjson_str_chunk *chunks; /* a linked list of chunks, nullable */ } yyjson_str_pool; /** A memory chunk in value memory pool. `sizeof(yyjson_val_chunk)` should not larger than `sizeof(yyjson_mut_val)`. */ typedef struct yyjson_val_chunk { struct yyjson_val_chunk *next; /* next chunk linked list */ size_t chunk_size; /* chunk size in bytes */ /* char pad[sizeof(yyjson_mut_val) - sizeof(yyjson_val_chunk)]; padding */ /* yyjson_mut_val vals[]; flexible array member */ } yyjson_val_chunk; /** A memory pool to hold all values in a mutable document. */ typedef struct yyjson_val_pool { yyjson_mut_val *cur; /* cursor inside current chunk */ yyjson_mut_val *end; /* the end of current chunk */ size_t chunk_size; /* chunk size in bytes while creating new chunk */ size_t chunk_size_max; /* maximum chunk size in bytes */ yyjson_val_chunk *chunks; /* a linked list of chunks, nullable */ } yyjson_val_pool; struct yyjson_mut_doc { yyjson_mut_val *root; /**< root value of the JSON document, nullable */ yyjson_alc alc; /**< a valid allocator, nonnull */ yyjson_str_pool str_pool; /**< string memory pool */ yyjson_val_pool val_pool; /**< value memory pool */ }; /* Ensures the capacity to at least equal to the specified byte length. */ yyjson_api bool unsafe_yyjson_str_pool_grow(yyjson_str_pool *pool, const yyjson_alc *alc, size_t len); /* Ensures the capacity to at least equal to the specified value count. */ yyjson_api bool unsafe_yyjson_val_pool_grow(yyjson_val_pool *pool, const yyjson_alc *alc, size_t count); /* Allocate memory for string. */ yyjson_api_inline char *unsafe_yyjson_mut_str_alc(yyjson_mut_doc *doc, size_t len) { char *mem; const yyjson_alc *alc = &doc->alc; yyjson_str_pool *pool = &doc->str_pool; if (yyjson_unlikely((size_t)(pool->end - pool->cur) <= len)) { if (yyjson_unlikely(!unsafe_yyjson_str_pool_grow(pool, alc, len + 1))) { return NULL; } } mem = pool->cur; pool->cur = mem + len + 1; return mem; } yyjson_api_inline char *unsafe_yyjson_mut_strncpy(yyjson_mut_doc *doc, const char *str, size_t len) { char *mem = unsafe_yyjson_mut_str_alc(doc, len); if (yyjson_unlikely(!mem)) return NULL; memcpy((void *)mem, (const void *)str, len); mem[len] = '\0'; return mem; } yyjson_api_inline yyjson_mut_val *unsafe_yyjson_mut_val(yyjson_mut_doc *doc, size_t count) { yyjson_mut_val *val; yyjson_alc *alc = &doc->alc; yyjson_val_pool *pool = &doc->val_pool; if (yyjson_unlikely((size_t)(pool->end - pool->cur) < count)) { if (yyjson_unlikely(!unsafe_yyjson_val_pool_grow(pool, alc, count))) { return NULL; } } val = pool->cur; pool->cur += count; return val; } /*============================================================================== * MARK: - Mutable JSON Document API (Implementation) *============================================================================*/ yyjson_api_inline yyjson_mut_val *yyjson_mut_doc_get_root(yyjson_mut_doc *doc) { return doc ? doc->root : NULL; } yyjson_api_inline void yyjson_mut_doc_set_root(yyjson_mut_doc *doc, yyjson_mut_val *root) { if (doc) doc->root = root; } /*============================================================================== * MARK: - Mutable JSON Value Type API (Implementation) *============================================================================*/ yyjson_api_inline bool yyjson_mut_is_raw(yyjson_mut_val *val) { return val ? unsafe_yyjson_is_raw(val) : false; } yyjson_api_inline bool yyjson_mut_is_null(yyjson_mut_val *val) { return val ? unsafe_yyjson_is_null(val) : false; } yyjson_api_inline bool yyjson_mut_is_true(yyjson_mut_val *val) { return val ? unsafe_yyjson_is_true(val) : false; } yyjson_api_inline bool yyjson_mut_is_false(yyjson_mut_val *val) { return val ? unsafe_yyjson_is_false(val) : false; } yyjson_api_inline bool yyjson_mut_is_bool(yyjson_mut_val *val) { return val ? unsafe_yyjson_is_bool(val) : false; } yyjson_api_inline bool yyjson_mut_is_uint(yyjson_mut_val *val) { return val ? unsafe_yyjson_is_uint(val) : false; } yyjson_api_inline bool yyjson_mut_is_sint(yyjson_mut_val *val) { return val ? unsafe_yyjson_is_sint(val) : false; } yyjson_api_inline bool yyjson_mut_is_int(yyjson_mut_val *val) { return val ? unsafe_yyjson_is_int(val) : false; } yyjson_api_inline bool yyjson_mut_is_real(yyjson_mut_val *val) { return val ? unsafe_yyjson_is_real(val) : false; } yyjson_api_inline bool yyjson_mut_is_num(yyjson_mut_val *val) { return val ? unsafe_yyjson_is_num(val) : false; } yyjson_api_inline bool yyjson_mut_is_str(yyjson_mut_val *val) { return val ? unsafe_yyjson_is_str(val) : false; } yyjson_api_inline bool yyjson_mut_is_arr(yyjson_mut_val *val) { return val ? unsafe_yyjson_is_arr(val) : false; } yyjson_api_inline bool yyjson_mut_is_obj(yyjson_mut_val *val) { return val ? unsafe_yyjson_is_obj(val) : false; } yyjson_api_inline bool yyjson_mut_is_ctn(yyjson_mut_val *val) { return val ? unsafe_yyjson_is_ctn(val) : false; } /*============================================================================== * MARK: - Mutable JSON Value Content API (Implementation) *============================================================================*/ yyjson_api_inline yyjson_type yyjson_mut_get_type(yyjson_mut_val *val) { return yyjson_get_type((yyjson_val *)val); } yyjson_api_inline yyjson_subtype yyjson_mut_get_subtype(yyjson_mut_val *val) { return yyjson_get_subtype((yyjson_val *)val); } yyjson_api_inline uint8_t yyjson_mut_get_tag(yyjson_mut_val *val) { return yyjson_get_tag((yyjson_val *)val); } yyjson_api_inline const char *yyjson_mut_get_type_desc(yyjson_mut_val *val) { return yyjson_get_type_desc((yyjson_val *)val); } yyjson_api_inline const char *yyjson_mut_get_raw(yyjson_mut_val *val) { return yyjson_get_raw((yyjson_val *)val); } yyjson_api_inline bool yyjson_mut_get_bool(yyjson_mut_val *val) { return yyjson_get_bool((yyjson_val *)val); } yyjson_api_inline uint64_t yyjson_mut_get_uint(yyjson_mut_val *val) { return yyjson_get_uint((yyjson_val *)val); } yyjson_api_inline int64_t yyjson_mut_get_sint(yyjson_mut_val *val) { return yyjson_get_sint((yyjson_val *)val); } yyjson_api_inline int yyjson_mut_get_int(yyjson_mut_val *val) { return yyjson_get_int((yyjson_val *)val); } yyjson_api_inline double yyjson_mut_get_real(yyjson_mut_val *val) { return yyjson_get_real((yyjson_val *)val); } yyjson_api_inline double yyjson_mut_get_num(yyjson_mut_val *val) { return yyjson_get_num((yyjson_val *)val); } yyjson_api_inline const char *yyjson_mut_get_str(yyjson_mut_val *val) { return yyjson_get_str((yyjson_val *)val); } yyjson_api_inline size_t yyjson_mut_get_len(yyjson_mut_val *val) { return yyjson_get_len((yyjson_val *)val); } yyjson_api_inline bool yyjson_mut_equals_str(yyjson_mut_val *val, const char *str) { return yyjson_equals_str((yyjson_val *)val, str); } yyjson_api_inline bool yyjson_mut_equals_strn(yyjson_mut_val *val, const char *str, size_t len) { return yyjson_equals_strn((yyjson_val *)val, str, len); } yyjson_api bool unsafe_yyjson_mut_equals(yyjson_mut_val *lhs, yyjson_mut_val *rhs); yyjson_api_inline bool yyjson_mut_equals(yyjson_mut_val *lhs, yyjson_mut_val *rhs) { if (yyjson_unlikely(!lhs || !rhs)) return false; return unsafe_yyjson_mut_equals(lhs, rhs); } yyjson_api_inline bool yyjson_mut_set_raw(yyjson_mut_val *val, const char *raw, size_t len) { if (yyjson_unlikely(!val || !raw)) return false; unsafe_yyjson_set_raw(val, raw, len); return true; } yyjson_api_inline bool yyjson_mut_set_null(yyjson_mut_val *val) { if (yyjson_unlikely(!val)) return false; unsafe_yyjson_set_null(val); return true; } yyjson_api_inline bool yyjson_mut_set_bool(yyjson_mut_val *val, bool num) { if (yyjson_unlikely(!val)) return false; unsafe_yyjson_set_bool(val, num); return true; } yyjson_api_inline bool yyjson_mut_set_uint(yyjson_mut_val *val, uint64_t num) { if (yyjson_unlikely(!val)) return false; unsafe_yyjson_set_uint(val, num); return true; } yyjson_api_inline bool yyjson_mut_set_sint(yyjson_mut_val *val, int64_t num) { if (yyjson_unlikely(!val)) return false; unsafe_yyjson_set_sint(val, num); return true; } yyjson_api_inline bool yyjson_mut_set_int(yyjson_mut_val *val, int num) { if (yyjson_unlikely(!val)) return false; unsafe_yyjson_set_sint(val, (int64_t)num); return true; } yyjson_api_inline bool yyjson_mut_set_float(yyjson_mut_val *val, float num) { if (yyjson_unlikely(!val)) return false; unsafe_yyjson_set_float(val, num); return true; } yyjson_api_inline bool yyjson_mut_set_double(yyjson_mut_val *val, double num) { if (yyjson_unlikely(!val)) return false; unsafe_yyjson_set_double(val, num); return true; } yyjson_api_inline bool yyjson_mut_set_real(yyjson_mut_val *val, double num) { if (yyjson_unlikely(!val)) return false; unsafe_yyjson_set_real(val, num); return true; } yyjson_api_inline bool yyjson_mut_set_fp_to_fixed(yyjson_mut_val *val, int prec) { if (yyjson_unlikely(!yyjson_mut_is_real(val))) return false; unsafe_yyjson_set_fp_to_fixed(val, prec); return true; } yyjson_api_inline bool yyjson_mut_set_fp_to_float(yyjson_mut_val *val, bool flt) { if (yyjson_unlikely(!yyjson_mut_is_real(val))) return false; unsafe_yyjson_set_fp_to_float(val, flt); return true; } yyjson_api_inline bool yyjson_mut_set_str(yyjson_mut_val *val, const char *str) { if (yyjson_unlikely(!val || !str)) return false; unsafe_yyjson_set_str(val, str); return true; } yyjson_api_inline bool yyjson_mut_set_strn(yyjson_mut_val *val, const char *str, size_t len) { if (yyjson_unlikely(!val || !str)) return false; unsafe_yyjson_set_strn(val, str, len); return true; } yyjson_api_inline bool yyjson_mut_set_str_noesc(yyjson_mut_val *val, bool noesc) { if (yyjson_unlikely(!yyjson_mut_is_str(val))) return false; unsafe_yyjson_set_str_noesc(val, noesc); return true; } yyjson_api_inline bool yyjson_mut_set_arr(yyjson_mut_val *val) { if (yyjson_unlikely(!val)) return false; unsafe_yyjson_set_arr(val, 0); return true; } yyjson_api_inline bool yyjson_mut_set_obj(yyjson_mut_val *val) { if (yyjson_unlikely(!val)) return false; unsafe_yyjson_set_obj(val, 0); return true; } /*============================================================================== * MARK: - Mutable JSON Value Creation API (Implementation) *============================================================================*/ #define yyjson_mut_val_one(func) \ if (yyjson_likely(doc)) { \ yyjson_mut_val *val = unsafe_yyjson_mut_val(doc, 1); \ if (yyjson_likely(val)) { \ func \ return val; \ } \ } \ return NULL #define yyjson_mut_val_one_str(func) \ if (yyjson_likely(doc && str)) { \ yyjson_mut_val *val = unsafe_yyjson_mut_val(doc, 1); \ if (yyjson_likely(val)) { \ func \ return val; \ } \ } \ return NULL yyjson_api_inline yyjson_mut_val *yyjson_mut_raw(yyjson_mut_doc *doc, const char *str) { yyjson_mut_val_one_str({ unsafe_yyjson_set_raw(val, str, strlen(str)); }); } yyjson_api_inline yyjson_mut_val *yyjson_mut_rawn(yyjson_mut_doc *doc, const char *str, size_t len) { yyjson_mut_val_one_str({ unsafe_yyjson_set_raw(val, str, len); }); } yyjson_api_inline yyjson_mut_val *yyjson_mut_rawcpy(yyjson_mut_doc *doc, const char *str) { yyjson_mut_val_one_str({ size_t len = strlen(str); char *new_str = unsafe_yyjson_mut_strncpy(doc, str, len); if (yyjson_unlikely(!new_str)) return NULL; unsafe_yyjson_set_raw(val, new_str, len); }); } yyjson_api_inline yyjson_mut_val *yyjson_mut_rawncpy(yyjson_mut_doc *doc, const char *str, size_t len) { yyjson_mut_val_one_str({ char *new_str = unsafe_yyjson_mut_strncpy(doc, str, len); if (yyjson_unlikely(!new_str)) return NULL; unsafe_yyjson_set_raw(val, new_str, len); }); } yyjson_api_inline yyjson_mut_val *yyjson_mut_null(yyjson_mut_doc *doc) { yyjson_mut_val_one({ unsafe_yyjson_set_null(val); }); } yyjson_api_inline yyjson_mut_val *yyjson_mut_true(yyjson_mut_doc *doc) { yyjson_mut_val_one({ unsafe_yyjson_set_bool(val, true); }); } yyjson_api_inline yyjson_mut_val *yyjson_mut_false(yyjson_mut_doc *doc) { yyjson_mut_val_one({ unsafe_yyjson_set_bool(val, false); }); } yyjson_api_inline yyjson_mut_val *yyjson_mut_bool(yyjson_mut_doc *doc, bool _val) { yyjson_mut_val_one({ unsafe_yyjson_set_bool(val, _val); }); } yyjson_api_inline yyjson_mut_val *yyjson_mut_uint(yyjson_mut_doc *doc, uint64_t num) { yyjson_mut_val_one({ unsafe_yyjson_set_uint(val, num); }); } yyjson_api_inline yyjson_mut_val *yyjson_mut_sint(yyjson_mut_doc *doc, int64_t num) { yyjson_mut_val_one({ unsafe_yyjson_set_sint(val, num); }); } yyjson_api_inline yyjson_mut_val *yyjson_mut_int(yyjson_mut_doc *doc, int64_t num) { yyjson_mut_val_one({ unsafe_yyjson_set_sint(val, num); }); } yyjson_api_inline yyjson_mut_val *yyjson_mut_float(yyjson_mut_doc *doc, float num) { yyjson_mut_val_one({ unsafe_yyjson_set_float(val, num); }); } yyjson_api_inline yyjson_mut_val *yyjson_mut_double(yyjson_mut_doc *doc, double num) { yyjson_mut_val_one({ unsafe_yyjson_set_double(val, num); }); } yyjson_api_inline yyjson_mut_val *yyjson_mut_real(yyjson_mut_doc *doc, double num) { yyjson_mut_val_one({ unsafe_yyjson_set_real(val, num); }); } yyjson_api_inline yyjson_mut_val *yyjson_mut_str(yyjson_mut_doc *doc, const char *str) { yyjson_mut_val_one_str({ unsafe_yyjson_set_str(val, str); }); } yyjson_api_inline yyjson_mut_val *yyjson_mut_strn(yyjson_mut_doc *doc, const char *str, size_t len) { yyjson_mut_val_one_str({ unsafe_yyjson_set_strn(val, str, len); }); } yyjson_api_inline yyjson_mut_val *yyjson_mut_strcpy(yyjson_mut_doc *doc, const char *str) { yyjson_mut_val_one_str({ size_t len = strlen(str); bool noesc = unsafe_yyjson_is_str_noesc(str, len); yyjson_subtype sub = noesc ? YYJSON_SUBTYPE_NOESC : YYJSON_SUBTYPE_NONE; char *new_str = unsafe_yyjson_mut_strncpy(doc, str, len); if (yyjson_unlikely(!new_str)) return NULL; unsafe_yyjson_set_tag(val, YYJSON_TYPE_STR, sub, len); val->uni.str = new_str; }); } yyjson_api_inline yyjson_mut_val *yyjson_mut_strncpy(yyjson_mut_doc *doc, const char *str, size_t len) { yyjson_mut_val_one_str({ char *new_str = unsafe_yyjson_mut_strncpy(doc, str, len); if (yyjson_unlikely(!new_str)) return NULL; unsafe_yyjson_set_strn(val, new_str, len); }); } #undef yyjson_mut_val_one #undef yyjson_mut_val_one_str /*============================================================================== * MARK: - Mutable JSON Array API (Implementation) *============================================================================*/ yyjson_api_inline size_t yyjson_mut_arr_size(yyjson_mut_val *arr) { return yyjson_mut_is_arr(arr) ? unsafe_yyjson_get_len(arr) : 0; } yyjson_api_inline yyjson_mut_val *yyjson_mut_arr_get(yyjson_mut_val *arr, size_t idx) { if (yyjson_likely(idx < yyjson_mut_arr_size(arr))) { yyjson_mut_val *val = (yyjson_mut_val *)arr->uni.ptr; while (idx-- > 0) val = val->next; return val->next; } return NULL; } yyjson_api_inline yyjson_mut_val *yyjson_mut_arr_get_first( yyjson_mut_val *arr) { if (yyjson_likely(yyjson_mut_arr_size(arr) > 0)) { return ((yyjson_mut_val *)arr->uni.ptr)->next; } return NULL; } yyjson_api_inline yyjson_mut_val *yyjson_mut_arr_get_last( yyjson_mut_val *arr) { if (yyjson_likely(yyjson_mut_arr_size(arr) > 0)) { return ((yyjson_mut_val *)arr->uni.ptr); } return NULL; } /*============================================================================== * MARK: - Mutable JSON Array Iterator API (Implementation) *============================================================================*/ yyjson_api_inline bool yyjson_mut_arr_iter_init(yyjson_mut_val *arr, yyjson_mut_arr_iter *iter) { if (yyjson_likely(yyjson_mut_is_arr(arr) && iter)) { iter->idx = 0; iter->max = unsafe_yyjson_get_len(arr); iter->cur = iter->max ? (yyjson_mut_val *)arr->uni.ptr : NULL; iter->pre = NULL; iter->arr = arr; return true; } if (iter) memset(iter, 0, sizeof(yyjson_mut_arr_iter)); return false; } yyjson_api_inline yyjson_mut_arr_iter yyjson_mut_arr_iter_with( yyjson_mut_val *arr) { yyjson_mut_arr_iter iter; yyjson_mut_arr_iter_init(arr, &iter); return iter; } yyjson_api_inline bool yyjson_mut_arr_iter_has_next(yyjson_mut_arr_iter *iter) { return iter ? iter->idx < iter->max : false; } yyjson_api_inline yyjson_mut_val *yyjson_mut_arr_iter_next( yyjson_mut_arr_iter *iter) { if (iter && iter->idx < iter->max) { yyjson_mut_val *val = iter->cur; iter->pre = val; iter->cur = val->next; iter->idx++; return iter->cur; } return NULL; } yyjson_api_inline yyjson_mut_val *yyjson_mut_arr_iter_remove( yyjson_mut_arr_iter *iter) { if (yyjson_likely(iter && 0 < iter->idx && iter->idx <= iter->max)) { yyjson_mut_val *prev = iter->pre; yyjson_mut_val *cur = iter->cur; yyjson_mut_val *next = cur->next; if (yyjson_unlikely(iter->idx == iter->max)) iter->arr->uni.ptr = prev; iter->idx--; iter->max--; unsafe_yyjson_set_len(iter->arr, iter->max); prev->next = next; iter->cur = prev; return cur; } return NULL; } /*============================================================================== * MARK: - Mutable JSON Array Creation API (Implementation) *============================================================================*/ yyjson_api_inline yyjson_mut_val *yyjson_mut_arr(yyjson_mut_doc *doc) { if (yyjson_likely(doc)) { yyjson_mut_val *val = unsafe_yyjson_mut_val(doc, 1); if (yyjson_likely(val)) { val->tag = YYJSON_TYPE_ARR | YYJSON_SUBTYPE_NONE; return val; } } return NULL; } #define yyjson_mut_arr_with_func(func) \ if (yyjson_likely(doc && ((0 < count && count < \ (~(size_t)0) / sizeof(yyjson_mut_val) && vals) || count == 0))) { \ yyjson_mut_val *arr = unsafe_yyjson_mut_val(doc, 1 + count); \ if (yyjson_likely(arr)) { \ arr->tag = ((uint64_t)count << YYJSON_TAG_BIT) | YYJSON_TYPE_ARR; \ if (count > 0) { \ size_t i; \ for (i = 0; i < count; i++) { \ yyjson_mut_val *val = arr + i + 1; \ func \ val->next = val + 1; \ } \ arr[count].next = arr + 1; \ arr->uni.ptr = arr + count; \ } \ return arr; \ } \ } \ return NULL yyjson_api_inline yyjson_mut_val *yyjson_mut_arr_with_bool( yyjson_mut_doc *doc, const bool *vals, size_t count) { yyjson_mut_arr_with_func({ unsafe_yyjson_set_bool(val, vals[i]); }); } yyjson_api_inline yyjson_mut_val *yyjson_mut_arr_with_sint( yyjson_mut_doc *doc, const int64_t *vals, size_t count) { return yyjson_mut_arr_with_sint64(doc, vals, count); } yyjson_api_inline yyjson_mut_val *yyjson_mut_arr_with_uint( yyjson_mut_doc *doc, const uint64_t *vals, size_t count) { return yyjson_mut_arr_with_uint64(doc, vals, count); } yyjson_api_inline yyjson_mut_val *yyjson_mut_arr_with_real( yyjson_mut_doc *doc, const double *vals, size_t count) { yyjson_mut_arr_with_func({ unsafe_yyjson_set_real(val, vals[i]); }); } yyjson_api_inline yyjson_mut_val *yyjson_mut_arr_with_sint8( yyjson_mut_doc *doc, const int8_t *vals, size_t count) { yyjson_mut_arr_with_func({ unsafe_yyjson_set_sint(val, vals[i]); }); } yyjson_api_inline yyjson_mut_val *yyjson_mut_arr_with_sint16( yyjson_mut_doc *doc, const int16_t *vals, size_t count) { yyjson_mut_arr_with_func({ unsafe_yyjson_set_sint(val, vals[i]); }); } yyjson_api_inline yyjson_mut_val *yyjson_mut_arr_with_sint32( yyjson_mut_doc *doc, const int32_t *vals, size_t count) { yyjson_mut_arr_with_func({ unsafe_yyjson_set_sint(val, vals[i]); }); } yyjson_api_inline yyjson_mut_val *yyjson_mut_arr_with_sint64( yyjson_mut_doc *doc, const int64_t *vals, size_t count) { yyjson_mut_arr_with_func({ unsafe_yyjson_set_sint(val, vals[i]); }); } yyjson_api_inline yyjson_mut_val *yyjson_mut_arr_with_uint8( yyjson_mut_doc *doc, const uint8_t *vals, size_t count) { yyjson_mut_arr_with_func({ unsafe_yyjson_set_uint(val, vals[i]); }); } yyjson_api_inline yyjson_mut_val *yyjson_mut_arr_with_uint16( yyjson_mut_doc *doc, const uint16_t *vals, size_t count) { yyjson_mut_arr_with_func({ unsafe_yyjson_set_uint(val, vals[i]); }); } yyjson_api_inline yyjson_mut_val *yyjson_mut_arr_with_uint32( yyjson_mut_doc *doc, const uint32_t *vals, size_t count) { yyjson_mut_arr_with_func({ unsafe_yyjson_set_uint(val, vals[i]); }); } yyjson_api_inline yyjson_mut_val *yyjson_mut_arr_with_uint64( yyjson_mut_doc *doc, const uint64_t *vals, size_t count) { yyjson_mut_arr_with_func({ unsafe_yyjson_set_uint(val, vals[i]); }); } yyjson_api_inline yyjson_mut_val *yyjson_mut_arr_with_float( yyjson_mut_doc *doc, const float *vals, size_t count) { yyjson_mut_arr_with_func({ unsafe_yyjson_set_float(val, vals[i]); }); } yyjson_api_inline yyjson_mut_val *yyjson_mut_arr_with_double( yyjson_mut_doc *doc, const double *vals, size_t count) { yyjson_mut_arr_with_func({ unsafe_yyjson_set_double(val, vals[i]); }); } yyjson_api_inline yyjson_mut_val *yyjson_mut_arr_with_str( yyjson_mut_doc *doc, const char **vals, size_t count) { yyjson_mut_arr_with_func({ if (yyjson_unlikely(!vals[i])) return NULL; unsafe_yyjson_set_str(val, vals[i]); }); } yyjson_api_inline yyjson_mut_val *yyjson_mut_arr_with_strn( yyjson_mut_doc *doc, const char **vals, const size_t *lens, size_t count) { if (yyjson_unlikely(count > 0 && !lens)) return NULL; yyjson_mut_arr_with_func({ if (yyjson_unlikely(!vals[i])) return NULL; unsafe_yyjson_set_strn(val, vals[i], lens[i]); }); } yyjson_api_inline yyjson_mut_val *yyjson_mut_arr_with_strcpy( yyjson_mut_doc *doc, const char **vals, size_t count) { size_t len; const char *str, *new_str; yyjson_mut_arr_with_func({ str = vals[i]; if (yyjson_unlikely(!str)) return NULL; len = strlen(str); new_str = unsafe_yyjson_mut_strncpy(doc, str, len); if (yyjson_unlikely(!new_str)) return NULL; unsafe_yyjson_set_strn(val, new_str, len); }); } yyjson_api_inline yyjson_mut_val *yyjson_mut_arr_with_strncpy( yyjson_mut_doc *doc, const char **vals, const size_t *lens, size_t count) { size_t len; const char *str, *new_str; if (yyjson_unlikely(count > 0 && !lens)) return NULL; yyjson_mut_arr_with_func({ str = vals[i]; if (yyjson_unlikely(!str)) return NULL; len = lens[i]; new_str = unsafe_yyjson_mut_strncpy(doc, str, len); if (yyjson_unlikely(!new_str)) return NULL; unsafe_yyjson_set_strn(val, new_str, len); }); } #undef yyjson_mut_arr_with_func /*============================================================================== * MARK: - Mutable JSON Array Modification API (Implementation) *============================================================================*/ yyjson_api_inline bool yyjson_mut_arr_insert(yyjson_mut_val *arr, yyjson_mut_val *val, size_t idx) { if (yyjson_likely(yyjson_mut_is_arr(arr) && val)) { size_t len = unsafe_yyjson_get_len(arr); if (yyjson_likely(idx <= len)) { unsafe_yyjson_set_len(arr, len + 1); if (len == 0) { val->next = val; arr->uni.ptr = val; } else { yyjson_mut_val *prev = ((yyjson_mut_val *)arr->uni.ptr); yyjson_mut_val *next = prev->next; if (idx == len) { prev->next = val; val->next = next; arr->uni.ptr = val; } else { while (idx-- > 0) { prev = next; next = next->next; } prev->next = val; val->next = next; } } return true; } } return false; } yyjson_api_inline bool yyjson_mut_arr_append(yyjson_mut_val *arr, yyjson_mut_val *val) { if (yyjson_likely(yyjson_mut_is_arr(arr) && val)) { size_t len = unsafe_yyjson_get_len(arr); unsafe_yyjson_set_len(arr, len + 1); if (len == 0) { val->next = val; } else { yyjson_mut_val *prev = ((yyjson_mut_val *)arr->uni.ptr); yyjson_mut_val *next = prev->next; prev->next = val; val->next = next; } arr->uni.ptr = val; return true; } return false; } yyjson_api_inline bool yyjson_mut_arr_prepend(yyjson_mut_val *arr, yyjson_mut_val *val) { if (yyjson_likely(yyjson_mut_is_arr(arr) && val)) { size_t len = unsafe_yyjson_get_len(arr); unsafe_yyjson_set_len(arr, len + 1); if (len == 0) { val->next = val; arr->uni.ptr = val; } else { yyjson_mut_val *prev = ((yyjson_mut_val *)arr->uni.ptr); yyjson_mut_val *next = prev->next; prev->next = val; val->next = next; } return true; } return false; } yyjson_api_inline yyjson_mut_val *yyjson_mut_arr_replace(yyjson_mut_val *arr, size_t idx, yyjson_mut_val *val) { if (yyjson_likely(yyjson_mut_is_arr(arr) && val)) { size_t len = unsafe_yyjson_get_len(arr); if (yyjson_likely(idx < len)) { if (yyjson_likely(len > 1)) { yyjson_mut_val *prev = ((yyjson_mut_val *)arr->uni.ptr); yyjson_mut_val *next = prev->next; while (idx-- > 0) { prev = next; next = next->next; } prev->next = val; val->next = next->next; if ((void *)next == arr->uni.ptr) arr->uni.ptr = val; return next; } else { yyjson_mut_val *prev = ((yyjson_mut_val *)arr->uni.ptr); val->next = val; arr->uni.ptr = val; return prev; } } } return NULL; } yyjson_api_inline yyjson_mut_val *yyjson_mut_arr_remove(yyjson_mut_val *arr, size_t idx) { if (yyjson_likely(yyjson_mut_is_arr(arr))) { size_t len = unsafe_yyjson_get_len(arr); if (yyjson_likely(idx < len)) { unsafe_yyjson_set_len(arr, len - 1); if (yyjson_likely(len > 1)) { yyjson_mut_val *prev = ((yyjson_mut_val *)arr->uni.ptr); yyjson_mut_val *next = prev->next; while (idx-- > 0) { prev = next; next = next->next; } prev->next = next->next; if ((void *)next == arr->uni.ptr) arr->uni.ptr = prev; return next; } else { return ((yyjson_mut_val *)arr->uni.ptr); } } } return NULL; } yyjson_api_inline yyjson_mut_val *yyjson_mut_arr_remove_first( yyjson_mut_val *arr) { if (yyjson_likely(yyjson_mut_is_arr(arr))) { size_t len = unsafe_yyjson_get_len(arr); if (len > 1) { yyjson_mut_val *prev = ((yyjson_mut_val *)arr->uni.ptr); yyjson_mut_val *next = prev->next; prev->next = next->next; unsafe_yyjson_set_len(arr, len - 1); return next; } else if (len == 1) { yyjson_mut_val *prev = ((yyjson_mut_val *)arr->uni.ptr); unsafe_yyjson_set_len(arr, 0); return prev; } } return NULL; } yyjson_api_inline yyjson_mut_val *yyjson_mut_arr_remove_last( yyjson_mut_val *arr) { if (yyjson_likely(yyjson_mut_is_arr(arr))) { size_t len = unsafe_yyjson_get_len(arr); if (yyjson_likely(len > 1)) { yyjson_mut_val *prev = ((yyjson_mut_val *)arr->uni.ptr); yyjson_mut_val *next = prev->next; unsafe_yyjson_set_len(arr, len - 1); while (--len > 0) prev = prev->next; prev->next = next; next = (yyjson_mut_val *)arr->uni.ptr; arr->uni.ptr = prev; return next; } else if (len == 1) { yyjson_mut_val *prev = ((yyjson_mut_val *)arr->uni.ptr); unsafe_yyjson_set_len(arr, 0); return prev; } } return NULL; } yyjson_api_inline bool yyjson_mut_arr_remove_range(yyjson_mut_val *arr, size_t _idx, size_t _len) { if (yyjson_likely(yyjson_mut_is_arr(arr))) { yyjson_mut_val *prev, *next; bool tail_removed; size_t len = unsafe_yyjson_get_len(arr); if (yyjson_unlikely(_idx + _len > len)) return false; if (yyjson_unlikely(_len == 0)) return true; unsafe_yyjson_set_len(arr, len - _len); if (yyjson_unlikely(len == _len)) return true; tail_removed = (_idx + _len == len); prev = ((yyjson_mut_val *)arr->uni.ptr); while (_idx-- > 0) prev = prev->next; next = prev->next; while (_len-- > 0) next = next->next; prev->next = next; if (yyjson_unlikely(tail_removed)) arr->uni.ptr = prev; return true; } return false; } yyjson_api_inline bool yyjson_mut_arr_clear(yyjson_mut_val *arr) { if (yyjson_likely(yyjson_mut_is_arr(arr))) { unsafe_yyjson_set_len(arr, 0); return true; } return false; } yyjson_api_inline bool yyjson_mut_arr_rotate(yyjson_mut_val *arr, size_t idx) { if (yyjson_likely(yyjson_mut_is_arr(arr) && unsafe_yyjson_get_len(arr) > idx)) { yyjson_mut_val *val = (yyjson_mut_val *)arr->uni.ptr; while (idx-- > 0) val = val->next; arr->uni.ptr = (void *)val; return true; } return false; } /*============================================================================== * MARK: - Mutable JSON Array Modification Convenience API (Implementation) *============================================================================*/ yyjson_api_inline bool yyjson_mut_arr_add_val(yyjson_mut_val *arr, yyjson_mut_val *val) { return yyjson_mut_arr_append(arr, val); } yyjson_api_inline bool yyjson_mut_arr_add_null(yyjson_mut_doc *doc, yyjson_mut_val *arr) { if (yyjson_likely(doc && yyjson_mut_is_arr(arr))) { yyjson_mut_val *val = yyjson_mut_null(doc); return yyjson_mut_arr_append(arr, val); } return false; } yyjson_api_inline bool yyjson_mut_arr_add_true(yyjson_mut_doc *doc, yyjson_mut_val *arr) { if (yyjson_likely(doc && yyjson_mut_is_arr(arr))) { yyjson_mut_val *val = yyjson_mut_true(doc); return yyjson_mut_arr_append(arr, val); } return false; } yyjson_api_inline bool yyjson_mut_arr_add_false(yyjson_mut_doc *doc, yyjson_mut_val *arr) { if (yyjson_likely(doc && yyjson_mut_is_arr(arr))) { yyjson_mut_val *val = yyjson_mut_false(doc); return yyjson_mut_arr_append(arr, val); } return false; } yyjson_api_inline bool yyjson_mut_arr_add_bool(yyjson_mut_doc *doc, yyjson_mut_val *arr, bool _val) { if (yyjson_likely(doc && yyjson_mut_is_arr(arr))) { yyjson_mut_val *val = yyjson_mut_bool(doc, _val); return yyjson_mut_arr_append(arr, val); } return false; } yyjson_api_inline bool yyjson_mut_arr_add_uint(yyjson_mut_doc *doc, yyjson_mut_val *arr, uint64_t num) { if (yyjson_likely(doc && yyjson_mut_is_arr(arr))) { yyjson_mut_val *val = yyjson_mut_uint(doc, num); return yyjson_mut_arr_append(arr, val); } return false; } yyjson_api_inline bool yyjson_mut_arr_add_sint(yyjson_mut_doc *doc, yyjson_mut_val *arr, int64_t num) { if (yyjson_likely(doc && yyjson_mut_is_arr(arr))) { yyjson_mut_val *val = yyjson_mut_sint(doc, num); return yyjson_mut_arr_append(arr, val); } return false; } yyjson_api_inline bool yyjson_mut_arr_add_int(yyjson_mut_doc *doc, yyjson_mut_val *arr, int64_t num) { if (yyjson_likely(doc && yyjson_mut_is_arr(arr))) { yyjson_mut_val *val = yyjson_mut_sint(doc, num); return yyjson_mut_arr_append(arr, val); } return false; } yyjson_api_inline bool yyjson_mut_arr_add_float(yyjson_mut_doc *doc, yyjson_mut_val *arr, float num) { if (yyjson_likely(doc && yyjson_mut_is_arr(arr))) { yyjson_mut_val *val = yyjson_mut_float(doc, num); return yyjson_mut_arr_append(arr, val); } return false; } yyjson_api_inline bool yyjson_mut_arr_add_double(yyjson_mut_doc *doc, yyjson_mut_val *arr, double num) { if (yyjson_likely(doc && yyjson_mut_is_arr(arr))) { yyjson_mut_val *val = yyjson_mut_double(doc, num); return yyjson_mut_arr_append(arr, val); } return false; } yyjson_api_inline bool yyjson_mut_arr_add_real(yyjson_mut_doc *doc, yyjson_mut_val *arr, double num) { if (yyjson_likely(doc && yyjson_mut_is_arr(arr))) { yyjson_mut_val *val = yyjson_mut_real(doc, num); return yyjson_mut_arr_append(arr, val); } return false; } yyjson_api_inline bool yyjson_mut_arr_add_str(yyjson_mut_doc *doc, yyjson_mut_val *arr, const char *str) { if (yyjson_likely(doc && yyjson_mut_is_arr(arr))) { yyjson_mut_val *val = yyjson_mut_str(doc, str); return yyjson_mut_arr_append(arr, val); } return false; } yyjson_api_inline bool yyjson_mut_arr_add_strn(yyjson_mut_doc *doc, yyjson_mut_val *arr, const char *str, size_t len) { if (yyjson_likely(doc && yyjson_mut_is_arr(arr))) { yyjson_mut_val *val = yyjson_mut_strn(doc, str, len); return yyjson_mut_arr_append(arr, val); } return false; } yyjson_api_inline bool yyjson_mut_arr_add_strcpy(yyjson_mut_doc *doc, yyjson_mut_val *arr, const char *str) { if (yyjson_likely(doc && yyjson_mut_is_arr(arr))) { yyjson_mut_val *val = yyjson_mut_strcpy(doc, str); return yyjson_mut_arr_append(arr, val); } return false; } yyjson_api_inline bool yyjson_mut_arr_add_strncpy(yyjson_mut_doc *doc, yyjson_mut_val *arr, const char *str, size_t len) { if (yyjson_likely(doc && yyjson_mut_is_arr(arr))) { yyjson_mut_val *val = yyjson_mut_strncpy(doc, str, len); return yyjson_mut_arr_append(arr, val); } return false; } yyjson_api_inline yyjson_mut_val *yyjson_mut_arr_add_arr(yyjson_mut_doc *doc, yyjson_mut_val *arr) { if (yyjson_likely(doc && yyjson_mut_is_arr(arr))) { yyjson_mut_val *val = yyjson_mut_arr(doc); return yyjson_mut_arr_append(arr, val) ? val : NULL; } return NULL; } yyjson_api_inline yyjson_mut_val *yyjson_mut_arr_add_obj(yyjson_mut_doc *doc, yyjson_mut_val *arr) { if (yyjson_likely(doc && yyjson_mut_is_arr(arr))) { yyjson_mut_val *val = yyjson_mut_obj(doc); return yyjson_mut_arr_append(arr, val) ? val : NULL; } return NULL; } /*============================================================================== * MARK: - Mutable JSON Object API (Implementation) *============================================================================*/ yyjson_api_inline size_t yyjson_mut_obj_size(yyjson_mut_val *obj) { return yyjson_mut_is_obj(obj) ? unsafe_yyjson_get_len(obj) : 0; } yyjson_api_inline yyjson_mut_val *yyjson_mut_obj_get(yyjson_mut_val *obj, const char *key) { return yyjson_mut_obj_getn(obj, key, key ? strlen(key) : 0); } yyjson_api_inline yyjson_mut_val *yyjson_mut_obj_getn(yyjson_mut_val *obj, const char *_key, size_t key_len) { size_t len = yyjson_mut_obj_size(obj); if (yyjson_likely(len && _key)) { yyjson_mut_val *key = ((yyjson_mut_val *)obj->uni.ptr)->next->next; while (len-- > 0) { if (unsafe_yyjson_equals_strn(key, _key, key_len)) return key->next; key = key->next->next; } } return NULL; } /*============================================================================== * MARK: - Mutable JSON Object Iterator API (Implementation) *============================================================================*/ yyjson_api_inline bool yyjson_mut_obj_iter_init(yyjson_mut_val *obj, yyjson_mut_obj_iter *iter) { if (yyjson_likely(yyjson_mut_is_obj(obj) && iter)) { iter->idx = 0; iter->max = unsafe_yyjson_get_len(obj); iter->cur = iter->max ? (yyjson_mut_val *)obj->uni.ptr : NULL; iter->pre = NULL; iter->obj = obj; return true; } if (iter) memset(iter, 0, sizeof(yyjson_mut_obj_iter)); return false; } yyjson_api_inline yyjson_mut_obj_iter yyjson_mut_obj_iter_with( yyjson_mut_val *obj) { yyjson_mut_obj_iter iter; yyjson_mut_obj_iter_init(obj, &iter); return iter; } yyjson_api_inline bool yyjson_mut_obj_iter_has_next(yyjson_mut_obj_iter *iter) { return iter ? iter->idx < iter->max : false; } yyjson_api_inline yyjson_mut_val *yyjson_mut_obj_iter_next( yyjson_mut_obj_iter *iter) { if (iter && iter->idx < iter->max) { yyjson_mut_val *key = iter->cur; iter->pre = key; iter->cur = key->next->next; iter->idx++; return iter->cur; } return NULL; } yyjson_api_inline yyjson_mut_val *yyjson_mut_obj_iter_get_val( yyjson_mut_val *key) { return key ? key->next : NULL; } yyjson_api_inline yyjson_mut_val *yyjson_mut_obj_iter_remove( yyjson_mut_obj_iter *iter) { if (yyjson_likely(iter && 0 < iter->idx && iter->idx <= iter->max)) { yyjson_mut_val *prev = iter->pre; yyjson_mut_val *cur = iter->cur; yyjson_mut_val *next = cur->next->next; if (yyjson_unlikely(iter->idx == iter->max)) iter->obj->uni.ptr = prev; iter->idx--; iter->max--; unsafe_yyjson_set_len(iter->obj, iter->max); prev->next->next = next; iter->cur = prev; return cur->next; } return NULL; } yyjson_api_inline yyjson_mut_val *yyjson_mut_obj_iter_get( yyjson_mut_obj_iter *iter, const char *key) { return yyjson_mut_obj_iter_getn(iter, key, key ? strlen(key) : 0); } yyjson_api_inline yyjson_mut_val *yyjson_mut_obj_iter_getn( yyjson_mut_obj_iter *iter, const char *key, size_t key_len) { if (iter && key) { size_t idx = 0; size_t max = iter->max; yyjson_mut_val *pre, *cur = iter->cur; while (idx++ < max) { pre = cur; cur = cur->next->next; if (unsafe_yyjson_equals_strn(cur, key, key_len)) { iter->idx += idx; if (iter->idx > max) iter->idx -= max + 1; iter->pre = pre; iter->cur = cur; return cur->next; } } } return NULL; } /*============================================================================== * MARK: - Mutable JSON Object Creation API (Implementation) *============================================================================*/ yyjson_api_inline yyjson_mut_val *yyjson_mut_obj(yyjson_mut_doc *doc) { if (yyjson_likely(doc)) { yyjson_mut_val *val = unsafe_yyjson_mut_val(doc, 1); if (yyjson_likely(val)) { val->tag = YYJSON_TYPE_OBJ | YYJSON_SUBTYPE_NONE; return val; } } return NULL; } yyjson_api_inline yyjson_mut_val *yyjson_mut_obj_with_str(yyjson_mut_doc *doc, const char **keys, const char **vals, size_t count) { if (yyjson_likely(doc && ((count > 0 && keys && vals) || (count == 0)))) { yyjson_mut_val *obj = unsafe_yyjson_mut_val(doc, 1 + count * 2); if (yyjson_likely(obj)) { obj->tag = ((uint64_t)count << YYJSON_TAG_BIT) | YYJSON_TYPE_OBJ; if (count > 0) { size_t i; for (i = 0; i < count; i++) { yyjson_mut_val *key = obj + (i * 2 + 1); yyjson_mut_val *val = obj + (i * 2 + 2); uint64_t key_len = (uint64_t)strlen(keys[i]); uint64_t val_len = (uint64_t)strlen(vals[i]); key->tag = (key_len << YYJSON_TAG_BIT) | YYJSON_TYPE_STR; val->tag = (val_len << YYJSON_TAG_BIT) | YYJSON_TYPE_STR; key->uni.str = keys[i]; val->uni.str = vals[i]; key->next = val; val->next = val + 1; } obj[count * 2].next = obj + 1; obj->uni.ptr = obj + (count * 2 - 1); } return obj; } } return NULL; } yyjson_api_inline yyjson_mut_val *yyjson_mut_obj_with_kv(yyjson_mut_doc *doc, const char **pairs, size_t count) { if (yyjson_likely(doc && ((count > 0 && pairs) || (count == 0)))) { yyjson_mut_val *obj = unsafe_yyjson_mut_val(doc, 1 + count * 2); if (yyjson_likely(obj)) { obj->tag = ((uint64_t)count << YYJSON_TAG_BIT) | YYJSON_TYPE_OBJ; if (count > 0) { size_t i; for (i = 0; i < count; i++) { yyjson_mut_val *key = obj + (i * 2 + 1); yyjson_mut_val *val = obj + (i * 2 + 2); const char *key_str = pairs[i * 2 + 0]; const char *val_str = pairs[i * 2 + 1]; uint64_t key_len = (uint64_t)strlen(key_str); uint64_t val_len = (uint64_t)strlen(val_str); key->tag = (key_len << YYJSON_TAG_BIT) | YYJSON_TYPE_STR; val->tag = (val_len << YYJSON_TAG_BIT) | YYJSON_TYPE_STR; key->uni.str = key_str; val->uni.str = val_str; key->next = val; val->next = val + 1; } obj[count * 2].next = obj + 1; obj->uni.ptr = obj + (count * 2 - 1); } return obj; } } return NULL; } /*============================================================================== * MARK: - Mutable JSON Object Modification API (Implementation) *============================================================================*/ yyjson_api_inline void unsafe_yyjson_mut_obj_add(yyjson_mut_val *obj, yyjson_mut_val *key, yyjson_mut_val *val, size_t len) { if (yyjson_likely(len)) { yyjson_mut_val *prev_val = ((yyjson_mut_val *)obj->uni.ptr)->next; yyjson_mut_val *next_key = prev_val->next; prev_val->next = key; val->next = next_key; } else { val->next = key; } key->next = val; obj->uni.ptr = (void *)key; unsafe_yyjson_set_len(obj, len + 1); } yyjson_api_inline yyjson_mut_val *unsafe_yyjson_mut_obj_remove( yyjson_mut_val *obj, const char *key, size_t key_len) { size_t obj_len = unsafe_yyjson_get_len(obj); if (obj_len) { yyjson_mut_val *pre_key = (yyjson_mut_val *)obj->uni.ptr; yyjson_mut_val *cur_key = pre_key->next->next; yyjson_mut_val *removed_item = NULL; size_t i; for (i = 0; i < obj_len; i++) { if (unsafe_yyjson_equals_strn(cur_key, key, key_len)) { if (!removed_item) removed_item = cur_key->next; cur_key = cur_key->next->next; pre_key->next->next = cur_key; if (i + 1 == obj_len) obj->uni.ptr = pre_key; i--; obj_len--; } else { pre_key = cur_key; cur_key = cur_key->next->next; } } unsafe_yyjson_set_len(obj, obj_len); return removed_item; } else { return NULL; } } yyjson_api_inline bool unsafe_yyjson_mut_obj_replace(yyjson_mut_val *obj, yyjson_mut_val *key, yyjson_mut_val *val) { size_t key_len = unsafe_yyjson_get_len(key); size_t obj_len = unsafe_yyjson_get_len(obj); if (obj_len) { yyjson_mut_val *pre_key = (yyjson_mut_val *)obj->uni.ptr; yyjson_mut_val *cur_key = pre_key->next->next; size_t i; for (i = 0; i < obj_len; i++) { if (unsafe_yyjson_equals_strn(cur_key, key->uni.str, key_len)) { cur_key->next->tag = val->tag; cur_key->next->uni.u64 = val->uni.u64; return true; } else { cur_key = cur_key->next->next; } } } return false; } yyjson_api_inline void unsafe_yyjson_mut_obj_rotate(yyjson_mut_val *obj, size_t idx) { yyjson_mut_val *key = (yyjson_mut_val *)obj->uni.ptr; while (idx-- > 0) key = key->next->next; obj->uni.ptr = (void *)key; } yyjson_api_inline bool yyjson_mut_obj_add(yyjson_mut_val *obj, yyjson_mut_val *key, yyjson_mut_val *val) { if (yyjson_likely(yyjson_mut_is_obj(obj) && yyjson_mut_is_str(key) && val)) { unsafe_yyjson_mut_obj_add(obj, key, val, unsafe_yyjson_get_len(obj)); return true; } return false; } yyjson_api_inline bool yyjson_mut_obj_put(yyjson_mut_val *obj, yyjson_mut_val *key, yyjson_mut_val *val) { bool replaced = false; size_t key_len; yyjson_mut_obj_iter iter; yyjson_mut_val *cur_key; if (yyjson_unlikely(!yyjson_mut_is_obj(obj) || !yyjson_mut_is_str(key))) return false; key_len = unsafe_yyjson_get_len(key); yyjson_mut_obj_iter_init(obj, &iter); while ((cur_key = yyjson_mut_obj_iter_next(&iter)) != 0) { if (unsafe_yyjson_equals_strn(cur_key, key->uni.str, key_len)) { if (!replaced && val) { replaced = true; val->next = cur_key->next->next; cur_key->next = val; } else { yyjson_mut_obj_iter_remove(&iter); } } } if (!replaced && val) unsafe_yyjson_mut_obj_add(obj, key, val, iter.max); return true; } yyjson_api_inline bool yyjson_mut_obj_insert(yyjson_mut_val *obj, yyjson_mut_val *key, yyjson_mut_val *val, size_t idx) { if (yyjson_likely(yyjson_mut_is_obj(obj) && yyjson_mut_is_str(key) && val)) { size_t len = unsafe_yyjson_get_len(obj); if (yyjson_likely(len >= idx)) { if (len > idx) { void *ptr = obj->uni.ptr; unsafe_yyjson_mut_obj_rotate(obj, idx); unsafe_yyjson_mut_obj_add(obj, key, val, len); obj->uni.ptr = ptr; } else { unsafe_yyjson_mut_obj_add(obj, key, val, len); } return true; } } return false; } yyjson_api_inline yyjson_mut_val *yyjson_mut_obj_remove(yyjson_mut_val *obj, yyjson_mut_val *key) { if (yyjson_likely(yyjson_mut_is_obj(obj) && yyjson_mut_is_str(key))) { return unsafe_yyjson_mut_obj_remove(obj, key->uni.str, unsafe_yyjson_get_len(key)); } return NULL; } yyjson_api_inline yyjson_mut_val *yyjson_mut_obj_remove_key( yyjson_mut_val *obj, const char *key) { if (yyjson_likely(yyjson_mut_is_obj(obj) && key)) { size_t key_len = strlen(key); return unsafe_yyjson_mut_obj_remove(obj, key, key_len); } return NULL; } yyjson_api_inline yyjson_mut_val *yyjson_mut_obj_remove_keyn( yyjson_mut_val *obj, const char *key, size_t key_len) { if (yyjson_likely(yyjson_mut_is_obj(obj) && key)) { return unsafe_yyjson_mut_obj_remove(obj, key, key_len); } return NULL; } yyjson_api_inline bool yyjson_mut_obj_clear(yyjson_mut_val *obj) { if (yyjson_likely(yyjson_mut_is_obj(obj))) { unsafe_yyjson_set_len(obj, 0); return true; } return false; } yyjson_api_inline bool yyjson_mut_obj_replace(yyjson_mut_val *obj, yyjson_mut_val *key, yyjson_mut_val *val) { if (yyjson_likely(yyjson_mut_is_obj(obj) && yyjson_mut_is_str(key) && val)) { return unsafe_yyjson_mut_obj_replace(obj, key, val); } return false; } yyjson_api_inline bool yyjson_mut_obj_rotate(yyjson_mut_val *obj, size_t idx) { if (yyjson_likely(yyjson_mut_is_obj(obj) && unsafe_yyjson_get_len(obj) > idx)) { unsafe_yyjson_mut_obj_rotate(obj, idx); return true; } return false; } /*============================================================================== * MARK: - Mutable JSON Object Modification Convenience API (Implementation) *============================================================================*/ #define yyjson_mut_obj_add_func(func) \ if (yyjson_likely(doc && yyjson_mut_is_obj(obj) && _key)) { \ yyjson_mut_val *key = unsafe_yyjson_mut_val(doc, 2); \ if (yyjson_likely(key)) { \ size_t len = unsafe_yyjson_get_len(obj); \ yyjson_mut_val *val = key + 1; \ size_t key_len = strlen(_key); \ bool noesc = unsafe_yyjson_is_str_noesc(_key, key_len); \ key->tag = YYJSON_TYPE_STR; \ key->tag |= noesc ? YYJSON_SUBTYPE_NOESC : YYJSON_SUBTYPE_NONE; \ key->tag |= (uint64_t)strlen(_key) << YYJSON_TAG_BIT; \ key->uni.str = _key; \ func \ unsafe_yyjson_mut_obj_add(obj, key, val, len); \ return true; \ } \ } \ return false yyjson_api_inline bool yyjson_mut_obj_add_null(yyjson_mut_doc *doc, yyjson_mut_val *obj, const char *_key) { yyjson_mut_obj_add_func({ unsafe_yyjson_set_null(val); }); } yyjson_api_inline bool yyjson_mut_obj_add_true(yyjson_mut_doc *doc, yyjson_mut_val *obj, const char *_key) { yyjson_mut_obj_add_func({ unsafe_yyjson_set_bool(val, true); }); } yyjson_api_inline bool yyjson_mut_obj_add_false(yyjson_mut_doc *doc, yyjson_mut_val *obj, const char *_key) { yyjson_mut_obj_add_func({ unsafe_yyjson_set_bool(val, false); }); } yyjson_api_inline bool yyjson_mut_obj_add_bool(yyjson_mut_doc *doc, yyjson_mut_val *obj, const char *_key, bool _val) { yyjson_mut_obj_add_func({ unsafe_yyjson_set_bool(val, _val); }); } yyjson_api_inline bool yyjson_mut_obj_add_uint(yyjson_mut_doc *doc, yyjson_mut_val *obj, const char *_key, uint64_t _val) { yyjson_mut_obj_add_func({ unsafe_yyjson_set_uint(val, _val); }); } yyjson_api_inline bool yyjson_mut_obj_add_sint(yyjson_mut_doc *doc, yyjson_mut_val *obj, const char *_key, int64_t _val) { yyjson_mut_obj_add_func({ unsafe_yyjson_set_sint(val, _val); }); } yyjson_api_inline bool yyjson_mut_obj_add_int(yyjson_mut_doc *doc, yyjson_mut_val *obj, const char *_key, int64_t _val) { yyjson_mut_obj_add_func({ unsafe_yyjson_set_sint(val, _val); }); } yyjson_api_inline bool yyjson_mut_obj_add_float(yyjson_mut_doc *doc, yyjson_mut_val *obj, const char *_key, float _val) { yyjson_mut_obj_add_func({ unsafe_yyjson_set_float(val, _val); }); } yyjson_api_inline bool yyjson_mut_obj_add_double(yyjson_mut_doc *doc, yyjson_mut_val *obj, const char *_key, double _val) { yyjson_mut_obj_add_func({ unsafe_yyjson_set_double(val, _val); }); } yyjson_api_inline bool yyjson_mut_obj_add_real(yyjson_mut_doc *doc, yyjson_mut_val *obj, const char *_key, double _val) { yyjson_mut_obj_add_func({ unsafe_yyjson_set_real(val, _val); }); } yyjson_api_inline bool yyjson_mut_obj_add_str(yyjson_mut_doc *doc, yyjson_mut_val *obj, const char *_key, const char *_val) { if (yyjson_unlikely(!_val)) return false; yyjson_mut_obj_add_func({ size_t val_len = strlen(_val); bool val_noesc = unsafe_yyjson_is_str_noesc(_val, val_len); val->tag = ((uint64_t)strlen(_val) << YYJSON_TAG_BIT) | YYJSON_TYPE_STR; val->tag |= val_noesc ? YYJSON_SUBTYPE_NOESC : YYJSON_SUBTYPE_NONE; val->uni.str = _val; }); } yyjson_api_inline bool yyjson_mut_obj_add_strn(yyjson_mut_doc *doc, yyjson_mut_val *obj, const char *_key, const char *_val, size_t _len) { if (yyjson_unlikely(!_val)) return false; yyjson_mut_obj_add_func({ val->tag = ((uint64_t)_len << YYJSON_TAG_BIT) | YYJSON_TYPE_STR; val->uni.str = _val; }); } yyjson_api_inline bool yyjson_mut_obj_add_strcpy(yyjson_mut_doc *doc, yyjson_mut_val *obj, const char *_key, const char *_val) { if (yyjson_unlikely(!_val)) return false; yyjson_mut_obj_add_func({ size_t _len = strlen(_val); val->uni.str = unsafe_yyjson_mut_strncpy(doc, _val, _len); if (yyjson_unlikely(!val->uni.str)) return false; val->tag = ((uint64_t)_len << YYJSON_TAG_BIT) | YYJSON_TYPE_STR; }); } yyjson_api_inline bool yyjson_mut_obj_add_strncpy(yyjson_mut_doc *doc, yyjson_mut_val *obj, const char *_key, const char *_val, size_t _len) { if (yyjson_unlikely(!_val)) return false; yyjson_mut_obj_add_func({ val->uni.str = unsafe_yyjson_mut_strncpy(doc, _val, _len); if (yyjson_unlikely(!val->uni.str)) return false; val->tag = ((uint64_t)_len << YYJSON_TAG_BIT) | YYJSON_TYPE_STR; }); } yyjson_api_inline yyjson_mut_val *yyjson_mut_obj_add_arr(yyjson_mut_doc *doc, yyjson_mut_val *obj, const char *_key) { yyjson_mut_val *key = yyjson_mut_str(doc, _key); yyjson_mut_val *val = yyjson_mut_arr(doc); return yyjson_mut_obj_add(obj, key, val) ? val : NULL; } yyjson_api_inline yyjson_mut_val *yyjson_mut_obj_add_obj(yyjson_mut_doc *doc, yyjson_mut_val *obj, const char *_key) { yyjson_mut_val *key = yyjson_mut_str(doc, _key); yyjson_mut_val *val = yyjson_mut_obj(doc); return yyjson_mut_obj_add(obj, key, val) ? val : NULL; } yyjson_api_inline bool yyjson_mut_obj_add_val(yyjson_mut_doc *doc, yyjson_mut_val *obj, const char *_key, yyjson_mut_val *_val) { if (yyjson_unlikely(!_val)) return false; yyjson_mut_obj_add_func({ val = _val; }); } yyjson_api_inline yyjson_mut_val *yyjson_mut_obj_remove_str(yyjson_mut_val *obj, const char *key) { return yyjson_mut_obj_remove_strn(obj, key, key ? strlen(key) : 0); } yyjson_api_inline yyjson_mut_val *yyjson_mut_obj_remove_strn( yyjson_mut_val *obj, const char *_key, size_t _len) { if (yyjson_likely(yyjson_mut_is_obj(obj) && _key)) { yyjson_mut_val *key; yyjson_mut_obj_iter iter; yyjson_mut_val *val_removed = NULL; yyjson_mut_obj_iter_init(obj, &iter); while ((key = yyjson_mut_obj_iter_next(&iter)) != NULL) { if (unsafe_yyjson_equals_strn(key, _key, _len)) { if (!val_removed) val_removed = key->next; yyjson_mut_obj_iter_remove(&iter); } } return val_removed; } return NULL; } yyjson_api_inline bool yyjson_mut_obj_rename_key(yyjson_mut_doc *doc, yyjson_mut_val *obj, const char *key, const char *new_key) { if (!key || !new_key) return false; return yyjson_mut_obj_rename_keyn(doc, obj, key, strlen(key), new_key, strlen(new_key)); } yyjson_api_inline bool yyjson_mut_obj_rename_keyn(yyjson_mut_doc *doc, yyjson_mut_val *obj, const char *key, size_t len, const char *new_key, size_t new_len) { char *cpy_key = NULL; yyjson_mut_val *old_key; yyjson_mut_obj_iter iter; if (!doc || !obj || !key || !new_key) return false; yyjson_mut_obj_iter_init(obj, &iter); while ((old_key = yyjson_mut_obj_iter_next(&iter))) { if (unsafe_yyjson_equals_strn((void *)old_key, key, len)) { if (!cpy_key) { cpy_key = unsafe_yyjson_mut_strncpy(doc, new_key, new_len); if (!cpy_key) return false; } yyjson_mut_set_strn(old_key, cpy_key, new_len); } } return cpy_key != NULL; } #if !defined(YYJSON_DISABLE_UTILS) || !YYJSON_DISABLE_UTILS /*============================================================================== * MARK: - JSON Pointer API (Implementation) *============================================================================*/ #define yyjson_ptr_set_err(_code, _msg) do { \ if (err) { \ err->code = YYJSON_PTR_ERR_##_code; \ err->msg = _msg; \ err->pos = 0; \ } \ } while(false) /* require: val != NULL, *ptr == '/', len > 0 */ yyjson_api yyjson_val *unsafe_yyjson_ptr_getx(yyjson_val *val, const char *ptr, size_t len, yyjson_ptr_err *err); /* require: val != NULL, *ptr == '/', len > 0 */ yyjson_api yyjson_mut_val *unsafe_yyjson_mut_ptr_getx(yyjson_mut_val *val, const char *ptr, size_t len, yyjson_ptr_ctx *ctx, yyjson_ptr_err *err); /* require: val/new_val/doc != NULL, *ptr == '/', len > 0 */ yyjson_api bool unsafe_yyjson_mut_ptr_putx(yyjson_mut_val *val, const char *ptr, size_t len, yyjson_mut_val *new_val, yyjson_mut_doc *doc, bool create_parent, bool insert_new, yyjson_ptr_ctx *ctx, yyjson_ptr_err *err); /* require: val/err != NULL, *ptr == '/', len > 0 */ yyjson_api yyjson_mut_val *unsafe_yyjson_mut_ptr_replacex( yyjson_mut_val *val, const char *ptr, size_t len, yyjson_mut_val *new_val, yyjson_ptr_ctx *ctx, yyjson_ptr_err *err); /* require: val/err != NULL, *ptr == '/', len > 0 */ yyjson_api yyjson_mut_val *unsafe_yyjson_mut_ptr_removex(yyjson_mut_val *val, const char *ptr, size_t len, yyjson_ptr_ctx *ctx, yyjson_ptr_err *err); yyjson_api_inline yyjson_val *yyjson_doc_ptr_get(yyjson_doc *doc, const char *ptr) { if (yyjson_unlikely(!ptr)) return NULL; return yyjson_doc_ptr_getn(doc, ptr, strlen(ptr)); } yyjson_api_inline yyjson_val *yyjson_doc_ptr_getn(yyjson_doc *doc, const char *ptr, size_t len) { return yyjson_doc_ptr_getx(doc, ptr, len, NULL); } yyjson_api_inline yyjson_val *yyjson_doc_ptr_getx(yyjson_doc *doc, const char *ptr, size_t len, yyjson_ptr_err *err) { yyjson_ptr_set_err(NONE, NULL); if (yyjson_unlikely(!doc || !ptr)) { yyjson_ptr_set_err(PARAMETER, "input parameter is NULL"); return NULL; } if (yyjson_unlikely(!doc->root)) { yyjson_ptr_set_err(NULL_ROOT, "document's root is NULL"); return NULL; } if (yyjson_unlikely(len == 0)) { return doc->root; } if (yyjson_unlikely(*ptr != '/')) { yyjson_ptr_set_err(SYNTAX, "no prefix '/'"); return NULL; } return unsafe_yyjson_ptr_getx(doc->root, ptr, len, err); } yyjson_api_inline yyjson_val *yyjson_ptr_get(yyjson_val *val, const char *ptr) { if (yyjson_unlikely(!ptr)) return NULL; return yyjson_ptr_getn(val, ptr, strlen(ptr)); } yyjson_api_inline yyjson_val *yyjson_ptr_getn(yyjson_val *val, const char *ptr, size_t len) { return yyjson_ptr_getx(val, ptr, len, NULL); } yyjson_api_inline yyjson_val *yyjson_ptr_getx(yyjson_val *val, const char *ptr, size_t len, yyjson_ptr_err *err) { yyjson_ptr_set_err(NONE, NULL); if (yyjson_unlikely(!val || !ptr)) { yyjson_ptr_set_err(PARAMETER, "input parameter is NULL"); return NULL; } if (yyjson_unlikely(len == 0)) { return val; } if (yyjson_unlikely(*ptr != '/')) { yyjson_ptr_set_err(SYNTAX, "no prefix '/'"); return NULL; } return unsafe_yyjson_ptr_getx(val, ptr, len, err); } yyjson_api_inline yyjson_mut_val *yyjson_mut_doc_ptr_get(yyjson_mut_doc *doc, const char *ptr) { if (!ptr) return NULL; return yyjson_mut_doc_ptr_getn(doc, ptr, strlen(ptr)); } yyjson_api_inline yyjson_mut_val *yyjson_mut_doc_ptr_getn(yyjson_mut_doc *doc, const char *ptr, size_t len) { return yyjson_mut_doc_ptr_getx(doc, ptr, len, NULL, NULL); } yyjson_api_inline yyjson_mut_val *yyjson_mut_doc_ptr_getx(yyjson_mut_doc *doc, const char *ptr, size_t len, yyjson_ptr_ctx *ctx, yyjson_ptr_err *err) { yyjson_ptr_set_err(NONE, NULL); if (ctx) memset(ctx, 0, sizeof(*ctx)); if (yyjson_unlikely(!doc || !ptr)) { yyjson_ptr_set_err(PARAMETER, "input parameter is NULL"); return NULL; } if (yyjson_unlikely(!doc->root)) { yyjson_ptr_set_err(NULL_ROOT, "document's root is NULL"); return NULL; } if (yyjson_unlikely(len == 0)) { return doc->root; } if (yyjson_unlikely(*ptr != '/')) { yyjson_ptr_set_err(SYNTAX, "no prefix '/'"); return NULL; } return unsafe_yyjson_mut_ptr_getx(doc->root, ptr, len, ctx, err); } yyjson_api_inline yyjson_mut_val *yyjson_mut_ptr_get(yyjson_mut_val *val, const char *ptr) { if (!ptr) return NULL; return yyjson_mut_ptr_getn(val, ptr, strlen(ptr)); } yyjson_api_inline yyjson_mut_val *yyjson_mut_ptr_getn(yyjson_mut_val *val, const char *ptr, size_t len) { return yyjson_mut_ptr_getx(val, ptr, len, NULL, NULL); } yyjson_api_inline yyjson_mut_val *yyjson_mut_ptr_getx(yyjson_mut_val *val, const char *ptr, size_t len, yyjson_ptr_ctx *ctx, yyjson_ptr_err *err) { yyjson_ptr_set_err(NONE, NULL); if (ctx) memset(ctx, 0, sizeof(*ctx)); if (yyjson_unlikely(!val || !ptr)) { yyjson_ptr_set_err(PARAMETER, "input parameter is NULL"); return NULL; } if (yyjson_unlikely(len == 0)) { return val; } if (yyjson_unlikely(*ptr != '/')) { yyjson_ptr_set_err(SYNTAX, "no prefix '/'"); return NULL; } return unsafe_yyjson_mut_ptr_getx(val, ptr, len, ctx, err); } yyjson_api_inline bool yyjson_mut_doc_ptr_add(yyjson_mut_doc *doc, const char *ptr, yyjson_mut_val *new_val) { if (yyjson_unlikely(!ptr)) return false; return yyjson_mut_doc_ptr_addn(doc, ptr, strlen(ptr), new_val); } yyjson_api_inline bool yyjson_mut_doc_ptr_addn(yyjson_mut_doc *doc, const char *ptr, size_t len, yyjson_mut_val *new_val) { return yyjson_mut_doc_ptr_addx(doc, ptr, len, new_val, true, NULL, NULL); } yyjson_api_inline bool yyjson_mut_doc_ptr_addx(yyjson_mut_doc *doc, const char *ptr, size_t len, yyjson_mut_val *new_val, bool create_parent, yyjson_ptr_ctx *ctx, yyjson_ptr_err *err) { yyjson_ptr_set_err(NONE, NULL); if (ctx) memset(ctx, 0, sizeof(*ctx)); if (yyjson_unlikely(!doc || !ptr || !new_val)) { yyjson_ptr_set_err(PARAMETER, "input parameter is NULL"); return false; } if (yyjson_unlikely(len == 0)) { if (doc->root) { yyjson_ptr_set_err(SET_ROOT, "cannot set document's root"); return false; } else { doc->root = new_val; return true; } } if (yyjson_unlikely(*ptr != '/')) { yyjson_ptr_set_err(SYNTAX, "no prefix '/'"); return false; } if (yyjson_unlikely(!doc->root && !create_parent)) { yyjson_ptr_set_err(NULL_ROOT, "document's root is NULL"); return false; } if (yyjson_unlikely(!doc->root)) { yyjson_mut_val *root = yyjson_mut_obj(doc); if (yyjson_unlikely(!root)) { yyjson_ptr_set_err(MEMORY_ALLOCATION, "failed to create value"); return false; } if (unsafe_yyjson_mut_ptr_putx(root, ptr, len, new_val, doc, create_parent, true, ctx, err)) { doc->root = root; return true; } return false; } return unsafe_yyjson_mut_ptr_putx(doc->root, ptr, len, new_val, doc, create_parent, true, ctx, err); } yyjson_api_inline bool yyjson_mut_ptr_add(yyjson_mut_val *val, const char *ptr, yyjson_mut_val *new_val, yyjson_mut_doc *doc) { if (yyjson_unlikely(!ptr)) return false; return yyjson_mut_ptr_addn(val, ptr, strlen(ptr), new_val, doc); } yyjson_api_inline bool yyjson_mut_ptr_addn(yyjson_mut_val *val, const char *ptr, size_t len, yyjson_mut_val *new_val, yyjson_mut_doc *doc) { return yyjson_mut_ptr_addx(val, ptr, len, new_val, doc, true, NULL, NULL); } yyjson_api_inline bool yyjson_mut_ptr_addx(yyjson_mut_val *val, const char *ptr, size_t len, yyjson_mut_val *new_val, yyjson_mut_doc *doc, bool create_parent, yyjson_ptr_ctx *ctx, yyjson_ptr_err *err) { yyjson_ptr_set_err(NONE, NULL); if (ctx) memset(ctx, 0, sizeof(*ctx)); if (yyjson_unlikely(!val || !ptr || !new_val || !doc)) { yyjson_ptr_set_err(PARAMETER, "input parameter is NULL"); return false; } if (yyjson_unlikely(len == 0)) { yyjson_ptr_set_err(SET_ROOT, "cannot set root"); return false; } if (yyjson_unlikely(*ptr != '/')) { yyjson_ptr_set_err(SYNTAX, "no prefix '/'"); return false; } return unsafe_yyjson_mut_ptr_putx(val, ptr, len, new_val, doc, create_parent, true, ctx, err); } yyjson_api_inline bool yyjson_mut_doc_ptr_set(yyjson_mut_doc *doc, const char *ptr, yyjson_mut_val *new_val) { if (yyjson_unlikely(!ptr)) return false; return yyjson_mut_doc_ptr_setn(doc, ptr, strlen(ptr), new_val); } yyjson_api_inline bool yyjson_mut_doc_ptr_setn(yyjson_mut_doc *doc, const char *ptr, size_t len, yyjson_mut_val *new_val) { return yyjson_mut_doc_ptr_setx(doc, ptr, len, new_val, true, NULL, NULL); } yyjson_api_inline bool yyjson_mut_doc_ptr_setx(yyjson_mut_doc *doc, const char *ptr, size_t len, yyjson_mut_val *new_val, bool create_parent, yyjson_ptr_ctx *ctx, yyjson_ptr_err *err) { yyjson_ptr_set_err(NONE, NULL); if (ctx) memset(ctx, 0, sizeof(*ctx)); if (yyjson_unlikely(!doc || !ptr)) { yyjson_ptr_set_err(PARAMETER, "input parameter is NULL"); return false; } if (yyjson_unlikely(len == 0)) { if (ctx) ctx->old = doc->root; doc->root = new_val; return true; } if (yyjson_unlikely(*ptr != '/')) { yyjson_ptr_set_err(SYNTAX, "no prefix '/'"); return false; } if (!new_val) { if (!doc->root) { yyjson_ptr_set_err(RESOLVE, "JSON pointer cannot be resolved"); return false; } return !!unsafe_yyjson_mut_ptr_removex(doc->root, ptr, len, ctx, err); } if (yyjson_unlikely(!doc->root && !create_parent)) { yyjson_ptr_set_err(NULL_ROOT, "document's root is NULL"); return false; } if (yyjson_unlikely(!doc->root)) { yyjson_mut_val *root = yyjson_mut_obj(doc); if (yyjson_unlikely(!root)) { yyjson_ptr_set_err(MEMORY_ALLOCATION, "failed to create value"); return false; } if (unsafe_yyjson_mut_ptr_putx(root, ptr, len, new_val, doc, create_parent, false, ctx, err)) { doc->root = root; return true; } return false; } return unsafe_yyjson_mut_ptr_putx(doc->root, ptr, len, new_val, doc, create_parent, false, ctx, err); } yyjson_api_inline bool yyjson_mut_ptr_set(yyjson_mut_val *val, const char *ptr, yyjson_mut_val *new_val, yyjson_mut_doc *doc) { if (yyjson_unlikely(!ptr)) return false; return yyjson_mut_ptr_setn(val, ptr, strlen(ptr), new_val, doc); } yyjson_api_inline bool yyjson_mut_ptr_setn(yyjson_mut_val *val, const char *ptr, size_t len, yyjson_mut_val *new_val, yyjson_mut_doc *doc) { return yyjson_mut_ptr_setx(val, ptr, len, new_val, doc, true, NULL, NULL); } yyjson_api_inline bool yyjson_mut_ptr_setx(yyjson_mut_val *val, const char *ptr, size_t len, yyjson_mut_val *new_val, yyjson_mut_doc *doc, bool create_parent, yyjson_ptr_ctx *ctx, yyjson_ptr_err *err) { yyjson_ptr_set_err(NONE, NULL); if (ctx) memset(ctx, 0, sizeof(*ctx)); if (yyjson_unlikely(!val || !ptr || !doc)) { yyjson_ptr_set_err(PARAMETER, "input parameter is NULL"); return false; } if (yyjson_unlikely(len == 0)) { yyjson_ptr_set_err(SET_ROOT, "cannot set root"); return false; } if (yyjson_unlikely(*ptr != '/')) { yyjson_ptr_set_err(SYNTAX, "no prefix '/'"); return false; } if (!new_val) { return !!unsafe_yyjson_mut_ptr_removex(val, ptr, len, ctx, err); } return unsafe_yyjson_mut_ptr_putx(val, ptr, len, new_val, doc, create_parent, false, ctx, err); } yyjson_api_inline yyjson_mut_val *yyjson_mut_doc_ptr_replace( yyjson_mut_doc *doc, const char *ptr, yyjson_mut_val *new_val) { if (!ptr) return NULL; return yyjson_mut_doc_ptr_replacen(doc, ptr, strlen(ptr), new_val); } yyjson_api_inline yyjson_mut_val *yyjson_mut_doc_ptr_replacen( yyjson_mut_doc *doc, const char *ptr, size_t len, yyjson_mut_val *new_val) { return yyjson_mut_doc_ptr_replacex(doc, ptr, len, new_val, NULL, NULL); } yyjson_api_inline yyjson_mut_val *yyjson_mut_doc_ptr_replacex( yyjson_mut_doc *doc, const char *ptr, size_t len, yyjson_mut_val *new_val, yyjson_ptr_ctx *ctx, yyjson_ptr_err *err) { yyjson_ptr_set_err(NONE, NULL); if (ctx) memset(ctx, 0, sizeof(*ctx)); if (yyjson_unlikely(!doc || !ptr || !new_val)) { yyjson_ptr_set_err(PARAMETER, "input parameter is NULL"); return NULL; } if (yyjson_unlikely(len == 0)) { yyjson_mut_val *root = doc->root; if (yyjson_unlikely(!root)) { yyjson_ptr_set_err(RESOLVE, "JSON pointer cannot be resolved"); return NULL; } if (ctx) ctx->old = root; doc->root = new_val; return root; } if (yyjson_unlikely(!doc->root)) { yyjson_ptr_set_err(NULL_ROOT, "document's root is NULL"); return NULL; } if (yyjson_unlikely(*ptr != '/')) { yyjson_ptr_set_err(SYNTAX, "no prefix '/'"); return NULL; } return unsafe_yyjson_mut_ptr_replacex(doc->root, ptr, len, new_val, ctx, err); } yyjson_api_inline yyjson_mut_val *yyjson_mut_ptr_replace( yyjson_mut_val *val, const char *ptr, yyjson_mut_val *new_val) { if (!ptr) return NULL; return yyjson_mut_ptr_replacen(val, ptr, strlen(ptr), new_val); } yyjson_api_inline yyjson_mut_val *yyjson_mut_ptr_replacen( yyjson_mut_val *val, const char *ptr, size_t len, yyjson_mut_val *new_val) { return yyjson_mut_ptr_replacex(val, ptr, len, new_val, NULL, NULL); } yyjson_api_inline yyjson_mut_val *yyjson_mut_ptr_replacex( yyjson_mut_val *val, const char *ptr, size_t len, yyjson_mut_val *new_val, yyjson_ptr_ctx *ctx, yyjson_ptr_err *err) { yyjson_ptr_set_err(NONE, NULL); if (ctx) memset(ctx, 0, sizeof(*ctx)); if (yyjson_unlikely(!val || !ptr || !new_val)) { yyjson_ptr_set_err(PARAMETER, "input parameter is NULL"); return NULL; } if (yyjson_unlikely(len == 0)) { yyjson_ptr_set_err(SET_ROOT, "cannot set root"); return NULL; } if (yyjson_unlikely(*ptr != '/')) { yyjson_ptr_set_err(SYNTAX, "no prefix '/'"); return NULL; } return unsafe_yyjson_mut_ptr_replacex(val, ptr, len, new_val, ctx, err); } yyjson_api_inline yyjson_mut_val *yyjson_mut_doc_ptr_remove( yyjson_mut_doc *doc, const char *ptr) { if (!ptr) return NULL; return yyjson_mut_doc_ptr_removen(doc, ptr, strlen(ptr)); } yyjson_api_inline yyjson_mut_val *yyjson_mut_doc_ptr_removen( yyjson_mut_doc *doc, const char *ptr, size_t len) { return yyjson_mut_doc_ptr_removex(doc, ptr, len, NULL, NULL); } yyjson_api_inline yyjson_mut_val *yyjson_mut_doc_ptr_removex( yyjson_mut_doc *doc, const char *ptr, size_t len, yyjson_ptr_ctx *ctx, yyjson_ptr_err *err) { yyjson_ptr_set_err(NONE, NULL); if (ctx) memset(ctx, 0, sizeof(*ctx)); if (yyjson_unlikely(!doc || !ptr)) { yyjson_ptr_set_err(PARAMETER, "input parameter is NULL"); return NULL; } if (yyjson_unlikely(!doc->root)) { yyjson_ptr_set_err(NULL_ROOT, "document's root is NULL"); return NULL; } if (yyjson_unlikely(len == 0)) { yyjson_mut_val *root = doc->root; if (ctx) ctx->old = root; doc->root = NULL; return root; } if (yyjson_unlikely(*ptr != '/')) { yyjson_ptr_set_err(SYNTAX, "no prefix '/'"); return NULL; } return unsafe_yyjson_mut_ptr_removex(doc->root, ptr, len, ctx, err); } yyjson_api_inline yyjson_mut_val *yyjson_mut_ptr_remove(yyjson_mut_val *val, const char *ptr) { if (!ptr) return NULL; return yyjson_mut_ptr_removen(val, ptr, strlen(ptr)); } yyjson_api_inline yyjson_mut_val *yyjson_mut_ptr_removen(yyjson_mut_val *val, const char *ptr, size_t len) { return yyjson_mut_ptr_removex(val, ptr, len, NULL, NULL); } yyjson_api_inline yyjson_mut_val *yyjson_mut_ptr_removex(yyjson_mut_val *val, const char *ptr, size_t len, yyjson_ptr_ctx *ctx, yyjson_ptr_err *err) { yyjson_ptr_set_err(NONE, NULL); if (ctx) memset(ctx, 0, sizeof(*ctx)); if (yyjson_unlikely(!val || !ptr)) { yyjson_ptr_set_err(PARAMETER, "input parameter is NULL"); return NULL; } if (yyjson_unlikely(len == 0)) { yyjson_ptr_set_err(SET_ROOT, "cannot set root"); return NULL; } if (yyjson_unlikely(*ptr != '/')) { yyjson_ptr_set_err(SYNTAX, "no prefix '/'"); return NULL; } return unsafe_yyjson_mut_ptr_removex(val, ptr, len, ctx, err); } yyjson_api_inline bool yyjson_ptr_ctx_append(yyjson_ptr_ctx *ctx, yyjson_mut_val *key, yyjson_mut_val *val) { yyjson_mut_val *ctn, *pre_key, *pre_val, *cur_key, *cur_val; if (!ctx || !ctx->ctn || !val) return false; ctn = ctx->ctn; if (yyjson_mut_is_obj(ctn)) { if (!key) return false; key->next = val; pre_key = ctx->pre; if (unsafe_yyjson_get_len(ctn) == 0) { val->next = key; ctn->uni.ptr = key; ctx->pre = key; } else if (!pre_key) { pre_key = (yyjson_mut_val *)ctn->uni.ptr; pre_val = pre_key->next; val->next = pre_val->next; pre_val->next = key; ctn->uni.ptr = key; ctx->pre = pre_key; } else { cur_key = pre_key->next->next; cur_val = cur_key->next; val->next = cur_val->next; cur_val->next = key; if (ctn->uni.ptr == cur_key) ctn->uni.ptr = key; ctx->pre = cur_key; } } else { pre_val = ctx->pre; if (unsafe_yyjson_get_len(ctn) == 0) { val->next = val; ctn->uni.ptr = val; ctx->pre = val; } else if (!pre_val) { pre_val = (yyjson_mut_val *)ctn->uni.ptr; val->next = pre_val->next; pre_val->next = val; ctn->uni.ptr = val; ctx->pre = pre_val; } else { cur_val = pre_val->next; val->next = cur_val->next; cur_val->next = val; if (ctn->uni.ptr == cur_val) ctn->uni.ptr = val; ctx->pre = cur_val; } } unsafe_yyjson_inc_len(ctn); return true; } yyjson_api_inline bool yyjson_ptr_ctx_replace(yyjson_ptr_ctx *ctx, yyjson_mut_val *val) { yyjson_mut_val *ctn, *pre_key, *cur_key, *pre_val, *cur_val; if (!ctx || !ctx->ctn || !ctx->pre || !val) return false; ctn = ctx->ctn; if (yyjson_mut_is_obj(ctn)) { pre_key = ctx->pre; pre_val = pre_key->next; cur_key = pre_val->next; cur_val = cur_key->next; /* replace current value */ cur_key->next = val; val->next = cur_val->next; ctx->old = cur_val; } else { pre_val = ctx->pre; cur_val = pre_val->next; /* replace current value */ if (pre_val != cur_val) { val->next = cur_val->next; pre_val->next = val; if (ctn->uni.ptr == cur_val) ctn->uni.ptr = val; } else { val->next = val; ctn->uni.ptr = val; ctx->pre = val; } ctx->old = cur_val; } return true; } yyjson_api_inline bool yyjson_ptr_ctx_remove(yyjson_ptr_ctx *ctx) { yyjson_mut_val *ctn, *pre_key, *pre_val, *cur_key, *cur_val; size_t len; if (!ctx || !ctx->ctn || !ctx->pre) return false; ctn = ctx->ctn; if (yyjson_mut_is_obj(ctn)) { pre_key = ctx->pre; pre_val = pre_key->next; cur_key = pre_val->next; cur_val = cur_key->next; /* remove current key-value */ pre_val->next = cur_val->next; if (ctn->uni.ptr == cur_key) ctn->uni.ptr = pre_key; ctx->pre = NULL; ctx->old = cur_val; } else { pre_val = ctx->pre; cur_val = pre_val->next; /* remove current key-value */ pre_val->next = cur_val->next; if (ctn->uni.ptr == cur_val) ctn->uni.ptr = pre_val; ctx->pre = NULL; ctx->old = cur_val; } len = unsafe_yyjson_get_len(ctn) - 1; if (len == 0) ctn->uni.ptr = NULL; unsafe_yyjson_set_len(ctn, len); return true; } #undef yyjson_ptr_set_err /*============================================================================== * MARK: - JSON Value at Pointer API (Implementation) *============================================================================*/ /** Set provided `value` if the JSON Pointer (RFC 6901) exists and is type bool. Returns true if value at `ptr` exists and is the correct type, otherwise false. */ yyjson_api_inline bool yyjson_ptr_get_bool( yyjson_val *root, const char *ptr, bool *value) { yyjson_val *val = yyjson_ptr_get(root, ptr); if (value && yyjson_is_bool(val)) { *value = unsafe_yyjson_get_bool(val); return true; } else { return false; } } /** Set provided `value` if the JSON Pointer (RFC 6901) exists and is an integer that fits in `uint64_t`. Returns true if successful, otherwise false. */ yyjson_api_inline bool yyjson_ptr_get_uint( yyjson_val *root, const char *ptr, uint64_t *value) { yyjson_val *val = yyjson_ptr_get(root, ptr); if (value && val) { uint64_t ret = val->uni.u64; if (unsafe_yyjson_is_uint(val) || (unsafe_yyjson_is_sint(val) && !(ret >> 63))) { *value = ret; return true; } } return false; } /** Set provided `value` if the JSON Pointer (RFC 6901) exists and is an integer that fits in `int64_t`. Returns true if successful, otherwise false. */ yyjson_api_inline bool yyjson_ptr_get_sint( yyjson_val *root, const char *ptr, int64_t *value) { yyjson_val *val = yyjson_ptr_get(root, ptr); if (value && val) { int64_t ret = val->uni.i64; if (unsafe_yyjson_is_sint(val) || (unsafe_yyjson_is_uint(val) && ret >= 0)) { *value = ret; return true; } } return false; } /** Set provided `value` if the JSON Pointer (RFC 6901) exists and is type real. Returns true if value at `ptr` exists and is the correct type, otherwise false. */ yyjson_api_inline bool yyjson_ptr_get_real( yyjson_val *root, const char *ptr, double *value) { yyjson_val *val = yyjson_ptr_get(root, ptr); if (value && yyjson_is_real(val)) { *value = unsafe_yyjson_get_real(val); return true; } else { return false; } } /** Set provided `value` if the JSON Pointer (RFC 6901) exists and is type sint, uint or real. Returns true if value at `ptr` exists and is the correct type, otherwise false. */ yyjson_api_inline bool yyjson_ptr_get_num( yyjson_val *root, const char *ptr, double *value) { yyjson_val *val = yyjson_ptr_get(root, ptr); if (value && yyjson_is_num(val)) { *value = unsafe_yyjson_get_num(val); return true; } else { return false; } } /** Set provided `value` if the JSON Pointer (RFC 6901) exists and is type string. Returns true if value at `ptr` exists and is the correct type, otherwise false. */ yyjson_api_inline bool yyjson_ptr_get_str( yyjson_val *root, const char *ptr, const char **value) { yyjson_val *val = yyjson_ptr_get(root, ptr); if (value && yyjson_is_str(val)) { *value = unsafe_yyjson_get_str(val); return true; } else { return false; } } /*============================================================================== * MARK: - Deprecated *============================================================================*/ /** @deprecated renamed to `yyjson_doc_ptr_get` */ yyjson_deprecated("renamed to yyjson_doc_ptr_get") yyjson_api_inline yyjson_val *yyjson_doc_get_pointer(yyjson_doc *doc, const char *ptr) { return yyjson_doc_ptr_get(doc, ptr); } /** @deprecated renamed to `yyjson_doc_ptr_getn` */ yyjson_deprecated("renamed to yyjson_doc_ptr_getn") yyjson_api_inline yyjson_val *yyjson_doc_get_pointern(yyjson_doc *doc, const char *ptr, size_t len) { return yyjson_doc_ptr_getn(doc, ptr, len); } /** @deprecated renamed to `yyjson_mut_doc_ptr_get` */ yyjson_deprecated("renamed to yyjson_mut_doc_ptr_get") yyjson_api_inline yyjson_mut_val *yyjson_mut_doc_get_pointer( yyjson_mut_doc *doc, const char *ptr) { return yyjson_mut_doc_ptr_get(doc, ptr); } /** @deprecated renamed to `yyjson_mut_doc_ptr_getn` */ yyjson_deprecated("renamed to yyjson_mut_doc_ptr_getn") yyjson_api_inline yyjson_mut_val *yyjson_mut_doc_get_pointern( yyjson_mut_doc *doc, const char *ptr, size_t len) { return yyjson_mut_doc_ptr_getn(doc, ptr, len); } /** @deprecated renamed to `yyjson_ptr_get` */ yyjson_deprecated("renamed to yyjson_ptr_get") yyjson_api_inline yyjson_val *yyjson_get_pointer(yyjson_val *val, const char *ptr) { return yyjson_ptr_get(val, ptr); } /** @deprecated renamed to `yyjson_ptr_getn` */ yyjson_deprecated("renamed to yyjson_ptr_getn") yyjson_api_inline yyjson_val *yyjson_get_pointern(yyjson_val *val, const char *ptr, size_t len) { return yyjson_ptr_getn(val, ptr, len); } /** @deprecated renamed to `yyjson_mut_ptr_get` */ yyjson_deprecated("renamed to yyjson_mut_ptr_get") yyjson_api_inline yyjson_mut_val *yyjson_mut_get_pointer(yyjson_mut_val *val, const char *ptr) { return yyjson_mut_ptr_get(val, ptr); } /** @deprecated renamed to `yyjson_mut_ptr_getn` */ yyjson_deprecated("renamed to yyjson_mut_ptr_getn") yyjson_api_inline yyjson_mut_val *yyjson_mut_get_pointern(yyjson_mut_val *val, const char *ptr, size_t len) { return yyjson_mut_ptr_getn(val, ptr, len); } /** @deprecated renamed to `yyjson_mut_ptr_getn` */ yyjson_deprecated("renamed to unsafe_yyjson_ptr_getn") yyjson_api_inline yyjson_val *unsafe_yyjson_get_pointer(yyjson_val *val, const char *ptr, size_t len) { yyjson_ptr_err err; return unsafe_yyjson_ptr_getx(val, ptr, len, &err); } /** @deprecated renamed to `unsafe_yyjson_mut_ptr_getx` */ yyjson_deprecated("renamed to unsafe_yyjson_mut_ptr_getx") yyjson_api_inline yyjson_mut_val *unsafe_yyjson_mut_get_pointer( yyjson_mut_val *val, const char *ptr, size_t len) { yyjson_ptr_err err; return unsafe_yyjson_mut_ptr_getx(val, ptr, len, NULL, &err); } #endif /* YYJSON_DISABLE_UTILS */ /*============================================================================== * MARK: - Compiler Hint End *============================================================================*/ #if defined(__clang__) # pragma clang diagnostic pop #elif defined(__GNUC__) # if (__GNUC__ > 4) || (__GNUC__ == 4 && __GNUC_MINOR__ >= 6) # pragma GCC diagnostic pop # endif #elif defined(_MSC_VER) # pragma warning(pop) #endif /* warning suppress end */ #ifdef __cplusplus } #endif /* extern "C" end */ #endif /* YYJSON_H */ ================================================ FILE: src/common/FFPlatform.h ================================================ #pragma once #include "common/FFstrbuf.h" #include "common/FFlist.h" typedef struct FFPlatformSysinfo { FFstrbuf name; FFstrbuf release; FFstrbuf version; FFstrbuf architecture; uint32_t pageSize; } FFPlatformSysinfo; typedef struct FFPlatform { FFstrbuf homeDir; // Trailing slash included FFstrbuf cacheDir; // Trailing slash included FFlist configDirs; // List of FFstrbuf, trailing slash included FFlist dataDirs; // List of FFstrbuf, trailing slash included FFstrbuf exePath; // The real path of current exe (empty if unavailable) FFstrbuf cwd; // Trailing slash included uint32_t pid; #ifndef _WIN32 uint32_t uid; #else FFstrbuf sid; #endif FFstrbuf userName; FFstrbuf fullUserName; FFstrbuf hostName; FFstrbuf userShell; FFPlatformSysinfo sysinfo; } FFPlatform; void ffPlatformInit(FFPlatform* platform); void ffPlatformDestroy(FFPlatform* platform); ================================================ FILE: src/common/FFcheckmacros.h ================================================ #pragma once #ifdef _MSC_VER #include #endif #if defined(__has_attribute) && __has_attribute(__warn_unused_result__) #define FF_C_NODISCARD __attribute__((__warn_unused_result__)) #elif defined(_MSC_VER) #define FF_C_NODISCARD _Check_return_ #else #define FF_C_NODISCARD #endif #if defined(__has_attribute) && __has_attribute(__format__) #define FF_C_PRINTF(formatStrIndex, argsStartIndex) __attribute__((__format__ (printf, formatStrIndex, argsStartIndex))) #else #define FF_C_PRINTF(formatStrIndex, argsStartIndex) #endif #if defined(__has_attribute) && __has_attribute(__format__) #define FF_C_SCANF(formatStrIndex, argsStartIndex) __attribute__((__format__ (scanf, formatStrIndex, argsStartIndex))) #else #define FF_C_SCANF(formatStrIndex, argsStartIndex) #endif #if defined(__has_attribute) && __has_attribute(__nonnull__) #define FF_C_NONNULL(argIndex, ...) __attribute__((__nonnull__(argIndex, ##__VA_ARGS__))) #else #define FF_C_NONNULL(argIndex, ...) #endif #if defined(__has_attribute) && __has_attribute(__returns_nonnull__) #define FF_C_RETURNS_NONNULL __attribute__((__returns_nonnull__)) #else #define FF_C_RETURNS_NONNULL #endif ================================================ FILE: src/common/FFlist.h ================================================ #pragma once #include "FFcheckmacros.h" #include #include #include #include #define FF_LIST_DEFAULT_ALLOC 16 typedef struct FFlist { uint8_t* data; uint32_t elementSize; uint32_t length; uint32_t capacity; } FFlist; void* ffListAdd(FFlist* list); // Removes the first element, and copy its value to `*result` bool ffListShift(FFlist* list, void* result); // Removes the last element, and copy its value to `*result` bool ffListPop(FFlist* list, void* result); static inline void ffListInit(FFlist* list, uint32_t elementSize) { assert(elementSize > 0); list->elementSize = elementSize; list->capacity = 0; list->length = 0; list->data = NULL; } static inline void ffListInitA(FFlist* list, uint32_t elementSize, uint32_t capacity) { ffListInit(list, elementSize); list->capacity = capacity; list->data = __builtin_expect(capacity == 0, 0) ? NULL : (uint8_t*) malloc((size_t)list->capacity * list->elementSize); } static inline FFlist ffListCreate(uint32_t elementSize) { FFlist result; ffListInit(&result, elementSize); return result; } static inline void* ffListGet(const FFlist* list, uint32_t index) { assert(list->capacity > index); return list->data + (index * list->elementSize); } FF_C_NODISCARD static inline uint32_t ffListFirstIndexComp(const FFlist* list, void* compElement, bool(*compFunc)(const void*, const void*)) { for(uint32_t i = 0; i < list->length; i++) { if(compFunc(ffListGet(list, i), compElement)) return i; } return list->length; } static inline bool ffListContains(const FFlist* list, void* compElement, bool(*compFunc)(const void*, const void*)) { return ffListFirstIndexComp(list, compElement, compFunc) != list->length; } static inline void ffListSort(FFlist* list, int(*compar)(const void*, const void*)) { qsort(list->data, list->length, list->elementSize, compar); } // Move the contents of `src` into `list`, and left `src` empty static inline void ffListInitMove(FFlist* list, FFlist* src) { if (src) { list->elementSize = src->elementSize; list->capacity = src->capacity; list->length = src->length; list->data = src->data; ffListInit(src, list->elementSize); } else { ffListInit(list, 0); } } static inline void ffListDestroy(FFlist* list) { if (!list->data) return; //Avoid free-after-use. These 3 assignments are cheap so don't remove them list->capacity = list->length = 0; free(list->data); list->data = NULL; } static inline void ffListClear(FFlist* list) { list->length = 0; } static inline void ffListReserve(FFlist* list, uint32_t newCapacity) { if (__builtin_expect(newCapacity <= list->capacity, false)) return; list->data = (uint8_t*) realloc(list->data, (size_t) newCapacity * list->elementSize); list->capacity = newCapacity; } #define FF_LIST_FOR_EACH(itemType, itemVarName, listVar) \ assert(sizeof(itemType) == (listVar).elementSize); \ for(itemType* itemVarName = (itemType*)(listVar).data; \ itemVarName - (itemType*)(listVar).data < (intptr_t)(listVar).length; \ ++itemVarName) #define FF_LIST_AUTO_DESTROY FFlist __attribute__((__cleanup__(ffListDestroy))) #define FF_LIST_GET(itemType, listVar, index) \ ({ \ assert(sizeof(itemType) == (listVar).elementSize); \ assert((listVar).capacity > (index)); \ (itemType*)(listVar).data + (index); \ }) #define FF_LIST_ADD(itemType, listVar) \ ({ \ assert(sizeof(itemType) == (listVar).elementSize); \ (itemType*) ffListAdd(&(listVar)); \ }) #define FF_LIST_FIRST(itemType, listVar) FF_LIST_GET(itemType, listVar, 0) #define FF_LIST_LAST(itemType, listVar) \ ({ \ assert((listVar).length > 0); \ FF_LIST_GET(itemType, listVar, ((listVar).length - 1)); \ }) ================================================ FILE: src/common/FFstrbuf.h ================================================ #pragma once #include "FFcheckmacros.h" #include #include #include #include #include #include #include #ifdef FF_USE_SYSTEM_YYJSON #include #else #include "3rdparty/yyjson/yyjson.h" #endif #ifdef _WIN32 // #include __stdcall char* StrStrIA(const char* lpFirst, const char* lpSrch); #define strcasestr StrStrIA #endif #define FASTFETCH_STRBUF_DEFAULT_ALLOC 32 // static string (allocated == 0), chars points to a string literal // dynamic string (allocated > 0), chars points to a heap allocated buffer typedef struct FFstrbuf { uint32_t allocated; uint32_t length; char* chars; } FFstrbuf; static inline void ffStrbufInit(FFstrbuf* strbuf); void ffStrbufInitA(FFstrbuf* strbuf, uint32_t allocate); void ffStrbufInitVF(FFstrbuf* strbuf, const char* format, va_list arguments); void ffStrbufInitMoveNS(FFstrbuf* strbuf, uint32_t length, char* heapStr); void ffStrbufEnsureFree(FFstrbuf* strbuf, uint32_t free); void ffStrbufEnsureFixedLengthFree(FFstrbuf* strbuf, uint32_t free); void ffStrbufClear(FFstrbuf* strbuf); static inline void ffStrbufAppend(FFstrbuf* __restrict strbuf, const FFstrbuf* __restrict value); void ffStrbufAppendC(FFstrbuf* strbuf, char c); void ffStrbufAppendNC(FFstrbuf* strbuf, uint32_t num, char c); void ffStrbufAppendNS(FFstrbuf* strbuf, uint32_t length, const char* value); void ffStrbufAppendTransformS(FFstrbuf* strbuf, const char* value, int(*transformFunc)(int)); FF_C_PRINTF(2, 3) void ffStrbufAppendF(FFstrbuf* strbuf, const char* format, ...); void ffStrbufAppendVF(FFstrbuf* strbuf, const char* format, va_list arguments); const char* ffStrbufAppendSUntilC(FFstrbuf* strbuf, const char* value, char until); void ffStrbufPrependNS(FFstrbuf* strbuf, uint32_t length, const char* value); void ffStrbufPrependC(FFstrbuf* strbuf, char c); void ffStrbufInsertNC(FFstrbuf* strbuf, uint32_t index, uint32_t num, char c); // Clear the content of strbuf and set new value // NOTE: Unlike ffStrbufAppend*, ffStrbufSet* functions may NOT reserve extra space void ffStrbufSet(FFstrbuf* strbuf, const FFstrbuf* value); void ffStrbufSetNS(FFstrbuf* strbuf, uint32_t length, const char* value); FF_C_PRINTF(2, 3) void ffStrbufSetF(FFstrbuf* strbuf, const char* format, ...); void ffStrbufTrimLeft(FFstrbuf* strbuf, char c); void ffStrbufTrimRight(FFstrbuf* strbuf, char c); void ffStrbufTrimLeftSpace(FFstrbuf* strbuf); void ffStrbufTrimRightSpace(FFstrbuf* strbuf); bool ffStrbufRemoveSubstr(FFstrbuf* strbuf, uint32_t startIndex, uint32_t endIndex); void ffStrbufRemoveS(FFstrbuf* strbuf, const char* str); void ffStrbufRemoveStrings(FFstrbuf* strbuf, uint32_t numStrings, const char* strings[]); FF_C_NODISCARD uint32_t ffStrbufNextIndexC(const FFstrbuf* strbuf, uint32_t start, char c); FF_C_NODISCARD uint32_t ffStrbufNextIndexS(const FFstrbuf* strbuf, uint32_t start, const char* str); FF_C_NODISCARD uint32_t ffStrbufPreviousIndexC(const FFstrbuf* strbuf, uint32_t start, char c); void ffStrbufReplaceAllC(FFstrbuf* strbuf, char find, char replace); // Returns true if the strbuf is modified bool ffStrbufSubstrBefore(FFstrbuf* strbuf, uint32_t index); bool ffStrbufSubstrAfter(FFstrbuf* strbuf, uint32_t index); // Not including the index bool ffStrbufSubstrAfterFirstC(FFstrbuf* strbuf, char c); bool ffStrbufSubstrAfterFirstS(FFstrbuf* strbuf, const char* str); bool ffStrbufSubstrAfterLastC(FFstrbuf* strbuf, char c); bool ffStrbufSubstr(FFstrbuf* strbuf, uint32_t start, uint32_t end); FF_C_NODISCARD uint32_t ffStrbufCountC(const FFstrbuf* strbuf, char c); bool ffStrbufRemoveIgnCaseEndS(FFstrbuf* strbuf, const char* end); bool ffStrbufEnsureEndsWithC(FFstrbuf* strbuf, char c); void ffStrbufWriteTo(const FFstrbuf* strbuf, FILE* file); void ffStrbufPutTo(const FFstrbuf* strbuf, FILE* file); FF_C_NODISCARD double ffStrbufToDouble(const FFstrbuf* strbuf, double defaultValue); FF_C_NODISCARD int64_t ffStrbufToSInt(const FFstrbuf* strbuf, int64_t defaultValue); FF_C_NODISCARD uint64_t ffStrbufToUInt(const FFstrbuf* strbuf, uint64_t defaultValue); void ffStrbufUpperCase(FFstrbuf* strbuf); void ffStrbufLowerCase(FFstrbuf* strbuf); // Function alters the buffer to extract lines or delimited segments (replaces the delimiter with '\0') // so that buffer MUST be heap allocated (NOT a static string) // `lineptr` must be `NULL` and `n` MUST be `0` for the first call // Caller MUST NOT free `*lineptr` bool ffStrbufGetdelim(char** lineptr, size_t* n, char delimiter, FFstrbuf* buffer); void ffStrbufGetdelimRestore(char** lineptr, size_t* n, char delimiter, FFstrbuf* buffer); /** * @brief Read a line from a FFstrbuf. * * @details Behaves like getline(3) but reads from a FFstrbuf. * * @param[in,out] lineptr The pointer to a pointer that will be set to the start of the line (points to buffer's internal memory address to avoid memory allocation and copy). MUST NOT be freed by the caller, unlike `getline(3)`. * MUST be NULL for the first call. * @param[in,out] n The pointer to the size of the buffer of lineptr. MUST be 0 for the first call. * @param[in] buffer The buffer to read from. MUST be heap allocated (NOT a static string). * * @return true if a line has been read, false if the end of the buffer has been reached. */ static inline bool ffStrbufGetline(char** lineptr, size_t* n, FFstrbuf* buffer) { return ffStrbufGetdelim(lineptr, n, '\n', buffer); } /** * @brief Restore the end of a line that was modified by ffStrbufGetline. * @warning This function should be called before breaking an ffStrbufGetline loop if `buffer` will be used later. */ static inline void ffStrbufGetlineRestore(char** lineptr, size_t* n, FFstrbuf* buffer) { ffStrbufGetdelimRestore(lineptr, n, '\n', buffer); } bool ffStrbufRemoveDupWhitespaces(FFstrbuf* strbuf); bool ffStrbufMatchSeparatedNS(const FFstrbuf* strbuf, uint32_t compLength, const char* comp, char separator); bool ffStrbufMatchSeparatedIgnCaseNS(const FFstrbuf* strbuf, uint32_t compLength, const char* comp, char separator); bool ffStrbufSeparatedContainNS(const FFstrbuf* strbuf, uint32_t compLength, const char* comp, char separator); bool ffStrbufSeparatedContainIgnCaseNS(const FFstrbuf* strbuf, uint32_t compLength, const char* comp, char separator); int ffStrbufAppendUtf32CodePoint(FFstrbuf* strbuf, uint32_t codepoint); void ffStrbufAppendSInt(FFstrbuf* strbuf, int64_t value); void ffStrbufAppendUInt(FFstrbuf* strbuf, uint64_t value); // Appends a double value to the string buffer with the specified precision (0~15). // if `precision < 0`, let yyjson decide the precision void ffStrbufAppendDouble(FFstrbuf* strbuf, double value, int8_t precision, bool trailingZeros); FF_C_NODISCARD static inline FFstrbuf ffStrbufCreateA(uint32_t allocate) { FFstrbuf strbuf; ffStrbufInitA(&strbuf, allocate); return strbuf; } static inline void ffStrbufInitCopy(FFstrbuf* __restrict strbuf, const FFstrbuf* __restrict src) { if (src->allocated == 0) // static string *strbuf = *src; else { ffStrbufInitA(strbuf, src->allocated); ffStrbufAppend(strbuf, src); } } FF_C_NODISCARD static inline FFstrbuf ffStrbufCreateCopy(const FFstrbuf* src) { FFstrbuf strbuf; ffStrbufInitCopy(&strbuf, src); return strbuf; } // Move the content of `src` into `strbuf`, and left `src` empty static inline void ffStrbufInitMove(FFstrbuf* strbuf, FFstrbuf* src) { if (src) { *strbuf = *src; ffStrbufInit(src); } else ffStrbufInit(strbuf); } FF_C_NODISCARD static inline FFstrbuf ffStrbufCreateMove(FFstrbuf* src) { FFstrbuf strbuf; ffStrbufInitMove(&strbuf, src); return strbuf; } FF_C_NODISCARD static inline FFstrbuf ffStrbufCreateVF(const char* format, va_list arguments) { FFstrbuf strbuf; ffStrbufInitVF(&strbuf, format, arguments); return strbuf; } FF_C_PRINTF(2, 3) static inline void ffStrbufInitF(FFstrbuf* strbuf, const char* format, ...) { va_list arguments; va_start(arguments, format); ffStrbufInitVF(strbuf, format, arguments); va_end(arguments); } FF_C_PRINTF(1, 2) FF_C_NODISCARD static inline FFstrbuf ffStrbufCreateF(const char* format, ...) { FFstrbuf strbuf; va_list arguments; va_start(arguments, format); ffStrbufInitVF(&strbuf, format, arguments); va_end(arguments); return strbuf; } static inline void ffStrbufInitMoveS(FFstrbuf* strbuf, char* heapStr) { ffStrbufInitMoveNS(strbuf, (uint32_t) strlen(heapStr), heapStr); } static inline void ffStrbufDestroy(FFstrbuf* strbuf) { if(strbuf->allocated > 0) free(strbuf->chars); ffStrbufInit(strbuf); } FF_C_NODISCARD static inline uint32_t ffStrbufGetFree(const FFstrbuf* strbuf) { assert(strbuf != NULL); if(strbuf->allocated == 0) return 0; return strbuf->allocated - strbuf->length - 1; // - 1 for the null byte } static inline void ffStrbufRecalculateLength(FFstrbuf* strbuf) { strbuf->length = (uint32_t) strlen(strbuf->chars); } static inline void ffStrbufSetS(FFstrbuf* strbuf, const char* value) { assert(strbuf != NULL); if (value == NULL) ffStrbufClear(strbuf); else ffStrbufSetNS(strbuf, (uint32_t) strlen(value), value); } static inline bool ffStrbufSetJsonVal(FFstrbuf* strbuf, yyjson_val* jsonVal) { assert(strbuf != NULL); if (yyjson_is_str(jsonVal)) { ffStrbufSetNS(strbuf, (uint32_t) unsafe_yyjson_get_len(jsonVal), unsafe_yyjson_get_str(jsonVal)); return true; } ffStrbufClear(strbuf); return false; } static inline void ffStrbufAppendS(FFstrbuf* strbuf, const char* value) { if(value == NULL) return; ffStrbufAppendNS(strbuf, (uint32_t) strlen(value), value); } static inline bool ffStrbufAppendJsonVal(FFstrbuf* strbuf, yyjson_val* jsonVal) { if (yyjson_is_str(jsonVal)) { ffStrbufAppendNS(strbuf, (uint32_t) unsafe_yyjson_get_len(jsonVal), unsafe_yyjson_get_str(jsonVal)); return true; } return false; } static inline void ffStrbufInit(FFstrbuf* strbuf) { extern char* CHAR_NULL_PTR; strbuf->allocated = strbuf->length = 0; strbuf->chars = CHAR_NULL_PTR; } FF_C_NODISCARD static inline FFstrbuf ffStrbufCreate(void) { FFstrbuf strbuf; ffStrbufInit(&strbuf); return strbuf; } static inline void ffStrbufInitStatic(FFstrbuf* strbuf, const char* str) { ffStrbufInit(strbuf); if (!str) return; strbuf->allocated = 0; strbuf->length = (uint32_t) strlen(str); strbuf->chars = (char*) str; } FF_C_NODISCARD static inline FFstrbuf ffStrbufCreateStatic(const char* str) { FFstrbuf strbuf; ffStrbufInitStatic(&strbuf, str); return strbuf; } static inline void ffStrbufSetStatic(FFstrbuf* strbuf, const char* value) { if(strbuf->allocated > 0) free(strbuf->chars); if(value != NULL) ffStrbufInitStatic(strbuf, value); else ffStrbufInit(strbuf); } static inline void ffStrbufInitNS(FFstrbuf* strbuf, uint32_t length, const char* str) { ffStrbufInit(strbuf); ffStrbufAppendNS(strbuf, length, str); } FF_C_NODISCARD static inline FFstrbuf ffStrbufCreateNS(uint32_t length, const char* str) { FFstrbuf strbuf; ffStrbufInitNS(&strbuf, length, str); return strbuf; } static inline bool ffStrbufInitJsonVal(FFstrbuf* strbuf, yyjson_val* jsonVal) { ffStrbufInit(strbuf); return ffStrbufAppendJsonVal(strbuf, jsonVal); } static inline void ffStrbufInitS(FFstrbuf* strbuf, const char* str) { ffStrbufInit(strbuf); ffStrbufAppendS(strbuf, str); } FF_C_NODISCARD static inline FFstrbuf ffStrbufCreateS(const char* str) { FFstrbuf strbuf; ffStrbufInitS(&strbuf, str); return strbuf; } static inline void ffStrbufAppend(FFstrbuf* __restrict strbuf, const FFstrbuf* __restrict value) { assert(value != strbuf); if(value == NULL) return; ffStrbufAppendNS(strbuf, value->length, value->chars); } static inline void ffStrbufPrepend(FFstrbuf* strbuf, FFstrbuf* value) { if(value == NULL) return; ffStrbufPrependNS(strbuf, value->length, value->chars); } static inline void ffStrbufPrependS(FFstrbuf* strbuf, const char* value) { if(value == NULL) return; ffStrbufPrependNS(strbuf, (uint32_t) strlen(value), value); } static inline FF_C_NODISCARD int ffStrbufComp(const FFstrbuf* strbuf, const FFstrbuf* comp) { uint32_t length = strbuf->length > comp->length ? comp->length : strbuf->length; return memcmp(strbuf->chars, comp->chars, length + 1); } static inline FF_C_NODISCARD bool ffStrbufEqual(const FFstrbuf* strbuf, const FFstrbuf* comp) { return ffStrbufComp(strbuf, comp) == 0; } static inline FF_C_NODISCARD int ffStrbufCompS(const FFstrbuf* strbuf, const char* comp) { return strcmp(strbuf->chars, comp); } static inline FF_C_NODISCARD bool ffStrbufEqualS(const FFstrbuf* strbuf, const char* comp) { return ffStrbufCompS(strbuf, comp) == 0; } static inline FF_C_NODISCARD int ffStrbufIgnCaseCompS(const FFstrbuf* strbuf, const char* comp) { return strcasecmp(strbuf->chars, comp); } static inline FF_C_NODISCARD bool ffStrbufIgnCaseEqualS(const FFstrbuf* strbuf, const char* comp) { return ffStrbufIgnCaseCompS(strbuf, comp) == 0; } static inline FF_C_NODISCARD int ffStrbufIgnCaseComp(const FFstrbuf* strbuf, const FFstrbuf* comp) { return ffStrbufIgnCaseCompS(strbuf, comp->chars); } static inline FF_C_NODISCARD bool ffStrbufIgnCaseEqual(const FFstrbuf* strbuf, const FFstrbuf* comp) { return ffStrbufIgnCaseComp(strbuf, comp) == 0; } static inline FF_C_NODISCARD bool ffStrbufContainC(const FFstrbuf* strbuf, char c) { return memchr(strbuf->chars, c, strbuf->length) != NULL; } static inline FF_C_NODISCARD bool ffStrbufContainS(const FFstrbuf* strbuf, const char* str) { return strstr(strbuf->chars, str) != NULL; } static inline FF_C_NODISCARD bool ffStrbufContain(const FFstrbuf* strbuf, const FFstrbuf* str) { return ffStrbufContainS(strbuf, str->chars); } static inline FF_C_NODISCARD bool ffStrbufContainIgnCaseS(const FFstrbuf* strbuf, const char* str) { return strcasestr(strbuf->chars, str) != NULL; } static inline FF_C_NODISCARD bool ffStrbufContainIgnCase(const FFstrbuf* strbuf, const FFstrbuf* str) { return ffStrbufContainIgnCaseS(strbuf, str->chars); } static inline FF_C_NODISCARD uint32_t ffStrbufFirstIndexC(const FFstrbuf* strbuf, char c) { return ffStrbufNextIndexC(strbuf, 0, c); } static inline FF_C_NODISCARD uint32_t ffStrbufFirstIndex(const FFstrbuf* strbuf, const FFstrbuf* searched) { return ffStrbufNextIndexS(strbuf, 0, searched->chars); } static inline FF_C_NODISCARD uint32_t ffStrbufFirstIndexS(const FFstrbuf* strbuf, const char* str) { return ffStrbufNextIndexS(strbuf, 0, str); } static inline FF_C_NODISCARD uint32_t ffStrbufLastIndexC(const FFstrbuf* strbuf, char c) { if(strbuf->length == 0) return 0; return ffStrbufPreviousIndexC(strbuf, strbuf->length - 1, c); } static inline bool ffStrbufSubstrBeforeFirstC(FFstrbuf* strbuf, char c) { return ffStrbufSubstrBefore(strbuf, ffStrbufFirstIndexC(strbuf, c)); } static inline bool ffStrbufSubstrBeforeLastC(FFstrbuf* strbuf, char c) { return ffStrbufSubstrBefore(strbuf, ffStrbufLastIndexC(strbuf, c)); } static inline FF_C_NODISCARD bool ffStrbufStartsWithC(const FFstrbuf* strbuf, char c) { return strbuf->chars[0] == c; } static inline FF_C_NODISCARD bool ffStrbufStartsWithSN(const FFstrbuf* strbuf, const char* start, uint32_t length) { if (length > strbuf->length) return false; return memcmp(strbuf->chars, start, length) == 0; } static inline FF_C_NODISCARD bool ffStrbufStartsWithS(const FFstrbuf* strbuf, const char* start) { return ffStrbufStartsWithSN(strbuf, start, (uint32_t) strlen(start)); } static inline FF_C_NODISCARD bool ffStrbufStartsWith(const FFstrbuf* strbuf, const FFstrbuf* start) { return ffStrbufStartsWithSN(strbuf, start->chars, start->length); } static inline FF_C_NODISCARD bool ffStrbufStartsWithIgnCaseNS(const FFstrbuf* strbuf, uint32_t length, const char* start) { if(length > strbuf->length) return false; return strncasecmp(strbuf->chars, start, length) == 0; } static inline FF_C_NODISCARD bool ffStrbufStartsWithIgnCaseS(const FFstrbuf* strbuf, const char* start) { return ffStrbufStartsWithIgnCaseNS(strbuf, (uint32_t) strlen(start), start); } static inline FF_C_NODISCARD bool ffStrbufStartsWithIgnCase(const FFstrbuf* strbuf, const FFstrbuf* start) { return ffStrbufStartsWithIgnCaseNS(strbuf, start->length, start->chars); } static inline FF_C_NODISCARD bool ffStrbufEndsWithC(const FFstrbuf* strbuf, char c) { return strbuf->length == 0 ? false : strbuf->chars[strbuf->length - 1] == c; } static inline FF_C_NODISCARD bool ffStrbufEndsWithNS(const FFstrbuf* strbuf, uint32_t endLength, const char* end) { if(endLength > strbuf->length) return false; return memcmp(strbuf->chars + strbuf->length - endLength, end, endLength) == 0; } static inline FF_C_NODISCARD bool ffStrbufEndsWithS(const FFstrbuf* strbuf, const char* end) { return ffStrbufEndsWithNS(strbuf, (uint32_t) strlen(end), end); } static inline FF_C_NODISCARD bool ffStrbufEndsWithFn(const FFstrbuf* strbuf, int (*const fn)(int)) { return strbuf->length == 0 ? false : fn(strbuf->chars[strbuf->length - 1]); } static inline FF_C_NODISCARD bool ffStrbufEndsWith(const FFstrbuf* strbuf, const FFstrbuf* end) { return ffStrbufEndsWithNS(strbuf, end->length, end->chars); } static inline FF_C_NODISCARD bool ffStrbufEndsWithIgnCaseNS(const FFstrbuf* strbuf, uint32_t endLength, const char* end) { if(endLength > strbuf->length) return false; return strcasecmp(strbuf->chars + strbuf->length - endLength, end) == 0; } static inline FF_C_NODISCARD bool ffStrbufEndsWithIgnCaseS(const FFstrbuf* strbuf, const char* end) { return ffStrbufEndsWithIgnCaseNS(strbuf, (uint32_t) strlen(end), end); } static inline FF_C_NODISCARD bool ffStrbufEndsWithIgnCase(const FFstrbuf* strbuf, const FFstrbuf* end) { return ffStrbufEndsWithIgnCaseNS(strbuf, end->length, end->chars); } static inline void ffStrbufTrim(FFstrbuf* strbuf, char c) { ffStrbufTrimRight(strbuf, c); ffStrbufTrimLeft(strbuf, c); } static inline void ffStrbufTrimSpace(FFstrbuf* strbuf) { ffStrbufTrimRightSpace(strbuf); ffStrbufTrimLeftSpace(strbuf); } static inline bool ffStrbufMatchSeparatedS(const FFstrbuf* strbuf, const char* comp, char separator) { return ffStrbufMatchSeparatedNS(strbuf, (uint32_t) strlen(comp), comp, separator); } static inline bool ffStrbufMatchSeparated(const FFstrbuf* strbuf, const FFstrbuf* comp, char separator) { return ffStrbufMatchSeparatedNS(strbuf, comp->length, comp->chars, separator); } static inline bool ffStrbufMatchSeparatedIgnCaseS(const FFstrbuf* strbuf, const char* comp, char separator) { return ffStrbufMatchSeparatedIgnCaseNS(strbuf, (uint32_t) strlen(comp), comp, separator); } static inline bool ffStrbufMatchSeparatedIgnCase(const FFstrbuf* strbuf, const FFstrbuf* comp, char separator) { return ffStrbufMatchSeparatedIgnCaseNS(strbuf, comp->length, comp->chars, separator); } static inline bool ffStrbufSeparatedContainS(const FFstrbuf* strbuf, const char* comp, char separator) { return ffStrbufSeparatedContainNS(strbuf, (uint32_t) strlen(comp), comp, separator); } static inline bool ffStrbufSeparatedContain(const FFstrbuf* strbuf, const FFstrbuf* comp, char separator) { return ffStrbufSeparatedContainNS(strbuf, comp->length, comp->chars, separator); } static inline bool ffStrbufSeparatedContainIgnCaseS(const FFstrbuf* strbuf, const char* comp, char separator) { return ffStrbufSeparatedContainIgnCaseNS(strbuf, (uint32_t) strlen(comp), comp, separator); } static inline bool ffStrbufSeparatedContainIgnCase(const FFstrbuf* strbuf, const FFstrbuf* comp, char separator) { return ffStrbufSeparatedContainIgnCaseNS(strbuf, comp->length, comp->chars, separator); } #define FF_STRBUF_AUTO_DESTROY FFstrbuf __attribute__((__cleanup__(ffStrbufDestroy))) ================================================ FILE: src/common/apple/Info.plist.in ================================================ CFBundleIdentifier fastfetch CFBundleName @PROJECT_NAME@ CFBundleShortVersionString @PROJECT_VERSION@ CFBundleDevelopmentRegion English NSBluetoothAlwaysUsageDescription For detecting Bluetooth devices ================================================ FILE: src/common/apple/cf_helpers.c ================================================ #include "cf_helpers.h" const char* ffCfNumGetInt64(CFTypeRef cf, int64_t* result) { if(CFGetTypeID(cf) == CFNumberGetTypeID()) { if(!CFNumberGetValue((CFNumberRef)cf, kCFNumberSInt64Type, result)) return "Number type is not SInt64"; return NULL; } else if(CFGetTypeID(cf) == CFDataGetTypeID()) { if(CFDataGetLength((CFDataRef)cf) != sizeof(int64_t)) return "Data length is not sizeof(int64_t)"; CFDataGetBytes((CFDataRef)cf, CFRangeMake(0, sizeof(int64_t)), (uint8_t*)result); return NULL; } return "TypeID is neither 'CFNumber' nor 'CFData'"; } const char* ffCfNumGetInt(CFTypeRef cf, int32_t* result) { if(CFGetTypeID(cf) == CFNumberGetTypeID()) { if(!CFNumberGetValue((CFNumberRef)cf, kCFNumberSInt32Type, result)) return "Number type is not SInt32"; return NULL; } else if(CFGetTypeID(cf) == CFDataGetTypeID()) { if(CFDataGetLength((CFDataRef)cf) != sizeof(int)) return "Data length is not sizeof(int)"; CFDataGetBytes((CFDataRef)cf, CFRangeMake(0, sizeof(int)), (uint8_t*)result); return NULL; } return "TypeID is neither 'CFNumber' nor 'CFData'"; } const char* ffCfStrGetString(CFTypeRef cf, FFstrbuf* result) { ffStrbufClear(result); if (!cf) return NULL; if (CFGetTypeID(cf) == CFStringGetTypeID()) { CFStringRef cfStr = (CFStringRef)cf; const char* cstr = CFStringGetCStringPtr(cfStr, kCFStringEncodingUTF8); if (cstr) ffStrbufSetS(result, cstr); else { uint32_t length = (uint32_t) CFStringGetLength(cfStr); if (length == 0) return NULL; ffStrbufEnsureFixedLengthFree(result, (uint32_t) CFStringGetMaximumSizeForEncoding(length, kCFStringEncodingUTF8)); if(!CFStringGetCString(cfStr, result->chars, result->allocated, kCFStringEncodingUTF8)) return "CFStringGetCString() failed"; // CFStringGetCString ensures the buffer is NUL terminated // https://developer.apple.com/documentation/corefoundation/1542721-cfstringgetcstring result->length = (uint32_t) strnlen(result->chars, (uint32_t)result->allocated); } } else if (CFGetTypeID(cf) == CFDataGetTypeID()) { CFDataRef cfData = (CFDataRef)cf; uint32_t length = (uint32_t)CFDataGetLength(cfData); if (length == 0) return NULL; ffStrbufEnsureFixedLengthFree(result, length + 1); CFDataGetBytes(cfData, CFRangeMake(0, length), (uint8_t*)result->chars); result->length = (uint32_t)strnlen(result->chars, length); result->chars[result->length] = '\0'; } else return "TypeID is neither 'CFString' nor 'CFData'"; return NULL; } const char* ffCfDataGetDataAsString(CFTypeRef cf, FFstrbuf* result) { ffStrbufClear(result); if (!cf) return NULL; if (CFGetTypeID(cf) == CFDataGetTypeID()) { CFDataRef cfData = (CFDataRef)cf; uint32_t length = (uint32_t)CFDataGetLength(cfData); if (length == 0) return NULL; ffStrbufEnsureFixedLengthFree(result, length + 1); CFDataGetBytes(cfData, CFRangeMake(0, length), (uint8_t*)result->chars); result->length = length; result->chars[result->length] = '\0'; } else return "TypeID is not 'CFData'"; return NULL; } const char* ffCfDictGetString(CFDictionaryRef dict, CFStringRef key, FFstrbuf* result) { CFTypeRef cf = (CFTypeRef)CFDictionaryGetValue(dict, key); if(cf == NULL) return "CFDictionaryGetValue() failed"; return ffCfStrGetString(cf, result); } const char* ffCfDictGetDataAsString(CFDictionaryRef dict, CFStringRef key, FFstrbuf* result) { CFTypeRef cf = (CFTypeRef)CFDictionaryGetValue(dict, key); if(cf == NULL) return "CFDictionaryGetValue() failed"; return ffCfDataGetDataAsString(cf, result); } const char* ffCfDictGetBool(CFDictionaryRef dict, CFStringRef key, bool* result) { CFBooleanRef cf = (CFBooleanRef)CFDictionaryGetValue(dict, key); if(cf == NULL) return "CFDictionaryGetValue() failed"; if(CFGetTypeID(cf) != CFBooleanGetTypeID()) return "TypeID is not 'CFBoolean'"; *result = CFBooleanGetValue(cf); return NULL; } const char* ffCfDictGetInt(CFDictionaryRef dict, CFStringRef key, int* result) { CFTypeRef cf = (CFTypeRef)CFDictionaryGetValue(dict, key); if(cf == NULL) return "CFDictionaryGetValue() failed"; return ffCfNumGetInt(cf, result); } const char* ffCfDictGetInt64(CFDictionaryRef dict, CFStringRef key, int64_t* result) { CFTypeRef cf = (CFTypeRef)CFDictionaryGetValue(dict, key); if(cf == NULL) return "CFDictionaryGetValue() failed"; return ffCfNumGetInt64(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) return "CFDictionaryGetValue() failed"; if(CFGetTypeID(cf) != CFDataGetTypeID()) return "TypeID is not 'CFData'"; CFIndex trueLength = CFDataGetLength((CFDataRef)cf); if(trueLength < offset + size) return "Data length is less than offset + size"; if(length) *length = (uint32_t) trueLength; CFDataGetBytes((CFDataRef)cf, CFRangeMake(offset, size), result); return NULL; } const char* ffCfDictGetDict(CFDictionaryRef dict, CFStringRef key, CFDictionaryRef* result) { CFDictionaryRef cf = (CFDictionaryRef)CFDictionaryGetValue(dict, key); if (cf == NULL || CFGetTypeID(cf) != CFDictionaryGetTypeID()) return "TypeID is not 'CFDictionary'"; *result = cf; return NULL; } ================================================ FILE: src/common/apple/cf_helpers.h ================================================ #pragma once #include "fastfetch.h" #include #include //Return error info if failed, NULL otherwise 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* 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* 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); static inline CFNumberRef ffCfCreateInt(int value) { return CFNumberCreate(kCFAllocatorDefault, kCFNumberIntType, &value); } static inline void cfReleaseWrapper(void* type) { assert(type); if (*(CFTypeRef*) type) CFRelease(*(CFTypeRef*) type); } #define FF_CFTYPE_AUTO_RELEASE __attribute__((__cleanup__(cfReleaseWrapper))) static inline void wrapIoObjectRelease(io_object_t* service) { assert(service); if (*service) IOObjectRelease(*service); } #define FF_IOOBJECT_AUTO_RELEASE __attribute__((__cleanup__(wrapIoObjectRelease))) ================================================ FILE: src/common/apple/osascript.h ================================================ #pragma once #include "fastfetch.h" bool ffOsascript(const char* input, FFstrbuf* result); ================================================ FILE: src/common/apple/osascript.m ================================================ #include "osascript.h" #import #import #import bool ffOsascript(const char* input, FFstrbuf* result) { NSAppleScript* script = [NSAppleScript.alloc initWithSource:@(input)]; NSDictionary* errInfo = nil; NSAppleEventDescriptor* descriptor = [script executeAndReturnError:&errInfo]; if (errInfo) return false; ffStrbufSetS(result, descriptor.stringValue.UTF8String); return true; } ================================================ FILE: src/common/apple/smc_temps.c ================================================ #include "smc_temps.h" #include "common/apple/cf_helpers.h" #include "common/stringUtils.h" #include #include #include static const char kSmcCmdReadBytes = 5; static const char kSmcCmdReadKeyInfo = 9; static const uint32_t kKernelIndexSmc = 2; typedef struct { char major; char minor; char build; char reserved[1]; uint16_t release; } SmcKeyData_vers_t; typedef struct { uint16_t version; uint16_t length; uint32_t cpuPLimit; uint32_t gpuPLimit; uint32_t memPLimit; } SmcKeyData_pLimitData_t; typedef struct { uint32_t dataSize; uint32_t dataType; char dataAttributes; } SmcKeyData_keyInfo_t; typedef unsigned char SmcBytes_t[32]; typedef struct { uint32_t key; SmcKeyData_vers_t vers; SmcKeyData_pLimitData_t pLimitData; SmcKeyData_keyInfo_t keyInfo; char result; char status; char data8; uint32_t data32; SmcBytes_t bytes; } SmcKeyData_t; typedef char UInt32Char_t[5]; typedef struct { UInt32Char_t key; uint32_t dataSize; UInt32Char_t dataType; SmcBytes_t bytes; } SmcVal_t; static uint32_t smcStrtoul(const char *str, int size, int base) { uint32_t total = 0; for (int i = 0; i < size; i++) { if (base == 16) total += (uint32_t)(str[i] << (size - 1 - i) * 8); else total += (uint32_t)((unsigned char)(str[i]) << (size - 1 - i) * 8); } return total; } static void smcUltostr(char *str, uint32_t val) { str[0] = (char)(val >> 24); str[1] = (char)(val >> 16); str[2] = (char)(val >> 8); str[3] = (char)val; str[4] = '\0'; } static const char *smcCall(io_connect_t conn, uint32_t selector, SmcKeyData_t *inputStructure, SmcKeyData_t *outputStructure) { size_t size = sizeof(SmcKeyData_t); if (IOConnectCallStructMethod(conn, selector, inputStructure, size, outputStructure, &size) != kIOReturnSuccess) return "IOConnectCallStructMethod(conn) failed"; return NULL; } // Provides key info, using a cache to dramatically improve the energy impact of smcFanControl static const char *smcGetKeyInfo(io_connect_t conn, const uint32_t key, SmcKeyData_keyInfo_t *key_info) { SmcKeyData_t inputStructure = {0}; SmcKeyData_t outputStructure = {0}; inputStructure.key = key; inputStructure.data8 = kSmcCmdReadKeyInfo; const char *error = smcCall(conn, kKernelIndexSmc, &inputStructure, &outputStructure); if (error) return error; *key_info = outputStructure.keyInfo; return NULL; } static const char *smcReadSmcVal(io_connect_t conn, const UInt32Char_t key, SmcVal_t *val) { SmcKeyData_t inputStructure = {0}; SmcKeyData_t outputStructure = {0}; inputStructure.key = smcStrtoul(key, 4, 16); strcpy(val->key, key); const char *error = smcGetKeyInfo(conn, inputStructure.key, &outputStructure.keyInfo); if (error) return error; val->dataSize = outputStructure.keyInfo.dataSize; smcUltostr(val->dataType, outputStructure.keyInfo.dataType); inputStructure.keyInfo.dataSize = val->dataSize; inputStructure.data8 = kSmcCmdReadBytes; error = smcCall(conn, kKernelIndexSmc, &inputStructure, &outputStructure); if (error) return error; memcpy(val->bytes, outputStructure.bytes, sizeof(outputStructure.bytes)); return NULL; } static const char *smcOpen(io_connect_t *conn) { FF_IOOBJECT_AUTO_RELEASE io_object_t device = IOServiceGetMatchingService(MACH_PORT_NULL, IOServiceMatching("AppleSMC")); if (!device) return "No SMC device found"; if (IOServiceOpen(device, mach_task_self(), 0, conn) != kIOReturnSuccess) return "IOServiceOpen() failed"; return NULL; } static const char *smcReadValue(io_connect_t conn, const UInt32Char_t key, double *value) { SmcVal_t val = {0}; const char* error = smcReadSmcVal(conn, key, &val); if (error != NULL) return error; if (val.dataSize == 0) return "Empty SMC result"; switch (val.dataType[0]) { case 'u': // unsigned integer types if (val.dataType[1] == 'i') { switch (val.dataSize) { case 1: *value = *(uint8_t *)(val.bytes); break; case 2: *value = ntohs(*(uint16_t *)(val.bytes)); break; case 4: *value = ntohl(*(uint32_t *)(val.bytes)); break; case 8: *value = (double) ntohll(*(uint64_t *)(val.bytes)); break; default: return "Unsupported SMC unsigned integer data size"; } } else return "Unsupported SMC unsigned data type"; break; case 'f': // floating point types if (ffStrEquals(val.dataType, "flt ") && val.dataSize == 4) *value = *(float *)(val.bytes); else if (val.dataType[1] == 'p' && val.dataSize == 2) // fixed point types { if (ffStrEquals(val.dataType, "fp1f")) *value = ntohs(*(uint16_t *)(val.bytes)) / 32768.0; else if (ffStrEquals(val.dataType, "fp4c")) *value = ntohs(*(uint16_t *)(val.bytes)) / 4096.0; else if (ffStrEquals(val.dataType, "fp5b")) *value = ntohs(*(uint16_t *)(val.bytes)) / 2048.0; else if (ffStrEquals(val.dataType, "fp6a")) *value = ntohs(*(uint16_t *)(val.bytes)) / 1024.0; else if (ffStrEquals(val.dataType, "fp79")) *value = ntohs(*(uint16_t *)(val.bytes)) / 512.0; else if (ffStrEquals(val.dataType, "fp88")) *value = ntohs(*(uint16_t *)(val.bytes)) / 256.0; else if (ffStrEquals(val.dataType, "fpa6")) *value = ntohs(*(uint16_t *)(val.bytes)) / 64.0; else if (ffStrEquals(val.dataType, "fpc4")) *value = ntohs(*(uint16_t *)(val.bytes)) / 16.0; else if (ffStrEquals(val.dataType, "fpe2")) *value = ntohs(*(uint16_t *)(val.bytes)) / 4.0; else return "Unsupported SMC floating point data type"; } else return "Unsupported SMC floating point data type"; break; case 's': // signed integer types if (val.dataType[1] == 'i') { switch (val.dataSize) { case 1: *value = *(int8_t *)(val.bytes); break; case 2: *value = ntohs(*(int16_t *)(val.bytes)); break; case 4: *value = ntohl(*(int32_t *)(val.bytes)); break; case 8: *value = (double)ntohll(*(int64_t *)(val.bytes)); break; default: return "Unsupported SMC signed integer data size"; } } else if (val.dataType[1] == 'p' && val.dataSize == 2) // signed fixed point types { if (ffStrEquals(val.dataType, "sp1e")) *value = (int16_t)ntohs(*(int16_t *)(val.bytes)) / 16384.0; else if (ffStrEquals(val.dataType, "sp3c")) *value = (int16_t)ntohs(*(int16_t *)(val.bytes)) / 4096.0; else if (ffStrEquals(val.dataType, "sp4b")) *value = (int16_t)ntohs(*(int16_t *)(val.bytes)) / 2048.0; else if (ffStrEquals(val.dataType, "sp5a")) *value = (int16_t)ntohs(*(int16_t *)(val.bytes)) / 1024.0; else if (ffStrEquals(val.dataType, "sp69")) *value = (int16_t)ntohs(*(int16_t *)(val.bytes)) / 512.0; else if (ffStrEquals(val.dataType, "sp78")) *value = (int16_t)ntohs(*(int16_t *)(val.bytes)) / 256.0; else if (ffStrEquals(val.dataType, "sp87")) *value = (int16_t)ntohs(*(int16_t *)(val.bytes)) / 128.0; else if (ffStrEquals(val.dataType, "sp96")) *value = (int16_t)ntohs(*(int16_t *)(val.bytes)) / 64.0; else if (ffStrEquals(val.dataType, "spb4")) *value = (int16_t)ntohs(*(int16_t *)(val.bytes)) / 16.0; else if (ffStrEquals(val.dataType, "spf0")) *value = (int16_t)ntohs(*(int16_t *)(val.bytes)) / 1.0; else return "Unsupported SMC signed integer data type"; } else return "Unsupported SMC signed data type"; break; case '{': // special types like pwm if (ffStrEquals(val.dataType, "{pwm") && val.dataSize == 2) { *value = (double)ntohs(*(uint16_t *)(val.bytes)) * 100 / 65536.0; } else return "Unsupported SMC special data type"; break; default: return "Unsupported SMC data type"; } return NULL; } static bool detectTemp(io_connect_t conn, const char* sensor, double* sum) { double temp = 0; const char* error = smcReadValue(conn, sensor, &temp); if (error) return false; // https://github.com/exelban/stats/blob/14e29c4d60229c363cca9c9d25c30c87b7870830/Modules/Sensors/readers.swift#L124 if (temp < 10 || temp > 120) return false; *sum += temp; return true; } static io_connect_t conn; const char* ffDetectSmcSpecificTemp(const char* sensor, double* result) { if (!conn) { if (smcOpen(&conn) != NULL) conn = (io_connect_t) -1; } if (conn == (io_connect_t) -1) return "Could not open SMC connection"; if (!detectTemp(conn, sensor, result)) return "Could not read SMC temperature"; return NULL; } const char* ffDetectSmcTemps(enum FFTempType type, double* result) { if (!conn) { if (smcOpen(&conn) != NULL) conn = (io_connect_t) -1; } if (conn == (io_connect_t) -1) return "Could not open SMC connection"; uint32_t count = 0; *result = 0; // https://github.com/exelban/stats/blob/master/Modules/Sensors/values.swift switch (type) { case FF_TEMP_CPU_X64: count += detectTemp(conn, "TC0D", result); // CPU diode count += detectTemp(conn, "TC0E", result); // CPU diode virtual count += detectTemp(conn, "TC0F", result); // CPU diode filtered count += detectTemp(conn, "TC0P", result); // CPU proximity break; case FF_TEMP_CPU_M1X: count += detectTemp(conn, "Tp09", result); // CPU efficient core 1 count += detectTemp(conn, "Tp0T", result); // CPU efficient core 2 count += detectTemp(conn, "Tp01", result); // CPU performance core 1 count += detectTemp(conn, "Tp05", result); // CPU performance core 2 count += detectTemp(conn, "Tp0D", result); // CPU performance core 3 count += detectTemp(conn, "Tp0H", result); // CPU performance core 4 count += detectTemp(conn, "Tp0L", result); // CPU performance core 5 count += detectTemp(conn, "Tp0P", result); // CPU performance core 6 count += detectTemp(conn, "Tp0X", result); // CPU performance core 7 count += detectTemp(conn, "Tp0b", result); // CPU performance core 8 break; case FF_TEMP_CPU_M2X: count += detectTemp(conn, "Tp1h", result); // CPU efficiency core 1 count += detectTemp(conn, "Tp1t", result); // CPU efficiency core 2 count += detectTemp(conn, "Tp1p", result); // CPU efficiency core 3 count += detectTemp(conn, "Tp1l", result); // CPU efficiency core 4 count += detectTemp(conn, "Tp01", result); // CPU performance core 1 count += detectTemp(conn, "Tp05", result); // CPU performance core 2 count += detectTemp(conn, "Tp09", result); // CPU performance core 3 count += detectTemp(conn, "Tp0D", result); // CPU performance core 4 count += detectTemp(conn, "Tp0X", result); // CPU performance core 5 count += detectTemp(conn, "Tp0b", result); // CPU performance core 6 count += detectTemp(conn, "Tp0f", result); // CPU performance core 7 count += detectTemp(conn, "Tp0j", result); // CPU performance core 8 break; case FF_TEMP_CPU_M3X: count += detectTemp(conn, "Te05", result); // CPU efficiency core 1 count += detectTemp(conn, "Te0L", result); // CPU efficiency core 2 count += detectTemp(conn, "Te0P", result); // CPU efficiency core 3 count += detectTemp(conn, "Te0S", result); // CPU efficiency core 4 count += detectTemp(conn, "Tf04", result); // CPU performance core 1 count += detectTemp(conn, "Tf09", result); // CPU performance core 2 count += detectTemp(conn, "Tf0A", result); // CPU performance core 3 count += detectTemp(conn, "Tf0B", result); // CPU performance core 4 count += detectTemp(conn, "Tf0D", result); // CPU performance core 5 count += detectTemp(conn, "Tf0E", result); // CPU performance core 6 count += detectTemp(conn, "Tf44", result); // CPU performance core 7 count += detectTemp(conn, "Tf49", result); // CPU performance core 8 count += detectTemp(conn, "Tf4A", result); // CPU performance core 9 count += detectTemp(conn, "Tf4B", result); // CPU performance core 10 count += detectTemp(conn, "Tf4D", result); // CPU performance core 11 count += detectTemp(conn, "Tf4E", result); // CPU performance core 12 break; case FF_TEMP_CPU_M4X: count += detectTemp(conn, "Te05", result); // CPU efficiency core 1 count += detectTemp(conn, "Te0S", result); // CPU efficiency core 2 count += detectTemp(conn, "Te09", result); // CPU efficiency core 3 count += detectTemp(conn, "Te0H", result); // CPU efficiency core 4 count += detectTemp(conn, "Tp01", result); // CPU performance core 1 count += detectTemp(conn, "Tp05", result); // CPU performance core 2 count += detectTemp(conn, "Tp09", result); // CPU performance core 3 count += detectTemp(conn, "Tp0D", result); // CPU performance core 4 count += detectTemp(conn, "Tp0V", result); // CPU performance core 5 count += detectTemp(conn, "Tp0Y", result); // CPU performance core 6 count += detectTemp(conn, "Tp0b", result); // CPU performance core 7 count += detectTemp(conn, "Tp0e", result); // CPU performance core 8 break; case FF_TEMP_GPU_INTEL: count += detectTemp(conn, "TCGC", result); // GPU Intel Graphics goto gpu_unknown; case FF_TEMP_GPU_AMD: count += detectTemp(conn, "TGDD", result); // GPU AMD Radeon goto gpu_unknown; case FF_TEMP_GPU_UNKNOWN: // Nvidia? gpu_unknown: count += detectTemp(conn, "TG0D", result); // GPU diode count += detectTemp(conn, "TG0P", result); // GPU proximity break; case FF_TEMP_GPU_M1X: count += detectTemp(conn, "Tg05", result); // GPU 1 count += detectTemp(conn, "Tg0D", result); // GPU 2 count += detectTemp(conn, "Tg0L", result); // GPU 3 count += detectTemp(conn, "Tg0T", result); // GPU 4 break; case FF_TEMP_GPU_M2X: count += detectTemp(conn, "Tg0f", result); // GPU 1 count += detectTemp(conn, "Tg0j", result); // GPU 2 break; case FF_TEMP_GPU_M3X: count += detectTemp(conn, "Tf14", result); // GPU 1 count += detectTemp(conn, "Tf18", result); // GPU 2 count += detectTemp(conn, "Tf19", result); // GPU 3 count += detectTemp(conn, "Tf1A", result); // GPU 4 count += detectTemp(conn, "Tf24", result); // GPU 5 count += detectTemp(conn, "Tf28", result); // GPU 6 count += detectTemp(conn, "Tf29", result); // GPU 7 count += detectTemp(conn, "Tf2A", result); // GPU 8 break; case FF_TEMP_GPU_M4X: count += detectTemp(conn, "Tg0G", result); // GPU 1 (Basic) count += detectTemp(conn, "Tg0H", result); // GPU 2 (Basic) count += detectTemp(conn, "Tg1U", result); // GPU 1 (Pro / Max) count += detectTemp(conn, "Tg1k", result); // GPU 2 (Pro / Max) count += detectTemp(conn, "Tg0K", result); // GPU 3 count += detectTemp(conn, "Tg0L", result); // GPU 4 count += detectTemp(conn, "Tg0d", result); // GPU 5 count += detectTemp(conn, "Tg0e", result); // GPU 6 count += detectTemp(conn, "Tg0j", result); // GPU 7 count += detectTemp(conn, "Tg0k", result); // GPU 8 break; case FF_TEMP_BATTERY: count += detectTemp(conn, "TB1T", result); // Battery count += detectTemp(conn, "TB2T", result); // Battery break; case FF_TEMP_MEMORY: count += detectTemp(conn, "Tm02", result); // Memory 1 count += detectTemp(conn, "Tm06", result); // Memory 2 count += detectTemp(conn, "Tm08", result); // Memory 3 count += detectTemp(conn, "Tm09", result); // Memory 4 break; } if (count == 0) return "No temperatures detected"; *result /= count; return NULL; } ================================================ FILE: src/common/apple/smc_temps.h ================================================ #pragma once #include "fastfetch.h" typedef struct FFTempValue { FFstrbuf name; FFstrbuf deviceClass; double value; } FFTempValue; enum FFTempType { FF_TEMP_CPU_X64, FF_TEMP_CPU_M1X, FF_TEMP_CPU_M2X, FF_TEMP_CPU_M3X, FF_TEMP_CPU_M4X, FF_TEMP_GPU_INTEL, FF_TEMP_GPU_AMD, FF_TEMP_GPU_UNKNOWN, FF_TEMP_GPU_M1X, FF_TEMP_GPU_M2X, FF_TEMP_GPU_M3X, FF_TEMP_GPU_M4X, FF_TEMP_BATTERY, FF_TEMP_MEMORY, }; const char* ffDetectSmcSpecificTemp(const char* sensor, double* result); const char* ffDetectSmcTemps(enum FFTempType type, double* result); ================================================ FILE: src/common/argType.h ================================================ #pragma once #include "common/FFstrbuf.h" typedef enum __attribute__((__packed__)) FFArgType { FF_ARG_TYPE_NULL = 0, FF_ARG_TYPE_UINT, FF_ARG_TYPE_UINT64, FF_ARG_TYPE_UINT16, FF_ARG_TYPE_UINT8, FF_ARG_TYPE_INT, FF_ARG_TYPE_STRING, FF_ARG_TYPE_STRBUF, FF_ARG_TYPE_FLOAT, FF_ARG_TYPE_DOUBLE, FF_ARG_TYPE_LIST, FF_ARG_TYPE_BOOL } FFArgType; #define FF_ARG(variable, var_name) { _Generic((variable), \ uint32_t: FF_ARG_TYPE_UINT, \ uint64_t: FF_ARG_TYPE_UINT64, \ uint16_t: FF_ARG_TYPE_UINT16, \ uint8_t: FF_ARG_TYPE_UINT8, \ int32_t: FF_ARG_TYPE_INT, \ char*: FF_ARG_TYPE_STRING, \ const char*: FF_ARG_TYPE_STRING, \ FFstrbuf: FF_ARG_TYPE_STRBUF, \ float: FF_ARG_TYPE_FLOAT, \ double: FF_ARG_TYPE_DOUBLE, \ FFlist: FF_ARG_TYPE_LIST, \ bool: FF_ARG_TYPE_BOOL \ ), _Generic((variable), char*: (variable), const char*: (variable), default: &(variable) ), (var_name) } ================================================ FILE: src/common/arrayUtils.h ================================================ #pragma once #include #ifdef __has_builtin #if !__cplusplus && FF_SUPPORTS_COUNT_OF #define ARRAY_SIZE(x) _Countof(x) #elif __has_builtin(__is_array) #define ARRAY_SIZE(x) ({ static_assert(__is_array(__typeof__(x)), "Must be an array"); (uint32_t) (sizeof(x) / sizeof(*(x))); }) #elif __has_builtin(__builtin_types_compatible_p) #define ARRAY_SIZE(x) ({ static_assert(!__builtin_types_compatible_p(__typeof__(x), __typeof__(&*(x))), "Must not be a pointer"); (uint32_t) (sizeof(x) / sizeof(*(x))); }) #endif #endif #ifndef ARRAY_SIZE #define ARRAY_SIZE(x) ((uint32_t) (sizeof(x) / sizeof(*(x)))) #endif ================================================ FILE: src/common/base64.h ================================================ #pragma once #include "fastfetch.h" void ffBase64EncodeRaw(uint32_t size, const char *str, uint32_t *out_size, char *output); static inline FFstrbuf ffBase64EncodeStrbuf(const FFstrbuf* in) { FFstrbuf out = ffStrbufCreateA(10 + in->length * 4 / 3); ffBase64EncodeRaw(in->length, in->chars, &out.length, out.chars); assert(out.length < out.allocated); return out; } bool ffBase64DecodeRaw(uint32_t size, const char *str, uint32_t *out_size, char *output); static inline FFstrbuf ffBase64DecodeStrbuf(const FFstrbuf* in) { FFstrbuf out = ffStrbufCreateA(10 + in->length * 3 / 4); ffBase64DecodeRaw(in->length, in->chars, &out.length, out.chars); assert(out.length < out.allocated); return out; } ================================================ FILE: src/common/binary.h ================================================ #pragma once #include "fastfetch.h" /** * Extracts string literals from a binary file * * @param file Path to the binary file to extract strings from * @param cb Callback function that will be called for each string found * Return false from callback to stop extraction * @param userdata User-provided data passed to the callback function * @param minLength Minimum length of strings to extract * * @return NULL on success, error message on failure. * @note This function won't return an error if no strings are found. * Always check if strings are correctly extracted after this function all. */ const char* ffBinaryExtractStrings(const char* file, bool (*cb)(const char* str, uint32_t len, void* userdata), void* userdata, uint32_t minLength); ================================================ FILE: src/common/color.h ================================================ #pragma once #define FF_COLOR_MODE_RESET "0;" #define FF_COLOR_MODE_BOLD "1;" #define FF_COLOR_MODE_DIM "2;" #define FF_COLOR_MODE_ITALIC "3;" #define FF_COLOR_MODE_UNDERLINE "4;" #define FF_COLOR_MODE_BLINK "5;" #define FF_COLOR_MODE_INVERSE "7;" #define FF_COLOR_MODE_HIDDEN "8;" #define FF_COLOR_MODE_STRIKETHROUGH "9;" #define FF_COLOR_FG_BLACK "30" #define FF_COLOR_FG_RED "31" #define FF_COLOR_FG_GREEN "32" #define FF_COLOR_FG_YELLOW "33" #define FF_COLOR_FG_BLUE "34" #define FF_COLOR_FG_MAGENTA "35" #define FF_COLOR_FG_CYAN "36" #define FF_COLOR_FG_WHITE "37" #define FF_COLOR_FG_DEFAULT "39" #define FF_COLOR_FG_LIGHT_BLACK "90" #define FF_COLOR_FG_LIGHT_RED "91" #define FF_COLOR_FG_LIGHT_GREEN "92" #define FF_COLOR_FG_LIGHT_YELLOW "93" #define FF_COLOR_FG_LIGHT_BLUE "94" #define FF_COLOR_FG_LIGHT_MAGENTA "95" #define FF_COLOR_FG_LIGHT_CYAN "96" #define FF_COLOR_FG_LIGHT_WHITE "97" #define FF_COLOR_BG_BLACK "40" #define FF_COLOR_BG_RED "41" #define FF_COLOR_BG_GREEN "42" #define FF_COLOR_BG_YELLOW "43" #define FF_COLOR_BG_BLUE "44" #define FF_COLOR_BG_MAGENTA "45" #define FF_COLOR_BG_CYAN "46" #define FF_COLOR_BG_WHITE "47" #define FF_COLOR_BG_DEFAULT "49" #define FF_COLOR_BG_LIGHT_BLACK "100" #define FF_COLOR_BG_LIGHT_RED "101" #define FF_COLOR_BG_LIGHT_GREEN "102" #define FF_COLOR_BG_LIGHT_YELLOW "103" #define FF_COLOR_BG_LIGHT_BLUE "104" #define FF_COLOR_BG_LIGHT_MAGENTA "105" #define FF_COLOR_BG_LIGHT_CYAN "106" #define FF_COLOR_BG_LIGHT_WHITE "107" #define FF_COLOR_FG_256 "38;5;" #define FF_COLOR_BG_256 "48;5;" #define FF_COLOR_FG_RGB "38;2;" #define FF_COLOR_BG_RGB "48;2;" ================================================ FILE: src/common/commandoption.h ================================================ #pragma once #include "common/ffdata.h" void ffPrepareCommandOption(FFdata* data); void ffPrintCommandOption(FFdata* data); void ffMigrateCommandOptionToJsonc(FFdata* data); bool ffParseModuleOptions(const char* key, const char* value); ================================================ FILE: src/common/dbus.h ================================================ #pragma once #ifdef FF_HAVE_DBUS #include #include "common/FFstrbuf.h" #include "common/library.h" typedef struct FFDBusLibrary { FF_LIBRARY_SYMBOL(dbus_bus_get) FF_LIBRARY_SYMBOL(dbus_message_new_method_call) FF_LIBRARY_SYMBOL(dbus_message_append_args) FF_LIBRARY_SYMBOL(dbus_message_iter_init) FF_LIBRARY_SYMBOL(dbus_message_iter_get_arg_type) FF_LIBRARY_SYMBOL(dbus_message_iter_get_basic) FF_LIBRARY_SYMBOL(dbus_message_iter_recurse) FF_LIBRARY_SYMBOL(dbus_message_iter_has_next) FF_LIBRARY_SYMBOL(dbus_message_iter_next) FF_LIBRARY_SYMBOL(dbus_message_unref) FF_LIBRARY_SYMBOL(dbus_connection_send_with_reply_and_block) FF_LIBRARY_SYMBOL(dbus_connection_unref) } FFDBusLibrary; typedef struct FFDBusData { const FFDBusLibrary* lib; DBusConnection* connection; } 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); 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); void ffDBusDestroyData(FFDBusData* data); static inline DBusMessage* ffDBusGetAllProperties(FFDBusData* dbus, const char* busName, const char* objectPath, const char* interface) { return ffDBusGetMethodReply(dbus, busName, objectPath, "org.freedesktop.DBus.Properties", "GetAll", interface, NULL); } #define FF_DBUS_AUTO_DESTROY_DATA __attribute__((__cleanup__(ffDBusDestroyData))) #endif // FF_HAVE_DBUS ================================================ FILE: src/common/debug.h ================================================ #pragma once #include "fastfetch.h" #include "common/time.h" static inline const char* ffFindFileName(const char* file) { const char* lastSlash = __builtin_strrchr(file, '/'); #ifdef _WIN32 if (lastSlash == NULL) lastSlash = __builtin_strrchr(file, '\\'); #endif if (lastSlash != NULL) return lastSlash + 1; return file; } #ifndef NDEBUG #define FF_DEBUG_PRINT(file_, line_, format_, ...) \ do { \ if (instance.config.display.debugMode) \ fprintf(stderr, "[%s%4d, %s] " format_ "\n", ffFindFileName(file_), line_, ffTimeToTimeStr(ffTimeGetNow()), ##__VA_ARGS__); \ } while (0) #else #define FF_DEBUG_PRINT(file_, line_, format_, ...) \ do { } while (0) #endif #define FF_DEBUG(format, ...) FF_DEBUG_PRINT(__FILE__, __LINE__, format, ##__VA_ARGS__) #if _WIN32 const char* ffDebugWin32Error(unsigned long errorCode); const char* ffDebugNtStatus(NTSTATUS status); #endif ================================================ FILE: src/common/duration.h ================================================ #pragma once #include "fastfetch.h" void ffDurationAppendNum(uint64_t totalSeconds, FFstrbuf* result); ================================================ FILE: src/common/edidHelper.h ================================================ #pragma once #include #include "common/FFstrbuf.h" void ffEdidGetVendorAndModel(const uint8_t edid[128], FFstrbuf* result); bool ffEdidGetName(const uint8_t edid[128], FFstrbuf* name); void ffEdidGetPreferredResolutionAndRefreshRate(const uint8_t edid[128], uint32_t* width, uint32_t* height, double* refreshRate); void ffEdidGetPhysicalResolution(const uint8_t edid[128], uint32_t* width, uint32_t* height); void ffEdidGetPhysicalSize(const uint8_t edid[128], uint32_t* width, uint32_t* height); // in mm void ffEdidGetSerialAndManufactureDate(const uint8_t edid[128], uint32_t* serial, uint16_t* year, uint16_t* week); bool ffEdidGetHdrCompatible(const uint8_t* edid, uint32_t length); bool ffEdidIsValid(const uint8_t edid[128], uint32_t length); ================================================ FILE: src/common/ffdata.h ================================================ #pragma once #include "common/FFstrbuf.h" typedef enum __attribute__((__packed__)) FFDataResultDocType { FF_RESULT_DOC_TYPE_DEFAULT = 0, FF_RESULT_DOC_TYPE_JSON, FF_RESULT_DOC_TYPE_CONFIG, FF_RESULT_DOC_TYPE_CONFIG_FULL, } FFDataResultDocType; // FFdata aggregates configuration, generation parameters, and output state used by fastfetch. // It holds the parsed configuration document, a mutable JSON document for results, and related metadata. typedef struct FFdata { yyjson_doc* configDoc; // Parsed JSON configuration document yyjson_mut_doc* resultDoc; // Mutable JSON document for storing results FFstrbuf structure; // Custom output structure from command line FFstrbuf structureDisabled; // Disabled modules in the output structure from command line FFstrbuf genConfigPath; // Path to generate configuration file FFDataResultDocType docType; // Type of result document bool configLoaded; } FFdata; ================================================ FILE: src/common/font.h ================================================ #pragma once #include "common/FFstrbuf.h" #include "common/FFlist.h" typedef struct FFfont { FFstrbuf pretty; FFstrbuf name; FFstrbuf size; FFlist styles; } FFfont; void ffFontInit(FFfont* font); void ffFontInitQt(FFfont* font, const char* data); void ffFontInitPango(FFfont* font, const char* data); void ffFontInitValues(FFfont* font, const char* name, const char* size); void ffFontInitXlfd(FFfont* font, const char* xlfd); void ffFontInitXft(FFfont* font, const char* xft); void ffFontInitMoveValues(FFfont* font, FFstrbuf* name, FFstrbuf* size, FFstrbuf* style); void ffFontInitWithSpace(FFfont* font, const char* rawName); void ffFontDestroy(FFfont* font); static inline void ffFontInitCopy(FFfont* font, const char* name) { ffFontInitValues(font, name, NULL); } ================================================ FILE: src/common/format.h ================================================ #pragma once #include "common/argType.h" typedef struct FFformatarg { FFArgType type; const void* value; const char* name; // argument name, must start with an alphabet } FFformatarg; void ffFormatAppendFormatArg(FFstrbuf* buffer, const FFformatarg* formatarg); void ffParseFormatString(FFstrbuf* buffer, const FFstrbuf* formatstr, uint32_t numArgs, const FFformatarg* arguments); #define FF_PARSE_FORMAT_STRING_CHECKED(buffer, formatstr, arguments) \ ffParseFormatString((buffer), (formatstr), sizeof(arguments) / sizeof(*arguments), (arguments)); ================================================ FILE: src/common/frequency.h ================================================ #pragma once #include "fastfetch.h" bool ffFreqAppendNum(uint32_t mhz, FFstrbuf* result); ================================================ FILE: src/common/haiku/version.cpp ================================================ extern "C" { #include "version.h" } #include #include bool ffGetFileVersion(const char* filePath, FFstrbuf* version) { BFile f(filePath, B_READ_ONLY); if (f.InitCheck() != B_OK) return false; BAppFileInfo fileInfo(&f); if (f.InitCheck() != B_OK) return false; version_info info; if (fileInfo.GetVersionInfo(&info, B_SYSTEM_VERSION_KIND) != B_OK) return false; ffStrbufSetF(version, "%d.%d.%d", (int)info.major, (int)info.middle, (int)info.minor); return true; } ================================================ FILE: src/common/haiku/version.h ================================================ #include "common/FFstrbuf.h" bool ffGetFileVersion(const char* filePath, FFstrbuf* version); ================================================ FILE: src/common/impl/FFPlatform.c ================================================ #include "FFPlatform_private.h" #include "common/stringUtils.h" #include "common/io.h" #include "detection/version/version.h" void ffPlatformInit(FFPlatform* platform) { ffStrbufInit(&platform->homeDir); ffStrbufInit(&platform->cacheDir); ffListInit(&platform->configDirs, sizeof(FFstrbuf)); ffListInit(&platform->dataDirs, sizeof(FFstrbuf)); ffStrbufInit(&platform->exePath); ffStrbufInit(&platform->cwd); ffStrbufInit(&platform->userName); ffStrbufInit(&platform->fullUserName); ffStrbufInit(&platform->hostName); ffStrbufInit(&platform->userShell); #ifdef _WIN32 ffStrbufInit(&platform->sid); #endif FFPlatformSysinfo* info = &platform->sysinfo; ffStrbufInit(&info->name); ffStrbufInit(&info->release); ffStrbufInit(&info->version); ffStrbufInit(&info->architecture); ffPlatformInitImpl(platform); if(info->name.length == 0) ffStrbufSetStatic(&info->name, ffVersionResult.sysName); if(info->architecture.length == 0) ffStrbufSetStatic(&info->architecture, ffVersionResult.architecture); } void ffPlatformDestroy(FFPlatform* platform) { ffStrbufDestroy(&platform->homeDir); ffStrbufDestroy(&platform->cacheDir); FF_LIST_FOR_EACH(FFstrbuf, dir, platform->configDirs) ffStrbufDestroy(dir); ffListDestroy(&platform->configDirs); FF_LIST_FOR_EACH(FFstrbuf, dir, platform->dataDirs) ffStrbufDestroy(dir); ffListDestroy(&platform->dataDirs); ffStrbufDestroy(&platform->exePath); ffStrbufDestroy(&platform->cwd); ffStrbufDestroy(&platform->userName); ffStrbufDestroy(&platform->hostName); ffStrbufDestroy(&platform->userShell); ffStrbufDestroy(&platform->fullUserName); #ifdef _WIN32 ffStrbufDestroy(&platform->sid); #endif FFPlatformSysinfo* info = &platform->sysinfo; ffStrbufDestroy(&info->architecture); ffStrbufDestroy(&info->name); ffStrbufDestroy(&info->release); ffStrbufDestroy(&info->version); } void ffPlatformPathAddAbsolute(FFlist* dirs, const char* path) { if (!ffPathExists(path, FF_PATHTYPE_DIRECTORY)) return; FF_STRBUF_AUTO_DESTROY buffer = ffStrbufCreateS(path); ffStrbufEnsureEndsWithC(&buffer, '/'); if (!ffListContains(dirs, &buffer, (void*) ffStrbufEqual)) ffStrbufInitMove((FFstrbuf*) ffListAdd(dirs), &buffer); } void ffPlatformPathAddHome(FFlist* dirs, const FFPlatform* platform, const char* suffix) { FF_STRBUF_AUTO_DESTROY buffer = ffStrbufCreateA(64); ffStrbufAppend(&buffer, &platform->homeDir); ffStrbufAppendS(&buffer, suffix); ffStrbufEnsureEndsWithC(&buffer, '/'); if (ffPathExists(buffer.chars, FF_PATHTYPE_DIRECTORY) && !ffListContains(dirs, &buffer, (void*) ffStrbufEqual)) ffStrbufInitMove((FFstrbuf*) ffListAdd(dirs), &buffer); } ================================================ FILE: src/common/impl/FFPlatform_private.h ================================================ #pragma once #include "common/FFPlatform.h" void ffPlatformInitImpl(FFPlatform* platform); void ffPlatformPathAddAbsolute(FFlist* dirs, const char* path); void ffPlatformPathAddHome(FFlist* dirs, const FFPlatform* platform, const char* suffix); ================================================ FILE: src/common/impl/FFPlatform_unix.c ================================================ #include "FFPlatform_private.h" #include "common/FFstrbuf.h" #include "common/arrayUtils.h" #include "common/stringUtils.h" #include "common/io.h" #include "fastfetch_config.h" #include #include #include #include #include #ifdef __APPLE__ #include #include #elif defined(__FreeBSD__) || defined(__NetBSD__) #include #elif defined(__OpenBSD__) #include #include #include "common/path.h" #elif defined(__HAIKU__) #include #include #endif static void getExePath(FFPlatform* platform) { char exePath[PATH_MAX]; #if defined(__linux__) || defined (__GNU__) ssize_t exePathLen = readlink("/proc/self/exe", exePath, sizeof(exePath) - 1); if (exePathLen >= 0) exePath[exePathLen] = '\0'; #elif defined(__APPLE__) uint32_t exePathLen = sizeof(exePath); if (_NSGetExecutablePath(exePath, &exePathLen) == 0) exePathLen = (uint32_t) strlen(exePath); else exePathLen = 0; #elif defined(__FreeBSD__) || defined(__NetBSD__) size_t exePathLen = sizeof(exePath); if(sysctl( (int[]){CTL_KERN, #ifdef __FreeBSD__ KERN_PROC, KERN_PROC_PATHNAME, (pid_t) platform->pid #else KERN_PROC_ARGS, platform->pid, KERN_PROC_PATHNAME #endif }, 4, exePath, &exePathLen, NULL, 0 ) < 0) exePathLen = 0; else exePathLen--; // remove terminating NUL #elif defined(__OpenBSD__) // OpenBSD doesn't have a reliable way to get the executable path. // Current implementation uses argv[0], which can be easily spoofed. // See #2195 size_t exePathLen = 0; kvm_t* kd = kvm_openfiles(NULL, NULL, NULL, KVM_NO_FILES, NULL); if (kd) { int kpCount; struct kinfo_proc* kp = kvm_getprocs(kd, KERN_PROC_PID, (pid_t) platform->pid, sizeof(*kp), &kpCount); if (kp && kpCount == 1) { char** argv = kvm_getargv(kd, kp, 0); if (argv && argv[0]) { char* arg0 = argv[0]; if (arg0[0]) { if (strchr(arg0, '/') != NULL) // likely a path (absolute or relative) { exePathLen = strlen(arg0); if (exePathLen < ARRAY_SIZE(exePath)) { memcpy(exePath, arg0, exePathLen); exePath[exePathLen] = '\0'; } else exePathLen = 0; } else { FF_STRBUF_AUTO_DESTROY tmpPath = ffStrbufCreate(); if (ffFindExecutableInPath(arg0, &tmpPath) == NULL && tmpPath.length < ARRAY_SIZE(exePath)) { memcpy(exePath, tmpPath.chars, tmpPath.length + 1); exePathLen = tmpPath.length; } } if (exePathLen > 0) { 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); if (kf) { int i; for (i = 0; i < cntp; i++) { if (kf[i].fd_fd == KERN_FILE_TEXT) { // KERN_FILE_TEXT is the executable file, not a shared library, and should be unique in the list. if (st.st_dev != (dev_t)kf[i].va_fsid || st.st_ino != (ino_t)kf[i].va_fileid) i = -1; break; } } if (i < 0) exePathLen = 0; } else { // If we can't get the list of open files, we can't verify that the file is actually the executable // Assume it is } } else exePathLen = 0; } } } } kvm_close(kd); } #elif defined(__sun) ssize_t exePathLen = readlink("/proc/self/path/a.out", exePath, sizeof(exePath) - 1); if (exePathLen >= 0) exePath[exePathLen] = '\0'; #elif defined(__HAIKU__) size_t exePathLen = 0; image_info info; int32 cookie = 0; while (get_next_image_info(B_CURRENT_TEAM, &cookie, &info) == B_OK) { if (info.type == B_APP_IMAGE) { exePathLen = strlcpy(exePath, info.name, sizeof(exePath)); break; } } #endif if (exePathLen > 0) { ffStrbufEnsureFree(&platform->exePath, PATH_MAX); if (realpath(exePath, platform->exePath.chars)) ffStrbufRecalculateLength(&platform->exePath); else ffStrbufSetNS(&platform->exePath, (uint32_t) exePathLen, exePath); } } static void platformPathAddEnv(FFlist* dirs, const char* env) { const char* envValue = getenv(env); if(!ffStrSet(envValue)) return; FF_STRBUF_AUTO_DESTROY value = ffStrbufCreateA(64); ffStrbufAppendS(&value, envValue); uint32_t startIndex = 0; while (startIndex < value.length) { uint32_t colonIndex = ffStrbufNextIndexC(&value, startIndex, ':'); value.chars[colonIndex] = '\0'; if(!ffStrSet(value.chars + startIndex)) { startIndex = colonIndex + 1; continue; } ffPlatformPathAddAbsolute(dirs, value.chars + startIndex); startIndex = colonIndex + 1; } } static void getHomeDir(FFPlatform* platform, const struct passwd* pwd) { const char* home = pwd ? pwd->pw_dir : getenv("HOME"); ffStrbufAppendS(&platform->homeDir, home); ffStrbufEnsureEndsWithC(&platform->homeDir, '/'); } static void getCacheDir(FFPlatform* platform) { const char* cache = getenv("XDG_CACHE_HOME"); if(ffStrSet(cache)) { ffStrbufAppendS(&platform->cacheDir, cache); ffStrbufEnsureEndsWithC(&platform->cacheDir, '/'); } else { ffStrbufAppend(&platform->cacheDir, &platform->homeDir); ffStrbufAppendS(&platform->cacheDir, ".cache/"); } } static void getConfigDirs(FFPlatform* platform) { // Always make sure `${XDG_CONFIG_HOME:-$HOME/.config}` is the first entry platformPathAddEnv(&platform->configDirs, "XDG_CONFIG_HOME"); ffPlatformPathAddHome(&platform->configDirs, platform, ".config/"); #if defined(__APPLE__) ffPlatformPathAddHome(&platform->configDirs, platform, "Library/Preferences/"); ffPlatformPathAddHome(&platform->configDirs, platform, "Library/Application Support/"); #endif #if defined(__HAIKU__) ffPlatformPathAddHome(&platform->configDirs, platform, "config/settings/"); #endif ffPlatformPathAddHome(&platform->configDirs, platform, ""); platformPathAddEnv(&platform->configDirs, "XDG_CONFIG_DIRS"); #if !defined(__APPLE__) ffPlatformPathAddAbsolute(&platform->configDirs, FASTFETCH_TARGET_DIR_ETC "/xdg/"); #endif ffPlatformPathAddAbsolute(&platform->configDirs, FASTFETCH_TARGET_DIR_ETC "/"); ffPlatformPathAddAbsolute(&platform->configDirs, FASTFETCH_TARGET_DIR_INSTALL_SYSCONF "/"); } static void getDataDirs(FFPlatform* platform) { platformPathAddEnv(&platform->dataDirs, "XDG_DATA_HOME"); ffPlatformPathAddHome(&platform->dataDirs, platform, ".local/share/"); // Add ${currentExePath}/../share if (platform->exePath.length > 0) { FF_STRBUF_AUTO_DESTROY path = ffStrbufCreateCopy(&platform->exePath); ffStrbufSubstrBeforeLastC(&path, '/'); ffStrbufSubstrBeforeLastC(&path, '/'); ffStrbufAppendS(&path, "/share"); ffPlatformPathAddAbsolute(&platform->dataDirs, path.chars); } #ifdef __APPLE__ ffPlatformPathAddHome(&platform->dataDirs, platform, "Library/Application Support/"); #endif ffPlatformPathAddHome(&platform->dataDirs, platform, ""); platformPathAddEnv(&platform->dataDirs, "XDG_DATA_DIRS"); #ifdef _PATH_LOCALBASE ffPlatformPathAddAbsolute(&platform->dataDirs, _PATH_LOCALBASE "/share/"); #endif ffPlatformPathAddAbsolute(&platform->dataDirs, FASTFETCH_TARGET_DIR_USR "/local/share/"); ffPlatformPathAddAbsolute(&platform->dataDirs, FASTFETCH_TARGET_DIR_USR "/share/"); } static void getUserName(FFPlatform* platform, const struct passwd* pwd) { if (pwd) { ffStrbufSetS(&platform->userName, pwd->pw_name); ffStrbufSetS(&platform->fullUserName, pwd->pw_gecos); ffStrbufTrimSpace(&platform->fullUserName); } else { ffStrbufSetS(&platform->userName, getenv("USER")); } } static void getHostName(FFPlatform* platform, const struct utsname* uts) { ffStrbufAppendS(&platform->hostName, uts->nodename); } static void getUserShell(FFPlatform* platform, const struct passwd* pwd) { const char* shell = getenv("SHELL"); if(!ffStrSet(shell) && pwd) shell = pwd->pw_shell; ffStrbufAppendS(&platform->userShell, shell); } static void getSysinfo(FFPlatformSysinfo* info, const struct utsname* uts) { ffStrbufAppendS(&info->name, uts->sysname); ffStrbufAppendS(&info->release, uts->release); ffStrbufAppendS(&info->version, uts->version); #ifdef __HAIKU__ /* historical reason */ if (ffStrEquals(uts->machine, "BePC")) ffStrbufSetStatic(&info->architecture, "i386"); else #endif ffStrbufAppendS(&info->architecture, uts->machine); #if defined(__FreeBSD__) || defined(__APPLE__) || defined(__OpenBSD__) || defined(__NetBSD__) size_t length = sizeof(info->pageSize); sysctl((int[]){ CTL_HW, HW_PAGESIZE }, 2, &info->pageSize, &length, NULL, 0); #else info->pageSize = (uint32_t) sysconf(_SC_PAGESIZE); #endif } static void getCwd(FFPlatform* platform) { char cwd[PATH_MAX]; if (getcwd(cwd, sizeof(cwd)) != NULL) { ffStrbufSetS(&platform->cwd, cwd); ffStrbufEnsureEndsWithC(&platform->cwd, '/'); } } void ffPlatformInitImpl(FFPlatform* platform) { platform->pid = (uint32_t) getpid(); platform->uid = getuid(); struct passwd* pwd = getpwuid(platform->uid); struct utsname uts; if(uname(&uts) < 0) memset(&uts, 0, sizeof(uts)); getExePath(platform); getCwd(platform); getHomeDir(platform, pwd); getCacheDir(platform); getConfigDirs(platform); getDataDirs(platform); getUserName(platform, pwd); getHostName(platform, &uts); getUserShell(platform, pwd); getSysinfo(&platform->sysinfo, &uts); } ================================================ FILE: src/common/impl/FFPlatform_windows.c ================================================ #include "FFPlatform_private.h" #include "common/io.h" #include "common/library.h" #include "common/stringUtils.h" #include "common/windows/unicode.h" #include "common/windows/registry.h" #include "common/windows/nt.h" #include #include #include #include #define SECURITY_WIN32 1 // For secext.h #include static void getExePath(FFPlatform* platform) { wchar_t exePathW[MAX_PATH]; FF_AUTO_CLOSE_FD HANDLE hPath = CreateFileW( ffGetPeb()->ProcessParameters->ImagePathName.Buffer, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS, NULL); if (hPath != INVALID_HANDLE_VALUE) { DWORD len = GetFinalPathNameByHandleW(hPath, exePathW, MAX_PATH, FILE_NAME_NORMALIZED); if (len > 0 && len < MAX_PATH) { ffStrbufSetNWS(&platform->exePath, len, exePathW); if (ffStrbufStartsWithS(&platform->exePath, "\\\\?\\")) ffStrbufSubstrAfter(&platform->exePath, 3); } } if (platform->exePath.length == 0) { PCUNICODE_STRING imagePathName = &ffGetPeb()->ProcessParameters->ImagePathName; ffStrbufSetNWS(&platform->exePath, imagePathName->Length / sizeof(wchar_t), imagePathName->Buffer); } ffStrbufReplaceAllC(&platform->exePath, '\\', '/'); } static void getHomeDir(FFPlatform* platform) { PWSTR pPath = NULL; if(SUCCEEDED(SHGetKnownFolderPath(&FOLDERID_Profile, KF_FLAG_DEFAULT, NULL, &pPath))) { ffStrbufSetWS(&platform->homeDir, pPath); ffStrbufReplaceAllC(&platform->homeDir, '\\', '/'); ffStrbufEnsureEndsWithC(&platform->homeDir, '/'); } else { ffStrbufSetS(&platform->homeDir, getenv("USERPROFILE")); ffStrbufReplaceAllC(&platform->homeDir, '\\', '/'); ffStrbufEnsureEndsWithC(&platform->homeDir, '/'); } CoTaskMemFree(pPath); } static void getCacheDir(FFPlatform* platform) { PWSTR pPath = NULL; if(SUCCEEDED(SHGetKnownFolderPath(&FOLDERID_LocalAppData, KF_FLAG_DEFAULT, NULL, &pPath))) { ffStrbufSetWS(&platform->cacheDir, pPath); ffStrbufReplaceAllC(&platform->cacheDir, '\\', '/'); ffStrbufEnsureEndsWithC(&platform->cacheDir, '/'); } else { ffStrbufAppend(&platform->cacheDir, &platform->homeDir); ffStrbufAppendS(&platform->cacheDir, "AppData/Local/"); } CoTaskMemFree(pPath); } static void platformPathAddKnownFolder(FFlist* dirs, REFKNOWNFOLDERID folderId) { PWSTR pPath = NULL; if (SUCCEEDED(SHGetKnownFolderPath(folderId, KF_FLAG_DEFAULT, NULL, &pPath))) { FF_STRBUF_AUTO_DESTROY buffer = ffStrbufCreateWS(pPath); CoTaskMemFree(pPath); ffStrbufReplaceAllC(&buffer, '\\', '/'); ffStrbufEnsureEndsWithC(&buffer, '/'); if (!ffListContains(dirs, &buffer, (void*) ffStrbufEqual)) ffStrbufInitMove((FFstrbuf*) ffListAdd(dirs), &buffer); } } static void platformPathAddEnvSuffix(FFlist* dirs, const char* env, const char* suffix) { const char* value = getenv(env); if(!ffStrSet(value)) return; FF_STRBUF_AUTO_DESTROY buffer = ffStrbufCreateA(64); ffStrbufAppendS(&buffer, value); ffStrbufReplaceAllC(&buffer, '\\', '/'); ffStrbufEnsureEndsWithC(&buffer, '/'); if (suffix) { ffStrbufAppendS(&buffer, suffix); ffStrbufEnsureEndsWithC(&buffer, '/'); } if (ffPathExists(buffer.chars, FF_PATHTYPE_DIRECTORY) && !ffListContains(dirs, &buffer, (void*) ffStrbufEqual)) ffStrbufInitMove((FFstrbuf*) ffListAdd(dirs), &buffer); } static void getConfigDirs(FFPlatform* platform) { if(getenv("MSYSTEM")) { // We are in MSYS2 / Git Bash platformPathAddEnvSuffix(&platform->configDirs, "HOME", ".config/"); platformPathAddEnvSuffix(&platform->configDirs, "HOME", NULL); platformPathAddEnvSuffix(&platform->configDirs, "MINGW_PREFIX", "etc"); } ffPlatformPathAddHome(&platform->configDirs, platform, ".config/"); platformPathAddKnownFolder(&platform->configDirs, &FOLDERID_ProgramData); platformPathAddKnownFolder(&platform->configDirs, &FOLDERID_RoamingAppData); platformPathAddKnownFolder(&platform->configDirs, &FOLDERID_LocalAppData); ffPlatformPathAddHome(&platform->configDirs, platform, ""); } static void getDataDirs(FFPlatform* platform) { if(getenv("MSYSTEM") && getenv("HOME")) { // We are in MSYS2 / Git Bash platformPathAddEnvSuffix(&platform->dataDirs, "HOME", ".local/share/"); platformPathAddEnvSuffix(&platform->dataDirs, "HOME", NULL); platformPathAddEnvSuffix(&platform->dataDirs, "MINGW_PREFIX", "share"); } ffPlatformPathAddHome(&platform->dataDirs, platform, ".local/share/"); platformPathAddKnownFolder(&platform->dataDirs, &FOLDERID_ProgramData); platformPathAddKnownFolder(&platform->dataDirs, &FOLDERID_RoamingAppData); platformPathAddKnownFolder(&platform->dataDirs, &FOLDERID_LocalAppData); ffPlatformPathAddHome(&platform->dataDirs, platform, ""); } static void getUserName(FFPlatform* platform) { wchar_t buffer[256]; DWORD size = ARRAY_SIZE(buffer); if (GetUserNameExW(NameDisplay, buffer, &size)) ffStrbufSetWS(&platform->fullUserName, buffer); size = ARRAY_SIZE(buffer); if (GetUserNameW(buffer, &size)) // GetUserNameExW(10002)? ffStrbufSetWS(&platform->userName, buffer); else ffStrbufSetS(&platform->userName, getenv("USERNAME")); alignas(TOKEN_USER) char buf[SECURITY_MAX_SID_SIZE + sizeof(TOKEN_USER)]; if (NT_SUCCESS(NtQueryInformationToken(NtCurrentProcessToken(), TokenUser, buf, sizeof(buf), &size))) { TOKEN_USER* tokenUser = (TOKEN_USER*) buf; UNICODE_STRING sidString = { .Buffer = buffer, .Length = 0, .MaximumLength = sizeof(buffer) }; if (NT_SUCCESS(RtlConvertSidToUnicodeString(&sidString, tokenUser->User.Sid, FALSE))) ffStrbufSetNWS(&platform->sid, sidString.Length / sizeof(wchar_t), sidString.Buffer); } } static void getHostName(FFPlatform* platform) { wchar_t buffer[256]; DWORD len = ARRAY_SIZE(buffer); if (GetComputerNameExW(ComputerNameDnsHostname, buffer, &len) && len > 0) ffStrbufSetNWS(&platform->hostName, len, buffer); else { len = ARRAY_SIZE(buffer); if (GetComputerNameExW(ComputerNameNetBIOS, buffer, &len) && len > 0) ffStrbufSetNWS(&platform->hostName, len, buffer); } } static void getUserShell(FFPlatform* platform) { // Works in MSYS2 const char* userShell = getenv("SHELL"); if (userShell) { ffStrbufAppendS(&platform->userShell, userShell); ffStrbufReplaceAllC(&platform->userShell, '\\', '/'); } } static const char* detectWine(void) { const char * __cdecl wine_get_version(void); void* hntdll = ffLibraryGetModule(L"ntdll.dll"); if (!hntdll) return NULL; FF_LIBRARY_LOAD_SYMBOL_LAZY(hntdll, wine_get_version); if (!ffwine_get_version) return NULL; return ffwine_get_version(); } static void getSystemReleaseAndVersion(FFPlatformSysinfo* info) { FF_AUTO_CLOSE_FD HANDLE hKey = NULL; if(!ffRegOpenKeyForRead(HKEY_LOCAL_MACHINE, L"SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion", &hKey, NULL)) return; uint32_t ubr = 0; ffRegReadValues(hKey, 2, (FFRegValueArg[]) { FF_ARG(ubr, L"UBR"), FF_ARG(info->version, L"BuildLabEx"), }, NULL); ffStrbufSetF(&info->release, "%u.%u.%u.%u", (unsigned) SharedUserData->NtMajorVersion, (unsigned) SharedUserData->NtMinorVersion, (unsigned) SharedUserData->NtBuildNumber, (unsigned) ubr); const char* wineVersion = detectWine(); if (wineVersion) ffStrbufSetF(&info->name, "Wine_%s", wineVersion); else ffStrbufSetStatic(&info->name, "WIN32_NT"); } static void getSystemPageSize(FFPlatformSysinfo* info) { SYSTEM_BASIC_INFORMATION sbi; if (NT_SUCCESS(NtQuerySystemInformation(SystemBasicInformation, &sbi, sizeof(sbi), NULL))) info->pageSize = sbi.PhysicalPageSize; else info->pageSize = 4096; } static void getSystemArchitecture(FFPlatformSysinfo* info) { SYSTEM_PROCESSOR_INFORMATION spi; if (NT_SUCCESS(NtQuerySystemInformation(SystemProcessorInformation, &spi, sizeof(spi), NULL))) { switch (spi.ProcessorArchitecture) { case PROCESSOR_ARCHITECTURE_AMD64: ffStrbufSetStatic(&info->architecture, "x86_64"); break; case PROCESSOR_ARCHITECTURE_IA64: ffStrbufSetStatic(&info->architecture, "ia64"); break; case PROCESSOR_ARCHITECTURE_INTEL: switch (spi.ProcessorLevel) { case 4: ffStrbufSetStatic(&info->architecture, "i486"); break; case 5: ffStrbufSetStatic(&info->architecture, "i586"); break; case 6: ffStrbufSetStatic(&info->architecture, "i686"); break; default: ffStrbufSetStatic(&info->architecture, "i386"); break; } break; case PROCESSOR_ARCHITECTURE_ARM64: ffStrbufSetStatic(&info->architecture, "aarch64"); break; case PROCESSOR_ARCHITECTURE_ARM: ffStrbufSetStatic(&info->architecture, "arm"); break; case PROCESSOR_ARCHITECTURE_PPC: ffStrbufSetStatic(&info->architecture, "ppc"); break; case PROCESSOR_ARCHITECTURE_MIPS: ffStrbufSetStatic(&info->architecture, "mips"); break; case PROCESSOR_ARCHITECTURE_ALPHA: ffStrbufSetStatic(&info->architecture, "alpha"); break; case PROCESSOR_ARCHITECTURE_ALPHA64: ffStrbufSetStatic(&info->architecture, "alpha64"); break; case PROCESSOR_ARCHITECTURE_UNKNOWN: default: ffStrbufSetStatic(&info->architecture, "unknown"); break; } } } static void getCwd(FFPlatform* platform) { PCURDIR cwd = &ffGetPeb()->ProcessParameters->CurrentDirectory; ffStrbufSetNWS(&platform->cwd, cwd->DosPath.Length / sizeof(WCHAR), cwd->DosPath.Buffer); ffStrbufReplaceAllC(&platform->cwd, '\\', '/'); ffStrbufEnsureEndsWithC(&platform->cwd, '/'); } void ffPlatformInitImpl(FFPlatform* platform) { platform->pid = (uint32_t) (uintptr_t) ffGetTeb()->ClientId.UniqueProcess; getExePath(platform); getCwd(platform); getHomeDir(platform); getCacheDir(platform); getConfigDirs(platform); getDataDirs(platform); getUserName(platform); getHostName(platform); getUserShell(platform); getSystemReleaseAndVersion(&platform->sysinfo); getSystemArchitecture(&platform->sysinfo); getSystemPageSize(&platform->sysinfo); } ================================================ FILE: src/common/impl/FFlist.c ================================================ #include "common/FFlist.h" #include #include void* ffListAdd(FFlist* list) { if(list->length == list->capacity) ffListReserve(list, list->capacity == 0 ? FF_LIST_DEFAULT_ALLOC : list->capacity * 2); ++list->length; return ffListGet(list, list->length - 1); } bool ffListShift(FFlist* list, void* result) { if(list->length == 0) return false; memcpy(result, list->data, list->elementSize); memmove(list->data, list->data + list->elementSize, (size_t) list->elementSize * (list->length - 1)); --list->length; return true; } bool ffListPop(FFlist* list, void* result) { if(list->length == 0) return false; memcpy(result, ffListGet(list, list->length - 1), list->elementSize); --list->length; return true; } ================================================ FILE: src/common/impl/FFstrbuf.c ================================================ #include "common/FFstrbuf.h" #include "common/mallocHelper.h" #include #include #include char* CHAR_NULL_PTR = ""; void ffStrbufInitA(FFstrbuf* strbuf, uint32_t allocate) { strbuf->allocated = allocate; if(strbuf->allocated > 0) strbuf->chars = (char*) malloc(sizeof(char) * strbuf->allocated); //This will set the length to zero and the null byte. ffStrbufClear(strbuf); } void ffStrbufInitVF(FFstrbuf* strbuf, const char* format, va_list arguments) { assert(format != NULL); char* buffer = NULL; int len = vasprintf(&buffer, format, arguments); assert(len >= 0); ffStrbufInitMoveNS(strbuf, (uint32_t)len, buffer); } // Takes ownership of `heapStr`. The caller must not free `heapStr` after calling this // function; the memory will be managed and freed via the associated FFstrbuf. void ffStrbufInitMoveNS(FFstrbuf* strbuf, uint32_t length, char* heapStr) { assert(heapStr != NULL); strbuf->length = length; size_t allocSize = ffMallocUsableSize(heapStr); if (allocSize == 0) allocSize = length + 1; else if (allocSize > UINT32_MAX) allocSize = UINT32_MAX; strbuf->allocated = (uint32_t) allocSize; strbuf->chars = heapStr; } void ffStrbufEnsureFree(FFstrbuf* strbuf, uint32_t free) { if(ffStrbufGetFree(strbuf) >= free && !(strbuf->allocated == 0 && strbuf->length > 0)) return; uint32_t allocate = strbuf->allocated; if(allocate < FASTFETCH_STRBUF_DEFAULT_ALLOC) allocate = FASTFETCH_STRBUF_DEFAULT_ALLOC; while((strbuf->length + free + 1) > allocate) // + 1 for the null byte allocate *= 2; if(strbuf->allocated == 0) { char* newbuf = malloc(sizeof(*strbuf->chars) * allocate); if(strbuf->length == 0) *newbuf = '\0'; else memcpy(newbuf, strbuf->chars, strbuf->length + 1); strbuf->chars = newbuf; } else strbuf->chars = realloc(strbuf->chars, sizeof(*strbuf->chars) * allocate); strbuf->allocated = allocate; } // Ensure that at least `free` bytes are available in the buffer besides the current length // for an empty buffer, free + 1 length memory will be allocated(+1 for the NUL) void ffStrbufEnsureFixedLengthFree(FFstrbuf* strbuf, uint32_t free) { uint32_t oldFree = ffStrbufGetFree(strbuf); if (oldFree >= free && !(strbuf->allocated == 0 && strbuf->length > 0)) return; uint32_t newCap = strbuf->allocated + (free - oldFree); if(strbuf->allocated == 0) { newCap += strbuf->length + 1; char* newbuf = malloc(sizeof(*strbuf->chars) * newCap); if(strbuf->length == 0) *newbuf = '\0'; else memcpy(newbuf, strbuf->chars, strbuf->length + 1); strbuf->chars = newbuf; } else strbuf->chars = realloc(strbuf->chars, sizeof(*strbuf->chars) * newCap); strbuf->allocated = newCap; } void ffStrbufClear(FFstrbuf* strbuf) { assert(strbuf != NULL); if(strbuf->allocated == 0) strbuf->chars = CHAR_NULL_PTR; else strbuf->chars[0] = '\0'; strbuf->length = 0; } void ffStrbufAppendC(FFstrbuf* strbuf, char c) { ffStrbufEnsureFree(strbuf, 1); strbuf->chars[strbuf->length++] = c; strbuf->chars[strbuf->length] = '\0'; } void ffStrbufAppendNC(FFstrbuf* strbuf, uint32_t num, char c) { if (num == 0) return; ffStrbufEnsureFree(strbuf, num); memset(&strbuf->chars[strbuf->length], c, num); strbuf->length += num; strbuf->chars[strbuf->length] = '\0'; } void ffStrbufAppendNS(FFstrbuf* strbuf, uint32_t length, const char* value) { if(value == NULL || length == 0) return; ffStrbufEnsureFree(strbuf, length); memcpy(&strbuf->chars[strbuf->length], value, length); strbuf->length += length; strbuf->chars[strbuf->length] = '\0'; } void ffStrbufAppendTransformS(FFstrbuf* strbuf, const char* value, int(*transformFunc)(int)) { if(value == NULL) return; //Ensure capacity > 0 or the modification below will fail uint32_t length = (uint32_t) strlen(value); if(length == 0) return; ffStrbufEnsureFree(strbuf, length); for(uint32_t i = 0; value[i] != '\0'; i++) { strbuf->chars[strbuf->length++] = (char) transformFunc(value[i]); } strbuf->chars[strbuf->length] = '\0'; } void ffStrbufAppendVF(FFstrbuf* strbuf, const char* format, va_list arguments) { assert(format != NULL); va_list copy; va_copy(copy, arguments); uint32_t free = ffStrbufGetFree(strbuf); int written = vsnprintf(strbuf->chars + strbuf->length, strbuf->allocated > 0 ? free + 1 : 0, format, arguments); if(written > 0 && (uint32_t) written > free) { ffStrbufEnsureFree(strbuf, (uint32_t) written); written = vsnprintf(strbuf->chars + strbuf->length, (uint32_t) written + 1, format, copy); } va_end(copy); if(written > 0) strbuf->length += (uint32_t) written; } const char* ffStrbufAppendSUntilC(FFstrbuf* strbuf, const char* value, char until) { if(value == NULL) return NULL; const char* end = strchr(value, until); if(end == NULL) ffStrbufAppendS(strbuf, value); else ffStrbufAppendNS(strbuf, (uint32_t) (end - value), value); return end; } void ffStrbufSetF(FFstrbuf* strbuf, const char* format, ...) { assert(format != NULL); va_list arguments; va_start(arguments, format); if(strbuf->allocated == 0) { ffStrbufInitVF(strbuf, format, arguments); va_end(arguments); return; } ffStrbufClear(strbuf); ffStrbufAppendVF(strbuf, format, arguments); va_end(arguments); } void ffStrbufAppendF(FFstrbuf* strbuf, const char* format, ...) { assert(format != NULL); va_list arguments; va_start(arguments, format); ffStrbufAppendVF(strbuf, format, arguments); va_end(arguments); } void ffStrbufPrependNS(FFstrbuf* strbuf, uint32_t length, const char* value) { if(value == NULL || length == 0) return; ffStrbufEnsureFree(strbuf, length); memmove(strbuf->chars + length, strbuf->chars, strbuf->length + 1); // + 1 for the null byte memcpy(strbuf->chars, value, length); strbuf->length += length; } void ffStrbufPrependC(FFstrbuf* strbuf, char c) { ffStrbufEnsureFree(strbuf, 1); memmove(strbuf->chars + 1, strbuf->chars, strbuf->length + 1); // + 1 for the null byte strbuf->chars[0] = c; strbuf->length += 1; } void ffStrbufSetNS(FFstrbuf* strbuf, uint32_t length, const char* value) { assert(strbuf != NULL); if (length == 0) { ffStrbufClear(strbuf); return; } assert(value != NULL); if (strbuf->allocated < length + 1) { if (strbuf->allocated > 0) free(strbuf->chars); strbuf->allocated = length + 1; strbuf->chars = malloc(sizeof(char) * strbuf->allocated); } memcpy(strbuf->chars, value, length); strbuf->length = length; strbuf->chars[length] = '\0'; } void ffStrbufSet(FFstrbuf* strbuf, const FFstrbuf* value) { assert(value && value != strbuf); if (value->length == 0) { ffStrbufClear(strbuf); return; } if (value->allocated == 0) // static string { if (strbuf->allocated != 0) { free(strbuf->chars); strbuf->allocated = 0; } strbuf->chars = value->chars; strbuf->length = value->length; return; } ffStrbufSetNS(strbuf, value->length, value->chars); } void ffStrbufTrimLeft(FFstrbuf* strbuf, char c) { if(strbuf->length == 0) return; uint32_t index = 0; while(index < strbuf->length && strbuf->chars[index] == c) ++index; if(index == 0) return; if(strbuf->allocated == 0) { //static string strbuf->length -= index; strbuf->chars += index; return; } memmove(strbuf->chars, strbuf->chars + index, strbuf->length - index); strbuf->length -= index; strbuf->chars[strbuf->length] = '\0'; } void ffStrbufTrimRight(FFstrbuf* strbuf, char c) { if (strbuf->length == 0) return; if (!ffStrbufEndsWithC(strbuf, c)) return; do --strbuf->length; while (ffStrbufEndsWithC(strbuf, c)); if (strbuf->allocated == 0) { //static string ffStrbufInitNS(strbuf, strbuf->length, strbuf->chars); return; } strbuf->chars[strbuf->length] = '\0'; } void ffStrbufTrimLeftSpace(FFstrbuf* strbuf) { if(strbuf->length == 0) return; uint32_t index = 0; while(index < strbuf->length && isspace(strbuf->chars[index])) ++index; if(index == 0) return; if(strbuf->allocated == 0) { //static string strbuf->length -= index; strbuf->chars += index; return; } memmove(strbuf->chars, strbuf->chars + index, strbuf->length - index); strbuf->length -= index; strbuf->chars[strbuf->length] = '\0'; } void ffStrbufTrimRightSpace(FFstrbuf* strbuf) { if (strbuf->length == 0) return; if (!ffStrbufEndsWithFn(strbuf, isspace)) return; do --strbuf->length; while (ffStrbufEndsWithFn(strbuf, isspace)); if (strbuf->allocated == 0) { //static string ffStrbufInitNS(strbuf, strbuf->length, strbuf->chars); return; } strbuf->chars[strbuf->length] = '\0'; } bool ffStrbufRemoveSubstr(FFstrbuf* strbuf, uint32_t startIndex, uint32_t endIndex) { if(startIndex > strbuf->length || startIndex >= endIndex) return false; if(endIndex > strbuf->length) { ffStrbufSubstrBefore(strbuf, startIndex); return true; } ffStrbufEnsureFree(strbuf, 0); memmove(strbuf->chars + startIndex, strbuf->chars + endIndex, strbuf->length - endIndex); strbuf->length -= (endIndex - startIndex); strbuf->chars[strbuf->length] = '\0'; return true; } void ffStrbufRemoveS(FFstrbuf* strbuf, const char* str) { uint32_t stringLength = (uint32_t) strlen(str); for(uint32_t i = ffStrbufNextIndexS(strbuf, 0, str); i < strbuf->length; i = ffStrbufNextIndexS(strbuf, i, str)) ffStrbufRemoveSubstr(strbuf, i, i + stringLength); } void ffStrbufRemoveStrings(FFstrbuf* strbuf, uint32_t numStrings, const char* strings[]) { for(uint32_t i = 0; i < numStrings; i++) ffStrbufRemoveS(strbuf, strings[i]); } uint32_t ffStrbufNextIndexC(const FFstrbuf* strbuf, uint32_t start, char c) { assert(start <= strbuf->length); const char* ptr = (const char*)memchr(strbuf->chars + start, c, strbuf->length - start); return ptr ? (uint32_t)(ptr - strbuf->chars) : strbuf->length; } uint32_t ffStrbufNextIndexS(const FFstrbuf* strbuf, uint32_t start, const char* str) { assert(start <= strbuf->length); const char* ptr = strstr(strbuf->chars + start, str); return ptr ? (uint32_t)(ptr - strbuf->chars) : strbuf->length; } uint32_t ffStrbufPreviousIndexC(const FFstrbuf* strbuf, uint32_t start, char c) { assert(start <= strbuf->length); //We need to loop one higher than the actual index, because uint32_t is guaranteed to be >= 0, so this statement would always be true for(uint32_t i = start + 1; i > 0; i--) { if(strbuf->chars[i - 1] == c) return i - 1; } return strbuf->length; } void ffStrbufReplaceAllC(FFstrbuf* strbuf, char find, char replace) { if (strbuf->length == 0) return; ffStrbufEnsureFree(strbuf, 0); for ( char *current_pos = memchr(strbuf->chars, find, strbuf->length); current_pos; current_pos = memchr( current_pos + 1, find, strbuf->length - (uint32_t)(current_pos + 1 - strbuf->chars) ) ) *current_pos = replace; } bool ffStrbufSubstrBefore(FFstrbuf* strbuf, uint32_t index) { if(strbuf->length <= index) return false; if(strbuf->allocated == 0) { //static string if (index < strbuf->length) ffStrbufInitNS(strbuf, index, strbuf->chars); return true; } strbuf->length = index; strbuf->chars[strbuf->length] = '\0'; return true; } bool ffStrbufSubstrAfter(FFstrbuf* strbuf, uint32_t index) { if(index >= strbuf->length) { ffStrbufClear(strbuf); return true; } if(strbuf->allocated == 0) { //static string strbuf->length -= index + 1; strbuf->chars += index + 1; return true; } memmove(strbuf->chars, strbuf->chars + index + 1, strbuf->length - index - 1); strbuf->length -= (index + 1); strbuf->chars[strbuf->length] = '\0'; return true; } bool ffStrbufSubstrAfterFirstC(FFstrbuf* strbuf, char c) { uint32_t index = ffStrbufFirstIndexC(strbuf, c); if(index >= strbuf->length) return false; ffStrbufSubstrAfter(strbuf, index); return true; } bool ffStrbufSubstrAfterFirstS(FFstrbuf* strbuf, const char* str) { if(*str == '\0') return false; uint32_t index = ffStrbufFirstIndexS(strbuf, str) + (uint32_t) strlen(str) - 1; // -1, because firstIndexS is already pointing to str[0], we want to add only the remaining length if(index >= strbuf->length) return false; ffStrbufSubstrAfter(strbuf, index); return true; } bool ffStrbufSubstrAfterLastC(FFstrbuf* strbuf, char c) { uint32_t index = ffStrbufLastIndexC(strbuf, c); if(index >= strbuf->length) return false; ffStrbufSubstrAfter(strbuf, index); return true; } bool ffStrbufSubstr(FFstrbuf* strbuf, uint32_t start, uint32_t end) { if (__builtin_expect(start >= end, false)) { ffStrbufClear(strbuf); return false; } if (__builtin_expect(start == 0, false)) return ffStrbufSubstrBefore(strbuf, end); if (__builtin_expect(end >= strbuf->length, false)) return ffStrbufSubstrAfter(strbuf, start - 1); uint32_t len = end - start; ffStrbufEnsureFixedLengthFree(strbuf, len); // In case of static string memmove(strbuf->chars, strbuf->chars + start, len); strbuf->length = len; strbuf->chars[len] = '\0'; return true; } uint32_t ffStrbufCountC(const FFstrbuf* strbuf, char c) { uint32_t result = 0; for(uint32_t i = 0; i < strbuf->length; i++) { if(strbuf->chars[i] == c) result++; } return result; } bool ffStrbufRemoveIgnCaseEndS(FFstrbuf* strbuf, const char* end) { uint32_t endLength = (uint32_t) strlen(end); if(ffStrbufEndsWithIgnCaseNS(strbuf, endLength, end)) { ffStrbufSubstrBefore(strbuf, strbuf->length - endLength); return true; } return false; } bool ffStrbufEnsureEndsWithC(FFstrbuf* strbuf, char c) { if(ffStrbufEndsWithC(strbuf, c)) return false; ffStrbufAppendC(strbuf, c); return true; } void ffStrbufWriteTo(const FFstrbuf* strbuf, FILE* file) { fwrite(strbuf->chars, sizeof(*strbuf->chars), strbuf->length, file); } void ffStrbufPutTo(const FFstrbuf* strbuf, FILE* file) { ffStrbufWriteTo(strbuf, file); fputc('\n', file); } double ffStrbufToDouble(const FFstrbuf* strbuf, double defaultValue) { char* str_end; double result = strtod(strbuf->chars, &str_end); return str_end == strbuf->chars ? defaultValue : result; } uint64_t ffStrbufToUInt(const FFstrbuf* strbuf, uint64_t defaultValue) { char* str_end; unsigned long long result = strtoull(strbuf->chars, &str_end, 10); return str_end == strbuf->chars ? defaultValue : (uint64_t)result; } int64_t ffStrbufToSInt(const FFstrbuf* strbuf, int64_t defaultValue) { char* str_end; long long result = strtoll(strbuf->chars, &str_end, 10); return str_end == strbuf->chars ? defaultValue : (int64_t)result; } void ffStrbufAppendSInt(FFstrbuf* strbuf, int64_t value) { ffStrbufEnsureFree(strbuf, 21); // Required by yyjson_write_number char* start = strbuf->chars + strbuf->length; yyjson_val val = {}; unsafe_yyjson_set_sint(&val, value); char* end = yyjson_write_number(&val, start); assert(end != NULL); strbuf->length += (uint32_t)(end - start); } void ffStrbufAppendUInt(FFstrbuf* strbuf, uint64_t value) { ffStrbufEnsureFree(strbuf, 21); // Required by yyjson_write_number char* start = strbuf->chars + strbuf->length; yyjson_val val = {}; unsafe_yyjson_set_uint(&val, value); char* end = yyjson_write_number(&val, start); assert(end != NULL); strbuf->length += (uint32_t)(end - start); } void ffStrbufAppendDouble(FFstrbuf* strbuf, double value, int8_t precision, bool trailingZeros) { assert(precision <= 15); // yyjson_write_number supports up to 15 digits after the decimal point ffStrbufEnsureFree(strbuf, 40); // Required by yyjson_write_number char* start = strbuf->chars + strbuf->length; if (precision == 0) value = round(value); yyjson_val val = {}; unsafe_yyjson_set_double(&val, value); if (precision > 0) unsafe_yyjson_set_fp_to_fixed(&val, precision); // Write at most digits after the decimal point; doesn't append trailing zeros char* end = yyjson_write_number(&val, start); assert(end > start); strbuf->length += (uint32_t)(end - start); if (__builtin_expect(value > 1e21 || value < -1e21, false)) { // If the value is too large, yyjson_write_number will write it in scientific notation return; } if (trailingZeros) { if (precision > 1) { for (char* p = end - 1; *p != '.' && p > start; --p) --precision; if (precision > 0) ffStrbufAppendNC(strbuf, (uint32_t) precision, '0'); } else if (precision == 0 || (precision < 0 && end[-1] == '0')) { goto removeDecimalPoint; } } else { if (end[-1] == '0') { removeDecimalPoint: // yyjson always appends ".0" to make it a float point number. We need to remove it strbuf->length -= 2; strbuf->chars[strbuf->length] = '\0'; } } } void ffStrbufUpperCase(FFstrbuf* strbuf) { for (uint32_t i = 0; i < strbuf->length; ++i) strbuf->chars[i] = (char) toupper(strbuf->chars[i]); } void ffStrbufLowerCase(FFstrbuf* strbuf) { for (uint32_t i = 0; i < strbuf->length; ++i) strbuf->chars[i] = (char) tolower(strbuf->chars[i]); } void ffStrbufInsertNC(FFstrbuf* strbuf, uint32_t index, uint32_t num, char c) { if(num == 0) return; if (index >= strbuf->length) index = strbuf->length; ffStrbufEnsureFree(strbuf, num); memmove(strbuf->chars + index + num, strbuf->chars + index, strbuf->length - index + 1); memset(&strbuf->chars[index], c, num); strbuf->length += num; } bool ffStrbufGetdelim(char** lineptr, size_t* n, char delimiter, FFstrbuf* buffer) { assert(lineptr && n && buffer); assert(buffer->allocated > 0 || (buffer->allocated == 0 && buffer->length == 0)); assert(!*lineptr || (*lineptr >= buffer->chars && *lineptr <= buffer->chars + buffer->length)); const char* pBufferEnd = buffer->chars + buffer->length; if (!*lineptr) *lineptr = buffer->chars; else { *lineptr += *n; if (*lineptr >= pBufferEnd) // non-empty last line return false; **lineptr = delimiter; ++*lineptr; } if (*lineptr >= pBufferEnd) // empty last line return false; size_t remaining = (size_t) (pBufferEnd - *lineptr); char* ending = memchr(*lineptr, delimiter, remaining); if (ending) { *n = (size_t) (ending - *lineptr); *ending = '\0'; } else *n = remaining; return true; } void ffStrbufGetdelimRestore(char** lineptr, size_t* n, char delimiter, FFstrbuf* buffer) { assert(buffer && lineptr && n); assert(buffer->allocated > 0 || (buffer->allocated == 0 && buffer->length == 0)); assert(!*lineptr || (*lineptr >= buffer->chars && *lineptr <= buffer->chars + buffer->length)); if (!*lineptr) return; *lineptr += *n; if (*lineptr < buffer->chars + buffer->length) **lineptr = delimiter; } bool ffStrbufRemoveDupWhitespaces(FFstrbuf* strbuf) { if (strbuf->allocated == 0) return false; // Doesn't work with static strings bool changed = false; for (uint32_t i = 0; i < strbuf->length; i++) { if (strbuf->chars[i] != ' ') continue; i++; uint32_t j = i; for (; j < strbuf->length && strbuf->chars[j] == ' '; j++); if (j == i) continue; memmove(&strbuf->chars[i], &strbuf->chars[j], strbuf->length - j + 1); strbuf->length -= j - i; changed = true; } return changed; } /// @brief Check if a separated string (comp) contains a substring (strbuf). /// @param strbuf The substring to check. /// @param compLength The length of the separated string to check. /// @param comp The separated string to check. /// @param separator The separator character. bool ffStrbufMatchSeparatedNS(const FFstrbuf* strbuf, uint32_t compLength, const char* comp, char separator) { if (strbuf->length == 0) return true; if (compLength == 0) return false; for (const char* p = comp; p < comp + compLength;) { const char* colon = memchr(p, separator, compLength); if (colon == NULL) return strcmp(strbuf->chars, p) == 0; uint32_t substrLength = (uint32_t) (colon - p); if (strbuf->length == substrLength && memcmp(strbuf->chars, p, substrLength) == 0) return true; p = colon + 1; } return false; } /// @brief Case insensitive version of ffStrbufMatchSeparatedNS. bool ffStrbufMatchSeparatedIgnCaseNS(const FFstrbuf* strbuf, uint32_t compLength, const char* comp, char separator) { if (strbuf->length == 0) return true; if (compLength == 0) return false; for (const char* p = comp; p < comp + compLength;) { const char* colon = memchr(p, separator, compLength); if (colon == NULL) return strcasecmp(strbuf->chars, p) == 0; uint32_t substrLength = (uint32_t) (colon - p); if (strbuf->length == substrLength && strncasecmp(strbuf->chars, p, substrLength) == 0) return true; p = colon + 1; } return false; } int ffStrbufAppendUtf32CodePoint(FFstrbuf* strbuf, uint32_t codepoint) { if (codepoint <= 0x7F) { ffStrbufAppendC(strbuf, (char)codepoint); return 1; } else if (codepoint <= 0x7FF) { ffStrbufAppendNS(strbuf, 2, (char[]){ (char) (0xC0 | (codepoint >> 6)), (char) (0x80 | (codepoint & 0x3F)) }); return 2; } else if (codepoint <= 0xFFFF) { ffStrbufAppendNS(strbuf, 3, (char[]){ (char) (0xE0 | (codepoint >> 12)), (char) (0x80 | ((codepoint >> 6) & 0x3F)), (char) (0x80 | (codepoint & 0x3F)) }); return 3; } else if (codepoint <= 0x10FFFF) { ffStrbufAppendNS(strbuf, 4, (char[]){ (char) (0xF0 | (codepoint >> 18)), (char) (0x80 | ((codepoint >> 12) & 0x3F)), (char) (0x80 | ((codepoint >> 6) & 0x3F)), (char) (0x80 | (codepoint & 0x3F)) }); return 4; } ffStrbufAppendS(strbuf, "�"); // U+FFFD REPLACEMENT CHARACTER return 1; } /// @brief Check if a separated string (strbuf) contains a substring (comp). /// @param strbuf The separated to check. /// @param compLength The length of the separated string to check. /// @param comp The substring to check. /// @param separator The separator character. bool ffStrbufSeparatedContainNS(const FFstrbuf* strbuf, uint32_t compLength, const char* comp, char separator) { uint32_t startIndex = 0; while(startIndex < strbuf->length) { uint32_t colonIndex = ffStrbufNextIndexC(strbuf, startIndex, separator); uint32_t folderLength = colonIndex - startIndex; if (folderLength == compLength && memcmp(strbuf->chars + startIndex, comp, compLength) == 0) return true; startIndex = colonIndex + 1; } return false; } bool ffStrbufSeparatedContainIgnCaseNS(const FFstrbuf* strbuf, uint32_t compLength, const char* comp, char separator) { uint32_t startIndex = 0; while(startIndex < strbuf->length) { uint32_t colonIndex = ffStrbufNextIndexC(strbuf, startIndex, separator); uint32_t folderLength = colonIndex - startIndex; if (folderLength == compLength && strncasecmp(strbuf->chars + startIndex, comp, compLength) == 0) return true; startIndex = colonIndex + 1; } return false; } ================================================ FILE: src/common/impl/base64.c ================================================ #include "common/base64.h" // https://github.com/kostya/benchmarks/blob/master/base64/test-nolib.c#L145 void ffBase64EncodeRaw(uint32_t size, const char *str, uint32_t *out_size, char *output) { static const char chars[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; char *out = output; const char *ends = str + (size - size % 3); while (str != ends) { uint32_t n = __builtin_bswap32(*(uint32_t *)str); *out++ = chars[(n >> 26) & 63]; *out++ = chars[(n >> 20) & 63]; *out++ = chars[(n >> 14) & 63]; *out++ = chars[(n >> 8) & 63]; str += 3; } if (size % 3 == 1) { uint64_t n = (uint64_t)*str << 16; *out++ = chars[(n >> 18) & 63]; *out++ = chars[(n >> 12) & 63]; *out++ = '='; *out++ = '='; } else if (size % 3 == 2) { uint64_t n = (uint64_t)*str++ << 16; n |= (uint64_t)*str << 8; *out++ = chars[(n >> 18) & 63]; *out++ = chars[(n >> 12) & 63]; *out++ = chars[(n >> 6) & 63]; *out++ = '='; } *out = '\0'; *out_size = (uint32_t)(out - output); } static uint8_t decode_table[256]; void init_decode_table() { uint8_t ch = 0; do { int32_t code = -1; if (ch >= 'A' && ch <= 'Z') code = ch - 0x41; if (ch >= 'a' && ch <= 'z') code = ch - 0x47; if (ch >= '0' && ch <= '9') code = ch + 0x04; if (ch == '+' || ch == '-') code = 0x3E; if (ch == '/' || ch == '_') code = 0x3F; decode_table[ch] = (uint8_t) code; } while (ch++ < 0xFF); } #define next_char(x) uint8_t x = decode_table[(uint8_t) *str++]; bool ffBase64DecodeRaw(uint32_t size, const char *str, uint32_t *out_size, char *output) { if (*(uint64_t*) decode_table == 0) init_decode_table(); char *out = output; while (size > 0 && (str[size - 1] == '\n' || str[size - 1] == '\r' || str[size - 1] == '=')) size--; const char *ends = str + size - 4; while (true) { if (str > ends) break; while (*str == '\n' || *str == '\r') str++; if (str > ends) break; next_char(a); next_char(b); next_char(c); next_char(d); *out++ = (char)(a << 2 | b >> 4); *out++ = (char)(b << 4 | c >> 2); *out++ = (char)(c << 6 | d >> 0); } uint8_t mod = (uint8_t) (ends - str + 4) % 4; if (mod == 2) { next_char(a); next_char(b); *out++ = (char)(a << 2 | b >> 4); } else if (mod == 3) { next_char(a); next_char(b); next_char(c); *out++ = (char)(a << 2 | b >> 4); *out++ = (char)(b << 4 | c >> 2); } *out = '\0'; *out_size = (uint32_t) (out - output); return true; } ================================================ FILE: src/common/impl/binary_apple.c ================================================ #include "common/binary.h" #include "common/io.h" #include "common/stringUtils.h" #include "common/mallocHelper.h" #include #include #include #include #include #include #pragma GCC diagnostic ignored "-Wdeprecated-declarations" // swap_fat_arch // Ref: https://github.com/AlexDenisov/segment_dumper/blob/master/main.c /** * Helper function to read data from a file at a specific offset */ static inline bool readData(FILE *objFile, void *buf, size_t size, off_t offset) { fseek(objFile, offset, SEEK_SET); return fread(buf, 1, size, objFile) == size; } /** * Handles a Mach-O section by extracting strings from the __cstring section * * @param objFile File handle to the Mach-O object file * @param name Section name to check * @param offset Offset of the section in the file * @param size Size of the section * @param cb Callback function to process strings * @param userdata User data for the callback * @param minLength Minimum string length to extract * * @return true to continue processing, false to stop */ static bool handleMachSection(FILE *objFile, const char *name, off_t offset, size_t size, bool (*cb)(const char *str, uint32_t len, void *userdata), void *userdata, uint32_t minLength) { if (!ffStrEquals(name, "__cstring")) return true; FF_AUTO_FREE char* data = (char*) malloc(size); if (!readData(objFile, data, size, offset)) return true; for (size_t off = 0; off < size; ++off) { const char* p = (const char*) data + off; if (*p == '\0') continue; uint32_t len = (uint32_t) strlen(p); if (len < minLength) continue; if (*p >= ' ' && *p <= '~') // Ignore control characters { if (!cb(p, len, userdata)) return false; } off += len; } return true; } /** * Processes a Mach-O header (32-bit or 64-bit) * * This function parses the load commands in a Mach-O header, looking for * LC_SEGMENT or LC_SEGMENT_64 commands that contain the __TEXT segment. * It then processes the sections within that segment to extract strings. * * @param objFile File handle to the Mach-O object file * @param offset Offset of the Mach header in the file * @param is_64 Whether this is a 64-bit Mach-O header * @param cb Callback function to process strings * @param userdata User data for the callback * @param minLength Minimum string length to extract * * @return NULL on success, error message on failure */ static const char* dumpMachHeader(FILE *objFile, off_t offset, bool is_64, bool (*cb)(const char *str, uint32_t len, void *userdata), void *userdata, uint32_t minLength) { uint32_t ncmds; off_t loadCommandsOffset = offset; if (is_64) { struct mach_header_64 header; if (!readData(objFile, &header, sizeof(header), offset)) return "read mach header failed"; ncmds = header.ncmds; loadCommandsOffset += sizeof(header); } else { struct mach_header header; if (!readData(objFile, &header, sizeof(header), offset)) return "read mach header failed"; ncmds = header.ncmds; loadCommandsOffset += sizeof(header); } off_t commandOffset = loadCommandsOffset; struct load_command cmd = {}; for (uint32_t i = 0U; i < ncmds; i++, commandOffset += cmd.cmdsize) { if (!readData(objFile, &cmd, sizeof(cmd), commandOffset)) continue; if (cmd.cmd == LC_SEGMENT_64) { struct segment_command_64 segment; if (!readData(objFile, &segment, sizeof(segment), commandOffset)) continue; if (!ffStrEquals(segment.segname, "__TEXT")) continue; for (uint32_t j = 0U; j < segment.nsects; j++) { struct section_64 section; if (!readData(objFile, §ion, sizeof(section), (off_t) ((size_t) commandOffset + sizeof(segment) + j * sizeof(section)))) continue; if (!handleMachSection(objFile, section.sectname, section.offset, section.size, cb, userdata, minLength)) return NULL; } } else if (cmd.cmd == LC_SEGMENT) { struct segment_command segment; if (!readData(objFile, &segment, sizeof(segment), commandOffset)) continue; if (!ffStrEquals(segment.segname, "__TEXT")) continue; for (uint32_t j = 0; j < segment.nsects; j++) { struct section section; if (!readData(objFile, §ion, sizeof(section), (off_t) ((size_t) commandOffset + sizeof(segment) + j * sizeof(section)))) continue; if (!handleMachSection(objFile, section.sectname, section.offset, section.size, cb, userdata, minLength)) return NULL; } } return NULL; } return NULL; } /** * Processes a Fat binary header (Universal binary) * * This function handles the fat header of a universal binary, which can contain * multiple Mach-O binaries for different architectures. It extracts and processes * each embedded Mach-O file. * * @param objFile File handle to the universal binary * @param cb Callback function to process strings * @param userdata User data for the callback * @param minLength Minimum string length to extract * * @return NULL on success, error message on failure */ static const char* dumpFatHeader(FILE *objFile, bool (*cb)(const char *str, uint32_t len, void *userdata), void *userdata, uint32_t minLength) { struct fat_header header; if (!readData(objFile, &header, sizeof(header), 0)) return "read fat header failed"; bool needSwap = header.magic == FAT_CIGAM || header.magic == FAT_CIGAM_64; if (needSwap) swap_fat_header(&header, NX_UnknownByteOrder); for (uint32_t i = 0U; i < header.nfat_arch; i++) { off_t machHeaderOffset = 0; if (header.magic == FAT_MAGIC) { struct fat_arch arch; if (!readData(objFile, &arch, sizeof(arch), (off_t) (sizeof(header) + i * sizeof(arch)))) continue; if (needSwap) swap_fat_arch(&arch, 1, NX_UnknownByteOrder); machHeaderOffset = (off_t)arch.offset; } else { struct fat_arch_64 arch; if (!readData(objFile, &arch, sizeof(arch), (off_t) (sizeof(header) + i * sizeof(arch)))) continue; if (needSwap) swap_fat_arch_64(&arch, 1, NX_UnknownByteOrder); machHeaderOffset = (off_t)arch.offset; } uint32_t magic; if (!readData(objFile, &magic, sizeof(magic), machHeaderOffset)) continue; if (magic == MH_MAGIC_64 || magic == MH_MAGIC) { dumpMachHeader(objFile, machHeaderOffset, magic == MH_MAGIC_64, cb, userdata, minLength); return NULL; } } return "Unsupported fat header"; } /** * Extracts string literals from a Mach-O (Apple) binary file * * This function supports both single-architecture Mach-O files and * universal binaries (fat binaries) containing multiple architectures. * It locates the __cstring section in the __TEXT segment which contains * the string literals used in the program. */ const char *ffBinaryExtractStrings(const char *machoFile, bool (*cb)(const char *str, uint32_t len, void *userdata), void *userdata, uint32_t minLength) { FF_AUTO_CLOSE_FILE FILE *objFile = fopen(machoFile, "rb"); if (objFile == NULL) return "File could not be opened"; // Read the magic number to determine the type of binary uint32_t magic; if (!readData(objFile, &magic, sizeof(magic), 0)) return "read magic number failed"; // Check for supported formats // MH_CIGAM and MH_CIGAM_64 seem to be no longer used, as `swap_mach_header` is marked as deprecated. // However FAT_CIGAM and FAT_CIGAM_64 are still used (/usr/bin/vim). if (magic != MH_MAGIC && magic != MH_MAGIC_64 && magic != FAT_CIGAM && magic != FAT_CIGAM_64 && magic != FAT_MAGIC && magic != FAT_MAGIC_64) return "Unsupported format or big endian mach-o file"; // Process either a fat binary or a regular Mach-O binary if (magic == FAT_MAGIC || magic == FAT_MAGIC_64 || magic == FAT_CIGAM || magic == FAT_CIGAM_64) return dumpFatHeader(objFile, cb, userdata, minLength); else return dumpMachHeader(objFile, 0, magic == MH_MAGIC_64, cb, userdata, minLength); } ================================================ FILE: src/common/impl/binary_linux.c ================================================ #include "common/binary.h" #if defined(FF_HAVE_ELF) || defined(__sun) || (defined(__FreeBSD__) && !defined(__DragonFly__)) || defined(__OpenBSD__) || defined(__NetBSD__) #include "common/io.h" #include "common/library.h" #include "common/stringUtils.h" #include // #1254 #include /** * Structure to hold dynamically loaded libelf function pointers */ struct FFElfData { FF_LIBRARY_SYMBOL(elf_version) FF_LIBRARY_SYMBOL(elf_begin) FF_LIBRARY_SYMBOL(elf_getshdrstrndx) FF_LIBRARY_SYMBOL(elf_nextscn) FF_LIBRARY_SYMBOL(elf64_getshdr) FF_LIBRARY_SYMBOL(elf32_getshdr) FF_LIBRARY_SYMBOL(elf_getdata) FF_LIBRARY_SYMBOL(elf_strptr) FF_LIBRARY_SYMBOL(elf_end) bool inited; } elfData; /** * Extracts string literals from an ELF (Linux/Unix) binary file * * This function loads the libelf library dynamically, opens the ELF file, * locates the .rodata section (which contains string literals), and * scans it for valid strings. Each string found is passed to the * callback function for processing. * * The function supports both 32-bit and 64-bit ELF formats. */ const char* ffBinaryExtractStrings(const char* elfFile, bool (*cb)(const char* str, uint32_t len, void* userdata), void* userdata, uint32_t minLength) { // Initialize libelf if not already done if (!elfData.inited) { elfData.inited = true; FF_LIBRARY_LOAD_MESSAGE(libelf, "libelf" FF_LIBRARY_EXTENSION, 1); FF_LIBRARY_LOAD_SYMBOL_VAR_MESSAGE(libelf, elfData, elf_version) if (elfData.ffelf_version(EV_CURRENT) == EV_NONE) return "elf_version() failed"; // Load all required libelf functions FF_LIBRARY_LOAD_SYMBOL_VAR_MESSAGE(libelf, elfData, elf_begin) FF_LIBRARY_LOAD_SYMBOL_VAR_MESSAGE(libelf, elfData, elf_getshdrstrndx) FF_LIBRARY_LOAD_SYMBOL_VAR_MESSAGE(libelf, elfData, elf_nextscn) FF_LIBRARY_LOAD_SYMBOL_VAR_MESSAGE(libelf, elfData, elf64_getshdr) FF_LIBRARY_LOAD_SYMBOL_VAR_MESSAGE(libelf, elfData, elf32_getshdr) FF_LIBRARY_LOAD_SYMBOL_VAR_MESSAGE(libelf, elfData, elf_getdata) FF_LIBRARY_LOAD_SYMBOL_VAR_MESSAGE(libelf, elfData, elf_strptr) FF_LIBRARY_LOAD_SYMBOL_VAR_MESSAGE(libelf, elfData, elf_end) libelf = NULL; } if (elfData.ffelf_end == NULL) return "load libelf failed"; // Open the ELF file FF_AUTO_CLOSE_FD int fd = open(elfFile, O_RDONLY | O_CLOEXEC); if (fd < 0) return "open() failed"; Elf* elf = elfData.ffelf_begin(fd, ELF_C_READ, NULL); if (elf == NULL) return "elf_begin() failed"; // Get the section header string table index size_t shstrndx = 0; if (elfData.ffelf_getshdrstrndx(elf, &shstrndx) < 0) { elfData.ffelf_end(elf); return "elf_getshdrstrndx() failed"; } // Iterate through all sections, looking for .rodata which contains string literals Elf_Scn* scn = NULL; while ((scn = elfData.ffelf_nextscn(elf, scn)) != NULL) { // Try 64-bit section header first, then 32-bit if that fails Elf64_Shdr* shdr64 = elfData.ffelf64_getshdr(scn); Elf32_Shdr* shdr32 = NULL; if (shdr64 == NULL) { shdr32 = elfData.ffelf32_getshdr(scn); if (shdr32 == NULL) continue; } // Get the section name and check if it's .rodata const char* name = elfData.ffelf_strptr(elf, shstrndx, shdr64 ? shdr64->sh_name : shdr32->sh_name); if (name == NULL || !ffStrEquals(name, ".rodata")) continue; // Get the section data Elf_Data* data = elfData.ffelf_getdata(scn, NULL); if (data == NULL) continue; // Scan the section for string literals for (size_t off = 0; off < data->d_size; ++off) { const char* p = (const char*) data->d_buf + off; if (*p == '\0') continue; uint32_t len = (uint32_t) strlen(p); if (len < minLength) continue; // Only process printable ASCII characters if (*p >= ' ' && *p <= '~') // Ignore control characters { if (!cb(p, len, userdata)) break; } off += len; } break; } elfData.ffelf_end(elf); return NULL; } #else /** * Fallback implementation when libelf is not available */ const char* ffBinaryExtractStrings(const char* file, bool (*cb)(const char* str, uint32_t len, void* userdata), void* userdata, uint32_t minLength) { FF_UNUSED(file, cb, userdata, minLength); return "Fastfetch was built without libelf support"; } #endif ================================================ FILE: src/common/impl/binary_windows.c ================================================ #include "common/binary.h" #include "common/io.h" #include "common/stringUtils.h" #include "common/windows/nt.h" #include #include #include /** * Extracts string literals from a PE (Windows) executable * * This function maps the PE file into memory, locates the .rdata section * (which typically contains string literals), and scans it for valid strings. * Each string found is passed to the callback function for processing. */ const char* ffBinaryExtractStrings(const char *peFile, bool (*cb)(const char *str, uint32_t len, void *userdata), void *userdata, uint32_t minLength) { FF_AUTO_CLOSE_FD HANDLE hFile = CreateFileA(peFile, GENERIC_READ, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL); if (hFile == INVALID_HANDLE_VALUE) return "CreateFileA() failed"; FF_AUTO_CLOSE_FD HANDLE hMap = CreateFileMappingW(hFile, NULL, PAGE_READONLY, 0, 0, NULL); if (!hMap) return "CreateFileMappingW() failed"; void* base = MapViewOfFile(hMap, FILE_MAP_READ, 0, 0, 0); if (!base) return "MapViewOfFile() failed"; PIMAGE_NT_HEADERS ntHeaders = RtlImageNtHeader(base); if (!ntHeaders) { UnmapViewOfFile(base); return "RtlImageNtHeader() failed"; } PIMAGE_SECTION_HEADER section = IMAGE_FIRST_SECTION(ntHeaders); for (WORD i = 0; i < ntHeaders->FileHeader.NumberOfSections; ++i, ++section) { // Look for initialized data sections with the name ".rdata" which typically contains string literals if ((section->Characteristics & IMAGE_SCN_CNT_INITIALIZED_DATA) && ffStrEquals((const char*) section->Name, ".rdata")) { uint8_t *data = (uint8_t *) base + section->PointerToRawData; // Scan the section for string literals for (size_t off = 0; off < section->SizeOfRawData; ++off) { const char* p = (const char*) data + off; if (*p == '\0') continue; uint32_t len = (uint32_t) strlen(p); if (len < minLength) continue; // Only process printable ASCII characters if (*p >= ' ' && *p <= '~') // Ignore control characters { if (!cb(p, len, userdata)) break; } off += len; } } } UnmapViewOfFile(base); return NULL; } ================================================ FILE: src/common/impl/commandoption.c ================================================ #include "common/commandoption.h" #include "common/color.h" #include "common/printing.h" #include "common/time.h" #include "common/jsonconfig.h" #include "common/stringUtils.h" #include "fastfetch_datatext.h" #include "modules/modules.h" #include #include bool ffParseModuleOptions(const char* key, const char* value) { if (!ffStrStartsWith(key, "--") || !ffCharIsEnglishAlphabet(key[2])) return false; if (value && !*value) value = NULL; for (FFModuleBaseInfo** modules = ffModuleInfos[toupper(key[2]) - 'A']; *modules; ++modules) { FFModuleBaseInfo* baseInfo = *modules; const char* subKey = ffOptionTestPrefix(key, baseInfo->name); if (subKey != NULL) { if (subKey[0] == '\0' || subKey[0] == '-') // Key is exactly the module name or has a leading '-' { fprintf(stderr, "Error: unknown module key %s\n", key); exit(477); } FF_STRBUF_AUTO_DESTROY moduleName = ffStrbufCreateS(baseInfo->name); ffStrbufLowerCase(&moduleName); FF_STRBUF_AUTO_DESTROY jsonKey = ffStrbufCreate(); bool flag = false; for (const char* p = subKey; *p; ++p) { if (*p == '-') { if (flag) { fprintf(stderr, "Error: invalid double `-` in module key %s\n", key); exit(477); } flag = true; } else { if (!isalpha((unsigned char)*p) && !isdigit((unsigned char)*p)) { fprintf(stderr, "Error: invalid character `%c` in module key %s\n", *p, key); exit(477); } if (flag) { flag = false; ffStrbufAppendC(&jsonKey, (char) toupper((unsigned char) *p)); } else ffStrbufAppendC(&jsonKey, *p); } } fprintf(stderr, "Error: Unsupported module option: %s\n", key); fputs(" Support of module options has been removed. Please add the flag to the JSON config instead.\n", stderr); fprintf(stderr, " Example (demonstration only): `{ \"modules\": [ { \"type\": \"%s\", \"%s\": %s%s%s } ] }`\n", moduleName.chars, jsonKey.chars, value ? "\"" : "", value ?: "true", value ? "\"" : ""); fputs(" See for more information.\n", stderr); exit(477); } } return false; } void ffPrepareCommandOption(FFdata* data) { char* moduleType = NULL; size_t moduleLen = 0; while (ffStrbufGetdelim(&moduleType, &moduleLen, ':', &data->structure)) { #define FF_IF_MODULE_MATCH(moduleNameConstant) if (moduleLen == strlen(moduleNameConstant) \ && ffStrEqualsIgnCase(moduleType, moduleNameConstant) \ && !ffStrbufSeparatedContainIgnCaseS(&data->structureDisabled, moduleNameConstant, ':')) switch (moduleType[0]) { case 'C': case 'c': FF_IF_MODULE_MATCH(FF_CPUUSAGE_MODULE_NAME) ffPrepareCPUUsage(); break; case 'D': case 'd': FF_IF_MODULE_MATCH(FF_DISKIO_MODULE_NAME) { __attribute__((__cleanup__(ffDestroyDiskIOOptions))) FFDiskIOOptions options; ffInitDiskIOOptions(&options); ffPrepareDiskIO(&options); } break; case 'N': case 'n': FF_IF_MODULE_MATCH(FF_NETIO_MODULE_NAME) { __attribute__((__cleanup__(ffDestroyNetIOOptions))) FFNetIOOptions options; ffInitNetIOOptions(&options); ffPrepareNetIO(&options); } break; case 'P': case 'p': FF_IF_MODULE_MATCH(FF_PUBLICIP_MODULE_NAME) { __attribute__((__cleanup__(ffDestroyPublicIpOptions))) FFPublicIPOptions options; ffInitPublicIpOptions(&options); ffPreparePublicIp(&options); } break; case 'W': case 'w': FF_IF_MODULE_MATCH(FF_WEATHER_MODULE_NAME) { __attribute__((__cleanup__(ffDestroyWeatherOptions))) FFWeatherOptions options; ffInitWeatherOptions(&options); ffPrepareWeather(&options); } break; } #undef FF_IF_MODULE_MATCH } } static void genJsonConfig(FFdata* data, FFModuleBaseInfo* baseInfo, void* options) { yyjson_mut_doc* doc = data->resultDoc; yyjson_mut_val* modules = yyjson_mut_obj_get(doc->root, "modules"); if (!modules) modules = yyjson_mut_obj_add_arr(doc, doc->root, "modules"); FF_STRBUF_AUTO_DESTROY type = ffStrbufCreateS(baseInfo->name); ffStrbufLowerCase(&type); if (data->docType == FF_RESULT_DOC_TYPE_CONFIG_FULL) { yyjson_mut_val* module = yyjson_mut_obj(doc); yyjson_mut_obj_add_strbuf(doc, module, "type", &type); if (baseInfo->generateJsonConfig) baseInfo->generateJsonConfig(options, doc, module); if (yyjson_mut_obj_size(module) > 1) yyjson_mut_arr_add_val(modules, module); else yyjson_mut_arr_add_strbuf(doc, modules, &type); } else { yyjson_mut_arr_add_strbuf(doc, modules, &type); } } static void genJsonResult(FFdata* data, FFModuleBaseInfo* baseInfo, void* options) { 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", baseInfo->name); if (baseInfo->generateJsonResult) baseInfo->generateJsonResult(options, doc, module); else yyjson_mut_obj_add_str(doc, module, "error", "Unsupported for JSON format"); } static void parseStructureCommand( FFdata* data, const char* line, void (*fn)(FFdata*, FFModuleBaseInfo* baseInfo, void* options) ) { if(ffCharIsEnglishAlphabet(line[0])) { for (FFModuleBaseInfo** modules = ffModuleInfos[toupper(line[0]) - 'A']; *modules; ++modules) { FFModuleBaseInfo* baseInfo = *modules; if (ffStrEqualsIgnCase(line, baseInfo->name)) { uint8_t optionBuf[FF_OPTION_MAX_SIZE]; baseInfo->initOptions(optionBuf); if (__builtin_expect(data->resultDoc != NULL, false)) fn(data, baseInfo, optionBuf); else baseInfo->printModule(optionBuf); baseInfo->destroyOptions(optionBuf); return; } } } ffPrintError(line, 0, NULL, FF_PRINT_TYPE_NO_CUSTOM_KEY, ""); } void ffPrintCommandOption(FFdata* data) { //Parse the structure and call the modules int32_t thres = instance.config.display.stat; char* moduleType = NULL; size_t moduleLen = 0; while (ffStrbufGetdelim(&moduleType, &moduleLen, ':', &data->structure)) { if (ffStrbufSeparatedContainIgnCaseS(&data->structureDisabled, moduleType, ':')) continue; double ms = 0; if(thres >= 0) ms = ffTimeGetTick(); parseStructureCommand(data, moduleType, genJsonResult); if(thres >= 0) { ms = ffTimeGetTick() - ms; if (data->resultDoc) { yyjson_mut_val* moduleJson = yyjson_mut_arr_get_last(data->resultDoc->root); yyjson_mut_obj_add_real(data->resultDoc, moduleJson, "stat", ms); } else { char str[64]; int len = snprintf(str, sizeof str, "%.3fms", ms); if (thres > 0) snprintf(str, sizeof str, "\e[%sm%.3fms\e[m", (ms <= thres ? FF_COLOR_FG_GREEN : ms <= 2 * thres ? FF_COLOR_FG_YELLOW : FF_COLOR_FG_RED), ms); printf("\e7\e[1A\e[9999999C\e[%dD%s\e8", len, str); // Save; Up 1; Right 9999999; Left ; Print ; Load } } #if defined(_WIN32) if (!data->resultDoc && !instance.config.display.noBuffer) fflush(stdout); #endif } } void ffMigrateCommandOptionToJsonc(FFdata* data) { //If we don't have a custom structure, use the default one if(data->structure.length == 0) ffStrbufAppendS(&data->structure, FASTFETCH_DATATEXT_STRUCTURE); // Cannot use `ffStrbufSetStatic` here because we will modify the string char* moduleType = NULL; size_t moduleLen = 0; while (ffStrbufGetdelim(&moduleType, &moduleLen, ':', &data->structure)) { if (ffStrbufSeparatedContainIgnCaseS(&data->structureDisabled, moduleType, ':')) continue; parseStructureCommand(data, moduleType, genJsonConfig); } } ================================================ FILE: src/common/impl/dbus.c ================================================ #include "common/dbus.h" #ifdef FF_HAVE_DBUS #include "common/thread.h" #include "common/stringUtils.h" static bool loadLibSymbols(FFDBusLibrary* lib) { FF_LIBRARY_LOAD(dbus, false, "libdbus-1" FF_LIBRARY_EXTENSION, 4); FF_LIBRARY_LOAD_SYMBOL_PTR(dbus, lib, dbus_bus_get, false) FF_LIBRARY_LOAD_SYMBOL_PTR(dbus, lib, dbus_message_new_method_call, false) FF_LIBRARY_LOAD_SYMBOL_PTR(dbus, lib, dbus_message_append_args, false) FF_LIBRARY_LOAD_SYMBOL_PTR(dbus, lib, dbus_message_iter_init, false) FF_LIBRARY_LOAD_SYMBOL_PTR(dbus, lib, dbus_message_iter_get_arg_type, false) FF_LIBRARY_LOAD_SYMBOL_PTR(dbus, lib, dbus_message_iter_get_basic, false) FF_LIBRARY_LOAD_SYMBOL_PTR(dbus, lib, dbus_message_iter_recurse, false) FF_LIBRARY_LOAD_SYMBOL_PTR(dbus, lib, dbus_message_iter_has_next, false) FF_LIBRARY_LOAD_SYMBOL_PTR(dbus, lib, dbus_message_iter_next, false) FF_LIBRARY_LOAD_SYMBOL_PTR(dbus, lib, dbus_message_unref, false) FF_LIBRARY_LOAD_SYMBOL_PTR(dbus, lib, dbus_connection_send_with_reply_and_block, false) FF_LIBRARY_LOAD_SYMBOL_PTR(dbus, lib, dbus_connection_unref, false) dbus = NULL; // don't auto dlclose return true; } static const FFDBusLibrary* loadLib(void) { static FFDBusLibrary lib; static bool loaded = false; static bool loadSuccess = false; if(!loaded) { loaded = true; loadSuccess = loadLibSymbols(&lib); } return loadSuccess ? &lib : NULL; } const char* ffDBusLoadData(DBusBusType busType, FFDBusData* data) { data->lib = loadLib(); if(data->lib == NULL) return "Failed to load DBus library"; data->connection = data->lib->ffdbus_bus_get(busType, NULL); if(data->connection == NULL) return "Failed to connect to DBus"; return NULL; } void ffDBusDestroyData(FFDBusData* data) { if (data->connection != NULL) { data->lib->ffdbus_connection_unref(data->connection); data->connection = NULL; } } bool ffDBusGetString(FFDBusData* dbus, DBusMessageIter* iter, FFstrbuf* result) { int argType = dbus->lib->ffdbus_message_iter_get_arg_type(iter); if(argType == DBUS_TYPE_STRING || argType == DBUS_TYPE_OBJECT_PATH) { const char* value = NULL; dbus->lib->ffdbus_message_iter_get_basic(iter, &value); if(!ffStrSet(value)) return false; ffStrbufAppendS(result, value); return true; } if (argType == DBUS_TYPE_BYTE) { uint8_t value; dbus->lib->ffdbus_message_iter_get_basic(iter, &value); ffStrbufAppendC(result, (char) value); return false; // Don't append a comma } if(argType != DBUS_TYPE_VARIANT && argType != DBUS_TYPE_ARRAY) return false; DBusMessageIter subIter; dbus->lib->ffdbus_message_iter_recurse(iter, &subIter); if(argType == DBUS_TYPE_VARIANT) return ffDBusGetString(dbus, &subIter, result); //At this point we have an array bool foundAValue = false; while(true) { if(ffDBusGetString(dbus, &subIter, result)) { foundAValue = true; ffStrbufAppendS(result, ", "); } if(!dbus->lib->ffdbus_message_iter_next(&subIter)) break; else continue; } if(foundAValue) ffStrbufSubstrBefore(result, result->length - 2); return foundAValue; } bool ffDBusGetBool(FFDBusData* dbus, DBusMessageIter* iter, bool* result) { int argType = dbus->lib->ffdbus_message_iter_get_arg_type(iter); if(argType == DBUS_TYPE_BOOLEAN) { dbus_bool_t value = 0; dbus->lib->ffdbus_message_iter_get_basic(iter, &value); *result = value != 0; return true; } if(argType != DBUS_TYPE_VARIANT) return false; DBusMessageIter subIter; dbus->lib->ffdbus_message_iter_recurse(iter, &subIter); return ffDBusGetBool(dbus, &subIter, result); } bool ffDBusGetUint(FFDBusData* dbus, DBusMessageIter* iter, uint32_t* result) { int argType = dbus->lib->ffdbus_message_iter_get_arg_type(iter); if(argType == DBUS_TYPE_BYTE) { uint8_t value = 0; dbus->lib->ffdbus_message_iter_get_basic(iter, &value); *result = value; return true; } if(argType == DBUS_TYPE_UINT16) { uint16_t value = 0; dbus->lib->ffdbus_message_iter_get_basic(iter, &value); *result = value; return true; } if(argType == DBUS_TYPE_UINT32) { dbus->lib->ffdbus_message_iter_get_basic(iter, result); return true; } if(argType != DBUS_TYPE_VARIANT) return false; DBusMessageIter subIter; dbus->lib->ffdbus_message_iter_recurse(iter, &subIter); return ffDBusGetUint(dbus, &subIter, result); } bool ffDBusGetInt(FFDBusData* dbus, DBusMessageIter* iter, int32_t* result) { int argType = dbus->lib->ffdbus_message_iter_get_arg_type(iter); if(argType == DBUS_TYPE_INT16) { int16_t value = 0; dbus->lib->ffdbus_message_iter_get_basic(iter, &value); *result = value; return true; } if(argType == DBUS_TYPE_INT32) { dbus->lib->ffdbus_message_iter_get_basic(iter, result); return true; } if(argType == DBUS_TYPE_BYTE) { uint8_t value = 0; dbus->lib->ffdbus_message_iter_get_basic(iter, &value); *result = value; return true; } if(argType == DBUS_TYPE_UINT16) { uint16_t value = 0; dbus->lib->ffdbus_message_iter_get_basic(iter, &value); *result = (int16_t) value; return true; } if(argType == DBUS_TYPE_UINT32) { dbus->lib->ffdbus_message_iter_get_basic(iter, result); return true; } if(argType != DBUS_TYPE_VARIANT) return false; DBusMessageIter subIter; dbus->lib->ffdbus_message_iter_recurse(iter, &subIter); return ffDBusGetInt(dbus, &subIter, result); } DBusMessage* ffDBusGetMethodReply(FFDBusData* dbus, const char* busName, const char* objectPath, const char* interface, const char* method, const char* arg1, const char* arg2) { DBusMessage* message = dbus->lib->ffdbus_message_new_method_call(busName, objectPath, interface, method); if(message == NULL) return NULL; if (arg1) { if (arg2) dbus->lib->ffdbus_message_append_args(message, DBUS_TYPE_STRING, &arg1, DBUS_TYPE_STRING, &arg2, DBUS_TYPE_INVALID); else dbus->lib->ffdbus_message_append_args(message, DBUS_TYPE_STRING, &arg1, DBUS_TYPE_INVALID); } DBusMessage* reply = dbus->lib->ffdbus_connection_send_with_reply_and_block(dbus->connection, message, instance.config.general.processingTimeout, NULL); dbus->lib->ffdbus_message_unref(message); return reply; } DBusMessage* ffDBusGetProperty(FFDBusData* dbus, const char* busName, const char* objectPath, const char* interface, const char* property) { DBusMessage* message = dbus->lib->ffdbus_message_new_method_call(busName, objectPath, "org.freedesktop.DBus.Properties", "Get"); if(message == NULL) return NULL; dbus->lib->ffdbus_message_append_args(message, DBUS_TYPE_STRING, &interface, DBUS_TYPE_STRING, &property, DBUS_TYPE_INVALID); DBusMessage* reply = dbus->lib->ffdbus_connection_send_with_reply_and_block(dbus->connection, message, instance.config.general.processingTimeout, NULL); dbus->lib->ffdbus_message_unref(message); return reply; } bool ffDBusGetPropertyString(FFDBusData* dbus, const char* busName, const char* objectPath, const char* interface, const char* property, FFstrbuf* result) { DBusMessage* reply = ffDBusGetProperty(dbus, busName, objectPath, interface, property); if(reply == NULL) return false; DBusMessageIter rootIterator; if(!dbus->lib->ffdbus_message_iter_init(reply, &rootIterator)) { dbus->lib->ffdbus_message_unref(reply); return false; } bool ret = ffDBusGetString(dbus, &rootIterator, result); dbus->lib->ffdbus_message_unref(reply); return ret; } bool ffDBusGetPropertyUint(FFDBusData* dbus, const char* busName, const char* objectPath, const char* interface, const char* property, uint32_t* result) { DBusMessage* reply = ffDBusGetProperty(dbus, busName, objectPath, interface, property); if(reply == NULL) return false; DBusMessageIter rootIterator; if(!dbus->lib->ffdbus_message_iter_init(reply, &rootIterator)) { dbus->lib->ffdbus_message_unref(reply); return false; } bool ret = ffDBusGetUint(dbus, &rootIterator, result); dbus->lib->ffdbus_message_unref(reply); return ret; } #endif //FF_HAVE_DBUS ================================================ FILE: src/common/impl/debug_windows.c ================================================ #include "common/debug.h" #include const char* ffDebugWin32Error(DWORD errorCode) { static char buffer[256]; DWORD len = FormatMessageA( FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS, NULL, (DWORD) errorCode, MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), buffer, sizeof(buffer), NULL); if (len == 0) { snprintf(buffer, sizeof(buffer), "Unknown error code (%lu)", errorCode); } else { // Remove trailing newline if (buffer[len - 1] == '\n') buffer[len - 1] = '\0'; if (buffer[len - 2] == '\r') buffer[len - 2] = '\0'; snprintf(buffer + len - 2, sizeof(buffer) - len + 2, " (%lu)", errorCode); } return buffer; } const char* ffDebugNtStatus(NTSTATUS status) { return ffDebugWin32Error(RtlNtStatusToDosError(status)); } ================================================ FILE: src/common/impl/duration.c ================================================ #include "common/duration.h" void ffDurationAppendNum(uint64_t totalSeconds, FFstrbuf* result) { const FFOptionsDisplay* options = &instance.config.display; bool spaceBeforeUnit = options->durationSpaceBeforeUnit != FF_SPACE_BEFORE_UNIT_NEVER; if (totalSeconds < 60) { ffStrbufAppendUInt(result, totalSeconds); if (spaceBeforeUnit) ffStrbufAppendC(result, ' '); ffStrbufAppendS(result, options->durationAbbreviation ? "sec" : "second"); if (totalSeconds != 1) ffStrbufAppendC(result, 's'); return; } uint32_t seconds = (uint32_t) (totalSeconds % 60); totalSeconds /= 60; if (seconds >= 30) totalSeconds++; uint32_t minutes = (uint32_t) (totalSeconds % 60); totalSeconds /= 60; uint32_t hours = (uint32_t) (totalSeconds % 24); totalSeconds /= 24; uint32_t days = (uint32_t) totalSeconds; if (days > 0) { ffStrbufAppendUInt(result, days); if (spaceBeforeUnit) ffStrbufAppendC(result, ' '); if (options->durationAbbreviation) { ffStrbufAppendC(result, 'd'); if (hours > 0 || minutes > 0) ffStrbufAppendC(result, ' '); } else { ffStrbufAppendS(result, days == 1 ? "day" : "days"); if (days >= 100) ffStrbufAppendS(result, "(!)"); if (hours > 0 || minutes > 0) ffStrbufAppendS(result, ", "); } } if (hours > 0) { ffStrbufAppendUInt(result, hours); if (spaceBeforeUnit) ffStrbufAppendC(result, ' '); if (options->durationAbbreviation) { ffStrbufAppendC(result, 'h'); if (minutes > 0) ffStrbufAppendC(result, ' '); } else { ffStrbufAppendS(result, hours == 1 ? "hour" : "hours"); if (minutes > 0) ffStrbufAppendS(result, ", "); } } if (minutes > 0) { ffStrbufAppendUInt(result, minutes); if (spaceBeforeUnit) ffStrbufAppendC(result, ' '); if (options->durationAbbreviation) ffStrbufAppendC(result, 'm'); else ffStrbufAppendS(result, minutes == 1 ? "min" : "mins"); } } ================================================ FILE: src/common/impl/edidHelper.c ================================================ #include "common/edidHelper.h" void ffEdidGetPhysicalResolution(const uint8_t edid[128], uint32_t* width, uint32_t* height) { const int dtd = 54; *width = (((uint32_t) edid[dtd + 4] >> 4) << 8) | edid[dtd + 2]; *height = (((uint32_t) edid[dtd + 7] >> 4) << 8) | edid[dtd + 5]; } void ffEdidGetPreferredResolutionAndRefreshRate(const uint8_t edid[128], uint32_t* width, uint32_t* height, double* refreshRate) { for (uint32_t i = 0x36; i < 0x7E; i += 0x12) { // read through descriptor blocks... if (edid[i] != 0x00 && edid[i + 1] != 0x00) { // a dtd uint32_t hactive = edid[i+2] + (uint32_t) ((edid[i + 4] & 0xf0) << 4); uint32_t hblank = edid[i+3] + (uint32_t) ((edid[i + 4] & 0x0f) << 8); uint32_t vactive = edid[i+5] + (uint32_t) ((edid[i + 7] & 0xf0) << 4); uint32_t vblank = edid[i+6] + (uint32_t) ((edid[i + 7] & 0x0f) << 8); uint32_t pixclk = ((uint32_t) edid[i+1] << 8) | (edid[i]); *width = hactive; *height = vactive; *refreshRate = (double)pixclk * 10000 / (double)(hactive+hblank) / (double)(vactive+vblank); return; } } } void ffEdidGetVendorAndModel(const uint8_t edid[128], FFstrbuf* result) { // https://github.com/jinksong/read_edid/blob/master/parse-edid/parse-edid.c ffStrbufAppendF(result, "%c%c%c%04X", (char) (((uint32_t)edid[8] >> 2 & 0x1f) + 'A' - 1), (char) (((((uint32_t)edid[8] & 0x3) << 3) | (((uint32_t)edid[9] & 0xe0) >> 5)) + 'A' - 1), (char) (((uint32_t)edid[9] & 0x1f) + 'A' - 1), (uint32_t) (edid[10] + (uint32_t) (edid[11] << 8)) ); } bool ffEdidGetName(const uint8_t edid[128], FFstrbuf* name) { // https://github.com/jinksong/read_edid/blob/master/parse-edid/parse-edid.c for (uint32_t i = 0x36; i < 0x7E; i += 0x12) { // read through descriptor blocks... if (edid[i] == 0x00) { // not a timing descriptor if (edid[i + 3] == 0xfc) { // Model Name tag for (uint32_t j = 0; j < 13; j++) { if (edid[i + 5 + j] == 0x0a) { ffStrbufAppendNS(name, j, (const char*) &edid[i + 5]); return true; } } } } } // use manufacturer + model number as monitor name ffEdidGetVendorAndModel(edid, name); return false; } void ffEdidGetPhysicalSize(const uint8_t edid[128], uint32_t* width, uint32_t* height) { // Detailed Timing Descriptors uint32_t dw = (((uint32_t) edid[68] & 0xF0) << 4) + edid[66]; uint32_t dh = (((uint32_t) edid[68] & 0x0F) << 8) + edid[67]; // Basic Display Parameters uint32_t bw = edid[21] * 10; uint32_t bh = edid[22] * 10; // Some monitors report invalid data in DTD. See #1406 if (abs((int)dw - (int)bw) < 10 && abs((int)dh - (int)bh) < 10) { *width = dw; *height = dh; } else { *width = bw; *height = bh; } } void ffEdidGetSerialAndManufactureDate(const uint8_t edid[128], uint32_t* serial, uint16_t* year, uint16_t* week) { if (edid[17] > 0 && edid[17] < 0xFF) { *year = (uint16_t) (edid[17] + 1990); *week = (uint16_t) edid[16]; if (*week == 0xFF) *week = 0; } else *year = *week = 0; *serial = *(uint32_t*) &edid[12]; } bool ffEdidGetHdrCompatible(const uint8_t* edid, uint32_t length) { if (length <= 128) return false; for (const uint8_t* cta = &edid[128]; cta < &edid[length]; cta += 128) { // https://en.wikipedia.org/wiki/Extended_Display_Identification_Data#CTA_EDID_Timing_Extension_Block if (cta[0] != 0x02 /* CTA EDID */) continue; if (cta[1] < 0x03 /* Version 3 */) continue; const uint8_t offset = cta[2]; if (offset <= 4) continue; for (uint8_t i = 4; i < offset;) { uint8_t blkLen = cta[i] & 0x1f; if (blkLen > 0) { uint8_t blkTag = (cta[i] & 0xe0) >> 5; if (blkTag == 0x07 /* Extended Block Type Tag */) { uint8_t extendedTag = cta[i + 1]; if (extendedTag == 6 /* HDR SMDB */ || extendedTag == 7 /* HDR DMDB */) return true; } } i += (uint8_t) (blkLen + 1); } } return false; } bool ffEdidIsValid(const uint8_t edid[128], uint32_t length) { if (length < 128 || length % 128 != 0) return false; static const uint8_t edidHeader[] = { 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x00 }; if (memcmp(edid, edidHeader, sizeof(edidHeader)) != 0) return false; uint8_t sum = 0; for (uint32_t i = 0; i < 128; i++) sum += edid[i]; return sum == 0; } ================================================ FILE: src/common/impl/font.c ================================================ #include "fastfetch.h" #include "common/FFlist.h" #include "common/FFstrbuf.h" #include "common/stringUtils.h" #include "common/font.h" #include #include void ffFontInit(FFfont* font) { // Ensure no memory allocates ffStrbufInit(&font->pretty); ffStrbufInit(&font->name); ffStrbufInit(&font->size); ffListInit(&font->styles, sizeof(FFstrbuf)); } static void strbufAppendNSExcludingC(FFstrbuf* strbuf, uint32_t length, const char* value, char exclude) { if(value == NULL || length == 0) return; ffStrbufEnsureFree(strbuf, length); for(uint32_t i = 0; i < length; i++) { if(value[i] != exclude) strbuf->chars[strbuf->length++] = value[i]; } strbuf->chars[strbuf->length] = '\0'; } static void fontInitPretty(FFfont* font) { ffStrbufAppend(&font->pretty, &font->name); if(font->size.length == 0 && font->styles.length == 0) return; else if(font->pretty.length == 0) ffStrbufAppendS(&font->pretty, "default"); ffStrbufAppendS(&font->pretty, " ("); if(font->size.length > 0) { ffStrbufAppend(&font->pretty, &font->size); if (!ffStrbufEndsWithS(&font->size, "pt") && !ffStrbufEndsWithS(&font->size, "px")) ffStrbufAppendS(&font->pretty, "pt"); if(font->styles.length > 0) ffStrbufAppendS(&font->pretty, ", "); } for(uint32_t i = 0; i < font->styles.length; i++) { ffStrbufAppend(&font->pretty, FF_LIST_GET(FFstrbuf, font->styles, i)); if(i < font->styles.length - 1) ffStrbufAppendS(&font->pretty, ", "); } ffStrbufAppendC(&font->pretty, ')'); } void ffFontInitQt(FFfont* font, const char* data) { ffFontInit(font); //See https://doc.qt.io/qt-5/qfont.html#toString //Family data = ffStrbufAppendSUntilC(&font->name, data, ','); ffStrbufTrim(&font->name, ' '); if (!data) goto exit; data++; //Size data = ffStrbufAppendSUntilC(&font->size, data, ','); ffStrbufTrim(&font->size, ' '); if (!data) goto exit; data++; //Style data = strrchr(data, ','); if (!data) goto exit; data++; if (isalpha(*data)) { do { FFstrbuf* style = (FFstrbuf*) ffListAdd(&font->styles); ffStrbufInit(style); data = ffStrbufAppendSUntilC(style, data, ' '); if (data) data++; } while (data); } exit: fontInitPretty(font); } static void fontPangoParseWord(const char** data, FFfont* font, FFstrbuf* alternativeBuffer) { while(**data == ' ' || **data == '\t' || **data == ',') ++(*data); const char* wordStart = *data; while(**data != ' ' && **data != '\t' && **data != ',' && **data != '\0' && **data != '`' && **data != '\\') ++(*data); uint32_t wordLength = (uint32_t) (*data - wordStart); if(wordLength == 0) return; if(**data == '\0' || **data == '`' || **data == '\\') { ffStrbufAppendNS(&font->size, wordLength, wordStart); if(ffStrbufEndsWithS(&font->size, "px")) ffStrbufSubstrBefore(&font->size, font->size.length - 2); double dummy; if(sscanf(font->size.chars, "%lf", &dummy) == 1) return; ffStrbufClear(&font->size); } if( ffStrStartsWithIgnCase(wordStart, "Ultra") || ffStrStartsWithIgnCase(wordStart, "Extra") || ffStrStartsWithIgnCase(wordStart, "Semi") || ffStrStartsWithIgnCase(wordStart, "Demi") || ffStrStartsWithIgnCase(wordStart, "Normal") || ffStrStartsWithIgnCase(wordStart, "Roman") || ffStrStartsWithIgnCase(wordStart, "Oblique") || ffStrStartsWithIgnCase(wordStart, "Italic") || ffStrStartsWithIgnCase(wordStart, "Thin") || ffStrStartsWithIgnCase(wordStart, "Light") || ffStrStartsWithIgnCase(wordStart, "Bold") || ffStrStartsWithIgnCase(wordStart, "Black") || ffStrStartsWithIgnCase(wordStart, "Condensed") || ffStrStartsWithIgnCase(wordStart, "Expanded") ) { if(alternativeBuffer == NULL) { alternativeBuffer = (FFstrbuf*) ffListAdd(&font->styles); ffStrbufInit(alternativeBuffer); } strbufAppendNSExcludingC(alternativeBuffer, wordLength, wordStart, '-'); if( ffStrStartsWithIgnCase(wordStart, "Ultra ") || ffStrStartsWithIgnCase(wordStart, "Extra ") || ffStrStartsWithIgnCase(wordStart, "Semi ") || ffStrStartsWithIgnCase(wordStart, "Demi ") ) { fontPangoParseWord(data, font, alternativeBuffer); } return; } if(alternativeBuffer != NULL) { strbufAppendNSExcludingC(alternativeBuffer, wordLength, wordStart, '-'); return; } if(font->name.length > 0) ffStrbufAppendC(&font->name, ' '); ffStrbufAppendNS(&font->name, wordLength, wordStart); } void ffFontInitPango(FFfont* font, const char* data) { ffFontInit(font); while(*data != '\0' && *data != '`' && *data != '\\') fontPangoParseWord(&data, font, NULL); fontInitPretty(font); } void ffFontInitValues(FFfont* font, const char* name, const char* size) { ffFontInit(font); ffStrbufAppendS(&font->name, name); ffStrbufTrim(&font->name, '"'); ffStrbufAppendS(&font->size, size); fontInitPretty(font); } void ffFontInitXlfd(FFfont* font, const char* xlfd) { assert(xlfd && *xlfd); // https://en.wikipedia.org/wiki/X_logical_font_description ffFontInit(font); // XLFD: -foundry-family-weight-slant-setwidth-addstyle-pixelsize-pointsize-xres-yres-spacing-averagewidth-charsetregistry-charsetencoding // It often starts with '-', which would create an empty first field. Skip it to align indexes. if(*xlfd == '-') xlfd++; const char* pstart = xlfd; for(int field = 0; field < 14; field++) { const char* pend = strchr(pstart, '-'); uint32_t length = pend ? (uint32_t)(pend - pstart) : (uint32_t) strlen(pstart); if(length > 0) { if(field == 1) // family { ffStrbufAppendNS(&font->name, length, pstart); } else if(field == 7) // pointsize (decipoints, preferred) { // parse positive integer from substring long deciPt = 0; bool ok = true; for(uint32_t i = 0; i < length; i++) { char c = pstart[i]; if(c < '0' || c > '9') { ok = false; break; } deciPt = deciPt * 10 + (c - '0'); } if(ok && deciPt > 0) { ffStrbufClear(&font->size); char tmp[32]; if(deciPt % 10 == 0) snprintf(tmp, sizeof(tmp), "%ldpt", deciPt / 10); else snprintf(tmp, sizeof(tmp), "%ld.%ldpt", deciPt / 10, deciPt % 10); ffStrbufAppendS(&font->size, tmp); } } else if(field == 6) // pixelsize (fallback if pointsize missing/invalid) { if(font->size.length == 0) { long px = 0; bool ok = true; for(uint32_t i = 0; i < length; i++) { char c = pstart[i]; if(c < '0' || c > '9') { ok = false; break; } px = px * 10 + (c - '0'); } if(ok && px > 0) { ffStrbufAppendNS(&font->size, length, pstart); ffStrbufAppendS(&font->size, "px"); } } } else if(field >= 2 && field <= 5) // weight/slant/setwidth/addstyle { // ignore "normal" (case-insensitive) if(!(length == 6 && ffStrStartsWithIgnCase(pstart, "normal"))) { FFstrbuf* style = (FFstrbuf*) ffListAdd(&font->styles); ffStrbufInitNS(style, length, pstart); } } } if(!pend) break; pstart = pend + 1; } fontInitPretty(font); } void ffFontInitXft(FFfont* font, const char* xft) { assert(xft); // https://en.wikipedia.org/wiki/Xft // Xft/Fontconfig pattern examples: // "DejaVu Sans Mono-10" // "monospace:size=10:weight=bold:slant=italic" // "Fira Code-12:style=Regular" // Goal: extract family(name), size, and some common styles. ffFontInit(font); // 1) Parse "head" part before first ':' => usually "family[-size]" (may include commas) const char* p = xft; while(*p == ' ' || *p == '\t') ++p; const char* headStart = p; while(*p != '\0' && *p != ':') ++p; const char* headEnd = p; // trim tail spaces while(headEnd > headStart && (headEnd[-1] == ' ' || headEnd[-1] == '\t')) --headEnd; // If multiple families are listed, take the first one (up to comma) for(const char* q = headStart; q < headEnd; ++q) { if(*q == ',') { headEnd = q; while(headEnd > headStart && (headEnd[-1] == ' ' || headEnd[-1] == '\t')) --headEnd; break; } } // Try parse trailing "-" as size, otherwise entire head is name const char* dashPos = NULL; const char* sizeStart = NULL; for(const char* q = headEnd; q > headStart; ) { --q; if(*q == '-' && (q + 1) < headEnd && ffCharIsDigit(q[1])) { dashPos = q; sizeStart = q + 1; break; } } if(dashPos) { bool ok = true; bool seenDigit = false; for(const char* q = sizeStart; q < headEnd; ++q) { if(ffCharIsDigit(*q)) seenDigit = true; else if(*q == '.') continue; else { ok = false; break; } } if(ok && seenDigit) { const char* nameEnd = dashPos; while(nameEnd > headStart && (nameEnd[-1] == ' ' || nameEnd[-1] == '\t')) --nameEnd; if(nameEnd > headStart) ffStrbufAppendNS(&font->name, (uint32_t) (nameEnd - headStart), headStart); if(headEnd > sizeStart) ffStrbufAppendNS(&font->size, (uint32_t) (headEnd - sizeStart), sizeStart); } else { if(headEnd > headStart) ffStrbufAppendNS(&font->name, (uint32_t) (headEnd - headStart), headStart); } } else { if(headEnd > headStart) ffStrbufAppendNS(&font->name, (uint32_t) (headEnd - headStart), headStart); } ffStrbufTrim(&font->name, ' '); ffStrbufTrim(&font->name, '"'); // 2) Parse key=value fields after ':' (Fontconfig-like). Fields separated by ':'. // Common keys: size, pixelsize, pointsize, style, weight, slant, width while(*p == ':') { ++p; // key const char* keyStart = p; while(*p != '\0' && *p != '=' && *p != ':') ++p; const char* keyEnd = p; if(*p != '=') continue; // skip tokens without '=' ++p; // skip '=' // value (until next ':', allow backslash-escaping) FF_STRBUF_AUTO_DESTROY value = ffStrbufCreate(); while(*p != '\0' && *p != ':') { if(*p == '\\' && p[1] != '\0') { ++p; ffStrbufAppendC(&value, *p); ++p; continue; } ffStrbufAppendC(&value, *p); ++p; } ffStrbufTrim(&value, ' '); ffStrbufTrim(&value, '"'); uint32_t keyLen = (uint32_t) (keyEnd - keyStart); // helper: set numeric size if not set yet const bool sizeEmpty = (font->size.length == 0); if(value.length > 0) { if( (keyLen == 4 && ffStrStartsWithIgnCase(keyStart, "size")) || (keyLen == 9 && ffStrStartsWithIgnCase(keyStart, "pixelsize")) ) { if(sizeEmpty && ffCharIsDigit(value.chars[0])) { ffStrbufAppend(&font->size, &value); ffStrbufAppendS(&font->size, keyLen == 4 ? "pt" : "px"); } } else if(keyLen == 5 && ffStrStartsWithIgnCase(keyStart, "style")) { // style may contain multiple words: "Bold Italic" const char* s = value.chars; while(*s != '\0') { while(*s == ' ' || *s == '\t' || *s == ',') ++s; const char* w = s; while(*s != '\0' && *s != ' ' && *s != '\t' && *s != ',') ++s; if(s > w) { FFstrbuf* style = FF_LIST_ADD(FFstrbuf, font->styles); ffStrbufInitNS(style, (uint32_t) (s - w), w); } } } else if( (keyLen == 6 && ffStrStartsWithIgnCase(keyStart, "weight")) || (keyLen == 5 && ffStrStartsWithIgnCase(keyStart, "slant")) || (keyLen == 5 && ffStrStartsWithIgnCase(keyStart, "width")) ) { // normalize: remove '-' to align with other parsers ("Semi-Bold" -> "SemiBold") FFstrbuf* style = FF_LIST_ADD(FFstrbuf, font->styles); ffStrbufInit(style); strbufAppendNSExcludingC(style, value.length, value.chars, '-'); ffStrbufTrim(style, ' '); } } } fontInitPretty(font); } void ffFontInitMoveValues(FFfont* font, FFstrbuf* name, FFstrbuf* size, FFstrbuf* style) { ffFontInit(font); if (name) ffStrbufInitMove(&font->name, name); if (size) ffStrbufInitMove(&font->size, size); if (style) { FFstrbuf* styleBuf = FF_LIST_ADD(FFstrbuf, font->styles); ffStrbufInitMove(styleBuf, style); } fontInitPretty(font); } void ffFontInitWithSpace(FFfont* font, const char* rawName) { const char* pspace = strrchr(rawName, ' '); if(pspace == NULL) { ffFontInitCopy(font, rawName); return; } ffFontInit(font); ffStrbufAppendNS(&font->name, (uint32_t)(pspace - rawName), rawName); ffStrbufAppendS(&font->size, pspace + 1); fontInitPretty(font); } void ffFontDestroy(FFfont* font) { ffStrbufDestroy(&font->pretty); ffStrbufDestroy(&font->name); ffStrbufDestroy(&font->size); FF_LIST_FOR_EACH(FFstrbuf, str, font->styles) ffStrbufDestroy(str); ffListDestroy(&font->styles); } ================================================ FILE: src/common/impl/format.c ================================================ #include "fastfetch.h" #include "common/format.h" #include "common/parsing.h" #include "common/textModifier.h" #include "common/stringUtils.h" #include void ffFormatAppendFormatArg(FFstrbuf* buffer, const FFformatarg* formatarg) { switch(formatarg->type) { case FF_ARG_TYPE_INT: ffStrbufAppendSInt(buffer, *(int32_t*)formatarg->value); break; case FF_ARG_TYPE_UINT: ffStrbufAppendUInt(buffer, *(uint32_t*)formatarg->value); break; case FF_ARG_TYPE_UINT64: ffStrbufAppendUInt(buffer, *(uint64_t*)formatarg->value); break; case FF_ARG_TYPE_UINT16: ffStrbufAppendUInt(buffer, *(uint16_t*)formatarg->value); break; case FF_ARG_TYPE_UINT8: ffStrbufAppendUInt(buffer, *(uint8_t*)formatarg->value); break; case FF_ARG_TYPE_STRING: ffStrbufAppendS(buffer, (const char*)formatarg->value); break; case FF_ARG_TYPE_STRBUF: ffStrbufAppend(buffer, (const FFstrbuf*)formatarg->value); break; case FF_ARG_TYPE_FLOAT: ffStrbufAppendDouble(buffer, *(float*)formatarg->value, instance.config.display.fractionNdigits, instance.config.display.fractionTrailingZeros != FF_FRACTION_TRAILING_ZEROS_TYPE_NEVER); break; case FF_ARG_TYPE_DOUBLE: ffStrbufAppendDouble(buffer, *(double*)formatarg->value, instance.config.display.fractionNdigits, instance.config.display.fractionTrailingZeros != FF_FRACTION_TRAILING_ZEROS_TYPE_NEVER); break; case FF_ARG_TYPE_BOOL: ffStrbufAppendS(buffer, *(bool*)formatarg->value ? "true" : "false"); break; case FF_ARG_TYPE_LIST: { const FFlist* list = (const FFlist*) formatarg->value; for(uint32_t i = 0; i < list->length; i++) { ffStrbufAppend(buffer, FF_LIST_GET(FFstrbuf, *list, i)); if(i < list->length - 1) ffStrbufAppendS(buffer, ", "); } break; } default: if(formatarg->type != FF_ARG_TYPE_NULL) fprintf(stderr, "Error: format string \"%s\": argument is not implemented: %i\n", buffer->chars, formatarg->type); break; } } /** * @brief parses a string to a uint32_t * * If the string can't be parsed, or is < 1, uint32_t max is returned. * * @param placeholderValue the string to parse * @return uint32_t the parsed value */ static uint32_t getArgumentIndex(const char* placeholderValue, uint32_t numArgs, const FFformatarg* arguments) { char firstChar = placeholderValue[0]; if (firstChar == '\0') return 0; // use arg counter if (firstChar >= '0' && firstChar <= '9') { char* pEnd = NULL; uint32_t result = (uint32_t) strtoul(placeholderValue, &pEnd, 10); if (result > numArgs) return UINT32_MAX; if (*pEnd != '\0') return UINT32_MAX; return result; } else if (ffCharIsEnglishAlphabet(firstChar)) { for (uint32_t i = 0; i < numArgs; ++i) { const FFformatarg* arg = &arguments[i]; if (arg->name && ffStrEqualsIgnCase(placeholderValue, arg->name)) return i + 1; } } return UINT32_MAX; } static inline void appendInvalidPlaceholder(FFstrbuf* buffer, const char* start, const FFstrbuf* placeholderValue, uint32_t index, uint32_t formatStringLength) { ffStrbufAppendS(buffer, start); ffStrbufAppend(buffer, placeholderValue); if(index < formatStringLength) ffStrbufAppendC(buffer, '}'); } 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) ); } void ffParseFormatString(FFstrbuf* buffer, const FFstrbuf* formatstr, uint32_t numArgs, const FFformatarg* arguments) { uint32_t argCounter = 0; uint32_t numOpenIfs = 0; uint32_t numOpenNotIfs = 0; FF_STRBUF_AUTO_DESTROY placeholderValue = ffStrbufCreate(); for(uint32_t i = 0; i < formatstr->length; ++i) { // if we don't have a placeholder start just copy the chars over to output buffer if(formatstr->chars[i] != '{') { ffStrbufAppendC(buffer, formatstr->chars[i]); continue; } // jump to next char, the start of the placeholder value ++i; // double {{ elvaluates to a single { and doesn't count as start if(formatstr->chars[i] == '{') { ffStrbufAppendC(buffer, '{'); continue; } ffStrbufClear(&placeholderValue); { uint32_t iEnd = ffStrbufNextIndexC(formatstr, i, '}'); ffStrbufAppendNS(&placeholderValue, iEnd - i, &formatstr->chars[i]); i = iEnd; } char firstChar = placeholderValue.chars[0]; if (placeholderValue.length == 1) { // test if for stop, if so break the loop if (firstChar == '-') break; // test for end of an if, if so do nothing if (firstChar == '?') { if(numOpenIfs == 0) appendInvalidPlaceholder(buffer, "{", &placeholderValue, i, formatstr->length); else --numOpenIfs; continue; } // test for end of a not if, if so do nothing if (firstChar == '/') { if(numOpenNotIfs == 0) appendInvalidPlaceholder(buffer, "{", &placeholderValue, i, formatstr->length); else --numOpenNotIfs; continue; } // test for end of a color, if so do nothing if (firstChar == '#') { if (!instance.config.display.pipe) ffStrbufAppendS(buffer, FASTFETCH_TEXT_MODIFIER_RESET); continue; } } // test for if, if so evaluate it if (firstChar == '?') { ffStrbufSubstrAfter(&placeholderValue, 0); uint32_t index = getArgumentIndex(placeholderValue.chars, numArgs, arguments); // testing for an invalid index if (index > numArgs || index < 1) { appendInvalidPlaceholder(buffer, "{?", &placeholderValue, i, formatstr->length); continue; } // continue normally if an format arg is set and the value is > 0 if (formatArgSet(&arguments[index - 1])) { ++numOpenIfs; continue; } // fastforward to the end of the if without printing the in between i = ffStrbufNextIndexS(formatstr, i, "{?}") + 2; // 2 is the length of "{?}" - 1 because the loop will increment it again directly after continue continue; } // test for not if, if so evaluate it if (firstChar == '/') { ffStrbufSubstrAfter(&placeholderValue, 0); uint32_t index = getArgumentIndex(placeholderValue.chars, numArgs, arguments); // testing for an invalid index if (index > numArgs || index < 1) { appendInvalidPlaceholder(buffer, "{/", &placeholderValue, i, formatstr->length); continue; } //continue normally if an format arg is not set or the value is 0 if (!formatArgSet(&arguments[index - 1])) { ++numOpenNotIfs; continue; } // fastforward to the end of the if without printing the in between i = ffStrbufNextIndexS(formatstr, i, "{/}") + 2; // 2 is the length of "{/}" - 1 because the loop will increment it again directly after continue continue; } //test for color, if so evaluate it if (firstChar == '#') { if (!instance.config.display.pipe) { ffStrbufAppendS(buffer, "\e["); ffOptionParseColorNoClear(placeholderValue.chars + 1, buffer); ffStrbufAppendC(buffer, 'm'); } continue; } //test for constant or env var, if so evaluate it if (firstChar == '$') { char* pend = NULL; int32_t indexSigned = (int32_t) strtol(placeholderValue.chars + 1, &pend, 10); if (pend == placeholderValue.chars + 1) { // treat placeholder as an environment variable char* envValue = getenv(placeholderValue.chars + 1); if (envValue) ffStrbufAppendS(buffer, envValue); else appendInvalidPlaceholder(buffer, "{", &placeholderValue, i, formatstr->length); } else { // treat placeholder as a constant uint32_t index = (uint32_t) (indexSigned < 0 ? (int32_t) instance.config.display.constants.length + indexSigned : indexSigned - 1); if (*pend != '\0' || instance.config.display.constants.length <= index) appendInvalidPlaceholder(buffer, "{", &placeholderValue, i, formatstr->length); else { FFstrbuf* item = FF_LIST_GET(FFstrbuf, instance.config.display.constants, index); ffStrbufAppend(buffer, item); } } continue; } char* pSep = placeholderValue.chars; char cSep = '\0'; while (*pSep && *pSep != ':' && *pSep != '<' && *pSep != '>' && *pSep != '~') ++pSep; if (*pSep) { cSep = *pSep; *pSep = '\0'; } else { pSep = NULL; } uint32_t index = getArgumentIndex(placeholderValue.chars, numArgs, arguments); // test for invalid index if (index == 0) index = ++argCounter; if (index > numArgs) { if (pSep) *pSep = cSep; appendInvalidPlaceholder(buffer, "{", &placeholderValue, i, formatstr->length); continue; } if (!cSep) ffFormatAppendFormatArg(buffer, &arguments[index - 1]); else if (cSep == '~') { FF_STRBUF_AUTO_DESTROY tempString = ffStrbufCreate(); ffFormatAppendFormatArg(&tempString, &arguments[index - 1]); char* pEnd = NULL; int32_t start = (int32_t) strtol(pSep + 1, &pEnd, 10); if (start < 0) start = (int32_t) tempString.length + start; if (start >= 0 && (uint32_t) start < tempString.length) { if (*pEnd == '\0') ffStrbufAppendNS(buffer, tempString.length - (uint32_t) start, &tempString.chars[start]); else if (*pEnd == ',') { int32_t end = (int32_t) strtol(pEnd + 1, &pEnd, 10); if (!*pEnd) { if (end < 0) end = (int32_t) tempString.length + end; if ((uint32_t) end > tempString.length) end = (int32_t) tempString.length; if (end > start) ffStrbufAppendNS(buffer, (uint32_t) (end - start), &tempString.chars[start]); } } } if (*pEnd) { *pSep = cSep; appendInvalidPlaceholder(buffer, "{", &placeholderValue, i, formatstr->length); continue; } } else { char* pEnd = NULL; int32_t truncLength = (int32_t) strtol(pSep + 1, &pEnd, 10); if (*pEnd != '\0') { *pSep = cSep; appendInvalidPlaceholder(buffer, "{", &placeholderValue, i, formatstr->length); continue; } bool ellipsis = false; if (truncLength < 0) { ellipsis = true; truncLength = -truncLength; } FF_STRBUF_AUTO_DESTROY tempString = ffStrbufCreate(); ffFormatAppendFormatArg(&tempString, &arguments[index - 1]); if (tempString.length == (uint32_t) truncLength) ffStrbufAppend(buffer, &tempString); else if (tempString.length > (uint32_t) truncLength) { if (cSep == ':') { ffStrbufSubstrBefore(&tempString, (uint32_t) truncLength); ffStrbufTrimRightSpace(&tempString); } else ffStrbufSubstrBefore(&tempString, (uint32_t) (!ellipsis? truncLength : truncLength - 1)); ffStrbufAppend(buffer, &tempString); if (ellipsis) ffStrbufAppendS(buffer, "…"); } else if (cSep == ':') ffStrbufAppend(buffer, &tempString); else { if (cSep == '<') { ffStrbufAppend(buffer, &tempString); ffStrbufAppendNC(buffer, (uint32_t) truncLength - tempString.length, ' '); } else { ffStrbufAppendNC(buffer, (uint32_t) truncLength - tempString.length, ' '); ffStrbufAppend(buffer, &tempString); } } } } if (!instance.config.display.pipe) ffStrbufAppendS(buffer, FASTFETCH_TEXT_MODIFIER_RESET); } ================================================ FILE: src/common/impl/frequency.c ================================================ #include "common/frequency.h" bool ffFreqAppendNum(uint32_t mhz, FFstrbuf* result) { if (mhz == 0) return false; const FFOptionsDisplay* options = &instance.config.display; bool spaceBeforeUnit = options->freqSpaceBeforeUnit != FF_SPACE_BEFORE_UNIT_NEVER; int8_t ndigits = options->freqNdigits; if (ndigits >= 0) { ffStrbufAppendDouble(result, mhz / 1000., ndigits, true); if (spaceBeforeUnit) ffStrbufAppendC(result, ' '); ffStrbufAppendS(result, "GHz"); } else { ffStrbufAppendUInt(result, mhz); if (spaceBeforeUnit) ffStrbufAppendC(result, ' '); ffStrbufAppendS(result, "MHz"); } return true; } ================================================ FILE: src/common/impl/init.c ================================================ #include "fastfetch.h" #include "common/init.h" #include "common/parsing.h" #include "common/thread.h" #include "common/textModifier.h" #include "detection/displayserver/displayserver.h" #include "detection/terminaltheme/terminaltheme.h" #include "logo/logo.h" #include #include #include #ifdef _WIN32 #include #include "common/windows/unicode.h" #else #include #endif FFinstance instance; // Global singleton static void initState(FFstate* state) { state->logoWidth = 0; state->logoHeight = 0; state->keysHeight = 0; state->terminalLightTheme = false; state->titleFqdn = false; ffPlatformInit(&state->platform); state->dynamicInterval = 0; { // don't enable bright color if the terminal is in light mode FFTerminalThemeResult result; if (ffDetectTerminalTheme(&result, true /* forceEnv for performance */) && !result.bg.dark) state->terminalLightTheme = true; } } static void defaultConfig(void) { ffOptionsInitLogo(&instance.config.logo); ffOptionsInitGeneral(&instance.config.general); ffOptionsInitDisplay(&instance.config.display); } void ffInitInstance(void) { #ifdef _WIN32 // https://learn.microsoft.com/en-us/cpp/c-runtime-library/reference/setlocale-wsetlocale?source=recommendat> setlocale(LC_ALL, ".UTF8"); #else // Never use `setlocale(LC_ALL, "")` setlocale(LC_TIME, ""); #endif defaultConfig(); initState(&instance.state); } static volatile bool ffDisableLinewrap = true; static volatile bool ffHideCursor = true; static void resetConsole(void) { if(ffDisableLinewrap) fputs("\033[?7h", stdout); if(ffHideCursor) fputs("\033[?25h", stdout); #if defined(_WIN32) fflush(stdout); #endif if(instance.state.dynamicInterval > 0) fputs("\033[?1049l", stdout); // Disable alternate buffer } #ifdef _WIN32 BOOL WINAPI consoleHandler(FF_MAYBE_UNUSED DWORD signal) { resetConsole(); exit(0); } #else static void exitSignalHandler(FF_MAYBE_UNUSED int signal) { resetConsole(); exit(0); } #endif void ffStart(void) { ffDisableLinewrap = instance.config.display.disableLinewrap && !instance.config.display.pipe; ffHideCursor = instance.config.display.hideCursor && !instance.config.display.pipe; #ifdef _WIN32 SetErrorMode(SEM_FAILCRITICALERRORS); if (instance.config.display.noBuffer) setvbuf(stdout, NULL, _IONBF, 0); else setvbuf(stdout, NULL, _IOFBF, 4096); SetConsoleCtrlHandler(consoleHandler, TRUE); HANDLE hStdout = GetStdHandle(STD_OUTPUT_HANDLE); DWORD mode = 0; GetConsoleMode(hStdout, &mode); SetConsoleMode(hStdout, mode | ENABLE_PROCESSED_OUTPUT | ENABLE_VIRTUAL_TERMINAL_PROCESSING); SetConsoleOutputCP(CP_UTF8); #else if (instance.config.display.noBuffer) setvbuf(stdout, NULL, _IONBF, 0); struct sigaction action = { .sa_handler = exitSignalHandler }; sigaction(SIGINT, &action, NULL); sigaction(SIGTERM, &action, NULL); sigaction(SIGQUIT, &action, NULL); sigset_t newmask; sigemptyset(&newmask); sigaddset(&newmask, SIGCHLD); sigprocmask(SIG_BLOCK, &newmask, NULL); #endif //reset everything to default before we start printing if(!instance.config.display.pipe) fputs(FASTFETCH_TEXT_MODIFIER_RESET, stdout); if(ffHideCursor) fputs("\033[?25l", stdout); if(ffDisableLinewrap) fputs("\033[?7l", stdout); if(instance.state.dynamicInterval > 0) { fputs("\033[?1049h\033[H", stdout); // Enable alternate buffer fflush(stdout); } } void ffFinish(void) { resetConsole(); } static void destroyConfig(void) { ffOptionsDestroyLogo(&instance.config.logo); ffOptionsDestroyGeneral(&instance.config.general); ffOptionsDestroyDisplay(&instance.config.display); } static void destroyState(void) { ffPlatformDestroy(&instance.state.platform); } void ffDestroyInstance(void) { destroyConfig(); destroyState(); } //Must be in a file compiled with the libfastfetch target, because the FF_HAVE* macros are not defined for the executable targets void ffListFeatures(void) { fputs( #if FF_HAVE_THREADS "threads\n" #endif #if FF_HAVE_VULKAN "vulkan\n" #endif #if FF_HAVE_WAYLAND "wayland\n" #endif #if FF_HAVE_XCB_RANDR "xcb-randr\n" #endif #if FF_HAVE_XRANDR "xrandr\n" #endif #if FF_HAVE_DRM "drm\n" #endif #if FF_HAVE_DRM_AMDGPU "drm_amdgpu\n" #endif #if FF_HAVE_GIO "gio\n" #endif #if FF_HAVE_DCONF "dconf\n" #endif #if FF_HAVE_DBUS "dbus\n" #endif #if FF_HAVE_IMAGEMAGICK7 "imagemagick7\n" #endif #if FF_HAVE_IMAGEMAGICK6 "imagemagick6\n" #endif #if FF_HAVE_CHAFA "chafa\n" #endif #if FF_HAVE_ZLIB "zlib\n" #endif #if FF_HAVE_SQLITE3 "sqlite3\n" #endif #if FF_HAVE_RPM "rpm\n" #endif #if FF_HAVE_EGL "egl\n" #endif #if FF_HAVE_GLX "glx\n" #endif #if FF_HAVE_OPENCL "opencl\n" #endif #if FF_HAVE_FREETYPE "freetype\n" #endif #if FF_HAVE_PULSE "libpulse\n" #endif #if FF_HAVE_DDCUTIL "libddcutil\n" #endif #if FF_HAVE_ELF || __sun || (__FreeBSD__ && !__DragonFly__) || __OpenBSD__ || __NetBSD__ "libelf\n" #endif #if FF_HAVE_LIBZFS "libzfs\n" #endif #if FF_HAVE_DIRECTX_HEADERS "Directx Headers\n" #endif #if FF_USE_SYSTEM_YYJSON "System yyjson\n" #endif #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 #if FF_WIN81_COMPAT "Windows 8.1 Compatibility\n" #endif #if FF_APPLE_MEMSIZE_USABLE "Apple memsize_usable\n" #endif "" , stdout); } ================================================ FILE: src/common/impl/io_unix.c ================================================ #include "common/io.h" #include "fastfetch.h" #include "common/stringUtils.h" #include "common/time.h" #include #include #include #include #ifndef __APPLE__ #include #else #include #endif #if FF_HAVE_WORDEXP #include #else #include #endif static void createSubfolders(const char* fileName) { FF_STRBUF_AUTO_DESTROY path = ffStrbufCreate(); const char *token = NULL; while((token = strchr(fileName, '/')) != NULL) { ffStrbufAppendNS(&path, (uint32_t)(token - fileName + 1), fileName); mkdir(path.chars, S_IRWXU | S_IRGRP | S_IROTH); fileName = token + 1; } } bool ffWriteFileData(const char* fileName, size_t dataSize, const void* data) { int openFlagsModes = O_WRONLY | O_CREAT | O_TRUNC | O_CLOEXEC; mode_t openFlagsRights = S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH; int FF_AUTO_CLOSE_FD fd = open(fileName, openFlagsModes, openFlagsRights); if(fd == -1) { if (errno == ENOENT) { createSubfolders(fileName); fd = open(fileName, openFlagsModes, openFlagsRights); if(fd == -1) return false; } else return false; } return write(fd, data, dataSize) > 0; } static inline void readWithLength(int fd, FFstrbuf* buffer, uint32_t length) { ffStrbufEnsureFixedLengthFree(buffer, length); ssize_t bytesRead = 0; while( length > 0 && (bytesRead = read(fd, buffer->chars + buffer->length, length)) > 0 ) { buffer->length += (uint32_t) bytesRead; length -= (uint32_t) bytesRead; } } static inline void readUntilEOF(int fd, FFstrbuf* buffer) { ffStrbufEnsureFree(buffer, 31); uint32_t available = ffStrbufGetFree(buffer); ssize_t bytesRead = 0; while( (bytesRead = read(fd, buffer->chars + buffer->length, available)) > 0 ) { buffer->length += (uint32_t) bytesRead; if((uint32_t) bytesRead == available) ffStrbufEnsureFree(buffer, buffer->allocated - 1); // Doubles capacity every round. -1 for the null byte. available = ffStrbufGetFree(buffer); } } bool ffAppendFDBuffer(int fd, FFstrbuf* buffer) { struct stat fileInfo; if(fstat(fd, &fileInfo) != 0) return false; if (fileInfo.st_size > 0) readWithLength(fd, buffer, (uint32_t)fileInfo.st_size); else readUntilEOF(fd, buffer); buffer->chars[buffer->length] = '\0'; return buffer->length > 0; } ssize_t ffReadFileData(const char* fileName, size_t dataSize, void* data) { int FF_AUTO_CLOSE_FD fd = open(fileName, O_RDONLY | O_CLOEXEC); if(fd == -1) return -1; return ffReadFDData(fd, dataSize, data); } ssize_t ffReadFileDataRelative(int dfd, const char* fileName, size_t dataSize, void* data) { int FF_AUTO_CLOSE_FD fd = openat(dfd, fileName, O_RDONLY | O_CLOEXEC); if(fd == -1) return -1; return ffReadFDData(fd, dataSize, data); } bool ffAppendFileBuffer(const char* fileName, FFstrbuf* buffer) { int FF_AUTO_CLOSE_FD fd = open(fileName, O_RDONLY | O_CLOEXEC); if(fd == -1) return false; return ffAppendFDBuffer(fd, buffer); } bool ffAppendFileBufferRelative(int dfd, const char* fileName, FFstrbuf* buffer) { int FF_AUTO_CLOSE_FD fd = openat(dfd, fileName, O_RDONLY | O_CLOEXEC); if(fd == -1) return false; return ffAppendFDBuffer(fd, buffer); } bool ffPathExpandEnv(const char* in, FFstrbuf* out) { bool result = false; #if FF_HAVE_WORDEXP wordexp_t exp; if (wordexp(in, &exp, 0) != 0) // WARN: 0 = no safety flags; command substitution allowed return false; if (exp.we_wordc >= 1) { result = true; ffStrbufSetS(out, exp.we_wordv[exp.we_wordc > 1 ? ffTimeGetNow() % exp.we_wordc : 0]); } wordfree(&exp); #else glob_t gb; if (glob(in, GLOB_NOSORT #ifdef GLOB_TILDE | GLOB_TILDE #endif #ifdef GLOB_BRACE | GLOB_BRACE #endif , NULL, &gb) != 0) return false; if (gb.gl_pathc >= 1) { result = true; ffStrbufSetS(out, gb.gl_pathv[gb.gl_pathc > 1 ? ffTimeGetNow() % (unsigned) gb.gl_pathc : 0]); } globfree(&gb); #endif return result; } static int ftty = -1; static struct termios oldTerm; void restoreTerm(void) { tcsetattr(ftty, TCSAFLUSH, &oldTerm); } const char* ffGetTerminalResponse(const char* request, int nParams, const char* format, ...) { if (ftty < 0) { ftty = open("/dev/tty", O_RDWR | O_NOCTTY | O_CLOEXEC); if (ftty < 0) return "open(\"/dev/tty\", O_RDWR | O_NOCTTY | O_CLOEXEC) failed"; if(tcgetattr(ftty, &oldTerm) == -1) return "tcgetattr(STDIN_FILENO, &oldTerm) failed"; struct termios newTerm = oldTerm; newTerm.c_lflag &= (tcflag_t) ~(ICANON | ECHO); if(tcsetattr(ftty, TCSAFLUSH, &newTerm) == -1) return "tcsetattr(STDIN_FILENO, TCSAFLUSH, &newTerm)"; atexit(restoreTerm); } ffWriteFDData(ftty, strlen(request), request); //Give the terminal some time to respond #ifndef __APPLE__ if(poll(&(struct pollfd) { .fd = ftty, .events = POLLIN }, 1, FF_IO_TERM_RESP_WAIT_MS) <= 0) return "poll(/dev/tty) timeout or failed"; #else { // On macOS, poll(/dev/tty) always returns immediately // See also https://nathancraddock.com/blog/macos-dev-tty-polling/ fd_set rd; FD_ZERO(&rd); FD_SET(ftty, &rd); if(select(ftty + 1, &rd, NULL, NULL, &(struct timeval) { .tv_sec = FF_IO_TERM_RESP_WAIT_MS / 1000, .tv_usec = (FF_IO_TERM_RESP_WAIT_MS % 1000) * 1000 }) <= 0) return "select(/dev/tty) timeout or failed"; } #endif char buffer[1024]; size_t bytesRead = 0; va_list args; va_start(args, format); while (true) { ssize_t nRead = read(ftty, buffer + bytesRead, sizeof(buffer) - bytesRead - 1); if (nRead <= 0) { va_end(args); return "read(STDIN_FILENO, buffer, sizeof(buffer) - 1) failed"; } bytesRead += (size_t) nRead; buffer[bytesRead] = '\0'; va_list cargs; va_copy(cargs, args); int ret = vsscanf(buffer, format, cargs); va_end(cargs); if (ret <= 0) { va_end(args); return "vsscanf(buffer, format, args) failed"; } if (ret >= nParams) break; } va_end(args); return NULL; } bool ffSuppressIO(bool suppress) { #ifndef NDEBUG if (instance.config.display.debugMode) return false; #endif static bool init = false; static int origOut = -1; static int origErr = -1; static int nullFile = -1; if(!init) { if(!suppress) return true; origOut = dup(STDOUT_FILENO); origErr = dup(STDERR_FILENO); nullFile = open("/dev/null", O_WRONLY | O_CLOEXEC); init = true; } if(nullFile == -1) return false; fflush(stdout); fflush(stderr); dup2(suppress ? nullFile : origOut, STDOUT_FILENO); dup2(suppress ? nullFile : origErr, STDERR_FILENO); return true; } 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); if (dfd < 0) return; DIR* dir = fdopendir(dfd); if(dir == NULL) return; uint32_t folderLength = folder->length; if(pretty && folderName != NULL) { for(uint8_t i = 0; i < indentation - 1; i++) fputs(" | ", stdout); printf("%s/\n", folderName); } struct dirent* entry; while((entry = readdir(dir)) != NULL) { if(entry->d_name[0] == '.') // skip hidden files continue; bool isDir = false; #if !defined(__sun) && !defined(__HAIKU__) if(entry->d_type != DT_UNKNOWN && entry->d_type != DT_LNK) isDir = entry->d_type == DT_DIR; else #endif { struct stat stbuf; if (fstatat(dfd, entry->d_name, &stbuf, 0) < 0) isDir = false; else isDir = S_ISDIR(stbuf.st_mode); } if (isDir) { ffStrbufAppendS(folder, entry->d_name); ffStrbufAppendC(folder, '/'); listFilesRecursively(baseLength, folder, (uint8_t) (indentation + 1), entry->d_name, pretty); ffStrbufSubstrBefore(folder, folderLength); continue; } if (pretty) { for(uint8_t i = 0; i < indentation; i++) fputs(" | ", stdout); } else { fputs(folder->chars + baseLength, stdout); } puts(entry->d_name); } closedir(dir); } void ffListFilesRecursively(const char* path, bool pretty) { FF_STRBUF_AUTO_DESTROY folder = ffStrbufCreateS(path); ffStrbufEnsureEndsWithC(&folder, '/'); listFilesRecursively(folder.length, &folder, 0, NULL, pretty); } FFNativeFD ffGetNullFD(void) { static FFNativeFD hNullFile = -1; if (hNullFile != -1) return hNullFile; hNullFile = open("/dev/null", O_WRONLY | O_CLOEXEC); return hNullFile; } bool ffRemoveFile(const char* fileName) { return unlink(fileName) == 0; } ================================================ FILE: src/common/impl/io_windows.c ================================================ #include "fastfetch.h" #include "common/io.h" #include "common/stringUtils.h" #include "common/windows/nt.h" #include "common/windows/unicode.h" #include static bool createSubfolders(wchar_t* fileName) { HANDLE hRoot = ffGetPeb()->ProcessParameters->CurrentDirectory.Handle; bool closeRoot = false; wchar_t* ptr = fileName; // Absolute drive path: C:\... if (ffCharIsEnglishAlphabet((char)ptr[0]) && ptr[1] == L':' && ptr[2] == L'\\') { wchar_t saved = ptr[3]; ptr[3] = L'\0'; hRoot = CreateFileW( fileName, FILE_LIST_DIRECTORY | FILE_TRAVERSE | SYNCHRONIZE, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, NULL, OPEN_EXISTING, FILE_DIRECTORY_FILE | FILE_SYNCHRONOUS_IO_NONALERT | FILE_FLAG_BACKUP_SEMANTICS, NULL ); ptr[3] = saved; if (hRoot == INVALID_HANDLE_VALUE) return false; closeRoot = true; ptr += 3; // skip "C:\" } // UNC path: \\server\share\... else if (ptr[0] == L'\\' && ptr[1] == L'\\') { wchar_t* serverEnd = wcschr(ptr + 2, L'\\'); if (serverEnd == NULL) return false; wchar_t* shareEnd = wcschr(serverEnd + 1, L'\\'); if (shareEnd == NULL) return true; // no parent subfolder exists before file name wchar_t saved = *shareEnd; *shareEnd = L'\0'; hRoot = CreateFileW( fileName, FILE_LIST_DIRECTORY | FILE_TRAVERSE | SYNCHRONIZE, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, NULL, OPEN_EXISTING, FILE_DIRECTORY_FILE | FILE_SYNCHRONOUS_IO_NONALERT | FILE_FLAG_BACKUP_SEMANTICS, NULL ); *shareEnd = saved; if (hRoot == INVALID_HANDLE_VALUE) return false; closeRoot = true; ptr = shareEnd + 1; // first component under share } // Rooted path on current drive: \foo\bar else if (ptr[0] == L'\\') { UNICODE_STRING* dosPath = &ffGetPeb()->ProcessParameters->CurrentDirectory.DosPath; wchar_t driveRoot[] = { dosPath->Buffer[0], L':', L'\\', L'\0' }; hRoot = CreateFileW( driveRoot, FILE_LIST_DIRECTORY | FILE_TRAVERSE | SYNCHRONIZE, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, NULL, OPEN_EXISTING, FILE_DIRECTORY_FILE | FILE_SYNCHRONOUS_IO_NONALERT | FILE_FLAG_BACKUP_SEMANTICS, NULL ); if (hRoot == INVALID_HANDLE_VALUE) return false; closeRoot = true; ptr++; // skip leading '\' } while (true) { wchar_t* token = wcschr(ptr, L'\\'); if (token == NULL) break; // Skip empty path segments caused by duplicated '\' if (token == ptr) { ptr = token + 1; continue; } HANDLE hNew = INVALID_HANDLE_VALUE; IO_STATUS_BLOCK iosb = {}; NTSTATUS status = NtCreateFile( &hNew, FILE_LIST_DIRECTORY | FILE_TRAVERSE | SYNCHRONIZE, &(OBJECT_ATTRIBUTES) { .Length = sizeof(OBJECT_ATTRIBUTES), .RootDirectory = hRoot, .ObjectName = &(UNICODE_STRING) { .Buffer = ptr, .Length = (USHORT)((USHORT)(token - ptr) * sizeof(wchar_t)), .MaximumLength = (USHORT)((USHORT)(token - ptr) * sizeof(wchar_t)), }, .Attributes = OBJ_CASE_INSENSITIVE, }, &iosb, NULL, FILE_ATTRIBUTE_NORMAL, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, FILE_OPEN_IF, FILE_DIRECTORY_FILE | FILE_SYNCHRONOUS_IO_NONALERT, NULL, 0 ); if (!NT_SUCCESS(status)) { if (closeRoot && hRoot != INVALID_HANDLE_VALUE) NtClose(hRoot); return false; } if (closeRoot && hRoot != INVALID_HANDLE_VALUE) NtClose(hRoot); hRoot = hNew; closeRoot = true; ptr = token + 1; } if (closeRoot && hRoot != INVALID_HANDLE_VALUE) NtClose(hRoot); return true; } bool ffWriteFileData(const char* fileName, size_t dataSize, const void* data) { wchar_t fileNameW[MAX_PATH]; ULONG len = 0; if (!NT_SUCCESS(RtlUTF8ToUnicodeN(fileNameW, (ULONG) sizeof(fileNameW), &len, fileName, (ULONG)strlen(fileName) + 1))) return false; for (ULONG i = 0; i < len / sizeof(wchar_t); ++i) { if (fileNameW[i] == L'/') fileNameW[i] = L'\\'; } HANDLE FF_AUTO_CLOSE_FD handle = CreateFileW(fileNameW, GENERIC_WRITE, FILE_SHARE_WRITE, NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL); if (handle == INVALID_HANDLE_VALUE) { if (GetLastError() == ERROR_PATH_NOT_FOUND) { if (!createSubfolders(fileNameW)) return false; handle = CreateFileW(fileNameW, GENERIC_WRITE, FILE_SHARE_WRITE, NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL); if (handle == INVALID_HANDLE_VALUE) return false; } else return false; } DWORD written; return !!WriteFile(handle, data, (DWORD)dataSize, &written, NULL); } static inline void readWithLength(HANDLE handle, FFstrbuf* buffer, uint32_t length) { ffStrbufEnsureFree(buffer, length); DWORD bytesRead = 0; while( length > 0 && ReadFile(handle, buffer->chars + buffer->length, length, &bytesRead, NULL) != FALSE && bytesRead > 0 ) { buffer->length += (uint32_t) bytesRead; length -= (uint32_t) bytesRead; } } static inline void readUntilEOF(HANDLE handle, FFstrbuf* buffer) { ffStrbufEnsureFree(buffer, 31); uint32_t available = ffStrbufGetFree(buffer); DWORD bytesRead = 0; while( ReadFile(handle, buffer->chars + buffer->length, available, &bytesRead, NULL) != FALSE && bytesRead > 0 ) { buffer->length += (uint32_t) bytesRead; if((uint32_t) bytesRead == available) ffStrbufEnsureFree(buffer, buffer->allocated - 1); // Doubles capacity every round. -1 for the null byte. available = ffStrbufGetFree(buffer); } } bool ffAppendFDBuffer(HANDLE handle, FFstrbuf* buffer) { FILE_STANDARD_INFORMATION fileInfo; IO_STATUS_BLOCK iosb; if(!NT_SUCCESS(NtQueryInformationFile(handle, &iosb, &fileInfo, sizeof(fileInfo), FileStandardInformation))) fileInfo.EndOfFile.QuadPart = 0; if (fileInfo.EndOfFile.QuadPart > 0) readWithLength(handle, buffer, (uint32_t)fileInfo.EndOfFile.QuadPart); else readUntilEOF(handle, buffer); buffer->chars[buffer->length] = '\0'; return buffer->length > 0; } ssize_t ffReadFileData(const char* fileName, size_t dataSize, void* data) { HANDLE FF_AUTO_CLOSE_FD handle = CreateFileA(fileName, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL); if(handle == INVALID_HANDLE_VALUE) return -1; return ffReadFDData(handle, dataSize, data); } bool ffAppendFileBuffer(const char* fileName, FFstrbuf* buffer) { HANDLE FF_AUTO_CLOSE_FD handle = CreateFileA(fileName, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL); if(handle == INVALID_HANDLE_VALUE) return false; return ffAppendFDBuffer(handle, buffer); } HANDLE openatW(HANDLE dfd, const wchar_t* fileName, uint16_t fileNameLen, bool directory) { assert(fileNameLen <= 0x7FFF); HANDLE hFile; IO_STATUS_BLOCK iosb = {}; if(!NT_SUCCESS(NtOpenFile(&hFile, (directory ? FILE_LIST_DIRECTORY | FILE_TRAVERSE : FILE_READ_DATA | FILE_READ_EA) | FILE_READ_ATTRIBUTES | SYNCHRONIZE, &(OBJECT_ATTRIBUTES) { .Length = sizeof(OBJECT_ATTRIBUTES), .RootDirectory = dfd, .ObjectName = &(UNICODE_STRING) { .Buffer = (PWSTR) fileName, .Length = fileNameLen * (USHORT) sizeof(wchar_t), .MaximumLength = (fileNameLen + 1) * (USHORT) sizeof(wchar_t), }, .Attributes = OBJ_CASE_INSENSITIVE, }, &iosb, FILE_SHARE_READ | (directory ? FILE_SHARE_WRITE | FILE_SHARE_DELETE : 0), FILE_SYNCHRONOUS_IO_NONALERT | (directory ? FILE_DIRECTORY_FILE : FILE_NON_DIRECTORY_FILE) ))) return INVALID_HANDLE_VALUE; return hFile; } HANDLE openat(HANDLE dfd, const char* fileName, bool directory) { wchar_t fileNameW[MAX_PATH]; ULONG len; if (!NT_SUCCESS(RtlUTF8ToUnicodeN(fileNameW, (ULONG) sizeof(fileNameW), &len, fileName, (ULONG)strlen(fileName) + 1))) return INVALID_HANDLE_VALUE; // Implies `fileNameW[len] = L'\0';` and `len` includes the null terminator len /= sizeof(wchar_t); // convert from bytes to characters for (uint32_t i = 0; i < len - 1; ++i) { if (fileNameW[i] == L'/') fileNameW[i] = L'\\'; } return openatW(dfd, fileNameW, (uint16_t)(len - 1), directory); } bool ffAppendFileBufferRelative(HANDLE dfd, const char* fileName, FFstrbuf* buffer) { HANDLE FF_AUTO_CLOSE_FD fd = openat(dfd, fileName, false); if(fd == INVALID_HANDLE_VALUE) return false; return ffAppendFDBuffer(fd, buffer); } ssize_t ffReadFileDataRelative(HANDLE dfd, const char* fileName, size_t dataSize, void* data) { HANDLE FF_AUTO_CLOSE_FD fd = openat(dfd, fileName, false); if(fd == INVALID_HANDLE_VALUE) return -1; return ffReadFDData(fd, dataSize, data); } bool ffPathExpandEnv(const char* in, FFstrbuf* out) { if (in[0] == '~') { if ((in[1] == '/' || in[1] == '\\' || in[1] == '\0') && !ffStrContainsC(in, '%')) { ffStrbufSet(out, &instance.state.platform.homeDir); ffStrbufAppendS(out, in + 1); return true; } } wchar_t pathInW[MAX_PATH], pathOutW[MAX_PATH]; ULONG len = (ULONG) strlen(in); if (!NT_SUCCESS(RtlUTF8ToUnicodeN(pathInW, (ULONG) sizeof(pathInW), &len, in, len))) return false; len /= sizeof(wchar_t); // convert from bytes to characters size_t outLen; // in characters, including null terminator if (!NT_SUCCESS(RtlExpandEnvironmentStrings(NULL, pathInW, len, pathOutW, ARRAY_SIZE(pathOutW), &outLen))) return false; ffStrbufSetNWS(out, (uint32_t) outLen - 1, pathOutW); return true; } bool ffSuppressIO(bool suppress) { #ifndef NDEBUG if (instance.config.display.debugMode) return false; #endif static bool init = false; static HANDLE hOrigOut = INVALID_HANDLE_VALUE; static HANDLE hOrigErr = INVALID_HANDLE_VALUE; HANDLE hNullFile = ffGetNullFD(); static int fOrigOut = -1; static int fOrigErr = -1; static int fNullFile = -1; if (!init) { if(!suppress) return true; hOrigOut = GetStdHandle(STD_OUTPUT_HANDLE); hOrigErr = GetStdHandle(STD_ERROR_HANDLE); fOrigOut = _dup(STDOUT_FILENO); fOrigErr = _dup(STDERR_FILENO); fNullFile = _open_osfhandle((intptr_t) hNullFile, 0); init = true; } if (hNullFile == INVALID_HANDLE_VALUE || fNullFile == -1) return false; fflush(stdout); fflush(stderr); SetStdHandle(STD_OUTPUT_HANDLE, suppress ? hNullFile : hOrigOut); SetStdHandle(STD_ERROR_HANDLE, suppress ? hNullFile : hOrigErr); _dup2(suppress ? fNullFile : fOrigOut, STDOUT_FILENO); _dup2(suppress ? fNullFile : fOrigErr, STDERR_FILENO); return true; } void listFilesRecursively(uint32_t baseLength, FFstrbuf* folder, uint8_t indentation, const char* folderName, bool pretty) { uint32_t folderLength = folder->length; if(pretty && folderName != NULL) { for(uint8_t i = 0; i < indentation - 1; i++) fputs(" | ", stdout); printf("%s/\n", folderName); } ffStrbufAppendC(folder, '*'); WIN32_FIND_DATAA entry; HANDLE hFind = FindFirstFileA(folder->chars, &entry); ffStrbufTrimRight(folder, '*'); if(hFind == INVALID_HANDLE_VALUE) return; do { if (entry.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) { if(ffStrEquals(entry.cFileName, ".") || ffStrEquals(entry.cFileName, "..")) continue; ffStrbufSubstrBefore(folder, folderLength); ffStrbufAppendS(folder, entry.cFileName); ffStrbufAppendC(folder, '/'); listFilesRecursively(baseLength, folder, (uint8_t) (indentation + 1), entry.cFileName, pretty); ffStrbufSubstrBefore(folder, folderLength); continue; } if (pretty) { for(uint8_t i = 0; i < indentation; i++) fputs(" | ", stdout); } else { fputs(folder->chars + baseLength, stdout); } puts(entry.cFileName); } while (FindNextFileA(hFind, &entry)); FindClose(hFind); } void ffListFilesRecursively(const char* path, bool pretty) { FF_STRBUF_AUTO_DESTROY folder = ffStrbufCreateS(path); ffStrbufEnsureEndsWithC(&folder, '/'); listFilesRecursively(folder.length, &folder, 0, NULL, 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)) { hConin = CreateFileW(L"CONIN$", GENERIC_READ | GENERIC_WRITE, FILE_SHARE_READ, 0, OPEN_EXISTING, 0, NULL); hInput = hConin; } SetConsoleMode(hInput, 0); FlushConsoleInputBuffer(hInput); { DWORD bytes = 0; HANDLE hOutput = GetStdHandle(STD_OUTPUT_HANDLE); FF_AUTO_CLOSE_FD HANDLE hConout = INVALID_HANDLE_VALUE; DWORD outputMode; if (!GetConsoleMode(hOutput, &outputMode)) { hConout = CreateFileW(L"CONOUT$", GENERIC_READ | GENERIC_WRITE, FILE_SHARE_WRITE, 0, OPEN_EXISTING, 0, NULL); hOutput = hConout; } WriteFile(hOutput, request, (DWORD) strlen(request), &bytes, NULL); } while (true) { if (NtWaitForSingleObject(hInput, TRUE, &(LARGE_INTEGER) { .QuadPart = (int64_t) FF_IO_TERM_RESP_WAIT_MS * -10000 }) != STATUS_WAIT_0) { SetConsoleMode(hInput, inputMode); return "NtWaitForSingleObject() failed or timeout"; } // Ignore all unexpected input events INPUT_RECORD record; DWORD len = 0; if (!PeekConsoleInputW(hInput, &record, 1, &len)) break; if ( record.EventType == KEY_EVENT && record.Event.KeyEvent.uChar.UnicodeChar != L'\r' && record.Event.KeyEvent.uChar.UnicodeChar != L'\n' ) break; else ReadConsoleInputW(hInput, &record, 1, &len); } va_list args; va_start(args, format); char buffer[1024]; uint32_t bytesRead = 0; while (true) { DWORD bytes = 0; if (!ReadFile(hInput, buffer, sizeof(buffer) - 1, &bytes, NULL) || bytes == 0) { va_end(args); return "ReadFile() failed"; } bytesRead += bytes; buffer[bytesRead] = '\0'; va_list cargs; va_copy(cargs, args); int ret = vsscanf(buffer, format, cargs); va_end(cargs); if (ret <= 0) { va_end(args); return "vsscanf(buffer, format, args) failed"; } if (ret >= nParams) break; } SetConsoleMode(hInput, inputMode); va_end(args); return NULL; } FFNativeFD ffGetNullFD(void) { static FFNativeFD hNullFile = INVALID_HANDLE_VALUE; if (hNullFile != INVALID_HANDLE_VALUE) return hNullFile; hNullFile = CreateFileW( L"NUL", GENERIC_READ | GENERIC_WRITE, FILE_SHARE_WRITE, 0, OPEN_EXISTING, 0, &(SECURITY_ATTRIBUTES){ .nLength = sizeof(SECURITY_ATTRIBUTES), .lpSecurityDescriptor = NULL, .bInheritHandle = TRUE, }); return hNullFile; } bool ffRemoveFile(const char* fileName) { return DeleteFileA(fileName) != FALSE; } ================================================ FILE: src/common/impl/jsonconfig.c ================================================ #include "fastfetch.h" #include "common/color.h" #include "common/jsonconfig.h" #include "common/printing.h" #include "common/io.h" #include "common/time.h" #include "common/stringUtils.h" #include "detection/version/version.h" #include "modules/modules.h" #include #include #include bool ffJsonConfigParseModuleArgs(yyjson_val* key, yyjson_val* val, FFModuleArgs* moduleArgs) { if (unsafe_yyjson_equals_str(key, "type") || unsafe_yyjson_equals_str(key, "condition")) return true; if (unsafe_yyjson_equals_str(key, "key")) { ffStrbufSetJsonVal(&moduleArgs->key, val); return true; } else if (unsafe_yyjson_equals_str(key, "format")) { ffStrbufSetJsonVal(&moduleArgs->outputFormat, val); return true; } else if (unsafe_yyjson_equals_str(key, "outputColor")) { ffOptionParseColor(yyjson_get_str(val), &moduleArgs->outputColor); return true; } else if (unsafe_yyjson_equals_str(key, "keyColor")) { ffOptionParseColor(yyjson_get_str(val), &moduleArgs->keyColor); return true; } else if (unsafe_yyjson_equals_str(key, "keyWidth")) { moduleArgs->keyWidth = (uint32_t) yyjson_get_uint(val); return true; } else if (unsafe_yyjson_equals_str(key, "keyIcon")) { ffStrbufSetJsonVal(&moduleArgs->keyIcon, val); return true; } return false; } void ffJsonConfigGenerateModuleArgsConfig(yyjson_mut_doc* doc, yyjson_mut_val* module, FFModuleArgs* moduleArgs) { if (moduleArgs->key.length > 0) yyjson_mut_obj_add_strbuf(doc, module, "key", &moduleArgs->key); if (moduleArgs->outputFormat.length > 0) yyjson_mut_obj_add_strbuf(doc, module, "format", &moduleArgs->outputFormat); if (moduleArgs->outputColor.length > 0) yyjson_mut_obj_add_strbuf(doc, module, "outputColor", &moduleArgs->outputColor); if (moduleArgs->keyColor.length > 0) yyjson_mut_obj_add_strbuf(doc, module, "keyColor", &moduleArgs->keyColor); if (moduleArgs->keyWidth > 0) yyjson_mut_obj_add_uint(doc, module, "keyWidth", moduleArgs->keyWidth); if (moduleArgs->keyIcon.length > 0) yyjson_mut_obj_add_strbuf(doc, module, "keyIcon", &moduleArgs->keyIcon); } const char* ffJsonConfigParseEnum(yyjson_val* val, int* result, FFKeyValuePair pairs[]) { if (yyjson_is_int(val)) { int intVal = yyjson_get_int(val); for (const FFKeyValuePair* pPair = pairs; pPair->key; ++pPair) { if (intVal == pPair->value) { *result = pPair->value; return NULL; } } return "Invalid enum integer"; } else if (yyjson_is_str(val)) { const char* strVal = yyjson_get_str(val); for (const FFKeyValuePair* pPair = pairs; pPair->key; ++pPair) { if (ffStrEqualsIgnCase(strVal, pPair->key)) { *result = pPair->value; return NULL; } } return "Invalid enum string"; } else return "Invalid enum value type; must be a string or integer"; } static bool parseModuleJsonObject(const char* type, yyjson_val* jsonVal, yyjson_mut_doc* jsonDoc) { if(!ffCharIsEnglishAlphabet(type[0])) return false; for (FFModuleBaseInfo** modules = ffModuleInfos[toupper(type[0]) - 'A']; *modules; ++modules) { FFModuleBaseInfo* baseInfo = *modules; if (ffStrEqualsIgnCase(type, baseInfo->name)) { uint8_t optionBuf[FF_OPTION_MAX_SIZE]; baseInfo->initOptions(optionBuf); if (jsonVal) baseInfo->parseJsonObject(optionBuf, jsonVal); bool succeeded; if (jsonDoc) { yyjson_mut_val* module = yyjson_mut_arr_add_obj(jsonDoc, jsonDoc->root); yyjson_mut_obj_add_str(jsonDoc, module, "type", baseInfo->name); if (baseInfo->generateJsonResult) succeeded = baseInfo->generateJsonResult(optionBuf, jsonDoc, module); else { yyjson_mut_obj_add_str(jsonDoc, module, "error", "Unsupported for JSON format"); succeeded = false; } } else succeeded = baseInfo->printModule(optionBuf); baseInfo->destroyOptions(optionBuf); return succeeded; } } if (jsonDoc) { yyjson_mut_val* module = yyjson_mut_arr_add_obj(jsonDoc, jsonDoc->root); yyjson_mut_obj_add_strcpy(jsonDoc, module, "type", type); yyjson_mut_obj_add_str(jsonDoc, module, "error", "Unknown module type"); } else { FFModuleArgs moduleArgs; ffOptionInitModuleArg(&moduleArgs, ""); ffPrintError(type, 0, &moduleArgs, FF_PRINT_TYPE_DEFAULT, "Unknown module type"); ffOptionDestroyModuleArg(&moduleArgs); } return false; } 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)) { __attribute__((__cleanup__(ffDestroyCommandOptions))) FFCommandOptions options; ffInitCommandOptions(&options); if (module) ffCommandModuleInfo.parseJsonObject(&options, module); ffPrepareCommand(&options); } break; } case 'd': case 'D': { if (ffStrEqualsIgnCase(type, FF_DISKIO_MODULE_NAME)) { __attribute__((__cleanup__(ffDestroyDiskIOOptions))) FFDiskIOOptions options; ffInitDiskIOOptions(&options); if (module) ffDiskIOModuleInfo.parseJsonObject(&options, module); ffPrepareDiskIO(&options); } break; } case 'n': case 'N': { if (ffStrEqualsIgnCase(type, FF_NETIO_MODULE_NAME)) { __attribute__((__cleanup__(ffDestroyNetIOOptions))) FFNetIOOptions options; ffInitNetIOOptions(&options); if (module) ffNetIOModuleInfo.parseJsonObject(&options, module); ffPrepareNetIO(&options); } break; } case 'p': case 'P': { if (ffStrEqualsIgnCase(type, FF_PUBLICIP_MODULE_NAME)) { __attribute__((__cleanup__(ffDestroyPublicIpOptions))) FFPublicIPOptions options; ffInitPublicIpOptions(&options); if (module) ffPublicIPModuleInfo.parseJsonObject(&options, module); ffPreparePublicIp(&options); } break; } case 'w': case 'W': { if (ffStrEqualsIgnCase(type, FF_WEATHER_MODULE_NAME)) { __attribute__((__cleanup__(ffDestroyWeatherOptions))) FFWeatherOptions options; ffInitWeatherOptions(&options); if (module) ffWeatherModuleInfo.parseJsonObject(&options, module); ffPrepareWeather(&options); } break; } } } static bool matchesJsonArray(const char* str, yyjson_val* val) { assert(val); if (unsafe_yyjson_is_str(val)) return ffStrEqualsIgnCase(str, unsafe_yyjson_get_str(val)); if (!unsafe_yyjson_is_arr(val)) return false; size_t idx, max; yyjson_val* item; yyjson_arr_foreach(val, idx, max, item) { if (yyjson_is_str(item) && ffStrEqualsIgnCase(str, unsafe_yyjson_get_str(item))) return true; } return false; } static const char* printJsonConfig(FFdata* data, bool prepare) { yyjson_mut_doc* jsonDoc = data->resultDoc; yyjson_val* const root = yyjson_doc_get_root(data->configDoc); assert(root); if (!yyjson_is_obj(root)) return "Invalid JSON config format. Root value must be an object"; yyjson_val* modules = yyjson_obj_get(root, "modules"); if (!modules) return NULL; if (!yyjson_is_arr(modules)) return "Property 'modules' must be an array of strings or objects"; bool succeeded = true; int32_t thres = instance.config.display.stat; yyjson_val* item; size_t idx, max; yyjson_arr_foreach(modules, idx, max, item) { double ms = 0; if(!prepare && thres >= 0) ms = ffTimeGetTick(); yyjson_val* module = item; const char* type = yyjson_get_str(module); if (type) module = NULL; else if (yyjson_is_obj(module)) { yyjson_val* conditions = yyjson_obj_get(module, "condition"); if (conditions) { if (!yyjson_is_obj(conditions)) return "Property 'conditions' must be an object"; yyjson_val* system = yyjson_obj_get(conditions, "system"); if (system && !matchesJsonArray(ffVersionResult.sysName, system)) continue; system = yyjson_obj_get(conditions, "!system"); if (system && matchesJsonArray(ffVersionResult.sysName, system)) continue; yyjson_val* arch = yyjson_obj_get(conditions, "arch"); if (arch && !matchesJsonArray(ffVersionResult.architecture, arch)) continue; arch = yyjson_obj_get(conditions, "!arch"); if (arch && matchesJsonArray(ffVersionResult.architecture, arch)) continue; yyjson_val* previousSucceeded = yyjson_obj_get(conditions, "succeeded"); if (previousSucceeded && !unsafe_yyjson_is_null(previousSucceeded)) { if (!unsafe_yyjson_is_bool(previousSucceeded)) return "Property 'succeeded' in 'condition' must be a boolean"; if (succeeded != unsafe_yyjson_get_bool(previousSucceeded)) continue; } } type = yyjson_get_str(yyjson_obj_get(module, "type")); if (!type) return "module object must contain a \"type\" key ( case sensitive )"; if (yyjson_obj_size(module) == 1) // contains only Property type module = NULL; } else return "modules must be an array of strings or objects"; if (ffStrbufSeparatedContainIgnCaseS(&data->structureDisabled, type, ':')) continue; if(prepare) prepareModuleJsonObject(type, module); else succeeded = parseModuleJsonObject(type, module, jsonDoc); if(!prepare && thres >= 0) { ms = ffTimeGetTick() - ms; if (jsonDoc) { yyjson_mut_val* moduleJson = yyjson_mut_arr_get_last(jsonDoc->root); yyjson_mut_obj_add_real(jsonDoc, moduleJson, "stat", ms); } else { char str[64]; int len = snprintf(str, sizeof str, "%.3fms", ms); if (thres > 0) snprintf(str, sizeof str, "\e[%sm%.3fms\e[m", (ms <= thres ? FF_COLOR_FG_GREEN : ms <= 2 * thres ? FF_COLOR_FG_YELLOW : FF_COLOR_FG_RED), ms); printf("\e7\e[1A\e[9999999C\e[%dD%s\e8", len, str); // Save; Up 1; Right 9999999; Left ; Print ; Load } } #if defined(_WIN32) if (!instance.config.display.noBuffer && !jsonDoc) fflush(stdout); #endif } return NULL; } void ffPrintJsonConfig(FFdata* data, bool prepare) { yyjson_mut_doc* jsonDoc = data->resultDoc; const char* error = printJsonConfig(data, prepare); if (error) { if (jsonDoc) { yyjson_mut_val* obj = yyjson_mut_obj(jsonDoc); yyjson_mut_obj_add_str(jsonDoc, obj, "error", error); yyjson_mut_doc_set_root(jsonDoc, obj); } else ffPrintError("JsonConfig", 0, NULL, FF_PRINT_TYPE_NO_CUSTOM_KEY, "%s", error); } } ================================================ FILE: src/common/impl/kmod_apple.c ================================================ #include "common/kmod.h" #include "common/apple/cf_helpers.h" #include #include bool ffKmodLoaded(const char* modName) { FF_CFTYPE_AUTO_RELEASE CFStringRef name = CFStringCreateWithCString(kCFAllocatorDefault, modName, kCFStringEncodingUTF8); FF_CFTYPE_AUTO_RELEASE CFArrayRef identifiers = CFArrayCreate(kCFAllocatorDefault, (const void**) &name, 1, &kCFTypeArrayCallBacks); FF_CFTYPE_AUTO_RELEASE CFArrayRef keys = CFArrayCreate(kCFAllocatorDefault, NULL, 0, NULL); FF_CFTYPE_AUTO_RELEASE CFDictionaryRef kextInfo = KextManagerCopyLoadedKextInfo(identifiers, keys); return CFDictionaryContainsKey(kextInfo, name); } ================================================ FILE: src/common/impl/kmod_bsd.c ================================================ #include "common/kmod.h" #include #include bool ffKmodLoaded(const char* modName) { return modfind(modName) >= 0; } ================================================ FILE: src/common/impl/kmod_linux.c ================================================ #include "common/kmod.h" #include "common/io.h" bool ffKmodLoaded(const char* modName) { static FFstrbuf modules; if (modules.chars == NULL) { ffStrbufInitS(&modules, "\n"); ffAppendFileBuffer("/proc/modules", &modules); } if (modules.length == 0) return false; uint32_t len = (uint32_t) strlen(modName); if (len > 250) return false; char temp[256]; temp[0] = '\n'; memcpy(temp + 1, modName, len); temp[1 + len] = ' '; return memmem(modules.chars, modules.length, temp, len + 2) != NULL; } ================================================ FILE: src/common/impl/kmod_nbsd.c ================================================ #include "common/kmod.h" #include "common/stringUtils.h" #include #include typedef struct __attribute__((__packed__)) FFNbsdModList { int len; modstat_t mods[]; } FFNbsdModList; bool ffKmodLoaded(const char* modName) { static FFNbsdModList* list = NULL; if (list == NULL) { struct iovec iov = {}; for (size_t len = 8192;; len = iov.iov_len) { iov.iov_len = len; iov.iov_base = realloc(iov.iov_base, len); if (modctl(MODCTL_STAT, &iov) < 0) { free(iov.iov_base); return true; // ignore errors } if (len >= iov.iov_len) break; } list = (FFNbsdModList*) iov.iov_base; } for (int i = 0; i < list->len; i++) { if (ffStrEquals(list->mods[i].ms_name, modName)) return true; } return false; } ================================================ FILE: src/common/impl/kmod_nosupport.c ================================================ #include "common/kmod.h" bool ffKmodLoaded(FF_MAYBE_UNUSED const char* modName) { return true; // Don't generate kernel module related errors } ================================================ FILE: src/common/impl/kmod_sunos.c ================================================ #include "common/kmod.h" #include "common/stringUtils.h" #include #include bool ffKmodLoaded(const char* modName) { struct modinfo modinfo = { .mi_id = -1, .mi_nextid = -1, .mi_info = MI_INFO_ALL, }; for (int id = -1; modctl(MODINFO, id, &modinfo) == 0; id = modinfo.mi_id) { modinfo.mi_name[MODMAXNAMELEN - 1] = '\0'; if (ffStrEquals(modinfo.mi_name, modName)) return true; } return !(errno == EINVAL || errno == ENOENT); } ================================================ FILE: src/common/impl/kmod_windows.c ================================================ #include "common/kmod.h" #include "common/windows/nt.h" #include "common/mallocHelper.h" #include "common/stringUtils.h" bool ffKmodLoaded(const char* modName) { ULONG bufferSize = 0; NtQuerySystemInformation(SystemModuleInformation, NULL, 0, &bufferSize); if (bufferSize == 0) return true; // ignore errors FF_AUTO_FREE RTL_PROCESS_MODULES* buffer = malloc(bufferSize); if (!NT_SUCCESS(NtQuerySystemInformation(SystemModuleInformation, buffer, bufferSize, &bufferSize))) return true; // ignore errors for (ULONG i = 0; i < buffer->NumberOfModules; i++) { const char* name = (const char*) buffer->Modules[i].FullPathName + buffer->Modules[i].OffsetToFileName; if (ffStrEqualsIgnCase(name, modName)) return true; } return false; } ================================================ FILE: src/common/impl/library.c ================================================ #include "fastfetch.h" #include "common/library.h" #if _WIN32 #include "common/debug.h" #include "common/windows/nt.h" #include #include #endif #ifndef FF_DISABLE_DLOPEN #include //Clang doesn't define __SANITIZE_ADDRESS__ but defines __has_feature(address_sanitizer) #if !defined(__SANITIZE_ADDRESS__) && defined(__has_feature) #if __has_feature(address_sanitizer) #define __SANITIZE_ADDRESS__ #endif #endif #ifndef FF_DLOPEN_FLAGS #ifdef __SANITIZE_ADDRESS__ #define FF_DLOPEN_FLAGS RTLD_LAZY | RTLD_NODELETE #else #define FF_DLOPEN_FLAGS RTLD_LAZY #endif #endif static void* libraryLoad(const char* path, int maxVersion) { void* result = dlopen(path, FF_DLOPEN_FLAGS); #ifdef _WIN32 // libX.dll.1 never exists on Windows, while libX-1.dll may exist FF_UNUSED(maxVersion) if(result != NULL) return result; uint32_t pathLen = ffStrbufLastIndexC(&instance.state.platform.exePath, '/'); if (pathLen == instance.state.platform.exePath.length) return result; char absPath[MAX_PATH * 2]; strcpy(mempcpy(absPath, instance.state.platform.exePath.chars, pathLen + 1), path); return dlopen(absPath, FF_DLOPEN_FLAGS); #else if(result != NULL || maxVersion < 0) return result; FF_STRBUF_AUTO_DESTROY pathbuf = ffStrbufCreateA(64); ffStrbufAppendS(&pathbuf, path); ffStrbufAppendC(&pathbuf, '.'); for(int i = maxVersion; i >= 0; --i) { uint32_t originalLength = pathbuf.length; ffStrbufAppendSInt(&pathbuf, i); result = dlopen(pathbuf.chars, FF_DLOPEN_FLAGS); if(result != NULL) break; ffStrbufSubstrBefore(&pathbuf, originalLength); } #endif return result; } void* ffLibraryLoad(const char* path, int maxVersion, ...) { void* result = libraryLoad(path, maxVersion); if (!result) { va_list defaultNames; va_start(defaultNames, maxVersion); do { const char* pathRest = va_arg(defaultNames, const char*); if(pathRest == NULL) break; int maxVersionRest = va_arg(defaultNames, int); result = libraryLoad(pathRest, maxVersionRest); } while (!result); va_end(defaultNames); } return result; } #endif #if _WIN32 void* dlopen(const char* path, FF_MAYBE_UNUSED int mode) { wchar_t pathW[MAX_PATH + 1]; ULONG pathWBytes = 0; NTSTATUS status = RtlUTF8ToUnicodeN(pathW, sizeof(pathW), &pathWBytes, path, (uint32_t)strlen(path) + 1); if (!NT_SUCCESS(status)) { FF_DEBUG("RtlUTF8ToUnicodeN failed for path %s with status 0x%08lX: %s", path, status, ffDebugNtStatus(status)); return NULL; } PVOID module = NULL; status = LdrLoadDll(NULL, NULL, &(UNICODE_STRING) { .Length = (USHORT) pathWBytes - sizeof(wchar_t), // Exclude null terminator .MaximumLength = (USHORT) pathWBytes, .Buffer = pathW, }, &module); if (!NT_SUCCESS(status)) { FF_DEBUG("LdrLoadDll failed for path %s with status 0x%08lX: %s", path, status, ffDebugNtStatus(status)); return NULL; } return module; } int dlclose(void* handle) { NTSTATUS status = LdrUnloadDll(handle); if (!NT_SUCCESS(status)) { FF_DEBUG("LdrUnloadDll failed for handle %p with status 0x%08lX: %s", handle, status, ffDebugNtStatus(status)); return -1; } return 0; } void* dlsym(void* handle, const char* symbol) { void* address; USHORT symbolBytes = (USHORT) strlen(symbol) + 1; NTSTATUS status = LdrGetProcedureAddress(handle, &(ANSI_STRING) { .Length = symbolBytes - sizeof(char), .MaximumLength = symbolBytes, .Buffer = (char*) symbol, }, 0, &address); if (!NT_SUCCESS(status)) { FF_DEBUG("LdrGetProcedureAddress failed for symbol %s with status 0x%08lX: %s", symbol, status, ffDebugNtStatus(status)); return NULL; } return address; } 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); NTSTATUS status = LdrGetDllHandle(NULL, NULL, &(UNICODE_STRING) { .Length = libraryFileNameBytes - sizeof(wchar_t), .MaximumLength = libraryFileNameBytes, .Buffer = (wchar_t*) libraryFileName, }, &module); if (!NT_SUCCESS(status)) { FF_DEBUG("LdrGetDllHandle failed for library %ls with status 0x%08lX: %s", libraryFileName, status, ffDebugNtStatus(status)); return NULL; } return module; } #endif ================================================ FILE: src/common/impl/memrchr.c ================================================ #include "common/memrchr.h" #include #include void* memrchr(const void* s, int c, size_t n) { if (n == 0) return NULL; const uint8_t uc = (uint8_t) c; const uint8_t* p = (const uint8_t*) s + n; while (n--) { if (*--p == uc) return (void*) p; } return NULL; } ================================================ FILE: src/common/impl/netif.c ================================================ #include "common/netif.h" #ifndef _WIN32 #include #include #endif const FFNetifDefaultRouteResult* ffNetifGetDefaultRouteV4(void) { static FFNetifDefaultRouteResult result; if (result.status == FF_NETIF_UNINITIALIZED) { result.status = ffNetifGetDefaultRouteImplV4(&result) ? FF_NETIF_OK : FF_NETIF_INVALID; } return &result; } const FFNetifDefaultRouteResult* ffNetifGetDefaultRouteV6(void) { static FFNetifDefaultRouteResult result; if (result.status == FF_NETIF_UNINITIALIZED) { result.status = ffNetifGetDefaultRouteImplV6(&result) ? FF_NETIF_OK : FF_NETIF_INVALID; } return &result; } ================================================ FILE: src/common/impl/netif_apple.c ================================================ #include "common/netif.h" #include "common/io.h" #include "common/mallocHelper.h" #include #include #include #include #include #define ROUNDUP2(a, n) ((a) > 0 ? (1 + (((a) - 1U) | ((n) - 1))) : (n)) #if __APPLE__ // https://github.com/apple-oss-distributions/network_cmds/blob/8f38231438e6a4d16ef8015e97e12c2c05105644/rtsol.tproj/if.c#L243 #define ROUNDUP(a) ROUNDUP2((a), sizeof(uint32_t)) #elif __sun // https://github.com/illumos/illumos-gate/blob/95b8c88950fa7b19af46bc63230137cf96b0bff7/usr/src/cmd/cmd-inet/usr.sbin/route.c#L339 #define ROUNDUP(a) ROUNDUP2((a), sizeof(long)) #else #error unknown platform #endif static struct sockaddr * 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) return sa; #ifndef __sun uint32_t salen = sa->sa_len; #else uint32_t salen; // https://github.com/illumos/illumos-gate/blob/95b8c88950fa7b19af46bc63230137cf96b0bff7/usr/src/cmd/cmd-inet/usr.sbin/route.c#L2941 switch (sa->sa_family) { case AF_INET: salen = sizeof (struct sockaddr_in); break; case AF_LINK: salen = sizeof (struct sockaddr_dl); break; case AF_INET6: salen = sizeof (struct sockaddr_in6); break; default: salen = sizeof (struct sockaddr); break; } #endif sa = (struct sockaddr *)(ROUNDUP(salen) + (char *)sa); } } return NULL; } bool ffNetifGetDefaultRouteImplV4(FFNetifDefaultRouteResult* result) { //https://github.com/hashPirate/copenheimer-masscan-fork/blob/36f1ed9f7b751a7dccd5ed27874e2e703db7d481/src/rawsock-getif.c#L104 FF_AUTO_CLOSE_FD int pfRoute = socket(PF_ROUTE, SOCK_RAW, AF_INET); if (pfRoute < 0) return false; { struct timeval timeout = {1, 0}; setsockopt(pfRoute, SOL_SOCKET, SO_RCVTIMEO, (char *)&timeout, sizeof(timeout)); setsockopt(pfRoute, SOL_SOCKET, SO_SNDTIMEO, (char *)&timeout, sizeof(timeout)); } uint32_t pid = instance.state.platform.pid; struct { struct rt_msghdr hdr; struct sockaddr_in dst; uint8_t data[512]; } rtmsg = { .hdr = { .rtm_type = RTM_GET, .rtm_flags = RTF_UP | RTF_GATEWAY, .rtm_version = RTM_VERSION, .rtm_addrs = RTA_DST | RTA_IFP | RTA_IFA, .rtm_msglen = sizeof(rtmsg.hdr) + sizeof(rtmsg.dst), .rtm_pid = (pid_t) pid, .rtm_seq = 1, }, .dst = { .sin_family = AF_INET, #ifndef __sun .sin_len = sizeof(rtmsg.dst), #endif }, }; if (send(pfRoute, &rtmsg, rtmsg.hdr.rtm_msglen, 0) != rtmsg.hdr.rtm_msglen) return false; while (recv(pfRoute, &rtmsg, sizeof(rtmsg), 0) > 0 && !(rtmsg.hdr.rtm_seq == 1 && rtmsg.hdr.rtm_pid == (pid_t) pid)) ; #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)) #endif { struct sockaddr_dl* sdl = (struct sockaddr_dl *)get_rt_address(&rtmsg.hdr, RTA_IFP); if (sdl #ifndef __sun && sdl->sdl_len #endif ) { assert(sdl->sdl_nlen <= IF_NAMESIZE); memcpy(result->ifName, sdl->sdl_data, sdl->sdl_nlen); result->ifName[sdl->sdl_nlen] = '\0'; result->ifIndex = sdl->sdl_index; // Get the preferred source address struct sockaddr_in* src = (struct sockaddr_in*)get_rt_address(&rtmsg.hdr, RTA_IFA); if (src && src->sin_family == AF_INET) result->preferredSourceAddrV4 = src->sin_addr.s_addr; return true; } return false; } return false; } bool ffNetifGetDefaultRouteImplV6(FFNetifDefaultRouteResult* result) { //https://github.com/hashPirate/copenheimer-masscan-fork/blob/36f1ed9f7b751a7dccd5ed27874e2e703db7d481/src/rawsock-getif.c#L104 FF_AUTO_CLOSE_FD int pfRoute = socket(PF_ROUTE, SOCK_RAW, AF_INET6); if (pfRoute < 0) return false; { struct timeval timeout = {1, 0}; setsockopt(pfRoute, SOL_SOCKET, SO_RCVTIMEO, (char *)&timeout, sizeof(timeout)); setsockopt(pfRoute, SOL_SOCKET, SO_SNDTIMEO, (char *)&timeout, sizeof(timeout)); } uint32_t pid = instance.state.platform.pid; struct { struct rt_msghdr hdr; struct sockaddr_in6 dst; uint8_t data[512]; } rtmsg = { .hdr = { .rtm_type = RTM_GET, .rtm_flags = RTF_UP | RTF_GATEWAY, .rtm_version = RTM_VERSION, .rtm_addrs = RTA_DST | RTA_IFP, .rtm_msglen = sizeof(rtmsg.hdr) + sizeof(rtmsg.dst), .rtm_pid = (pid_t) pid, .rtm_seq = 2, }, .dst = { .sin6_family = AF_INET6, #ifndef __sun .sin6_len = sizeof(rtmsg.dst), #endif }, }; if (send(pfRoute, &rtmsg, rtmsg.hdr.rtm_msglen, 0) != rtmsg.hdr.rtm_msglen) return false; while (recv(pfRoute, &rtmsg, sizeof(rtmsg), 0) > 0 && !(rtmsg.hdr.rtm_seq == 2 && rtmsg.hdr.rtm_pid == (pid_t) pid)) ; #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)) #endif { struct sockaddr_dl* sdl = (struct sockaddr_dl *)get_rt_address(&rtmsg.hdr, RTA_IFP); if (sdl #ifndef __sun && sdl->sdl_len #endif ) { assert(sdl->sdl_nlen <= IF_NAMESIZE); memcpy(result->ifName, sdl->sdl_data, sdl->sdl_nlen); result->ifName[sdl->sdl_nlen] = '\0'; result->ifIndex = sdl->sdl_index; return true; } return false; } return false; } ================================================ FILE: src/common/impl/netif_bsd.c ================================================ #include "common/netif.h" #include "common/io.h" #include "common/mallocHelper.h" #include #include #include #include #include #include #define ROUNDUP2(a, n) ((a) > 0 ? (1 + (((a) - 1U) | ((n) - 1))) : (n)) #if __DragonFly__ // https://github.com/DragonFlyBSD/DragonFlyBSD/blob/cf0aa2f1e47a3f0a6055fe427563cb3f3e627064/sys/net/route.h#L315C9-L315C19 #define ROUNDUP(a) ROUNDUP2((a), sizeof(long)) #elif __FreeBSD__ // https://github.com/freebsd/freebsd-src/blob/e4c0ecba44b20ebb2e4d80978c2cb6d16b730cb9/sys/net/route.h#L368C9-L368C16 #define ROUNDUP(a) ROUNDUP2((a), sizeof(long)) #elif __NetBSD__ // https://github.com/NetBSD/src/blob/29beb637d057520c0ed37ac2cde966f7cc0cadf4/sys/net/route.h#L330 #define ROUNDUP(a) ROUNDUP2((a), sizeof(uint64_t)) #elif __OpenBSD__ // https://github.com/openbsd/src/blob/ca647cfa4ec3ccb8360714bc0ebc32a394f7fb6a/regress/sys/netinet/bindconnect/bindconnect.c#L250 #define ROUNDUP(a) ROUNDUP2((a), sizeof(long)) #else #error unknown platform #endif static struct sockaddr * 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) return sa; sa = (struct sockaddr *)(ROUNDUP(sa->sa_len) + (char *)sa); } } return NULL; } bool ffNetifGetDefaultRouteImplV4(FFNetifDefaultRouteResult* result) { int mib[6] = {CTL_NET, PF_ROUTE, 0, AF_INET, NET_RT_FLAGS, RTF_GATEWAY}; size_t needed; if (sysctl(mib, 6, NULL, &needed, NULL, 0) < 0 || needed == 0) return false; FF_AUTO_FREE char* buf = malloc(needed); if (sysctl(mib, 6, buf, &needed, NULL, 0) < 0) return false; char* lim = buf + needed; struct rt_msghdr* rtm; for (char* next = buf; next < lim; next += rtm->rtm_msglen) { rtm = (struct rt_msghdr *)next; struct sockaddr* sa = (struct sockaddr *)(rtm + 1); 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); memcpy(result->ifName, sdl->sdl_data, sdl->sdl_nlen); result->ifName[sdl->sdl_nlen] = '\0'; result->ifIndex = sdl->sdl_index; // Get the preferred source address struct sockaddr_in* src = (struct sockaddr_in*)get_rt_address(rtm, RTA_IFA); if (src && src->sin_family == AF_INET) result->preferredSourceAddrV4 = src->sin_addr.s_addr; return true; } } } return false; } bool ffNetifGetDefaultRouteImplV6(FFNetifDefaultRouteResult* result) { int mib[6] = {CTL_NET, PF_ROUTE, 0, AF_INET6, NET_RT_FLAGS, RTF_GATEWAY}; size_t needed; if (sysctl(mib, 6, NULL, &needed, NULL, 0) < 0 || needed == 0) return false; FF_AUTO_FREE char* buf = malloc(needed); if (sysctl(mib, 6, buf, &needed, NULL, 0) < 0) return false; char* lim = buf + needed; struct rt_msghdr* rtm; for (char* next = buf; next < lim; next += rtm->rtm_msglen) { rtm = (struct rt_msghdr *)next; struct sockaddr* sa = (struct sockaddr *)(rtm + 1); 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); memcpy(result->ifName, sdl->sdl_data, sdl->sdl_nlen); result->ifName[sdl->sdl_nlen] = '\0'; result->ifIndex = sdl->sdl_index; return true; } } } return false; } ================================================ FILE: src/common/impl/netif_gnu.c ================================================ #include "common/netif.h" #include "common/io.h" #include #include #define FF_STR_INDIR(x) #x #define FF_STR(x) FF_STR_INDIR(x) bool ffNetifGetDefaultRouteImplV4(FFNetifDefaultRouteResult* result) { FILE* FF_AUTO_CLOSE_FILE netRoute = fopen("/proc/route", "r"); if (!netRoute) return false; // skip first line FF_UNUSED(fscanf(netRoute, "%*[^\n]\n")); unsigned long long destination; //, gateway, flags, refCount, use, metric, mask, mtu, ... while (fscanf(netRoute, "%" FF_STR(IF_NAMESIZE) "s%llx%*[^\n]", result->ifName, &destination) == 2) { if (destination != 0) continue; result->ifIndex = if_nametoindex(result->ifName); // TODO: Get the preferred source address return true; } result->ifName[0] = '0'; return false; } bool ffNetifGetDefaultRouteImplV6(FFNetifDefaultRouteResult* result) { // TODO: AF_INET6 FF_UNUSED(result); return false; } ================================================ FILE: src/common/impl/netif_haiku.c ================================================ #include "common/netif.h" #include "common/io.h" #include "common/mallocHelper.h" #include #include #include #include #include #include // loosely based on Haiku's src/bin/network/route/route.cpp bool ffNetifGetDefaultRouteImplV4(FFNetifDefaultRouteResult* result) { FF_AUTO_CLOSE_FD int pfRoute = socket(AF_INET, SOCK_RAW, AF_INET); if (pfRoute < 0) return false; struct ifconf config; config.ifc_len = sizeof(config.ifc_value); if (ioctl(pfRoute, SIOCGRTSIZE, &config, sizeof(struct ifconf)) < 0) return false; int size = config.ifc_value; if (size <= 0) return false; FF_AUTO_FREE void *buffer = malloc((size_t) size); if (buffer == NULL) { return false; } config.ifc_len = size; config.ifc_buf = buffer; if (ioctl(pfRoute, SIOCGRTTABLE, &config, sizeof(struct ifconf)) < 0) return false; struct ifreq *interface = (struct ifreq*)buffer; struct ifreq *end = (struct ifreq*)((uint8_t*)buffer + size); while (interface < end) { if (interface->ifr_route.flags & RTF_DEFAULT) { // interface->ifr_metric? strlcpy(result->ifName, interface->ifr_name, IF_NAMESIZE); result->ifIndex = if_nametoindex(interface->ifr_name); if (interface->ifr_route.source) result->preferredSourceAddrV4 = ((struct sockaddr_in*)interface->ifr_route.source)->sin_addr.s_addr; return true; } size_t addressSize = 0; if (interface->ifr_route.destination != NULL) addressSize += interface->ifr_route.destination->sa_len; if (interface->ifr_route.mask != NULL) addressSize += interface->ifr_route.mask->sa_len; if (interface->ifr_route.gateway != NULL) addressSize += interface->ifr_route.gateway->sa_len; interface = (struct ifreq*)((addr_t)interface + IF_NAMESIZE + sizeof(struct route_entry) + addressSize); } return false; } bool ffNetifGetDefaultRouteImplV6(FFNetifDefaultRouteResult* result) { FF_AUTO_CLOSE_FD int pfRoute = socket(AF_INET, SOCK_RAW, AF_INET6); if (pfRoute < 0) return false; struct ifconf config; config.ifc_len = sizeof(config.ifc_value); if (ioctl(pfRoute, SIOCGRTSIZE, &config, sizeof(struct ifconf)) < 0) return false; int size = config.ifc_value; if (size <= 0) return false; FF_AUTO_FREE void *buffer = malloc((size_t) size); if (buffer == NULL) { return false; } config.ifc_len = size; config.ifc_buf = buffer; if (ioctl(pfRoute, SIOCGRTTABLE, &config, sizeof(struct ifconf)) < 0) return false; struct ifreq *interface = (struct ifreq*)buffer; struct ifreq *end = (struct ifreq*)((uint8_t*)buffer + size); while (interface < end) { if (interface->ifr_route.flags & RTF_DEFAULT) { strlcpy(result->ifName, interface->ifr_name, IF_NAMESIZE); result->ifIndex = if_nametoindex(interface->ifr_name); return true; } size_t addressSize = 0; if (interface->ifr_route.destination != NULL) addressSize += interface->ifr_route.destination->sa_len; if (interface->ifr_route.mask != NULL) addressSize += interface->ifr_route.mask->sa_len; if (interface->ifr_route.gateway != NULL) addressSize += interface->ifr_route.gateway->sa_len; interface = (struct ifreq*)((addr_t)interface + IF_NAMESIZE + sizeof(struct route_entry) + addressSize); } return false; } ================================================ FILE: src/common/impl/netif_linux.c ================================================ #include "common/netif.h" #include "common/io.h" #include "common/mallocHelper.h" #include "common/debug.h" #include #include #include bool ffNetifGetDefaultRouteImplV4(FFNetifDefaultRouteResult* result) { FF_DEBUG("Starting IPv4 default route detection"); FF_AUTO_CLOSE_FD int sock_fd = socket(AF_NETLINK, SOCK_RAW | SOCK_CLOEXEC, NETLINK_ROUTE); if (sock_fd < 0) { FF_DEBUG("Failed to create netlink socket: %s", strerror(errno)); return false; } FF_DEBUG("Created netlink socket: fd=%d", sock_fd); uint32_t pid = instance.state.platform.pid; FF_DEBUG("Process PID: %u", pid); // Bind socket struct sockaddr_nl addr = { .nl_family = AF_NETLINK, .nl_pid = 0, // Let kernel choose PID .nl_groups = 0, // No multicast groups }; if (bind(sock_fd, (struct sockaddr*)&addr, sizeof(addr)) < 0) { FF_DEBUG("Failed to bind socket: %s", strerror(errno)); return false; } FF_DEBUG("Successfully bound socket"); struct __attribute__((__packed__)) { struct nlmsghdr nlh; struct rtmsg rtm; struct rtattr rta; uint32_t table; } req = { // Netlink message header .nlh = { .nlmsg_len = sizeof(req), .nlmsg_type = RTM_GETROUTE, .nlmsg_flags = NLM_F_REQUEST | NLM_F_DUMP, .nlmsg_seq = 1, .nlmsg_pid = pid, }, // Route message .rtm = { .rtm_family = AF_INET, .rtm_dst_len = 0, // Match all destinations .rtm_src_len = 0, // Match all sources .rtm_tos = 0, .rtm_table = RT_TABLE_UNSPEC, .rtm_protocol = RTPROT_UNSPEC, .rtm_scope = RT_SCOPE_UNIVERSE, .rtm_type = RTN_UNSPEC, .rtm_flags = 0, }, // Route attribute for main table .rta = { .rta_len = RTA_LENGTH(sizeof(uint32_t)), .rta_type = RTA_TABLE, }, .table = RT_TABLE_MAIN, }; struct sockaddr_nl dest_addr = { .nl_family = AF_NETLINK, .nl_pid = 0, // Kernel .nl_groups = 0, // No multicast groups }; ssize_t sent = sendto(sock_fd, &req, sizeof(req), 0, (struct sockaddr*)&dest_addr, sizeof(dest_addr)); if (sent != sizeof(req)) { FF_DEBUG("Failed to send netlink request: sent=%zd, expected=%zu", sent, sizeof(req)); return false; } FF_DEBUG("Sent netlink request: %zd bytes", sent); struct sockaddr_nl src_addr = {}; socklen_t src_addr_len = sizeof(src_addr); uint8_t buffer[1024 * 16]; // 16 KB buffer should be sufficient uint32_t minMetric = UINT32_MAX; FF_MAYBE_UNUSED int routeCount = 0; while (true) { ssize_t received = recvfrom(sock_fd, buffer, sizeof(buffer), 0, (struct sockaddr*)&src_addr, &src_addr_len); if (received < 0) { FF_DEBUG("Failed to receive netlink response: %s", strerror(errno)); return false; } if (received >= (ssize_t)sizeof(buffer)) { FF_DEBUG("Received truncated message: received %zd, bufsize %zu", received, sizeof(buffer)); return false; } FF_DEBUG("Received netlink response: %zd bytes", received); if (received == 0) { FF_DEBUG("Received zero-length netlink response, ending processing"); break; } struct { uint32_t metric; uint32_t ifindex; uint32_t prefsrc; } entry; for (const struct nlmsghdr* nlh = (struct nlmsghdr*)buffer; NLMSG_OK(nlh, received); nlh = NLMSG_NEXT(nlh, received)) { if (nlh->nlmsg_seq != 1 || nlh->nlmsg_pid != pid) continue; if (nlh->nlmsg_type == NLMSG_DONE) { FF_DEBUG("Received NLMSG_DONE, processed %d routes", routeCount); goto exit; } if (nlh->nlmsg_type == NLMSG_ERROR) { FF_DEBUG("Netlink reports error: %s", strerror(-((struct nlmsgerr*)NLMSG_DATA(nlh))->error)); continue; } if (nlh->nlmsg_type != RTM_NEWROUTE) { FF_DEBUG("Skipping non-route message: type=%d", nlh->nlmsg_type); continue; } routeCount++; struct rtmsg* rtm = (struct rtmsg*)NLMSG_DATA(nlh); if (rtm->rtm_family != AF_INET) { FF_DEBUG("Skipping non-IPv4 route #%d (family=%d)", routeCount, rtm->rtm_family); continue; } if (rtm->rtm_dst_len != 0) { FF_DEBUG("Skipping non-default route #%d (dst_len=%d)", routeCount, rtm->rtm_dst_len); continue; } // Skip local/loopback routes if (rtm->rtm_scope == RT_SCOPE_HOST || rtm->rtm_type == RTN_LOCAL) { FF_DEBUG("Skipping local route #%d (scope=%d, type=%d)", routeCount, rtm->rtm_scope, rtm->rtm_type); continue; } FF_DEBUG("Processing IPv4 default route candidate #%d", routeCount); entry = (__typeof__(entry)) { }; // Default to zero metric (no RTA_PRIORITY found) // Parse route attributes size_t rtm_len = RTM_PAYLOAD(nlh); for (struct rtattr* rta = RTM_RTA(rtm); RTA_OK(rta, rtm_len); rta = RTA_NEXT(rta, rtm_len)) { if (RTA_PAYLOAD(rta) < sizeof(uint32_t)) continue; // Skip invalid attributes uint32_t rta_data = *(uint32_t*) RTA_DATA(rta); switch (rta->rta_type) { case RTA_DST: FF_DEBUG("Unexpected RTA_DST: %s (len=%u)", inet_ntoa((struct in_addr) { .s_addr = rta_data }), rtm->rtm_dst_len); goto next; case RTA_OIF: entry.ifindex = rta_data; FF_DEBUG("Found interface index: %u", entry.ifindex); break; case RTA_GATEWAY: FF_DEBUG("Found gateway: %s", inet_ntoa(*(struct in_addr*)&rta_data)); if (rta_data == 0) goto next; break; case RTA_PRIORITY: FF_DEBUG("Found metric: %u", rta_data); if (rta_data >= minMetric) goto next; entry.metric = rta_data; break; case RTA_PREFSRC: entry.prefsrc = rta_data; FF_DEBUG("Found preferred source: %s", inet_ntoa(*(struct in_addr*)&rta_data)); break; } } if (entry.ifindex == 0 || entry.metric >= minMetric) { next: FF_DEBUG("Skipping route: ifindex=%u, metric=%u", entry.ifindex, entry.metric); continue; } minMetric = entry.metric; result->ifIndex = entry.ifindex; FF_DEBUG("Updated best route: ifindex=%u, metric=%u, prefsrc=%x", entry.ifindex, entry.metric, entry.prefsrc); result->preferredSourceAddrV4 = entry.prefsrc; if (minMetric == 0) { FF_DEBUG("Found zero metric route, stopping further processing"); break; // Stop processing if we found a zero metric route } } } exit: if (minMetric < UINT32_MAX) { if_indextoname(result->ifIndex, result->ifName); FF_DEBUG("Found default IPv4 route: interface=%s, index=%u, metric=%u", result->ifName, result->ifIndex, minMetric); return true; } FF_DEBUG("No IPv4 default route found"); return false; } bool ffNetifGetDefaultRouteImplV6(FFNetifDefaultRouteResult* result) { FF_DEBUG("Starting IPv6 default route detection"); FF_AUTO_CLOSE_FD int sock_fd = socket(AF_NETLINK, SOCK_RAW | SOCK_CLOEXEC, NETLINK_ROUTE); if (sock_fd < 0) { FF_DEBUG("Failed to create netlink socket: %s", strerror(errno)); return false; } FF_DEBUG("Created netlink socket: fd=%d", sock_fd); uint32_t pid = instance.state.platform.pid; FF_DEBUG("Process PID: %u", pid); // Bind socket struct sockaddr_nl addr = { .nl_family = AF_NETLINK, .nl_pid = 0, // Let kernel choose PID .nl_groups = 0, // No multicast groups }; if (bind(sock_fd, (struct sockaddr*)&addr, sizeof(addr)) < 0) { FF_DEBUG("Failed to bind socket: %s", strerror(errno)); return false; } FF_DEBUG("Successfully bound socket"); struct __attribute__((__packed__)) { struct nlmsghdr nlh; struct rtmsg rtm; struct rtattr rta; uint32_t table; } req = { // Netlink message header .nlh = { .nlmsg_len = sizeof(req), .nlmsg_type = RTM_GETROUTE, .nlmsg_flags = NLM_F_REQUEST | NLM_F_DUMP, .nlmsg_seq = 1, .nlmsg_pid = pid, }, // Route message .rtm = { .rtm_family = AF_INET6, // IPv6 instead of IPv4 .rtm_dst_len = 0, // Match all destinations .rtm_src_len = 0, // Match all sources .rtm_tos = 0, .rtm_table = RT_TABLE_UNSPEC, .rtm_protocol = RTPROT_UNSPEC, .rtm_scope = RT_SCOPE_UNIVERSE, .rtm_type = RTN_UNSPEC, .rtm_flags = 0, }, // Route attribute for main table .rta = { .rta_len = RTA_LENGTH(sizeof(uint32_t)), .rta_type = RTA_TABLE, }, .table = RT_TABLE_MAIN, }; struct sockaddr_nl dest_addr = { .nl_family = AF_NETLINK, .nl_pid = 0, // Kernel .nl_groups = 0, // No multicast groups }; ssize_t sent = sendto(sock_fd, &req, sizeof(req), 0, (struct sockaddr*)&dest_addr, sizeof(dest_addr)); if (sent != sizeof(req)) { FF_DEBUG("Failed to send netlink request: sent=%zd, expected=%zu", sent, sizeof(req)); return false; } FF_DEBUG("Sent netlink request: %zd bytes", sent); struct sockaddr_nl src_addr = {}; socklen_t src_addr_len = sizeof(src_addr); uint8_t buffer[1024 * 16]; // 16 KB buffer should be sufficient uint32_t minMetric = UINT32_MAX; FF_MAYBE_UNUSED int routeCount = 0; while (true) { ssize_t received = recvfrom(sock_fd, buffer, sizeof(buffer), 0, (struct sockaddr*)&src_addr, &src_addr_len); if (received < 0) { FF_DEBUG("Failed to receive netlink response: %s", strerror(errno)); return false; } if (received >= (ssize_t)sizeof(buffer)) { FF_DEBUG("Received truncated message: received %zd, bufsize %zu", received, sizeof(buffer)); return false; } FF_DEBUG("Received netlink response: %zd bytes", received); if (received == 0) { FF_DEBUG("Received zero-length netlink response, ending processing"); break; } struct { uint32_t metric; uint32_t ifindex; } entry; for (const struct nlmsghdr* nlh = (struct nlmsghdr*)buffer; NLMSG_OK(nlh, received); nlh = NLMSG_NEXT(nlh, received)) { if (nlh->nlmsg_seq != 1 || nlh->nlmsg_pid != pid) continue; if (nlh->nlmsg_type == NLMSG_DONE) { FF_DEBUG("Received NLMSG_DONE, processed %d routes", routeCount); goto exit; } if (nlh->nlmsg_type == NLMSG_ERROR) { FF_DEBUG("Netlink reports error: %s", strerror(-((struct nlmsgerr*)NLMSG_DATA(nlh))->error)); continue; } if (nlh->nlmsg_type != RTM_NEWROUTE) { FF_DEBUG("Skipping non-route message: type=%d", nlh->nlmsg_type); continue; } routeCount++; struct rtmsg* rtm = (struct rtmsg*)NLMSG_DATA(nlh); if (rtm->rtm_family != AF_INET6) { FF_DEBUG("Skipping non-IPv6 route #%d (family=%d)", routeCount, rtm->rtm_family); continue; } if (rtm->rtm_dst_len != 0) { FF_DEBUG("Skipping non-default route #%d (dst_len=%d)", routeCount, rtm->rtm_dst_len); continue; } // Skip local/loopback routes if (rtm->rtm_scope == RT_SCOPE_HOST || rtm->rtm_type == RTN_LOCAL) { FF_DEBUG("Skipping local route #%d (scope=%d, type=%d)", routeCount, rtm->rtm_scope, rtm->rtm_type); continue; } FF_DEBUG("Processing IPv6 default route candidate #%d", routeCount); entry = (__typeof__(entry)) { }; // Default to zero metric (no RTA_PRIORITY found) // Parse route attributes size_t rtm_len = RTM_PAYLOAD(nlh); for (struct rtattr* rta = RTM_RTA(rtm); RTA_OK(rta, rtm_len); rta = RTA_NEXT(rta, rtm_len)) { switch (rta->rta_type) { case RTA_DST: if (RTA_PAYLOAD(rta) >= sizeof(struct in6_addr)) { FF_MAYBE_UNUSED char str[INET6_ADDRSTRLEN]; FF_DEBUG("Unexpected RTA_DST: %s", inet_ntop(AF_INET6, RTA_DATA(rta), str, sizeof(str))); goto next; } break; case RTA_OIF: if (RTA_PAYLOAD(rta) >= sizeof(uint32_t)) { entry.ifindex = *(uint32_t*) RTA_DATA(rta); FF_DEBUG("Found interface index: %u", entry.ifindex); } break; case RTA_GATEWAY: if (RTA_PAYLOAD(rta) >= sizeof(struct in6_addr)) { struct in6_addr* gw = (struct in6_addr*) RTA_DATA(rta); if (IN6_IS_ADDR_UNSPECIFIED(gw)) goto next; FF_MAYBE_UNUSED char str[INET6_ADDRSTRLEN]; FF_DEBUG("Found gateway: %s", inet_ntop(AF_INET6, gw, str, sizeof(str))); } break; case RTA_PRIORITY: if (RTA_PAYLOAD(rta) >= sizeof(uint32_t)) { uint32_t metric = *(uint32_t*) RTA_DATA(rta); FF_DEBUG("Found metric: %u", metric); if (metric >= minMetric) goto next; entry.metric = metric; } break; } } if (entry.ifindex == 0 || entry.metric >= minMetric) { next: FF_DEBUG("Skipping route: ifindex=%u, metric=%u", entry.ifindex, entry.metric); continue; } minMetric = entry.metric; result->ifIndex = entry.ifindex; FF_DEBUG("Updated best route: ifindex=%u, metric=%u", entry.ifindex, entry.metric); if (minMetric == 0) { FF_DEBUG("Found zero metric route, stopping further processing"); break; // Stop processing if we found a zero metric route } } } exit: if (minMetric < UINT32_MAX) { if_indextoname(result->ifIndex, result->ifName); FF_DEBUG("Found default IPv6 route: interface=%s, index=%u, metric=%u", result->ifName, result->ifIndex, minMetric); return true; } FF_DEBUG("No IPv6 default route found"); return false; } ================================================ FILE: src/common/impl/netif_windows.c ================================================ #include "common/netif.h" #include "common/mallocHelper.h" #include // AF_INET6, IN6_IS_ADDR_UNSPECIFIED #include bool ffNetifGetDefaultRouteImplV4(FFNetifDefaultRouteResult* result) { PMIB_IPFORWARD_TABLE2 pIpForwardTable = NULL; if (!NETIO_SUCCESS(GetIpForwardTable2(AF_UNSPEC, &pIpForwardTable))) return false; bool foundDefault = false; uint32_t smallestMetric = UINT32_MAX; for (ULONG i = 0; i < pIpForwardTable->NumEntries; ++i) { 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, }; if (NETIO_SUCCESS(GetIfEntry2(&ifRow)) && ifRow.OperStatus == IfOperStatusUp) { MIB_IPINTERFACE_ROW ipInterfaceRow = { .Family = AF_INET, .InterfaceIndex = row->InterfaceIndex, }; uint32_t realMetric = row->Metric /* Metric offset */; if (NETIO_SUCCESS(GetIpInterfaceEntry(&ipInterfaceRow))) realMetric += ipInterfaceRow.Metric /* Interface metric */; if (realMetric < smallestMetric) { smallestMetric = realMetric; result->ifIndex = row->InterfaceIndex; foundDefault = true; break; } } } } FreeMibTable(pIpForwardTable); return foundDefault; } bool ffNetifGetDefaultRouteImplV6(FFNetifDefaultRouteResult* result) { PMIB_IPFORWARD_TABLE2 pIpForwardTable = NULL; if (!NETIO_SUCCESS(GetIpForwardTable2(AF_UNSPEC, &pIpForwardTable))) return false; bool foundDefault = false; uint32_t smallestMetric = UINT32_MAX; for (ULONG i = 0; i < pIpForwardTable->NumEntries; ++i) { 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, }; if (NETIO_SUCCESS(GetIfEntry2(&ifRow)) && ifRow.OperStatus == IfOperStatusUp) { MIB_IPINTERFACE_ROW ipInterfaceRow = { .Family = AF_INET6, .InterfaceIndex = row->InterfaceIndex, }; uint32_t realMetric = row->Metric /* Metric offset */; if (NETIO_SUCCESS(GetIpInterfaceEntry(&ipInterfaceRow))) realMetric += ipInterfaceRow.Metric /* Interface metric */; if (realMetric < smallestMetric) { smallestMetric = realMetric; result->ifIndex = row->InterfaceIndex; foundDefault = true; } } } } FreeMibTable(pIpForwardTable); return foundDefault; } ================================================ FILE: src/common/impl/networking_common.c ================================================ #include "fastfetch.h" #include "common/library.h" #include "common/networking.h" #include "common/stringUtils.h" #include "common/debug.h" #ifdef FF_HAVE_ZLIB #include struct FFZlibLibrary { FF_LIBRARY_SYMBOL(inflateInit2_) FF_LIBRARY_SYMBOL(inflate) FF_LIBRARY_SYMBOL(inflateEnd) bool inited; } zlibData; const char* ffNetworkingLoadZlibLibrary(void) { if (!zlibData.inited) { zlibData.inited = true; FF_LIBRARY_LOAD_MESSAGE(zlib, #ifdef _WIN32 "zlib1" #else "libz" #endif FF_LIBRARY_EXTENSION, 2) FF_LIBRARY_LOAD_SYMBOL_VAR_MESSAGE(zlib, zlibData, inflateInit2_) FF_LIBRARY_LOAD_SYMBOL_VAR_MESSAGE(zlib, zlibData, inflate) FF_LIBRARY_LOAD_SYMBOL_VAR_MESSAGE(zlib, zlibData, inflateEnd) zlib = NULL; // don't auto dlclose } return zlibData.ffinflateEnd == NULL ? "Failed to load libz" : NULL; } // Try to pre-read gzip header to determine uncompressed size static uint32_t guessGzipOutputSize(const void* data, uint32_t dataSize) { // gzip file format: http://www.zlib.org/rfc-gzip.html if (dataSize < 10 || ((const uint8_t*)data)[0] != 0x1f || ((const uint8_t*)data)[1] != 0x8b) return 0; // Uncompressed size in gzip format is stored in the last 4 bytes, but only valid if data is less than 4GB if (dataSize > 18) { // Get ISIZE value from the end of file (little endian) const uint8_t* tail = (const uint8_t*)data + dataSize - 4; uint32_t uncompressedSize = (uint32_t) tail[0] | ((uint32_t) tail[1] << 8u) | ((uint32_t) tail[2] << 16u) | ((uint32_t) tail[3] << 24u); // For valid gzip files, this value is the length of the uncompressed data modulo 2^32 if (uncompressedSize > 0) { FF_DEBUG("Read uncompressed size from GZIP trailer: %u bytes", uncompressedSize); // Add some margin to the estimated size for safety return uncompressedSize + 64; } } // If unable to get size from trailer or size is 0, use estimated value // Typically, text data compression ratio is between 3-5x, we use the larger value uint32_t estimatedSize = dataSize * 5; FF_DEBUG("Unable to read exact uncompressed size, estimated as 5x of compressed data: %u bytes", estimatedSize); return estimatedSize; } // Decompress gzip content bool ffNetworkingDecompressGzip(FFstrbuf* buffer, char* headerEnd) { assert(headerEnd != NULL && *headerEnd == '\r'); // Calculate header size uint32_t headerSize = (uint32_t) (headerEnd - buffer->chars); *headerEnd = '\0'; // Replace delimiter with null character for easier processing // Ensure Content-Encoding is in response headers, not in response body bool hasGzip = strcasestr(buffer->chars, "\nContent-Encoding: gzip") != NULL; *headerEnd = '\r'; // Restore delimiter if (!hasGzip) { FF_DEBUG("No gzip compressed content detected, skipping decompression"); return true; } FF_DEBUG("Gzip compressed content detected, preparing for decompression"); const char* bodyStart = headerEnd + 4; // Skip delimiter if (buffer->length <= headerSize + 4) { // No content to decompress FF_DEBUG("Compressed content size is 0, skipping decompression"); return true; } // Calculate compressed content size uint32_t compressedSize = buffer->length - headerSize - 4; // Check if content is actually in gzip format (gzip header magic is 0x1f 0x8b) if (compressedSize < 2 || (uint8_t)bodyStart[0] != 0x1f || (uint8_t)bodyStart[1] != 0x8b) { FF_DEBUG("Content is not valid gzip format, skipping decompression"); return false; } // Predict uncompressed size uint32_t estimatedSize = guessGzipOutputSize(bodyStart, compressedSize); // Create decompression buffer with estimated size FF_STRBUF_AUTO_DESTROY decompressedBuffer = ffStrbufCreateA(estimatedSize > 0 ? estimatedSize : compressedSize * 5); FF_DEBUG("Created decompression buffer: %u bytes", decompressedBuffer.allocated); // Initialize decompression z_stream zs = { .zalloc = Z_NULL, .zfree = Z_NULL, .opaque = Z_NULL, .avail_in = (uInt)compressedSize, .next_in = (Bytef*)bodyStart, .avail_out = (uInt)ffStrbufGetFree(&decompressedBuffer), .next_out = (Bytef*)decompressedBuffer.chars, }; // Initialize decompression engine if (zlibData.ffinflateInit2_(&zs, 16 + MAX_WBITS, ZLIB_VERSION, (int)sizeof(z_stream)) != Z_OK) { FF_DEBUG("Failed to initialize decompression engine"); return false; } uInt availableOut = zs.avail_out; // Perform decompression int result = zlibData.ffinflate(&zs, Z_FINISH); // If output buffer is insufficient, try to extend buffer while (result == Z_BUF_ERROR || (result != Z_STREAM_END && zs.avail_out == 0)) { FF_DEBUG("Output buffer insufficient, trying to extend"); // Save already decompressed data amount uint32_t alreadyDecompressed = (uint32_t)(availableOut - zs.avail_out); decompressedBuffer.length += alreadyDecompressed; decompressedBuffer.chars[decompressedBuffer.length] = '\0'; ffStrbufEnsureFree(&decompressedBuffer, decompressedBuffer.length / 2); // Set output parameters to point to new buffer zs.avail_out = (uInt)ffStrbufGetFree(&decompressedBuffer); zs.next_out = (Bytef*)(decompressedBuffer.chars + decompressedBuffer.length); availableOut = zs.avail_out; // Decompress again result = zlibData.ffinflate(&zs, Z_FINISH); } zlibData.ffinflateEnd(&zs); // Calculate decompressed size 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); char* line = NULL; size_t len = 0; while (ffStrbufGetline(&line, &len, buffer)) { if (ffStrStartsWithIgnCase(line, "Content-Encoding:")) { continue; } else if (ffStrStartsWithIgnCase(line, "Content-Length:")) { ffStrbufAppendF(&newBuffer, "Content-Length: %u\r\n", decompressedBuffer.length); continue; } else if (line[0] == '\r') { ffStrbufAppendS(&newBuffer, "\r\n"); ffStrbufGetlineRestore(&line, &len, buffer); break; } ffStrbufAppendS(&newBuffer, line); // Including the trailing \r ffStrbufAppendC(&newBuffer, '\n'); } ffStrbufAppend(&newBuffer, &decompressedBuffer); ffStrbufDestroy(buffer); ffStrbufInitMove(buffer, &newBuffer); return true; } #endif // FF_HAVE_ZLIB ================================================ FILE: src/common/impl/networking_linux.c ================================================ #include "fastfetch.h" #include "common/networking.h" #include "common/time.h" #include "common/library.h" #include "common/stringUtils.h" #include "common/mallocHelper.h" #include "common/debug.h" #include #include #include #include #include #include // For FreeBSD #include #include #include static const char* tryNonThreadingFastPath(FFNetworkingState* state) { #if defined(TCP_FASTOPEN) || __APPLE__ if (!state->tfo) { #if __linux__ || __GNU__ // Linux doesn't support sendto() on unconnected sockets FF_DEBUG("TCP Fast Open disabled, skipping"); return "TCP Fast Open disabled"; #endif } else { FF_DEBUG("Attempting to use TCP Fast Open to connect"); #ifndef __APPLE__ // On macOS, TCP_FASTOPEN doesn't seem to be needed // Set TCP Fast Open int flag = 1; if (setsockopt(state->sockfd, IPPROTO_TCP, #ifdef __APPLE__ // https://github.com/rust-lang/libc/pull/3135 0x218 // TCP_FASTOPEN_FORCE_ENABLE #else TCP_FASTOPEN #endif , &flag, sizeof(flag)) != 0) { FF_DEBUG("Failed to set TCP_FASTOPEN option: %s", strerror(errno)); return "setsockopt(TCP_FASTOPEN) failed"; } else { #if __linux__ || __GNU__ FF_DEBUG("Successfully set TCP_FASTOPEN option, queue length: %d", flag); #elif defined(__APPLE__) FF_DEBUG("Successfully set TCP_FASTOPEN_FORCE_ENABLE option"); #else FF_DEBUG("Successfully set TCP_FASTOPEN option"); #endif } #endif } #ifndef __APPLE__ FF_DEBUG("Using sendto() + MSG_DONTWAIT to send %u bytes of data", state->command.length); ssize_t sent = sendto(state->sockfd, state->command.chars, state->command.length, #ifdef MSG_FASTOPEN MSG_FASTOPEN | #endif #ifdef MSG_NOSIGNAL MSG_NOSIGNAL | #endif MSG_DONTWAIT, state->addr->ai_addr, state->addr->ai_addrlen); #else if (fcntl(state->sockfd, F_SETFL, O_NONBLOCK) == -1) { FF_DEBUG("fcntl(F_SETFL) failed: %s", strerror(errno)); return "fcntl(F_SETFL) failed"; } FF_DEBUG("Using connectx() to send %u bytes of data", state->command.length); // Use connectx to establish connection and send data in one call size_t sent; if (connectx(state->sockfd, &(sa_endpoints_t) { .sae_dstaddr = state->addr->ai_addr, .sae_dstaddrlen = state->addr->ai_addrlen, }, SAE_ASSOCID_ANY, state->tfo ? CONNECT_DATA_IDEMPOTENT : 0, &(struct iovec) { .iov_base = state->command.chars, .iov_len = state->command.length, }, 1, &sent, NULL) != 0) sent = 0; if (fcntl(state->sockfd, F_SETFL, 0) == -1) { FF_DEBUG("fcntl(F_SETFL) failed: %s", strerror(errno)); return "fcntl(F_SETFL) failed"; } #endif if (sent > 0 || (errno == EAGAIN || errno == EWOULDBLOCK #ifdef __APPLE__ // On macOS EINPROGRESS means the connection cannot be completed immediately // On Linux, it means the TFO cookie is not available locally || errno == EINPROGRESS #endif )) { FF_DEBUG( #ifdef __APPLE__ "connectx()" #else "sendto()" #endif " %s (sent=%zd, errno=%d: %s)", errno == 0 ? "succeeded" : "was in progress", sent, errno, strerror(errno)); freeaddrinfo(state->addr); state->addr = NULL; ffStrbufDestroy(&state->command); return NULL; } FF_DEBUG( #ifdef __APPLE__ "connectx()" #else "sendto()" #endif " failed: %s (errno=%d)", strerror(errno), errno); #ifdef __APPLE__ return "connectx() failed"; #else return "sendto() failed"; #endif #else FF_UNUSED(state); return "TFO support is not available"; #endif } // Traditional connect and send function static const char* connectAndSend(FFNetworkingState* state) { const char* ret = NULL; FF_DEBUG("Using traditional connection method to connect"); FF_DEBUG("Attempting connect() to server..."); if(connect(state->sockfd, state->addr->ai_addr, state->addr->ai_addrlen) == -1) { FF_DEBUG("connect() failed: %s (errno=%d)", strerror(errno), errno); ret = "connect() failed"; goto error; } FF_DEBUG("connect() succeeded"); FF_DEBUG("Attempting to send %u bytes of data...", state->command.length); if(send(state->sockfd, state->command.chars, state->command.length, 0) < 0) { FF_DEBUG("send() failed: %s (errno=%d)", strerror(errno), errno); ret = "send() failed"; goto error; } FF_DEBUG("Data sent successfully"); goto exit; error: FF_DEBUG("Error occurred, closing socket"); close(state->sockfd); state->sockfd = -1; exit: FF_DEBUG("Releasing address info and other resources"); freeaddrinfo(state->addr); state->addr = NULL; ffStrbufDestroy(&state->command); return ret; } FF_THREAD_ENTRY_DECL_WRAPPER(connectAndSend, FFNetworkingState*); // Parallel DNS resolution and socket creation static const char* initNetworkingState(FFNetworkingState* state, const char* host, const char* path, const char* headers) { FF_DEBUG("Initializing network connection state: host=%s, path=%s", host, path); // Initialize command and host information ffStrbufInitA(&state->command, 128); ffStrbufAppendS(&state->command, "GET "); ffStrbufAppendS(&state->command, path); ffStrbufAppendS(&state->command, " HTTP/1.0\r\nHost: "); ffStrbufAppendS(&state->command, host); ffStrbufAppendS(&state->command, "\r\nConnection: close\r\n"); // Explicitly tell the server we don't need to keep the connection // If compression needs to be enabled if (state->compression) { FF_DEBUG("Enabling HTTP content compression"); ffStrbufAppendS(&state->command, "Accept-Encoding: gzip\r\n"); } ffStrbufAppendS(&state->command, headers); ffStrbufAppendS(&state->command, "\r\n"); #ifdef FF_HAVE_THREADS state->thread = 0; FF_DEBUG("Thread ID initialized to 0"); #endif const char* ret = NULL; struct addrinfo hints = { .ai_family = state->ipv6 ? AF_INET6 : AF_INET, .ai_socktype = SOCK_STREAM, .ai_flags = AI_NUMERICSERV }; FF_DEBUG("Resolving address: %s (%s)", host, state->ipv6 ? "IPv6" : "IPv4"); // Use AI_NUMERICSERV flag to indicate the service is a numeric port, reducing parsing time int gaiRes = getaddrinfo(host, "80", &hints, &state->addr); if(gaiRes != 0) { FF_DEBUG("getaddrinfo() failed: %s (res=%d)", gai_strerror(gaiRes), gaiRes); ret = "getaddrinfo() failed"; goto error; } FF_DEBUG("Address resolution successful"); FF_DEBUG("Creating socket"); state->sockfd = socket(state->addr->ai_family, state->addr->ai_socktype, state->addr->ai_protocol); if(state->sockfd == -1) { FF_DEBUG("socket() failed: %s (errno=%d)", strerror(errno), errno); ret = "socket() failed"; goto error; } FF_DEBUG("Socket creation successful: fd=%d", state->sockfd); int flag = 1; #ifdef TCP_NODELAY // Disable Nagle's algorithm to reduce small packet transmission delay if (setsockopt(state->sockfd, IPPROTO_TCP, TCP_NODELAY, &flag, sizeof(flag)) != 0) { FF_DEBUG("Failed to set TCP_NODELAY: %s", strerror(errno)); } else { FF_DEBUG("Successfully disabled Nagle's algorithm"); } #endif #ifdef TCP_QUICKACK // Set TCP_QUICKACK option to avoid delayed acknowledgments if (setsockopt(state->sockfd, IPPROTO_TCP, TCP_QUICKACK, &flag, sizeof(flag)) != 0) { FF_DEBUG("Failed to set TCP_QUICKACK: %s", strerror(errno)); } else { FF_DEBUG("Successfully enabled TCP quick acknowledgment"); } #endif if (state->timeout > 0) { FF_DEBUG("Setting connection timeout: %u ms", state->timeout); FF_MAYBE_UNUSED uint32_t sec = state->timeout / 1000; if (sec == 0) sec = 1; #ifdef TCP_CONNECTIONTIMEOUT FF_DEBUG("Using TCP_CONNECTIONTIMEOUT: %u seconds", sec); setsockopt(state->sockfd, IPPROTO_TCP, TCP_CONNECTIONTIMEOUT, &sec, sizeof(sec)); #elif defined(TCP_KEEPINIT) FF_DEBUG("Using TCP_KEEPINIT: %u seconds", sec); setsockopt(state->sockfd, IPPROTO_TCP, TCP_KEEPINIT, &sec, sizeof(sec)); #elif defined(TCP_USER_TIMEOUT) FF_DEBUG("Using TCP_USER_TIMEOUT: %u milliseconds", state->timeout); setsockopt(state->sockfd, IPPROTO_TCP, TCP_USER_TIMEOUT, &state->timeout, sizeof(state->timeout)); #else FF_DEBUG("Current platform does not support TCP connection timeout"); #endif } return NULL; error: FF_DEBUG("Error occurred during initialization"); if (state->addr != NULL) { FF_DEBUG("Releasing address information"); freeaddrinfo(state->addr); state->addr = NULL; } if (state->sockfd > 0) { FF_DEBUG("Closing socket: fd=%d", state->sockfd); close(state->sockfd); state->sockfd = -1; } return ret; } const char* ffNetworkingSendHttpRequest(FFNetworkingState* state, const char* host, const char* path, const char* headers) { FF_DEBUG("Preparing to send HTTP request: host=%s, path=%s", host, path); if (state->compression) { FF_DEBUG("Compression enabled, checking if zlib is available"); #ifdef FF_HAVE_ZLIB const char* zlibError = ffNetworkingLoadZlibLibrary(); // Only enable compression if zlib library is successfully loaded if (zlibError == NULL) { FF_DEBUG("Successfully loaded zlib library, compression enabled"); } else { FF_DEBUG("Failed to load zlib library, compression disabled: %s", zlibError); state->compression = false; } #else FF_DEBUG("zlib not supported at build time, compression disabled"); state->compression = false; #endif } else { FF_DEBUG("Compression disabled"); } const char* initResult = initNetworkingState(state, host, path, headers); if (initResult != NULL) { FF_DEBUG("Initialization failed: %s", initResult); return initResult; } FF_DEBUG("Network state initialization successful"); const char* tfoResult = tryNonThreadingFastPath(state); if (tfoResult == NULL) { FF_DEBUG("TryNonThreadingFastPath() succeeded or in progress"); return NULL; } FF_DEBUG("TryNonThreadingFastPath() failed: %s, trying traditional connection", tfoResult); #ifdef FF_HAVE_THREADS if (instance.config.general.multithreading) { FF_DEBUG("Multithreading mode enabled, creating connection thread"); state->thread = ffThreadCreate(connectAndSendThreadMain, state); if (state->thread) { FF_DEBUG("Thread creation successful: thread=%p", (void*)(uintptr_t)state->thread); return NULL; } FF_DEBUG("Thread creation failed"); } else { FF_DEBUG("Multithreading mode disabled, connecting in main thread"); } #endif return connectAndSend(state); } const char* ffNetworkingRecvHttpResponse(FFNetworkingState* state, FFstrbuf* buffer) { assert(buffer->allocated > 0); FF_DEBUG("Preparing to receive HTTP response"); uint32_t timeout = state->timeout; #ifdef FF_HAVE_THREADS if (state->thread) { FF_DEBUG("Connection thread is running, waiting for it to complete (timeout=%u ms)", timeout); if (!ffThreadJoin(state->thread, timeout)) { FF_DEBUG("Thread join failed or timed out"); return "ffThreadJoin() failed or timeout"; } FF_DEBUG("Thread completed successfully"); state->thread = 0; } #endif if(state->sockfd == -1) { FF_DEBUG("Invalid socket, HTTP request might have failed"); return "ffNetworkingSendHttpRequest() failed"; } // Set larger initial receive buffer instead of small repeated receives int rcvbuf = 65536; // 64KB setsockopt(state->sockfd, SOL_SOCKET, SO_RCVBUF, &rcvbuf, sizeof(rcvbuf)); #ifdef __APPLE__ // poll for the socket to be readable. // Because of the non-blocking connectx() call, the connection might not be established yet FF_DEBUG("Using poll() to check if socket is readable"); { int pollRes = poll(&(struct pollfd) { .fd = state->sockfd, .events = POLLIN }, 1, timeout > 0 ? (int) timeout : -1); if (pollRes == 0) { FF_DEBUG("poll() timed out after %u ms", timeout); close(state->sockfd); state->sockfd = -1; return "poll() timeout"; } else if (pollRes == -1) { FF_DEBUG("poll() failed: %s (errno=%d)", strerror(errno), errno); close(state->sockfd); state->sockfd = -1; return "poll() failed"; } } FF_DEBUG("Socket is readable, proceeding to receive data"); #else if(timeout > 0) { FF_DEBUG("Setting receive timeout: %u ms", timeout); struct timeval timev; timev.tv_sec = timeout / 1000; timev.tv_usec = (__typeof__(timev.tv_usec)) ((timeout % 1000) * 1000); //milliseconds to microseconds setsockopt(state->sockfd, SOL_SOCKET, SO_RCVTIMEO, &timev, sizeof(timev)); } #endif FF_DEBUG("Starting data reception"); FF_MAYBE_UNUSED int recvCount = 0; uint32_t contentLength = 0; uint32_t headerEnd = 0; do { FF_DEBUG("Data reception loop #%d, current buffer size: %u, available space: %u", ++recvCount, buffer->length, ffStrbufGetFree(buffer)); // We set `Connection: close`, so the server will close the connection when done. // Thus we can use MSG_WAITALL to wait until the buffer is full or the connection is closed. ssize_t received = recv(state->sockfd, buffer->chars + buffer->length, ffStrbufGetFree(buffer), MSG_WAITALL); if (received <= 0) { if (received == 0) { FF_DEBUG("Connection closed (received=0)"); } else { FF_DEBUG("Reception failed: %s (errno=%d)", strerror(errno), errno); } break; } buffer->length += (uint32_t) received; buffer->chars[buffer->length] = '\0'; FF_DEBUG("Successfully received %zd bytes of data, total: %u bytes", received, buffer->length); // Check if HTTP header end marker is found if (headerEnd == 0) { char* pHeaderEnd = memmem(buffer->chars, buffer->length, "\r\n\r\n", 4); if (pHeaderEnd) { headerEnd = (uint32_t)(pHeaderEnd - buffer->chars); FF_DEBUG("Found HTTP header end marker, position: %u", headerEnd); // 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); if (contentLength > 0) { FF_DEBUG("Detected Content-Length: %u, pre-allocating buffer", contentLength); // Ensure buffer is large enough, adding header size and some margin ffStrbufEnsureFree(buffer, contentLength + 16); FF_DEBUG("Extended receive buffer to %u bytes", buffer->allocated); } } } } } while (ffStrbufGetFree(buffer) > 0); FF_DEBUG("Closing socket: fd=%d", state->sockfd); close(state->sockfd); state->sockfd = -1; if (buffer->length == 0) { FF_DEBUG("Server response is empty"); return "Empty server response received"; } if (headerEnd == 0) { 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 { FF_DEBUG("Invalid response: %.40s...", buffer->chars); return "Invalid response"; } // If compression was used, try to decompress #ifdef FF_HAVE_ZLIB if (state->compression) { FF_DEBUG("Content received, checking if compressed"); if (!ffNetworkingDecompressGzip(buffer, buffer->chars + headerEnd)) { FF_DEBUG("Decompression failed or invalid compression format"); return "Failed to decompress or invalid format"; } else { FF_DEBUG("Decompression successful or no decompression needed, total length after decompression: %u bytes", buffer->length); } } #endif return NULL; } ================================================ FILE: src/common/impl/networking_windows.c ================================================ #include #include //Must be included after #include "fastfetch.h" #include "common/networking.h" #include "common/stringUtils.h" #include "common/debug.h" static LPFN_CONNECTEX ConnectEx; static const char* initWsaData(WSADATA* wsaData) { FF_DEBUG("Initializing WinSock"); if(WSAStartup(MAKEWORD(2, 2), wsaData) != 0) { FF_DEBUG("WSAStartup() failed"); return "WSAStartup() failed"; } if(LOBYTE(wsaData->wVersion) != 2 || HIBYTE(wsaData->wVersion) != 2) { FF_DEBUG("Invalid wsaData version found: %d.%d", LOBYTE(wsaData->wVersion), HIBYTE(wsaData->wVersion)); WSACleanup(); return "Invalid wsaData version found"; } //Dummy socket needed for WSAIoctl SOCKET sockfd = WSASocketW(AF_INET, SOCK_STREAM, 0, NULL, 0, 0); if(sockfd == INVALID_SOCKET) { FF_DEBUG("WSASocketW(AF_INET, SOCK_STREAM) failed"); WSACleanup(); return "WSASocketW(AF_INET, SOCK_STREAM) failed"; } DWORD dwBytes; GUID guid = WSAID_CONNECTEX; if(WSAIoctl(sockfd, SIO_GET_EXTENSION_FUNCTION_POINTER, &guid, sizeof(guid), &ConnectEx, sizeof(ConnectEx), &dwBytes, NULL, NULL) != 0) { FF_DEBUG("WSAIoctl(sockfd, SIO_GET_EXTENSION_FUNCTION_POINTER) failed"); closesocket(sockfd); WSACleanup(); return "WSAIoctl(sockfd, SIO_GET_EXTENSION_FUNCTION_POINTER) failed"; } closesocket(sockfd); FF_DEBUG("WinSock initialized successfully"); return NULL; } const char* ffNetworkingSendHttpRequest(FFNetworkingState* state, const char* host, const char* path, const char* headers) { FF_DEBUG("Preparing to send HTTP request: host=%s, path=%s", host, path); if (state->compression) { #ifdef FF_HAVE_ZLIB const char* zlibError = ffNetworkingLoadZlibLibrary(); // Only enable compression if zlib library is successfully loaded if (zlibError == NULL) { FF_DEBUG("Successfully loaded zlib library, compression enabled"); } else { FF_DEBUG("Failed to load zlib library, compression disabled: %s", zlibError); state->compression = false; } #else FF_DEBUG("zlib not supported at build time, compression disabled"); state->compression = false; #endif } else { FF_DEBUG("Compression disabled"); } static WSADATA wsaData; if (wsaData.wVersion == 0) { const char* error = initWsaData(&wsaData); if (error != NULL) { wsaData.wVersion = (WORD) -1; FF_DEBUG("WinSock initialization failed: %s", error); return error; } } else if (wsaData.wVersion == (WORD) -1) { FF_DEBUG("WinSock initialization previously failed"); return "initWsaData() failed before"; } ADDRINFOW* addr; ADDRINFOW hints = { .ai_flags = AI_NUMERICSERV, .ai_family = state->ipv6 ? AF_INET6 : AF_INET, .ai_socktype = SOCK_STREAM, }; wchar_t hostW[256]; if (!NT_SUCCESS(RtlUTF8ToUnicodeN(hostW, (ULONG) sizeof(hostW), NULL, host, (ULONG) strlen(host) + 1))) { FF_DEBUG("Failed to convert host to wide string: %s", host); return "Failed to convert host to wide string"; } FF_DEBUG("Resolving address: %s (%s)", host, state->ipv6 ? "IPv6" : "IPv4"); if(GetAddrInfoW(hostW, L"80", &hints, &addr) != 0) { FF_DEBUG("GetAddrInfoW() failed"); return "GetAddrInfoW() failed"; } state->sockfd = WSASocketW(addr->ai_family, addr->ai_socktype, addr->ai_protocol, NULL, 0, 0); if(state->sockfd == INVALID_SOCKET) { FF_DEBUG("WSASocketW() failed"); FreeAddrInfoW(addr); return "WSASocketW() failed"; } DWORD flag = 1; #ifdef TCP_NODELAY // Enable TCP_NODELAY to disable Nagle's algorithm if (setsockopt(state->sockfd, IPPROTO_TCP, TCP_NODELAY, (char*)&flag, sizeof(flag)) != 0) { FF_DEBUG("Failed to set TCP_NODELAY: %s", ffDebugWin32Error((DWORD) WSAGetLastError())); } else { FF_DEBUG("Successfully disabled Nagle's algorithm"); } #endif // Set timeout if needed if (state->timeout > 0) { FF_DEBUG("Setting connection timeout: %u ms", state->timeout); setsockopt(state->sockfd, SOL_SOCKET, SO_SNDTIMEO, (const char*)&state->timeout, sizeof(state->timeout)); } //ConnectEx requires the socket to be initially bound if((state->ipv6 ? bind(state->sockfd, (SOCKADDR *) &(struct sockaddr_in6) { .sin6_family = AF_INET6, .sin6_addr = in6addr_any, }, sizeof(struct sockaddr_in6)) : bind(state->sockfd, (SOCKADDR *) &(struct sockaddr_in) { .sin_family = AF_INET, .sin_addr.s_addr = INADDR_ANY, }, sizeof(struct sockaddr_in))) != 0) { FF_DEBUG("bind() failed: %s", ffDebugWin32Error((DWORD) WSAGetLastError())); closesocket(state->sockfd); FreeAddrInfoW(addr); state->sockfd = INVALID_SOCKET; return "bind() failed"; } // Initialize overlapped structure with WSA event for asynchronous I/O state->overlapped = (OVERLAPPED){ .hEvent = WSACreateEvent() }; if (state->overlapped.hEvent == WSA_INVALID_EVENT) { FF_DEBUG("WSACreateEvent() failed"); closesocket(state->sockfd); FreeAddrInfoW(addr); state->sockfd = INVALID_SOCKET; return "WSACreateEvent() failed"; } // Build HTTP command ffStrbufInitA(&state->command, 128); ffStrbufAppendS(&state->command, "GET "); ffStrbufAppendS(&state->command, path); ffStrbufAppendS(&state->command, " HTTP/1.0\r\nHost: "); ffStrbufAppendS(&state->command, host); ffStrbufAppendS(&state->command, "\r\nConnection: close\r\n"); // Explicitly request connection closure // Add compression support if enabled if (state->compression) { FF_DEBUG("Enabling HTTP content compression"); ffStrbufAppendS(&state->command, "Accept-Encoding: gzip\r\n"); } ffStrbufAppendS(&state->command, headers); ffStrbufAppendS(&state->command, "\r\n"); #ifdef TCP_FASTOPEN if (state->tfo) { // Set TCP Fast Open flag = 1; if (setsockopt(state->sockfd, IPPROTO_TCP, TCP_FASTOPEN, (char*)&flag, sizeof(flag)) != 0) { FF_DEBUG("Failed to set TCP_FASTOPEN option: %s", ffDebugWin32Error((DWORD) WSAGetLastError())); } else { FF_DEBUG("Successfully set TCP_FASTOPEN option"); } } else { FF_DEBUG("TCP Fast Open disabled"); } #endif FF_DEBUG("Using ConnectEx to send %u bytes of data", state->command.length); DWORD sent = 0; BOOL result = ConnectEx(state->sockfd, addr->ai_addr, (int)addr->ai_addrlen, state->command.chars, state->command.length, &sent, &state->overlapped); FreeAddrInfoW(addr); addr = NULL; if(!result) { if (WSAGetLastError() != WSA_IO_PENDING) { FF_DEBUG("ConnectEx() failed: %s", ffDebugWin32Error((DWORD) WSAGetLastError())); WSACloseEvent(state->overlapped.hEvent); closesocket(state->sockfd); state->sockfd = INVALID_SOCKET; ffStrbufDestroy(&state->command); return "ConnectEx() failed"; } else { FF_DEBUG("ConnectEx() pending"); } } else { FF_DEBUG("ConnectEx() succeeded, sent %u bytes of data", (unsigned) sent); } // No need to cleanup state fields here since we need them in the receive function return NULL; } const char* ffNetworkingRecvHttpResponse(FFNetworkingState* state, FFstrbuf* buffer) { assert(buffer->allocated > 0); FF_DEBUG("Preparing to receive HTTP response"); if (state->sockfd == INVALID_SOCKET) { FF_DEBUG("Invalid socket, HTTP request might have failed"); return "ffNetworkingSendHttpRequest() failed"; } uint32_t timeout = state->timeout; if (timeout > 0) { FF_DEBUG("WSAWaitForMultipleEvents with timeout: %u ms", timeout); DWORD result = WSAWaitForMultipleEvents(1, &state->overlapped.hEvent, TRUE, timeout, FALSE); if (result != WSA_WAIT_EVENT_0) { if (result == WSA_WAIT_TIMEOUT) { FF_DEBUG("WSAWaitForMultipleEvents timed out"); } else { FF_DEBUG("WSAWaitForMultipleEvents failed: %s", ffDebugWin32Error((DWORD) WSAGetLastError())); } CancelIo((HANDLE) state->sockfd); WSACloseEvent(state->overlapped.hEvent); closesocket(state->sockfd); ffStrbufDestroy(&state->command); return "WSAWaitForMultipleEvents() failed or timeout"; } } DWORD transfer, flags; if (!WSAGetOverlappedResult(state->sockfd, &state->overlapped, &transfer, TRUE, &flags)) { FF_DEBUG("WSAGetOverlappedResult failed: %s", ffDebugWin32Error((DWORD) WSAGetLastError())); closesocket(state->sockfd); WSACloseEvent(state->overlapped.hEvent); ffStrbufDestroy(&state->command); return "WSAGetOverlappedResult() failed"; } FF_DEBUG("WSAGetOverlappedResult succeeded, %u bytes sent", (unsigned) transfer); ffStrbufDestroy(&state->command); WSACloseEvent(state->overlapped.hEvent); state->overlapped.hEvent = NULL; if (setsockopt(state->sockfd, SOL_SOCKET, SO_UPDATE_CONNECT_CONTEXT, NULL, 0) != 0) { FF_DEBUG("Failed to update connect context: %s", ffDebugWin32Error((DWORD) WSAGetLastError())); // Not a critical error, continue anyway } if(timeout > 0) { FF_DEBUG("Setting receive timeout: %u ms", timeout); setsockopt(state->sockfd, SOL_SOCKET, SO_RCVTIMEO, (const char*)&timeout, sizeof(timeout)); } // Set larger receive buffer for better performance int rcvbuf = 65536; // 64KB if (setsockopt(state->sockfd, SOL_SOCKET, SO_RCVBUF, (const char*)&rcvbuf, sizeof(rcvbuf))) { FF_DEBUG("Failed to set SO_RCVBUF: %s", ffDebugWin32Error((DWORD) WSAGetLastError())); // Not a critical error, continue anyway } FF_DEBUG("Starting data reception"); FF_MAYBE_UNUSED int recvCount = 0; uint32_t contentLength = 0; uint32_t headerEnd = 0; do { FF_DEBUG("Data reception loop #%d, current buffer size: %u, available space: %u", ++recvCount, buffer->length, ffStrbufGetFree(buffer)); DWORD received = 0, recvFlags = 0; int recvResult = WSARecv(state->sockfd, &(WSABUF) { .buf = buffer->chars + buffer->length, .len = (ULONG) ffStrbufGetFree(buffer), }, 1, &received, &recvFlags, NULL, NULL); if (recvResult == SOCKET_ERROR || received == 0) { if (recvResult == 0 && received == 0) { FF_DEBUG("Connection closed (received=0)"); } else { FF_DEBUG("Reception failed: %s", ffDebugWin32Error((DWORD) WSAGetLastError())); } break; } buffer->length += (uint32_t) received; buffer->chars[buffer->length] = '\0'; FF_DEBUG("Successfully received %u bytes of data, total: %u bytes", (unsigned) received, buffer->length); // Check if HTTP header end marker is found if (headerEnd == 0) { char* pHeaderEnd = strstr(buffer->chars, "\r\n\r\n"); if (pHeaderEnd) { headerEnd = (uint32_t)(pHeaderEnd - buffer->chars); FF_DEBUG("Found HTTP header end marker, position: %u", headerEnd); // 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); if (contentLength > 0) { FF_DEBUG("Detected Content-Length: %u, pre-allocating buffer", contentLength); // Ensure buffer is large enough, adding header size and some margin ffStrbufEnsureFree(buffer, contentLength + 16); FF_DEBUG("Extended receive buffer to %u bytes", buffer->allocated); } } } } } while (ffStrbufGetFree(buffer) > 0); FF_DEBUG("Closing socket: fd=%u", (unsigned)state->sockfd); closesocket(state->sockfd); state->sockfd = INVALID_SOCKET; if (buffer->length == 0) { FF_DEBUG("Server response is empty"); return "Empty server response received"; } if (headerEnd == 0) { 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 { FF_DEBUG("Invalid response: %.40s...", buffer->chars); return "Invalid response"; } // If compression was used, try to decompress #ifdef FF_HAVE_ZLIB if (state->compression) { FF_DEBUG("Content received, checking if compressed"); if (!ffNetworkingDecompressGzip(buffer, buffer->chars + headerEnd)) { FF_DEBUG("Decompression failed or invalid compression format"); return "Failed to decompress or invalid format"; } else { FF_DEBUG("Decompression successful or no decompression needed, total length after decompression: %u bytes", buffer->length); } } #endif return NULL; } ================================================ FILE: src/common/impl/option.c ================================================ #include "fastfetch.h" #include "common/option.h" #include "common/color.h" #include "common/stringUtils.h" // 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) { const char* subKey = argumentKey; if(!(subKey[0] == '-' && subKey[1] == '-')) return NULL; subKey += 2; uint32_t moduleNameLen = (uint32_t)strlen(moduleName); if(strncasecmp(subKey, moduleName, moduleNameLen) != 0) return NULL; subKey += moduleNameLen; if(subKey[0] == '\0') return subKey; if(subKey[0] != '-') return NULL; subKey += 1; return subKey; } void ffOptionParseString(const char* argumentKey, const char* value, FFstrbuf* buffer) { if(value == NULL) { fprintf(stderr, "Error: usage: %s \n", argumentKey); exit(477); } ffStrbufSetS(buffer, value); } uint32_t ffOptionParseUInt32(const char* argumentKey, const char* value) { if(value == NULL) { fprintf(stderr, "Error: usage: %s \n", argumentKey); exit(480); } char* end; uint32_t num = (uint32_t) strtoul(value, &end, 10); if(*end != '\0') { fprintf(stderr, "Error: usage: %s \n", argumentKey); exit(479); } return num; } int32_t ffOptionParseInt32(const char* argumentKey, const char* value) { if(value == NULL) { fprintf(stderr, "Error: usage: %s \n", argumentKey); exit(480); } char* end; int32_t num = (int32_t) strtol(value, &end, 10); if(*end != '\0') { fprintf(stderr, "Error: usage: %s \n", argumentKey); exit(479); } return num; } int ffOptionParseEnum(const char* argumentKey, const char* requestedKey, FFKeyValuePair pairs[]) { if(requestedKey == NULL) { fprintf(stderr, "Error: usage: %s \n", argumentKey); exit(476); } for (const FFKeyValuePair* pPair = pairs; pPair->key; ++pPair) { if(ffStrEqualsIgnCase(requestedKey, pPair->key)) return pPair->value; } fprintf(stderr, "Error: unknown %s value: %s\n", argumentKey, requestedKey); exit(478); } bool ffOptionParseBoolean(const char* str) { return ( !ffStrSet(str) || ffStrEqualsIgnCase(str, "true") || ffStrEqualsIgnCase(str, "yes") || ffStrEqualsIgnCase(str, "on") || ffStrEqualsIgnCase(str, "1") ); } void ffOptionParseColorNoClear(const char* value, FFstrbuf* buffer) { if (!value || value[0] == '\0') return; // If value is already an ANSI escape code, use it if (value[0] == '\e' && value[1] == '[') { ffStrbufAppendS(buffer, value + 2); ffStrbufTrimRight(buffer, 'm'); return; } ffStrbufEnsureFree(buffer, 63); while(*value != '\0') { #define FF_APPEND_COLOR_CODE_COND(prefix, code) \ if(ffStrStartsWithIgnCase(value, #prefix)) { ffStrbufAppendS(buffer, code); value += strlen(#prefix); continue; } #define FF_APPEND_COLOR_PROP_COND(prefix, prop) \ if(ffStrStartsWithIgnCase(value, #prefix)) { if (instance.config.display.prop.length) ffStrbufAppend(buffer, &instance.config.display.prop); else ffStrbufAppendS(buffer, FF_COLOR_FG_DEFAULT); value += strlen(#prefix); continue; } if (ffCharIsEnglishAlphabet(value[0])) { FF_APPEND_COLOR_CODE_COND(reset_, FF_COLOR_MODE_RESET) else FF_APPEND_COLOR_CODE_COND(bold_, FF_COLOR_MODE_BOLD) else FF_APPEND_COLOR_CODE_COND(bright_, FF_COLOR_MODE_BOLD) else FF_APPEND_COLOR_CODE_COND(dim_, FF_COLOR_MODE_DIM) else FF_APPEND_COLOR_CODE_COND(italic_, FF_COLOR_MODE_ITALIC) else FF_APPEND_COLOR_CODE_COND(underline_, FF_COLOR_MODE_UNDERLINE) else FF_APPEND_COLOR_CODE_COND(blink_, FF_COLOR_MODE_BLINK) else FF_APPEND_COLOR_CODE_COND(inverse_, FF_COLOR_MODE_INVERSE) else FF_APPEND_COLOR_CODE_COND(hidden_, FF_COLOR_MODE_HIDDEN) else FF_APPEND_COLOR_CODE_COND(strike_, FF_COLOR_MODE_STRIKETHROUGH) else FF_APPEND_COLOR_CODE_COND(black, FF_COLOR_FG_BLACK) else FF_APPEND_COLOR_CODE_COND(red, FF_COLOR_FG_RED) else FF_APPEND_COLOR_CODE_COND(green, FF_COLOR_FG_GREEN) else FF_APPEND_COLOR_CODE_COND(yellow, FF_COLOR_FG_YELLOW) else FF_APPEND_COLOR_CODE_COND(blue, FF_COLOR_FG_BLUE) else FF_APPEND_COLOR_CODE_COND(magenta, FF_COLOR_FG_MAGENTA) else FF_APPEND_COLOR_CODE_COND(cyan, FF_COLOR_FG_CYAN) else FF_APPEND_COLOR_CODE_COND(white, FF_COLOR_FG_WHITE) else FF_APPEND_COLOR_CODE_COND(default, FF_COLOR_FG_DEFAULT) else FF_APPEND_COLOR_CODE_COND(light_black, FF_COLOR_FG_LIGHT_BLACK) else FF_APPEND_COLOR_CODE_COND(light_red, FF_COLOR_FG_LIGHT_RED) else FF_APPEND_COLOR_CODE_COND(light_green, FF_COLOR_FG_LIGHT_GREEN) else FF_APPEND_COLOR_CODE_COND(light_yellow, FF_COLOR_FG_LIGHT_YELLOW) else FF_APPEND_COLOR_CODE_COND(light_blue, FF_COLOR_FG_LIGHT_BLUE) else FF_APPEND_COLOR_CODE_COND(light_magenta, FF_COLOR_FG_LIGHT_MAGENTA) else FF_APPEND_COLOR_CODE_COND(light_cyan, FF_COLOR_FG_LIGHT_CYAN) else FF_APPEND_COLOR_CODE_COND(light_white, FF_COLOR_FG_LIGHT_WHITE) else FF_APPEND_COLOR_PROP_COND(keys, colorKeys) else FF_APPEND_COLOR_PROP_COND(title, colorTitle) else FF_APPEND_COLOR_PROP_COND(output, colorOutput) else FF_APPEND_COLOR_PROP_COND(separator, colorSeparator) else { fprintf(stderr, "Error: invalid color code found: %s\n", value); exit(479); } } else if (value[0] == '@') { // Xterm 256 color ++value; char* pend = NULL; uint32_t color = (uint32_t) strtoul(value, &pend, 10); if (pend == value || color > 255) { fprintf(stderr, "Error: invalid 256 color code found: %s\n", value); exit(479); } ffStrbufAppendS(buffer, FF_COLOR_FG_256); ffStrbufAppendUInt(buffer, color); value = pend; continue; } else if (value[0] == '#') { // RGB color ++value; char* pend = NULL; uint32_t rgb = (uint32_t) strtoul(value, &pend, 16); if (pend == value) { fprintf(stderr, "Error: invalid RGB color code found: %s\n", value); exit(479); } if (pend - value > 6) { fprintf(stderr, "Error: RGB color code too long: %s\n", value); exit(479); } else if (pend - value == 3) { rgb = ((rgb & 0xF00) >> 8) * 0x110000 + ((rgb & 0x0F0) >> 4) * 0x001100 + ((rgb & 0x00F) >> 0) * 0x000011; } else if (pend - value != 6) { fprintf(stderr, "Error: invalid RGB color code length: %s\n", value); exit(479); } uint32_t r = rgb >> 16, g = (rgb >> 8) & 0xFF, b = rgb & 0xFF; ffStrbufAppendF(buffer, FF_COLOR_FG_RGB "%u;%u;%u", r, g, b); value = pend; continue; } ffStrbufAppendC(buffer, *value); ++value; #undef FF_APPEND_COLOR_CODE_COND #undef FF_APPEND_COLOR_PROP_COND } } ================================================ FILE: src/common/impl/parsing.c ================================================ #include "fastfetch.h" #include "common/parsing.h" #include #ifdef _WIN32 #pragma GCC diagnostic push #pragma GCC diagnostic ignored "-Wformat" #endif void ffParseSemver(FFstrbuf* buffer, const FFstrbuf* major, const FFstrbuf* minor, const FFstrbuf* patch) { if(major->length > 0) ffStrbufAppend(buffer, major); else if(minor->length > 0 || patch->length > 0) ffStrbufAppendC(buffer, '1'); if(minor->length == 0 && patch->length == 0) return; ffStrbufAppendC(buffer, '.'); if(minor->length > 0) ffStrbufAppend(buffer, minor); else if(patch->length > 0) ffStrbufAppendC(buffer, '0'); if(patch->length == 0) return; ffStrbufAppendC(buffer, '.'); ffStrbufAppend(buffer, patch); } int8_t ffVersionCompare(const FFVersion* version1, const FFVersion* version2) { if(version1->major != version2->major) return version1->major > version2->major ? 1 : -1; if(version1->minor != version2->minor) return version1->minor > version2->minor ? 1 : -1; if(version1->patch != version2->patch) return version1->patch > version2->patch ? 1 : -1; return 0; } void ffVersionToPretty(const FFVersion* version, FFstrbuf* pretty) { if(version->major > 0 || version->minor > 0 || version->patch > 0) { ffStrbufAppendUInt(pretty, version->major); } if(version->minor > 0 || version->patch > 0) { ffStrbufAppendC(pretty, '.'); ffStrbufAppendUInt(pretty, version->minor); } if(version->patch > 0) { ffStrbufAppendC(pretty, '.'); ffStrbufAppendUInt(pretty, version->patch); } } void ffParseGTK(FFstrbuf* buffer, const FFstrbuf* gtk2, const FFstrbuf* gtk3, const FFstrbuf* gtk4) { if(gtk2->length > 0 && gtk3->length > 0 && gtk4->length > 0) { if((ffStrbufIgnCaseEqual(gtk2, gtk3)) && (ffStrbufIgnCaseEqual(gtk2, gtk4))) { ffStrbufAppend(buffer, gtk4); ffStrbufAppendS(buffer, " [GTK2/3/4]"); } else if(ffStrbufIgnCaseEqual(gtk2, gtk3)) { ffStrbufAppend(buffer, gtk3); ffStrbufAppendS(buffer, " [GTK2/3], "); ffStrbufAppend(buffer, gtk4); ffStrbufAppendS(buffer, " [GTK4]"); } else if(ffStrbufIgnCaseEqual(gtk3, gtk4)) { ffStrbufAppend(buffer, gtk2); ffStrbufAppendS(buffer, " [GTK2], "); ffStrbufAppend(buffer, gtk4); ffStrbufAppendS(buffer, " [GTK3/4]"); } else { ffStrbufAppend(buffer, gtk2); ffStrbufAppendS(buffer, " [GTK2], "); ffStrbufAppend(buffer, gtk3); ffStrbufAppendS(buffer, " [GTK3], "); ffStrbufAppend(buffer, gtk4); ffStrbufAppendS(buffer, " [GTK4]"); } } else if(gtk2->length > 0 && gtk3->length > 0) { if(ffStrbufIgnCaseEqual(gtk2, gtk3)) { ffStrbufAppend(buffer, gtk3); ffStrbufAppendS(buffer, " [GTK2/3]"); } else { ffStrbufAppend(buffer, gtk2); ffStrbufAppendS(buffer, " [GTK2], "); ffStrbufAppend(buffer, gtk3); ffStrbufAppendS(buffer, " [GTK3]"); } } else if(gtk3->length > 0 && gtk4->length > 0) { if(ffStrbufIgnCaseEqual(gtk3, gtk4)) { ffStrbufAppend(buffer, gtk4); ffStrbufAppendS(buffer, " [GTK3/4]"); } else { ffStrbufAppend(buffer, gtk3); ffStrbufAppendS(buffer, " [GTK3], "); ffStrbufAppend(buffer, gtk4); ffStrbufAppendS(buffer, " [GTK4]"); } } else if(gtk2->length > 0) { ffStrbufAppend(buffer, gtk2); ffStrbufAppendS(buffer, " [GTK2]"); } else if(gtk3->length > 0) { ffStrbufAppend(buffer, gtk3); ffStrbufAppendS(buffer, " [GTK3]"); } else if(gtk4->length > 0) { ffStrbufAppend(buffer, gtk4); ffStrbufAppendS(buffer, " [GTK4]"); } } #ifdef _WIN32 #pragma GCC diagnostic pop #endif ================================================ FILE: src/common/impl/path.c ================================================ #include "common/path.h" #include "common/io.h" #include "common/arrayUtils.h" #if !_WIN32 const char* ffFindExecutableInPath(const char* name, FFstrbuf* result) { char* path = getenv("PATH"); if(!path) return "$PATH not set"; #ifdef _WIN32 const bool appendExe = !ffStrEndsWithIgnCase(name, ".exe"); #endif for (char* token = path; *token; path = token + 1) { token = strchr(path, #ifdef _WIN32 ';' #else ':' #endif ); if (!token) token = path + strlen(path); ffStrbufSetNS(result, (uint32_t)(token - path), path); ffStrbufEnsureEndsWithC(result, #ifdef _WIN32 '\\' #else '/' #endif ); ffStrbufAppendS(result, name); #ifdef _WIN32 if (appendExe) ffStrbufAppendS(result, ".exe"); if (!ffPathExists(result->chars, FF_PATHTYPE_FILE)) continue; #else if (access(result->chars, X_OK) != 0) continue; #endif return NULL; } ffStrbufClear(result); return "Executable not found"; } #else #include #include #include #include const char* ffFindExecutableInPath(const char* name, FFstrbuf* result) { char buffer[MAX_PATH + 1]; DWORD length = SearchPathA(NULL, name, ".exe", sizeof(buffer), buffer, NULL); if (length == 0) { ffStrbufClear(result); return "Executable not found"; } ffStrbufSetS(result, buffer); return NULL; } static inline int winerr2Errno(DWORD err) { switch (err) { case ERROR_FILE_NOT_FOUND: case ERROR_PATH_NOT_FOUND: case ERROR_INVALID_NAME: return ENOENT; case ERROR_ACCESS_DENIED: case ERROR_SHARING_VIOLATION: case ERROR_LOCK_VIOLATION: return EACCES; case ERROR_BUFFER_OVERFLOW: case ERROR_INSUFFICIENT_BUFFER: return ENAMETOOLONG; case ERROR_INVALID_PARAMETER: case ERROR_NOT_A_REPARSE_POINT: return EINVAL; default: return EIO; } } char* frealpath(HANDLE hFile, char* resolved_name) { if (__builtin_expect(hFile == INVALID_HANDLE_VALUE || !hFile, false)) { errno = EINVAL; return NULL; } wchar_t resolvedNameW[MAX_PATH + 4]; /* +4 for "\\\\?\\" prefix */ DWORD lenW = GetFinalPathNameByHandleW(hFile, resolvedNameW, (DWORD)ARRAY_SIZE(resolvedNameW), FILE_NAME_NORMALIZED); if (lenW == 0) { errno = winerr2Errno(GetLastError()); return NULL; } if (lenW >= ARRAY_SIZE(resolvedNameW)) { errno = E2BIG; return NULL; } lenW++; // Include null terminator wchar_t* srcW = resolvedNameW; DWORD srcLenW = lenW; if (srcLenW >= 8 && wcsncmp(resolvedNameW, L"\\\\?\\UNC\\", 8) == 0) { /* Convert "\\?\UNC\server\share" to "\\server\share" */ srcW += 6; srcLenW -= 6; *srcW = L'\\'; } else if (srcLenW >= 4 && wcsncmp(resolvedNameW, L"\\\\?\\", 4) == 0) { srcW += 4; srcLenW -= 4; } if (resolved_name) { ULONG outBytes = 0; if (!NT_SUCCESS(RtlUnicodeToUTF8N(resolved_name, MAX_PATH, &outBytes, srcW, (ULONG)(srcLenW * sizeof(wchar_t))))) { 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]; ULONG outBytes = 0; if (!NT_SUCCESS(RtlUnicodeToUTF8N(tmp, (ULONG)sizeof(tmp), &outBytes, srcW, (ULONG)(srcLenW * sizeof(wchar_t))))) { errno = E2BIG; return NULL; } resolved_name = (char*)malloc(outBytes); if (!resolved_name) { errno = ENOMEM; return NULL; } memcpy(resolved_name, tmp, outBytes); return resolved_name; } return resolved_name; } char* realpath(const char* __restrict file_name, char* __restrict resolved_name) { if (!file_name) { errno = EINVAL; return NULL; } wchar_t fileNameW[MAX_PATH]; ULONG lenBytes = 0; if (!NT_SUCCESS(RtlUTF8ToUnicodeN(fileNameW, (ULONG)sizeof(fileNameW), &lenBytes, file_name, (ULONG)strlen(file_name) + 1))) { errno = EINVAL; return NULL; } FF_AUTO_CLOSE_FD HANDLE hFile = CreateFileW( fileNameW, 0, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, NULL, OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS, NULL); if (hFile == INVALID_HANDLE_VALUE) { errno = winerr2Errno(GetLastError()); return NULL; } return frealpath(hFile, resolved_name); } ssize_t freadlink(HANDLE hFile, char* buf, size_t bufsiz) { if (__builtin_expect(hFile == INVALID_HANDLE_VALUE || !buf || bufsiz == 0, false)) { errno = EINVAL; return -1; } alignas(REPARSE_DATA_BUFFER) BYTE reparseBuf[MAXIMUM_REPARSE_DATA_BUFFER_SIZE]; DWORD bytesReturned = 0; if (!DeviceIoControl(hFile, FSCTL_GET_REPARSE_POINT, NULL, 0, reparseBuf, (DWORD) sizeof(reparseBuf), &bytesReturned, NULL)) { errno = winerr2Errno(GetLastError()); return -1; } REPARSE_DATA_BUFFER* rp = (REPARSE_DATA_BUFFER*) reparseBuf; const wchar_t* targetW = NULL; USHORT targetBytes = 0; if (rp->ReparseTag == IO_REPARSE_TAG_SYMLINK) { if (rp->SymbolicLinkReparseBuffer.PrintNameLength > 0) { targetW = rp->SymbolicLinkReparseBuffer.PathBuffer + (rp->SymbolicLinkReparseBuffer.PrintNameOffset / sizeof(wchar_t)); targetBytes = rp->SymbolicLinkReparseBuffer.PrintNameLength; } else { targetW = rp->SymbolicLinkReparseBuffer.PathBuffer + (rp->SymbolicLinkReparseBuffer.SubstituteNameOffset / sizeof(wchar_t)); targetBytes = rp->SymbolicLinkReparseBuffer.SubstituteNameLength; if (targetBytes >= 8 && wcsncmp(targetW, L"\\??\\", 4) == 0) { targetW += 4; targetBytes -= 8; } } } else if (rp->ReparseTag == IO_REPARSE_TAG_MOUNT_POINT) { if (rp->MountPointReparseBuffer.PrintNameLength > 0) { targetW = rp->MountPointReparseBuffer.PathBuffer + (rp->MountPointReparseBuffer.PrintNameOffset / sizeof(wchar_t)); targetBytes = rp->MountPointReparseBuffer.PrintNameLength; } else { targetW = rp->MountPointReparseBuffer.PathBuffer + (rp->MountPointReparseBuffer.SubstituteNameOffset / sizeof(wchar_t)); targetBytes = rp->MountPointReparseBuffer.SubstituteNameLength; if (targetBytes >= 8 && wcsncmp(targetW, L"\\??\\", 4) == 0) { targetW += 4; targetBytes -= 8; } } } else { errno = EINVAL; return -1; } ULONG outBytes = 0; if (!NT_SUCCESS(RtlUnicodeToUTF8N(buf, (ULONG) bufsiz, &outBytes, targetW, targetBytes))) { errno = E2BIG; return -1; } // Not null-terminated return (ssize_t) outBytes; } ssize_t readlink(const char* path, char* buf, size_t bufsiz) { if (!path || !buf || bufsiz == 0) { errno = EINVAL; return -1; } wchar_t pathW[MAX_PATH]; ULONG pathWBytes = 0; if (!NT_SUCCESS(RtlUTF8ToUnicodeN(pathW, (ULONG) sizeof(pathW), &pathWBytes, path, (ULONG) strlen(path) + 1))) { errno = EINVAL; return -1; } FF_AUTO_CLOSE_FD HANDLE hFile = CreateFileW( pathW, 0, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, NULL, OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS | FILE_FLAG_OPEN_REPARSE_POINT, NULL ); if (hFile == INVALID_HANDLE_VALUE) { errno = winerr2Errno(GetLastError()); return -1; } return freadlink(hFile, buf, bufsiz); } #endif ================================================ FILE: src/common/impl/percent.c ================================================ #include "fastfetch.h" #include "common/percent.h" #include "common/color.h" #include "common/option.h" #include "common/jsonconfig.h" #include "common/textModifier.h" #include "common/stringUtils.h" static void appendOutputColor(FFstrbuf* buffer, const FFModuleArgs* module) { if (module->outputColor.length) ffStrbufAppendF(buffer, "\e[%sm", module->outputColor.chars); else if (instance.config.display.colorOutput.length) ffStrbufAppendF(buffer, "\e[%sm", instance.config.display.colorOutput.chars); } const char* ffPercentParseTypeJsonConfig(yyjson_val* jsonVal, FFPercentageTypeFlags* result) { if (yyjson_is_uint(jsonVal)) { *result = (FFPercentageTypeFlags) yyjson_get_uint(jsonVal); return NULL; } if (yyjson_is_arr(jsonVal)) { FFPercentageTypeFlags flags = 0; yyjson_val* item; size_t idx, max; yyjson_arr_foreach(jsonVal, idx, max, item) { const char* flag = yyjson_get_str(item); if (!flag) return "Error: percent.type: invalid flag string"; if (ffStrEqualsIgnCase(flag, "num")) flags |= FF_PERCENTAGE_TYPE_NUM_BIT; else if (ffStrEqualsIgnCase(flag, "bar")) flags |= FF_PERCENTAGE_TYPE_BAR_BIT; else if (ffStrEqualsIgnCase(flag, "hide-others")) flags |= FF_PERCENTAGE_TYPE_HIDE_OTHERS_BIT; else if (ffStrEqualsIgnCase(flag, "num-color")) flags |= FF_PERCENTAGE_TYPE_NUM_COLOR_BIT; else if (ffStrEqualsIgnCase(flag, "bar-monochrome")) flags |= FF_PERCENTAGE_TYPE_BAR_MONOCHROME_BIT; else return "Error: percent.type: unknown flag string"; } *result = flags; return NULL; } return "Error: usage: percent.type must be a number or an array of strings"; } void ffPercentAppendBar(FFstrbuf* buffer, double percent, FFPercentageModuleConfig config, const FFModuleArgs* module) { uint8_t green = config.green, yellow = config.yellow; assert(green <= 100 && yellow <= 100); const FFOptionsDisplay* options = &instance.config.display; 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); ffStrbufAppend(buffer, &options->barBorderLeft); } if (percent == -DBL_MAX) { // Use total color for simplification if(!options->pipe && options->barColorTotal.length > 0) ffStrbufAppendS(buffer, "\e[" FF_COLOR_FG_LIGHT_BLACK "m"); for (uint8_t i = 0; i < options->barWidth; ++i) { ffStrbufAppend(buffer, borderAsValue && i == 0 ? &options->barBorderLeft : borderAsValue && i == options->barWidth - 1 ? &options->barBorderRight : &options->barCharTotal); } } else { const char* colorGreen = options->percentColorGreen.chars; const char* colorYellow = options->percentColorYellow.chars; const char* colorRed = options->percentColorRed.chars; FFPercentageTypeFlags percentType = config.type == 0 ? options->percentType : config.type; bool autoColorElapsed = ffStrbufIgnCaseEqualS(&options->barColorElapsed, "auto"); bool monochrome = (percentType & FF_PERCENTAGE_TYPE_BAR_MONOCHROME_BIT) || !autoColorElapsed; if (!options->pipe && options->barColorElapsed.length > 0 && monochrome) { const char* color = NULL; if (!autoColorElapsed) color = options->barColorElapsed.chars; else if (green <= yellow) { if (percent < green) color = colorGreen; else if (percent < yellow) color = colorYellow; else color = colorRed; } else { if (percent < yellow) color = colorRed; else if (percent < green) color = colorYellow; else color = colorGreen; } ffStrbufAppendF(buffer, "\e[%sm", color); } for (uint8_t i = 0; i < blocksPercent; ++i) { if (!options->pipe && options->barColorElapsed.length > 0 && !monochrome) { uint32_t section1Begin = (uint32_t) ((green <= yellow ? green : yellow) / 100.0 * options->barWidth + 0.5); uint32_t section2Begin = (uint32_t) ((green > yellow ? green : yellow) / 100.0 * options->barWidth + 0.5); if (i == section2Begin) ffStrbufAppendF(buffer, "\e[%sm", (green > yellow ? colorGreen : colorRed)); else if (i == section1Begin) ffStrbufAppendF(buffer, "\e[%sm", colorYellow); else if (i == 0) ffStrbufAppendF(buffer, "\e[%sm", (green <= yellow ? colorGreen : colorRed)); } ffStrbufAppend(buffer, borderAsValue && i == 0 ? &options->barBorderLeftElapsed : borderAsValue && i == options->barWidth - 1 ? &options->barBorderRightElapsed : &options->barCharElapsed); } if (blocksPercent < options->barWidth) { if(!options->pipe && options->barColorTotal.length > 0) ffStrbufAppendF(buffer, "\e[%sm", options->barColorTotal.chars); for (uint8_t i = blocksPercent; i < options->barWidth; ++i) { ffStrbufAppend(buffer, borderAsValue && i == 0 ? &options->barBorderLeft : borderAsValue && i == options->barWidth - 1 ? &options->barBorderRight : &options->barCharTotal); } } } if(!borderAsValue && options->barBorderRight.length) { if(!options->pipe && options->barColorBorder.length > 0) ffStrbufAppendF(buffer, "\e[%sm", options->barColorBorder.chars); ffStrbufAppend(buffer, &options->barBorderRight); } if(!options->pipe && (options->barColorElapsed.length > 0 || options->barColorTotal.length > 0 || options->barColorBorder.length > 0)) { ffStrbufAppendS(buffer, FASTFETCH_TEXT_MODIFIER_RESET); appendOutputColor(buffer, module); } } void ffPercentAppendNum(FFstrbuf* buffer, double percent, FFPercentageModuleConfig config, bool parentheses, const FFModuleArgs* module) { uint8_t green = config.green, yellow = config.yellow; assert(green <= 100 && yellow <= 100); const FFOptionsDisplay* options = &instance.config.display; FFPercentageTypeFlags percentType = config.type == 0 ? options->percentType : config.type; bool colored = !!(percentType & FF_PERCENTAGE_TYPE_NUM_COLOR_BIT); if (parentheses) ffStrbufAppendC(buffer, '('); if (colored && !options->pipe) { const char* colorGreen = options->percentColorGreen.chars; const char* colorYellow = options->percentColorYellow.chars; const char* colorRed = options->percentColorRed.chars; if(percent == -DBL_MAX) ffStrbufAppendS(buffer, "\e[" FF_COLOR_FG_LIGHT_BLACK "m"); else if(green <= yellow) { if (percent > yellow) ffStrbufAppendF(buffer, "\e[%sm", colorRed); else if (percent > green) ffStrbufAppendF(buffer, "\e[%sm", colorYellow); else ffStrbufAppendF(buffer, "\e[%sm", colorGreen); } else { if (percent < yellow) ffStrbufAppendF(buffer, "\e[%sm", colorRed); else if (percent < green) ffStrbufAppendF(buffer, "\e[%sm", colorYellow); else ffStrbufAppendF(buffer, "\e[%sm", colorGreen); } } 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); appendOutputColor(buffer, module); } if (parentheses) ffStrbufAppendC(buffer, ')'); } bool ffPercentParseCommandOptions(const char* key, const char* subkey, const char* value, FFPercentageModuleConfig* config) { if (!ffStrStartsWithIgnCase(subkey, "percent-")) return false; subkey += strlen("percent-"); if (ffStrEqualsIgnCase(subkey, "green")) { uint32_t num = ffOptionParseUInt32(key, value); if (num > 100) { fprintf(stderr, "Error: usage: %s must be between 0 and 100\n", key); exit(480); } config->green = (uint8_t) num; return true; } if (ffStrEqualsIgnCase(subkey, "yellow")) { uint32_t num = ffOptionParseUInt32(key, value); if (num > 100) { fprintf(stderr, "Error: usage: %s must be between 0 and 100\n", key); exit(480); } config->yellow = (uint8_t) num; return true; } if (ffStrEqualsIgnCase(subkey, "type")) { config->type = (FFPercentageTypeFlags) ffOptionParseUInt32(key, value); return true; } return false; } bool ffPercentParseJsonObject(yyjson_val* key, yyjson_val* value, FFPercentageModuleConfig* config) { assert(key); if (!unsafe_yyjson_equals_str(key, "percent")) return false; if (!yyjson_is_obj(value)) { fprintf(stderr, "Error: usage: %s must be an object\n", unsafe_yyjson_get_str(key)); exit(480); } yyjson_val* greenVal = yyjson_obj_get(value, "green"); if (greenVal) { int num = yyjson_get_int(greenVal); if (num < 0 || num > 100) { fputs("Error: usage: percent.green must be between 0 and 100\n", stderr); exit(480); } config->green = (uint8_t) num; } yyjson_val* yellowVal = yyjson_obj_get(value, "yellow"); if (yellowVal) { int num = yyjson_get_int(yellowVal); if (num < 0 || num > 100) { fputs("Error: usage: percent.yellow must be between 0 and 100\n", stderr); exit(480); } config->yellow = (uint8_t) num; } yyjson_val* typeVal = yyjson_obj_get(value, "type"); if (typeVal) { const char* error = ffPercentParseTypeJsonConfig(typeVal, &config->type); if (error) { fputs(error, stderr); exit(480); } } return true; } void ffPercentGenerateJsonConfig(yyjson_mut_doc* doc, yyjson_mut_val* module, FFPercentageModuleConfig config) { yyjson_mut_val* percent = yyjson_mut_obj_add_obj(doc, module, "percent"); yyjson_mut_obj_add_uint(doc, percent, "green", config.green); yyjson_mut_obj_add_uint(doc, percent, "yellow", config.yellow); yyjson_mut_obj_add_uint(doc, percent, "type", config.type); } ================================================ FILE: src/common/impl/printing.c ================================================ #include "fastfetch.h" #include "common/printing.h" #include "common/textModifier.h" #include "logo/logo.h" void ffPrintLogoAndKey(const char* moduleName, uint8_t moduleIndex, const FFModuleArgs* moduleArgs, FFPrintType printType) { ffLogoPrintLine(); //This is used by --set-keyless, in this case we want neither the module name nor the separator if(moduleName == NULL) return; //This is used as a magic value for hiding keys if (!(moduleArgs && ffStrbufEqualS(&moduleArgs->key, " ")) && instance.config.display.keyType != FF_MODULE_KEY_TYPE_NONE) { ffPrintCharTimes(' ', instance.config.display.keyPaddingLeft); if(!instance.config.display.pipe) { fputs(FASTFETCH_TEXT_MODIFIER_RESET, stdout); if (instance.config.display.brightColor) fputs(FASTFETCH_TEXT_MODIFIER_BOLT, stdout); if(moduleArgs && !(printType & FF_PRINT_TYPE_NO_CUSTOM_KEY_COLOR) && moduleArgs->keyColor.length > 0) ffPrintColor(&moduleArgs->keyColor); else ffPrintColor(&instance.config.display.colorKeys); } if (instance.config.display.keyType & FF_MODULE_KEY_TYPE_ICON && moduleArgs && moduleArgs->keyIcon.length > 0) ffStrbufWriteTo(&moduleArgs->keyIcon, stdout); if (instance.config.display.keyType & FF_MODULE_KEY_TYPE_STRING) { ffPrintCharTimes(' ', instance.config.display.keyType >> FF_MODULE_KEY_TYPE_SPACE_SHIFT); //NULL check is required for modules with custom keys, e.g. disk with the folder path if((printType & FF_PRINT_TYPE_NO_CUSTOM_KEY) || !moduleArgs || moduleArgs->key.length == 0) { fputs(moduleName, stdout); if(moduleIndex > 0) printf(" %hhu", moduleIndex); } else { FF_STRBUF_AUTO_DESTROY key = ffStrbufCreate(); FF_PARSE_FORMAT_STRING_CHECKED(&key, &moduleArgs->key, ((FFformatarg[]) { FF_ARG(moduleIndex, "index"), FF_ARG(moduleArgs->keyIcon, "icon"), })); ffStrbufWriteTo(&key, stdout); } } if(!instance.config.display.pipe) { fputs(FASTFETCH_TEXT_MODIFIER_RESET, stdout); ffPrintColor(&instance.config.display.colorSeparator); } ffStrbufWriteTo(&instance.config.display.keyValueSeparator, stdout); if(!instance.config.display.pipe && instance.config.display.colorSeparator.length) fputs(FASTFETCH_TEXT_MODIFIER_RESET, stdout); if (!(printType & FF_PRINT_TYPE_NO_CUSTOM_KEY_WIDTH)) { uint32_t keyWidth = moduleArgs && moduleArgs->keyWidth > 0 ? moduleArgs->keyWidth : instance.config.display.keyWidth; if (keyWidth > 0) printf("\e[%uG", (unsigned) (keyWidth + instance.state.logoWidth)); } } if(!instance.config.display.pipe) { fputs(FASTFETCH_TEXT_MODIFIER_RESET, stdout); if (moduleArgs && moduleArgs->outputColor.length) ffPrintColor(&moduleArgs->outputColor); else if (instance.config.display.colorOutput.length) ffPrintColor(&instance.config.display.colorOutput); } } void ffPrintFormat(const char* moduleName, uint8_t moduleIndex, const FFModuleArgs* moduleArgs, FFPrintType printType, uint32_t numArgs, const FFformatarg* arguments) { FF_STRBUF_AUTO_DESTROY buffer = ffStrbufCreate(); if (moduleArgs) ffParseFormatString(&buffer, &moduleArgs->outputFormat, numArgs, arguments); else ffStrbufAppendS(&buffer, "unknown"); ffPrintLogoAndKey(moduleName, moduleIndex, moduleArgs, printType); ffStrbufPutTo(&buffer, stdout); } void ffPrintError(const char* moduleName, uint8_t moduleIndex, const FFModuleArgs* moduleArgs, FFPrintType printType, const char* message, ...) { if(!instance.config.display.showErrors) return; ffPrintLogoAndKey(moduleName, moduleIndex, moduleArgs, printType); if(!instance.config.display.pipe) fputs(FASTFETCH_TEXT_MODIFIER_ERROR, stdout); va_list arguments; va_start(arguments, message); vprintf(message, arguments); va_end(arguments); if(!instance.config.display.pipe) fputs(FASTFETCH_TEXT_MODIFIER_RESET, stdout); putchar('\n'); } void ffPrintColor(const FFstrbuf* colorValue) { //If the color is not set, this would reset in \033[m, which resets everything. //So we only print it, if the main color is at least one char. if(colorValue->length == 0) return; printf("\e[%sm", colorValue->chars); } void ffPrintCharTimes(char c, uint32_t times) { if(times == 0) return; if(times == 1) { putchar(c); return; } char str[32]; memset(str, c, sizeof(str)); //2 instructions when compiling with AVX2 enabled for(uint32_t i = sizeof(str); i <= times; i += (uint32_t)sizeof(str)) fwrite(str, 1, sizeof(str), stdout); uint32_t remaining = times % sizeof(str); if(remaining > 0) fwrite(str, 1, remaining, stdout); } ================================================ FILE: src/common/impl/processing_linux.c ================================================ #include "fastfetch.h" #include "common/processing.h" #include "common/io.h" #include "common/stringUtils.h" #include "common/mallocHelper.h" #include #include #include #include #include #include #include #if !(__ANDROID__ || __OpenBSD__) #include #endif #if defined(__FreeBSD__) || defined(__APPLE__) #include #include #include #endif #if defined(__APPLE__) #include #elif defined(__sun) #include #elif defined(__OpenBSD__) #include #include #include #elif defined(__NetBSD__) #include #include #elif defined(__HAIKU__) #include #include #endif #ifndef environ extern char** environ; #endif enum { FF_PIPE_BUFSIZ = 8192 }; static inline int ffPipe2(int* fds, int flags) { #ifndef FF_HAVE_PIPE2 if(pipe(fds) == -1) return -1; fcntl(fds[0], F_SETFL, fcntl(fds[0], F_GETFL) | flags); fcntl(fds[1], F_SETFL, fcntl(fds[1], F_GETFL) | flags); return 0; #else return pipe2(fds, flags); #endif } // Not thread-safe const char* ffProcessSpawn(char* const argv[], bool useStdErr, FFProcessHandle* outHandle) { int pipes[2]; if(ffPipe2(pipes, O_CLOEXEC) == -1) return "pipe() failed"; pid_t childPid = -1; int nullFile = ffGetNullFD(); #if !(__ANDROID__ || __OpenBSD__) // NetBSD / Darwin: native syscall // Linux (glibc): clone3-execve // FreeBSD: vfork-execve // illumos: vforkx-execve // OpenBSD / Android (bionic): fork-execve posix_spawn_file_actions_t file_actions; posix_spawn_file_actions_init(&file_actions); posix_spawn_file_actions_adddup2(&file_actions, pipes[1], useStdErr ? STDERR_FILENO : STDOUT_FILENO); posix_spawn_file_actions_adddup2(&file_actions, nullFile, useStdErr ? STDOUT_FILENO : STDERR_FILENO); static char* oldLang = NULL; static int langIndex = -1; if (langIndex >= 0) { // Found before if (oldLang) // oldLang was set only if it needed to be changed { if (environ[langIndex] != oldLang) { // environ is changed outside of this function langIndex = -1; } else environ[langIndex] = (char*) "LANG=C.UTF-8"; } } if (langIndex < 0) { for (int i = 0; environ[i] != NULL; i++) { if (ffStrStartsWith(environ[i], "LANG=")) { langIndex = i; const char* langValue = environ[i] + 5; // Skip "LANG=" if (ffStrEqualsIgnCase(langValue, "C") || ffStrStartsWithIgnCase(environ[i], "C.") || ffStrEqualsIgnCase(langValue, "en_US") || ffStrStartsWithIgnCase(langValue, "en_US.")) break; // No need to change LANG oldLang = environ[i]; environ[i] = (char*) "LANG=C.UTF-8"; // Set LANG to C.UTF-8 for consistent output break; } } } int ret = posix_spawnp(&childPid, argv[0], &file_actions, NULL, argv, environ); if (oldLang) environ[langIndex] = oldLang; posix_spawn_file_actions_destroy(&file_actions); if (ret != 0) { close(pipes[0]); close(pipes[1]); return "posix_spawnp() failed"; } #else // https://github.com/termux/termux-packages/issues/25369 childPid = fork(); if(childPid == -1) { close(pipes[0]); close(pipes[1]); return "fork() failed"; } if(childPid == 0) { // Child process dup2(pipes[1], useStdErr ? STDERR_FILENO : STDOUT_FILENO); dup2(nullFile, useStdErr ? STDOUT_FILENO : STDERR_FILENO); putenv("LANG=C.UTF-8"); execvp(argv[0], argv); _exit(127); } #endif close(pipes[1]); outHandle->pid = childPid; outHandle->pipeRead = pipes[0]; return NULL; } const char* ffProcessReadOutput(FFProcessHandle* handle, FFstrbuf* buffer) { assert(handle->pipeRead != -1); assert(handle->pid != -1); const int32_t timeout = instance.config.general.processingTimeout; FF_AUTO_CLOSE_FD int childPipeFd = handle->pipeRead; pid_t childPid = handle->pid; handle->pipeRead = -1; handle->pid = -1; char str[FF_PIPE_BUFSIZ]; for (;;) { if (timeout >= 0) { struct pollfd pollfd = { childPipeFd, POLLIN, 0 }; int pollret = poll(&pollfd, 1, timeout); if (pollret == 0) { kill(childPid, SIGTERM); waitpid(childPid, NULL, 0); return "poll(&pollfd, 1, timeout) timeout (try increasing --processing-timeout)"; } else if (pollret < 0 || (pollfd.revents & POLLERR)) { kill(childPid, SIGTERM); waitpid(childPid, NULL, 0); return pollret < 0 ? "poll(&pollfd, 1, timeout) error: pollret < 0" : "poll(&pollfd, 1, timeout) error: pollfd.revents & POLLERR"; } } ssize_t nRead = read(childPipeFd, str, FF_PIPE_BUFSIZ); if (nRead > 0) ffStrbufAppendNS(buffer, (uint32_t) nRead, str); else if (nRead == 0) { int stat_loc = 0; if (childPid > 0 && waitpid(childPid, &stat_loc, 0) == childPid) { if (!WIFEXITED(stat_loc)) return "child process exited abnormally"; if (WEXITSTATUS(stat_loc) == 127) return "command not found"; // We only handle 127 as an error. See `getTerminalVersionUrxvt` in `terminalshell.c` return NULL; } return NULL; } else if (nRead < 0) break; } return "read(childPipeFd, str, FF_PIPE_BUFSIZ) failed"; } void ffProcessGetInfoLinux(pid_t pid, FFstrbuf* processName, FFstrbuf* exe, const char** exeName, FFstrbuf* exePath) { assert(processName->length > 0); ffStrbufClear(exe); if (exePath) ffStrbufClear(exePath); #if defined(__linux__) || defined(__GNU__) char filePath[64]; snprintf(filePath, sizeof(filePath), "/proc/%d/cmdline", (int)pid); if(ffReadFileBuffer(filePath, exe)) { const char* p = exe->chars; uint32_t len = (uint32_t) strlen(p); if (len + 1 < exe->length) { const char* name = memrchr(p, '/', len); if (name) name++; else name = p; // For interpreters, try to find the real script path in the arguments if (ffStrStartsWith(name, "python") #ifndef __ANDROID__ || ffStrEquals(name, "guile") // for shepherd #endif ) { // `cmdline` always ends with a trailing '\0', and ffReadFileBuffer appends another \0 // So `exe->chars` is always double '\0' terminated for (p = p + len + 1; *p && *p == '-'; p += strlen(p) + 1) // Skip arguments assert(p - exe->chars < exe->allocated); if (*p) { len = (uint32_t) strlen(p); memmove(exe->chars, p, len + 1); } } } assert(len < exe->allocated); exe->length = len; ffStrbufTrimLeft(exe, '-'); //Login shells start with a dash } if (exePath) { snprintf(filePath, sizeof(filePath), "/proc/%d/exe", (int)pid); char buf[PATH_MAX]; ssize_t length = readlink(filePath, buf, PATH_MAX - 1); if (length > 0) // doesn't contain trailing NUL { buf[length] = '\0'; // When the process is a deleted executable, the resolved path is like `/usr/bin/app (deleted)` // But we can still access the binary via `/proc/pid/exe`. See #2136 if (ffPathExists(buf, FF_PATHTYPE_ANY)) ffStrbufSetNS(exePath, (uint32_t)length, buf); } if (exePath->length == 0) ffStrbufSetS(exePath, filePath); } #elif defined(__APPLE__) size_t len = 0; int mibs[] = { CTL_KERN, KERN_PROCARGS2, pid }; if (sysctl(mibs, ARRAY_SIZE(mibs), NULL, &len, NULL, 0) == 0) {// try get arg0 //don't know why if don't let len longer, proArgs2 and len will change during the following sysctl() in old MacOS version. len++; FF_AUTO_FREE char* const procArgs2 = malloc(len); if (sysctl(mibs, ARRAY_SIZE(mibs), procArgs2, &len, NULL, 0) == 0) { // https://gist.github.com/nonowarn/770696#file-getargv-c-L46 uint32_t argc = *(uint32_t*) procArgs2; const char* realExePath = procArgs2 + sizeof(argc); const char* arg0 = memchr(realExePath, '\0', len - (size_t) (realExePath - procArgs2)); if (exePath) ffStrbufSetNS(exePath, (uint32_t) (arg0 - realExePath), realExePath); do arg0++; while (*arg0 == '\0'); assert(arg0 < procArgs2 + len); if (argc > 1) { // #977 const char* p = strrchr(arg0, '/'); if (p) p++; else p = arg0; if (ffStrStartsWithIgnCase(p, "python")) // /opt/homebrew/Cellar/python@3.12/3.12.3/Frameworks/Python.framework/Versions/3.12/Resources/Python.app/Contents/MacOS/Python /Users/carter/.local/bin/xonsh arg0 = p + strlen(p) + 1; } if (*arg0 == '-') arg0++; // Login shells ffStrbufSetS(exe, arg0); } } if (exePath || exe->length == 0) { char buf[PROC_PIDPATHINFO_MAXSIZE]; int length = proc_pidpath(pid, buf, ARRAY_SIZE(buf)); if (length > 0) { if (exe->length == 0) ffStrbufSetNS(exe, (uint32_t) length, buf); if (exePath) { // We don't use exec_path above as exePath because it's a relative path and can be different // from the actual executable being run (for example, when the original file is moved) ffStrbufSetNS(exePath, (uint32_t) length, buf); } } } #elif defined(__FreeBSD__) || defined(__NetBSD__) size_t size = ARG_MAX; FF_AUTO_FREE char* args = malloc(size); static_assert(ARG_MAX > PATH_MAX, ""); if(exePath && sysctl( (int[]){CTL_KERN, #if __FreeBSD__ KERN_PROC, KERN_PROC_PATHNAME, pid #else KERN_PROC_ARGS, pid, KERN_PROC_PATHNAME #endif }, 4, args, &size, NULL, 0 ) == 0) ffStrbufSetNS(exePath, (uint32_t) (size - 1), args); size = ARG_MAX; if(sysctl( (int[]){CTL_KERN, #if __FreeBSD__ KERN_PROC, KERN_PROC_ARGS, pid #else KERN_PROC_ARGS, pid, KERN_PROC_ARGV, #endif }, 4, args, &size, NULL, 0 ) == 0) { char* arg0 = args; size_t arg0Len = strlen(args); if (size > arg0Len + 1) { char* p = (char*) memrchr(args, '/', arg0Len); if (p) p++; else p = arg0; if (ffStrStartsWith(p, "python")) // /usr/local/bin/python3.9 /home/carter/.local/bin/xonsh { arg0 += arg0Len + 1; } } if (arg0[0] == '-') arg0++; ffStrbufSetS(exe, arg0); } #elif defined(__sun) char filePath[128]; snprintf(filePath, sizeof(filePath), "/proc/%d/psinfo", (int) pid); psinfo_t proc; if (ffReadFileData(filePath, sizeof(proc), &proc) == sizeof(proc)) { const char* args = proc.pr_psargs; if (args[0] == '-') ++args; const char* end = strchr(args, ' '); ffStrbufSetNS(exe, end ? (uint32_t) (end - args) : (uint32_t) strlen(args), args); } if (exePath) { snprintf(filePath, sizeof(filePath), "/proc/%d/path/a.out", (int) pid); char buf[PATH_MAX]; ssize_t length = readlink(filePath, buf, PATH_MAX - 1); if (length > 0) // doesn't contain trailing NUL { buf[length] = '\0'; ffStrbufSetNS(exePath, (uint32_t)length, buf); } } #elif defined(__OpenBSD__) kvm_t* kd = kvm_open(NULL, NULL, NULL, KVM_NO_FILES, NULL); int count = 0; const struct kinfo_proc* proc = kvm_getprocs(kd, KERN_PROC_PID, pid, sizeof(struct kinfo_proc), &count); if (proc) { char** argv = kvm_getargv(kd, proc, 0); if (argv) { const char* arg0 = argv[0]; if (arg0[0] == '-') arg0++; ffStrbufSetS(exe, arg0); } } kvm_close(kd); #elif defined(__HAIKU__) image_info info; int32 cookie = 0; while (get_next_image_info(pid, &cookie, &info) == B_OK) { if (info.type != B_APP_IMAGE) continue; ffStrbufSetS(exe, info.name); if (exePath) ffStrbufSet(exePath, exe); break; } #endif if(exe->length == 0) ffStrbufSet(exe, processName); assert(exe->length > 0); uint32_t lastSlashIndex = ffStrbufLastIndexC(exe, '/'); if(lastSlashIndex < exe->length) *exeName = exe->chars + lastSlashIndex + 1; } const char* ffProcessGetBasicInfoLinux(pid_t pid, FFstrbuf* name, pid_t* ppid, int32_t* tty) { if (pid <= 0) return "Invalid pid"; #if defined(__linux__) || defined(__GNU__) char procFilePath[64]; #if __linux__ if (ppid || tty) #endif { snprintf(procFilePath, sizeof(procFilePath), "/proc/%d/stat", (int)pid); char buf[PROC_FILE_BUFFSIZ]; ssize_t nRead = ffReadFileData(procFilePath, sizeof(buf) - 1, buf); if(nRead <= 8) return "ffReadFileData(/proc/pid/stat, PROC_FILE_BUFFSIZ-1, buf) failed"; buf[nRead] = '\0'; // pid (comm) state ppid pgrp session tty const char* pState = NULL; { // comm in `/proc/pid/stat` is not encoded, and may contain ' ', ')' or even `\n` const char* start = memchr(buf, '(', (size_t) nRead); if (!start) return "memchr(stat, '(') failed"; start++; const char* end = memrchr(start, ')', (size_t) nRead - (size_t) (start - buf)); if (!end) return "memrchr(stat, ')') failed"; ffStrbufSetNS(name, (uint32_t) (end - start), start); ffStrbufTrimRightSpace(name); if (name->chars[0] == '\0') return "process name is empty"; pState = end + 2; // skip ") " } #if !__linux__ if (ppid || tty) #endif { int ppid_, tty_; if(sscanf(pState + 2, "%d %*d %*d %d", &ppid_, &tty_) < 2) return "sscanf(stat) failed"; if (ppid) *ppid = (pid_t) ppid_; if (tty) *tty = tty_ & 0xFF; } } #if __linux__ else { snprintf(procFilePath, sizeof(procFilePath), "/proc/%d/comm", (int)pid); ssize_t nRead = ffReadFileBuffer(procFilePath, name); if(nRead <= 0) return "ffReadFileBuffer(/proc/pid/comm, name) failed"; ffStrbufTrimRightSpace(name); } #endif #elif defined(__APPLE__) struct kinfo_proc proc; size_t size = sizeof(proc); if(sysctl( (int[]){CTL_KERN, KERN_PROC, KERN_PROC_PID, pid}, 4, &proc, &size, NULL, 0 )) return "sysctl(KERN_PROC_PID) failed"; ffStrbufSetS(name, proc.kp_proc.p_comm); //trancated to 16 chars if (ppid) *ppid = (pid_t)proc.kp_eproc.e_ppid; if (tty) { *tty = ((proc.kp_eproc.e_tdev >> 24) & 0xFF) == 0x10 ? proc.kp_eproc.e_tdev & 0xFFFFFF : -1; } #elif defined(__FreeBSD__) #ifdef __DragonFly__ #define ki_comm kp_comm #define ki_ppid kp_ppid #define ki_tdev kp_tdev #define ki_flag kp_flags #endif struct kinfo_proc proc; size_t size = sizeof(proc); if(sysctl( (int[]){CTL_KERN, KERN_PROC, KERN_PROC_PID, pid}, 4, &proc, &size, NULL, 0 )) return "sysctl(KERN_PROC_PID) failed"; ffStrbufSetS(name, proc.ki_comm); if (ppid) *ppid = (pid_t)proc.ki_ppid; if (tty) { if (proc.ki_tdev != NODEV && proc.ki_flag & P_CONTROLT) { const char* ttyName = devname(proc.ki_tdev, S_IFCHR); if (ffStrStartsWith(ttyName, "pts/")) *tty = (int32_t) strtol(ttyName + strlen("pts/"), NULL, 10); else *tty = -1; } else *tty = -1; } #elif defined(__NetBSD__) struct kinfo_proc2 proc; size_t size = sizeof(proc); if(sysctl( (int[]){CTL_KERN, KERN_PROC2, KERN_PROC_PID, pid, sizeof(proc), 1}, 6, &proc, &size, NULL, 0 ) != 0) return "sysctl(KERN_PROC_PID) failed"; ffStrbufSetS(name, proc.p_comm); if (ppid) *ppid = (pid_t)proc.p_ppid; if (tty) { if (proc.p_flag & P_CONTROLT) { const char* ttyName = devname(proc.p_tdev, S_IFCHR); if (ffStrStartsWith(ttyName, "pts/")) *tty = (int32_t) strtol(ttyName + strlen("pts/"), NULL, 10); else *tty = -1; } else *tty = -1; } #elif defined(__sun) char path[128]; snprintf(path, sizeof(path), "/proc/%d/psinfo", (int) pid); psinfo_t proc; if (ffReadFileData(path, sizeof(proc), &proc) != sizeof(proc)) return "ffReadFileData(psinfo) failed"; ffStrbufSetS(name, proc.pr_fname); if (ppid) *ppid = proc.pr_ppid; if (tty) *tty = (int) proc.pr_ttydev; #elif defined(__OpenBSD__) kvm_t* kd = kvm_open(NULL, NULL, NULL, KVM_NO_FILES, NULL); int count = 0; const struct kinfo_proc* proc = kvm_getprocs(kd, KERN_PROC_PID, pid, sizeof(struct kinfo_proc), &count); if (proc) { ffStrbufSetS(name, proc->p_comm); if (ppid) *ppid = proc->p_ppid; if (tty) *tty = (int) proc->p_tdev; } kvm_close(kd); if (!proc) return "kvm_getprocs() failed"; #elif defined(__HAIKU__) team_info info; if (get_team_info(pid, &info) == B_OK) { ffStrbufSetS(name, info.name); if (ppid) *ppid = info.parent; } FF_UNUSED(tty); #else return "Unsupported platform"; #endif return NULL; } ================================================ FILE: src/common/impl/processing_windows.c ================================================ #include "fastfetch.h" #include "common/mallocHelper.h" #include "common/processing.h" #include "common/io.h" #include "common/windows/unicode.h" #include "common/windows/nt.h" #include #include #include enum { FF_PIPE_BUFSIZ = 8192 }; static void argvToCmdline(char* const argv[], FFstrbuf* result) { // From https://gist.github.com/jin-x/cdd641d98887524b091fb1f82a68717d FF_STRBUF_AUTO_DESTROY temp = ffStrbufCreate(); for (int i = 0; argv[i] != NULL; i++) { ffStrbufSetS(&temp, argv[i]); // Add slash (\) before double quotes (") and duplicate slashes before it for ( uint32_t pos = ffStrbufFirstIndexC(&temp, '"'), cnt; pos != temp.length; pos = ffStrbufNextIndexC(&temp, pos + cnt * 2, '"') ) { cnt = 1; while (pos > 0 && temp.chars[pos - 1] == '\\') { ++cnt, --pos; } ffStrbufInsertNC(&temp, pos, cnt, '\\'); } // Add quotes around string if whitespace chars are present (with slash duplicating at the end of string) if (ffStrbufFirstIndexS(&temp, " \t") != temp.length) { uint32_t pos = temp.length; uint32_t cnt = 0; while (pos > 0 && temp.chars[pos - 1] == '\\') { ++cnt, --pos; } if (cnt > 0) ffStrbufAppendNC(&temp, cnt, '\\'); ffStrbufPrependC(&temp, '"'); ffStrbufAppendC(&temp, '"'); } // Add space delimiter if (i > 0) ffStrbufAppendC(result, ' '); ffStrbufAppend(result, &temp); ffStrbufClear(&temp); } } const char* ffProcessSpawn(char* const argv[], bool useStdErr, FFProcessHandle* outHandle) { const int32_t timeout = instance.config.general.processingTimeout; wchar_t pipeName[32]; static unsigned pidCounter = 0; swprintf(pipeName, ARRAY_SIZE(pipeName), L"\\\\.\\pipe\\FASTFETCH-%u-%u", instance.state.platform.pid, ++pidCounter); FF_AUTO_CLOSE_FD HANDLE hChildPipeRead = CreateNamedPipeW( pipeName, PIPE_ACCESS_INBOUND | FILE_FLAG_FIRST_PIPE_INSTANCE | (timeout < 0 ? 0 : FILE_FLAG_OVERLAPPED), 0, 1, FF_PIPE_BUFSIZ, FF_PIPE_BUFSIZ, 0, NULL ); if (hChildPipeRead == INVALID_HANDLE_VALUE) return "CreateNamedPipeW(L\"\\\\.\\pipe\\FASTFETCH-$(PID)\") failed"; HANDLE hChildPipeWrite = CreateFileW( pipeName, GENERIC_WRITE, 0, &(SECURITY_ATTRIBUTES){ .nLength = sizeof(SECURITY_ATTRIBUTES), .lpSecurityDescriptor = NULL, .bInheritHandle = TRUE, }, OPEN_EXISTING, 0, NULL ); if (hChildPipeWrite == INVALID_HANDLE_VALUE) return "CreateFileW(L\"\\\\.\\pipe\\FASTFETCH-$(PID)\") failed"; PROCESS_INFORMATION piProcInfo = {}; STARTUPINFOW siStartInfo = { .cb = sizeof(siStartInfo), .dwFlags = STARTF_USESTDHANDLES, }; if (useStdErr) { siStartInfo.hStdOutput = ffGetNullFD(); siStartInfo.hStdError = hChildPipeWrite; } else { siStartInfo.hStdOutput = hChildPipeWrite; siStartInfo.hStdError = ffGetNullFD(); } FF_AUTO_FREE wchar_t* cmdline = NULL; { FF_STRBUF_AUTO_DESTROY buf = ffStrbufCreate(); argvToCmdline(argv, &buf); uint32_t cmdlineBytes = (buf.length + 1) * sizeof(wchar_t); cmdline = malloc(cmdlineBytes); if (!NT_SUCCESS(RtlUTF8ToUnicodeN(cmdline, cmdlineBytes, NULL, buf.chars, buf.length + 1))) return "RtlUTF8ToUnicodeN() failed"; } BOOL success = CreateProcessW( NULL, // application name cmdline, // command line NULL, // process security attributes NULL, // primary thread security attributes TRUE, // handles are inherited 0, // creation flags NULL, // use parent's environment NULL, // use parent's current directory &siStartInfo, // STARTUPINFO pointer &piProcInfo // receives PROCESS_INFORMATION ); NtClose(hChildPipeWrite); if(!success) { if (GetLastError() == ERROR_FILE_NOT_FOUND) return "command not found"; return "CreateProcessW() failed"; } NtClose(piProcInfo.hThread); // we don't need the thread handle outHandle->pid = piProcInfo.hProcess; outHandle->pipeRead = hChildPipeRead; hChildPipeRead = INVALID_HANDLE_VALUE; // ownership transferred, don't close it return NULL; } const char* ffProcessReadOutput(FFProcessHandle* handle, FFstrbuf* buffer) { assert(handle->pipeRead != INVALID_HANDLE_VALUE); assert(handle->pid != INVALID_HANDLE_VALUE); int32_t timeout = instance.config.general.processingTimeout; FF_AUTO_CLOSE_FD HANDLE hProcess = handle->pid; FF_AUTO_CLOSE_FD HANDLE hChildPipeRead = handle->pipeRead; FF_AUTO_CLOSE_FD HANDLE hReadEvent = NULL; handle->pid = INVALID_HANDLE_VALUE; handle->pipeRead = INVALID_HANDLE_VALUE; if (timeout >= 0 && !NT_SUCCESS(NtCreateEvent(&hReadEvent, EVENT_ALL_ACCESS, NULL, SynchronizationEvent, FALSE))) return "NtCreateEvent() failed"; char str[FF_PIPE_BUFSIZ]; uint32_t nRead = 0; IO_STATUS_BLOCK iosb = {}; do { NTSTATUS status = NtReadFile( hChildPipeRead, hReadEvent, NULL, NULL, &iosb, str, (ULONG) sizeof(str), NULL, NULL ); if (status == STATUS_PENDING) { switch (NtWaitForSingleObject(hReadEvent, TRUE, &(LARGE_INTEGER) { .QuadPart = (int64_t) timeout * -10000 })) { case STATUS_WAIT_0: status = iosb.Status; break; case STATUS_TIMEOUT: CancelIo(hChildPipeRead); TerminateProcess(hProcess, 1); return "NtReadFile(hChildPipeRead) timed out"; default: CancelIo(hChildPipeRead); TerminateProcess(hProcess, 1); return "NtWaitForSingleObject(hReadEvent) failed"; } } if (status == STATUS_PIPE_BROKEN || status == STATUS_END_OF_FILE) goto exit; if (!NT_SUCCESS(status)) { CancelIo(hChildPipeRead); TerminateProcess(hProcess, 1); return "NtReadFile(hChildPipeRead) failed"; } nRead = (uint32_t) iosb.Information; ffStrbufAppendNS(buffer, nRead, str); } while (nRead > 0); exit: { PROCESS_BASIC_INFORMATION info = {}; ULONG size; if(NT_SUCCESS(NtQueryInformationProcess(hProcess, ProcessBasicInformation, &info, sizeof(info), &size))) { assert(size == sizeof(info)); if (info.ExitStatus != STILL_ACTIVE && info.ExitStatus != 0) return "Child process exited with an error"; } else return "NtQueryInformationProcess(ProcessBasicInformation) failed"; } return NULL; } bool ffProcessGetInfoWindows(uint32_t pid, uint32_t* ppid, FFstrbuf* pname, FFstrbuf* exe, const char** exeName, FFstrbuf* exePath, bool* gui) { FF_AUTO_CLOSE_FD HANDLE hProcess = NtCurrentProcess(); if(pid != 0) { if (!NT_SUCCESS(NtOpenProcess(&hProcess, PROCESS_QUERY_LIMITED_INFORMATION, &(OBJECT_ATTRIBUTES) { .Length = sizeof(OBJECT_ATTRIBUTES), }, &(CLIENT_ID) { .UniqueProcess = (HANDLE)(uintptr_t) pid }))) return false; } if(ppid) { PROCESS_BASIC_INFORMATION info = {}; ULONG size; if(NT_SUCCESS(NtQueryInformationProcess(hProcess, ProcessBasicInformation, &info, sizeof(info), &size))) { assert(size == sizeof(info)); *ppid = (uint32_t)info.InheritedFromUniqueProcessId; } else return false; } if(exe) { // TODO: It's possible to query the command line with `NtQueryInformationProcess(60/*ProcessCommandLineInformation*/)` since Windows 8.1 alignas(UNICODE_STRING) uint8_t buffer[4096]; ULONG size; if(NT_SUCCESS(NtQueryInformationProcess(hProcess, ProcessImageFileNameWin32, &buffer, sizeof(buffer), &size))) { UNICODE_STRING* imagePath = (UNICODE_STRING*)buffer; ffStrbufSetNWS(exe, imagePath->Length / sizeof(wchar_t), imagePath->Buffer); if (exePath) ffStrbufSet(exePath, exe); if (pname && exeName) { *exeName = exe->chars + ffStrbufLastIndexC(exe, '\\') + 1; ffStrbufSetS(pname, *exeName); } } else return false; } if (gui) { SECTION_IMAGE_INFORMATION info = {}; ULONG size; if(NT_SUCCESS(NtQueryInformationProcess(hProcess, ProcessImageInformation, &info, sizeof(info), &size))) { assert(size == sizeof(info)); *gui = info.SubSystemType == IMAGE_SUBSYSTEM_WINDOWS_GUI; } else return false; } return true; } ================================================ FILE: src/common/impl/properties.c ================================================ #include "fastfetch.h" #include "common/properties.h" #include "common/io.h" #include "common/mallocHelper.h" #include #include #ifdef _WIN32 #include "common/windows/getline.h" #endif bool ffParsePropLinePointer(const char** line, const char* start, FFstrbuf* buffer) { if(**line == '\0') return false; //Skip any amount of whitespace at the begin of line while(**line == ' ' || **line == '\t') ++(*line); while(*start != '\0') { // Any amount of whitespace in the format string matches any amount of whitespace in the line, even none if(*start == ' ' || *start == '\t') { while(*start == ' ' || *start == '\t') ++start; while(**line == ' ' || **line == '\t') ++(*line); continue; } //Line doesn't match start, skip it if(tolower(**line) != tolower(*start) || **line == '\0') return false; //Line and start match, continue testing ++(*line); ++start; } char valueEnd = '\n'; //Allow faster parsing of XML if(*(*line - 1) == '>') valueEnd = '<'; //Skip any amount of whitespace at the begin of the value while(**line == ' ' || **line == '\t') ++(*line); //Allow faster parsing of quotet values if(**line == '"' || **line == '\'') { valueEnd = **line; ++(*line); } //Copy the value to the buffer while(**line != valueEnd && **line != '\n' && **line != '\0') { ffStrbufAppendC(buffer, **line); ++(*line); } ffStrbufTrimRight(buffer, ' '); return true; } bool ffParsePropLines(const char* lines, const char* start, FFstrbuf* buffer) { while(!ffParsePropLinePointer(&lines, start, buffer)) { while(*lines != '\0' && *lines != '\n') ++lines; if(*lines == '\0') return false; //Skip '\n' ++lines; } return true; } // The following functions return true if the file was found, independently if start was found // Buffers which already contain content are not overwritten // The last occurrence of start in the first file will be the one used bool ffParsePropFileValues(const char* filename, uint32_t numQueries, FFpropquery* queries) { FF_AUTO_CLOSE_FILE FILE* file = fopen(filename, "r"); if (file == NULL) return false; bool valueStorage[32]; bool* unsetValues = valueStorage; if (numQueries > ARRAY_SIZE(valueStorage)) unsetValues = malloc(sizeof(bool) * numQueries); bool allSet = true; for (uint32_t i = 0; i < numQueries; i++) { unsetValues[i] = queries[i].buffer->length == 0; if (unsetValues[i]) allSet = false; } if (!allSet) { FF_AUTO_FREE char* line = NULL; size_t len = 0; while (getline(&line, &len, file) != -1) { for(uint32_t i = 0; i < numQueries; i++) { if(!unsetValues[i]) continue; uint32_t currentLength = queries[i].buffer->length; queries[i].buffer->length = 0; if(!ffParsePropLine(line, queries[i].start, queries[i].buffer)) queries[i].buffer->length = currentLength; } } } if(unsetValues != valueStorage) free(unsetValues); return true; } bool ffParsePropFileHomeValues(const char* relativeFile, uint32_t numQueries, FFpropquery* queries) { FF_STRBUF_AUTO_DESTROY absolutePath = ffStrbufCreateF("%s/%s", instance.state.platform.homeDir.chars, relativeFile); return ffParsePropFileValues(absolutePath.chars, numQueries, queries); } bool ffParsePropFileListValues(const FFlist* list, const char* relativeFile, uint32_t numQueries, FFpropquery* queries) { bool foundAFile = false; FF_LIST_FOR_EACH(FFstrbuf, dirPrefix, *list) { const uint32_t dirPrefixLength = dirPrefix->length; ffStrbufAppendS(dirPrefix, relativeFile); if(ffParsePropFileValues(dirPrefix->chars, numQueries, queries)) foundAFile = true; ffStrbufSubstrBefore(dirPrefix, dirPrefixLength); bool allSet = true; for(uint32_t k = 0; k < numQueries; k++) { if(queries[k].buffer->length == 0) { allSet = false; break; } } if(allSet) break; } return foundAFile; } ================================================ FILE: src/common/impl/settings.c ================================================ #include "fastfetch.h" #include "common/settings.h" #include "common/library.h" #include "common/thread.h" #include "common/io.h" #include #ifdef FF_HAVE_GIO #include typedef struct GVariantGetters { FF_LIBRARY_SYMBOL(g_variant_dup_string) FF_LIBRARY_SYMBOL(g_variant_get_boolean) FF_LIBRARY_SYMBOL(g_variant_get_int32) FF_LIBRARY_SYMBOL(g_variant_unref) } GVariantGetters; static FFvariant getGVariantValue(GVariant* variant, FFvarianttype type, const GVariantGetters* variantGetters) { FFvariant result; if(variant == NULL) result = FF_VARIANT_NULL; else if(type == FF_VARIANT_TYPE_STRING) result = (FFvariant) {.strValue = variantGetters->ffg_variant_dup_string(variant, NULL)}; // Dup string, so that variant itself can be freed else if(type == FF_VARIANT_TYPE_BOOL) result = (FFvariant) {.boolValue = (bool) variantGetters->ffg_variant_get_boolean(variant), .boolValueSet = true}; else if(type == FF_VARIANT_TYPE_INT) result = (FFvariant) {.intValue = variantGetters->ffg_variant_get_int32(variant)}; else result = FF_VARIANT_NULL; if(variant) variantGetters->ffg_variant_unref(variant); return result; } typedef struct GSettingsData { FF_LIBRARY_SYMBOL(g_settings_schema_source_lookup) FF_LIBRARY_SYMBOL(g_settings_schema_has_key) FF_LIBRARY_SYMBOL(g_settings_new_full) FF_LIBRARY_SYMBOL(g_settings_get_value) FF_LIBRARY_SYMBOL(g_settings_get_user_value) FF_LIBRARY_SYMBOL(g_settings_get_default_value) FF_LIBRARY_SYMBOL(g_settings_schema_source_get_default) GSettingsSchemaSource* schemaSource; GVariantGetters variantGetters; bool inited; } GSettingsData; static const GSettingsData* getGSettingsData(void) { static GSettingsData data; if (!data.inited) { data.inited = true; FF_LIBRARY_LOAD(libgsettings, NULL, "libgio-2.0" FF_LIBRARY_EXTENSION, 1); FF_LIBRARY_LOAD_SYMBOL_VAR(libgsettings, data, g_settings_schema_source_lookup, NULL) FF_LIBRARY_LOAD_SYMBOL_VAR(libgsettings, data, g_settings_schema_has_key, NULL) FF_LIBRARY_LOAD_SYMBOL_VAR(libgsettings, data, g_settings_new_full, NULL) FF_LIBRARY_LOAD_SYMBOL_VAR(libgsettings, data, g_settings_get_value, NULL) FF_LIBRARY_LOAD_SYMBOL_VAR(libgsettings, data, g_settings_get_user_value, NULL) FF_LIBRARY_LOAD_SYMBOL_VAR(libgsettings, data, g_settings_get_default_value, NULL) FF_LIBRARY_LOAD_SYMBOL_VAR(libgsettings, data, g_settings_schema_source_get_default, NULL) FF_LIBRARY_LOAD_SYMBOL_VAR(libgsettings, data.variantGetters, g_variant_dup_string, NULL) FF_LIBRARY_LOAD_SYMBOL_VAR(libgsettings, data.variantGetters, g_variant_get_boolean, NULL) FF_LIBRARY_LOAD_SYMBOL_VAR(libgsettings, data.variantGetters, g_variant_get_int32, NULL) FF_LIBRARY_LOAD_SYMBOL_VAR(libgsettings, data.variantGetters, g_variant_unref, NULL); data.schemaSource = data.ffg_settings_schema_source_get_default(); if (data.schemaSource) libgsettings = NULL; } if(!data.schemaSource) return NULL; return &data; } FFvariant ffSettingsGetGSettings(const char* schemaName, const char* path, const char* key, FFvarianttype type) { const GSettingsData* data = getGSettingsData(); if(data == NULL) return FF_VARIANT_NULL; GSettingsSchema* schema = data->ffg_settings_schema_source_lookup(data->schemaSource, schemaName, true); if(schema == NULL) return FF_VARIANT_NULL; if(data->ffg_settings_schema_has_key(schema, key) == false) return FF_VARIANT_NULL; GSettings* settings = data->ffg_settings_new_full(schema, NULL, path); if(settings == NULL) return FF_VARIANT_NULL; GVariant* variant = data->ffg_settings_get_value(settings, key); if(variant != NULL) return getGVariantValue(variant, type, &data->variantGetters); variant = data->ffg_settings_get_user_value(settings, key); if(variant != NULL) return getGVariantValue(variant, type, &data->variantGetters); variant = data->ffg_settings_get_default_value(settings, key); return getGVariantValue(variant, type, &data->variantGetters); } #else //FF_HAVE_GIO FFvariant ffSettingsGetGSettings(const char* schemaName, const char* path, const char* key, FFvarianttype type) { FF_UNUSED(schemaName, path, key, type) return FF_VARIANT_NULL; } #endif //FF_HAVE_GIO #ifdef FF_HAVE_DCONF #include typedef struct DConfData { FF_LIBRARY_SYMBOL(dconf_client_read_full) FF_LIBRARY_SYMBOL(dconf_client_new) GVariantGetters variantGetters; DConfClient* client; bool inited; } DConfData; static const DConfData* getDConfData(void) { static DConfData data; if (!data.inited) { data.inited = true; FF_LIBRARY_LOAD(libdconf, NULL, "libdconf" FF_LIBRARY_EXTENSION, 2); FF_LIBRARY_LOAD_SYMBOL_VAR(libdconf, data, dconf_client_read_full, NULL) FF_LIBRARY_LOAD_SYMBOL_VAR(libdconf, data, dconf_client_new, NULL) FF_LIBRARY_LOAD_SYMBOL_VAR(libdconf, data.variantGetters, g_variant_dup_string, NULL) FF_LIBRARY_LOAD_SYMBOL_VAR(libdconf, data.variantGetters, g_variant_get_boolean, NULL) FF_LIBRARY_LOAD_SYMBOL_VAR(libdconf, data.variantGetters, g_variant_get_int32, NULL) FF_LIBRARY_LOAD_SYMBOL_VAR(libdconf, data.variantGetters, g_variant_unref, NULL) data.client = data.ffdconf_client_new(); if (data.client) libdconf = NULL; } if(!data.client) return NULL; return &data; } FFvariant ffSettingsGetDConf(const char* key, FFvarianttype type) { const DConfData* data = getDConfData(); if(data == NULL) return FF_VARIANT_NULL; GVariant* variant = data->ffdconf_client_read_full(data->client, key, DCONF_READ_FLAGS_NONE, NULL); if(variant != NULL) return getGVariantValue(variant, type, &data->variantGetters); variant = data->ffdconf_client_read_full(data->client, key, DCONF_READ_USER_VALUE, NULL); if(variant != NULL) return getGVariantValue(variant, type, &data->variantGetters); variant = data->ffdconf_client_read_full(data->client, key, DCONF_READ_DEFAULT_VALUE, NULL); return getGVariantValue(variant, type, &data->variantGetters); } #else //FF_HAVE_DCONF FFvariant ffSettingsGetDConf(const char* key, FFvarianttype type) { FF_UNUSED(key, type) return FF_VARIANT_NULL; } #endif //FF_HAVE_DCONF FFvariant ffSettingsGetGnome(const char* dconfKey, const char* gsettingsSchemaName, const char* gsettingsPath, const char* gsettingsKey, FFvarianttype type) { FFvariant gsettings = ffSettingsGetGSettings(gsettingsSchemaName, gsettingsPath, gsettingsKey, type); if( (type == FF_VARIANT_TYPE_BOOL && gsettings.boolValueSet) || (type != FF_VARIANT_TYPE_BOOL && gsettings.strValue != NULL) ) return gsettings; return ffSettingsGetDConf(dconfKey, type); } #ifdef FF_HAVE_DBUS #include "common/dbus.h" FFvariant ffSettingsGetXFConf(const char* channelName, const char* propertyName, FFvarianttype type) { FF_DBUS_AUTO_DESTROY_DATA FFDBusData dbus = {}; if (ffDBusLoadData(DBUS_BUS_SESSION, &dbus) != NULL) return FF_VARIANT_NULL; DBusMessage* reply = ffDBusGetMethodReply(&dbus, "org.xfce.Xfconf", "/org/xfce/Xfconf", "org.xfce.Xfconf", "GetProperty", channelName, propertyName); if(!reply) return FF_VARIANT_NULL; DBusMessageIter rootIterator; if(!dbus.lib->ffdbus_message_iter_init(reply, &rootIterator)) { dbus.lib->ffdbus_message_unref(reply); return FF_VARIANT_NULL; } if(type == FF_VARIANT_TYPE_INT) { int32_t value; if (ffDBusGetInt(&dbus, &rootIterator, &value)) { dbus.lib->ffdbus_message_unref(reply); return (FFvariant) { .intValue = value }; } return FF_VARIANT_NULL; } if(type == FF_VARIANT_TYPE_STRING) { FFstrbuf value = ffStrbufCreate(); if (ffDBusGetString(&dbus, &rootIterator, &value)) { dbus.lib->ffdbus_message_unref(reply); return (FFvariant) { .strValue = value.chars }; // Leaks value.chars } return FF_VARIANT_NULL; } if(type == FF_VARIANT_TYPE_BOOL) { bool value; if (ffDBusGetBool(&dbus, &rootIterator, &value)) { dbus.lib->ffdbus_message_unref(reply); return (FFvariant) { .boolValue = value, .boolValueSet = true }; } } return FF_VARIANT_NULL; } #define FF_DBUS_ITER_CONTINUE(dbus, iterator) \ { \ if(!(dbus).lib->ffdbus_message_iter_next(iterator)) \ break; \ continue; \ } FFvariant ffSettingsGetXFConfFirstMatch(const char* channelName, const char* propertyPrefix, FFvarianttype type, void* data, FFTestXfconfPropCallback* cb) { FF_DBUS_AUTO_DESTROY_DATA FFDBusData dbus = {}; if (ffDBusLoadData(DBUS_BUS_SESSION, &dbus) != NULL) return FF_VARIANT_NULL; DBusMessage* reply = ffDBusGetMethodReply(&dbus, "org.xfce.Xfconf", "/org/xfce/Xfconf", "org.xfce.Xfconf", "GetAllProperties", channelName, propertyPrefix); if(!reply) return FF_VARIANT_NULL; DBusMessageIter rootIterator; if(!dbus.lib->ffdbus_message_iter_init(reply, &rootIterator)) { dbus.lib->ffdbus_message_unref(reply); return FF_VARIANT_NULL; } DBusMessageIter arrayIterator; dbus.lib->ffdbus_message_iter_recurse(&rootIterator, &arrayIterator); while(true) { if(dbus.lib->ffdbus_message_iter_get_arg_type(&arrayIterator) != DBUS_TYPE_DICT_ENTRY) FF_DBUS_ITER_CONTINUE(dbus, &arrayIterator) DBusMessageIter dictIterator; dbus.lib->ffdbus_message_iter_recurse(&arrayIterator, &dictIterator); const char* key; dbus.lib->ffdbus_message_iter_get_basic(&dictIterator, &key); if (cb(data, key)) FF_DBUS_ITER_CONTINUE(dbus, &arrayIterator) dbus.lib->ffdbus_message_iter_next(&dictIterator); if(type == FF_VARIANT_TYPE_INT) { int32_t value; if (ffDBusGetInt(&dbus, &dictIterator, &value)) { dbus.lib->ffdbus_message_unref(reply); return (FFvariant) { .intValue = value }; } return FF_VARIANT_NULL; } if(type == FF_VARIANT_TYPE_STRING) { FFstrbuf value = ffStrbufCreate(); if (ffDBusGetString(&dbus, &dictIterator, &value)) { dbus.lib->ffdbus_message_unref(reply); return (FFvariant) { .strValue = value.chars }; // Leaks value.chars } return FF_VARIANT_NULL; } if(type == FF_VARIANT_TYPE_BOOL) { bool value; if (ffDBusGetBool(&dbus, &dictIterator, &value)) { dbus.lib->ffdbus_message_unref(reply); return (FFvariant) { .boolValue = value, .boolValueSet = true }; } } return FF_VARIANT_NULL; } return FF_VARIANT_NULL; } #else //FF_HAVE_DBUS FFvariant ffSettingsGetXFConf(const char* channelName, const char* propertyName, FFvarianttype type) { FF_UNUSED(channelName, propertyName, type) return FF_VARIANT_NULL; } FFvariant ffSettingsGetXFConfFirstMatch(const char* channelName, const char* propertyPrefix, FFvarianttype type, void* data, FFTestXfconfPropCallback* cb) { FF_UNUSED(channelName, propertyPrefix, type, data, cb); return FF_VARIANT_NULL; } #endif //FF_HAVE_DBUS #ifdef FF_HAVE_SQLITE3 #include typedef struct SQLiteData { FF_LIBRARY_SYMBOL(sqlite3_open_v2) FF_LIBRARY_SYMBOL(sqlite3_prepare_v2) FF_LIBRARY_SYMBOL(sqlite3_step) FF_LIBRARY_SYMBOL(sqlite3_data_count) FF_LIBRARY_SYMBOL(sqlite3_column_int) FF_LIBRARY_SYMBOL(sqlite3_column_text) FF_LIBRARY_SYMBOL(sqlite3_finalize) FF_LIBRARY_SYMBOL(sqlite3_close) bool inited; } SQLiteData; static const SQLiteData* getSQLiteData(void) { static SQLiteData data; if (!data.inited) { data.inited = true; FF_LIBRARY_LOAD(libsqlite, NULL, "libsqlite3" FF_LIBRARY_EXTENSION, 1); FF_LIBRARY_LOAD_SYMBOL_VAR(libsqlite, data, sqlite3_open_v2, NULL) FF_LIBRARY_LOAD_SYMBOL_VAR(libsqlite, data, sqlite3_prepare_v2, NULL) FF_LIBRARY_LOAD_SYMBOL_VAR(libsqlite, data, sqlite3_step, NULL) FF_LIBRARY_LOAD_SYMBOL_VAR(libsqlite, data, sqlite3_data_count, NULL) FF_LIBRARY_LOAD_SYMBOL_VAR(libsqlite, data, sqlite3_column_int, NULL) FF_LIBRARY_LOAD_SYMBOL_VAR(libsqlite, data, sqlite3_column_text, NULL) FF_LIBRARY_LOAD_SYMBOL_VAR(libsqlite, data, sqlite3_finalize, NULL) FF_LIBRARY_LOAD_SYMBOL_VAR(libsqlite, data, sqlite3_close, NULL) libsqlite = NULL; } if (!data.ffsqlite3_close) return NULL; return &data; } int ffSettingsGetSQLite3Int(const char* dbPath, const char* query) { if(!ffPathExists(dbPath, FF_PATHTYPE_FILE)) return 0; const SQLiteData* data = getSQLiteData(); if(data == NULL) return 0; sqlite3* db; if(data->ffsqlite3_open_v2(dbPath, &db, SQLITE_OPEN_READONLY, NULL) != SQLITE_OK) return 0; sqlite3_stmt* stmt; if(data->ffsqlite3_prepare_v2(db, query, (int) strlen(query), &stmt, NULL) != SQLITE_OK) { data->ffsqlite3_close(db); return 0; } if(data->ffsqlite3_step(stmt) != SQLITE_ROW || data->ffsqlite3_data_count(stmt) < 1) { data->ffsqlite3_finalize(stmt); data->ffsqlite3_close(db); return 0; } int result = data->ffsqlite3_column_int(stmt, 0); data->ffsqlite3_finalize(stmt); data->ffsqlite3_close(db); return result; } bool ffSettingsGetSQLite3String(const char* dbPath, const char* query, FFstrbuf* result) { if(!ffPathExists(dbPath, FF_PATHTYPE_FILE)) return false; const SQLiteData* data = getSQLiteData(); if(data == NULL) return false; sqlite3* db; if(data->ffsqlite3_open_v2(dbPath, &db, SQLITE_OPEN_READONLY, NULL) != SQLITE_OK) return false; sqlite3_stmt* stmt; if(data->ffsqlite3_prepare_v2(db, query, (int) strlen(query), &stmt, NULL) != SQLITE_OK) { data->ffsqlite3_close(db); return false; } if(data->ffsqlite3_step(stmt) != SQLITE_ROW || data->ffsqlite3_data_count(stmt) < 1) { data->ffsqlite3_finalize(stmt); data->ffsqlite3_close(db); return false; } ffStrbufSetS(result, (const char *) data->ffsqlite3_column_text(stmt, 0)); data->ffsqlite3_finalize(stmt); data->ffsqlite3_close(db); return true; } #else //FF_HAVE_SQLITE3 int ffSettingsGetSQLite3Int(const char* dbPath, const char* query) { FF_UNUSED(dbPath, query) return 0; } bool ffSettingsGetSQLite3String(const char* dbPath, const char* query, FFstrbuf* result) { FF_UNUSED(dbPath, query, result) return false; } #endif //FF_HAVE_SQLITE3 #ifdef __ANDROID__ #include bool ffSettingsGetAndroidProperty(const char* propName, FFstrbuf* result) { ffStrbufEnsureFree(result, PROP_VALUE_MAX); int len = __system_property_get(propName, result->chars + result->length); if (len <= 0) return false; result->length += (uint32_t) len; result->chars[result->length] = '\0'; return true; } #elif defined(__FreeBSD__) #include bool ffSettingsGetFreeBSDKenv(const char* propName, FFstrbuf* result) { //https://wiki.ghostbsd.org/index.php/Kenv ffStrbufEnsureFree(result, KENV_MVALLEN); int len = kenv(KENV_GET, propName, result->chars + result->length, KENV_MVALLEN); if (len <= 1) return false; // number of bytes copied, including NUL terminator result->length += (uint32_t) len - 1; return true; } #endif ================================================ FILE: src/common/impl/size.c ================================================ #include "common/size.h" #include static void appendNum(FFstrbuf* result, uint64_t bytes, uint32_t base, const char** prefixes) { const FFOptionsDisplay* options = &instance.config.display; double size = (double) bytes; uint8_t counter = 0; while(size >= base && counter < options->sizeMaxPrefix && prefixes[counter + 1]) { size /= base; counter++; } if (counter == 0) ffStrbufAppendUInt(result, bytes); else ffStrbufAppendDouble(result, size, (int8_t) options->sizeNdigits, true); if (options->sizeSpaceBeforeUnit != FF_SPACE_BEFORE_UNIT_NEVER) ffStrbufAppendC(result, ' '); ffStrbufAppendS(result, prefixes[counter]); } void ffSizeAppendNum(uint64_t bytes, FFstrbuf* result) { const FFOptionsDisplay* options = &instance.config.display; switch (options->sizeBinaryPrefix) { case FF_SIZE_BINARY_PREFIX_TYPE_IEC: appendNum(result, bytes, 1024, (const char*[]) {"B", "KiB", "MiB", "GiB", "TiB", "PiB", "EiB", "ZiB", "YiB", NULL}); break; case FF_SIZE_BINARY_PREFIX_TYPE_SI: appendNum(result, bytes, 1000, (const char*[]) {"B", "kB", "MB", "GB", "TB", "PB", "EB", "ZB", "YB", NULL}); break; case FF_SIZE_BINARY_PREFIX_TYPE_JEDEC: appendNum(result, bytes, 1024, (const char*[]) {"B", "KB", "MB", "GB", "TB", NULL}); break; default: appendNum(result, bytes, 1024, (const char*[]) {"B", NULL}); break; } } ================================================ FILE: src/common/impl/smbiosHelper.c ================================================ #include "common/smbiosHelper.h" #include "common/io.h" #include "common/unused.h" #include "common/mallocHelper.h" #include "common/debug.h" bool ffIsSmbiosValueSet(FFstrbuf* value) { ffStrbufTrimRightSpace(value); return value->length > 0 && !ffStrbufStartsWithIgnCaseS(value, "To be filled") && !ffStrbufStartsWithIgnCaseS(value, "To be set") && !ffStrbufStartsWithIgnCaseS(value, "OEM") && !ffStrbufStartsWithIgnCaseS(value, "O.E.M.") && !ffStrbufStartsWithIgnCaseS(value, "System Product") && !ffStrbufStartsWithIgnCaseS(value, "Unknown Product") && !ffStrbufIgnCaseEqualS(value, "None") && !ffStrbufIgnCaseEqualS(value, "System Name") && !ffStrbufIgnCaseEqualS(value, "System Version") && !ffStrbufIgnCaseEqualS(value, "Default string") && !ffStrbufIgnCaseEqualS(value, "Undefined") && !ffStrbufIgnCaseEqualS(value, "Not Specified") && !ffStrbufIgnCaseEqualS(value, "Not Applicable") && !ffStrbufIgnCaseEqualS(value, "Not Defined") && !ffStrbufIgnCaseEqualS(value, "Not Available") && !ffStrbufIgnCaseEqualS(value, "INVALID") && !ffStrbufIgnCaseEqualS(value, "Type1ProductConfigId") && !ffStrbufIgnCaseEqualS(value, "TBD by OEM") && !ffStrbufIgnCaseEqualS(value, "No Enclosure") && !ffStrbufIgnCaseEqualS(value, "Chassis Version") && !ffStrbufIgnCaseEqualS(value, "All Series") && !ffStrbufIgnCaseEqualS(value, "N/A") && !ffStrbufIgnCaseEqualS(value, "Unknown") && !ffStrbufIgnCaseEqualS(value, "Standard") && !ffStrbufIgnCaseEqualS(value, "0x0000") ; } const FFSmbiosHeader* ffSmbiosNextEntry(const FFSmbiosHeader* header) { const char* p = ((const char*) header) + header->Length; if (*p) { do p += strlen(p) + 1; while (*p); } else // The terminator is always double 0 even if there is no string p ++; return (const FFSmbiosHeader*) (p + 1); } #if defined(__linux__) || defined(__FreeBSD__) || defined(__NetBSD__) || defined(__sun) || defined(__HAIKU__) || defined(__OpenBSD__) || defined(__GNU__) #include #include #include #include #include #ifdef __linux__ #include "common/properties.h" #elif defined(__FreeBSD__) #include "common/settings.h" #define loff_t off_t // FreeBSD doesn't have loff_t #elif defined(__sun) #define loff_t off_t #elif defined(__NetBSD__) #include "common/sysctl.h" #define loff_t off_t #endif #ifdef __linux__ bool ffGetSmbiosValue(const char* devicesPath, const char* classPath, FFstrbuf* buffer) { if (ffReadFileBuffer(devicesPath, buffer)) { ffStrbufTrimRightSpace(buffer); if(ffIsSmbiosValueSet(buffer)) return true; } if (ffReadFileBuffer(classPath, buffer)) { ffStrbufTrimRightSpace(buffer); if(ffIsSmbiosValueSet(buffer)) return true; } ffStrbufClear(buffer); return false; } #endif typedef struct FFSmbios20EntryPoint { uint8_t AnchorString[4]; uint8_t EntryPointStructureChecksum; uint8_t EntryPointLength; uint8_t SmbiosMajorVersion; uint8_t SmbiosMinorVersion; uint16_t MaximumStructureSize; uint8_t EntryPointRevision; uint8_t FormattedArea[5]; uint8_t IntermediateAnchorString[5]; uint8_t IntermediateChecksum; uint16_t StructureTableLength; uint32_t StructureTableAddress; uint16_t NumberOfSmbiosStructures; uint8_t SmbiosBcdRevision; } __attribute__((__packed__)) FFSmbios20EntryPoint; static_assert(offsetof(FFSmbios20EntryPoint, SmbiosBcdRevision) == 0x1E, "FFSmbios20EntryPoint: Wrong struct alignment"); typedef struct FFSmbios30EntryPoint { uint8_t AnchorString[5]; uint8_t EntryPointStructureChecksum; uint8_t EntryPointLength; uint8_t SmbiosMajorVersion; uint8_t SmbiosMinorVersion; uint8_t SmbiosDocrev; uint8_t EntryPointRevision; uint8_t Reversed; uint32_t StructureTableMaximumSize; uint64_t StructureTableAddress; } __attribute__((__packed__)) FFSmbios30EntryPoint; static_assert(offsetof(FFSmbios30EntryPoint, StructureTableAddress) == 0x10, "FFSmbios30EntryPoint: Wrong struct alignment"); typedef union FFSmbiosEntryPoint { FFSmbios20EntryPoint Smbios20; FFSmbios30EntryPoint Smbios30; } FFSmbiosEntryPoint; const FFSmbiosHeaderTable* ffGetSmbiosHeaderTable() { static FFstrbuf buffer; static FFSmbiosHeaderTable table; if (buffer.chars == NULL) { FF_DEBUG("Initializing SMBIOS buffer"); ffStrbufInit(&buffer); #if !__HAIKU__ && !__OpenBSD__ && !__DragonFly__ && !__GNU__ #ifdef __linux__ FF_DEBUG("Using Linux implementation - trying /sys/firmware/dmi/tables/DMI"); if (!ffAppendFileBuffer("/sys/firmware/dmi/tables/DMI", &buffer)) #endif { #if !defined(__sun) && !defined(__NetBSD__) FF_DEBUG("Using memory-mapped implementation"); FF_STRBUF_AUTO_DESTROY strEntryAddress = ffStrbufCreate(); #ifdef __FreeBSD__ FF_DEBUG("Using FreeBSD kenv implementation"); if (!ffSettingsGetFreeBSDKenv("hint.smbios.0.mem", &strEntryAddress)) { FF_DEBUG("Failed to get SMBIOS address from FreeBSD kenv"); return NULL; } FF_DEBUG("Got SMBIOS address from kenv: %s", strEntryAddress.chars); #elif defined(__linux__) { FF_DEBUG("Using Linux EFI systab implementation"); FF_STRBUF_AUTO_DESTROY systab = ffStrbufCreate(); if (!ffAppendFileBuffer("/sys/firmware/efi/systab", &systab)) { FF_DEBUG("Failed to read /sys/firmware/efi/systab"); return NULL; } if (!ffParsePropLines(systab.chars, "SMBIOS3=", &strEntryAddress) && !ffParsePropLines(systab.chars, "SMBIOS=", &strEntryAddress)) { FF_DEBUG("Failed to find SMBIOS entry in systab"); return NULL; } FF_DEBUG("Found SMBIOS entry in systab: %s", strEntryAddress.chars); } #endif loff_t entryAddress = (loff_t) strtol(strEntryAddress.chars, NULL, 16); if (entryAddress == 0) { FF_DEBUG("Invalid SMBIOS entry address: 0"); return NULL; } FF_DEBUG("Parsed SMBIOS entry address: 0x%lx", (unsigned long)entryAddress); FF_AUTO_CLOSE_FD int fd = open("/dev/mem", O_RDONLY | O_CLOEXEC); if (fd < 0) { FF_DEBUG("Failed to open /dev/mem: %s", strerror(errno)); return NULL; } FF_DEBUG("/dev/mem opened successfully with fd=%d", fd); FFSmbiosEntryPoint entryPoint; FF_DEBUG("Attempting to read %zu bytes from physical address 0x%lx", sizeof(entryPoint), (unsigned long)entryAddress); if (pread(fd, &entryPoint, sizeof(entryPoint), entryAddress) < 0x10) { FF_DEBUG("pread failed, trying mmap"); // `pread /dev/mem` returns EFAULT in FreeBSD // https://stackoverflow.com/questions/69372330/how-to-read-dev-mem-using-read void* p = mmap(NULL, sizeof(entryPoint), PROT_READ, MAP_SHARED, fd, entryAddress); if (p == MAP_FAILED) { FF_DEBUG("mmap failed: %s", strerror(errno)); return NULL; } memcpy(&entryPoint, p, sizeof(entryPoint)); munmap(p, sizeof(entryPoint)); FF_DEBUG("Successfully read entry point data via mmap"); } else { FF_DEBUG("Successfully read entry point data via pread"); } #else // Sun or NetBSD FF_DEBUG("Using %s specific implementation", #ifdef __NetBSD__ "NetBSD" #else "SunOS" #endif ); FF_AUTO_CLOSE_FD int fd = open("/dev/smbios", O_RDONLY | O_CLOEXEC); if (fd < 0) { FF_DEBUG("Failed to open /dev/smbios: %s", strerror(errno)); return NULL; } FF_DEBUG("/dev/smbios opened successfully with fd=%d", fd); FFSmbiosEntryPoint entryPoint; #ifdef __NetBSD__ off_t addr = (off_t) ffSysctlGetInt64("machdep.smbios", 0); if (addr == 0) { FF_DEBUG("Failed to get SMBIOS address from sysctl"); return NULL; } FF_DEBUG("Got SMBIOS address from sysctl: 0x%lx", (unsigned long)addr); if (pread(fd, &entryPoint, sizeof(entryPoint), addr) < 1) { FF_DEBUG("Failed to read SMBIOS entry point: %s", strerror(errno)); return NULL; } FF_DEBUG("Successfully read SMBIOS entry point"); #else FF_DEBUG("Reading SMBIOS entry point from /dev/smbios"); if (ffReadFDData(fd, sizeof(entryPoint), &entryPoint) < 1) { FF_DEBUG("Failed to read SMBIOS entry point: %s", strerror(errno)); return NULL; } FF_DEBUG("Successfully read SMBIOS entry point"); #endif #endif uint32_t tableLength = 0; loff_t tableAddress = 0; if (memcmp(entryPoint.Smbios20.AnchorString, "_SM_", sizeof(entryPoint.Smbios20.AnchorString)) == 0) { FF_DEBUG("Found SMBIOS 2.0 entry point"); if (entryPoint.Smbios20.EntryPointLength != sizeof(entryPoint.Smbios20)) { FF_DEBUG("Invalid SMBIOS 2.0 entry point length: %u (expected %zu)", entryPoint.Smbios20.EntryPointLength, sizeof(entryPoint.Smbios20)); return NULL; } tableLength = entryPoint.Smbios20.StructureTableLength; tableAddress = (loff_t) entryPoint.Smbios20.StructureTableAddress; FF_DEBUG("SMBIOS 2.0: tableLength=0x%x, tableAddress=0x%lx, version=%u.%u", tableLength, (unsigned long)tableAddress, entryPoint.Smbios20.SmbiosMajorVersion, entryPoint.Smbios20.SmbiosMinorVersion); } else if (memcmp(entryPoint.Smbios30.AnchorString, "_SM3_", sizeof(entryPoint.Smbios30.AnchorString)) == 0) { FF_DEBUG("Found SMBIOS 3.0 entry point"); if (entryPoint.Smbios30.EntryPointLength != sizeof(entryPoint.Smbios30)) { FF_DEBUG("Invalid SMBIOS 3.0 entry point length: %u (expected %zu)", entryPoint.Smbios30.EntryPointLength, sizeof(entryPoint.Smbios30)); return NULL; } tableLength = entryPoint.Smbios30.StructureTableMaximumSize; tableAddress = (loff_t) entryPoint.Smbios30.StructureTableAddress; FF_DEBUG("SMBIOS 3.0: tableLength=0x%x, tableAddress=0x%lx, version=%u.%u.%u", tableLength, (unsigned long)tableAddress, entryPoint.Smbios30.SmbiosMajorVersion, entryPoint.Smbios30.SmbiosMinorVersion, entryPoint.Smbios30.SmbiosDocrev); } else { FF_DEBUG("Unknown SMBIOS entry point format"); return NULL; } ffStrbufEnsureFixedLengthFree(&buffer, tableLength); FF_DEBUG("Attempting to read SMBIOS table data: %u bytes at 0x%lx", tableLength, (unsigned long)tableAddress); if (pread(fd, buffer.chars, tableLength, tableAddress) == (ssize_t) tableLength) { buffer.length = tableLength; buffer.chars[buffer.length] = '\0'; FF_DEBUG("Successfully read SMBIOS table data: %u bytes", tableLength); } else { FF_DEBUG("pread failed, trying mmap"); // entryPoint.StructureTableAddress must be page aligned. // Unaligned physical memory access results in all kinds of crashes. void* p = mmap(NULL, tableLength, PROT_READ, MAP_SHARED, fd, tableAddress); if (p == MAP_FAILED) { FF_DEBUG("mmap failed: %s", strerror(errno)); ffStrbufDestroy(&buffer); // free buffer and reset state return NULL; } ffStrbufSetNS(&buffer, tableLength, (char*) p); munmap(p, tableLength); FF_DEBUG("Successfully read SMBIOS table data via mmap: %u bytes", tableLength); } } #else { FF_DEBUG("Using %s implementation", #if __HAIKU__ "Haiku" #else "OpenBSD" #endif ); uint32_t tableLength = 0; off_t tableAddress = 0; FF_AUTO_CLOSE_FD int fd = open( #if __HAIKU__ "/dev/misc/mem" #else "/dev/mem" // kern.securelevel must be -1 #endif , O_RDONLY | O_CLOEXEC); if (fd < 0) { FF_DEBUG("Failed to open memory device: %s", strerror(errno)); return NULL; } FF_DEBUG("Memory device opened successfully with fd=%d", fd); // Works on legacy BIOS only // See: https://wiki.osdev.org/System_Management_BIOS#UEFI_systems // On BSD systems, we can get EFI system resource table (ESRT) via EFIIOC_GET_TABLE // However, to acquire SMBIOS entry point, we need EFI configuration table (provided by EFI system table) // which is not available via EFIIOC_GET_TABLE. FF_AUTO_FREE uint8_t* smBiosBase = malloc(0x10000); if (pread(fd, smBiosBase, 0x10000, 0xF0000) != 0x10000) { FF_DEBUG("Failed to read SMBIOS memory region: %s", strerror(errno)); return NULL; } FF_DEBUG("Successfully read 0x10000 bytes from physical address 0xF0000"); for (off_t offset = 0; offset <= 0xffe0; offset += 0x10) { FFSmbiosEntryPoint* p = (void*)(smBiosBase + offset); if (memcmp(p, "_SM3_", sizeof(p->Smbios30.AnchorString)) == 0) { FF_DEBUG("Found SMBIOS 3.0 entry point at offset 0x%lx", (unsigned long)offset); if (p->Smbios30.EntryPointLength != sizeof(p->Smbios30)) { FF_DEBUG("Invalid SMBIOS 3.0 entry point length: %u (expected %zu)", p->Smbios30.EntryPointLength, sizeof(p->Smbios30)); return NULL; } tableLength = p->Smbios30.StructureTableMaximumSize; tableAddress = (off_t) p->Smbios30.StructureTableAddress; FF_DEBUG("SMBIOS 3.0: tableLength=0x%x, tableAddress=0x%lx, version=%u.%u.%u", tableLength, (unsigned long)tableAddress, p->Smbios30.SmbiosMajorVersion, p->Smbios30.SmbiosMinorVersion, p->Smbios30.SmbiosDocrev); break; } else if (memcmp(p, "_SM_", sizeof(p->Smbios20.AnchorString)) == 0) { FF_DEBUG("Found SMBIOS 2.0 entry point at offset 0x%lx", (unsigned long)offset); if (p->Smbios20.EntryPointLength != sizeof(p->Smbios20)) { FF_DEBUG("Invalid SMBIOS 2.0 entry point length: %u (expected %zu)", p->Smbios20.EntryPointLength, sizeof(p->Smbios20)); return NULL; } tableLength = p->Smbios20.StructureTableLength; tableAddress = (off_t) p->Smbios20.StructureTableAddress; FF_DEBUG("SMBIOS 2.0: tableLength=0x%x, tableAddress=0x%lx, version=%u.%u", tableLength, (unsigned long)tableAddress, p->Smbios20.SmbiosMajorVersion, p->Smbios20.SmbiosMinorVersion); break; } } if (tableLength == 0) { FF_DEBUG("No valid SMBIOS entry point found in memory region"); return NULL; } ffStrbufEnsureFixedLengthFree(&buffer, tableLength); FF_DEBUG("Attempting to read SMBIOS table data: %u bytes at 0x%lx", tableLength, (unsigned long)tableAddress); if (pread(fd, buffer.chars, tableLength, tableAddress) == tableLength) { buffer.length = tableLength; buffer.chars[buffer.length] = '\0'; FF_DEBUG("Successfully read SMBIOS table data: %u bytes", tableLength); } else { FF_DEBUG("Failed to read SMBIOS table data: %s", strerror(errno)); return NULL; } } #endif FF_DEBUG("Parsing SMBIOS table structures"); FF_MAYBE_UNUSED int structureCount = 0; for ( const FFSmbiosHeader* header = (const FFSmbiosHeader*) buffer.chars; (const uint8_t*) header < (const uint8_t*) buffer.chars + buffer.length; header = ffSmbiosNextEntry(header) ) { if (header->Type < FF_SMBIOS_TYPE_END_OF_TABLE) { if (!table[header->Type]) { table[header->Type] = header; FF_DEBUG("Found SMBIOS structure type %u, handle 0x%04X, length %u", header->Type, header->Handle, header->Length); structureCount++; } } else if (header->Type == FF_SMBIOS_TYPE_END_OF_TABLE) { FF_DEBUG("Reached end-of-table marker"); break; } } FF_DEBUG("Parsed %d SMBIOS structures", structureCount); } if (buffer.length == 0) { FF_DEBUG("No valid SMBIOS data available"); return NULL; } return &table; } #elif defined(_WIN32) #include "common/windows/nt.h" #pragma GCC diagnostic ignored "-Wmultichar" typedef struct FFRawSmbiosData { uint8_t Used20CallingMethod; uint8_t SMBIOSMajorVersion; uint8_t SMBIOSMinorVersion; uint8_t DmiRevision; uint32_t Length; uint8_t SMBIOSTableData[]; } FFRawSmbiosData; const FFSmbiosHeaderTable* ffGetSmbiosHeaderTable() { static SYSTEM_FIRMWARE_TABLE_INFORMATION* buffer; static FFSmbiosHeaderTable table; if (!buffer) { FF_DEBUG("Initializing Windows SMBIOS buffer"); FF_DEBUG("Querying system firmware table size with signature 'RSMB'"); SYSTEM_FIRMWARE_TABLE_INFORMATION sfti = { .ProviderSignature = 'RSMB', .Action = SystemFirmwareTableGet, }; ULONG bufSize = 0; NtQuerySystemInformation(SystemFirmwareTableInformation, &sfti, sizeof(sfti), &bufSize); if (bufSize <= sizeof(FFRawSmbiosData) + sizeof(sfti)) { FF_DEBUG("Invalid firmware table size: %lu (must be > %zu)", bufSize, sizeof(FFRawSmbiosData) + sizeof(sfti)); return NULL; } if (bufSize != sfti.TableBufferLength + (ULONG) sizeof(sfti)) { FF_DEBUG("Firmware table size mismatch: NtQuerySystemInformation returned %lu but expected %lu", bufSize, sfti.TableBufferLength + (ULONG) sizeof(sfti)); return NULL; } FF_DEBUG("Firmware table size: %lu bytes", bufSize); buffer = malloc(bufSize); *buffer = sfti; FF_DEBUG("Allocated buffer for SMBIOS data"); if (!NT_SUCCESS(NtQuerySystemInformation(SystemFirmwareTableInformation, buffer, bufSize, &bufSize))) { FF_DEBUG("NtQuerySystemInformation(SystemFirmwareTableInformation) failed"); free(buffer); buffer = NULL; return NULL; } FFRawSmbiosData* rawData = (FFRawSmbiosData*) buffer->TableBuffer; FF_DEBUG("Successfully retrieved SMBIOS data: version %u.%u, length %u bytes", rawData->SMBIOSMajorVersion, rawData->SMBIOSMinorVersion, rawData->Length); FF_DEBUG("Parsing SMBIOS table structures"); FF_MAYBE_UNUSED int structureCount = 0; for ( const FFSmbiosHeader* header = (const FFSmbiosHeader*) rawData->SMBIOSTableData; (const uint8_t*) header < rawData->SMBIOSTableData + rawData->Length; header = ffSmbiosNextEntry(header) ) { if (header->Type < FF_SMBIOS_TYPE_END_OF_TABLE) { if (!table[header->Type]) { table[header->Type] = header; FF_DEBUG("Found SMBIOS structure type %u, handle 0x%04X, length %u", header->Type, header->Handle, header->Length); structureCount++; } } else if (header->Type == FF_SMBIOS_TYPE_END_OF_TABLE) { FF_DEBUG("Reached end-of-table marker"); break; } } FF_DEBUG("Parsed %d SMBIOS structures", structureCount); } return &table; } #endif ================================================ FILE: src/common/impl/sysctl.c ================================================ #include "common/sysctl.h" #include #ifdef __OpenBSD__ const char* ffSysctlGetString(int mib1, int mib2, FFstrbuf* result) { size_t neededLength; if (sysctl((int[]) {mib1, mib2}, 2, NULL, &neededLength, NULL, 0) != 0 || neededLength == 1) //neededLength is 1 for empty strings, because of the null terminator return "sysctlbyname() failed"; ffStrbufEnsureFree(result, (uint32_t) neededLength - 1); if (sysctl((int[]) {mib1, mib2}, 2, result->chars + result->length, &neededLength, NULL, 0) == 0) result->length += (uint32_t) neededLength - 1; result->chars[result->length] = '\0'; return NULL; } int ffSysctlGetInt(int mib1, int mib2, int defaultValue) { int result; size_t neededLength = sizeof(result); if (sysctl((int[]) {mib1, mib2}, 2, &result, &neededLength, NULL, 0) != 0) return defaultValue; return result; } int64_t ffSysctlGetInt64(int mib1, int mib2, int64_t defaultValue) { int64_t result; size_t neededLength = sizeof(result); if(sysctl((int[]) {mib1, mib2}, 2, &result, &neededLength, NULL, 0) != 0) return defaultValue; return result; } #else const char* ffSysctlGetString(const char* propName, FFstrbuf* result) { size_t neededLength; if(sysctlbyname(propName, NULL, &neededLength, NULL, 0) != 0 || neededLength == 1) //neededLength is 1 for empty strings, because of the null terminator return "sysctlbyname() failed"; ffStrbufEnsureFree(result, (uint32_t) neededLength - 1); if(sysctlbyname(propName, result->chars + result->length, &neededLength, NULL, 0) == 0) result->length += (uint32_t) neededLength - 1; result->chars[result->length] = '\0'; return NULL; } int ffSysctlGetInt(const char* propName, int defaultValue) { int result; size_t neededLength = sizeof(result); if(sysctlbyname(propName, &result, &neededLength, NULL, 0) != 0) return defaultValue; return result; } int64_t ffSysctlGetInt64(const char* propName, int64_t defaultValue) { int64_t result; size_t neededLength = sizeof(result); if(sysctlbyname(propName, &result, &neededLength, NULL, 0) != 0) return defaultValue; return result; } #endif // OpenBSD void* ffSysctlGetData(int* request, u_int requestLength, size_t* resultLength) { if(sysctl(request, requestLength, NULL, resultLength, NULL, 0) != 0) return NULL; void* data = malloc(*resultLength); if(data == NULL) return NULL; if(sysctl(request, requestLength, data, resultLength, NULL, 0) != 0) { free(data); return NULL; } return data; } ================================================ FILE: src/common/impl/temps.c ================================================ #include "fastfetch.h" #include "common/temps.h" #include "common/textModifier.h" #include "common/stringUtils.h" void ffTempsAppendNum(double celsius, FFstrbuf* buffer, FFColorRangeConfig config, const FFModuleArgs* module) { if (celsius == -DBL_MAX) // ignores invalid value return; const FFOptionsDisplay* options = &instance.config.display; const char* colorGreen = options->tempColorGreen.chars; const char* colorYellow = options->tempColorYellow.chars; const char* colorRed = options->tempColorRed.chars; uint8_t green = config.green, yellow = config.yellow; if (!options->pipe) { if(green <= yellow) { if (celsius > yellow) ffStrbufAppendF(buffer, "\e[%sm", colorRed); else if (celsius > green) ffStrbufAppendF(buffer, "\e[%sm", colorYellow); else ffStrbufAppendF(buffer, "\e[%sm", colorGreen); } else { if (celsius < yellow) ffStrbufAppendF(buffer, "\e[%sm", colorRed); else if (celsius < green) ffStrbufAppendF(buffer, "\e[%sm", colorYellow); else ffStrbufAppendF(buffer, "\e[%sm", colorGreen); } } switch (options->tempUnit) { case FF_TEMPERATURE_UNIT_DEFAULT: case FF_TEMPERATURE_UNIT_CELSIUS: ffStrbufAppendF(buffer, "%.*f%s°C", options->tempNdigits, celsius, options->tempSpaceBeforeUnit == FF_SPACE_BEFORE_UNIT_ALWAYS ? " " : ""); break; case FF_TEMPERATURE_UNIT_FAHRENHEIT: ffStrbufAppendF(buffer, "%.*f%s°F", options->tempNdigits, celsius * 1.8 + 32, options->tempSpaceBeforeUnit == FF_SPACE_BEFORE_UNIT_ALWAYS ? " " : ""); break; case FF_TEMPERATURE_UNIT_KELVIN: ffStrbufAppendF(buffer, "%.*f%sK", options->tempNdigits, celsius + 273.15, options->tempSpaceBeforeUnit == FF_SPACE_BEFORE_UNIT_NEVER ? "" : " "); break; } if (!options->pipe) { ffStrbufAppendS(buffer, FASTFETCH_TEXT_MODIFIER_RESET); if (module->outputColor.length) ffStrbufAppendF(buffer, "\e[%sm", module->outputColor.chars); else if (instance.config.display.colorOutput.length) ffStrbufAppendF(buffer, "\e[%sm", instance.config.display.colorOutput.chars); } } bool ffTempsParseCommandOptions(const char* key, const char* subkey, const char* value, bool* useTemp, FFColorRangeConfig* config) { if (!ffStrStartsWithIgnCase(subkey, "temp")) return false; if (subkey[strlen("temp")] == '\0') { *useTemp = ffOptionParseBoolean(value); return true; } if (subkey[strlen("temp")] != '-') return false; subkey += strlen("temp-"); if (ffStrEqualsIgnCase(subkey, "green")) { uint32_t num = ffOptionParseUInt32(key, value); if (num > 100) { fprintf(stderr, "Error: usage: %s must be between 0 and 100\n", key); exit(480); } config->green = (uint8_t) num; return true; } if (ffStrEqualsIgnCase(subkey, "yellow")) { uint32_t num = ffOptionParseUInt32(key, value); if (num > 100) { fprintf(stderr, "Error: usage: %s must be between 0 and 100\n", key); exit(480); } config->yellow = (uint8_t) num; return true; } return false; } bool ffTempsParseJsonObject(yyjson_val* key, yyjson_val* value, bool* useTemp, FFColorRangeConfig* config) { assert(key); if (!unsafe_yyjson_equals_str(key, "temp")) return false; if (yyjson_is_bool(value)) { *useTemp = yyjson_get_bool(value); return true; } if (yyjson_is_null(value)) { *useTemp = false; return true; } if (!yyjson_is_obj(value)) { fprintf(stderr, "Error: usage: %s must be an object or a boolean\n", unsafe_yyjson_get_str(key)); exit(480); } *useTemp = true; yyjson_val* greenVal = yyjson_obj_get(value, "green"); if (greenVal) { int num = yyjson_get_int(greenVal); if (num < 0 || num > 100) { fputs("Error: usage: temp.green must be between 0 and 100\n", stderr); exit(480); } config->green = (uint8_t) num; } yyjson_val* yellowVal = yyjson_obj_get(value, "yellow"); if (yellowVal) { int num = yyjson_get_int(yellowVal); if (num < 0 || num > 100) { fputs("Error: usage: temp.yellow must be between 0 and 100\n", stderr); exit(480); } config->yellow = (uint8_t) num; } return true; } void ffTempsGenerateJsonConfig(yyjson_mut_doc* doc, yyjson_mut_val* module, bool temp, FFColorRangeConfig config) { if (!temp) yyjson_mut_obj_add_bool(doc, module, "temp", false); else { yyjson_mut_val* temp = yyjson_mut_obj_add_obj(doc, module, "temp"); yyjson_mut_obj_add_uint(doc, temp, "green", config.green); yyjson_mut_obj_add_uint(doc, temp, "yellow", config.yellow); } } ================================================ FILE: src/common/impl/time.c ================================================ #include "common/time.h" #include char ffTimeInternalBuffer[64]; // Reduce memory usage and prevent redundant allocations #ifdef _WIN32 #pragma GCC diagnostic push #pragma GCC diagnostic ignored "-Wformat" #endif const char* ffTimeToFullStr(uint64_t msec) { if (msec == 0) return ""; time_t tsec = (time_t) (msec / 1000); const struct tm* tm = localtime(&tsec); uint32_t len = 0; len += (uint32_t) strftime(ffTimeInternalBuffer, ARRAY_SIZE(ffTimeInternalBuffer) - len, "%FT%T", tm); len += (uint32_t) snprintf(ffTimeInternalBuffer + len, ARRAY_SIZE(ffTimeInternalBuffer) - len, ".%03u", (unsigned) (msec % 1000)); len += (uint32_t) strftime(ffTimeInternalBuffer + len, ARRAY_SIZE(ffTimeInternalBuffer) - len, "%z", tm); return ffTimeInternalBuffer; } const char* ffTimeToShortStr(uint64_t msec) { if (msec == 0) return ""; time_t tsec = (time_t) (msec / 1000); strftime(ffTimeInternalBuffer, ARRAY_SIZE(ffTimeInternalBuffer), "%F %T", localtime(&tsec)); return ffTimeInternalBuffer; } const char* ffTimeToTimeStr(uint64_t msec) { if (msec == 0) return ""; time_t tsec = (time_t) (msec / 1000); uint32_t len = (uint32_t) strftime(ffTimeInternalBuffer, ARRAY_SIZE(ffTimeInternalBuffer), "%T", localtime(&tsec)); sprintf(ffTimeInternalBuffer + len, ".%03u", (unsigned) (msec % 1000)); return ffTimeInternalBuffer; } #ifdef _WIN32 #pragma GCC diagnostic pop #endif FFTimeGetAgeResult ffTimeGetAge(uint64_t birthMs, uint64_t nowMs) { FFTimeGetAgeResult result = {}; if (__builtin_expect(birthMs == 0 || nowMs < birthMs, 0)) return result; time_t birth_s = (time_t) (birthMs / 1000); struct tm birth_tm; #ifdef _WIN32 localtime_s(&birth_tm, &birth_s); #else localtime_r(&birth_s, &birth_tm); #endif time_t now_s = (time_t) (nowMs / 1000); struct tm now_tm; #ifdef _WIN32 localtime_s(&now_tm, &now_s); #else localtime_r(&now_s, &now_tm); #endif result.years = (uint32_t) (now_tm.tm_year - birth_tm.tm_year); if (now_tm.tm_yday < birth_tm.tm_yday) result.years--; birth_tm.tm_year += (int) result.years; birth_s = mktime(&birth_tm); uint32_t diff_s = (uint32_t) (now_s - birth_s); result.daysOfYear = diff_s / (24 * 60 * 60); birth_tm.tm_year += 1; result.yearsFraction = (double) diff_s / (double) (mktime(&birth_tm) - birth_s) + result.years; return result; } #ifdef _WIN32 double ffQpcMultiplier; __attribute__((constructor)) static void ffTimeInitQpcMultiplier(void) { LARGE_INTEGER frequency; QueryPerformanceFrequency(&frequency); ffQpcMultiplier = 1000. / (double) frequency.QuadPart; } #endif ================================================ FILE: src/common/impl/wcwidth.c ================================================ #include "common/wcwidth.h" #include "3rdparty/widecharwidth/widechar_width_c.h" int mk_wcwidth(uint32_t wc) { // // We render U+1F6E1 (🛡) with a width of 2, // // but widechar_width says it has a width of 1 because Unicode classifies it as "neutral". // // // // So we simply decide the width ourselves // if (wc == 0x1F6E1) return 2; // // Well terminals do show it as width 1 after all int width = widechar_wcwidth(wc); switch (width) { case widechar_ambiguous: case widechar_private_use: return 1; case widechar_widened_in_9: // Our renderer supports Unicode 9 return 2; // case widechar_nonprint: // case widechar_combining: // case widechar_unassigned: // case widechar_non_character: // return -1; default: // Use the width widechar_width gave us. return width; } } ================================================ FILE: src/common/init.h ================================================ #pragma once void ffInitInstance(void); void ffStart(void); void ffFinish(void); void ffDestroyInstance(void); void ffListFeatures(void); ================================================ FILE: src/common/io.h ================================================ #pragma once #include "common/FFstrbuf.h" #include "common/FFlist.h" #ifdef _WIN32 #include #include #include #include "common/windows/nt.h" typedef HANDLE FFNativeFD; #define FF_INVALID_FD INVALID_HANDLE_VALUE #else #include #include #include #include #include typedef int FFNativeFD; #define FF_INVALID_FD (-1) // procfs's file can be changed between read calls such as /proc/meminfo and /proc/uptime. // one safe way to read correct data is reading the whole file in a single read syscall #define PROC_FILE_BUFFSIZ (32 * 1024) #endif static inline FFNativeFD FFUnixFD2NativeFD(int unixfd) { #ifndef _WIN32 return unixfd; #else return (FFNativeFD) _get_osfhandle(unixfd); #endif } FF_C_NONNULL(3) static inline bool ffWriteFDData(FFNativeFD fd, size_t dataSize, const void* data) { #ifndef _WIN32 return write(fd, data, dataSize) != -1; #else DWORD written; return WriteFile(fd, data, (DWORD) dataSize, &written, NULL) && written == dataSize; #endif } FF_C_NONNULL(2) static inline bool ffWriteFDBuffer(FFNativeFD fd, const FFstrbuf* content) { return ffWriteFDData(fd, content->length, content->chars); } FF_C_NONNULL(1, 3) bool ffWriteFileData(const char* fileName, size_t dataSize, const void* data); FF_C_NONNULL(1, 2) static inline bool ffWriteFileBuffer(const char* fileName, const FFstrbuf* buffer) { return ffWriteFileData(fileName, buffer->length, buffer->chars); } FF_C_NONNULL(3) static inline ssize_t ffReadFDData(FFNativeFD fd, size_t dataSize, void* data) { #ifndef _WIN32 return read(fd, data, dataSize); #else DWORD bytesRead; if(!ReadFile(fd, data, (DWORD)dataSize, &bytesRead, NULL)) return -1; return (ssize_t)bytesRead; #endif } FF_C_NONNULL(1, 3) ssize_t ffReadFileData(const char* fileName, size_t dataSize, void* data); FF_C_NONNULL(2, 4) ssize_t ffReadFileDataRelative(FFNativeFD dfd, const char* fileName, size_t dataSize, void* data); FF_C_NONNULL(2) bool ffAppendFDBuffer(FFNativeFD fd, FFstrbuf* buffer); FF_C_NONNULL(1, 2) bool ffAppendFileBuffer(const char* fileName, FFstrbuf* buffer); FF_C_NONNULL(2, 3) bool ffAppendFileBufferRelative(FFNativeFD dfd, const char* fileName, FFstrbuf* buffer); FF_C_NONNULL(2) static inline bool ffReadFDBuffer(FFNativeFD fd, FFstrbuf* buffer) { ffStrbufClear(buffer); return ffAppendFDBuffer(fd, buffer); } FF_C_NONNULL(1, 2) static inline bool ffReadFileBuffer(const char* fileName, FFstrbuf* buffer) { ffStrbufClear(buffer); return ffAppendFileBuffer(fileName, buffer); } FF_C_NONNULL(2, 3) static inline bool ffReadFileBufferRelative(FFNativeFD dfd, const char* fileName, FFstrbuf* buffer) { ffStrbufClear(buffer); return ffAppendFileBufferRelative(dfd, fileName, buffer); } typedef enum __attribute__((__packed__)) FFPathType { FF_PATHTYPE_FILE = 1 << 0, FF_PATHTYPE_DIRECTORY = 1 << 1, FF_PATHTYPE_ANY = FF_PATHTYPE_FILE | FF_PATHTYPE_DIRECTORY, FF_PATHTYPE_FORCE_UNSIGNED = UINT8_MAX, } FFPathType; FF_C_NONNULL(1) static inline bool ffPathExists(const char* path, FFPathType pathType) { #ifdef _WIN32 wchar_t wPath[MAX_PATH]; if (!NT_SUCCESS(RtlUTF8ToUnicodeN(wPath, (ULONG) sizeof(wPath), NULL, path, (ULONG)strlen(path) + 1))) return false; DWORD attr = GetFileAttributesW(wPath); if(attr == INVALID_FILE_ATTRIBUTES) return false; if(pathType & FF_PATHTYPE_FILE && !(attr & FILE_ATTRIBUTE_DIRECTORY)) return true; if(pathType & FF_PATHTYPE_DIRECTORY && (attr & FILE_ATTRIBUTE_DIRECTORY)) return true; #else if (pathType == FF_PATHTYPE_ANY) { // Zero overhead return access(path, F_OK) == 0; } else { struct stat fileStat; if(stat(path, &fileStat) != 0) return false; unsigned int mode = fileStat.st_mode & S_IFMT; if(pathType & FF_PATHTYPE_FILE && mode != S_IFDIR) return true; if(pathType & FF_PATHTYPE_DIRECTORY && mode == S_IFDIR) return true; } #endif return false; } FF_C_NONNULL(1, 2) bool ffPathExpandEnv(const char* in, FFstrbuf* out); #define FF_IO_TERM_RESP_WAIT_MS 100 // #554 FF_C_SCANF(3, 4) FF_C_NONNULL(1, 3) const char* ffGetTerminalResponse(const char* request, int nParams, const char* format, ...); // Not thread safe! bool ffSuppressIO(bool suppress); static inline void ffUnsuppressIO(bool* suppressed) { if (!*suppressed) return; ffSuppressIO(false); *suppressed = false; } #define FF_SUPPRESS_IO() bool __attribute__((__cleanup__(ffUnsuppressIO), __unused__)) io_suppressed__ = ffSuppressIO(true) void ffListFilesRecursively(const char* path, bool pretty); static inline bool ffIsValidNativeFD(FFNativeFD fd) { #ifndef _WIN32 return fd >= 0; #else // https://devblogs.microsoft.com/oldnewthing/20040302-00/?p=40443 return fd != INVALID_HANDLE_VALUE && fd != NULL; #endif } FF_C_NONNULL(1) static inline bool wrapClose(FFNativeFD* pfd) { assert(pfd); if (!ffIsValidNativeFD(*pfd)) return false; #ifndef _WIN32 close(*pfd); #else NtClose(*pfd); #endif return true; } #define FF_AUTO_CLOSE_FD __attribute__((__cleanup__(wrapClose))) FF_C_NONNULL(1) static inline bool wrapFclose(FILE** pfile) { assert(pfile); if (!*pfile) return false; fclose(*pfile); return true; } #define FF_AUTO_CLOSE_FILE __attribute__((__cleanup__(wrapFclose))) FF_C_NONNULL(1) #ifndef _WIN32 static inline bool wrapClosedir(DIR** pdir) { assert(pdir); if (!*pdir) return false; closedir(*pdir); return true; } #else static inline bool wrapClosedir(HANDLE* pdir) { assert(pdir); if (!*pdir) return false; FindClose(*pdir); return true; } #endif #define FF_AUTO_CLOSE_DIR __attribute__((__cleanup__(wrapClosedir))) FF_C_NONNULL(1, 2, 3) static inline bool ffSearchUserConfigFile(const FFlist* configDirs, const char* fileSubpath, FFstrbuf* result) { // configDirs is a list of FFstrbufs include the trailing slash FF_LIST_FOR_EACH(FFstrbuf, dir, *configDirs) { ffStrbufClear(result); ffStrbufAppend(result, dir); ffStrbufAppendS(result, fileSubpath); if (ffPathExists(result->chars, FF_PATHTYPE_FILE)) return true; } return false; } FFNativeFD ffGetNullFD(void); bool ffRemoveFile(const char* fileName); #ifdef _WIN32 // Only O_RDONLY is supported HANDLE openat(HANDLE dfd, const char* fileName, bool directory); HANDLE openatW(HANDLE dfd, const wchar_t* fileName, uint16_t fileNameLen, bool directory); #endif ================================================ FILE: src/common/jsonconfig.h ================================================ #pragma once #include "common/ffdata.h" #include "common/option.h" bool ffJsonConfigParseModuleArgs(yyjson_val* key, yyjson_val* val, FFModuleArgs* moduleArgs); const char* ffJsonConfigParseEnum(yyjson_val* val, int* result, FFKeyValuePair pairs[]); yyjson_api_inline yyjson_mut_val* yyjson_mut_strbuf(yyjson_mut_doc *doc, const FFstrbuf* buf) { return yyjson_mut_strncpy(doc, buf->chars, buf->length); } yyjson_api_inline bool yyjson_mut_obj_add_strbuf(yyjson_mut_doc *doc, yyjson_mut_val *obj, const char *_key, const FFstrbuf* buf) { return yyjson_mut_obj_add_strncpy(doc, obj, _key, buf->chars, buf->length); } yyjson_api_inline bool yyjson_mut_arr_add_strbuf(yyjson_mut_doc *doc, yyjson_mut_val *obj, const FFstrbuf* buf) { return yyjson_mut_arr_add_strncpy(doc, obj, buf->chars, buf->length); } void ffPrintJsonConfig(FFdata* data, bool prepare); void ffJsonConfigGenerateModuleArgsConfig(yyjson_mut_doc* doc, yyjson_mut_val* module, FFModuleArgs* moduleArgs); ================================================ FILE: src/common/kmod.h ================================================ #pragma once #include "fastfetch.h" bool ffKmodLoaded(const char* modName); ================================================ FILE: src/common/library.h ================================================ #pragma once #include "fastfetch.h" #include "common/FFcheckmacros.h" #ifndef FF_DISABLE_DLOPEN #if defined(_WIN32) #define FF_DLOPEN_FLAGS 0 FF_C_NODISCARD void* dlopen(const char* path, int mode); FF_C_NODISCARD void* dlsym(void* handle, const char* symbol); int dlclose(void* handle); #else #include #endif #ifdef _WIN32 #define FF_LIBRARY_EXTENSION ".dll" #elif defined(__APPLE__) #define FF_LIBRARY_EXTENSION ".dylib" #else #define FF_LIBRARY_EXTENSION ".so" #endif static inline void ffLibraryUnload(void** handle) { assert(handle); if (*handle) dlclose(*handle); } #if __cplusplus #define __auto_type auto #endif #define FF_LIBRARY_SYMBOL(symbolName) \ __typeof__(&symbolName) ff ## symbolName; #define FF_LIBRARY_LOAD(libraryObjectName, returnValue, ...) \ void* __attribute__((__cleanup__(ffLibraryUnload))) libraryObjectName = ffLibraryLoad(__VA_ARGS__, NULL);\ if(libraryObjectName == NULL) \ return returnValue; #define FF_LIBRARY_LOAD_MESSAGE(libraryObjectName, libraryFileName, maxVersion, ...) \ FF_LIBRARY_LOAD(libraryObjectName, "dlopen(" libraryFileName ") failed", libraryFileName, maxVersion, ##__VA_ARGS__) #define FF_LIBRARY_LOAD_SYMBOL_ADDRESS(library, symbolMapping, symbolName, returnValue) \ symbolMapping = (__typeof__(&symbolName)) dlsym(library, #symbolName); \ if(symbolMapping == NULL) \ return returnValue; #define FF_LIBRARY_LOAD_SYMBOL(library, symbolName, returnValue) \ __auto_type FF_LIBRARY_LOAD_SYMBOL_ADDRESS(library, ff ## symbolName, symbolName, returnValue); #define FF_LIBRARY_LOAD_SYMBOL_LAZY(library, symbolName) \ __auto_type ff ## symbolName = (__typeof__(&symbolName)) dlsym(library, #symbolName); #define FF_LIBRARY_LOAD_SYMBOL_MESSAGE(library, symbolName) \ __auto_type FF_LIBRARY_LOAD_SYMBOL_ADDRESS(library, ff ## symbolName, symbolName, "dlsym " #symbolName " failed"); #define FF_LIBRARY_LOAD_SYMBOL_VAR(library, varName, symbolName, returnValue) \ FF_LIBRARY_LOAD_SYMBOL_ADDRESS(library, (varName).ff ## symbolName, symbolName, returnValue); #define FF_LIBRARY_LOAD_SYMBOL_VAR_MESSAGE(library, varName, symbolName) \ FF_LIBRARY_LOAD_SYMBOL_ADDRESS(library, (varName).ff ## symbolName, symbolName, "dlsym " #symbolName " failed"); #define FF_LIBRARY_LOAD_SYMBOL_PTR(library, varName, symbolName, returnValue) \ FF_LIBRARY_LOAD_SYMBOL_ADDRESS(library, (varName)->ff ## symbolName, symbolName, returnValue); void* ffLibraryLoad(const char* path, int maxVersion, ...); #else #define FF_LIBRARY_EXTENSION "" #define FF_LIBRARY_SYMBOL(symbolName) \ __typeof__(&symbolName) ff ## symbolName; #define FF_LIBRARY_LOAD(libraryObjectName, returnValue, ...) \ FF_MAYBE_UNUSED void* libraryObjectName = NULL; // Placeholder #define FF_LIBRARY_LOAD_MESSAGE(libraryObjectName, libraryFileName, maxVersion, ...) \ FF_LIBRARY_LOAD(libraryObjectName, , libraryFileName, maxVersion, ##__VA_ARGS__) #define FF_LIBRARY_LOAD_SYMBOL_ADDRESS(library, symbolMapping, symbolName, returnValue) \ symbolMapping = (__typeof__(&symbolName)) &symbolName; #define FF_LIBRARY_LOAD_SYMBOL(library, symbolName, returnValue) \ FF_MAYBE_UNUSED __auto_type FF_LIBRARY_LOAD_SYMBOL_ADDRESS(library, ff ## symbolName, symbolName, returnValue); #define FF_LIBRARY_LOAD_SYMBOL_LAZY(library, symbolName) \ FF_MAYBE_UNUSED __auto_type ff ## symbolName = (__typeof__(&symbolName)) &symbolName; #define FF_LIBRARY_LOAD_SYMBOL_MESSAGE(library, symbolName) \ FF_MAYBE_UNUSED __auto_type FF_LIBRARY_LOAD_SYMBOL_ADDRESS(library, ff ## symbolName, symbolName, "dlsym " #symbolName " failed"); #define FF_LIBRARY_LOAD_SYMBOL_VAR(library, varName, symbolName, returnValue) \ FF_LIBRARY_LOAD_SYMBOL_ADDRESS(library, (varName).ff ## symbolName, symbolName, returnValue); #define FF_LIBRARY_LOAD_SYMBOL_VAR_MESSAGE(library, varName, symbolName) \ FF_LIBRARY_LOAD_SYMBOL_ADDRESS(library, (varName).ff ## symbolName, symbolName, "dlsym " #symbolName " failed"); #define FF_LIBRARY_LOAD_SYMBOL_PTR(library, varName, symbolName, returnValue) \ FF_LIBRARY_LOAD_SYMBOL_ADDRESS(library, (varName)->ff ## symbolName, symbolName, returnValue); #endif #if _WIN32 void* ffLibraryGetModule(const wchar_t* libraryFileName); #endif ================================================ FILE: src/common/mallocHelper.h ================================================ #pragma once #include #include #if FF_HAVE_MALLOC_USABLE_SIZE || FF_HAVE_MSVC_MSIZE #if __has_include() #include #else #include // For DragonFly BSD #endif #elif FF_HAVE_MALLOC_SIZE #include #endif static inline void ffWrapFree(const void* pPtr) { assert(pPtr); if(*(void**)pPtr) free(*(void**)pPtr); } #define FF_AUTO_FREE __attribute__((__cleanup__(ffWrapFree))) // ptr MUST be a malloc'ed pointer static inline size_t ffMallocUsableSize(const void* ptr) { assert(ptr); #if FF_HAVE_MALLOC_USABLE_SIZE return malloc_usable_size((void*) ptr); #elif FF_HAVE_MALLOC_SIZE return malloc_size((void*) ptr); #elif FF_HAVE_MSVC_MSIZE return _msize((void*) ptr); #else (void) ptr; return 0; // Not supported #endif } ================================================ FILE: src/common/memrchr.h ================================================ #pragma once #include #ifdef __cplusplus extern "C" { #endif // `memrchr` is a GNU extension and may not be declared by system headers even when the symbol exists. // Declare it unconditionally; the build system provides a fallback implementation when missing. void* memrchr(const void* s, int c, size_t n); #ifdef __cplusplus } #endif ================================================ FILE: src/common/netif.h ================================================ #pragma once #include "fastfetch.h" #ifndef _WIN32 #include #include #endif typedef enum __attribute__((__packed__)) FFNetifDefaultRouteResultStatus { FF_NETIF_UNINITIALIZED, FF_NETIF_INVALID, FF_NETIF_OK } FFNetifDefaultRouteResultStatus; typedef struct FFNetifDefaultRouteResult { uint32_t ifIndex; #ifndef _WIN32 char ifName[IF_NAMESIZE + 1]; uint32_t preferredSourceAddrV4; #endif enum FFNetifDefaultRouteResultStatus status; } FFNetifDefaultRouteResult; bool ffNetifGetDefaultRouteImplV4(FFNetifDefaultRouteResult* result); bool ffNetifGetDefaultRouteImplV6(FFNetifDefaultRouteResult* result); const FFNetifDefaultRouteResult* ffNetifGetDefaultRouteV4(void); const FFNetifDefaultRouteResult* ffNetifGetDefaultRouteV6(void); ================================================ FILE: src/common/networking.h ================================================ #pragma once #include "common/thread.h" #include "common/FFstrbuf.h" #ifdef _WIN32 #include #endif struct addrinfo; typedef struct FFNetworkingState { #ifdef _WIN32 uintptr_t sockfd; OVERLAPPED overlapped; #else int sockfd; struct addrinfo* addr; #ifdef FF_HAVE_THREADS FFThreadType thread; #endif #endif FFstrbuf command; uint32_t timeout; bool ipv6; bool compression; // if true, HTTP content compression will be enabled if supported bool tfo; // if true, TCP Fast Open will be attempted first, and fallback to traditional connection if it fails } FFNetworkingState; const char* ffNetworkingSendHttpRequest(FFNetworkingState* state, const char* host, const char* path, const char* headers); const char* ffNetworkingRecvHttpResponse(FFNetworkingState* state, FFstrbuf* buffer); #ifdef FF_HAVE_ZLIB const char* ffNetworkingLoadZlibLibrary(void); bool ffNetworkingDecompressGzip(FFstrbuf* buffer, char* headerEnd); #endif ================================================ FILE: src/common/option.h ================================================ #pragma once #include "common/FFstrbuf.h" struct yyjson_val; struct yyjson_mut_doc; struct yyjson_mut_val; typedef struct FFModuleFormatArg { const char* desc; const char* name; } FFModuleFormatArg; typedef struct FFModuleFormatArgList { FFModuleFormatArg* args; uint32_t count; } FFModuleFormatArgList; #define FF_FORMAT_ARG_LIST(list) { .args = list, .count = sizeof(list) / sizeof(FFModuleFormatArg) } // Must be the first field of FFModuleOptions typedef struct FFModuleBaseInfo { const char* name; const char* description; // A dirty polymorphic implementation in C. // This is UB, because `void*` is not compatible with `FF*Options*`. // However we can't do it better unless we move to C++, so that `option` becomes a `this` pointer // https://stackoverflow.com/questions/559581/casting-a-function-pointer-to-another-type void (*initOptions)(void* options); void (*destroyOptions)(void* options); void (*parseJsonObject)(void* options, struct yyjson_val *module); bool (*printModule)(void* options); // true on success bool (*generateJsonResult)(void* options, struct yyjson_mut_doc* doc, struct yyjson_mut_val* module); // true on success void (*generateJsonConfig)(void* options, struct yyjson_mut_doc* doc, struct yyjson_mut_val* obj); FFModuleFormatArgList formatArgs; } FFModuleBaseInfo; typedef enum __attribute__((__packed__)) FFModuleKeyType { FF_MODULE_KEY_TYPE_NONE = 0, FF_MODULE_KEY_TYPE_STRING = 1 << 0, FF_MODULE_KEY_TYPE_ICON = 1 << 1, FF_MODULE_KEY_TYPE_SPACE_SHIFT = 4, FF_MODULE_KEY_TYPE_BOTH_0 = FF_MODULE_KEY_TYPE_STRING | FF_MODULE_KEY_TYPE_ICON, FF_MODULE_KEY_TYPE_BOTH_1 = FF_MODULE_KEY_TYPE_BOTH_0 | (1 << FF_MODULE_KEY_TYPE_SPACE_SHIFT), FF_MODULE_KEY_TYPE_BOTH = FF_MODULE_KEY_TYPE_BOTH_1, // alias FF_MODULE_KEY_TYPE_BOTH_2 = FF_MODULE_KEY_TYPE_BOTH_0 | (2 << FF_MODULE_KEY_TYPE_SPACE_SHIFT), FF_MODULE_KEY_TYPE_BOTH_3 = FF_MODULE_KEY_TYPE_BOTH_0 | (3 << FF_MODULE_KEY_TYPE_SPACE_SHIFT), FF_MODULE_KEY_TYPE_BOTH_4 = FF_MODULE_KEY_TYPE_BOTH_0 | (4 << FF_MODULE_KEY_TYPE_SPACE_SHIFT), FF_MODULE_KEY_TYPE_FORCE_UNSIGNED = UINT8_MAX, } FFModuleKeyType; typedef struct FFModuleArgs { FFstrbuf key; FFstrbuf keyColor; FFstrbuf keyIcon; FFstrbuf outputFormat; FFstrbuf outputColor; uint32_t keyWidth; } FFModuleArgs; typedef struct FFKeyValuePair { const char* key; int value; } FFKeyValuePair; const char* ffOptionTestPrefix(const char* argumentKey, const char* moduleName); void ffOptionParseString(const char* argumentKey, const char* value, FFstrbuf* buffer); FF_C_NODISCARD uint32_t ffOptionParseUInt32(const char* argumentKey, const char* value); FF_C_NODISCARD int32_t ffOptionParseInt32(const char* argumentKey, const char* value); FF_C_NODISCARD int ffOptionParseEnum(const char* argumentKey, const char* requestedKey, FFKeyValuePair pairs[]); FF_C_NODISCARD bool ffOptionParseBoolean(const char* str); void ffOptionParseColorNoClear(const char* value, FFstrbuf* buffer); static inline void ffOptionParseColor(const char* value, FFstrbuf* buffer) { ffStrbufClear(buffer); ffOptionParseColorNoClear(value, buffer); } static inline void ffOptionInitModuleArg(FFModuleArgs* args, const char* icon) { ffStrbufInit(&args->key); ffStrbufInit(&args->keyColor); ffStrbufInitStatic(&args->keyIcon, icon); ffStrbufInit(&args->outputFormat); ffStrbufInit(&args->outputColor); args->keyWidth = 0; } static inline void ffOptionDestroyModuleArg(FFModuleArgs* args) { ffStrbufDestroy(&args->key); ffStrbufDestroy(&args->keyColor); ffStrbufDestroy(&args->keyIcon); ffStrbufDestroy(&args->outputFormat); ffStrbufDestroy(&args->outputColor); } enum { FF_OPTION_MAX_SIZE = 1 << 8 }; // Maximum size of a single option value, used for static allocation ================================================ FILE: src/common/parsing.h ================================================ #pragma once #include "common/FFstrbuf.h" #include typedef struct FFVersion { uint32_t major; uint32_t minor; uint32_t patch; } FFVersion; typedef struct FFColorRangeConfig { uint8_t green; uint8_t yellow; } FFColorRangeConfig; #define FF_VERSION_INIT ((FFVersion) {0}) void ffParseSemver(FFstrbuf* buffer, const FFstrbuf* major, const FFstrbuf* minor, const FFstrbuf* patch); void ffParseGTK(FFstrbuf* buffer, const FFstrbuf* gtk2, const FFstrbuf* gtk3, const FFstrbuf* gtk4); void ffVersionToPretty(const FFVersion* version, FFstrbuf* pretty); int8_t ffVersionCompare(const FFVersion* version1, const FFVersion* version2); ================================================ FILE: src/common/path.h ================================================ #pragma once #include "common/FFstrbuf.h" #include "common/stringUtils.h" const char* ffFindExecutableInPath(const char* name, FFstrbuf* result); static inline bool ffIsAbsolutePath(const char* path) { #ifdef _WIN32 return (ffCharIsEnglishAlphabet(path[0]) && path[1] == ':' && (path[2] == '\\' || path[2] == '/')) // drive letter path || (path[0] == '\\' && path[1] == '\\'); // UNC path #else return path[0] == '/'; #endif } #if _WIN32 char* frealpath(void* __restrict hFile, char* __restrict resolved_name /*MAX_PATH*/); char* realpath(const char* __restrict file_name, char* __restrict resolved_name /*MAX_PATH*/); ssize_t freadlink(void* hFile, char* buf, size_t bufsiz); ssize_t readlink(const char* path, char* buf, size_t bufsiz); #endif ================================================ FILE: src/common/percent.h ================================================ #pragma once #include "common/FFstrbuf.h" #include "common/parsing.h" #include "common/option.h" typedef enum __attribute__((__packed__)) FFPercentageTypeFlags { FF_PERCENTAGE_TYPE_NONE = 0, FF_PERCENTAGE_TYPE_NUM_BIT = 1 << 0, FF_PERCENTAGE_TYPE_BAR_BIT = 1 << 1, FF_PERCENTAGE_TYPE_HIDE_OTHERS_BIT = 1 << 2, FF_PERCENTAGE_TYPE_NUM_COLOR_BIT = 1 << 3, FF_PERCENTAGE_TYPE_BAR_MONOCHROME_BIT = 1 << 4, FF_PERCENTAGE_TYPE_FORCE_UNSIGNED_ = UINT8_MAX, } FFPercentageTypeFlags; static_assert(sizeof(FFPercentageTypeFlags) == 1, ""); typedef struct FFPercentageModuleConfig { uint8_t green; uint8_t yellow; FFPercentageTypeFlags type; } FFPercentageModuleConfig; // if (green <= yellow) // [0, green]: print green // (green, yellow]: print yellow // (yellow, 100]: print red // // if (green > yellow) // [green, 100]: print green // [yellow, green): print yellow // [0, yellow): print red void ffPercentAppendBar(FFstrbuf* buffer, double percent, FFPercentageModuleConfig config, const FFModuleArgs* module); void ffPercentAppendNum(FFstrbuf* buffer, double percent, FFPercentageModuleConfig config, bool parentheses, const FFModuleArgs* module); typedef struct yyjson_val yyjson_val; typedef struct yyjson_mut_doc yyjson_mut_doc; typedef struct yyjson_mut_val yyjson_mut_val; bool ffPercentParseCommandOptions(const char* key, const char* subkey, const char* value, FFPercentageModuleConfig* config); bool ffPercentParseJsonObject(yyjson_val* key, yyjson_val* value, FFPercentageModuleConfig* config); void ffPercentGenerateJsonConfig(yyjson_mut_doc* doc, yyjson_mut_val* module, FFPercentageModuleConfig config); const char* ffPercentParseTypeJsonConfig(yyjson_val* value, FFPercentageTypeFlags* result); ================================================ FILE: src/common/printing.h ================================================ #pragma once #include "fastfetch.h" #include "common/format.h" typedef enum __attribute__((__packed__)) FFPrintType { FF_PRINT_TYPE_DEFAULT = 0, FF_PRINT_TYPE_NO_CUSTOM_KEY = 1 << 0, // key has been formatted outside FF_PRINT_TYPE_NO_CUSTOM_KEY_COLOR = 1 << 1, FF_PRINT_TYPE_NO_CUSTOM_KEY_WIDTH = 1 << 2, FF_PRINT_TYPE_NO_CUSTOM_OUTPUT_FORMAT = 1 << 3, // reserved FF_PRINT_TYPE_FORCE_UNSIGNED = UINT8_MAX, } FFPrintType; void ffPrintLogoAndKey(const char* moduleName, uint8_t moduleIndex, const FFModuleArgs* moduleArgs, FFPrintType printType); void ffPrintFormat(const char* moduleName, uint8_t moduleIndex, const FFModuleArgs* moduleArgs, FFPrintType printType, uint32_t numArgs, const FFformatarg* arguments); #define FF_PRINT_FORMAT_CHECKED(moduleName, moduleIndex, moduleArgs, printType, arguments) \ ffPrintFormat((moduleName), (moduleIndex), (moduleArgs), (printType), (sizeof(arguments) / sizeof(*arguments)), (arguments)); FF_C_PRINTF(5, 6) void ffPrintError(const char* moduleName, uint8_t moduleIndex, const FFModuleArgs* moduleArgs, FFPrintType printType, const char* message, ...); void ffPrintColor(const FFstrbuf* colorValue); void ffPrintCharTimes(char c, uint32_t times); ================================================ FILE: src/common/processing.h ================================================ #pragma once #include "common/FFstrbuf.h" #ifndef _WIN32 #include // pid_t #endif typedef struct FFProcessHandle { #if _WIN32 void* pid; // HANDLE void* pipeRead; // HANDLE #else pid_t pid; int pipeRead; #endif } FFProcessHandle; const char* ffProcessSpawn(char* const argv[], bool useStdErr, FFProcessHandle* outHandle); const char* ffProcessReadOutput(FFProcessHandle* handle, FFstrbuf* buffer); // Destroys handle internally static inline const char* ffProcessAppendStdOut(FFstrbuf* buffer, char* const argv[]) { FFProcessHandle handle; const char* error = ffProcessSpawn(argv, false, &handle); if (error) return error; error = ffProcessReadOutput(&handle, buffer); if (!error) ffStrbufTrimRightSpace(buffer); return error; } static inline const char* ffProcessAppendStdErr(FFstrbuf* buffer, char* const argv[]) { FFProcessHandle handle; const char* error = ffProcessSpawn(argv, true, &handle); if (error) return error; error = ffProcessReadOutput(&handle, buffer); if (!error) ffStrbufTrimRightSpace(buffer); return error; } #ifdef _WIN32 bool ffProcessGetInfoWindows(uint32_t pid, uint32_t* ppid, FFstrbuf* pname, FFstrbuf* exe, const char** exeName, FFstrbuf* exePath, bool* gui); #else void ffProcessGetInfoLinux(pid_t pid, FFstrbuf* processName, FFstrbuf* exe, const char** exeName, FFstrbuf* exePath); const char* ffProcessGetBasicInfoLinux(pid_t pid, FFstrbuf* name, pid_t* ppid, int32_t* tty); #endif ================================================ FILE: src/common/properties.h ================================================ #pragma once #include "fastfetch.h" typedef struct FFpropquery { const char* start; FFstrbuf* buffer; } FFpropquery; bool ffParsePropLines(const char* lines, const char* start, FFstrbuf* buffer); bool ffParsePropFileValues(const char* filename, uint32_t numQueries, FFpropquery* queries); bool ffParsePropFileHomeValues(const char* relativeFile, uint32_t numQueries, FFpropquery* queries); bool ffParsePropFileListValues(const FFlist* list, const char* relativeFile, uint32_t numQueries, FFpropquery* queries); bool ffParsePropLinePointer(const char** line, const char* start, FFstrbuf* buffer); static inline bool ffParsePropLine(const char* line, const char* start, FFstrbuf* buffer) { return ffParsePropLinePointer(&line, start, buffer); } static inline bool ffParsePropFile(const char* filename, const char* start, FFstrbuf* buffer) { return ffParsePropFileValues(filename, 1, (FFpropquery[]){{start, buffer}}); } static inline bool ffParsePropFileHome(const char* relativeFile, const char* start, FFstrbuf* buffer) { return ffParsePropFileHomeValues(relativeFile, 1, (FFpropquery[]){{start, buffer}}); } static inline bool ffParsePropFileList(const FFlist* list, const char* relativeFile, const char* start, FFstrbuf* buffer) { return ffParsePropFileListValues(list, relativeFile, 1, (FFpropquery[]){{start, buffer}}); } static inline bool ffParsePropFileConfigValues(const char* relativeFile, uint32_t numQueries, FFpropquery* queries) { return ffParsePropFileListValues(&instance.state.platform.configDirs, relativeFile, numQueries, queries); } static inline bool ffParsePropFileConfig(const char* relativeFile, const char* start, FFstrbuf* buffer) { return ffParsePropFileConfigValues(relativeFile, 1, (FFpropquery[]){{start, buffer}}); } static inline bool ffParsePropFileDataValues(const char* relativeFile, uint32_t numQueries, FFpropquery* queries) { return ffParsePropFileListValues(&instance.state.platform.dataDirs, relativeFile, numQueries, queries); } static inline bool ffParsePropFileData(const char* relativeFile, const char* start, FFstrbuf* buffer) { return ffParsePropFileDataValues(relativeFile, 1, (FFpropquery[]){{start, buffer}}); } ================================================ FILE: src/common/settings.h ================================================ #pragma once #include "fastfetch.h" typedef enum __attribute__((__packed__)) FFvarianttype { FF_VARIANT_TYPE_STRING, FF_VARIANT_TYPE_BOOL, FF_VARIANT_TYPE_INT } FFvarianttype; typedef union FFvariant { const char* strValue; int32_t intValue; struct { bool boolValueSet; bool boolValue; }; } FFvariant; #define FF_VARIANT_NULL ((FFvariant){.strValue = NULL}) FFvariant ffSettingsGetDConf(const char* key, FFvarianttype type); FFvariant ffSettingsGetGSettings(const char* schemaName, const char* path, const char* key, FFvarianttype type); FFvariant ffSettingsGetGnome(const char* dconfKey, const char* gsettingsSchemaName, const char* gsettingsPath, const char* gsettingsKey, FFvarianttype type); FFvariant ffSettingsGetXFConf(const char* channelName, const char* propertyName, FFvarianttype type); typedef bool FFTestXfconfPropCallback(void* data, const char* propertyName); // Return false to break loop FFvariant ffSettingsGetXFConfFirstMatch(const char* channelName, const char* propertyPrefix, FFvarianttype type, void* data, FFTestXfconfPropCallback* cb); int ffSettingsGetSQLite3Int(const char* dbPath, const char* query); bool ffSettingsGetSQLite3String(const char* dbPath, const char* query, FFstrbuf* result); #ifdef __ANDROID__ bool ffSettingsGetAndroidProperty(const char* propName, FFstrbuf* result); #elif defined(__FreeBSD__) bool ffSettingsGetFreeBSDKenv(const char* propName, FFstrbuf* result); #endif ================================================ FILE: src/common/size.h ================================================ #pragma once #include "fastfetch.h" void ffSizeAppendNum(uint64_t bytes, FFstrbuf* result); ================================================ FILE: src/common/smbiosHelper.h ================================================ #pragma once #include "common/FFstrbuf.h" bool ffIsSmbiosValueSet(FFstrbuf* value); static inline void ffCleanUpSmbiosValue(FFstrbuf* value) { if (!ffIsSmbiosValueSet(value)) ffStrbufClear(value); } // https://github.com/KunYi/DumpSMBIOS // https://www.dmtf.org/sites/default/files/standards/documents/DSP0134_3.7.0.pdf typedef enum __attribute__((__packed__)) FFSmbiosType // : uint8_t { FF_SMBIOS_TYPE_BIOS = 0, FF_SMBIOS_TYPE_SYSTEM_INFO = 1, FF_SMBIOS_TYPE_BASEBOARD_INFO = 2, FF_SMBIOS_TYPE_SYSTEM_ENCLOSURE = 3, FF_SMBIOS_TYPE_PROCESSOR_INFO = 4, FF_SMBIOS_TYPE_MEMORY_CONTROLLER_INFO = 5, // obsolete FF_SMBIOS_TYPE_MEMORY_MODULE_INFO = 6, // obsolete FF_SMBIOS_TYPE_CACHE_INFO = 7, FF_SMBIOS_TYPE_PORT_CONNECTOR_INFO = 8, FF_SMBIOS_TYPE_SYSTEM_SLOTS = 9, FF_SMBIOS_TYPE_ON_BOARD_DEVICES_INFO = 10, // obsolete FF_SMBIOS_TYPE_OEM_STRING = 11, FF_SMBIOS_TYPE_SYSTEM_CONFIGURATION_OPTIONS = 12, FF_SMBIOS_TYPE_BIOS_LANGUAGE_INFO = 13, FF_SMBIOS_TYPE_GROUP_ASSOCIATIONS = 14, FF_SMBIOS_TYPE_SYSTEM_EVENT_LOG = 15, FF_SMBIOS_TYPE_PHYSICAL_MEMORY_ARRAY = 16, FF_SMBIOS_TYPE_MEMORY_DEVICE = 17, FF_SMBIOS_TYPE_32BIT_MEMORY_ERROR_INFO = 18, FF_SMBIOS_TYPE_MEMORY_ARRAY_MAPPED_ADDRESS = 19, FF_SMBIOS_TYPE_MEMORY_DEVICE_MAPPED_ADDRESS = 20, FF_SMBIOS_TYPE_BUILTIN_POINTING_DEVICE = 21, FF_SMBIOS_TYPE_PORTABLE_BATTERY = 22, FF_SMBIOS_TYPE_SYSTEM_RESET = 23, FF_SMBIOS_TYPE_HARDWARE_SECURITY = 24, FF_SMBIOS_TYPE_SYSTEM_POWER_CONTROLS = 25, FF_SMBIOS_TYPE_VOLTAGE_PROBE = 26, FF_SMBIOS_TYPE_COOLING_DEVICE = 27, FF_SMBIOS_TYPE_TEMPERATURE_PROBE = 28, FF_SMBIOS_TYPE_ELECTRICAL_CURRENT_PROBE = 29, FF_SMBIOS_TYPE_OUT_OF_BAND_REMOTE_ACCESS = 30, FF_SMBIOS_TYPE_BOOT_INTEGRITY_SERVICES_ENTRY_POINT = 31, // reserved FF_SMBIOS_TYPE_SYSTEM_BOOT_INFO = 32, FF_SMBIOS_TYPE_64BIT_MEMORY_ERROR_INFO = 33, FF_SMBIOS_TYPE_MANAGEMENT_DEVICE = 34, FF_SMBIOS_TYPE_MANAGEMENT_DEVICE_COMPONENT = 35, FF_SMBIOS_TYPE_MANAGEMENT_DEVICE_THRESHOLD_DATA = 36, FF_SMBIOS_TYPE_MEMORY_CHANNEL = 37, FF_SMBIOS_TYPE_IPMI_DEVICE_INFO = 38, FF_SMBIOS_TYPE_SYSTEM_POWER_SUPPLY = 39, FF_SMBIOS_TYPE_ADDITIONAL_INFO = 40, FF_SMBIOS_TYPE_ONBOARD_DEVICE_EXTENDED_INFO = 41, FF_SMBIOS_TYPE_MANAGEMENT_CONTROLLER_HOST_INTERFACE = 42, FF_SMBIOS_TYPE_TPM_DEVICE = 43, FF_SMBIOS_TYPE_PROCESSOR_ADDITIONAL_INFO = 44, FF_SMBIOS_TYPE_FIRMWARE_INVENTORY_INFO = 45, FF_SMBIOS_TYPE_STRING_PROPERTY = 46, FF_SMBIOS_TYPE_INACTIVE = 126, FF_SMBIOS_TYPE_END_OF_TABLE = 127, // system- and OEM-specific information 128~256 } FFSmbiosType; static_assert(sizeof(FFSmbiosType) == 1, "FFSmbiosType should be 1 byte"); typedef struct FFSmbiosHeader { FFSmbiosType Type; uint8_t Length; uint16_t Handle; } __attribute__((__packed__)) FFSmbiosHeader; static_assert(sizeof(FFSmbiosHeader) == 4, "FFSmbiosHeader should be 4 bytes"); static inline const char* ffSmbiosLocateString(const char* start, uint8_t index /* start from 1 */) { if (index == 0 || *start == '\0') return NULL; while (--index) start += strlen(start) + 1; return start; } typedef const FFSmbiosHeader* FFSmbiosHeaderTable[FF_SMBIOS_TYPE_END_OF_TABLE]; const FFSmbiosHeader* ffSmbiosNextEntry(const FFSmbiosHeader* header); const FFSmbiosHeaderTable* ffGetSmbiosHeaderTable(); #ifdef __linux__ bool ffGetSmbiosValue(const char* devicesPath, const char* classPath, FFstrbuf* buffer); #endif ================================================ FILE: src/common/stringUtils.h ================================================ #pragma once #include #include #include #include static inline bool ffStrSet(const char* str) { if(str == NULL) return false; while(isspace(*str)) str++; return *str != '\0'; } static inline bool ffStrStartsWithIgnCase(const char* str, const char* compareTo) { return strncasecmp(str, compareTo, strlen(compareTo)) == 0; } static inline bool ffStrEqualsIgnCase(const char* str, const char* compareTo) { return strcasecmp(str, compareTo) == 0; } static inline bool ffStrStartsWith(const char* str, const char* compareTo) { return strncmp(str, compareTo, strlen(compareTo)) == 0; } static inline bool ffStrEndsWith(const char* str, const char* compareTo) { size_t strLength = strlen(str); size_t compareToLength = strlen(compareTo); if (strLength < compareToLength) return false; return memcmp(str + strLength - compareToLength, compareTo, compareToLength) == 0; } static inline bool ffStrEndsWithIgnCase(const char* str, const char* compareTo) { size_t strLength = strlen(str); size_t compareToLength = strlen(compareTo); if (strLength < compareToLength) return false; return strncasecmp(str + strLength - compareToLength, compareTo, compareToLength) == 0; } static inline bool ffStrEquals(const char* str, const char* compareTo) { return strcmp(str, compareTo) == 0; } static inline bool ffStrContains(const char* str, const char* compareTo) { return strstr(str, compareTo) != NULL; } static inline bool ffStrContainsIgnCase(const char* str, const char* compareTo) { return strcasestr(str, compareTo) != NULL; } static inline bool ffStrContainsC(const char* str, char compareTo) { return strchr(str, compareTo) != NULL; } static inline bool ffCharIsEnglishAlphabet(char c) { return ('a' <= c && c <= 'z') || ('A' <= c && c <= 'Z'); } static inline bool ffCharIsDigit(char c) { return '0' <= c && c <= '9'; } // Copies at most (dstBufSiz - 1) bytes from src to dst; dst is always null-terminated static inline char* ffStrCopy(char* __restrict__ dst, const char* __restrict__ src, size_t dstBufSiz) { if (__builtin_expect(dst == NULL, false) || dstBufSiz == 0) return dst; size_t len = strnlen(src, dstBufSiz - 1); memcpy(dst, src, len); dst[len] = '\0'; return dst + len; } ================================================ FILE: src/common/sysctl.h ================================================ #pragma once #include "fastfetch.h" #include "common/FFcheckmacros.h" #include #include #ifdef __OpenBSD__ const char* ffSysctlGetString(int mib1, int mib2, FFstrbuf* result); FF_C_NODISCARD int ffSysctlGetInt(int mib1, int mib2, int defaultValue); FF_C_NODISCARD int64_t ffSysctlGetInt64(int mib1, int mib2, int64_t defaultValue); #else const char* ffSysctlGetString(const char* propName, FFstrbuf* result); FF_C_NODISCARD int ffSysctlGetInt(const char* propName, int defaultValue); FF_C_NODISCARD int64_t ffSysctlGetInt64(const char* propName, int64_t defaultValue); #endif FF_C_NODISCARD void* ffSysctlGetData(int* request, u_int requestLength, size_t* resultLength); ================================================ FILE: src/common/temps.h ================================================ #pragma once #include "common/parsing.h" #include "common/option.h" void ffTempsAppendNum(double celsius, FFstrbuf* buffer, FFColorRangeConfig config, const FFModuleArgs* module); bool ffTempsParseCommandOptions(const char* key, const char* subkey, const char* value, bool* useTemp, FFColorRangeConfig* config); bool ffTempsParseJsonObject(yyjson_val* key, yyjson_val* value, bool* useTemp, FFColorRangeConfig* config); void ffTempsGenerateJsonConfig(yyjson_mut_doc* doc, yyjson_mut_val* module, bool temp, FFColorRangeConfig config); ================================================ FILE: src/common/textModifier.h ================================================ #pragma once #define FASTFETCH_TEXT_MODIFIER_BOLT "\033[1m" #define FASTFETCH_TEXT_MODIFIER_ERROR "\033[1;31m" #define FASTFETCH_TEXT_MODIFIER_RESET "\033[m" ================================================ FILE: src/common/thread.h ================================================ #pragma once #include "fastfetch.h" #ifdef FF_HAVE_THREADS #if defined(_WIN32) #include #include #include #include #define FF_THREAD_MUTEX_INITIALIZER SRWLOCK_INIT typedef SRWLOCK FFThreadMutex; typedef HANDLE FFThreadType; static inline void ffThreadMutexLock(FFThreadMutex* mutex) { AcquireSRWLockExclusive(mutex); } static inline void ffThreadMutexUnlock(FFThreadMutex* mutex) { ReleaseSRWLockExclusive(mutex); } static inline FFThreadType ffThreadCreate(unsigned (__stdcall* func)(void*), void* data) { return (FFThreadType)_beginthreadex(NULL, 0, func, data, 0, NULL); } #define FF_THREAD_ENTRY_DECL_WRAPPER(fn, paramType) static __stdcall unsigned fn ## ThreadMain (void* data) { fn((paramType)data); return 0; } #define FF_THREAD_ENTRY_DECL_WRAPPER_NOPARAM(fn) static __stdcall unsigned fn ## ThreadMain () { fn(); return 0; } static inline void ffThreadDetach(FFThreadType thread) { NtClose(thread); } static inline bool ffThreadJoin(FFThreadType thread, uint32_t timeout) { if (NtWaitForSingleObject(thread, TRUE, timeout == 0 ? NULL : &(LARGE_INTEGER) { .QuadPart = (int64_t) timeout * -10000 }) != STATUS_WAIT_0) { TerminateThread(thread, (DWORD) -1); NtClose(thread); return false; } NtClose(thread); return true; } #else #include #include #if FF_HAVE_PTHREAD_NP #include #endif typedef pthread_t FFThreadType; #if __APPLE__ #include #define FF_THREAD_MUTEX_INITIALIZER OS_UNFAIR_LOCK_INIT typedef os_unfair_lock FFThreadMutex; static inline void ffThreadMutexLock(os_unfair_lock* mutex) { os_unfair_lock_lock(mutex); } static inline void ffThreadMutexUnlock(os_unfair_lock* mutex) { os_unfair_lock_unlock(mutex); } #else #define FF_THREAD_MUTEX_INITIALIZER PTHREAD_MUTEX_INITIALIZER typedef pthread_mutex_t FFThreadMutex; static inline void ffThreadMutexLock(FFThreadMutex* mutex) { pthread_mutex_lock(mutex); } static inline void ffThreadMutexUnlock(FFThreadMutex* mutex) { pthread_mutex_unlock(mutex); } #endif static inline FFThreadType ffThreadCreate(void* (* func)(void*), void* data) { FFThreadType newThread = 0; pthread_create(&newThread, NULL, func, data); return newThread; } #define FF_THREAD_ENTRY_DECL_WRAPPER(fn, paramType) static void* fn ## ThreadMain (void* data) { fn((paramType)data); return NULL; } #define FF_THREAD_ENTRY_DECL_WRAPPER_NOPARAM(fn) static void* fn ## ThreadMain () { fn(); return NULL; } static inline void ffThreadDetach(FFThreadType thread) { pthread_detach(thread); } static inline bool ffThreadJoin(FFThreadType thread, FF_MAYBE_UNUSED uint32_t timeout) { #if HAVE_TIMEDJOIN_NP if (timeout > 0) { struct timespec ts; if (clock_gettime(CLOCK_REALTIME, &ts) == 0) { ts.tv_sec += timeout / 1000; ts.tv_nsec += (timeout % 1000) * 1000000; if (pthread_timedjoin_np(thread, NULL, &ts) != 0) { pthread_kill(thread, SIGTERM); return false; } return true; } } #endif pthread_join(thread, NULL); return true; } #endif #else //FF_HAVE_THREADS #define FF_THREAD_MUTEX_INITIALIZER 0 typedef char FFThreadMutex; static inline void ffThreadMutexLock(FFThreadMutex* mutex) { FF_UNUSED(mutex) } static inline void ffThreadMutexUnlock(FFThreadMutex* mutex) { FF_UNUSED(mutex) } #define FF_THREAD_ENTRY_DECL_WRAPPER(fn, paramType) #endif //FF_HAVE_THREADS ================================================ FILE: src/common/time.h ================================================ #pragma once #include #include #include #ifdef _WIN32 #include #include "common/windows/nt.h" #include #elif defined(__HAIKU__) #include #endif #include "common/arrayUtils.h" static inline double ffTimeGetTick(void) //In msec { #ifdef _WIN32 extern double ffQpcMultiplier; LARGE_INTEGER start; QueryPerformanceCounter(&start); return (double) start.QuadPart * ffQpcMultiplier; #elif defined(__HAIKU__) return (double) system_time() / 1000.; #else struct timespec timeNow; clock_gettime(CLOCK_MONOTONIC, &timeNow); return (double) timeNow.tv_sec * 1000. + (double) timeNow.tv_nsec / 1000000.; #endif } #if _WIN32 static inline uint64_t ffFileTimeToUnixMs(uint64_t value) { if (__builtin_expect(__builtin_usubll_overflow(value, 116444736000000000ull, &value), false)) return 0; return value / 10000ull; } #endif static inline uint64_t ffTimeGetNow(void) { #ifdef _WIN32 uint64_t timeNow = ffKSystemTimeToUInt64(&SharedUserData->SystemTime); return ffFileTimeToUnixMs((uint64_t) timeNow); #elif defined(__HAIKU__) return (uint64_t) real_time_clock_usecs() / 1000u; #else struct timespec timeNow; clock_gettime(CLOCK_REALTIME, &timeNow); return (uint64_t)(((uint64_t) timeNow.tv_sec * 1000u) + ((uint64_t) timeNow.tv_nsec / 1000000u)); #endif } // Returns true if not interrupted static inline bool ffTimeSleep(uint32_t msec) { #ifdef _WIN32 LARGE_INTEGER interval; interval.QuadPart = -(int64_t) msec * 10000; // Relative time in 100-nanosecond intervals return NT_SUCCESS(NtDelayExecution(TRUE, &interval)); #else return nanosleep(&(struct timespec){ msec / 1000, (long) (msec % 1000) * 1000000 }, NULL) == 0; #endif } // Not thread-safe const char* ffTimeToFullStr(uint64_t msec); // Not thread-safe const char* ffTimeToShortStr(uint64_t msec); // Not thread-safe const char* ffTimeToTimeStr(uint64_t msec); typedef struct FFTimeGetAgeResult { uint32_t years; uint32_t daysOfYear; double yearsFraction; } FFTimeGetAgeResult; FFTimeGetAgeResult ffTimeGetAge(uint64_t birthMs, uint64_t nowMs); ================================================ FILE: src/common/unused.h ================================================ #pragma once static inline void ffUnused(int dummy, ...) { (void) dummy; } #define FF_UNUSED(...) ffUnused(0, __VA_ARGS__); #if defined(__GNUC__) || defined(__clang__) #define FF_MAYBE_UNUSED __attribute__ ((__unused__)) #else #define FF_MAYBE_UNUSED #endif ================================================ FILE: src/common/wcwidth.h ================================================ #pragma once #include #ifdef FF_HAVE_WCWIDTH #include // Should be char32_t but it's not defined on macOS static_assert(sizeof(wchar_t) == sizeof(uint32_t), "wcwidth implementation requires wchar_t to be 32 bits"); static inline int mk_wcwidth(uint32_t ucs) { return wcwidth((wchar_t) ucs); } #else int mk_wcwidth(uint32_t wc); #endif ================================================ FILE: src/common/windows/c-logo.sh ================================================ #!/bin/sh # Convert logo.svg to logo.ico rsvg-convert -w 16 -h 16 logo.svg > logo16.png rsvg-convert -w 32 -h 32 logo.svg > logo32.png rsvg-convert -w 48 -h 48 logo.svg > logo48.png rsvg-convert -w 64 -h 64 logo.svg > logo64.png rsvg-convert -w 128 -h 128 logo.svg > logo128.png rsvg-convert -w 256 -h 256 logo.svg > logo256.png convert logo16.png logo32.png logo48.png logo64.png logo128.png logo256.png logo.ico rm logo16.png logo32.png logo48.png logo64.png logo128.png logo256.png ================================================ FILE: src/common/windows/com.cpp ================================================ #include "com.hpp" #include "fastfetch.h" #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 const char* doInitCom() { // Initialize COM if (FAILED(CoInitializeEx(NULL, COINIT_MULTITHREADED))) return "CoInitializeEx() failed"; // 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); return NULL; } const char* ffInitCom(void) { static const char* error = ""; if (error && error[0] == '\0') error = doInitCom(); return error; } ================================================ FILE: src/common/windows/com.hpp ================================================ #pragma once #ifdef __cplusplus #include const char* ffInitCom(void); static inline void ffReleaseComObject(void* ppUnknown) { IUnknown* pUnknown = *(IUnknown**) ppUnknown; if (pUnknown) pUnknown->Release(); } #define FF_AUTO_RELEASE_COM_OBJECT __attribute__((__cleanup__(ffReleaseComObject))) #else // Win32 COM headers requires C++ compiler #error Must be included in C++ source file #endif ================================================ FILE: src/common/windows/getline.c ================================================ #include "getline.h" #include #include ssize_t getline(char **lineptr, size_t *n, FILE *stream) { ssize_t pos = -1; int c; if (lineptr == NULL || stream == NULL || n == NULL) { errno = EINVAL; return -1; } _lock_file(stream); c = _getc_nolock(stream); if (c == EOF) { goto exit; } if (*lineptr == NULL) { *lineptr = malloc(128); if (*lineptr == NULL) { goto exit; } *n = 128; } pos = 0; while(c != EOF) { if ((size_t)(pos + 1) >= *n) { size_t new_size = *n + (*n >> 2); if (new_size < 128) { new_size = 128; } char *new_ptr = realloc(*lineptr, new_size); if (new_ptr == NULL) { pos = -1; goto exit; } *n = new_size; *lineptr = new_ptr; } ((char *)(*lineptr))[pos ++] = (char)c; if (c == '\n') { break; } c = _getc_nolock(stream); } (*lineptr)[pos] = '\0'; exit: _unlock_file(stream); return pos; } ================================================ FILE: src/common/windows/getline.h ================================================ #pragma once #include #include ssize_t getline(char **lineptr, size_t *n, FILE *stream); ================================================ FILE: src/common/windows/manifest.xml ================================================ UTF-8 true/pm PerMonitor SegmentHeap ================================================ FILE: src/common/windows/nt.h ================================================ #pragma once #include #include #include #include #include enum { SystemModuleInformation = 11, SystemFirmwareTableInformation = 76, SystemBootEnvironmentInformation = 90, SystemLogicalProcessorAndGroupInformation = 107, SystemSecureBootInformation = 146, }; #define D3DKMT_ALIGN64 __attribute__((aligned(8))) typedef struct _PROCESSOR_POWER_INFORMATION { ULONG Number; ULONG MaxMhz; ULONG CurrentMhz; ULONG MhzLimit; ULONG MaxIdleState; ULONG CurrentIdleState; } PROCESSOR_POWER_INFORMATION, *PPROCESSOR_POWER_INFORMATION; NTSYSAPI NTSTATUS NTAPI NtPowerInformation( IN POWER_INFORMATION_LEVEL InformationLevel, IN PVOID InputBuffer OPTIONAL, IN ULONG InputBufferLength, OUT PVOID OutputBuffer OPTIONAL, IN ULONG OutputBufferLength); NTSYSAPI NTSTATUS NTAPI RtlGetVersion( _Inout_ PRTL_OSVERSIONINFOW lpVersionInformation ); #if __has_include() #include #else typedef UINT D3DKMT_HANDLE; typedef struct _D3DKMT_OPENADAPTERFROMLUID { LUID AdapterLuid; D3DKMT_HANDLE hAdapter; } D3DKMT_OPENADAPTERFROMLUID; EXTERN_C _Check_return_ NTSTATUS APIENTRY D3DKMTOpenAdapterFromLuid(_Inout_ CONST D3DKMT_OPENADAPTERFROMLUID*); typedef struct _D3DKMT_CLOSEADAPTER { D3DKMT_HANDLE hAdapter; // in: adapter handle } D3DKMT_CLOSEADAPTER; EXTERN_C _Check_return_ NTSTATUS APIENTRY D3DKMTCloseAdapter(_In_ CONST D3DKMT_CLOSEADAPTER*); typedef struct _D3DKMT_ADAPTERTYPE { union { struct { UINT RenderSupported : 1; // WDDM 1.2, Windows 8 UINT DisplaySupported : 1; UINT SoftwareDevice : 1; UINT PostDevice : 1; UINT HybridDiscrete : 1; // WDDM 1.3, Windows 8.1 UINT HybridIntegrated : 1; UINT IndirectDisplayDevice : 1; UINT Paravirtualized : 1; // WDDM 2.3, Windows 10 Fall Creators Update (version 1709) UINT ACGSupported : 1; UINT SupportSetTimingsFromVidPn : 1; UINT Detachable : 1; UINT ComputeOnly : 1; // WDDM 2.6, Windows 10 May 2019 Update (Version 1903) UINT Prototype : 1; UINT RuntimePowerManagement : 1; // WDDM 2.9, Windows 10 Insider Preview "Iron" UINT Reserved : 18; }; UINT Value; }; } D3DKMT_ADAPTERTYPE; typedef enum _KMTQUERYADAPTERINFOTYPE { KMTQAITYPE_ADAPTERTYPE = 15, // WDDM 1.2, Windows 8 KMTQAITYPE_NODEMETADATA = 25, // WDDM 2.0, Windows 10 } KMTQUERYADAPTERINFOTYPE; typedef struct _D3DKMT_QUERYADAPTERINFO { D3DKMT_HANDLE hAdapter; KMTQUERYADAPTERINFOTYPE Type; VOID* pPrivateDriverData; UINT PrivateDriverDataSize; } D3DKMT_QUERYADAPTERINFO; EXTERN_C _Check_return_ NTSTATUS APIENTRY D3DKMTQueryAdapterInfo(_Inout_ CONST D3DKMT_QUERYADAPTERINFO*); typedef enum _D3DKMT_QUERYSTATISTICS_TYPE { D3DKMT_QUERYSTATISTICS_PHYSICAL_ADAPTER = 10, // WDDM 2.4, Windows 10 April 2018 Update (version 1803) D3DKMT_QUERYSTATISTICS_NODE2 = 18, // WDDM 3.1, Windows 11 2022 Update (version 22H2) } D3DKMT_QUERYSTATISTICS_TYPE; typedef struct _D3DKMT_QUERYSTATISTICS_QUERY_PHYSICAL_ADAPTER { ULONG PhysicalAdapterIndex; } D3DKMT_QUERYSTATISTICS_QUERY_PHYSICAL_ADAPTER; typedef struct _D3DKMT_QUERYSTATISTICS_QUERY_NODE2 { UINT16 PhysicalAdapterIndex; UINT16 NodeOrdinal; } D3DKMT_QUERYSTATISTICS_QUERY_NODE2; typedef struct _D3DKMT_ADAPTER_PERFDATA { UINT32 PhysicalAdapterIndex; // in: The physical adapter index, in an LDA chain D3DKMT_ALIGN64 ULONGLONG MemoryFrequency; // out: Clock frequency of the memory in hertz D3DKMT_ALIGN64 ULONGLONG MaxMemoryFrequency; // out: Max memory clock frequency D3DKMT_ALIGN64 ULONGLONG MaxMemoryFrequencyOC; // out: Clock frequency of the memory while overclocked in hertz. D3DKMT_ALIGN64 ULONGLONG MemoryBandwidth; // out: Amount of memory transferred in bytes D3DKMT_ALIGN64 ULONGLONG PCIEBandwidth; // out: Amount of memory transferred over PCI-E in bytes ULONG FanRPM; // out: Fan rpm ULONG Power; // out: Power draw of the adapter in tenths of a percentage ULONG Temperature; // out: Temperature in deci-Celsius 1 = 0.1C UCHAR PowerStateOverride; // out: Overrides dxgkrnls power view of linked adapters. } D3DKMT_ADAPTER_PERFDATA; typedef struct _D3DKMT_ADAPTER_PERFDATACAPS { UINT32 PhysicalAdapterIndex; // in: The physical adapter index, in an LDA chain D3DKMT_ALIGN64 ULONGLONG MaxMemoryBandwidth; // out: Max memory bandwidth in bytes for 1 second D3DKMT_ALIGN64 ULONGLONG MaxPCIEBandwidth; // out: Max pcie bandwidth in bytes for 1 second ULONG MaxFanRPM; // out: Max fan rpm ULONG TemperatureMax; // out: Max temperature before damage levels ULONG TemperatureWarning; // out: The temperature level where throttling begins. } D3DKMT_ADAPTER_PERFDATACAPS; #define DXGK_MAX_GPUVERSION_NAME_LENGTH 32 typedef struct _D3DKMT_GPUVERSION { UINT32 PhysicalAdapterIndex; // in: The physical adapter index, in an LDA chain WCHAR BiosVersion[DXGK_MAX_GPUVERSION_NAME_LENGTH]; //out: The gpu bios version WCHAR GpuArchitecture[DXGK_MAX_GPUVERSION_NAME_LENGTH]; //out: The gpu architectures name. } D3DKMT_GPUVERSION; typedef struct _D3DKMT_QUERYSTATISTICS_PHYSICAL_ADAPTER_INFORMATION { D3DKMT_ADAPTER_PERFDATA AdapterPerfData; D3DKMT_ADAPTER_PERFDATACAPS AdapterPerfDataCaps; D3DKMT_GPUVERSION GpuVersion; } D3DKMT_QUERYSTATISTICS_PHYSICAL_ADAPTER_INFORMATION; typedef struct _D3DKMT_QUERYSTATISTICS_PROCESS_NODE_INFORMATION { D3DKMT_ALIGN64 UINT64 Reserved[34]; } D3DKMT_QUERYSTATISTICS_PROCESS_NODE_INFORMATION; typedef struct _D3DKMT_NODE_PERFDATA { UINT32 NodeOrdinal; // in: Node ordinal of the requested engine. UINT32 PhysicalAdapterIndex; // in: The physical adapter index, in an LDA chain D3DKMT_ALIGN64 ULONGLONG Frequency; // out: Clock frequency of the engine in hertz D3DKMT_ALIGN64 ULONGLONG MaxFrequency; // out: Max engine clock frequency D3DKMT_ALIGN64 ULONGLONG MaxFrequencyOC;// out: Max engine over clock frequency ULONG Voltage; // out: Voltage of the engine in milli volts mV ULONG VoltageMax; // out: Max voltage levels in milli volts. ULONG VoltageMaxOC; // out: Max voltage level while overclocked in milli volts. // WDDM 2.5 D3DKMT_ALIGN64 ULONGLONG MaxTransitionLatency; // out: Max transition latency to change the frequency in 100 nanoseconds } D3DKMT_NODE_PERFDATA; typedef struct _D3DKMT_QUERYSTATISTICS_NODE_INFORMATION { D3DKMT_QUERYSTATISTICS_PROCESS_NODE_INFORMATION GlobalInformation; //Global statistics D3DKMT_QUERYSTATISTICS_PROCESS_NODE_INFORMATION SystemInformation; //Statistics for system thread D3DKMT_NODE_PERFDATA NodePerfData; UINT32 Reserved[3]; } D3DKMT_QUERYSTATISTICS_NODE_INFORMATION; typedef union _D3DKMT_QUERYSTATISTICS_RESULT { D3DKMT_QUERYSTATISTICS_PHYSICAL_ADAPTER_INFORMATION PhysAdapterInformation; D3DKMT_QUERYSTATISTICS_NODE_INFORMATION NodeInformation; uint8_t Padding[776]; } D3DKMT_QUERYSTATISTICS_RESULT; typedef struct _D3DKMT_QUERYSTATISTICS { D3DKMT_QUERYSTATISTICS_TYPE Type; // in: type of data requested LUID AdapterLuid; // in: adapter to get export / statistics from HANDLE* hProcess; // in: process to get statistics for, if required for this query type D3DKMT_QUERYSTATISTICS_RESULT QueryResult; // out: requested data union { D3DKMT_QUERYSTATISTICS_QUERY_PHYSICAL_ADAPTER QueryPhysAdapter; // in: id of physical adapter to get statistics for D3DKMT_QUERYSTATISTICS_QUERY_NODE2 QueryNode2; // in: id of node to get statistics for }; } D3DKMT_QUERYSTATISTICS; static_assert(sizeof(D3DKMT_QUERYSTATISTICS) == #if _WIN64 0x328 #else 0x320 #endif , "D3DKMT_QUERYSTATISTICS structure size mismatch"); EXTERN_C _Check_return_ NTSTATUS APIENTRY D3DKMTQueryStatistics(_In_ CONST D3DKMT_QUERYSTATISTICS*); #define DXGK_MAX_METADATA_NAME_LENGTH 32 typedef enum { DXGK_ENGINE_TYPE_OTHER, DXGK_ENGINE_TYPE_3D, DXGK_ENGINE_TYPE_VIDEO_DECODE, DXGK_ENGINE_TYPE_VIDEO_ENCODE, DXGK_ENGINE_TYPE_VIDEO_PROCESSING, DXGK_ENGINE_TYPE_SCENE_ASSEMBLY, DXGK_ENGINE_TYPE_COPY, DXGK_ENGINE_TYPE_OVERLAY, DXGK_ENGINE_TYPE_CRYPTO, DXGK_ENGINE_TYPE_VIDEO_CODEC, DXGK_ENGINE_TYPE_MAX } DXGK_ENGINE_TYPE; typedef struct _DXGK_NODEMETADATA_FLAGS { union { struct { UINT ContextSchedulingSupported : 1; // WDDM 2.2 UINT RingBufferFenceRelease : 1; // WDDM 2.5 UINT SupportTrackedWorkload : 1; UINT UserModeSubmission : 1; UINT SupportBuildTestCommandBuffer : 1; // WDDM 3.2 UINT Reserved : 11; UINT MaxInFlightHwQueueBuffers : 16; }; UINT32 Value; }; } DXGK_NODEMETADATA_FLAGS; typedef struct _DXGK_NODEMETADATA { DXGK_ENGINE_TYPE EngineType; WCHAR FriendlyName[DXGK_MAX_METADATA_NAME_LENGTH]; DXGK_NODEMETADATA_FLAGS Flags; // WDDM 2.2 BOOLEAN GpuMmuSupported; // WDDM 2.0 ??? BOOLEAN IoMmuSupported; } __attribute__((packed)) DXGK_NODEMETADATA; typedef struct _D3DKMT_NODEMETADATA { _In_ UINT NodeOrdinalAndAdapterIndex; // WDDMv2: High word is physical adapter index, low word is node ordinal _Out_ DXGK_NODEMETADATA NodeData; } __attribute__((packed)) D3DKMT_NODEMETADATA; static_assert(sizeof(D3DKMT_NODEMETADATA) == 0x4E, "D3DKMT_NODEMETADATA structure size mismatch"); #endif NTSYSAPI NTSTATUS NTAPI NtQueryDirectoryFile( IN HANDLE FileHandle, IN HANDLE Event OPTIONAL, IN PIO_APC_ROUTINE ApcRoutine OPTIONAL, IN PVOID ApcContext OPTIONAL, OUT PIO_STATUS_BLOCK IoStatusBlock, OUT PVOID FileInformation, IN ULONG Length, IN FILE_INFORMATION_CLASS FileInformationClass, IN BOOLEAN ReturnSingleEntry, IN PUNICODE_STRING FileName OPTIONAL, IN BOOLEAN RestartScan); // https://ntdoc.m417z.com/process_devicemap_information_ex typedef struct _PROCESS_DEVICEMAP_INFORMATION_EX { union { struct { HANDLE DirectoryHandle; // A handle to a directory object that can be set as the new device map for the process. This handle must have DIRECTORY_TRAVERSE access. } Set; struct { ULONG DriveMap; // A bitmask that indicates which drive letters are currently in use in the process's device map. UCHAR DriveType[32]; // A value that indicates the type of each drive (e.g., local disk, network drive, etc.). // DRIVE_* WinBase.h } Query; }; ULONG Flags; // PROCESS_LUID_DOSDEVICES_ONLY } PROCESS_DEVICEMAP_INFORMATION_EX, *PPROCESS_DEVICEMAP_INFORMATION_EX; #ifndef NtCurrentProcess #define NtCurrentProcess() ((HANDLE)(LONG_PTR)-1) #endif typedef struct _CURDIR { UNICODE_STRING DosPath; HANDLE Handle; } CURDIR, *PCURDIR; NTSYSAPI PIMAGE_NT_HEADERS NTAPI RtlImageNtHeader(IN PVOID BaseOfImage); /** * The SECTION_IMAGE_INFORMATION structure contains detailed information about an image section. */ typedef struct _SECTION_IMAGE_INFORMATION { PVOID TransferAddress; // The address of the image entry point function. ULONG ZeroBits; // The number of high-order address bits that must be zero in the image base address. SIZE_T MaximumStackSize; // The maximum stack size of threads from the PE file header. SIZE_T CommittedStackSize; // The initial stack size of threads from the PE file header. ULONG SubSystemType; // The image subsystem from the PE file header (e.g., Windows GUI, Windows CUI, POSIX). union { struct { USHORT SubSystemMinorVersion; USHORT SubSystemMajorVersion; }; ULONG SubSystemVersion; }; union { struct { USHORT MajorOperatingSystemVersion; USHORT MinorOperatingSystemVersion; }; ULONG OperatingSystemVersion; }; USHORT ImageCharacteristics; // The image characteristics from the PE file header. USHORT DllCharacteristics; // The DLL characteristics flags (e.g., ASLR, NX compatibility). USHORT Machine; // The image architecture (e.g., x86, x64, ARM). BOOLEAN ImageContainsCode; // The image contains native executable code. union { UCHAR ImageFlags; struct { UCHAR ComPlusNativeReady : 1; // The image contains precompiled .NET assembly generated by NGEN (Native Image Generator). UCHAR ComPlusILOnly : 1; // the image contains only Microsoft Intermediate Language (IL) assembly. UCHAR ImageDynamicallyRelocated : 1; // The image was mapped using a random base address rather than the preferred base address. UCHAR ImageMappedFlat : 1; // The image was mapped using a single contiguous region, rather than separate regions for each section. UCHAR BaseBelow4gb : 1; // The image was mapped using a base address below the 4 GB boundary. UCHAR ComPlusPrefer32bit : 1; // The image prefers to run as a 32-bit process, even on a 64-bit system. UCHAR Reserved : 2; }; }; ULONG LoaderFlags; // Reserved by ntdll.dll for the Windows loader. ULONG ImageFileSize; // The size of the image, in bytes, including all headers. ULONG CheckSum; // The image file checksum, from the PE optional header. } SECTION_IMAGE_INFORMATION, *PSECTION_IMAGE_INFORMATION; typedef struct _SYSTEM_BOOT_ENVIRONMENT_INFORMATION { GUID BootIdentifier; FIRMWARE_TYPE FirmwareType; union { ULONGLONG BootFlags; struct { ULONGLONG DbgMenuOsSelection : 1; // REDSTONE4 ULONGLONG DbgHiberBoot : 1; ULONGLONG DbgSoftBoot : 1; ULONGLONG DbgMeasuredLaunch : 1; ULONGLONG DbgMeasuredLaunchCapable : 1; // 19H1 ULONGLONG DbgSystemHiveReplace : 1; ULONGLONG DbgMeasuredLaunchSmmProtections : 1; ULONGLONG DbgMeasuredLaunchSmmLevel : 7; // 20H1 ULONGLONG DbgBugCheckRecovery : 1; // 24H2 ULONGLONG DbgFASR : 1; ULONGLONG DbgUseCachedBcd : 1; }; }; } SYSTEM_BOOT_ENVIRONMENT_INFORMATION; typedef struct _RTL_PROCESS_MODULE_INFORMATION { PVOID Section; PVOID MappedBase; PVOID ImageBase; ULONG ImageSize; ULONG Flags; USHORT LoadOrderIndex; USHORT InitOrderIndex; USHORT LoadCount; USHORT OffsetToFileName; UCHAR FullPathName[256]; } RTL_PROCESS_MODULE_INFORMATION, *PRTL_PROCESS_MODULE_INFORMATION; typedef struct _RTL_PROCESS_MODULES { ULONG NumberOfModules; _Field_size_(NumberOfModules) RTL_PROCESS_MODULE_INFORMATION Modules[1]; } RTL_PROCESS_MODULES, *PRTL_PROCESS_MODULES; NTSTATUS NTAPI NtQuerySystemEnvironmentValueEx( _In_ PCUNICODE_STRING VariableName, _In_ const GUID* VendorGuid, _Out_writes_bytes_opt_(*BufferLength) PVOID Buffer, _Inout_ PULONG BufferLength, _Out_opt_ PULONG Attributes // EFI_VARIABLE_* ); NTSTATUS NTAPI RtlGUIDFromString(IN PCUNICODE_STRING GuidString, OUT GUID* Guid); typedef struct _SYSTEM_SECUREBOOT_INFORMATION { BOOLEAN SecureBootEnabled; BOOLEAN SecureBootCapable; } SYSTEM_SECUREBOOT_INFORMATION, *PSYSTEM_SECUREBOOT_INFORMATION; NTSTATUS NTAPI NtQuerySystemInformationEx( _In_ SYSTEM_INFORMATION_CLASS SystemInformationClass, _In_reads_bytes_(InputBufferLength) PVOID InputBuffer, _In_ ULONG InputBufferLength, _Out_writes_bytes_opt_(SystemInformationLength) PVOID SystemInformation, _In_ ULONG SystemInformationLength, _Out_opt_ PULONG ReturnLength ); typedef enum _SYSTEM_FIRMWARE_TABLE_ACTION { SystemFirmwareTableEnumerate, SystemFirmwareTableGet, SystemFirmwareTableMax } SYSTEM_FIRMWARE_TABLE_ACTION; typedef struct _SYSTEM_FIRMWARE_TABLE_INFORMATION { ULONG ProviderSignature; // (same as the GetSystemFirmwareTable function) SYSTEM_FIRMWARE_TABLE_ACTION Action; ULONG TableID; ULONG TableBufferLength; _Field_size_bytes_(TableBufferLength) UCHAR TableBuffer[]; } SYSTEM_FIRMWARE_TABLE_INFORMATION, *PSYSTEM_FIRMWARE_TABLE_INFORMATION; NTSYSAPI NTSTATUS NTAPI NtDelayExecution(_In_ BOOLEAN Alertable, _In_ PLARGE_INTEGER DelayInterval); /** * The KSYSTEM_TIME structure represents interrupt time, system time, and time zone bias. */ typedef struct _KSYSTEM_TIME { ULONG LowPart; LONG High1Time; LONG High2Time; } KSYSTEM_TIME, *PKSYSTEM_TIME; /** * PROCESSOR_FEATURE_MAX defines the maximum number of processor feature flags * that may be reported by the system. */ #define PROCESSOR_FEATURE_MAX 64 /** * The ALTERNATIVE_ARCHITECTURE_TYPE enumeration specifies the hardware * architecture variant used by the system. * * \remarks NEC98x86 represents the NEC PC-98 architecture, * supported only on very early Windows releases. */ typedef enum _ALTERNATIVE_ARCHITECTURE_TYPE { StandardDesign, NEC98x86, EndAlternatives } ALTERNATIVE_ARCHITECTURE_TYPE; /** * The KUSER_SHARED_DATA structure contains information shared with user-mode. * * \sa https://learn.microsoft.com/en-us/windows-hardware/drivers/ddi/ntddk/ns-ntddk-kuser_shared_data */ typedef struct _KUSER_SHARED_DATA { // // Current low 32-bit of tick count and tick count multiplier. // // N.B. The tick count is updated each time the clock ticks. // ULONG TickCountLowDeprecated; ULONG TickCountMultiplier; // // Current 64-bit interrupt time in 100ns units. // volatile KSYSTEM_TIME InterruptTime; // // Current 64-bit system time in 100ns units. // volatile KSYSTEM_TIME SystemTime; // // Current 64-bit time zone bias. // volatile KSYSTEM_TIME TimeZoneBias; // // Support image magic number range for the host system. // // N.B. This is an inclusive range. // USHORT ImageNumberLow; USHORT ImageNumberHigh; // // Copy of system root in unicode. // // N.B. This field must be accessed via the RtlGetNtSystemRoot API for // an accurate result. // WCHAR NtSystemRoot[260]; // // Maximum stack trace depth if tracing enabled. // ULONG MaxStackTraceDepth; // // Crypto exponent value. // ULONG CryptoExponent; // // Time zone ID. // ULONG TimeZoneId; // // Minimum size of a large page on the system, in bytes. // // N.B. Returned by GetLargePageMinimum() function. // ULONG LargePageMinimum; // // This value controls the Application Impact Telemetry (AIT) Sampling rate. // // This value determines how frequently the system records AIT events, // which are used by the Application Experience and compatibility // subsystems to evaluate application behavior, performance, and // potential compatibility issues. // // Lower values increase sampling frequency, while higher values reduce it. // The kernel updates this field as part of its internal telemetry and // heuristics logic. // ULONG AitSamplingValue; // // This value controls Application Compatibility (AppCompat) switchback processing. // union { ULONG AppCompatFlag; struct { ULONG SwitchbackEnabled : 1; // Basic switchback processing ULONG ExtendedHeuristics : 1; // Extended switchback heuristics ULONG TelemetryFallback : 1; // Telemetry-driven fallback ULONG Reserved : 29; } AppCompatFlags; }; // // Current Kernel Root RNG state seed version // ULONGLONG RNGSeedVersion; // // This value controls assertion failure handling. // // Historically (prior to Windows 10), this value was also used by // Code Integrity (CI), AppLocker, and related security components to // determine the minimum validation requirements for executable images, // drivers, and privileged operations. // // In modern Windows versions, this field is used primarily by the kernel's // diagnostic and validation infrastructure to decide how assertion failures // should be handled (e.g., logging, debugger break-in, or bugcheck). ULONG GlobalValidationRunlevel; // // Monotonic stamp incremented by the kernel whenever the system's // time zone bias value changes. // // N.B. This field must be accessed via the RtlGetSystemTimeAndBias API for // an accurate result. // This value is read before and after accessing the bias fields to determine // whether the time zone data changed during the read. If the stamp differs, // the caller must re-read the bias values to ensure consistency. // volatile LONG TimeZoneBiasStamp; // // The shared collective build number undecorated with C or F. // GetVersionEx hides the real number // ULONG NtBuildNumber; // // Product type. // // N.B. This field must be accessed via the RtlGetNtProductType API for // an accurate result. // NT_PRODUCT_TYPE NtProductType; BOOLEAN ProductTypeIsValid; BOOLEAN Reserved0[1]; // // Native hardware processor architecture of the running system. // // N.B. User-mode components read this field to determine the true system // architecture, especially in WOW64 scenarios where the process architecture // differs from the native one. // USHORT NativeProcessorArchitecture; // // The NT Version. // // N. B. Note that each process sees a version from its PEB, but if the // process is running with an altered view of the system version, // the following two fields are used to correctly identify the // version // ULONG NtMajorVersion; ULONG NtMinorVersion; // // Processor features. // BOOLEAN ProcessorFeatures[PROCESSOR_FEATURE_MAX]; // // Reserved fields - do not use. // ULONG MaximumUserModeAddressDeprecated; // Deprecated, use SystemBasicInformation instead. ULONG SystemRangeStartDeprecated; // Deprecated, use SystemRangeStartInformation instead. // // Time slippage while in debugger. // volatile ULONG TimeSlip; // // Alternative system architecture, e.g., NEC PC98xx on x86. // ALTERNATIVE_ARCHITECTURE_TYPE AlternativeArchitecture; // // Boot sequence, incremented for each boot attempt by the OS loader. // ULONG BootId; // // If the system is an evaluation unit, the following field contains the // date and time that the evaluation unit expires. A value of 0 indicates // that there is no expiration. A non-zero value is the UTC absolute time // that the system expires. // LARGE_INTEGER SystemExpirationDate; // // Suite support. // // N.B. This field must be accessed via the RtlGetSuiteMask API for // an accurate result. // ULONG SuiteMask; // // TRUE if a kernel debugger is connected/enabled. // BOOLEAN KdDebuggerEnabled; // // Mitigation policies. // union { UCHAR MitigationPolicies; struct { UCHAR NXSupportPolicy : 2; UCHAR SEHValidationPolicy : 2; UCHAR CurDirDevicesSkippedForDlls : 2; UCHAR Reserved : 2; }; }; // // Measured duration of a single processor yield, in cycles. This is used by // lock packages to determine how many times to spin waiting for a state // change before blocking. // USHORT CyclesPerYield; // // Current console session Id. Always zero on non-TS systems. // // N.B. This field must be accessed via the RtlGetActiveConsoleId API for an // accurate result. // volatile ULONG ActiveConsoleId; // // Force-dismounts cause handles to become invalid. Rather than always // probe handles, a serial number of dismounts is maintained that clients // can use to see if they need to probe handles. // volatile ULONG DismountCount; // // This field indicates the status of the 64-bit COM+ package on the // system. It indicates whether the Intermediate Language (IL) COM+ // images need to use the 64-bit COM+ runtime or the 32-bit COM+ runtime. // ULONG ComPlusPackage; // // Time in tick count for system-wide last user input across all terminal // sessions. For MP performance, it is not updated all the time (e.g. once // a minute per session). It is used for idle detection. // ULONG LastSystemRITEventTickCount; // // Number of physical pages in the system. This can dynamically change as // physical memory can be added or removed from a running system. This // cell is too small to hold the non-truncated value on very large memory // machines so code that needs the full value should access // FullNumberOfPhysicalPages instead. // ULONG NumberOfPhysicalPages; // // True if the system was booted in safe boot mode. // BOOLEAN SafeBootMode; // // Virtualization flags. // union { UCHAR VirtualizationFlags; #if defined(_ARM64_) // // N.B. Keep this bitfield in sync with the one in arc.w. // struct { UCHAR ArchStartedInEl2 : 1; UCHAR QcSlIsSupported : 1; UCHAR : 6; }; #endif }; // // Reserved (available for reuse). // UCHAR Reserved12[2]; // // This is a packed bitfield that contains various flags concerning // the system state. They must be manipulated using interlocked // operations. // // N.B. DbgMultiSessionSku must be accessed via the RtlIsMultiSessionSku // API for an accurate result // union { ULONG SharedDataFlags; struct { // // The following bit fields are for the debugger only. Do not use. // Use the bit definitions instead. // ULONG DbgErrorPortPresent : 1; ULONG DbgElevationEnabled : 1; ULONG DbgVirtEnabled : 1; ULONG DbgInstallerDetectEnabled : 1; ULONG DbgLkgEnabled : 1; ULONG DbgDynProcessorEnabled : 1; ULONG DbgConsoleBrokerEnabled : 1; ULONG DbgSecureBootEnabled : 1; ULONG DbgMultiSessionSku : 1; ULONG DbgMultiUsersInSessionSku : 1; ULONG DbgStateSeparationEnabled : 1; ULONG DbgSplitTokenEnabled : 1; ULONG DbgShadowAdminEnabled : 1; ULONG SpareBits : 19; }; }; // ... more fields follow, but we don't need them } KUSER_SHARED_DATA, *PKUSER_SHARED_DATA; #define SharedUserData ((const KUSER_SHARED_DATA*) 0x7FFE0000UL) static inline uint64_t ffKSystemTimeToUInt64(const volatile KSYSTEM_TIME* pTime) { #if _WIN64 return *(uint64_t*) pTime; #else uint32_t low, high1, high2; do { high1 = pTime->High1Time; low = pTime->LowPart; high2 = pTime->High2Time; } while (high1 != high2); return ((uint64_t) high1 << 32) | low; #endif } static inline bool ffIsWindows10OrGreater() { #if FF_WIN81_COMPAT return SharedUserData->NtMajorVersion >= 10; #else return true; #endif } static inline bool ffIsWindows11OrGreater() { return ffIsWindows10OrGreater() && SharedUserData->NtBuildNumber >= 22000; } NTSYSAPI NTSTATUS NTAPI NtOpenProcessToken( _In_ HANDLE ProcessHandle, _In_ ACCESS_MASK DesiredAccess, _Out_ PHANDLE TokenHandle ); NTSYSAPI NTSTATUS NTAPI NtAdjustPrivilegesToken( _In_ HANDLE TokenHandle, _In_ BOOLEAN DisableAllPrivileges, _In_opt_ PTOKEN_PRIVILEGES NewState, _In_ ULONG BufferLength, _Out_writes_bytes_to_opt_(BufferLength, *ReturnLength) PTOKEN_PRIVILEGES PreviousState, _Out_opt_ PULONG ReturnLength ); NTSYSAPI NTSTATUS NTAPI NtQueryInformationToken( _In_ HANDLE TokenHandle, _In_ TOKEN_INFORMATION_CLASS TokenInformationClass, _Out_writes_bytes_to_opt_(TokenInformationLength, *ReturnLength) PVOID TokenInformation, _In_ ULONG TokenInformationLength, _Out_ PULONG ReturnLength ); #define NtCurrentProcessToken() ((HANDLE)(LONG_PTR)-4) // for NtQueryInformationToken only; Windows 8+ NTSYSAPI NTSTATUS NTAPI NtReadFile( _In_ HANDLE FileHandle, _In_opt_ HANDLE Event, _In_opt_ PIO_APC_ROUTINE ApcRoutine, _In_opt_ PVOID ApcContext, _Out_ PIO_STATUS_BLOCK IoStatusBlock, _Out_writes_bytes_(Length) PVOID Buffer, _In_ ULONG Length, _In_opt_ PLARGE_INTEGER ByteOffset, _In_opt_ PULONG Key ); NTSYSAPI NTSTATUS NTAPI NtCreateEvent( _Out_ PHANDLE EventHandle, _In_ ACCESS_MASK DesiredAccess, _In_opt_ POBJECT_ATTRIBUTES ObjectAttributes, _In_ EVENT_TYPE EventType, _In_ BOOLEAN InitialState ); NTSYSAPI NTSTATUS NTAPI NtQueryAttributesFile( _In_ PCOBJECT_ATTRIBUTES ObjectAttributes, _Out_ PFILE_BASIC_INFORMATION FileInformation ); NTSYSAPI NTSTATUS NTAPI RtlUnicodeToUTF8N( _Out_writes_bytes_to_(UTF8StringMaxByteCount, *UTF8StringActualByteCount) PCHAR UTF8StringDestination, _In_ ULONG UTF8StringMaxByteCount, _Out_opt_ PULONG UTF8StringActualByteCount, _In_reads_bytes_(UnicodeStringByteCount) PCWCH UnicodeStringSource, _In_ ULONG UnicodeStringByteCount ); NTSYSAPI NTSTATUS NTAPI RtlUTF8ToUnicodeN( _Out_writes_bytes_to_(UnicodeStringMaxByteCount, *UnicodeStringActualByteCount) PWSTR UnicodeStringDestination, _In_ ULONG UnicodeStringMaxByteCount, _Out_opt_ PULONG UnicodeStringActualByteCount, _In_reads_bytes_(UTF8StringByteCount) PCCH UTF8StringSource, _In_ ULONG UTF8StringByteCount ); #define RTL_MAX_DRIVE_LETTERS 32 typedef struct _RTL_DRIVE_LETTER_CURDIR { USHORT Flags; USHORT Length; ULONG TimeStamp; STRING DosPath; } RTL_DRIVE_LETTER_CURDIR, *PRTL_DRIVE_LETTER_CURDIR; typedef struct _RTL_USER_PROCESS_PARAMETERS_FULL { ULONG MaximumLength; ULONG Length; ULONG Flags; ULONG DebugFlags; HANDLE ConsoleHandle; ULONG ConsoleFlags; HANDLE StandardInput; HANDLE StandardOutput; HANDLE StandardError; CURDIR CurrentDirectory; UNICODE_STRING DllPath; UNICODE_STRING ImagePathName; UNICODE_STRING CommandLine; PVOID Environment; ULONG StartingX; ULONG StartingY; ULONG CountX; ULONG CountY; ULONG CountCharsX; ULONG CountCharsY; ULONG FillAttribute; ULONG WindowFlags; ULONG ShowWindowFlags; UNICODE_STRING WindowTitle; UNICODE_STRING DesktopInfo; UNICODE_STRING ShellInfo; UNICODE_STRING RuntimeData; RTL_DRIVE_LETTER_CURDIR CurrentDirectories[RTL_MAX_DRIVE_LETTERS]; // Windows Vista ULONG_PTR EnvironmentSize; // Windows 7 ULONG_PTR EnvironmentVersion; // Windows 8 PVOID PackageDependencyData; ULONG ProcessGroupId; // ... } RTL_USER_PROCESS_PARAMETERS_FULL, *PRTL_USER_PROCESS_PARAMETERS_FULL; typedef struct _PEB_FULL { // // The process was cloned with an inherited address space. // BOOLEAN InheritedAddressSpace; // // The process has image file execution options (IFEO). // BOOLEAN ReadImageFileExecOptions; // // The process has a debugger attached. // BOOLEAN BeingDebugged; union { BOOLEAN BitField; struct { BOOLEAN ImageUsesLargePages : 1; // The process uses large image regions (4 MB). BOOLEAN IsProtectedProcess : 1; // The process is a protected process. BOOLEAN IsImageDynamicallyRelocated : 1; // The process image base address was relocated. BOOLEAN SkipPatchingUser32Forwarders : 1; // The process skipped forwarders for User32.dll functions. 1 for 64-bit, 0 for 32-bit. BOOLEAN IsPackagedProcess : 1; // The process is a packaged store process (APPX/MSIX). BOOLEAN IsAppContainerProcess : 1; // The process has an AppContainer token. BOOLEAN IsProtectedProcessLight : 1; // The process is a protected process (light). BOOLEAN IsLongPathAwareProcess : 1; // The process is long path aware. }; }; // // Handle to a mutex for synchronization. // HANDLE Mutant; // // Pointer to the base address of the process image. // PVOID ImageBaseAddress; // // Pointer to the process loader data. // PPEB_LDR_DATA Ldr; // // Pointer to the process parameters. // PRTL_USER_PROCESS_PARAMETERS_FULL ProcessParameters; // // Reserved. // PVOID SubSystemData; // // Pointer to the process default heap. // PVOID ProcessHeap; // ... } PEB_FULL, *PPEB_FULL; typedef struct _TEB_FULL { // // Thread Information Block (TIB) contains the thread's stack, base and limit addresses, the current stack pointer, and the exception list. // NT_TIB NtTib; // // Reserved. // PVOID EnvironmentPointer; // // Client ID for this thread. // CLIENT_ID ClientId; // // A handle to an active Remote Procedure Call (RPC) if the thread is currently involved in an RPC operation. // PVOID ActiveRpcHandle; // // A pointer to the __declspec(thread) local storage array. // PVOID ThreadLocalStoragePointer; // // A pointer to the Process Environment Block (PEB), which contains information about the process. // PPEB_FULL ProcessEnvironmentBlock; // // The previous Win32 error value for this thread. // ULONG LastErrorValue; // // The number of critical sections currently owned by this thread. // ULONG CountOfOwnedCriticalSections; // // Reserved. // PVOID CsrClientThread; // // Reserved for win32k.sys // PVOID Win32ThreadInfo; // // Reserved for user32.dll // ULONG User32Reserved[26]; // // Reserved for winsrv.dll // ULONG UserReserved[5]; // // Reserved. // PVOID WOW32Reserved; // // The LCID of the current thread. (Kernel32!GetThreadLocale) // LCID CurrentLocale; } TEB_FULL, *PTEB_FULL; static inline PTEB_FULL ffGetTeb() { return (PTEB_FULL) NtCurrentTeb(); } static inline PPEB_FULL ffGetPeb() { return ffGetTeb()->ProcessEnvironmentBlock; } NTSYSAPI NTSTATUS NTAPI RtlExpandEnvironmentStrings( _In_opt_ PVOID Environment, _In_reads_(SourceLength) PCWSTR Source, _In_ SIZE_T SourceLength, _Out_writes_(DestinationLength) PWSTR Destination, _In_ SIZE_T DestinationLength, _Out_opt_ PSIZE_T ReturnLength ); NTSYSAPI NTSTATUS NTAPI NtOpenKey( _Out_ PHANDLE KeyHandle, _In_ ACCESS_MASK DesiredAccess, _In_ POBJECT_ATTRIBUTES ObjectAttributes ); typedef enum _KEY_VALUE_INFORMATION_CLASS { KeyValueBasicInformation, // KEY_VALUE_BASIC_INFORMATION KeyValueFullInformation, // KEY_VALUE_FULL_INFORMATION KeyValuePartialInformation, // KEY_VALUE_PARTIAL_INFORMATION KeyValueFullInformationAlign64, // KEY_VALUE_FULL_INFORMATION_ALIGN64 KeyValuePartialInformationAlign64, // KEY_VALUE_PARTIAL_INFORMATION_ALIGN64 KeyValueLayerInformation, // KEY_VALUE_LAYER_INFORMATION MaxKeyValueInfoClass } KEY_VALUE_INFORMATION_CLASS; NTSYSAPI NTSTATUS NTAPI NtQueryValueKey( _In_ HANDLE KeyHandle, _In_ PCUNICODE_STRING ValueName, _In_ KEY_VALUE_INFORMATION_CLASS KeyValueInformationClass, _Out_writes_bytes_to_opt_(Length, *ResultLength) PVOID KeyValueInformation, _In_ ULONG Length, _Out_ PULONG ResultLength ); NTSYSAPI NTSTATUS NTAPI RtlFormatCurrentUserKeyPath( _Out_ PUNICODE_STRING CurrentUserKeyPath ); typedef struct _KEY_VALUE_PARTIAL_INFORMATION { ULONG TitleIndex; ULONG Type; ULONG DataLength; _Field_size_bytes_(DataLength) UCHAR Data[]; } KEY_VALUE_PARTIAL_INFORMATION, *PKEY_VALUE_PARTIAL_INFORMATION; typedef enum _KEY_INFORMATION_CLASS { KeyBasicInformation, // KEY_BASIC_INFORMATION KeyNodeInformation, // KEY_NODE_INFORMATION KeyFullInformation, // KEY_FULL_INFORMATION KeyNameInformation, // KEY_NAME_INFORMATION KeyCachedInformation, // KEY_CACHED_INFORMATION KeyFlagsInformation, // KEY_FLAGS_INFORMATION KeyVirtualizationInformation, // KEY_VIRTUALIZATION_INFORMATION KeyHandleTagsInformation, // KEY_HANDLE_TAGS_INFORMATION KeyTrustInformation, // KEY_TRUST_INFORMATION KeyLayerInformation, // KEY_LAYER_INFORMATION MaxKeyInfoClass } KEY_INFORMATION_CLASS; NTSYSAPI NTSTATUS NTAPI NtEnumerateKey( _In_ HANDLE KeyHandle, _In_ ULONG Index, _In_ KEY_INFORMATION_CLASS KeyInformationClass, _Out_writes_bytes_to_opt_(Length, *ResultLength) PVOID KeyInformation, _In_ ULONG Length, _Out_ PULONG ResultLength ); typedef struct _KEY_BASIC_INFORMATION { LARGE_INTEGER LastWriteTime; // Number of 100-nanosecond intervals since this key or any of its values changed. ULONG TitleIndex; // Reserved // A legacy field originally intended for use with localization such as an index of a resource table. ULONG NameLength; // The size, in bytes, of the key name string in the Name array. _Field_size_bytes_(NameLength) WCHAR Name[]; // The name of the registry key. This string is not null-terminated. } KEY_BASIC_INFORMATION, *PKEY_BASIC_INFORMATION; typedef struct _KEY_FULL_INFORMATION { LARGE_INTEGER LastWriteTime; ULONG TitleIndex; ULONG ClassOffset; ULONG ClassLength; ULONG SubKeys; ULONG MaxNameLength; ULONG MaxClassLength; ULONG Values; ULONG MaxValueNameLength; ULONG MaxValueDataLength; WCHAR Class[]; } KEY_FULL_INFORMATION, *PKEY_FULL_INFORMATION; NTSYSAPI NTSTATUS NTAPI NtQueryKey( _In_ HANDLE KeyHandle, _In_ KEY_INFORMATION_CLASS KeyInformationClass, _Out_writes_bytes_to_opt_(Length, *ResultLength) PVOID KeyInformation, _In_ ULONG Length, _Out_ PULONG ResultLength ); NTSYSAPI NTSTATUS NTAPI NtOpenProcess( _Out_ PHANDLE ProcessHandle, _In_ ACCESS_MASK DesiredAccess, _In_ PCOBJECT_ATTRIBUTES ObjectAttributes, _In_opt_ PCLIENT_ID ClientId ); NTSYSAPI NTSTATUS NTAPI LdrLoadDll( _In_opt_ PCWSTR DllPath, _In_opt_ PULONG DllCharacteristics, _In_ PCUNICODE_STRING DllName, _Out_ PVOID *DllHandle ); NTSYSAPI NTSTATUS NTAPI LdrUnloadDll( _In_ PVOID DllHandle ); NTSYSAPI NTSTATUS NTAPI LdrGetDllHandle( _In_opt_ PCWSTR DllPath, _In_opt_ PULONG DllCharacteristics, _In_ PCUNICODE_STRING DllName, _Out_ PVOID *DllHandle ); NTSYSAPI NTSTATUS NTAPI LdrGetProcedureAddress( _In_ PVOID DllHandle, _In_opt_ PCANSI_STRING ProcedureName, _In_opt_ ULONG ProcedureNumber, _Out_ PVOID *ProcedureAddress ); ================================================ FILE: src/common/windows/perflib_.h ================================================ #pragma once #include #include // Missing from of MinGW-w64 SDK #define PERF_WILDCARD_COUNTER 0xFFFFFFFF #define PERF_WILDCARD_INSTANCE L"*" #define PERF_AGGREGATE_INSTANCE L"_Total" #define PERF_MAX_INSTANCE_NAME 1024 typedef struct _PERF_INSTANCE_HEADER { ULONG Size; // = sizeof(PERF_INSTANCE_HEADER) + sizeof(InstanceName) + sizeof(Padding) ULONG InstanceId; // Instance ID. // Followed by: // WCHAR InstanceName[]; // Nul-terminated. // WCHAR Padding[]; // Pad to a multiple of 8 bytes } PERF_INSTANCE_HEADER, *PPERF_INSTANCE_HEADER; typedef struct _PERF_COUNTER_IDENTIFIER { GUID CounterSetGuid; // The GUID of the counterset. ULONG Status; // Win32 error code indicating success/failure of the add/delete operation. ULONG Size; // sizeof(PERF_COUNTER_IDENTIFIER) + sizeof(InstanceName) + sizeof(Padding) ULONG CounterId; // CounterId, or PERF_WILDCARD_COUNTER for all counters. ULONG InstanceId; // InstanceId, or 0xFFFFFFFF to not filter on instance ID. ULONG Index; // Set by PerfQueryCounterInfo to the position in which the corresponding counter data is returned. ULONG Reserved; // Reserved. // Followed by: // WCHAR InstanceName[]; // WCHAR Padding[]; } PERF_COUNTER_IDENTIFIER, * PPERF_COUNTER_IDENTIFIER; typedef struct _PERF_DATA_HEADER { ULONG dwTotalSize; // = sizeof(PERF_DATA_HEADER) + sizeof(PERF_COUNTER_HEADER blocks...) ULONG dwNumCounters; // The number of PERF_COUNTER_HEADER blocks. LONGLONG PerfTimeStamp; // Timestamp from a high-resolution clock. LONGLONG PerfTime100NSec; // The number of 100 nanosecond intervals since January 1, 1601, in Coordinated Universal Time (UTC). LONGLONG PerfFreq; // The frequency of a high-resolution clock. SYSTEMTIME SystemTime; // The time at which data is collected on the provider side. // Followed by: // PERF_COUNTER_HEADER blocks...; } PERF_DATA_HEADER, * PPERF_DATA_HEADER; typedef enum _PerfCounterDataType { PERF_ERROR_RETURN = 0, /* An error occurred when the performance counter value was queried. */ PERF_SINGLE_COUNTER = 1, /* Query returned a single counter from a single-instance. */ PERF_MULTIPLE_COUNTERS = 2, /* Query returned multiple counters from a single instance. */ PERF_MULTIPLE_INSTANCES = 4, /* Query returned a single counter from each of multiple instances. */ PERF_COUNTERSET = 6 /* Query returned multiple counters from each of multiple instances. */ } PerfCounterDataType; typedef struct _PERF_COUNTER_HEADER { ULONG dwStatus; // Win32 error code indicating success/failure of the query operation. PerfCounterDataType dwType; // Result type - error, single/single, multi/single, single/multi, multi/multi. ULONG dwSize; // = sizeof(PERF_COUNTER_HEADER) + sizeof(Additional data) ULONG Reserved; // Reserved. // Followed by additional data: // If dwType == PERF_ERROR_RETURN: nothing. // If dwType == PERF_SINGLE_COUNTER: PERF_COUNTER_DATA block. // If dwType == PERF_MULTIPLE_COUNTERS: PERF_MULTI_COUNTERS block + PERF_COUNTER_DATA blocks. // If dwType == PERF_MULTIPLE_INSTANCES: PERF_MULTI_INSTANCES block. // If dwType == PERF_COUNTERSET: PERF_MULTI_COUNTERS block + PERF_MULTI_INSTANCES block. } PERF_COUNTER_HEADER, * PPERF_COUNTER_HEADER; typedef struct _PERF_MULTI_INSTANCES { ULONG dwTotalSize; // = sizeof(PERF_MULTI_INSTANCES) + sizeof(instance data blocks...) ULONG dwInstances; // Number of instance data blocks. // Followed by: // Instance data blocks...; } PERF_MULTI_INSTANCES, * PPERF_MULTI_INSTANCES; typedef struct _PERF_MULTI_COUNTERS { ULONG dwSize; // sizeof(PERF_MULTI_COUNTERS) + sizeof(CounterIds) ULONG dwCounters; // Number of counter ids. // Followed by: // DWORD CounterIds[dwCounters]; } PERF_MULTI_COUNTERS, * PPERF_MULTI_COUNTERS; typedef struct _PERF_COUNTER_DATA { ULONG dwDataSize; // Size of the counter data, in bytes. ULONG dwSize; // = sizeof(PERF_COUNTER_DATA) + sizeof(Data) + sizeof(Padding) // Followed by: // BYTE Data[dwDataSize]; // BYTE Padding[]; } PERF_COUNTER_DATA, * PPERF_COUNTER_DATA; _Success_(return == ERROR_SUCCESS) ULONG WINAPI PerfEnumerateCounterSetInstances( _In_opt_z_ LPCWSTR szMachine, _In_ LPCGUID pCounterSetId, _Out_opt_bytecap_post_bytecount_(cbInstances, *pcbInstancesActual) PPERF_INSTANCE_HEADER pInstances, DWORD cbInstances, _Out_ LPDWORD pcbInstancesActual ); _Success_(return == ERROR_SUCCESS) ULONG WINAPI PerfOpenQueryHandle( _In_opt_z_ LPCWSTR szMachine, _Out_ HANDLE * phQuery ); _Success_(return == ERROR_SUCCESS) ULONG WINAPI PerfCloseQueryHandle( _In_ HANDLE hQuery ); _Success_(return == ERROR_SUCCESS) ULONG WINAPI PerfAddCounters( _In_ HANDLE hQuery, _Inout_bytecount_(cbCounters) PPERF_COUNTER_IDENTIFIER pCounters, DWORD cbCounters ); _Success_(return == ERROR_SUCCESS) ULONG WINAPI PerfDeleteCounters( _In_ HANDLE hQuery, _Inout_bytecount_(cbCounters) PPERF_COUNTER_IDENTIFIER pCounters, DWORD cbCounters ); _Success_(return == ERROR_SUCCESS) ULONG WINAPI PerfQueryCounterData( _In_ HANDLE hQuery, _Out_opt_bytecap_post_bytecount_(cbCounterBlock, *pcbCounterBlockActual) PPERF_DATA_HEADER pCounterBlock, DWORD cbCounterBlock, _Out_ LPDWORD pcbCounterBlockActual ); ================================================ FILE: src/common/windows/registry.c ================================================ #include "registry.h" #include "unicode.h" #include "common/mallocHelper.h" #include "common/debug.h" #include "common/windows/nt.h" #include #include static HANDLE hRootKeys[8 /*(uintptr_t) HKEY_CURRENT_USER_LOCAL_SETTINGS - (uintptr_t) HKEY_CLASSES_ROOT + 1*/]; static const char* hKey2Str(HANDLE hRootKey) { #define HKEY_CASE(compareKey) if(hRootKey == hRootKeys[(uintptr_t)compareKey - (uintptr_t)HKEY_CLASSES_ROOT]) return #compareKey; HKEY_CASE(HKEY_CLASSES_ROOT) HKEY_CASE(HKEY_CURRENT_USER) HKEY_CASE(HKEY_LOCAL_MACHINE) HKEY_CASE(HKEY_USERS) HKEY_CASE(HKEY_PERFORMANCE_DATA) HKEY_CASE(HKEY_CURRENT_CONFIG) HKEY_CASE(HKEY_DYN_DATA) HKEY_CASE(HKEY_CURRENT_USER_LOCAL_SETTINGS) #undef HKEY_CASE return "UNKNOWN"; } HANDLE ffRegGetRootKeyHandle(HKEY hKey) { assert(hKey); assert((uintptr_t) hKey >= (uintptr_t) HKEY_CLASSES_ROOT && (uintptr_t) hKey <= (uintptr_t) HKEY_CURRENT_USER_LOCAL_SETTINGS); FF_DEBUG("Getting root key handle for HKEY %08llx", (uint64_t)(uintptr_t) hKey); HANDLE result = hRootKeys[(uintptr_t) hKey - (uintptr_t) HKEY_CLASSES_ROOT]; if (result) { FF_DEBUG("Found cached root key handle for %s -> %p", hKey2Str(result), result); return result; } switch ((uintptr_t) hKey) { case (uintptr_t) HKEY_CURRENT_USER: { UNICODE_STRING path = {}; NTSTATUS status = RtlFormatCurrentUserKeyPath(&path); if (!NT_SUCCESS(status)) { FF_DEBUG("RtlFormatCurrentUserKeyPath() failed: %s", ffDebugNtStatus(status)); return NULL; } status = NtOpenKey(&result, KEY_READ, &(OBJECT_ATTRIBUTES) { .Length = sizeof(OBJECT_ATTRIBUTES), .RootDirectory = NULL, .ObjectName = &path, }); if (!NT_SUCCESS(status)) { FF_DEBUG("NtOpenKey(%ls) failed: %s (0x%08lx)", path.Buffer, ffDebugNtStatus(status), status); RtlFreeUnicodeString(&path); return NULL; } RtlFreeUnicodeString(&path); break; } case (uintptr_t) HKEY_LOCAL_MACHINE: { UNICODE_STRING path = RTL_CONSTANT_STRING(L"\\Registry\\Machine"); NTSTATUS status = NtOpenKey(&result, KEY_READ, &(OBJECT_ATTRIBUTES) { .Length = sizeof(OBJECT_ATTRIBUTES), .RootDirectory = NULL, .ObjectName = &path, .Attributes = OBJ_CASE_INSENSITIVE, }); if (!NT_SUCCESS(status)) { FF_DEBUG("NtOpenKey(%ls) failed: %s (0x%08lx)", path.Buffer, ffDebugNtStatus(status), status); return NULL; } break; } default: // Unsupported FF_DEBUG("Unsupported root key: %p", hKey); assert(false); return NULL; } hRootKeys[(uintptr_t) hKey - (uintptr_t) HKEY_CLASSES_ROOT] = result; FF_DEBUG("Opened root key %s -> %p", hKey2Str(result), result); return result; } bool ffRegOpenSubkeyForRead(HANDLE hKey, const wchar_t* subKeyW, HANDLE* result, FFstrbuf* error) { assert(hKey); assert(subKeyW); assert(result); FF_DEBUG("Opening subkey %s\\%ls for read", hKey2Str(hKey), subKeyW); USHORT subKeyLen = (USHORT) (wcslen(subKeyW) * sizeof(wchar_t)); if (!NT_SUCCESS(NtOpenKey(result, KEY_READ, &(OBJECT_ATTRIBUTES) { .Length = sizeof(OBJECT_ATTRIBUTES), .RootDirectory = hKey, .ObjectName = &(UNICODE_STRING) { .Length = subKeyLen, .MaximumLength = subKeyLen + (USHORT) sizeof(wchar_t), .Buffer = (wchar_t*) subKeyW, }, }))) { FF_DEBUG("NtOpenKey(%s\\) failed", hKey2Str(hKey)); if (error) { FF_STRBUF_AUTO_DESTROY subKeyA = ffStrbufCreateWS(subKeyW); ffStrbufAppendF(error, "NtOpenKey(%s\\%s) failed", hKey2Str(hKey), subKeyA.chars); } return false; } FF_DEBUG("Opened subkey under %s -> %p", hKey2Str(hKey), *result); return true; } static bool processRegValue(const FFRegValueArg* arg, const ULONG regType, const void* regData, ULONG regDataLen, FFstrbuf* error) { switch (arg->type) { case FF_ARG_TYPE_STRBUF: { if (regType != REG_SZ && regType != REG_EXPAND_SZ) goto type_mismatch; FFstrbuf* strbuf = (FFstrbuf*) arg->value; uint32_t strLen = regDataLen / sizeof(wchar_t); if (strLen == 0) ffStrbufClear(strbuf); else { const wchar_t* ws = (const wchar_t*) regData; if (ws[strLen - 1] == L'\0') --strLen; ffStrbufSetNWS(strbuf, strLen, ws); } break; } case FF_ARG_TYPE_UINT: case FF_ARG_TYPE_UINT64: case FF_ARG_TYPE_UINT16: case FF_ARG_TYPE_UINT8: case FF_ARG_TYPE_BOOL: { uint64_t value = 0; if (regType == REG_DWORD) { if (regDataLen < sizeof(uint32_t)) goto type_mismatch; value = *(uint32_t*) regData; } else if (regType == REG_QWORD) { if (regDataLen < sizeof(uint64_t)) goto type_mismatch; value = *(uint64_t*) regData; } else goto type_mismatch; if (arg->type == FF_ARG_TYPE_UINT) *(uint32_t*) arg->value = (uint32_t) value; else if (arg->type == FF_ARG_TYPE_UINT64) *(uint64_t*) arg->value = (uint64_t) value; else if (arg->type == FF_ARG_TYPE_UINT16) *(uint16_t*) arg->value = (uint16_t) value; else if (arg->type == FF_ARG_TYPE_UINT8) *(uint8_t*) arg->value = (uint8_t) value; else if (arg->type == FF_ARG_TYPE_BOOL) *(bool*) arg->value = value != 0; break; } case FF_ARG_TYPE_FLOAT: { if (regDataLen < sizeof(float)) goto type_mismatch; *(float*) arg->value = *(float*) regData; break; } case FF_ARG_TYPE_DOUBLE: { if (regDataLen < sizeof(double)) goto type_mismatch; *(double*) arg->value = *(double*) regData; break; } case FF_ARG_TYPE_LIST: { if (regType != REG_MULTI_SZ && regType != REG_BINARY) goto type_mismatch; FFlist* list = (FFlist*) arg->value; ffListClear(list); if (regType == REG_MULTI_SZ) { if (list->elementSize != sizeof(FFstrbuf)) { if (error) { FF_STRBUF_AUTO_DESTROY nameA = arg->name ? ffStrbufCreateWS(arg->name) : ffStrbufCreateStatic("(default)"); ffStrbufAppendF(error, "ffRegReadValues(%s) type mismatch: expected list of strbuf for REG_MULTI_SZ", nameA.chars); } return false; } for ( const wchar_t* ptr = (const wchar_t*) regData; (const uint8_t*) ptr < (const uint8_t*) regData + regDataLen && *ptr; ptr++ ) { uint32_t strLen = (uint32_t) wcsnlen(ptr, regDataLen / sizeof(wchar_t) - (size_t) (ptr - (const wchar_t*) regData)); ffStrbufInitNWS(FF_LIST_ADD(FFstrbuf, *list), strLen, ptr); ptr += strLen; } } else { if (list->elementSize != sizeof(uint8_t)) { if (error) { FF_STRBUF_AUTO_DESTROY nameA = arg->name ? ffStrbufCreateWS(arg->name) : ffStrbufCreateStatic("(default)"); ffStrbufAppendF(error, "ffRegReadValues(%s) type mismatch: expected list of uint8_t for REG_BINARY", nameA.chars); } return false; } ffListReserve(list, regDataLen); memcpy(list->data, regData, regDataLen); list->length = regDataLen; } break; } case FF_ARG_TYPE_INT: // Use UINT instead case FF_ARG_TYPE_STRING: case FF_ARG_TYPE_NULL: default: if (error) { FF_STRBUF_AUTO_DESTROY nameA = arg->name ? ffStrbufCreateWS(arg->name) : ffStrbufCreateStatic("(default)"); ffStrbufAppendF(error, "processRegValue(%s) unsupported FFArgType %u", nameA.chars, (unsigned) arg->type); } return false; } return true; type_mismatch: FF_DEBUG("ffRegReadValues(%ls) type mismatch: regType=%u, argType=%u, dataLen=%u", arg->name ?: L"(default)", (unsigned) regType, (unsigned) arg->type, (unsigned) regDataLen); if (error) { FF_STRBUF_AUTO_DESTROY nameA = arg->name ? ffStrbufCreateWS(arg->name) : ffStrbufCreateStatic("(default)"); ffStrbufAppendF(error, "ffRegReadValues(%s) type mismatch: regType=%u, argType=%u, dataLen=%u", nameA.chars, (unsigned) regType, (unsigned) arg->type, (unsigned) regDataLen); } return false; } bool ffRegReadValue(HANDLE hKey, const FFRegValueArg* arg, FFstrbuf* error) { UNICODE_STRING* valueNameU = &(UNICODE_STRING) { .Length = arg->name ? (USHORT) (wcslen(arg->name) * sizeof(wchar_t)) : 0 /*(default)*/, .MaximumLength = 0, .Buffer = (wchar_t*) arg->name, }; alignas(KEY_VALUE_PARTIAL_INFORMATION) uint8_t staticBuffer[128 + sizeof(KEY_VALUE_PARTIAL_INFORMATION)]; FF_AUTO_FREE uint8_t* dynamicBuffer = NULL; KEY_VALUE_PARTIAL_INFORMATION* buffer = (KEY_VALUE_PARTIAL_INFORMATION*) &staticBuffer; DWORD bufSize = sizeof(staticBuffer); if (NT_SUCCESS(NtQueryValueKey(hKey, valueNameU, KeyValuePartialInformation, buffer, bufSize, &bufSize))) goto process_value; if (bufSize == 0) { FF_DEBUG("NtQueryValueKey(%p, %ls) failed (bufSize=0)", hKey, arg->name ?: L"(default)"); if (error) { FF_STRBUF_AUTO_DESTROY valueNameA = arg->name ? ffStrbufCreateWS(arg->name) : ffStrbufCreateStatic("(default)"); ffStrbufAppendF(error, "NtQueryValueKey(%p, %s) failed", hKey, valueNameA.chars); } return false; } dynamicBuffer = (uint8_t*) malloc(bufSize); buffer = (KEY_VALUE_PARTIAL_INFORMATION*) dynamicBuffer; if (!NT_SUCCESS(NtQueryValueKey(hKey, valueNameU, KeyValuePartialInformation, buffer, bufSize, &bufSize))) { FF_DEBUG("NtQueryValueKey(%p, %ls, buffer=%u) failed", hKey, arg->name ?: L"(default)", (unsigned) bufSize); if (error) { FF_STRBUF_AUTO_DESTROY valueNameA = arg->name ? ffStrbufCreateWS(arg->name) : ffStrbufCreateStatic("(default)"); ffStrbufAppendF(error, "NtQueryValueKey(%p, %s, buffer) failed", hKey, valueNameA.chars); } return false; } process_value: FF_DEBUG("Read value from %p (%ls), type=%u, len=%u", hKey, arg->name ?: L"(default)", (unsigned) buffer->Type, (unsigned) buffer->DataLength); return processRegValue(arg, buffer->Type, buffer->Data, buffer->DataLength, error); } bool ffRegReadValues(HANDLE hKey, uint32_t argc, const FFRegValueArg argv[], FFstrbuf* error) { if (__builtin_expect(argc == 0, false)) return true; assert(argv); FF_AUTO_FREE UNICODE_STRING* names = (UNICODE_STRING*) calloc(argc, sizeof(*names)); FF_AUTO_FREE KEY_VALUE_ENTRY* entries = (KEY_VALUE_ENTRY*) calloc(argc, sizeof(*entries)); for (uint32_t i = 0; i < argc; ++i) { if (__builtin_expect(!argv[i].value, false)) { FF_DEBUG("ffRegReadValues(argv[%u].value) is NULL", (unsigned) i); if (error) ffStrbufAppendF(error, "ffRegReadValues(argv[%u].pVar) is NULL", (unsigned) i); return false; } names[i] = (UNICODE_STRING) { .Length = argv[i].name ? (USHORT) (wcslen(argv[i].name) * sizeof(wchar_t)) : 0 /*(default)*/, .MaximumLength = 0, .Buffer = (wchar_t*) argv[i].name, }; entries[i].ValueName = &names[i]; } ULONG bufferSize = argc * 128; if (bufferSize < 512) bufferSize = 512; FF_AUTO_FREE uint8_t* buffer = NULL; while (true) { buffer = (uint8_t*) realloc(buffer, bufferSize); ULONG writtenSize = bufferSize; ULONG requiredSize = 0; NTSTATUS status = NtQueryMultipleValueKey(hKey, entries, argc, buffer, &writtenSize, &requiredSize); if (!NT_SUCCESS(status)) { // Buffer too small: docs guarantee requiredSize is returned when provided. if (requiredSize > bufferSize) { FF_DEBUG("NtQueryMultipleValueKey(%p) resize buffer: %u -> %u", hKey, (unsigned) bufferSize, (unsigned) requiredSize); bufferSize = requiredSize; continue; } FF_DEBUG("NtQueryMultipleValueKey(%p, argc=%u) failed, status=0x%08X", hKey, (unsigned) argc, (unsigned) status); if (error) ffStrbufAppendF(error, "NtQueryMultipleValueKey(%p, argc=%u) failed, status=0x%08X", hKey, (unsigned) argc, (unsigned) status); return false; } break; } for (uint32_t i = 0; i < argc; ++i) { const FFRegValueArg* arg = &argv[i]; const KEY_VALUE_ENTRY* entry = &entries[i]; FF_DEBUG("Read value[%u] from %p: type=%u, len=%u", (unsigned) i, hKey, (unsigned) entry->Type, (unsigned) entry->DataLength); if (!processRegValue(arg, entry->Type, buffer + entry->DataOffset, entry->DataLength, error)) return false; } return true; } bool ffRegGetSubKey(HANDLE hKey, uint32_t index, FFstrbuf* result, FFstrbuf* error) { assert(hKey); assert(result); alignas(KEY_BASIC_INFORMATION) uint8_t buffer[sizeof(KEY_BASIC_INFORMATION) + MAX_PATH * sizeof(wchar_t)]; ULONG bufSize = (ULONG) sizeof(buffer); KEY_BASIC_INFORMATION* keyInfo = (KEY_BASIC_INFORMATION*) buffer; if (!NT_SUCCESS(NtEnumerateKey(hKey, index, KeyBasicInformation, keyInfo, bufSize, &bufSize))) { FF_DEBUG("NtEnumerateKey(hKey=%p, index=%u) failed", hKey, (unsigned) index); if (error) ffStrbufAppendF(error, "NtEnumerateKey(hKey=%p, %u, keyInfo) failed", hKey, (unsigned) index); return false; } ffStrbufSetNWS(result, keyInfo->NameLength / sizeof(wchar_t), keyInfo->Name); return true; } bool ffRegGetNSubKeys(HANDLE hKey, uint32_t* result, FFstrbuf* error) { assert(hKey); assert(result); alignas(KEY_FULL_INFORMATION) uint8_t buffer[sizeof(KEY_FULL_INFORMATION) + MAX_PATH * sizeof(wchar_t)]; ULONG bufSize = sizeof(buffer); KEY_FULL_INFORMATION* keyInfo = (KEY_FULL_INFORMATION*) buffer; if (!NT_SUCCESS(NtQueryKey(hKey, KeyFullInformation, keyInfo, bufSize, &bufSize))) { FF_DEBUG("NtQueryKey(hKey=%p, KeyFullInformation) failed", hKey); if (error) ffStrbufAppendF(error, "NtQueryKey(hKey=%p, KeyFullInformation, keyInfo) failed", hKey); return false; } *result = (uint32_t) keyInfo->SubKeys; return true; } ================================================ FILE: src/common/windows/registry.h ================================================ #pragma once #include "fastfetch.h" #include "common/argType.h" #include "common/io.h" #ifndef HKEY_CURRENT_USER #define HKEY_CLASSES_ROOT ((HKEY) (ULONG_PTR)((LONG)0x80000000)) #define HKEY_CURRENT_USER ((HKEY) (ULONG_PTR)((LONG)0x80000001)) #define HKEY_LOCAL_MACHINE ((HKEY) (ULONG_PTR)((LONG)0x80000002)) #define HKEY_USERS ((HKEY) (ULONG_PTR)((LONG)0x80000003)) #define HKEY_PERFORMANCE_DATA ((HKEY) (ULONG_PTR)((LONG)0x80000004)) #define HKEY_CURRENT_CONFIG ((HKEY) (ULONG_PTR)((LONG)0x80000005)) #define HKEY_DYN_DATA ((HKEY) (ULONG_PTR)((LONG)0x80000006)) #define HKEY_CURRENT_USER_LOCAL_SETTINGS ((HKEY) (ULONG_PTR)((LONG)0x80000007)) #endif typedef struct FFRegValueArg { FFArgType type; const void* value; const wchar_t* name; } FFRegValueArg; HANDLE ffRegGetRootKeyHandle(HKEY hKey); bool ffRegOpenSubkeyForRead(HANDLE hKey, const wchar_t* subKeyW, HANDLE* result, FFstrbuf* error); bool ffRegReadValue(HANDLE hKey, const FFRegValueArg* arg, FFstrbuf* error); bool ffRegReadValues(HANDLE hKey, uint32_t argc, const FFRegValueArg argv[], FFstrbuf* error); bool ffRegGetSubKey(HANDLE hKey, uint32_t index, FFstrbuf* result, FFstrbuf* error); bool ffRegGetNSubKeys(HANDLE hKey, uint32_t* result, FFstrbuf* error); static inline bool ffRegOpenKeyForRead(HKEY hRootKey, const wchar_t* subKeyW, HANDLE* result, FFstrbuf* error) { return ffRegOpenSubkeyForRead(ffRegGetRootKeyHandle(hRootKey), subKeyW, result, error); } static inline bool ffRegReadStrbuf(HANDLE hKey, const wchar_t* valueNameW, FFstrbuf* result, FFstrbuf* error) { return ffRegReadValue(hKey, &(FFRegValueArg) { .type = FF_ARG_TYPE_STRBUF, .value = result, .name = valueNameW, }, error); } static inline bool ffRegReadUint(HANDLE hKey, const wchar_t* valueNameW, uint32_t* result, FFstrbuf* error) { return ffRegReadValue(hKey, &(FFRegValueArg) { .type = FF_ARG_TYPE_UINT, .value = result, .name = valueNameW, }, error); } static inline bool ffRegReadUint64(HANDLE hKey, const wchar_t* valueNameW, uint64_t* result, FFstrbuf* error) { return ffRegReadValue(hKey, &(FFRegValueArg) { .type = FF_ARG_TYPE_UINT64, .value = result, .name = valueNameW, }, error); } static inline bool ffRegReadData(HANDLE hKey, const wchar_t* valueNameW, FFlist* result /*list of uint8_t*/, FFstrbuf* error) { return ffRegReadValue(hKey, &(FFRegValueArg) { .type = FF_ARG_TYPE_LIST, .value = result, .name = valueNameW, }, error); } ================================================ FILE: src/common/windows/unicode.c ================================================ #include "unicode.h" #include "common/windows/nt.h" void ffStrbufSetNWS(FFstrbuf* result, uint32_t length, const wchar_t* source) { if(!length) { ffStrbufClear(result); return; } ULONG size_needed = 0; NTSTATUS status = RtlUnicodeToUTF8N(NULL, 0, &size_needed, source, length * sizeof(wchar_t)); if (size_needed == 0) { ffStrbufSetF(result, "RtlUnicodeToUTF8N failed: %X", (unsigned) status); return; } ffStrbufEnsureFixedLengthFree(result, size_needed); RtlUnicodeToUTF8N(result->chars, size_needed, &size_needed, source, length * sizeof(wchar_t)); result->length = size_needed; result->chars[size_needed] = '\0'; } void ffStrbufAppendNWS(FFstrbuf* result, uint32_t length, const wchar_t* source) { if(!length) return; ULONG size_needed = 0; NTSTATUS status = RtlUnicodeToUTF8N(NULL, 0, &size_needed, source, length * sizeof(wchar_t)); if (size_needed == 0) { ffStrbufAppendF(result, "RtlUnicodeToUTF8N failed: %X", (unsigned) status); return; } ffStrbufEnsureFree(result, size_needed); RtlUnicodeToUTF8N(result->chars + result->length, size_needed, &size_needed, source, length * sizeof(wchar_t)); result->length += size_needed; result->chars[result->length] = '\0'; } ================================================ FILE: src/common/windows/unicode.h ================================================ #pragma once #include "common/FFstrbuf.h" #include void ffStrbufSetNWS(FFstrbuf* result, uint32_t length, const wchar_t* source); void ffStrbufAppendNWS(FFstrbuf* result, uint32_t length, const wchar_t* source); static inline void ffStrbufSetWS(FFstrbuf* result, const wchar_t* source) { if (!source) return ffStrbufClear(result); return ffStrbufSetNWS(result, (uint32_t)wcslen(source), source); } static inline void ffStrbufAppendWS(FFstrbuf* result, const wchar_t* source) { if (!source) return; return ffStrbufAppendNWS(result, (uint32_t)wcslen(source), source); } static inline void ffStrbufInitNWS(FFstrbuf* result, uint32_t length, const wchar_t* source) { ffStrbufInit(result); return ffStrbufSetNWS(result, length, source); } static inline void ffStrbufInitWS(FFstrbuf* result, const wchar_t* source) { if (!source) return ffStrbufInit(result); return ffStrbufInitNWS(result, (uint32_t)wcslen(source), source); } static inline FFstrbuf ffStrbufCreateNWS(uint32_t length, const wchar_t* source) { FFstrbuf result; ffStrbufInitNWS(&result, length, source); return result; } static inline FFstrbuf ffStrbufCreateWS(const wchar_t* source) { if (!source) return ffStrbufCreate(); return ffStrbufCreateNWS((uint32_t)wcslen(source), source); } ================================================ FILE: src/common/windows/unicode.hpp ================================================ #pragma once #ifdef __cplusplus extern "C" { #include "unicode.h" } #include static inline void ffStrbufInitWSV(FFstrbuf* result, const std::wstring_view source) { return ffStrbufInitNWS(result, (uint32_t) source.size(), source.data()); } static inline FFstrbuf ffStrbufCreateWSV(const std::wstring_view source) { return ffStrbufCreateNWS((uint32_t) source.size(), source.data()); } static inline void ffStrbufSetWSV(FFstrbuf* result, const std::wstring_view source) { return ffStrbufSetNWS(result, (uint32_t) source.size(), source.data()); } #else #error Must be included in C++ source file #endif ================================================ FILE: src/common/windows/util.hpp ================================================ #pragma once #include #include template struct on_scope_exit { static_assert(std::is_nothrow_move_constructible::value, "Fn must be nothrow move constructible"); explicit on_scope_exit(Fn &&fn) noexcept : _fn(std::move(fn)) {}; on_scope_exit(const on_scope_exit&) = delete; on_scope_exit& operator=(const on_scope_exit&) = delete; ~on_scope_exit() noexcept { this->_fn(); } private: Fn _fn; }; ================================================ FILE: src/common/windows/variant.cpp ================================================ #include "variant.hpp" #include FFWmiVariant::FFWmiVariant(std::initializer_list strings): FFWmiVariant() { SAFEARRAYBOUND bound = { .cElements = (ULONG) strings.size(), .lLbound = 0, }; SAFEARRAY* psa = SafeArrayCreate(VT_BSTR, 1, &bound); LONG i = 0; for (PCWSTR str : strings) { SafeArrayPutElement(psa, &i, bstr_t(str)); ++i; } this->vt = VT_ARRAY | VT_BSTR; this->parray = psa; } ================================================ FILE: src/common/windows/variant.hpp ================================================ #include #include #include #include #include #include #include template struct FFBaseVariant: TVariant { bool hasValue() { return this->vt != VT_EMPTY; } explicit operator bool() { return this->hasValue(); } template T get() { // boolean if constexpr (std::is_same_v) { assert(this->vt == VT_BOOL); return this->boolVal != VARIANT_FALSE; } // signed else if constexpr (std::is_same_v) { assert(this->vt == VT_I1); return this->cVal; } else if constexpr (std::is_same_v) { assert(this->vt == VT_I2); return this->iVal; } else if constexpr (std::is_same_v) { assert(this->vt == VT_I4 || this->vt == VT_INT); return this->intVal; } else if constexpr (std::is_same_v) { assert(this->vt == VT_I8); return this->llVal; } // unsigned else if constexpr (std::is_same_v) { assert(this->vt == VT_UI1); return this->bVal; } else if constexpr (std::is_same_v) { assert(this->vt == VT_UI2); return this->uiVal; } else if constexpr (std::is_same_v) { assert(this->vt == VT_UI4 || this->vt == VT_UINT); return this->uintVal; } else if constexpr (std::is_same_v) { assert(this->vt == VT_UI8); return this->ullVal; } // decimal else if constexpr (std::is_same_v) { assert(this->vt == VT_R4); return this->fltVal; } else if constexpr (std::is_same_v) { assert(this->vt == VT_R8); return this->dblVal; } // string else if constexpr (std::is_same_v) { assert(this->vt == VT_LPSTR); return this->pcVal; } else if constexpr (std::is_same_v) { assert(this->vt == VT_BSTR || this->vt == VT_LPWSTR); if (this->vt == VT_LPWSTR) return this->bstrVal; else return { this->bstrVal, SysStringLen(this->bstrVal) }; } // array signed else if constexpr (std::is_same_v>) { assert(this->vt & VT_ARRAY); assert((this->vt & ~VT_ARRAY) == VT_I1); return std::make_pair((int8_t*)this->parray->pvData, this->parray->cDims); } else if constexpr (std::is_same_v>) { assert(this->vt & VT_ARRAY); assert((this->vt & ~VT_ARRAY) == VT_I2); return std::make_pair((int16_t*)this->parray->pvData, this->parray->cDims); } else if constexpr (std::is_same_v>) { assert(this->vt & VT_ARRAY); assert((this->vt & ~VT_ARRAY) == VT_I4); return std::make_pair((int32_t*)this->parray->pvData, this->parray->cDims); } else if constexpr (std::is_same_v>) { assert(this->vt & VT_ARRAY); assert((this->vt & ~VT_ARRAY) == VT_I8); return std::make_pair((int64_t*)this->parray->pvData, this->parray->cDims); } // array unsigned else if constexpr (std::is_same_v>) { assert(this->vt & VT_ARRAY); assert((this->vt & ~VT_ARRAY) == VT_UI1); return std::make_pair((uint8_t*)this->parray->pvData, this->parray->cDims); } else if constexpr (std::is_same_v>) { assert(this->vt & VT_ARRAY); assert((this->vt & ~VT_ARRAY) == VT_UI2); return std::make_pair((uint16_t*)this->parray->pvData, this->parray->cDims); } else if constexpr (std::is_same_v>) { assert(this->vt & VT_ARRAY); assert((this->vt & ~VT_ARRAY) == VT_UI4); return std::make_pair((uint32_t*)this->parray->pvData, this->parray->cDims); } else if constexpr (std::is_same_v>) { assert(this->vt & VT_ARRAY); assert((this->vt & ~VT_ARRAY) == VT_UI8); return std::make_pair((uint64_t*)this->parray->pvData, this->parray->cDims); } else { assert(false && "unsupported type"); __builtin_unreachable(); } } }; struct FFWmiVariant: FFBaseVariant { FFWmiVariant(const FFWmiVariant&) = delete; FFWmiVariant(FFWmiVariant&&); // don't define it to enforce NRVO optimization explicit FFWmiVariant() { VariantInit(this); } explicit FFWmiVariant(std::initializer_list strings); ~FFWmiVariant() { VariantClear(this); } }; static_assert(sizeof(FFWmiVariant) == sizeof(VARIANT), ""); struct FFPropVariant: FFBaseVariant { FFPropVariant(const FFPropVariant&) = delete; FFPropVariant(FFPropVariant&&); // don't define it to enforce NRVO optimization explicit FFPropVariant() { PropVariantInit(this); } ~FFPropVariant() { PropVariantClear(this); } }; static_assert(sizeof(FFPropVariant) == sizeof(PROPVARIANT), ""); namespace { // Provide our bstr_t to avoid libstdc++ dependency struct bstr_t { explicit bstr_t(const wchar_t* str) noexcept: _bstr(SysAllocString(str)) {} ~bstr_t(void) noexcept { SysFreeString(_bstr); } explicit operator const wchar_t*(void) const noexcept { return _bstr; } operator BSTR(void) const noexcept { return _bstr; } private: BSTR _bstr; }; } ================================================ FILE: src/common/windows/version.c ================================================ #include "common/debug.h" #include "common/mallocHelper.h" #include "common/windows/version.h" #include "common/windows/unicode.h" #include #define FF_VERSION_LANG_EN_US L"040904b0" bool ffGetFileVersion(const wchar_t* filePath, const wchar_t* stringName, FFstrbuf* version) { FF_DEBUG("ffGetFileVersion: enter filePath=%ls stringName=%ls", filePath, stringName); 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)); return false; } FF_DEBUG("GetFileVersionInfoSizeW ok: size=%lu handle=%lu", (unsigned long) size, (unsigned long) handle); FF_AUTO_FREE void* versionData = malloc(size); if (!versionData) { FF_DEBUG("malloc failed: size=%lu", (unsigned long) size); return false; } if (!GetFileVersionInfoW(filePath, handle, size, versionData)) { DWORD err = GetLastError(); FF_DEBUG("GetFileVersionInfoW failed: err=%lu (%s)", (unsigned long) err, ffDebugWin32Error(err)); return false; } FF_DEBUG("GetFileVersionInfoW ok"); if (!stringName) { VS_FIXEDFILEINFO* verInfo; UINT len; if (VerQueryValueW(versionData, L"\\", (void**) &verInfo, &len) && len && verInfo->dwSignature == 0xFEEF04BD) { ffStrbufSetF(version, "%u.%u.%u.%u", (unsigned) ((verInfo->dwProductVersionMS >> 16) & 0xffff), (unsigned) ((verInfo->dwProductVersionMS >> 0) & 0xffff), (unsigned) ((verInfo->dwProductVersionLS >> 16) & 0xffff), (unsigned) ((verInfo->dwProductVersionLS >> 0) & 0xffff)); FF_DEBUG("fixed version resolved: %s", version->chars); return true; } FF_DEBUG("fixed version query failed or invalid signature"); return false; } wchar_t* value; UINT valueLen; wchar_t subBlock[128]; snwprintf(subBlock, ARRAY_SIZE(subBlock), L"\\StringFileInfo\\" FF_VERSION_LANG_EN_US L"\\%ls", stringName); FF_DEBUG("query version string with default lang (en_US): %ls", subBlock); if (VerQueryValueW(versionData, subBlock, (void**) &value, &valueLen) && valueLen > 0) { ffStrbufSetNWS(version, valueLen / sizeof(wchar_t), value); FF_DEBUG("version string resolved (default lang): %s", version->chars); return true; } FF_DEBUG("default lang query failed, trying translation fallback"); struct { WORD language; WORD codePage; }* translations; UINT translationsLen; if (VerQueryValueW(versionData, L"\\VarFileInfo\\Translation", (void**) &translations, &translationsLen) && translationsLen >= sizeof(*translations)) { snwprintf(subBlock, ARRAY_SIZE(subBlock), L"\\StringFileInfo\\%04x%04x\\%ls", translations[0].language, translations[0].codePage, stringName); FF_DEBUG("query version string with translation: %ls", subBlock); if (VerQueryValueW(versionData, subBlock, (void**) &value, &valueLen) && valueLen > 0) { ffStrbufSetNWS(version, valueLen / sizeof(wchar_t), value); FF_DEBUG("version string resolved (translation fallback): %s", version->chars); return true; } FF_DEBUG("translation fallback query failed"); } else { FF_DEBUG("no translation table found in version resource"); } FF_DEBUG("ffGetFileVersion failed"); return false; } ================================================ FILE: src/common/windows/version.h ================================================ #include "fastfetch.h" /** * @brief Retrieves a specific version string for a Windows file. * * This function gets a version string from a Windows file's version information. * * @param filePath The path to the file for which version information is requested. * @param stringName The name of the specific version string to retrieve (e.g., "FileVersion", "ProductVersion"). * @param version Pointer to an FFstrbuf where the version string will be stored. * * @return true if the version string was successfully retrieved, false otherwise. */ bool ffGetFileVersion(const wchar_t* filePath, const wchar_t* stringName, FFstrbuf* version); ================================================ FILE: src/common/windows/version.rc ================================================ #ifdef RC_INVOKED #include #include #include #include "fastfetch_config.h" #define FF_TO_STR1(str) #str #define FF_TO_STR(str) FF_TO_STR1(str) CREATEPROCESS_MANIFEST_RESOURCE_ID RT_MANIFEST "manifest.xml" id ICON "logo.ico" VS_VERSION_INFO VERSIONINFO FILEVERSION FASTFETCH_PROJECT_VERSION_MAJOR,FASTFETCH_PROJECT_VERSION_MINOR,FASTFETCH_PROJECT_VERSION_PATCH,FASTFETCH_PROJECT_VERSION_TWEAK_NUM PRODUCTVERSION FASTFETCH_PROJECT_VERSION_MAJOR,FASTFETCH_PROJECT_VERSION_MINOR,FASTFETCH_PROJECT_VERSION_PATCH,FASTFETCH_PROJECT_VERSION_TWEAK_NUM FILEOS VOS_NT FILETYPE VFT_APP { BLOCK "StringFileInfo" { BLOCK "040904b0" { VALUE "Comments", FASTFETCH_PROJECT_DESCRIPTION VALUE "FileDescription", FF_TO_STR(FASTFETCH_TARGET_BINARY_NAME) VALUE "FileVersion", FASTFETCH_PROJECT_VERSION FASTFETCH_PROJECT_VERSION_TWEAK VALUE "InternalName", FF_TO_STR(FASTFETCH_TARGET_BINARY_NAME) VALUE "LegalCopyright", FASTFETCH_PROJECT_LICENSE VALUE "OriginalFilename", FF_TO_STR(FASTFETCH_TARGET_BINARY_NAME) ".exe" VALUE "ProductName", FASTFETCH_PROJECT_NAME " - " FASTFETCH_PROJECT_DESCRIPTION VALUE "ProductVersion", FASTFETCH_PROJECT_VERSION FASTFETCH_PROJECT_VERSION_TWEAK VALUE "CompanyName", FASTFETCH_PROJECT_HOMEPAGE_URL } } BLOCK "VarFileInfo" { VALUE "Translation", 0x0409, 1200 } } #endif ================================================ FILE: src/common/windows/wmi.cpp ================================================ #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; } ================================================ FILE: src/common/windows/wmi.hpp ================================================ #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 ================================================ FILE: src/data/help.json ================================================ { "Informative": [ { "short": "h", "long": "help", "desc": "Display this help message or help for a specific command", "arg": { "type": "command", "optional": true } }, { "short": "v", "long": "version", "desc": "Show the full version of fastfetch" }, { "long": "version-raw", "desc": "Display the raw version string (major.minor.patch)" }, { "long": "list-config-paths", "desc": "List search paths for config files" }, { "long": "list-data-paths", "desc": "List search paths for presets and logos" }, { "long": "list-logos", "desc": "List available logos" }, { "long": "list-modules", "desc": "List available modules" }, { "long": "list-presets", "desc": "List presets that fastfetch knows about", "remark": "Presets can be loaded with \"--config \"" }, { "long": "list-features", "desc": "List the supported features that fastfetch was compiled with", "remark": "Mainly for development" }, { "long": "print-logos", "desc": "Display available logos" }, { "long": "print-structure", "desc": "Display the default structure" }, { "long": "format", "desc": "Set output format", "arg": { "type": "enum", "enum": { "default": "Default format", "json": "JSON format" }, "default": "default" } }, { "long": "json", "short": "j", "arg": { "type": "bool", "default": false, "optional": true }, "desc": "Enable or disable JSON output", "remark": "Shortcut for `--format json`" }, { "long": "dynamic-interval", "desc": "Keep fastfetch open and update the output every milliseconds", "remark": "0 (default) to disable the behavior; don't work with --json", "arg": { "type": "num", "default": 0 } } ], "Config": [ { "short": "c", "long": "config", "desc": "Specify the config file or preset to load", "remark": "The file will be searched according to the order shown by \"fastfetch --list-config-paths\". Use \"-\" to read config from stdin or \"none\" to disable further config loading. See also https://github.com/fastfetch-cli/fastfetch/wiki/Configuration for more info", "arg": { "type": "config" } }, { "long": "gen-config", "desc": "Generate a minimal config file at the specified path", "remark": "Defaults to \"~/.config/fastfetch/config.jsonc\". Will print the generated config if is \"-\"", "arg": { "type": "path", "optional": true } }, { "long": "gen-config-full", "desc": "Generate a full config file with all optional options at the specified path", "remark": "Defaults to \"~/.config/fastfetch/config.jsonc\". Will print the generated config if is \"-\"", "arg": { "type": "path", "optional": true } }, { "long": "gen-config-force", "desc": "Generate a config file at the specified path, overwriting any existing file", "remark": "Defaults to \"~/.config/fastfetch/config.jsonc\"", "arg": { "type": "path", "optional": true } } ], "General": [ { "long": "thread", "desc": "Use separate threads for HTTP requests", "arg": { "type": "bool", "optional": true, "default": true } }, { "long": "wmi-timeout", "desc": "Set the timeout (ms) for WMI queries", "remark": "Windows only", "arg": { "type": "num", "default": 5000 } }, { "long": "processing-timeout", "desc": "Set the timeout (ms) when waiting for child processes", "arg": { "type": "num", "default": 5000 } }, { "long": "ds-force-drm", "desc": "Specify whether only DRM should be used to detect displays", "remark": [ "Use this option if you encounter problems with other detection methods.", "Linux only" ], "arg": { "type": "enum", "optional": true, "default": "false", "enum": { "true": "Try `libdrm` first, then `sysfs` if libdrm fails", "sysfs-only": "Use `/sys/class/drm` only", "false": "Try `wayland`, then `x11`, then `drm`" } } }, { "long": "detect-version", "desc": "Specify whether to detect and display versions of terminal, shell, editor, and others", "remark": "Mainly for benchmarking", "arg": { "type": "bool", "optional": true, "default": true } } ], "Logo": [ { "short": "l", "long": "logo", "desc": "Set the logo source. Use \"none\" to disable the logo", "remark": "Should be the name of a built-in logo or a path to an image file. See also https://github.com/fastfetch-cli/fastfetch/wiki/Logo-options", "arg": { "type": "logo" } }, { "long": "logo-type", "desc": "Set the type of the logo specified in \"--logo\"", "remark": "See also https://github.com/fastfetch-cli/fastfetch/wiki/Logo-options", "arg": { "type": "enum", "enum": { "auto": "If something is given, first try built-in, then file. Otherwise detect logo", "builtin": "Built-in ASCII art", "small": "Built-in ASCII art, small version", "file": "Text file, printed with color code replacement", "file-raw": "Text file, printed as is", "data": "Text data, printed with color code replacement", "data-raw": "Text data, printed as is", "sixel": "Image file, printed as sixel codes", "kitty": "Image file, printed using kitty graphics protocol", "kitty-direct": "Image file, tells the terminal emulator to read image data from the specified file", "kitty-icat": "Image file, uses `kitten icat` to display the image. Requires binary `kitten` to be installed", "iterm": "Image file, printed using iterm graphics protocol", "chafa": "Image file, printed as ASCII art using libchafa", "raw": "Image file, printed as raw binary string", "none": "Disable logo printing" } } }, { "long": "logo-width", "desc": "Set the width of the logo (in characters) if it is an image", "remark": "Required for iTerm image protocol", "arg": { "type": "num" } }, { "long": "logo-height", "desc": "Set the height of the logo (in characters) if it is an image", "remark": "Required for iTerm image protocol", "arg": { "type": "num" } }, { "long": "logo-preserve-aspect-ratio", "desc": "Specify whether the logo should fill the specified width and height as much as possible without stretching", "remark": "Supported by iTerm image protocol only", "arg": { "type": "bool", "optional": true, "default": false } }, { "long": "logo-color-[1-9]", "desc": "Override a color in the logo", "remark": "See `-h color` for the list of available colors", "arg": { "type": "color" }, "pseudo": true }, { "long": "logo-padding", "desc": "Set the padding on the left and right sides of the logo", "arg": { "type": "num" } }, { "long": "logo-padding-left", "desc": "Set the padding on the left side of the logo", "arg": { "type": "num" } }, { "long": "logo-padding-right", "desc": "Set the padding on the right side of the logo", "arg": { "type": "num" } }, { "long": "logo-padding-top", "desc": "Set the padding at the top of the logo", "arg": { "type": "num" } }, { "long": "logo-print-remaining", "desc": "Specify whether to print the remaining logo if it has more lines than modules to display", "arg": { "type": "bool", "optional": true, "default": true } }, { "long": "logo-position", "desc": "Set the position where the logo should be displayed", "arg": { "type": "enum", "enum": { "left": "Left", "top": "Top", "right": "Right" } } }, { "long": "logo-recache", "desc": "If true, regenerate the image logo cache", "arg": { "type": "bool", "optional": true, "default": false } }, { "long": "file", "desc": "Short for --logo-type file --logo ", "remark": "See \"--help logo-type\" for more info", "arg": { "type": "path" } }, { "long": "file-raw", "desc": "Short for --logo-type file-raw --logo ", "remark": "See \"--help logo-type\" for more info", "arg": { "type": "path" } }, { "long": "data", "desc": "Short for --logo-type data --logo ", "remark": "See \"--help logo-type\" for more info", "arg": { "type": "data" } }, { "long": "data-raw", "desc": "Short for --logo-type data-raw --logo ", "remark": "See \"--help logo-type\" for more info", "arg": { "type": "data" } }, { "long": "raw", "desc": "Short for --logo-type raw --logo ", "remark": "See \"--help logo-type\" for more info", "arg": { "type": "path" } }, { "long": "sixel", "desc": "Short for --logo-type sixel --logo ", "remark": "See \"--help logo-type\" for more info", "arg": { "type": "path" } }, { "long": "kitty", "desc": "Short for --logo-type kitty --logo ", "remark": "See \"--help logo-type\" for more info", "arg": { "type": "path" } }, { "long": "kitty-direct", "desc": "Short for --logo-type kitty-direct --logo ", "remark": "See \"--help logo-type\" for more info", "arg": { "type": "path" } }, { "long": "kitty-icat", "desc": "Short for --logo-type kitty-icat --logo ", "remark": "See \"--help logo-type\" for more info", "arg": { "type": "path" } }, { "long": "iterm", "desc": "Short for --logo-type iterm --logo ", "remark": "See \"--help logo-type\" for more info", "arg": { "type": "path" } }, { "long": "chafa", "desc": "Short for --logo-type chafa --logo ", "remark": "See \"--help logo-type\" for more info", "arg": { "type": "path" } }, { "long": "chafa-fg-only", "desc": "Produce character-cell output using foreground colors only", "remark": "See chafa document for detail", "arg": { "type": "bool", "optional": true, "default": false } }, { "long": "chafa-symbols", "desc": "Specify character symbols to employ in final output", "remark": "See chafa document for detail", "arg": { "type": "str" } }, { "long": "chafa-canvas-mode", "desc": "Determine how colors are used in the output", "remark": "This value maps the int value of enum ChafaCanvasMode. See chafa document for detail", "arg": { "type": "enum", "enum": { "TRUECOLOR": "Truecolor", "INDEXED_256": "256 colors", "INDEXED_240": "256 colors, but avoid using the lower 16 whose values vary between terminal environments", "INDEXED_16": "16 colors using the aixterm ANSI extension", "FGBG_BGFG": "Default foreground and background colors, plus inversion", "FGBG": "Default foreground and background colors. No ANSI codes will be used", "INDEXED_8": "8 colors, compatible with original ANSI X3.64", "INDEXED_16_8": "16 FG colors (8 of which enabled with bold/bright) and 8 BG colors" } } }, { "long": "chafa-color-space", "desc": "Set color space used for quantization", "remark": "This value maps the int value of enum ChafaColorSpace. See chafa document for detail", "arg": { "type": "enum", "enum": { "RGB": "RGB color space. Fast but imprecise", "DIN99D": "DIN99d color space. Slower, but good perceptual color precision" } } }, { "long": "chafa-dither-mode", "desc": "Set output dither mode (No effect with 24-bit color)", "remark": "This value maps the int value of enum ChafaDitherMode. See chafa document for detail", "arg": { "type": "enum", "enum": { "NONE": "No dithering", "ORDERED": "Ordered dithering (Bayer or similar)", "DIFFUSION": "Error diffusion dithering (Floyd-Steinberg or similar)" } } } ], "Display": [ { "short": "s", "long": "structure", "desc": "Set the structure of the fetch", "remark": "Must be a colon-separated list of keys. Use \"fastfetch --list-modules\" to see available options", "arg": { "type": "structure", "default": "\"fastfetch --print-structure\"" } }, { "long": "structure-disabled", "desc": "Disable specific modules in the structure", "remark": "Must be a colon-separated list of keys", "arg": { "type": "structure" } }, { "long": "stat", "desc": "Show time usage (in ms) for individual modules", "arg": { "type": "bool", "optional": true, "default": false } }, { "long": "pipe", "desc": "Disable colors", "remark": "Auto-detected based on isatty(1) by default", "arg": { "type": "bool", "optional": true, "default": false } }, { "long": "color", "desc": "Set the color of both keys and title", "remark": [ "Shortcut for \"--color-keys \" and \"--color-title \"", "For color syntax, see " ], "arg": { "type": "color" } }, { "long": "color-keys", "desc": "Set the color of the keys", "remark": "Doesn't affect Title, Separator, and Colors modules. See `-h color` for the list of available colors", "arg": { "type": "color" } }, { "long": "color-title", "desc": "Set the color of the title", "remark": "See `-h color` for the list of available colors", "arg": { "type": "color" } }, { "long": "color-output", "desc": "Set the color of module output", "remark": "See `-h color` for the list of available colors", "arg": { "type": "color" } }, { "long": "color-separator", "desc": "Set the color of the key-value separator", "remark": "See `-h color` for the list of available colors", "arg": { "type": "color" } }, { "long": "duration-abbreviation", "desc": "Specify whether to abbreviate duration values", "remark": "If true, the output will be in the form of \"1h 2m\" instead of \"1 hour, 2 mins\"", "arg": { "type": "bool", "optional": true, "default": false } }, { "long": "duration-space-before-unit", "desc": "Specify whether to put a space before the unit in duration values", "arg": { "type": "enum", "enum": { "default": "Use the default behavior of the module", "always": "Always put a space before the unit", "never": "Never put a space before the unit" } } }, { "long": "key-width", "desc": "Align the width of keys to characters", "arg": { "type": "num" } }, { "long": "key-padding-left", "desc": "Set the left padding of keys to characters", "arg": { "type": "num" } }, { "long": "key-type", "desc": "Specify whether to show an icon before string keys", "arg": { "type": "enum", "enum": { "none": "Disable keys", "string": "Show string", "icon": "Show icon (requires newest nerd font)", "both": "Show both icon and string (alias of `both-1`)", "both-0": "Show both icon and string with no spaces between them", "both-1": "Show both icon and string with a space between them", "both-2": "Show both icon and string with 2 spaces between them", "both-3": "Show both icon and string with 3 spaces between them", "both-4": "Show both icon and string with 4 spaces between them" }, "default": "string" } }, { "long": "bright-color", "desc": "Specify whether keys, title, and ASCII logo should be printed in bright color", "arg": { "type": "bool", "optional": true, "default": true } }, { "long": "separator", "desc": "Set the separator between key and value", "arg": { "type": "str", "default": ": " } }, { "long": "show-errors", "desc": "Print errors when they occur", "arg": { "type": "bool", "optional": true, "default": false } }, { "long": "disable-linewrap", "desc": "Specify whether to disable line wrap during execution", "arg": { "type": "bool", "optional": true, "default": true } }, { "long": "hide-cursor", "desc": "Specify whether to hide the cursor during execution", "arg": { "type": "bool", "optional": true, "default": false } }, { "long": "percent-type", "desc": "Set the percentage output type", "remark": [ "1 for percentage number", "2 for multi-color bar", "3 for both", "6 for bar only", "9 for colored number", "10 for monochrome bar" ], "arg": { "type": "num", "default": 9 } }, { "long": "percent-ndigits", "desc": "Set the number of digits to keep after the decimal point when formatting percentage numbers", "arg": { "type": "num", "default": 0 } }, { "long": "percent-color-green", "desc": "Set color used for the green state of percentage bars and numbers", "remark": "See `-h color` for the list of available colors", "arg": { "type": "color", "default": "green" } }, { "long": "percent-color-yellow", "desc": "Set color used for the yellow state of percentage bars and numbers", "remark": "See `-h color` for the list of available colors", "arg": { "type": "color", "default": "light_yellow" } }, { "long": "percent-color-red", "desc": "Set color used for the red state of percentage bars and numbers", "remark": "See `-h color` for the list of available colors", "arg": { "type": "color", "default": "light_red" } }, { "long": "percent-space-before-unit", "desc": "Specify whether to put a space before the percentage symbol", "arg": { "type": "enum", "enum": { "default": "Use the default behavior of the module", "always": "Always put a space before the unit", "never": "Never put a space before the unit" } } }, { "long": "percent-width", "desc": "Specify the width of the percentage number, in number of characters", "remark": "This option affects only percentage numbers, not bars", "arg": { "type": "num", "default": 0 } }, { "long": "bar-char-elapsed", "desc": "Set the character to use in the elapsed part of percentage bars", "arg": { "type": "str", "default": "\u25a0" } }, { "long": "bar-char-total", "desc": "Set the character to use in the total part of percentage bars", "arg": { "type": "str", "default": "-" } }, { "long": "bar-border-left", "desc": "Set the string to use at the left border of percentage bars", "arg": { "type": "string", "default": "[ " } }, { "long": "bar-border-right", "desc": "Set the string to use at the right border of percentage bars", "arg": { "type": "string", "default": " ]" } }, { "long": "bar-border-left-elapsed", "desc": "If both bar-border-left-elapsed and bar-border-right-elapsed are set, the border will be used as parts of bar content", "arg": { "type": "string", "default": "" } }, { "long": "bar-border-right-elapsed", "desc": "If both bar-border-left-elapsed and bar-border-right-elapsed are set, the border will be used as parts of bar content", "arg": { "type": "string", "default": "" } }, { "long": "bar-color-elapsed", "desc": "Set the color to use in the elapsed part of percentage bars", "remark": "By default, auto selected by percent-color-{green,yellow,red}", "arg": { "type": "color", "default": "" } }, { "long": "bar-color-total", "desc": "Set the color to use in the total part of percentage bars", "arg": { "type": "color", "default": "light_white" } }, { "long": "bar-color-border", "desc": "Set the color to use in the borders of percentage bars", "arg": { "type": "color", "default": "light_white" } }, { "long": "bar-width", "desc": "Set the width of percentage bars in characters", "arg": { "type": "num", "default": 10 } }, { "long": "no-buffer", "desc": "Specify whether the stdout application buffer should be disabled", "arg": { "type": "bool", "optional": true, "default": false } }, { "long": "size-ndigits", "desc": "Set the number of digits to keep after the decimal point when formatting sizes", "arg": { "type": "num" } }, { "long": "size-binary-prefix", "desc": "Set the binary prefix to use when formatting sizes", "arg": { "type": "enum", "enum": { "IEC": "1024 Bytes = 1 KiB, 1024 KiB = 1 MiB, ...", "SI": "1000 Bytes = 1 kB, 1000 kB = 1 MB, ...", "JEDEC": "1024 Bytes = 1 KB, 1024 KB = 1 MB, ..." }, "default": "IEC" } }, { "long": "size-max-prefix", "desc": "Set the largest binary prefix to use when formatting sizes", "arg": { "type": "enum", "enum": { "B": "Bytes", "kB": "KiB", "MB": "MiB", "GB": "GiB", "TB": "TiB", "PB": "PiB", "EB": "EiB", "ZB": "ZiB", "YB": "YiB" }, "default": "YB" } }, { "long": "size-space-before-unit", "desc": "Specify whether to put a space before the unit", "arg": { "type": "enum", "enum": { "default": "Use the default behavior of the module", "always": "Always put a space before the unit", "never": "Never put a space before the unit" } } }, { "long": "freq-ndigits", "desc": "Set the number of digits to keep after the decimal point when printing CPU/GPU frequency in GHz", "arg": { "type": "num", "default": 2 } }, { "long": "freq-space-before-unit", "desc": "Specify whether to put a space before the unit", "arg": { "type": "enum", "enum": { "default": "Use the default behavior of the module", "always": "Always put a space before the unit", "never": "Never put a space before the unit" } } }, { "long": "fraction-ndigits", "desc": "Set the number of digits to keep after the decimal point when printing ordinary fraction numbers", "remark": "If negative, the number of digits will be automatically determined based on the value", "arg": { "type": "num", "default": -1 } }, { "long": "fraction-trailing-zeros", "desc": "Set when to keep trailing zeros", "arg": { "type": "enum", "enum": { "default": "Use the behavior defined internally", "always": "Always keep trailing zeros", "never": "Never keep trailing zeros" } } }, { "long": "temp-unit", "desc": "Set the temperature unit", "arg": { "type": "enum", "enum": { "D": "Default", "C": "Celsius", "F": "Fahrenheit", "K": "Kelvin" }, "default": "D" } }, { "long": "temp-ndigits", "desc": "Set the number of digits to keep after the decimal point when printing temperature", "arg": { "type": "num", "default": 2 } }, { "long": "temp-color-green", "desc": "Set color used for the green state of temperature values", "remark": "See `-h color` for the list of available colors", "arg": { "type": "color", "default": "green" } }, { "long": "temp-color-yellow", "desc": "Set color used for the yellow state of temperature values", "remark": "See `-h color` for the list of available colors", "arg": { "type": "color", "default": "light_yellow" } }, { "long": "temp-color-red", "desc": "Set color used for the red state of temperature values", "remark": "See `-h color` for the list of available colors", "arg": { "type": "color", "default": "light_red" } }, { "long": "temp-space-before-unit", "desc": "Specify whether to put a space before the unit", "arg": { "type": "enum", "enum": { "default": "Use the default behavior of the module", "always": "Always put a space before the unit", "never": "Never put a space before the unit" } } } ] } ================================================ FILE: src/data/structure.txt ================================================ Title:Separator:OS:Host:Kernel:Uptime:Packages:Shell:Display:DE:WM:WMTheme:Theme:Icons:Font:Cursor:Terminal:TerminalFont:CPU:GPU:Memory:Swap:Disk:LocalIp:Battery:PowerAdapter:Locale:Break:Colors ================================================ FILE: src/detection/battery/battery.h ================================================ #pragma once #include "fastfetch.h" #include "modules/battery/option.h" #define FF_BATTERY_TEMP_UNSET (-DBL_MAX) typedef struct FFBatteryResult { FFstrbuf manufacturer; FFstrbuf manufactureDate; FFstrbuf modelName; FFstrbuf technology; FFstrbuf status; FFstrbuf serial; double capacity; double temperature; uint32_t cycleCount; int32_t timeRemaining; // in seconds, -1 if unknown } FFBatteryResult; const char* ffDetectBattery(FFBatteryOptions* options, FFlist* results); ================================================ FILE: src/detection/battery/battery_android.c ================================================ #include "fastfetch.h" #include "battery.h" #include "common/stringUtils.h" #include "common/processing.h" #include "common/properties.h" #define FF_TERMUX_API_PATH FASTFETCH_TARGET_DIR_ROOT "/libexec/termux-api" #define FF_TERMUX_API_PARAM "BatteryStatus" static inline void wrapYyjsonFree(yyjson_doc** doc) { assert(doc); if (*doc) yyjson_doc_free(*doc); } static const char* parseTermuxApi(FFBatteryOptions* options, FFlist* results) { FF_STRBUF_AUTO_DESTROY buffer = ffStrbufCreate(); if(ffProcessAppendStdOut(&buffer, (char* const[]){ FF_TERMUX_API_PATH, FF_TERMUX_API_PARAM, NULL })) return "Starting `" FF_TERMUX_API_PATH " " FF_TERMUX_API_PARAM "` failed"; yyjson_doc* __attribute__((__cleanup__(wrapYyjsonFree))) doc = yyjson_read_opts(buffer.chars, buffer.length, 0, NULL, NULL); if (!doc) return "Failed to parse battery info"; yyjson_val* root = yyjson_doc_get_root(doc); if (!yyjson_is_obj(root)) return "Battery info result is not a JSON object"; FFBatteryResult* battery = ffListAdd(results); battery->temperature = FF_BATTERY_TEMP_UNSET; battery->cycleCount = 0; battery->timeRemaining = -1; ffStrbufInit(&battery->manufacturer); ffStrbufInit(&battery->modelName); ffStrbufInit(&battery->status); ffStrbufInit(&battery->technology); ffStrbufInit(&battery->serial); ffStrbufInit(&battery->manufactureDate); battery->capacity = yyjson_get_num(yyjson_obj_get(root, "percentage")); const char* acStatus = yyjson_get_str(yyjson_obj_get(root, "plugged")); if (acStatus) { if (ffStrEquals(acStatus, "PLUGGED_AC")) ffStrbufAppendS(&battery->status, "AC Connected, "); else if (ffStrEquals(acStatus, "PLUGGED_USB")) ffStrbufAppendS(&battery->status, "USB Connected, "); else if (ffStrEquals(acStatus, "PLUGGED_WIRELESS")) ffStrbufAppendS(&battery->status, "Wireless Connected, "); } const char* status = yyjson_get_str(yyjson_obj_get(root, "status")); if (status) { if (ffStrEquals(status, "CHARGING")) ffStrbufAppendS(&battery->status, "Charging"); else if (ffStrEquals(status, "DISCHARGING")) ffStrbufAppendS(&battery->status, "Discharging"); } ffStrbufTrimRight(&battery->status, ' '); ffStrbufTrimRight(&battery->status, ','); if(options->temp) battery->temperature = yyjson_get_num(yyjson_obj_get(root, "temperature")); return NULL; } static const char* parseDumpsys(FFBatteryOptions* options, FFlist* results) { FF_STRBUF_AUTO_DESTROY buf = ffStrbufCreate(); if (ffProcessAppendStdOut(&buf, (char* []) { "/system/bin/dumpsys", "battery", NULL, }) != NULL || buf.length == 0) return "Executing `/system/bin/dumpsys battery` failed"; // Only works in `adb shell`, or when rooted if (!ffStrbufStartsWithS(&buf, "Current Battery Service state:\n")) return "Invalid `/system/bin/dumpsys battery` result"; const char* start = buf.chars + strlen("Current Battery Service state:\n"); FF_STRBUF_AUTO_DESTROY temp = ffStrbufCreate(); if (!ffParsePropLines(start, "present: ", &temp) || !ffStrbufEqualS(&temp, "true")) return NULL; ffStrbufClear(&temp); FFBatteryResult* battery = ffListAdd(results); battery->temperature = FF_BATTERY_TEMP_UNSET; battery->cycleCount = 0; battery->timeRemaining = -1; battery->capacity = 0; ffStrbufInit(&battery->manufacturer); ffStrbufInit(&battery->modelName); ffStrbufInit(&battery->status); ffStrbufInit(&battery->technology); ffStrbufInit(&battery->serial); ffStrbufInit(&battery->manufactureDate); if (ffParsePropLines(start, "AC powered: ", &temp) && ffStrbufEqualS(&temp, "true")) ffStrbufAppendS(&battery->status, "AC powered"); ffStrbufClear(&temp); if (ffParsePropLines(start, "USB powered: ", &temp) && ffStrbufEqualS(&temp, "true")) { if (battery->status.length) ffStrbufAppendS(&battery->status, ", "); ffStrbufAppendS(&battery->status, "USB powered"); } ffStrbufClear(&temp); if (ffParsePropLines(start, "Wireless powered: ", &temp) && ffStrbufEqualS(&temp, "true")) { if (battery->status.length) ffStrbufAppendS(&battery->status, ", "); ffStrbufAppendS(&battery->status, "Wireless powered"); } ffStrbufClear(&temp); { double level = 0, scale = 0; if (ffParsePropLines(start, "level: ", &temp)) level = ffStrbufToDouble(&temp, -DBL_MAX); ffStrbufClear(&temp); if (ffParsePropLines(start, "scale: ", &temp)) scale = ffStrbufToDouble(&temp, -DBL_MAX); ffStrbufClear(&temp); if (level > 0 && scale > 0) battery->capacity = level * 100 / scale; } if(options->temp) { if (ffParsePropLines(start, "temperature: ", &temp)) { battery->temperature = ffStrbufToDouble(&temp, FF_BATTERY_TEMP_UNSET); if (battery->temperature != FF_BATTERY_TEMP_UNSET) battery->temperature /= 10.0; // Android returns temperature in tenths of a degree } ffStrbufClear(&temp); } ffParsePropLines(start, "technology: ", &battery->technology); return NULL; } const char* ffDetectBattery(FFBatteryOptions* options, FFlist* results) { const char* error = parseTermuxApi(options, results); if (error && parseDumpsys(options, results) == NULL) return NULL; return error; } ================================================ FILE: src/detection/battery/battery_apple.c ================================================ #include "fastfetch.h" #include "battery.h" #include "common/apple/cf_helpers.h" #include "common/apple/smc_temps.h" #include #include const char* ffDetectBattery(FFBatteryOptions* options, FFlist* results) { FF_IOOBJECT_AUTO_RELEASE io_iterator_t iterator = IO_OBJECT_NULL; if (IOServiceGetMatchingServices(MACH_PORT_NULL, IOServiceMatching("AppleSmartBattery"), &iterator) != kIOReturnSuccess) return "IOServiceGetMatchingServices() failed"; io_registry_entry_t registryEntry; while ((registryEntry = IOIteratorNext(iterator)) != IO_OBJECT_NULL) { FF_IOOBJECT_AUTO_RELEASE io_registry_entry_t entryBattery = registryEntry; FF_CFTYPE_AUTO_RELEASE CFMutableDictionaryRef properties = NULL; if (IORegistryEntryCreateCFProperties(entryBattery, &properties, kCFAllocatorDefault, kNilOptions) != kIOReturnSuccess) continue; int currentCapacity, maxCapacity; if (ffCfDictGetInt(properties, CFSTR(kIOPMPSMaxCapacityKey), &maxCapacity) != NULL || maxCapacity <= 0) continue; if (ffCfDictGetInt(properties, CFSTR(kIOPMPSCurrentCapacityKey), ¤tCapacity) != NULL || currentCapacity <= 0) continue; bool boolValue; FFBatteryResult* battery = ffListAdd(results); battery->temperature = FF_BATTERY_TEMP_UNSET; ffStrbufInit(&battery->manufacturer); ffStrbufInit(&battery->modelName); ffStrbufInit(&battery->serial); ffStrbufInit(&battery->technology); ffStrbufInit(&battery->status); ffStrbufInit(&battery->manufactureDate); battery->capacity = currentCapacity * 100.0 / maxCapacity; ffCfDictGetString(properties, CFSTR(kIOPMDeviceNameKey), &battery->modelName); ffCfDictGetString(properties, CFSTR(kIOPMPSSerialKey), &battery->serial); ffCfDictGetString(properties, CFSTR(kIOPMPSManufacturerKey), &battery->manufacturer); if (!ffCfDictGetBool(properties, CFSTR("built-in"), &boolValue) && boolValue) { if (!battery->manufacturer.length) ffStrbufAppendS(&battery->manufacturer, "Apple Inc."); ffStrbufAppendS(&battery->technology, "Lithium"); if (!battery->modelName.length) ffStrbufAppendS(&battery->modelName, "Built-in"); } int32_t cycleCount = 0; ffCfDictGetInt(properties, CFSTR(kIOPMPSCycleCountKey), &cycleCount); battery->cycleCount = cycleCount < 0 ? 0 : (uint32_t) cycleCount; battery->timeRemaining = -1; if (ffCfDictGetBool(properties, CFSTR(kIOPMPSExternalConnectedKey), &boolValue) == NULL) { if (boolValue) ffStrbufAppendS(&battery->status, "AC connected, "); else { ffStrbufAppendS(&battery->status, "Discharging, "); ffCfDictGetInt(properties, CFSTR("AvgTimeToEmpty"), &battery->timeRemaining); // in minutes if (battery->timeRemaining < 0 || battery->timeRemaining >= 0xFFFF) battery->timeRemaining = -1; else battery->timeRemaining *= 60; } } if (ffCfDictGetBool(properties, CFSTR(kIOPMPSIsChargingKey), &boolValue) == NULL && boolValue) ffStrbufAppendS(&battery->status, "Charging, "); if (ffCfDictGetBool(properties, CFSTR(kIOPMPSAtCriticalLevelKey), &boolValue) == NULL && boolValue) ffStrbufAppendS(&battery->status, "Critical, "); ffStrbufTrimRight(&battery->status, ' '); ffStrbufTrimRight(&battery->status, ','); int sbdsManufactureDate = 0; if (ffCfDictGetInt(properties, CFSTR(kIOPMPSManufactureDateKey), &sbdsManufactureDate) == NULL) { int day = sbdsManufactureDate & 0b11111; int month = (sbdsManufactureDate >> 5) & 0b1111; int year = (sbdsManufactureDate >> 9) + 1800; ffStrbufSetF(&battery->manufactureDate, "%.4d-%.2d-%.2d", year, month, day); } else { CFDictionaryRef batteryData; if (ffCfDictGetDict(properties, CFSTR("BatteryData"), &batteryData) == NULL) { char manufactureDate[sizeof(uint64_t)]; if (ffCfDictGetInt64(batteryData, CFSTR(kIOPMPSManufactureDateKey), (int64_t*) manufactureDate) == NULL) { // https://github.com/AsahiLinux/linux/blob/b5c05cbffb0488c7618106926d522cc3b43d93d5/drivers/power/supply/macsmc_power.c#L410-L419 int year = (manufactureDate[0] - '0') * 10 + (manufactureDate[1] - '0') + 2000 - 8; int month = (manufactureDate[2] - '0') * 10 + (manufactureDate[3] - '0'); int day = (manufactureDate[4] - '0') * 10 + (manufactureDate[3] - '0'); ffStrbufSetF(&battery->manufactureDate, "%.4d-%.2d-%.2d", year, month, day); } } } if (options->temp) { int64_t temp; if (!ffCfDictGetInt64(properties, CFSTR(kIOPMPSBatteryTemperatureKey), &temp)) battery->temperature = (double) temp / 10 - 273.15; else ffDetectSmcTemps(FF_TEMP_BATTERY, &battery->temperature); } } return NULL; } ================================================ FILE: src/detection/battery/battery_bsd.c ================================================ #include "fastfetch.h" #include "common/sysctl.h" #include "common/io.h" #include "battery.h" #include #include #include #include const char* ffDetectBattery(FF_MAYBE_UNUSED FFBatteryOptions* options, FFlist* results) { //https://www.freebsd.org/cgi/man.cgi?acpi_battery(4) //https://gitlab.xfce.org/panel-plugins/xfce4-battery-plugin/-/blob/master/panel-plugin/libacpi.c int units = ffSysctlGetInt("hw.acpi.battery.units", -100); if (units < 0) return "sysctlbyname(\"hw.acpi.battery.units\") failed"; if(units == 0) return NULL; FF_AUTO_CLOSE_FD int acpifd = open("/dev/acpi", O_RDONLY | O_CLOEXEC); if(acpifd < 0) return "open(\"/dev/acpi\", O_RDONLY | O_CLOEXEC) failed"; for(int i = 0; i < units; ++i) { union acpi_battery_ioctl_arg battio; battio.unit = i; if(ioctl(acpifd, ACPIIO_BATT_GET_BATTINFO, &battio) < 0 || (battio.battinfo.state == ACPI_BATT_STAT_NOT_PRESENT)) continue; FFBatteryResult* battery = ffListAdd(results); battery->temperature = FF_BATTERY_TEMP_UNSET; battery->cycleCount = 0; ffStrbufInit(&battery->manufacturer); ffStrbufInit(&battery->modelName); ffStrbufInit(&battery->status); ffStrbufInit(&battery->technology); ffStrbufInit(&battery->serial); ffStrbufInit(&battery->manufactureDate); battery->timeRemaining = -1; if (battio.battinfo.min > 0) battery->timeRemaining = battio.battinfo.min * 60; battery->capacity = battio.battinfo.cap; if(battio.battinfo.state == ACPI_BATT_STAT_INVALID) { ffStrbufAppendS(&battery->status, "Unknown, "); } else { if(battio.battinfo.state & ACPI_BATT_STAT_DISCHARG) ffStrbufAppendS(&battery->status, "Discharging, "); else if(battio.battinfo.state & ACPI_BATT_STAT_CHARGING) ffStrbufAppendS(&battery->status, "Charging, "); if(battio.battinfo.state & ACPI_BATT_STAT_CRITICAL) ffStrbufAppendS(&battery->status, "Critical, "); } int acadStatus; if (ioctl(acpifd, ACPIIO_ACAD_GET_STATUS, &acadStatus) >= 0 && acadStatus) { ffStrbufAppendS(&battery->status, "AC Connected"); } else { ffStrbufTrimRight(&battery->status, ' '); ffStrbufTrimRight(&battery->status, ','); } #ifdef ACPIIO_BATT_GET_BIX battio.unit = i; if (ioctl(acpifd, ACPIIO_BATT_GET_BIX, &battio) >= 0) { ffStrbufAppendS(&battery->manufacturer, battio.bix.oeminfo); ffStrbufAppendS(&battery->modelName, battio.bix.model); ffStrbufAppendS(&battery->technology, battio.bix.type); ffStrbufAppendS(&battery->serial, battio.bix.serial); battery->cycleCount = battio.bix.cycles; } #elif defined(ACPIIO_BATT_GET_BIF) battio.unit = i; if (ioctl(acpifd, ACPIIO_BATT_GET_BIF, &battio) >= 0) { ffStrbufAppendS(&battery->manufacturer, battio.bif.oeminfo); ffStrbufAppendS(&battery->modelName, battio.bif.model); ffStrbufAppendS(&battery->technology, battio.bif.type); ffStrbufAppendS(&battery->serial, battio.bif.serial); } #endif } return NULL; } ================================================ FILE: src/detection/battery/battery_haiku.c ================================================ #include "fastfetch.h" #include "battery.h" #include "common/io.h" #include #include #include const char* parseBattery(int dfd, const char* battId, FFlist* results) { FF_AUTO_CLOSE_FD int fd = openat(dfd, battId, O_RDWR); if (fd < 0) return "openat() failed"; acpi_battery_info basic = {}; if (ioctl(fd, GET_BATTERY_INFO, &basic, sizeof(basic)) != 0) return "ioctl(GET_BATTERY_INFO) failed"; acpi_extended_battery_info extended = {}; if (ioctl(fd, GET_EXTENDED_BATTERY_INFO, &extended, sizeof(extended)) != 0) return "ioctl(GET_EXTENDED_BATTERY_INFO) failed"; if (extended.last_full_charge == (uint32)-1) return "Skipped"; FFBatteryResult* battery = (FFBatteryResult*)ffListAdd(results); ffStrbufInitS(&battery->modelName, extended.model_number); ffStrbufInitS(&battery->manufacturer, extended.oem_info); ffStrbufInit(&battery->manufactureDate); ffStrbufInitS(&battery->technology, extended.type); // extended.technology? ffStrbufInit(&battery->status); ffStrbufInitS(&battery->serial, extended.serial_number); battery->temperature = FF_BATTERY_TEMP_UNSET; battery->cycleCount = extended.cycles; battery->timeRemaining = -1; battery->capacity = (double) basic.capacity * 100. / (double) extended.last_full_charge; if (basic.state & BATTERY_DISCHARGING) ffStrbufAppendS(&battery->status, "Discharging, "); if (basic.state & BATTERY_CHARGING) ffStrbufAppendS(&battery->status, "Charging, "); if (basic.state & BATTERY_CRITICAL_STATE) ffStrbufAppendS(&battery->status, "Critical, "); if (basic.state & BATTERY_NOT_CHARGING) ffStrbufAppendS(&battery->status, "AC Connected, "); ffStrbufTrimRight(&battery->status, ' '); ffStrbufTrimRight(&battery->status, ','); return NULL; } const char* ffDetectBattery(FF_MAYBE_UNUSED FFBatteryOptions* options, FFlist* results) { FF_AUTO_CLOSE_DIR DIR* dir = opendir("/dev/power/acpi_battery/"); if (!dir) return "opendir(/dev/power/acpi_battery) failed"; struct dirent* entry; while ((entry = readdir(dir))) { if (entry->d_name[0] == '.') continue; parseBattery(dirfd(dir), entry->d_name, results); } return NULL; } ================================================ FILE: src/detection/battery/battery_linux.c ================================================ #include "battery.h" #include "common/io.h" #include "common/stringUtils.h" #include #include #include // https://www.kernel.org/doc/Documentation/ABI/testing/sysfs-class-power static bool checkAc(const char* id, FFstrbuf* tmpBuffer) { if (ffStrStartsWith(id, "BAT")) ffStrbufSetS(tmpBuffer, "/sys/class/power_supply/ADP1/online"); else if (ffStrStartsWith(id, "macsmc-battery")) ffStrbufSetS(tmpBuffer, "/sys/class/power_supply/macsmc-ac/online"); else ffStrbufClear(tmpBuffer); char online = '\0'; return ffReadFileData(tmpBuffer->chars, 1, &online) == 1 && online == '1'; } static void parseBattery(int dfd, const char* id, FFBatteryOptions* options, FFlist* results) { FF_STRBUF_AUTO_DESTROY tmpBuffer = ffStrbufCreate(); // type must exist and be "Battery" if (!ffReadFileBufferRelative(dfd, "type", &tmpBuffer)) return; ffStrbufTrimRightSpace(&tmpBuffer); if(!ffStrbufIgnCaseEqualS(&tmpBuffer, "Battery")) return; // scope may not exist or must not be "Device" if (ffReadFileBufferRelative(dfd, "scope", &tmpBuffer)) ffStrbufTrimRightSpace(&tmpBuffer); if(ffStrbufIgnCaseEqualS(&tmpBuffer, "Device")) return; // capacity must exist and be not empty // This is expensive in my laptop if (!ffReadFileBufferRelative(dfd, "capacity", &tmpBuffer)) return; FFBatteryResult* result = ffListAdd(results); result->capacity = ffStrbufToDouble(&tmpBuffer, 0); //At this point, we have a battery. Try to get as much values as possible. ffStrbufInit(&result->manufacturer); if (ffReadFileBufferRelative(dfd, "manufacturer", &result->manufacturer)) ffStrbufTrimRightSpace(&result->manufacturer); else if (ffStrEquals(id, "macsmc-battery")) // asahi ffStrbufSetStatic(&result->manufacturer, "Apple Inc."); ffStrbufInit(&result->modelName); if (ffReadFileBufferRelative(dfd, "model_name", &result->modelName)) ffStrbufTrimRightSpace(&result->modelName); ffStrbufInit(&result->technology); if (ffReadFileBufferRelative(dfd, "technology", &result->technology)) ffStrbufTrimRightSpace(&result->technology); ffStrbufInit(&result->status); if (ffReadFileBufferRelative(dfd, "status", &result->status)) ffStrbufTrimRightSpace(&result->status); // Unknown, Charging, Discharging, Not charging, Full result->timeRemaining = -1; if (ffStrbufEqualS(&result->status, "Discharging")) { if (ffReadFileBufferRelative(dfd, "time_to_empty_now", &tmpBuffer)) result->timeRemaining = (int32_t) ffStrbufToSInt(&tmpBuffer, 0); else { if (ffReadFileBufferRelative(dfd, "charge_now", &tmpBuffer)) { int64_t chargeNow = ffStrbufToSInt(&tmpBuffer, 0); if (chargeNow > 0) { if (ffReadFileBufferRelative(dfd, "current_now", &tmpBuffer)) { int64_t currentNow = ffStrbufToSInt(&tmpBuffer, INT64_MIN); if (currentNow < 0) currentNow = -currentNow; if (currentNow > 0) result->timeRemaining = (int32_t) ((chargeNow * 3600) / currentNow); } } } } if (checkAc(id, &tmpBuffer)) ffStrbufAppendS(&result->status, ", AC Connected"); } else if (ffStrbufEqualS(&result->status, "Not charging") || ffStrbufEqualS(&result->status, "Full")) ffStrbufSetStatic(&result->status, "AC Connected"); else if (ffStrbufEqualS(&result->status, "Charging")) ffStrbufAppendS(&result->status, ", AC Connected"); else if (ffStrbufEqualS(&result->status, "Unknown")) { ffStrbufClear(&result->status); if (checkAc(id, &tmpBuffer)) ffStrbufAppendS(&result->status, "AC Connected"); } if (ffReadFileBufferRelative(dfd, "capacity_level", &tmpBuffer)) { ffStrbufTrimRightSpace(&tmpBuffer); if (ffStrbufEqualS(&tmpBuffer, "Critical")) { if (result->status.length) ffStrbufAppendS(&result->status, ", Critical"); else ffStrbufSetStatic(&result->status, "Critical"); } } ffStrbufInit(&result->serial); if (ffReadFileBufferRelative(dfd, "serial_number", &result->serial)) ffStrbufTrimRightSpace(&result->serial); if (ffReadFileBufferRelative(dfd, "cycle_count", &tmpBuffer)) { int64_t cycleCount = ffStrbufToSInt(&tmpBuffer, 0); result->cycleCount = cycleCount < 0 || cycleCount > UINT32_MAX ? 0 : (uint32_t) cycleCount; } ffStrbufInit(&result->manufactureDate); if (ffReadFileBufferRelative(dfd, "manufacture_year", &tmpBuffer)) { int year = (int) ffStrbufToSInt(&tmpBuffer, 0); if (year > 0) { if (ffReadFileBufferRelative(dfd, "manufacture_month", &tmpBuffer)) { int month = (int) ffStrbufToSInt(&tmpBuffer, 0); if (month > 0) { if (ffReadFileBufferRelative(dfd, "manufacture_day", &tmpBuffer)) { int day = (int) ffStrbufToSInt(&tmpBuffer, 0); if (day > 0) ffStrbufSetF(&result->manufactureDate, "%.4d-%.2d-%.2d", year, month, day); } } } } } result->temperature = FF_BATTERY_TEMP_UNSET; if (options->temp) { if (ffReadFileBufferRelative(dfd, "temp", &tmpBuffer)) { result->temperature = ffStrbufToDouble(&tmpBuffer, FF_BATTERY_TEMP_UNSET); if (result->temperature != FF_BATTERY_TEMP_UNSET) result->temperature /= 10; } } } const char* ffDetectBattery(FFBatteryOptions* options, FFlist* results) { FF_AUTO_CLOSE_DIR DIR* dirp = opendir("/sys/class/power_supply/"); if(dirp == NULL) return "opendir(\"/sys/class/power_supply/\") == NULL"; struct dirent* entry; while((entry = readdir(dirp)) != NULL) { if(entry->d_name[0] == '.') continue; FF_AUTO_CLOSE_FD int dfd = openat(dirfd(dirp), entry->d_name, O_RDONLY | O_CLOEXEC | O_PATH | O_DIRECTORY); if (dfd > 0) parseBattery(dfd, entry->d_name, options, results); } return NULL; } ================================================ FILE: src/detection/battery/battery_nbsd.c ================================================ #include "battery.h" #include "common/io.h" #include "common/FFstrbuf.h" #include "common/stringUtils.h" #include #include #include #include #include #include #include #include #include #include const char* ffDetectBattery(FF_MAYBE_UNUSED FFBatteryOptions* options, FFlist* results) { FF_AUTO_CLOSE_FD int fd = open(_PATH_SYSMON, O_RDONLY | O_CLOEXEC); if (fd < 0) return "open(_PATH_SYSMON, O_RDONLY | O_CLOEXEC) failed"; prop_dictionary_t root = NULL; if (prop_dictionary_recv_ioctl(fd, ENVSYS_GETDICTIONARY, &root) < 0) return "prop_dictionary_recv_ioctl(ENVSYS_GETDICTIONARY) failed"; bool acConnected = false; { prop_array_t acad = prop_dictionary_get(root, "acpiacad0"); if (acad) { prop_dictionary_t dict = prop_array_get(acad, 0); prop_dictionary_get_uint8(dict, "cur-value", (uint8_t*) &acConnected); } } prop_object_iterator_t itKey = prop_dictionary_iterator(root); for (prop_dictionary_keysym_t key; (key = prop_object_iterator_next(itKey)) != NULL; ) { if (!ffStrStartsWith(prop_dictionary_keysym_value(key), "acpibat")) continue; prop_array_t bat = prop_dictionary_get_keysym(root, key); uint32_t max = 0, curr = 0, dischargeRate = 0; bool charging = false, critical = false; prop_object_iterator_t iter = prop_array_iterator(bat); for (prop_dictionary_t dict; (dict = prop_object_iterator_next(iter)) != NULL;) { if (prop_object_type(dict) != PROP_TYPE_DICTIONARY) continue; const char* desc = NULL; if (!prop_dictionary_get_string(dict, "description", &desc)) continue; if (ffStrEquals(desc, "present")) { int value = 0; if (prop_dictionary_get_int(dict, "cur-value", &value) && value == 0) continue; } else if (ffStrEquals(desc, "charging")) { prop_dictionary_get_uint8(dict, "cur-value", (uint8_t*) &charging); } else if (ffStrEquals(desc, "charge")) { prop_dictionary_get_uint32(dict, "max-value", &max); prop_dictionary_get_uint32(dict, "cur-value", &curr); const char* state = NULL; if (prop_dictionary_get_string(dict, "state", &state) && ffStrEquals(state, "critical")) critical = true; } else if (ffStrEquals(desc, "discharge rate")) prop_dictionary_get_uint(dict, "cur-value", &dischargeRate); } if (max > 0) { FFBatteryResult* battery = ffListAdd(results); battery->temperature = FF_BATTERY_TEMP_UNSET; battery->cycleCount = 0; ffStrbufInit(&battery->manufacturer); ffStrbufInit(&battery->modelName); ffStrbufInit(&battery->status); ffStrbufInit(&battery->technology); ffStrbufInit(&battery->serial); ffStrbufInit(&battery->manufactureDate); battery->timeRemaining = -1; battery->capacity = (double) curr / (double) max * 100.; if (charging) ffStrbufAppendS(&battery->status, "Charging, "); else if (dischargeRate) { ffStrbufAppendS(&battery->status, "Discharging, "); battery->timeRemaining = (int32_t)((double)curr / dischargeRate * 3600); } if (critical) ffStrbufAppendS(&battery->status, "Critical, "); if (acConnected) ffStrbufAppendS(&battery->status, "AC Connected"); ffStrbufTrimRight(&battery->status, ' '); ffStrbufTrimRight(&battery->status, ','); } prop_object_iterator_release(iter); } prop_object_iterator_release(itKey); prop_object_release(root); return NULL; } ================================================ FILE: src/detection/battery/battery_nosupport.c ================================================ #include "fastfetch.h" #include "battery.h" const char* ffDetectBattery(FFBatteryOptions* options, FFlist* results) { FF_UNUSED(options, results) return "Not supported on this platform"; } ================================================ FILE: src/detection/battery/battery_obsd.c ================================================ #include "battery.h" #include "common/io.h" #include #include #include #include const char* ffDetectBattery(FF_MAYBE_UNUSED FFBatteryOptions* options, FFlist* result) { FF_AUTO_CLOSE_FD int devfd = open("/dev/apm", O_RDONLY | O_CLOEXEC); if (devfd < 0) return "open(dev/apm, O_RDONLY | O_CLOEXEC) failed"; struct apm_power_info info = {}; if (ioctl(devfd, APM_IOC_GETPOWER, &info) < 0) return "ioctl(APM_IOC_GETPOWER) failed"; if (info.battery_state == APM_BATTERY_ABSENT) return NULL; FFBatteryResult* battery = (FFBatteryResult*) ffListAdd(result); battery->temperature = FF_BATTERY_TEMP_UNSET; battery->cycleCount = 0; battery->timeRemaining = -1; battery->capacity = info.battery_life; ffStrbufInit(&battery->manufacturer); ffStrbufInit(&battery->modelName); ffStrbufInit(&battery->status); ffStrbufInit(&battery->technology); ffStrbufInit(&battery->serial); ffStrbufInit(&battery->manufactureDate); if (info.ac_state == APM_AC_ON) ffStrbufAppendS(&battery->status, "AC Connected"); else if (info.ac_state == APM_AC_BACKUP) ffStrbufAppendS(&battery->status, "Backup In Use"); else if (info.ac_state == APM_AC_OFF) { battery->timeRemaining = (int) info.minutes_left * 60; ffStrbufAppendS(&battery->status, "Discharging"); } if (info.battery_state == APM_BATT_CRITICAL || info.battery_state == APM_BATT_CHARGING) { if (battery->status.length) ffStrbufAppendS(&battery->status, ", "); ffStrbufAppendS(&battery->status, info.battery_state == APM_BATT_CRITICAL ? "Critical" : "Charging"); } return NULL; } ================================================ FILE: src/detection/battery/battery_windows.c ================================================ #include "battery.h" #include "common/io.h" #include "common/windows/nt.h" #include "common/windows/unicode.h" #include "common/mallocHelper.h" #include "common/smbiosHelper.h" #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; CONFIGRET cr = CM_Get_Device_Interface_List_SizeW(&cchDeviceInterfaces, (LPGUID)&GUID_DEVCLASS_BATTERY, NULL, CM_GET_DEVICE_INTERFACE_LIST_PRESENT); if (cr != CR_SUCCESS) return "CM_Get_Device_Interface_List_SizeW() failed"; if (cchDeviceInterfaces <= 1) return NULL; // Not found wchar_t* FF_AUTO_FREE mszDeviceInterfaces = (wchar_t*)malloc(cchDeviceInterfaces * sizeof(wchar_t)); cr = CM_Get_Device_Interface_ListW((LPGUID)&GUID_DEVCLASS_BATTERY, NULL, mszDeviceInterfaces, cchDeviceInterfaces, CM_GET_DEVICE_INTERFACE_LIST_PRESENT); if (cr != CR_SUCCESS) return "CM_Get_Device_Interface_ListW() failed"; for (const wchar_t* pDeviceInterface = mszDeviceInterfaces; *pDeviceInterface; pDeviceInterface += wcslen(pDeviceInterface) + 1) { HANDLE FF_AUTO_CLOSE_FD hBattery = CreateFileW(pDeviceInterface, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL); if(hBattery == INVALID_HANDLE_VALUE) continue; BATTERY_QUERY_INFORMATION bqi = { .InformationLevel = BatteryInformation }; DWORD dwWait = 0; DWORD dwOut; if(!DeviceIoControl(hBattery, IOCTL_BATTERY_QUERY_TAG, &dwWait, sizeof(dwWait), &bqi.BatteryTag, sizeof(bqi.BatteryTag), &dwOut, NULL) && bqi.BatteryTag) continue; BATTERY_INFORMATION bi = {0}; if(!DeviceIoControl(hBattery, IOCTL_BATTERY_QUERY_INFORMATION, &bqi, sizeof(bqi), &bi, sizeof(bi), &dwOut, NULL)) continue; if(!(bi.Capabilities & BATTERY_SYSTEM_BATTERY)) continue; FFBatteryResult* battery = (FFBatteryResult*)ffListAdd(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"); { 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); } { 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); } { 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); } { 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); } battery->cycleCount = bi.CycleCount; 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; } { 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; } { 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; else battery->capacity = 0; ffStrbufInit(&battery->status); if(bs.PowerState & BATTERY_POWER_ON_LINE) ffStrbufAppendS(&battery->status, "AC Connected, "); if(bs.PowerState & BATTERY_DISCHARGING) ffStrbufAppendS(&battery->status, "Discharging, "); if(bs.PowerState & BATTERY_CHARGING) ffStrbufAppendS(&battery->status, "Charging, "); if(bs.PowerState & BATTERY_CRITICAL) ffStrbufAppendS(&battery->status, "Critical, "); ffStrbufTrimRight(&battery->status, ' '); ffStrbufTrimRight(&battery->status, ','); } } 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 } __attribute__((__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"; const FFSmbiosPortableBattery* data = (const FFSmbiosPortableBattery*) (*smbiosTable)[FF_SMBIOS_TYPE_PORTABLE_BATTERY]; if (!data) return "Portable battery section is not found in SMBIOS data"; 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); } 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; } 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); } return NULL; } static const char* detectWithNtApi(FF_MAYBE_UNUSED FFBatteryOptions* options, FFlist* results) { SYSTEM_BATTERY_STATE info; if (NT_SUCCESS(NtPowerInformation(SystemBatteryState, NULL, 0, &info, sizeof(info))) && info.BatteryPresent) { FFBatteryResult* battery = (FFBatteryResult*)ffListAdd(results); ffStrbufInit(&battery->modelName); ffStrbufInit(&battery->manufacturer); ffStrbufInit(&battery->manufactureDate); ffStrbufInit(&battery->technology); ffStrbufInit(&battery->status); 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->capacity = info.RemainingCapacity * 100.0 / info.MaxCapacity; if(info.AcOnLine) { ffStrbufAppendS(&battery->status, "AC Connected"); if(info.Charging) ffStrbufAppendS(&battery->status, ", Charging"); } else if(info.Discharging) ffStrbufAppendS(&battery->status, "Discharging"); detectBySmbios(battery); return NULL; } return "NtPowerInformation(SystemBatteryState) failed"; } const char* ffDetectBattery(FFBatteryOptions* options, FFlist* results) { return options->useSetupApi ? detectWithCmApi(options, results) : detectWithNtApi(options, results); } ================================================ FILE: src/detection/bios/bios.h ================================================ #pragma once #include "fastfetch.h" #include "modules/bios/option.h" typedef struct FFBiosResult { FFstrbuf date; FFstrbuf release; FFstrbuf vendor; FFstrbuf version; FFstrbuf type; } FFBiosResult; const char* ffDetectBios(FFBiosResult* bios); ================================================ FILE: src/detection/bios/bios_android.c ================================================ #include "bios.h" #include "common/settings.h" const char* ffDetectBios(FFBiosResult* bios) { if (!ffSettingsGetAndroidProperty("ro.bootloader", &bios->version)) ffSettingsGetAndroidProperty("ro.boot.bootloader", &bios->version); if (ffStrbufIgnCaseEqualS(&bios->version, "unknown")) ffStrbufClear(&bios->version); ffStrbufSetStatic(&bios->type, "Bootloader"); return NULL; } ================================================ FILE: src/detection/bios/bios_apple.c ================================================ #include "bios.h" #include "common/apple/cf_helpers.h" #include const char* ffDetectBios(FFBiosResult* bios) { #ifndef __aarch64__ //https://github.com/osquery/osquery/blob/master/osquery/tables/system/darwin/smbios_tables.cpp //For Intel FF_IOOBJECT_AUTO_RELEASE io_registry_entry_t deviceRom = IORegistryEntryFromPath(MACH_PORT_NULL, "IODeviceTree:/rom"); if (!deviceRom) return "IODeviceTree:/rom not found"; FF_CFTYPE_AUTO_RELEASE CFMutableDictionaryRef deviceRomProps = NULL; if(IORegistryEntryCreateCFProperties(deviceRom, &deviceRomProps, kCFAllocatorDefault, kNilOptions) != kIOReturnSuccess) return "IORegistryEntryCreateCFProperties(deviceRom) failed"; ffCfDictGetString(deviceRomProps, CFSTR("vendor"), &bios->vendor); ffCfDictGetString(deviceRomProps, CFSTR("version"), &bios->version); ffCfDictGetString(deviceRomProps, CFSTR("release-date"), &bios->date); ffStrbufSetStatic(&bios->type, "UEFI"); #else //For arm64 FF_IOOBJECT_AUTO_RELEASE io_registry_entry_t device = IORegistryEntryFromPath(MACH_PORT_NULL, "IODeviceTree:/"); if (!device) return "IODeviceTree:/ not found"; FF_CFTYPE_AUTO_RELEASE CFMutableDictionaryRef deviceProps = NULL; if(IORegistryEntryCreateCFProperties(device, &deviceProps, kCFAllocatorDefault, kNilOptions) != kIOReturnSuccess) return "IORegistryEntryCreateCFProperties(device) failed"; ffCfDictGetString(deviceProps, CFSTR("manufacturer"), &bios->vendor); ffCfDictGetString(deviceProps, CFSTR("time-stamp"), &bios->date); FF_IOOBJECT_AUTO_RELEASE io_registry_entry_t deviceChosen = IORegistryEntryFromPath(MACH_PORT_NULL, "IODeviceTree:/chosen"); if (deviceChosen) { FF_CFTYPE_AUTO_RELEASE CFStringRef systemFirmWareVersion = IORegistryEntryCreateCFProperty(deviceChosen, CFSTR("system-firmware-version"), kCFAllocatorDefault, kNilOptions); if (systemFirmWareVersion) { ffCfStrGetString(systemFirmWareVersion, &bios->version); uint32_t index = ffStrbufFirstIndexC(&bios->version, '-'); if (index != bios->version.length) { ffStrbufAppendNS(&bios->type, index, bios->version.chars); ffStrbufRemoveSubstr(&bios->version, 0, index + 1); } } } if (!bios->type.length) ffStrbufSetStatic(&bios->type, "iBoot"); #endif return NULL; } ================================================ FILE: src/detection/bios/bios_bsd.c ================================================ #include "bios.h" #include "common/settings.h" #include "common/sysctl.h" #include "common/io.h" #include "common/smbiosHelper.h" const char* ffDetectBios(FFBiosResult* result) { ffSettingsGetFreeBSDKenv("smbios.bios.reldate", &result->date); ffCleanUpSmbiosValue(&result->date); ffSettingsGetFreeBSDKenv("smbios.bios.revision", &result->release); ffCleanUpSmbiosValue(&result->release); ffSettingsGetFreeBSDKenv("smbios.bios.vendor", &result->vendor); ffCleanUpSmbiosValue(&result->vendor); ffSettingsGetFreeBSDKenv("smbios.bios.version", &result->version); ffCleanUpSmbiosValue(&result->version); ffSysctlGetString("machdep.bootmethod", &result->type); if (result->type.length == 0) { if (ffSettingsGetFreeBSDKenv("loader.efi", &result->type)) ffStrbufSetStatic(&result->type, ffStrbufEqualS(&result->type, "1") ? "UEFI" : "BIOS"); else { ffStrbufSetStatic(&result->type, ffPathExists("/dev/efi" /*efidev*/, FF_PATHTYPE_FILE) || ffPathExists("/boot/efi/efi/" /*efi partition. Note /boot/efi exists on BIOS system*/, FF_PATHTYPE_DIRECTORY) ? "UEFI" : "BIOS"); } } return NULL; } ================================================ FILE: src/detection/bios/bios_linux.c ================================================ #include "bios.h" #include "common/io.h" #include "common/smbiosHelper.h" const char* ffDetectBios(FFBiosResult* bios) { if (ffGetSmbiosValue("/sys/devices/virtual/dmi/id/bios_date", "/sys/class/dmi/id/bios_date", &bios->date)) { ffGetSmbiosValue("/sys/devices/virtual/dmi/id/bios_release", "/sys/class/dmi/id/bios_release", &bios->release); ffGetSmbiosValue("/sys/devices/virtual/dmi/id/bios_vendor", "/sys/class/dmi/id/bios_vendor", &bios->vendor); ffGetSmbiosValue("/sys/devices/virtual/dmi/id/bios_version", "/sys/class/dmi/id/bios_version", &bios->version); } else if (ffReadFileBuffer("/proc/device-tree/chosen/u-boot,version", &bios->version)) { ffStrbufTrimRight(&bios->version, '\0'); ffStrbufSetStatic(&bios->vendor, "U-Boot"); } if (ffPathExists("/sys/firmware/efi/", FF_PATHTYPE_DIRECTORY) || ffPathExists("/sys/firmware/acpi/tables/UEFI", FF_PATHTYPE_FILE)) ffStrbufSetStatic(&bios->type, "UEFI"); else ffStrbufSetStatic(&bios->type, "BIOS"); return NULL; } ================================================ FILE: src/detection/bios/bios_nbsd.c ================================================ #include "bios.h" #include "common/sysctl.h" #include "common/smbiosHelper.h" const char* ffDetectBios(FFBiosResult* bios) { if (ffSysctlGetString("machdep.dmi.bios-date", &bios->date) == NULL) ffCleanUpSmbiosValue(&bios->date); if (ffSysctlGetString("machdep.dmi.bios-version", &bios->version) == NULL) ffCleanUpSmbiosValue(&bios->version); if (ffSysctlGetString("machdep.dmi.bios-vendor", &bios->vendor) == NULL) ffCleanUpSmbiosValue(&bios->vendor); ffSysctlGetString("machdep.bootmethod", &bios->type); return NULL; } ================================================ FILE: src/detection/bios/bios_nosupport.c ================================================ #include "bios.h" const char* ffDetectBios(FF_MAYBE_UNUSED FFBiosResult* bios) { return "Not supported on this platform"; } ================================================ FILE: src/detection/bios/bios_windows.c ================================================ #include "bios.h" #include "common/smbiosHelper.h" #ifdef _WIN32 #include "common/windows/registry.h" #include #include "common/windows/nt.h" #elif __OpenBSD__ #include "common/io.h" #include #include #elif __sun #include #include #endif typedef struct FFSmbiosBios { FFSmbiosHeader Header; uint8_t Vendor; // string uint8_t BiosVersion; // string uint16_t BiosStartingAddressSegment; // varies uint8_t BiosReleaseDate; // string uint8_t BiosRomSize; // string uint64_t BiosCharacteristics; // bit field // 2.4+ uint8_t BiosCharacteristicsExtensionBytes[2]; // bit field uint8_t SystemBiosMajorRelease; // varies uint8_t SystemBiosMinorRelease; // varies uint8_t EmbeddedControllerFirmwareMajorRelease; // varies uint8_t EmbeddedControllerFirmwareMinorRelease; // varies // 3.1+ uint16_t ExtendedBiosRomSize; // bit field } __attribute__((__packed__)) FFSmbiosBios; static_assert(offsetof(FFSmbiosBios, ExtendedBiosRomSize) == 0x18, "FFSmbiosBios: Wrong struct alignment"); const char* ffDetectBios(FFBiosResult* bios) { const FFSmbiosHeaderTable* smbiosTable = ffGetSmbiosHeaderTable(); if (!smbiosTable) return "Failed to get SMBIOS data"; const FFSmbiosBios* data = (const FFSmbiosBios*) (*smbiosTable)[FF_SMBIOS_TYPE_BIOS]; if (!data) return "BIOS section is not found in SMBIOS data"; const char* strings = (const char*) data + data->Header.Length; ffStrbufSetStatic(&bios->version, ffSmbiosLocateString(strings, data->BiosVersion)); ffCleanUpSmbiosValue(&bios->version); ffStrbufSetStatic(&bios->vendor, ffSmbiosLocateString(strings, data->Vendor)); ffCleanUpSmbiosValue(&bios->vendor); ffStrbufSetStatic(&bios->date, ffSmbiosLocateString(strings, data->BiosReleaseDate)); ffCleanUpSmbiosValue(&bios->date); if (data->Header.Length > offsetof(FFSmbiosBios, SystemBiosMajorRelease)) ffStrbufSetF(&bios->release, "%u.%u", data->SystemBiosMajorRelease, data->SystemBiosMinorRelease); #ifdef _WIN32 // Same as GetFirmwareType, but support (?) Windows 7 // https://ntdoc.m417z.com/system_information_class SYSTEM_BOOT_ENVIRONMENT_INFORMATION sbei; if (NT_SUCCESS(NtQuerySystemInformation(SystemBootEnvironmentInformation, &sbei, sizeof(sbei), NULL))) { switch (sbei.FirmwareType) { case FirmwareTypeBios: ffStrbufSetStatic(&bios->type, "BIOS"); break; case FirmwareTypeUefi: ffStrbufSetStatic(&bios->type, "UEFI"); break; default: break; } } #elif __sun di_node_t rootNode = di_init("/", DINFOPROP); if (rootNode != DI_NODE_NIL) { char* efiVersion = NULL; if (di_prop_lookup_strings(DDI_DEV_T_ANY, rootNode, "efi-version", &efiVersion) > 0) ffStrbufSetStatic(&bios->type, "UEFI"); else ffStrbufSetStatic(&bios->type, "BIOS"); } di_fini(rootNode); #elif __HAIKU__ || __OpenBSD__ // Currently SMBIOS detection is supported in legancy BIOS only ffStrbufSetStatic(&bios->type, "BIOS"); #endif return NULL; } ================================================ FILE: src/detection/bluetooth/bluetooth.h ================================================ #pragma once #include "fastfetch.h" #include "modules/bluetooth/option.h" typedef struct FFBluetoothResult { FFstrbuf name; FFstrbuf address; FFstrbuf type; uint8_t battery; // 0-100% bool connected; } FFBluetoothResult; const char* ffDetectBluetooth(FFBluetoothOptions* options, FFlist* devices /* FFBluetoothResult */); ================================================ FILE: src/detection/bluetooth/bluetooth_apple.m ================================================ #include "bluetooth.h" #import @interface IOBluetoothDevice() @property (nonatomic) uint8_t batteryPercentCase; @property (nonatomic) uint8_t batteryPercentCombined; @property (nonatomic) uint8_t batteryPercentLeft; @property (nonatomic) uint8_t batteryPercentRight; @property (nonatomic) uint8_t batteryPercentSingle; @end const char* ffDetectBluetooth(FFBluetoothOptions* options, FFlist* devices /* FFBluetoothResult */) { NSArray* ioDevices = IOBluetoothDevice.pairedDevices; if(!ioDevices) return "IOBluetoothDevice.pairedDevices failed"; for(IOBluetoothDevice* ioDevice in ioDevices) { if (!options->showDisconnected && !ioDevice.isConnected) continue; FFBluetoothResult* device = ffListAdd(devices); ffStrbufInitS(&device->name, ioDevice.name.UTF8String); ffStrbufInitS(&device->address, ioDevice.addressString.UTF8String); ffStrbufReplaceAllC(&device->address, '-', ':'); ffStrbufUpperCase(&device->address); ffStrbufInit(&device->type); if (ioDevice.batteryPercentSingle) device->battery = ioDevice.batteryPercentSingle; else if (ioDevice.batteryPercentCombined) device->battery = ioDevice.batteryPercentCombined; else if (ioDevice.batteryPercentCase) device->battery = ioDevice.batteryPercentCase; device->connected = !!ioDevice.isConnected; if(ioDevice.serviceClassMajor & kBluetoothServiceClassMajorLimitedDiscoverableMode) ffStrbufAppendS(&device->type, "Limited Discoverable Mode, "); if(ioDevice.serviceClassMajor & kBluetoothServiceClassMajorReserved1) ffStrbufAppendS(&device->type, "LE audio, "); if(ioDevice.serviceClassMajor & kBluetoothServiceClassMajorReserved2) ffStrbufAppendS(&device->type, "Reserved for future use, "); if(ioDevice.serviceClassMajor & kBluetoothServiceClassMajorPositioning) ffStrbufAppendS(&device->type, "Positioning, "); if(ioDevice.serviceClassMajor & kBluetoothServiceClassMajorNetworking) ffStrbufAppendS(&device->type, "Networking, "); if(ioDevice.serviceClassMajor & kBluetoothServiceClassMajorRendering) ffStrbufAppendS(&device->type, "Rendering, "); if(ioDevice.serviceClassMajor & kBluetoothServiceClassMajorCapturing) ffStrbufAppendS(&device->type, "Capturing, "); if(ioDevice.serviceClassMajor & kBluetoothServiceClassMajorObjectTransfer) ffStrbufAppendS(&device->type, "Object Transfer, "); if(ioDevice.serviceClassMajor & kBluetoothServiceClassMajorAudio) ffStrbufAppendS(&device->type, "Audio, "); if(ioDevice.serviceClassMajor & kBluetoothServiceClassMajorTelephony) ffStrbufAppendS(&device->type, "Telephony, "); if(ioDevice.serviceClassMajor & kBluetoothServiceClassMajorInformation) ffStrbufAppendS(&device->type, "Information, "); if(device->type.length == 0) { switch(ioDevice.deviceClassMajor) { case kBluetoothDeviceClassMajorMiscellaneous: ffStrbufAppendS(&device->type, "Miscellaneous"); break; case kBluetoothDeviceClassMajorComputer: ffStrbufAppendS(&device->type, "Computer"); break; case kBluetoothDeviceClassMajorPhone: ffStrbufAppendS(&device->type, "Phone"); break; case kBluetoothDeviceClassMajorLANAccessPoint: ffStrbufAppendS(&device->type, "LAN/Network Access point"); break; case kBluetoothDeviceClassMajorAudio: ffStrbufAppendS(&device->type, "Audio/Video"); break; case kBluetoothDeviceClassMajorPeripheral: ffStrbufAppendS(&device->type, "Peripheral"); break; case kBluetoothDeviceClassMajorImaging: ffStrbufAppendS(&device->type, "Imaging"); break; case kBluetoothDeviceClassMajorWearable: ffStrbufAppendS(&device->type, "Wearable"); break; case kBluetoothDeviceClassMajorToy: ffStrbufAppendS(&device->type, "Toy"); break; case kBluetoothDeviceClassMajorHealth: ffStrbufAppendS(&device->type, "Health"); break; case kBluetoothDeviceClassMajorUnclassified: ffStrbufAppendS(&device->type, "Uncategorized"); break; default: ffStrbufAppendS(&device->type, "Unknown"); break; } } else { ffStrbufTrimRight(&device->type, ' '); ffStrbufTrimRight(&device->type, ','); } } return NULL; } ================================================ FILE: src/detection/bluetooth/bluetooth_bsd.c ================================================ #include "bluetooth.h" #define L2CAP_SOCKET_CHECKED #include static int enumDev(FF_MAYBE_UNUSED int sockfd, struct bt_devinfo const* dev, FFlist* devices) { FFBluetoothResult* device = ffListAdd(devices); ffStrbufInitS(&device->name, #if __FreeBSD__ bt_devremote_name_gen(dev->devname, &dev->bdaddr) #else dev->devname #endif ); ffStrbufInitS(&device->address, bt_ntoa(&dev->bdaddr, NULL)); ffStrbufUpperCase(&device->address); ffStrbufInit(&device->type); device->battery = 0; device->connected = true; return 0; } const char* ffDetectBluetooth(FF_MAYBE_UNUSED FFBluetoothOptions* options, FF_MAYBE_UNUSED FFlist* devices /* FFBluetoothResult */) { // struct hostent* ent = bt_gethostent(); if (bt_devenum((void*) enumDev, devices) < 0) return "bt_devenum() failed"; return NULL; } ================================================ FILE: src/detection/bluetooth/bluetooth_haiku.cpp ================================================ extern "C" { #include "bluetooth.h" #include "common/io.h" } #include const char* ffDetectBluetooth(FF_MAYBE_UNUSED FFBluetoothOptions* options, FFlist* devices /* FFBluetoothResult */) { using namespace Bluetooth; FF_SUPPRESS_IO(); LocalDevice* dev = LocalDevice::GetLocalDevice(); if (!dev) return NULL; BString devClass; dev->GetDeviceClass().DumpDeviceClass(devClass); FFBluetoothResult* device = (FFBluetoothResult*) ffListAdd(devices); ffStrbufInitS(&device->name, dev->GetFriendlyName()); ffStrbufInitS(&device->address, bdaddrUtils::ToString(dev->GetBluetoothAddress()).String()); ffStrbufInitS(&device->type, devClass.String()); device->battery = 0; device->connected = true; // TODO: more devices? return NULL; } ================================================ FILE: src/detection/bluetooth/bluetooth_linux.c ================================================ #include "bluetooth.h" #include "common/stringUtils.h" #ifdef FF_HAVE_DBUS #include "common/dbus.h" #include "common/io.h" /* Example dbus reply, striped to only the relevant parts: array [ //root dict entry( //object object path "/org/bluez/hci0/dev_03_21_8B_91_16_4D" array [ dict entry( //property string "org.bluez.Device1" array [ dict entry( //value string "Address" variant string "03:21:8B:91:16:4D" ) dict entry( //value string "Name" variant string "JBL TUNE160BT" ) dict entry( //value string "Icon" variant string "audio-headset" ) dict entry( //value string "Connected" variant boolean true ) ] ) dict entry( //property string "org.bluez.Battery1" array [ dict entry( //value string "Percentage" variant byte 100 ) ] ) ] ) ] */ static bool detectBluetoothValue(FFDBusData* dbus, DBusMessageIter* iter, FFBluetoothResult* device) { if(dbus->lib->ffdbus_message_iter_get_arg_type(iter) != DBUS_TYPE_DICT_ENTRY) return true; DBusMessageIter dictIter; dbus->lib->ffdbus_message_iter_recurse(iter, &dictIter); if(dbus->lib->ffdbus_message_iter_get_arg_type(&dictIter) != DBUS_TYPE_STRING) return true; const char* deviceProperty; dbus->lib->ffdbus_message_iter_get_basic(&dictIter, &deviceProperty); dbus->lib->ffdbus_message_iter_next(&dictIter); if(ffStrEquals(deviceProperty, "Address")) ffDBusGetString(dbus, &dictIter, &device->address); else if(ffStrEquals(deviceProperty, "Name")) ffDBusGetString(dbus, &dictIter, &device->name); else if(ffStrEquals(deviceProperty, "Icon")) ffDBusGetString(dbus, &dictIter, &device->type); else if(ffStrEquals(deviceProperty, "Percentage")) { uint32_t percentage; if (ffDBusGetUint(dbus, &dictIter, &percentage)) device->battery = (uint8_t) percentage; } else if(ffStrEquals(deviceProperty, "Connected")) ffDBusGetBool(dbus, &dictIter, &device->connected); else if(ffStrEquals(deviceProperty, "Paired")) { bool paired = true; ffDBusGetBool(dbus, &dictIter, &paired); if (!paired) return false; } return true; } static void detectBluetoothProperty(FFDBusData* dbus, DBusMessageIter* iter, FFBluetoothResult* device) { if(dbus->lib->ffdbus_message_iter_get_arg_type(iter) != DBUS_TYPE_DICT_ENTRY) return; DBusMessageIter dictIter; dbus->lib->ffdbus_message_iter_recurse(iter, &dictIter); if(dbus->lib->ffdbus_message_iter_get_arg_type(&dictIter) != DBUS_TYPE_STRING) return; const char* propertyType; dbus->lib->ffdbus_message_iter_get_basic(&dictIter, &propertyType); if(!ffStrContains(propertyType, ".Device") && !ffStrContains(propertyType, ".Battery")) return; // We don't care about other properties dbus->lib->ffdbus_message_iter_next(&dictIter); if(dbus->lib->ffdbus_message_iter_get_arg_type(&dictIter) != DBUS_TYPE_ARRAY) return; DBusMessageIter arrayIter; dbus->lib->ffdbus_message_iter_recurse(&dictIter, &arrayIter); do { bool shouldContinue = detectBluetoothValue(dbus, &arrayIter, device); if (!shouldContinue) { ffStrbufClear(&device->name); break; } } while (dbus->lib->ffdbus_message_iter_next(&arrayIter)); } static FFBluetoothResult* detectBluetoothObject(FFlist* devices, FFDBusData* dbus, DBusMessageIter* iter) { if(dbus->lib->ffdbus_message_iter_get_arg_type(iter) != DBUS_TYPE_DICT_ENTRY) return NULL; DBusMessageIter dictIter; dbus->lib->ffdbus_message_iter_recurse(iter, &dictIter); if(dbus->lib->ffdbus_message_iter_get_arg_type(&dictIter) != DBUS_TYPE_OBJECT_PATH) return NULL; const char* objectPath; dbus->lib->ffdbus_message_iter_get_basic(&dictIter, &objectPath); // We don't want adapter objects if(!ffStrContains(objectPath, "/dev_")) return NULL; dbus->lib->ffdbus_message_iter_next(&dictIter); if(dbus->lib->ffdbus_message_iter_get_arg_type(&dictIter) != DBUS_TYPE_ARRAY) return NULL; DBusMessageIter arrayIter; dbus->lib->ffdbus_message_iter_recurse(&dictIter, &arrayIter); FFBluetoothResult* device = ffListAdd(devices); ffStrbufInit(&device->name); ffStrbufInit(&device->address); ffStrbufInit(&device->type); device->battery = 0; device->connected = false; do { detectBluetoothProperty(dbus, &arrayIter, device); } while (dbus->lib->ffdbus_message_iter_next(&arrayIter)); return device; } static void detectBluetoothRoot(FFlist* devices, FFDBusData* dbus, DBusMessageIter* iter, int32_t connectedCount) { if(dbus->lib->ffdbus_message_iter_get_arg_type(iter) != DBUS_TYPE_ARRAY) return; DBusMessageIter arrayIter; dbus->lib->ffdbus_message_iter_recurse(iter, &arrayIter); do { FFBluetoothResult* device = detectBluetoothObject(devices, dbus, &arrayIter); if (device) { if(device->name.length == 0 || (connectedCount > 0 && !device->connected)) { ffStrbufDestroy(&device->name); ffStrbufDestroy(&device->address); ffStrbufDestroy(&device->type); --devices->length; } if (device->connected && --connectedCount == 0) break; } } while (dbus->lib->ffdbus_message_iter_next(&arrayIter)); } static const char* detectBluetooth(FFlist* devices, int32_t connectedCount) { FF_DBUS_AUTO_DESTROY_DATA FFDBusData dbus = {}; const char* error = ffDBusLoadData(DBUS_BUS_SYSTEM, &dbus); if(error) return error; DBusMessage* managedObjects = ffDBusGetMethodReply(&dbus, "org.bluez", "/", "org.freedesktop.DBus.ObjectManager", "GetManagedObjects", NULL, NULL); if(!managedObjects) return "Failed to call GetManagedObjects"; DBusMessageIter rootIter; if(!dbus.lib->ffdbus_message_iter_init(managedObjects, &rootIter)) { dbus.lib->ffdbus_message_unref(managedObjects); return "Failed to get root iterator of GetManagedObjects"; } detectBluetoothRoot(devices, &dbus, &rootIter, connectedCount); dbus.lib->ffdbus_message_unref(managedObjects); return NULL; } static uint32_t connectedDevices(void) { FF_AUTO_CLOSE_DIR DIR* dirp = opendir("/sys/class/bluetooth"); if(dirp == NULL) return 0; uint32_t result = 0; struct dirent* entry; while ((entry = readdir(dirp)) != NULL) { if (strchr(entry->d_name, ':') != NULL) ++result; } return result; } #endif const char* ffDetectBluetooth(FF_MAYBE_UNUSED FFBluetoothOptions* options, FF_MAYBE_UNUSED FFlist* devices /* FFBluetoothResult */) { #ifdef FF_HAVE_DBUS int32_t connectedCount = -1; if (!options->showDisconnected) { connectedCount = (int32_t) connectedDevices(); if (connectedCount == 0) return NULL; } return detectBluetooth(devices, connectedCount); #else return "Fastfetch was compiled without DBus support"; #endif } ================================================ FILE: src/detection/bluetooth/bluetooth_nosupport.c ================================================ #include "bluetooth.h" const char* ffDetectBluetooth(FF_MAYBE_UNUSED FFBluetoothOptions* options, FF_MAYBE_UNUSED FFlist* devices /* FFBluetoothResult */) { return "Not supported on this platform"; } ================================================ FILE: src/detection/bluetooth/bluetooth_windows.c ================================================ #include "bluetooth.h" #include "common/library.h" #include "common/windows/unicode.h" #include #include #pragma GCC diagnostic ignored "-Wpointer-sign" const char* ffDetectBluetooth(FFBluetoothOptions* options, FFlist* devices /* FFBluetoothResult */) { FF_LIBRARY_LOAD_MESSAGE(bluetoothapis, "bluetoothapis.dll", 1) FF_LIBRARY_LOAD_SYMBOL_MESSAGE(bluetoothapis, BluetoothFindFirstDevice) FF_LIBRARY_LOAD_SYMBOL_MESSAGE(bluetoothapis, BluetoothFindNextDevice) FF_LIBRARY_LOAD_SYMBOL_MESSAGE(bluetoothapis, BluetoothFindDeviceClose) BLUETOOTH_DEVICE_INFO btdi = { .dwSize = sizeof(btdi) }; HBLUETOOTH_DEVICE_FIND hFind = ffBluetoothFindFirstDevice(&(BLUETOOTH_DEVICE_SEARCH_PARAMS) { .fReturnConnected = TRUE, .fReturnRemembered = options->showDisconnected, .fReturnAuthenticated = options->showDisconnected, .dwSize = sizeof(BLUETOOTH_DEVICE_SEARCH_PARAMS) }, &btdi); if(!hFind) { if (GetLastError() == ERROR_NO_MORE_ITEMS) return NULL; return "BluetoothFindFirstDevice() failed"; } do { FFBluetoothResult* device = ffListAdd(devices); ffStrbufInitWS(&device->name, btdi.szName); ffStrbufInitF(&device->address, "%02X:%02X:%02X:%02X:%02X:%02X", btdi.Address.rgBytes[5], btdi.Address.rgBytes[4], btdi.Address.rgBytes[3], btdi.Address.rgBytes[2], btdi.Address.rgBytes[1], btdi.Address.rgBytes[0]); ffStrbufInit(&device->type); device->battery = 0; device->connected = !!btdi.fConnected; //https://btprodspecificationrefs.blob.core.windows.net/assigned-numbers/Assigned%20Number%20Types/Assigned%20Numbers.pdf if(BitTest(&btdi.ulClassofDevice, 13)) ffStrbufAppendS(&device->type, "Limited Discoverable Mode, "); if(BitTest(&btdi.ulClassofDevice, 14)) ffStrbufAppendS(&device->type, "LE audio, "); if(BitTest(&btdi.ulClassofDevice, 15)) ffStrbufAppendS(&device->type, "Reserved for future use, "); if(BitTest(&btdi.ulClassofDevice, 16)) ffStrbufAppendS(&device->type, "Positioning, "); if(BitTest(&btdi.ulClassofDevice, 17)) ffStrbufAppendS(&device->type, "Networking, "); if(BitTest(&btdi.ulClassofDevice, 18)) ffStrbufAppendS(&device->type, "Rendering, "); if(BitTest(&btdi.ulClassofDevice, 19)) ffStrbufAppendS(&device->type, "Capturing, "); if(BitTest(&btdi.ulClassofDevice, 20)) ffStrbufAppendS(&device->type, "Object Transfer, "); if(BitTest(&btdi.ulClassofDevice, 21)) ffStrbufAppendS(&device->type, "Audio, "); if(BitTest(&btdi.ulClassofDevice, 22)) ffStrbufAppendS(&device->type, "Telephony, "); if(BitTest(&btdi.ulClassofDevice, 23)) ffStrbufAppendS(&device->type, "Information, "); if(device->type.length == 0) { uint32_t majorDeviceClasses = (btdi.ulClassofDevice >> 8) & ~(UINT32_MAX << 5); switch(majorDeviceClasses) { case 0b00000: ffStrbufAppendS(&device->type, "Miscellaneous"); break; case 0b00001: ffStrbufAppendS(&device->type, "Computer"); break; case 0b00010: ffStrbufAppendS(&device->type, "Phone"); break; case 0b00011: ffStrbufAppendS(&device->type, "LAN/Network Access point"); break; case 0b00100: ffStrbufAppendS(&device->type, "Audio/Video"); break; case 0b00101: ffStrbufAppendS(&device->type, "Peripheral"); break; case 0b00110: ffStrbufAppendS(&device->type, "Imaging"); break; case 0b00111: ffStrbufAppendS(&device->type, "Wearable"); break; case 0b01000: ffStrbufAppendS(&device->type, "Toy"); break; case 0b01001: ffStrbufAppendS(&device->type, "Health"); break; case 0b11111: ffStrbufAppendS(&device->type, "Uncategorized"); break; default: ffStrbufAppendS(&device->type, "Unknown"); break; } } else { ffStrbufTrimRight(&device->type, ' '); ffStrbufTrimRight(&device->type, ','); } } while (ffBluetoothFindNextDevice(hFind, &btdi)); ffBluetoothFindDeviceClose(hFind); const char* ffBluetoothDetectBattery(FFlist* result); ffBluetoothDetectBattery(devices); return NULL; } ================================================ FILE: src/detection/bluetooth/bluetooth_windows.cpp ================================================ 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; } ================================================ FILE: src/detection/bluetoothradio/bluetoothradio.c ================================================ #include "bluetoothradio.h" // https://github.com/ziglang/zig/blob/a84951465b409495095a9598db0cae745f34fa7b/lib/libc/include/any-windows-any/bthdef.h#L187-L236 #define BTH_MFG_ERICSSON 0 #define BTH_MFG_NOKIA 1 #define BTH_MFG_INTEL 2 #define BTH_MFG_IBM 3 #define BTH_MFG_TOSHIBA 4 #define BTH_MFG_3COM 5 #define BTH_MFG_MICROSOFT 6 #define BTH_MFG_LUCENT 7 #define BTH_MFG_MOTOROLA 8 #define BTH_MFG_INFINEON 9 #define BTH_MFG_CSR 10 #define BTH_MFG_SILICONWAVE 11 #define BTH_MFG_DIGIANSWER 12 #define BTH_MFG_TI 13 #define BTH_MFG_PARTHUS 14 #define BTH_MFG_BROADCOM 15 #define BTH_MFG_MITEL 16 #define BTH_MFG_WIDCOMM 17 #define BTH_MFG_ZEEVO 18 #define BTH_MFG_ATMEL 19 #define BTH_MFG_MITSIBUSHI 20 #define BTH_MFG_RTX_TELECOM 21 #define BTH_MFG_KC_TECHNOLOGY 22 #define BTH_MFG_NEWLOGIC 23 #define BTH_MFG_TRANSILICA 24 #define BTH_MFG_ROHDE_SCHWARZ 25 #define BTH_MFG_TTPCOM 26 #define BTH_MFG_SIGNIA 27 #define BTH_MFG_CONEXANT 28 #define BTH_MFG_QUALCOMM 29 #define BTH_MFG_INVENTEL 30 #define BTH_MFG_AVM_BERLIN 31 #define BTH_MFG_BANDSPEED 32 #define BTH_MFG_MANSELLA 33 #define BTH_MFG_NEC 34 #define BTH_MFG_WAVEPLUS_TECHNOLOGY_CO 35 #define BTH_MFG_ALCATEL 36 #define BTH_MFG_PHILIPS_SEMICONDUCTOR 37 #define BTH_MFG_C_TECHNOLOGIES 38 #define BTH_MFG_OPEN_INTERFACE 39 #define BTH_MFG_RF_MICRO_DEVICES 40 #define BTH_MFG_HITACHI 41 #define BTH_MFG_SYMBOL_TECHNOLOGIES 42 #define BTH_MFG_TENOVIS 43 #define BTH_MFG_MACRONIX_INTERNATIONAL 44 #define BTH_MFG_MARVELL 72 #define BTH_MFG_APPLE 76 #define BTH_MFG_NORDIC_SEMICONDUCTORS_ASA 89 #define BTH_MFG_ARUBA_NETWORKS 283 #define BTH_MFG_INTERNAL_USE 65535 const char* ffBluetoothRadioGetVendor(uint32_t manufacturerId) { switch (manufacturerId) { case BTH_MFG_ERICSSON: return "Ericsson"; case BTH_MFG_NOKIA: return "Nokia"; case BTH_MFG_INTEL: return "Intel"; case BTH_MFG_IBM: return "IBM"; case BTH_MFG_TOSHIBA: return "Toshiba"; case BTH_MFG_3COM: return "3Com"; case BTH_MFG_MICROSOFT: return "Microsoft"; case BTH_MFG_LUCENT: return "Lucent"; case BTH_MFG_MOTOROLA: return "Motorola"; case BTH_MFG_INFINEON: return "Infineon"; case BTH_MFG_CSR: return "CSR"; case BTH_MFG_SILICONWAVE: return "Silicon-Wave"; case BTH_MFG_DIGIANSWER: return "Digi-Answer"; case BTH_MFG_TI: return "Ti"; case BTH_MFG_PARTHUS: return "Parthus"; case BTH_MFG_BROADCOM: return "Broadcom"; case BTH_MFG_MITEL: return "Mitel"; case BTH_MFG_WIDCOMM: return "Widcomm"; case BTH_MFG_ZEEVO: return "Zeevo"; case BTH_MFG_ATMEL: return "Atmel"; case BTH_MFG_MITSIBUSHI: return "Mitsubishi"; case BTH_MFG_RTX_TELECOM: return "RTX Telecom"; case BTH_MFG_KC_TECHNOLOGY: return "KC Technology"; case BTH_MFG_NEWLOGIC: return "Newlogic"; case BTH_MFG_TRANSILICA: return "Transilica"; case BTH_MFG_ROHDE_SCHWARZ: return "Rohde-Schwarz"; case BTH_MFG_TTPCOM: return "TTPCom"; case BTH_MFG_SIGNIA: return "Signia"; case BTH_MFG_CONEXANT: return "Conexant"; case BTH_MFG_QUALCOMM: return "Qualcomm"; case BTH_MFG_INVENTEL: return "Inventel"; case BTH_MFG_AVM_BERLIN: return "AVM Berlin"; case BTH_MFG_BANDSPEED: return "Bandspeed"; case BTH_MFG_MANSELLA: return "Mansella"; case BTH_MFG_NEC: return "NEC"; case BTH_MFG_WAVEPLUS_TECHNOLOGY_CO: return "Waveplus"; case BTH_MFG_ALCATEL: return "Alcatel"; case BTH_MFG_PHILIPS_SEMICONDUCTOR: return "Philips Semiconductors"; case BTH_MFG_C_TECHNOLOGIES: return "C Technologies"; case BTH_MFG_OPEN_INTERFACE: return "Open Interface"; case BTH_MFG_RF_MICRO_DEVICES: return "RF Micro Devices"; case BTH_MFG_HITACHI: return "Hitachi"; case BTH_MFG_SYMBOL_TECHNOLOGIES: return "Symbol Technologies"; case BTH_MFG_TENOVIS: return "Tenovis"; case BTH_MFG_MACRONIX_INTERNATIONAL: return "Macronix International"; case BTH_MFG_MARVELL: return "Marvell"; case BTH_MFG_APPLE: return "Apple"; case BTH_MFG_NORDIC_SEMICONDUCTORS_ASA: return "Nordic Semiconductor ASA"; case BTH_MFG_ARUBA_NETWORKS: return "Aruba Networks"; case BTH_MFG_INTERNAL_USE: return "Internal Use"; default: return "Unknown"; } } ================================================ FILE: src/detection/bluetoothradio/bluetoothradio.h ================================================ #pragma once #include "fastfetch.h" #include "modules/bluetoothradio/option.h" typedef struct FFBluetoothRadioResult { FFstrbuf name; FFstrbuf address; FFstrbuf vendor; int32_t lmpVersion; int32_t lmpSubversion; bool enabled; bool discoverable; bool connectable; } FFBluetoothRadioResult; const char* ffDetectBluetoothRadio(FFlist* devices /* FFBluetoothRadioResult */); const char* ffBluetoothRadioGetVendor(uint32_t manufacturerId); ================================================ FILE: src/detection/bluetoothradio/bluetoothradio_apple.m ================================================ #include "bluetoothradio.h" #include "common/processing.h" #import // For some reason the official declaration of IOBluetoothHostController doesn't include property `controllers` @interface IOBluetoothHostController() + (id)controllers; @end const char* ffDetectBluetoothRadio(FFlist* devices /* FFBluetoothRadioResult */) { NSArray* ctrls = IOBluetoothHostController.controllers; if(!ctrls) return "IOBluetoothHostController.controllers returns nil"; FF_STRBUF_AUTO_DESTROY buffer = ffStrbufCreate(); if (ffProcessAppendStdOut(&buffer, (char* const[]) { "system_profiler", "SPBluetoothDataType", "-xml", "-detailLevel", "basic", NULL }) != NULL) return "Starting `system_profiler SPBluetoothDataType -xml -detailLevel basic` failed"; NSArray* arr = [NSPropertyListSerialization propertyListWithData:[NSData dataWithBytes:buffer.chars length:buffer.length] options:NSPropertyListImmutable format:nil error:nil]; if (!arr || !arr.count) return "system_profiler SPBluetoothDataType returned an empty array"; for (IOBluetoothHostController* ctrl in ctrls) { FFBluetoothRadioResult* device = ffListAdd(devices); ffStrbufInitS(&device->name, ctrl.nameAsString.UTF8String); ffStrbufInitS(&device->address, ctrl.addressAsString.UTF8String); ffStrbufInitStatic(&device->vendor, "Apple"); device->lmpVersion = INT_MIN; device->lmpSubversion = INT_MIN; device->enabled = ctrl.powerState == kBluetoothHCIPowerStateON; device->discoverable = false; device->connectable = true; for (NSDictionary* itemDict in arr[0][@"_items"]) { NSDictionary* props = itemDict[@"controller_properties"]; if (!props) continue; if (![ctrl.addressAsString isEqualToString:props[@"controller_address"]]) continue; NSString* services = props[@"controller_supportedServices"]; if ([services containsString:@" LEA "]) device->lmpVersion = -11; else if ([services containsString:@" GATT "]) device->lmpVersion = -6; device->discoverable = ![props[@"controller_discoverable"] isEqualToString:@"attrib_off"]; ffStrbufSetS(&device->vendor, ((NSString*) props[@"controller_vendorID"]).UTF8String); ffStrbufSubstrAfterFirstC(&device->vendor, '('); ffStrbufTrimRight(&device->vendor, ')'); break; } } return NULL; } ================================================ FILE: src/detection/bluetoothradio/bluetoothradio_linux.c ================================================ #include "bluetoothradio.h" #include "common/stringUtils.h" #ifdef FF_HAVE_DBUS #include "common/dbus.h" #include "common/io.h" /* Example dbus reply: array [ dict entry( string "Address" variant string "XX:XX:XX:XX:XX:XX" ) dict entry( string "Name" variant string "xxxxxxxx" ) dict entry( string "Powered" variant boolean true ) dict entry( string "PowerState" variant string "on" ) dict entry( string "Manufacturer" variant uint16 2 ) dict entry( string "Version" variant byte 12 ) ] */ static const char* detectBluetoothProperty(FFBluetoothRadioResult* device, FFDBusData* dbus, DBusMessageIter* iter) { if(dbus->lib->ffdbus_message_iter_get_arg_type(iter) != DBUS_TYPE_DICT_ENTRY) return "Expected dict entry"; DBusMessageIter dictIter; dbus->lib->ffdbus_message_iter_recurse(iter, &dictIter); if(dbus->lib->ffdbus_message_iter_get_arg_type(&dictIter) != DBUS_TYPE_STRING) return "Expected dict entry key to be a string"; const char* deviceProperty; dbus->lib->ffdbus_message_iter_get_basic(&dictIter, &deviceProperty); dbus->lib->ffdbus_message_iter_next(&dictIter); if(ffStrEquals(deviceProperty, "Address")) ffDBusGetString(dbus, &dictIter, &device->address); else if(ffStrEquals(deviceProperty, "Alias")) ffDBusGetString(dbus, &dictIter, &device->name); else if(ffStrEquals(deviceProperty, "Manufacturer")) { uint32_t vendorId; if (ffDBusGetUint(dbus, &dictIter, &vendorId)) ffStrbufSetStatic(&device->vendor, ffBluetoothRadioGetVendor(vendorId)); } else if(ffStrEquals(deviceProperty, "Version")) ffDBusGetUint(dbus, &dictIter, (uint32_t*) &device->lmpVersion); else if(ffStrEquals(deviceProperty, "Powered")) ffDBusGetBool(dbus, &dictIter, &device->enabled); else if(ffStrEquals(deviceProperty, "Discoverable")) ffDBusGetBool(dbus, &dictIter, &device->discoverable); else if(ffStrEquals(deviceProperty, "Pairable")) ffDBusGetBool(dbus, &dictIter, &device->connectable); return NULL; } static const char* detectBluetoothRoot(FFBluetoothRadioResult* device, const char* hciName, FFDBusData* dbus) { char objPath[300]; snprintf(objPath, sizeof(objPath), "/org/bluez/%s", hciName); DBusMessage* properties = ffDBusGetMethodReply(dbus, "org.bluez", objPath, "org.freedesktop.DBus.Properties", "GetAll", "org.bluez.Adapter1", NULL); if(!properties) return "Failed to call org.freedesktop.DBus.Properties.GetAll"; DBusMessageIter rootIter; if(!dbus->lib->ffdbus_message_iter_init(properties, &rootIter)) { dbus->lib->ffdbus_message_unref(properties); return "Failed to get root iterator of org.freedesktop.DBus.Properties.GetAll"; } if(dbus->lib->ffdbus_message_iter_get_arg_type(&rootIter) != DBUS_TYPE_ARRAY) { dbus->lib->ffdbus_message_unref(properties); return "Expected array"; } DBusMessageIter arrayIter; dbus->lib->ffdbus_message_iter_recurse(&rootIter, &arrayIter); do { detectBluetoothProperty(device, dbus, &arrayIter); } while (dbus->lib->ffdbus_message_iter_next(&arrayIter)); dbus->lib->ffdbus_message_unref(properties); return NULL; } static const char* detectBluetooth(FFlist* devices) { FF_AUTO_CLOSE_DIR DIR* dirp = opendir("/sys/class/bluetooth"); if(dirp == NULL) return "Failed to open /sys/class/bluetooth"; FF_DBUS_AUTO_DESTROY_DATA FFDBusData dbus = {}; const char* error = ffDBusLoadData(DBUS_BUS_SYSTEM, &dbus); if(error) return error; struct dirent* entry; while ((entry = readdir(dirp)) != NULL) { if (entry->d_name[0] == '.') continue; if (strchr(entry->d_name, ':') != NULL) // ignore connected devices continue; FFBluetoothRadioResult* device = ffListAdd(devices); ffStrbufInit(&device->name); ffStrbufInit(&device->address); ffStrbufInitStatic(&device->vendor, "Unknown"); device->lmpVersion = INT_MIN; device->lmpSubversion = INT_MIN; device->enabled = false; detectBluetoothRoot(device, entry->d_name, &dbus); } return NULL; } #endif const char* ffDetectBluetoothRadio(FFlist* devices /* FFBluetoothRadioResult */) { #ifdef FF_HAVE_DBUS return detectBluetooth(devices); #else FF_UNUSED(devices) return "Fastfetch was compiled without DBus support"; #endif } ================================================ FILE: src/detection/bluetoothradio/bluetoothradio_nosupport.c ================================================ #include "bluetoothradio.h" const char* ffDetectBluetoothRadio(FF_MAYBE_UNUSED FFlist* devices /* FFBluetoothRadioResult */) { return "Not supported on this platform"; } ================================================ FILE: src/detection/bluetoothradio/bluetoothradio_windows.c ================================================ #include "bluetoothradio.h" #include "common/library.h" #include "common/io.h" #include "common/windows/unicode.h" #include #include #include // #include #define BTH_IOCTL_BASE 0 #define BTH_CTL(id) CTL_CODE(FILE_DEVICE_BLUETOOTH, (id), METHOD_BUFFERED, FILE_ANY_ACCESS) #define IOCTL_BTH_GET_LOCAL_INFO BTH_CTL(BTH_IOCTL_BASE+0x00) #define LMP_LE_SUPPORTED(x) ((x >> 38) & 1) typedef struct _BTH_RADIO_INFO { // Supported LMP features of the radio. Use LMP_XXX() to extract // the desired bits. ULONGLONG lmpSupportedFeatures; // Manufacturer ID (possibly BTH_MFG_XXX) USHORT mfg; // LMP subversion USHORT lmpSubversion; // LMP version UCHAR lmpVersion; } __attribute__((__packed__)) BTH_RADIO_INFO; typedef struct _BTH_LOCAL_RADIO_INFO { // Local BTH_ADDR, class of device, and radio name BTH_DEVICE_INFO localInfo; // Combo of LOCAL_RADIO_XXX values ULONG flags; // HCI revision, see core spec USHORT hciRevision; // HCI version, see core spec UCHAR hciVersion; // More information about the local radio (LMP, MFG) BTH_RADIO_INFO radioInfo; } __attribute__((__packed__)) BTH_LOCAL_RADIO_INFO; static_assert(sizeof(BTH_LOCAL_RADIO_INFO) == 292, "BTH_LOCAL_RADIO_INFO should be 292 bytes"); #pragma GCC diagnostic ignored "-Wpointer-sign" const char* ffDetectBluetoothRadio(FFlist* devices /* FFBluetoothRadioResult */) { // Actually bluetoothapis.dll, but it's missing on Windows 7 FF_LIBRARY_LOAD_MESSAGE(bluetoothapis, "bluetoothapis.dll", 1) FF_LIBRARY_LOAD_SYMBOL_MESSAGE(bluetoothapis, BluetoothFindFirstRadio) FF_LIBRARY_LOAD_SYMBOL_MESSAGE(bluetoothapis, BluetoothFindNextRadio) FF_LIBRARY_LOAD_SYMBOL_MESSAGE(bluetoothapis, BluetoothFindRadioClose) FF_LIBRARY_LOAD_SYMBOL_MESSAGE(bluetoothapis, BluetoothIsConnectable) FF_LIBRARY_LOAD_SYMBOL_MESSAGE(bluetoothapis, BluetoothIsDiscoverable) HANDLE hRadio = NULL; HBLUETOOTH_DEVICE_FIND hFind = ffBluetoothFindFirstRadio(&(BLUETOOTH_FIND_RADIO_PARAMS) { .dwSize = sizeof(BLUETOOTH_FIND_RADIO_PARAMS) }, &hRadio); if(!hFind) { if (GetLastError() == ERROR_NO_MORE_ITEMS) return "No Bluetooth radios found or service disabled"; else return "BluetoothFindFirstRadio() failed"; } do { BTH_LOCAL_RADIO_INFO blri; DWORD returned; if (!DeviceIoControl(hRadio, IOCTL_BTH_GET_LOCAL_INFO, NULL, 0, &blri, sizeof(blri), &returned, NULL)) continue; FFBluetoothRadioResult* device = ffListAdd(devices); ffStrbufInitS(&device->name, blri.localInfo.name); BLUETOOTH_ADDRESS_STRUCT addr = { .ullLong = blri.localInfo.address }; ffStrbufInitF(&device->address, "%02X:%02X:%02X:%02X:%02X:%02X", addr.rgBytes[5], addr.rgBytes[4], addr.rgBytes[3], addr.rgBytes[2], addr.rgBytes[1], addr.rgBytes[0]); device->lmpVersion = blri.radioInfo.lmpVersion; device->lmpSubversion = blri.radioInfo.lmpSubversion; ffStrbufInitStatic(&device->vendor, ffBluetoothRadioGetVendor(blri.radioInfo.mfg)); device->enabled = true; device->connectable = ffBluetoothIsConnectable(hRadio); device->discoverable = ffBluetoothIsDiscoverable(hRadio); NtClose(hRadio); } while (ffBluetoothFindNextRadio(hFind, &hRadio)); ffBluetoothFindRadioClose(hFind); return NULL; } ================================================ FILE: src/detection/board/board.h ================================================ #pragma once #include "fastfetch.h" #include "modules/board/option.h" typedef struct FFBoardResult { FFstrbuf name; FFstrbuf vendor; FFstrbuf version; FFstrbuf serial; } FFBoardResult; const char* ffDetectBoard(FFBoardResult* board); ================================================ FILE: src/detection/board/board_android.c ================================================ #include "board.h" #include "common/settings.h" const char* ffDetectBoard(FFBoardResult* board) { if (!ffSettingsGetAndroidProperty("ro.product.board", &board->name)) ffSettingsGetAndroidProperty("ro.board.platform", &board->name); return NULL; } ================================================ FILE: src/detection/board/board_apple.c ================================================ #include "board.h" #include "common/apple/cf_helpers.h" const char* ffDetectBoard(FFBoardResult* result) { FF_IOOBJECT_AUTO_RELEASE io_registry_entry_t service = IOServiceGetMatchingService(MACH_PORT_NULL, IOServiceMatching("IOPlatformExpertDevice")); if (!service) return "No IOPlatformExpertDevice found"; FF_CFTYPE_AUTO_RELEASE CFTypeRef boardId = IORegistryEntryCreateCFProperty(service, CFSTR("board-id"), kCFAllocatorDefault, kNilOptions); if (boardId) ffCfStrGetString(boardId, &result->name); else { io_name_t name; if (IORegistryEntryGetName(service, name) == kIOReturnSuccess) ffStrbufSetS(&result->name, name); } FF_CFTYPE_AUTO_RELEASE CFStringRef version = IORegistryEntryCreateCFProperty(service, CFSTR("version"), kCFAllocatorDefault, kNilOptions); if (version) ffCfStrGetString(version, &result->version); FF_CFTYPE_AUTO_RELEASE CFTypeRef manufacturer = IORegistryEntryCreateCFProperty(service, CFSTR("manufacturer"), kCFAllocatorDefault, kNilOptions); if (manufacturer) ffCfStrGetString(manufacturer, &result->vendor); return NULL; } ================================================ FILE: src/detection/board/board_bsd.c ================================================ #include "board.h" #include "common/settings.h" #include "common/smbiosHelper.h" const char* ffDetectBoard(FFBoardResult* result) { ffSettingsGetFreeBSDKenv("smbios.planar.product", &result->name); ffCleanUpSmbiosValue(&result->name); ffSettingsGetFreeBSDKenv("smbios.planar.serial", &result->serial); ffCleanUpSmbiosValue(&result->serial); ffSettingsGetFreeBSDKenv("smbios.planar.maker", &result->vendor); ffCleanUpSmbiosValue(&result->vendor); ffSettingsGetFreeBSDKenv("smbios.planar.version", &result->version); ffCleanUpSmbiosValue(&result->version); return NULL; } ================================================ FILE: src/detection/board/board_linux.c ================================================ #include "board.h" #include "common/io.h" #include "common/smbiosHelper.h" const char* ffDetectBoard(FFBoardResult* board) { if (ffGetSmbiosValue("/sys/devices/virtual/dmi/id/board_name", "/sys/class/dmi/id/board_name", &board->name)) { ffGetSmbiosValue("/sys/devices/virtual/dmi/id/board_serial", "/sys/class/dmi/id/board_serial", &board->serial); ffGetSmbiosValue("/sys/devices/virtual/dmi/id/board_vendor", "/sys/class/dmi/id/board_vendor", &board->vendor); ffGetSmbiosValue("/sys/devices/virtual/dmi/id/board_version", "/sys/class/dmi/id/board_version", &board->version); } else if (ffReadFileBuffer("/sys/firmware/devicetree/base/smbios/smbios/baseboard/product", &board->name)) { ffStrbufTrimRight(&board->name, '\0'); if (ffReadFileBuffer("/sys/firmware/devicetree/base/smbios/smbios/baseboard/manufacturer", &board->vendor)) ffStrbufTrimRight(&board->vendor, '\0'); } else if (ffReadFileBuffer("/sys/firmware/devicetree/base/board", &board->name)) { ffStrbufTrimRightSpace(&board->name); } else if (ffReadFileBuffer("/sys/firmware/devicetree/base/compatible", &board->vendor)) { uint32_t comma = ffStrbufFirstIndexC(&board->vendor, ','); if (comma < board->vendor.length) { ffStrbufSetS(&board->name, board->vendor.chars + comma + 1); ffStrbufTrimRightSpace(&board->name); ffStrbufSubstrBefore(&board->vendor, comma); } } return NULL; } ================================================ FILE: src/detection/board/board_nbsd.c ================================================ #include "board.h" #include "common/sysctl.h" #include "common/smbiosHelper.h" const char* ffDetectBoard(FFBoardResult* board) { if (ffSysctlGetString("machdep.dmi.board-product", &board->name) == NULL) ffCleanUpSmbiosValue(&board->name); if (ffSysctlGetString("machdep.dmi.board-version", &board->version) == NULL) ffCleanUpSmbiosValue(&board->version); if (ffSysctlGetString("machdep.dmi.board-vendor", &board->vendor) == NULL) ffCleanUpSmbiosValue(&board->vendor); if (ffSysctlGetString("machdep.dmi.board-serial", &board->serial) == NULL) ffCleanUpSmbiosValue(&board->serial); return NULL; } ================================================ FILE: src/detection/board/board_nosupport.c ================================================ #include "board.h" const char* ffDetectBoard(FF_MAYBE_UNUSED FFBoardResult* board) { return "Not supported on this platform"; } ================================================ FILE: src/detection/board/board_windows.c ================================================ #include "board.h" #include "common/smbiosHelper.h" typedef struct FFSmbiosBaseboard { FFSmbiosHeader Header; uint8_t Manufacturer; // string uint8_t Product; // string uint8_t Version; // string uint8_t SerialNumber; // string uint8_t AssetTag; // string uint8_t FeatureFlags; // bit field uint8_t LocationInChassis; // string uint16_t ChassisHandle; // varies uint8_t BoardType; // enum uint8_t NumberOfContainedObjectHandles; // varies uint16_t ContainedObjectHandles[]; // varies } __attribute__((__packed__)) FFSmbiosBaseboard; static_assert(offsetof(FFSmbiosBaseboard, ContainedObjectHandles) == 0x0F, "FFSmbiosBaseboard: Wrong struct alignment"); const char* ffDetectBoard(FFBoardResult* board) { const FFSmbiosHeaderTable* smbiosTable = ffGetSmbiosHeaderTable(); if (!smbiosTable) return "Failed to get SMBIOS data"; const FFSmbiosBaseboard* data = (const FFSmbiosBaseboard*) (*smbiosTable)[FF_SMBIOS_TYPE_BASEBOARD_INFO]; if (!data) return "Baseboard information section is not found in SMBIOS data"; const char* strings = (const char*) data + data->Header.Length; ffStrbufSetStatic(&board->name, ffSmbiosLocateString(strings, data->Product)); ffCleanUpSmbiosValue(&board->name); ffStrbufSetStatic(&board->serial, ffSmbiosLocateString(strings, data->SerialNumber)); ffCleanUpSmbiosValue(&board->serial); ffStrbufSetStatic(&board->vendor, ffSmbiosLocateString(strings, data->Manufacturer)); ffCleanUpSmbiosValue(&board->vendor); ffStrbufSetStatic(&board->version, ffSmbiosLocateString(strings, data->Version)); ffCleanUpSmbiosValue(&board->version); return NULL; } ================================================ FILE: src/detection/bootmgr/bootmgr.c ================================================ #include "efi_helper.h" static inline uint8_t evBits(uint16_t val, uint8_t mask, uint8_t shift) { return (uint8_t) ((val & (mask << shift)) >> shift); } static void ffEfiUcs2ToUtf8(const uint16_t *const chars, FFstrbuf* result) { for (uint32_t i = 0; chars[i]; i++) { if (chars[i] <= 0x007f) ffStrbufAppendC(result, (char) chars[i]); else if (chars[i] > 0x007f && chars[i] <= 0x07ff) { ffStrbufAppendC(result, (char) (0xc0 | evBits(chars[i], 0x1f, 6))); ffStrbufAppendC(result, (char) (0x80 | evBits(chars[i], 0x3f, 0))); } else { ffStrbufAppendC(result, (char) (0xe0 | evBits(chars[i], 0xf, 12))); ffStrbufAppendC(result, (char) (0x80 | evBits(chars[i], 0x3f, 6))); ffStrbufAppendC(result, (char) (0x80 | evBits(chars[i], 0x3f, 0))); } } } bool ffEfiFillLoadOption(const FFEfiLoadOption* efiOption, FFBootmgrResult* result) { uint32_t descLen = 0; while (efiOption->Description[descLen]) ++descLen; if (descLen) ffEfiUcs2ToUtf8(efiOption->Description, &result->name); for ( ffEfiDevicePathProtocol* filePathList = (ffEfiDevicePathProtocol*) &efiOption->Description[descLen + 1]; filePathList->Type != 0x7F; // End of Hardware Device Path filePathList = (ffEfiDevicePathProtocol*) ((uint8_t*) filePathList + filePathList->Length)) { if (filePathList->Type == 4 && filePathList->SubType == 4) { // https://uefi.org/specs/UEFI/2.10/10_Protocols_Device_Path_Protocol.html#file-path-media-device-path ffEfiUcs2ToUtf8((uint16_t*) filePathList->SpecificDevicePathData, &result->firmware); return true; } } return false; } ================================================ FILE: src/detection/bootmgr/bootmgr.h ================================================ #pragma once #include "fastfetch.h" #include "modules/bootmgr/option.h" typedef struct FFBootmgrResult { FFstrbuf name; FFstrbuf firmware; uint16_t order; bool secureBoot; } FFBootmgrResult; const char* ffDetectBootmgr(FFBootmgrResult* bios); ================================================ FILE: src/detection/bootmgr/bootmgr_apple.c ================================================ #include "bootmgr.h" #include "common/io.h" #include "common/apple/cf_helpers.h" #include static const char* detectSecureBoot(bool* result) { #if __aarch64__ FF_IOOBJECT_AUTO_RELEASE io_registry_entry_t entryDevice = IORegistryEntryFromPath(MACH_PORT_NULL, "IODeviceTree:/chosen"); if (!entryDevice) return "IORegistryEntryFromPath() failed"; FF_CFTYPE_AUTO_RELEASE CFTypeRef prop = IORegistryEntryCreateCFProperty(entryDevice, CFSTR("secure-boot"), kCFAllocatorDefault, 0); if (!prop) return "IORegistryEntryCreateCFProperty() failed"; if (CFGetTypeID(prop) != CFDataGetTypeID() || CFDataGetLength((CFDataRef) prop) == 0) return "Invalid secure-boot property"; *result = (bool) *CFDataGetBytePtr((CFDataRef) prop); #else FF_IOOBJECT_AUTO_RELEASE io_registry_entry_t entryDevice = IORegistryEntryFromPath(MACH_PORT_NULL, "IODeviceTree:/options"); if (!entryDevice) return "IORegistryEntryFromPath() failed"; FF_CFTYPE_AUTO_RELEASE CFTypeRef prop = IORegistryEntryCreateCFProperty(entryDevice, CFSTR("94b73556-2197-4702-82a8-3e1337dafbfb:AppleSecureBootPolicy"), kCFAllocatorDefault, 0); if (!prop) return "IORegistryEntryCreateCFProperty() failed"; if (CFGetTypeID(prop) != CFDataGetTypeID() || CFDataGetLength((CFDataRef) prop) == 0) return "Invalid secure-boot property"; *result = *CFDataGetBytePtr((CFDataRef) prop) != 0x02 /* Permissive Security */; #endif return NULL; } const char* ffDetectBootmgr(FFBootmgrResult* result) { if (ffPathExists("/System/Library/CoreServices/boot.efi", FF_PATHTYPE_FILE)) ffStrbufSetStatic(&result->firmware, "/System/Library/CoreServices/boot.efi"); ffStrbufSetStatic(&result->name, "iBoot"); detectSecureBoot(&result->secureBoot); return NULL; } ================================================ FILE: src/detection/bootmgr/bootmgr_bsd.c ================================================ #include "bootmgr.h" #include "efi_helper.h" #include "common/io.h" #ifdef __OpenBSD__ #include #else #include #endif #include #include #include #ifdef __NetBSD__ typedef uint16_t efi_char; #endif #ifndef EFI_GLOBAL_VARIABLE #define EFI_GLOBAL_VARIABLE { 0x8be4df61, 0x93ca, 0x11d2, 0xaa, 0x0d, { 0x00, 0xe0, 0x98, 0x03, 0x2b, 0x8c } } #endif const char* ffDetectBootmgr(FFBootmgrResult* result) { FF_AUTO_CLOSE_FD int efifd = open("/dev/efi", O_RDWR | O_CLOEXEC); if (efifd < 0) return "open(/dev/efi) failed"; alignas(uint16_t) uint8_t buffer[2048]; struct efi_var_ioc ioc = { .vendor = EFI_GLOBAL_VARIABLE, .data = buffer, }; ioc.datasize = sizeof(buffer); ioc.name = (efi_char[]){ 'B', 'o', 'o', 't', 'C', 'u', 'r', 'r', 'e', 'n', 't', '\0' }; ioc.namesize = sizeof("BootCurrent") * 2; if (ioctl(efifd, EFIIOC_VAR_GET, &ioc) < 0 || ioc.datasize != 2) return "ioctl(EFIIOC_VAR_GET, BootCurrent) failed"; result->order = *(uint16_t*)buffer; unsigned char hex[5]; snprintf((char*) hex, sizeof(hex), "%04X", result->order); ioc.datasize = sizeof(buffer); ioc.name = (efi_char[]){ 'B', 'o', 'o', 't', hex[0], hex[1], hex[2], hex[3], '\0' }; ioc.namesize = sizeof("Boot####") * 2; if (ioctl(efifd, EFIIOC_VAR_GET, &ioc) < 0 || ioc.datasize == sizeof(buffer)) return "ioctl(EFIIOC_VAR_GET, Boot####) failed"; ffEfiFillLoadOption((FFEfiLoadOption *)buffer, result); ioc.name = (efi_char[]){ 'S', 'e', 'c', 'u', 'r', 'e', 'B', 'o', 'o', 't', '\0' }; ioc.namesize = sizeof("SecureBoot") * 2; ioc.datasize = sizeof(buffer); if (ioctl(efifd, EFIIOC_VAR_GET, &ioc) == 0 && ioc.datasize == 1) result->secureBoot = !!buffer[0]; return NULL; } ================================================ FILE: src/detection/bootmgr/bootmgr_linux.c ================================================ #include "bootmgr.h" #include "common/io.h" #include "efi_helper.h" #include #define FF_EFIVARS_PATH_PREFIX "/sys/firmware/efi/efivars/" const char* ffDetectBootmgr(FFBootmgrResult* result) { alignas(uint16_t) uint8_t buffer[2048]; if (ffReadFileData(FF_EFIVARS_PATH_PREFIX "BootCurrent-" FF_EFI_GLOBAL_GUID, sizeof(buffer), buffer) != 6) return "Failed to read efivar: BootCurrent"; result->order = *(uint16_t *)&buffer[4]; snprintf((char*) buffer, sizeof(buffer), FF_EFIVARS_PATH_PREFIX "Boot%04X-" FF_EFI_GLOBAL_GUID, result->order); ssize_t size = ffReadFileData((const char*) buffer, sizeof(buffer), buffer); if (size < 5 + (int) sizeof(FFEfiLoadOption) || size == (ssize_t) sizeof(buffer)) return "Failed to read efivar: Boot####"; ffEfiFillLoadOption((FFEfiLoadOption *)&buffer[4], result); if (ffReadFileData(FF_EFIVARS_PATH_PREFIX "SecureBoot-" FF_EFI_GLOBAL_GUID, sizeof(buffer), buffer) >= 5) result->secureBoot = buffer[4] == 1; return NULL; } ================================================ FILE: src/detection/bootmgr/bootmgr_nosupport.c ================================================ #include "bootmgr.h" const char* ffDetectBootmgr(FF_MAYBE_UNUSED FFBootmgrResult* result) { return "Not supported on this platform"; } ================================================ FILE: src/detection/bootmgr/bootmgr_windows.c ================================================ #include "bootmgr.h" #include "efi_helper.h" #include "common/io.h" #include "common/windows/nt.h" #include #include const char* enablePrivilege(const wchar_t* privilege) { FF_AUTO_CLOSE_FD HANDLE token = NULL; if (!NT_SUCCESS(NtOpenProcessToken(NtCurrentProcess(), TOKEN_ADJUST_PRIVILEGES, &token))) return "NtOpenProcessToken() failed"; TOKEN_PRIVILEGES tp = { .PrivilegeCount = 1, .Privileges = { (LUID_AND_ATTRIBUTES) { .Attributes = SE_PRIVILEGE_ENABLED } }, }; if (!LookupPrivilegeValueW(NULL, privilege, &tp.Privileges[0].Luid)) return "LookupPrivilegeValue() failed"; NTSTATUS status = NtAdjustPrivilegesToken(token, false, &tp, sizeof(tp), NULL, NULL); if (!NT_SUCCESS(status)) return "NtAdjustPrivilegesToken() failed"; if (status == STATUS_NOT_ALL_ASSIGNED) return "The token does not have the specified privilege; try sudo please"; return NULL; } const char* ffDetectBootmgr(FFBootmgrResult* result) { const char* err = enablePrivilege(L"SeSystemEnvironmentPrivilege"); if (err != NULL) return err; GUID efiGlobalGuid; if (!NT_SUCCESS(RtlGUIDFromString(&(UNICODE_STRING) RTL_CONSTANT_STRING(L"{" FF_EFI_GLOBAL_GUID L"}"), &efiGlobalGuid))) return "RtlGUIDFromString() failed"; ULONG size = sizeof(result->order); if (!NT_SUCCESS(NtQuerySystemEnvironmentValueEx(&(UNICODE_STRING) RTL_CONSTANT_STRING(L"BootCurrent"), &efiGlobalGuid, &result->order, &size, NULL))) return "NtQuerySystemEnvironmentValueEx(BootCurrent) failed"; if (size != sizeof(result->order)) return "NtQuerySystemEnvironmentValueEx(BootCurrent) returned unexpected size"; uint8_t buffer[2048]; wchar_t key[9]; swprintf(key, ARRAY_SIZE(key), L"Boot%04X", result->order); size = sizeof(buffer); if (!NT_SUCCESS(NtQuerySystemEnvironmentValueEx(&(UNICODE_STRING) RTL_CONSTANT_STRING(key), &efiGlobalGuid, buffer, &size, NULL))) return "NtQuerySystemEnvironmentValueEx(Boot####) failed"; if (size < sizeof(FFEfiLoadOption) || size == ARRAY_SIZE(buffer)) return "NtQuerySystemEnvironmentValueEx(Boot####) returned unexpected size"; ffEfiFillLoadOption((FFEfiLoadOption *)buffer, result); SYSTEM_SECUREBOOT_INFORMATION ssi; if (NT_SUCCESS(NtQuerySystemInformation(SystemSecureBootInformation, &ssi, sizeof(ssi), NULL))) result->secureBoot = ssi.SecureBootEnabled; return NULL; } ================================================ FILE: src/detection/bootmgr/efi_helper.h ================================================ #include "bootmgr.h" // https://uefi.org/specs/UEFI/2.10/10_Protocols_Device_Path_Protocol.html#generic-device-path-structures typedef struct ffEfiDevicePathProtocol { uint8_t Type; uint8_t SubType; uint16_t Length; uint8_t SpecificDevicePathData[]; } ffEfiDevicePathProtocol; // https://uefi.org/specs/UEFI/2.10/03_Boot_Manager.html#load-options typedef struct FFEfiLoadOption { uint32_t Attributes; uint16_t FilePathListLength; uint16_t Description[]; // ffEfiDevicePathProtocol FilePathList[]; // uint8_t OptionalData[]; } FFEfiLoadOption; bool ffEfiFillLoadOption(const FFEfiLoadOption* efiOption, FFBootmgrResult* result); #define FF_EFI_GLOBAL_GUID "8be4df61-93ca-11d2-aa0d-00e098032b8c" ================================================ FILE: src/detection/brightness/brightness.h ================================================ #pragma once #include "fastfetch.h" #include "modules/brightness/option.h" typedef struct FFBrightnessResult { FFstrbuf name; double min, max, current; bool builtin; } FFBrightnessResult; const char* ffDetectBrightness(FFBrightnessOptions* options, FFlist* result); // list of FFBrightnessResult ================================================ FILE: src/detection/brightness/brightness_apple.c ================================================ #include "brightness.h" #include "detection/displayserver/displayserver.h" #include "common/apple/cf_helpers.h" #include "common/edidHelper.h" #include // DDC/CI #ifdef __aarch64__ typedef CFTypeRef IOAVServiceRef; extern IOAVServiceRef IOAVServiceCreate(CFAllocatorRef allocator) __attribute__((weak_import)); extern IOAVServiceRef IOAVServiceCreateWithService(CFAllocatorRef allocator, io_service_t service) __attribute__((weak_import)); extern IOReturn IOAVServiceCopyEDID(IOAVServiceRef service, CFDataRef* x2) __attribute__((weak_import)); extern IOReturn IOAVServiceReadI2C(IOAVServiceRef service, uint32_t chipAddress, uint32_t offset, void* outputBuffer, uint32_t outputBufferSize) __attribute__((weak_import)); extern IOReturn IOAVServiceWriteI2C(IOAVServiceRef service, uint32_t chipAddress, uint32_t dataAddress, void* inputBuffer, uint32_t inputBufferSize) __attribute__((weak_import)); #else // DDC/CI (Intel) #include #include #include extern void CGSServiceForDisplayNumber(CGDirectDisplayID display, io_service_t* service) __attribute__((weak_import)); #endif // ACPI extern int DisplayServicesGetBrightness(CGDirectDisplayID display, float *brightness) __attribute__((weak_import)); // Works for internal display static const char* detectWithDisplayServices(const FFDisplayServerResult* displayServer, FFlist* result) { if(DisplayServicesGetBrightness == NULL) return "DisplayServices function DisplayServicesGetBrightness is not available"; FF_LIST_FOR_EACH(FFDisplayResult, display, displayServer->displays) { if (display->type == FF_DISPLAY_TYPE_BUILTIN || display->type == FF_DISPLAY_TYPE_UNKNOWN) { float value; if(DisplayServicesGetBrightness((CGDirectDisplayID) display->id, &value) == kCGErrorSuccess) { FFBrightnessResult* brightness = (FFBrightnessResult*) ffListAdd(result); brightness->current = value; brightness->max = 1; brightness->min = 0; ffStrbufInitCopy(&brightness->name, &display->name); brightness->builtin = true; } } } return NULL; } #ifdef __aarch64__ // https://github.com/waydabber/m1ddc // Works for Apple Silicon and USB-C adapter connection ( but not HTMI ) static const char* detectWithDdcci(FF_MAYBE_UNUSED const FFDisplayServerResult* displayServer, FFBrightnessOptions* options, FFlist* result) { if (!IOAVServiceCreate || !IOAVServiceReadI2C || !IOAVServiceWriteI2C) return "IOAVService is not available"; FF_IOOBJECT_AUTO_RELEASE io_iterator_t iterator = IO_OBJECT_NULL; if (IOServiceGetMatchingServices(MACH_PORT_NULL, IOServiceMatching("DCPAVServiceProxy"), &iterator) != kIOReturnSuccess) return "IOServiceGetMatchingServices() failed"; io_registry_entry_t registryEntry; while ((registryEntry = IOIteratorNext(iterator)) != IO_OBJECT_NULL) { FF_CFTYPE_AUTO_RELEASE IOAVServiceRef service = NULL; { FF_IOOBJECT_AUTO_RELEASE io_registry_entry_t entryAv = registryEntry; FF_CFTYPE_AUTO_RELEASE CFBooleanRef IOAVServiceUserInterfaceSupported = IORegistryEntryCreateCFProperty(entryAv, CFSTR("IOAVServiceUserInterfaceSupported"), kCFAllocatorDefault, kNilOptions); if (IOAVServiceUserInterfaceSupported && !CFBooleanGetValue(IOAVServiceUserInterfaceSupported)) { // IOAVServiceCreateWithService won't work continue; } FF_CFTYPE_AUTO_RELEASE CFStringRef location = IORegistryEntryCreateCFProperty(entryAv, CFSTR("Location"), kCFAllocatorDefault, kNilOptions); if (location && CFStringCompare(location, CFSTR("Embedded"), 0) == 0) { // Builtin display should be handled by DisplayServices continue; } service = IOAVServiceCreateWithService(kCFAllocatorDefault, (io_service_t) registryEntry); if (!service) continue; } { uint8_t i2cIn[4] = { 0x82, 0x01, 0x10 /* luminance */ }; i2cIn[3] = 0x6e ^ i2cIn[0] ^ i2cIn[1] ^ i2cIn[2]; for (uint32_t i = 0; i < 2; ++i) { IOAVServiceWriteI2C(service, 0x37, 0x51, i2cIn, ARRAY_SIZE(i2cIn)); usleep(options->ddcciSleep * 1000); } } uint8_t i2cOut[12] = {}; if (IOAVServiceReadI2C(service, 0x37, 0x51, i2cOut, ARRAY_SIZE(i2cOut)) == KERN_SUCCESS) { if (i2cOut[2] != 0x02 || i2cOut[3] != 0x00) continue; uint32_t current = ((uint32_t) i2cOut[8] << 8u) + (uint32_t) i2cOut[9]; uint32_t max = ((uint32_t) i2cOut[6] << 8u) + (uint32_t) i2cOut[7]; FFBrightnessResult* brightness = (FFBrightnessResult*) ffListAdd(result); brightness->max = max; brightness->min = 0; brightness->current = current; ffStrbufInit(&brightness->name); brightness->builtin = false; uint8_t edid[128] = {}; if (IOAVServiceReadI2C(service, 0x50, 0x00, edid, ARRAY_SIZE(edid)) == KERN_SUCCESS) ffEdidGetName(edid, &brightness->name); } } return NULL; } #else static IOOptionBits getSupportedTransactionType(void) { FF_IOOBJECT_AUTO_RELEASE io_iterator_t iterator = IO_OBJECT_NULL; if (IOServiceGetMatchingServices(MACH_PORT_NULL, IOServiceNameMatching("IOFramebufferI2CInterface"), &iterator) != KERN_SUCCESS) return kIOI2CNoTransactionType; io_registry_entry_t registryEntry; while ((registryEntry = IOIteratorNext(iterator)) != MACH_PORT_NULL) { FF_IOOBJECT_AUTO_RELEASE io_service_t io_service = registryEntry; FF_CFTYPE_AUTO_RELEASE CFNumberRef IOI2CTransactionTypes = IORegistryEntryCreateCFProperty(io_service, CFSTR(kIOI2CTransactionTypesKey), kCFAllocatorDefault, kNilOptions); if (IOI2CTransactionTypes) { int64_t types = 0; ffCfNumGetInt64(IOI2CTransactionTypes, &types); if (types) { if ((1 << kIOI2CDDCciReplyTransactionType) & (uint64_t) types) return kIOI2CDDCciReplyTransactionType; if ((1 << kIOI2CSimpleTransactionType) & (uint64_t) types) return kIOI2CSimpleTransactionType; } } break; } return kIOI2CNoTransactionType; } static const char* detectWithDdcci(const FFDisplayServerResult* displayServer, FFBrightnessOptions* options, FFlist* result) { if (!CGSServiceForDisplayNumber) return "CGSServiceForDisplayNumber is not available"; IOOptionBits transactionType = getSupportedTransactionType(); if (transactionType == kIOI2CNoTransactionType) return "No supported IOI2C transaction type found"; FF_LIST_FOR_EACH(FFDisplayResult, display, displayServer->displays) { if (display->type == FF_DISPLAY_TYPE_EXTERNAL) { FF_IOOBJECT_AUTO_RELEASE io_service_t framebuffer = IO_OBJECT_NULL; CGSServiceForDisplayNumber((CGDirectDisplayID)display->id, &framebuffer); if (framebuffer == IO_OBJECT_NULL) continue; IOItemCount count; if (IOFBGetI2CInterfaceCount(framebuffer, &count) != KERN_SUCCESS || count == 0) continue; for (IOItemCount bus = 0; bus < count; ++bus) { FF_IOOBJECT_AUTO_RELEASE io_service_t interface = IO_OBJECT_NULL; if (IOFBCopyI2CInterfaceForBus(framebuffer, bus, &interface) != KERN_SUCCESS) continue; uint8_t i2cOut[12] = {}; IOI2CConnectRef connect = NULL; if (IOI2CInterfaceOpen(interface, kNilOptions, &connect) != KERN_SUCCESS) continue; uint8_t i2cIn[] = { 0x51, 0x82, 0x01, 0x10 /* luminance */, 0 }; i2cIn[4] = 0x6E ^ i2cIn[0] ^ i2cIn[1] ^ i2cIn[2] ^ i2cIn[3]; IOI2CRequest request = { .commFlags = kNilOptions, .sendAddress = 0x6e, .sendTransactionType = kIOI2CSimpleTransactionType, .sendBuffer = (vm_address_t) i2cIn, .sendBytes = ARRAY_SIZE(i2cIn), .minReplyDelay = options->ddcciSleep * 1000ULL, .replyAddress = 0x6F, .replySubAddress = 0x51, .replyTransactionType = transactionType, .replyBytes = ARRAY_SIZE(i2cOut), .replyBuffer = (vm_address_t) i2cOut, }; IOReturn ret = IOI2CSendRequest(connect, kNilOptions, &request); IOI2CInterfaceClose(connect, kNilOptions); if (ret != KERN_SUCCESS || request.result != kIOReturnSuccess || request.replyBytes < 10) continue; if (i2cOut[2] != 0x02 || i2cOut[3] != 0x00) continue; uint32_t current = ((uint32_t) i2cOut[8] << 8u) + (uint32_t) i2cOut[9]; uint32_t max = ((uint32_t) i2cOut[6] << 8u) + (uint32_t) i2cOut[7]; FFBrightnessResult* brightness = (FFBrightnessResult*) ffListAdd(result); brightness->max = max; brightness->min = 0; brightness->current = current; ffStrbufInitCopy(&brightness->name, &display->name); brightness->builtin = false; break; } } } return NULL; } #endif const char* ffDetectBrightness(FFBrightnessOptions* options, FFlist* result) { const FFDisplayServerResult* displayServer = ffConnectDisplayServer(); detectWithDisplayServices(displayServer, result); if (displayServer->displays.length > result->length) detectWithDdcci(displayServer, options, result); return NULL; } ================================================ FILE: src/detection/brightness/brightness_bsd.c ================================================ #include "brightness.h" #if __has_include() #include "common/io.h" #include #include #include #include const char* ffDetectBrightness(FF_MAYBE_UNUSED FFBrightnessOptions* options, FFlist* result) { // https://man.freebsd.org/cgi/man.cgi?query=backlight&sektion=9 char path[] = "/dev/backlight/backlight0"; for (char i = '0'; i <= '9'; ++i) { path[ARRAY_SIZE(path) - 2] = i; FF_AUTO_CLOSE_FD int blfd = open(path, O_RDONLY | O_CLOEXEC); if (blfd < 0) continue; struct backlight_props status; if(ioctl(blfd, BACKLIGHTGETSTATUS, &status) < 0) continue; FFBrightnessResult* brightness = (FFBrightnessResult*) ffListAdd(result); ffStrbufInit(&brightness->name); brightness->max = BACKLIGHTMAXLEVELS; brightness->min = 0; brightness->current = status.brightness; brightness->builtin = true; struct backlight_info info; if(ioctl(blfd, BACKLIGHTGETINFO, &info) == 0) ffStrbufAppendS(&brightness->name, info.name); else ffStrbufAppendS(&brightness->name, path + strlen("/dev/backlight/")); } return NULL; } #else const char* ffDetectBrightness(FF_MAYBE_UNUSED FFBrightnessOptions* options, FF_MAYBE_UNUSED FFlist* result) { return "Backlight is supported only on FreeBSD 13 and newer"; } #endif ================================================ FILE: src/detection/brightness/brightness_linux.c ================================================ #include "brightness.h" #include "common/io.h" #include "common/edidHelper.h" #include "common/stringUtils.h" #include #include static const char* detectWithBacklight(FFlist* result) { //https://www.kernel.org/doc/Documentation/ABI/stable/sysfs-class-backlight const char* backlightDirPath = "/sys/class/backlight/"; DIR* dirp = opendir(backlightDirPath); if(dirp == NULL) return "Failed to open `/sys/class/backlight/`"; FF_STRBUF_AUTO_DESTROY backlightDir = ffStrbufCreateA(64); ffStrbufAppendS(&backlightDir, backlightDirPath); uint32_t backlightDirLength = backlightDir.length; FF_STRBUF_AUTO_DESTROY buffer = ffStrbufCreate(); struct dirent* entry; while((entry = readdir(dirp)) != NULL) { if(entry->d_name[0] == '.') continue; ffStrbufAppendS(&backlightDir, entry->d_name); ffStrbufAppendS(&backlightDir, "/brightness"); if(ffReadFileBuffer(backlightDir.chars, &buffer)) { double actualBrightness = ffStrbufToDouble(&buffer, 0); ffStrbufSubstrBefore(&backlightDir, backlightDirLength); ffStrbufAppendS(&backlightDir, entry->d_name); ffStrbufAppendS(&backlightDir, "/max_brightness"); if(ffReadFileBuffer(backlightDir.chars, &buffer)) { FFBrightnessResult* brightness = (FFBrightnessResult*) ffListAdd(result); ffStrbufSubstrBeforeLastC(&backlightDir, '/'); ffStrbufAppendS(&backlightDir, "/device"); ffStrbufInitA(&brightness->name, PATH_MAX); if(realpath(backlightDir.chars, brightness->name.chars)) { ffStrbufRecalculateLength(&brightness->name); // if we managed to get edid, use it ffStrbufAppendS(&brightness->name, "/edid"); uint8_t edidData[128]; if(ffReadFileData(brightness->name.chars, ARRAY_SIZE(edidData), edidData) == ARRAY_SIZE(edidData)) { ffStrbufClear(&brightness->name); ffEdidGetName(edidData, &brightness->name); } else { ffStrbufSubstrBeforeLastC(&brightness->name, '/'); // remove "/edid" ffStrbufSubstrAfterLastC(&brightness->name, '/'); // try getting DRM connector name if(ffCharIsDigit(brightness->name.chars[0])) { // PCI address or some unknown path, give up ffStrbufSetS(&brightness->name, entry->d_name); } else { if(ffStrbufStartsWithS(&brightness->name, "card") && ffCharIsDigit(brightness->name.chars[4])) ffStrbufSubstrAfterFirstC(&brightness->name, '-'); } } } else ffStrbufInitS(&brightness->name, entry->d_name); brightness->max = ffStrbufToDouble(&buffer, 0); brightness->min = 0; brightness->current = actualBrightness; brightness->builtin = true; } } ffStrbufSubstrBefore(&backlightDir, backlightDirLength); } closedir(dirp); return NULL; } #ifdef FF_HAVE_DDCUTIL #include "detection/displayserver/displayserver.h" #include "common/library.h" #include "common/mallocHelper.h" #include #include // Try to be compatible with ddcutil 2.0 #if DDCUTIL_VMAJOR >= 2 double ddca_set_default_sleep_multiplier(double multiplier); // ddcutil 1.4 #else DDCA_Status ddca_init(const char *libopts, int syslog_level, int opts); #endif static const char* detectWithDdcci(FF_MAYBE_UNUSED FFBrightnessOptions* options, FFlist* result) { FF_LIBRARY_LOAD_MESSAGE(libddcutil, "libddcutil" FF_LIBRARY_EXTENSION, 5); FF_LIBRARY_LOAD_SYMBOL_MESSAGE(libddcutil, ddca_get_display_info_list2) FF_LIBRARY_LOAD_SYMBOL_MESSAGE(libddcutil, ddca_open_display2) FF_LIBRARY_LOAD_SYMBOL_MESSAGE(libddcutil, ddca_get_any_vcp_value_using_explicit_type) FF_LIBRARY_LOAD_SYMBOL_MESSAGE(libddcutil, ddca_free_any_vcp_value) FF_LIBRARY_LOAD_SYMBOL_MESSAGE(libddcutil, ddca_close_display) #ifndef FF_DISABLE_DLOPEN FF_LIBRARY_LOAD_SYMBOL_LAZY(libddcutil, ddca_init) if (ffddca_init) { FF_SUPPRESS_IO(); // Ref: https://github.com/rockowitz/ddcutil/issues/344 if (ffddca_init(NULL, -1 /*DDCA_SYSLOG_NOT_SET*/, 1 /*DDCA_INIT_OPTIONS_DISABLE_CONFIG_FILE*/) < 0) return "ddca_init() failed"; } else { FF_LIBRARY_LOAD_SYMBOL_LAZY(libddcutil, ddca_set_default_sleep_multiplier); if (ffddca_set_default_sleep_multiplier) ffddca_set_default_sleep_multiplier(options->ddcciSleep / 40.0); libddcutil = NULL; // Don't dlclose libddcutil. See https://github.com/rockowitz/ddcutil/issues/330 } #else #if DDCUTIL_VMAJOR >= 2 if (ddca_init(NULL, -1 /*DDCA_SYSLOG_NOT_SET*/, 1 /*DDCA_INIT_OPTIONS_DISABLE_CONFIG_FILE*/) < 0) return "ddca_init() failed"; #else ddca_set_default_sleep_multiplier(options->ddcciSleep / 40.0); #endif #endif FF_AUTO_FREE DDCA_Display_Info_List* infoList = NULL; if (ffddca_get_display_info_list2(false, &infoList) < 0) return "ddca_get_display_info_list2(false, &infoList) failed"; if (infoList->ct == 0) return "No DDC/CI compatible displays found"; for (int index = 0; index < infoList->ct; ++index) { const DDCA_Display_Info* display = &infoList->info[index]; DDCA_Display_Handle handle; if (ffddca_open_display2(display->dref, false, &handle) >= 0) { DDCA_Any_Vcp_Value* vcpValue = NULL; if (ffddca_get_any_vcp_value_using_explicit_type(handle, 0x10 /*brightness*/, DDCA_NON_TABLE_VCP_VALUE, &vcpValue) >= 0) { assert(vcpValue->value_type == DDCA_NON_TABLE_VCP_VALUE); int current = VALREC_CUR_VAL(vcpValue), max = VALREC_MAX_VAL(vcpValue); ffddca_free_any_vcp_value(vcpValue); FFBrightnessResult* brightness = (FFBrightnessResult*) ffListAdd(result); brightness->max = max; brightness->min = 0; brightness->current = current; ffStrbufInitS(&brightness->name, display->model_name); } ffddca_close_display(handle); } } return NULL; } #endif const char* ffDetectBrightness(FF_MAYBE_UNUSED FFBrightnessOptions* options, FFlist* result) { detectWithBacklight(result); #ifdef FF_HAVE_DDCUTIL const FFDisplayServerResult* displayServer = ffConnectDisplayServer(); if (result->length < displayServer->displays.length) detectWithDdcci(options, result); #endif return NULL; } ================================================ FILE: src/detection/brightness/brightness_nbsd.c ================================================ #include "brightness.h" #include "common/sysctl.h" const char* ffDetectBrightness(FF_MAYBE_UNUSED FFBrightnessOptions* options, FFlist* result) { // https://man.netbsd.org/NetBSD-10.1/acpiout.4#DESCRIPTION char key[] = "hw.acpi.acpiout0.brightness"; char* pn = key + strlen("hw.acpi.acpiout"); for (uint32_t i = 0; i <= 9; ++i) { *pn = (char) ('0' + i); int value = ffSysctlGetInt(key, -1); if (value == -1) continue; FFBrightnessResult* brightness = (FFBrightnessResult*) ffListAdd(result); ffStrbufInitF(&brightness->name, "acpiout%d", i); brightness->max = 100; brightness->min = 0; brightness->current = value; brightness->builtin = true; } return NULL; } ================================================ FILE: src/detection/brightness/brightness_nosupport.c ================================================ #include "brightness.h" const char* ffDetectBrightness(FF_MAYBE_UNUSED FFBrightnessOptions* options, FF_MAYBE_UNUSED FFlist* result) { return "Not supported on this platform"; } ================================================ FILE: src/detection/brightness/brightness_obsd.c ================================================ #include "brightness.h" #include "common/io.h" #include #include #include #include const char* ffDetectBrightness(FF_MAYBE_UNUSED FFBrightnessOptions* options, FFlist* result) { char path[] = "/dev/ttyCX"; for (char i = '0'; i <= '9'; ++i) { path[strlen("/dev/ttyC")] = i; FF_AUTO_CLOSE_FD int devfd = open(path, O_RDONLY | O_CLOEXEC); if (devfd < 0) { if (errno == EACCES && i == '0') return "Permission denied when opening tty device"; if (errno == ENOENT) break; continue; } struct wsdisplay_param param = { .param = WSDISPLAYIO_PARAM_BRIGHTNESS, }; if (ioctl(devfd, WSDISPLAYIO_GETPARAM, ¶m) < 0) continue; FFBrightnessResult* brightness = (FFBrightnessResult*) ffListAdd(result); ffStrbufInitF(&brightness->name, "ttyC%c", i); brightness->max = param.max; brightness->min = param.min; brightness->current = param.curval; brightness->builtin = true; } return NULL; } ================================================ FILE: src/detection/brightness/brightness_windows.cpp ================================================ 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 = (FFBrightnessResult*) ffListAdd(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 = (FFBrightnessResult*) ffListAdd(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_MAYBE_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; } ================================================ FILE: src/detection/btrfs/btrfs.h ================================================ #pragma once #include "fastfetch.h" #include "modules/btrfs/option.h" typedef struct FFBtrfsDiskUsage { uint64_t total; uint64_t used; const char* type; const char* profile; // single / dup / raidx uint8_t copies; } FFBtrfsDiskUsage; typedef struct FFBtrfsResult { FFstrbuf name; FFstrbuf uuid; FFstrbuf devices; FFstrbuf features; uint32_t generation; uint32_t nodeSize; uint32_t sectorSize; uint64_t totalSize; uint64_t globalReservationUsed; uint64_t globalReservationTotal; FFBtrfsDiskUsage allocation[3]; } FFBtrfsResult; const char* ffDetectBtrfs(FFlist* result /* list of FFBtrfsResult */); ================================================ FILE: src/detection/btrfs/btrfs_linux.c ================================================ #include "btrfs.h" #include "common/io.h" #include enum { uuidLen = (uint32_t) __builtin_strlen("00000000-0000-0000-0000-000000000000") }; static const char* enumerateDevices(FFBtrfsResult* item, int dfd, FFstrbuf* buffer) { int subfd = openat(dfd, "devices", O_RDONLY | O_CLOEXEC | O_DIRECTORY); if (subfd < 0) return "openat(\"/sys/fs/btrfs/UUID/devices\") == -1"; FF_AUTO_CLOSE_DIR DIR* dirp = fdopendir(subfd); if(dirp == NULL) { close(subfd); return "fdopendir(\"/sys/fs/btrfs/UUID/devices\") == NULL"; } struct dirent* entry; while ((entry = readdir(dirp)) != NULL) { if (entry->d_name[0] == '.') continue; if (item->devices.length) ffStrbufAppendC(&item->devices, ','); ffStrbufAppendS(&item->devices, entry->d_name); char path[sizeof(entry->d_name) + sizeof("/size") + 1]; snprintf(path, ARRAY_SIZE(path), "%s/size", entry->d_name); if (ffReadFileBufferRelative(subfd, path, buffer)) item->totalSize += ffStrbufToUInt(buffer, 0) * 512; } return NULL; } static const char* enumerateFeatures(FFBtrfsResult* item, int dfd) { int subfd = openat(dfd, "features", O_RDONLY | O_CLOEXEC | O_DIRECTORY); if (subfd < 0) return "openat(\"/sys/fs/btrfs/UUID/features\") == -1"; FF_AUTO_CLOSE_DIR DIR* dirp = fdopendir(subfd); if(dirp == NULL) return "fdopendir(\"/sys/fs/btrfs/UUID/features\") == NULL"; struct dirent* entry; while ((entry = readdir(dirp)) != NULL) { if (entry->d_name[0] == '.') continue; if (item->features.length) ffStrbufAppendC(&item->features, ','); ffStrbufAppendS(&item->features, entry->d_name); } return NULL; } static const char* detectAllocation(FFBtrfsResult* item, int dfd, FFstrbuf* buffer) { FF_AUTO_CLOSE_FD int subfd = openat(dfd, "allocation", O_RDONLY | O_CLOEXEC | O_PATH | O_DIRECTORY); if (subfd < 0) return "openat(\"/sys/fs/btrfs/UUID/allocation\") == -1"; if (ffReadFileBufferRelative(subfd, "global_rsv_size", buffer)) item->globalReservationTotal = ffStrbufToUInt(buffer, 0); else return "ffReadFileBuffer(\"/sys/fs/btrfs/UUID/allocation/global_rsv_size\") == NULL"; if (ffReadFileBufferRelative(subfd, "global_rsv_reserved", buffer)) item->globalReservationUsed = ffStrbufToUInt(buffer, 0); item->globalReservationUsed = item->globalReservationTotal - item->globalReservationUsed; #define FF_BTRFS_DETECT_PROFILE(_index, _type, _profile, _copies) \ else if (faccessat(subfd, _type "/" _profile "/", F_OK, 0) == 0) { \ item->allocation[_index].profile = _profile; \ item->allocation[_index].copies = _copies; \ } #define FF_BTRFS_DETECT_TYPE(_index, _type) \ do { \ item->allocation[_index].type = _type; \ if (ffReadFileBufferRelative(subfd, _type "/total_bytes", buffer)) \ item->allocation[_index].total = ffStrbufToUInt(buffer, 0); \ \ if (ffReadFileBufferRelative(subfd, _type "/bytes_used", buffer)) \ item->allocation[_index].used = ffStrbufToUInt(buffer, 0); \ \ if (false) {} \ FF_BTRFS_DETECT_PROFILE(_index, _type, "single", 1) \ FF_BTRFS_DETECT_PROFILE(_index, _type, "dup", 2) \ FF_BTRFS_DETECT_PROFILE(_index, _type, "raid0", 1) \ FF_BTRFS_DETECT_PROFILE(_index, _type, "raid1", 2) \ FF_BTRFS_DETECT_PROFILE(_index, _type, "raid10", 2) \ FF_BTRFS_DETECT_PROFILE(_index, _type, "raid1c3", 3) \ FF_BTRFS_DETECT_PROFILE(_index, _type, "raid1c4", 4) \ FF_BTRFS_DETECT_PROFILE(_index, _type, "raid5", 1) /* (n-1)/n */ \ FF_BTRFS_DETECT_PROFILE(_index, _type, "raid6", 1) /* (n-2)/n */ \ else { \ item->allocation[_index].profile = "unknown"; \ item->allocation[_index].copies = 1; \ } \ } while (0) FF_BTRFS_DETECT_TYPE(0, "data"); FF_BTRFS_DETECT_TYPE(1, "metadata"); FF_BTRFS_DETECT_TYPE(2, "system"); #undef FF_BTRFS_DETECT_TYPE return NULL; } const char* ffDetectBtrfs(FFlist* result) { FF_AUTO_CLOSE_DIR DIR* dirp = opendir("/sys/fs/btrfs/"); if(dirp == NULL) return "opendir(\"/sys/fs/btrfs\") == NULL"; FF_STRBUF_AUTO_DESTROY buffer = ffStrbufCreate(); struct dirent* entry; while ((entry = readdir(dirp)) != NULL) { if (entry->d_name[0] == '.') continue; if (strlen(entry->d_name) != uuidLen) continue; FFBtrfsResult* item = ffListAdd(result); (*item) = (FFBtrfsResult){ .uuid = ffStrbufCreateNS(uuidLen, entry->d_name), .name = ffStrbufCreate(), .devices = ffStrbufCreate(), .features = ffStrbufCreate(), }; FF_AUTO_CLOSE_FD int dfd = openat(dirfd(dirp), entry->d_name, O_RDONLY | O_CLOEXEC | O_PATH | O_DIRECTORY); if (dfd < 0) continue; if (ffAppendFileBufferRelative(dfd, "label", &item->name)) ffStrbufTrimRightSpace(&item->name); enumerateDevices(item, dfd, &buffer); enumerateFeatures(item, dfd); if (ffReadFileBufferRelative(dfd, "generation", &buffer)) item->generation = (uint32_t) ffStrbufToUInt(&buffer, 0); if (ffReadFileBufferRelative(dfd, "nodesize", &buffer)) item->nodeSize = (uint32_t) ffStrbufToUInt(&buffer, 0); if (ffReadFileBufferRelative(dfd, "sectorsize", &buffer)) item->sectorSize = (uint32_t) ffStrbufToUInt(&buffer, 0); detectAllocation(item, dfd, &buffer); } return NULL; } ================================================ FILE: src/detection/btrfs/btrfs_nosupport.c ================================================ #include "btrfs.h" const char* ffDetectBtrfs(FF_MAYBE_UNUSED FFlist* result) { return "Not supported on this platform"; } ================================================ FILE: src/detection/camera/camera.h ================================================ #pragma once #include "fastfetch.h" #include "modules/camera/option.h" typedef struct FFCameraResult { FFstrbuf name; FFstrbuf vendor; FFstrbuf id; FFstrbuf colorspace; uint32_t width; uint32_t height; } FFCameraResult; const char* ffDetectCamera(FFlist* result /* list of FFCameraResult */); ================================================ FILE: src/detection/camera/camera_android.c ================================================ #include "camera.h" #include "common/processing.h" #include "common/properties.h" #define FF_TERMUX_API_PATH FASTFETCH_TARGET_DIR_ROOT "/libexec/termux-api" #define FF_TERMUX_API_PARAM "CameraInfo" static inline void wrapYyjsonFree(yyjson_doc** doc) { assert(doc); if (*doc) yyjson_doc_free(*doc); } const char* ffDetectCamera(FF_MAYBE_UNUSED FFlist* result) { FF_STRBUF_AUTO_DESTROY buffer = ffStrbufCreate(); if(ffProcessAppendStdOut(&buffer, (char* const[]){ FF_TERMUX_API_PATH, FF_TERMUX_API_PARAM, NULL })) return "Starting `" FF_TERMUX_API_PATH " " FF_TERMUX_API_PARAM "` failed"; yyjson_doc* __attribute__((__cleanup__(wrapYyjsonFree))) doc = yyjson_read_opts(buffer.chars, buffer.length, 0, NULL, NULL); if (!doc) return "Failed to parse camera info"; yyjson_val* root = yyjson_doc_get_root(doc); if (!yyjson_is_arr(root)) return "Camera info result is not a JSON array"; yyjson_val* device; size_t idx, max; yyjson_arr_foreach(root, idx, max, device) { FFCameraResult* camera = (FFCameraResult*) ffListAdd(result); { const char* facing = yyjson_get_str(yyjson_obj_get(device, "facing")); if (facing) ffStrbufInitF(&camera->name, "builtin-%s", facing); else ffStrbufInitStatic(&camera->name, "Unknown"); } ffStrbufInit(&camera->vendor); ffStrbufInitJsonVal(&camera->id, yyjson_obj_get(device, "id")); yyjson_val* sizes = yyjson_arr_get_first(yyjson_obj_get(device, "jpeg_output_sizes")); if (yyjson_is_obj(sizes)) { camera->width = (uint32_t) yyjson_get_uint(yyjson_obj_get(sizes, "width")); camera->height = (uint32_t) yyjson_get_uint(yyjson_obj_get(sizes, "height")); } else camera->width = camera->height = 0; ffStrbufInit(&camera->colorspace); } return NULL; } ================================================ FILE: src/detection/camera/camera_apple.m ================================================ #include "camera.h" #include "common/io.h" #import // warning: 'AVCaptureDeviceTypeExternalUnknown' is deprecated #pragma GCC diagnostic ignored "-Wdeprecated-declarations" #ifdef MAC_OS_VERSION_14_0 // To make fastfetch compiled on newer macOS versions runs on older ones AVF_EXPORT __attribute__((weak_import)) AVCaptureDeviceType const AVCaptureDeviceTypeExternal; #endif const char* ffDetectCamera(FFlist* result) { #ifdef MAC_OS_X_VERSION_10_15 FF_SUPPRESS_IO(); // #822 AVCaptureDeviceType deviceType = NULL; #ifdef MAC_OS_VERSION_14_0 // Strangely `@available(macOS 14.0, *)` doesn't work here (#1594) if (@available(macOS 14.0, *)) { if (&AVCaptureDeviceTypeExternal) deviceType = AVCaptureDeviceTypeExternal; } #endif if (deviceType == NULL) deviceType = AVCaptureDeviceTypeExternalUnknown; AVCaptureDeviceDiscoverySession* session = [AVCaptureDeviceDiscoverySession discoverySessionWithDeviceTypes:@[AVCaptureDeviceTypeBuiltInWideAngleCamera, deviceType] mediaType:AVMediaTypeVideo position:AVCaptureDevicePositionUnspecified]; if (!session) return "Failed to create AVCaptureDeviceDiscoverySession"; for (AVCaptureDevice* device in session.devices) { FFCameraResult* camera = (FFCameraResult*) ffListAdd(result); ffStrbufInitS(&camera->name, device.localizedName.UTF8String); ffStrbufInitS(&camera->vendor, device.manufacturer.UTF8String); ffStrbufInitS(&camera->id, device.uniqueID.UTF8String); switch (device.activeColorSpace) { case AVCaptureColorSpace_sRGB: ffStrbufInitStatic(&camera->colorspace, "sRGB"); break; case AVCaptureColorSpace_P3_D65: ffStrbufInitStatic(&camera->colorspace, "P3-D65"); break; case 2 /*AVCaptureColorSpace_HLG_BT2020*/: ffStrbufInitStatic(&camera->colorspace, "BT2020-HLG"); break; case 3 /*AVCaptureColorSpace_AppleLog*/: ffStrbufInitStatic(&camera->colorspace, "AppleLog"); break; } CMVideoDimensions size = CMVideoFormatDescriptionGetDimensions(device.activeFormat.formatDescription); camera->width = size.width < 0 ? 0 : (uint32_t) size.width; camera->height = size.height < 0 ? 0 : (uint32_t) size.height; } return NULL; #else return "No support for old MacOS version"; #endif } ================================================ FILE: src/detection/camera/camera_linux.c ================================================ #include "camera.h" #include "common/io.h" #include #include #include #if FF_HAVE_LINUX_VIDEODEV2 #include #elif __has_include() // OpenBSD #include #define FF_HAVE_LINUX_VIDEODEV2 1 #endif const char* ffDetectCamera(FFlist* result) { #if FF_HAVE_LINUX_VIDEODEV2 char path[] = "/dev/videoN"; for (uint32_t i = 0; i <= 9; ++i) { path[ARRAY_SIZE(path) - 2] = (char) (i + '0'); FF_AUTO_CLOSE_FD int fd = open(path, O_RDONLY | O_CLOEXEC); if (fd < 0) { if (errno == ENOENT) break; if (errno == ENXIO) continue; return "Failed to open /dev/videoN"; } struct v4l2_capability cap = {}; if (ioctl(fd, VIDIOC_QUERYCAP, &cap) < 0 || !(cap.capabilities & V4L2_CAP_VIDEO_CAPTURE)) continue; struct v4l2_format fmt = { .type = V4L2_BUF_TYPE_VIDEO_CAPTURE }; if (ioctl(fd, VIDIOC_G_FMT, &fmt) < 0) continue; FFCameraResult* camera = (FFCameraResult*) ffListAdd(result); ffStrbufInitS(&camera->name, (const char*) cap.card); ffStrbufInit(&camera->vendor); ffStrbufInitS(&camera->id, (const char*) cap.bus_info); switch (fmt.fmt.pix.colorspace) { case V4L2_COLORSPACE_SMPTE170M: ffStrbufInitStatic(&camera->colorspace, "SMPTE 170M"); break; case V4L2_COLORSPACE_SMPTE240M: ffStrbufInitStatic(&camera->colorspace, "SMPTE 240M"); break; case V4L2_COLORSPACE_BT878: ffStrbufInitStatic(&camera->colorspace, "BT.878"); break; case V4L2_COLORSPACE_470_SYSTEM_M: ffStrbufInitStatic(&camera->colorspace, "NTSC"); break; case V4L2_COLORSPACE_470_SYSTEM_BG: ffStrbufInitStatic(&camera->colorspace, "EBU 3213"); break; case V4L2_COLORSPACE_JPEG: ffStrbufInitStatic(&camera->colorspace, "JPEG"); break; case V4L2_COLORSPACE_REC709: case V4L2_COLORSPACE_SRGB: ffStrbufInitStatic(&camera->colorspace, "sRGB"); break; case 9 /* V4L2_COLORSPACE_OPRGB */: ffStrbufInitStatic(&camera->colorspace, "Adobe RGB"); break; case 10 /* V4L2_COLORSPACE_BT2020 */: ffStrbufInitStatic(&camera->colorspace, "BT.2020"); break; case 11 /* V4L2_COLORSPACE_RAW */: ffStrbufInitStatic(&camera->colorspace, "RAW"); break; case 12 /* V4L2_COLORSPACE_DCI_P3 */: ffStrbufInitStatic(&camera->colorspace, "DCI-P3"); break; default: ffStrbufInit(&camera->colorspace); break; } camera->width = fmt.fmt.pix.width; camera->height = fmt.fmt.pix.height; } return NULL; #else FF_UNUSED(result); return "Fastfetch was compiled without "; #endif } ================================================ FILE: src/detection/camera/camera_nosupport.c ================================================ #include "camera.h" const char* ffDetectCamera(FF_MAYBE_UNUSED FFlist* result) { return "Not support on this platform"; } ================================================ FILE: src/detection/camera/camera_windows.cpp ================================================ extern "C" { #include "camera.h" #include "common/library.h" } #include "common/windows/com.hpp" #include "common/windows/unicode.hpp" #include "common/windows/util.hpp" #include #include #include extern "C" const char* ffDetectCamera(FF_MAYBE_UNUSED FFlist* result) { FF_LIBRARY_LOAD_MESSAGE(mfplat, "mfplat" FF_LIBRARY_EXTENSION, 1) FF_LIBRARY_LOAD_SYMBOL_MESSAGE(mfplat, MFCreateAttributes) FF_LIBRARY_LOAD_MESSAGE(mf, "mf" FF_LIBRARY_EXTENSION, 1) FF_LIBRARY_LOAD_SYMBOL_MESSAGE(mf, MFEnumDeviceSources) const char* error = ffInitCom(); if (error) return error; IMFAttributes* FF_AUTO_RELEASE_COM_OBJECT attrs = nullptr; if (FAILED(ffMFCreateAttributes(&attrs, 1))) return "MFCreateAttributes() failed"; if (FAILED(attrs->SetGUID( MF_DEVSOURCE_ATTRIBUTE_SOURCE_TYPE, MF_DEVSOURCE_ATTRIBUTE_SOURCE_TYPE_VIDCAP_GUID ))) return "SetGUID(MF_*) failed"; IMFActivate** devices = NULL; uint32_t count; if (FAILED(ffMFEnumDeviceSources(attrs, &devices, &count))) return "MFEnumDeviceSources() failed"; for (uint32_t i = 0; i < count; i++) { IMFActivate* FF_AUTO_RELEASE_COM_OBJECT device = devices[i]; wchar_t buffer[256]; uint32_t length = 0; if (FAILED(device->GetString(MF_DEVSOURCE_ATTRIBUTE_FRIENDLY_NAME, buffer, ARRAY_SIZE(buffer), &length)) || length == 0) continue; FFCameraResult* camera = (FFCameraResult*) ffListAdd(result); ffStrbufInitNWS(&camera->name, length, buffer); ffStrbufInit(&camera->colorspace); ffStrbufInit(&camera->vendor); ffStrbufInit(&camera->id); camera->width = 0; camera->height = 0; if (SUCCEEDED(device->GetString(MF_DEVSOURCE_ATTRIBUTE_SOURCE_TYPE_VIDCAP_SYMBOLIC_LINK, buffer, ARRAY_SIZE(buffer), &length)) && length > 0) ffStrbufSetNWS(&camera->id, length, buffer); IMFMediaSource* FF_AUTO_RELEASE_COM_OBJECT source = nullptr; if (FAILED(device->ActivateObject(IID_PPV_ARGS(&source)))) continue; on_scope_exit destroySource([&] { source->Shutdown(); }); IMFPresentationDescriptor* FF_AUTO_RELEASE_COM_OBJECT pd = nullptr; if (FAILED(source->CreatePresentationDescriptor(&pd))) continue; IMFStreamDescriptor* FF_AUTO_RELEASE_COM_OBJECT sd = NULL; BOOL selected; if (FAILED(pd->GetStreamDescriptorByIndex(0, &selected, &sd))) continue; IMFMediaTypeHandler* FF_AUTO_RELEASE_COM_OBJECT handler = NULL; if (FAILED(sd->GetMediaTypeHandler(&handler))) continue; DWORD mediaTypeCount; if (FAILED(handler->GetMediaTypeCount(&mediaTypeCount))) continue; // Assume first type is the maximum resolution IMFMediaType* type = NULL; for (DWORD idx = 0; SUCCEEDED(handler->GetMediaTypeByIndex(idx, &type)); ++idx) { on_scope_exit destroyType([=] { type->Release(); }); GUID majorType; if (FAILED(type->GetMajorType(&majorType)) || majorType != MFMediaType_Video) continue; MFVideoPrimaries primaries; static_assert(sizeof(primaries) == sizeof(uint32_t), ""); if (SUCCEEDED(type->GetUINT32(MF_MT_VIDEO_PRIMARIES, (uint32_t*) &primaries))) { switch (primaries) { case MFVideoPrimaries_BT709: ffStrbufSetStatic(&camera->colorspace, "sRGB"); break; case MFVideoPrimaries_BT470_2_SysM: case MFVideoPrimaries_BT470_2_SysBG: ffStrbufSetStatic(&camera->colorspace, "NTSC"); break; case MFVideoPrimaries_SMPTE170M: ffStrbufSetStatic(&camera->colorspace, "SMPTE 170M"); break; case MFVideoPrimaries_SMPTE240M: ffStrbufSetStatic(&camera->colorspace, "SMPTE 240M"); break; case MFVideoPrimaries_EBU3213: ffStrbufSetStatic(&camera->colorspace, "EBU 3213"); break; case MFVideoPrimaries_SMPTE_C: ffStrbufSetStatic(&camera->colorspace, "SMPTE C"); break; case MFVideoPrimaries_BT2020: ffStrbufSetStatic(&camera->colorspace, "BT.2020"); break; case MFVideoPrimaries_XYZ: ffStrbufSetStatic(&camera->colorspace, "XYZ"); break; case MFVideoPrimaries_DCI_P3: ffStrbufSetStatic(&camera->colorspace, "DCI-P3"); break; case MFVideoPrimaries_ACES: ffStrbufSetStatic(&camera->colorspace, "ACES"); break; default: break; } } MFGetAttributeSize(type, MF_MT_FRAME_SIZE, &camera->width, &camera->height); break; } } if (devices) CoTaskMemFree(devices); return nullptr; } ================================================ FILE: src/detection/chassis/chassis.c ================================================ #include "chassis.h" const char* ffChassisTypeToString(uint32_t type) { // https://www.dmtf.org/sites/default/files/standards/documents/DSP0134_3.7.0.pdf // 7.4.1 System Enclosure or Chassis Types switch (type & 0b01111111) { case 0x01: return "Other"; case 0x02: return "Unknown"; case 0x03: return "Desktop"; case 0x04: return "Low Profile Desktop"; case 0x05: return "Pizza Box"; case 0x06: return "Mini Tower"; case 0x07: return "Tower"; case 0x08: return "Portable"; case 0x09: return "Laptop"; case 0x0A: return "Notebook"; case 0x0B: return "Hand Held"; case 0x0C: return "Docking Station"; case 0x0D: return "All in One"; case 0x0E: return "Sub Notebook"; case 0x0F: return "Space-saving"; case 0x10: return "Lunch Box"; case 0x11: return "Main Server Chassis"; case 0x12: return "Expansion Chassis"; case 0x13: return "SubChassis"; case 0x14: return "Bus Expansion Chassis"; case 0x15: return "Peripheral Chassis"; case 0x16: return "RAID Chassis"; case 0x17: return "Rack Mount Chassis"; case 0x18: return "Sealed-case PC"; case 0x19: return "Multi-system chassis"; case 0x1A: return "Compact PCI"; case 0x1B: return "Advanced TCA"; case 0x1C: return "Blade"; case 0x1D: return "Mobile Workstation"; case 0x1E: return "Tablet"; case 0x1F: return "Convertible"; case 0x20: return "Detachable"; case 0x21: return "IoT Gateway"; case 0x22: return "Embedded PC"; case 0x23: return "Mini PC"; case 0x24: return "Stick PC"; default: return NULL; } } ================================================ FILE: src/detection/chassis/chassis.h ================================================ #pragma once #include "fastfetch.h" #include "modules/chassis/option.h" typedef struct FFChassisResult { FFstrbuf type; FFstrbuf serial; FFstrbuf vendor; FFstrbuf version; } FFChassisResult; const char* ffDetectChassis(FFChassisResult* result); const char* ffChassisTypeToString(uint32_t type); ================================================ FILE: src/detection/chassis/chassis_apple.c ================================================ #include "chassis.h" #include "detection/host/host.h" const char* ffDetectChassis(FFChassisResult* result) { FFHostResult host = { .family = ffStrbufCreate(), .name = ffStrbufCreate(), .version = ffStrbufCreate(), .sku = ffStrbufCreate(), .serial = ffStrbufCreate(), .uuid = ffStrbufCreate(), .vendor = ffStrbufCreate(), }; if (ffDetectHost(&host) != NULL) return "Failed to detect host"; if (ffStrbufStartsWithS(&host.name, "MacBook ")) ffStrbufSetStatic(&result->type, "Laptop"); else if (ffStrbufStartsWithS(&host.name, "Mac mini ") || ffStrbufStartsWithS(&host.name, "Mac Studio ")) ffStrbufSetStatic(&result->type, "Mini PC"); else if (ffStrbufStartsWithS(&host.name, "iMac ")) ffStrbufSetStatic(&result->type, "All in One"); else ffStrbufSetStatic(&result->type, "Desktop"); ffStrbufSet(&result->vendor, &host.vendor); ffStrbufDestroy(&host.family); ffStrbufDestroy(&host.name); ffStrbufDestroy(&host.version); ffStrbufDestroy(&host.sku); ffStrbufDestroy(&host.serial); ffStrbufDestroy(&host.uuid); ffStrbufDestroy(&host.vendor); return NULL; } ================================================ FILE: src/detection/chassis/chassis_bsd.c ================================================ #include "chassis.h" #include "common/settings.h" #include "common/smbiosHelper.h" const char* ffDetectChassis(FFChassisResult* result) { // Unlike other platforms, `smbios.chassis.type` return display string directly on my machine ffSettingsGetFreeBSDKenv("smbios.chassis.type", &result->type); ffCleanUpSmbiosValue(&result->type); ffSettingsGetFreeBSDKenv("smbios.chassis.maker", &result->vendor); ffCleanUpSmbiosValue(&result->vendor); ffSettingsGetFreeBSDKenv("smbios.chassis.serial", &result->serial); ffCleanUpSmbiosValue(&result->serial); ffSettingsGetFreeBSDKenv("smbios.chassis.version", &result->version); ffCleanUpSmbiosValue(&result->version); return NULL; } ================================================ FILE: src/detection/chassis/chassis_linux.c ================================================ #include "chassis.h" #include "common/io.h" #include "common/smbiosHelper.h" #include const char* ffDetectChassis(FFChassisResult* result) { if (ffGetSmbiosValue("/sys/devices/virtual/dmi/id/chassis_type", "/sys/class/dmi/id/chassis_type", &result->type)) { ffGetSmbiosValue("/sys/devices/virtual/dmi/id/chassis_serial", "/sys/class/dmi/id/chassis_serial", &result->serial); ffGetSmbiosValue("/sys/devices/virtual/dmi/id/chassis_vendor", "/sys/class/dmi/id/chassis_vendor", &result->vendor); ffGetSmbiosValue("/sys/devices/virtual/dmi/id/chassis_version", "/sys/class/dmi/id/chassis_version", &result->version); if(result->type.length) { const char* typeStr = ffChassisTypeToString((uint32_t) ffStrbufToUInt(&result->type, 9999)); if(typeStr) ffStrbufSetStatic(&result->type, typeStr); } } else { // Available on Asahi Linux uint32_t chassisType = 0; if (ffReadFileData("/sys/firmware/devicetree/base/smbios/smbios/chassis/chassis-type", sizeof(chassisType), &chassisType)) // big endian { chassisType = __builtin_bswap32(chassisType); const char* typeStr = ffChassisTypeToString(chassisType); if(typeStr) ffStrbufSetStatic(&result->type, typeStr); if(ffReadFileBuffer("/sys/firmware/devicetree/base/smbios/smbios/chassis/manufacturer", &result->vendor) && result->vendor.length > 0) ffStrbufTrimRight(&result->vendor, '\0'); } else if(ffReadFileBuffer("/sys/firmware/devicetree/base/chassis-type", &result->type) && result->type.length > 0) { ffStrbufTrimRight(&result->type, '\0'); result->type.chars[0] = (char) toupper(result->type.chars[0]); } } return NULL; } ================================================ FILE: src/detection/chassis/chassis_nbsd.c ================================================ #include "chassis.h" #include "common/sysctl.h" #include "common/smbiosHelper.h" const char* ffDetectChassis(FFChassisResult* chassis) { if (ffSysctlGetString("machdep.dmi.chassis-type", &chassis->type) == NULL) ffCleanUpSmbiosValue(&chassis->type); if (ffSysctlGetString("machdep.dmi.chassis-version", &chassis->version) == NULL) ffCleanUpSmbiosValue(&chassis->version); if (ffSysctlGetString("machdep.dmi.chassis-vendor", &chassis->vendor) == NULL) ffCleanUpSmbiosValue(&chassis->vendor); if (ffSysctlGetString("machdep.dmi.chassis-serial", &chassis->serial) == NULL) ffCleanUpSmbiosValue(&chassis->serial); return NULL; } ================================================ FILE: src/detection/chassis/chassis_nosupport.c ================================================ #include "chassis.h" const char* ffDetectChassis(FF_MAYBE_UNUSED FFChassisResult* result) { return "Not supported on this platform"; } ================================================ FILE: src/detection/chassis/chassis_windows.c ================================================ #include "chassis.h" #include "common/smbiosHelper.h" // 7.4 typedef struct FFSmbiosSystemEnclosure { FFSmbiosHeader Header; uint8_t Manufacturer; // string uint8_t Type; // varies uint8_t Version; // string uint8_t SerialNumber; // string uint8_t AssetTagNumber; // string // 2.1+ uint8_t BootupState; // enum uint8_t PowerSupplyState; // enum uint8_t ThermalState; // enum uint8_t SecurityStatus; // enum // 2.3+ uint32_t OEMDefined; // varies uint8_t Height; // varies uint8_t NumberOfPowerCords; // varies uint8_t ContainedElementCount; // varies uint8_t ContainedRecordLength; // varies uint8_t ContainedElements[]; // varies } __attribute__((__packed__)) FFSmbiosSystemEnclosure; static_assert(offsetof(FFSmbiosSystemEnclosure, ContainedElements) == 0x15, "FFSmbiosSystemEnclosure: Wrong struct alignment"); const char* ffDetectChassis(FFChassisResult* result) { const FFSmbiosHeaderTable* smbiosTable = ffGetSmbiosHeaderTable(); if (!smbiosTable) return "Failed to get SMBIOS data"; const FFSmbiosSystemEnclosure* data = (const FFSmbiosSystemEnclosure*) (*smbiosTable)[FF_SMBIOS_TYPE_SYSTEM_ENCLOSURE]; if (!data) return "System enclosure is not found in SMBIOS data"; const char* strings = (const char*) data + data->Header.Length; ffStrbufSetStatic(&result->vendor, ffSmbiosLocateString(strings, data->Manufacturer)); ffCleanUpSmbiosValue(&result->vendor); ffStrbufSetStatic(&result->serial, ffSmbiosLocateString(strings, data->SerialNumber)); ffCleanUpSmbiosValue(&result->serial); ffStrbufSetStatic(&result->version, ffSmbiosLocateString(strings, data->Version)); ffCleanUpSmbiosValue(&result->version); ffStrbufSetStatic(&result->type, ffChassisTypeToString(data->Type)); return NULL; } ================================================ FILE: src/detection/command/command.c ================================================ #include "detection/command/command.h" #include "common/processing.h" #include "common/FFstrbuf.h" typedef struct FFCommandResultBundle { FFProcessHandle handle; const char* error; } FFCommandResultBundle; // FIFO, non-thread-safe list of running commands static FFlist commandQueue = { .elementSize = sizeof(FFCommandResultBundle), }; static const char* spawnProcess(FFCommandOptions* options, FFProcessHandle* handle) { if (options->text.length == 0) return "No command text specified"; return ffProcessSpawn(options->param.length ? (char* const[]){ options->shell.chars, options->param.chars, options->text.chars, NULL } : (char* const[]){ options->shell.chars, options->text.chars, NULL }, options->useStdErr, handle); } bool ffPrepareCommand(FFCommandOptions* options) { if (!options->parallel) return false; FFCommandResultBundle* bundle = ffListAdd(&commandQueue); bundle->error = spawnProcess(options, &bundle->handle); return true; } const char* ffDetectCommand(FFCommandOptions* options, FFstrbuf* result) { FFCommandResultBundle bundle = {}; if (!options->parallel) bundle.error = spawnProcess(options, &bundle.handle); else if (!ffListShift(&commandQueue, &bundle)) return "[BUG] command queue is empty"; if (bundle.error) return bundle.error; bundle.error = ffProcessReadOutput(&bundle.handle, result); if (bundle.error) return bundle.error; ffStrbufTrimRightSpace(result); return NULL; } ================================================ FILE: src/detection/command/command.h ================================================ #pragma once #include "fastfetch.h" #include "modules/command/option.h" const char* ffDetectCommand(FFCommandOptions* options, FFstrbuf* result); ================================================ FILE: src/detection/cpu/cpu.c ================================================ #include "cpu.h" const char* ffDetectCPUImpl(const FFCPUOptions* options, FFCPUResult* cpu); const char* ffDetectCPU(const FFCPUOptions* options, FFCPUResult* cpu) { const char* error = ffDetectCPUImpl(options, cpu); if (error) return error; const char* removeStrings[] = { " CPU", " FPU", " APU", " Processor", " Dual-Core", " Quad-Core", " Six-Core", " Eight-Core", " Ten-Core", " 2-Core", " 4-Core", " 6-Core", " 8-Core", " 10-Core", " 12-Core", " 14-Core", " 16-Core" }; ffStrbufRemoveStrings(&cpu->name, ARRAY_SIZE(removeStrings), removeStrings); uint32_t radeonGraphics = ffStrbufFirstIndexS(&cpu->name, " w/ Radeon "); // w/ Radeon 780M Graphics if (radeonGraphics >= cpu->name.length) radeonGraphics = ffStrbufFirstIndexS(&cpu->name, " with Radeon "); if (radeonGraphics < cpu->name.length) ffStrbufSubstrBefore(&cpu->name, radeonGraphics); ffStrbufSubstrBeforeFirstC(&cpu->name, '@'); //Cut the speed output in the name as we append our own ffStrbufTrimRight(&cpu->name, ' '); //If we removed the @ in previous step there was most likely a space before it ffStrbufRemoveDupWhitespaces(&cpu->name); return NULL; } const char* ffCPUAppleCodeToName(uint32_t code) { // https://github.com/AsahiLinux/docs/wiki/Codenames switch (code) { case 8103: return "Apple M1"; case 6000: return "Apple M1 Pro"; case 6001: return "Apple M1 Max"; case 6002: return "Apple M1 Ultra"; case 8112: return "Apple M2"; case 6020: return "Apple M2 Pro"; case 6021: return "Apple M2 Max"; case 6022: return "Apple M2 Ultra"; case 8122: return "Apple M3"; case 6030: return "Apple M3 Pro"; case 6031: case 6034: return "Apple M3 Max"; case 8132: return "Apple M4"; case 6040: return "Apple M4 Pro"; case 6041: return "Apple M4 Max"; default: return NULL; } } const char* ffCPUQualcommCodeToName(uint32_t code) { // https://github.com/AsahiLinux/docs/wiki/Codenames switch (code) { case 7180: return "Qualcomm Snapdragon 7c"; case 7280: return "Qualcomm Snapdragon 7c+ Gen 3"; case 8180: return "Qualcomm Snapdragon 8cx Gen 2 5G"; case 8280: return "Qualcomm Snapdragon 8cx Gen 3"; default: return NULL; } } #if defined(__x86_64__) || defined(__i386__) #include void ffCPUDetectByCpuid(FFCPUResult* cpu) { uint32_t eax = 0, ebx = 0, ecx = 0, edx = 0; if (__get_cpuid(0x16, &eax, &ebx, &ecx, &edx)) { // WARNING: CPUID may report frequencies of efficient cores // cpuid returns 0 MHz when hypervisor is enabled if (eax) cpu->frequencyBase = eax; if (ebx) cpu->frequencyMax = ebx; } if (__get_cpuid(1, &eax, &ebx, &ecx, &edx)) { // Feature tests (leaf1.ecx, leaf7.ebx) bool sse2 = (ecx & bit_SSE2) != 0; bool sse4_2 = (ecx & bit_SSE4_2) != 0; bool pclmul = (ecx & bit_PCLMUL) != 0; bool popcnt = (ecx & bit_POPCNT) != 0; bool fma = (ecx & bit_FMA) != 0; bool osxsave = (ecx & bit_OSXSAVE) != 0; unsigned int eax7 = 0, ebx7 = 0, ecx7 = 0, edx7 = 0; __get_cpuid_count(7, 0, &eax7, &ebx7, &ecx7, &edx7); bool avx2 = (ebx7 & bit_AVX2) != 0; bool bmi2 = (ebx7 & bit_BMI2) != 0; bool avx512f = (ebx7 & bit_AVX512F) != 0; bool avx512bw = (ebx7 & bit_AVX512BW) != 0; bool avx512dq = (ebx7 & bit_AVX512DQ) != 0; // OS support for AVX/AVX512: check XGETBV (requires OSXSAVE) bool avx_os = false; bool avx512_os = false; if (osxsave) { __asm__ __volatile__( "xgetbv" : "=a"(eax), "=d"(edx) : "c"(0) : ); uint64_t xcr0 = ((uint64_t)edx << 32) | eax; // AVX requires XCR0[1:2] == 11b (XMM and YMM state) avx_os = (xcr0 & 0x6ULL) == 0x6ULL; // AVX512 requires XCR0[7,5,6] etc. common mask 0xE6 (bits 1,2,5,6,7) avx512_os = (xcr0 & 0xE6ULL) == 0xE6ULL; } cpu->march = "unknown"; if (avx512f && avx512bw && avx512dq && avx512_os) cpu->march = "x86_64-v4"; else if (avx2 && fma && bmi2 && avx_os) cpu->march = "x86_64-v3"; else if (sse4_2 && popcnt && pclmul) cpu->march = "x86_64-v2"; else if (sse2) cpu->march = "x86_64-v1"; } } #elif defined(__aarch64__) // This is not accurate because a lot of flags are optional from old versions // https://developer.arm.com/documentation/109697/2025_06/Feature-descriptions?lang=en // https://en.wikipedia.org/wiki/AArch64#ARM-A_(application_architecture) // Worth noting: Apple M1 is marked as ARMv8.5-A on Wikipedia, but it lacks BTI (mandatory in v8.5) #ifdef __linux__ #include "common/io.h" #include // #include void ffCPUDetectByCpuid(FFCPUResult* cpu) { char buf[PROC_FILE_BUFFSIZ]; ssize_t nRead = ffReadFileData("/proc/self/auxv", ARRAY_SIZE(buf), buf); if (nRead < (ssize_t) sizeof(Elf64_auxv_t)) return; uint64_t hwcap = 0, hwcap2 = 0; for (Elf64_auxv_t* auxv = (Elf64_auxv_t*)buf; (char*)auxv < buf + nRead; ++auxv) { if (auxv->a_type == AT_HWCAP) { hwcap = auxv->a_un.a_val; } else if (auxv->a_type == AT_HWCAP2) { hwcap2 = auxv->a_un.a_val; } } if (!hwcap) return; cpu->march = "unknown"; // ARMv8-A bool has_fp = (hwcap & (1 << 0) /* HWCAP_FP */) != 0; bool has_asimd = (hwcap & (1 << 1) /* HWCAP_ASIMD */) != 0; // ARMv8.1-A bool has_atomics = (hwcap & (1 << 8) /* HWCAP_ATOMICS */) != 0; // optional from v8.0 bool has_crc32 = (hwcap & (1 << 7) /* HWCAP_CRC32 */) != 0; // optional from v8.0 bool has_asimdrdm = (hwcap & (1 << 12) /* HWCAP_ASIMDRDM */) != 0; // optional from v8.0 // ARMv8.2-A bool has_fphp = (hwcap & (1 << 9) /* HWCAP_FPHP */) != 0; // optional bool has_dcpop = (hwcap & (1 << 16) /* HWCAP_DCPOP */) != 0; // DC CVAP, optional from v8.1 // ARMv8.3-A bool has_paca = (hwcap & (1 << 30) /* HWCAP_PACA */) != 0; // optional from v8.2 bool has_lrcpc = (hwcap & (1 << 15) /* HWCAP_LRCPC */) != 0; // optional from v8.2 bool has_fcma = (hwcap & (1 << 14) /* HWCAP_FCMA */) != 0; // optional from v8.2 bool has_jscvt = (hwcap & (1 << 13) /* HWCAP_JSCVT */) != 0; // optional from v8.2 // ARMv8.4-A bool has_dit = (hwcap & (1 << 24) /* HWCAP_DIT */) != 0; // optional from v8.3 bool has_flagm = (hwcap & (1 << 27) /* HWCAP_FLAGM */) != 0; // optional from v8.1 bool has_ilrcpc = (hwcap & (1 << 26) /* HWCAP_ILRCPC */) != 0; // optional from v8.2 // ARMv8.5-A bool has_bti = (hwcap2 & (1 << 17) /* HWCAP2_BTI */) != 0; // optional from v8.4 bool has_sb = (hwcap & (1 << 29) /* HWCAP_SB */) != 0; // optional from v8.0 bool has_dcpodp = (hwcap2 & (1 << 0) /* HWCAP2_DCPODP */) != 0; // optional from v8.1 bool has_flagm2 = (hwcap2 & (1 << 7) /* HWCAP2_FLAGM2 */) != 0; // optional from v8.4 bool has_frint = (hwcap2 & (1 << 8) /* HWCAP2_FRINT */) != 0; // optional from v8.4 // ARMv9.0-A bool has_sve2 = (hwcap2 & (1 << 1) /* HWCAP2_SVE2 */) != 0; // ARMv9.1-A // ARMv8.6-A bool has_bf16 = (hwcap2 & (1 << 14) /* HWCAP2_BF16 */) != 0; // optional from v8.2 bool has_i8mm = (hwcap2 & (1 << 13) /* HWCAP2_I8MM */) != 0; // optional from v8.1 // ARMv8.7-A bool has_afp = (hwcap2 & (1 << 20) /* HWCAP2_AFP */) != 0; // optional from v8.6 // ARMv9.2-A bool has_sme = (hwcap2 & (1 << 23) /* HWCAP2_SME */) != 0; // ARMv9.3-A bool has_sme2 = (hwcap2 & (1UL << 37) /* HWCAP2_SME2 */) != 0; // optional from v9.2 // ARMv8.8-A bool has_mops = (hwcap2 & (1UL << 43) /* HWCAP2_MOPS */) != 0; // optional from v8.7 // ARMv8.9-A bool has_cssc = (hwcap2 & (1UL << 34) /* HWCAP2_CSSC */) != 0; // optional from v8.7 // ARMv9.4-A bool has_sme2p1 = (hwcap2 & (1UL << 38) /* HWCAP2_SME2P1 */) != 0; // optional from v9.2 // ARMv9.5-A bool has_f8e4m3 = (hwcap2 & (1UL << 55) /* HWCAP2_F8E4M3 */) != 0; // optional from v9.2 bool has_f8e5m2 = (hwcap2 & (1UL << 56) /* HWCAP2_F8E5M2 */) != 0; // optional from v9.2 // ARMv9.6-A bool has_cmpbr = (hwcap & (1UL << 33) /* HWCAP_CMPBR */) != 0; // optional from v9.5 bool has_fprcvt = (hwcap & (1UL << 34) /* HWCAP_FPRCVT */) != 0; // optional from v9.5 if (has_sve2 || has_sme) { // ARMv9 if (has_cmpbr && has_fprcvt) { cpu->march = "ARMv9.6-A"; } else if (has_f8e5m2 && has_f8e4m3) { cpu->march = "ARMv9.5-A"; } else if (has_sme2p1) { cpu->march = "ARMv9.4-A"; } else if (has_sme2) { cpu->march = "ARMv9.3-A"; } else if (has_sme) { cpu->march = "ARMv9.2-A"; } else if (has_i8mm && has_bf16) { cpu->march = "ARMv9.1-A"; } else { cpu->march = "ARMv9.0-A"; } } else { // ARMv8 if (has_cssc) { cpu->march = "ARMv8.9-A"; } else if (has_mops) { cpu->march = "ARMv8.8-A"; } else if (has_afp) { cpu->march = "ARMv8.7-A"; } else if (has_i8mm && has_bf16) { cpu->march = "ARMv8.6-A"; } else if (has_bti && has_sb && has_dcpodp && has_flagm2 && has_frint) { cpu->march = "ARMv8.5-A"; } else if (has_dit && has_flagm && has_ilrcpc) { cpu->march = "ARMv8.4-A"; } else if (has_paca && has_lrcpc && has_fcma && has_jscvt) { cpu->march = "ARMv8.3-A"; } else if (has_fphp && has_dcpop) { cpu->march = "ARMv8.2-A"; } else if (has_atomics && has_crc32 && has_asimdrdm) { cpu->march = "ARMv8.1-A"; } else if (has_asimd && has_fp) { cpu->march = "ARMv8-A"; } } } #elif __APPLE__ #include // #include // Not available in macOS 14- void ffCPUDetectByCpuid(FFCPUResult* cpu) { uint64_t caps[2] = {0}; // 80-bit capability mask, split into two 64-bit values size_t size = sizeof(caps); if (sysctlbyname("hw.optional.arm.caps", caps, &size, NULL, 0) != 0) return; // Helper macro to test bit in 80-bit capability mask #define FF_HAS_CAP(bit) \ (((bit) < 64) ? ((caps[0] >> (bit)) & 1ULL) : ((caps[1] >> ((bit) - 64U)) & 1ULL)) cpu->march = "unknown"; // ARMv8-A bool has_fp = FF_HAS_CAP(50); /* CAP_BIT_AdvSIMD_HPFPCvt */ // Full FP16 support (implies FP/ASIMD) bool has_asimd = FF_HAS_CAP(49); /* CAP_BIT_AdvSIMD */ // Advanced SIMD (NEON) // ARMv8.1-A bool has_lse = FF_HAS_CAP(6); /* CAP_BIT_FEAT_LSE */ // Large System Extensions, optional in v8.0 bool has_crc32 = FF_HAS_CAP(51); /* CAP_BIT_FEAT_CRC32 */ // CRC32 instructions, optional in v8.0 bool has_rdm = FF_HAS_CAP(5); /* CAP_BIT_FEAT_RDM */ // AdvSIMD rounding double multiply accumulate, optional in v8.0 // ARMv8.2-A bool has_fp16 = FF_HAS_CAP(34); /* CAP_BIT_FEAT_FP16 */ // Half-precision FP support, optional bool has_dpb = FF_HAS_CAP(22); /* CAP_BIT_FEAT_DPB */ // DC CVAP, optional from v8.1 // ARMv8.3-A bool has_pauth = FF_HAS_CAP(19); /* CAP_BIT_FEAT_PAuth */ // Pointer Authentication (PAC), optional from v8.2 bool has_lrcpc = FF_HAS_CAP(15); /* CAP_BIT_FEAT_LRCPC */ // LDAPR/LR with RCPC semantics, optional from v8.2 bool has_fcma = FF_HAS_CAP(17); /* CAP_BIT_FEAT_FCMA */ // Complex number multiply-add, optional from v8.2 bool has_jscvt = FF_HAS_CAP(18); /* CAP_BIT_FEAT_JSCVT */ // JavaScript-style conversion (FJCVTZS), optional from v8.2 // ARMv8.4-A bool has_lse2 = FF_HAS_CAP(30); /* CAP_BIT_FEAT_LSE2 */ // Large System Extensions version 2, optional from v8.2 bool has_dit = FF_HAS_CAP(33); /* CAP_BIT_FEAT_DIT */ // Data Independent Timing, optional from v8.3 bool has_flagm = FF_HAS_CAP(0); /* CAP_BIT_FEAT_FlagM */ // Flag manipulation (FMOV/FCVT), optional from v8.1 bool has_lrcpc2 = FF_HAS_CAP(16); /* CAP_BIT_FEAT_LRCPC2 */ // Enhanced RCPC (LDAPUR/LDAPST), optional from v8.2 // ARMv8.5-A bool has_bti = FF_HAS_CAP(36); /* CAP_BIT_FEAT_BTI */ // Branch Target Identification, optional from v8.4 bool has_sb = FF_HAS_CAP(13); /* CAP_BIT_FEAT_SB */ // Speculative Barrier, optional from v8.0 bool has_dpb2 = FF_HAS_CAP(23); /* CAP_BIT_FEAT_DPB2 */ // DC CVADP (DPB2), optional from v8.1 bool has_flagm2 = FF_HAS_CAP(1); /* CAP_BIT_FEAT_FlagM2 */ // Enhanced FlagM, optional from v8.4 bool has_frintts = FF_HAS_CAP(14); /* CAP_BIT_FEAT_FRINTTS */ // Floating-point to integer instructions, optional from v8.4 // ARMv9.0-A bool has_sve2 = false; // Not exposed and not supported by Apple M4 // ARMv9.1-A // ARMv8.6-A bool has_bf16 = FF_HAS_CAP(24); /* CAP_BIT_FEAT_BF16 */ // Brain float16, optional from v8.2 bool has_i8mm = FF_HAS_CAP(25); /* CAP_BIT_FEAT_I8MM */ // Int8 Matrix Multiply, optional from v8.1 // ARMv8.7-A bool has_afp = FF_HAS_CAP(29); /* CAP_BIT_FEAT_AFP */ // Alternate FP16 (FEXPA), optional from v8.6 // ARMv9.2-A bool has_sme = FF_HAS_CAP(40); /* CAP_BIT_FEAT_SME */ // Scalable Matrix Extension, optional from v9.2 // ARMv9.3-A bool has_sme2 = FF_HAS_CAP(41); /* CAP_BIT_FEAT_SME2 */ // SME2, optional from v9.2 // ARMv8.8-A bool has_hbc = FF_HAS_CAP(64); /* CAP_BIT_FEAT_HBC */ // Hinted conditional branches, optional from v8.7 // ARMv8.9-A bool has_cssc = FF_HAS_CAP(67); /* CAP_BIT_FEAT_CSSC */ // Common Short String Compare, optional from v8.7 // ARMv9.4-A+ are not exposed yet if (has_sve2 || has_sme) { // ARMv9 family if (has_sme2) { cpu->march = "ARMv9.3-A"; } else if (has_sme) { cpu->march = "ARMv9.2-A"; } else if (has_i8mm && has_bf16) { cpu->march = "ARMv9.1-A"; } else { cpu->march = "ARMv9.0-A"; } } else { // ARMv8 family if (has_cssc) { cpu->march = "ARMv8.9-A"; } else if (has_hbc) { cpu->march = "ARMv8.8-A"; } else if (has_afp) { cpu->march = "ARMv8.7-A"; } else if (has_i8mm && has_bf16) { cpu->march = "ARMv8.6-A"; } else if (has_bti && has_sb && has_dpb2 && has_flagm2 && has_frintts) { cpu->march = "ARMv8.5-A"; } else if (has_lse2 && has_dit && has_flagm && has_lrcpc2) { cpu->march = "ARMv8.4-A"; } else if (has_pauth && has_lrcpc && has_fcma && has_jscvt) { cpu->march = "ARMv8.3-A"; } else if (has_fp16 && has_dpb) { cpu->march = "ARMv8.2-A"; } else if (has_lse && has_crc32 && has_rdm) { cpu->march = "ARMv8.1-A"; } else if (has_asimd && has_fp) { cpu->march = "ARMv8-A"; } } #undef HAS_CAP } #elif _WIN32 #include // Missing from winnt.h of MinGW-w64 #define PF_ARM_LSE2_AVAILABLE 62 #define PF_RESERVED_FEATURE 63 #define PF_ARM_SHA3_INSTRUCTIONS_AVAILABLE 64 #define PF_ARM_SHA512_INSTRUCTIONS_AVAILABLE 65 #define PF_ARM_V82_I8MM_INSTRUCTIONS_AVAILABLE 66 #define PF_ARM_V82_FP16_INSTRUCTIONS_AVAILABLE 67 #define PF_ARM_V86_BF16_INSTRUCTIONS_AVAILABLE 68 #define PF_ARM_V86_EBF16_INSTRUCTIONS_AVAILABLE 69 #define PF_ARM_SME_INSTRUCTIONS_AVAILABLE 70 #define PF_ARM_SME2_INSTRUCTIONS_AVAILABLE 71 #define PF_ARM_SME2_1_INSTRUCTIONS_AVAILABLE 72 #define PF_ARM_SME2_2_INSTRUCTIONS_AVAILABLE 73 #define PF_ARM_SME_AES_INSTRUCTIONS_AVAILABLE 74 #define PF_ARM_SME_SBITPERM_INSTRUCTIONS_AVAILABLE 75 #define PF_ARM_SME_SF8MM4_INSTRUCTIONS_AVAILABLE 76 #define PF_ARM_SME_SF8MM8_INSTRUCTIONS_AVAILABLE 77 #define PF_ARM_SME_SF8DP2_INSTRUCTIONS_AVAILABLE 78 #define PF_ARM_SME_SF8DP4_INSTRUCTIONS_AVAILABLE 79 #define PF_ARM_SME_SF8FMA_INSTRUCTIONS_AVAILABLE 80 #define PF_ARM_SME_F8F32_INSTRUCTIONS_AVAILABLE 81 #define PF_ARM_SME_F8F16_INSTRUCTIONS_AVAILABLE 82 #define PF_ARM_SME_F16F16_INSTRUCTIONS_AVAILABLE 83 #define PF_ARM_SME_B16B16_INSTRUCTIONS_AVAILABLE 84 #define PF_ARM_SME_F64F64_INSTRUCTIONS_AVAILABLE 85 #define PF_ARM_SME_I16I64_INSTRUCTIONS_AVAILABLE 86 #define PF_ARM_SME_LUTv2_INSTRUCTIONS_AVAILABLE 87 #define PF_ARM_SME_FA64_INSTRUCTIONS_AVAILABLE 88 void ffCPUDetectByCpuid(FFCPUResult* cpu) { // ARMv8-A bool has_vfp = IsProcessorFeaturePresent(PF_ARM_VFP_32_REGISTERS_AVAILABLE); // Implies basic FP support bool has_neon = IsProcessorFeaturePresent(PF_ARM_NEON_INSTRUCTIONS_AVAILABLE); // NEON (ASIMD) // ARMv8.1-A bool has_atomics = IsProcessorFeaturePresent(PF_ARM_V81_ATOMIC_INSTRUCTIONS_AVAILABLE); // LSE atomics bool has_crc32 = IsProcessorFeaturePresent(PF_ARM_V8_CRC32_INSTRUCTIONS_AVAILABLE); // CRC32 // ARMv8.2-A bool has_fp16 = IsProcessorFeaturePresent(PF_ARM_V82_FP16_INSTRUCTIONS_AVAILABLE); // Half-precision FP // ARMv8.3-A bool has_lrcpc = IsProcessorFeaturePresent(PF_ARM_V83_LRCPC_INSTRUCTIONS_AVAILABLE); // LDAPR/LR with RCPC semantics bool has_jscvt = IsProcessorFeaturePresent(PF_ARM_V83_JSCVT_INSTRUCTIONS_AVAILABLE); // FJCVTZS // ARMv8.4-A // My CPU (Apple M1 Pro in VM) does support LSE2, but Windows doesn't detect it for some reason bool has_lse2 = IsProcessorFeaturePresent(PF_ARM_LSE2_AVAILABLE); // Large System Extensions version 2, optional from v8.2 bool has_dp = IsProcessorFeaturePresent(PF_ARM_V82_DP_INSTRUCTIONS_AVAILABLE); // DotProd, optional from v8.1 (*) // ARMv9.0-A bool has_sve2 = IsProcessorFeaturePresent(PF_ARM_SVE2_INSTRUCTIONS_AVAILABLE); // SVE2 // ARMv9.1-A // ARMv8.6-A bool has_bf16 = IsProcessorFeaturePresent(PF_ARM_V86_BF16_INSTRUCTIONS_AVAILABLE); // BF16, optional from v8.2 bool has_i8mm = IsProcessorFeaturePresent(PF_ARM_V82_I8MM_INSTRUCTIONS_AVAILABLE); // Int8 matrix multiply, optional from v8.2 // ARMv8.7-A bool has_ebf16 = IsProcessorFeaturePresent(PF_ARM_V86_EBF16_INSTRUCTIONS_AVAILABLE); // Extended BFloat16 behaviors, optional from v8.2 // ARMv9.2-A bool has_sme = IsProcessorFeaturePresent(PF_ARM_SME_INSTRUCTIONS_AVAILABLE); // SME // ARMv9.3-A bool has_sme2 = IsProcessorFeaturePresent(PF_ARM_SME2_INSTRUCTIONS_AVAILABLE); // SME2 // ARMv9.4-A bool has_sme2p1 = IsProcessorFeaturePresent(PF_ARM_SME2_1_INSTRUCTIONS_AVAILABLE); // SME2.1 if (has_sve2 || has_sme) { // ARMv9 family if (has_sme2p1) { cpu->march = "ARMv9.4-A"; } else if (has_sme2) { cpu->march = "ARMv9.3-A"; } else if (has_sme) { cpu->march = "ARMv9.2-A"; } else if (has_i8mm && has_bf16) { cpu->march = "ARMv9.1-A"; } else { cpu->march = "ARMv9.0-A"; } } else { // ARMv8 family if (has_ebf16) { cpu->march = "ARMv8.7-A"; } else if (has_i8mm && has_bf16) { cpu->march = "ARMv8.6-A"; } else if (has_dp && has_lse2) { cpu->march = "ARMv8.4-A"; } else if (has_lrcpc && has_jscvt) { cpu->march = "ARMv8.3-A"; } else if (has_fp16) { cpu->march = "ARMv8.2-A"; } else if (has_atomics && has_crc32) { cpu->march = "ARMv8.1-A"; } else if (has_neon && has_vfp) { cpu->march = "ARMv8-A"; } } } #else void ffCPUDetectByCpuid(FF_MAYBE_UNUSED FFCPUResult* cpu) { // Unsupported system } #endif #else void ffCPUDetectByCpuid(FF_MAYBE_UNUSED FFCPUResult* cpu) { // Unsupported architecture } #endif ================================================ FILE: src/detection/cpu/cpu.h ================================================ #pragma once #include "fastfetch.h" #include "modules/cpu/option.h" #define FF_CPU_TEMP_UNSET (-DBL_MAX) typedef struct FFCPUCore { uint32_t freq; uint32_t count; } FFCPUCore; typedef struct FFCPUResult { FFstrbuf name; FFstrbuf vendor; const char* march; // Microarchitecture uint16_t packages; uint16_t coresPhysical; uint16_t coresLogical; uint16_t coresOnline; uint16_t numaNodes; uint32_t frequencyBase; // GHz uint32_t frequencyMax; // GHz FFCPUCore coreTypes[16]; // number of P cores, E cores, etc. double temperature; } FFCPUResult; const char* ffDetectCPU(const FFCPUOptions* options, FFCPUResult* cpu); const char* ffCPUAppleCodeToName(uint32_t code); const char* ffCPUQualcommCodeToName(uint32_t code); void ffCPUDetectByCpuid(FFCPUResult* cpu); ================================================ FILE: src/detection/cpu/cpu_apple.c ================================================ #include "cpu.h" #include "common/sysctl.h" #include "common/apple/smc_temps.h" #include "common/stringUtils.h" static double detectCpuTemp(const FFCPUOptions* options, const FFstrbuf* cpuName) { double result = 0; const char* error = NULL; if (options->tempSensor.length) { error = ffDetectSmcSpecificTemp(options->tempSensor.chars, &result); } else { if (ffStrbufStartsWithS(cpuName, "Apple M")) { switch (strtol(cpuName->chars + strlen("Apple M"), NULL, 10)) { case 1: error = ffDetectSmcTemps(FF_TEMP_CPU_M1X, &result); break; case 2: error = ffDetectSmcTemps(FF_TEMP_CPU_M2X, &result); break; case 3: error = ffDetectSmcTemps(FF_TEMP_CPU_M3X, &result); break; case 4: error = ffDetectSmcTemps(FF_TEMP_CPU_M4X, &result); break; default: error = "Unsupported Apple Silicon CPU"; } } else // PPC? error = ffDetectSmcTemps(FF_TEMP_CPU_X64, &result); } if (error) return FF_CPU_TEMP_UNSET; return result; } #ifdef __aarch64__ #include "common/apple/cf_helpers.h" #include static const char* detectFrequency(FFCPUResult* cpu) { // https://github.com/giampaolo/psutil/pull/2222/files FF_IOOBJECT_AUTO_RELEASE io_registry_entry_t entryDevice = IOServiceGetMatchingService(MACH_PORT_NULL, IOServiceNameMatching("pmgr")); if (!entryDevice) return "IOServiceGetMatchingService() failed"; if (!IOObjectConformsTo(entryDevice, "AppleARMIODevice")) return "\"pmgr\" should conform to \"AppleARMIODevice\""; FF_CFTYPE_AUTO_RELEASE CFDataRef freqProperty = (CFDataRef) IORegistryEntryCreateCFProperty(entryDevice, CFSTR("voltage-states5-sram"), kCFAllocatorDefault, kNilOptions); if (!freqProperty || CFGetTypeID(freqProperty) != CFDataGetTypeID()) return "\"voltage-states5-sram\" in \"pmgr\" is not found"; // voltage-states5-sram stores supported pairs of pcores from the lowest to the highest // voltage-states1-sram stores ecores' CFIndex propLength = CFDataGetLength(freqProperty); if (propLength == 0 || propLength % (CFIndex) sizeof(uint32_t) * 2 != 0) return "Invalid \"voltage-states5-sram\" length"; uint32_t* pStart = (uint32_t*) CFDataGetBytePtr(freqProperty); uint32_t pMax = *pStart; for (CFIndex i = 2; i < propLength / (CFIndex) sizeof(uint32_t) && pStart[i] > 0; i += 2 /* skip voltage */) pMax = pMax > pStart[i] ? pMax : pStart[i]; if (pMax > 0) { if (pMax > 100000000) // Assume that pMax is in Hz, M1~M3 cpu->frequencyMax = pMax / 1000 / 1000; else // Assume that pMax is in kHz, M4 and later (#1394) cpu->frequencyMax = pMax / 1000; } return NULL; } #else static const char* detectFrequency(FFCPUResult* cpu) { cpu->frequencyBase = (uint32_t) (ffSysctlGetInt64("hw.cpufrequency", 0) / 1000 / 1000); cpu->frequencyMax = (uint32_t) (ffSysctlGetInt64("hw.cpufrequency_max", 0) / 1000 / 1000); if(cpu->frequencyBase == 0) { unsigned current = 0; size_t size = sizeof(current); if (sysctl((int[]){ CTL_HW, HW_CPU_FREQ }, 2, ¤t, &size, NULL, 0) == 0) cpu->frequencyBase = (uint32_t) (current / 1000 / 1000); } return NULL; } #endif static const char* detectCoreCount(FFCPUResult* cpu) { uint32_t nPerfLevels = (uint32_t) ffSysctlGetInt("hw.nperflevels", 0); if (nPerfLevels <= 0) return "sysctl(hw.nperflevels) failed"; char sysctlKey[] = "hw.perflevelN.logicalcpu"; if (nPerfLevels > ARRAY_SIZE(cpu->coreTypes)) nPerfLevels = ARRAY_SIZE(cpu->coreTypes); for (uint32_t i = 0; i < nPerfLevels; ++i) { sysctlKey[strlen("hw.perflevel")] = (char) ('0' + i); cpu->coreTypes[i] = (FFCPUCore) { .freq = nPerfLevels - i, .count = (uint32_t) ffSysctlGetInt(sysctlKey, 0), }; } return NULL; } const char* ffDetectCPUImpl(const FFCPUOptions* options, FFCPUResult* cpu) { if (ffSysctlGetString("machdep.cpu.brand_string", &cpu->name) != NULL) return "sysctlbyname(machdep.cpu.brand_string) failed"; ffSysctlGetString("machdep.cpu.vendor", &cpu->vendor); cpu->packages = (uint16_t) ffSysctlGetInt("hw.packages", 1); if (cpu->vendor.length == 0 && ffStrbufStartsWithS(&cpu->name, "Apple ")) ffStrbufAppendS(&cpu->vendor, "Apple"); cpu->coresPhysical = (uint16_t) ffSysctlGetInt("hw.physicalcpu_max", 1); if(cpu->coresPhysical == 1) cpu->coresPhysical = (uint16_t) ffSysctlGetInt("hw.physicalcpu", 1); cpu->coresLogical = (uint16_t) ffSysctlGetInt("hw.logicalcpu_max", 1); if(cpu->coresLogical == 1) cpu->coresLogical = (uint16_t) ffSysctlGetInt("hw.ncpu", 1); cpu->coresOnline = (uint16_t) ffSysctlGetInt("hw.logicalcpu", 1); if(cpu->coresOnline == 1) cpu->coresOnline = (uint16_t) ffSysctlGetInt("hw.activecpu", 1); ffCPUDetectByCpuid(cpu); detectFrequency(cpu); if (options->showPeCoreCount) detectCoreCount(cpu); cpu->temperature = options->temp ? detectCpuTemp(options, &cpu->name) : FF_CPU_TEMP_UNSET; return NULL; } ================================================ FILE: src/detection/cpu/cpu_arm.h ================================================ #pragma once #include "fastfetch.h" // https://github.com/util-linux/util-linux/blob/master/sys-utils/lscpu-arm.c // We use the util-linux's data but not its code. Call me if it violates util-linux's GPL license. static const char* hwImplId2Vendor(uint32_t implId) { switch (implId) { case 0x41: return "ARM"; case 0x42: return "Broadcom"; case 0x43: return "Cavium"; case 0x44: return "DEC"; case 0x46: return "FUJITSU"; case 0x48: return "HiSilicon"; case 0x49: return "Infineon"; case 0x4d: return "Motorola"; case 0x4e: return "NVIDIA"; case 0x50: return "APM"; case 0x51: return "Qualcomm"; case 0x53: return "Samsung"; case 0x56: return "Marvell"; case 0x61: return "Apple"; case 0x66: return "Faraday"; case 0x69: return "Intel"; case 0x6D: return "Microsoft"; case 0x70: return "Phytium"; case 0xc0: return "Ampere"; default: return "Unknown"; } } static const char* armPartId2name(uint32_t partId) { switch (partId) { case 0x810: return "ARM810"; case 0x920: return "ARM920"; case 0x922: return "ARM922"; case 0x926: return "ARM926"; case 0x940: return "ARM940"; case 0x946: return "ARM946"; case 0x966: return "ARM966"; case 0xa20: return "ARM1020"; case 0xa22: return "ARM1022"; case 0xa26: return "ARM1026"; case 0xb02: return "ARM11-MPCore"; case 0xb36: return "ARM1136"; case 0xb56: return "ARM1156"; case 0xb76: return "ARM1176"; case 0xc05: return "Cortex-A5"; case 0xc07: return "Cortex-A7"; case 0xc08: return "Cortex-A8"; case 0xc09: return "Cortex-A9"; case 0xc0d: return "Cortex-A17"; /* Originally A12 */ case 0xc0f: return "Cortex-A15"; case 0xc0e: return "Cortex-A17"; case 0xc14: return "Cortex-R4"; case 0xc15: return "Cortex-R5"; case 0xc17: return "Cortex-R7"; case 0xc18: return "Cortex-R8"; case 0xc20: return "Cortex-M0"; case 0xc21: return "Cortex-M1"; case 0xc23: return "Cortex-M3"; case 0xc24: return "Cortex-M4"; case 0xc27: return "Cortex-M7"; case 0xc60: return "Cortex-M0+"; case 0xd01: return "Cortex-A32"; case 0xd02: return "Cortex-A34"; case 0xd03: return "Cortex-A53"; case 0xd04: return "Cortex-A35"; case 0xd05: return "Cortex-A55"; case 0xd06: return "Cortex-A65"; case 0xd07: return "Cortex-A57"; case 0xd08: return "Cortex-A72"; case 0xd09: return "Cortex-A73"; case 0xd0a: return "Cortex-A75"; case 0xd0b: return "Cortex-A76"; case 0xd0c: return "Neoverse-N1"; case 0xd0d: return "Cortex-A77"; case 0xd0e: return "Cortex-A76AE"; case 0xd13: return "Cortex-R52"; case 0xd14: return "Cortex-R82AE"; case 0xd15: return "Cortex-R82"; case 0xd16: return "Cortex-R52+"; case 0xd20: return "Cortex-M23"; case 0xd21: return "Cortex-M33"; case 0xd24: return "Cortex-M52"; case 0xd22: return "Cortex-M55"; case 0xd23: return "Cortex-M85"; case 0xd40: return "Neoverse-V1"; case 0xd41: return "Cortex-A78"; case 0xd42: return "Cortex-A78AE"; case 0xd43: return "Cortex-A65AE"; case 0xd44: return "Cortex-X1"; case 0xd46: return "Cortex-A510"; case 0xd47: return "Cortex-A710"; case 0xd48: return "Cortex-X2"; case 0xd49: return "Neoverse-N2"; case 0xd4a: return "Neoverse-E1"; case 0xd4b: return "Cortex-A78C"; case 0xd4c: return "Cortex-X1C"; case 0xd4d: return "Cortex-A715"; case 0xd4e: return "Cortex-X3"; case 0xd4f: return "Neoverse-V2"; case 0xd80: return "Cortex-A520"; case 0xd81: return "Cortex-A720"; case 0xd82: return "Cortex-X4"; case 0xd83: return "Neoverse-V3AE"; case 0xd84: return "Neoverse-V3"; case 0xd85: return "Cortex-X925"; case 0xd87: return "Cortex-A725"; case 0xd88: return "Cortex-A520AE"; case 0xd89: return "Cortex-A720AE"; case 0xd8a: return "C1-Nano"; case 0xd8b: return "C1-Pro"; case 0xd8c: return "C1-Ultra"; case 0xd8e: return "Neoverse-N3"; case 0xd8f: return "Cortex-A320"; case 0xd90: return "C1-Premium"; default: return NULL; } } static const char* brcmPartId2name(uint32_t partId) { switch (partId) { case 0x0f: return "Brahma-B15"; case 0x100: return "Brahma-B53"; case 0x516: return "ThunderX2"; default: return NULL; } } static const char* decPartId2name(uint32_t partId) { switch (partId) { case 0xa10: return "SA110"; case 0xa11: return "SA1100"; default: return NULL; } } static const char* caviumPartId2name(uint32_t partId) { switch (partId) { case 0x0a0: return "ThunderX"; case 0x0a1: return "ThunderX-88XX"; case 0x0a2: return "ThunderX-81XX"; case 0x0a3: return "ThunderX-83XX"; case 0x0af: return "ThunderX2-99xx"; case 0x0b0: return "OcteonTX2"; case 0x0b1: return "OcteonTX2-98XX"; case 0x0b2: return "OcteonTX2-96XX"; case 0x0b3: return "OcteonTX2-95XX"; case 0x0b4: return "OcteonTX2-95XXN"; case 0x0b5: return "OcteonTX2-95XXMM"; case 0x0b6: return "OcteonTX2-95XXO"; case 0x0b8: return "ThunderX3-T110"; default: return NULL; } } static const char* apmPartId2name(uint32_t partId) { switch (partId) { case 0x000: return "X-Gene"; default: return NULL; } } static const char* qcomPartId2name(uint32_t partId) { switch (partId) { case 0x001: return "Oryon 1"; case 0x002: return "Oryon 2"; case 0x00f: return "Scorpion"; case 0x02d: return "Scorpion"; case 0x04d: return "Krait"; case 0x06f: return "Krait"; case 0x201: return "Kryo"; case 0x205: return "Kryo"; case 0x211: return "Kryo"; case 0x800: return "Falkor-V1/Kryo"; case 0x801: return "Kryo-V2"; case 0x802: return "Kryo-3XX-Gold"; case 0x803: return "Kryo-3XX-Silver"; case 0x804: return "Kryo-4XX-Gold"; case 0x805: return "Kryo-4XX-Silver"; case 0xc00: return "Falkor"; case 0xc01: return "Saphira"; default: return NULL; } } static const char* samsungPartId2name(uint32_t partId) { switch (partId) { case 0x001: return "Exynos-M1"; case 0x002: return "Exynos-M3"; case 0x003: return "Exynos-M4"; case 0x004: return "Exynos-M5"; default: return NULL; } } static const char* nvidiaPartId2name(uint32_t partId) { switch (partId) { case 0x000: return "Denver"; case 0x003: return "Denver-2"; case 0x004: return "Carmel"; case 0x010: return "Olympus"; default: return NULL; } } static const char* marvellPartId2name(uint32_t partId) { switch (partId) { case 0x131: return "Feroceon-88FR131"; case 0x581: return "PJ4/PJ4b"; case 0x584: return "PJ4B-MP"; default: return NULL; } } static const char* applePartId2name(uint32_t partId) { switch (partId) { case 0x000: return "Swift"; case 0x001: return "Cyclone"; case 0x002: return "Typhoon"; case 0x003: return "Typhoon/Capri"; case 0x004: return "Twister"; case 0x005: return "Twister/Elba/Malta"; case 0x006: return "Hurricane"; case 0x007: return "Hurricane/Myst"; case 0x008: return "Monsoon"; case 0x009: return "Mistral"; case 0x00b: return "Vortex"; case 0x00c: return "Tempest"; case 0x00f: return "Tempest-M9"; case 0x010: return "Vortex/Aruba"; case 0x011: return "Tempest/Aruba"; case 0x012: return "Lightning"; case 0x013: return "Thunder"; case 0x020: return "Icestorm-A14"; case 0x021: return "Firestorm-A14"; case 0x022: return "Icestorm-M1"; case 0x023: return "Firestorm-M1"; case 0x024: return "Icestorm-M1-Pro"; case 0x025: return "Firestorm-M1-Pro"; case 0x026: return "Thunder-M10"; case 0x028: return "Icestorm-M1-Max"; case 0x029: return "Firestorm-M1-Max"; case 0x030: return "Blizzard-A15"; case 0x031: return "Avalanche-A15"; case 0x032: return "Blizzard-M2"; case 0x033: return "Avalanche-M2"; case 0x034: return "Blizzard-M2-Pro"; case 0x035: return "Avalanche-M2-Pro"; case 0x036: return "Sawtooth-A16"; case 0x037: return "Everest-A16"; case 0x038: return "Blizzard-M2-Max"; case 0x039: return "Avalanche-M2-Max"; case 0x046: return "Sawtooth-M11"; case 0x048: return "Sawtooth-M3-Max"; case 0x049: return "Everest-M3-Max"; default: return NULL; } } static const char* faradayPartId2name(uint32_t partId) { switch (partId) { case 0x526: return "FA526"; case 0x626: return "FA626"; default: return NULL; } } static const char* intelPartId2name(uint32_t partId) { switch (partId) { case 0x200: return "i80200"; case 0x210: return "PXA250A"; case 0x212: return "PXA210A"; case 0x242: return "i80321-400"; case 0x243: return "i80321-600"; case 0x290: return "PXA250B/PXA26x"; case 0x292: return "PXA210B"; case 0x2c2: return "i80321-400-B0"; case 0x2c3: return "i80321-600-B0"; case 0x2d0: return "PXA250C/PXA255/PXA26x"; case 0x2d2: return "PXA210C"; case 0x411: return "PXA27x"; case 0x41c: return "IPX425-533"; case 0x41d: return "IPX425-400"; case 0x41f: return "IPX425-266"; case 0x682: return "PXA32x"; case 0x683: return "PXA930/PXA935"; case 0x688: return "PXA30x"; case 0x689: return "PXA31x"; case 0xb11: return "SA1110"; case 0xc12: return "IPX1200"; default: return NULL; } } static const char* fujitsuPartId2name(uint32_t partId) { switch (partId) { case 0x001: return "A64FX"; case 0x003: return "MONAKA"; default: return NULL; } } static const char* hisiPartId2name(uint32_t partId) { switch (partId) { case 0xd01: return "TaiShan-v110"; /* used in Kunpeng-920 SoC */ case 0xd02: return "TaiShan-v120"; /* used in Kirin 990A and 9000S SoCs */ case 0xd40: return "Cortex-A76"; /* HiSilicon uses this ID though advertises A76 */ case 0xd41: return "Cortex-A77"; /* HiSilicon uses this ID though advertises A77 */ default: return NULL; } } static const char* amperePartId2name(uint32_t partId) { switch (partId) { case 0xac3: return "Ampere-1"; case 0xac4: return "Ampere-1a"; default: return NULL; } } static const char* ftPartId2name(uint32_t partId) { switch (partId) { case 0x303: return "FTC310"; case 0x660: return "FTC660"; case 0x661: return "FTC661"; case 0x662: return "FTC662"; case 0x663: return "FTC663"; case 0x664: return "FTC664"; case 0x862: return "FTC862"; default: return NULL; } } static const char* msPartId2name(uint32_t partId) { switch (partId) { case 0xd49: return "Azure-Cobalt-100"; default: return NULL; } } ================================================ FILE: src/detection/cpu/cpu_bsd.c ================================================ #include "cpu.h" #include "common/sysctl.h" #include "common/stringUtils.h" #include #if __has_include() #include #define FF_HAVE_CPUSET 1 #endif static const char* detectCpuTemp(const FFCPUOptions* options, double* current) { int temp; if (options->tempSensor.length > 0) { temp = ffSysctlGetInt(options->tempSensor.chars, -999999); if (temp == -999999) return "ffSysctlGetInt(options->tempSensor) failed"; } else { temp = ffSysctlGetInt("dev.cpu.0.temperature", -999999); if (temp == -999999) { // Thermal zone temperature temp = ffSysctlGetInt("hw.acpi.thermal.tz0.temperature", -999999); if (temp == -999999) return "ffSysctlGetInt(\"dev.cpu.0.temperature\" or \"hw.acpi.thermal.tz0.temperature\") failed"; } } // In tenth of degrees Kelvin *current = (double) temp / 10 - 273.15; return NULL; } const char* ffDetectCPUImpl(const FFCPUOptions* options, FFCPUResult* cpu) { if (ffSysctlGetString("hw.model", &cpu->name) != NULL) return "sysctlbyname(hw.model) failed"; cpu->coresLogical = (uint16_t) ffSysctlGetInt("hw.ncpu", 1); cpu->coresPhysical = (uint16_t) ffSysctlGetInt("kern.smp.cores", 0); cpu->coresOnline = (uint16_t) ffSysctlGetInt("kern.smp.cpus", cpu->coresLogical); FF_STRBUF_AUTO_DESTROY buffer = ffStrbufCreate(); if (ffSysctlGetString("kern.sched.topology_spec", &buffer) == NULL && buffer.length > 0) { // // // 0, 1, 2, 3 // // // 0, 1 // THREAD groupSMT group // // // 2, 3 // THREAD groupSMT group // // // // for (char* p = buffer.chars; (p = strstr(p, "\n \n")); ++p) cpu->packages++; } #if FF_HAVE_CPUSET && (__x86_64__ || __i386__) // Bind current process to the first two cores, which is *usually* a performance core cpuset_t currentCPU; CPU_ZERO(¤tCPU); CPU_SET(1, ¤tCPU); CPU_SET(2, ¤tCPU); cpuset_setaffinity(CPU_LEVEL_WHICH, CPU_WHICH_TID, -1, sizeof(cpuset_t), ¤tCPU); #endif ffCPUDetectByCpuid(cpu); uint32_t clockrate = (uint32_t) ffSysctlGetInt("hw.clockrate", 0); if (clockrate > cpu->frequencyBase) cpu->frequencyBase = clockrate; for (uint16_t i = 0; i < cpu->coresLogical; ++i) { ffStrbufClear(&buffer); char key[32]; snprintf(key, sizeof(key), "dev.cpu.%u.freq_levels", i); if (ffSysctlGetString(key, &buffer) == NULL) { if (buffer.length == 0) continue; // MHz/Watts pairs like: 2501/32000 2187/27125 2000/24000 uint32_t fmax = (uint32_t) strtoul(buffer.chars, NULL, 10); if (cpu->frequencyMax < fmax) cpu->frequencyMax = fmax; } else break; } cpu->temperature = FF_CPU_TEMP_UNSET; if (options->temp) detectCpuTemp(options, &cpu->temperature); cpu->numaNodes = (uint16_t) ffSysctlGetInt("vm.ndomains", 0); return NULL; } ================================================ FILE: src/detection/cpu/cpu_haiku.c ================================================ #include "cpu.h" #include "common/mallocHelper.h" #include #include const char* ffDetectCPUImpl(FF_MAYBE_UNUSED const FFCPUOptions* options, FFCPUResult* cpu) { system_info sysInfo; if (get_system_info(&sysInfo) != B_OK) return "get_system_info() failed"; uint32 topoNodeCount = 0; get_cpu_topology_info(NULL, &topoNodeCount); if (topoNodeCount == 0) return "get_cpu_topology_info(NULL) failed"; FF_AUTO_FREE cpu_topology_node_info* topology = malloc(sizeof(*topology) * topoNodeCount); if (get_cpu_topology_info(topology, &topoNodeCount) != B_OK) return "get_cpu_topology_info(topology) failed"; enum cpu_platform platform = B_CPU_UNKNOWN; enum cpu_vendor cpuVendor = B_CPU_VENDOR_UNKNOWN; uint32 cpuModel = 0, frequency = 0; uint16_t packages = 0, cores = 0; for (uint32 i = 0; i < topoNodeCount; i++) { switch (topology[i].type) { case B_TOPOLOGY_ROOT: platform = topology[i].data.root.platform; break; case B_TOPOLOGY_PACKAGE: cpuVendor = topology[i].data.package.vendor; ++packages; break; case B_TOPOLOGY_CORE: cpuModel = topology[i].data.core.model; uint32_t freq = (uint32_t) (topology[i].data.core.default_frequency / 1000000); frequency = freq > frequency ? freq : frequency; ++cores; break; default: break; } } const char *model = get_cpu_model_string(platform, cpuVendor, cpuModel); if (model) ffStrbufSetS(&cpu->name, model); else ffStrbufSetF(&cpu->name, "(Unknown %" B_PRIx32 ")", cpuModel); ffStrbufSetS(&cpu->vendor, get_cpu_vendor_string(cpuVendor)); ffCPUDetectByCpuid(cpu); if (cpu->frequencyBase < frequency) cpu->frequencyBase = frequency; cpu->packages = packages; cpu->coresPhysical = cores; cpu->coresOnline = cpu->coresLogical = (uint16_t) sysInfo.cpu_count; return NULL; } ================================================ FILE: src/detection/cpu/cpu_linux.c ================================================ #include "cpu.h" #include "common/io.h" #include "common/processing.h" #include "common/properties.h" #include "common/mallocHelper.h" #include "common/stringUtils.h" #include "common/path.h" #include #include #include #include #include #define FF_CPUINFO_PATH "/proc/cpuinfo" static double readTempFile(int dfd, const char* filename, FFstrbuf* buffer) { if (filename ? !ffReadFileBufferRelative(dfd, filename, buffer) : !ffReadFDBuffer(dfd, buffer)) return FF_CPU_TEMP_UNSET; double value = ffStrbufToDouble(buffer, FF_CPU_TEMP_UNSET); // millidegree Celsius if (value == FF_CPU_TEMP_UNSET) return FF_CPU_TEMP_UNSET; return value / 1000.; } static double parseTZDir(int dfd, FFstrbuf* buffer) { if (!ffReadFileBufferRelative(dfd, "type", buffer)) return FF_CPU_TEMP_UNSET; if (!ffStrbufStartsWithS(buffer, "cpu") && !ffStrbufStartsWithS(buffer, "soc") && #if __x86_64__ || __i386__ !ffStrbufEqualS(buffer, "x86_pkg_temp") && #endif true ) return FF_CPU_TEMP_UNSET; return readTempFile(dfd, "temp", buffer); } static double parseHwmonDir(int dfd, FFstrbuf* buffer) { //https://www.kernel.org/doc/Documentation/hwmon/sysfs-interface if (!ffReadFileBufferRelative(dfd, "name", buffer)) return FF_CPU_TEMP_UNSET; ffStrbufTrimRightSpace(buffer); if ( !ffStrbufContainS(buffer, "cpu") && #if __x86_64__ || __i386__ !ffStrbufEqualS(buffer, "k10temp") && // AMD !ffStrbufEqualS(buffer, "fam15h_power") && // AMD !ffStrbufEqualS(buffer, "coretemp") && // Intel #endif true ) return FF_CPU_TEMP_UNSET; return readTempFile(dfd, "temp1_input", buffer); } static double detectCPUTemp(const FFCPUOptions* options) { FF_STRBUF_AUTO_DESTROY buffer = ffStrbufCreate(); if (options->tempSensor.length > 0) { FF_AUTO_CLOSE_FD int subfd = -1; const char* fileName = NULL; if (ffStrbufStartsWithS(&options->tempSensor, "hwmon") && ffCharIsDigit(options->tempSensor.chars[strlen("hwmon")])) { FF_AUTO_CLOSE_FD int dfd = open("/sys/class/hwmon/", O_PATH | O_CLOEXEC); subfd = openat(dfd, options->tempSensor.chars, O_RDONLY | O_DIRECTORY | O_CLOEXEC); if (subfd >= 0) fileName = "temp1_input"; else subfd = openat(dfd, options->tempSensor.chars, O_RDONLY | O_CLOEXEC); } else if (ffStrbufStartsWithS(&options->tempSensor, "thermal_zone") && ffCharIsDigit(options->tempSensor.chars[strlen("thermal_zone")])) { FF_AUTO_CLOSE_FD int dfd = open("/sys/class/thermal/", O_PATH | O_CLOEXEC); subfd = openat(dfd, options->tempSensor.chars, O_RDONLY | O_DIRECTORY | O_CLOEXEC); if (subfd >= 0) fileName = "temp"; else subfd = openat(dfd, options->tempSensor.chars, O_RDONLY | O_CLOEXEC); } else if (ffStrbufStartsWithS(&options->tempSensor, "cputemp.") && ffCharIsDigit(options->tempSensor.chars[strlen("cputemp.")])) { FF_AUTO_CLOSE_FD int dfd = open("/sys/class/platform/", O_PATH | O_CLOEXEC); subfd = openat(dfd, options->tempSensor.chars, O_RDONLY | O_DIRECTORY | O_CLOEXEC); if (subfd >= 0) fileName = "temp1_input"; else subfd = openat(dfd, options->tempSensor.chars, O_RDONLY | O_CLOEXEC); } else if (ffIsAbsolutePath(options->tempSensor.chars)) { subfd = open(options->tempSensor.chars, O_RDONLY | O_DIRECTORY | O_CLOEXEC); if (subfd >= 0) fileName = "temp1_input"; else subfd = open(options->tempSensor.chars, O_RDONLY | O_CLOEXEC); } if (subfd < 0) return FF_CPU_TEMP_UNSET; return readTempFile(subfd, fileName, &buffer); } { FF_AUTO_CLOSE_DIR DIR* dirp = opendir("/sys/class/hwmon/"); if(dirp) { int dfd = dirfd(dirp); struct dirent* entry; while((entry = readdir(dirp)) != NULL) { if(entry->d_name[0] == '.') continue; FF_AUTO_CLOSE_FD int subfd = openat(dfd, entry->d_name, O_RDONLY | O_DIRECTORY | O_CLOEXEC); if(subfd < 0) continue; double result = parseHwmonDir(subfd, &buffer); if (result != FF_CPU_TEMP_UNSET) return result; } } } { FF_AUTO_CLOSE_DIR DIR* dirp = opendir("/sys/class/thermal/"); if(dirp) { int dfd = dirfd(dirp); struct dirent* entry; while((entry = readdir(dirp)) != NULL) { if(entry->d_name[0] == '.') continue; if(!ffStrStartsWith(entry->d_name, "thermal_zone")) continue; FF_AUTO_CLOSE_FD int subfd = openat(dfd, entry->d_name, O_RDONLY | O_DIRECTORY | O_CLOEXEC); if(subfd < 0) continue; double result = parseTZDir(subfd, &buffer); if (result != FF_CPU_TEMP_UNSET) return result; } } } { FF_AUTO_CLOSE_DIR DIR* dirp = opendir("/sys/devices/platform/"); if(dirp) { int dfd = dirfd(dirp); struct dirent* entry; while((entry = readdir(dirp)) != NULL) { if(entry->d_name[0] == '.') continue; if(!ffStrStartsWith(entry->d_name, "cputemp.")) continue; FF_AUTO_CLOSE_FD int subfd = openat(dfd, entry->d_name, O_RDONLY | O_DIRECTORY | O_CLOEXEC); if(subfd < 0) continue; double result = parseHwmonDir(subfd, &buffer); if (result != FF_CPU_TEMP_UNSET) return result; } } } return FF_CPU_TEMP_UNSET; } static void detectNumaNodes(FFCPUResult* cpu) { FF_AUTO_CLOSE_DIR DIR* dir = opendir("/sys/devices/system/node/"); if (!dir) return; struct dirent* entry; while ((entry = readdir(dir)) != NULL) { if (entry->d_type != DT_DIR && entry->d_type != DT_UNKNOWN) continue; if (ffStrStartsWith(entry->d_name, "node") && ffCharIsDigit(entry->d_name[strlen("node")])) cpu->numaNodes++; } } #ifdef __ANDROID__ #include "common/settings.h" static void detectQualcomm(FFCPUResult* cpu) { // https://en.wikipedia.org/wiki/List_of_Qualcomm_Snapdragon_systems_on_chips assert(cpu->name.length >= 2); uint32_t code = (uint32_t) strtoul(cpu->name.chars + 2, NULL, 10); const char* name = NULL; switch (code) { case 8845: name = "8 Gen 5"; break; // ? case 8850: name = "8 Elite Gen 5"; break; case 8735: name = "8s Gen 4"; break; case 8750: name = "8 Elite"; break; case 8635: name = "8s Gen 3"; break; case 8650: name = "8 Gen 3"; break; case 8550: name = "8 Gen 2"; break; case 8475: name = "8+ Gen 1"; break; case 8450: name = "8 Gen 1"; break; case 7750: name = "7 Gen 4"; break; case 7675: name = "7+ Gen 3"; break; case 7635: name = "7s Gen 3"; break; case 7550: name = "7 Gen 3"; break; case 7475: name = "7+ Gen 2"; break; case 7435: name = "7s Gen 2"; break; case 7450: name = "7 Gen 1"; break; case 6650: name = "6 Gen 4"; break; case 6375: name = "6s Gen 3"; break; case 6475: name = "6 Gen 3"; break; case 6115: name = "6s Gen 1"; break; case 6450: name = "6 Gen 1"; break; case 4635: name = "4s Gen 2"; break; case 4450: name = "4 Gen 2"; break; case 4375: name = "4 Gen 1"; break; } if (name) { char str[32]; ffStrCopy(str, cpu->name.chars, sizeof(str)); ffStrbufSetF(&cpu->name, "Qualcomm Snapdragon %s [%s]", name, str); return; } } static void detectMediaTek(FFCPUResult* cpu) { // https://en.wikipedia.org/wiki/List_of_MediaTek_systems_on_chips assert(cpu->name.length >= 2); uint32_t code = (uint32_t) strtoul(cpu->name.chars + 2, NULL, 10); const char* name = NULL; switch (code) // The SOC code of MTK Dimensity series is full of mess { case 6993: name = "9500"; break; case 6991: name = "9400"; break; case 6989: case 8796: name = "9300"; break; case 6985: name = "9200"; break; case 6983: case 8798: name = "9000"; break; case 6899: name = "8400"; break; case 6897: case 8792: name = "8300"; break; case 6896: name = "8200"; break; case 8795: name = "8100"; break; case 6895: name = "8000"; break; } if (name) { char str[32]; ffStrCopy(str, cpu->name.chars, sizeof(str)); ffStrbufSetF(&cpu->name, "MediaTek Dimensity %s [%s]", name, str); return; } } static void detectExynos(FFCPUResult* cpu) { // https://en.wikipedia.org/wiki/Exynos assert(cpu->name.length > 3); uint32_t code = (uint32_t) strtoul(cpu->name.chars + 3, NULL, 10); const char* name = NULL; switch (code) { case 9955: name = "2500"; break; case 9945: name = "2400"; break; // No 2300 case 9925: name = "2200"; break; case 9840: name = "2100"; break; case 8855: name = "1580"; break; case 8845: name = "1480"; break; case 8835: name = "1380"; break; case 8535: name = "1330"; break; case 8825: name = "1280"; break; case 9815: name = "1080"; break; case 9830: name = "990"; break; case 9630: name = "980"; break; case 8805: name = "880"; break; case 3830: name = "850"; break; } if (name) { char str[32]; ffStrCopy(str, cpu->name.chars, sizeof(str)); ffStrbufSetF(&cpu->name, "Samsung Exynos %s [%s]", name, str); return; } } static void detectAndroid(FFCPUResult* cpu) { if (cpu->name.length == 0) { if (ffSettingsGetAndroidProperty("ro.soc.model", &cpu->name)) ffStrbufClear(&cpu->vendor); // We usually detect the vendor of CPU core as ARM, but instead we want the vendor of SOC } if (cpu->vendor.length == 0) { if (!ffSettingsGetAndroidProperty("ro.soc.manufacturer", &cpu->vendor)) if (!ffSettingsGetAndroidProperty("ro.product.product.manufacturer", &cpu->vendor)) if (!ffSettingsGetAndroidProperty("ro.product.vendor.manufacturer", &cpu->vendor)) if(ffSettingsGetAndroidProperty("ro.mediatek.platform", &cpu->name)) ffStrbufSetStatic(&cpu->vendor, "MediaTek"); } if (ffStrbufEqualS(&cpu->vendor, "QTI")) ffStrbufSetStatic(&cpu->vendor, "Qualcomm"); else if (ffStrbufIgnCaseEqualS(&cpu->vendor, "MediaTek")) // sometimes "Mediatek" ffStrbufSetStatic(&cpu->vendor, "MediaTek"); else if (cpu->vendor.length > 0) cpu->vendor.chars[0] = (char) toupper(cpu->vendor.chars[0]); if (ffStrbufEqualS(&cpu->vendor, "Qualcomm") && ffStrbufStartsWithS(&cpu->name, "SM")) detectQualcomm(cpu); else if (ffStrbufEqualS(&cpu->vendor, "MediaTek") && ffStrbufStartsWithS(&cpu->name, "MT")) detectMediaTek(cpu); else if (ffStrbufEqualS(&cpu->vendor, "Samsung") && ffStrbufStartsWithS(&cpu->name, "s5e")) { cpu->name.chars[0] = 'S'; cpu->name.chars[2] = 'E'; detectExynos(cpu); } } #endif #if __arm__ || __aarch64__ #include "cpu_arm.h" static void detectArmName(FFstrbuf* cpuinfo, FFCPUResult* cpu, uint32_t implId) { char* line = NULL; size_t len = 0; uint32_t lastPartId = UINT32_MAX; uint32_t num = 0; while(ffStrbufGetline(&line, &len, cpuinfo)) { if (!ffStrStartsWith(line, "CPU part\t: ")) continue; uint32_t partId = (uint32_t) strtoul(line + strlen("CPU part\t: "), NULL, 16); const char* name = NULL; switch (implId) { case 0x41: name = armPartId2name(partId); break; case 0x42: name = brcmPartId2name(partId); break; case 0x43: name = caviumPartId2name(partId); break; case 0x44: name = decPartId2name(partId); break; case 0x46: name = fujitsuPartId2name(partId); break; case 0x48: name = hisiPartId2name(partId); break; case 0x4e: name = nvidiaPartId2name(partId); break; case 0x50: name = apmPartId2name(partId); break; case 0x51: name = qcomPartId2name(partId); break; case 0x53: name = samsungPartId2name(partId); break; case 0x56: name = marvellPartId2name(partId); break; case 0x61: if (partId == 0) { // https://github.com/Dr-Noob/cpufetch/issues/213#issuecomment-1927782105 ffStrbufSetStatic(&cpu->name, "Virtualized Apple Silicon"); ffStrbufGetlineRestore(&line, &len, cpuinfo); return; } name = applePartId2name(partId); break; case 0x66: name = faradayPartId2name(partId); break; case 0x69: name = intelPartId2name(partId); break; case 0x6d: name = msPartId2name(partId); break; case 0x70: name = ftPartId2name(partId); break; case 0xc0: name = amperePartId2name(partId); break; } if (lastPartId != partId) { if (lastPartId != UINT32_MAX) { if (num > 1) ffStrbufAppendF(&cpu->name, "*%u", num); ffStrbufAppendS(&cpu->name, " + "); } if (name) ffStrbufAppendS(&cpu->name, name); else if (partId) ffStrbufAppendF(&cpu->name, "%s-%X", cpu->vendor.chars, partId); else ffStrbufAppend(&cpu->name, &cpu->vendor); lastPartId = partId; num = 1; } else ++num; } if (num > 1) ffStrbufAppendF(&cpu->name, "*%u", num); } #endif static const char* parseCpuInfo( FFstrbuf* cpuinfo, FFCPUResult* cpu, FF_MAYBE_UNUSED FFstrbuf* physicalCoresBuffer, FF_MAYBE_UNUSED FFstrbuf* cpuMHz, FF_MAYBE_UNUSED FFstrbuf* cpuIsa, FF_MAYBE_UNUSED FFstrbuf* cpuUarch, FF_MAYBE_UNUSED FFstrbuf* cpuImplementer) { char* line = NULL; size_t len = 0; while(ffStrbufGetline(&line, &len, cpuinfo)) { //Stop after reasonable information is acquired if((*line == '\0' || *line == '\n') && cpu->name.length > 0) { ffStrbufGetlineRestore(&line, &len, cpuinfo); break; } (void)( // arm64 doesn't have "model name"; arm32 does have "model name" but its value is not useful. // "Hardware" should always be used in this case #if __x86_64__ || __i386__ (cpu->name.length == 0 && ffParsePropLine(line, "model name :", &cpu->name)) || (cpu->vendor.length == 0 && ffParsePropLine(line, "vendor_id :", &cpu->vendor)) || (physicalCoresBuffer->length == 0 && ffParsePropLine(line, "cpu cores :", physicalCoresBuffer)) || (cpuMHz->length == 0 && ffParsePropLine(line, "cpu MHz :", cpuMHz)) || #elif __arm__ || __aarch64__ (cpuImplementer->length == 0 && ffParsePropLine(line, "CPU implementer :", cpuImplementer)) || (cpu->name.length == 0 && ffParsePropLine(line, "Hardware :", &cpu->name)) || //For Android devices #elif __powerpc__ || __powerpc (cpuMHz->length == 0 && ffParsePropLine(line, "clock :", cpuMHz)) || (cpu->name.length == 0 && ffParsePropLine(line, "cpu :", &cpu->name)) || #elif __mips__ || __mips (cpu->name.length == 0 && ffParsePropLine(line, "cpu model :", &cpu->name)) || #elif __loongarch__ (cpu->name.length == 0 && ffParsePropLine(line, "Model Name :", &cpu->name)) || (cpuMHz->length == 0 && ffParsePropLine(line, "CPU MHz :", cpuMHz)) || #elif __riscv__ || __riscv (cpuIsa->length == 0 && ffParsePropLine(line, "isa :", cpuIsa)) || (cpuUarch->length == 0 && ffParsePropLine(line, "uarch :", cpuUarch)) || #elif __s390x__ (cpu->name.length == 0 && ffParsePropLine(line, "machine :", &cpu->name)) || (cpu->vendor.length == 0 && ffParsePropLine(line, "vendor_id :", &cpu->vendor)) || (cpuMHz->length == 0 && ffParsePropLine(line, "cpu MHz static :", cpuMHz)) || #elif __ia64__ (cpu->name.length == 0 && ffParsePropLine(line, "model name :", &cpu->name)) || (cpu->vendor.length == 0 && ffParsePropLine(line, "vendor :", &cpu->vendor)) || (cpuMHz->length == 0 && ffParsePropLine(line, "cpu MHz :", cpuMHz)) || #elif __hppa__ (cpu->name.length == 0 && ffParsePropLine(line, "cpu :", &cpu->name)) || #elif __sh__ (cpu->name.length == 0 && ffParsePropLine(line, "cpu type :", &cpu->name)) || #else (cpu->name.length == 0 && ffParsePropLine(line, "model name :", &cpu->name)) || (cpu->name.length == 0 && ffParsePropLine(line, "model :", &cpu->name)) || (cpu->name.length == 0 && ffParsePropLine(line, "cpu model :", &cpu->name)) || (cpu->name.length == 0 && ffParsePropLine(line, "hardware :", &cpu->name)) || (cpu->name.length == 0 && ffParsePropLine(line, "processor :", &cpu->name)) || #endif false ); } return NULL; } static uint32_t getFrequency(FFstrbuf* basePath, const char* cpuinfoFileName, const char* scalingFileName, FFstrbuf* buffer) { uint32_t baseLen = basePath->length; ffStrbufAppendS(basePath, cpuinfoFileName); bool ok = ffReadFileBuffer(basePath->chars, buffer); ffStrbufSubstrBefore(basePath, baseLen); if (ok) return (uint32_t) (ffStrbufToUInt(buffer, 0) / 1000); if (scalingFileName) { ffStrbufAppendS(basePath, scalingFileName); ok = ffReadFileBuffer(basePath->chars, buffer); ffStrbufSubstrBefore(basePath, baseLen); if (ok) return (uint32_t) (ffStrbufToUInt(buffer, 0) / 1000); } return 0; } static uint8_t getNumCores(FFstrbuf* basePath, FFstrbuf* buffer) { uint32_t baseLen = basePath->length; ffStrbufAppendS(basePath, "/affected_cpus"); bool ok = ffReadFileBuffer(basePath->chars, buffer); ffStrbufSubstrBefore(basePath, baseLen); if (ok) return (uint8_t) (ffStrbufCountC(buffer, ' ') + 1); ffStrbufAppendS(basePath, "/related_cpus"); ok = ffReadFileBuffer(basePath->chars, buffer); ffStrbufSubstrBefore(basePath, baseLen); if (ok) return (uint8_t) (ffStrbufCountC(buffer, ' ') + 1); return 0; } static bool detectFrequency(FFCPUResult* cpu, const FFCPUOptions* options) { FF_STRBUF_AUTO_DESTROY path = ffStrbufCreateS("/sys/devices/system/cpu/cpufreq/"); FF_AUTO_CLOSE_DIR DIR* dir = opendir(path.chars); if (!dir) return false; FF_STRBUF_AUTO_DESTROY buffer = ffStrbufCreate(); uint32_t baseLen = path.length; struct dirent* entry; while ((entry = readdir(dir)) != NULL) { if (ffStrStartsWith(entry->d_name, "policy") && ffCharIsDigit(entry->d_name[strlen("policy")])) { ffStrbufAppendS(&path, entry->d_name); uint32_t fmax = getFrequency(&path, "/cpuinfo_max_freq", "/scaling_max_freq", &buffer); if (fmax == 0) continue; if (cpu->frequencyMax >= fmax) { if (!options->showPeCoreCount) { ffStrbufSubstrBefore(&path, baseLen); continue; } } else cpu->frequencyMax = fmax; uint32_t fbase = getFrequency(&path, "/base_frequency", NULL, &buffer); if (fbase > 0) cpu->frequencyBase = cpu->frequencyBase > fbase ? cpu->frequencyBase : fbase; if (options->showPeCoreCount) { uint32_t freq = fbase == 0 ? fmax : fbase; // seems base frequencies are more stable uint32_t ifreq = 0; while (cpu->coreTypes[ifreq].freq != freq && cpu->coreTypes[ifreq].freq > 0) ++ifreq; if (cpu->coreTypes[ifreq].freq == 0) cpu->coreTypes[ifreq].freq = freq; cpu->coreTypes[ifreq].count += getNumCores(&path, &buffer); } ffStrbufSubstrBefore(&path, baseLen); } } return true; } #if __i386__ || __x86_64__ FF_MAYBE_UNUSED static uint16_t getPackageCount(FFstrbuf* cpuinfo) { const char* p = cpuinfo->chars; uint64_t low = 0, high = 0; while ((p = memmem(p, cpuinfo->length - (uint32_t) (p - cpuinfo->chars), "\nphysical id\t:", strlen("\nphysical id\t:")))) { p += strlen("\nphysical id\t:"); char* pend; unsigned long long id = strtoul(p, &pend, 10); if (__builtin_expect(id > 64, false)) // Do 129-socket boards exist? high |= 1ULL << (id - 64); else low |= 1ULL << id; p = pend; } return (uint16_t) (__builtin_popcountll(low) + __builtin_popcountll(high)); } FF_MAYBE_UNUSED static const char* detectCPUX86(const FFCPUOptions* options, FFCPUResult* cpu) { FF_STRBUF_AUTO_DESTROY cpuinfo = ffStrbufCreateA(PROC_FILE_BUFFSIZ); if (!ffReadFileBuffer(FF_CPUINFO_PATH, &cpuinfo) || cpuinfo.length == 0) return "ffReadFileBuffer(\"" FF_CPUINFO_PATH "\") failed"; FF_STRBUF_AUTO_DESTROY physicalCoresBuffer = ffStrbufCreate(); FF_STRBUF_AUTO_DESTROY cpuMHz = ffStrbufCreate(); const char* error = parseCpuInfo(&cpuinfo, cpu, &physicalCoresBuffer, &cpuMHz, NULL,NULL, NULL); if (error) return error; cpu->coresLogical = (uint16_t) get_nprocs_conf(); cpu->coresOnline = (uint16_t) get_nprocs(); cpu->packages = getPackageCount(&cpuinfo); cpu->coresPhysical = (uint16_t) ffStrbufToUInt(&physicalCoresBuffer, 0); // physical cores in single package if (cpu->coresPhysical > 0 && cpu->packages > 1) cpu->coresPhysical *= cpu->packages; // Ref https://github.com/fastfetch-cli/fastfetch/issues/1194#issuecomment-2295058252 ffCPUDetectByCpuid(cpu); if (!detectFrequency(cpu, options) || cpu->frequencyBase == 0) cpu->frequencyBase = (uint32_t) ffStrbufToUInt(&cpuMHz, 0); detectNumaNodes(cpu); return NULL; } #else static const char* detectPhysicalCores(FFCPUResult* cpu) { int dfd = open("/sys/devices/system/cpu/", O_RDONLY | O_DIRECTORY | O_CLOEXEC); if (dfd < 0) return "open(\"/sys/devices/system/cpu/\") failed"; FF_AUTO_CLOSE_DIR DIR* dir = fdopendir(dfd); if (!dir) return "fdopendir(dfd) failed"; uint64_t pkgLow = 0, pkgHigh = 0; struct dirent* entry; FF_LIST_AUTO_DESTROY cpuList = ffListCreate(sizeof(uint32_t)); while ((entry = readdir(dir)) != NULL) { if (entry->d_type != DT_DIR || !ffStrStartsWith(entry->d_name, "cpu") || !ffCharIsDigit(entry->d_name[strlen("cpu")])) continue; FF_AUTO_CLOSE_FD int cpuxfd = openat(dirfd(dir), entry->d_name, O_RDONLY | O_DIRECTORY); if (cpuxfd < 0) continue; char buf[128]; // Check if the directory contains a file named "topology/physical_package_id" // that lists the physical package id of the CPU. ssize_t len = ffReadFileDataRelative(cpuxfd, "topology/physical_package_id", sizeof(buf) - 1, buf); if (len > 0) { buf[len] = '\0'; unsigned long long id = strtoul(buf, NULL, 10); if (__builtin_expect(id > 64, false)) // Do 129-socket boards exist? pkgHigh |= 1ULL << (id - 64); else pkgLow |= 1ULL << id; } // Check if the directory contains a file named "topology/core_cpus_list" // that lists the physical cores in the package. len = ffReadFileDataRelative(cpuxfd, "topology/core_cpus_list", sizeof(buf) - 1, buf); if (len > 0) { buf[len] = '\0'; // low-high or low for (const char* p = buf; *p;) { char* pend; uint32_t coreId = (uint32_t) strtoul(p, &pend, 10); if (pend == p) break; bool found = false; FF_LIST_FOR_EACH(uint32_t, id, cpuList) { if (*id == coreId) { // This core is already counted found = true; break; } } if (!found) *(uint32_t*) ffListAdd(&cpuList) = coreId; p = strchr(pend, ','); if (!p) break; ++p; } } } cpu->coresPhysical = (uint16_t) cpuList.length; cpu->packages = (uint16_t) (__builtin_popcountll(pkgLow) + __builtin_popcountll(pkgHigh)); return NULL; } FF_MAYBE_UNUSED static void parseIsa(FFstrbuf* cpuIsa) { // Always use the last part of the ISA string. Ref: #590 #1204 ffStrbufSubstrAfterLastC(cpuIsa, ' '); if(ffStrbufStartsWithS(cpuIsa, "rv")) { // RISC-V ISA string example: "rv64imafdch_zicsr_zifencei". // The _z parts are not important for CPU showcasing, so we remove them. if(ffStrbufContainC(cpuIsa, '_')) ffStrbufSubstrBeforeFirstC(cpuIsa, '_'); // Then we replace "imafd" with "g" since "g" is a shorthand. if(ffStrbufContainS(cpuIsa, "imafd")) { // Remove 4 of the 5 characters and replace the remaining one with "g". ffStrbufRemoveSubstr(cpuIsa, 4, 8); cpuIsa->chars[4] = 'g'; } // The final ISA output of the above example is "rv64gch". } } FF_MAYBE_UNUSED static void detectSocName(FFCPUResult* cpu) { if (cpu->name.length > 0) return; // [x-vendor,x-model\0]*N char content[512]; ssize_t length = ffReadFileData("/sys/firmware/devicetree/base/compatible", ARRAY_SIZE(content), content); if (length < 4) return; // v,m\0 if (content[length - 1] != '\0') return; // must end with \0 --length; char* vendor = NULL; char* model = NULL; for (char* p; length > 0; length = p ? (ssize_t) (p - content) - 1 : 0) { p = memrchr(content, '\0', (size_t) length); vendor = p /* first entry */ ? p + 1 : content; size_t partLen = (size_t) (length - (vendor - content)); if (partLen < 3) continue; char* comma = memchr(vendor, ',', partLen); if (!comma) continue; size_t vendorLen = (size_t) (comma - vendor); if (vendorLen == 0) continue; model = comma + 1; size_t modelLen = (size_t) (partLen - (size_t) (model - vendor)); if (modelLen == 0) continue; if ((modelLen >= strlen("-platform") && ffStrEndsWith(model, "-platform")) || (modelLen >= strlen("-soc") && ffStrEndsWith(model, "-soc"))) continue; *comma = '\0'; break; } if (!length) return; if (false) {} #if __aarch64__ else if (ffStrEquals(vendor, "apple")) { // https://elixir.bootlin.com/linux/v6.11/source/arch/arm64/boot/dts/apple if (model[0] == 't') { uint32_t deviceId = (uint32_t) strtoul(model + 1, NULL, 10); ffStrbufSetStatic(&cpu->name, ffCPUAppleCodeToName(deviceId)); if (!cpu->name.length) { ffStrbufSetS(&cpu->name, "Apple Silicon "); ffStrbufAppendS(&cpu->name, model); } } else ffStrbufSetS(&cpu->name, model); ffStrbufSetStatic(&cpu->vendor, "Apple"); } #endif else if (ffStrEquals(vendor, "qcom")) { // https://elixir.bootlin.com/linux/v6.11/source/arch/arm64/boot/dts/qcom if (ffStrStartsWith(model, "x")) { ffStrbufSetS(&cpu->name, "Qualcomm Snapdragon X Elite "); for (const char* p = model + 1; *p; ++p) ffStrbufAppendC(&cpu->name, (char) toupper(*p)); } else if (ffStrStartsWith(model, "sc")) { const char* code = model + 2; uint32_t deviceId = (uint32_t) strtoul(code, NULL, 10); ffStrbufSetStatic(&cpu->name, ffCPUQualcommCodeToName(deviceId)); if (!cpu->name.length) { ffStrbufAppendS(&cpu->name, "Qualcomm Snapdragon SC"); ffStrbufAppendS(&cpu->name, code); } } else ffStrbufSetS(&cpu->name, model); ffStrbufSetStatic(&cpu->vendor, "Qualcomm"); } else if (ffStrEquals(vendor, "brcm")) { // Raspberry Pi ffStrbufSetStatic(&cpu->vendor, "Broadcom"); for (const char* p = model; *p; ++p) ffStrbufAppendC(&cpu->name, (char) toupper(*p)); } else if (ffStrEquals(vendor, "thead")) { // Lichee Pi? ffStrbufSetStatic(&cpu->vendor, "T-Head"); for (const char* p = model; *p; ++p) ffStrbufAppendC(&cpu->name, (char) toupper(*p)); } else { ffStrbufSetS(&cpu->name, model); ffStrbufSetS(&cpu->vendor, vendor); cpu->vendor.chars[0] = (char) toupper(vendor[0]); } } #ifdef __loongarch__ FF_MAYBE_UNUSED static uint16_t getLoongarchPropCount(FFstrbuf* cpuinfo, const char* key) { const char* p = cpuinfo->chars; uint64_t low = 0, high = 0; uint32_t keylen = (uint32_t) strlen(key); while ((p = memmem(p, cpuinfo->length - (uint32_t) (p - cpuinfo->chars), key, keylen))) { p += keylen; char* pend; unsigned long id = strtoul(p, &pend, 10); if (__builtin_expect(id > 64, false)) high |= 1UL << (id - 64); else low |= 1UL << id; p = pend; } return (uint16_t) (__builtin_popcountll(low) + __builtin_popcountll(high)); } #endif FF_MAYBE_UNUSED static const char* detectCPUOthers(const FFCPUOptions* options, FFCPUResult* cpu) { cpu->coresLogical = (uint16_t) get_nprocs_conf(); cpu->coresOnline = (uint16_t) get_nprocs(); #if __ANDROID__ detectAndroid(cpu); #elif !__powerpc__ && !__powerpc detectSocName(cpu); #endif detectFrequency(cpu, options); if (cpu->name.length == 0) { FF_STRBUF_AUTO_DESTROY cpuinfo = ffStrbufCreateA(PROC_FILE_BUFFSIZ); if (!ffReadFileBuffer(FF_CPUINFO_PATH, &cpuinfo) || cpuinfo.length == 0) return "ffReadFileBuffer(\"" FF_CPUINFO_PATH "\") failed"; FF_STRBUF_AUTO_DESTROY cpuMHz = ffStrbufCreate(); FF_STRBUF_AUTO_DESTROY cpuIsa = ffStrbufCreate(); FF_STRBUF_AUTO_DESTROY cpuUarch = ffStrbufCreate(); FF_STRBUF_AUTO_DESTROY cpuImplementerStr = ffStrbufCreate(); const char* error = parseCpuInfo(&cpuinfo, cpu, NULL, &cpuMHz, &cpuIsa, &cpuUarch, &cpuImplementerStr); if (error) return error; if (cpu->frequencyBase == 0) cpu->frequencyBase = (uint32_t) ffStrbufToUInt(&cpuMHz, 0); #if __arm__ || __aarch64__ uint32_t cpuImplementer = (uint32_t) strtoul(cpuImplementerStr.chars, NULL, 16); ffStrbufSetStatic(&cpu->vendor, hwImplId2Vendor(cpuImplementer)); if (cpu->name.length == 0) detectArmName(&cpuinfo, cpu, cpuImplementer); #elif __riscv__ || __riscv if (cpu->name.length == 0) { if(cpuUarch.length > 0) { if(cpu->name.length > 0) ffStrbufAppendC(&cpu->name, ' '); ffStrbufAppend(&cpu->name, &cpuUarch); } if(cpuIsa.length > 0) { parseIsa(&cpuIsa); if(cpu->name.length > 0) ffStrbufAppendC(&cpu->name, ' '); ffStrbufAppend(&cpu->name, &cpuIsa); } } #elif __loongarch__ cpu->packages = getLoongarchPropCount(&cpuinfo, "\npackage\t\t\t:"); cpu->coresPhysical = getLoongarchPropCount(&cpuinfo, "\ncore\t\t\t:"); if (cpu->packages > 1) cpu->coresPhysical *= cpu->packages; #elif __s390x__ if (cpu->name.length) ffStrbufPrependS(&cpu->name, "Machine "); #endif } if (cpu->coresPhysical == 0) detectPhysicalCores(cpu); ffCPUDetectByCpuid(cpu); detectNumaNodes(cpu); return NULL; } #endif const char* ffDetectCPUImpl(const FFCPUOptions* options, FFCPUResult* cpu) { cpu->temperature = options->temp ? detectCPUTemp(options) : FF_CPU_TEMP_UNSET; #if __x86_64__ || __i386__ return detectCPUX86(options, cpu); #else return detectCPUOthers(options, cpu); #endif } ================================================ FILE: src/detection/cpu/cpu_nbsd.c ================================================ #include "cpu.h" #include "common/sysctl.h" #include "common/io.h" #include #include #include #include #include #include static void freePropDict(prop_dictionary_t* pdict) { assert(pdict != NULL); if (*pdict == NULL) return; prop_object_release(*pdict); } static const char* detectCpuTemp(const FFCPUOptions* options, double* current) { FF_AUTO_CLOSE_FD int fd = open(_PATH_SYSMON, O_RDONLY | O_CLOEXEC); if (fd < 0) return "open(_PATH_SYSMON, O_RDONLY | O_CLOEXEC) failed"; __attribute__((__cleanup__(freePropDict))) prop_dictionary_t root = NULL; if (prop_dictionary_recv_ioctl(fd, ENVSYS_GETDICTIONARY, &root) < 0) return "prop_dictionary_recv_ioctl(ENVSYS_GETDICTIONARY) failed"; prop_array_t array; if (options->tempSensor.length > 0) { array = prop_dictionary_get(root, options->tempSensor.chars); if (!array) return "No temp data found in specified sensor"; } else { array = prop_dictionary_get(root, "coretemp0"); if (!array) array = prop_dictionary_get(root, "amdzentemp0"); if (!array) array = prop_dictionary_get(root, "viac7temp0"); if (!array) array = prop_dictionary_get(root, "acpitz0"); // Thermal Zones if (!array) return "No temp data found in root dictionary"; } if (prop_array_count(array) != 2) return "Unexpected `xtemp0` data"; prop_dictionary_t dict = prop_array_get(array, 0); if (prop_object_type(dict) != PROP_TYPE_DICTIONARY) return "Unexpected `xtemp0[0]`"; int temp = 0; // in µK if (!prop_dictionary_get_int(dict, "cur-value", &temp)) return "Failed to get temperature"; *current = temp / 1e6 - 273.15; return NULL; } const char* ffDetectCPUImpl(const FFCPUOptions* options, FFCPUResult* cpu) { if (ffSysctlGetString("machdep.cpu_brand", &cpu->name) != NULL && ffSysctlGetString("machdep.dmi.processor-version", &cpu->name) != NULL && ffSysctlGetString("hw.cpu0.name", &cpu->name) != NULL && ffSysctlGetString("hw.model", &cpu->name) != NULL) { ffStrbufSetS(&cpu->name, "Unknown CPU"); } if (ffSysctlGetString("machdep.dmi.processor-vendor", &cpu->vendor) == NULL) ffStrbufTrimRightSpace(&cpu->vendor); cpu->coresPhysical = (uint16_t) ffSysctlGetInt("hw.ncpu", 1); cpu->coresLogical = cpu->coresPhysical; cpu->coresOnline = (uint16_t) ffSysctlGetInt("hw.ncpuonline", cpu->coresLogical); ffCPUDetectByCpuid(cpu); uint32_t freq = (uint32_t) ffSysctlGetInt("machdep.cpu.frequency.target", 0); if (freq == 0) freq = (uint32_t) (ffSysctlGetInt64("hw.cpu0.clock_frequency", 0) / 1000000); if (freq == 0) freq = (uint32_t) ffSysctlGetInt("machdep.dmi.processor-frequency", 0); if (freq > cpu->frequencyBase) cpu->frequencyBase = freq; cpu->temperature = FF_CPU_TEMP_UNSET; if (options->temp) detectCpuTemp(options, &cpu->temperature); return NULL; } ================================================ FILE: src/detection/cpu/cpu_nosupport.c ================================================ #include "cpu.h" const char* ffDetectCPUImpl(FF_MAYBE_UNUSED const FFCPUOptions* options, FF_MAYBE_UNUSED FFCPUResult* cpu) { return "Not supported on this platform"; } ================================================ FILE: src/detection/cpu/cpu_obsd.c ================================================ #include "cpu.h" #include "common/sysctl.h" #include "common/stringUtils.h" #include #include #include static const char* detectCPUTemp(const FFCPUOptions* options, FFCPUResult* cpu) { int mib[5] = {CTL_HW, HW_SENSORS, 0, SENSOR_TEMP, 0}; for (mib[2] = 0; mib[2] < 1024; mib[2]++) { struct sensordev sensordev; size_t sdlen = sizeof(struct sensordev); if (sysctl(mib, 3, &sensordev, &sdlen, NULL, 0) < 0) { if (errno == ENOENT) break; if (errno == ENXIO) continue; return "sysctl(sensordev) failed"; } if (options->tempSensor.length > 0) { if (!ffStrbufEqualS(&options->tempSensor, sensordev.xname)) continue; } else { if (!ffStrStartsWith(sensordev.xname, "cpu")) continue; } for (mib[4] = 0; mib[4] < sensordev.maxnumt[SENSOR_TEMP]; mib[4]++) { struct sensor sensor; size_t slen = sizeof(struct sensor); if (sysctl(mib, 5, &sensor, &slen, NULL, 0) < 0) { if (errno != ENOENT) return "sysctl(sensor) failed"; continue; } if (sensor.flags & SENSOR_FINVALID) continue; cpu->temperature = (double)(sensor.value - 273150000) / 1E6; return NULL; } } return "No sensor for CPU temp found"; } const char *ffDetectCPUImpl(const FFCPUOptions* options, FFCPUResult* cpu) { if (ffSysctlGetString(CTL_HW, HW_MODEL, &cpu->name)) return "sysctl(hw.model) failed"; cpu->coresPhysical = (uint16_t) ffSysctlGetInt(CTL_HW, HW_NCPU, 1); cpu->coresLogical = cpu->coresPhysical; cpu->coresOnline = (uint16_t) ffSysctlGetInt(CTL_HW, HW_NCPUONLINE, cpu->coresLogical); ffCPUDetectByCpuid(cpu); uint32_t cpuspeed = (uint32_t) ffSysctlGetInt(CTL_HW, HW_CPUSPEED, 0); if (cpuspeed > cpu->frequencyBase) cpu->frequencyBase = cpuspeed; cpu->temperature = FF_CPU_TEMP_UNSET; if (options->temp) detectCPUTemp(options, cpu); return NULL; } ================================================ FILE: src/detection/cpu/cpu_sunos.c ================================================ #include "cpu.h" #include "common/processing.h" #include "common/stringUtils.h" #include static const char* detectCPUTempByKstat(const FFCPUOptions* options, kstat_ctl_t* kc, FFCPUResult* cpu) { const char* possibleModules[] = {"temperature", "cpu_temp", "acpi_thermal", NULL}; if (options->tempSensor.length > 0) { possibleModules[0] = options->tempSensor.chars; possibleModules[1] = NULL; } for (int i = 0; possibleModules[i] != NULL; i++) { kstat_t* ks = kstat_lookup(kc, possibleModules[i], -1, NULL); if (ks && kstat_read(kc, ks, NULL) >= 0) { kstat_named_t* kn = kstat_data_lookup(ks, "temperature"); if (kn) { switch (kn->data_type) { case KSTAT_DATA_INT32: cpu->temperature = (float)kn->value.i32; return NULL; case KSTAT_DATA_UINT32: cpu->temperature = (float)kn->value.ui32; return NULL; case KSTAT_DATA_FLOAT: cpu->temperature = kn->value.f; return NULL; } } } } return "Failed to find CPU temperature using kstat"; } static const char* detectCPUTempByIpmiTool(FFCPUResult* cpu) { FF_STRBUF_AUTO_DESTROY buffer = ffStrbufCreate(); const char* error = ffProcessAppendStdOut(&buffer, (char* const[]){ "ipmitool", "-c", "sdr", "list", NULL }); if (error) return error; char* line = NULL; size_t len = 0; while (ffStrbufGetline(&line, &len, &buffer)) { if (sscanf(line, "CPU%*d Temp,%lf,degrees C,ok", &cpu->temperature) == 1) return NULL; } return "ipmitool sdr list failed to find CPU temperature"; } static inline void kstatFreeWrap(kstat_ctl_t** pkc) { assert(pkc); if (*pkc) kstat_close(*pkc); } static inline uint16_t countTypeId(kstat_ctl_t* kc, const char* type) { uint64_t low = 0, high = 0; for (kstat_t* ksp = kc->kc_chain; ksp; ksp = ksp->ks_next) { if (ffStrStartsWith(ksp->ks_module, "cpu_info")) { if (kstat_read(kc, ksp, NULL) < 0) continue; kstat_named_t* stat = kstat_data_lookup(ksp, type); if (!stat) continue; uint32_t id = 0; switch (stat->data_type) { #ifdef _INT64_TYPE case KSTAT_DATA_INT64: case KSTAT_DATA_UINT64: id = (uint32_t) stat->value.ui64; break; #endif case KSTAT_DATA_INT32: case KSTAT_DATA_UINT32: id = stat->value.ui32; break; default: continue; } if (__builtin_expect(id > 64, false)) high |= 1ULL << (id - 64); else low |= 1ULL << id; } } return (uint16_t) (__builtin_popcountll(low) + __builtin_popcountll(high)); } const char* ffDetectCPUImpl(const FFCPUOptions* options, FFCPUResult* cpu) { __attribute__((__cleanup__(kstatFreeWrap))) kstat_ctl_t* kc = kstat_open(); if (!kc) return "kstat_open() failed"; kstat_t* ks = kstat_lookup(kc, "cpu_info", -1, NULL); if (!ks) return "kstat_lookup() failed"; if (kstat_read(kc, ks, NULL) < 0) return "kstat_read() failed"; { kstat_named_t* kn = kstat_data_lookup(ks, "brand"); if (kn) ffStrbufSetNS(&cpu->name, KSTAT_NAMED_STR_BUFLEN(kn) - 1, KSTAT_NAMED_STR_PTR(kn)); } { kstat_named_t* kn = kstat_data_lookup(ks, "vendor_id"); if (kn) ffStrbufSetNS(&cpu->vendor, KSTAT_NAMED_STR_BUFLEN(kn) - 1, KSTAT_NAMED_STR_PTR(kn)); } ffCPUDetectByCpuid(cpu); { kstat_named_t* kn = kstat_data_lookup(ks, "clock_MHz"); if (kn && kn->value.ui32 > cpu->frequencyBase) cpu->frequencyBase = kn->value.ui32; } ks = kstat_lookup(kc, "unix", -1, "system_misc"); if (ks && kstat_read(kc, ks, NULL) >= 0) { kstat_named_t* kn = kstat_data_lookup(ks, "ncpus"); if (kn) cpu->coresLogical = cpu->coresOnline = (uint16_t) kn->value.ui32; } cpu->packages = countTypeId(kc, "chip_id"); cpu->coresPhysical = countTypeId(kc, "core_id"); if (options->temp) { if (detectCPUTempByKstat(options, kc, cpu) != NULL) detectCPUTempByIpmiTool(cpu); } return NULL; } ================================================ FILE: src/detection/cpu/cpu_windows.c ================================================ #include "cpu.h" #include "common/windows/registry.h" #include "common/windows/nt.h" #include "common/mallocHelper.h" #include "common/smbiosHelper.h" #include #include "common/windows/perflib_.h" #include "common/windows/nt.h" #include static inline void ffPerfCloseQueryHandle(HANDLE* phQuery) { if (*phQuery != NULL) { PerfCloseQueryHandle(*phQuery); *phQuery = NULL; } } const char* detectThermalTemp(const FFCPUOptions* options, double* result) { struct FFPerfQuerySpec { PERF_COUNTER_IDENTIFIER Identifier; WCHAR Name[16]; } querySpec = { .Identifier = { // Thermal Zone Information // HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Perflib\_V2Providers\{383487a6-3676-4870-a4e7-d45b30c35629}\{52bc5412-dac2-449c-8bc2-96443888fe6b} .CounterSetGuid = { 0x52bc5412, 0xdac2, 0x449c, {0x8b, 0xc2, 0x96, 0x44, 0x38, 0x88, 0xfe, 0x6b} }, .Size = sizeof(querySpec), .CounterId = PERF_WILDCARD_COUNTER, .InstanceId = PERF_WILDCARD_COUNTER, }, .Name = L"\\_TZ.CPUZ", // The standard(?) instance name for CPU temperature in the thermal provider }; if (options->tempSensor.length > 0) { if (!NT_SUCCESS(RtlUTF8ToUnicodeN(querySpec.Name, (ULONG) sizeof(querySpec.Name), NULL, options->tempSensor.chars, (ULONG)options->tempSensor.length + 1))) return "Invalid temp sensor string"; } DWORD dataSize = 0; if (PerfEnumerateCounterSetInstances(NULL, &querySpec.Identifier.CounterSetGuid, NULL, 0, &dataSize) != ERROR_NOT_ENOUGH_MEMORY) return "PerfEnumerateCounterSetInstances() failed"; if (dataSize <= sizeof(PERF_INSTANCE_HEADER)) return "No `Thermal Zone Information` instances found"; { FF_AUTO_FREE PERF_INSTANCE_HEADER* const pHead = malloc(dataSize); if (PerfEnumerateCounterSetInstances(NULL, &querySpec.Identifier.CounterSetGuid, pHead, dataSize, &dataSize) != ERROR_SUCCESS) return "PerfEnumerateCounterSetInstances() failed to get instance headers"; PERF_INSTANCE_HEADER* pInstanceHeader = pHead; while (1) { const wchar_t* instanceName = (const wchar_t*)((BYTE*)pInstanceHeader + sizeof(*pInstanceHeader)); if (wcscmp(instanceName, querySpec.Name) == 0) break; dataSize -= pInstanceHeader->Size; if (dataSize == 0) break; pInstanceHeader = (PERF_INSTANCE_HEADER*)((BYTE*)pInstanceHeader + pInstanceHeader->Size); } if (dataSize == 0) { if (options->tempSensor.length > 0) return "Unable to find CPU sensor"; const wchar_t* instanceName = (const wchar_t*)((BYTE*)pHead + sizeof(*pHead)); wcscpy(querySpec.Name, instanceName); // Use the first instance name if the specific one is not found } } __attribute__((__cleanup__(ffPerfCloseQueryHandle))) HANDLE hQuery = NULL; if (PerfOpenQueryHandle(NULL, &hQuery) != ERROR_SUCCESS) return "PerfOpenQueryHandle() failed"; if (PerfAddCounters(hQuery, &querySpec.Identifier, sizeof(querySpec)) != ERROR_SUCCESS) return "PerfAddCounters() failed"; if (querySpec.Identifier.Status != ERROR_SUCCESS) return "PerfAddCounters() reports invalid identifier"; if (PerfQueryCounterData(hQuery, NULL, 0, &dataSize) != ERROR_NOT_ENOUGH_MEMORY) return "PerfQueryCounterData(NULL) failed"; if (dataSize <= sizeof(PERF_DATA_HEADER) + sizeof(PERF_COUNTER_HEADER)) // PERF_ERROR_RETURN, should not happen return "instance doesn't exist"; FF_AUTO_FREE PERF_DATA_HEADER* const pDataHeader = malloc(dataSize); if (PerfQueryCounterData(hQuery, pDataHeader, dataSize, &dataSize) != ERROR_SUCCESS) return "PerfQueryCounterData(pDataHeader) failed"; PERF_COUNTER_HEADER* pCounterHeader = (PERF_COUNTER_HEADER*)(pDataHeader + 1); if (pCounterHeader->dwType != PERF_MULTIPLE_COUNTERS) return "Invalid counter type"; PERF_MULTI_COUNTERS* pMultiCounters = (PERF_MULTI_COUNTERS*)(pCounterHeader + 1); PERF_COUNTER_DATA* pCounterData = (PERF_COUNTER_DATA*)((BYTE*)pMultiCounters + pMultiCounters->dwSize); for (ULONG iCounter = 0; iCounter != pMultiCounters->dwCounters; iCounter++) { if (pCounterData->dwDataSize == sizeof(int32_t)) { DWORD* pCounterIds = (DWORD*)(pMultiCounters + 1); int32_t value = *(int32_t*)(pCounterData + 1); if (value == 0) return "Temperature data is zero"; switch (pCounterIds[iCounter]) { case 0: // Temperature *result = value - 273; break; case 3: // High Precision Temperature *result = value / 10.0 - 273; break; } } pCounterData = (PERF_COUNTER_DATA*)((BYTE*)pCounterData + pCounterData->dwSize); } return NULL; } // 7.5 typedef struct FFSmbiosProcessorInfo { FFSmbiosHeader Header; uint8_t SocketDesignation; // string uint8_t ProcessorType; // enum uint8_t ProcessorFamily; // enum uint8_t ProcessorManufacturer; // string uint64_t ProcessorID; // varies uint8_t ProcessorVersion; // string uint8_t Voltage; // varies uint16_t ExternalClock; // varies uint16_t MaxSpeed; // varies uint16_t CurrentSpeed; // varies uint8_t Status; // varies uint8_t ProcessorUpgrade; // enum // 2.1+ uint16_t L1CacheHandle; // varies uint16_t L2CacheHandle; // varies uint16_t L3CacheHandle; // varies // 2.3+ uint8_t SerialNumber; // string uint8_t AssertTag; // string uint8_t PartNumber; // string // 2.5+ uint8_t CoreCount; // varies uint8_t CoreEnabled; // varies uint8_t ThreadCount; // varies uint16_t ProcessorCharacteristics; // bit field // 2.6+ uint16_t ProcessorFamily2; // enum // 3.0+ uint16_t CoreCount2; // varies uint16_t CoreEnabled2; // varies uint16_t ThreadCount2; // varies // 3.6+ uint16_t ThreadEnabled; // varies } __attribute__((__packed__)) FFSmbiosProcessorInfo; static_assert(offsetof(FFSmbiosProcessorInfo, ThreadEnabled) == 0x30, "FFSmbiosProcessorInfo: Wrong struct alignment"); static const char* detectMaxSpeedBySmbios(FFCPUResult* cpu) { const FFSmbiosHeaderTable* smbiosTable = ffGetSmbiosHeaderTable(); if (!smbiosTable) return "Failed to get SMBIOS data"; const FFSmbiosProcessorInfo* data = (const FFSmbiosProcessorInfo*) (*smbiosTable)[FF_SMBIOS_TYPE_PROCESSOR_INFO]; if (!data) return "Processor information is not found in SMBIOS data"; while (data->ProcessorType != 0x03 /*Central Processor*/ || (data->Status & 0b00000111) != 1 /*Enabled*/) { data = (const FFSmbiosProcessorInfo*) ffSmbiosNextEntry(&data->Header); if (data->Header.Type != FF_SMBIOS_TYPE_PROCESSOR_INFO) return "No active CPU is found in SMBIOS data"; } uint32_t speed = data->MaxSpeed; // Sometimes SMBIOS reports invalid value. We assume that max speed is small than 2x of base if (speed < cpu->frequencyBase || speed > cpu->frequencyBase * 2) return "Possible invalid CPU max speed in SMBIOS data. See #800"; cpu->frequencyMax = speed; return NULL; } static const char* detectNCores(FFCPUResult* cpu) { LOGICAL_PROCESSOR_RELATIONSHIP lpr = RelationAll; ULONG length = 0; NtQuerySystemInformationEx(SystemLogicalProcessorAndGroupInformation, &lpr, sizeof(lpr), NULL, 0, &length); if (length == 0) return "GetLogicalProcessorInformationEx(RelationAll, NULL, &length) failed"; SYSTEM_LOGICAL_PROCESSOR_INFORMATION_EX* FF_AUTO_FREE pProcessorInfo = (SYSTEM_LOGICAL_PROCESSOR_INFORMATION_EX*)malloc(length); if (!NT_SUCCESS(NtQuerySystemInformationEx(SystemLogicalProcessorAndGroupInformation, &lpr, sizeof(lpr), pProcessorInfo, length, &length))) return "GetLogicalProcessorInformationEx(RelationAll, pProcessorInfo, &length) failed"; for( SYSTEM_LOGICAL_PROCESSOR_INFORMATION_EX* ptr = pProcessorInfo; (uint8_t*)ptr < ((uint8_t*)pProcessorInfo) + length; ptr = (SYSTEM_LOGICAL_PROCESSOR_INFORMATION_EX*)(((uint8_t*)ptr) + ptr->Size) ) { if (ptr->Relationship == RelationGroup) { for (uint32_t index = 0; index < ptr->Group.ActiveGroupCount; ++index) { cpu->coresOnline += ptr->Group.GroupInfo[index].ActiveProcessorCount; cpu->coresLogical += ptr->Group.GroupInfo[index].MaximumProcessorCount; } } else if (ptr->Relationship == RelationProcessorCore) ++cpu->coresPhysical; else if (ptr->Relationship == RelationProcessorPackage) ++cpu->packages; else if (ptr->Relationship == RelationNumaNode) ++cpu->numaNodes; } return NULL; } static const char* detectByRegistry(FFCPUResult* cpu) { FF_AUTO_CLOSE_FD HANDLE hKey = NULL; if(!ffRegOpenKeyForRead(HKEY_LOCAL_MACHINE, L"HARDWARE\\DESCRIPTION\\System\\CentralProcessor\\0", &hKey, NULL)) return "ffRegOpenKeyForRead(HKEY_LOCAL_MACHINE, L\"HARDWARE\\DESCRIPTION\\System\\CentralProcessor\\0\", &hKey, NULL) failed"; if (ffRegReadValues(hKey, 3, (FFRegValueArg[]) { FF_ARG(cpu->name, L"ProcessorNameString"), FF_ARG(cpu->vendor, L"VendorIdentifier"), FF_ARG(cpu->frequencyBase, L"~MHz"), }, NULL)) ffStrbufTrimRightSpace(&cpu->vendor); else return "ffRegReadValues() failed for CPU registry key"; return NULL; } static const char* detectCoreTypes(FFCPUResult* cpu) { FF_AUTO_FREE PROCESSOR_POWER_INFORMATION* pinfo = calloc(cpu->coresLogical, sizeof(PROCESSOR_POWER_INFORMATION)); if (!NT_SUCCESS(NtPowerInformation(ProcessorInformation, NULL, 0, pinfo, (ULONG) sizeof(PROCESSOR_POWER_INFORMATION) * cpu->coresLogical))) return "NtPowerInformation(ProcessorInformation, NULL, 0, pinfo, size) failed"; for (uint32_t icore = 0; icore < cpu->coresLogical && pinfo[icore].MhzLimit; ++icore) { uint32_t ifreq = 0; while (cpu->coreTypes[ifreq].freq != pinfo[icore].MhzLimit && cpu->coreTypes[ifreq].freq > 0) ++ifreq; if (cpu->coreTypes[ifreq].freq == 0) cpu->coreTypes[ifreq].freq = pinfo[icore].MhzLimit; ++cpu->coreTypes[ifreq].count; } if (cpu->frequencyBase == 0) cpu->frequencyBase = pinfo->MaxMhz; return NULL; } const char* ffDetectCPUImpl(const FFCPUOptions* options, FFCPUResult* cpu) { detectNCores(cpu); const char* error = detectByRegistry(cpu); if (error) return error; ffCPUDetectByCpuid(cpu); if (options->showPeCoreCount) detectCoreTypes(cpu); if (cpu->frequencyMax == 0) detectMaxSpeedBySmbios(cpu); if(options->temp) detectThermalTemp(options, &cpu->temperature); return NULL; } ================================================ FILE: src/detection/cpucache/cpucache.h ================================================ #pragma once #include "fastfetch.h" #include "modules/cpucache/option.h" typedef enum __attribute__((__packed__)) FFCPUCacheType { FF_CPU_CACHE_TYPE_UNIFIED = 0, FF_CPU_CACHE_TYPE_INSTRUCTION = 1, FF_CPU_CACHE_TYPE_DATA = 2, FF_CPU_CACHE_TYPE_TRACE = 3, } FFCPUCacheType; typedef struct FFCPUCache { uint32_t size; uint32_t num; uint32_t lineSize; FFCPUCacheType type; } FFCPUCache; typedef struct FFCPUCacheResult { FFlist caches[4]; // L1, L2, L3, L4(?) } FFCPUCacheResult; const char* ffDetectCPUCache(FFCPUCacheResult* result); static inline FFCPUCache* ffCPUCacheAddItem(FFCPUCacheResult* result, uint32_t level, uint32_t size, uint32_t lineSize, FFCPUCacheType type) { FFlist* cacheLevel = &result->caches[level - 1]; FF_LIST_FOR_EACH(FFCPUCache, item, *cacheLevel) { if (item->type == type && item->size == size && item->lineSize == lineSize) { item->num++; return item; } } FFCPUCache* item = (FFCPUCache*)ffListAdd(cacheLevel); *item = (FFCPUCache) { .size = size, .num = 1, .lineSize = lineSize, .type = type, }; return item; } ================================================ FILE: src/detection/cpucache/cpucache_apple.c ================================================ #include "cpucache.h" #include "common/sysctl.h" #include "common/stringUtils.h" const char* ffDetectCPUCache(FFCPUCacheResult* result) { // https://developer.apple.com/documentation/kernel/1387446-sysctlbyname/determining_system_capabilities#3901385 uint32_t nPerfLevels = (uint32_t) ffSysctlGetInt("hw.nperflevels", 0); if (nPerfLevels <= 0) return "sysctl(hw.nperflevels) failed"; // macOS provides the global system cache line size uint32_t lineSize = (uint32_t) ffSysctlGetInt64("hw.cachelinesize", 0); char sysctlKey[128] = "hw.perflevelN."; char* pNum = sysctlKey + strlen("hw.perflevel"); char* pSubkey = sysctlKey + strlen("hw.perflevelN."); const size_t lenLeft = ARRAY_SIZE(sysctlKey) - strlen("hw.perflevelN."); for (uint32_t i = 0; i < nPerfLevels; ++i) { *pNum = (char) ('0' + i); ffStrCopy(pSubkey, "physicalcpu", lenLeft); uint32_t ncpu = (uint32_t) ffSysctlGetInt(sysctlKey, 0); if (ncpu <= 0) continue; ffStrCopy(pSubkey, "l1icachesize", lenLeft); uint32_t size = (uint32_t) ffSysctlGetInt(sysctlKey, 0); if (size) ffCPUCacheAddItem(result, 1, size, lineSize, FF_CPU_CACHE_TYPE_INSTRUCTION)->num = ncpu; ffStrCopy(pSubkey, "l1dcachesize", lenLeft); size = (uint32_t) ffSysctlGetInt(sysctlKey, 0); if (size) ffCPUCacheAddItem(result, 1, size, lineSize, FF_CPU_CACHE_TYPE_DATA)->num = ncpu; ffStrCopy(pSubkey, "l2cachesize", lenLeft); size = (uint32_t) ffSysctlGetInt(sysctlKey, 0); if (size) { ffStrCopy(pSubkey, "cpusperl2", lenLeft); uint32_t cpuSper = (uint32_t) ffSysctlGetInt(sysctlKey, 0); if (cpuSper) ffCPUCacheAddItem(result, 2, size, lineSize, FF_CPU_CACHE_TYPE_UNIFIED)->num = ncpu / cpuSper; } ffStrCopy(pSubkey, "l3cachesize", lenLeft); size = (uint32_t) ffSysctlGetInt(sysctlKey, 0); if (size) { ffStrCopy(pSubkey, "cpusperl3", lenLeft); uint32_t cpuSper = (uint32_t) ffSysctlGetInt(sysctlKey, 0); if (cpuSper) ffCPUCacheAddItem(result, 3, size, lineSize, FF_CPU_CACHE_TYPE_UNIFIED)->num = ncpu / cpuSper; } } return NULL; } ================================================ FILE: src/detection/cpucache/cpucache_linux.c ================================================ #include "cpucache.h" #include "common/io.h" #include "common/stringUtils.h" static const char* parseCpuCacheIndex(FFstrbuf* path, FFCPUCacheResult* result, FFstrbuf* buffer, FFstrbuf* added) { uint32_t baseLen = path->length; ffStrbufAppendS(path, "/level"); if (!ffReadFileBuffer(path->chars, buffer)) return "ffReadFileBuffer(\"/sys/devices/system/cpu/cpuX/cache/indexX/level\") == NULL"; uint32_t level = (uint32_t) ffStrbufToUInt(buffer, 0); if (level < 1 || level > 4) return "level < 1 || level > 4"; ffStrbufSubstrBefore(path, baseLen); ffStrbufAppendS(path, "/size"); if (!ffReadFileBuffer(path->chars, buffer)) return "ffReadFileBuffer(\"/sys/devices/system/cpu/cpuX/cache/indexX/size\") == NULL"; uint32_t sizeKb = (uint32_t) ffStrbufToUInt(buffer, 0); if (sizeKb == 0) return "size == 0"; ffStrbufSubstrBefore(path, baseLen); ffStrbufAppendS(path, "/type"); if (!ffReadFileBuffer(path->chars, buffer)) return "ffReadFileBuffer(\"/sys/devices/system/cpu/cpuX/cache/indexX/type\") == NULL"; ffStrbufTrimRightSpace(buffer); FFCPUCacheType cacheType = 0; switch (buffer->chars[0]) { case 'I': cacheType = FF_CPU_CACHE_TYPE_INSTRUCTION; break; case 'D': cacheType = FF_CPU_CACHE_TYPE_DATA; break; case 'U': cacheType = FF_CPU_CACHE_TYPE_UNIFIED; break; case 'T': cacheType = FF_CPU_CACHE_TYPE_TRACE; break; default: return "unknown cache type"; } uint32_t lineSize = 0; ffStrbufSubstrBefore(path, baseLen); ffStrbufAppendS(path, "/coherency_line_size"); if (ffReadFileBuffer(path->chars, buffer)) lineSize = (uint32_t) ffStrbufToUInt(buffer, 0); ffStrbufSubstrBefore(path, baseLen); ffStrbufAppendS(path, "/shared_cpu_list"); ffStrbufClear(buffer); ffStrbufAppendC(buffer, '['); if (!ffAppendFileBuffer(path->chars, buffer)) return "ffAppendFileBuffer(\"/sys/devices/system/cpu/cpuX/cache/indexX/shared_cpu_list\") == NULL"; ffStrbufTrimRightSpace(buffer); // deduplicate shared caches ffStrbufAppendF(buffer, "_%u_%u_%u_%u]", level, sizeKb, lineSize, cacheType); if (ffStrbufContain(added, buffer)) return NULL; ffStrbufAppend(added, buffer); ffCPUCacheAddItem(result, level, sizeKb * 1024, lineSize, cacheType); return NULL; } static const char* parseCpuCache(FFstrbuf* path, FFCPUCacheResult* result, FFstrbuf* buffer, FFstrbuf* added) { ffStrbufAppendS(path, "/cache/"); uint32_t baseLen = path->length; FF_AUTO_CLOSE_DIR DIR* pathCacheDir = opendir(path->chars); if (!pathCacheDir) return "opendir(\"/sys/devices/system/cpu/cpuX/cache/\") == NULL"; struct dirent* pathCacheEntry; while ((pathCacheEntry = readdir(pathCacheDir)) != NULL) { if (!ffStrStartsWith(pathCacheEntry->d_name, "index") || !ffCharIsDigit(pathCacheEntry->d_name[strlen("index")])) continue; ffStrbufAppendS(path, pathCacheEntry->d_name); const char* error = parseCpuCacheIndex(path, result, buffer, added); if (error) return error; ffStrbufSubstrBefore(path, baseLen); } return NULL; } const char* ffDetectCPUCache(FFCPUCacheResult* result) { // https://www.kernel.org/doc/Documentation/ABI/testing/sysfs-devices-system-cpu FF_STRBUF_AUTO_DESTROY path = ffStrbufCreateS("/sys/devices/system/cpu/"); uint32_t baseLen = path.length; FF_AUTO_CLOSE_DIR DIR* pathCpuDir = opendir(path.chars); if (!pathCpuDir) return "opendir(\"/sys/devices/system/cpu/\") == NULL"; FF_STRBUF_AUTO_DESTROY buffer = ffStrbufCreate(); FF_STRBUF_AUTO_DESTROY added = ffStrbufCreate(); struct dirent* pathCpuEntry; while ((pathCpuEntry = readdir(pathCpuDir)) != NULL) { if (!ffStrStartsWith(pathCpuEntry->d_name, "cpu") || !ffCharIsDigit(pathCpuEntry->d_name[strlen("cpu")])) continue; ffStrbufAppendS(&path, pathCpuEntry->d_name); const char* error = parseCpuCache(&path, result, &buffer, &added); if (error) return error; ffStrbufSubstrBefore(&path, baseLen); } return NULL; } ================================================ FILE: src/detection/cpucache/cpucache_nosupport.c ================================================ #include "cpucache.h" const char* ffDetectCPUCache(FF_MAYBE_UNUSED FFCPUCacheResult* result) { return "Not supported on this platform"; } ================================================ FILE: src/detection/cpucache/cpucache_shared.c ================================================ #include "cpucache.h" #include "common/smbiosHelper.h" #include "common/stringUtils.h" typedef struct FFSmbiosCacheInfo { FFSmbiosHeader Header; uint8_t SocketDesignation; // string uint16_t CacheConfiguration; // varies uint16_t MaximumCacheSize; // varies uint16_t InstalledSize; // varies uint16_t SupportedSramType; // bit field uint16_t CurrentSramType; // bit field // 2.1+ uint8_t CacheSpeed; // varies uint8_t ErrorCorrectionType; // enum uint8_t SystemCacheType; // enum uint8_t Associativity; // enum // 3.1+ uint32_t MaximumCacheSize2; // bit field uint32_t InstalledCacheSize2; // bit field } __attribute__((__packed__)) FFSmbiosCacheInfo; static_assert(offsetof(FFSmbiosCacheInfo, InstalledCacheSize2) == 0x17, "FFSmbiosCacheInfo: Wrong struct alignment"); const char* ffDetectCPUCache(FFCPUCacheResult* result) { const FFSmbiosHeaderTable* smbiosTable = ffGetSmbiosHeaderTable(); if (!smbiosTable) return "Failed to get SMBIOS data"; const FFSmbiosCacheInfo* data = (const FFSmbiosCacheInfo*) (*smbiosTable)[FF_SMBIOS_TYPE_CACHE_INFO]; if (!data) return "Cache information is not found in SMBIOS data"; for (; data->Header.Type == FF_SMBIOS_TYPE_CACHE_INFO; data = (const FFSmbiosCacheInfo*) ffSmbiosNextEntry(&data->Header)) { bool enabled = !!(data->CacheConfiguration & (1 << 7)); if (!enabled) continue; uint32_t size = data->InstalledSize; if (size == 0) continue; if (data->InstalledSize != 0xFFFF) { size *= (size >> 15 ? 64 : 1) * 1024u; } else if (data->Header.Length > offsetof(FFSmbiosCacheInfo, InstalledCacheSize2)) { size = data->InstalledCacheSize2; size *= (size >> 31 ? 64 : 1) * 1024u; } uint32_t level = (data->CacheConfiguration & 0b111u) + 1; FFCPUCacheType type; switch (data->SystemCacheType) { case 3: type = FF_CPU_CACHE_TYPE_INSTRUCTION; break; case 4: type = FF_CPU_CACHE_TYPE_DATA; break; default: type = FF_CPU_CACHE_TYPE_UNIFIED; break; } ffCPUCacheAddItem(result, level, size, 0, type); } return NULL; } ================================================ FILE: src/detection/cpucache/cpucache_windows.c ================================================ #include "cpucache.h" #include "common/mallocHelper.h" #include "common/windows/nt.h" const char* ffDetectCPUCache(FFCPUCacheResult* result) { LOGICAL_PROCESSOR_RELATIONSHIP lpr = RelationCache; DWORD length = 0; NtQuerySystemInformationEx(SystemLogicalProcessorAndGroupInformation, &lpr, sizeof(lpr), NULL, 0, &length); if (length == 0) return "GetLogicalProcessorInformationEx(RelationCache, NULL, &length) failed"; SYSTEM_LOGICAL_PROCESSOR_INFORMATION_EX* FF_AUTO_FREE pProcessorInfo = (SYSTEM_LOGICAL_PROCESSOR_INFORMATION_EX*)malloc(length); if (!NT_SUCCESS(NtQuerySystemInformationEx(SystemLogicalProcessorAndGroupInformation, &lpr, sizeof(lpr), pProcessorInfo, length, &length))) return "GetLogicalProcessorInformationEx(RelationCache, pProcessorInfo, &length) failed"; for( SYSTEM_LOGICAL_PROCESSOR_INFORMATION_EX* ptr = pProcessorInfo; (uint8_t*)ptr < ((uint8_t*)pProcessorInfo) + length; ptr = (SYSTEM_LOGICAL_PROCESSOR_INFORMATION_EX*)(((uint8_t*)ptr) + ptr->Size) ) { if(__builtin_expect(ptr->Relationship == RelationCache && ptr->Cache.Level > 0 && ptr->Cache.Level <= 4, true)) { FFCPUCacheType cacheType = 0; switch (ptr->Cache.Type) { case CacheUnified: cacheType = FF_CPU_CACHE_TYPE_UNIFIED; break; case CacheInstruction: cacheType = FF_CPU_CACHE_TYPE_INSTRUCTION; break; case CacheData: cacheType = FF_CPU_CACHE_TYPE_DATA; break; case CacheTrace: cacheType = FF_CPU_CACHE_TYPE_TRACE; break; default: break; } ffCPUCacheAddItem(result, ptr->Cache.Level, ptr->Cache.CacheSize, ptr->Cache.LineSize, cacheType); } } return NULL; } ================================================ FILE: src/detection/cpuusage/cpuusage.c ================================================ #include "fastfetch.h" #include "detection/cpuusage/cpuusage.h" #include "common/time.h" #include static FFlist cpuTimes1; static uint64_t startTime; void ffPrepareCPUUsage(void) { if (cpuTimes1.elementSize != 0) return; // Already prepared ffListInit(&cpuTimes1, sizeof(FFCpuUsageInfo)); ffGetCpuUsageInfo(&cpuTimes1); startTime = ffTimeGetNow(); } const char* ffGetCpuUsageResult(FFCPUUsageOptions* options, FFlist* result) { const char* error = NULL; if(cpuTimes1.elementSize == 0) { ffListInit(&cpuTimes1, sizeof(FFCpuUsageInfo)); error = ffGetCpuUsageInfo(&cpuTimes1); if(error) return error; ffTimeSleep(options->waitTime); } else { uint64_t elapsedTime = ffTimeGetNow() - startTime; if (elapsedTime < options->waitTime) ffTimeSleep(options->waitTime - (uint32_t)elapsedTime); } if(cpuTimes1.length == 0) return "No CPU cores found"; FF_LIST_AUTO_DESTROY cpuTimes2 = ffListCreate(sizeof(FFCpuUsageInfo)); uint32_t retryCount = 0; retry: error = ffGetCpuUsageInfo(&cpuTimes2); if(error) return error; if(cpuTimes1.length != cpuTimes2.length) return "Unexpected CPU usage result"; for (uint32_t i = 0; i < cpuTimes1.length; ++i) { FFCpuUsageInfo* cpuTime1 = FF_LIST_GET(FFCpuUsageInfo, cpuTimes1, i); FFCpuUsageInfo* cpuTime2 = FF_LIST_GET(FFCpuUsageInfo, cpuTimes2, i); if (cpuTime2->totalAll <= cpuTime1->totalAll) { if (++retryCount <= 3) { ffListClear(&cpuTimes2); ffTimeSleep(options->waitTime); goto retry; } return "CPU time did not increase. Try increasing wait time."; } } for (uint32_t i = 0; i < cpuTimes1.length; ++i) { FFCpuUsageInfo* cpuTime1 = FF_LIST_GET(FFCpuUsageInfo, cpuTimes1, i); FFCpuUsageInfo* cpuTime2 = FF_LIST_GET(FFCpuUsageInfo, cpuTimes2, i); *(double*) ffListAdd(result) = (double)(cpuTime2->inUseAll - cpuTime1->inUseAll) / (double)(cpuTime2->totalAll - cpuTime1->totalAll) * 100; cpuTime1->inUseAll = cpuTime2->inUseAll; cpuTime1->totalAll = cpuTime2->totalAll; } startTime = ffTimeGetNow(); return NULL; } ================================================ FILE: src/detection/cpuusage/cpuusage.h ================================================ #pragma once #include "fastfetch.h" #include "modules/cpuusage/option.h" typedef struct FFCpuUsageInfo { uint64_t inUseAll; uint64_t totalAll; } FFCpuUsageInfo; const char* ffGetCpuUsageInfo(FFlist* cpuTimes); const char* ffGetCpuUsageResult(FFCPUUsageOptions* options, FFlist* result); // list of double ================================================ FILE: src/detection/cpuusage/cpuusage_apple.c ================================================ #include "fastfetch.h" #include "detection/cpuusage/cpuusage.h" #include #include #include const char* ffGetCpuUsageInfo(FFlist* cpuTimes) { natural_t numCPUs = 0U; processor_info_array_t cpuInfo; mach_msg_type_number_t numCpuInfo; if (host_processor_info(mach_host_self(), PROCESSOR_CPU_LOAD_INFO, &numCPUs, &cpuInfo, &numCpuInfo) != KERN_SUCCESS) return "host_processor_info() failed"; if (numCPUs * CPU_STATE_MAX != numCpuInfo) return "Unexpected host_processor_info() result"; for (natural_t i = 0U; i < numCPUs; ++i) { integer_t inUse = cpuInfo[CPU_STATE_MAX * i + CPU_STATE_USER] + cpuInfo[CPU_STATE_MAX * i + CPU_STATE_SYSTEM] + cpuInfo[CPU_STATE_MAX * i + CPU_STATE_NICE]; integer_t total = inUse + cpuInfo[CPU_STATE_MAX * i + CPU_STATE_IDLE]; FFCpuUsageInfo* info = (FFCpuUsageInfo*) ffListAdd(cpuTimes); *info = (FFCpuUsageInfo) { .inUseAll = (uint64_t)inUse, .totalAll = (uint64_t)total, }; } vm_deallocate(mach_task_self(), (vm_address_t) cpuInfo, numCpuInfo * sizeof(integer_t)); return NULL; } ================================================ FILE: src/detection/cpuusage/cpuusage_bsd.c ================================================ #include "detection/cpuusage/cpuusage.h" #include "common/mallocHelper.h" #include #include #include #include #if __OpenBSD__ || __NetBSD__ #include #endif const char* ffGetCpuUsageInfo(FFlist* cpuTimes) { size_t neededLength = 0; #if __OpenBSD__|| __NetBSD__ #ifdef KERN_CPTIME int ctls[] = {CTL_KERN, KERN_CPTIME}; #else int ctls[] = {CTL_KERN, KERN_CP_TIME}; #endif if (sysctl(ctls, 2, NULL, &neededLength, NULL, 0) != 0) return "sysctl({CTL_KERN, KERN_CPTIME}, 2, NULL) failed"; #else if(sysctlbyname("kern.cp_times", NULL, &neededLength, NULL, 0) != 0) return "sysctlbyname(kern.cp_times, NULL) failed"; #endif uint32_t coreCount = (uint32_t) (neededLength / (CPUSTATES * sizeof(uint64_t))); assert(coreCount > 0); FF_AUTO_FREE uint64_t (*cpTimes)[CPUSTATES] = malloc(neededLength); #if __OpenBSD__ || __NetBSD__ if (sysctl(ctls, 2, cpTimes, &neededLength, NULL, 0) != 0) return "sysctl({CTL_KERN, KERN_CPTIME}, 2, NULL) failed"; #else if(sysctlbyname("kern.cp_times", cpTimes, &neededLength, NULL, 0) != 0) return "sysctlbyname(kern.cp_times, cpTime) failed"; #endif for (uint32_t i = 0; i < coreCount; ++i) { uint64_t* cpTime = cpTimes[i]; uint64_t inUse = cpTime[CP_USER] + cpTime[CP_NICE] + cpTime[CP_SYS] + cpTime[CP_INTR]; uint64_t total = inUse + cpTime[CP_IDLE]; FFCpuUsageInfo* info = (FFCpuUsageInfo*) ffListAdd(cpuTimes); *info = (FFCpuUsageInfo) { .inUseAll = inUse, .totalAll = total, }; } return NULL; } ================================================ FILE: src/detection/cpuusage/cpuusage_haiku.c ================================================ #include "fastfetch.h" #include "detection/cpuusage/cpuusage.h" #include "common/mallocHelper.h" #include const char* ffGetCpuUsageInfo(FFlist* cpuTimes) { system_info sysInfo; if (get_system_info(&sysInfo) != B_OK) return "get_system_info() failed"; FF_AUTO_FREE cpu_info* cpuInfo = malloc(sizeof(*cpuInfo) * sysInfo.cpu_count); if (get_cpu_info(0, sysInfo.cpu_count, cpuInfo) != B_OK) return "get_cpu_info() failed"; uint64_t uptime = (uint64_t) system_time(); for (uint32_t i = 0; i < sysInfo.cpu_count; ++i) { FFCpuUsageInfo* info = (FFCpuUsageInfo*) ffListAdd(cpuTimes); info->inUseAll = (uint64_t) cpuInfo[i].active_time; info->totalAll = uptime; } return NULL; } ================================================ FILE: src/detection/cpuusage/cpuusage_linux.c ================================================ #include "fastfetch.h" #include "detection/cpuusage/cpuusage.h" #include "common/io.h" #include #include const char* ffGetCpuUsageInfo(FFlist* cpuTimes) { char buf[PROC_FILE_BUFFSIZ]; ssize_t nRead = ffReadFileData("/proc/stat", ARRAY_SIZE(buf) - 1, buf); if(nRead < 0) { #ifdef __ANDROID__ return "Accessing \"/proc/stat\" is restricted on Android O+"; #else return "ffReadFileData(\"/proc/stat\", ARRAY_SIZE(buf) - 1, buf) failed"; #endif } buf[nRead] = '\0'; // Skip first line char *start = NULL; if((start = strchr(buf, '\n')) == NULL) return "skip first line failed"; ++start; uint64_t user = 0, nice = 0, system = 0, idle = 0, iowait = 0, irq = 0, softirq = 0; char *token = NULL; while ((token = strchr(start, '\n'))) { if(sscanf(start, "cpu%*d%" PRIu64 "%" PRIu64 "%" PRIu64 "%" PRIu64 "%" PRIu64 "%" PRIu64 "%" PRIu64 "%*[^\n]\n", &user, &nice, &system, &idle, &iowait, &irq, &softirq) == 7) { uint64_t inUse = user + nice + system + irq + softirq; uint64_t total = inUse + idle + iowait; FFCpuUsageInfo* info = (FFCpuUsageInfo*) ffListAdd(cpuTimes); *info = (FFCpuUsageInfo) { .inUseAll = inUse, .totalAll = total, }; } else break; start = token + 1; } return NULL; } ================================================ FILE: src/detection/cpuusage/cpuusage_nosupport.c ================================================ #include "fastfetch.h" #include "detection/cpuusage/cpuusage.h" const char* ffGetCpuUsageInfo(FF_MAYBE_UNUSED FFlist* cpuTimes) { return "Not support on this platform"; } ================================================ FILE: src/detection/cpuusage/cpuusage_sunos.c ================================================ #include "fastfetch.h" #include "detection/cpuusage/cpuusage.h" #include #include static inline void kstatFreeWrap(kstat_ctl_t** pkc) { assert(pkc); if (*pkc) kstat_close(*pkc); } const char* ffGetCpuUsageInfo(FFlist* cpuTimes) { __attribute__((__cleanup__(kstatFreeWrap))) kstat_ctl_t* kc = kstat_open(); if (!kc) return "kstat_open() failed"; for (int i = 0;; ++i) { kstat_t* ks = kstat_lookup(kc, "cpu_stat", i, NULL); cpu_stat_t cs; if (!ks || kstat_read(kc, ks, &cs) < 0) break; uint64_t inUse = cs.cpu_sysinfo.cpu[CPU_USER] + cs.cpu_sysinfo.cpu[CPU_KERNEL]; uint64_t total = inUse + cs.cpu_sysinfo.cpu[CPU_IDLE] + cs.cpu_sysinfo.cpu[CPU_WAIT]; FFCpuUsageInfo* info = (FFCpuUsageInfo*) ffListAdd(cpuTimes); *info = (FFCpuUsageInfo) { .inUseAll = inUse, .totalAll = total, }; } return NULL; } ================================================ FILE: src/detection/cpuusage/cpuusage_windows.c ================================================ #include "detection/cpuusage/cpuusage.h" #include "common/mallocHelper.h" #include "common/debug.h" #include #include #include #include "common/windows/perflib_.h" #include "common/windows/nt.h" static const char* getInfoByNqsi(FFlist* cpuTimes) { ULONG size = 0; if(NtQuerySystemInformation(SystemProcessorPerformanceInformation, NULL, 0, &size) != STATUS_INFO_LENGTH_MISMATCH) return "NtQuerySystemInformation(SystemProcessorPerformanceInformation, NULL) failed"; SYSTEM_PROCESSOR_PERFORMANCE_INFORMATION* FF_AUTO_FREE pinfo = (SYSTEM_PROCESSOR_PERFORMANCE_INFORMATION*)malloc(size); if(!NT_SUCCESS(NtQuerySystemInformation(SystemProcessorPerformanceInformation, pinfo, size, &size))) return "NtQuerySystemInformation(SystemProcessorPerformanceInformation, size) failed"; for (uint32_t i = 0; i < size / sizeof(SYSTEM_PROCESSOR_PERFORMANCE_INFORMATION); ++i) { SYSTEM_PROCESSOR_PERFORMANCE_INFORMATION* coreInfo = pinfo + i; // KernelTime includes IdleTime and DpcTime. coreInfo->KernelTime.QuadPart -= coreInfo->IdleTime.QuadPart; uint64_t inUse = (uint64_t) (coreInfo->UserTime.QuadPart + coreInfo->KernelTime.QuadPart); uint64_t total = inUse + (uint64_t)coreInfo->IdleTime.QuadPart; FFCpuUsageInfo* info = (FFCpuUsageInfo*) ffListAdd(cpuTimes); *info = (FFCpuUsageInfo) { .inUseAll = inUse, .totalAll = total, }; } return NULL; } static const char* getInfoByPerflib(FFlist* cpuTimes) { static HANDLE hQuery = NULL; if (hQuery == NULL) { struct FFPerfQuerySpec { PERF_COUNTER_IDENTIFIER Identifier; WCHAR Name[16]; } querySpec = { .Identifier = { // Processor Information GUID // HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Perflib\_V2Providers\{383487a6-3676-4870-a4e7-d45b30c35629}\{b4fc721a-0378-476f-89ba-a5a79f810b36} .CounterSetGuid = { 0xb4fc721a, 0x0378, 0x476f, {0x89, 0xba, 0xa5, 0xa7, 0x9f, 0x81, 0x0b, 0x36} }, .Size = sizeof(querySpec), .CounterId = PERF_WILDCARD_COUNTER, // https://learn.microsoft.com/en-us/windows/win32/perfctrs/using-the-perflib-functions-to-consume-counter-data .InstanceId = PERF_WILDCARD_COUNTER, }, .Name = PERF_WILDCARD_INSTANCE, }; if (PerfOpenQueryHandle(NULL, &hQuery) != ERROR_SUCCESS) { PerfCloseQueryHandle(hQuery); hQuery = INVALID_HANDLE_VALUE; return "PerfOpenQueryHandle() failed"; } if (PerfAddCounters(hQuery, &querySpec.Identifier, sizeof(querySpec)) != ERROR_SUCCESS) { PerfCloseQueryHandle(hQuery); hQuery = INVALID_HANDLE_VALUE; return "PerfAddCounters() failed"; } if (querySpec.Identifier.Status != ERROR_SUCCESS) { PerfCloseQueryHandle(hQuery); hQuery = INVALID_HANDLE_VALUE; return "PerfAddCounters() reports invalid identifier"; } } if (hQuery == INVALID_HANDLE_VALUE) return "Init hQuery failed"; DWORD dataSize = 0; if (PerfQueryCounterData(hQuery, NULL, 0, &dataSize) != ERROR_NOT_ENOUGH_MEMORY) return "PerfQueryCounterData(NULL) failed"; if (dataSize <= sizeof(PERF_DATA_HEADER) + sizeof(PERF_COUNTER_HEADER)) return "instance doesn't exist"; FF_AUTO_FREE PERF_DATA_HEADER* const pDataHeader = (PERF_DATA_HEADER*)malloc(dataSize); if (PerfQueryCounterData(hQuery, pDataHeader, dataSize, &dataSize) != ERROR_SUCCESS) return "PerfQueryCounterData(pDataHeader) failed"; PERF_COUNTER_HEADER* pCounterHeader = (PERF_COUNTER_HEADER*)(pDataHeader + 1); if (pCounterHeader->dwType != PERF_COUNTERSET) return "Invalid counter type"; PERF_MULTI_COUNTERS* pMultiCounters = (PERF_MULTI_COUNTERS*)(pCounterHeader + 1); if (pMultiCounters->dwCounters == 0) return "No CPU counters found"; PERF_MULTI_INSTANCES* pMultiInstances = (PERF_MULTI_INSTANCES*)((BYTE*)pMultiCounters + pMultiCounters->dwSize); if (pMultiInstances->dwInstances == 0) return "No CPU instances found"; PERF_INSTANCE_HEADER* pInstanceHeader = (PERF_INSTANCE_HEADER*)(pMultiInstances + 1); for (DWORD iInstance = 0; iInstance < pMultiInstances->dwInstances; ++iInstance) { const wchar_t* instanceName = (const wchar_t*)((BYTE*)pInstanceHeader + sizeof(*pInstanceHeader)); PERF_COUNTER_DATA* pCounterData = (PERF_COUNTER_DATA*)((BYTE*)pInstanceHeader + pInstanceHeader->Size); uint64_t processorUtility = UINT64_MAX, utilityBase = UINT64_MAX; for (ULONG iCounter = 0; iCounter != pMultiCounters->dwCounters; iCounter++) { DWORD* pCounterIds = (DWORD*)(pMultiCounters + 1); // https://learn.microsoft.com/en-us/windows/win32/perfctrs/using-the-perflib-functions-to-consume-counter-data switch (pCounterIds[iCounter]) { case 26: // % Processor Utility (#26, Type=PERF_AVERAGE_BULK) assert(pCounterData->dwDataSize == sizeof(uint64_t)); processorUtility = *(uint64_t*)(pCounterData + 1); break; case 27: // % Utility Base (#27, Type=PERF_AVERAGE_BASE) assert(pCounterData->dwDataSize == sizeof(uint32_t)); utilityBase = *(uint32_t*)(pCounterData + 1) * 100LLU; break; } pCounterData = (PERF_COUNTER_DATA*)((BYTE*)pCounterData + pCounterData->dwSize); } if (wcschr(instanceName, L'_') == NULL /* ignore `_Total` */) { if (processorUtility == UINT64_MAX) return "Counter \"% Processor Utility\" are not supported"; FFCpuUsageInfo* info = (FFCpuUsageInfo*) ffListAdd(cpuTimes); *info = (FFCpuUsageInfo) { .inUseAll = processorUtility, .totalAll = utilityBase, }; } pInstanceHeader = (PERF_INSTANCE_HEADER*)pCounterData; } return NULL; } const char* ffGetCpuUsageInfo(FFlist* cpuTimes) { const char* error = NULL; if (ffIsWindows10OrGreater()) { error = getInfoByPerflib(cpuTimes); FF_DEBUG("Get CPU usage info by Perflib: %s", error ?: "success"); if (!error) return NULL; ffListClear(cpuTimes); } error = getInfoByNqsi(cpuTimes); FF_DEBUG("Get CPU usage info by NtQuerySystemInformation: %s", error ?: "success"); return error; } ================================================ FILE: src/detection/cursor/cursor.h ================================================ #pragma once #include "fastfetch.h" #include "modules/cursor/option.h" typedef struct FFCursorResult { FFstrbuf theme; FFstrbuf size; FFstrbuf error; } FFCursorResult; void ffDetectCursor(FFCursorResult* result); ================================================ FILE: src/detection/cursor/cursor_apple.m ================================================ #include "cursor.h" #import static void appendColor(FFstrbuf* str, NSDictionary* dict) { uint32_t r = (uint32_t) (((NSNumber*) dict[@"red"]).doubleValue * 255 + .5); uint32_t g = (uint32_t) (((NSNumber*) dict[@"green"]).doubleValue * 255 + .5); uint32_t b = (uint32_t) (((NSNumber*) dict[@"blue"]).doubleValue * 255 + .5); uint32_t a = (uint32_t) (((NSNumber*) dict[@"alpha"]).doubleValue * 255 + .5); uint32_t color = (r << 24) | (g << 16) | (b << 8) | a; switch (color) { case 0x000000FF: ffStrbufAppendS(str, "Black"); return; case 0x0433FFFF: ffStrbufAppendS(str, "Blue"); return; case 0xAA7942FF: ffStrbufAppendS(str, "Brown"); return; case 0x00FDFFFF: ffStrbufAppendS(str, "Cyan"); return; case 0x00F900FF: ffStrbufAppendS(str, "Green"); return; case 0xFF40FFFF: ffStrbufAppendS(str, "Magenta"); return; case 0xFF9300FF: ffStrbufAppendS(str, "Orange"); return; case 0x942192FF: ffStrbufAppendS(str, "Purple"); return; case 0xFF2600FF: ffStrbufAppendS(str, "Red"); return; case 0xFFFB00FF: ffStrbufAppendS(str, "Yellow"); return; case 0xFFFFFFFF: ffStrbufAppendS(str, "White"); return; case 0x00000000: ffStrbufAppendS(str, "Transparent"); return; default: ffStrbufAppendF(str, "#%08X", color); return; } } void ffDetectCursor(FFCursorResult* result) { NSError* error; NSString* fileName = [NSString stringWithFormat:@"file://%s/Library/Preferences/com.apple.universalaccess.plist", instance.state.platform.homeDir.chars]; NSDictionary* dict = [NSDictionary dictionaryWithContentsOfURL:[NSURL URLWithString:fileName] error:&error]; if(error) { ffStrbufAppendS(&result->error, error.localizedDescription.UTF8String); return; } NSDictionary* color; ffStrbufAppendS(&result->theme, "Fill - "); if ((color = dict[@"cursorFill"])) appendColor(&result->theme, color); else ffStrbufAppendS(&result->theme, "Black"); ffStrbufAppendS(&result->theme, ", Outline - "); if ((color = dict[@"cursorOutline"])) appendColor(&result->theme, color); else ffStrbufAppendS(&result->theme, "White"); NSNumber* mouseDriverCursorSize = dict[@"mouseDriverCursorSize"]; if (mouseDriverCursorSize) ffStrbufAppendF(&result->size, "%d", (int) (mouseDriverCursorSize.doubleValue * 32 + 0.5)); else ffStrbufAppendS(&result->size, "32"); } ================================================ FILE: src/detection/cursor/cursor_linux.c ================================================ #include "cursor.h" #include "detection/gtk_qt/gtk_qt.h" #include "detection/displayserver/displayserver.h" #include "common/properties.h" #include "common/parsing.h" #include "common/settings.h" #include "common/stringUtils.h" #include static bool detectCursorGTK(FFCursorResult* result) { const FFGTKResult* gtk = ffDetectGTK4(); if(gtk->cursor.length == 0) gtk = ffDetectGTK3(); if(gtk->cursor.length == 0) gtk = ffDetectGTK2(); if(gtk->cursor.length == 0) return false; ffStrbufAppend(&result->theme, >k->cursor); ffStrbufAppend(&result->size, >k->cursorSize); return true; } static void detectCursorFromConfigFile(const char* relativeFilePath, const char* themeStart, const char* themeDefault, const char* sizeStart, const char* sizeDefault, FFCursorResult* result) { if(ffParsePropFileConfigValues(relativeFilePath, 2, (FFpropquery[]) { {themeStart, &result->theme}, {sizeStart, &result->size} })) { if(result->theme.length == 0) ffStrbufAppendS(&result->theme, themeDefault); if(result->size.length == 0) ffStrbufAppendS(&result->size, sizeDefault); } if(result->theme.length == 0) ffStrbufAppendF(&result->error, "Couldn't find cursor in %s", relativeFilePath); } static bool detectCursorFromXResources(FFCursorResult* result) { ffParsePropFileHomeValues(".Xresources", 2, (FFpropquery[]) { {"Xcursor.theme :", &result->theme}, {"Xcursor.size :", &result->size} }); return result->theme.length > 0; } static bool detectCursorFromEnv(FFCursorResult* result) { const char* xcursor_theme = getenv("XCURSOR_THEME"); if(!ffStrSet(xcursor_theme)) return false; ffStrbufAppendS(&result->theme, xcursor_theme); ffStrbufAppendS(&result->size, getenv("XCURSOR_SIZE")); return true; } static bool detectCursorHyprcursor(FFCursorResult* result) { const char* hyprcursor_theme = getenv("HYPRCURSOR_THEME"); if(!ffStrSet(hyprcursor_theme)) return false; ffStrbufAppendS(&result->theme, hyprcursor_theme); ffStrbufAppendS(&result->size, getenv("HYPRCURSOR_SIZE")); return true; } void ffDetectCursor(FFCursorResult* result) { const FFDisplayServerResult* wmde = ffConnectDisplayServer(); if(ffStrbufEqualS(&wmde->wmPrettyName, FF_WM_PRETTY_WSLG)) ffStrbufAppendS(&result->error, "WSLg uses native windows cursor"); else if(ffStrbufIgnCaseEqualS(&wmde->wmProtocolName, FF_WM_PROTOCOL_TTY)) ffStrbufAppendS(&result->error, "Cursor isn't supported in TTY"); else if(ffStrbufIgnCaseEqualS(&wmde->dePrettyName, FF_DE_PRETTY_PLASMA)) detectCursorFromConfigFile("kcminputrc", "cursorTheme =", "Breeze", "cursorSize =", "24", result); else if(ffStrbufIgnCaseEqualS(&wmde->dePrettyName, FF_DE_PRETTY_LXQT)) detectCursorFromConfigFile("lxqt/session.conf", "cursor_theme =", "Adwaita", "cursor_size =", "24", result); else if(ffStrbufIgnCaseEqualS(&wmde->wmPrettyName, FF_WM_PRETTY_HYPRLAND) && detectCursorHyprcursor(result)) return; else if( !detectCursorGTK(result) && !detectCursorFromEnv(result) && !ffParsePropFileHome(".icons/default/index.theme", "Inherits =", &result->theme) && !detectCursorFromXResources(result) && !ffParsePropFileData("icons/default/index.theme", "Inherits =", &result->theme) ) ffStrbufAppendS(&result->error, "Couldn't find cursor"); } ================================================ FILE: src/detection/cursor/cursor_nosupport.c ================================================ #include "cursor.h" void ffDetectCursor(FF_MAYBE_UNUSED FFCursorResult* result) { ffStrbufInitS(&result->error, "Not supported on this platform"); } ================================================ FILE: src/detection/cursor/cursor_windows.c ================================================ #include "cursor.h" #include "common/io.h" #include "common/windows/registry.h" void ffDetectCursor(FFCursorResult* result) { FF_AUTO_CLOSE_FD HANDLE hKey = NULL; if(ffRegOpenKeyForRead(HKEY_CURRENT_USER, L"Control Panel\\Cursors", &hKey, &result->error)) { uint32_t cursorBaseSize; if (ffRegReadValues(hKey, 2, (FFRegValueArg[]) { FF_ARG(result->theme, NULL), FF_ARG(cursorBaseSize, L"CursorBaseSize"), }, &result->error)) ffStrbufAppendUInt(&result->size, cursorBaseSize); } } ================================================ FILE: src/detection/de/de.h ================================================ #pragma once #include "fastfetch.h" #include "modules/de/option.h" const char* ffDetectDEVersion(const FFstrbuf* deName, FFstrbuf* result, FFDEOptions* options); ================================================ FILE: src/detection/de/de_linux.c ================================================ #include "de.h" #include "common/dbus.h" #include "common/io.h" #include "common/library.h" #include "common/parsing.h" #include "common/properties.h" #include "common/processing.h" #include "common/binary.h" #include "common/path.h" #include "detection/displayserver/displayserver.h" #include #ifdef __FreeBSD__ #include #ifndef _PATH_LOCALBASE #define _PATH_LOCALBASE "/usr/local" #endif #elif __OpenBSD__ #define _PATH_LOCALBASE "/usr/local" #elif __NetBSD__ #define _PATH_LOCALBASE "/usr/pkg" #endif static void getKDE(FFstrbuf* result, FF_MAYBE_UNUSED FFDEOptions* options) { #ifdef _PATH_LOCALBASE ffParsePropFile(_PATH_LOCALBASE "/share/wayland-sessions/plasma.desktop", "X-KDE-PluginInfo-Version =", result); if(result->length == 0) ffParsePropFile(_PATH_LOCALBASE "/share/xsessions/plasmax11.desktop", "X-KDE-PluginInfo-Version =", result); #else ffParsePropFile(FASTFETCH_TARGET_DIR_USR "/share/wayland-sessions/plasma.desktop", "X-KDE-PluginInfo-Version =", result); if(result->length == 0) ffParsePropFile(FASTFETCH_TARGET_DIR_USR "/share/xsessions/plasmax11.desktop", "X-KDE-PluginInfo-Version =", result); #endif if(result->length == 0) ffParsePropFileData("xsessions/plasma.desktop", "X-KDE-PluginInfo-Version =", result); if(result->length == 0) ffParsePropFileData("xsessions/plasma5.desktop", "X-KDE-PluginInfo-Version =", result); if(result->length == 0) ffParsePropFileData("wayland-sessions/plasmawayland.desktop", "X-KDE-PluginInfo-Version =", result); if(result->length == 0) ffParsePropFileData("wayland-sessions/plasmawayland5.desktop", "X-KDE-PluginInfo-Version =", result); if(result->length == 0) { if (ffProcessAppendStdOut(result, (char* const[]){ "plasmashell", "--version", NULL }) == NULL) // plasmashell 5.27.5 ffStrbufSubstrAfterLastC(result, ' '); } } static const char* getGnomeByDbus(FF_MAYBE_UNUSED FFstrbuf* result) { #ifdef FF_HAVE_DBUS FF_DBUS_AUTO_DESTROY_DATA FFDBusData dbus = {}; if (ffDBusLoadData(DBUS_BUS_SESSION, &dbus) != NULL) return "ffDBusLoadData() failed"; ffDBusGetPropertyString(&dbus, "org.gnome.Shell", "/org/gnome/Shell", "org.gnome.Shell", "ShellVersion", result); return NULL; #else // FF_HAVE_DBUS return "ffDBusLoadData() failed: dbus support not compiled in"; #endif // FF_HAVE_DBUS } static void getGnome(FFstrbuf* result, FF_MAYBE_UNUSED FFDEOptions* options) { getGnomeByDbus(result); if (result->length == 0) { if (ffProcessAppendStdOut(result, (char* const[]){ "gnome-shell", "--version", NULL }) == NULL) // GNOME Shell 44.1 ffStrbufSubstrAfterLastC(result, ' '); } } static void getCinnamon(FFstrbuf* result, FF_MAYBE_UNUSED FFDEOptions* options) { ffStrbufSetS(result, getenv("CINNAMON_VERSION")); if (result->length == 0) ffParsePropFileData("applications/cinnamon.desktop", "X-GNOME-Bugzilla-Version =", result); if (result->length == 0) { if (ffProcessAppendStdOut(result, (char* const[]){ "cinnamon", "--version", NULL }) == NULL) // Cinnamon 6.2.2 ffStrbufSubstrAfterLastC(result, ' '); } } static void getMate(FFstrbuf* result, FF_MAYBE_UNUSED FFDEOptions* options) { FF_STRBUF_AUTO_DESTROY major = ffStrbufCreate(); FF_STRBUF_AUTO_DESTROY minor = ffStrbufCreate(); FF_STRBUF_AUTO_DESTROY micro = ffStrbufCreate(); ffParsePropFileDataValues("mate-about/mate-version.xml", 3, (FFpropquery[]) { {"", &major}, {"", &minor}, {"", µ} }); ffParseSemver(result, &major, &minor, µ); if(result->length == 0) { ffProcessAppendStdOut(result, (char* const[]){ "mate-session", "--version", NULL }); ffStrbufSubstrAfterFirstC(result, ' '); ffStrbufTrim(result, ' '); } } static const char* getXfce4ByLib(FFstrbuf* result) { #ifndef FF_DISABLE_DLOPEN const char* xfce_version_string(void); // from `xfce4/libxfce4util/xfce-misutils.h FF_LIBRARY_LOAD_MESSAGE(xfce4util, "libxfce4util" FF_LIBRARY_EXTENSION, 7); FF_LIBRARY_LOAD_SYMBOL_MESSAGE(xfce4util, xfce_version_string); ffStrbufSetS(result, ffxfce_version_string()); return NULL; #else FF_UNUSED(result); return "dlopen is disabled"; #endif } static void getXFCE4(FFstrbuf* result, FF_MAYBE_UNUSED FFDEOptions* options) { getXfce4ByLib(result); if(result->length == 0) { //This is somewhat slow ffProcessAppendStdOut(result, (char* const[]){ "xfce4-session", "--version", NULL }); ffStrbufSubstrBeforeFirstC(result, ')'); ffStrbufSubstrAfterLastC(result, ' '); ffStrbufTrim(result, ' '); } } static void getLXQt(FFstrbuf* result, FF_MAYBE_UNUSED FFDEOptions* options) { ffParsePropFileData("gconfig/lxqt.pc", "Version:", result); if(result->length == 0) ffParsePropFileData("cmake/lxqt/lxqt-config.cmake", "set ( LXQT_VERSION", result); if(result->length == 0) ffParsePropFileData("cmake/lxqt/lxqt-config-version.cmake", "set ( PACKAGE_VERSION", result); if(result->length == 0) { //This is really, really, really slow. Thank you, LXQt developers ffProcessAppendStdOut(result, (char* const[]){ "lxqt-session", "-v", NULL }); result->length = 0; //don't set '\0' byte ffParsePropLines(result->chars , "liblxqt", result); } } static void getBudgie(FFstrbuf* result, FF_MAYBE_UNUSED FFDEOptions* options) { ffParsePropFileData("budgie/budgie-version.xml", "", result); } static void getUnity(FFstrbuf* result, FF_MAYBE_UNUSED FFDEOptions* options) { if (ffParsePropFile("/usr/bin/unity", "parser = OptionParser(version= \"%prog ", result)) ffStrbufSubstrBeforeFirstC(result, '"'); } static bool extractTdeVersion(const char* line, uint32_t len, void *userdata) { int count = 0; sscanf(line, "R%*d.%*d.%*d%n", &count); if (count == 0) return true; ffStrbufSetNS((FFstrbuf*) userdata, len, line); return false; } static const char* getTrinity(FFstrbuf* result, FF_MAYBE_UNUSED FFDEOptions* options) { FF_STRBUF_AUTO_DESTROY path = ffStrbufCreate(); const char* error = ffFindExecutableInPath("tde-config", &path); if (error) return "Failed to find tde-config path"; ffStrbufSubstrBeforeLastC(&path, '/'); ffStrbufAppendS(&path, "/../lib/libtdecore.so"); if (ffBinaryExtractStrings(path.chars, extractTdeVersion, result, strlen("R0.0.0")) == NULL) return NULL; ffStrbufClear(&path); if (ffProcessAppendStdOut(&path, (char* const[]){ "tde-config", "--version", NULL }) == NULL) { ffParsePropLines(path.chars , "TDE: ", result); return NULL; } return "All methods failed"; } static const char* getCosmic(FFstrbuf* result, FF_MAYBE_UNUSED FFDEOptions* options) { if (ffProcessAppendStdOut(result, (char* const[]){ "cosmic-comp", "--version", NULL }) == NULL) { // cosmic-comp 0.1.0 (git commit fa88002ba41d2edec25dd7ffdee9719fbb928fc0) ffStrbufSubstrAfterFirstC(result, ' '); ffStrbufSubstrBeforeFirstC(result, ' '); return NULL; } return "All methods failed"; } const char* ffDetectDEVersion(const FFstrbuf* deName, FFstrbuf* result, FFDEOptions* options) { if (!instance.config.general.detectVersion) return "Disabled by config"; if (ffStrbufEqualS(deName, FF_DE_PRETTY_PLASMA)) getKDE(result, options); else if (ffStrbufEqualS(deName, FF_DE_PRETTY_GNOME)) getGnome(result, options); else if (ffStrbufEqualS(deName, FF_DE_PRETTY_CINNAMON)) getCinnamon(result, options); else if (ffStrbufEqualS(deName, FF_DE_PRETTY_XFCE4)) getXFCE4(result, options); else if (ffStrbufEqualS(deName, FF_DE_PRETTY_MATE)) getMate(result, options); else if (ffStrbufEqualS(deName, FF_DE_PRETTY_LXQT)) getLXQt(result, options); else if (ffStrbufEqualS(deName, FF_DE_PRETTY_BUDGIE)) getBudgie(result, options); else if (ffStrbufEqualS(deName, FF_DE_PRETTY_UNITY)) getUnity(result, options); else if (ffStrbufEqualS(deName, "trinity")) getTrinity(result, options); else if (ffStrbufEqualS(deName, "COSMIC")) getCosmic(result, options); else return "Unsupported DE"; return NULL; } ================================================ FILE: src/detection/de/de_nosupport.c ================================================ #include "de.h" const char* ffDetectDEVersion(FF_MAYBE_UNUSED const FFstrbuf* deName, FF_MAYBE_UNUSED FFstrbuf* result, FF_MAYBE_UNUSED FFDEOptions* options) { return "Not supported on this platform"; } ================================================ FILE: src/detection/disk/disk.c ================================================ #include "disk.h" static int compareDisks(const FFDisk* disk1, const FFDisk* disk2) { return ffStrbufComp(&disk1->mountpoint, &disk2->mountpoint); } const char* ffDetectDisks(FFDiskOptions* options, FFlist* disks) { const char* error = ffDetectDisksImpl(options, disks); if (error) return error; if (disks->length == 0) return NULL; //We need to sort the disks, so that we can detect, which disk a path resides on // For example for /boot/efi/bootmgr we need to check /boot/efi before /boot //Note that we sort alphabetically here for a better ordering when printing the list, // so the check must be done in reverse order ffListSort(disks, (void*) compareDisks); FF_LIST_FOR_EACH(FFDisk, disk, *disks) { if(disk->bytesTotal == 0) disk->type |= FF_DISK_VOLUME_TYPE_UNKNOWN_BIT; else { disk->bytesUsed = disk->bytesTotal - ( options->calcType == FF_DISK_CALC_TYPE_FREE ? disk->bytesFree : disk->bytesAvailable ); } } return NULL; } #ifndef _WIN32 #include bool ffDiskMatchesFolderPatterns(FFstrbuf* folders, const char* path, char separator) { uint32_t startIndex = 0; while(startIndex < folders->length) { uint32_t sepIndex = ffStrbufNextIndexC(folders, startIndex, separator); char savedSep = folders->chars[sepIndex]; // Can be '\0' if at end folders->chars[sepIndex] = '\0'; bool matched = fnmatch(&folders->chars[startIndex], path, 0) == 0; folders->chars[sepIndex] = savedSep; if (matched) return true; startIndex = sepIndex + 1; } return false; } #endif ================================================ FILE: src/detection/disk/disk.h ================================================ #pragma once #include "fastfetch.h" #include "modules/disk/option.h" #ifdef _WIN32 #define FF_DISK_FOLDER_SEPARATOR ';' #else #define FF_DISK_FOLDER_SEPARATOR ':' #endif typedef struct FFDisk { FFstrbuf mountFrom; FFstrbuf mountpoint; FFstrbuf filesystem; FFstrbuf name; FFDiskVolumeType type; uint64_t bytesUsed; uint64_t bytesFree; uint64_t bytesAvailable; uint64_t bytesTotal; uint32_t filesUsed; uint32_t filesTotal; uint64_t createTime; } FFDisk; /** * Returns a List of FFDisk, sorted alphabetically by mountpoint. * If error is not set, disks contains at least one disk. */ const char* ffDetectDisks(FFDiskOptions* options, FFlist* disks /* list of FFDisk */); const char* ffDetectDisksImpl(FFDiskOptions* options, FFlist* disks); #ifndef _WIN32 bool ffDiskMatchesFolderPatterns(FFstrbuf* folders, const char* path, char separator); #endif ================================================ FILE: src/detection/disk/disk_bsd.c ================================================ #include "disk.h" #include "common/mallocHelper.h" #include "common/stringUtils.h" #include #include #ifdef __NetBSD__ #include #include #define statfs statvfs #define f_flags f_flag #define f_bsize f_frsize #endif #ifdef __FreeBSD__ #if __has_include() #include static const char* detectFsLabel(struct statfs* fs, FFDisk* disk) { if (!ffStrStartsWith(fs->f_mntfromname, "/dev/")) return "Only block devices are supported"; // Detect volume label in geom tree static struct gmesh geomTree; static struct gclass* cLabels; if (!cLabels) { if (geomTree.lg_ident) return "Previous geom_gettree() failed"; if (geom_gettree(&geomTree) < 0) { geomTree.lg_ident = (void*)(intptr_t)-1; return "geom_gettree() failed"; } for (cLabels = geomTree.lg_class.lh_first; cLabels && !ffStrEquals(cLabels->lg_name, "LABEL"); cLabels = cLabels->lg_class.le_next); if (!cLabels) return "Class LABEL is not found"; } for (struct ggeom* label = cLabels->lg_geom.lh_first; label; label = label->lg_geom.le_next) { struct gprovider* provider = label->lg_provider.lh_first; if (!provider || !ffStrEquals(label->lg_name, fs->f_mntfromname + strlen("/dev/"))) continue; const char* str = strchr(provider->lg_name, '/'); ffStrbufSetS(&disk->name, str ? str + 1 : provider->lg_name); } return NULL; } #else static const char* detectFsLabel(FF_MAYBE_UNUSED struct statfs* fs, FF_MAYBE_UNUSED FFDisk* disk) { return "Fastfetch was compiled without libgeom support"; } #endif static void detectFsInfo(struct statfs* fs, FFDisk* disk) { if(ffStrbufEqualS(&disk->filesystem, "zfs")) { disk->type = !ffStrbufStartsWithS(&disk->mountFrom, "zroot/") || ffStrbufStartsWithS(&disk->mountFrom, "zroot/ROOT/") ? FF_DISK_VOLUME_TYPE_REGULAR_BIT : FF_DISK_VOLUME_TYPE_SUBVOLUME_BIT; } else if(fs->f_flags & MNT_IGNORE) disk->type = FF_DISK_VOLUME_TYPE_HIDDEN_BIT; else if(!(fs->f_flags & MNT_LOCAL)) disk->type = FF_DISK_VOLUME_TYPE_EXTERNAL_BIT; else disk->type = FF_DISK_VOLUME_TYPE_REGULAR_BIT; detectFsLabel(fs, disk); } #elif __APPLE__ #include "common/apple/cf_helpers.h" #include #include #ifndef MAC_OS_X_VERSION_10_15 #define MNT_REMOVABLE 0x00000200 #endif struct CmnAttrBuf { uint32_t length; attrreference_t nameRef; char nameSpace[NAME_MAX * 3 + 1]; } __attribute__((aligned(4), packed)); void detectFsInfo(struct statfs* fs, FFDisk* disk) { if(fs->f_flags & MNT_DONTBROWSE) disk->type = FF_DISK_VOLUME_TYPE_HIDDEN_BIT; else if(fs->f_flags & MNT_REMOVABLE || !(fs->f_flags & MNT_LOCAL)) disk->type = FF_DISK_VOLUME_TYPE_EXTERNAL_BIT; else disk->type = FF_DISK_VOLUME_TYPE_REGULAR_BIT; struct CmnAttrBuf attrBuf; if (getattrlist(disk->mountpoint.chars, &(struct attrlist) { .bitmapcount = ATTR_BIT_MAP_COUNT, .commonattr = ATTR_CMN_NAME, }, &attrBuf, sizeof(attrBuf), 0) == 0) ffStrbufInitNS(&disk->name, attrBuf.nameRef.attr_length - 1 /* excluding '\0' */, attrBuf.nameSpace); } #else static void detectFsInfo(struct statfs* fs, FFDisk* disk) { #ifdef MNT_IGNORE if(fs->f_flags & MNT_IGNORE) disk->type = FF_DISK_VOLUME_TYPE_HIDDEN_BIT; else #endif if(!(fs->f_flags & MNT_LOCAL)) disk->type = FF_DISK_VOLUME_TYPE_EXTERNAL_BIT; else disk->type = FF_DISK_VOLUME_TYPE_REGULAR_BIT; } #endif const char* ffDetectDisksImpl(FFDiskOptions* options, FFlist* disks) { #ifndef __NetBSD__ int size = getfsstat(NULL, 0, MNT_WAIT); if(size <= 0) return "getfsstat(NULL, 0, MNT_WAIT) failed"; #else int size = getvfsstat(NULL, 0, ST_WAIT); if(size <= 0) return "getvfsstat(NULL, 0, ST_WAIT) failed"; #endif FF_AUTO_FREE struct statfs* buf = malloc(sizeof(*buf) * (unsigned) size); #ifndef __NetBSD__ if(getfsstat(buf, (int) (sizeof(*buf) * (unsigned) size), MNT_NOWAIT) <= 0) return "getfsstat(buf, size, MNT_NOWAIT) failed"; #else if(getvfsstat(buf, sizeof(*buf) * (unsigned) size, ST_NOWAIT) <= 0) return "getvfsstat(buf, size, ST_NOWAIT) failed"; #endif for(struct statfs* fs = buf; fs < buf + size; ++fs) { if(__builtin_expect(options->folders.length > 0, 0)) { if(!ffStrbufSeparatedContainS(&options->folders, fs->f_mntonname, FF_DISK_FOLDER_SEPARATOR)) continue; } else if(!ffStrEquals(fs->f_mntonname, "/") && !ffStrStartsWith(fs->f_mntfromname, "/dev/") && !ffStrEquals(fs->f_fstypename, "zfs") && !ffStrEquals(fs->f_fstypename, "fusefs.sshfs")) continue; if (options->hideFolders.length && ffDiskMatchesFolderPatterns(&options->hideFolders, fs->f_mntonname, FF_DISK_FOLDER_SEPARATOR)) continue; if (options->hideFS.length && ffStrbufSeparatedContainS(&options->hideFS, fs->f_fstypename, ':')) continue; #ifdef __FreeBSD__ // f_bavail and f_ffree are signed on FreeBSD... if(fs->f_bavail < 0) fs->f_bavail = 0; if(fs->f_ffree < 0) fs->f_ffree = 0; #endif FFDisk* disk = ffListAdd(disks); disk->bytesTotal = (uint64_t)fs->f_blocks * (uint64_t)fs->f_bsize; disk->bytesFree = (uint64_t)fs->f_bfree * (uint64_t)fs->f_bsize; disk->bytesAvailable = (uint64_t)fs->f_bavail * (uint64_t)fs->f_bsize; disk->bytesUsed = 0; // To be filled in ./disk.c disk->filesTotal = (uint32_t) fs->f_files; disk->filesUsed = (uint32_t) fs->f_files - (uint32_t) fs->f_ffree; ffStrbufInitS(&disk->mountFrom, fs->f_mntfromname); ffStrbufInitS(&disk->mountpoint, fs->f_mntonname); ffStrbufInitS(&disk->filesystem, fs->f_fstypename); ffStrbufInit(&disk->name); disk->type = 0; disk->createTime = 0; detectFsInfo(fs, disk); if(fs->f_flags & MNT_RDONLY) disk->type |= FF_DISK_VOLUME_TYPE_READONLY_BIT; #ifdef __OpenBSD__ #define st_birthtimespec __st_birthtim #endif #ifndef __DragonFly__ struct stat st; if(stat(fs->f_mntonname, &st) == 0 && st.st_birthtimespec.tv_sec > 0) disk->createTime = (uint64_t)(((uint64_t) st.st_birthtimespec.tv_sec * 1000) + ((uint64_t) st.st_birthtimespec.tv_nsec / 1000000)); #endif } return NULL; } ================================================ FILE: src/detection/disk/disk_haiku.cpp ================================================ extern "C" { #include "disk.h" #include "common/stringUtils.h" } #include #include #include const char* ffDetectDisksImpl(FFDiskOptions* options, FFlist* disks) { int32 pos = 0; for (dev_t dev; (dev = next_dev(&pos)) >= B_OK;) { fs_info fs; if (fs_stat_dev(dev, &fs) < 0) continue; node_ref node(fs.dev, fs.root); BDirectory dir(&node); BPath path(&dir); if (path.InitCheck() != B_OK) continue; if (__builtin_expect(options->folders.length, 0)) { if (!ffStrbufSeparatedContainS(&options->folders, path.Path(), FF_DISK_FOLDER_SEPARATOR)) continue; } if (options->hideFolders.length && ffDiskMatchesFolderPatterns(&options->hideFolders, path.Path(), FF_DISK_FOLDER_SEPARATOR)) continue; if (options->hideFS.length && ffStrbufSeparatedContainS(&options->hideFS, fs.fsh_name, ':')) continue; FFDisk* disk = (FFDisk*) ffListAdd(disks); disk->bytesTotal = (uint64_t)fs.total_blocks * (uint64_t) fs.block_size; disk->bytesFree = (uint64_t)fs.free_blocks * (uint64_t) fs.block_size; disk->bytesAvailable = disk->bytesFree; disk->bytesUsed = 0; // To be filled in ./disk.c disk->filesTotal = (uint32_t) fs.total_nodes; disk->filesUsed = (uint32_t) (fs.total_nodes - fs.free_nodes); ffStrbufInitS(&disk->mountFrom, fs.device_name); ffStrbufInitS(&disk->mountpoint, path.Path()); ffStrbufInitS(&disk->filesystem, fs.fsh_name); ffStrbufInitS(&disk->name, fs.volume_name); disk->type = FF_DISK_VOLUME_TYPE_NONE; if (!(fs.flags & B_FS_IS_PERSISTENT)) disk->type = (FFDiskVolumeType) (disk->type | FF_DISK_VOLUME_TYPE_HIDDEN_BIT); if (fs.flags & B_FS_IS_READONLY) disk->type = (FFDiskVolumeType) (disk->type | FF_DISK_VOLUME_TYPE_READONLY_BIT); if (fs.flags & B_FS_IS_REMOVABLE) disk->type = (FFDiskVolumeType) (disk->type | FF_DISK_VOLUME_TYPE_EXTERNAL_BIT); if (disk->type == FF_DISK_VOLUME_TYPE_NONE) disk->type = FF_DISK_VOLUME_TYPE_REGULAR_BIT; disk->createTime = 0; time_t crTime; if (dir.GetCreationTime(&crTime) == B_OK) disk->createTime = (uint64_t) crTime * 1000; } return NULL; } ================================================ FILE: src/detection/disk/disk_linux.c ================================================ #include "disk.h" #include "common/io.h" #include "common/stringUtils.h" #include #include #include #include #include #include #if defined(STATX_BTIME) && !defined(__ANDROID__) #include #endif #ifdef __USE_LARGEFILE64 #define stat stat64 #define statvfs statvfs64 #define dirent dirent64 #define readdir readdir64 #endif static bool isPhysicalDevice(const struct mntent* device) { #ifndef __ANDROID__ //On Android, `/dev` is not accessible, so that the following checks always fail //Always show the root path if(ffStrEquals(device->mnt_dir, "/")) return true; if(ffStrEquals(device->mnt_fsname, "none")) return false; //DrvFs is a filesystem plugin to WSL that was designed to support interop between WSL and the Windows filesystem. if(ffStrEquals(device->mnt_type, "9p")) return ffStrContains(device->mnt_opts, "aname=drvfs"); //ZFS pool if(ffStrEquals(device->mnt_type, "zfs")) return true; //sshfs if(ffStrEquals(device->mnt_type, "fuse.sshfs")) return true; //Pseudo filesystems don't have a device in /dev if(!ffStrStartsWith(device->mnt_fsname, "/dev/")) return false; //#731 if(ffStrEquals(device->mnt_type, "bcachefs")) return true; if( ffStrStartsWith(device->mnt_fsname + 5, "loop") || //Ignore loop devices ffStrStartsWith(device->mnt_fsname + 5, "ram") || //Ignore ram devices ffStrStartsWith(device->mnt_fsname + 5, "fd") //Ignore fd devices ) return false; if (ffStrStartsWith(device->mnt_dir, "/bedrock/")) // Ignore Bedrock Linux subvolumes return false; struct stat deviceStat; if(stat(device->mnt_fsname, &deviceStat) != 0) return false; //Ignore all devices that are not block devices if(!S_ISBLK(deviceStat.st_mode)) return false; #else //Pseudo filesystems don't have a device in /dev if(!ffStrStartsWith(device->mnt_fsname, "/dev/")) return false; if( ffStrStartsWith(device->mnt_fsname + 5, "loop") || //Ignore loop devices ffStrStartsWith(device->mnt_fsname + 5, "ram") || //Ignore ram devices ffStrStartsWith(device->mnt_fsname + 5, "fd") //Ignore fd devices ) return false; // https://source.android.com/docs/core/ota/apex?hl=zh-cn if(ffStrStartsWith(device->mnt_dir, "/apex/")) return false; #endif // __ANDROID__ return true; } static void detectNameFromPath(FFDisk* disk, const struct stat* deviceStat, FFstrbuf* basePath) { FF_AUTO_CLOSE_DIR DIR* dir = opendir(basePath->chars); if(dir == NULL) return; uint32_t basePathLength = basePath->length; struct dirent* entry; while((entry = readdir(dir)) != NULL) { if(entry->d_name[0] == '.') continue; ffStrbufAppendS(basePath, entry->d_name); struct stat entryStat; bool ret = stat(basePath->chars, &entryStat) == 0; ffStrbufSubstrBefore(basePath, basePathLength); if(!ret || deviceStat->st_ino != entryStat.st_ino) continue; ffStrbufAppendS(&disk->name, entry->d_name); break; } } static void detectName(FFDisk* disk) { struct stat deviceStat; if(stat(disk->mountFrom.chars, &deviceStat) != 0) return; FF_STRBUF_AUTO_DESTROY basePath = ffStrbufCreate(); //Try label first ffStrbufSetS(&basePath, "/dev/disk/by-label/"); detectNameFromPath(disk, &deviceStat, &basePath); if(disk->name.length == 0) { //Try partlabel second ffStrbufSetS(&basePath, "/dev/disk/by-partlabel/"); detectNameFromPath(disk, &deviceStat, &basePath); } if (disk->name.length == 0) return; // Basic\x20data\x20partition for (uint32_t i = ffStrbufFirstIndexS(&disk->name, "\\x"); i != disk->name.length; i = ffStrbufNextIndexS(&disk->name, i + 1, "\\x")) { uint32_t len = (uint32_t) strlen("\\x20"); if (disk->name.length >= len) { char bak = disk->name.chars[i + len]; disk->name.chars[i + len] = '\0'; disk->name.chars[i] = (char) strtoul(&disk->name.chars[i + 2], NULL, 16); ffStrbufRemoveSubstr(&disk->name, i + 1, i + len); disk->name.chars[i + 1] = bak; } } } #ifdef __ANDROID__ static void detectType(FF_MAYBE_UNUSED const FFlist* disks, FFDisk* currentDisk, FF_MAYBE_UNUSED struct mntent* device) { if(ffStrbufEqualS(¤tDisk->mountpoint, "/") || ffStrbufEqualS(¤tDisk->mountpoint, "/storage/emulated")) currentDisk->type = FF_DISK_VOLUME_TYPE_REGULAR_BIT; else if(ffStrbufStartsWithS(¤tDisk->mountpoint, "/mnt/media_rw/")) currentDisk->type = FF_DISK_VOLUME_TYPE_EXTERNAL_BIT; else currentDisk->type = FF_DISK_VOLUME_TYPE_HIDDEN_BIT; } #else static bool isSubvolume(const FFlist* disks, FFDisk* currentDisk) { if(ffStrbufEqualS(¤tDisk->mountFrom, "drvfs")) // WSL Windows drives return false; if(ffStrbufEqualS(¤tDisk->filesystem, "zfs")) { //ZFS subvolumes uint32_t index = ffStrbufFirstIndexC(¤tDisk->mountFrom, '/'); if (index == currentDisk->mountFrom.length) return false; FF_STRBUF_AUTO_DESTROY zpoolName = ffStrbufCreateNS(index, currentDisk->mountFrom.chars); for(uint32_t i = 0; i < disks->length - 1; i++) { const FFDisk* otherDevice = FF_LIST_GET(FFDisk, *disks, i); if(ffStrbufEqualS(&otherDevice->filesystem, "zfs") && ffStrbufStartsWith(&otherDevice->mountFrom, &zpoolName)) return true; } return false; } else { //Filter all disks which device was already found. This catches BTRFS subvolumes. for(uint32_t i = 0; i < disks->length - 1; i++) { const FFDisk* otherDevice = FF_LIST_GET(FFDisk, *disks, i); if(ffStrbufEqual(¤tDisk->mountFrom, &otherDevice->mountFrom)) return true; } } return false; } static bool isRemovable(FFDisk* currentDisk) { if (!ffStrbufStartsWithS(¤tDisk->mountFrom, "/dev/")) return false; char sysBlockPartition[64]; snprintf(sysBlockPartition, ARRAY_SIZE(sysBlockPartition), "/sys/class/block/%s", currentDisk->mountFrom.chars + strlen("/dev/")); char sysBlockVolume[PATH_MAX]; // /sys/devices/pci0000:00/0000:00:14.0/usb4/4-3/4-3:1.0/host0/target0:0:0/0:0:0:0/block/sda/sda1 if (realpath(sysBlockPartition, sysBlockVolume) == NULL) return false; char* lastSlash = strrchr(sysBlockVolume, '/'); if (lastSlash == NULL) return false; strcpy(lastSlash + 1, "removable"); char removableChar = '0'; return ffReadFileData(sysBlockVolume, 1, &removableChar) > 0 && removableChar == '1'; } static void detectType(const FFlist* disks, FFDisk* currentDisk, struct mntent* device) { if(hasmntopt(device, "x-gvfs-hide") || hasmntopt(device, "hidden")) currentDisk->type = FF_DISK_VOLUME_TYPE_HIDDEN_BIT; else if(isSubvolume(disks, currentDisk)) currentDisk->type = FF_DISK_VOLUME_TYPE_SUBVOLUME_BIT; else if(isRemovable(currentDisk)) currentDisk->type = FF_DISK_VOLUME_TYPE_EXTERNAL_BIT; else currentDisk->type = FF_DISK_VOLUME_TYPE_REGULAR_BIT; if (hasmntopt(device, MNTOPT_RO)) currentDisk->type |= FF_DISK_VOLUME_TYPE_READONLY_BIT; } #endif static void detectStats(FFDisk* disk) { struct statvfs fs; if(statvfs(disk->mountpoint.chars, &fs) != 0) memset(&fs, 0, sizeof(fs)); //Set all values to 0, so our values get initialized to 0 too disk->bytesTotal = fs.f_blocks * (uint64_t) fs.f_frsize; disk->bytesFree = fs.f_bfree * (uint64_t) fs.f_frsize; disk->bytesAvailable = fs.f_bavail * (uint64_t) fs.f_frsize; disk->bytesUsed = 0; // To be filled in ./disk.c if (fs.f_files >= fs.f_ffree) { disk->filesTotal = (uint32_t) fs.f_files; disk->filesUsed = (uint32_t) (disk->filesTotal - fs.f_ffree); } else { // Windows filesystem in WSL disk->filesTotal = disk->filesUsed = 0; } disk->createTime = 0; #ifdef SYS_statx struct statx stx; if (syscall(SYS_statx, 0, disk->mountpoint.chars, 0, STATX_BTIME, &stx) == 0 && (stx.stx_mask & STATX_BTIME) && stx.stx_btime.tv_sec > 685065600 /*birth of Linux*/) disk->createTime = (uint64_t)((stx.stx_btime.tv_sec * 1000) + (stx.stx_btime.tv_nsec / 1000000)); #endif #ifdef __ANDROID__ // hasmntopt requires a higher Android API level if(fs.f_flag & ST_RDONLY) disk->type |= FF_DISK_VOLUME_TYPE_READONLY_BIT; #endif } const char* ffDetectDisksImpl(FFDiskOptions* options, FFlist* disks) { FILE* mountsFile = setmntent("/proc/mounts", "r"); if(mountsFile == NULL) return "setmntent(\"/proc/mounts\", \"r\") == NULL"; struct mntent* device; while((device = getmntent(mountsFile))) { if (__builtin_expect(options->folders.length > 0, false)) { if (!ffStrbufSeparatedContainS(&options->folders, device->mnt_dir, FF_DISK_FOLDER_SEPARATOR)) continue; } else if(!isPhysicalDevice(device)) continue; if (options->hideFolders.length && ffDiskMatchesFolderPatterns(&options->hideFolders, device->mnt_dir, FF_DISK_FOLDER_SEPARATOR)) continue; if (options->hideFS.length && ffStrbufSeparatedContainS(&options->hideFS, device->mnt_type, ':')) continue; //We have a valid device, add it to the list FFDisk* disk = ffListAdd(disks); disk->type = FF_DISK_VOLUME_TYPE_NONE; //detect mountFrom ffStrbufInitS(&disk->mountFrom, device->mnt_fsname); //detect mountpoint ffStrbufInitS(&disk->mountpoint, device->mnt_dir); //detect filesystem ffStrbufInitS(&disk->filesystem, device->mnt_type); //detect name ffStrbufInit(&disk->name); detectName(disk); // Also detects external devices //detect type detectType(disks, disk, device); //Detects stats detectStats(disk); } endmntent(mountsFile); return NULL; } ================================================ FILE: src/detection/disk/disk_nosupport.c ================================================ #include "disk.h" const char* ffDetectDisksImpl(FF_MAYBE_UNUSED FFDiskOptions* options, FF_MAYBE_UNUSED FFlist* disks) { return "Not supported on this platform"; } ================================================ FILE: src/detection/disk/disk_sunos.c ================================================ #include "disk.h" #include "common/io.h" #include "common/stringUtils.h" #include #include #include #include #include static bool isPhysicalDevice(const struct mnttab* device) { //Always show the root path if(ffStrEquals(device->mnt_mountp, "/")) return true; if(ffStrEquals(device->mnt_special, "none")) return false; //ZFS pool if(ffStrEquals(device->mnt_fstype, "zfs")) return true; //Pseudo filesystems don't have a device in /dev if(!ffStrStartsWith(device->mnt_special, "/dev/")) return false; struct stat deviceStat; if(stat(device->mnt_special, &deviceStat) != 0) return false; //Ignore all devices that are not block devices if(!S_ISBLK(deviceStat.st_mode)) return false; return true; } static bool isSubvolume(const FFlist* disks, FFDisk* currentDisk) { if(ffStrbufEqualS(¤tDisk->filesystem, "zfs")) { //ZFS subvolumes uint32_t index = ffStrbufFirstIndexC(¤tDisk->mountFrom, '/'); if (index == currentDisk->mountFrom.length) return false; FF_STRBUF_AUTO_DESTROY zpoolName = ffStrbufCreateNS(index, currentDisk->mountFrom.chars); for(uint32_t i = 0; i < disks->length - 1; i++) { const FFDisk* otherDevice = FF_LIST_GET(FFDisk, *disks, i); if(ffStrbufEqualS(&otherDevice->filesystem, "zfs") && ffStrbufStartsWith(&otherDevice->mountFrom, &zpoolName)) return true; } return false; } else { //Filter all disks which device was already found. This catches BTRFS subvolumes. for(uint32_t i = 0; i < disks->length - 1; i++) { const FFDisk* otherDevice = FF_LIST_GET(FFDisk, *disks, i); if(ffStrbufEqual(¤tDisk->mountFrom, &otherDevice->mountFrom)) return true; } } return false; } static void detectType(const FFlist* disks, FFDisk* currentDisk, struct mnttab* device) { if(hasmntopt(device, MNTOPT_NOBROWSE)) currentDisk->type = FF_DISK_VOLUME_TYPE_HIDDEN_BIT; else if(isSubvolume(disks, currentDisk)) currentDisk->type = FF_DISK_VOLUME_TYPE_SUBVOLUME_BIT; else currentDisk->type = FF_DISK_VOLUME_TYPE_REGULAR_BIT; if (hasmntopt(device, MNTOPT_RO)) currentDisk->type |= FF_DISK_VOLUME_TYPE_READONLY_BIT; } static void detectStats(FFDisk* disk) { struct statvfs fs; if(statvfs(disk->mountpoint.chars, &fs) != 0) memset(&fs, 0, sizeof(fs)); disk->bytesTotal = fs.f_blocks * fs.f_frsize; disk->bytesFree = fs.f_bfree * fs.f_frsize; disk->bytesAvailable = fs.f_bavail * fs.f_frsize; disk->bytesUsed = 0; // To be filled in ./disk.c disk->filesTotal = (uint32_t) fs.f_files; disk->filesUsed = (uint32_t) (disk->filesTotal - fs.f_ffree); ffStrbufSetS(&disk->name, fs.f_fstr); disk->createTime = 0; } const char* ffDetectDisksImpl(FFDiskOptions* options, FFlist* disks) { FF_AUTO_CLOSE_FILE FILE* mountsFile = fopen(MNTTAB, "r"); if(mountsFile == NULL) return "fopen(\"" MNTTAB "\", \"r\") == NULL"; struct mnttab device; while (getmntent(mountsFile, &device) == 0) { if (__builtin_expect(options->folders.length, 0)) { if (!ffStrbufSeparatedContainS(&options->folders, device.mnt_mountp, FF_DISK_FOLDER_SEPARATOR)) continue; } else if(!isPhysicalDevice(&device)) continue; if (options->hideFolders.length && ffDiskMatchesFolderPatterns(&options->hideFolders, device.mnt_mountp, FF_DISK_FOLDER_SEPARATOR)) continue; if (options->hideFS.length && ffStrbufSeparatedContainS(&options->hideFS, device.mnt_fstype, ':')) continue; //We have a valid device, add it to the list FFDisk* disk = ffListAdd(disks); disk->type = FF_DISK_VOLUME_TYPE_NONE; ffStrbufInitS(&disk->mountFrom, device.mnt_special); ffStrbufInitS(&disk->mountpoint, device.mnt_mountp); ffStrbufInitS(&disk->filesystem, device.mnt_fstype); ffStrbufInit(&disk->name); detectType(disks, disk, &device); detectStats(disk); } return NULL; } ================================================ FILE: src/detection/disk/disk_windows.c ================================================ #include "disk.h" #include "common/io.h" #include "common/time.h" #include "common/windows/unicode.h" #include "common/windows/nt.h" #include #include #include #include const char* ffDetectDisksImpl(FFDiskOptions* options, FFlist* disks) { PROCESS_DEVICEMAP_INFORMATION_EX info = {}; ULONG size = 0; if(!NT_SUCCESS(NtQueryInformationProcess(NtCurrentProcess(), ProcessDeviceMap, &info, sizeof(info), &size))) return "NtQueryInformationProcess(ProcessDeviceMap) failed"; // For cross-platform portability; used by `presets/examples/13.jsonc` if (options->folders.length == 1 && options->folders.chars[0] == '/') { options->folders.chars[0] = (char) SharedUserData->NtSystemRoot[0]; ffStrbufAppendS(&options->folders, ":\\"); } wchar_t mountpointW[] = L"X:\\"; char mountpointA[] = "X:\\"; for (wchar_t i = L'A'; i <= L'Z'; i++) { if (!(info.Query.DriveMap & (1 << (i - L'A')))) continue; mountpointW[0] = i; mountpointA[0] = (char) i; UINT driveType = info.Query.DriveType[i - L'A']; if (__builtin_expect((long) options->folders.length, 0)) { if (!ffStrbufSeparatedContainNS(&options->folders, 3, mountpointA, FF_DISK_FOLDER_SEPARATOR)) continue; } else if(driveType == DRIVE_NO_ROOT_DIR) continue; if (options->hideFolders.length && ffStrbufSeparatedContainNS(&options->hideFolders, 3, mountpointA, FF_DISK_FOLDER_SEPARATOR)) continue; FF_AUTO_CLOSE_FD HANDLE handle = CreateFileW(mountpointW, FILE_READ_ATTRIBUTES, FILE_SHARE_READ, NULL, OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS, NULL); if (handle == INVALID_HANDLE_VALUE) continue; IO_STATUS_BLOCK iosb; alignas(FILE_FS_ATTRIBUTE_INFORMATION) uint8_t bufFsAttr[1024]; FILE_FS_ATTRIBUTE_INFORMATION* fsAttr = NT_SUCCESS(NtQueryVolumeInformationFile(handle, &iosb, bufFsAttr, sizeof(bufFsAttr), FileFsAttributeInformation)) ? (FILE_FS_ATTRIBUTE_INFORMATION*) bufFsAttr : NULL; FF_STRBUF_AUTO_DESTROY diskFileSystemBuf = ffStrbufCreate(); if (fsAttr) { ffStrbufSetNWS(&diskFileSystemBuf, fsAttr->FileSystemNameLength / sizeof(WCHAR), fsAttr->FileSystemName); if (options->hideFS.length && ffStrbufSeparatedContain(&options->hideFS, &diskFileSystemBuf, ':')) continue; } FFDisk* disk = ffListAdd(disks); disk->filesUsed = 0; disk->filesTotal = 0; disk->bytesTotal = 0; disk->bytesFree = 0; disk->bytesUsed = 0; // To be filled in ./disk.c disk->bytesAvailable = 0; disk->createTime = 0; ffStrbufInit(&disk->filesystem); ffStrbufInit(&disk->name); ffStrbufInitNS(&disk->mountpoint, 3, mountpointA); ffStrbufInit(&disk->mountFrom); disk->type = driveType == DRIVE_REMOVABLE || driveType == DRIVE_REMOTE || driveType == DRIVE_CDROM ? FF_DISK_VOLUME_TYPE_EXTERNAL_BIT : driveType == DRIVE_FIXED ? FF_DISK_VOLUME_TYPE_REGULAR_BIT : FF_DISK_VOLUME_TYPE_HIDDEN_BIT; { wchar_t volumeName[MAX_PATH + 1]; mountpointW[2] = L'\0'; if(QueryDosDeviceW(mountpointW, volumeName, ARRAY_SIZE(volumeName))) ffStrbufSetWS(&disk->mountFrom, volumeName); mountpointW[2] = L'\\'; } alignas(FILE_FS_VOLUME_INFORMATION) uint8_t bufFsVolume[1024]; FILE_FS_VOLUME_INFORMATION* fsVolume = NT_SUCCESS(NtQueryVolumeInformationFile(handle, &iosb, bufFsVolume, sizeof(bufFsVolume), FileFsVolumeInformation)) ? (FILE_FS_VOLUME_INFORMATION*) bufFsVolume : NULL; if (fsVolume) { if (fsVolume->VolumeLabelLength > 0) ffStrbufSetNWS(&disk->name, fsVolume->VolumeLabelLength / sizeof(WCHAR), fsVolume->VolumeLabel); if (fsVolume->VolumeCreationTime.QuadPart) disk->createTime = ffFileTimeToUnixMs((uint64_t) fsVolume->VolumeCreationTime.QuadPart); } if (fsAttr) { ffStrbufInitMove(&disk->filesystem, &diskFileSystemBuf); if(fsAttr->FileSystemAttributes & FILE_READ_ONLY_VOLUME) disk->type |= FF_DISK_VOLUME_TYPE_READONLY_BIT; } FILE_FS_FULL_SIZE_INFORMATION fsFullSize; if (NT_SUCCESS(NtQueryVolumeInformationFile(handle, &iosb, &fsFullSize, sizeof(fsFullSize), FileFsFullSizeInformation))) { uint64_t units = fsFullSize.BytesPerSector * fsFullSize.SectorsPerAllocationUnit; disk->bytesTotal = (uint64_t) fsFullSize.TotalAllocationUnits.QuadPart * units; disk->bytesFree = (uint64_t) fsFullSize.ActualAvailableAllocationUnits.QuadPart * units; disk->bytesAvailable = (uint64_t) fsFullSize.CallerAvailableAllocationUnits.QuadPart * units; } } return NULL; } ================================================ FILE: src/detection/diskio/diskio.c ================================================ #include "diskio.h" #include "common/time.h" const char* ffDiskIOGetIoCounters(FFlist* result, FFDiskIOOptions* options); static FFlist ioCounters1; static uint64_t time1; void ffPrepareDiskIO(FFDiskIOOptions* options) { if (options->detectTotal) return; if (time1 != 0) return; // Already prepared ffListInit(&ioCounters1, sizeof(FFDiskIOResult)); ffDiskIOGetIoCounters(&ioCounters1, options); time1 = ffTimeGetNow(); } const char* ffDetectDiskIO(FFlist* result, FFDiskIOOptions* options) { const char* error = NULL; if (options->detectTotal) { error = ffDiskIOGetIoCounters(result, options); if (error) return error; return NULL; } if (time1 == 0) { ffListInit(&ioCounters1, sizeof(FFDiskIOResult)); error = ffDiskIOGetIoCounters(&ioCounters1, options); if (error) return error; time1 = ffTimeGetNow(); } if (ioCounters1.length == 0) return "No physical disk found"; uint64_t time2 = ffTimeGetNow(); while (time2 - time1 < options->waitTime) { ffTimeSleep((uint32_t) (options->waitTime - (time2 - time1))); time2 = ffTimeGetNow(); } error = ffDiskIOGetIoCounters(result, options); if (error) return error; if (result->length != ioCounters1.length) return "Different number of physical disks. Hardware change?"; for (uint32_t i = 0; i < result->length; ++i) { FFDiskIOResult* icPrev = FF_LIST_GET(FFDiskIOResult, ioCounters1, i); FFDiskIOResult* icCurr = FF_LIST_GET(FFDiskIOResult, *result, i); if (!ffStrbufEqual(&icPrev->devPath, &icCurr->devPath)) return "Physical disk device path changed"; static_assert(sizeof(FFDiskIOResult) - offsetof(FFDiskIOResult, bytesRead) == sizeof(uint64_t) * 4, "Unexpected struct FFDiskIOResult layout"); for (size_t off = offsetof(FFDiskIOResult, bytesRead); off < sizeof(FFDiskIOResult); off += sizeof(uint64_t)) { uint64_t* prevValue = (uint64_t*) ((uint8_t*) icPrev + off); uint64_t* currValue = (uint64_t*) ((uint8_t*) icCurr + off); uint64_t temp = *currValue; *currValue -= *prevValue; *currValue /= (time2 - time1) / 1000 /* seconds */; // For next function call *prevValue = temp; } } // For next function call time1 = time2; // Leak ioCounters1 here return NULL; } ================================================ FILE: src/detection/diskio/diskio.h ================================================ #pragma once #include "fastfetch.h" #include "modules/diskio/option.h" typedef struct FFDiskIOResult { FFstrbuf name; FFstrbuf devPath; uint64_t bytesRead; uint64_t readCount; uint64_t bytesWritten; uint64_t writeCount; } FFDiskIOResult; const char* ffDetectDiskIO(FFlist* result, FFDiskIOOptions* options); ================================================ FILE: src/detection/diskio/diskio_apple.c ================================================ #include "diskio.h" #include "common/apple/cf_helpers.h" #include #include #include #include #include #include const char* ffDiskIOGetIoCounters(FFlist* result, FFDiskIOOptions* options) { FF_IOOBJECT_AUTO_RELEASE io_iterator_t iterator = 0; if (IOServiceGetMatchingServices(MACH_PORT_NULL, IOServiceMatching(kIOMediaClass), &iterator) != KERN_SUCCESS) return "IOServiceGetMatchingServices() failed"; io_registry_entry_t registryEntry; while ((registryEntry = IOIteratorNext(iterator)) != IO_OBJECT_NULL) { FF_IOOBJECT_AUTO_RELEASE io_registry_entry_t entryPartition = registryEntry; io_name_t deviceName; if (IORegistryEntryGetName(registryEntry, deviceName) != KERN_SUCCESS) continue; if (options->namePrefix.length && strncmp(deviceName, options->namePrefix.chars, options->namePrefix.length) != 0) continue; FF_IOOBJECT_AUTO_RELEASE io_registry_entry_t entryDriver = 0; if (IORegistryEntryGetParentEntry(entryPartition, kIOServicePlane, &entryDriver) != KERN_SUCCESS) continue; if (!IOObjectConformsTo(entryDriver, kIOBlockStorageDriverClass)) // physical disk only continue; FF_CFTYPE_AUTO_RELEASE CFDictionaryRef statistics = IORegistryEntryCreateCFProperty(entryDriver, CFSTR(kIOBlockStorageDriverStatisticsKey), kCFAllocatorDefault, kNilOptions); if (!statistics) continue; FFDiskIOResult* device = (FFDiskIOResult*) ffListAdd(result); ffStrbufInitS(&device->name, deviceName); ffStrbufInit(&device->devPath); ffCfDictGetInt64(statistics, CFSTR(kIOBlockStorageDriverStatisticsBytesReadKey), (int64_t*) &device->bytesRead); ffCfDictGetInt64(statistics, CFSTR(kIOBlockStorageDriverStatisticsBytesWrittenKey), (int64_t*) &device->bytesWritten); ffCfDictGetInt64(statistics, CFSTR(kIOBlockStorageDriverStatisticsReadsKey), (int64_t*) &device->readCount); ffCfDictGetInt64(statistics, CFSTR(kIOBlockStorageDriverStatisticsWritesKey), (int64_t*) &device->writeCount); FF_CFTYPE_AUTO_RELEASE CFStringRef bsdName = IORegistryEntryCreateCFProperty(entryPartition, CFSTR(kIOBSDNameKey), kCFAllocatorDefault, kNilOptions); if (bsdName) { ffCfStrGetString(bsdName, &device->devPath); ffStrbufPrependS(&device->devPath, "/dev/"); } } return NULL; } ================================================ FILE: src/detection/diskio/diskio_bsd.c ================================================ #include "diskio.h" #if __has_include() #include "common/stringUtils.h" #include #include #include #include #include #include const char* ffDiskIOGetIoCounters(FFlist* result, FFDiskIOOptions* options) { __attribute__((__cleanup__(geom_deletetree))) struct gmesh geomTree = {}; if (geom_gettree(&geomTree) < 0) return "geom_gettree() failed"; if (geom_stats_open() < 0) return "geom_stats_open() failed"; void* snap = geom_stats_snapshot_get(); if (!snap) return "geom_stats_snapshot_get() failed"; struct devstat* snapIter; while ((snapIter = geom_stats_snapshot_next(snap)) != NULL) { if (snapIter->device_type & DEVSTAT_TYPE_PASS) continue; struct gident* geomId = geom_lookupid(&geomTree, snapIter->id); if (geomId == NULL) continue; if (geomId->lg_what != ISPROVIDER) continue; struct gprovider* provider = (struct gprovider*) geomId->lg_ptr; if (provider->lg_geom->lg_rank != 1) continue; FF_STRBUF_AUTO_DESTROY name = ffStrbufCreate(); for (struct gconfig* ptr = provider->lg_config.lh_first; ptr; ptr = ptr->lg_config.le_next) { if (ffStrEquals(ptr->lg_name, "descr")) ffStrbufSetS(&name, ptr->lg_val); } if (name.length == 0) ffStrbufSetS(&name, provider->lg_name); if (options->namePrefix.length && !ffStrbufStartsWith(&name, &options->namePrefix)) continue; FFDiskIOResult* device = (FFDiskIOResult*) ffListAdd(result); ffStrbufInitF(&device->devPath, "/dev/%s", provider->lg_name); device->bytesRead = snapIter->bytes[DEVSTAT_READ]; device->readCount = snapIter->operations[DEVSTAT_READ]; device->bytesWritten = snapIter->bytes[DEVSTAT_WRITE]; device->writeCount = snapIter->operations[DEVSTAT_WRITE]; ffStrbufInitMove(&device->name, &name); } geom_stats_snapshot_free(snap); geom_stats_close(); return NULL; } #else #include #include const char* ffDiskIOGetIoCounters(FFlist* result, FFDiskIOOptions* options) { if (checkversion() < 0) return "checkversion() failed"; struct statinfo stats = { .dinfo = (struct devinfo *)calloc(1, sizeof(struct devinfo)), }; if (getdevs(&stats) < 0) return "getdevs() failed"; for (int i = 0; i < stats.dinfo->numdevs; i++) { struct devstat* current = &stats.dinfo->devices[i]; if (current->device_type & DEVSTAT_TYPE_PASS) continue; char deviceName[128]; snprintf(deviceName, sizeof(deviceName), "%s%d", current->device_name, current->unit_number); if (options->namePrefix.length && strncmp(deviceName, options->namePrefix.chars, options->namePrefix.length) != 0) continue; FFDiskIOResult* device = (FFDiskIOResult*) ffListAdd(result); ffStrbufInitS(&device->name, deviceName); ffStrbufInitF(&device->devPath, "/dev/%s", deviceName); device->bytesRead = current->bytes_read; device->readCount = current->num_reads; device->bytesWritten = current->bytes_written; device->writeCount = current->num_writes; } free(stats.dinfo->mem_ptr); free(stats.dinfo); return NULL; } #endif ================================================ FILE: src/detection/diskio/diskio_linux.c ================================================ #include "diskio.h" #include "common/io.h" #include "common/properties.h" #include "common/stringUtils.h" #include #include #include #include static const char* parseDiskIOCounters(int dfd, const char* devName, FFlist* result, FFDiskIOOptions* options) { FF_AUTO_CLOSE_FD int devfd = openat(dfd, "device", O_RDONLY | O_CLOEXEC | O_PATH | O_DIRECTORY); if (devfd < 0) return "virtual device"; FF_STRBUF_AUTO_DESTROY name = ffStrbufCreate(); { if (ffAppendFileBufferRelative(devfd, "vendor", &name)) { ffStrbufTrimRightSpace(&name); if (name.length > 0) ffStrbufAppendC(&name, ' '); } ffAppendFileBufferRelative(devfd, "model", &name); ffStrbufTrimRightSpace(&name); if (name.length == 0) ffStrbufSetS(&name, devName); else if (ffStrStartsWith(devName, "nvme")) { int devid, nsid; if (sscanf(devName, "nvme%dn%d", &devid, &nsid) == 2) { bool multiNs = nsid > 1; if (!multiNs) { char pathSysBlock[16]; snprintf(pathSysBlock, ARRAY_SIZE(pathSysBlock), "nvme%dn2", devid); multiNs = faccessat(devfd, pathSysBlock, F_OK, 0) == 0; } if (multiNs) { // In Asahi Linux, there are multiple namespaces for the same NVMe drive. ffStrbufAppendF(&name, " - %d", nsid); } } } if (options->namePrefix.length && !ffStrbufStartsWith(&name, &options->namePrefix)) return "ignored"; } // I/Os merges sectors ticks ... uint64_t nRead, sectorRead, nWritten, sectorWritten; { char sysBlockStat[PROC_FILE_BUFFSIZ]; ssize_t fileSize = ffReadFileDataRelative(dfd, "stat", ARRAY_SIZE(sysBlockStat) - 1, sysBlockStat); if (fileSize <= 0) return "failed to read stat file"; sysBlockStat[fileSize] = '\0'; if (sscanf(sysBlockStat, "%" PRIu64 "%*u%" PRIu64 "%*u%" PRIu64 "%*u%" PRIu64 "%*u", &nRead, §orRead, &nWritten, §orWritten) <= 0) return "invalid stat file format"; } FFDiskIOResult* device = (FFDiskIOResult*) ffListAdd(result); ffStrbufInitMove(&device->name, &name); ffStrbufInitF(&device->devPath, "/dev/%s", devName); device->bytesRead = sectorRead * 512; device->bytesWritten = sectorWritten * 512; device->readCount = nRead; device->writeCount = nWritten; return NULL; } const char* ffDiskIOGetIoCounters(FFlist* result, FFDiskIOOptions* options) { FF_AUTO_CLOSE_DIR DIR* sysBlockDirp = opendir("/sys/block/"); if(sysBlockDirp == NULL) return "opendir(\"/sys/block/\") == NULL"; struct dirent* sysBlockEntry; while ((sysBlockEntry = readdir(sysBlockDirp)) != NULL) { const char* const devName = sysBlockEntry->d_name; if (devName[0] == '.') continue; FF_AUTO_CLOSE_FD int dfd = openat(dirfd(sysBlockDirp), devName, O_RDONLY | O_CLOEXEC | O_PATH | O_DIRECTORY); if (dfd > 0) parseDiskIOCounters(dfd, devName, result, options); } return NULL; } ================================================ FILE: src/detection/diskio/diskio_nbsd.c ================================================ #include "diskio.h" #include "common/stringUtils.h" #include "common/mallocHelper.h" #include #include const char* ffDiskIOGetIoCounters(FFlist* result, FFDiskIOOptions* options) { int mib[] = {CTL_HW, HW_IOSTATS, sizeof(struct io_sysctl)}; size_t len; if (sysctl(mib, ARRAY_SIZE(mib), NULL, &len, NULL, 0) < 0) return "sysctl({HW_IOSTATS}, NULL) failed"; uint32_t nDrive = (uint32_t) (len / sizeof(struct io_sysctl)); FF_AUTO_FREE struct io_sysctl* stats = malloc(len); if (sysctl(mib, ARRAY_SIZE(mib), stats, &len, NULL, 0) < 0) return "sysctl({HW_IOSTATS}, stats) failed"; for (uint32_t i = 0; i < nDrive; ++i) { struct io_sysctl* st = &stats[i]; if (options->namePrefix.length && strncmp(st->name, options->namePrefix.chars, options->namePrefix.length) != 0) continue; FFDiskIOResult* device = (FFDiskIOResult*) ffListAdd(result); ffStrbufInitF(&device->devPath, "/dev/%s", st->name); ffStrbufInitS(&device->name, st->name); device->bytesRead = st->rbytes; device->readCount = st->rxfer; device->bytesWritten = st->wbytes; device->writeCount = st->wxfer; } return NULL; } ================================================ FILE: src/detection/diskio/diskio_nosupport.c ================================================ #include "diskio.h" const char* ffDiskIOGetIoCounters(FF_MAYBE_UNUSED FFlist* result, FF_MAYBE_UNUSED FFDiskIOOptions* options) { return "Not supported on this platform"; } ================================================ FILE: src/detection/diskio/diskio_obsd.c ================================================ #include "diskio.h" #include "common/stringUtils.h" #include "common/mallocHelper.h" #include #include const char* ffDiskIOGetIoCounters(FFlist* result, FFDiskIOOptions* options) { int mib[] = {CTL_HW, HW_DISKSTATS}; size_t len; if (sysctl(mib, ARRAY_SIZE(mib), NULL, &len, NULL, 0) < 0) return "sysctl({HW_DISKSTATS}, NULL) failed"; uint32_t nDrive = (uint32_t) (len / sizeof(struct diskstats)); FF_AUTO_FREE struct diskstats* stats = malloc(len); if (sysctl(mib, ARRAY_SIZE(mib), stats, &len, NULL, 0) < 0) return "sysctl({HW_DISKSTATS}, stats) failed"; for (uint32_t i = 0; i < nDrive; ++i) { struct diskstats* st = &stats[i]; if (options->namePrefix.length && strncmp(st->ds_name, options->namePrefix.chars, options->namePrefix.length) != 0) continue; FFDiskIOResult* device = (FFDiskIOResult*) ffListAdd(result); ffStrbufInitF(&device->devPath, "/dev/%s", st->ds_name); ffStrbufInitS(&device->name, st->ds_name); device->bytesRead = st->ds_rbytes; device->readCount = st->ds_rxfer; device->bytesWritten = st->ds_wbytes; device->writeCount = st->ds_wxfer; } return NULL; } ================================================ FILE: src/detection/diskio/diskio_sunos.c ================================================ #include "diskio.h" #include "common/stringUtils.h" #include static inline void kstatFreeWrap(kstat_ctl_t** pkc) { assert(pkc); if (*pkc) kstat_close(*pkc); } const char* ffDiskIOGetIoCounters(FFlist* result, FFDiskIOOptions* options) { __attribute__((__cleanup__(kstatFreeWrap))) kstat_ctl_t* kc = kstat_open(); if (!kc) return "kstat_open() failed"; for (kstat_t* ks = kc->kc_chain; ks; ks = ks->ks_next) { if (ks->ks_type != KSTAT_TYPE_IO || !ffStrEquals(ks->ks_class, "disk")) continue; if (options->namePrefix.length && strncmp(ks->ks_name, options->namePrefix.chars, options->namePrefix.length) != 0) continue; kstat_io_t kio; if (kstat_read(kc, ks, &kio) < 0) continue; FFDiskIOResult* device = (FFDiskIOResult*) ffListAdd(result); ffStrbufInit(&device->devPath); // unlike other platforms, `/dev/ks_name` is not available ffStrbufInitS(&device->name, ks->ks_name); device->bytesRead = kio.nread; device->readCount = kio.reads; device->bytesWritten = kio.nwritten; device->writeCount = kio.writes; } return NULL; } ================================================ FILE: src/detection/diskio/diskio_windows.c ================================================ #include "diskio.h" #include "common/io.h" #include "common/windows/unicode.h" #include #include static bool detectPhysicalDisk(const wchar_t* szDevice, FFlist* result, FFDiskIOOptions* options) { FF_AUTO_CLOSE_FD HANDLE hDevice = CreateFileW(szDevice, FILE_READ_ATTRIBUTES, FILE_SHARE_READ, NULL, OPEN_EXISTING, 0, NULL); if (hDevice == INVALID_HANDLE_VALUE) return false; DWORD retSize; char sddBuffer[4096]; if(!DeviceIoControl( hDevice, IOCTL_STORAGE_QUERY_PROPERTY, &(STORAGE_PROPERTY_QUERY) { .PropertyId = StorageDeviceProperty, .QueryType = PropertyStandardQuery, }, sizeof(STORAGE_PROPERTY_QUERY), &sddBuffer, ARRAY_SIZE(sddBuffer), &retSize, NULL ) || retSize == 0) return true; FFDiskIOResult* device = (FFDiskIOResult*) ffListAdd(result); STORAGE_DEVICE_DESCRIPTOR* sdd = (STORAGE_DEVICE_DESCRIPTOR*) sddBuffer; ffStrbufInit(&device->name); if (sdd->VendorIdOffset != 0) { ffStrbufSetS(&device->name, (const char*) sddBuffer + sdd->VendorIdOffset); ffStrbufTrim(&device->name, ' '); } if (sdd->ProductIdOffset != 0) { if (device->name.length) ffStrbufAppendC(&device->name, ' '); ffStrbufAppendS(&device->name, (const char*) sddBuffer + sdd->ProductIdOffset); ffStrbufTrimRight(&device->name, ' '); } if (!device->name.length) ffStrbufSetWS(&device->name, szDevice); if (options->namePrefix.length && !ffStrbufStartsWith(&device->name, &options->namePrefix)) { ffStrbufDestroy(&device->name); result->length--; return true; } DISK_PERFORMANCE dp = {}; if (DeviceIoControl(hDevice, IOCTL_DISK_PERFORMANCE, NULL, 0, &dp, sizeof(dp), &retSize, NULL)) { device->bytesRead = (uint64_t) dp.BytesRead.QuadPart; device->readCount = (uint64_t) dp.ReadCount; device->bytesWritten = (uint64_t) dp.BytesWritten.QuadPart; device->writeCount = (uint64_t) dp.WriteCount; } else { ffStrbufDestroy(&device->name); result->length--; } ffStrbufInitWS(&device->devPath, szDevice); return true; } const char* ffDiskIOGetIoCounters(FFlist* result, FFDiskIOOptions* options) { { wchar_t szPhysicalDrive[32] = L"\\\\.\\PhysicalDrive"; wchar_t* pNum = szPhysicalDrive + strlen("\\\\.\\PhysicalDrive"); for (uint32_t idev = 0; ; ++idev) { _ultow(idev, pNum, 10); if (!detectPhysicalDisk(szPhysicalDrive, result, options)) break; } } { wchar_t szCdrom[32] = L"\\\\.\\CDROM"; wchar_t* pNum = szCdrom + strlen("\\\\.\\CDROM"); for (uint32_t idev = 0; ; ++idev) { _ultow(idev, pNum, 10); if (!detectPhysicalDisk(szCdrom, result, options)) break; } } return NULL; } ================================================ FILE: src/detection/displayserver/displayserver.c ================================================ #include "displayserver.h" FFDisplayResult* ffdsAppendDisplay( FFDisplayServerResult* result, uint32_t width, uint32_t height, double refreshRate, uint32_t dpi, uint32_t preferredWidth, uint32_t preferredHeight, double preferredRefreshRate, uint32_t rotation, FFstrbuf* name, FFDisplayType type, bool primary, uint64_t id, uint32_t physicalWidth, uint32_t physicalHeight, const char* platformApi) { if(width == 0 || height == 0) return NULL; FFDisplayResult* display = (FFDisplayResult*) ffListAdd(&result->displays); display->width = width; display->height = height; display->refreshRate = refreshRate; display->dpi = dpi ?: 96; // 0 means unknown display->preferredWidth = preferredWidth; display->preferredHeight = preferredHeight; display->preferredRefreshRate = preferredRefreshRate; display->rotation = rotation; ffStrbufInitMove(&display->name, name); display->type = type; display->id = id; display->physicalWidth = physicalWidth; display->physicalHeight = physicalHeight; display->primary = primary; display->platformApi = platformApi; display->bitDepth = 0; display->hdrStatus = FF_DISPLAY_HDR_STATUS_UNKNOWN; display->manufactureYear = 0; display->manufactureWeek = 0; display->serial = 0; display->drrStatus = FF_DISPLAY_DRR_STATUS_UNKNOWN; return display; } void ffConnectDisplayServerImpl(FFDisplayServerResult* ds); const FFDisplayServerResult* ffConnectDisplayServer() { static FFDisplayServerResult result; if (result.displays.elementSize == 0) { ffStrbufInit(&result.wmProcessName); ffStrbufInit(&result.wmPrettyName); ffStrbufInit(&result.wmProtocolName); ffStrbufInit(&result.deProcessName); ffStrbufInit(&result.dePrettyName); ffListInit(&result.displays, sizeof(FFDisplayResult)); ffConnectDisplayServerImpl(&result); } return &result; } ================================================ FILE: src/detection/displayserver/displayserver.h ================================================ #pragma once #include "fastfetch.h" #include "modules/display/option.h" #define FF_DE_PRETTY_PLASMA "KDE Plasma" #define FF_DE_PRETTY_GNOME "GNOME" #define FF_DE_PRETTY_GNOME_CLASSIC "GNOME Classic" #define FF_DE_PRETTY_XFCE4 "Xfce4" #define FF_DE_PRETTY_CINNAMON "Cinnamon" #define FF_DE_PRETTY_MATE "Mate" #define FF_DE_PRETTY_LXDE "LXDE" #define FF_DE_PRETTY_LXQT "LXQt" #define FF_DE_PRETTY_BUDGIE "Budgie" #define FF_DE_PRETTY_CDE "CDE" #define FF_DE_PRETTY_UNITY "Unity" #define FF_DE_PRETTY_UKUI "UKUI" #define FF_WM_PRETTY_KWIN "KWin" #define FF_WM_PRETTY_MUTTER "Mutter" #define FF_WM_PRETTY_MUFFIN "Muffin" #define FF_WM_PRETTY_MARCO "Marco" #define FF_WM_PRETTY_XFWM4 "Xfwm4" #define FF_WM_PRETTY_OPENBOX "Openbox" #define FF_WM_PRETTY_I3 "i3" #define FF_WM_PRETTY_HYPRLAND "Hyprland" #define FF_WM_PRETTY_WAYFIRE "Wayfire" #define FF_WM_PRETTY_SWAY "Sway" #define FF_WM_PRETTY_BSPWM "bspwm" #define FF_WM_PRETTY_DWM "dwm" #define FF_WM_PRETTY_WESTON "Weston" #define FF_WM_PRETTY_XMONAD "XMonad" #define FF_WM_PRETTY_WSLG "WSLg" #define FF_WM_PRETTY_TINYWM "TinyWM" #define FF_WM_PRETTY_QTILE "Qtile" #define FF_WM_PRETTY_HERBSTLUFTWM "herbstluftwm" #define FF_WM_PRETTY_ICEWM "IceWM" #define FF_WM_PRETTY_SPECTRWM "spectrwm" #define FF_WM_PRETTY_DTWM "dtwm" #define FF_WM_PRETTY_FVWM "fvwm" #define FF_WM_PRETTY_CTWM "ctwm" #define FF_WM_PRETTY_RATPOISON "ratpoison" #define FF_WM_PROTOCOL_TTY "TTY" #define FF_WM_PROTOCOL_X11 "X11" #define FF_WM_PROTOCOL_WAYLAND "Wayland" #define FF_WM_PROTOCOL_SURFACEFLINGER "SurfaceFlinger" typedef enum __attribute__((__packed__)) FFDisplayType { FF_DISPLAY_TYPE_UNKNOWN, FF_DISPLAY_TYPE_BUILTIN, FF_DISPLAY_TYPE_EXTERNAL, } FFDisplayType; typedef enum __attribute__((__packed__)) FFDisplayHdrStatus { FF_DISPLAY_HDR_STATUS_UNKNOWN, FF_DISPLAY_HDR_STATUS_UNSUPPORTED, FF_DISPLAY_HDR_STATUS_SUPPORTED, FF_DISPLAY_HDR_STATUS_ENABLED, } FFDisplayHdrStatus; typedef enum __attribute__((__packed__)) FFDisplayVrrStatus { FF_DISPLAY_DRR_STATUS_UNKNOWN, FF_DISPLAY_DRR_STATUS_DISABLED, FF_DISPLAY_DRR_STATUS_ENABLED, } FFDisplayVrrStatus; typedef struct FFDisplayResult { uint32_t width; // in px uint32_t height; // in px double refreshRate; // in Hz uint32_t dpi; // Base 96 uint32_t preferredWidth; // in px uint32_t preferredHeight; // in px double preferredRefreshRate; // in Hz FFstrbuf name; FFDisplayType type; uint32_t rotation; uint64_t id; // platform dependent uint32_t physicalWidth; // in mm uint32_t physicalHeight; // in mm bool primary; const char* platformApi; uint8_t bitDepth; FFDisplayHdrStatus hdrStatus; uint16_t manufactureYear; uint16_t manufactureWeek; uint32_t serial; FFDisplayVrrStatus drrStatus; } FFDisplayResult; typedef struct FFDisplayServerResult { FFstrbuf wmProcessName; FFstrbuf wmPrettyName; FFstrbuf wmProtocolName; FFstrbuf deProcessName; FFstrbuf dePrettyName; FFlist displays; //List of FFDisplayResult } FFDisplayServerResult; const FFDisplayServerResult* ffConnectDisplayServer(); FFDisplayResult* ffdsAppendDisplay( FFDisplayServerResult* result, uint32_t width, uint32_t height, double refreshRate, uint32_t dpi, uint32_t preferredWidth, uint32_t preferredHeight, double preferredRefreshRate, uint32_t rotation, FFstrbuf* name, FFDisplayType type, bool primary, uint64_t id, uint32_t physicalWidth, uint32_t physicalHeight, const char* platformApi); ================================================ FILE: src/detection/displayserver/displayserver_android.c ================================================ #include "displayserver.h" #include "common/settings.h" #include "common/processing.h" #include "linux/displayserver_linux.h" #include static bool checkHdrStatus(FFDisplayResult* display) { FF_STRBUF_AUTO_DESTROY buffer = ffStrbufCreate(); if (ffSettingsGetAndroidProperty("ro.surface_flinger.has_HDR_display", &buffer)) { if (ffStrbufIgnCaseEqualS(&buffer, "true")) { display->hdrStatus = FF_DISPLAY_HDR_STATUS_SUPPORTED; if (ffSettingsGetAndroidProperty("persist.sys.hdr_mode", &buffer) && ffStrbufToUInt(&buffer, 0) > 0) display->hdrStatus = FF_DISPLAY_HDR_STATUS_ENABLED; return true; } else { display->hdrStatus = FF_DISPLAY_HDR_STATUS_UNSUPPORTED; return true; } } display->hdrStatus = FF_DISPLAY_HDR_STATUS_UNKNOWN; return false; } static void detectWithDumpsys(FFDisplayServerResult* ds) { FF_STRBUF_AUTO_DESTROY buf = ffStrbufCreate(); if (ffProcessAppendStdOut(&buf, (char* []) { "/system/bin/dumpsys", "display", NULL, }) != NULL || buf.length == 0) return; // Only works in `adb shell`, or when rooted uint32_t index = 0; while ((index = ffStrbufNextIndexS(&buf, index, "DisplayDeviceInfo")) < buf.length) { index += strlen("DisplayDeviceInfo"); uint32_t nextIndex = ffStrbufNextIndexC(&buf, index, '\n'); buf.chars[nextIndex] = '\0'; const char* info = buf.chars + index; // {"Builtin display": uniqueId="local:4630947134992368259", 1440 x 3200, modeId 2, defaultModeId 1, supportedModes [{id=1, width=1440, height=3200, fps=60.000004, alternativeRefreshRates=[24.000002, 30.000002, 40.0, 120.00001, 120.00001, 120.00001, 120.00001, 120.00001]}, FF_STRBUF_AUTO_DESTROY name = ffStrbufCreateA(64); unsigned width = 0, height = 0, modeId = 0; double refreshRate = 0; // {"Builtin display": uniqueId="local:4630947134992368259", 1440 x 3200, modeId 2 int res = sscanf(info, "{\"%63[^\"]\":%*s%u x %u, modeId%u", name.chars, &width, &height, &modeId); if (res >= 3) { if (res == 4) { ++info; // skip first '{' while ((info = strchr(info, '{'))) { ++info; unsigned id; double fps; // id=1, width=1440, height=3200, fps=60.000004, if (sscanf(info, "id=%u, %*s%*s fps=%lf", &id, &fps) >= 2) { if (id == modeId) { refreshRate = fps; break; } } else break; } } ffStrbufRecalculateLength(&name); FFDisplayResult* display = ffdsAppendDisplay(ds, (uint32_t)width, (uint32_t)height, refreshRate, 0, 0, 0, 0, 0, &name, FF_DISPLAY_TYPE_UNKNOWN, false, 0, 0, 0, "dumpsys" ); if (display) display->hdrStatus = checkHdrStatus(display); } index = nextIndex + 1; } } static bool detectWithGetprop(FFDisplayServerResult* ds) { // Only for MiUI FF_STRBUF_AUTO_DESTROY buffer = ffStrbufCreate(); if (ffSettingsGetAndroidProperty("persist.sys.miui_resolution", &buffer) && ffStrbufContainC(&buffer, ',')) { // 1440,3200,560 => width,height,densityDpi uint32_t width = (uint32_t) ffStrbufToUInt(&buffer, 0); ffStrbufSubstrAfterFirstC(&buffer, ','); uint32_t height = (uint32_t) ffStrbufToUInt(&buffer, 0); ffStrbufSubstrAfterFirstC(&buffer, ','); uint32_t dpi = (uint32_t) ffStrbufToUInt(&buffer, 0) * 96 / 160; FFDisplayResult* display = ffdsAppendDisplay(ds, width, height, 0, dpi, 0, 0, 0, 0, NULL, FF_DISPLAY_TYPE_BUILTIN, false, 0, 0, 0, "getprop" ); if (display) display->hdrStatus = checkHdrStatus(display); return !!display; } return false; } static bool detectDE(FFDisplayServerResult* ds) { if (ffSettingsGetAndroidProperty("ro.vivo.os.build.display.id", &ds->dePrettyName)) // OriginOS 6 { ffStrbufAppendC(&ds->dePrettyName, ' '); ffSettingsGetAndroidProperty("ro.vivo.product.version", &ds->dePrettyName); // PD2505D_xxx return true; } if (ffSettingsGetAndroidProperty("ro.build.version.magic", &ds->dePrettyName) || ffSettingsGetAndroidProperty("ro.build.version.emui", &ds->dePrettyName)) { ffStrbufReplaceAllC(&ds->dePrettyName, '_', ' '); return true; } if (ffSettingsGetAndroidProperty("ro.mi.os.version.name", &ds->dePrettyName)) { // MiUI like ffStrbufClear(&ds->dePrettyName); ffSettingsGetAndroidProperty("ro.build.version.incremental", &ds->dePrettyName); // Detail version number if (ffStrbufStartsWithS(&ds->dePrettyName, "OS")) { ds->dePrettyName.chars[0] = 'S'; ds->dePrettyName.chars[1] = ' '; ffStrbufPrependS(&ds->dePrettyName, "HyperO"); } else if (ffStrbufStartsWithS(&ds->dePrettyName, "V")) { ds->dePrettyName.chars[0] = ' '; ffStrbufPrependS(&ds->dePrettyName, "MiUI"); } else ffStrbufSetStatic(&ds->dePrettyName, "MiUI"); return true; } if (ffSettingsGetAndroidProperty("ro.build.version.oplusrom", &ds->dePrettyName)) { if (ffStrbufStartsWithS(&ds->dePrettyName, "V")) ffStrbufSubstrAfter(&ds->dePrettyName, 0); ffStrbufPrependS(&ds->dePrettyName, "ColorOS"); return true; } if (ffSettingsGetAndroidProperty("ro.oxygen.version", &ds->dePrettyName)) { ffStrbufPrependS(&ds->dePrettyName, "OxygenOS"); return true; } if (ffSettingsGetAndroidProperty("ro.build.display.id", &ds->dePrettyName)) { if (ffStrbufStartsWithS(&ds->dePrettyName, "RedMagicOS")) ffStrbufInsertNC(&ds->dePrettyName, strlen("RedMagicOS"), 1, ' '); // Google Pixel uses native Android return true; } return false; } void ffConnectDisplayServerImpl(FFDisplayServerResult* ds) { const char* error = ffdsConnectXcbRandr(ds); if (error) error = ffdsConnectXrandr(ds); if (!error) { ffdsDetectWMDE(ds); return; } // https://source.android.com/docs/core/graphics/surfaceflinger-windowmanager ffStrbufSetStatic(&ds->wmProcessName, "system_server"); ffStrbufSetStatic(&ds->wmPrettyName, "WindowManager"); // A system service managed by system_server ffStrbufSetStatic(&ds->wmProtocolName, FF_WM_PROTOCOL_SURFACEFLINGER); if (!detectWithGetprop(ds)) detectWithDumpsys(ds); detectDE(ds); } ================================================ FILE: src/detection/displayserver/displayserver_apple.c ================================================ #include "displayserver.h" #include "common/apple/cf_helpers.h" #include "common/stringUtils.h" #include "common/edidHelper.h" #include "detection/os/os.h" #include #include #include #include #include #ifdef MAC_OS_X_VERSION_10_15 extern Boolean CoreDisplay_Display_SupportsHDRMode(CGDirectDisplayID display) __attribute__((weak_import)); extern Boolean CoreDisplay_Display_IsHDRModeEnabled(CGDirectDisplayID display) __attribute__((weak_import)); extern CFDictionaryRef CoreDisplay_DisplayCreateInfoDictionary(CGDirectDisplayID display) __attribute__((weak_import)); #else #include #endif static void detectDisplays(FFDisplayServerResult* ds) { CGDirectDisplayID screens[128]; uint32_t screenCount; if(CGGetOnlineDisplayList(ARRAY_SIZE(screens), screens, &screenCount) != kCGErrorSuccess) return; FF_STRBUF_AUTO_DESTROY buffer = ffStrbufCreate(); for(uint32_t i = 0; i < screenCount; i++) { CGDirectDisplayID screen = screens[i]; CGDisplayModeRef mode = CGDisplayCopyDisplayMode(screen); if(mode) { //https://github.com/glfw/glfw/commit/aab08712dd8142b642e2042e7b7ba563acd07a45 double refreshRate = CGDisplayModeGetRefreshRate(mode); if (refreshRate == 0) { #pragma clang diagnostic push #pragma clang diagnostic ignored "-Wdeprecated-declarations" CVDisplayLinkRef link; if(CVDisplayLinkCreateWithCGDisplay(screen, &link) == kCVReturnSuccess) { const CVTime time = CVDisplayLinkGetNominalOutputVideoRefreshPeriod(link); if (!(time.flags & kCVTimeIsIndefinite)) refreshRate = time.timeScale / (double) time.timeValue; //59.97... CVDisplayLinkRelease(link); } #pragma clang diagnostic pop } ffStrbufClear(&buffer); CFDictionaryRef FF_CFTYPE_AUTO_RELEASE displayInfo = NULL; #ifdef MAC_OS_X_VERSION_10_15 if(CoreDisplay_DisplayCreateInfoDictionary) displayInfo = CoreDisplay_DisplayCreateInfoDictionary(screen); #else { io_service_t servicePort = CGDisplayIOServicePort(screen); displayInfo = IODisplayCreateInfoDictionary(servicePort, kIODisplayOnlyPreferredName); } #endif uint32_t physicalWidth = 0, physicalHeight = 0; uint32_t preferredWidth = 0, preferredHeight = 0; double preferredRefreshRate = 0; if(displayInfo) { CFDictionaryRef productNames; if(ffCfDictGetDict(displayInfo, CFSTR(kDisplayProductName), &productNames) == NULL) ffCfDictGetString(productNames, CFSTR("en_US"), &buffer); // CGDisplayScreenSize reports invalid result for external displays on old Intel MacBook Pro CFDataRef edidRef = (CFDataRef) CFDictionaryGetValue(displayInfo, CFSTR(kIODisplayEDIDKey)); if (edidRef && CFGetTypeID(edidRef) == CFDataGetTypeID()) { const uint8_t* edidData = CFDataGetBytePtr(edidRef); uint32_t edidLength = (uint32_t) CFDataGetLength(edidRef); if (edidLength >= 128) ffEdidGetPhysicalSize(edidData, &physicalWidth, &physicalHeight); } if (!physicalWidth || !physicalHeight) { if (ffCfDictGetInt(displayInfo, CFSTR(kDisplayHorizontalImageSize), (int*) &physicalWidth) == NULL) ffCfDictGetInt(displayInfo, CFSTR(kDisplayVerticalImageSize), (int*) &physicalHeight); } ffCfDictGetInt(displayInfo, CFSTR("kCGDisplayPixelWidth"), (int*) &preferredWidth); ffCfDictGetInt(displayInfo, CFSTR("kCGDisplayPixelHeight"), (int*) &preferredHeight); if (preferredWidth && preferredHeight) { FF_CFTYPE_AUTO_RELEASE CFArrayRef allModes = CGDisplayCopyAllDisplayModes(screen, NULL); if (allModes) { for (CFIndex i = 0, count = CFArrayGetCount(allModes); i < count; i++) { CGDisplayModeRef modeInfo = (CGDisplayModeRef) CFArrayGetValueAtIndex(allModes, i); if (CGDisplayModeGetPixelWidth(modeInfo) == preferredWidth && CGDisplayModeGetPixelHeight(modeInfo) == preferredHeight) { double rr = CGDisplayModeGetRefreshRate(modeInfo); if (rr > preferredRefreshRate) preferredRefreshRate = rr; break; } } } } } if ((!physicalWidth || !physicalHeight) && CGDisplayPrimaryDisplay(screen) == screen) // #1406 { CGSize size = CGDisplayScreenSize(screen); physicalWidth = (uint32_t) (size.width + 0.5); physicalHeight = (uint32_t) (size.height + 0.5); } uint32_t pixelWidth = (uint32_t) CGDisplayModeGetPixelWidth(mode); uint32_t pixelHeight = (uint32_t) CGDisplayModeGetPixelHeight(mode); FFDisplayResult* display = ffdsAppendDisplay(ds, pixelWidth, pixelHeight, refreshRate, pixelHeight * 96 / (uint32_t)CGDisplayModeGetHeight(mode), preferredWidth, preferredHeight, preferredRefreshRate, (uint32_t)CGDisplayRotation(screen), &buffer, CGDisplayIsBuiltin(screen) ? FF_DISPLAY_TYPE_BUILTIN : FF_DISPLAY_TYPE_EXTERNAL, CGDisplayIsMain(screen), (uint64_t)screen, physicalWidth, physicalHeight, "CoreGraphics" ); if (display) { #ifndef MAC_OS_X_VERSION_10_11 FF_CFTYPE_AUTO_RELEASE CFStringRef pe = CGDisplayModeCopyPixelEncoding(mode); if (pe) display->bitDepth = (uint8_t) (CFStringGetLength(pe) - CFStringFind(pe, CFSTR("B"), 0).location); #else // https://stackoverflow.com/a/33519316/9976392 // Also shitty, but better than parsing `CFCopyDescription(mode)` CFDictionaryRef dict = (CFDictionaryRef) *((int64_t *)mode + 2); if (CFGetTypeID(dict) == CFDictionaryGetTypeID()) { int32_t bitDepth; ffCfDictGetInt(dict, kCGDisplayBitsPerSample, &bitDepth); display->bitDepth = (uint8_t) bitDepth; } #endif if (display->type == FF_DISPLAY_TYPE_BUILTIN && displayInfo) display->hdrStatus = CFDictionaryContainsKey(displayInfo, CFSTR("ReferencePeakHDRLuminance")) ? FF_DISPLAY_HDR_STATUS_SUPPORTED : FF_DISPLAY_HDR_STATUS_UNSUPPORTED; #ifdef MAC_OS_X_VERSION_10_15 else if (CoreDisplay_Display_SupportsHDRMode) { if (CoreDisplay_Display_SupportsHDRMode(screen)) { display->hdrStatus = FF_DISPLAY_HDR_STATUS_SUPPORTED; if (CoreDisplay_Display_IsHDRModeEnabled && CoreDisplay_Display_IsHDRModeEnabled(screen)) display->hdrStatus = FF_DISPLAY_HDR_STATUS_ENABLED; } else display->hdrStatus = FF_DISPLAY_HDR_STATUS_UNSUPPORTED; } #endif display->serial = CGDisplaySerialNumber(screen); if (displayInfo) { int value; if (ffCfDictGetInt(displayInfo, CFSTR(kDisplayYearOfManufacture), &value) == NULL) display->manufactureYear = (uint16_t) value; if (ffCfDictGetInt(displayInfo, CFSTR(kDisplayWeekOfManufacture), &value) == NULL) display->manufactureWeek = (uint16_t) value; } } CGDisplayModeRelease(mode); } CGDisplayRelease(screen); } } void ffConnectDisplayServerImpl(FFDisplayServerResult* ds) { { FF_CFTYPE_AUTO_RELEASE CFMachPortRef port = CGWindowServerCreateServerPort(); if (port) { ffStrbufSetStatic(&ds->wmProcessName, "WindowServer"); ffStrbufSetStatic(&ds->wmPrettyName, "Quartz Compositor"); } } detectDisplays(ds); } ================================================ FILE: src/detection/displayserver/displayserver_haiku.cpp ================================================ extern "C" { #include "displayserver.h" } #include #include #include extern "C" void ffConnectDisplayServerImpl(FFDisplayServerResult* ds); static void detectDisplays(FFDisplayServerResult* ds) { // We need a valid be_app to query the app_server here. BApplication app("application/x-vnd.fastfetch-cli-fastfetch"); BScreen s{}; // default screen is the main one bool main = true; do { if (!s.IsValid()) continue; display_mode mode; if (s.GetMode(&mode) != B_OK) continue; FF_STRBUF_AUTO_DESTROY name = ffStrbufCreateA(128); monitor_info monitor; // WARNING: This is experimental new Haiku API status_t err = s.GetMonitorInfo(&monitor); if (err == B_OK) { ffStrbufSetF(&name, "%s %s", monitor.vendor, monitor.name); ffStrbufTrimRightSpace(&name); } uint32_t width = (uint32_t) s.Frame().Width() + 1; uint32_t height = (uint32_t) (uint32_t)s.Frame().Height() + 1; FFDisplayResult* res = ffdsAppendDisplay(ds, width, height, (double)mode.timing.pixel_clock * 1000 / (mode.timing.v_total * mode.timing.h_total), 0, 0, 0, 0, 0, &name, FF_DISPLAY_TYPE_UNKNOWN, main, (uint64_t) s.ID().id, 0, 0, "BScreen" ); if (err == B_OK) { res->manufactureWeek = monitor.produced.week; res->manufactureYear = monitor.produced.year; } main = false; } while (s.SetToNext() == B_OK); return; } void ffConnectDisplayServerImpl(FFDisplayServerResult* ds) { ffStrbufSetStatic(&ds->wmProcessName, "app_server"); ffStrbufSetStatic(&ds->wmPrettyName, "Application Server"); ffStrbufSetStatic(&ds->dePrettyName, "Application Kit"); detectDisplays(ds); } ================================================ FILE: src/detection/displayserver/displayserver_windows.c ================================================ #include "displayserver.h" #include "common/edidHelper.h" #include "common/windows/registry.h" #include "common/windows/unicode.h" #include #include // http://undoc.airesoft.co.uk/user32.dll/IsThreadDesktopComposited.php BOOL WINAPI IsThreadDesktopComposited(); BOOL WINAPI GetDpiForMonitorInternal(HMONITOR hmonitor, MONITOR_DPI_TYPE dpiType, UINT* dpiX, UINT* dpiY); static void detectDisplays(FFDisplayServerResult* ds) { DISPLAYCONFIG_PATH_INFO paths[128]; uint32_t pathCount = ARRAY_SIZE(paths); DISPLAYCONFIG_MODE_INFO modes[256]; uint32_t modeCount = ARRAY_SIZE(modes); if (QueryDisplayConfig( QDC_ONLY_ACTIVE_PATHS, &pathCount, paths, &modeCount, modes, NULL) == ERROR_SUCCESS) { for (uint32_t i = 0; i < pathCount; ++i) { const DISPLAYCONFIG_PATH_INFO* path = &paths[i]; const DISPLAYCONFIG_SOURCE_MODE* sourceMode = &modes[path->sourceInfo.modeInfoIdx].sourceMode; FF_STRBUF_AUTO_DESTROY name = ffStrbufCreate(); uint32_t physicalWidth = 0, physicalHeight = 0; DISPLAYCONFIG_TARGET_DEVICE_NAME targetName = { .header = { .type = DISPLAYCONFIG_DEVICE_INFO_GET_TARGET_NAME, .size = sizeof(targetName), .adapterId = path->targetInfo.adapterId, .id = path->targetInfo.id, }, }; FF_LIST_AUTO_DESTROY edid = ffListCreate(sizeof(uint8_t)); if(DisplayConfigGetDeviceInfo(&targetName.header) == ERROR_SUCCESS) { wchar_t regPath[256] = L"SYSTEM\\CurrentControlSet\\Enum"; wchar_t* pRegPath = regPath + strlen("SYSTEM\\CurrentControlSet\\Enum"); wchar_t* pDevPath = targetName.monitorDevicePath + strlen("\\\\?"); while (*pDevPath && *pDevPath != L'{') { if (*pDevPath == L'#') *pRegPath = L'\\'; else *pRegPath = *pDevPath; ++pRegPath; ++pDevPath; assert(pRegPath < regPath + ARRAY_SIZE(regPath) + strlen("Device Parameters")); } wcscpy(pRegPath, L"Device Parameters"); FF_AUTO_CLOSE_FD HANDLE hKey = NULL; if (ffRegOpenKeyForRead(HKEY_LOCAL_MACHINE, regPath, &hKey, NULL) && ffRegReadData(hKey, L"EDID", &edid, NULL) && ffEdidIsValid(edid.data, edid.length)) { ffEdidGetName(edid.data, &name); ffEdidGetPhysicalSize(edid.data, &physicalWidth, &physicalHeight); } else { ffListClear(&edid); if (targetName.flags.friendlyNameFromEdid) ffStrbufSetWS(&name, targetName.monitorFriendlyDeviceName); else { ffStrbufSetWS(&name, targetName.monitorDevicePath); ffStrbufSubstrAfterFirstC(&name, '#'); ffStrbufSubstrBeforeFirstC(&name, '#'); } } } uint32_t width = sourceMode->width; uint32_t height = sourceMode->height; uint32_t rotation; switch (path->targetInfo.rotation) { case DISPLAYCONFIG_ROTATION_ROTATE90: rotation = 90; break; case DISPLAYCONFIG_ROTATION_ROTATE180: rotation = 180; break; case DISPLAYCONFIG_ROTATION_ROTATE270: rotation = 270; break; default: rotation = 0; break; } DISPLAYCONFIG_TARGET_PREFERRED_MODE preferredMode = { .header = { .type = DISPLAYCONFIG_DEVICE_INFO_GET_TARGET_PREFERRED_MODE, .size = sizeof(preferredMode), .adapterId = path->targetInfo.adapterId, .id = path->targetInfo.id, } }; double preferredRefreshRate = 0; if (DisplayConfigGetDeviceInfo(&preferredMode.header) == ERROR_SUCCESS) { DISPLAYCONFIG_RATIONAL freq = preferredMode.targetMode.targetVideoSignalInfo.vSyncFreq; preferredRefreshRate = freq.Numerator / (double) freq.Denominator; } uint32_t systemDpi = 0; HMONITOR hMonitor = MonitorFromPoint(*(POINT*)&sourceMode->position, MONITOR_DEFAULTTONULL); if (hMonitor) { UINT ignored; GetDpiForMonitorInternal(hMonitor, MDT_EFFECTIVE_DPI, &systemDpi, &ignored); } if (systemDpi == 0) { HDC hdc = GetDC(NULL); systemDpi = (uint32_t) GetDeviceCaps(hdc, LOGPIXELSX); if (systemDpi == 0) systemDpi = 96; ReleaseDC(NULL, hdc); } if (path->targetInfo.rotation == DISPLAYCONFIG_ROTATION_ROTATE90 || path->targetInfo.rotation == DISPLAYCONFIG_ROTATION_ROTATE270) { uint32_t temp = width; width = height; height = temp; } FFDisplayResult* display = ffdsAppendDisplay(ds, width, height, path->targetInfo.refreshRate.Numerator / (double) path->targetInfo.refreshRate.Denominator, systemDpi, preferredMode.width, preferredMode.height, preferredRefreshRate, rotation, &name, path->targetInfo.outputTechnology == DISPLAYCONFIG_OUTPUT_TECHNOLOGY_OTHER ? FF_DISPLAY_TYPE_UNKNOWN : path->targetInfo.outputTechnology == DISPLAYCONFIG_OUTPUT_TECHNOLOGY_INTERNAL || path->targetInfo.outputTechnology == DISPLAYCONFIG_OUTPUT_TECHNOLOGY_DISPLAYPORT_EMBEDDED || path->targetInfo.outputTechnology == DISPLAYCONFIG_OUTPUT_TECHNOLOGY_UDI_EMBEDDED ? FF_DISPLAY_TYPE_BUILTIN : FF_DISPLAY_TYPE_EXTERNAL, sourceMode->position.x == 0 && sourceMode->position.y == 0, (uintptr_t) hMonitor, physicalWidth, physicalHeight, "GDI" ); if (display) { DISPLAYCONFIG_GET_ADVANCED_COLOR_INFO_2 advColorInfo2 = { .header = { .type = DISPLAYCONFIG_DEVICE_INFO_GET_ADVANCED_COLOR_INFO_2, .size = sizeof(advColorInfo2), .adapterId = path->targetInfo.adapterId, .id = path->targetInfo.id, } }; if (DisplayConfigGetDeviceInfo(&advColorInfo2.header) == ERROR_SUCCESS) { if (advColorInfo2.highDynamicRangeUserEnabled) display->hdrStatus = FF_DISPLAY_HDR_STATUS_ENABLED; else if (advColorInfo2.highDynamicRangeSupported) display->hdrStatus = FF_DISPLAY_HDR_STATUS_SUPPORTED; else display->hdrStatus = FF_DISPLAY_HDR_STATUS_UNSUPPORTED; display->bitDepth = (uint8_t) advColorInfo2.bitsPerColorChannel; } else { DISPLAYCONFIG_GET_ADVANCED_COLOR_INFO advColorInfo = { .header = { .type = DISPLAYCONFIG_DEVICE_INFO_GET_ADVANCED_COLOR_INFO, .size = sizeof(advColorInfo), .adapterId = path->targetInfo.adapterId, .id = path->targetInfo.id, } }; if (DisplayConfigGetDeviceInfo(&advColorInfo.header) == ERROR_SUCCESS) { if (advColorInfo.advancedColorEnabled) display->hdrStatus = FF_DISPLAY_HDR_STATUS_ENABLED; else if (advColorInfo.advancedColorSupported) display->hdrStatus = FF_DISPLAY_HDR_STATUS_SUPPORTED; else display->hdrStatus = FF_DISPLAY_HDR_STATUS_UNSUPPORTED; display->bitDepth = (uint8_t) advColorInfo.bitsPerColorChannel; } else display->hdrStatus = FF_DISPLAY_HDR_STATUS_UNKNOWN; } if (edid.length > 0) ffEdidGetSerialAndManufactureDate(edid.data, &display->serial, &display->manufactureYear, &display->manufactureWeek); display->drrStatus = path->flags & DISPLAYCONFIG_PATH_BOOST_REFRESH_RATE ? FF_DISPLAY_DRR_STATUS_ENABLED : FF_DISPLAY_DRR_STATUS_DISABLED; } } } } void ffConnectDisplayServerImpl(FFDisplayServerResult* ds) { if (IsThreadDesktopComposited()) { ffStrbufSetStatic(&ds->wmProcessName, "dwm.exe"); ffStrbufSetStatic(&ds->wmPrettyName, "Desktop Window Manager"); } else { // `explorer.exe` only provides a subset of WM functions, as well as the taskbar and desktop icons. // While a window itself is drawn by kernel (GDI). Killing `explorer.exe` won't affect how windows are displayed generally. ffStrbufSetStatic(&ds->wmProcessName, "explorer.exe"); ffStrbufSetStatic(&ds->wmPrettyName, "Internal"); } detectDisplays(ds); } ================================================ FILE: src/detection/displayserver/linux/common.c ================================================ #include "displayserver_linux.h" #include "common/stringUtils.h" FFDisplayType ffdsGetDisplayType(const char* name) { if(ffStrStartsWith(name, "eDP-") || ffStrStartsWith(name, "LVDS-")) return FF_DISPLAY_TYPE_BUILTIN; else if(ffStrStartsWith(name, "HDMI-") || ffStrStartsWith(name, "DP-") || ffStrStartsWith(name, "DisplayPort-") || ffStrStartsWith(name, "DVI-") || ffStrStartsWith(name, "VGA-")) return FF_DISPLAY_TYPE_EXTERNAL; return FF_DISPLAY_TYPE_UNKNOWN; } ================================================ FILE: src/detection/displayserver/linux/displayserver_linux.c ================================================ #include "displayserver_linux.h" #include "common/io.h" #include "common/stringUtils.h" #ifdef __FreeBSD__ #include "common/settings.h" #endif static void getWMProtocolNameFromEnv(FFDisplayServerResult* result) { const char* env = getenv("XDG_SESSION_TYPE"); if(env) { if(ffStrEqualsIgnCase(env, "wayland")) ffStrbufSetS(&result->wmProtocolName, FF_WM_PROTOCOL_WAYLAND); else if(ffStrEqualsIgnCase(env, "x11") || ffStrEqualsIgnCase(env, "xorg")) ffStrbufSetS(&result->wmProtocolName, FF_WM_PROTOCOL_X11); else if(ffStrEqualsIgnCase(env, "tty")) ffStrbufSetS(&result->wmProtocolName, FF_WM_PROTOCOL_TTY); else ffStrbufSetS(&result->wmProtocolName, env); return; } if(getenv("WAYLAND_DISPLAY") != NULL || getenv("WAYLAND_SOCKET") != NULL) { ffStrbufSetStatic(&result->wmProtocolName, FF_WM_PROTOCOL_WAYLAND); return; } if(getenv("DISPLAY") != NULL) // XWayland also set this { ffStrbufSetStatic(&result->wmProtocolName, FF_WM_PROTOCOL_X11); return; } env = getenv("TERM"); if(ffStrSet(env) && ffStrEqualsIgnCase(env, "linux")) { ffStrbufSetStatic(&result->wmProtocolName, FF_WM_PROTOCOL_TTY); return; } } void ffConnectDisplayServerImpl(FFDisplayServerResult* ds) { if (instance.config.general.dsForceDrm == FF_DS_FORCE_DRM_TYPE_FALSE) { //We try wayland as our preferred display server, as it supports the most features. //This method can't detect the name of our WM / DE ffdsConnectWayland(ds); //Try the x11 libs, from most feature rich to least. //We use the display list to detect if a connection is needed. //They respect wmProtocolName, and only detect display if it is set. if(ds->displays.length == 0) ffdsConnectXcbRandr(ds); if(ds->displays.length == 0) ffdsConnectXrandr(ds); } //This display detection method is display server independent. //Use it if all connections failed if(ds->displays.length == 0) ffdsConnectDrm(ds); #ifdef __FreeBSD__ if(ds->displays.length == 0) { FF_STRBUF_AUTO_DESTROY buf = ffStrbufCreate(); if (ffSettingsGetFreeBSDKenv("screen.width", &buf)) { uint32_t width = (uint32_t) ffStrbufToUInt(&buf, 0); if (width) { ffStrbufClear(&buf); if (ffSettingsGetFreeBSDKenv("screen.height", &buf)) { uint32_t height = (uint32_t) ffStrbufToUInt(&buf, 0); ffdsAppendDisplay(ds, width, height, 0, 0, 0, 0, 0, 0, NULL, FF_DISPLAY_TYPE_UNKNOWN, false, 0, 0, 0, "kenv"); } } } } #endif if (ds->wmProtocolName.length == 0) getWMProtocolNameFromEnv(ds); if(!ffStrbufEqualS(&ds->wmProtocolName, FF_WM_PROTOCOL_TTY)) { //This fills in missing information about WM / DE by using env vars and iterating processes ffdsDetectWMDE(ds); } } ================================================ FILE: src/detection/displayserver/linux/displayserver_linux.h ================================================ #pragma once #include "detection/displayserver/displayserver.h" const char* ffdsConnectWayland(FFDisplayServerResult* result); const char* ffdsConnectXcbRandr(FFDisplayServerResult* result); const char* ffdsConnectXrandr(FFDisplayServerResult* result); const char* ffdsConnectDrm(FFDisplayServerResult* result); void ffdsDetectWMDE(FFDisplayServerResult* result); FFDisplayType ffdsGetDisplayType(const char* drmConnectorName); ================================================ FILE: src/detection/displayserver/linux/drm.c ================================================ #include "displayserver_linux.h" #include "common/io.h" #include "common/edidHelper.h" #include "common/stringUtils.h" #ifdef __linux__ #include static const char* drmParseSysfs(FFDisplayServerResult* result) { const char* drmDirPath = "/sys/class/drm/"; FF_AUTO_CLOSE_DIR DIR* dirp = opendir(drmDirPath); if(dirp == NULL) return "opendir(drmDirPath) failed"; FF_STRBUF_AUTO_DESTROY drmDir = ffStrbufCreateA(64); ffStrbufAppendS(&drmDir, drmDirPath); uint32_t drmDirLength = drmDir.length; struct dirent* entry; while((entry = readdir(dirp)) != NULL) { if(entry->d_name[0] == '.') continue; ffStrbufAppendS(&drmDir, entry->d_name); uint32_t drmDirWithDnameLength = drmDir.length; char buf; ffStrbufAppendS(&drmDir, "/enabled"); if (ffReadFileData(drmDir.chars, sizeof(buf), &buf) <= 0 || buf != 'e') { /* read failed or enabled != "enabled" */ ffStrbufSubstrBefore(&drmDir, drmDirWithDnameLength); ffStrbufAppendS(&drmDir, "/status"); buf = 'd'; ffReadFileData(drmDir.chars, sizeof(buf), &buf); if (buf != 'c') { /* read failed or status != "connected" */ ffStrbufSubstrBefore(&drmDir, drmDirLength); continue; } } unsigned width = 0, height = 0, physicalWidth = 0, physicalHeight = 0; double refreshRate = 0; FF_STRBUF_AUTO_DESTROY name = ffStrbufCreate(); ffStrbufSubstrBefore(&drmDir, drmDirWithDnameLength); ffStrbufAppendS(&drmDir, "/edid"); const char* plainName = entry->d_name; if (ffStrStartsWith(plainName, "card")) { const char* tmp = strchr(plainName + strlen("card"), '-'); if (tmp) plainName = tmp + 1; } uint8_t edidData[512]; ssize_t edidLength = ffReadFileData(drmDir.chars, ARRAY_SIZE(edidData), edidData); if(edidLength <= 0 || edidLength % 128 != 0) { edidLength = 0; ffStrbufSubstrBefore(&drmDir, drmDirWithDnameLength); ffStrbufAppendS(&drmDir, "/modes"); char modes[32]; if (ffReadFileData(drmDir.chars, ARRAY_SIZE(modes), modes) >= 3) { sscanf(modes, "%ux%u", &width, &height); ffStrbufAppendS(&name, plainName); } } else { ffEdidGetName(edidData, &name); ffEdidGetPreferredResolutionAndRefreshRate(edidData, &width, &height, &refreshRate); ffEdidGetPhysicalSize(edidData, &physicalWidth, &physicalHeight); } FFDisplayResult* item = ffdsAppendDisplay( result, width, height, refreshRate, 0, 0, 0, 0, 0, &name, ffdsGetDisplayType(plainName), false, 0, physicalWidth, physicalHeight, "sysfs-drm" ); if (item && edidLength) { item->hdrStatus = ffEdidGetHdrCompatible(edidData, (uint32_t) edidLength) ? FF_DISPLAY_HDR_STATUS_SUPPORTED : FF_DISPLAY_HDR_STATUS_UNSUPPORTED; ffEdidGetSerialAndManufactureDate(edidData, &item->serial, &item->manufactureYear, &item->manufactureWeek); } ffStrbufSubstrBefore(&drmDir, drmDirLength); } return NULL; } #endif #ifdef FF_HAVE_DRM #include "common/library.h" #include #include #include // https://gitlab.freedesktop.org/mesa/drm/-/blob/main/xf86drmMode.c#L1785 // It's not supported on Ubuntu 20.04 static inline const char* drmType2Name(uint32_t connector_type) { /* Keep the strings in sync with the kernel's drm_connector_enum_list in * drm_connector.c. */ switch (connector_type) { case DRM_MODE_CONNECTOR_Unknown: return "Unknown"; case DRM_MODE_CONNECTOR_VGA: return "VGA"; case DRM_MODE_CONNECTOR_DVII: return "DVI-I"; case DRM_MODE_CONNECTOR_DVID: return "DVI-D"; case DRM_MODE_CONNECTOR_DVIA: return "DVI-A"; case DRM_MODE_CONNECTOR_Composite: return "Composite"; case DRM_MODE_CONNECTOR_SVIDEO: return "SVIDEO"; case DRM_MODE_CONNECTOR_LVDS: return "LVDS"; case DRM_MODE_CONNECTOR_Component: return "Component"; case DRM_MODE_CONNECTOR_9PinDIN: return "DIN"; case DRM_MODE_CONNECTOR_DisplayPort: return "DP"; case DRM_MODE_CONNECTOR_HDMIA: return "HDMI-A"; case DRM_MODE_CONNECTOR_HDMIB: return "HDMI-B"; case DRM_MODE_CONNECTOR_TV: return "TV"; case DRM_MODE_CONNECTOR_eDP: return "eDP"; case DRM_MODE_CONNECTOR_VIRTUAL: return "Virtual"; case DRM_MODE_CONNECTOR_DSI: return "DSI"; case DRM_MODE_CONNECTOR_DPI: return "DPI"; case DRM_MODE_CONNECTOR_WRITEBACK: return "Writeback"; case 19 /*DRM_MODE_CONNECTOR_SPI*/: return "SPI"; case 20 /*DRM_MODE_CONNECTOR_USB*/: return "USB"; default: return "Unsupported"; } } FF_MAYBE_UNUSED static const char* drmGetEdidByConnId(uint32_t connId, uint8_t* edidData, ssize_t* edidLength) { const char* drmDirPath = "/sys/class/drm/"; FF_AUTO_CLOSE_DIR DIR* dirp = opendir(drmDirPath); if(dirp == NULL) return "opendir(drmDirPath) failed"; FF_STRBUF_AUTO_DESTROY drmDir = ffStrbufCreateA(64); ffStrbufAppendS(&drmDir, drmDirPath); uint32_t drmDirLength = drmDir.length; struct dirent* entry; while((entry = readdir(dirp)) != NULL) { if(entry->d_name[0] == '.') continue; ffStrbufAppendS(&drmDir, entry->d_name); uint32_t drmDirWithDnameLength = drmDir.length; char connectorId[16] = {}; ffStrbufAppendS(&drmDir, "/connector_id"); ffReadFileData(drmDir.chars, ARRAY_SIZE(connectorId), connectorId); if (strtoul(connectorId, NULL, 10) != connId) { ffStrbufSubstrBefore(&drmDir, drmDirLength); continue; } ffStrbufSubstrBefore(&drmDir, drmDirWithDnameLength); ffStrbufAppendS(&drmDir, "/edid"); *edidLength = ffReadFileData(drmDir.chars, (uint32_t) *edidLength, edidData); return NULL; } return "Failed to match connector ID"; } static const char* drmConnectLibdrm(FFDisplayServerResult* result) { FF_LIBRARY_LOAD_MESSAGE(libdrm, "libdrm" FF_LIBRARY_EXTENSION, 2) FF_LIBRARY_LOAD_SYMBOL_MESSAGE(libdrm, drmGetDevices) FF_LIBRARY_LOAD_SYMBOL_MESSAGE(libdrm, drmModeGetResources) FF_LIBRARY_LOAD_SYMBOL_MESSAGE(libdrm, drmModeGetConnectorCurrent) FF_LIBRARY_LOAD_SYMBOL_MESSAGE(libdrm, drmModeGetCrtc) FF_LIBRARY_LOAD_SYMBOL_MESSAGE(libdrm, drmModeGetEncoder) FF_LIBRARY_LOAD_SYMBOL_MESSAGE(libdrm, drmModeGetFB) FF_LIBRARY_LOAD_SYMBOL_MESSAGE(libdrm, drmModeGetProperty) FF_LIBRARY_LOAD_SYMBOL_MESSAGE(libdrm, drmModeGetPropertyBlob) FF_LIBRARY_LOAD_SYMBOL_MESSAGE(libdrm, drmModeFreeResources) FF_LIBRARY_LOAD_SYMBOL_MESSAGE(libdrm, drmModeFreeCrtc) FF_LIBRARY_LOAD_SYMBOL_MESSAGE(libdrm, drmModeFreeConnector) FF_LIBRARY_LOAD_SYMBOL_MESSAGE(libdrm, drmModeFreeEncoder) FF_LIBRARY_LOAD_SYMBOL_MESSAGE(libdrm, drmModeFreeFB) FF_LIBRARY_LOAD_SYMBOL_MESSAGE(libdrm, drmModeFreeProperty) FF_LIBRARY_LOAD_SYMBOL_MESSAGE(libdrm, drmModeFreePropertyBlob) FF_LIBRARY_LOAD_SYMBOL_MESSAGE(libdrm, drmFreeDevices) drmDevice* devices[64]; int nDevices = ffdrmGetDevices(devices, ARRAY_SIZE(devices)); if (nDevices <= 0) return "drmGetDevices() failed"; FF_STRBUF_AUTO_DESTROY name = ffStrbufCreate(); for (int iDev = 0; iDev < nDevices; ++iDev) { drmDevice* dev = devices[iDev]; if (!(dev->available_nodes & (1 << DRM_NODE_PRIMARY))) continue; const char* path = dev->nodes[DRM_NODE_PRIMARY]; #if __linux__ ffStrbufSetF(&name, "/sys/class/drm/%s/device/power/runtime_status", strrchr(path, '/') + 1); char buffer[8] = ""; if (ffReadFileData(name.chars, strlen("suspend"), buffer) > 0 && ffStrStartsWith(buffer, "suspend")) continue; #endif FF_AUTO_CLOSE_FD int primaryFd = open(path, O_RDWR | O_CLOEXEC); if (primaryFd < 0) continue; drmModeRes* res = ffdrmModeGetResources(primaryFd); if (!res) continue; for (int iConn = 0; iConn < res->count_connectors; ++iConn) { drmModeConnector* conn = ffdrmModeGetConnectorCurrent(primaryFd, res->connectors[iConn]); if (!conn) continue; if (conn->connection != DRM_MODE_DISCONNECTED) { drmModeEncoder* encoder = ffdrmModeGetEncoder(primaryFd, conn->encoder_id); uint32_t width = 0, height = 0, refreshRate = 0; uint8_t bitDepth = 0; if (encoder) { drmModeCrtc* crtc = ffdrmModeGetCrtc(primaryFd, encoder->crtc_id); if (crtc) { width = crtc->mode.hdisplay; height = crtc->mode.vdisplay; refreshRate = crtc->mode.vrefresh; if (refreshRate == 0) { // There are weird cases that we can't get the refresh rate from the CRTC but from the modes for (int iMode = 0; iMode < conn->count_modes; ++iMode) { drmModeModeInfo* mode = &conn->modes[iMode]; if (mode->clock == crtc->mode.clock && mode->htotal == crtc->mode.htotal) { refreshRate = mode->vrefresh; break; } } } drmModeFBPtr fb = ffdrmModeGetFB(primaryFd, crtc->buffer_id); if (fb) { bitDepth = (uint8_t) (fb->depth / 3); ffdrmModeFreeFB(fb); } ffdrmModeFreeCrtc(crtc); } ffdrmModeFreeEncoder(encoder); } uint32_t preferredWidth = 0, preferredHeight = 0, preferredRefreshRate = 0; for (int iMode = 0; iMode < conn->count_modes; ++iMode) { drmModeModeInfo* mode = &conn->modes[iMode]; if (mode->type & DRM_MODE_TYPE_PREFERRED) { preferredWidth = mode->hdisplay; preferredHeight = mode->vdisplay; preferredRefreshRate = mode->vrefresh; break; } } // NVIDIA DRM driver seems incomplete and conn->encoder_id == 0 // Assume preferred resolution is used as what we do in drmParseSys if (width == 0 || height == 0) { width = preferredWidth; height = preferredHeight; refreshRate = preferredRefreshRate; } ffStrbufClear(&name); uint16_t myear = 0, mweak = 0; uint32_t serial = 0; FFDisplayHdrStatus hdrStatus = FF_DISPLAY_HDR_STATUS_UNKNOWN; for (int iProp = 0; iProp < conn->count_props; ++iProp) { drmModePropertyRes *prop = ffdrmModeGetProperty(primaryFd, conn->props[iProp]); if (!prop) continue; uint32_t type = prop->flags & (DRM_MODE_PROP_LEGACY_TYPE | DRM_MODE_PROP_EXTENDED_TYPE); if (type == DRM_MODE_PROP_BLOB && ffStrEquals(prop->name, "EDID")) { drmModePropertyBlobPtr blob = NULL; if (prop->count_blobs > 0 && prop->blob_ids != NULL) blob = ffdrmModeGetPropertyBlob(primaryFd, prop->blob_ids[0]); else blob = ffdrmModeGetPropertyBlob(primaryFd, (uint32_t) conn->prop_values[iProp]); if (blob) { if (blob->length >= 128) { ffEdidGetName(blob->data, &name); hdrStatus = ffEdidGetHdrCompatible(blob->data, blob->length) ? FF_DISPLAY_HDR_STATUS_SUPPORTED : FF_DISPLAY_HDR_STATUS_UNSUPPORTED; ffEdidGetSerialAndManufactureDate(blob->data, &serial, &myear, &mweak); } ffdrmModeFreePropertyBlob(blob); } break; } ffdrmModeFreeProperty(prop); } #if __linux__ if (name.length == 0) { uint8_t edidData[512]; ssize_t edidLength = 0; drmGetEdidByConnId(conn->connector_id, edidData, &edidLength); if (edidLength > 0 && edidLength % 128 == 0) { ffEdidGetName(edidData, &name); hdrStatus = ffEdidGetHdrCompatible(edidData, (uint32_t) edidLength) ? FF_DISPLAY_HDR_STATUS_SUPPORTED : FF_DISPLAY_HDR_STATUS_UNSUPPORTED; ffEdidGetSerialAndManufactureDate(edidData, &serial, &myear, &mweak); } } #endif if (name.length == 0) { const char* connectorTypeName = drmType2Name(conn->connector_type); if (connectorTypeName == NULL) connectorTypeName = "Unknown"; ffStrbufSetF(&name, "%s-%d", connectorTypeName, iConn + 1); } FFDisplayResult* item = ffdsAppendDisplay(result, width, height, refreshRate, 0, preferredWidth, preferredHeight, preferredRefreshRate, 0, &name, conn->connector_type == DRM_MODE_CONNECTOR_eDP || conn->connector_type == DRM_MODE_CONNECTOR_LVDS ? FF_DISPLAY_TYPE_BUILTIN : conn->connector_type == DRM_MODE_CONNECTOR_HDMIA || conn->connector_type == DRM_MODE_CONNECTOR_HDMIB || conn->connector_type == DRM_MODE_CONNECTOR_DisplayPort ? FF_DISPLAY_TYPE_EXTERNAL : FF_DISPLAY_TYPE_UNKNOWN, false, conn->connector_id, conn->mmWidth, conn->mmHeight, "libdrm" ); if (item) { item->hdrStatus = hdrStatus; item->serial = serial; item->manufactureYear = myear; item->manufactureWeek = mweak; item->bitDepth = bitDepth; } } ffdrmModeFreeConnector(conn); } ffdrmModeFreeResources(res); } ffdrmFreeDevices(devices, nDevices); return NULL; } #endif const char* ffdsConnectDrm(FF_MAYBE_UNUSED FFDisplayServerResult* result) { #ifdef FF_HAVE_DRM if (instance.config.general.dsForceDrm != FF_DS_FORCE_DRM_TYPE_SYSFS_ONLY) { if (drmConnectLibdrm(result) == NULL) return NULL; } #endif #ifdef __linux__ return drmParseSysfs(result); #endif return "fastfetch was compiled without drm support"; } ================================================ FILE: src/detection/displayserver/linux/wayland/global-output.c ================================================ #ifdef FF_HAVE_WAYLAND #include "wayland.h" #include "common/stringUtils.h" #include "xdg-output-unstable-v1-client-protocol.h" static void waylandOutputModeListener(void* data, FF_MAYBE_UNUSED struct wl_output* output, uint32_t flags, int32_t width, int32_t height, int32_t refreshRate) { WaylandDisplay* display = data; if (flags & WL_OUTPUT_MODE_CURRENT) { display->width = width; display->height = height; display->refreshRate = refreshRate; } if (flags & WL_OUTPUT_MODE_PREFERRED) { display->preferredWidth = width; display->preferredHeight = height; display->preferredRefreshRate = refreshRate; } } static void waylandOutputScaleListener(void* data, FF_MAYBE_UNUSED struct wl_output* output, int32_t scale) { WaylandDisplay* display = data; display->dpi = 96 * (uint32_t) scale; } static void waylandOutputGeometryListener(void *data, FF_MAYBE_UNUSED struct wl_output *output, FF_MAYBE_UNUSED int32_t x, FF_MAYBE_UNUSED int32_t y, int32_t physical_width, int32_t physical_height, FF_MAYBE_UNUSED int32_t subpixel, FF_MAYBE_UNUSED const char *make, FF_MAYBE_UNUSED const char *model, int32_t transform) { WaylandDisplay* display = data; display->physicalWidth = physical_width; display->physicalHeight = physical_height; display->transform = (enum wl_output_transform) transform; } static void handleXdgLogicalSize(void *data, FF_MAYBE_UNUSED struct zxdg_output_v1 *_, int32_t width, FF_MAYBE_UNUSED int32_t height) { WaylandDisplay* display = data; // Seems the values are only useful when ractional scale is enabled if (width < display->width) { display->dpi = (uint32_t) (display->width * 96 / width); } } // Dirty hack for #477 // The order of these callbacks MUST follow `struct wl_output_listener` static void* outputListener[] = { waylandOutputGeometryListener, // geometry waylandOutputModeListener, // mode stubListener, // done waylandOutputScaleListener, // scale ffWaylandOutputNameListener, // name ffWaylandOutputDescriptionListener, // description }; static_assert( sizeof(outputListener) >= sizeof(struct wl_output_listener), "sizeof(outputListener) is too small. Please report it to fastfetch github issue" ); static struct zxdg_output_v1_listener zxdgOutputListener = { .logical_position = (void*) stubListener, .logical_size = handleXdgLogicalSize, .done = (void*) stubListener, .name = (void*) ffWaylandOutputNameListener, .description = (void*) ffWaylandOutputDescriptionListener, }; 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); if(output == NULL) return "Failed to create wl_output"; WaylandDisplay display = { .parent = wldata, .transform = WL_OUTPUT_TRANSFORM_NORMAL, .type = FF_DISPLAY_TYPE_UNKNOWN, .name = ffStrbufCreate(), .description = ffStrbufCreate(), .edidName = ffStrbufCreate(), }; if (wldata->ffwl_proxy_add_listener(output, (void(**)(void)) &outputListener, &display) < 0) { wldata->ffwl_proxy_destroy(output); return "Failed to add listener to wl_output"; } if (wldata->ffwl_display_roundtrip(wldata->display) < 0) { wldata->ffwl_proxy_destroy(output); return "Failed to roundtrip wl_output"; } 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); if (zxdgOutput) { wldata->ffwl_proxy_add_listener(zxdgOutput, (void(**)(void)) &zxdgOutputListener, &display); wldata->ffwl_display_roundtrip(wldata->display); wldata->ffwl_proxy_destroy(zxdgOutput); } } wldata->ffwl_proxy_destroy(output); if(display.width <= 0 || display.height <= 0) return "Failed to get display information from wl_output"; uint32_t rotation = ffWaylandHandleRotation(&display); FFDisplayResult* item = ffdsAppendDisplay(wldata->result, (uint32_t) display.width, (uint32_t) display.height, display.refreshRate / 1000.0, display.dpi, (uint32_t) display.preferredWidth, (uint32_t) display.preferredHeight, display.preferredRefreshRate / 1000.0, rotation, display.edidName.length ? &display.edidName // Try ignoring `eDP-1-unknown`, where `unknown` is localized : display.description.length && !ffStrbufContain(&display.description, &display.name) ? &display.description : &display.name, display.type, false, display.id, (uint32_t) display.physicalWidth, (uint32_t) display.physicalHeight, "wayland-global" ); if (item) { if (display.hdrSupported) item->hdrStatus = FF_DISPLAY_HDR_STATUS_SUPPORTED; else if (display.hdrInfoAvailable) item->hdrStatus = FF_DISPLAY_HDR_STATUS_UNSUPPORTED; else item->hdrStatus = FF_DISPLAY_HDR_STATUS_UNKNOWN; item->manufactureYear = display.myear; item->manufactureWeek = display.mweek; item->serial = display.serial; } ffStrbufDestroy(&display.description); ffStrbufDestroy(&display.name); ffStrbufDestroy(&display.edidName); return NULL; } 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); if(manager == NULL) return "Failed to create zxdg_output_manager_v1"; wldata->zxdgOutputManager = manager; return NULL; } #endif ================================================ FILE: src/detection/displayserver/linux/wayland/kde-output-device-v2-client-protocol.h ================================================ /* Generated by wayland-scanner 1.24.0 */ #ifndef KDE_OUTPUT_DEVICE_V2_CLIENT_PROTOCOL_H #define KDE_OUTPUT_DEVICE_V2_CLIENT_PROTOCOL_H #include #include #include #ifdef __cplusplus extern "C" { #endif /** * @page page_kde_output_device_v2 The kde_output_device_v2 protocol * @section page_ifaces_kde_output_device_v2 Interfaces * - @subpage page_iface_kde_output_device_registry_v2 - output devices * - @subpage page_iface_kde_output_device_v2 - output configuration representation * - @subpage page_iface_kde_output_device_mode_v2 - output mode * @section page_copyright_kde_output_device_v2 Copyright *
 *
 * SPDX-FileCopyrightText: 2008-2011 Kristian Høgsberg
 * SPDX-FileCopyrightText: 2010-2011 Intel Corporation
 * SPDX-FileCopyrightText: 2012-2013 Collabora, Ltd.
 * SPDX-FileCopyrightText: 2015 Sebastian Kügler 
 * SPDX-FileCopyrightText: 2021 Méven Car 
 *
 * SPDX-License-Identifier: MIT-CMU
 * 
*/ struct kde_output_device_mode_v2; struct kde_output_device_registry_v2; struct kde_output_device_v2; #ifndef KDE_OUTPUT_DEVICE_REGISTRY_V2_INTERFACE #define KDE_OUTPUT_DEVICE_REGISTRY_V2_INTERFACE /** * @page page_iface_kde_output_device_registry_v2 kde_output_device_registry_v2 * @section page_iface_kde_output_device_registry_v2_desc Description * * This interface can be used to list output devices. * * If this global is bound with a version less than 21, the unsupported_version * protocol error will be posted. * @section page_iface_kde_output_device_registry_v2_api API * See @ref iface_kde_output_device_registry_v2. */ /** * @defgroup iface_kde_output_device_registry_v2 The kde_output_device_registry_v2 interface * * This interface can be used to list output devices. * * If this global is bound with a version less than 21, the unsupported_version * protocol error will be posted. */ extern const struct wl_interface kde_output_device_registry_v2_interface; #endif #ifndef KDE_OUTPUT_DEVICE_V2_INTERFACE #define KDE_OUTPUT_DEVICE_V2_INTERFACE /** * @page page_iface_kde_output_device_v2 kde_output_device_v2 * @section page_iface_kde_output_device_v2_desc Description * * An output device describes a display device available to the compositor. * output_device is similar to wl_output, but focuses on output * configuration management. * * A client can query all global output_device objects to enlist all * available display devices, even those that may currently not be * represented by the compositor as a wl_output. * * The client sends configuration changes to the server through the * outputconfiguration interface, and the server applies the configuration * changes to the hardware and signals changes to the output devices * accordingly. * * This object is published as global during start up for every available * display devices, or when one later becomes available, for example by * being hotplugged via a physical connector. * * Warning! The protocol described in this file is a desktop environment * implementation detail. Regular clients must not use this protocol. * Backward incompatible changes may be added without bumping the major * version of the extension. * @section page_iface_kde_output_device_v2_api API * See @ref iface_kde_output_device_v2. */ /** * @defgroup iface_kde_output_device_v2 The kde_output_device_v2 interface * * An output device describes a display device available to the compositor. * output_device is similar to wl_output, but focuses on output * configuration management. * * A client can query all global output_device objects to enlist all * available display devices, even those that may currently not be * represented by the compositor as a wl_output. * * The client sends configuration changes to the server through the * outputconfiguration interface, and the server applies the configuration * changes to the hardware and signals changes to the output devices * accordingly. * * This object is published as global during start up for every available * display devices, or when one later becomes available, for example by * being hotplugged via a physical connector. * * Warning! The protocol described in this file is a desktop environment * implementation detail. Regular clients must not use this protocol. * Backward incompatible changes may be added without bumping the major * version of the extension. */ extern const struct wl_interface kde_output_device_v2_interface; #endif #ifndef KDE_OUTPUT_DEVICE_MODE_V2_INTERFACE #define KDE_OUTPUT_DEVICE_MODE_V2_INTERFACE /** * @page page_iface_kde_output_device_mode_v2 kde_output_device_mode_v2 * @section page_iface_kde_output_device_mode_v2_desc Description * * This object describes an output mode. * * Some heads don't support output modes, in which case modes won't be * advertised. * * Properties sent via this interface are applied atomically via the * kde_output_device.done event. No guarantees are made regarding the order * in which properties are sent. * @section page_iface_kde_output_device_mode_v2_api API * See @ref iface_kde_output_device_mode_v2. */ /** * @defgroup iface_kde_output_device_mode_v2 The kde_output_device_mode_v2 interface * * This object describes an output mode. * * Some heads don't support output modes, in which case modes won't be * advertised. * * Properties sent via this interface are applied atomically via the * kde_output_device.done event. No guarantees are made regarding the order * in which properties are sent. */ extern const struct wl_interface kde_output_device_mode_v2_interface; #endif #ifndef KDE_OUTPUT_DEVICE_REGISTRY_V2_ERROR_ENUM #define KDE_OUTPUT_DEVICE_REGISTRY_V2_ERROR_ENUM /** * @ingroup iface_kde_output_device_registry_v2 * kde_output_device_registry_v2 error values * * These errors can be emitted in response to some requests. */ enum kde_output_device_registry_v2_error { /** * the registry was bound with an unsupported version */ KDE_OUTPUT_DEVICE_REGISTRY_V2_ERROR_UNSUPPORTED_VERSION = 0, }; #endif /* KDE_OUTPUT_DEVICE_REGISTRY_V2_ERROR_ENUM */ /** * @ingroup iface_kde_output_device_registry_v2 * @struct kde_output_device_registry_v2_listener */ struct kde_output_device_registry_v2_listener { /** * no new output announcements * * This event is sent in response to the stop request. The * compositor will immediately destroy the object after sending * this event. * @since 21 */ void (*finished)(void *data, struct kde_output_device_registry_v2 *kde_output_device_registry_v2); /** * new available output * * This event is sent when a new output is connected or after * binding this global to list all available outputs. * @since 21 */ void (*output)(void *data, struct kde_output_device_registry_v2 *kde_output_device_registry_v2, struct kde_output_device_v2 *output); }; /** * @ingroup iface_kde_output_device_registry_v2 */ static inline int kde_output_device_registry_v2_add_listener(struct kde_output_device_registry_v2 *kde_output_device_registry_v2, const struct kde_output_device_registry_v2_listener *listener, void *data) { return wl_proxy_add_listener((struct wl_proxy *) kde_output_device_registry_v2, (void (**)(void)) listener, data); } #define KDE_OUTPUT_DEVICE_REGISTRY_V2_STOP 0 /** * @ingroup iface_kde_output_device_registry_v2 */ #define KDE_OUTPUT_DEVICE_REGISTRY_V2_FINISHED_SINCE_VERSION 21 /** * @ingroup iface_kde_output_device_registry_v2 */ #define KDE_OUTPUT_DEVICE_REGISTRY_V2_OUTPUT_SINCE_VERSION 21 /** * @ingroup iface_kde_output_device_registry_v2 */ #define KDE_OUTPUT_DEVICE_REGISTRY_V2_STOP_SINCE_VERSION 21 /** @ingroup iface_kde_output_device_registry_v2 */ static inline void kde_output_device_registry_v2_set_user_data(struct kde_output_device_registry_v2 *kde_output_device_registry_v2, void *user_data) { wl_proxy_set_user_data((struct wl_proxy *) kde_output_device_registry_v2, user_data); } /** @ingroup iface_kde_output_device_registry_v2 */ static inline void * kde_output_device_registry_v2_get_user_data(struct kde_output_device_registry_v2 *kde_output_device_registry_v2) { return wl_proxy_get_user_data((struct wl_proxy *) kde_output_device_registry_v2); } static inline uint32_t kde_output_device_registry_v2_get_version(struct kde_output_device_registry_v2 *kde_output_device_registry_v2) { return wl_proxy_get_version((struct wl_proxy *) kde_output_device_registry_v2); } /** @ingroup iface_kde_output_device_registry_v2 */ static inline void kde_output_device_registry_v2_destroy(struct kde_output_device_registry_v2 *kde_output_device_registry_v2) { wl_proxy_destroy((struct wl_proxy *) kde_output_device_registry_v2); } /** * @ingroup iface_kde_output_device_registry_v2 * * This request indicates that the client no longer wants to receive new * output announcements. The compositor will send the * kde_output_device_registry_v2.finished event in response to this request. * The compositor may still send new output announcements after calling this * 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); } #ifndef KDE_OUTPUT_DEVICE_V2_SUBPIXEL_ENUM #define KDE_OUTPUT_DEVICE_V2_SUBPIXEL_ENUM /** * @ingroup iface_kde_output_device_v2 * subpixel geometry information * * This enumeration describes how the physical pixels on an output are * laid out. */ enum kde_output_device_v2_subpixel { KDE_OUTPUT_DEVICE_V2_SUBPIXEL_UNKNOWN = 0, KDE_OUTPUT_DEVICE_V2_SUBPIXEL_NONE = 1, KDE_OUTPUT_DEVICE_V2_SUBPIXEL_HORIZONTAL_RGB = 2, KDE_OUTPUT_DEVICE_V2_SUBPIXEL_HORIZONTAL_BGR = 3, KDE_OUTPUT_DEVICE_V2_SUBPIXEL_VERTICAL_RGB = 4, KDE_OUTPUT_DEVICE_V2_SUBPIXEL_VERTICAL_BGR = 5, }; #endif /* KDE_OUTPUT_DEVICE_V2_SUBPIXEL_ENUM */ #ifndef KDE_OUTPUT_DEVICE_V2_TRANSFORM_ENUM #define KDE_OUTPUT_DEVICE_V2_TRANSFORM_ENUM /** * @ingroup iface_kde_output_device_v2 * transform from framebuffer to output * * This describes the transform, that a compositor will apply to a * surface to compensate for the rotation or mirroring of an * output device. * * The flipped values correspond to an initial flip around a * vertical axis followed by rotation. * * The purpose is mainly to allow clients to render accordingly and * tell the compositor, so that for fullscreen surfaces, the * compositor is still able to scan out directly client surfaces. */ enum kde_output_device_v2_transform { KDE_OUTPUT_DEVICE_V2_TRANSFORM_NORMAL = 0, KDE_OUTPUT_DEVICE_V2_TRANSFORM_90 = 1, KDE_OUTPUT_DEVICE_V2_TRANSFORM_180 = 2, KDE_OUTPUT_DEVICE_V2_TRANSFORM_270 = 3, KDE_OUTPUT_DEVICE_V2_TRANSFORM_FLIPPED = 4, KDE_OUTPUT_DEVICE_V2_TRANSFORM_FLIPPED_90 = 5, KDE_OUTPUT_DEVICE_V2_TRANSFORM_FLIPPED_180 = 6, KDE_OUTPUT_DEVICE_V2_TRANSFORM_FLIPPED_270 = 7, }; #endif /* KDE_OUTPUT_DEVICE_V2_TRANSFORM_ENUM */ #ifndef KDE_OUTPUT_DEVICE_V2_CAPABILITY_ENUM #define KDE_OUTPUT_DEVICE_V2_CAPABILITY_ENUM /** * @ingroup iface_kde_output_device_v2 * describes capabilities of the outputdevice * * Describes what capabilities this device has. */ enum kde_output_device_v2_capability { /** * if this output_device can use overscan */ KDE_OUTPUT_DEVICE_V2_CAPABILITY_OVERSCAN = 0x1, /** * if this outputdevice supports variable refresh rate */ KDE_OUTPUT_DEVICE_V2_CAPABILITY_VRR = 0x2, /** * if setting the rgb range is possible */ KDE_OUTPUT_DEVICE_V2_CAPABILITY_RGB_RANGE = 0x4, /** * if this outputdevice supports high dynamic range * @since 3 */ KDE_OUTPUT_DEVICE_V2_CAPABILITY_HIGH_DYNAMIC_RANGE = 0x8, /** * if this outputdevice supports a wide color gamut * @since 3 */ KDE_OUTPUT_DEVICE_V2_CAPABILITY_WIDE_COLOR_GAMUT = 0x10, /** * if this outputdevice supports autorotation * @since 4 */ KDE_OUTPUT_DEVICE_V2_CAPABILITY_AUTO_ROTATE = 0x20, /** * if this outputdevice supports icc profiles * @since 5 */ KDE_OUTPUT_DEVICE_V2_CAPABILITY_ICC_PROFILE = 0x40, /** * if this outputdevice supports the brightness setting * @since 9 */ KDE_OUTPUT_DEVICE_V2_CAPABILITY_BRIGHTNESS = 0x80, /** * if this outputdevice supports the built-in color profile * @since 12 */ KDE_OUTPUT_DEVICE_V2_CAPABILITY_BUILT_IN_COLOR = 0x100, /** * if this outputdevice supports DDC/CI * @since 14 */ KDE_OUTPUT_DEVICE_V2_CAPABILITY_DDC_CI = 0x200, /** * if this outputdevice supports setting max bpc * @since 15 */ KDE_OUTPUT_DEVICE_V2_CAPABILITY_MAX_BITS_PER_COLOR = 0x400, /** * if this outputdevice supports EDR * @since 16 */ KDE_OUTPUT_DEVICE_V2_CAPABILITY_EDR = 0x800, /** * if this outputdevice supports the sharpness setting * @since 17 */ KDE_OUTPUT_DEVICE_V2_CAPABILITY_SHARPNESS = 0x1000, /** * if this outputdevice supports custom modes * @since 18 */ KDE_OUTPUT_DEVICE_V2_CAPABILITY_CUSTOM_MODES = 0x2000, /** * @since 19 */ KDE_OUTPUT_DEVICE_V2_CAPABILITY_AUTO_BRIGHTNESS = 0x4000, }; /** * @ingroup iface_kde_output_device_v2 */ #define KDE_OUTPUT_DEVICE_V2_CAPABILITY_HIGH_DYNAMIC_RANGE_SINCE_VERSION 3 /** * @ingroup iface_kde_output_device_v2 */ #define KDE_OUTPUT_DEVICE_V2_CAPABILITY_WIDE_COLOR_GAMUT_SINCE_VERSION 3 /** * @ingroup iface_kde_output_device_v2 */ #define KDE_OUTPUT_DEVICE_V2_CAPABILITY_AUTO_ROTATE_SINCE_VERSION 4 /** * @ingroup iface_kde_output_device_v2 */ #define KDE_OUTPUT_DEVICE_V2_CAPABILITY_ICC_PROFILE_SINCE_VERSION 5 /** * @ingroup iface_kde_output_device_v2 */ #define KDE_OUTPUT_DEVICE_V2_CAPABILITY_BRIGHTNESS_SINCE_VERSION 9 /** * @ingroup iface_kde_output_device_v2 */ #define KDE_OUTPUT_DEVICE_V2_CAPABILITY_BUILT_IN_COLOR_SINCE_VERSION 12 /** * @ingroup iface_kde_output_device_v2 */ #define KDE_OUTPUT_DEVICE_V2_CAPABILITY_DDC_CI_SINCE_VERSION 14 /** * @ingroup iface_kde_output_device_v2 */ #define KDE_OUTPUT_DEVICE_V2_CAPABILITY_MAX_BITS_PER_COLOR_SINCE_VERSION 15 /** * @ingroup iface_kde_output_device_v2 */ #define KDE_OUTPUT_DEVICE_V2_CAPABILITY_EDR_SINCE_VERSION 16 /** * @ingroup iface_kde_output_device_v2 */ #define KDE_OUTPUT_DEVICE_V2_CAPABILITY_SHARPNESS_SINCE_VERSION 17 /** * @ingroup iface_kde_output_device_v2 */ #define KDE_OUTPUT_DEVICE_V2_CAPABILITY_CUSTOM_MODES_SINCE_VERSION 18 /** * @ingroup iface_kde_output_device_v2 */ #define KDE_OUTPUT_DEVICE_V2_CAPABILITY_AUTO_BRIGHTNESS_SINCE_VERSION 19 #endif /* KDE_OUTPUT_DEVICE_V2_CAPABILITY_ENUM */ #ifndef KDE_OUTPUT_DEVICE_V2_VRR_POLICY_ENUM #define KDE_OUTPUT_DEVICE_V2_VRR_POLICY_ENUM /** * @ingroup iface_kde_output_device_v2 * describes vrr policy * * Describes when the compositor may employ variable refresh rate */ enum kde_output_device_v2_vrr_policy { KDE_OUTPUT_DEVICE_V2_VRR_POLICY_NEVER = 0, KDE_OUTPUT_DEVICE_V2_VRR_POLICY_ALWAYS = 1, KDE_OUTPUT_DEVICE_V2_VRR_POLICY_AUTOMATIC = 2, }; #endif /* KDE_OUTPUT_DEVICE_V2_VRR_POLICY_ENUM */ #ifndef KDE_OUTPUT_DEVICE_V2_RGB_RANGE_ENUM #define KDE_OUTPUT_DEVICE_V2_RGB_RANGE_ENUM /** * @ingroup iface_kde_output_device_v2 * describes RGB range policy * * Whether full or limited color range should be used */ enum kde_output_device_v2_rgb_range { KDE_OUTPUT_DEVICE_V2_RGB_RANGE_AUTOMATIC = 0, KDE_OUTPUT_DEVICE_V2_RGB_RANGE_FULL = 1, KDE_OUTPUT_DEVICE_V2_RGB_RANGE_LIMITED = 2, }; #endif /* KDE_OUTPUT_DEVICE_V2_RGB_RANGE_ENUM */ #ifndef KDE_OUTPUT_DEVICE_V2_AUTO_ROTATE_POLICY_ENUM #define KDE_OUTPUT_DEVICE_V2_AUTO_ROTATE_POLICY_ENUM /** * @ingroup iface_kde_output_device_v2 * describes when auto rotate should be used */ enum kde_output_device_v2_auto_rotate_policy { KDE_OUTPUT_DEVICE_V2_AUTO_ROTATE_POLICY_NEVER = 0, KDE_OUTPUT_DEVICE_V2_AUTO_ROTATE_POLICY_IN_TABLET_MODE = 1, KDE_OUTPUT_DEVICE_V2_AUTO_ROTATE_POLICY_ALWAYS = 2, }; #endif /* KDE_OUTPUT_DEVICE_V2_AUTO_ROTATE_POLICY_ENUM */ #ifndef KDE_OUTPUT_DEVICE_V2_COLOR_PROFILE_SOURCE_ENUM #define KDE_OUTPUT_DEVICE_V2_COLOR_PROFILE_SOURCE_ENUM /** * @ingroup iface_kde_output_device_v2 * which source the compositor should use for the color profile on an output */ enum kde_output_device_v2_color_profile_source { KDE_OUTPUT_DEVICE_V2_COLOR_PROFILE_SOURCE_SRGB = 0, KDE_OUTPUT_DEVICE_V2_COLOR_PROFILE_SOURCE_ICC = 1, KDE_OUTPUT_DEVICE_V2_COLOR_PROFILE_SOURCE_EDID = 2, }; #endif /* KDE_OUTPUT_DEVICE_V2_COLOR_PROFILE_SOURCE_ENUM */ #ifndef KDE_OUTPUT_DEVICE_V2_COLOR_POWER_TRADEOFF_ENUM #define KDE_OUTPUT_DEVICE_V2_COLOR_POWER_TRADEOFF_ENUM /** * @ingroup iface_kde_output_device_v2 * tradeoff between power and accuracy * * The compositor can do a lot of things that trade between * performance, power and color accuracy. This setting describes * a high level preference from the user about in which direction * that tradeoff should be made. */ enum kde_output_device_v2_color_power_tradeoff { /** * prefer efficiency and performance */ KDE_OUTPUT_DEVICE_V2_COLOR_POWER_TRADEOFF_EFFICIENCY = 0, /** * prefer accuracy */ KDE_OUTPUT_DEVICE_V2_COLOR_POWER_TRADEOFF_ACCURACY = 1, }; #endif /* KDE_OUTPUT_DEVICE_V2_COLOR_POWER_TRADEOFF_ENUM */ #ifndef KDE_OUTPUT_DEVICE_V2_EDR_POLICY_ENUM #define KDE_OUTPUT_DEVICE_V2_EDR_POLICY_ENUM /** * @ingroup iface_kde_output_device_v2 * when the compositor may make use of EDR */ enum kde_output_device_v2_edr_policy { KDE_OUTPUT_DEVICE_V2_EDR_POLICY_NEVER = 0, KDE_OUTPUT_DEVICE_V2_EDR_POLICY_ALWAYS = 1, }; #endif /* KDE_OUTPUT_DEVICE_V2_EDR_POLICY_ENUM */ /** * @ingroup iface_kde_output_device_v2 * @struct kde_output_device_v2_listener */ struct kde_output_device_v2_listener { /** * geometric properties of the output * * The geometry event describes geometric properties of the * output. The event is sent when binding to the output object and * whenever any of the properties change. * @param x x position within the global compositor space * @param y y position within the global compositor space * @param physical_width width in millimeters of the output * @param physical_height height in millimeters of the output * @param subpixel subpixel orientation of the output * @param make textual description of the manufacturer * @param model textual description of the model * @param transform transform that maps framebuffer to output */ void (*geometry)(void *data, struct kde_output_device_v2 *kde_output_device_v2, int32_t x, int32_t y, int32_t physical_width, int32_t physical_height, int32_t subpixel, const char *make, const char *model, int32_t transform); /** * current mode * * This event describes the mode currently in use for this head. * It is only sent if the output is enabled. */ void (*current_mode)(void *data, struct kde_output_device_v2 *kde_output_device_v2, struct kde_output_device_mode_v2 *mode); /** * advertise available output modes and current one * * The mode event describes an available mode for the output. * * When the client binds to the output_device object, the server * sends this event once for every available mode the output_device * can be operated by. * * There will always be at least one event sent out on initial * binding, which represents the current mode. * * Later if an output changes, its mode event is sent again for the * eventual added modes and lastly the current mode. In other * words, the current mode is always represented by the latest * event sent with the current flag set. * * The size of a mode is given in physical hardware units of the * output device. This is not necessarily the same as the output * size in the global compositor space. For instance, the output * may be scaled, as described in kde_output_device_v2.scale, or * transformed, as described in kde_output_device_v2.transform. */ void (*mode)(void *data, struct kde_output_device_v2 *kde_output_device_v2, struct kde_output_device_mode_v2 *mode); /** * sent all information about output * * This event is sent after all other properties have been sent * on binding to the output object as well as after any other * output property change have been applied later on. This allows * to see changes to the output properties as atomic, even if * multiple events successively announce them. */ void (*done)(void *data, struct kde_output_device_v2 *kde_output_device_v2); /** * output scaling properties * * This event contains scaling geometry information that is not * in the geometry event. It may be sent after binding the output * object or if the output scale changes later. If it is not sent, * the client should assume a scale of 1. * * A scale larger than 1 means that the compositor will * automatically scale surface buffers by this amount when * rendering. This is used for high resolution displays where * applications rendering at the native resolution would be too * small to be legible. * * It is intended that scaling aware clients track the current * output of a surface, and if it is on a scaled output it should * use wl_surface.set_buffer_scale with the scale of the output. * That way the compositor can avoid scaling the surface, and the * client can supply a higher detail image. * @param factor scaling factor of output */ void (*scale)(void *data, struct kde_output_device_v2 *kde_output_device_v2, wl_fixed_t factor); /** * advertise EDID data for the output * * The edid event encapsulates the EDID data for the * outputdevice. * * The event is sent when binding to the output object. The EDID * data may be empty, in which case this event is sent anyway. If * the EDID information is empty, you can fall back to the name et * al. properties of the outputdevice. * @param raw base64-encoded EDID string */ void (*edid)(void *data, struct kde_output_device_v2 *kde_output_device_v2, const char *raw); /** * output is enabled or disabled * * The enabled event notifies whether this output is currently * enabled and used for displaying content by the server. The event * is sent when binding to the output object and whenever later on * an output changes its state by becoming enabled or disabled. * @param enabled output enabled state */ void (*enabled)(void *data, struct kde_output_device_v2 *kde_output_device_v2, int32_t enabled); /** * A unique id for this outputdevice * * The uuid can be used to identify the output. It's controlled * by the server entirely. The server should make sure the uuid is * persistent across restarts. An empty uuid is considered invalid. * @param uuid output devices ID */ void (*uuid)(void *data, struct kde_output_device_v2 *kde_output_device_v2, const char *uuid); /** * Serial Number * * Serial ID of the monitor, sent on startup before the first * done event. * @param serialNumber textual representation of serial number */ void (*serial_number)(void *data, struct kde_output_device_v2 *kde_output_device_v2, const char *serialNumber); /** * EISA ID * * EISA ID of the monitor, sent on startup before the first done * event. * @param eisaId textual representation of EISA identifier */ void (*eisa_id)(void *data, struct kde_output_device_v2 *kde_output_device_v2, const char *eisaId); /** * capability flags * * What capabilities this device has, sent on startup before the * first done event. */ void (*capabilities)(void *data, struct kde_output_device_v2 *kde_output_device_v2, uint32_t flags); /** * overscan * * Overscan value of the monitor in percent, sent on startup * before the first done event. * @param overscan amount of overscan of the monitor */ void (*overscan)(void *data, struct kde_output_device_v2 *kde_output_device_v2, uint32_t overscan); /** * Variable Refresh Rate Policy * * What policy the compositor will employ regarding its use of * variable refresh rate. */ void (*vrr_policy)(void *data, struct kde_output_device_v2 *kde_output_device_v2, uint32_t vrr_policy); /** * RGB range * * What rgb range the compositor is using for this output */ void (*rgb_range)(void *data, struct kde_output_device_v2 *kde_output_device_v2, uint32_t rgb_range); /** * Output's name * * Name of the output, it's useful to cross-reference to an * zxdg_output_v1 and ultimately QScreen * @since 2 */ void (*name)(void *data, struct kde_output_device_v2 *kde_output_device_v2, const char *name); /** * if HDR is enabled * * Whether or not high dynamic range is enabled for this output * @param hdr_enabled 1 if enabled, 0 if disabled * @since 3 */ void (*high_dynamic_range)(void *data, struct kde_output_device_v2 *kde_output_device_v2, uint32_t hdr_enabled); /** * the brightness of sdr if hdr is enabled * * If high dynamic range is used, this value defines the * brightness in nits for content that's in standard dynamic range * format. Note that while the value is in nits, that doesn't * necessarily translate to the same brightness on the screen. * @since 3 */ void (*sdr_brightness)(void *data, struct kde_output_device_v2 *kde_output_device_v2, uint32_t sdr_brightness); /** * if WCG is enabled * * Whether or not the use of a wide color gamut is enabled for * this output * @param wcg_enabled 1 if enabled, 0 if disabled * @since 3 */ void (*wide_color_gamut)(void *data, struct kde_output_device_v2 *kde_output_device_v2, uint32_t wcg_enabled); /** * describes when auto rotate is used * * * @since 4 */ void (*auto_rotate_policy)(void *data, struct kde_output_device_v2 *kde_output_device_v2, uint32_t policy); /** * describes when auto rotate is used * * * @since 5 */ void (*icc_profile_path)(void *data, struct kde_output_device_v2 *kde_output_device_v2, const char *profile_path); /** * metadata about the screen's brightness limits * * * @param max_peak_brightness in nits * @param max_frame_average_brightness in nits * @param min_brightness in 0.0001 nits * @since 6 */ void (*brightness_metadata)(void *data, struct kde_output_device_v2 *kde_output_device_v2, uint32_t max_peak_brightness, uint32_t max_frame_average_brightness, uint32_t min_brightness); /** * overrides for the screen's brightness limits * * * @param max_peak_brightness -1 for no override, positive values are the brightness in nits * @param max_average_brightness -1 for no override, positive values are the brightness in nits * @param min_brightness -1 for no override, positive values are the brightness in 0.0001 nits * @since 6 */ void (*brightness_overrides)(void *data, struct kde_output_device_v2 *kde_output_device_v2, int32_t max_peak_brightness, int32_t max_average_brightness, int32_t min_brightness); /** * describes which gamut is assumed for sRGB applications * * This can be used to provide the colors users assume sRGB * applications should have based on the default experience on many * modern sRGB screens. * @param gamut_wideness 0 means rec.709 primaries, 10000 means native primaries * @since 6 */ void (*sdr_gamut_wideness)(void *data, 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 * * * @since 7 */ void (*color_profile_source)(void *data, struct kde_output_device_v2 *kde_output_device_v2, uint32_t source); /** * brightness multiplier * * This is the brightness modifier of the output. It doesn't * specify any absolute values, but is merely a multiplier on top * of other brightness values, like sdr_brightness and * brightness_metadata. 0 is the minimum brightness (not completely * dark) and 10000 is the maximum brightness. This is currently * only supported / meaningful while HDR is active. * @param brightness brightness in 0-10000 * @since 8 */ void (*brightness)(void *data, struct kde_output_device_v2 *kde_output_device_v2, uint32_t brightness); /** * the preferred color/power tradeoff * * * @since 10 */ void (*color_power_tradeoff)(void *data, struct kde_output_device_v2 *kde_output_device_v2, uint32_t preference); /** * dimming multiplier * * This is the dimming multiplier of the output. This is similar * to the brightness setting, except it's meant to be a temporary * setting only, not persistent and may be implemented differently * depending on the display. 0 is the minimum dimming factor (not * completely dark) and 10000 means the output is not dimmed. * @param multiplier multiplier in 0-10000 * @since 11 */ void (*dimming)(void *data, struct kde_output_device_v2 *kde_output_device_v2, uint32_t multiplier); /** * source output for mirroring * * * @param source uuid of the source output * @since 13 */ void (*replication_source)(void *data, struct kde_output_device_v2 *kde_output_device_v2, const char *source); /** * if DDC/CI should be used to control brightness etc. * * If the ddc_ci capability is present, this determines if * settings such as brightness, contrast or others should be set * using DDC/CI. * @param allowed 1 if allowed, 0 if disabled * @since 14 */ void (*ddc_ci_allowed)(void *data, struct kde_output_device_v2 *kde_output_device_v2, uint32_t allowed); /** * override max bpc * * This limits the amount of bits per color that are sent to the * display. * @param max_bpc 0 for the default / automatic * @since 15 */ void (*max_bits_per_color)(void *data, struct kde_output_device_v2 *kde_output_device_v2, uint32_t max_bpc); /** * range of max bits per color value * * * @param min_value the minimum supported by the driver * @param max_value the maximum supported by the driver * @since 15 */ void (*max_bits_per_color_range)(void *data, struct kde_output_device_v2 *kde_output_device_v2, uint32_t min_value, uint32_t max_value); /** * if and to what value automatic max bpc is limited * * * @param max_bpc_limit which value automatic bpc gets limited to. 0 if not limited * @since 15 */ void (*automatic_max_bits_per_color_limit)(void *data, struct kde_output_device_v2 *kde_output_device_v2, uint32_t max_bpc_limit); /** * when the compositor may apply EDR * * When EDR is enabled, the compositor may increase the backlight * beyond the user-specified setting, in order to present HDR * content on displays without native HDR support. This will * usually result in better visuals, but also increases battery * usage. * @since 16 */ void (*edr_policy)(void *data, struct kde_output_device_v2 *kde_output_device_v2, uint32_t policy); /** * sharpness strength * * This is the sharpness modifier of the output. 0 is sharpness * disabled and 10000 is the maximum sharpness * @param sharpness sharpness in 0-10000 * @since 17 */ void (*sharpness)(void *data, struct kde_output_device_v2 *kde_output_device_v2, uint32_t sharpness); /** * output priority * * Describes the position of the output in the output order list, * with lower values being earlier in the list. There's no specific * value the list has to start at, this value is only used in * sorting outputs. * * Note that the output order protocol is not sufficient for this, * as an output may not be in the output order if it's disabled or * mirroring another screen. * @param priority priority * @since 18 */ void (*priority)(void *data, struct kde_output_device_v2 *kde_output_device_v2, uint32_t priority); /** * whether or not automatic brightness is enabled * * * @param enabled 1 for enabled, 0 for disabled * @since 20 */ void (*auto_brightness)(void *data, struct kde_output_device_v2 *kde_output_device_v2, uint32_t enabled); /** * the output has been removed * * This event is sent when the output device is disconnected and * no new updates will be sent. The client should call the * kde_output_device_v2.release request after receiving this event. * @since 21 */ void (*removed)(void *data, struct kde_output_device_v2 *kde_output_device_v2); }; /** * @ingroup iface_kde_output_device_v2 */ static inline int kde_output_device_v2_add_listener(struct kde_output_device_v2 *kde_output_device_v2, const struct kde_output_device_v2_listener *listener, void *data) { return wl_proxy_add_listener((struct wl_proxy *) kde_output_device_v2, (void (**)(void)) listener, data); } #define KDE_OUTPUT_DEVICE_V2_RELEASE 0 /** * @ingroup iface_kde_output_device_v2 */ #define KDE_OUTPUT_DEVICE_V2_GEOMETRY_SINCE_VERSION 1 /** * @ingroup iface_kde_output_device_v2 */ #define KDE_OUTPUT_DEVICE_V2_CURRENT_MODE_SINCE_VERSION 1 /** * @ingroup iface_kde_output_device_v2 */ #define KDE_OUTPUT_DEVICE_V2_MODE_SINCE_VERSION 1 /** * @ingroup iface_kde_output_device_v2 */ #define KDE_OUTPUT_DEVICE_V2_DONE_SINCE_VERSION 1 /** * @ingroup iface_kde_output_device_v2 */ #define KDE_OUTPUT_DEVICE_V2_SCALE_SINCE_VERSION 1 /** * @ingroup iface_kde_output_device_v2 */ #define KDE_OUTPUT_DEVICE_V2_EDID_SINCE_VERSION 1 /** * @ingroup iface_kde_output_device_v2 */ #define KDE_OUTPUT_DEVICE_V2_ENABLED_SINCE_VERSION 1 /** * @ingroup iface_kde_output_device_v2 */ #define KDE_OUTPUT_DEVICE_V2_UUID_SINCE_VERSION 1 /** * @ingroup iface_kde_output_device_v2 */ #define KDE_OUTPUT_DEVICE_V2_SERIAL_NUMBER_SINCE_VERSION 1 /** * @ingroup iface_kde_output_device_v2 */ #define KDE_OUTPUT_DEVICE_V2_EISA_ID_SINCE_VERSION 1 /** * @ingroup iface_kde_output_device_v2 */ #define KDE_OUTPUT_DEVICE_V2_CAPABILITIES_SINCE_VERSION 1 /** * @ingroup iface_kde_output_device_v2 */ #define KDE_OUTPUT_DEVICE_V2_OVERSCAN_SINCE_VERSION 1 /** * @ingroup iface_kde_output_device_v2 */ #define KDE_OUTPUT_DEVICE_V2_VRR_POLICY_SINCE_VERSION 1 /** * @ingroup iface_kde_output_device_v2 */ #define KDE_OUTPUT_DEVICE_V2_RGB_RANGE_SINCE_VERSION 1 /** * @ingroup iface_kde_output_device_v2 */ #define KDE_OUTPUT_DEVICE_V2_NAME_SINCE_VERSION 2 /** * @ingroup iface_kde_output_device_v2 */ #define KDE_OUTPUT_DEVICE_V2_HIGH_DYNAMIC_RANGE_SINCE_VERSION 3 /** * @ingroup iface_kde_output_device_v2 */ #define KDE_OUTPUT_DEVICE_V2_SDR_BRIGHTNESS_SINCE_VERSION 3 /** * @ingroup iface_kde_output_device_v2 */ #define KDE_OUTPUT_DEVICE_V2_WIDE_COLOR_GAMUT_SINCE_VERSION 3 /** * @ingroup iface_kde_output_device_v2 */ #define KDE_OUTPUT_DEVICE_V2_AUTO_ROTATE_POLICY_SINCE_VERSION 4 /** * @ingroup iface_kde_output_device_v2 */ #define KDE_OUTPUT_DEVICE_V2_ICC_PROFILE_PATH_SINCE_VERSION 5 /** * @ingroup iface_kde_output_device_v2 */ #define KDE_OUTPUT_DEVICE_V2_BRIGHTNESS_METADATA_SINCE_VERSION 6 /** * @ingroup iface_kde_output_device_v2 */ #define KDE_OUTPUT_DEVICE_V2_BRIGHTNESS_OVERRIDES_SINCE_VERSION 6 /** * @ingroup iface_kde_output_device_v2 */ #define KDE_OUTPUT_DEVICE_V2_SDR_GAMUT_WIDENESS_SINCE_VERSION 6 /** * @ingroup iface_kde_output_device_v2 */ #define KDE_OUTPUT_DEVICE_V2_COLOR_PROFILE_SOURCE_SINCE_VERSION 7 /** * @ingroup iface_kde_output_device_v2 */ #define KDE_OUTPUT_DEVICE_V2_BRIGHTNESS_SINCE_VERSION 8 /** * @ingroup iface_kde_output_device_v2 */ #define KDE_OUTPUT_DEVICE_V2_COLOR_POWER_TRADEOFF_SINCE_VERSION 10 /** * @ingroup iface_kde_output_device_v2 */ #define KDE_OUTPUT_DEVICE_V2_DIMMING_SINCE_VERSION 11 /** * @ingroup iface_kde_output_device_v2 */ #define KDE_OUTPUT_DEVICE_V2_REPLICATION_SOURCE_SINCE_VERSION 13 /** * @ingroup iface_kde_output_device_v2 */ #define KDE_OUTPUT_DEVICE_V2_DDC_CI_ALLOWED_SINCE_VERSION 14 /** * @ingroup iface_kde_output_device_v2 */ #define KDE_OUTPUT_DEVICE_V2_MAX_BITS_PER_COLOR_SINCE_VERSION 15 /** * @ingroup iface_kde_output_device_v2 */ #define KDE_OUTPUT_DEVICE_V2_MAX_BITS_PER_COLOR_RANGE_SINCE_VERSION 15 /** * @ingroup iface_kde_output_device_v2 */ #define KDE_OUTPUT_DEVICE_V2_AUTOMATIC_MAX_BITS_PER_COLOR_LIMIT_SINCE_VERSION 15 /** * @ingroup iface_kde_output_device_v2 */ #define KDE_OUTPUT_DEVICE_V2_EDR_POLICY_SINCE_VERSION 16 /** * @ingroup iface_kde_output_device_v2 */ #define KDE_OUTPUT_DEVICE_V2_SHARPNESS_SINCE_VERSION 17 /** * @ingroup iface_kde_output_device_v2 */ #define KDE_OUTPUT_DEVICE_V2_PRIORITY_SINCE_VERSION 18 /** * @ingroup iface_kde_output_device_v2 */ #define KDE_OUTPUT_DEVICE_V2_AUTO_BRIGHTNESS_SINCE_VERSION 20 /** * @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_RELEASE_SINCE_VERSION 21 /** @ingroup iface_kde_output_device_v2 */ static inline void kde_output_device_v2_set_user_data(struct kde_output_device_v2 *kde_output_device_v2, void *user_data) { wl_proxy_set_user_data((struct wl_proxy *) kde_output_device_v2, user_data); } /** @ingroup iface_kde_output_device_v2 */ static inline void * kde_output_device_v2_get_user_data(struct kde_output_device_v2 *kde_output_device_v2) { return wl_proxy_get_user_data((struct wl_proxy *) kde_output_device_v2); } static inline uint32_t kde_output_device_v2_get_version(struct kde_output_device_v2 *kde_output_device_v2) { return wl_proxy_get_version((struct wl_proxy *) kde_output_device_v2); } /** @ingroup iface_kde_output_device_v2 */ static inline void kde_output_device_v2_destroy(struct kde_output_device_v2 *kde_output_device_v2) { wl_proxy_destroy((struct wl_proxy *) kde_output_device_v2); } /** * @ingroup iface_kde_output_device_v2 * * This notifies the compositor that the client no longer wishes to use * 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); } #ifndef KDE_OUTPUT_DEVICE_MODE_V2_FLAGS_ENUM #define KDE_OUTPUT_DEVICE_MODE_V2_FLAGS_ENUM /** * @ingroup iface_kde_output_device_mode_v2 * mode flags */ enum kde_output_device_mode_v2_flags { KDE_OUTPUT_DEVICE_MODE_V2_FLAGS_CUSTOM = 0x1, KDE_OUTPUT_DEVICE_MODE_V2_FLAGS_REDUCED_BLANKING = 0x2, }; #endif /* KDE_OUTPUT_DEVICE_MODE_V2_FLAGS_ENUM */ /** * @ingroup iface_kde_output_device_mode_v2 * @struct kde_output_device_mode_v2_listener */ struct kde_output_device_mode_v2_listener { /** * mode size * * This event describes the mode size. The size is given in * physical hardware units of the output device. This is not * necessarily the same as the output size in the global compositor * space. For instance, the output may be scaled or transformed. * @param width width of the mode in hardware units * @param height height of the mode in hardware units */ void (*size)(void *data, struct kde_output_device_mode_v2 *kde_output_device_mode_v2, int32_t width, int32_t height); /** * mode refresh rate * * This event describes the mode's fixed vertical refresh rate. * It is only sent if the mode has a fixed refresh rate. * @param refresh vertical refresh rate in mHz */ void (*refresh)(void *data, struct kde_output_device_mode_v2 *kde_output_device_mode_v2, int32_t refresh); /** * mode is preferred * * This event advertises this mode as preferred. */ void (*preferred)(void *data, struct kde_output_device_mode_v2 *kde_output_device_mode_v2); /** * the mode has been destroyed * * The compositor will destroy the object immediately after * sending this event, so it will become invalid and the client * should release any resources associated with it. */ void (*removed)(void *data, struct kde_output_device_mode_v2 *kde_output_device_mode_v2); /** * mode flags * * This event describes the mode's flags. * @since 19 */ void (*flags)(void *data, struct kde_output_device_mode_v2 *kde_output_device_mode_v2, uint32_t flags); }; /** * @ingroup iface_kde_output_device_mode_v2 */ static inline int kde_output_device_mode_v2_add_listener(struct kde_output_device_mode_v2 *kde_output_device_mode_v2, const struct kde_output_device_mode_v2_listener *listener, void *data) { return wl_proxy_add_listener((struct wl_proxy *) kde_output_device_mode_v2, (void (**)(void)) listener, data); } /** * @ingroup iface_kde_output_device_mode_v2 */ #define KDE_OUTPUT_DEVICE_MODE_V2_SIZE_SINCE_VERSION 1 /** * @ingroup iface_kde_output_device_mode_v2 */ #define KDE_OUTPUT_DEVICE_MODE_V2_REFRESH_SINCE_VERSION 1 /** * @ingroup iface_kde_output_device_mode_v2 */ #define KDE_OUTPUT_DEVICE_MODE_V2_PREFERRED_SINCE_VERSION 1 /** * @ingroup iface_kde_output_device_mode_v2 */ #define KDE_OUTPUT_DEVICE_MODE_V2_REMOVED_SINCE_VERSION 1 /** * @ingroup iface_kde_output_device_mode_v2 */ #define KDE_OUTPUT_DEVICE_MODE_V2_FLAGS_SINCE_VERSION 19 /** @ingroup iface_kde_output_device_mode_v2 */ static inline void kde_output_device_mode_v2_set_user_data(struct kde_output_device_mode_v2 *kde_output_device_mode_v2, void *user_data) { wl_proxy_set_user_data((struct wl_proxy *) kde_output_device_mode_v2, user_data); } /** @ingroup iface_kde_output_device_mode_v2 */ static inline void * kde_output_device_mode_v2_get_user_data(struct kde_output_device_mode_v2 *kde_output_device_mode_v2) { return wl_proxy_get_user_data((struct wl_proxy *) kde_output_device_mode_v2); } static inline uint32_t kde_output_device_mode_v2_get_version(struct kde_output_device_mode_v2 *kde_output_device_mode_v2) { return wl_proxy_get_version((struct wl_proxy *) kde_output_device_mode_v2); } /** @ingroup iface_kde_output_device_mode_v2 */ static inline void kde_output_device_mode_v2_destroy(struct kde_output_device_mode_v2 *kde_output_device_mode_v2) { wl_proxy_destroy((struct wl_proxy *) kde_output_device_mode_v2); } #ifdef __cplusplus } #endif #endif ================================================ FILE: src/detection/displayserver/linux/wayland/kde-output-device-v2-protocol.c ================================================ #ifdef FF_HAVE_WAYLAND /* Generated by wayland-scanner 1.24.0 */ /* * SPDX-FileCopyrightText: 2008-2011 Kristian Høgsberg * SPDX-FileCopyrightText: 2010-2011 Intel Corporation * SPDX-FileCopyrightText: 2012-2013 Collabora, Ltd. * SPDX-FileCopyrightText: 2015 Sebastian Kügler * SPDX-FileCopyrightText: 2021 Méven Car * * SPDX-License-Identifier: MIT-CMU */ #include #include #include #include extern const struct wl_interface kde_output_device_mode_v2_interface; extern const struct wl_interface kde_output_device_v2_interface; static const struct wl_interface *kde_output_device_v2_types[] = { NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, &kde_output_device_v2_interface, &kde_output_device_mode_v2_interface, &kde_output_device_mode_v2_interface, }; static const struct wl_message kde_output_device_registry_v2_requests[] = { { "stop", "21", kde_output_device_v2_types + 0 }, }; static const struct wl_message kde_output_device_registry_v2_events[] = { { "finished", "21", kde_output_device_v2_types + 0 }, { "output", "21n", kde_output_device_v2_types + 8 }, }; WL_EXPORT const struct wl_interface kde_output_device_registry_v2_interface = { "kde_output_device_registry_v2", 21, 1, kde_output_device_registry_v2_requests, 2, kde_output_device_registry_v2_events, }; static const struct wl_message kde_output_device_v2_requests[] = { { "release", "21", kde_output_device_v2_types + 0 }, }; static const struct wl_message kde_output_device_v2_events[] = { { "geometry", "iiiiissi", kde_output_device_v2_types + 0 }, { "current_mode", "o", kde_output_device_v2_types + 9 }, { "mode", "n", kde_output_device_v2_types + 10 }, { "done", "", kde_output_device_v2_types + 0 }, { "scale", "f", kde_output_device_v2_types + 0 }, { "edid", "s", kde_output_device_v2_types + 0 }, { "enabled", "i", kde_output_device_v2_types + 0 }, { "uuid", "s", kde_output_device_v2_types + 0 }, { "serial_number", "s", kde_output_device_v2_types + 0 }, { "eisa_id", "s", kde_output_device_v2_types + 0 }, { "capabilities", "u", kde_output_device_v2_types + 0 }, { "overscan", "u", kde_output_device_v2_types + 0 }, { "vrr_policy", "u", kde_output_device_v2_types + 0 }, { "rgb_range", "u", kde_output_device_v2_types + 0 }, { "name", "2s", kde_output_device_v2_types + 0 }, { "high_dynamic_range", "3u", kde_output_device_v2_types + 0 }, { "sdr_brightness", "3u", kde_output_device_v2_types + 0 }, { "wide_color_gamut", "3u", kde_output_device_v2_types + 0 }, { "auto_rotate_policy", "4u", kde_output_device_v2_types + 0 }, { "icc_profile_path", "5s", kde_output_device_v2_types + 0 }, { "brightness_metadata", "6uuu", kde_output_device_v2_types + 0 }, { "brightness_overrides", "6iii", kde_output_device_v2_types + 0 }, { "sdr_gamut_wideness", "6u", kde_output_device_v2_types + 0 }, { "color_profile_source", "7u", kde_output_device_v2_types + 0 }, { "brightness", "8u", kde_output_device_v2_types + 0 }, { "color_power_tradeoff", "10u", kde_output_device_v2_types + 0 }, { "dimming", "11u", kde_output_device_v2_types + 0 }, { "replication_source", "13s", kde_output_device_v2_types + 0 }, { "ddc_ci_allowed", "14u", kde_output_device_v2_types + 0 }, { "max_bits_per_color", "15u", kde_output_device_v2_types + 0 }, { "max_bits_per_color_range", "15uu", kde_output_device_v2_types + 0 }, { "automatic_max_bits_per_color_limit", "15u", kde_output_device_v2_types + 0 }, { "edr_policy", "16u", kde_output_device_v2_types + 0 }, { "sharpness", "17u", kde_output_device_v2_types + 0 }, { "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 }, }; WL_EXPORT const struct wl_interface kde_output_device_v2_interface = { "kde_output_device_v2", 21, 1, kde_output_device_v2_requests, 37, kde_output_device_v2_events, }; static const struct wl_message kde_output_device_mode_v2_events[] = { { "size", "ii", kde_output_device_v2_types + 0 }, { "refresh", "i", kde_output_device_v2_types + 0 }, { "preferred", "", kde_output_device_v2_types + 0 }, { "removed", "", kde_output_device_v2_types + 0 }, { "flags", "19u", kde_output_device_v2_types + 0 }, }; WL_EXPORT const struct wl_interface kde_output_device_mode_v2_interface = { "kde_output_device_mode_v2", 21, 0, NULL, 5, kde_output_device_mode_v2_events, }; #endif ================================================ FILE: src/detection/displayserver/linux/wayland/kde-output-order-v1-client-protocol.h ================================================ /* Generated by wayland-scanner 1.22.0 */ #ifndef KDE_OUTPUT_ORDER_V1_CLIENT_PROTOCOL_H #define KDE_OUTPUT_ORDER_V1_CLIENT_PROTOCOL_H #include #include #include "wayland-client.h" #ifdef __cplusplus extern "C" { #endif /** * @page page_kde_output_order_v1 The kde_output_order_v1 protocol * @section page_ifaces_kde_output_order_v1 Interfaces * - @subpage page_iface_kde_output_order_v1 - announce order of outputs * @section page_copyright_kde_output_order_v1 Copyright *
 *
 * SPDX-FileCopyrightText: 2022 Xaver Hugl 
 *
 * SPDX-License-Identifier: MIT-CMU
 * 
*/ struct kde_output_order_v1; #ifndef KDE_OUTPUT_ORDER_V1_INTERFACE #define KDE_OUTPUT_ORDER_V1_INTERFACE /** * @page page_iface_kde_output_order_v1 kde_output_order_v1 * @section page_iface_kde_output_order_v1_desc Description * * Announce the order in which desktop environment components should be placed on outputs. * The compositor will send the list of outputs when the global is bound and whenever there is a change. * @section page_iface_kde_output_order_v1_api API * See @ref iface_kde_output_order_v1. */ /** * @defgroup iface_kde_output_order_v1 The kde_output_order_v1 interface * * Announce the order in which desktop environment components should be placed on outputs. * The compositor will send the list of outputs when the global is bound and whenever there is a change. */ extern const struct wl_interface kde_output_order_v1_interface; #endif /** * @ingroup iface_kde_output_order_v1 * @struct kde_output_order_v1_listener */ struct kde_output_order_v1_listener { /** * output name * * Specifies the output identified by their wl_output.name. * @param output_name the name of the output */ void (*output)(void *data, struct kde_output_order_v1 *kde_output_order_v1, const char *output_name); /** * done * * Specifies that the output list is complete. On the next output * event, a new list begins. */ void (*done)(void *data, struct kde_output_order_v1 *kde_output_order_v1); }; /** * @ingroup iface_kde_output_order_v1 */ static inline int kde_output_order_v1_add_listener(struct kde_output_order_v1 *kde_output_order_v1, const struct kde_output_order_v1_listener *listener, void *data) { return wl_proxy_add_listener((struct wl_proxy *) kde_output_order_v1, (void (**)(void)) listener, data); } #define KDE_OUTPUT_ORDER_V1_DESTROY 0 /** * @ingroup iface_kde_output_order_v1 */ #define KDE_OUTPUT_ORDER_V1_OUTPUT_SINCE_VERSION 1 /** * @ingroup iface_kde_output_order_v1 */ #define KDE_OUTPUT_ORDER_V1_DONE_SINCE_VERSION 1 /** * @ingroup iface_kde_output_order_v1 */ #define KDE_OUTPUT_ORDER_V1_DESTROY_SINCE_VERSION 1 // /** @ingroup iface_kde_output_order_v1 */ // static inline void // kde_output_order_v1_set_user_data(struct kde_output_order_v1 *kde_output_order_v1, void *user_data) // { // wl_proxy_set_user_data((struct wl_proxy *) kde_output_order_v1, user_data); // } // /** @ingroup iface_kde_output_order_v1 */ // static inline void * // kde_output_order_v1_get_user_data(struct kde_output_order_v1 *kde_output_order_v1) // { // return wl_proxy_get_user_data((struct wl_proxy *) kde_output_order_v1); // } // static inline uint32_t // kde_output_order_v1_get_version(struct kde_output_order_v1 *kde_output_order_v1) // { // return wl_proxy_get_version((struct wl_proxy *) kde_output_order_v1); // } // /** // * @ingroup iface_kde_output_order_v1 // */ // static inline void // kde_output_order_v1_destroy(struct kde_output_order_v1 *kde_output_order_v1) // { // wl_proxy_marshal_flags((struct wl_proxy *) kde_output_order_v1, // KDE_OUTPUT_ORDER_V1_DESTROY, NULL, wl_proxy_get_version((struct wl_proxy *) kde_output_order_v1), WL_MARSHAL_FLAG_DESTROY); // } #ifdef __cplusplus } #endif #endif ================================================ FILE: src/detection/displayserver/linux/wayland/kde-output-order-v1-protocol.c ================================================ #ifdef FF_HAVE_WAYLAND /* Generated by wayland-scanner 1.22.0 */ /* * SPDX-FileCopyrightText: 2022 Xaver Hugl * * SPDX-License-Identifier: MIT-CMU */ #include #include #include "wayland-util.h" static const struct wl_interface *kde_output_order_v1_types[] = { NULL, }; static const struct wl_message kde_output_order_v1_requests[] = { { "destroy", "", kde_output_order_v1_types + 0 }, }; static const struct wl_message kde_output_order_v1_events[] = { { "output", "s", kde_output_order_v1_types + 0 }, { "done", "", kde_output_order_v1_types + 0 }, }; WL_EXPORT const struct wl_interface kde_output_order_v1_interface = { "kde_output_order_v1", 1, 1, kde_output_order_v1_requests, 2, kde_output_order_v1_events, }; #endif ================================================ FILE: src/detection/displayserver/linux/wayland/kde-output.c ================================================ #ifdef FF_HAVE_WAYLAND #include "wayland.h" #include "kde-output-device-v2-client-protocol.h" #include "kde-output-order-v1-client-protocol.h" #include "common/edidHelper.h" #include "common/base64.h" typedef struct WaylandKdeMode { int32_t width; int32_t height; int32_t refreshRate; bool preferred; struct kde_output_device_mode_v2* pMode; } WaylandKdeMode; static void waylandKdeModeSizeListener(void* data, FF_MAYBE_UNUSED struct kde_output_device_mode_v2 *_, int32_t width, int32_t height) { WaylandKdeMode* mode = (WaylandKdeMode*) data; mode->width = width; mode->height = height; } static void waylandKdeModeRefreshListener(void* data, FF_MAYBE_UNUSED struct kde_output_device_mode_v2 *_, int32_t rate) { WaylandKdeMode* mode = (WaylandKdeMode*) data; mode->refreshRate = rate; } static void waylandKdeModePreferredListener(void* data, FF_MAYBE_UNUSED struct kde_output_device_mode_v2 *_) { WaylandKdeMode* mode = (WaylandKdeMode*) data; mode->preferred = true; } static const struct kde_output_device_mode_v2_listener modeListener = { .size = waylandKdeModeSizeListener, .refresh = waylandKdeModeRefreshListener, .preferred = waylandKdeModePreferredListener, .removed = (void*) stubListener, .flags = (void*) stubListener, }; static void waylandKdeModeListener(void* data, FF_MAYBE_UNUSED struct kde_output_device_v2* _, struct kde_output_device_mode_v2 *mode) { WaylandDisplay* wldata = (WaylandDisplay*) data; if (!wldata->internal) return; WaylandKdeMode* newMode = ffListAdd((FFlist*) wldata->internal); *newMode = (WaylandKdeMode) { .pMode = mode }; // Strangely, the listener is called only in this function, but not in `waylandKdeCurrentModeListener` wldata->parent->ffwl_proxy_add_listener((struct wl_proxy *) mode, (void (**)(void)) &modeListener, newMode); } static void waylandKdeCurrentModeListener(void* data, FF_MAYBE_UNUSED struct kde_output_device_v2 *_, struct kde_output_device_mode_v2 *mode) { // waylandKdeModeListener is always run before this WaylandDisplay* wldata = (WaylandDisplay*) data; if (!wldata->internal) return; int set = 0; FF_LIST_FOR_EACH(WaylandKdeMode, m, *(FFlist*) wldata->internal) { if (m->pMode == mode) { wldata->width = m->width; wldata->height = m->height; wldata->refreshRate = m->refreshRate; if (++set == 2) break; } if (m->preferred) { wldata->preferredWidth = m->width; wldata->preferredHeight = m->height; wldata->preferredRefreshRate = m->refreshRate; if (++set == 2) break; } } } static void waylandKdeScaleListener(void* data, FF_MAYBE_UNUSED struct kde_output_device_v2* _, wl_fixed_t scale) { WaylandDisplay* wldata = (WaylandDisplay*) data; wldata->dpi = (uint32_t) scale * 3 / 8; // wl_fixed_to_double(scale) * 96; } static void waylandKdeEdidListener(void* data, FF_MAYBE_UNUSED struct kde_output_device_v2* _, const char* raw) { if (!*raw) return; WaylandDisplay* wldata = (WaylandDisplay*) data; FF_STRBUF_AUTO_DESTROY b64 = ffStrbufCreateStatic(raw); FF_STRBUF_AUTO_DESTROY edid = ffBase64DecodeStrbuf(&b64); if (edid.length < 128) return; ffEdidGetName((const uint8_t*) edid.chars, &wldata->edidName); wldata->hdrSupported = ffEdidGetHdrCompatible((const uint8_t*) edid.chars, edid.length); ffEdidGetSerialAndManufactureDate((const uint8_t*) edid.chars, &wldata->serial, &wldata->myear, &wldata->mweek); wldata->hdrInfoAvailable = true; } static void waylandKdeEnabledListener(void* data, FF_MAYBE_UNUSED struct kde_output_device_v2* _, int32_t enabled) { WaylandDisplay* wldata = (WaylandDisplay*) data; if (!enabled) wldata->internal = NULL; } static void waylandKdeGeometryListener(void *data, FF_MAYBE_UNUSED struct kde_output_device_v2 *kde_output_device_v2, FF_MAYBE_UNUSED int32_t x, FF_MAYBE_UNUSED int32_t y, int32_t physical_width, int32_t physical_height, FF_MAYBE_UNUSED int32_t subpixel, FF_MAYBE_UNUSED const char *make, FF_MAYBE_UNUSED const char *model, int32_t transform) { WaylandDisplay* display = data; display->physicalWidth = physical_width; display->physicalHeight = physical_height; display->transform = (enum wl_output_transform) transform; } static void waylandKdeNameListener(void* data, FF_MAYBE_UNUSED struct kde_output_device_v2* kde_output_device_v2, const char *name) { WaylandDisplay* display = data; display->type = ffdsGetDisplayType(name); // As display->id is used as an internal identifier, we don't need it to be NUL terminated strncpy((char*) &display->id, name, sizeof(display->id)); ffStrbufAppendS(&display->name, name); } static void waylandKdeHdrListener(void *data, FF_MAYBE_UNUSED struct kde_output_device_v2 *kde_output_device_v2, uint32_t hdr_enabled) { WaylandDisplay* display = data; display->hdrEnabled = !!hdr_enabled; } static void waylandKdeMaxBitsPerColorListener(void *data, FF_MAYBE_UNUSED struct kde_output_device_v2 *kde_output_device_v2, uint32_t max_bpc) { WaylandDisplay* display = data; display->bitDepth = (uint8_t) max_bpc; } static struct kde_output_device_v2_listener outputListener = { .geometry = waylandKdeGeometryListener, .current_mode = waylandKdeCurrentModeListener, .mode = waylandKdeModeListener, .done = (void*) stubListener, .scale = waylandKdeScaleListener, .edid = waylandKdeEdidListener, .enabled = waylandKdeEnabledListener, .uuid = (void*) stubListener, .serial_number = (void*) stubListener, .eisa_id = (void*) stubListener, .capabilities = (void*) stubListener, .overscan = (void*) stubListener, .vrr_policy = (void*) stubListener, .rgb_range = (void*) stubListener, .name = waylandKdeNameListener, .high_dynamic_range = waylandKdeHdrListener, .sdr_brightness = (void*) stubListener, .wide_color_gamut = (void*) stubListener, .auto_rotate_policy = (void*) stubListener, .icc_profile_path = (void*) stubListener, .brightness_metadata = (void*) stubListener, .brightness_overrides = (void*) stubListener, .sdr_gamut_wideness = (void*) stubListener, .color_profile_source = (void*) stubListener, .brightness = (void*) stubListener, .color_power_tradeoff = (void*) stubListener, .dimming = (void*) stubListener, .replication_source = (void*) stubListener, .ddc_ci_allowed = (void*) stubListener, .max_bits_per_color = (void*) waylandKdeMaxBitsPerColorListener, .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, }; 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); if(output == NULL) return "Failed to create kde_output_device_v2"; FF_LIST_AUTO_DESTROY modes = ffListCreate(sizeof(WaylandKdeMode)); WaylandDisplay display = { .parent = wldata, .transform = WL_OUTPUT_TRANSFORM_NORMAL, .type = FF_DISPLAY_TYPE_UNKNOWN, .name = ffStrbufCreate(), .description = ffStrbufCreate(), .edidName = ffStrbufCreate(), .internal = &modes, }; if (wldata->ffwl_proxy_add_listener(output, (void(**)(void)) &outputListener, &display) < 0) { wldata->ffwl_proxy_destroy(output); return "Failed to add listener to kde_output_device_v2"; } if (wldata->ffwl_display_roundtrip(wldata->display) < 0) { wldata->ffwl_proxy_destroy(output); return "Failed to roundtrip kde_output_device_v2"; } wldata->ffwl_proxy_destroy(output); if(display.width <= 0 || display.height <= 0 || !display.internal) return "Failed to get display information from kde_output_device_v2"; uint32_t rotation = ffWaylandHandleRotation(&display); FFDisplayResult* item = ffdsAppendDisplay(wldata->result, (uint32_t) display.width, (uint32_t) display.height, display.refreshRate / 1000.0, display.dpi, (uint32_t) display.preferredWidth, (uint32_t) display.preferredHeight, display.preferredRefreshRate / 1000.0, rotation, display.edidName.length ? &display.edidName : &display.name, display.type, false, display.id, (uint32_t) display.physicalWidth, (uint32_t) display.physicalHeight, "wayland-kde" ); if (item) { if (display.hdrEnabled) item->hdrStatus = FF_DISPLAY_HDR_STATUS_ENABLED; else if (display.hdrSupported) item->hdrStatus = FF_DISPLAY_HDR_STATUS_SUPPORTED; else if (display.hdrInfoAvailable) item->hdrStatus = FF_DISPLAY_HDR_STATUS_UNSUPPORTED; else item->hdrStatus = FF_DISPLAY_HDR_STATUS_UNKNOWN; item->manufactureYear = display.myear; item->manufactureWeek = display.mweek; item->serial = display.serial; item->bitDepth = display.bitDepth; } ffStrbufDestroy(&display.description); ffStrbufDestroy(&display.name); ffStrbufDestroy(&display.edidName); return NULL; } static void waylandKdeOutputOrderListener(void *data, FF_MAYBE_UNUSED struct kde_output_order_v1 *_, const char *output_name) { uint64_t* id = (uint64_t*) data; if (*id == 0) *id = ffWaylandGenerateIdFromName(output_name); } 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); 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"; } if (wldata->ffwl_display_roundtrip(wldata->display) < 0) { wldata->ffwl_proxy_destroy(output); return "Failed to roundtrip kde_output_order_v1"; } wldata->ffwl_proxy_destroy(output); return NULL; } #endif ================================================ FILE: src/detection/displayserver/linux/wayland/wayland.c ================================================ #include "../displayserver_linux.h" #include "common/io.h" #include "common/edidHelper.h" #include "common/stringUtils.h" #include #include #ifdef FF_HAVE_WAYLAND #include #include "common/properties.h" #include "wayland.h" #include "wlr-output-management-unstable-v1-client-protocol.h" #include "kde-output-device-v2-client-protocol.h" #include "kde-output-order-v1-client-protocol.h" #include "xdg-output-unstable-v1-client-protocol.h" #if __FreeBSD__ #include #include #include #endif static bool waylandDetectWM(int fd, FFDisplayServerResult* result) { #if __linux__ || __GNU__ || (__FreeBSD__ && !__DragonFly__) #if __linux__ || __GNU__ struct ucred ucred = {}; socklen_t len = sizeof(ucred); if (getsockopt(fd, SOL_SOCKET, SO_PEERCRED, &ucred, &len) == -1 || ucred.pid <= 0) return false; FF_STRBUF_AUTO_DESTROY procPath = ffStrbufCreate(); ffStrbufAppendF(&procPath, "/proc/%d/cmdline", ucred.pid); //We check the cmdline for the process name, because it is not trimmed. if (!ffReadFileBuffer(procPath.chars, &result->wmProcessName)) return false; #else struct xucred ucred = {}; socklen_t len = sizeof(ucred); if (getsockopt(fd, AF_UNSPEC, LOCAL_PEERCRED, &ucred, &len) == -1 || ucred.cr_pid <= 0) return false; size_t size = 4096; ffStrbufEnsureFixedLengthFree(&result->wmProcessName, (uint32_t) size); if(sysctl((int[]){CTL_KERN, KERN_PROC, KERN_PROC_ARGS, ucred.cr_pid}, 4, result->wmProcessName.chars, &size, NULL, 0 ) != 0) return false; result->wmProcessName.length = (uint32_t) size - 1; #endif // #1135: wl-restart is a special case const char* filename = strrchr(result->wmProcessName.chars, '/'); if (filename) filename++; else filename = result->wmProcessName.chars; if (ffStrEquals(filename, "wl-restart")) ffStrbufSubstrAfterLastC(&result->wmProcessName, '\0'); ffStrbufSubstrBeforeFirstC(&result->wmProcessName, '\0'); //Trim the arguments ffStrbufSubstrAfterLastC(&result->wmProcessName, '/'); //Trim the path return true; #else FF_UNUSED(fd, result); return false; #endif } static void waylandGlobalAddListener(void* data, struct wl_registry* registry, uint32_t name, const char* interface, uint32_t version) { WaylandData* wldata = data; if((wldata->protocolType == FF_WAYLAND_PROTOCOL_TYPE_NONE || wldata->protocolType == FF_WAYLAND_PROTOCOL_TYPE_GLOBAL) && ffStrEquals(interface, wldata->ffwl_output_interface->name)) { wldata->protocolType = FF_WAYLAND_PROTOCOL_TYPE_GLOBAL; if (ffWaylandHandleGlobalOutput(wldata, registry, name, version) != NULL) wldata->protocolType = FF_WAYLAND_PROTOCOL_TYPE_NONE; } else if((wldata->protocolType == FF_WAYLAND_PROTOCOL_TYPE_NONE || wldata->protocolType == FF_WAYLAND_PROTOCOL_TYPE_ZWLR) && ffStrEquals(interface, zwlr_output_manager_v1_interface.name)) { wldata->protocolType = FF_WAYLAND_PROTOCOL_TYPE_ZWLR; if (ffWaylandHandleZwlrOutput(wldata, registry, name, version) != NULL) wldata->protocolType = FF_WAYLAND_PROTOCOL_TYPE_NONE; } else if((wldata->protocolType == FF_WAYLAND_PROTOCOL_TYPE_NONE || wldata->protocolType == FF_WAYLAND_PROTOCOL_TYPE_KDE) && ffStrEquals(interface, kde_output_device_v2_interface.name)) { wldata->protocolType = FF_WAYLAND_PROTOCOL_TYPE_KDE; if (ffWaylandHandleKdeOutput(wldata, registry, name, version) != NULL) wldata->protocolType = FF_WAYLAND_PROTOCOL_TYPE_NONE; } else if(ffStrEquals(interface, kde_output_order_v1_interface.name)) { ffWaylandHandleKdeOutputOrder(wldata, registry, name, version); } else if((wldata->protocolType == FF_WAYLAND_PROTOCOL_TYPE_GLOBAL || wldata->protocolType == FF_WAYLAND_PROTOCOL_TYPE_NONE) && ffStrEquals(interface, zxdg_output_manager_v1_interface.name)) { ffWaylandHandleZxdgOutput(wldata, registry, name, version); } } static FF_MAYBE_UNUSED bool matchDrmConnector(const char* connName, WaylandDisplay* wldata) { // https://wayland.freedesktop.org/docs/html/apa.html#protocol-spec-wl_output-event-name // The doc says that "do not assume that the name is a reflection of an underlying DRM connector, X11 connection, etc." // However I can't find a better method to get the edid data const char* drmDirPath = "/sys/class/drm/"; FF_AUTO_CLOSE_DIR DIR* dirp = opendir(drmDirPath); if(dirp == NULL) return false; struct dirent* entry; while((entry = readdir(dirp)) != NULL) { const char* plainName = entry->d_name; if (ffStrStartsWith(plainName, "card")) { const char* tmp = strchr(plainName + strlen("card"), '-'); if (tmp) plainName = tmp + 1; } if (ffStrEquals(plainName, connName)) { FF_STRBUF_AUTO_DESTROY path = ffStrbufCreateF("%s%s/edid", drmDirPath, entry->d_name); uint8_t edidData[512]; ssize_t edidLength = ffReadFileData(path.chars, ARRAY_SIZE(edidData), edidData); if (edidLength > 0 && edidLength % 128 == 0) { ffEdidGetName(edidData, &wldata->edidName); ffEdidGetHdrCompatible(edidData, (uint32_t) edidLength); ffEdidGetSerialAndManufactureDate(edidData, &wldata->serial, &wldata->myear, &wldata->mweek); wldata->hdrInfoAvailable = true; return true; } break; } } return false; } void ffWaylandOutputNameListener(void* data, FF_MAYBE_UNUSED void* output, const char *name) { WaylandDisplay* display = data; if (display->id) return; display->type = ffdsGetDisplayType(name); #if __linux__ if (!display->edidName.length) matchDrmConnector(name, display); #endif display->id = ffWaylandGenerateIdFromName(name); ffStrbufAppendS(&display->name, name); } void ffWaylandOutputDescriptionListener(void* data, FF_MAYBE_UNUSED void* output, const char* description) { WaylandDisplay* display = data; if (display->description.length) return; while (*description == ' ') ++description; if (!ffStrEquals(description, "Unknown Display") && !ffStrContains(description, "(null)")) ffStrbufAppendS(&display->description, description); } uint32_t ffWaylandHandleRotation(WaylandDisplay* display) { uint32_t rotation; switch(display->transform) { case WL_OUTPUT_TRANSFORM_FLIPPED_90: case WL_OUTPUT_TRANSFORM_90: rotation = 90; break; case WL_OUTPUT_TRANSFORM_FLIPPED_180: case WL_OUTPUT_TRANSFORM_180: rotation = 180; break; case WL_OUTPUT_TRANSFORM_FLIPPED_270: case WL_OUTPUT_TRANSFORM_270: rotation = 270; break; default: rotation = 0; break; } switch(rotation) { case 90: case 270: { int32_t temp = display->width; display->width = display->height; display->height = temp; temp = display->physicalWidth; display->physicalWidth = display->physicalHeight; display->physicalHeight = temp; break; } default: break; } return rotation; } const char* ffdsConnectWayland(FFDisplayServerResult* result) { if (getenv("XDG_RUNTIME_DIR") == NULL) return "Wayland requires $XDG_RUNTIME_DIR being set"; FF_LIBRARY_LOAD_MESSAGE(wayland, "libwayland-client" FF_LIBRARY_EXTENSION, 1) FF_LIBRARY_LOAD_SYMBOL_MESSAGE(wayland, wl_display_connect) FF_LIBRARY_LOAD_SYMBOL_MESSAGE(wayland, wl_display_get_fd) FF_LIBRARY_LOAD_SYMBOL_MESSAGE(wayland, wl_proxy_marshal_constructor) FF_LIBRARY_LOAD_SYMBOL_MESSAGE(wayland, wl_display_disconnect) FF_LIBRARY_LOAD_SYMBOL_MESSAGE(wayland, wl_registry_interface) WaylandData data = {}; FF_LIBRARY_LOAD_SYMBOL_VAR_MESSAGE(wayland, data, wl_proxy_marshal_constructor_versioned) FF_LIBRARY_LOAD_SYMBOL_VAR_MESSAGE(wayland, data, wl_proxy_add_listener) FF_LIBRARY_LOAD_SYMBOL_VAR_MESSAGE(wayland, data, wl_proxy_destroy) FF_LIBRARY_LOAD_SYMBOL_VAR_MESSAGE(wayland, data, wl_display_roundtrip) FF_LIBRARY_LOAD_SYMBOL_VAR_MESSAGE(wayland, data, wl_output_interface) data.display = ffwl_display_connect(NULL); if(data.display == NULL) return "wl_display_connect returned NULL"; waylandDetectWM(ffwl_display_get_fd(data.display), result); struct wl_proxy* registry = ffwl_proxy_marshal_constructor((struct wl_proxy*) data.display, WL_DISPLAY_GET_REGISTRY, ffwl_registry_interface, NULL); if(registry == NULL) { ffwl_display_disconnect(data.display); return "wl_display_get_registry returned NULL"; } data.result = result; struct wl_registry_listener registry_listener = { .global = waylandGlobalAddListener, .global_remove = (void*) stubListener }; data.ffwl_proxy_add_listener(registry, (void(**)(void)) ®istry_listener, &data); data.ffwl_display_roundtrip(data.display); if (data.zxdgOutputManager) data.ffwl_proxy_destroy(data.zxdgOutputManager); data.ffwl_proxy_destroy(registry); ffwl_display_disconnect(data.display); if(data.primaryDisplayId == 0 && result->wmProcessName.length > 0) { const char* fileName = ffStrbufEqualS(&result->wmProcessName, "gnome-shell") ? "monitors.xml" : ffStrbufEqualS(&result->wmProcessName, "cinnamon") ? "cinnamon-monitors.xml" : NULL; if (fileName) { FF_STRBUF_AUTO_DESTROY monitorsXml = ffStrbufCreate(); FF_LIST_FOR_EACH(FFstrbuf, basePath, instance.state.platform.configDirs) { char path[1024]; snprintf(path, ARRAY_SIZE(path), "%s%s", basePath->chars, fileName); if (ffReadFileBuffer(path, &monitorsXml)) break; } if (monitorsXml.length) { // // // // 0 // 0 // 1.7489879131317139 // yes // // // Virtual-1 // unknown // unknown // unknown // // // 3456 // 2160 // 60.000068664550781 // // // // // uint32_t start = ffStrbufFirstIndexS(&monitorsXml, "yes"); if (start < monitorsXml.length) { start = ffStrbufNextIndexS(&monitorsXml, start, ""); if (start < monitorsXml.length) { uint32_t end = ffStrbufNextIndexS(&monitorsXml, start, ""); if (end < monitorsXml.length) { ffStrbufSubstrBefore(&monitorsXml, end); const char* name = monitorsXml.chars + start + strlen(""); data.primaryDisplayId = ffWaylandGenerateIdFromName(name); } } } } } } if(data.primaryDisplayId) { FF_LIST_FOR_EACH(FFDisplayResult, d, data.result->displays) { if(d->id == data.primaryDisplayId) { d->primary = true; break; } } } //We successfully connected to wayland and detected the display. //So we can set set the session type to wayland. //This is used as an indicator that we are running wayland by the x11 backends. ffStrbufSetStatic(&result->wmProtocolName, FF_WM_PROTOCOL_WAYLAND); return NULL; } #else const char* ffdsConnectWayland(FF_MAYBE_UNUSED FFDisplayServerResult* result) { return "Fastfetch was compiled without Wayland support"; } #endif ================================================ FILE: src/detection/displayserver/linux/wayland/wayland.h ================================================ #pragma once #ifdef FF_HAVE_WAYLAND #include "common/library.h" #include "common/stringUtils.h" #include #include "../displayserver_linux.h" typedef enum __attribute__((__packed__)) WaylandProtocolType { FF_WAYLAND_PROTOCOL_TYPE_NONE, FF_WAYLAND_PROTOCOL_TYPE_GLOBAL, FF_WAYLAND_PROTOCOL_TYPE_ZWLR, FF_WAYLAND_PROTOCOL_TYPE_KDE, } WaylandProtocolType; typedef struct WaylandData { FFDisplayServerResult* result; FF_LIBRARY_SYMBOL(wl_proxy_marshal_constructor_versioned) FF_LIBRARY_SYMBOL(wl_proxy_add_listener) FF_LIBRARY_SYMBOL(wl_proxy_destroy) FF_LIBRARY_SYMBOL(wl_display_roundtrip) struct wl_display* display; const struct wl_interface* ffwl_output_interface; WaylandProtocolType protocolType; uint64_t primaryDisplayId; struct wl_proxy* zxdgOutputManager; } WaylandData; typedef struct WaylandDisplay { WaylandData* parent; void* internal; int32_t width; int32_t height; int32_t refreshRate; int32_t preferredWidth; int32_t preferredHeight; int32_t preferredRefreshRate; int32_t physicalWidth; int32_t physicalHeight; uint32_t dpi; enum wl_output_transform transform; FFDisplayType type; FFstrbuf name; FFstrbuf description; FFstrbuf edidName; uint64_t id; bool hdrInfoAvailable; bool hdrSupported; bool hdrEnabled; uint16_t myear; uint16_t mweek; uint32_t serial; uint8_t bitDepth; } WaylandDisplay; inline static void stubListener(void* data, ...) { (void) data; } inline static uint64_t ffWaylandGenerateIdFromName(const char* name) { uint64_t id = 0; size_t len = strlen(name); if (len > sizeof(id)) memcpy(&id, name + (len - sizeof(id)), sizeof(id)); // copy the last 8 bytes else if (len > 0) memcpy(&id, name, len); return id; } void ffWaylandOutputNameListener(void* data, FF_MAYBE_UNUSED void* output, const char *name); void ffWaylandOutputDescriptionListener(void* data, FF_MAYBE_UNUSED void* output, const char* description); // Modifies content of display. Don't call this function when calling ffdsAppendDisplay uint32_t ffWaylandHandleRotation(WaylandDisplay* display); const char* ffWaylandHandleGlobalOutput(WaylandData* wldata, struct wl_registry* registry, uint32_t name, uint32_t version); const char* ffWaylandHandleZwlrOutput(WaylandData* wldata, struct wl_registry* registry, uint32_t name, uint32_t version); const char* ffWaylandHandleKdeOutput(WaylandData* wldata, struct wl_registry* registry, uint32_t name, uint32_t version); const char* ffWaylandHandleKdeOutputOrder(WaylandData* wldata, struct wl_registry* registry, uint32_t name, uint32_t version); const char* ffWaylandHandleZxdgOutput(WaylandData* wldata, struct wl_registry* registry, uint32_t name, uint32_t version); #endif ================================================ FILE: src/detection/displayserver/linux/wayland/wlr-output-management-unstable-v1-client-protocol.h ================================================ /* Generated by wayland-scanner 1.24.0 */ #ifndef WLR_OUTPUT_MANAGEMENT_UNSTABLE_V1_CLIENT_PROTOCOL_H #define WLR_OUTPUT_MANAGEMENT_UNSTABLE_V1_CLIENT_PROTOCOL_H #include #include #include #ifdef __cplusplus extern "C" { #endif /** * @page page_wlr_output_management_unstable_v1 The wlr_output_management_unstable_v1 protocol * protocol to configure output devices * * @section page_desc_wlr_output_management_unstable_v1 Description * * This protocol exposes interfaces to obtain and modify output device * configuration. * * Warning! The protocol described in this file is experimental and * backward incompatible changes may be made. Backward compatible changes * may be added together with the corresponding interface version bump. * Backward incompatible changes are done by bumping the version number in * the protocol and interface names and resetting the interface version. * Once the protocol is to be declared stable, the 'z' prefix and the * version number in the protocol and interface names are removed and the * interface version number is reset. * * @section page_ifaces_wlr_output_management_unstable_v1 Interfaces * - @subpage page_iface_zwlr_output_manager_v1 - output device configuration manager * - @subpage page_iface_zwlr_output_head_v1 - output device * - @subpage page_iface_zwlr_output_mode_v1 - output mode * - @subpage page_iface_zwlr_output_configuration_v1 - output configuration * - @subpage page_iface_zwlr_output_configuration_head_v1 - head configuration * @section page_copyright_wlr_output_management_unstable_v1 Copyright *
 *
 * Copyright © 2019 Purism SPC
 *
 * Permission to use, copy, modify, distribute, and sell this
 * software and its documentation for any purpose is hereby granted
 * without fee, provided that the above copyright notice appear in
 * all copies and that both that copyright notice and this permission
 * notice appear in supporting documentation, and that the name of
 * the copyright holders not be used in advertising or publicity
 * pertaining to distribution of the software without specific,
 * written prior permission.  The copyright holders make no
 * representations about the suitability of this software for any
 * purpose.  It is provided "as is" without express or implied
 * warranty.
 *
 * THE COPYRIGHT HOLDERS DISCLAIM ALL WARRANTIES WITH REGARD TO THIS
 * SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND
 * FITNESS, IN NO EVENT SHALL THE COPYRIGHT HOLDERS BE LIABLE FOR ANY
 * SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN
 * AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION,
 * ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF
 * THIS SOFTWARE.
 * 
*/ struct zwlr_output_configuration_head_v1; struct zwlr_output_configuration_v1; struct zwlr_output_head_v1; struct zwlr_output_manager_v1; struct zwlr_output_mode_v1; #ifndef ZWLR_OUTPUT_MANAGER_V1_INTERFACE #define ZWLR_OUTPUT_MANAGER_V1_INTERFACE /** * @page page_iface_zwlr_output_manager_v1 zwlr_output_manager_v1 * @section page_iface_zwlr_output_manager_v1_desc Description * * This interface is a manager that allows reading and writing the current * output device configuration. * * Output devices that display pixels (e.g. a physical monitor or a virtual * output in a window) are represented as heads. Heads cannot be created nor * destroyed by the client, but they can be enabled or disabled and their * properties can be changed. Each head may have one or more available modes. * * Whenever a head appears (e.g. a monitor is plugged in), it will be * advertised via the head event. Immediately after the output manager is * bound, all current heads are advertised. * * Whenever a head's properties change, the relevant wlr_output_head events * will be sent. Not all head properties will be sent: only properties that * have changed need to. * * Whenever a head disappears (e.g. a monitor is unplugged), a * wlr_output_head.finished event will be sent. * * After one or more heads appear, change or disappear, the done event will * be sent. It carries a serial which can be used in a create_configuration * request to update heads properties. * * The information obtained from this protocol should only be used for output * configuration purposes. This protocol is not designed to be a generic * output property advertisement protocol for regular clients. Instead, * protocols such as xdg-output should be used. * @section page_iface_zwlr_output_manager_v1_api API * See @ref iface_zwlr_output_manager_v1. */ /** * @defgroup iface_zwlr_output_manager_v1 The zwlr_output_manager_v1 interface * * This interface is a manager that allows reading and writing the current * output device configuration. * * Output devices that display pixels (e.g. a physical monitor or a virtual * output in a window) are represented as heads. Heads cannot be created nor * destroyed by the client, but they can be enabled or disabled and their * properties can be changed. Each head may have one or more available modes. * * Whenever a head appears (e.g. a monitor is plugged in), it will be * advertised via the head event. Immediately after the output manager is * bound, all current heads are advertised. * * Whenever a head's properties change, the relevant wlr_output_head events * will be sent. Not all head properties will be sent: only properties that * have changed need to. * * Whenever a head disappears (e.g. a monitor is unplugged), a * wlr_output_head.finished event will be sent. * * After one or more heads appear, change or disappear, the done event will * be sent. It carries a serial which can be used in a create_configuration * request to update heads properties. * * The information obtained from this protocol should only be used for output * configuration purposes. This protocol is not designed to be a generic * output property advertisement protocol for regular clients. Instead, * protocols such as xdg-output should be used. */ extern const struct wl_interface zwlr_output_manager_v1_interface; #endif #ifndef ZWLR_OUTPUT_HEAD_V1_INTERFACE #define ZWLR_OUTPUT_HEAD_V1_INTERFACE /** * @page page_iface_zwlr_output_head_v1 zwlr_output_head_v1 * @section page_iface_zwlr_output_head_v1_desc Description * * A head is an output device. The difference between a wl_output object and * a head is that heads are advertised even if they are turned off. A head * object only advertises properties and cannot be used directly to change * them. * * A head has some read-only properties: modes, name, description and * physical_size. These cannot be changed by clients. * * Other properties can be updated via a wlr_output_configuration object. * * Properties sent via this interface are applied atomically via the * wlr_output_manager.done event. No guarantees are made regarding the order * in which properties are sent. * @section page_iface_zwlr_output_head_v1_api API * See @ref iface_zwlr_output_head_v1. */ /** * @defgroup iface_zwlr_output_head_v1 The zwlr_output_head_v1 interface * * A head is an output device. The difference between a wl_output object and * a head is that heads are advertised even if they are turned off. A head * object only advertises properties and cannot be used directly to change * them. * * A head has some read-only properties: modes, name, description and * physical_size. These cannot be changed by clients. * * Other properties can be updated via a wlr_output_configuration object. * * Properties sent via this interface are applied atomically via the * wlr_output_manager.done event. No guarantees are made regarding the order * in which properties are sent. */ extern const struct wl_interface zwlr_output_head_v1_interface; #endif #ifndef ZWLR_OUTPUT_MODE_V1_INTERFACE #define ZWLR_OUTPUT_MODE_V1_INTERFACE /** * @page page_iface_zwlr_output_mode_v1 zwlr_output_mode_v1 * @section page_iface_zwlr_output_mode_v1_desc Description * * This object describes an output mode. * * Some heads don't support output modes, in which case modes won't be * advertised. * * Properties sent via this interface are applied atomically via the * wlr_output_manager.done event. No guarantees are made regarding the order * in which properties are sent. * @section page_iface_zwlr_output_mode_v1_api API * See @ref iface_zwlr_output_mode_v1. */ /** * @defgroup iface_zwlr_output_mode_v1 The zwlr_output_mode_v1 interface * * This object describes an output mode. * * Some heads don't support output modes, in which case modes won't be * advertised. * * Properties sent via this interface are applied atomically via the * wlr_output_manager.done event. No guarantees are made regarding the order * in which properties are sent. */ extern const struct wl_interface zwlr_output_mode_v1_interface; #endif #ifndef ZWLR_OUTPUT_CONFIGURATION_V1_INTERFACE #define ZWLR_OUTPUT_CONFIGURATION_V1_INTERFACE /** * @page page_iface_zwlr_output_configuration_v1 zwlr_output_configuration_v1 * @section page_iface_zwlr_output_configuration_v1_desc Description * * This object is used by the client to describe a full output configuration. * * First, the client needs to setup the output configuration. Each head can * be either enabled (and configured) or disabled. It is a protocol error to * send two enable_head or disable_head requests with the same head. It is a * protocol error to omit a head in a configuration. * * Then, the client can apply or test the configuration. The compositor will * then reply with a succeeded, failed or cancelled event. Finally the client * should destroy the configuration object. * @section page_iface_zwlr_output_configuration_v1_api API * See @ref iface_zwlr_output_configuration_v1. */ /** * @defgroup iface_zwlr_output_configuration_v1 The zwlr_output_configuration_v1 interface * * This object is used by the client to describe a full output configuration. * * First, the client needs to setup the output configuration. Each head can * be either enabled (and configured) or disabled. It is a protocol error to * send two enable_head or disable_head requests with the same head. It is a * protocol error to omit a head in a configuration. * * Then, the client can apply or test the configuration. The compositor will * then reply with a succeeded, failed or cancelled event. Finally the client * should destroy the configuration object. */ extern const struct wl_interface zwlr_output_configuration_v1_interface; #endif #ifndef ZWLR_OUTPUT_CONFIGURATION_HEAD_V1_INTERFACE #define ZWLR_OUTPUT_CONFIGURATION_HEAD_V1_INTERFACE /** * @page page_iface_zwlr_output_configuration_head_v1 zwlr_output_configuration_head_v1 * @section page_iface_zwlr_output_configuration_head_v1_desc Description * * This object is used by the client to update a single head's configuration. * * It is a protocol error to set the same property twice. * @section page_iface_zwlr_output_configuration_head_v1_api API * See @ref iface_zwlr_output_configuration_head_v1. */ /** * @defgroup iface_zwlr_output_configuration_head_v1 The zwlr_output_configuration_head_v1 interface * * This object is used by the client to update a single head's configuration. * * It is a protocol error to set the same property twice. */ extern const struct wl_interface zwlr_output_configuration_head_v1_interface; #endif /** * @ingroup iface_zwlr_output_manager_v1 * @struct zwlr_output_manager_v1_listener */ struct zwlr_output_manager_v1_listener { /** * introduce a new head * * This event introduces a new head. This happens whenever a new * head appears (e.g. a monitor is plugged in) or after the output * manager is bound. */ void (*head)(void *data, struct zwlr_output_manager_v1 *zwlr_output_manager_v1, struct zwlr_output_head_v1 *head); /** * sent all information about current configuration * * This event is sent after all information has been sent after * binding to the output manager object and after any subsequent * changes. This applies to child head and mode objects as well. In * other words, this event is sent whenever a head or mode is * created or destroyed and whenever one of their properties has * been changed. Not all state is re-sent each time the current * configuration changes: only the actual changes are sent. * * This allows changes to the output configuration to be seen as * atomic, even if they happen via multiple events. * * A serial is sent to be used in a future create_configuration * request. * @param serial current configuration serial */ void (*done)(void *data, struct zwlr_output_manager_v1 *zwlr_output_manager_v1, uint32_t serial); /** * the compositor has finished with the manager * * This event indicates that the compositor is done sending * manager events. The compositor will destroy the object * immediately after sending this event, so it will become invalid * and the client should release any resources associated with it. */ void (*finished)(void *data, struct zwlr_output_manager_v1 *zwlr_output_manager_v1); }; /** * @ingroup iface_zwlr_output_manager_v1 */ static inline int zwlr_output_manager_v1_add_listener(struct zwlr_output_manager_v1 *zwlr_output_manager_v1, const struct zwlr_output_manager_v1_listener *listener, void *data) { return wl_proxy_add_listener((struct wl_proxy *) zwlr_output_manager_v1, (void (**)(void)) listener, data); } #define ZWLR_OUTPUT_MANAGER_V1_CREATE_CONFIGURATION 0 #define ZWLR_OUTPUT_MANAGER_V1_STOP 1 /** * @ingroup iface_zwlr_output_manager_v1 */ #define ZWLR_OUTPUT_MANAGER_V1_HEAD_SINCE_VERSION 1 /** * @ingroup iface_zwlr_output_manager_v1 */ #define ZWLR_OUTPUT_MANAGER_V1_DONE_SINCE_VERSION 1 /** * @ingroup iface_zwlr_output_manager_v1 */ #define ZWLR_OUTPUT_MANAGER_V1_FINISHED_SINCE_VERSION 1 /** * @ingroup iface_zwlr_output_manager_v1 */ #define ZWLR_OUTPUT_MANAGER_V1_CREATE_CONFIGURATION_SINCE_VERSION 1 /** * @ingroup iface_zwlr_output_manager_v1 */ #define ZWLR_OUTPUT_MANAGER_V1_STOP_SINCE_VERSION 1 /** @ingroup iface_zwlr_output_manager_v1 */ static inline void zwlr_output_manager_v1_set_user_data(struct zwlr_output_manager_v1 *zwlr_output_manager_v1, void *user_data) { wl_proxy_set_user_data((struct wl_proxy *) zwlr_output_manager_v1, user_data); } /** @ingroup iface_zwlr_output_manager_v1 */ static inline void * zwlr_output_manager_v1_get_user_data(struct zwlr_output_manager_v1 *zwlr_output_manager_v1) { return wl_proxy_get_user_data((struct wl_proxy *) zwlr_output_manager_v1); } static inline uint32_t zwlr_output_manager_v1_get_version(struct zwlr_output_manager_v1 *zwlr_output_manager_v1) { return wl_proxy_get_version((struct wl_proxy *) zwlr_output_manager_v1); } /** @ingroup iface_zwlr_output_manager_v1 */ static inline void zwlr_output_manager_v1_destroy(struct zwlr_output_manager_v1 *zwlr_output_manager_v1) { wl_proxy_destroy((struct wl_proxy *) zwlr_output_manager_v1); } // /** // * @ingroup iface_zwlr_output_manager_v1 // * // * Create a new output configuration object. This allows to update head // * properties. // */ // static inline struct zwlr_output_configuration_v1 * // zwlr_output_manager_v1_create_configuration(struct zwlr_output_manager_v1 *zwlr_output_manager_v1, uint32_t serial) // { // struct wl_proxy *id; // id = wl_proxy_marshal_flags((struct wl_proxy *) zwlr_output_manager_v1, // ZWLR_OUTPUT_MANAGER_V1_CREATE_CONFIGURATION, &zwlr_output_configuration_v1_interface, wl_proxy_get_version((struct wl_proxy *) zwlr_output_manager_v1), 0, NULL, serial); // return (struct zwlr_output_configuration_v1 *) id; // } // /** // * @ingroup iface_zwlr_output_manager_v1 // * // * Indicates the client no longer wishes to receive events for output // * configuration changes. However the compositor may emit further events, // * until the finished event is emitted. // * // * The client must not send any more requests after this one. // */ // static inline void // zwlr_output_manager_v1_stop(struct zwlr_output_manager_v1 *zwlr_output_manager_v1) // { // wl_proxy_marshal_flags((struct wl_proxy *) zwlr_output_manager_v1, // ZWLR_OUTPUT_MANAGER_V1_STOP, NULL, wl_proxy_get_version((struct wl_proxy *) zwlr_output_manager_v1), 0); // } #ifndef ZWLR_OUTPUT_HEAD_V1_ADAPTIVE_SYNC_STATE_ENUM #define ZWLR_OUTPUT_HEAD_V1_ADAPTIVE_SYNC_STATE_ENUM enum zwlr_output_head_v1_adaptive_sync_state { /** * adaptive sync is disabled */ ZWLR_OUTPUT_HEAD_V1_ADAPTIVE_SYNC_STATE_DISABLED = 0, /** * adaptive sync is enabled */ ZWLR_OUTPUT_HEAD_V1_ADAPTIVE_SYNC_STATE_ENABLED = 1, }; #endif /* ZWLR_OUTPUT_HEAD_V1_ADAPTIVE_SYNC_STATE_ENUM */ /** * @ingroup iface_zwlr_output_head_v1 * @struct zwlr_output_head_v1_listener */ struct zwlr_output_head_v1_listener { /** * head name * * This event describes the head name. * * The naming convention is compositor defined, but limited to * alphanumeric characters and dashes (-). Each name is unique * among all wlr_output_head objects, but if a wlr_output_head * object is destroyed the same name may be reused later. The names * will also remain consistent across sessions with the same * hardware and software configuration. * * Examples of names include 'HDMI-A-1', 'WL-1', 'X11-1', etc. * However, do not assume that the name is a reflection of an * underlying DRM connector, X11 connection, etc. * * If this head matches a wl_output, the wl_output.name event must * report the same name. * * The name event is sent after a wlr_output_head object is * created. This event is only sent once per object, and the name * does not change over the lifetime of the wlr_output_head object. */ void (*name)(void *data, struct zwlr_output_head_v1 *zwlr_output_head_v1, const char *name); /** * head description * * This event describes a human-readable description of the head. * * The description is a UTF-8 string with no convention defined for * its contents. Examples might include 'Foocorp 11" Display' or * 'Virtual X11 output via :1'. However, do not assume that the * name is a reflection of the make, model, serial of the * underlying DRM connector or the display name of the underlying * X11 connection, etc. * * If this head matches a wl_output, the wl_output.description * event must report the same name. * * The description event is sent after a wlr_output_head object is * created. This event is only sent once per object, and the * description does not change over the lifetime of the * wlr_output_head object. */ void (*description)(void *data, struct zwlr_output_head_v1 *zwlr_output_head_v1, const char *description); /** * head physical size * * This event describes the physical size of the head. This event * is only sent if the head has a physical size (e.g. is not a * projector or a virtual device). * * The physical size event is sent after a wlr_output_head object * is created. This event is only sent once per object, and the * physical size does not change over the lifetime of the * wlr_output_head object. * @param width width in millimeters of the output * @param height height in millimeters of the output */ void (*physical_size)(void *data, struct zwlr_output_head_v1 *zwlr_output_head_v1, int32_t width, int32_t height); /** * introduce a mode * * This event introduces a mode for this head. It is sent once * per supported mode. */ void (*mode)(void *data, struct zwlr_output_head_v1 *zwlr_output_head_v1, struct zwlr_output_mode_v1 *mode); /** * head is enabled or disabled * * This event describes whether the head is enabled. A disabled * head is not mapped to a region of the global compositor space. * * When a head is disabled, some properties (current_mode, * position, transform and scale) are irrelevant. * @param enabled zero if disabled, non-zero if enabled */ void (*enabled)(void *data, struct zwlr_output_head_v1 *zwlr_output_head_v1, int32_t enabled); /** * current mode * * This event describes the mode currently in use for this head. * It is only sent if the output is enabled. */ void (*current_mode)(void *data, struct zwlr_output_head_v1 *zwlr_output_head_v1, struct zwlr_output_mode_v1 *mode); /** * current position * * This events describes the position of the head in the global * compositor space. It is only sent if the output is enabled. * @param x x position within the global compositor space * @param y y position within the global compositor space */ void (*position)(void *data, struct zwlr_output_head_v1 *zwlr_output_head_v1, int32_t x, int32_t y); /** * current transformation * * This event describes the transformation currently applied to * the head. It is only sent if the output is enabled. */ void (*transform)(void *data, struct zwlr_output_head_v1 *zwlr_output_head_v1, int32_t transform); /** * current scale * * This events describes the scale of the head in the global * compositor space. It is only sent if the output is enabled. */ void (*scale)(void *data, struct zwlr_output_head_v1 *zwlr_output_head_v1, wl_fixed_t scale); /** * the head has disappeared * * This event indicates that the head is no longer available. The * head object becomes inert. Clients should send a destroy request * and release any resources associated with it. */ void (*finished)(void *data, struct zwlr_output_head_v1 *zwlr_output_head_v1); /** * head manufacturer * * This event describes the manufacturer of the head. * * Together with the model and serial_number events the purpose is * to allow clients to recognize heads from previous sessions and * for example load head-specific configurations back. * * It is not guaranteed this event will be ever sent. A reason for * that can be that the compositor does not have information about * the make of the head or the definition of a make is not sensible * in the current setup, for example in a virtual session. Clients * can still try to identify the head by available information from * other events but should be aware that there is an increased risk * of false positives. * * If sent, the make event is sent after a wlr_output_head object * is created and only sent once per object. The make does not * change over the lifetime of the wlr_output_head object. * * It is not recommended to display the make string in UI to users. * For that the string provided by the description event should be * preferred. * @since 2 */ void (*make)(void *data, struct zwlr_output_head_v1 *zwlr_output_head_v1, const char *make); /** * head model * * This event describes the model of the head. * * Together with the make and serial_number events the purpose is * to allow clients to recognize heads from previous sessions and * for example load head-specific configurations back. * * It is not guaranteed this event will be ever sent. A reason for * that can be that the compositor does not have information about * the model of the head or the definition of a model is not * sensible in the current setup, for example in a virtual session. * Clients can still try to identify the head by available * information from other events but should be aware that there is * an increased risk of false positives. * * If sent, the model event is sent after a wlr_output_head object * is created and only sent once per object. The model does not * change over the lifetime of the wlr_output_head object. * * It is not recommended to display the model string in UI to * users. For that the string provided by the description event * should be preferred. * @since 2 */ void (*model)(void *data, struct zwlr_output_head_v1 *zwlr_output_head_v1, const char *model); /** * head serial number * * This event describes the serial number of the head. * * Together with the make and model events the purpose is to allow * clients to recognize heads from previous sessions and for * example load head- specific configurations back. * * It is not guaranteed this event will be ever sent. A reason for * that can be that the compositor does not have information about * the serial number of the head or the definition of a serial * number is not sensible in the current setup. Clients can still * try to identify the head by available information from other * events but should be aware that there is an increased risk of * false positives. * * If sent, the serial number event is sent after a wlr_output_head * object is created and only sent once per object. The serial * number does not change over the lifetime of the wlr_output_head * object. * * It is not recommended to display the serial_number string in UI * to users. For that the string provided by the description event * should be preferred. * @since 2 */ void (*serial_number)(void *data, struct zwlr_output_head_v1 *zwlr_output_head_v1, const char *serial_number); /** * current adaptive sync state * * This event describes whether adaptive sync is currently * enabled for the head or not. Adaptive sync is also known as * Variable Refresh Rate or VRR. * @since 4 */ void (*adaptive_sync)(void *data, struct zwlr_output_head_v1 *zwlr_output_head_v1, uint32_t state); }; /** * @ingroup iface_zwlr_output_head_v1 */ static inline int zwlr_output_head_v1_add_listener(struct zwlr_output_head_v1 *zwlr_output_head_v1, const struct zwlr_output_head_v1_listener *listener, void *data) { return wl_proxy_add_listener((struct wl_proxy *) zwlr_output_head_v1, (void (**)(void)) listener, data); } #define ZWLR_OUTPUT_HEAD_V1_RELEASE 0 /** * @ingroup iface_zwlr_output_head_v1 */ #define ZWLR_OUTPUT_HEAD_V1_NAME_SINCE_VERSION 1 /** * @ingroup iface_zwlr_output_head_v1 */ #define ZWLR_OUTPUT_HEAD_V1_DESCRIPTION_SINCE_VERSION 1 /** * @ingroup iface_zwlr_output_head_v1 */ #define ZWLR_OUTPUT_HEAD_V1_PHYSICAL_SIZE_SINCE_VERSION 1 /** * @ingroup iface_zwlr_output_head_v1 */ #define ZWLR_OUTPUT_HEAD_V1_MODE_SINCE_VERSION 1 /** * @ingroup iface_zwlr_output_head_v1 */ #define ZWLR_OUTPUT_HEAD_V1_ENABLED_SINCE_VERSION 1 /** * @ingroup iface_zwlr_output_head_v1 */ #define ZWLR_OUTPUT_HEAD_V1_CURRENT_MODE_SINCE_VERSION 1 /** * @ingroup iface_zwlr_output_head_v1 */ #define ZWLR_OUTPUT_HEAD_V1_POSITION_SINCE_VERSION 1 /** * @ingroup iface_zwlr_output_head_v1 */ #define ZWLR_OUTPUT_HEAD_V1_TRANSFORM_SINCE_VERSION 1 /** * @ingroup iface_zwlr_output_head_v1 */ #define ZWLR_OUTPUT_HEAD_V1_SCALE_SINCE_VERSION 1 /** * @ingroup iface_zwlr_output_head_v1 */ #define ZWLR_OUTPUT_HEAD_V1_FINISHED_SINCE_VERSION 1 /** * @ingroup iface_zwlr_output_head_v1 */ #define ZWLR_OUTPUT_HEAD_V1_MAKE_SINCE_VERSION 2 /** * @ingroup iface_zwlr_output_head_v1 */ #define ZWLR_OUTPUT_HEAD_V1_MODEL_SINCE_VERSION 2 /** * @ingroup iface_zwlr_output_head_v1 */ #define ZWLR_OUTPUT_HEAD_V1_SERIAL_NUMBER_SINCE_VERSION 2 /** * @ingroup iface_zwlr_output_head_v1 */ #define ZWLR_OUTPUT_HEAD_V1_ADAPTIVE_SYNC_SINCE_VERSION 4 /** * @ingroup iface_zwlr_output_head_v1 */ #define ZWLR_OUTPUT_HEAD_V1_RELEASE_SINCE_VERSION 3 /** @ingroup iface_zwlr_output_head_v1 */ static inline void zwlr_output_head_v1_set_user_data(struct zwlr_output_head_v1 *zwlr_output_head_v1, void *user_data) { wl_proxy_set_user_data((struct wl_proxy *) zwlr_output_head_v1, user_data); } /** @ingroup iface_zwlr_output_head_v1 */ static inline void * zwlr_output_head_v1_get_user_data(struct zwlr_output_head_v1 *zwlr_output_head_v1) { return wl_proxy_get_user_data((struct wl_proxy *) zwlr_output_head_v1); } static inline uint32_t zwlr_output_head_v1_get_version(struct zwlr_output_head_v1 *zwlr_output_head_v1) { return wl_proxy_get_version((struct wl_proxy *) zwlr_output_head_v1); } /** @ingroup iface_zwlr_output_head_v1 */ static inline void zwlr_output_head_v1_destroy(struct zwlr_output_head_v1 *zwlr_output_head_v1) { wl_proxy_destroy((struct wl_proxy *) zwlr_output_head_v1); } // /** // * @ingroup iface_zwlr_output_head_v1 // * // * This request indicates that the client will no longer use this head // * object. // */ // static inline void // zwlr_output_head_v1_release(struct zwlr_output_head_v1 *zwlr_output_head_v1) // { // wl_proxy_marshal_flags((struct wl_proxy *) zwlr_output_head_v1, // ZWLR_OUTPUT_HEAD_V1_RELEASE, NULL, wl_proxy_get_version((struct wl_proxy *) zwlr_output_head_v1), WL_MARSHAL_FLAG_DESTROY); // } /** * @ingroup iface_zwlr_output_mode_v1 * @struct zwlr_output_mode_v1_listener */ struct zwlr_output_mode_v1_listener { /** * mode size * * This event describes the mode size. The size is given in * physical hardware units of the output device. This is not * necessarily the same as the output size in the global compositor * space. For instance, the output may be scaled or transformed. * @param width width of the mode in hardware units * @param height height of the mode in hardware units */ void (*size)(void *data, struct zwlr_output_mode_v1 *zwlr_output_mode_v1, int32_t width, int32_t height); /** * mode refresh rate * * This event describes the mode's fixed vertical refresh rate. * It is only sent if the mode has a fixed refresh rate. * @param refresh vertical refresh rate in mHz */ void (*refresh)(void *data, struct zwlr_output_mode_v1 *zwlr_output_mode_v1, int32_t refresh); /** * mode is preferred * * This event advertises this mode as preferred. */ void (*preferred)(void *data, struct zwlr_output_mode_v1 *zwlr_output_mode_v1); /** * the mode has disappeared * * This event indicates that the mode is no longer available. The * mode object becomes inert. Clients should send a destroy request * and release any resources associated with it. */ void (*finished)(void *data, struct zwlr_output_mode_v1 *zwlr_output_mode_v1); }; /** * @ingroup iface_zwlr_output_mode_v1 */ static inline int zwlr_output_mode_v1_add_listener(struct zwlr_output_mode_v1 *zwlr_output_mode_v1, const struct zwlr_output_mode_v1_listener *listener, void *data) { return wl_proxy_add_listener((struct wl_proxy *) zwlr_output_mode_v1, (void (**)(void)) listener, data); } #define ZWLR_OUTPUT_MODE_V1_RELEASE 0 /** * @ingroup iface_zwlr_output_mode_v1 */ #define ZWLR_OUTPUT_MODE_V1_SIZE_SINCE_VERSION 1 /** * @ingroup iface_zwlr_output_mode_v1 */ #define ZWLR_OUTPUT_MODE_V1_REFRESH_SINCE_VERSION 1 /** * @ingroup iface_zwlr_output_mode_v1 */ #define ZWLR_OUTPUT_MODE_V1_PREFERRED_SINCE_VERSION 1 /** * @ingroup iface_zwlr_output_mode_v1 */ #define ZWLR_OUTPUT_MODE_V1_FINISHED_SINCE_VERSION 1 /** * @ingroup iface_zwlr_output_mode_v1 */ #define ZWLR_OUTPUT_MODE_V1_RELEASE_SINCE_VERSION 3 /** @ingroup iface_zwlr_output_mode_v1 */ static inline void zwlr_output_mode_v1_set_user_data(struct zwlr_output_mode_v1 *zwlr_output_mode_v1, void *user_data) { wl_proxy_set_user_data((struct wl_proxy *) zwlr_output_mode_v1, user_data); } /** @ingroup iface_zwlr_output_mode_v1 */ static inline void * zwlr_output_mode_v1_get_user_data(struct zwlr_output_mode_v1 *zwlr_output_mode_v1) { return wl_proxy_get_user_data((struct wl_proxy *) zwlr_output_mode_v1); } static inline uint32_t zwlr_output_mode_v1_get_version(struct zwlr_output_mode_v1 *zwlr_output_mode_v1) { return wl_proxy_get_version((struct wl_proxy *) zwlr_output_mode_v1); } /** @ingroup iface_zwlr_output_mode_v1 */ static inline void zwlr_output_mode_v1_destroy(struct zwlr_output_mode_v1 *zwlr_output_mode_v1) { wl_proxy_destroy((struct wl_proxy *) zwlr_output_mode_v1); } // /** // * @ingroup iface_zwlr_output_mode_v1 // * // * This request indicates that the client will no longer use this mode // * object. // */ // static inline void // zwlr_output_mode_v1_release(struct zwlr_output_mode_v1 *zwlr_output_mode_v1) // { // wl_proxy_marshal_flags((struct wl_proxy *) zwlr_output_mode_v1, // ZWLR_OUTPUT_MODE_V1_RELEASE, NULL, wl_proxy_get_version((struct wl_proxy *) zwlr_output_mode_v1), WL_MARSHAL_FLAG_DESTROY); // } #ifndef ZWLR_OUTPUT_CONFIGURATION_V1_ERROR_ENUM #define ZWLR_OUTPUT_CONFIGURATION_V1_ERROR_ENUM enum zwlr_output_configuration_v1_error { /** * head has been configured twice */ ZWLR_OUTPUT_CONFIGURATION_V1_ERROR_ALREADY_CONFIGURED_HEAD = 1, /** * head has not been configured */ ZWLR_OUTPUT_CONFIGURATION_V1_ERROR_UNCONFIGURED_HEAD = 2, /** * request sent after configuration has been applied or tested */ ZWLR_OUTPUT_CONFIGURATION_V1_ERROR_ALREADY_USED = 3, }; #endif /* ZWLR_OUTPUT_CONFIGURATION_V1_ERROR_ENUM */ /** * @ingroup iface_zwlr_output_configuration_v1 * @struct zwlr_output_configuration_v1_listener */ struct zwlr_output_configuration_v1_listener { /** * configuration changes succeeded * * Sent after the compositor has successfully applied the changes * or tested them. * * Upon receiving this event, the client should destroy this * object. * * If the current configuration has changed, events to describe the * changes will be sent followed by a wlr_output_manager.done * event. */ void (*succeeded)(void *data, struct zwlr_output_configuration_v1 *zwlr_output_configuration_v1); /** * configuration changes failed * * Sent if the compositor rejects the changes or failed to apply * them. The compositor should revert any changes made by the apply * request that triggered this event. * * Upon receiving this event, the client should destroy this * object. */ void (*failed)(void *data, struct zwlr_output_configuration_v1 *zwlr_output_configuration_v1); /** * configuration has been cancelled * * Sent if the compositor cancels the configuration because the * state of an output changed and the client has outdated * information (e.g. after an output has been hotplugged). * * The client can create a new configuration with a newer serial * and try again. * * Upon receiving this event, the client should destroy this * object. */ void (*cancelled)(void *data, struct zwlr_output_configuration_v1 *zwlr_output_configuration_v1); }; /** * @ingroup iface_zwlr_output_configuration_v1 */ static inline int zwlr_output_configuration_v1_add_listener(struct zwlr_output_configuration_v1 *zwlr_output_configuration_v1, const struct zwlr_output_configuration_v1_listener *listener, void *data) { return wl_proxy_add_listener((struct wl_proxy *) zwlr_output_configuration_v1, (void (**)(void)) listener, data); } #define ZWLR_OUTPUT_CONFIGURATION_V1_ENABLE_HEAD 0 #define ZWLR_OUTPUT_CONFIGURATION_V1_DISABLE_HEAD 1 #define ZWLR_OUTPUT_CONFIGURATION_V1_APPLY 2 #define ZWLR_OUTPUT_CONFIGURATION_V1_TEST 3 #define ZWLR_OUTPUT_CONFIGURATION_V1_DESTROY 4 /** * @ingroup iface_zwlr_output_configuration_v1 */ #define ZWLR_OUTPUT_CONFIGURATION_V1_SUCCEEDED_SINCE_VERSION 1 /** * @ingroup iface_zwlr_output_configuration_v1 */ #define ZWLR_OUTPUT_CONFIGURATION_V1_FAILED_SINCE_VERSION 1 /** * @ingroup iface_zwlr_output_configuration_v1 */ #define ZWLR_OUTPUT_CONFIGURATION_V1_CANCELLED_SINCE_VERSION 1 /** * @ingroup iface_zwlr_output_configuration_v1 */ #define ZWLR_OUTPUT_CONFIGURATION_V1_ENABLE_HEAD_SINCE_VERSION 1 /** * @ingroup iface_zwlr_output_configuration_v1 */ #define ZWLR_OUTPUT_CONFIGURATION_V1_DISABLE_HEAD_SINCE_VERSION 1 /** * @ingroup iface_zwlr_output_configuration_v1 */ #define ZWLR_OUTPUT_CONFIGURATION_V1_APPLY_SINCE_VERSION 1 /** * @ingroup iface_zwlr_output_configuration_v1 */ #define ZWLR_OUTPUT_CONFIGURATION_V1_TEST_SINCE_VERSION 1 /** * @ingroup iface_zwlr_output_configuration_v1 */ #define ZWLR_OUTPUT_CONFIGURATION_V1_DESTROY_SINCE_VERSION 1 /** @ingroup iface_zwlr_output_configuration_v1 */ static inline void zwlr_output_configuration_v1_set_user_data(struct zwlr_output_configuration_v1 *zwlr_output_configuration_v1, void *user_data) { wl_proxy_set_user_data((struct wl_proxy *) zwlr_output_configuration_v1, user_data); } /** @ingroup iface_zwlr_output_configuration_v1 */ static inline void * zwlr_output_configuration_v1_get_user_data(struct zwlr_output_configuration_v1 *zwlr_output_configuration_v1) { return wl_proxy_get_user_data((struct wl_proxy *) zwlr_output_configuration_v1); } static inline uint32_t zwlr_output_configuration_v1_get_version(struct zwlr_output_configuration_v1 *zwlr_output_configuration_v1) { return wl_proxy_get_version((struct wl_proxy *) zwlr_output_configuration_v1); } // /** // * @ingroup iface_zwlr_output_configuration_v1 // * // * Enable a head. This request creates a head configuration object that can // * be used to change the head's properties. // */ // static inline struct zwlr_output_configuration_head_v1 * // zwlr_output_configuration_v1_enable_head(struct zwlr_output_configuration_v1 *zwlr_output_configuration_v1, struct zwlr_output_head_v1 *head) // { // struct wl_proxy *id; // id = wl_proxy_marshal_flags((struct wl_proxy *) zwlr_output_configuration_v1, // ZWLR_OUTPUT_CONFIGURATION_V1_ENABLE_HEAD, &zwlr_output_configuration_head_v1_interface, wl_proxy_get_version((struct wl_proxy *) zwlr_output_configuration_v1), 0, NULL, head); // return (struct zwlr_output_configuration_head_v1 *) id; // } // /** // * @ingroup iface_zwlr_output_configuration_v1 // * // * Disable a head. // */ // static inline void // zwlr_output_configuration_v1_disable_head(struct zwlr_output_configuration_v1 *zwlr_output_configuration_v1, struct zwlr_output_head_v1 *head) // { // wl_proxy_marshal_flags((struct wl_proxy *) zwlr_output_configuration_v1, // ZWLR_OUTPUT_CONFIGURATION_V1_DISABLE_HEAD, NULL, wl_proxy_get_version((struct wl_proxy *) zwlr_output_configuration_v1), 0, head); // } // /** // * @ingroup iface_zwlr_output_configuration_v1 // * // * Apply the new output configuration. // * // * In case the configuration is successfully applied, there is no guarantee // * that the new output state matches completely the requested // * configuration. For instance, a compositor might round the scale if it // * doesn't support fractional scaling. // * // * After this request has been sent, the compositor must respond with an // * succeeded, failed or cancelled event. Sending a request that isn't the // * destructor is a protocol error. // */ // static inline void // zwlr_output_configuration_v1_apply(struct zwlr_output_configuration_v1 *zwlr_output_configuration_v1) // { // wl_proxy_marshal_flags((struct wl_proxy *) zwlr_output_configuration_v1, // ZWLR_OUTPUT_CONFIGURATION_V1_APPLY, NULL, wl_proxy_get_version((struct wl_proxy *) zwlr_output_configuration_v1), 0); // } // /** // * @ingroup iface_zwlr_output_configuration_v1 // * // * Test the new output configuration. The configuration won't be applied, // * but will only be validated. // * // * Even if the compositor succeeds to test a configuration, applying it may // * fail. // * // * After this request has been sent, the compositor must respond with an // * succeeded, failed or cancelled event. Sending a request that isn't the // * destructor is a protocol error. // */ // static inline void // zwlr_output_configuration_v1_test(struct zwlr_output_configuration_v1 *zwlr_output_configuration_v1) // { // wl_proxy_marshal_flags((struct wl_proxy *) zwlr_output_configuration_v1, // ZWLR_OUTPUT_CONFIGURATION_V1_TEST, NULL, wl_proxy_get_version((struct wl_proxy *) zwlr_output_configuration_v1), 0); // } // /** // * @ingroup iface_zwlr_output_configuration_v1 // * // * Using this request a client can tell the compositor that it is not going // * to use the configuration object anymore. Any changes to the outputs // * that have not been applied will be discarded. // * // * This request also destroys wlr_output_configuration_head objects created // * via this object. // */ // static inline void // zwlr_output_configuration_v1_destroy(struct zwlr_output_configuration_v1 *zwlr_output_configuration_v1) // { // wl_proxy_marshal_flags((struct wl_proxy *) zwlr_output_configuration_v1, // ZWLR_OUTPUT_CONFIGURATION_V1_DESTROY, NULL, wl_proxy_get_version((struct wl_proxy *) zwlr_output_configuration_v1), WL_MARSHAL_FLAG_DESTROY); // } #ifndef ZWLR_OUTPUT_CONFIGURATION_HEAD_V1_ERROR_ENUM #define ZWLR_OUTPUT_CONFIGURATION_HEAD_V1_ERROR_ENUM enum zwlr_output_configuration_head_v1_error { /** * property has already been set */ ZWLR_OUTPUT_CONFIGURATION_HEAD_V1_ERROR_ALREADY_SET = 1, /** * mode doesn't belong to head */ ZWLR_OUTPUT_CONFIGURATION_HEAD_V1_ERROR_INVALID_MODE = 2, /** * mode is invalid */ ZWLR_OUTPUT_CONFIGURATION_HEAD_V1_ERROR_INVALID_CUSTOM_MODE = 3, /** * transform value outside enum */ ZWLR_OUTPUT_CONFIGURATION_HEAD_V1_ERROR_INVALID_TRANSFORM = 4, /** * scale negative or zero */ ZWLR_OUTPUT_CONFIGURATION_HEAD_V1_ERROR_INVALID_SCALE = 5, /** * invalid enum value used in the set_adaptive_sync request * @since 4 */ ZWLR_OUTPUT_CONFIGURATION_HEAD_V1_ERROR_INVALID_ADAPTIVE_SYNC_STATE = 6, }; /** * @ingroup iface_zwlr_output_configuration_head_v1 */ #define ZWLR_OUTPUT_CONFIGURATION_HEAD_V1_ERROR_INVALID_ADAPTIVE_SYNC_STATE_SINCE_VERSION 4 #endif /* ZWLR_OUTPUT_CONFIGURATION_HEAD_V1_ERROR_ENUM */ #define ZWLR_OUTPUT_CONFIGURATION_HEAD_V1_SET_MODE 0 #define ZWLR_OUTPUT_CONFIGURATION_HEAD_V1_SET_CUSTOM_MODE 1 #define ZWLR_OUTPUT_CONFIGURATION_HEAD_V1_SET_POSITION 2 #define ZWLR_OUTPUT_CONFIGURATION_HEAD_V1_SET_TRANSFORM 3 #define ZWLR_OUTPUT_CONFIGURATION_HEAD_V1_SET_SCALE 4 #define ZWLR_OUTPUT_CONFIGURATION_HEAD_V1_SET_ADAPTIVE_SYNC 5 /** * @ingroup iface_zwlr_output_configuration_head_v1 */ #define ZWLR_OUTPUT_CONFIGURATION_HEAD_V1_SET_MODE_SINCE_VERSION 1 /** * @ingroup iface_zwlr_output_configuration_head_v1 */ #define ZWLR_OUTPUT_CONFIGURATION_HEAD_V1_SET_CUSTOM_MODE_SINCE_VERSION 1 /** * @ingroup iface_zwlr_output_configuration_head_v1 */ #define ZWLR_OUTPUT_CONFIGURATION_HEAD_V1_SET_POSITION_SINCE_VERSION 1 /** * @ingroup iface_zwlr_output_configuration_head_v1 */ #define ZWLR_OUTPUT_CONFIGURATION_HEAD_V1_SET_TRANSFORM_SINCE_VERSION 1 /** * @ingroup iface_zwlr_output_configuration_head_v1 */ #define ZWLR_OUTPUT_CONFIGURATION_HEAD_V1_SET_SCALE_SINCE_VERSION 1 /** * @ingroup iface_zwlr_output_configuration_head_v1 */ #define ZWLR_OUTPUT_CONFIGURATION_HEAD_V1_SET_ADAPTIVE_SYNC_SINCE_VERSION 4 /** @ingroup iface_zwlr_output_configuration_head_v1 */ static inline void zwlr_output_configuration_head_v1_set_user_data(struct zwlr_output_configuration_head_v1 *zwlr_output_configuration_head_v1, void *user_data) { wl_proxy_set_user_data((struct wl_proxy *) zwlr_output_configuration_head_v1, user_data); } /** @ingroup iface_zwlr_output_configuration_head_v1 */ static inline void * zwlr_output_configuration_head_v1_get_user_data(struct zwlr_output_configuration_head_v1 *zwlr_output_configuration_head_v1) { return wl_proxy_get_user_data((struct wl_proxy *) zwlr_output_configuration_head_v1); } static inline uint32_t zwlr_output_configuration_head_v1_get_version(struct zwlr_output_configuration_head_v1 *zwlr_output_configuration_head_v1) { return wl_proxy_get_version((struct wl_proxy *) zwlr_output_configuration_head_v1); } /** @ingroup iface_zwlr_output_configuration_head_v1 */ static inline void zwlr_output_configuration_head_v1_destroy(struct zwlr_output_configuration_head_v1 *zwlr_output_configuration_head_v1) { wl_proxy_destroy((struct wl_proxy *) zwlr_output_configuration_head_v1); } // /** // * @ingroup iface_zwlr_output_configuration_head_v1 // * // * This request sets the head's mode. // */ // static inline void // zwlr_output_configuration_head_v1_set_mode(struct zwlr_output_configuration_head_v1 *zwlr_output_configuration_head_v1, struct zwlr_output_mode_v1 *mode) // { // wl_proxy_marshal_flags((struct wl_proxy *) zwlr_output_configuration_head_v1, // ZWLR_OUTPUT_CONFIGURATION_HEAD_V1_SET_MODE, NULL, wl_proxy_get_version((struct wl_proxy *) zwlr_output_configuration_head_v1), 0, mode); // } // /** // * @ingroup iface_zwlr_output_configuration_head_v1 // * // * This request assigns a custom mode to the head. The size is given in // * physical hardware units of the output device. If set to zero, the // * refresh rate is unspecified. // * // * It is a protocol error to set both a mode and a custom mode. // */ // static inline void // zwlr_output_configuration_head_v1_set_custom_mode(struct zwlr_output_configuration_head_v1 *zwlr_output_configuration_head_v1, int32_t width, int32_t height, int32_t refresh) // { // wl_proxy_marshal_flags((struct wl_proxy *) zwlr_output_configuration_head_v1, // ZWLR_OUTPUT_CONFIGURATION_HEAD_V1_SET_CUSTOM_MODE, NULL, wl_proxy_get_version((struct wl_proxy *) zwlr_output_configuration_head_v1), 0, width, height, refresh); // } // /** // * @ingroup iface_zwlr_output_configuration_head_v1 // * // * This request sets the head's position in the global compositor space. // */ // static inline void // zwlr_output_configuration_head_v1_set_position(struct zwlr_output_configuration_head_v1 *zwlr_output_configuration_head_v1, int32_t x, int32_t y) // { // wl_proxy_marshal_flags((struct wl_proxy *) zwlr_output_configuration_head_v1, // ZWLR_OUTPUT_CONFIGURATION_HEAD_V1_SET_POSITION, NULL, wl_proxy_get_version((struct wl_proxy *) zwlr_output_configuration_head_v1), 0, x, y); // } // /** // * @ingroup iface_zwlr_output_configuration_head_v1 // * // * This request sets the head's transform. // */ // static inline void // zwlr_output_configuration_head_v1_set_transform(struct zwlr_output_configuration_head_v1 *zwlr_output_configuration_head_v1, int32_t transform) // { // wl_proxy_marshal_flags((struct wl_proxy *) zwlr_output_configuration_head_v1, // ZWLR_OUTPUT_CONFIGURATION_HEAD_V1_SET_TRANSFORM, NULL, wl_proxy_get_version((struct wl_proxy *) zwlr_output_configuration_head_v1), 0, transform); // } // /** // * @ingroup iface_zwlr_output_configuration_head_v1 // * // * This request sets the head's scale. // */ // static inline void // zwlr_output_configuration_head_v1_set_scale(struct zwlr_output_configuration_head_v1 *zwlr_output_configuration_head_v1, wl_fixed_t scale) // { // wl_proxy_marshal_flags((struct wl_proxy *) zwlr_output_configuration_head_v1, // ZWLR_OUTPUT_CONFIGURATION_HEAD_V1_SET_SCALE, NULL, wl_proxy_get_version((struct wl_proxy *) zwlr_output_configuration_head_v1), 0, scale); // } // /** // * @ingroup iface_zwlr_output_configuration_head_v1 // * // * This request enables/disables adaptive sync. Adaptive sync is also // * known as Variable Refresh Rate or VRR. // */ // static inline void // zwlr_output_configuration_head_v1_set_adaptive_sync(struct zwlr_output_configuration_head_v1 *zwlr_output_configuration_head_v1, uint32_t state) // { // wl_proxy_marshal_flags((struct wl_proxy *) zwlr_output_configuration_head_v1, // ZWLR_OUTPUT_CONFIGURATION_HEAD_V1_SET_ADAPTIVE_SYNC, NULL, wl_proxy_get_version((struct wl_proxy *) zwlr_output_configuration_head_v1), 0, state); // } #ifdef __cplusplus } #endif #endif ================================================ FILE: src/detection/displayserver/linux/wayland/wlr-output-management-unstable-v1-protocol.c ================================================ #ifdef FF_HAVE_WAYLAND /* Generated by wayland-scanner 1.24.0 */ /* * Copyright © 2019 Purism SPC * * Permission to use, copy, modify, distribute, and sell this * software and its documentation for any purpose is hereby granted * without fee, provided that the above copyright notice appear in * all copies and that both that copyright notice and this permission * notice appear in supporting documentation, and that the name of * the copyright holders not be used in advertising or publicity * pertaining to distribution of the software without specific, * written prior permission. The copyright holders make no * representations about the suitability of this software for any * purpose. It is provided "as is" without express or implied * warranty. * * THE COPYRIGHT HOLDERS DISCLAIM ALL WARRANTIES WITH REGARD TO THIS * SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND * FITNESS, IN NO EVENT SHALL THE COPYRIGHT HOLDERS BE LIABLE FOR ANY * SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN * AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, * ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF * THIS SOFTWARE. */ #include #include #include #include #ifndef __has_attribute # define __has_attribute(x) 0 /* Compatibility with non-clang compilers. */ #endif #if (__has_attribute(visibility) || defined(__GNUC__) && __GNUC__ >= 4) #define WL_PRIVATE __attribute__ ((visibility("hidden"))) #else #define WL_PRIVATE #endif extern const struct wl_interface zwlr_output_configuration_head_v1_interface; extern const struct wl_interface zwlr_output_configuration_v1_interface; extern const struct wl_interface zwlr_output_head_v1_interface; extern const struct wl_interface zwlr_output_mode_v1_interface; static const struct wl_interface *wlr_output_management_unstable_v1_types[] = { NULL, NULL, NULL, &zwlr_output_configuration_v1_interface, NULL, &zwlr_output_head_v1_interface, &zwlr_output_mode_v1_interface, &zwlr_output_mode_v1_interface, &zwlr_output_configuration_head_v1_interface, &zwlr_output_head_v1_interface, &zwlr_output_head_v1_interface, &zwlr_output_mode_v1_interface, }; static const struct wl_message zwlr_output_manager_v1_requests[] = { { "create_configuration", "nu", wlr_output_management_unstable_v1_types + 3 }, { "stop", "", wlr_output_management_unstable_v1_types + 0 }, }; static const struct wl_message zwlr_output_manager_v1_events[] = { { "head", "n", wlr_output_management_unstable_v1_types + 5 }, { "done", "u", wlr_output_management_unstable_v1_types + 0 }, { "finished", "", wlr_output_management_unstable_v1_types + 0 }, }; WL_PRIVATE const struct wl_interface zwlr_output_manager_v1_interface = { "zwlr_output_manager_v1", 4, 2, zwlr_output_manager_v1_requests, 3, zwlr_output_manager_v1_events, }; static const struct wl_message zwlr_output_head_v1_requests[] = { { "release", "3", wlr_output_management_unstable_v1_types + 0 }, }; static const struct wl_message zwlr_output_head_v1_events[] = { { "name", "s", wlr_output_management_unstable_v1_types + 0 }, { "description", "s", wlr_output_management_unstable_v1_types + 0 }, { "physical_size", "ii", wlr_output_management_unstable_v1_types + 0 }, { "mode", "n", wlr_output_management_unstable_v1_types + 6 }, { "enabled", "i", wlr_output_management_unstable_v1_types + 0 }, { "current_mode", "o", wlr_output_management_unstable_v1_types + 7 }, { "position", "ii", wlr_output_management_unstable_v1_types + 0 }, { "transform", "i", wlr_output_management_unstable_v1_types + 0 }, { "scale", "f", wlr_output_management_unstable_v1_types + 0 }, { "finished", "", wlr_output_management_unstable_v1_types + 0 }, { "make", "2s", wlr_output_management_unstable_v1_types + 0 }, { "model", "2s", wlr_output_management_unstable_v1_types + 0 }, { "serial_number", "2s", wlr_output_management_unstable_v1_types + 0 }, { "adaptive_sync", "4u", wlr_output_management_unstable_v1_types + 0 }, }; WL_PRIVATE const struct wl_interface zwlr_output_head_v1_interface = { "zwlr_output_head_v1", 4, 1, zwlr_output_head_v1_requests, 14, zwlr_output_head_v1_events, }; static const struct wl_message zwlr_output_mode_v1_requests[] = { { "release", "3", wlr_output_management_unstable_v1_types + 0 }, }; static const struct wl_message zwlr_output_mode_v1_events[] = { { "size", "ii", wlr_output_management_unstable_v1_types + 0 }, { "refresh", "i", wlr_output_management_unstable_v1_types + 0 }, { "preferred", "", wlr_output_management_unstable_v1_types + 0 }, { "finished", "", wlr_output_management_unstable_v1_types + 0 }, }; WL_PRIVATE const struct wl_interface zwlr_output_mode_v1_interface = { "zwlr_output_mode_v1", 3, 1, zwlr_output_mode_v1_requests, 4, zwlr_output_mode_v1_events, }; static const struct wl_message zwlr_output_configuration_v1_requests[] = { { "enable_head", "no", wlr_output_management_unstable_v1_types + 8 }, { "disable_head", "o", wlr_output_management_unstable_v1_types + 10 }, { "apply", "", wlr_output_management_unstable_v1_types + 0 }, { "test", "", wlr_output_management_unstable_v1_types + 0 }, { "destroy", "", wlr_output_management_unstable_v1_types + 0 }, }; static const struct wl_message zwlr_output_configuration_v1_events[] = { { "succeeded", "", wlr_output_management_unstable_v1_types + 0 }, { "failed", "", wlr_output_management_unstable_v1_types + 0 }, { "cancelled", "", wlr_output_management_unstable_v1_types + 0 }, }; WL_PRIVATE const struct wl_interface zwlr_output_configuration_v1_interface = { "zwlr_output_configuration_v1", 4, 5, zwlr_output_configuration_v1_requests, 3, zwlr_output_configuration_v1_events, }; static const struct wl_message zwlr_output_configuration_head_v1_requests[] = { { "set_mode", "o", wlr_output_management_unstable_v1_types + 11 }, { "set_custom_mode", "iii", wlr_output_management_unstable_v1_types + 0 }, { "set_position", "ii", wlr_output_management_unstable_v1_types + 0 }, { "set_transform", "i", wlr_output_management_unstable_v1_types + 0 }, { "set_scale", "f", wlr_output_management_unstable_v1_types + 0 }, { "set_adaptive_sync", "4u", wlr_output_management_unstable_v1_types + 0 }, }; WL_PRIVATE const struct wl_interface zwlr_output_configuration_head_v1_interface = { "zwlr_output_configuration_head_v1", 4, 6, zwlr_output_configuration_head_v1_requests, 0, NULL, }; #endif ================================================ FILE: src/detection/displayserver/linux/wayland/xdg-output-unstable-v1-client-protocol.h ================================================ /* Generated by wayland-scanner 1.22.0 */ #ifndef XDG_OUTPUT_UNSTABLE_V1_CLIENT_PROTOCOL_H #define XDG_OUTPUT_UNSTABLE_V1_CLIENT_PROTOCOL_H #include #include #include #ifdef __cplusplus extern "C" { #endif /** * @page page_xdg_output_unstable_v1 The xdg_output_unstable_v1 protocol * Protocol to describe output regions * * @section page_desc_xdg_output_unstable_v1 Description * * This protocol aims at describing outputs in a way which is more in line * with the concept of an output on desktop oriented systems. * * Some information are more specific to the concept of an output for * a desktop oriented system and may not make sense in other applications, * such as IVI systems for example. * * Typically, the global compositor space on a desktop system is made of * a contiguous or overlapping set of rectangular regions. * * The logical_position and logical_size events defined in this protocol * might provide information identical to their counterparts already * available from wl_output, in which case the information provided by this * protocol should be preferred to their equivalent in wl_output. The goal is * to move the desktop specific concepts (such as output location within the * global compositor space, etc.) out of the core wl_output protocol. * * Warning! The protocol described in this file is experimental and * backward incompatible changes may be made. Backward compatible * changes may be added together with the corresponding interface * version bump. * Backward incompatible changes are done by bumping the version * number in the protocol and interface names and resetting the * interface version. Once the protocol is to be declared stable, * the 'z' prefix and the version number in the protocol and * interface names are removed and the interface version number is * reset. * * @section page_ifaces_xdg_output_unstable_v1 Interfaces * - @subpage page_iface_zxdg_output_manager_v1 - manage xdg_output objects * - @subpage page_iface_zxdg_output_v1 - compositor logical output region * @section page_copyright_xdg_output_unstable_v1 Copyright *
 *
 * Copyright © 2017 Red Hat Inc.
 *
 * Permission is hereby granted, free of charge, to any person obtaining a
 * copy of this software and associated documentation files (the "Software"),
 * to deal in the Software without restriction, including without limitation
 * the rights to use, copy, modify, merge, publish, distribute, sublicense,
 * and/or sell copies of the Software, and to permit persons to whom the
 * Software is furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice (including the next
 * paragraph) shall be included in all copies or substantial portions of the
 * Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL
 * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
 * DEALINGS IN THE SOFTWARE.
 * 
*/ struct wl_output; struct zxdg_output_manager_v1; struct zxdg_output_v1; #ifndef ZXDG_OUTPUT_MANAGER_V1_INTERFACE #define ZXDG_OUTPUT_MANAGER_V1_INTERFACE /** * @page page_iface_zxdg_output_manager_v1 zxdg_output_manager_v1 * @section page_iface_zxdg_output_manager_v1_desc Description * * A global factory interface for xdg_output objects. * @section page_iface_zxdg_output_manager_v1_api API * See @ref iface_zxdg_output_manager_v1. */ /** * @defgroup iface_zxdg_output_manager_v1 The zxdg_output_manager_v1 interface * * A global factory interface for xdg_output objects. */ extern const struct wl_interface zxdg_output_manager_v1_interface; #endif #ifndef ZXDG_OUTPUT_V1_INTERFACE #define ZXDG_OUTPUT_V1_INTERFACE /** * @page page_iface_zxdg_output_v1 zxdg_output_v1 * @section page_iface_zxdg_output_v1_desc Description * * An xdg_output describes part of the compositor geometry. * * This typically corresponds to a monitor that displays part of the * compositor space. * * For objects version 3 onwards, after all xdg_output properties have been * sent (when the object is created and when properties are updated), a * wl_output.done event is sent. This allows changes to the output * properties to be seen as atomic, even if they happen via multiple events. * @section page_iface_zxdg_output_v1_api API * See @ref iface_zxdg_output_v1. */ /** * @defgroup iface_zxdg_output_v1 The zxdg_output_v1 interface * * An xdg_output describes part of the compositor geometry. * * This typically corresponds to a monitor that displays part of the * compositor space. * * For objects version 3 onwards, after all xdg_output properties have been * sent (when the object is created and when properties are updated), a * wl_output.done event is sent. This allows changes to the output * properties to be seen as atomic, even if they happen via multiple events. */ extern const struct wl_interface zxdg_output_v1_interface; #endif #define ZXDG_OUTPUT_MANAGER_V1_DESTROY 0 #define ZXDG_OUTPUT_MANAGER_V1_GET_XDG_OUTPUT 1 /** * @ingroup iface_zxdg_output_manager_v1 */ #define ZXDG_OUTPUT_MANAGER_V1_DESTROY_SINCE_VERSION 1 /** * @ingroup iface_zxdg_output_manager_v1 */ #define ZXDG_OUTPUT_MANAGER_V1_GET_XDG_OUTPUT_SINCE_VERSION 1 // /** @ingroup iface_zxdg_output_manager_v1 */ // static inline void // zxdg_output_manager_v1_set_user_data(struct zxdg_output_manager_v1 *zxdg_output_manager_v1, void *user_data) // { // wl_proxy_set_user_data((struct wl_proxy *) zxdg_output_manager_v1, user_data); // } // /** @ingroup iface_zxdg_output_manager_v1 */ // static inline void * // zxdg_output_manager_v1_get_user_data(struct zxdg_output_manager_v1 *zxdg_output_manager_v1) // { // return wl_proxy_get_user_data((struct wl_proxy *) zxdg_output_manager_v1); // } // static inline uint32_t // zxdg_output_manager_v1_get_version(struct zxdg_output_manager_v1 *zxdg_output_manager_v1) // { // return wl_proxy_get_version((struct wl_proxy *) zxdg_output_manager_v1); // } // /** // * @ingroup iface_zxdg_output_manager_v1 // * // * Using this request a client can tell the server that it is not // * going to use the xdg_output_manager object anymore. // * // * Any objects already created through this instance are not affected. // */ // static inline void // zxdg_output_manager_v1_destroy(struct zxdg_output_manager_v1 *zxdg_output_manager_v1) // { // wl_proxy_marshal_flags((struct wl_proxy *) zxdg_output_manager_v1, // ZXDG_OUTPUT_MANAGER_V1_DESTROY, NULL, wl_proxy_get_version((struct wl_proxy *) zxdg_output_manager_v1), WL_MARSHAL_FLAG_DESTROY); // } // /** // * @ingroup iface_zxdg_output_manager_v1 // * // * This creates a new xdg_output object for the given wl_output. // */ // static inline struct zxdg_output_v1 * // zxdg_output_manager_v1_get_xdg_output(struct zxdg_output_manager_v1 *zxdg_output_manager_v1, struct wl_output *output) // { // struct wl_proxy *id; // id = wl_proxy_marshal_flags((struct wl_proxy *) zxdg_output_manager_v1, // ZXDG_OUTPUT_MANAGER_V1_GET_XDG_OUTPUT, &zxdg_output_v1_interface, wl_proxy_get_version((struct wl_proxy *) zxdg_output_manager_v1), 0, NULL, output); // return (struct zxdg_output_v1 *) id; // } /** * @ingroup iface_zxdg_output_v1 * @struct zxdg_output_v1_listener */ struct zxdg_output_v1_listener { /** * position of the output within the global compositor space * * The position event describes the location of the wl_output * within the global compositor space. * * The logical_position event is sent after creating an xdg_output * (see xdg_output_manager.get_xdg_output) and whenever the * location of the output changes within the global compositor * space. * @param x x position within the global compositor space * @param y y position within the global compositor space */ void (*logical_position)(void *data, struct zxdg_output_v1 *zxdg_output_v1, int32_t x, int32_t y); /** * size of the output in the global compositor space * * The logical_size event describes the size of the output in the * global compositor space. * * Most regular Wayland clients should not pay attention to the * logical size and would rather rely on xdg_shell interfaces. * * Some clients such as Xwayland, however, need this to configure * their surfaces in the global compositor space as the compositor * may apply a different scale from what is advertised by the * output scaling property (to achieve fractional scaling, for * example). * * For example, for a wl_output mode 3840×2160 and a scale factor * 2: * * - A compositor not scaling the monitor viewport in its * compositing space will advertise a logical size of 3840×2160, * * - A compositor scaling the monitor viewport with scale factor 2 * will advertise a logical size of 1920×1080, * * - A compositor scaling the monitor viewport using a fractional * scale of 1.5 will advertise a logical size of 2560×1440. * * For example, for a wl_output mode 1920×1080 and a 90 degree * rotation, the compositor will advertise a logical size of * 1080x1920. * * The logical_size event is sent after creating an xdg_output (see * xdg_output_manager.get_xdg_output) and whenever the logical size * of the output changes, either as a result of a change in the * applied scale or because of a change in the corresponding output * mode(see wl_output.mode) or transform (see wl_output.transform). * @param width width in global compositor space * @param height height in global compositor space */ void (*logical_size)(void *data, struct zxdg_output_v1 *zxdg_output_v1, int32_t width, int32_t height); /** * all information about the output have been sent * * This event is sent after all other properties of an xdg_output * have been sent. * * This allows changes to the xdg_output properties to be seen as * atomic, even if they happen via multiple events. * * For objects version 3 onwards, this event is deprecated. * Compositors are not required to send it anymore and must send * wl_output.done instead. */ void (*done)(void *data, struct zxdg_output_v1 *zxdg_output_v1); /** * name of this output * * Many compositors will assign names to their outputs, show them * to the user, allow them to be configured by name, etc. The * client may wish to know this name as well to offer the user * similar behaviors. * * The naming convention is compositor defined, but limited to * alphanumeric characters and dashes (-). Each name is unique * among all wl_output globals, but if a wl_output global is * destroyed the same name may be reused later. The names will also * remain consistent across sessions with the same hardware and * software configuration. * * Examples of names include 'HDMI-A-1', 'WL-1', 'X11-1', etc. * However, do not assume that the name is a reflection of an * underlying DRM connector, X11 connection, etc. * * The name event is sent after creating an xdg_output (see * xdg_output_manager.get_xdg_output). This event is only sent once * per xdg_output, and the name does not change over the lifetime * of the wl_output global. * * This event is deprecated, instead clients should use * wl_output.name. Compositors must still support this event. * @param name output name * @since 2 */ void (*name)(void *data, struct zxdg_output_v1 *zxdg_output_v1, const char *name); /** * human-readable description of this output * * Many compositors can produce human-readable descriptions of * their outputs. The client may wish to know this description as * well, to communicate the user for various purposes. * * The description is a UTF-8 string with no convention defined for * its contents. Examples might include 'Foocorp 11" Display' or * 'Virtual X11 output via :1'. * * The description event is sent after creating an xdg_output (see * xdg_output_manager.get_xdg_output) and whenever the description * changes. The description is optional, and may not be sent at * all. * * For objects of version 2 and lower, this event is only sent once * per xdg_output, and the description does not change over the * lifetime of the wl_output global. * * This event is deprecated, instead clients should use * wl_output.description. Compositors must still support this * event. * @param description output description * @since 2 */ void (*description)(void *data, struct zxdg_output_v1 *zxdg_output_v1, const char *description); }; /** * @ingroup iface_zxdg_output_v1 */ static inline int zxdg_output_v1_add_listener(struct zxdg_output_v1 *zxdg_output_v1, const struct zxdg_output_v1_listener *listener, void *data) { return wl_proxy_add_listener((struct wl_proxy *) zxdg_output_v1, (void (**)(void)) listener, data); } #define ZXDG_OUTPUT_V1_DESTROY 0 /** * @ingroup iface_zxdg_output_v1 */ #define ZXDG_OUTPUT_V1_LOGICAL_POSITION_SINCE_VERSION 1 /** * @ingroup iface_zxdg_output_v1 */ #define ZXDG_OUTPUT_V1_LOGICAL_SIZE_SINCE_VERSION 1 /** * @ingroup iface_zxdg_output_v1 */ #define ZXDG_OUTPUT_V1_DONE_SINCE_VERSION 1 /** * @ingroup iface_zxdg_output_v1 */ #define ZXDG_OUTPUT_V1_NAME_SINCE_VERSION 2 /** * @ingroup iface_zxdg_output_v1 */ #define ZXDG_OUTPUT_V1_DESCRIPTION_SINCE_VERSION 2 /** * @ingroup iface_zxdg_output_v1 */ #define ZXDG_OUTPUT_V1_DESTROY_SINCE_VERSION 1 // /** @ingroup iface_zxdg_output_v1 */ // static inline void // zxdg_output_v1_set_user_data(struct zxdg_output_v1 *zxdg_output_v1, void *user_data) // { // wl_proxy_set_user_data((struct wl_proxy *) zxdg_output_v1, user_data); // } // /** @ingroup iface_zxdg_output_v1 */ // static inline void * // zxdg_output_v1_get_user_data(struct zxdg_output_v1 *zxdg_output_v1) // { // return wl_proxy_get_user_data((struct wl_proxy *) zxdg_output_v1); // } // static inline uint32_t // zxdg_output_v1_get_version(struct zxdg_output_v1 *zxdg_output_v1) // { // return wl_proxy_get_version((struct wl_proxy *) zxdg_output_v1); // } // /** // * @ingroup iface_zxdg_output_v1 // * // * Using this request a client can tell the server that it is not // * going to use the xdg_output object anymore. // */ // static inline void // zxdg_output_v1_destroy(struct zxdg_output_v1 *zxdg_output_v1) // { // wl_proxy_marshal_flags((struct wl_proxy *) zxdg_output_v1, // ZXDG_OUTPUT_V1_DESTROY, NULL, wl_proxy_get_version((struct wl_proxy *) zxdg_output_v1), WL_MARSHAL_FLAG_DESTROY); // } #ifdef __cplusplus } #endif #endif ================================================ FILE: src/detection/displayserver/linux/wayland/xdg-output-unstable-v1-protocol.c ================================================ #ifdef FF_HAVE_WAYLAND /* Generated by wayland-scanner 1.22.0 */ /* * Copyright © 2017 Red Hat Inc. * * Permission is hereby granted, free of charge, to any person obtaining a * copy of this software and associated documentation files (the "Software"), * to deal in the Software without restriction, including without limitation * the rights to use, copy, modify, merge, publish, distribute, sublicense, * and/or sell copies of the Software, and to permit persons to whom the * Software is furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice (including the next * paragraph) shall be included in all copies or substantial portions of the * Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER * DEALINGS IN THE SOFTWARE. */ #include #include #include extern const struct wl_interface wl_output_interface; extern const struct wl_interface zxdg_output_v1_interface; static const struct wl_interface *xdg_output_unstable_v1_types[] = { NULL, NULL, &zxdg_output_v1_interface, NULL, // &wl_output_interface, }; static const struct wl_message zxdg_output_manager_v1_requests[] = { { "destroy", "", xdg_output_unstable_v1_types + 0 }, { "get_xdg_output", "no", xdg_output_unstable_v1_types + 2 }, }; WL_EXPORT const struct wl_interface zxdg_output_manager_v1_interface = { "zxdg_output_manager_v1", 3, 2, zxdg_output_manager_v1_requests, 0, NULL, }; static const struct wl_message zxdg_output_v1_requests[] = { { "destroy", "", xdg_output_unstable_v1_types + 0 }, }; static const struct wl_message zxdg_output_v1_events[] = { { "logical_position", "ii", xdg_output_unstable_v1_types + 0 }, { "logical_size", "ii", xdg_output_unstable_v1_types + 0 }, { "done", "", xdg_output_unstable_v1_types + 0 }, { "name", "2s", xdg_output_unstable_v1_types + 0 }, { "description", "2s", xdg_output_unstable_v1_types + 0 }, }; WL_EXPORT const struct wl_interface zxdg_output_v1_interface = { "zxdg_output_v1", 3, 1, zxdg_output_v1_requests, 5, zxdg_output_v1_events, }; #endif ================================================ FILE: src/detection/displayserver/linux/wayland/zwlr-output.c ================================================ #ifdef FF_HAVE_WAYLAND #include "wayland.h" #include "wlr-output-management-unstable-v1-client-protocol.h" static void waylandZwlrTransformListener(void* data, FF_MAYBE_UNUSED struct zwlr_output_head_v1 *zwlr_output_head_v1, int32_t transform) { WaylandDisplay* wldata = (WaylandDisplay*) data; wldata->transform = (enum wl_output_transform) transform; } static void waylandZwlrScaleListener(void* data, FF_MAYBE_UNUSED struct zwlr_output_head_v1 *zwlr_output_head_v1, wl_fixed_t scale) { WaylandDisplay* wldata = (WaylandDisplay*) data; wldata->dpi = (uint32_t) scale * 3 / 8; // wl_fixed_to_double(scale) * 96; } typedef struct WaylandZwlrMode { int32_t width; int32_t height; int32_t refreshRate; bool preferred; struct zwlr_output_mode_v1* pMode; } WaylandZwlrMode; static void waylandZwlrModeSizeListener(void* data, FF_MAYBE_UNUSED struct zwlr_output_mode_v1 *zwlr_output_mode_v1, int32_t width, int32_t height) { WaylandZwlrMode* mode = (WaylandZwlrMode*) data; mode->width = width; mode->height = height; } static void waylandZwlrModeRefreshListener(void* data, FF_MAYBE_UNUSED struct zwlr_output_mode_v1 *zwlr_output_mode_v1, int32_t rate) { WaylandZwlrMode* mode = (WaylandZwlrMode*) data; mode->refreshRate = rate; } static void waylandZwlrModePreferredListener(void* data, FF_MAYBE_UNUSED struct zwlr_output_mode_v1 *zwlr_output_mode_v1) { WaylandZwlrMode* mode = (WaylandZwlrMode*) data; mode->preferred = true; } static const struct zwlr_output_mode_v1_listener modeListener = { .size = waylandZwlrModeSizeListener, .refresh = waylandZwlrModeRefreshListener, .preferred = waylandZwlrModePreferredListener, .finished = (void*) stubListener, }; static void waylandZwlrModeListener(void* data, FF_MAYBE_UNUSED struct zwlr_output_head_v1 *zwlr_output_head_v1, struct zwlr_output_mode_v1 *mode) { WaylandDisplay* wldata = (WaylandDisplay*) data; if (!wldata->internal) return; WaylandZwlrMode* newMode = ffListAdd((FFlist*) wldata->internal); *newMode = (WaylandZwlrMode) { .pMode = mode }; // Strangely, the listener is called only in this function, but not in `waylandZwlrCurrentModeListener` wldata->parent->ffwl_proxy_add_listener((struct wl_proxy *) mode, (void (**)(void)) &modeListener, newMode); } static void waylandZwlrCurrentModeListener(void* data, FF_MAYBE_UNUSED struct zwlr_output_head_v1 *zwlr_output_head_v1, struct zwlr_output_mode_v1 *mode) { // waylandZwlrModeListener is always run before this WaylandDisplay* wldata = (WaylandDisplay*) data; if (!wldata->internal) return; int set = 0; FF_LIST_FOR_EACH(WaylandZwlrMode, m, *(FFlist*) wldata->internal) { if (m->pMode == mode) { wldata->width = m->width; wldata->height = m->height; wldata->refreshRate = m->refreshRate; if (++set == 2) break; } if (m->preferred) { wldata->preferredWidth = m->width; wldata->preferredHeight = m->height; wldata->preferredRefreshRate = m->refreshRate; if (++set == 2) break; } } } static void waylandZwlrPhysicalSizeListener(void* data, FF_MAYBE_UNUSED struct zwlr_output_head_v1 *zwlr_output_head_v1, int32_t width, int32_t height) { WaylandDisplay* wldata = (WaylandDisplay*) data; wldata->physicalWidth = width; wldata->physicalHeight = height; } static void waylandZwlrEnabledListener(void* data, FF_MAYBE_UNUSED struct zwlr_output_head_v1 *zwlr_output_head_v1, bool enabled) { WaylandDisplay* wldata = (WaylandDisplay*) data; if (!enabled) wldata->internal = NULL; } static const struct zwlr_output_head_v1_listener headListener = { .name = (void*) ffWaylandOutputNameListener, .description = (void*) ffWaylandOutputDescriptionListener, .physical_size = waylandZwlrPhysicalSizeListener, .mode = waylandZwlrModeListener, .enabled = (void*) waylandZwlrEnabledListener, .current_mode = waylandZwlrCurrentModeListener, .position = (void*) stubListener, .transform = waylandZwlrTransformListener, .scale = waylandZwlrScaleListener, .finished = (void*) stubListener, .make = (void*) stubListener, .model = (void*) stubListener, .serial_number = (void*) stubListener, .adaptive_sync = (void*) stubListener, }; static void waylandHandleZwlrHead(void *data, FF_MAYBE_UNUSED struct zwlr_output_manager_v1 *zwlr_output_manager_v1, struct zwlr_output_head_v1 *head) { WaylandData* wldata = data; FF_LIST_AUTO_DESTROY modes = ffListCreate(sizeof(WaylandZwlrMode)); WaylandDisplay display = { .parent = wldata, .transform = WL_OUTPUT_TRANSFORM_NORMAL, .type = FF_DISPLAY_TYPE_UNKNOWN, .name = ffStrbufCreate(), .description = ffStrbufCreate(), .edidName = ffStrbufCreate(), .internal = &modes, }; wldata->ffwl_proxy_add_listener((struct wl_proxy*) head, (void(**)(void)) &headListener, &display); wldata->ffwl_display_roundtrip(wldata->display); if(display.width <= 0 || display.height <= 0 || !display.internal) return; uint32_t rotation = ffWaylandHandleRotation(&display); FFDisplayResult* item = ffdsAppendDisplay(wldata->result, (uint32_t) display.width, (uint32_t) display.height, display.refreshRate / 1000.0, (uint32_t) display.dpi, (uint32_t) display.preferredWidth, (uint32_t) display.preferredHeight, display.preferredRefreshRate / 1000.0, rotation, display.edidName.length ? &display.edidName : display.description.length && !ffStrbufContain(&display.description, &display.name) ? &display.description : &display.name, display.type, false, display.id, (uint32_t) display.physicalWidth, (uint32_t) display.physicalHeight, "wayland-zwlr" ); if (item) { if (display.hdrSupported) item->hdrStatus = FF_DISPLAY_HDR_STATUS_SUPPORTED; else if (display.hdrInfoAvailable) item->hdrStatus = FF_DISPLAY_HDR_STATUS_UNSUPPORTED; else item->hdrStatus = FF_DISPLAY_HDR_STATUS_UNKNOWN; item->manufactureYear = display.myear; item->manufactureWeek = display.mweek; item->serial = display.serial; } ffStrbufDestroy(&display.description); ffStrbufDestroy(&display.name); ffStrbufDestroy(&display.edidName); // These must be released manually FF_LIST_FOR_EACH(WaylandZwlrMode, m, modes) wldata->ffwl_proxy_destroy((void*) m->pMode); wldata->ffwl_proxy_destroy((void*) head); } 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); 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"; } if (wldata->ffwl_display_roundtrip(wldata->display) < 0) { wldata->ffwl_proxy_destroy(output); return "Failed to roundtrip display"; } wldata->ffwl_proxy_destroy(output); return NULL; } #endif ================================================ FILE: src/detection/displayserver/linux/wmde.c ================================================ #include "displayserver_linux.h" #include "common/io.h" #include "common/properties.h" #include "common/stringUtils.h" #include "common/mallocHelper.h" #include #include #include #if __FreeBSD__ #include #include #include #elif __OpenBSD__ #include #include #include #elif __sun #include #elif __NetBSD__ #include #include #endif static const char* parseEnv(void) { const char* env; env = getenv("XDG_CURRENT_DESKTOP"); if(ffStrSet(env)) return env; env = getenv("XDG_SESSION_DESKTOP"); if(ffStrSet(env)) return env; env = getenv("CURRENT_DESKTOP"); if(ffStrSet(env)) return env; env = getenv("SESSION_DESKTOP"); if(ffStrSet(env)) return env; env = getenv("DESKTOP_SESSION"); if(ffStrSet(env)) return env; if(getenv("KDE_FULL_SESSION") != NULL || getenv("KDE_SESSION_UID") != NULL || getenv("KDE_SESSION_VERSION") != NULL) return "KDE"; if(getenv("GNOME_DESKTOP_SESSION_ID") != NULL) return "GNOME"; if(getenv("MATE_DESKTOP_SESSION_ID") != NULL) return "Mate"; if(getenv("TDE_FULL_SESSION") != NULL) return "Trinity"; if(getenv("HYPRLAND_CMD") != NULL) return "Hyprland"; if(getenv("SWAYSOCK") != NULL) return "Sway"; #if __linux__ && !__ANDROID__ if( getenv("WAYLAND_DISPLAY") != NULL && ffPathExists("/mnt/wslg/", FF_PATHTYPE_DIRECTORY) ) return "WSLg"; #endif return NULL; } static void applyPrettyNameIfWM(FFDisplayServerResult* result, const char* name) { if(!ffStrSet(name)) return; if( ffStrEqualsIgnCase(name, "kwin") || ffStrStartsWithIgnCase(name, "kwin_") || ffStrEndsWithIgnCase(name, "-kwin_wayland") || ffStrEndsWithIgnCase(name, "-kwin_x11") ) ffStrbufSetS(&result->wmPrettyName, FF_WM_PRETTY_KWIN); else if( ffStrEqualsIgnCase(name, "gnome-shell") || ffStrEqualsIgnCase(name, "gnome shell") || ffStrEqualsIgnCase(name, "gnome-session-binary") || ffStrEqualsIgnCase(name, "Mutter") ) ffStrbufSetS(&result->wmPrettyName, FF_WM_PRETTY_MUTTER); else if( ffStrEqualsIgnCase(name, "cinnamon") || ffStrStartsWithIgnCase(name, "cinnamon-") || ffStrEqualsIgnCase(name, "Muffin") || ffStrEqualsIgnCase(name, "Mutter (Muffin)") ) ffStrbufSetS(&result->wmPrettyName, FF_WM_PRETTY_MUFFIN); else if(ffStrEqualsIgnCase(name, "sway")) ffStrbufSetS(&result->wmPrettyName, FF_WM_PRETTY_SWAY); else if(ffStrEqualsIgnCase(name, "weston")) ffStrbufSetS(&result->wmPrettyName, FF_WM_PRETTY_WESTON); else if(ffStrEqualsIgnCase(name, "wayfire")) ffStrbufSetS(&result->wmPrettyName, FF_WM_PRETTY_WAYFIRE); else if(ffStrEqualsIgnCase(name, "openbox")) ffStrbufSetS(&result->wmPrettyName, FF_WM_PRETTY_OPENBOX); else if(ffStrEqualsIgnCase(name, "xfwm4")) ffStrbufSetS(&result->wmPrettyName, FF_WM_PRETTY_XFWM4); else if(ffStrEqualsIgnCase(name, "Marco") || ffStrEqualsIgnCase(name, "Metacity (Marco)")) ffStrbufSetS(&result->wmPrettyName, FF_WM_PRETTY_MARCO); else if(ffStrEqualsIgnCase(name, "xmonad")) ffStrbufSetS(&result->wmPrettyName, FF_WM_PRETTY_XMONAD); else if(ffStrEqualsIgnCase(name, "WSLg")) ffStrbufSetS(&result->wmPrettyName, FF_WM_PRETTY_WSLG); else if(ffStrEqualsIgnCase(name, "dwm")) ffStrbufSetS(&result->wmPrettyName, FF_WM_PRETTY_DWM); else if(ffStrEqualsIgnCase(name, "bspwm")) ffStrbufSetS(&result->wmPrettyName, FF_WM_PRETTY_BSPWM); else if(ffStrEqualsIgnCase(name, "tinywm")) ffStrbufSetS(&result->wmPrettyName, FF_WM_PRETTY_TINYWM); else if(ffStrEqualsIgnCase(name, "qtile")) ffStrbufSetS(&result->wmPrettyName, FF_WM_PRETTY_QTILE); else if(ffStrEqualsIgnCase(name, "herbstluftwm")) ffStrbufSetS(&result->wmPrettyName, FF_WM_PRETTY_HERBSTLUFTWM); else if(ffStrEqualsIgnCase(name, "icewm")) ffStrbufSetS(&result->wmPrettyName, FF_WM_PRETTY_ICEWM); else if(ffStrEqualsIgnCase(name, "dtwm")) ffStrbufSetS(&result->wmPrettyName, FF_WM_PRETTY_DTWM); else if(ffStrEqualsIgnCase(name, "fvwm")) ffStrbufSetS(&result->wmPrettyName, FF_WM_PRETTY_FVWM); else if(ffStrEqualsIgnCase(name, "ctwm")) ffStrbufSetS(&result->wmPrettyName, FF_WM_PRETTY_CTWM); else if(ffStrEqualsIgnCase(name, "hyprland")) ffStrbufSetS(&result->wmPrettyName, FF_WM_PRETTY_HYPRLAND); else if(ffStrEqualsIgnCase(name, "ratpoison")) ffStrbufSetS(&result->wmPrettyName, FF_WM_PRETTY_RATPOISON); } static void applyNameIfWM(FFDisplayServerResult* result, const char* processName) { applyPrettyNameIfWM(result, processName); if(result->wmPrettyName.length > 0) ffStrbufSetS(&result->wmProcessName, processName); } static void applyBetterWM(FFDisplayServerResult* result, const char* processName) { if(!ffStrSet(processName)) return; ffStrbufSetS(&result->wmProcessName, processName); //If it is a known wm, this will set the pretty name applyPrettyNameIfWM(result, processName); //If it isn't a known wm, set the pretty name to the process name if(result->wmPrettyName.length == 0) ffStrbufAppend(&result->wmPrettyName, &result->wmProcessName); } static void applyPrettyNameIfDE(FFDisplayServerResult* result, const char* name) { if(!ffStrSet(name)) return; else if( ffStrEqualsIgnCase(name, "KDE") || ffStrEqualsIgnCase(name, "plasma") || ffStrEqualsIgnCase(name, "plasmashell") || ffStrEqualsIgnCase(name, "plasmawayland") ) { ffStrbufSetStatic(&result->deProcessName, "plasmashell"); ffStrbufSetStatic(&result->dePrettyName, FF_DE_PRETTY_PLASMA); applyBetterWM(result, getenv("KDEWM")); } else if( ffStrEqualsIgnCase(name, "GNOME") || ffStrEqualsIgnCase(name, "ubuntu:GNOME") || ffStrEqualsIgnCase(name, "ubuntu") || ffStrEqualsIgnCase(name, "gnome-shell") ) { ffStrbufSetStatic(&result->deProcessName, "gnome-shell"); const char* sessionMode = getenv("GNOME_SHELL_SESSION_MODE"); if (sessionMode && ffStrEquals(sessionMode, "classic")) ffStrbufSetStatic(&result->dePrettyName, FF_DE_PRETTY_GNOME_CLASSIC); else ffStrbufSetStatic(&result->dePrettyName, FF_DE_PRETTY_GNOME); } else if( ffStrEqualsIgnCase(name, "X-Cinnamon") || ffStrEqualsIgnCase(name, "Cinnamon") ) { ffStrbufSetS(&result->deProcessName, "cinnamon"); ffStrbufSetS(&result->dePrettyName, FF_DE_PRETTY_CINNAMON); } else if( ffStrEqualsIgnCase(name, "XFCE") || ffStrEqualsIgnCase(name, "X-XFCE") || ffStrEqualsIgnCase(name, "XFCE4") || ffStrEqualsIgnCase(name, "X-XFCE4") || ffStrEqualsIgnCase(name, "xfce4-session") ) { ffStrbufSetS(&result->deProcessName, "xfce4-session"); ffStrbufSetS(&result->dePrettyName, FF_DE_PRETTY_XFCE4); } else if( ffStrEqualsIgnCase(name, "MATE") || ffStrEqualsIgnCase(name, "X-MATE") || ffStrEqualsIgnCase(name, "mate-session") ) { ffStrbufSetS(&result->deProcessName, "mate-session"); ffStrbufSetS(&result->dePrettyName, FF_DE_PRETTY_MATE); } else if( ffStrEqualsIgnCase(name, "LXQt") || ffStrEqualsIgnCase(name, "X-LXQt") || ffStrEqualsIgnCase(name, "lxqt-session") ) { ffStrbufSetS(&result->deProcessName, "lxqt-session"); ffStrbufSetS(&result->dePrettyName, FF_DE_PRETTY_LXQT); if (result->wmProcessName.length == 0) { FF_STRBUF_AUTO_DESTROY wmProcessNameBuffer = ffStrbufCreate(); ffParsePropFileConfig("lxqt/session.conf", "window_manager =", &wmProcessNameBuffer); applyBetterWM(result, wmProcessNameBuffer.chars); } } else if( ffStrEqualsIgnCase(name, "Budgie") || ffStrEqualsIgnCase(name, "X-Budgie") || ffStrEqualsIgnCase(name, "budgie-desktop") || ffStrEqualsIgnCase(name, "Budgie:GNOME") ) { ffStrbufSetS(&result->deProcessName, "budgie-desktop"); ffStrbufSetS(&result->dePrettyName, FF_DE_PRETTY_BUDGIE); } else if( ffStrEqualsIgnCase(name, "dtsession") ) { ffStrbufSetS(&result->deProcessName, "dtsession"); ffStrbufSetS(&result->dePrettyName, FF_DE_PRETTY_CDE); } else if( ffStrEqualsIgnCase(name, "ukui-session") ) { ffStrbufSetS(&result->deProcessName, "ukui-session"); ffStrbufSetS(&result->dePrettyName, FF_DE_PRETTY_UKUI); } else if( ffStrStartsWithIgnCase(name, "Unity:Unity") ) { ffStrbufSetS(&result->deProcessName, "unity-session"); ffStrbufSetS(&result->dePrettyName, FF_DE_PRETTY_UNITY); } } static const char* getFromProcesses(FFDisplayServerResult* result) { uint32_t userId = instance.state.platform.uid; #if __FreeBSD__ #ifdef __DragonFly__ #define ki_comm kp_comm #endif int request[] = {CTL_KERN, KERN_PROC, KERN_PROC_UID, (int) userId}; size_t length = 0; if(sysctl(request, ARRAY_SIZE(request), NULL, &length, NULL, 0) != 0) return "sysctl({CTL_KERN, KERN_PROC, KERN_PROC_UID}, NULL) failed"; FF_AUTO_FREE struct kinfo_proc* procs = (struct kinfo_proc*) malloc(length); if(sysctl(request, ARRAY_SIZE(request), procs, &length, NULL, 0) != 0) return "sysctl({CTL_KERN, KERN_PROC, KERN_PROC_UID}, procs) failed"; length /= sizeof(*procs); for (struct kinfo_proc* proc = procs; proc < procs + length; ++proc) { if(result->dePrettyName.length == 0) applyPrettyNameIfDE(result, proc->ki_comm); if(result->wmPrettyName.length == 0) applyNameIfWM(result, proc->ki_comm); if(result->dePrettyName.length > 0 && result->wmPrettyName.length > 0) break; } #elif __OpenBSD__ kvm_t* kd = kvm_open(NULL, NULL, NULL, KVM_NO_FILES, NULL); int count = 0; const struct kinfo_proc* proc = kvm_getprocs(kd, KERN_PROC_UID, userId, sizeof(*proc), &count); if (proc) { for (int i = 0; i < count; ++i) { if(result->dePrettyName.length == 0) applyPrettyNameIfDE(result, proc[i].p_comm); if(result->wmPrettyName.length == 0) applyNameIfWM(result, proc[i].p_comm); if(result->dePrettyName.length > 0 && result->wmPrettyName.length > 0) break; } } kvm_close(kd); #elif __sun FF_AUTO_CLOSE_DIR DIR* procdir = opendir("/proc"); if(procdir == NULL) return "opendir(\"/proc\") failed"; FF_STRBUF_AUTO_DESTROY procPath = ffStrbufCreateA(64); ffStrbufAppendS(&procPath, "/proc/"); uint32_t procPathLength = procPath.length; struct dirent* dirent; while((dirent = readdir(procdir)) != NULL) { if (!ffCharIsDigit(dirent->d_name[0])) continue; ffStrbufAppendS(&procPath, dirent->d_name); ffStrbufAppendS(&procPath, "/psinfo"); psinfo_t proc; if (ffReadFileData(procPath.chars, sizeof(proc), &proc) == sizeof(proc)) { ffStrbufSubstrBefore(&procPath, procPathLength); if (proc.pr_uid != userId) continue; if(result->dePrettyName.length == 0) applyPrettyNameIfDE(result, proc.pr_fname); if(result->wmPrettyName.length == 0) applyNameIfWM(result, proc.pr_fname); if(result->dePrettyName.length > 0 && result->wmPrettyName.length > 0) break; } } #elif __linux__ || __GNU__ FF_AUTO_CLOSE_DIR DIR* procdir = opendir("/proc"); if(procdir == NULL) return "opendir(\"/proc\") failed"; FF_STRBUF_AUTO_DESTROY procPath = ffStrbufCreateA(64); ffStrbufAppendS(&procPath, "/proc/"); uint32_t procPathLength = procPath.length; FF_STRBUF_AUTO_DESTROY loginuid = ffStrbufCreate(); FF_STRBUF_AUTO_DESTROY processName = ffStrbufCreateA(256); //Some processes have large command lines (looking at you chrome) struct dirent* dirent; while((dirent = readdir(procdir)) != NULL) { //Match only folders starting with a number (the pid folders) if(dirent->d_type != DT_DIR || !ffCharIsDigit(dirent->d_name[0])) continue; ffStrbufAppendS(&procPath, dirent->d_name); uint32_t procFolderPathLength = procPath.length; //Don't check for processes not owned by the current user. ffStrbufAppendS(&procPath, "/loginuid"); ffReadFileBuffer(procPath.chars, &loginuid); if(ffStrbufToUInt(&loginuid, (uint64_t) -1) != userId) { ffStrbufSubstrBefore(&procPath, procPathLength); continue; } ffStrbufSubstrBefore(&procPath, procFolderPathLength); //We check the cmdline for the process name, because it is not trimmed. ffStrbufAppendS(&procPath, "/cmdline"); ffReadFileBuffer(procPath.chars, &processName); ffStrbufTrimRightSpace(&processName); ffStrbufSubstrBeforeFirstC(&processName, '\0'); //Trim the arguments ffStrbufSubstrAfterLastC(&processName, '/'); ffStrbufSubstrBefore(&procPath, procPathLength); if(result->dePrettyName.length == 0) applyPrettyNameIfDE(result, processName.chars); if(result->wmPrettyName.length == 0) applyNameIfWM(result, processName.chars); if(result->dePrettyName.length > 0 && result->wmPrettyName.length > 0) break; } #elif __NetBSD__ int request[] = {CTL_KERN, KERN_PROC2, KERN_PROC_UID, (int) userId, sizeof(struct kinfo_proc2), INT_MAX}; size_t size = 0; if(sysctl(request, ARRAY_SIZE(request), NULL, &size, NULL, 0) != 0) return "sysctl(KERN_PROC_UID, NULL) failed"; FF_AUTO_FREE struct kinfo_proc2* procs = malloc(size); if(sysctl(request, ARRAY_SIZE(request), procs, &size, NULL, 0) != 0) return "sysctl(KERN_PROC_UID, procs) failed"; for(struct kinfo_proc2* proc = procs; proc < procs + (size / sizeof(struct kinfo_proc2)); proc++) { if(result->dePrettyName.length == 0) applyPrettyNameIfDE(result, proc->p_comm); if(result->wmPrettyName.length == 0) applyNameIfWM(result, proc->p_comm); if(result->dePrettyName.length > 0 && result->wmPrettyName.length > 0) break; } #endif return NULL; } void ffdsDetectWMDE(FFDisplayServerResult* result) { #if __ANDROID__ if(ffStrbufIgnCaseEqualS(&result->wmProtocolName, FF_WM_PROTOCOL_SURFACEFLINGER)) return; // Only supported when connected to X11 #endif const char* env = parseEnv(); if(result->wmProcessName.length > 0) { //If we found the processName via display server, use it. //This will set the pretty name if it is a known WM, otherwise the prettyName to the processName applyPrettyNameIfWM(result, result->wmProcessName.chars); if(result->wmPrettyName.length == 0) ffStrbufSet(&result->wmPrettyName, &result->wmProcessName); } else { //if env is a known WM, use it applyNameIfWM(result, env); } //Connecting to a display server only gives WM results, not DE results. //If we find it in the environment, use that. applyPrettyNameIfDE(result, env); //If WM was found by connection to the sever, and DE in the environment, we can return //This way we never call getFromProcDir(), which has slow initialization time if(result->dePrettyName.length > 0 && result->wmPrettyName.length > 0) return; //Get missing WM / DE from processes. getFromProcesses(result); //Return if both wm and de are set, or if env doesn't contain anything if( (result->wmPrettyName.length > 0 && result->dePrettyName.length > 0) || !ffStrSet(env) ) return; //If nothing is set, use env as WM else if(result->wmPrettyName.length == 0 && result->dePrettyName.length == 0) { ffStrbufSetS(&result->wmProcessName, env); ffStrbufSetS(&result->wmPrettyName, env); } //If only WM is not set, and DE doesn't equal env, use env as WM else if( result->wmPrettyName.length == 0 && ffStrbufIgnCaseCompS(&result->deProcessName, env) != 0 && ffStrbufIgnCaseCompS(&result->dePrettyName, env) != 0 ) { ffStrbufSetS(&result->wmProcessName, env); ffStrbufSetS(&result->wmPrettyName, env); } //If only DE is not set, and WM doesn't equal env, use env as DE else if( result->dePrettyName.length == 0 && ffStrbufIgnCaseCompS(&result->wmProcessName, env) != 0 && ffStrbufIgnCaseCompS(&result->wmPrettyName, env) != 0 ) { ffStrbufSetS(&result->deProcessName, env); ffStrbufSetS(&result->dePrettyName, env); } } ================================================ FILE: src/detection/displayserver/linux/xcb.c ================================================ #include "displayserver_linux.h" #ifdef FF_HAVE_XCB_RANDR #include "common/library.h" #include "common/properties.h" #include "common/edidHelper.h" #include "common/mallocHelper.h" #include "common/stringUtils.h" #include #include #include #include typedef struct XcbRandrData { FF_LIBRARY_SYMBOL(xcb_randr_get_screen_resources_current) FF_LIBRARY_SYMBOL(xcb_randr_get_screen_resources_current_reply) FF_LIBRARY_SYMBOL(xcb_randr_get_screen_resources_current_modes_iterator) FF_LIBRARY_SYMBOL(xcb_randr_mode_info_next) FF_LIBRARY_SYMBOL(xcb_randr_get_monitors) FF_LIBRARY_SYMBOL(xcb_randr_get_monitors_reply) FF_LIBRARY_SYMBOL(xcb_randr_get_monitors_monitors_iterator) FF_LIBRARY_SYMBOL(xcb_randr_monitor_info_next) FF_LIBRARY_SYMBOL(xcb_randr_monitor_info_outputs_length) FF_LIBRARY_SYMBOL(xcb_randr_monitor_info_outputs) FF_LIBRARY_SYMBOL(xcb_randr_output_next) FF_LIBRARY_SYMBOL(xcb_randr_get_output_info) FF_LIBRARY_SYMBOL(xcb_randr_get_output_info_reply) FF_LIBRARY_SYMBOL(xcb_randr_get_crtc_info) FF_LIBRARY_SYMBOL(xcb_randr_get_crtc_info_reply) FF_LIBRARY_SYMBOL(xcb_randr_get_output_property) FF_LIBRARY_SYMBOL(xcb_randr_get_output_property_reply) FF_LIBRARY_SYMBOL(xcb_randr_get_output_property_data) FF_LIBRARY_SYMBOL(xcb_randr_get_output_property_data_length) FF_LIBRARY_SYMBOL(xcb_intern_atom) FF_LIBRARY_SYMBOL(xcb_intern_atom_reply) FF_LIBRARY_SYMBOL(xcb_get_property) FF_LIBRARY_SYMBOL(xcb_get_property_reply) FF_LIBRARY_SYMBOL(xcb_get_property_value) FF_LIBRARY_SYMBOL(xcb_get_property_value_length) FF_LIBRARY_SYMBOL(xcb_get_atom_name) FF_LIBRARY_SYMBOL(xcb_get_atom_name_name) FF_LIBRARY_SYMBOL(xcb_get_atom_name_name_length) FF_LIBRARY_SYMBOL(xcb_get_atom_name_reply) FF_LIBRARY_SYMBOL(xcb_get_setup) FF_LIBRARY_SYMBOL(xcb_setup_vendor) FF_LIBRARY_SYMBOL(xcb_setup_vendor_length) //init once xcb_connection_t* connection; FFDisplayServerResult* result; } XcbRandrData; static void* xcbGetProperty(XcbRandrData* data, xcb_window_t window, const char* request) { xcb_intern_atom_cookie_t requestAtomCookie = data->ffxcb_intern_atom(data->connection, true, (uint16_t) strlen(request), request); FF_AUTO_FREE xcb_intern_atom_reply_t* requestAtomReply = data->ffxcb_intern_atom_reply(data->connection, requestAtomCookie, NULL); if(requestAtomReply == NULL) return NULL; xcb_get_property_cookie_t propertyCookie = data->ffxcb_get_property(data->connection, false, window, requestAtomReply->atom, XCB_ATOM_ANY, 0, 8 * 1024); FF_AUTO_FREE xcb_get_property_reply_t* propertyReply = data->ffxcb_get_property_reply(data->connection, propertyCookie, NULL); if(propertyReply == NULL) return NULL; int length = data->ffxcb_get_property_value_length(propertyReply); if(length <= 0) return NULL; //Why are xcb property strings not null terminated??? void* replyValue = malloc((size_t)length + 1); memcpy(replyValue, data->ffxcb_get_property_value(propertyReply), (size_t) length); ((char*) replyValue)[length] = '\0'; return replyValue; } static xcb_randr_get_output_property_reply_t* xcbRandrGetProperty(XcbRandrData* data, xcb_randr_output_t output, const char* name) { xcb_intern_atom_cookie_t requestAtomCookie = data->ffxcb_intern_atom(data->connection, true, (uint16_t) strlen(name), name); FF_AUTO_FREE xcb_intern_atom_reply_t* requestAtomReply = data->ffxcb_intern_atom_reply(data->connection, requestAtomCookie, NULL); if(requestAtomReply) { xcb_randr_get_output_property_cookie_t outputPropertyCookie = data->ffxcb_randr_get_output_property(data->connection, output, requestAtomReply->atom, XCB_GET_PROPERTY_TYPE_ANY, 0, 100, false, false); return data->ffxcb_randr_get_output_property_reply(data->connection, outputPropertyCookie, NULL); } return NULL; } static void xcbDetectWMfromEWMH(XcbRandrData* data, xcb_window_t rootWindow, FFDisplayServerResult* result) { if(result->wmProcessName.length > 0 || ffStrbufEqualS(&result->wmProtocolName, FF_WM_PROTOCOL_WAYLAND)) return; FF_AUTO_FREE xcb_window_t* wmWindow = (xcb_window_t*) xcbGetProperty(data, rootWindow, "_NET_SUPPORTING_WM_CHECK"); if(wmWindow == NULL) return; FF_AUTO_FREE char* wmName = (char*) xcbGetProperty(data, *wmWindow, "WM_NAME"); if(!ffStrSet(wmName)) wmName = (char*) xcbGetProperty(data, *wmWindow, "_NET_WM_NAME"); if(!ffStrSet(wmName)) return; ffStrbufSetS(&result->wmProcessName, wmName); } static void xcbFetchServerVendor(XcbRandrData* data, FFDisplayServerResult* result) { const xcb_setup_t* setup = data->ffxcb_get_setup(data->connection); int length = data->ffxcb_setup_vendor_length(setup); if(length <= 0) return; FF_STRBUF_AUTO_DESTROY serverVendor = ffStrbufCreateNS((uint32_t) length, data->ffxcb_setup_vendor(setup)); if (!ffStrbufEqualS(&serverVendor, "The X.Org Foundation")) // Original { ffStrbufDestroy(&result->wmProtocolName); ffStrbufInitMove(&result->wmProtocolName, &serverVendor); } } static bool xcbRandrHandleOutput(XcbRandrData* data, xcb_randr_output_t output, FFstrbuf* name, bool primary, FFDisplayType displayType, struct xcb_randr_get_screen_resources_current_reply_t* screenResources, uint8_t bitDepth, uint32_t dpi) { xcb_randr_get_output_info_cookie_t outputInfoCookie = data->ffxcb_randr_get_output_info(data->connection, output, XCB_CURRENT_TIME); FF_AUTO_FREE xcb_randr_get_output_info_reply_t* outputInfoReply = data->ffxcb_randr_get_output_info_reply(data->connection, outputInfoCookie, NULL); if(outputInfoReply == NULL) return false; FF_AUTO_FREE xcb_randr_get_output_property_reply_t* edidReply = xcbRandrGetProperty(data, output, "EDID"); uint8_t* edidData = NULL; uint32_t edidLength = 0; if(edidReply) { int len = data->ffxcb_randr_get_output_property_data_length(edidReply); if(len >= 128) { edidData = data->ffxcb_randr_get_output_property_data(edidReply); edidLength = (uint32_t) len; } } if(edidData) { ffStrbufClear(name); ffEdidGetName(edidData, name); } bool randrEmulation = false; FF_AUTO_FREE xcb_randr_get_output_property_reply_t* randrEmulationReply = xcbRandrGetProperty(data, output, "RANDR Emulation"); if(randrEmulationReply) { int len = data->ffxcb_randr_get_output_property_data_length(randrEmulationReply); if(len >= 1) randrEmulation = !!data->ffxcb_randr_get_output_property_data(randrEmulationReply)[0]; } xcb_randr_get_crtc_info_cookie_t crtcInfoCookie = data->ffxcb_randr_get_crtc_info(data->connection, outputInfoReply->crtc, XCB_CURRENT_TIME); FF_AUTO_FREE xcb_randr_get_crtc_info_reply_t* crtcInfoReply = data->ffxcb_randr_get_crtc_info_reply(data->connection, crtcInfoCookie, NULL); if(crtcInfoReply == NULL) return false; uint32_t rotation; switch (crtcInfoReply->rotation) { case XCB_RANDR_ROTATION_ROTATE_90: rotation = 90; break; case XCB_RANDR_ROTATION_ROTATE_180: rotation = 180; break; case XCB_RANDR_ROTATION_ROTATE_270: rotation = 270; break; default: rotation = 0; break; } xcb_randr_mode_info_t* currentMode = NULL; xcb_randr_mode_info_t* preferredMode = NULL; if(screenResources) { xcb_randr_mode_info_iterator_t modesIterator = data->ffxcb_randr_get_screen_resources_current_modes_iterator(screenResources); if (outputInfoReply->num_preferred > 0) preferredMode = modesIterator.data; while (modesIterator.rem > 0) { if (modesIterator.data->id == crtcInfoReply->mode) { currentMode = modesIterator.data; break; } data->ffxcb_randr_mode_info_next(&modesIterator); } } FFDisplayResult* item = ffdsAppendDisplay( data->result, (uint32_t) (currentMode ? currentMode->width : crtcInfoReply->width), (uint32_t) (currentMode ? currentMode->height : crtcInfoReply->height), currentMode ? (double) currentMode->dot_clock / (double) ((uint32_t) currentMode->htotal * currentMode->vtotal) : 0, dpi, preferredMode ? (uint32_t) preferredMode->width : 0, preferredMode ? (uint32_t) preferredMode->height : 0, preferredMode ? (double) preferredMode->dot_clock / (double) ((uint32_t) preferredMode->htotal * preferredMode->vtotal) : 0, rotation, name, displayType, primary, 0, (uint32_t) outputInfoReply->mm_width, (uint32_t) outputInfoReply->mm_height, randrEmulation ? (currentMode ? "xcb-randr-emu-mode" : "xcb-randr-emu-crtc") : (currentMode ? "xcb-randr-mode" : "xcb-randr-crtc") ); if (item) { if (edidData && edidLength >= 128) { item->hdrStatus = ffEdidGetHdrCompatible(edidData, (uint32_t) edidLength) ? FF_DISPLAY_HDR_STATUS_SUPPORTED : FF_DISPLAY_HDR_STATUS_UNSUPPORTED; ffEdidGetSerialAndManufactureDate(edidData, &item->serial, &item->manufactureYear, &item->manufactureWeek); } item->bitDepth = bitDepth; if ((rotation == 90 || rotation == 180) && !randrEmulation) { // In XWayland mode, width / height has been swapped out of box uint32_t tmp = item->width; item->width = item->height; item->height = tmp; } } return !!item; } static bool xcbRandrHandleMonitor(XcbRandrData* data, xcb_randr_monitor_info_t* monitor, struct xcb_randr_get_screen_resources_current_reply_t* screenResources, uint8_t bitDepth, uint32_t dpi) { //for some reasons, we have to construct this our self xcb_randr_output_iterator_t outputIterator = { .index = 0, .data = data->ffxcb_randr_monitor_info_outputs(monitor), .rem = data->ffxcb_randr_monitor_info_outputs_length(monitor) }; FF_AUTO_FREE xcb_get_atom_name_reply_t* nameReply = data->ffxcb_get_atom_name_reply( data->connection, data->ffxcb_get_atom_name(data->connection, monitor->name), NULL ); FF_STRBUF_AUTO_DESTROY name = ffStrbufCreateNS( (uint32_t) data->ffxcb_get_atom_name_name_length(nameReply), data->ffxcb_get_atom_name_name(nameReply) ); const FFDisplayType displayType = ffdsGetDisplayType(name.chars); bool foundOutput = false; while(outputIterator.rem > 0) { if(xcbRandrHandleOutput(data, *outputIterator.data, &name, monitor->primary, displayType, screenResources, bitDepth, dpi)) foundOutput = true; data->ffxcb_randr_output_next(&outputIterator); } if (foundOutput) return true; FFDisplayResult* display = ffdsAppendDisplay( data->result, (uint32_t) monitor->width, (uint32_t) monitor->height, 0, dpi, 0, 0, 0, 0, &name, displayType, !!monitor->primary, 0, (uint32_t) monitor->width_in_millimeters, (uint32_t) monitor->height_in_millimeters, "xcb-randr-monitor" ); if (display) display->bitDepth = bitDepth; return !!display; } static bool xcbRandrHandleMonitors(XcbRandrData* data, xcb_screen_t* screen) { xcb_randr_get_monitors_cookie_t monitorsCookie = data->ffxcb_randr_get_monitors(data->connection, screen->root, true); FF_AUTO_FREE xcb_randr_get_monitors_reply_t* monitorsReply = data->ffxcb_randr_get_monitors_reply(data->connection, monitorsCookie, NULL); if(monitorsReply == NULL) return false; //Init screen resources. They are used to iterate over all modes. xcbRandrHandleMode checks for " == NULL", to fail as late as possible. xcb_randr_get_screen_resources_current_cookie_t screenResourcesCookie = data->ffxcb_randr_get_screen_resources_current(data->connection, screen->root); FF_AUTO_FREE struct xcb_randr_get_screen_resources_current_reply_t* screenResources = data->ffxcb_randr_get_screen_resources_current_reply(data->connection, screenResourcesCookie, NULL); uint32_t dpi = 0; FF_AUTO_FREE const char* resourceManager = xcbGetProperty(data, screen->root, "RESOURCE_MANAGER"); if (resourceManager) { FF_STRBUF_AUTO_DESTROY dpiStr = ffStrbufCreate(); if (ffParsePropLines(resourceManager, "Xft.dpi:", &dpiStr)) dpi = (uint32_t) ffStrbufToUInt(&dpiStr, 96); } uint8_t bitDepth = (uint8_t) (screen->root_depth / 3); xcb_randr_monitor_info_iterator_t monitorInfoIterator = data->ffxcb_randr_get_monitors_monitors_iterator(monitorsReply); bool foundMonitor = false; while(monitorInfoIterator.rem > 0) { if(xcbRandrHandleMonitor(data, monitorInfoIterator.data, screenResources, bitDepth, dpi)) foundMonitor = true; data->ffxcb_randr_monitor_info_next(&monitorInfoIterator); } return foundMonitor; } static void xcbRandrHandleScreen(XcbRandrData* data, xcb_screen_t* screen) { //With all the initialisation done, start the detection if(xcbRandrHandleMonitors(data, screen)) return; //If detetction failed, fallback to screen = monitor, like in the libxcb.so implementation ffdsAppendDisplay( data->result, (uint32_t) screen->width_in_pixels, (uint32_t) screen->height_in_pixels, 0, 0, 0, 0, 0, 0, NULL, FF_DISPLAY_TYPE_UNKNOWN, false, (uint64_t) screen->root, (uint32_t) screen->width_in_millimeters, (uint32_t) screen->height_in_millimeters, "xcb-randr-screen" ); } const char* ffdsConnectXcbRandr(FFDisplayServerResult* result) { FF_LIBRARY_LOAD_MESSAGE(xcbRandr, "libxcb-randr" FF_LIBRARY_EXTENSION, 1) FF_LIBRARY_LOAD_SYMBOL_MESSAGE(xcbRandr, xcb_connect) FF_LIBRARY_LOAD_SYMBOL_MESSAGE(xcbRandr, xcb_connection_has_error) FF_LIBRARY_LOAD_SYMBOL_MESSAGE(xcbRandr, xcb_get_setup) FF_LIBRARY_LOAD_SYMBOL_MESSAGE(xcbRandr, xcb_setup_roots_iterator) FF_LIBRARY_LOAD_SYMBOL_MESSAGE(xcbRandr, xcb_screen_next) FF_LIBRARY_LOAD_SYMBOL_MESSAGE(xcbRandr, xcb_disconnect) XcbRandrData data; FF_LIBRARY_LOAD_SYMBOL_VAR_MESSAGE(xcbRandr, data, xcb_intern_atom) FF_LIBRARY_LOAD_SYMBOL_VAR_MESSAGE(xcbRandr, data, xcb_intern_atom_reply) FF_LIBRARY_LOAD_SYMBOL_VAR_MESSAGE(xcbRandr, data, xcb_get_property) FF_LIBRARY_LOAD_SYMBOL_VAR_MESSAGE(xcbRandr, data, xcb_get_property_reply) FF_LIBRARY_LOAD_SYMBOL_VAR_MESSAGE(xcbRandr, data, xcb_get_property_value) FF_LIBRARY_LOAD_SYMBOL_VAR_MESSAGE(xcbRandr, data, xcb_get_property_value_length) FF_LIBRARY_LOAD_SYMBOL_VAR_MESSAGE(xcbRandr, data, xcb_get_atom_name) FF_LIBRARY_LOAD_SYMBOL_VAR_MESSAGE(xcbRandr, data, xcb_get_atom_name_name) FF_LIBRARY_LOAD_SYMBOL_VAR_MESSAGE(xcbRandr, data, xcb_get_atom_name_name_length) FF_LIBRARY_LOAD_SYMBOL_VAR_MESSAGE(xcbRandr, data, xcb_get_atom_name_reply) FF_LIBRARY_LOAD_SYMBOL_VAR_MESSAGE(xcbRandr, data, xcb_get_setup) FF_LIBRARY_LOAD_SYMBOL_VAR_MESSAGE(xcbRandr, data, xcb_setup_vendor) FF_LIBRARY_LOAD_SYMBOL_VAR_MESSAGE(xcbRandr, data, xcb_setup_vendor_length) FF_LIBRARY_LOAD_SYMBOL_VAR_MESSAGE(xcbRandr, data, xcb_randr_get_screen_resources_current) FF_LIBRARY_LOAD_SYMBOL_VAR_MESSAGE(xcbRandr, data, xcb_randr_get_screen_resources_current_reply) FF_LIBRARY_LOAD_SYMBOL_VAR_MESSAGE(xcbRandr, data, xcb_randr_get_screen_resources_current_modes_iterator) FF_LIBRARY_LOAD_SYMBOL_VAR_MESSAGE(xcbRandr, data, xcb_randr_mode_info_next) FF_LIBRARY_LOAD_SYMBOL_VAR_MESSAGE(xcbRandr, data, xcb_randr_get_monitors) FF_LIBRARY_LOAD_SYMBOL_VAR_MESSAGE(xcbRandr, data, xcb_randr_get_monitors_reply) FF_LIBRARY_LOAD_SYMBOL_VAR_MESSAGE(xcbRandr, data, xcb_randr_get_monitors_monitors_iterator) FF_LIBRARY_LOAD_SYMBOL_VAR_MESSAGE(xcbRandr, data, xcb_randr_monitor_info_next) FF_LIBRARY_LOAD_SYMBOL_VAR_MESSAGE(xcbRandr, data, xcb_randr_monitor_info_outputs_length) FF_LIBRARY_LOAD_SYMBOL_VAR_MESSAGE(xcbRandr, data, xcb_randr_monitor_info_outputs) FF_LIBRARY_LOAD_SYMBOL_VAR_MESSAGE(xcbRandr, data, xcb_randr_output_next) FF_LIBRARY_LOAD_SYMBOL_VAR_MESSAGE(xcbRandr, data, xcb_randr_get_output_info) FF_LIBRARY_LOAD_SYMBOL_VAR_MESSAGE(xcbRandr, data, xcb_randr_get_output_info_reply) FF_LIBRARY_LOAD_SYMBOL_VAR_MESSAGE(xcbRandr, data, xcb_randr_get_output_property) FF_LIBRARY_LOAD_SYMBOL_VAR_MESSAGE(xcbRandr, data, xcb_randr_get_output_property_reply) FF_LIBRARY_LOAD_SYMBOL_VAR_MESSAGE(xcbRandr, data, xcb_randr_get_output_property_data) FF_LIBRARY_LOAD_SYMBOL_VAR_MESSAGE(xcbRandr, data, xcb_randr_get_output_property_data_length) FF_LIBRARY_LOAD_SYMBOL_VAR_MESSAGE(xcbRandr, data, xcb_randr_get_crtc_info) FF_LIBRARY_LOAD_SYMBOL_VAR_MESSAGE(xcbRandr, data, xcb_randr_get_crtc_info_reply) data.connection = ffxcb_connect(NULL, NULL); if(ffxcb_connection_has_error(data.connection) > 0) { ffxcb_disconnect(data.connection); return "xcb_connect() failed"; } data.result = result; xcb_screen_iterator_t iterator = ffxcb_setup_roots_iterator(ffxcb_get_setup(data.connection)); if(iterator.rem > 0) { xcbDetectWMfromEWMH(&data, iterator.data->root, result); xcbFetchServerVendor(&data, result); } while(iterator.rem > 0) { xcbRandrHandleScreen(&data, iterator.data); ffxcb_screen_next(&iterator); } ffxcb_disconnect(data.connection); //If wayland hasn't set this, connection failed for it. So we are running only a X Server, not XWayland. if(result->wmProtocolName.length == 0) ffStrbufSetS(&result->wmProtocolName, FF_WM_PROTOCOL_X11); return NULL; } #else const char* ffdsConnectXcbRandr(FFDisplayServerResult* result) { //Do nothing. There are other implementations coming FF_UNUSED(result) return "Fastfetch was compiled without libxcb-randr support"; } #endif ================================================ FILE: src/detection/displayserver/linux/xlib.c ================================================ #include "displayserver_linux.h" #ifdef FF_HAVE_XRANDR #include "common/library.h" #include "common/properties.h" #include "common/edidHelper.h" #include "common/stringUtils.h" #include #include typedef struct XrandrData { FF_LIBRARY_SYMBOL(XInternAtom) FF_LIBRARY_SYMBOL(XGetAtomName) FF_LIBRARY_SYMBOL(XGetWindowProperty) FF_LIBRARY_SYMBOL(XServerVendor) FF_LIBRARY_SYMBOL(XFree) FF_LIBRARY_SYMBOL(XRRGetMonitors) FF_LIBRARY_SYMBOL(XRRGetScreenResourcesCurrent) FF_LIBRARY_SYMBOL(XRRGetOutputInfo) FF_LIBRARY_SYMBOL(XRRGetOutputProperty) FF_LIBRARY_SYMBOL(XRRGetCrtcInfo) FF_LIBRARY_SYMBOL(XRRFreeCrtcInfo) FF_LIBRARY_SYMBOL(XRRFreeOutputInfo) FF_LIBRARY_SYMBOL(XRRFreeScreenResources) FF_LIBRARY_SYMBOL(XRRFreeMonitors) //Init once Display* display; FFDisplayServerResult* result; } XrandrData; static unsigned char* x11GetProperty(XrandrData* data, Display* display, Window window, const char* request) { Atom requestAtom = data->ffXInternAtom(display, request, False); if(requestAtom == None) return NULL; Atom actualType; unsigned long unused; unsigned char* result = NULL; if(data->ffXGetWindowProperty(display, window, requestAtom, 0, 64, False, AnyPropertyType, &actualType, (int*) &unused, &unused, &unused, &result) != Success) return NULL; return result; } static uint8_t* xrandrGetProperty(XrandrData* data, RROutput output, const char* name, uint32_t* bufSize) { unsigned long size = 0; uint8_t* result = NULL; Atom atomEdid = data->ffXInternAtom(data->display, name, true); if (atomEdid != None) { int actual_format = 0; unsigned long bytes_after = 0; Atom actual_type = None; if (data->ffXRRGetOutputProperty(data->display, output, atomEdid, 0, 100, false, false, AnyPropertyType, &actual_type, &actual_format, &size, &bytes_after, &result) == Success) { if (size == 0) data->ffXFree(result); else { if (bufSize) *bufSize = (uint32_t) size; return result; } } } return NULL; } static void x11DetectWMFromEWMH(XrandrData* data, FFDisplayServerResult* result) { if(result->wmProcessName.length > 0 || ffStrbufEqualS(&result->wmProtocolName, FF_WM_PROTOCOL_WAYLAND)) return; Window* wmWindow = (Window*) x11GetProperty(data, data->display, DefaultRootWindow(data->display), "_NET_SUPPORTING_WM_CHECK"); if(wmWindow == NULL) return; char* wmName = (char*) x11GetProperty(data, data->display, *wmWindow, "WM_NAME"); if(!ffStrSet(wmName)) wmName = (char*) x11GetProperty(data, data->display, *wmWindow, "_NET_WM_NAME"); if(ffStrSet(wmName)) ffStrbufSetS(&result->wmProcessName, wmName); data->ffXFree(wmName); data->ffXFree(wmWindow); } static void x11FetchServerVendor(XrandrData* data, FFDisplayServerResult* result) { const char* serverVendor = data->ffXServerVendor(data->display); if (serverVendor && !ffStrEquals(serverVendor, "The X.Org Foundation")) ffStrbufSetS(&result->wmProtocolName, serverVendor); } static bool xrandrHandleCrtc(XrandrData* data, XRROutputInfo* output, FFstrbuf* name, bool primary, FFDisplayType displayType, uint8_t* edidData, uint32_t edidLength, XRRScreenResources* screenResources, uint8_t bitDepth, uint32_t dpi, bool randrEmulation) { //We do the check here, because we want the best fallback display if this call failed if(screenResources == NULL) return false; XRRCrtcInfo* crtcInfo = data->ffXRRGetCrtcInfo(data->display, screenResources, output->crtc); if(crtcInfo == NULL) return false; uint32_t rotation; switch (crtcInfo->rotation) { case RR_Rotate_90: rotation = 90; break; case RR_Rotate_180: rotation = 180; break; case RR_Rotate_270: rotation = 270; break; default: rotation = 0; break; } XRRModeInfo* currentMode = NULL; for(int i = 0; i < screenResources->nmode; i++) { if(screenResources->modes[i].id == crtcInfo->mode) { currentMode = &screenResources->modes[i]; break; } } XRRModeInfo* preferredMode = output->npreferred > 0 ? &screenResources->modes[0] : NULL; FFDisplayResult* item = ffdsAppendDisplay( data->result, (uint32_t) (currentMode ? currentMode->width : crtcInfo->width), (uint32_t) (currentMode ? currentMode->height : crtcInfo->height), currentMode ? (double) currentMode->dotClock / (double) ((uint32_t) currentMode->hTotal * currentMode->vTotal) : 0, dpi, preferredMode ? (uint32_t) preferredMode->width : 0, preferredMode ? (uint32_t) preferredMode->height : 0, preferredMode ? (double) preferredMode->dotClock / (double) ((uint32_t) preferredMode->hTotal * preferredMode->vTotal) : 0, rotation, name, displayType, primary, 0, (uint32_t) output->mm_width, (uint32_t) output->mm_height, randrEmulation ? (currentMode ? "xlib-randr-emu-mode" : "xlib-randr-emu-crtc") : (currentMode ? "xlib-randr-mode" : "xlib-randr-crtc") ); if (item) { if (edidLength) { item->hdrStatus = ffEdidGetHdrCompatible(edidData, edidLength) ? FF_DISPLAY_HDR_STATUS_SUPPORTED : FF_DISPLAY_HDR_STATUS_UNSUPPORTED; ffEdidGetSerialAndManufactureDate(edidData, &item->serial, &item->manufactureYear, &item->manufactureWeek); } item->bitDepth = bitDepth; if ((rotation == 90 || rotation == 180) && !randrEmulation) { // In XWayland mode, width / height has been swapped out of box uint32_t tmp = item->width; item->width = item->height; item->height = tmp; } } data->ffXRRFreeCrtcInfo(crtcInfo); return !!item; } static bool xrandrHandleOutput(XrandrData* data, RROutput output, FFstrbuf* name, bool primary, FFDisplayType displayType, XRRScreenResources* screenResources, uint8_t bitDepth, uint32_t dpi) { XRROutputInfo* outputInfo = data->ffXRRGetOutputInfo(data->display, screenResources, output); if(outputInfo == NULL) return false; uint32_t edidLength = 0; uint8_t* edidData = xrandrGetProperty(data, output, RR_PROPERTY_RANDR_EDID, &edidLength); if (edidLength >= 128) { ffStrbufClear(name); ffEdidGetName(edidData, name); } else edidLength = 0; uint8_t* randrEmulation = xrandrGetProperty(data, output, "RANDR Emulation", NULL); bool res = xrandrHandleCrtc(data, outputInfo, name, primary, displayType, edidData, edidLength, screenResources, bitDepth, dpi, randrEmulation ? !!randrEmulation[0] : false); if (edidData) data->ffXFree(edidData); if (randrEmulation) data->ffXFree(randrEmulation); data->ffXRRFreeOutputInfo(outputInfo); return res; } static bool xrandrHandleMonitor(XrandrData* data, XRRMonitorInfo* monitorInfo, XRRScreenResources* screenResources, uint8_t bitDepth, uint32_t dpi) { bool foundOutput = false; char* xname = data->ffXGetAtomName(data->display, monitorInfo->name); FF_STRBUF_AUTO_DESTROY name = ffStrbufCreateS(xname); data->ffXFree(xname); FFDisplayType displayType = ffdsGetDisplayType(name.chars); for(int i = 0; i < monitorInfo->noutput; i++) { if(xrandrHandleOutput(data, monitorInfo->outputs[i], &name, monitorInfo->primary, displayType, screenResources, bitDepth, dpi)) foundOutput = true; } if (foundOutput) return true; FFDisplayResult* display = ffdsAppendDisplay( data->result, (uint32_t) monitorInfo->width, (uint32_t) monitorInfo->height, 0, dpi, 0, 0, 0, 0, &name, displayType, !!monitorInfo->primary, 0, (uint32_t) monitorInfo->mwidth, (uint32_t) monitorInfo->mheight, "xlib-randr-monitor" ); if (display) display->bitDepth = bitDepth; return !!display; } static bool xrandrHandleMonitors(XrandrData* data, Screen* screen) { int numberOfMonitors; XRRMonitorInfo* monitorInfos = data->ffXRRGetMonitors(data->display, RootWindowOfScreen(screen), True, &numberOfMonitors); if(monitorInfos == NULL) return false; XRRScreenResources* screenResources = data->ffXRRGetScreenResourcesCurrent(data->display, RootWindowOfScreen(screen)); uint32_t dpi = 1; char* resourceManager = (char*) x11GetProperty(data, data->display, screen->root, "RESOURCE_MANAGER"); if (resourceManager) { FF_STRBUF_AUTO_DESTROY dpiStr = ffStrbufCreate(); if (ffParsePropLines(resourceManager, "Xft.dpi:", &dpiStr)) dpi = (uint32_t) ffStrbufToUInt(&dpiStr, 96); data->ffXFree(resourceManager); } uint8_t bitDepth = (uint8_t) (screen->root_depth / 3); bool foundAMonitor = false; for(int i = 0; i < numberOfMonitors; i++) { if(xrandrHandleMonitor(data, &monitorInfos[i], screenResources, bitDepth, dpi)) foundAMonitor = true; } data->ffXRRFreeMonitors(monitorInfos); data->ffXRRFreeScreenResources(screenResources); return foundAMonitor; } static void xrandrHandleScreen(XrandrData* data, Screen* screen) { if(xrandrHandleMonitors(data, screen)) return; //Fallback to screen ffdsAppendDisplay( data->result, (uint32_t) WidthOfScreen(screen), (uint32_t) HeightOfScreen(screen), 0, 0, 0, 0, 0, 0, NULL, FF_DISPLAY_TYPE_UNKNOWN, false, RootWindowOfScreen(screen), (uint32_t) WidthMMOfScreen(screen), (uint32_t) HeightMMOfScreen(screen), "xlib-randr-screen" ); } const char* ffdsConnectXrandr(FFDisplayServerResult* result) { FF_LIBRARY_LOAD_MESSAGE(xrandr, "libXrandr" FF_LIBRARY_EXTENSION, 3) FF_LIBRARY_LOAD_SYMBOL_MESSAGE(xrandr, XOpenDisplay) FF_LIBRARY_LOAD_SYMBOL_MESSAGE(xrandr, XCloseDisplay) XrandrData data; FF_LIBRARY_LOAD_SYMBOL_VAR_MESSAGE(xrandr, data, XInternAtom); FF_LIBRARY_LOAD_SYMBOL_VAR_MESSAGE(xrandr, data, XGetAtomName); FF_LIBRARY_LOAD_SYMBOL_VAR_MESSAGE(xrandr, data, XGetWindowProperty); FF_LIBRARY_LOAD_SYMBOL_VAR_MESSAGE(xrandr, data, XServerVendor); FF_LIBRARY_LOAD_SYMBOL_VAR_MESSAGE(xrandr, data, XFree); FF_LIBRARY_LOAD_SYMBOL_VAR_MESSAGE(xrandr, data, XRRGetMonitors); FF_LIBRARY_LOAD_SYMBOL_VAR_MESSAGE(xrandr, data, XRRGetScreenResourcesCurrent); FF_LIBRARY_LOAD_SYMBOL_VAR_MESSAGE(xrandr, data, XRRGetOutputInfo); FF_LIBRARY_LOAD_SYMBOL_VAR_MESSAGE(xrandr, data, XRRGetOutputProperty); FF_LIBRARY_LOAD_SYMBOL_VAR_MESSAGE(xrandr, data, XRRGetCrtcInfo); FF_LIBRARY_LOAD_SYMBOL_VAR_MESSAGE(xrandr, data, XRRFreeCrtcInfo); FF_LIBRARY_LOAD_SYMBOL_VAR_MESSAGE(xrandr, data, XRRFreeOutputInfo); FF_LIBRARY_LOAD_SYMBOL_VAR_MESSAGE(xrandr, data, XRRFreeScreenResources); FF_LIBRARY_LOAD_SYMBOL_VAR_MESSAGE(xrandr, data, XRRFreeMonitors); data.display = ffXOpenDisplay(NULL); if(data.display == NULL) return "XOpenDisplay() failed"; if(ScreenCount(data.display) > 0) { x11DetectWMFromEWMH(&data, result); x11FetchServerVendor(&data, result); } data.result = result; for(int i = 0; i < ScreenCount(data.display); i++) xrandrHandleScreen(&data, ScreenOfDisplay(data.display, i)); ffXCloseDisplay(data.display); //If wayland hasn't set this, connection failed for it. So we are running only a X Server, not XWayland. if(result->wmProtocolName.length == 0) ffStrbufSetS(&result->wmProtocolName, FF_WM_PROTOCOL_X11); return NULL; } #else const char* ffdsConnectXrandr(FFDisplayServerResult* result) { //Do nothing here. There are more x11 implementations to come. FF_UNUSED(result); return "Fastfetch was compiled without libXrandr support"; } #endif // FF_HAVE_XRANDR ================================================ FILE: src/detection/dns/dns.h ================================================ #pragma once #include "fastfetch.h" #include "modules/dns/option.h" const char* ffDetectDNS(FFDNSOptions* options, FFlist* results /* list of FFstrbuf */); ================================================ FILE: src/detection/dns/dns_apple.c ================================================ #include "detection/dns/dns.h" #include "common/io.h" #include "common/mallocHelper.h" #include "common/stringUtils.h" #include "common/apple/cf_helpers.h" #include "common/debug.h" #include static const char* detectDnsFromConf(const char* path, FFDNSOptions* options, FFlist* results) { FF_DEBUG("Attempting to read DNS config from %s", path); FF_AUTO_CLOSE_FILE FILE* file = fopen(path, "r"); if (!file) { FF_DEBUG("Failed to open %s: %m", path); return "fopen(path, r) failed"; } if (results->length > 0) { FF_DEBUG("Clearing existing DNS entries (%u entries)", results->length); FF_LIST_FOR_EACH(FFstrbuf, item, *results) ffStrbufDestroy(item); ffListClear(results); } FF_AUTO_FREE char* line = NULL; size_t len = 0; while (getline(&line, &len, file) != -1) { if (ffStrStartsWith(line, "nameserver")) { char* nameserver = line + strlen("nameserver"); while (*nameserver == ' ' || *nameserver == '\t') nameserver++; if (*nameserver == '\0') continue; char* comment = strchr(nameserver, '#'); if (comment) *comment = '\0'; if ((ffStrContainsC(nameserver, ':') && !(options->showType & FF_DNS_TYPE_IPV6_BIT)) || (ffStrContainsC(nameserver, '.') && !(options->showType & FF_DNS_TYPE_IPV4_BIT))) continue; FFstrbuf* item = (FFstrbuf*) ffListAdd(results); ffStrbufInitS(item, nameserver); ffStrbufTrimRightSpace(item); FF_DEBUG("Found DNS server: %s", item->chars); } } FF_DEBUG("Found %u DNS servers in %s", results->length, path); return NULL; } const char* ffDetectDNS(FFDNSOptions* options, FFlist* results) { // Handle macOS-specific DNS configurations FF_DEBUG("Using SystemConfiguration framework for macOS"); // Create a reference to the dynamic store FF_CFTYPE_AUTO_RELEASE SCDynamicStoreRef store = SCDynamicStoreCreate(NULL, CFSTR("fastfetch"), NULL, NULL); if (store) { // Get the network global IPv4 and IPv6 configuration FF_CFTYPE_AUTO_RELEASE CFStringRef key = SCDynamicStoreKeyCreateNetworkGlobalEntity(NULL, kSCDynamicStoreDomainState, kSCEntNetDNS); if (key) { FF_CFTYPE_AUTO_RELEASE CFDictionaryRef dict = SCDynamicStoreCopyValue(store, key); if (dict) { // Get the DNS server addresses array CFArrayRef dnsServers = CFDictionaryGetValue(dict, kSCPropNetDNSServerAddresses); if (dnsServers) { FF_STRBUF_AUTO_DESTROY buffer = ffStrbufCreate(); for (CFIndex i = 0; i < CFArrayGetCount(dnsServers); i++) { if (ffCfStrGetString(CFArrayGetValueAtIndex(dnsServers, i), &buffer) == NULL) { // Check if the address matches our filter if ((ffStrbufContainC(&buffer, ':') && !(options->showType & FF_DNS_TYPE_IPV6_BIT)) || (ffStrbufContainC(&buffer, '.') && !(options->showType & FF_DNS_TYPE_IPV4_BIT))) continue; // Add to results FFstrbuf* item = (FFstrbuf*) ffListAdd(results); ffStrbufInitMove(item, &buffer); FF_DEBUG("Found DNS server on macOS: %s", item->chars); } } } } } } // If we didn't find any servers, try resolv.conf as fallback if (results->length > 0) return NULL; FF_DEBUG("No DNS servers found via SystemConfiguration, trying resolv.conf"); // Try standard resolv.conf location on macOS as a fallback return detectDnsFromConf("/var/run/resolv.conf", options, results); } ================================================ FILE: src/detection/dns/dns_linux.c ================================================ #include "detection/dns/dns.h" #include "common/io.h" #include "common/mallocHelper.h" #include "common/stringUtils.h" #include "common/debug.h" #ifdef __HAIKU__ #define RESOLV_CONF "/system/settings/network/resolv.conf" #else #define RESOLV_CONF "/etc/resolv.conf" #endif static const char* detectDnsFromConf(const char* path, FFDNSOptions* options, FFlist* results) { FF_DEBUG("Attempting to read DNS config from %s", path); FF_AUTO_CLOSE_FILE FILE* file = fopen(path, "r"); if (!file) { FF_DEBUG("Failed to open %s: %m", path); return "fopen(path, r) failed"; } if (results->length > 0) { FF_DEBUG("Clearing existing DNS entries (%u entries)", results->length); FF_LIST_FOR_EACH(FFstrbuf, item, *results) ffStrbufDestroy(item); ffListClear(results); } FF_AUTO_FREE char* line = NULL; size_t len = 0; while (getline(&line, &len, file) != -1) { if (ffStrStartsWith(line, "nameserver")) { char* nameserver = line + strlen("nameserver"); while (*nameserver == ' ' || *nameserver == '\t') nameserver++; if (*nameserver == '\0') continue; char* comment = strchr(nameserver, '#'); if (comment) *comment = '\0'; if ((ffStrContainsC(nameserver, ':') && !(options->showType & FF_DNS_TYPE_IPV6_BIT)) || (ffStrContainsC(nameserver, '.') && !(options->showType & FF_DNS_TYPE_IPV4_BIT))) continue; FFstrbuf* item = (FFstrbuf*) ffListAdd(results); ffStrbufInitS(item, nameserver); ffStrbufTrimRightSpace(item); FF_DEBUG("Found DNS server: %s", item->chars); } } FF_DEBUG("Found %u DNS servers in %s", results->length, path); return NULL; } const char* ffDetectDNS(FFDNSOptions* options, FFlist* results) { FF_DEBUG("Starting DNS detection"); const char* error = detectDnsFromConf(FASTFETCH_TARGET_DIR_ROOT RESOLV_CONF, options, results); if (error != NULL) { FF_DEBUG("Error detecting DNS: %s", error); return error; } #if __linux__ && !__ANDROID__ // Handle different DNS management services if (results->length == 1) { const FFstrbuf* firstEntry = FF_LIST_FIRST(FFstrbuf, *results); if (ffStrbufEqualS(firstEntry, "127.0.0.53")) { FF_DEBUG("Detected systemd-resolved (127.0.0.53), checking actual DNS servers"); // Managed by systemd-resolved if (detectDnsFromConf("/run/systemd/resolve/resolv.conf", options, results) == NULL) return NULL; } else if (ffStrbufEqualS(firstEntry, "127.0.0.1")) { FF_DEBUG("Detected possible NetworkManager (127.0.0.1), checking actual DNS servers"); // Managed by NetworkManager if (detectDnsFromConf("/var/run/NetworkManager/resolv.conf", options, results) == NULL) return NULL; } } // Check other possible DNS configuration files if (results->length == 0) { FF_DEBUG("No DNS servers found, trying alternative config files"); // Try resolvconf FF_DEBUG("Trying resolvconf configuration"); if (detectDnsFromConf(FASTFETCH_TARGET_DIR_ROOT "/run/resolvconf/resolv.conf", options, results) == NULL && results->length > 0) return NULL; // Try dnsmasq FF_DEBUG("Trying dnsmasq configuration"); if (detectDnsFromConf(FASTFETCH_TARGET_DIR_ROOT "/var/run/dnsmasq/resolv.conf", options, results) == NULL && results->length > 0) return NULL; // Try openresolv FF_DEBUG("Trying openresolv configuration"); if (detectDnsFromConf(FASTFETCH_TARGET_DIR_ROOT "/etc/resolv.conf.openresolv", options, results) == NULL && results->length > 0) return NULL; } #elif defined(__FreeBSD__) || defined(__DragonFly__) || defined(__NetBSD__) || defined(__OpenBSD__) // Handle BSD-specific DNS configurations if (results->length == 0) { FF_DEBUG("No DNS servers found, trying BSD-specific config files"); // FreeBSD and other BSDs may use resolvconf service FF_DEBUG("Trying BSD resolvconf configuration"); if (detectDnsFromConf(FASTFETCH_TARGET_DIR_ROOT "/var/run/resolvconf/resolv.conf", options, results) == NULL && results->length > 0) return NULL; // Some BSDs store DNS configuration here FF_DEBUG("Trying BSD nameserver configuration"); if (detectDnsFromConf(FASTFETCH_TARGET_DIR_ROOT "/var/run/nameserver", options, results) == NULL && results->length > 0) return NULL; // Try common BSD paths FF_DEBUG("Trying BSD common paths"); if (detectDnsFromConf(FASTFETCH_TARGET_DIR_ROOT "/etc/nameserver", options, results) == NULL && results->length > 0) return NULL; } #endif FF_DEBUG("DNS detection completed with %u servers found", results->length); return NULL; } ================================================ FILE: src/detection/dns/dns_windows.c ================================================ #include "detection/dns/dns.h" #include "common/netif.h" #include "common/mallocHelper.h" #include #include const char* ffDetectDNS(FFDNSOptions* options, FFlist* results) { IP_ADAPTER_ADDRESSES* FF_AUTO_FREE adapter_addresses = NULL; // Multiple attempts in case interfaces change while // we are in the middle of querying them. DWORD adapter_addresses_buffer_size = 0; for (int attempts = 0;; ++attempts) { if (adapter_addresses_buffer_size) { adapter_addresses = (IP_ADAPTER_ADDRESSES*)realloc(adapter_addresses, adapter_addresses_buffer_size); assert(adapter_addresses); } DWORD error = GetAdaptersAddresses( options->showType & FF_DNS_TYPE_IPV4_BIT ? options->showType & FF_DNS_TYPE_IPV6_BIT ? AF_UNSPEC : AF_INET : AF_INET6, GAA_FLAG_SKIP_UNICAST | GAA_FLAG_SKIP_ANYCAST | GAA_FLAG_SKIP_MULTICAST | GAA_FLAG_SKIP_FRIENDLY_NAME, NULL, adapter_addresses, &adapter_addresses_buffer_size); if (error == ERROR_SUCCESS) break; else if (ERROR_BUFFER_OVERFLOW == error && attempts < 4) continue; else return "GetAdaptersAddresses() failed"; } uint32_t defaultRouteIfIndex = ffNetifGetDefaultRouteV4()->ifIndex; // Iterate through all of the adapters for (IP_ADAPTER_ADDRESSES* adapter = adapter_addresses; adapter; adapter = adapter->Next) { if (adapter->IfIndex != defaultRouteIfIndex) continue; if (adapter->OperStatus != IfOperStatusUp) continue; for (IP_ADAPTER_DNS_SERVER_ADDRESS_XP * ifa = adapter->FirstDnsServerAddress; ifa; ifa = ifa->Next) { FFstrbuf* item = (FFstrbuf*) ffListAdd(results); if (ifa->Address.lpSockaddr->sa_family == AF_INET) { SOCKADDR_IN* ipv4 = (SOCKADDR_IN*) ifa->Address.lpSockaddr; ffStrbufInitA(item, INET_ADDRSTRLEN); item->length = (uint32_t) (RtlIpv4AddressToStringA(&ipv4->sin_addr, item->chars) - item->chars); } else if (ifa->Address.lpSockaddr->sa_family == AF_INET6) { SOCKADDR_IN6* ipv6 = (SOCKADDR_IN6*) ifa->Address.lpSockaddr; ffStrbufInitA(item, INET6_ADDRSTRLEN); item->length = (uint32_t) (RtlIpv6AddressToStringA(&ipv6->sin6_addr, item->chars) - item->chars); } } break; } return NULL; } ================================================ FILE: src/detection/editor/editor.c ================================================ #include "editor.h" #include "common/processing.h" #include "common/library.h" #include "common/stringUtils.h" #include "common/path.h" #include "common/binary.h" #include static bool extractNvimVersionFromBinary(const char* str, FF_MAYBE_UNUSED uint32_t len, void* userdata) { if (!ffStrStartsWith(str, "NVIM v")) return true; ffStrbufSetS((FFstrbuf*) userdata, str + strlen("NVIM v")); return false; } static bool extractVimVersionFromBinary(const char* str, FF_MAYBE_UNUSED uint32_t len, void* userdata) { if (!ffStrStartsWith(str, "VIM - Vi IMproved ")) return true; ffStrbufSetS((FFstrbuf*) userdata, str + strlen("VIM - Vi IMproved ")); ffStrbufSubstrBeforeFirstC(userdata, ' '); return false; } static bool extractNanoVersionFromBinary(const char* str, FF_MAYBE_UNUSED uint32_t len, void* userdata) { if (!ffStrStartsWith(str, "GNU nano ")) return true; ffStrbufSetS((FFstrbuf*) userdata, str + strlen("GNU nano ")); return false; } const char* ffDetectEditor(FFEditorResult* result) { ffStrbufSetS(&result->name, getenv("VISUAL")); if (result->name.length) result->type = "Visual"; else { ffStrbufSetS(&result->name, getenv("EDITOR")); if (result->name.length) result->type = "Editor"; else return "$VISUAL or $EDITOR not set"; } if (ffIsAbsolutePath(result->name.chars)) ffStrbufSet(&result->path, &result->name); else { const char* error = ffFindExecutableInPath(result->name.chars, &result->path); if (error) return NULL; } { char buf[PATH_MAX + 1]; if (!realpath(result->path.chars, buf)) return NULL; // WIN32: Should we handle scoop shim exe here? #ifdef __linux__ if (!ffStrEndsWith(buf, "/snap")) #endif ffStrbufSetS(&result->path, buf); } { uint32_t index = ffStrbufLastIndexC(&result->path, #ifndef _WIN32 '/' #else '\\' #endif ); if (index == result->path.length) return NULL; ffStrbufSetS(&result->exe, &result->path.chars[index + 1]); if (!result->exe.length) return NULL; #ifdef _WIN32 if (ffStrbufEndsWithS(&result->exe, ".exe")) ffStrbufSubstrBefore(&result->exe, result->exe.length - 4); #endif } if (!instance.config.general.detectVersion) return NULL; if (ffStrbufEqualS(&result->exe, "nvim")) ffBinaryExtractStrings(result->path.chars, extractNvimVersionFromBinary, &result->version, (uint32_t) strlen("NVIM v0.0.0")); else if (ffStrbufEqualS(&result->exe, "vim") || ffStrbufStartsWithS(&result->exe, "vim.")) ffBinaryExtractStrings(result->path.chars, extractVimVersionFromBinary, &result->version, (uint32_t) strlen("VIM - Vi IMproved 0.0")); else if (ffStrbufEqualS(&result->exe, "nano")) ffBinaryExtractStrings(result->path.chars, extractNanoVersionFromBinary, &result->version, (uint32_t) strlen("GNU nano 0.0")); if (result->version.length > 0) return NULL; const char* param = NULL; if ( ffStrbufEqualS(&result->exe, "nano") || ffStrbufEqualS(&result->exe, "vim") || ffStrbufStartsWithS(&result->exe, "vim.") || // vim.basic/vim.tiny ffStrbufEqualS(&result->exe, "nvim") || ffStrbufEqualS(&result->exe, "micro") || ffStrbufEqualS(&result->exe, "emacs") || ffStrbufStartsWithS(&result->exe, "emacs-") || // emacs-29.3 ffStrbufEqualS(&result->exe, "hx") || ffStrbufEqualS(&result->exe, "code") || ffStrbufEqualS(&result->exe, "pluma") || ffStrbufEqualS(&result->exe, "sublime_text") || ffStrbufEqualS(&result->exe, "zeditor") ) param = "--version"; else if ( ffStrbufEqualS(&result->exe, "kak") || ffStrbufEqualS(&result->exe, "pico") ) param = "-version"; else if ( ffStrbufEqualS(&result->exe, "ne") ) param = "-h"; else return NULL; ffProcessAppendStdOut(&result->version, (char* const[]){ result->path.chars, (char*) param, NULL, }); if (result->version.length == 0) return NULL; ffStrbufSubstrBeforeFirstC(&result->version, '\n'); const char* versionStart = strpbrk(result->version.chars, "0123456789"); if (versionStart != NULL) { const char* versionEnd = strpbrk(versionStart, " \t\v\f\r"); if (versionEnd != NULL) ffStrbufSubstrBefore(&result->version, (uint32_t)(versionEnd - result->version.chars)); if (versionStart != result->version.chars) ffStrbufSubstrAfter(&result->version, (uint32_t)(versionStart - result->version.chars - 1)); } return NULL; } ================================================ FILE: src/detection/editor/editor.h ================================================ #pragma once #include "fastfetch.h" #include "modules/editor/option.h" typedef struct FFEditorResult { const char* type; FFstrbuf name; FFstrbuf exe; FFstrbuf path; FFstrbuf version; } FFEditorResult; const char* ffDetectEditor(FFEditorResult* result); ================================================ FILE: src/detection/font/font.c ================================================ #include "font.h" const char* ffDetectFontImpl(FFFontResult* font); const char* ffDetectFont(FFFontResult* font) { const char* error = ffDetectFontImpl(font); if(error) return error; for(uint32_t i = 0; i < FF_DETECT_FONT_NUM_FONTS; ++i) { if(font->fonts[i].length > 0) return NULL; } return "No fonts found"; } ================================================ FILE: src/detection/font/font.h ================================================ #pragma once #include "fastfetch.h" #include "modules/font/option.h" enum { FF_DETECT_FONT_NUM_FONTS = 4 }; typedef struct FFFontResult { /** * Linux / BSD: Qt, GTK2, GTK3, GTK4 * MacOS: System, User, System Mono, User Mono * Windows: Caption, Menu, Message, Status * Haiku: Plain, Menu, Bold, Mono * Other: Unset, Unset, Unset, Unset */ FFstrbuf fonts[FF_DETECT_FONT_NUM_FONTS]; FFstrbuf display; } FFFontResult; const char* ffDetectFont(FFFontResult* font); ================================================ FILE: src/detection/font/font_apple.m ================================================ #include "common/font.h" #include "common/io.h" #include "font.h" #import static void generateString(FFFontResult* font) { if(font->fonts[0].length > 0) { ffStrbufAppend(&font->display, &font->fonts[0]); ffStrbufAppendS(&font->display, " [System]"); if(font->fonts[1].length > 0) ffStrbufAppendS(&font->display, ", "); } if(font->fonts[1].length > 0) { ffStrbufAppend(&font->display, &font->fonts[1]); ffStrbufAppendS(&font->display, " [User]"); } } const char* ffDetectFontImpl(FFFontResult* result) { ffStrbufAppendS(&result->fonts[0], [NSFont systemFontOfSize:12].familyName.UTF8String); ffStrbufAppendS(&result->fonts[1], [NSFont userFontOfSize:12].familyName.UTF8String); #ifdef MAC_OS_X_VERSION_10_15 ffStrbufAppendS(&result->fonts[2], [NSFont monospacedSystemFontOfSize:12 weight:400].familyName.UTF8String); #else ffStrbufAppendS(&result->fonts[2], ""); #endif ffStrbufAppendS(&result->fonts[3], [NSFont userFixedPitchFontOfSize:12].familyName.UTF8String); generateString(result); return NULL; } ================================================ FILE: src/detection/font/font_haiku.cpp ================================================ extern "C" { #include "font.h" } #include #include #include extern "C" { const char* ffDetectFontImpl(FFFontResult* result); } static void generateString(FFFontResult* font) { const char* types[] = { "Plain", "Menu", "Bold", "Mono" }; for(uint32_t i = 0; i < ARRAY_SIZE(types); ++i) { if(i == 0 || !ffStrbufEqual(&font->fonts[i - 1], &font->fonts[i])) { if(i > 0) ffStrbufAppendS(&font->display, "], "); ffStrbufAppendF(&font->display, "%s [%s", font->fonts[i].chars, types[i]); } else { ffStrbufAppendS(&font->display, " / "); ffStrbufAppendS(&font->display, types[i]); } } ffStrbufAppendC(&font->display, ']'); } const char* ffDetectFontImpl(FFFontResult* result) { struct menu_info menuInfo; const BFont *f; // We need a valid be_app to query the app_server here. BApplication app("application/x-vnd.fastfetch-cli-fastfetch"); if ((f = be_plain_font) != NULL) { f->GetFamilyAndStyle(&menuInfo.f_family, &menuInfo.f_style); ffStrbufAppendF(&result->fonts[0], "%s %s (%dpt)", menuInfo.f_family, menuInfo.f_style, (int)f->Size()); } if (get_menu_info(&menuInfo) == B_OK) { ffStrbufAppendF(&result->fonts[1], "%s %s (%dpt)", menuInfo.f_family, menuInfo.f_style, (int)menuInfo.font_size); } if ((f = be_bold_font) != NULL) { f->GetFamilyAndStyle(&menuInfo.f_family, &menuInfo.f_style); ffStrbufAppendF(&result->fonts[2], "%s %s (%dpt)", menuInfo.f_family, menuInfo.f_style, (int)f->Size()); } if ((f = be_fixed_font) != NULL) { f->GetFamilyAndStyle(&menuInfo.f_family, &menuInfo.f_style); ffStrbufAppendF(&result->fonts[3], "%s %s (%dpt)", menuInfo.f_family, menuInfo.f_style, (int)f->Size()); } generateString(result); return NULL; } ================================================ FILE: src/detection/font/font_linux.c ================================================ #include "common/font.h" #include "common/parsing.h" #include "detection/displayserver/displayserver.h" #include "detection/gtk_qt/gtk_qt.h" #include "font.h" static void generateString(FFFontResult* font) { if(font->fonts[0].length > 0) { ffStrbufAppend(&font->display, &font->fonts[0]); ffStrbufAppendS(&font->display, " [Qt]"); for(uint8_t i = 1; i < ARRAY_SIZE(font->fonts); i++) { if(font->fonts[i].length > 0) { ffStrbufAppendS(&font->display, ", "); break; } } } ffParseGTK(&font->display, &font->fonts[1], &font->fonts[2], &font->fonts[3]); } const char* ffDetectFontImpl(FFFontResult* result) { const FFDisplayServerResult* wmde = ffConnectDisplayServer(); if(ffStrbufIgnCaseEqualS(&wmde->wmProtocolName, FF_WM_PROTOCOL_TTY)) return "Font isn't supported in TTY"; FFfont qt; ffFontInitQt(&qt, ffDetectQt()->font.chars); ffStrbufAppend(&result->fonts[0], &qt.pretty); ffFontDestroy(&qt); FFfont gtk2; ffFontInitPango(>k2, ffDetectGTK2()->font.chars); ffStrbufAppend(&result->fonts[1], >k2.pretty); ffFontDestroy(>k2); FFfont gtk3; ffFontInitPango(>k3, ffDetectGTK3()->font.chars); ffStrbufAppend(&result->fonts[2], >k3.pretty); ffFontDestroy(>k3); FFfont gtk4; ffFontInitPango(>k4, ffDetectGTK4()->font.chars); ffStrbufAppend(&result->fonts[3], >k4.pretty); ffFontDestroy(>k4); generateString(result); return NULL; } ================================================ FILE: src/detection/font/font_nosupport.c ================================================ #include "fastfetch.h" #include "font.h" const char* ffDetectFontImpl(FF_MAYBE_UNUSED FFFontResult* result) { FF_UNUSED(result); return "Not supported on this platform"; } ================================================ FILE: src/detection/font/font_windows.c ================================================ #include "font.h" #include "common/windows/unicode.h" #include static void generateString(FFFontResult* font) { const char* types[] = { "Caption", "Menu", "Message", "Status" }; for(uint32_t i = 0; i < ARRAY_SIZE(types); ++i) { if(i == 0 || !ffStrbufEqual(&font->fonts[i - 1], &font->fonts[i])) { if(i > 0) ffStrbufAppendS(&font->display, "], "); ffStrbufAppendF(&font->display, "%s [%s", font->fonts[i].chars, types[i]); } else { ffStrbufAppendS(&font->display, " / "); ffStrbufAppendS(&font->display, types[i]); } } ffStrbufAppendC(&font->display, ']'); } const char* ffDetectFontImpl(FFFontResult* result) { NONCLIENTMETRICSW info = { .cbSize = sizeof(info) }; if(!SystemParametersInfoW(SPI_GETNONCLIENTMETRICS, sizeof(info), &info, 0)) return "SystemParametersInfoW(SPI_GETNONCLIENTMETRICS) failed"; LOGFONTW* fonts[4] = { &info.lfCaptionFont, &info.lfMenuFont, &info.lfMessageFont, &info.lfStatusFont }; for(uint32_t i = 0; i < ARRAY_SIZE(fonts); ++i) { ffStrbufSetWS(&result->fonts[i], fonts[i]->lfFaceName); if(fonts[i]->lfHeight < 0) ffStrbufAppendF(&result->fonts[i], " (%dpt)", (int)-fonts[i]->lfHeight); } generateString(result); return NULL; } ================================================ FILE: src/detection/gamepad/gamepad.h ================================================ #pragma once #include "fastfetch.h" typedef struct FFGamepadDevice { FFstrbuf serial; FFstrbuf name; uint8_t battery; // 0-100% } FFGamepadDevice; const char* ffDetectGamepad(FFlist* devices /* List of FFGamepadDevice */); ================================================ FILE: src/detection/gamepad/gamepad_apple.c ================================================ #include "gamepad.h" #include "common/apple/cf_helpers.h" #include "common/mallocHelper.h" #include #include static void enumSet(IOHIDDeviceRef value, FFlist* results) { FFGamepadDevice* device = (FFGamepadDevice*) ffListAdd(results); ffStrbufInit(&device->serial); ffStrbufInit(&device->name); device->battery = 0; CFStringRef manufacturer = IOHIDDeviceGetProperty(value, CFSTR(kIOHIDManufacturerKey)); ffCfStrGetString(manufacturer, &device->name); CFStringRef product = IOHIDDeviceGetProperty(value, CFSTR(kIOHIDProductKey)); if (device->name.length) { ffCfStrGetString(product, &device->serial); ffStrbufAppendC(&device->name, ' '); ffStrbufAppend(&device->name, &device->serial); } else { ffCfStrGetString(product, &device->name); } CFStringRef serialNumber = IOHIDDeviceGetProperty(value, CFSTR(kIOHIDSerialNumberKey)); ffCfStrGetString(serialNumber, &device->serial); } const char* ffDetectGamepad(FFlist* devices /* List of FFGamepadDevice */) { IOHIDManagerRef FF_CFTYPE_AUTO_RELEASE manager = IOHIDManagerCreate(kCFAllocatorDefault, kIOHIDOptionsTypeNone); if (IOHIDManagerOpen(manager, kIOHIDOptionsTypeNone) != kIOReturnSuccess) return "IOHIDManagerOpen() failed"; CFDictionaryRef FF_CFTYPE_AUTO_RELEASE matching1 = CFDictionaryCreate(kCFAllocatorDefault, (const void **)(CFStringRef[]){ CFSTR(kIOHIDDeviceUsagePageKey), CFSTR(kIOHIDDeviceUsageKey) }, (const void **)(CFNumberRef[]){ ffCfCreateInt(kHIDPage_GenericDesktop), ffCfCreateInt(kHIDUsage_GD_Joystick) }, 2, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks); CFDictionaryRef FF_CFTYPE_AUTO_RELEASE matching2 = CFDictionaryCreate(kCFAllocatorDefault, (const void **)(CFStringRef[]){ CFSTR(kIOHIDDeviceUsagePageKey), CFSTR(kIOHIDDeviceUsageKey) }, (const void **)(CFNumberRef[]){ ffCfCreateInt(kHIDPage_GenericDesktop), ffCfCreateInt(kHIDUsage_GD_GamePad) }, 2, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks); CFArrayRef FF_CFTYPE_AUTO_RELEASE matchings = CFArrayCreate(kCFAllocatorDefault, (const void **)(CFTypeRef[]){ matching1, matching2 }, 2, &kCFTypeArrayCallBacks); IOHIDManagerSetDeviceMatchingMultiple(manager, matchings); CFSetRef FF_CFTYPE_AUTO_RELEASE set = IOHIDManagerCopyDevices(manager); if (set) CFSetApplyFunction(set, (CFSetApplierFunction) &enumSet, devices); IOHIDManagerClose(manager, kIOHIDOptionsTypeNone); return NULL; } ================================================ FILE: src/detection/gamepad/gamepad_bsd.c ================================================ #include "gamepad.h" #include "common/io.h" #include #include #include #if __has_include() #include // FreeBSD #else #include // DragonFly #endif #define MAX_UHID_JOYS 64 const char* ffDetectGamepad(FFlist* devices /* List of FFGamepadDevice */) { char path[16]; for (int i = 0; i < MAX_UHID_JOYS; i++) { snprintf(path, ARRAY_SIZE(path), "/dev/uhid%d", i); FF_AUTO_CLOSE_FD int fd = open(path, O_RDONLY | O_CLOEXEC); if (fd < 0) { if (errno == ENOENT) break; // No more devices continue; // Device not found } report_desc_t repDesc = hid_get_report_desc(fd); if (!repDesc) continue; int reportId = hid_get_report_id(fd); struct hid_data* hData = hid_start_parse(repDesc, 0, reportId); if (hData) { struct hid_item hItem; while (hid_get_item(hData, &hItem) > 0) { if (HID_PAGE(hItem.usage) != 1) continue; switch (HID_USAGE(hItem.usage)) { case 1: // Pointer. FreeBSD returns 1 for my Pro Controller for some reason case 4: // Joystick case 5: // Gamepad break; default: continue; } struct usb_device_info di; if (ioctl(fd, USB_GET_DEVICEINFO, &di) != -1) { FFGamepadDevice* device = (FFGamepadDevice*) ffListAdd(devices); ffStrbufInitS(&device->serial, di.udi_serial); ffStrbufInitF(&device->name, "%s %s", di.udi_vendor, di.udi_product); device->battery = 0; } } hid_end_parse(hData); } hid_dispose_report_desc(repDesc); } return NULL; } ================================================ FILE: src/detection/gamepad/gamepad_haiku.cpp ================================================ extern "C" { #include "gamepad.h" } #include const char* ffDetectGamepad(FFlist* devices /* List of FFGamepadDevice */) { BJoystick js; for (int32 i = 0, n = js.CountDevices(); i < n; ++i) { char name[B_OS_NAME_LENGTH]; if (js.GetDeviceName(i, name) == B_OK) { FFGamepadDevice* device = (FFGamepadDevice*) ffListAdd(devices); ffStrbufInit(&device->serial); ffStrbufInitS(&device->name, name); device->battery = 0; } } return NULL; } ================================================ FILE: src/detection/gamepad/gamepad_linux.c ================================================ #include "gamepad.h" #include "common/io.h" #include "common/stringUtils.h" static void detectGamepad(FFlist* devices, FFstrbuf* name, FFstrbuf* path) { uint32_t baseLen = path->length; FFGamepadDevice* device = (FFGamepadDevice*) ffListAdd(devices); ffStrbufInit(&device->serial); ffStrbufInitMove(&device->name, name); device->battery = 0; ffStrbufAppendS(path, "uniq"); if (ffAppendFileBuffer(path->chars, &device->serial)) ffStrbufTrimRightSpace(&device->serial); ffStrbufSubstrBefore(path, baseLen); ffStrbufAppendS(path, "device/power_supply/"); // /sys/class/input/jsX/device/device/power_supply FF_AUTO_CLOSE_DIR DIR* dirp = opendir(path->chars); if (dirp) { struct dirent* entry; while ((entry = readdir(dirp)) != NULL) { if (entry->d_name[0] == '.' || (entry->d_type != DT_DIR && entry->d_type != DT_UNKNOWN)) continue; ffStrbufAppendS(path, entry->d_name); ffStrbufAppendS(path, "/capacity"); // /sys/class/input/jsX/device/device/power_supply/XXX/capacity char capacity[32]; ssize_t nRead = ffReadFileData(path->chars, ARRAY_SIZE(capacity) - 1, capacity); if (nRead > 0) // Tested with a PS4 controller { capacity[nRead] = '\0'; device->battery = (uint8_t) strtoul(capacity, NULL, 10); break; } ffStrbufAppendS(path, "_level"); nRead = ffReadFileData(path->chars, ARRAY_SIZE(capacity) - 1, capacity); if (nRead > 0) // Tested with a NS Pro controller { // https://github.com/torvalds/linux/blob/52b1853b080a082ec3749c3a9577f6c71b1d4a90/drivers/power/supply/power_supply_sysfs.c#L124 switch (capacity[0]) { case 'C': device->battery = 1; break; // Critical case 'L': device->battery = 25; break; // Low case 'N': device->battery = 50; break; // Normal case 'H': device->battery = 75; break; // High case 'F': device->battery = 100; break; // Full } } } } } const char* ffDetectGamepad(FFlist* devices /* List of FFGamepadDevice */) { FF_AUTO_CLOSE_DIR DIR* dirp = opendir("/sys/class/input/"); if (dirp == NULL) return "opendir(\"/sys/class/input/\") == NULL"; FF_STRBUF_AUTO_DESTROY path = ffStrbufCreateS("/sys/class/input/"); uint32_t baseLen = path.length; struct dirent* entry; while ((entry = readdir(dirp)) != NULL) { if (!ffStrStartsWith(entry->d_name, "js")) continue; if (!ffCharIsDigit(entry->d_name[strlen("js")])) continue; ffStrbufAppendS(&path, entry->d_name); ffStrbufAppendS(&path, "/device/name"); FF_STRBUF_AUTO_DESTROY name = ffStrbufCreate(); if (ffAppendFileBuffer(path.chars, &name)) { ffStrbufTrimRightSpace(&name); ffStrbufSubstrBefore(&path, path.length - 4); detectGamepad(devices, &name, &path); } ffStrbufSubstrBefore(&path, baseLen); } return NULL; } ================================================ FILE: src/detection/gamepad/gamepad_nosupport.c ================================================ #include "gamepad.h" const char* ffDetectGamepad(FF_MAYBE_UNUSED FFlist* devices /* List of FFGamepadDevice */) { return "Not supported on this platform"; } ================================================ FILE: src/detection/gamepad/gamepad_windows.c ================================================ #include "gamepad.h" #include "common/io.h" #include "common/mallocHelper.h" #include "common/windows/unicode.h" #include #include static const char* detectKnownDeviceName(uint32_t vendorId, uint32_t productId) { switch (vendorId) { // Nintendo case 0x057E: { switch (productId) { case 0x2006: return "Nintendo Switch Joycon L"; case 0x2007: return "Nintendo Switch Joycon R"; case 0x2009: return "Nintendo Switch Pro Controller"; case 0x200E: return "Nintendo Switch Charging Grip"; case 0x2017: return "Nintendo Switch SNES Controller"; default: return NULL; } } // Sony case 0x054C: { switch (productId) { case 0x0268: return "Sony DualShock 3 / Six Axis"; case 0x05C4: return "Sony DualShock 4 Gen1"; case 0x09CC: return "Sony DualShock 4 Gen2"; case 0x0BA0: return "Sony DualShock 4 USB receiver"; case 0x0CE6: return "Sony DualSense"; case 0x0DF2: return "Sony DualSense Edge"; default: return NULL; } } // Logitech case 0x046D: { switch (productId) { case 0xC216: return "Logitech F310, DirectInput"; case 0xC218: return "Logitech F510, DirectInput"; case 0xC219: return "Logitech F710, DirectInput"; case 0xC21D: return "Logitech F310"; case 0xC21E: return "Logitech F510"; case 0xC21F: return "Logitech F710"; default: return NULL; } } case 0x045E: // Microsoft Xbox compatible controllers should be handled by Windows without problems default: return NULL; } } const char* ffDetectGamepad(FFlist* devices /* List of FFGamepadDevice */) { UINT nDevices = 0; if (GetRawInputDeviceList(NULL, &nDevices, sizeof(RAWINPUTDEVICELIST))) return "GetRawInputDeviceList(NULL) failed"; if (nDevices == 0) return "No HID devices found"; RAWINPUTDEVICELIST* FF_AUTO_FREE pRawInputDeviceList = (RAWINPUTDEVICELIST*) malloc(sizeof(RAWINPUTDEVICELIST) * nDevices); if ((nDevices = GetRawInputDeviceList(pRawInputDeviceList, &nDevices, sizeof(RAWINPUTDEVICELIST))) == (UINT) -1) return "GetRawInputDeviceList(pRawInputDeviceList) failed"; for (UINT i = 0; i < nDevices; ++i) { if (pRawInputDeviceList[i].dwType != RIM_TYPEHID) continue; HANDLE hDevice = pRawInputDeviceList[i].hDevice; RID_DEVICE_INFO rdi; UINT rdiSize = sizeof(rdi); if (GetRawInputDeviceInfoW(hDevice, RIDI_DEVICEINFO, &rdi, &rdiSize) == (UINT) -1) continue; if (rdi.hid.usUsagePage != 1 || (rdi.hid.usUsage != 4/*Joystick*/ && rdi.hid.usUsage != 5/*Gamepad*/)) continue; WCHAR devName[MAX_PATH] = L""; UINT nameSize = MAX_PATH; if (GetRawInputDeviceInfoW(hDevice, RIDI_DEVICENAME, devName, &nameSize) == (UINT) -1) continue; FFGamepadDevice* device = (FFGamepadDevice*) ffListAdd(devices); ffStrbufInit(&device->serial); ffStrbufInit(&device->name); device->battery = 0; const char* knownGamepad = detectKnownDeviceName(rdi.hid.dwVendorId, rdi.hid.dwProductId); if (knownGamepad) ffStrbufSetS(&device->name, knownGamepad); HANDLE FF_AUTO_CLOSE_FD hHidFile = CreateFileW(devName, GENERIC_READ | GENERIC_WRITE, FILE_SHARE_READ | FILE_SHARE_WRITE, NULL, OPEN_EXISTING, FILE_FLAG_OVERLAPPED, NULL); if (hHidFile == INVALID_HANDLE_VALUE) { if (!knownGamepad) ffStrbufSetF(&device->name, "Unknown gamepad %04X-%04X", (unsigned) rdi.hid.dwVendorId, (unsigned) rdi.hid.dwProductId); continue; } if (!knownGamepad) { wchar_t displayName[126]; if (HidD_GetProductString(hHidFile, displayName, sizeof(displayName) /*in bytes*/)) { wchar_t manufacturer[126]; if (HidD_GetManufacturerString(hHidFile, manufacturer, sizeof(manufacturer) /*in bytes*/)) { ffStrbufSetWS(&device->name, manufacturer); FF_STRBUF_AUTO_DESTROY displayNameStr = ffStrbufCreateWS(displayName); ffStrbufAppendC(&device->name, ' '); ffStrbufAppend(&device->name, &displayNameStr); } else { ffStrbufSetWS(&device->name, displayName); } } } wchar_t serialNumber[127] = L""; if (HidD_GetSerialNumberString(hHidFile, serialNumber, sizeof(serialNumber) /*in bytes*/)) ffStrbufSetWS(&device->serial, serialNumber); PHIDP_PREPARSED_DATA preparsedData = NULL; if (HidD_GetPreparsedData(hHidFile, &preparsedData)) { HIDP_CAPS caps; NTSTATUS capsResult = HidP_GetCaps(preparsedData, &caps); HidD_FreePreparsedData(preparsedData); if (!NT_SUCCESS(capsResult)) continue; if ( (rdi.hid.dwVendorId == 0x054C && ( rdi.hid.dwProductId == 0x05C4 || // PS4 Gen1 rdi.hid.dwProductId == 0x09CC // PS4 Gen2 )) || (rdi.hid.dwVendorId == 0x057E && ( rdi.hid.dwProductId == 0x2009 // NS Pro )) ) { // Controller must be connected by other programs FF_AUTO_FREE uint8_t* reportBuffer = malloc(caps.InputReportByteLength); OVERLAPPED overlapped = { }; DWORD nBytes; if (ReadFile(hHidFile, reportBuffer, caps.InputReportByteLength, &nBytes, &overlapped) || GetOverlappedResultEx(hHidFile, &overlapped, &nBytes, FF_IO_TERM_RESP_WAIT_MS, TRUE)) { if (rdi.hid.dwVendorId == 0x054C) { if (nBytes > 31) { uint8_t batteryInfo = reportBuffer[caps.InputReportByteLength == 64 /*USB?*/ ? 30 : 32]; device->battery = (uint8_t) ((batteryInfo & 0x0f) * 100 / (batteryInfo & 0x10 /*charging?*/ ? 11 /*BATTERY_MAX_USB*/ : 8 /*BATTERY_MAX*/)); if (device->battery > 100) device->battery = 100; } } else { if (nBytes > 3 && reportBuffer[0] == 0x30) { uint8_t batteryInfo = reportBuffer[2]; device->battery = (uint8_t) (((batteryInfo & 0xE0) >> 4) * 100 / 8); if (device->battery == 0) device->battery = 1; else if (device->battery > 100) device->battery = 100; } } } else CancelIo(hHidFile); } } } return NULL; } ================================================ FILE: src/detection/gpu/adl.h ================================================ #pragma once #include "3rdparty/display-library/adl_sdk.h" // https://gpuopen-librariesandsdks.github.io/adl/modules.html // Function to initialize the ADL2 interface and to obtain client's context handle. extern int ADL2_Main_Control_Create(ADL_MAIN_MALLOC_CALLBACK callback, int iEnumConnectedAdapters, ADL_CONTEXT_HANDLE* context); // Destroy client's ADL context. extern int ADL2_Main_Control_Destroy(ADL_CONTEXT_HANDLE context); // Retrieves adapter information for given adapter or all OS-known adapters. // Return ADL_OK on success, DESPITE THE OFFICIAL DOCUMENT SAYS IT RETURNS 1 FOR SUCCESS! extern int ADL2_Adapter_AdapterInfoX3_Get(ADL_CONTEXT_HANDLE context, int iAdapterIndex, int* numAdapters, AdapterInfo** lppAdapterInfo); // Function to retrieve Graphic Core Info. extern int ADL2_Adapter_Graphic_Core_Info_Get(ADL_CONTEXT_HANDLE context, int iAdapterIndex, ADLGraphicCoreInfo* pGraphicCoreInfo); // Function to retrieve memory information from the adapter. Version 2 extern int ADL2_Adapter_MemoryInfo2_Get(ADL_CONTEXT_HANDLE context, int iAdapterIndex, ADLMemoryInfo2* lpMemoryInfo2); // This function retrieves the Dedicated VRAM usage of given adapter. extern int ADL2_Adapter_DedicatedVRAMUsage_Get(ADL_CONTEXT_HANDLE context, int iAdapterIndex, int* iVRAMUsageInMB); // Function to get the ASICFamilyType from the adapter. extern int ADL2_Adapter_ASICFamilyType_Get(ADL_CONTEXT_HANDLE context, int iAdapterIndex, int* lpAsicTypes, int* lpValids); // Function to retrieve current power management capabilities. extern int ADL2_Overdrive_Caps(ADL_CONTEXT_HANDLE context, int iAdapterIndex, int* iSupported, int* iEnabled, int* iVersion); /////////// Overdrive 6 functions // Function to retrieve current Overdrive and performance-related activity. extern int ADL2_Overdrive6_CurrentStatus_Get(ADL_CONTEXT_HANDLE context, int iAdapterIndex, ADLOD6CurrentStatus* lpCurrentStatus); // Function to retrieve GPU temperature from the thermal controller. extern int ADL2_Overdrive6_Temperature_Get(ADL_CONTEXT_HANDLE context, int iAdapterIndex, int* lpTemperature); // Function to retrieve the current or default Overdrive clock ranges. extern int ADL2_Overdrive6_StateInfo_Get(ADL_CONTEXT_HANDLE context, int iAdapterIndex, int iStateType, ADLOD6StateInfo* lpStateInfo); /// Overdrive N functions // Despite the name (N means Next), this is actually Overdrive7 API // https://github.com/GPUOpen-LibrariesAndSDKs/display-library/blob/master/Sample/OverdriveN/OverdriveN.cpp#L209 // Function to retrieve the OverdriveN capabilities. extern int ADL2_OverdriveN_CapabilitiesX2_Get(ADL_CONTEXT_HANDLE context, int iAdapterIndex, ADLODNCapabilitiesX2* lpODCapabilities); // Function to retrieve the current OD performance status. extern int ADL2_OverdriveN_PerformanceStatus_Get(ADL_CONTEXT_HANDLE context, int iAdapterIndex, ADLODNPerformanceStatus *lpODPerformanceStatus); // Function to retrieve the current temperature. extern int ADL2_OverdriveN_Temperature_Get(ADL_CONTEXT_HANDLE context, int iAdapterIndex, int iTemperatureType, int *iTemperature); // Function to retrieve the current GPU clocks settings. extern int ADL2_OverdriveN_SystemClocksX2_Get(ADL_CONTEXT_HANDLE context, int iAdapterIndex, ADLODNPerformanceLevelsX2 *lpODPerformanceLevels); /// Overdrive 8 functions // Function to retrieve the Overdrive8 current settings. extern int ADL2_Overdrive8_Current_Setting_Get(ADL_CONTEXT_HANDLE context, int iAdapterIndex, ADLOD8CurrentSetting *lpCurrentSetting); // Function to retrieve the Overdrive8 current settings. extern int ADL2_New_QueryPMLogData_Get(ADL_CONTEXT_HANDLE context, int iAdapterIndex, ADLPMLogDataOutput *lpDataOutput); ================================================ FILE: src/detection/gpu/asahi_drm.h ================================================ /* SPDX-License-Identifier: MIT */ /* * Copyright (C) The Asahi Linux Contributors * Copyright (C) 2018-2023 Collabora Ltd. * Copyright (C) 2014-2018 Broadcom */ #ifndef _ASAHI_DRM_H_ #define _ASAHI_DRM_H_ #include #if defined(__cplusplus) extern "C" { #endif /** * DOC: Introduction to the Asahi UAPI * * This documentation describes the Asahi IOCTLs. * * Just a few generic rules about the data passed to the Asahi IOCTLs (cribbed * from Panthor): * * - Structures must be aligned on 64-bit/8-byte. If the object is not * naturally aligned, a padding field must be added. * - Fields must be explicitly aligned to their natural type alignment with * pad[0..N] fields. * - All padding fields will be checked by the driver to make sure they are * zeroed. * - Flags can be added, but not removed/replaced. * - New fields can be added to the main structures (the structures * directly passed to the ioctl). Those fields can be added at the end of * the structure, or replace existing padding fields. Any new field being * added must preserve the behavior that existed before those fields were * added when a value of zero is passed. * - New fields can be added to indirect objects (objects pointed by the * main structure), iff those objects are passed a size to reflect the * size known by the userspace driver (see * drm_asahi_cmd_header::size). * - If the kernel driver is too old to know some fields, those will be * ignored if zero, and otherwise rejected (and so will be zero on output). * - If userspace is too old to know some fields, those will be zeroed * (input) before the structure is parsed by the kernel driver. * - Each new flag/field addition must come with a driver version update so * the userspace driver doesn't have to guess which flags are supported. * - Structures should not contain unions, as this would defeat the * extensibility of such structures. * - IOCTLs can't be removed or replaced. New IOCTL IDs should be placed * at the end of the drm_asahi_ioctl_id enum. */ /** * enum drm_asahi_ioctl_id - IOCTL IDs * * Place new ioctls at the end, don't re-order, don't replace or remove entries. * * These IDs are not meant to be used directly. Use the DRM_IOCTL_ASAHI_xxx * definitions instead. */ enum drm_asahi_ioctl_id { /** @DRM_ASAHI_GET_PARAMS: Query device properties. */ DRM_ASAHI_GET_PARAMS = 0, /** @DRM_ASAHI_GET_TIME: Query device time. */ DRM_ASAHI_GET_TIME, /** @DRM_ASAHI_VM_CREATE: Create a GPU VM address space. */ DRM_ASAHI_VM_CREATE, /** @DRM_ASAHI_VM_DESTROY: Destroy a VM. */ DRM_ASAHI_VM_DESTROY, /** @DRM_ASAHI_VM_BIND: Bind/unbind memory to a VM. */ DRM_ASAHI_VM_BIND, /** @DRM_ASAHI_GEM_CREATE: Create a buffer object. */ DRM_ASAHI_GEM_CREATE, /** * @DRM_ASAHI_GEM_MMAP_OFFSET: Get offset to pass to mmap() to map a * given GEM handle. */ DRM_ASAHI_GEM_MMAP_OFFSET, /** @DRM_ASAHI_GEM_BIND_OBJECT: Bind memory as a special object */ DRM_ASAHI_GEM_BIND_OBJECT, /** @DRM_ASAHI_QUEUE_CREATE: Create a scheduling queue. */ DRM_ASAHI_QUEUE_CREATE, /** @DRM_ASAHI_QUEUE_DESTROY: Destroy a scheduling queue. */ DRM_ASAHI_QUEUE_DESTROY, /** @DRM_ASAHI_SUBMIT: Submit commands to a queue. */ DRM_ASAHI_SUBMIT, }; #define DRM_ASAHI_MAX_CLUSTERS 64 /** * struct drm_asahi_params_global - Global parameters. * * This struct may be queried by drm_asahi_get_params. */ struct drm_asahi_params_global { /** @features: Feature bits from drm_asahi_feature */ __u64 features; /** @gpu_generation: GPU generation, e.g. 13 for G13G */ __u32 gpu_generation; /** @gpu_variant: GPU variant as a character, e.g. 'C' for G13C */ __u32 gpu_variant; /** * @gpu_revision: GPU revision in BCD, e.g. 0x00 for 'A0' or * 0x21 for 'C1' */ __u32 gpu_revision; /** @chip_id: Chip ID in BCD, e.g. 0x8103 for T8103 */ __u32 chip_id; /** @num_dies: Number of dies in the SoC */ __u32 num_dies; /** @num_clusters_total: Number of GPU clusters (across all dies) */ __u32 num_clusters_total; /** * @num_cores_per_cluster: Number of logical cores per cluster * (including inactive/nonexistent) */ __u32 num_cores_per_cluster; /** @max_frequency_khz: Maximum GPU core clock frequency */ __u32 max_frequency_khz; /** @core_masks: Bitmask of present/enabled cores per cluster */ __u64 core_masks[DRM_ASAHI_MAX_CLUSTERS]; /** * @vm_start: VM range start VMA. Together with @vm_end, this defines * the window of valid GPU VAs. Userspace is expected to subdivide VAs * out of this window. * * This window contains all virtual addresses that userspace needs to * know about. There may be kernel-internal GPU VAs outside this range, * but that detail is not relevant here. */ __u64 vm_start; /** @vm_end: VM range end VMA */ __u64 vm_end; /** * @vm_kernel_min_size: Minimum kernel VMA window size. * * When creating a VM, userspace is required to carve out a section of * virtual addresses (within the range given by @vm_start and * @vm_end). The kernel will allocate various internal structures * within the specified VA range. * * Allowing userspace to choose the VA range for the kernel, rather than * the kernel reserving VAs and requiring userspace to cope, can assist * in implementing SVM. */ __u64 vm_kernel_min_size; /** * @max_commands_per_submission: Maximum number of supported commands * per submission. This mirrors firmware limits. Userspace must split up * larger command buffers, which may require inserting additional * synchronization. */ __u32 max_commands_per_submission; /** * @max_attachments: Maximum number of drm_asahi_attachment's per * command */ __u32 max_attachments; /** * @command_timestamp_frequency_hz: Timebase frequency for timestamps * written during command exeuction, specified via drm_asahi_timestamp * structures. As this rate is controlled by the firmware, it is a * queryable parameter. * * Userspace must divide by this frequency to convert timestamps to * seconds, rather than hardcoding a particular firmware's rate. */ __u64 command_timestamp_frequency_hz; }; /** * enum drm_asahi_feature - Feature bits * * This covers only features that userspace cannot infer from the architecture * version. Most features don't need to be here. */ enum drm_asahi_feature { /** * @DRM_ASAHI_FEATURE_SOFT_FAULTS: GPU has "soft fault" enabled. Shader * loads of unmapped memory will return zero. Shader stores to unmapped * memory will be silently discarded. Note that only shader load/store * is affected. Other hardware units are not affected, notably including * texture sampling. * * Soft fault is set when initializing the GPU and cannot be runtime * toggled. Therefore, it is exposed as a feature bit and not a * userspace-settable flag on the VM. When soft fault is enabled, * userspace can speculate memory accesses more aggressively. */ DRM_ASAHI_FEATURE_SOFT_FAULTS = (1UL) << 0, }; /** * struct drm_asahi_get_params - Arguments passed to DRM_IOCTL_ASAHI_GET_PARAMS */ struct drm_asahi_get_params { /** @param_group: Parameter group to fetch (MBZ) */ __u32 param_group; /** @pad: MBZ */ __u32 pad; /** @pointer: User pointer to write parameter struct */ __u64 pointer; /** * @size: Size of the user buffer. In case of older userspace, this may * be less than sizeof(struct drm_asahi_params_global). The kernel will * not write past the length specified here, allowing extensibility. */ __u64 size; }; /** * struct drm_asahi_vm_create - Arguments passed to DRM_IOCTL_ASAHI_VM_CREATE */ struct drm_asahi_vm_create { /** * @kernel_start: Start of the kernel-reserved address range. See * drm_asahi_params_global::vm_kernel_min_size. * * Both @kernel_start and @kernel_end must be within the range of * valid VAs given by drm_asahi_params_global::vm_start and * drm_asahi_params_global::vm_end. The size of the kernel range * (@kernel_end - @kernel_start) must be at least * drm_asahi_params_global::vm_kernel_min_size. * * Userspace must not bind any memory on this VM into this reserved * range, it is for kernel use only. */ __u64 kernel_start; /** * @kernel_end: End of the kernel-reserved address range. See * @kernel_start. */ __u64 kernel_end; /** @vm_id: Returned VM ID */ __u32 vm_id; /** @pad: MBZ */ __u32 pad; }; /** * struct drm_asahi_vm_destroy - Arguments passed to DRM_IOCTL_ASAHI_VM_DESTROY */ struct drm_asahi_vm_destroy { /** @vm_id: VM ID to be destroyed */ __u32 vm_id; /** @pad: MBZ */ __u32 pad; }; /** * enum drm_asahi_gem_flags - Flags for GEM creation */ enum drm_asahi_gem_flags { /** * @DRM_ASAHI_GEM_WRITEBACK: BO should be CPU-mapped as writeback. * * Map as writeback instead of write-combine. This optimizes for CPU * reads. */ DRM_ASAHI_GEM_WRITEBACK = (1L << 0), /** * @DRM_ASAHI_GEM_VM_PRIVATE: BO is private to this GPU VM (no exports). */ DRM_ASAHI_GEM_VM_PRIVATE = (1L << 1), }; /** * struct drm_asahi_gem_create - Arguments passed to DRM_IOCTL_ASAHI_GEM_CREATE */ struct drm_asahi_gem_create { /** @size: Size of the BO */ __u64 size; /** @flags: Combination of drm_asahi_gem_flags flags. */ __u32 flags; /** * @vm_id: VM ID to assign to the BO, if DRM_ASAHI_GEM_VM_PRIVATE is set */ __u32 vm_id; /** @handle: Returned GEM handle for the BO */ __u32 handle; /** @pad: MBZ */ __u32 pad; }; /** * struct drm_asahi_gem_mmap_offset - Arguments passed to * DRM_IOCTL_ASAHI_GEM_MMAP_OFFSET */ struct drm_asahi_gem_mmap_offset { /** @handle: Handle for the object being mapped. */ __u32 handle; /** @flags: Must be zero */ __u32 flags; /** @offset: The fake offset to use for subsequent mmap call */ __u64 offset; }; /** * enum drm_asahi_bind_flags - Flags for GEM binding */ enum drm_asahi_bind_flags { /** * @DRM_ASAHI_BIND_UNBIND: Instead of binding a GEM object to the range, * simply unbind the GPU VMA range. */ DRM_ASAHI_BIND_UNBIND = (1L << 0), /** @DRM_ASAHI_BIND_READ: Map BO with GPU read permission */ DRM_ASAHI_BIND_READ = (1L << 1), /** @DRM_ASAHI_BIND_WRITE: Map BO with GPU write permission */ DRM_ASAHI_BIND_WRITE = (1L << 2), /** * @DRM_ASAHI_BIND_SINGLE_PAGE: Map a single page of the BO repeatedly * across the VA range. * * This is useful to fill a VA range with scratch pages or zero pages. * It is intended as a mechanism to accelerate sparse. */ DRM_ASAHI_BIND_SINGLE_PAGE = (1L << 3), }; /** * struct drm_asahi_gem_bind_op - Description of a single GEM bind operation. */ struct drm_asahi_gem_bind_op { /** @flags: Combination of drm_asahi_bind_flags flags. */ __u32 flags; /** @handle: GEM object to bind (except for UNBIND) */ __u32 handle; /** * @offset: Offset into the object (except for UNBIND). * * For a regular bind, this is the beginning of the region of the GEM * object to bind. * * For a single-page bind, this is the offset to the single page that * will be repeatedly bound. * * Must be page-size aligned. */ __u64 offset; /** * @range: Number of bytes to bind/unbind to @addr. * * Must be page-size aligned. */ __u64 range; /** * @addr: Address to bind to. * * Must be page-size aligned. */ __u64 addr; }; /** * struct drm_asahi_vm_bind - Arguments passed to * DRM_IOCTL_ASAHI_VM_BIND */ struct drm_asahi_vm_bind { /** @vm_id: The ID of the VM to bind to */ __u32 vm_id; /** @num_binds: number of binds in this IOCTL. */ __u32 num_binds; /** * @stride: Stride in bytes between consecutive binds. This allows * extensibility of drm_asahi_gem_bind_op. */ __u32 stride; /** @pad: MBZ */ __u32 pad; /** * @userptr: User pointer to an array of @num_binds structures of type * @drm_asahi_gem_bind_op and size @stride bytes. */ __u64 userptr; }; /** * enum drm_asahi_bind_object_op - Special object bind operation */ enum drm_asahi_bind_object_op { /** @DRM_ASAHI_BIND_OBJECT_OP_BIND: Bind a BO as a special GPU object */ DRM_ASAHI_BIND_OBJECT_OP_BIND = 0, /** @DRM_ASAHI_BIND_OBJECT_OP_UNBIND: Unbind a special GPU object */ DRM_ASAHI_BIND_OBJECT_OP_UNBIND = 1, }; /** * enum drm_asahi_bind_object_flags - Special object bind flags */ enum drm_asahi_bind_object_flags { /** * @DRM_ASAHI_BIND_OBJECT_USAGE_TIMESTAMPS: Map a BO as a timestamp * buffer. */ DRM_ASAHI_BIND_OBJECT_USAGE_TIMESTAMPS = (1L << 0), }; /** * struct drm_asahi_gem_bind_object - Arguments passed to * DRM_IOCTL_ASAHI_GEM_BIND_OBJECT */ struct drm_asahi_gem_bind_object { /** @op: Bind operation (enum drm_asahi_bind_object_op) */ __u32 op; /** @flags: Combination of drm_asahi_bind_object_flags flags. */ __u32 flags; /** @handle: GEM object to bind/unbind (BIND) */ __u32 handle; /** @vm_id: The ID of the VM to operate on (MBZ currently) */ __u32 vm_id; /** @offset: Offset into the object (BIND only) */ __u64 offset; /** @range: Number of bytes to bind/unbind (BIND only) */ __u64 range; /** @object_handle: Object handle (out for BIND, in for UNBIND) */ __u32 object_handle; /** @pad: MBZ */ __u32 pad; }; /** * enum drm_asahi_cmd_type - Command type */ enum drm_asahi_cmd_type { /** * @DRM_ASAHI_CMD_RENDER: Render command, executing on the render * subqueue. Combined vertex and fragment operation. * * Followed by a @drm_asahi_cmd_render payload. */ DRM_ASAHI_CMD_RENDER = 0, /** * @DRM_ASAHI_CMD_COMPUTE: Compute command on the compute subqueue. * * Followed by a @drm_asahi_cmd_compute payload. */ DRM_ASAHI_CMD_COMPUTE = 1, /** * @DRM_ASAHI_SET_VERTEX_ATTACHMENTS: Software command to set * attachments for subsequent vertex shaders in the same submit. * * Followed by (possibly multiple) @drm_asahi_attachment payloads. */ DRM_ASAHI_SET_VERTEX_ATTACHMENTS = 2, /** * @DRM_ASAHI_SET_FRAGMENT_ATTACHMENTS: Software command to set * attachments for subsequent fragment shaders in the same submit. * * Followed by (possibly multiple) @drm_asahi_attachment payloads. */ DRM_ASAHI_SET_FRAGMENT_ATTACHMENTS = 3, /** * @DRM_ASAHI_SET_COMPUTE_ATTACHMENTS: Software command to set * attachments for subsequent compute shaders in the same submit. * * Followed by (possibly multiple) @drm_asahi_attachment payloads. */ DRM_ASAHI_SET_COMPUTE_ATTACHMENTS = 4, }; /** * enum drm_asahi_priority - Scheduling queue priority. * * These priorities are forwarded to the firmware to influence firmware * scheduling. The exact policy is ultimately decided by firmware, but * these enums allow userspace to communicate the intentions. */ enum drm_asahi_priority { /** @DRM_ASAHI_PRIORITY_LOW: Low priority queue. */ DRM_ASAHI_PRIORITY_LOW = 0, /** @DRM_ASAHI_PRIORITY_MEDIUM: Medium priority queue. */ DRM_ASAHI_PRIORITY_MEDIUM = 1, /** * @DRM_ASAHI_PRIORITY_HIGH: High priority queue. * * Reserved for future extension. */ DRM_ASAHI_PRIORITY_HIGH = 2, /** * @DRM_ASAHI_PRIORITY_REALTIME: Real-time priority queue. * * Reserved for future extension. */ DRM_ASAHI_PRIORITY_REALTIME = 3, }; /** * struct drm_asahi_queue_create - Arguments passed to * DRM_IOCTL_ASAHI_QUEUE_CREATE */ struct drm_asahi_queue_create { /** @flags: MBZ */ __u32 flags; /** @vm_id: The ID of the VM this queue is bound to */ __u32 vm_id; /** @priority: One of drm_asahi_priority */ __u32 priority; /** @queue_id: The returned queue ID */ __u32 queue_id; /** * @usc_exec_base: GPU base address for all USC binaries (shaders) on * this queue. USC addresses are 32-bit relative to this 64-bit base. * * This sets the following registers on all queue commands: * * USC_EXEC_BASE_TA (vertex) * USC_EXEC_BASE_ISP (fragment) * USC_EXEC_BASE_CP (compute) * * While the hardware lets us configure these independently per command, * we do not have a use case for this. Instead, we expect userspace to * fix a 4GiB VA carveout for USC memory and pass its base address here. */ __u64 usc_exec_base; }; /** * struct drm_asahi_queue_destroy - Arguments passed to * DRM_IOCTL_ASAHI_QUEUE_DESTROY */ struct drm_asahi_queue_destroy { /** @queue_id: The queue ID to be destroyed */ __u32 queue_id; /** @pad: MBZ */ __u32 pad; }; /** * enum drm_asahi_sync_type - Sync item type */ enum drm_asahi_sync_type { /** @DRM_ASAHI_SYNC_SYNCOBJ: Binary sync object */ DRM_ASAHI_SYNC_SYNCOBJ = 0, /** @DRM_ASAHI_SYNC_TIMELINE_SYNCOBJ: Timeline sync object */ DRM_ASAHI_SYNC_TIMELINE_SYNCOBJ = 1, }; /** * struct drm_asahi_sync - Sync item */ struct drm_asahi_sync { /** @sync_type: One of drm_asahi_sync_type */ __u32 sync_type; /** @handle: The sync object handle */ __u32 handle; /** @timeline_value: Timeline value for timeline sync objects */ __u64 timeline_value; }; /** * define DRM_ASAHI_BARRIER_NONE - Command index for no barrier * * This special value may be passed in to drm_asahi_command::vdm_barrier or * drm_asahi_command::cdm_barrier to indicate that the respective subqueue * should not wait on any previous work. */ #define DRM_ASAHI_BARRIER_NONE (0xFFFFu) /** * struct drm_asahi_cmd_header - Top level command structure * * This struct is core to the command buffer definition and therefore is not * extensible. */ struct drm_asahi_cmd_header { /** @cmd_type: One of drm_asahi_cmd_type */ __u16 cmd_type; /** * @size: Size of this command, not including this header. * * For hardware commands, this enables extensibility of commands without * requiring extra command types. Passing a command that is shorter * than expected is explicitly allowed for backwards-compatibility. * Truncated fields will be zeroed. * * For the synthetic attachment setting commands, this implicitly * encodes the number of attachments. These commands take multiple * fixed-size @drm_asahi_attachment structures as their payload, so size * equals number of attachments * sizeof(struct drm_asahi_attachment). */ __u16 size; /** * @vdm_barrier: VDM (render) command index to wait on. * * Barriers are indices relative to the beginning of a given submit. A * barrier of 0 waits on commands submitted to the respective subqueue * in previous submit ioctls. A barrier of N waits on N previous * commands on the subqueue within the current submit ioctl. As a * special case, passing @DRM_ASAHI_BARRIER_NONE avoids waiting on any * commands in the subqueue. * * Examples: * * 0: This waits on all previous work. * * NONE: This does not wait for anything on this subqueue. * * 1: This waits on the first render command in the submit. * This is valid only if there are multiple render commands in the * same submit. * * Barriers are valid only for hardware commands. Synthetic software * commands to set attachments must pass NONE here. */ __u16 vdm_barrier; /** * @cdm_barrier: CDM (compute) command index to wait on. * * See @vdm_barrier, and replace VDM/render with CDM/compute. */ __u16 cdm_barrier; }; /** * struct drm_asahi_submit - Arguments passed to DRM_IOCTL_ASAHI_SUBMIT */ struct drm_asahi_submit { /** * @syncs: An optional pointer to an array of drm_asahi_sync. The first * @in_sync_count elements are in-syncs, then the remaining * @out_sync_count elements are out-syncs. Using a single array with * explicit partitioning simplifies handling. */ __u64 syncs; /** * @cmdbuf: Pointer to the command buffer to submit. * * This is a flat command buffer. By design, it contains no CPU * pointers, which makes it suitable for a virtgpu wire protocol without * requiring any serializing/deserializing step. * * It consists of a series of commands. Each command begins with a * fixed-size @drm_asahi_cmd_header header and is followed by a * variable-length payload according to the type and size in the header. * * The combined count of "real" hardware commands must be nonzero and at * most drm_asahi_params_global::max_commands_per_submission. */ __u64 cmdbuf; /** @flags: Flags for command submission (MBZ) */ __u32 flags; /** @queue_id: The queue ID to be submitted to */ __u32 queue_id; /** * @in_sync_count: Number of sync objects to wait on before starting * this job. */ __u32 in_sync_count; /** * @out_sync_count: Number of sync objects to signal upon completion of * this job. */ __u32 out_sync_count; /** @cmdbuf_size: Command buffer size in bytes */ __u32 cmdbuf_size; /** @pad: MBZ */ __u32 pad; }; /** * struct drm_asahi_attachment - Describe an "attachment". * * Attachments are any memory written by shaders, notably including render * target attachments written by the end-of-tile program. This is purely a hint * about the accessed memory regions. It is optional to specify, which is * fortunate as it cannot be specified precisely with bindless access anyway. * But where possible, it's probably a good idea for userspace to include these * hints, forwarded to the firmware. * * This struct is implicitly sized and therefore is not extensible. */ struct drm_asahi_attachment { /** @pointer: Base address of the attachment */ __u64 pointer; /** @size: Size of the attachment in bytes */ __u64 size; /** @pad: MBZ */ __u32 pad; /** @flags: MBZ */ __u32 flags; }; enum drm_asahi_render_flags { /** * @DRM_ASAHI_RENDER_VERTEX_SCRATCH: A vertex stage shader uses scratch * memory. */ DRM_ASAHI_RENDER_VERTEX_SCRATCH = (1U << 0), /** * @DRM_ASAHI_RENDER_PROCESS_EMPTY_TILES: Process even empty tiles. * This must be set when clearing render targets. */ DRM_ASAHI_RENDER_PROCESS_EMPTY_TILES = (1U << 1), /** * @DRM_ASAHI_RENDER_NO_VERTEX_CLUSTERING: Run vertex stage on a single * cluster (on multi-cluster GPUs) * * This harms performance but can workaround certain sync/coherency * bugs, and therefore is useful for debugging. */ DRM_ASAHI_RENDER_NO_VERTEX_CLUSTERING = (1U << 2), /** * @DRM_ASAHI_RENDER_DBIAS_IS_INT: Use integer depth bias formula. * * Graphics specifications contain two alternate formulas for depth * bias, a float formula used with floating-point depth buffers and an * integer formula using with unorm depth buffers. This flag specifies * that the integer formula should be used. If omitted, the float * formula is used instead. * * This corresponds to bit 18 of the relevant hardware control register, * so we match that here for efficiency. */ DRM_ASAHI_RENDER_DBIAS_IS_INT = (1U << 18), }; /** * struct drm_asahi_zls_buffer - Describe a depth or stencil buffer. * * These fields correspond to hardware registers in the ZLS (Z Load/Store) unit. * There are three hardware registers for each field respectively for loads, * stores, and partial renders. In practice, it makes sense to set all to the * same values, except in exceptional cases not yet implemented in userspace, so * we do not duplicate here for simplicity/efficiency. * * This struct is embedded in other structs and therefore is not extensible. */ struct drm_asahi_zls_buffer { /** @base: Base address of the buffer */ __u64 base; /** * @comp_base: If the load buffer is compressed, address of the * compression metadata section. */ __u64 comp_base; /** * @stride: If layered rendering is enabled, the number of bytes * between each layer of the buffer. */ __u32 stride; /** * @comp_stride: If layered rendering is enabled, the number of bytes * between each layer of the compression metadata. */ __u32 comp_stride; }; /** * struct drm_asahi_timestamp - Describe a timestamp write. * * The firmware can optionally write the GPU timestamp at render pass * granularities, but it needs to be mapped specially via * DRM_IOCTL_ASAHI_GEM_BIND_OBJECT. This structure therefore describes where to * write as a handle-offset pair, rather than a GPU address like normal. * * This struct is embedded in other structs and therefore is not extensible. */ struct drm_asahi_timestamp { /** * @handle: Handle of the timestamp buffer, or 0 to skip this * timestamp. If nonzero, this must equal the value returned in * drm_asahi_gem_bind_object::object_handle. */ __u32 handle; /** @offset: Offset to write into the timestamp buffer */ __u32 offset; }; /** * struct drm_asahi_timestamps - Describe timestamp writes. * * Each operation that can be timestamped, can be timestamped at the start and * end. Therefore, drm_asahi_timestamp structs always come in pairs, bundled * together into drm_asahi_timestamps. * * This struct is embedded in other structs and therefore is not extensible. */ struct drm_asahi_timestamps { /** @start: Timestamp recorded at the start of the operation */ struct drm_asahi_timestamp start; /** @end: Timestamp recorded at the end of the operation */ struct drm_asahi_timestamp end; }; /** * struct drm_asahi_helper_program - Describe helper program configuration. * * The helper program is a compute-like kernel required for various hardware * functionality. Its most important role is dynamically allocating * scratch/stack memory for individual subgroups, by partitioning a static * allocation shared for the whole device. It is supplied by userspace via * drm_asahi_helper_program and internally dispatched by the hardware as needed. * * This struct is embedded in other structs and therefore is not extensible. */ struct drm_asahi_helper_program { /** * @binary: USC address to the helper program binary. This is a tagged * pointer with configuration in the bottom bits. */ __u32 binary; /** @cfg: Additional configuration bits for the helper program. */ __u32 cfg; /** * @data: Data passed to the helper program. This value is not * interpreted by the kernel, firmware, or hardware in any way. It is * simply a sideband for userspace, set with the submit ioctl and read * via special registers inside the helper program. * * In practice, userspace will pass a 64-bit GPU VA here pointing to the * actual arguments, which presumably don't fit in 64-bits. */ __u64 data; }; /** * struct drm_asahi_bg_eot - Describe a background or end-of-tile program. * * The background and end-of-tile programs are dispatched by the hardware at the * beginning and end of rendering. As the hardware "tilebuffer" is simply local * memory, these programs are necessary to implement API-level render targets. * The fragment-like background program is responsible for loading either the * clear colour or the existing render target contents, while the compute-like * end-of-tile program stores the tilebuffer contents to memory. * * This struct is embedded in other structs and therefore is not extensible. */ struct drm_asahi_bg_eot { /** * @usc: USC address of the hardware USC words binding resources * (including images and uniforms) and the program itself. Note this is * an additional layer of indirection compared to the helper program, * avoiding the need for a sideband for data. This is a tagged pointer * with additional configuration in the bottom bits. */ __u32 usc; /** * @rsrc_spec: Resource specifier for the program. This is a packed * hardware data structure describing the required number of registers, * uniforms, bound textures, and bound samplers. */ __u32 rsrc_spec; }; /** * struct drm_asahi_cmd_render - Command to submit 3D * * This command submits a single render pass. The hardware control stream may * include many draws and subpasses, but within the command, the framebuffer * dimensions and attachments are fixed. * * The hardware requires the firmware to set a large number of Control Registers * setting up state at render pass granularity before each command rendering 3D. * The firmware bundles this state into data structures. Unfortunately, we * cannot expose either any of that directly to userspace, because the * kernel-firmware ABI is not stable. Although we can guarantee the firmware * updates in tandem with the kernel, we cannot break old userspace when * upgrading the firmware and kernel. Therefore, we need to abstract well the * data structures to avoid tying our hands with future firmwares. * * The bulk of drm_asahi_cmd_render therefore consists of values of hardware * control registers, marshalled via the firmware interface. * * The framebuffer/tilebuffer dimensions are also specified here. In addition to * being passed to the firmware/hardware, the kernel requires these dimensions * to calculate various essential tiling-related data structures. It is * unfortunate that our submits are heavier than on vendors with saner * hardware-software interfaces. The upshot is all of this information is * readily available to userspace with all current APIs. * * It looks odd - but it's not overly burdensome and it ensures we can remain * compatible with old userspace. */ struct drm_asahi_cmd_render { /** @flags: Combination of drm_asahi_render_flags flags. */ __u32 flags; /** * @isp_zls_pixels: ISP_ZLS_PIXELS register value. This contains the * depth/stencil width/height, which may differ from the framebuffer * width/height. */ __u32 isp_zls_pixels; /** * @vdm_ctrl_stream_base: VDM_CTRL_STREAM_BASE register value. GPU * address to the beginning of the VDM control stream. */ __u64 vdm_ctrl_stream_base; /** @vertex_helper: Helper program used for the vertex shader */ struct drm_asahi_helper_program vertex_helper; /** @fragment_helper: Helper program used for the fragment shader */ struct drm_asahi_helper_program fragment_helper; /** * @isp_scissor_base: ISP_SCISSOR_BASE register value. GPU address of an * array of scissor descriptors indexed in the render pass. */ __u64 isp_scissor_base; /** * @isp_dbias_base: ISP_DBIAS_BASE register value. GPU address of an * array of depth bias values indexed in the render pass. */ __u64 isp_dbias_base; /** * @isp_oclqry_base: ISP_OCLQRY_BASE register value. GPU address of an * array of occlusion query results written by the render pass. */ __u64 isp_oclqry_base; /** @depth: Depth buffer */ struct drm_asahi_zls_buffer depth; /** @stencil: Stencil buffer */ struct drm_asahi_zls_buffer stencil; /** @zls_ctrl: ZLS_CTRL register value */ __u64 zls_ctrl; /** @ppp_multisamplectl: PPP_MULTISAMPLECTL register value */ __u64 ppp_multisamplectl; /** * @sampler_heap: Base address of the sampler heap. This heap is used * for both vertex shaders and fragment shaders. The registers are * per-stage, but there is no known use case for separate heaps. */ __u64 sampler_heap; /** @ppp_ctrl: PPP_CTRL register value */ __u32 ppp_ctrl; /** @width_px: Framebuffer width in pixels */ __u16 width_px; /** @height_px: Framebuffer height in pixels */ __u16 height_px; /** @layers: Number of layers in the framebuffer */ __u16 layers; /** @sampler_count: Number of samplers in the sampler heap. */ __u16 sampler_count; /** @utile_width_px: Width of a logical tilebuffer tile in pixels */ __u8 utile_width_px; /** @utile_height_px: Height of a logical tilebuffer tile in pixels */ __u8 utile_height_px; /** @samples: # of samples in the framebuffer. Must be 1, 2, or 4. */ __u8 samples; /** @sample_size_B: # of bytes in the tilebuffer required per sample. */ __u8 sample_size_B; /** * @isp_merge_upper_x: 32-bit float used in the hardware triangle * merging. Calculate as: tan(60 deg) * width. * * Making these values UAPI avoids requiring floating-point calculations * in the kernel in the hot path. */ __u32 isp_merge_upper_x; /** * @isp_merge_upper_y: 32-bit float. Calculate as: tan(60 deg) * height. * See @isp_merge_upper_x. */ __u32 isp_merge_upper_y; /** @bg: Background program run for each tile at the start */ struct drm_asahi_bg_eot bg; /** @eot: End-of-tile program ran for each tile at the end */ struct drm_asahi_bg_eot eot; /** * @partial_bg: Background program ran at the start of each tile when * resuming the render pass during a partial render. */ struct drm_asahi_bg_eot partial_bg; /** * @partial_eot: End-of-tile program ran at the end of each tile when * pausing the render pass during a partial render. */ struct drm_asahi_bg_eot partial_eot; /** * @isp_bgobjdepth: ISP_BGOBJDEPTH register value. This is the depth * buffer clear value, encoded in the depth buffer's format: either a * 32-bit float or a 16-bit unorm (with upper bits zeroed). */ __u32 isp_bgobjdepth; /** * @isp_bgobjvals: ISP_BGOBJVALS register value. The bottom 8-bits * contain the stencil buffer clear value. */ __u32 isp_bgobjvals; /** @ts_vtx: Timestamps for the vertex portion of the render */ struct drm_asahi_timestamps ts_vtx; /** @ts_frag: Timestamps for the fragment portion of the render */ struct drm_asahi_timestamps ts_frag; }; /** * struct drm_asahi_cmd_compute - Command to submit compute * * This command submits a control stream consisting of compute dispatches. There * is essentially no limit on how many compute dispatches may be included in a * single compute command, although timestamps are at command granularity. */ struct drm_asahi_cmd_compute { /** @flags: MBZ */ __u32 flags; /** @sampler_count: Number of samplers in the sampler heap. */ __u32 sampler_count; /** * @cdm_ctrl_stream_base: CDM_CTRL_STREAM_BASE register value. GPU * address to the beginning of the CDM control stream. */ __u64 cdm_ctrl_stream_base; /** * @cdm_ctrl_stream_end: GPU base address to the end of the hardware * control stream. Note this only considers the first contiguous segment * of the control stream, as the stream might jump elsewhere. */ __u64 cdm_ctrl_stream_end; /** @sampler_heap: Base address of the sampler heap. */ __u64 sampler_heap; /** @helper: Helper program used for this compute command */ struct drm_asahi_helper_program helper; /** @ts: Timestamps for the compute command */ struct drm_asahi_timestamps ts; }; /** * struct drm_asahi_get_time - Arguments passed to DRM_IOCTL_ASAHI_GET_TIME */ struct drm_asahi_get_time { /** @flags: MBZ. */ __u64 flags; /** @gpu_timestamp: On return, the GPU timestamp in nanoseconds. */ __u64 gpu_timestamp; }; /** * DRM_IOCTL_ASAHI() - Build an Asahi IOCTL number * @__access: Access type. Must be R, W or RW. * @__id: One of the DRM_ASAHI_xxx id. * @__type: Suffix of the type being passed to the IOCTL. * * Don't use this macro directly, use the DRM_IOCTL_ASAHI_xxx * values instead. * * Return: An IOCTL number to be passed to ioctl() from userspace. */ #define DRM_IOCTL_ASAHI(__access, __id, __type) \ DRM_IO ## __access(DRM_COMMAND_BASE + DRM_ASAHI_ ## __id, \ struct drm_asahi_ ## __type) /* Note: this is an enum so that it can be resolved by Rust bindgen. */ enum { DRM_IOCTL_ASAHI_GET_PARAMS = DRM_IOCTL_ASAHI(W, GET_PARAMS, get_params), DRM_IOCTL_ASAHI_GET_TIME = DRM_IOCTL_ASAHI(WR, GET_TIME, get_time), DRM_IOCTL_ASAHI_VM_CREATE = DRM_IOCTL_ASAHI(WR, VM_CREATE, vm_create), DRM_IOCTL_ASAHI_VM_DESTROY = DRM_IOCTL_ASAHI(W, VM_DESTROY, vm_destroy), DRM_IOCTL_ASAHI_VM_BIND = DRM_IOCTL_ASAHI(W, VM_BIND, vm_bind), DRM_IOCTL_ASAHI_GEM_CREATE = DRM_IOCTL_ASAHI(WR, GEM_CREATE, gem_create), DRM_IOCTL_ASAHI_GEM_MMAP_OFFSET = DRM_IOCTL_ASAHI(WR, GEM_MMAP_OFFSET, gem_mmap_offset), DRM_IOCTL_ASAHI_GEM_BIND_OBJECT = DRM_IOCTL_ASAHI(WR, GEM_BIND_OBJECT, gem_bind_object), DRM_IOCTL_ASAHI_QUEUE_CREATE = DRM_IOCTL_ASAHI(WR, QUEUE_CREATE, queue_create), DRM_IOCTL_ASAHI_QUEUE_DESTROY = DRM_IOCTL_ASAHI(W, QUEUE_DESTROY, queue_destroy), DRM_IOCTL_ASAHI_SUBMIT = DRM_IOCTL_ASAHI(W, SUBMIT, submit), }; #if defined(__cplusplus) } #endif #endif /* _ASAHI_DRM_H_ */ ================================================ FILE: src/detection/gpu/gpu.c ================================================ #include "gpu.h" #include "common/debug.h" #include "detection/vulkan/vulkan.h" #include "detection/opencl/opencl.h" #include "detection/opengl/opengl.h" #include "modules/opengl/opengl.h" const char* FF_GPU_VENDOR_NAME_APPLE = "Apple"; const char* FF_GPU_VENDOR_NAME_AMD = "AMD"; const char* FF_GPU_VENDOR_NAME_INTEL = "Intel"; const char* FF_GPU_VENDOR_NAME_NVIDIA = "NVIDIA"; const char* FF_GPU_VENDOR_NAME_MTHREADS = "Moore Threads"; const char* FF_GPU_VENDOR_NAME_QUALCOMM = "Qualcomm"; const char* FF_GPU_VENDOR_NAME_MTK = "MTK"; const char* FF_GPU_VENDOR_NAME_VMWARE = "VMware"; const char* FF_GPU_VENDOR_NAME_PARALLEL = "Parallel"; const char* FF_GPU_VENDOR_NAME_MICROSOFT = "Microsoft"; const char* FF_GPU_VENDOR_NAME_REDHAT = "RedHat"; const char* FF_GPU_VENDOR_NAME_ORACLE = "Oracle"; const char* FF_GPU_VENDOR_NAME_BROADCOM = "Broadcom"; const char* FF_GPU_VENDOR_NAME_LOONGSON = "Loongson"; const char* FF_GPU_VENDOR_NAME_JINGJIA_MICRO = "Jingjia Micro"; const char* FF_GPU_VENDOR_NAME_HUAWEI = "Huawei"; const char* FF_GPU_VENDOR_NAME_ZHAOXIN = "Zhaoxin"; const char* ffGPUGetVendorString(unsigned vendorId) { // https://devicehunt.com/all-pci-vendors switch (vendorId) { case 0x106b: return FF_GPU_VENDOR_NAME_APPLE; case 0x1002: case 0x1022: case 0x1dd8: return FF_GPU_VENDOR_NAME_AMD; case 0x8086: case 0x8087: case 0x03e7: return FF_GPU_VENDOR_NAME_INTEL; case 0x0955: case 0x10de: case 0x12d2: return FF_GPU_VENDOR_NAME_NVIDIA; case 0x1ed5: return FF_GPU_VENDOR_NAME_MTHREADS; case 0x17cb: case 0x5143: return FF_GPU_VENDOR_NAME_QUALCOMM; case 0x14c3: return FF_GPU_VENDOR_NAME_MTK; case 0x15ad: return FF_GPU_VENDOR_NAME_VMWARE; case 0x1af4: return FF_GPU_VENDOR_NAME_REDHAT; case 0x1ab8: return FF_GPU_VENDOR_NAME_PARALLEL; case 0x1414: return FF_GPU_VENDOR_NAME_MICROSOFT; case 0x108e: return FF_GPU_VENDOR_NAME_ORACLE; case 0x182f: case 0x14e4: return FF_GPU_VENDOR_NAME_BROADCOM; case 0x0014: return FF_GPU_VENDOR_NAME_LOONGSON; case 0x0731: return FF_GPU_VENDOR_NAME_JINGJIA_MICRO; case 0x19e5: return FF_GPU_VENDOR_NAME_HUAWEI; case 0x1d17: return FF_GPU_VENDOR_NAME_ZHAOXIN; default: return NULL; } } const char* detectByOpenGL(FFlist* gpus) { FF_DEBUG("Starting OpenGL GPU detection fallback"); FFOpenGLResult result; ffStrbufInit(&result.version); ffStrbufInit(&result.renderer); ffStrbufInit(&result.vendor); ffStrbufInit(&result.slv); ffStrbufInit(&result.library); __attribute__((__cleanup__(ffDestroyOpenGLOptions))) FFOpenGLOptions options; ffInitOpenGLOptions(&options); const char* error = ffDetectOpenGL(&options, &result); FF_DEBUG("OpenGL detection returns: %s", error ?: "success"); if (!error) { FFGPUResult* gpu = (FFGPUResult*) ffListAdd(gpus); gpu->type = FF_GPU_TYPE_UNKNOWN; ffStrbufInitMove(&gpu->vendor, &result.vendor); ffStrbufInitMove(&gpu->name, &result.renderer); ffStrbufInit(&gpu->driver); ffStrbufInitF(&gpu->platformApi, "OpenGL %s", result.version.chars); ffStrbufInit(&gpu->memoryType); gpu->index = FF_GPU_INDEX_UNSET; gpu->temperature = FF_GPU_TEMP_UNSET; gpu->coreCount = FF_GPU_CORE_COUNT_UNSET; gpu->frequency = FF_GPU_FREQUENCY_UNSET; gpu->coreUsage = FF_GPU_CORE_USAGE_UNSET; gpu->dedicated = gpu->shared = (FFGPUMemory){0, 0}; gpu->deviceId = 0; FF_DEBUG("OpenGL reported renderer='%s', vendor='%s', version='%s'", gpu->name.chars, gpu->vendor.chars, result.version.chars); if (ffStrbufContainS(&gpu->name, "Apple")) { ffStrbufSetStatic(&gpu->vendor, FF_GPU_VENDOR_NAME_APPLE); gpu->type = FF_GPU_TYPE_INTEGRATED; } else if (ffStrbufContainS(&gpu->name, "Intel")) ffStrbufSetStatic(&gpu->vendor, FF_GPU_VENDOR_NAME_INTEL); else if (ffStrbufContainS(&gpu->name, "AMD") || ffStrbufContainS(&gpu->name, "ATI")) ffStrbufSetStatic(&gpu->vendor, FF_GPU_VENDOR_NAME_AMD); else if (ffStrbufContainS(&gpu->name, "NVIDIA")) ffStrbufSetStatic(&gpu->vendor, FF_GPU_VENDOR_NAME_NVIDIA); else if (ffStrbufContainS(&gpu->name, "MTT")) ffStrbufSetStatic(&gpu->vendor, FF_GPU_VENDOR_NAME_MTHREADS); FF_DEBUG("OpenGL fallback produced GPU: name='%s', vendor='%s', type=%u", gpu->name.chars, gpu->vendor.chars, gpu->type); } ffStrbufDestroy(&result.version); ffStrbufDestroy(&result.renderer); ffStrbufDestroy(&result.vendor); ffStrbufDestroy(&result.slv); ffStrbufDestroy(&result.library); return error; } const char* ffDetectGPU(const FFGPUOptions* options, FFlist* result) { FF_DEBUG("Starting GPU detection with method=%d", (int) options->detectionMethod); if (options->detectionMethod <= FF_GPU_DETECTION_METHOD_PCI) { FF_DEBUG("Trying PCI/native GPU detection"); const char* error = ffDetectGPUImpl(options, result); if (!error && result->length > 0) { FF_DEBUG("PCI/native GPU detection succeeded with %u GPU(s)", result->length); return NULL; } FF_DEBUG("PCI/native GPU detection did not produce results (error=%s, gpuCount=%u)", error ?: "none", result->length); } if (options->detectionMethod <= FF_GPU_DETECTION_METHOD_VULKAN) { FF_DEBUG("Trying Vulkan GPU detection fallback"); FFVulkanResult* vulkan = ffDetectVulkan(); if (!vulkan->error && vulkan->gpus.length > 0) { FF_DEBUG("Vulkan detection succeeded with %u GPU(s)", vulkan->gpus.length); ffListDestroy(result); ffListInitMove(result, &vulkan->gpus); #ifdef __ANDROID__ double ffGPUDetectTempFromTZ(void); if (options->temp && result->length == 1) { FF_DEBUG("Applying Android thermal-zone temperature to single Vulkan GPU"); FF_LIST_GET(FFGPUResult, *result, 0)->temperature = ffGPUDetectTempFromTZ(); } #endif return NULL; } FF_DEBUG("Vulkan detection did not produce results (error=%s, gpuCount=%u)", vulkan->error ?: "none", vulkan->gpus.length); } if (options->detectionMethod <= FF_GPU_DETECTION_METHOD_OPENCL) { FF_DEBUG("Trying OpenCL GPU detection fallback"); FFOpenCLResult* opencl = ffDetectOpenCL(); if (!opencl->error && opencl->gpus.length > 0) { FF_DEBUG("OpenCL detection succeeded with %u GPU(s)", opencl->gpus.length); ffListDestroy(result); ffListInitMove(result, &opencl->gpus); return NULL; } FF_DEBUG("OpenCL detection did not produce results (error=%s, gpuCount=%u)", opencl->error ?: "none", opencl->gpus.length); } if (options->detectionMethod <= FF_GPU_DETECTION_METHOD_OPENGL) { FF_DEBUG("Trying OpenGL GPU detection fallback"); const char* error = detectByOpenGL(result); if (error == NULL) { FF_DEBUG("OpenGL fallback succeeded with %u GPU(s)", result->length); return NULL; } FF_DEBUG("OpenGL fallback failed: %s", error); } FF_DEBUG("GPU detection failed in all enabled backends"); return "GPU detection failed"; } ================================================ FILE: src/detection/gpu/gpu.h ================================================ #pragma once #include "fastfetch.h" #include "modules/gpu/option.h" #define FF_GPU_TEMP_UNSET (-DBL_MAX) #define FF_GPU_CORE_COUNT_UNSET -1 #define FF_GPU_VMEM_SIZE_UNSET ((uint64_t)-1) #define FF_GPU_FREQUENCY_UNSET 0 #define FF_GPU_CORE_USAGE_UNSET (-DBL_MAX) #define FF_GPU_INDEX_UNSET ((uint32_t)-1) extern const char* FF_GPU_VENDOR_NAME_APPLE; extern const char* FF_GPU_VENDOR_NAME_AMD; extern const char* FF_GPU_VENDOR_NAME_INTEL; extern const char* FF_GPU_VENDOR_NAME_NVIDIA; extern const char* FF_GPU_VENDOR_NAME_MTHREADS; extern const char* FF_GPU_VENDOR_NAME_QUALCOMM; extern const char* FF_GPU_VENDOR_NAME_MTK; extern const char* FF_GPU_VENDOR_NAME_VMWARE; extern const char* FF_GPU_VENDOR_NAME_PARALLEL; extern const char* FF_GPU_VENDOR_NAME_MICROSOFT; extern const char* FF_GPU_VENDOR_NAME_REDHAT; extern const char* FF_GPU_VENDOR_NAME_ORACLE; extern const char* FF_GPU_VENDOR_NAME_BROADCOM; extern const char* FF_GPU_VENDOR_NAME_LOONGSON; extern const char* FF_GPU_VENDOR_NAME_JINGJIA_MICRO; extern const char* FF_GPU_VENDOR_NAME_HUAWEI; extern const char* FF_GPU_VENDOR_NAME_ZHAOXIN; typedef struct FFGPUMemory { uint64_t total; uint64_t used; } FFGPUMemory; typedef struct FFGPUResult { uint32_t index; FFGPUType type; FFstrbuf vendor; FFstrbuf name; FFstrbuf driver; FFstrbuf platformApi; FFstrbuf memoryType; double temperature; double coreUsage; int32_t coreCount; uint32_t frequency; // Maximum time clock frequency in MHz FFGPUMemory dedicated; FFGPUMemory shared; uint64_t deviceId; } FFGPUResult; const char* ffDetectGPU(const FFGPUOptions* options, FFlist* result); const char* ffDetectGPUImpl(const FFGPUOptions* options, FFlist* gpus); const char* ffGPUGetVendorString(unsigned vendorId); typedef struct FFGpuDriverPciBusId { uint32_t domain; uint32_t bus; uint32_t device; uint32_t func; } FFGpuDriverPciBusId; #if defined(__linux__) || defined(__FreeBSD__) || defined(__sun) || defined(__NetBSD__) || defined(__OpenBSD__) || defined(__HAIKU__) || defined(__GNU__) void ffGPUFillVendorAndName(uint8_t subclass, uint16_t vendor, uint16_t device, FFGPUResult* gpu); void ffGPUQueryAmdGpuName(uint16_t deviceId, uint8_t revisionId, FFGPUResult* gpu); #if FF_HAVE_DRM const char* ffDrmDetectRadeon(const FFGPUOptions* options, FFGPUResult* gpu, const char* renderPath); const char* ffDrmDetectAmdgpu(const FFGPUOptions* options, FFGPUResult* gpu, const char* renderPath); const char* ffDrmDetectI915(FFGPUResult* gpu, int fd); const char* ffDrmDetectXe(FFGPUResult* gpu, int fd); const char* ffDrmDetectAsahi(FFGPUResult* gpu, int fd); const char* ffDrmDetectNouveau(FFGPUResult* gpu, int fd); #endif // FF_HAVE_DRM const char* ffGPUDetectDriverSpecific(const FFGPUOptions* options, FFGPUResult* gpu, FFGpuDriverPciBusId pciBusId); #endif // defined(XXX) static inline uint64_t ffGPUPciAddr2Id(uint64_t domain, uint64_t bus, uint64_t device, uint64_t function) { return (domain << 16) | (bus << 8) | (device << 3) | function; } static inline uint64_t ffGPUGeneral2Id(uint64_t originalId) { // Note: originalId may already have the MSB set return (1ULL << 63) | originalId; } ================================================ FILE: src/detection/gpu/gpu_amd.c ================================================ #include "gpu_driver_specific.h" #include "adl.h" #include "common/library.h" #include "common/mallocHelper.h" #include "common/debug.h" // Helper function to convert ADL status code to string FF_MAYBE_UNUSED static const char* ffAdlStatusToString(int status) { switch (status) { #define FF_ADL_STATUS_CASE(name) case name: return #name; FF_ADL_STATUS_CASE(ADL_OK) FF_ADL_STATUS_CASE(ADL_OK_WARNING) FF_ADL_STATUS_CASE(ADL_OK_MODE_CHANGE) FF_ADL_STATUS_CASE(ADL_OK_RESTART) FF_ADL_STATUS_CASE(ADL_OK_WAIT) FF_ADL_STATUS_CASE(ADL_ERR) FF_ADL_STATUS_CASE(ADL_ERR_NOT_INIT) FF_ADL_STATUS_CASE(ADL_ERR_INVALID_PARAM) FF_ADL_STATUS_CASE(ADL_ERR_INVALID_PARAM_SIZE) FF_ADL_STATUS_CASE(ADL_ERR_INVALID_ADL_IDX) FF_ADL_STATUS_CASE(ADL_ERR_INVALID_CONTROLLER_IDX) FF_ADL_STATUS_CASE(ADL_ERR_INVALID_DIPLAY_IDX) FF_ADL_STATUS_CASE(ADL_ERR_NOT_SUPPORTED) FF_ADL_STATUS_CASE(ADL_ERR_NULL_POINTER) FF_ADL_STATUS_CASE(ADL_ERR_DISABLED_ADAPTER) FF_ADL_STATUS_CASE(ADL_ERR_INVALID_CALLBACK) FF_ADL_STATUS_CASE(ADL_ERR_RESOURCE_CONFLICT) FF_ADL_STATUS_CASE(ADL_ERR_SET_INCOMPLETE) FF_ADL_STATUS_CASE(ADL_ERR_NO_XDISPLAY) FF_ADL_STATUS_CASE(ADL_ERR_CALL_TO_INCOMPATIABLE_DRIVER) FF_ADL_STATUS_CASE(ADL_ERR_NO_ADMINISTRATOR_PRIVILEGES) FF_ADL_STATUS_CASE(ADL_ERR_FEATURESYNC_NOT_STARTED) FF_ADL_STATUS_CASE(ADL_ERR_INVALID_POWER_STATE) #undef FF_ADL_STATUS_CASE default: return "Unknown ADL error"; } } // Memory allocation function static void* __attribute__((__stdcall__)) ffAdlMainMemoryAlloc(int iSize) { return malloc((size_t) iSize); } struct FFAdlData { FF_LIBRARY_SYMBOL(ADL2_Main_Control_Destroy) FF_LIBRARY_SYMBOL(ADL2_Adapter_AdapterInfoX3_Get) FF_LIBRARY_SYMBOL(ADL2_Adapter_Graphic_Core_Info_Get) FF_LIBRARY_SYMBOL(ADL2_Adapter_MemoryInfo2_Get) FF_LIBRARY_SYMBOL(ADL2_Adapter_DedicatedVRAMUsage_Get) FF_LIBRARY_SYMBOL(ADL2_Adapter_ASICFamilyType_Get) FF_LIBRARY_SYMBOL(ADL2_Overdrive_Caps) FF_LIBRARY_SYMBOL(ADL2_OverdriveN_CapabilitiesX2_Get) FF_LIBRARY_SYMBOL(ADL2_OverdriveN_SystemClocksX2_Get) FF_LIBRARY_SYMBOL(ADL2_OverdriveN_PerformanceStatus_Get) FF_LIBRARY_SYMBOL(ADL2_OverdriveN_Temperature_Get) FF_LIBRARY_SYMBOL(ADL2_Overdrive8_Current_Setting_Get) FF_LIBRARY_SYMBOL(ADL2_New_QueryPMLogData_Get) FF_LIBRARY_SYMBOL(ADL2_Overdrive6_CurrentStatus_Get) FF_LIBRARY_SYMBOL(ADL2_Overdrive6_Temperature_Get) FF_LIBRARY_SYMBOL(ADL2_Overdrive6_StateInfo_Get) bool inited; ADL_CONTEXT_HANDLE apiHandle; } adlData; static void shutdownAdl() { if (adlData.apiHandle) { FF_DEBUG("Destroying ADL context"); adlData.ffADL2_Main_Control_Destroy(adlData.apiHandle); adlData.apiHandle = NULL; } } const char* ffDetectAmdGpuInfo(const FFGpuDriverCondition* cond, FFGpuDriverResult result, const char* soName) { FF_DEBUG("Attempting to detect AMD GPU info using '%s'", soName); if (!adlData.inited) { adlData.inited = true; FF_DEBUG("Initializing ADL library"); FF_LIBRARY_LOAD(atiadl, "dlopen atiadlxx failed", soName , 1); FF_LIBRARY_LOAD_SYMBOL_MESSAGE(atiadl, ADL2_Main_Control_Create) FF_LIBRARY_LOAD_SYMBOL_VAR_MESSAGE(atiadl, adlData, ADL2_Main_Control_Destroy) FF_LIBRARY_LOAD_SYMBOL_VAR_MESSAGE(atiadl, adlData, ADL2_Adapter_AdapterInfoX3_Get) FF_LIBRARY_LOAD_SYMBOL_VAR_MESSAGE(atiadl, adlData, ADL2_Adapter_Graphic_Core_Info_Get) FF_LIBRARY_LOAD_SYMBOL_VAR_MESSAGE(atiadl, adlData, ADL2_Adapter_MemoryInfo2_Get) FF_LIBRARY_LOAD_SYMBOL_VAR_MESSAGE(atiadl, adlData, ADL2_Adapter_DedicatedVRAMUsage_Get) FF_LIBRARY_LOAD_SYMBOL_VAR_MESSAGE(atiadl, adlData, ADL2_Adapter_ASICFamilyType_Get) FF_LIBRARY_LOAD_SYMBOL_VAR_MESSAGE(atiadl, adlData, ADL2_Overdrive_Caps) FF_LIBRARY_LOAD_SYMBOL_VAR_MESSAGE(atiadl, adlData, ADL2_OverdriveN_CapabilitiesX2_Get) FF_LIBRARY_LOAD_SYMBOL_VAR_MESSAGE(atiadl, adlData, ADL2_OverdriveN_SystemClocksX2_Get) FF_LIBRARY_LOAD_SYMBOL_VAR_MESSAGE(atiadl, adlData, ADL2_OverdriveN_PerformanceStatus_Get) FF_LIBRARY_LOAD_SYMBOL_VAR_MESSAGE(atiadl, adlData, ADL2_Overdrive8_Current_Setting_Get) FF_LIBRARY_LOAD_SYMBOL_VAR_MESSAGE(atiadl, adlData, ADL2_New_QueryPMLogData_Get) FF_LIBRARY_LOAD_SYMBOL_VAR_MESSAGE(atiadl, adlData, ADL2_OverdriveN_Temperature_Get) FF_LIBRARY_LOAD_SYMBOL_VAR_MESSAGE(atiadl, adlData, ADL2_Overdrive6_CurrentStatus_Get) FF_LIBRARY_LOAD_SYMBOL_VAR_MESSAGE(atiadl, adlData, ADL2_Overdrive6_Temperature_Get) FF_LIBRARY_LOAD_SYMBOL_VAR_MESSAGE(atiadl, adlData, ADL2_Overdrive6_StateInfo_Get) FF_DEBUG("ADL library loaded"); int result = ffADL2_Main_Control_Create(ffAdlMainMemoryAlloc, 1 /*iEnumConnectedAdapters*/, &adlData.apiHandle); FF_DEBUG("ADL2_Main_Control_Create returned %s (%d)", ffAdlStatusToString(result), result); if (result != ADL_OK) return "ffADL2_Main_Control_Create() failed"; atexit(shutdownAdl); atiadl = NULL; // don't close atiadl FF_DEBUG("ADL initialization complete"); } if (!adlData.apiHandle) { FF_DEBUG("ADL context not initialized"); return "ffADL2_Main_Control_Create() failed"; } FF_AUTO_FREE AdapterInfo* devices = NULL; int numDevices = 0; int adapterResult = adlData.ffADL2_Adapter_AdapterInfoX3_Get(adlData.apiHandle, -1, &numDevices, &devices); FF_DEBUG("ADL2_Adapter_AdapterInfoX3_Get returned %s (%d)", ffAdlStatusToString(adapterResult), adapterResult); if (adapterResult == ADL_OK) { FF_DEBUG("found %d adapters", numDevices); } else { FF_DEBUG("ffADL2_Adapter_AdapterInfoX3_Get() failed"); return "ffADL2_Adapter_AdapterInfoX3_Get() failed"; } const AdapterInfo* device = NULL; for (int iDev = 0; iDev < numDevices; iDev++) { if (cond->type & FF_GPU_DRIVER_CONDITION_TYPE_BUS_ID) { FF_DEBUG("Checking device %d: bus=%d, device=%d, func=%d against requested bus=%u, device=%u, func=%u", iDev, devices[iDev].iBusNumber, devices[iDev].iDeviceNumber, devices[iDev].iFunctionNumber, cond->pciBusId.bus, cond->pciBusId.device, cond->pciBusId.func); if ( cond->pciBusId.bus == (uint32_t) devices[iDev].iBusNumber && cond->pciBusId.device == (uint32_t) devices[iDev].iDeviceNumber && cond->pciBusId.func == (uint32_t) devices[iDev].iFunctionNumber) { device = &devices[iDev]; FF_DEBUG("Found matching device: %s (index: %d)", device->strAdapterName, device->iAdapterIndex); break; } } } if (!device) { FF_DEBUG("Device not found"); return "Device not found"; } if (result.coreCount) { ADLGraphicCoreInfo coreInfo; int status = adlData.ffADL2_Adapter_Graphic_Core_Info_Get(adlData.apiHandle, device->iAdapterIndex, &coreInfo); FF_DEBUG("ADL2_Adapter_Graphic_Core_Info_Get returned %s (%d)", ffAdlStatusToString(status), status); if (status == ADL_OK) { FF_DEBUG("Core info - NumCUs: %d, NumPEsPerCU: %d", coreInfo.iNumCUs, coreInfo.iNumPEsPerCU); *result.coreCount = (uint32_t) coreInfo.iNumCUs * (uint32_t) coreInfo.iNumPEsPerCU; FF_DEBUG("Got core count: %u", *result.coreCount); } else { FF_DEBUG("Failed to get core count"); } } if (result.memory) { int vramUsage = 0; int status = adlData.ffADL2_Adapter_DedicatedVRAMUsage_Get(adlData.apiHandle, device->iAdapterIndex, &vramUsage); FF_DEBUG("ADL2_Adapter_DedicatedVRAMUsage_Get returned %s (%d), usage: %d MB", ffAdlStatusToString(status), status, vramUsage); if (status == ADL_OK && vramUsage >= 0) { result.memory->used = (uint64_t) vramUsage * 1024 * 1024; FF_DEBUG("Dedicated VRAM usage: %llu bytes (%d MB)", result.memory->used, vramUsage); } else { FF_DEBUG("Failed to get dedicated VRAM usage"); } } if (result.memoryType) { ADLMemoryInfo2 memoryInfo; int status = adlData.ffADL2_Adapter_MemoryInfo2_Get(adlData.apiHandle, device->iAdapterIndex, &memoryInfo); FF_DEBUG("ADL2_Adapter_MemoryInfo2_Get returned %s (%d)", ffAdlStatusToString(status), status); if (status == ADL_OK) { FF_DEBUG("Memory info - Type: %s, Size: %lld MB", memoryInfo.strMemoryType, memoryInfo.iMemorySize / 1024 / 1024); ffStrbufSetS(result.memoryType, memoryInfo.strMemoryType); FF_DEBUG("Got memory type: %s", memoryInfo.strMemoryType); } else { FF_DEBUG("Failed to get memory type"); } } if (result.type) { int asicTypes = 0; int valids = 0; int status = adlData.ffADL2_Adapter_ASICFamilyType_Get(adlData.apiHandle, device->iAdapterIndex, &asicTypes, &valids); FF_DEBUG("ADL2_Adapter_ASICFamilyType_Get returned %s (%d), asicTypes: 0x%x, valids: 0x%x", ffAdlStatusToString(status), status, asicTypes, valids); if (status == ADL_OK) { asicTypes &= valids; // This design is strange *result.type = asicTypes & ADL_ASIC_INTEGRATED ? FF_GPU_TYPE_INTEGRATED : FF_GPU_TYPE_DISCRETE; FF_DEBUG("GPU type: %s (asicTypes: 0x%x, valids: 0x%x)", *result.type == FF_GPU_TYPE_INTEGRATED ? "Integrated" : "Discrete", asicTypes, valids); } else { FF_DEBUG("Failed to get GPU type"); } } if (result.index) { *result.index = (uint32_t) device->iAdapterIndex; FF_DEBUG("Setting adapter index: %u", *result.index); } if (result.name) { ffStrbufSetS(result.name, device->strAdapterName); FF_DEBUG("Setting adapter name: %s; UDID: %s, Present: %d, Exist: %d", device->strAdapterName, device->strUDID, device->iPresent, device->iExist); } int odVersion = 0; { int odSupported = 0; int odEnabled = 0; int status = adlData.ffADL2_Overdrive_Caps(adlData.apiHandle, device->iAdapterIndex, &odSupported, &odEnabled, &odVersion); FF_DEBUG("ADL2_Overdrive_Caps returned %s (%d); supported %d, enabled %d; version %d", ffAdlStatusToString(status), status, odSupported, odEnabled, odVersion); if (status != ADL_OK) { FF_DEBUG("Overdrive not supported, results may be inaccurate"); // Note even if Overdrive is not supported, we can still get the OD version } } if (odVersion == 8) { FF_DEBUG("Using Overdrive8 API (odVersion=%d)", odVersion); if (result.frequency) { ADLOD8CurrentSetting currentSetting = { .count = OD8_COUNT }; int status = adlData.ffADL2_Overdrive8_Current_Setting_Get(adlData.apiHandle, device->iAdapterIndex, ¤tSetting); FF_DEBUG("ADL2_Overdrive8_Current_Setting_Get returned %s (%d)", ffAdlStatusToString(status), status); if (status == ADL_OK) { FF_DEBUG("OD8 Settings count: %d", currentSetting.count); *result.frequency = (uint32_t) currentSetting.Od8SettingTable[OD8_GFXCLK_FMAX]; FF_DEBUG("Got max engine clock (OD8_GFXCLK_FMAX): %u MHz", *result.frequency); } else { FF_DEBUG("Failed to get max frequency information"); } } if (result.temp || result.coreUsage) { ADLPMLogDataOutput pmLogDataOutput = {}; int status = adlData.ffADL2_New_QueryPMLogData_Get(adlData.apiHandle, device->iAdapterIndex, &pmLogDataOutput); FF_DEBUG("ADL2_New_QueryPMLogData_Get returned %s (%d)", ffAdlStatusToString(status), status); if (status == ADL_OK) { if (result.temp) { ADLSingleSensorData* sensor = &pmLogDataOutput.sensors[ADL_PMLOG_TEMPERATURE_HOTSPOT]; FF_DEBUG("Sensor %d: %s, supported: %d, value: %d", ADL_PMLOG_TEMPERATURE_HOTSPOT, "ADL_PMLOG_TEMPERATURE_HOTSPOT", sensor->supported, sensor->value); if (sensor->supported) { *result.temp = sensor->value; FF_DEBUG("Temperature: %.1f°C (HOTSPOT)", *result.temp); } else { sensor = &pmLogDataOutput.sensors[ADL_PMLOG_TEMPERATURE_GFX]; FF_DEBUG("Sensor %d: %s, supported: %d, value: %d", ADL_PMLOG_TEMPERATURE_GFX, "ADL_PMLOG_TEMPERATURE_GFX", sensor->supported, sensor->value); if (sensor->supported) { *result.temp = sensor->value; FF_DEBUG("Temperature: %.1f°C (GFX)", *result.temp); } else { sensor = &pmLogDataOutput.sensors[ADL_PMLOG_TEMPERATURE_SOC]; FF_DEBUG("Sensor %d: %s, supported: %d, value: %d", ADL_PMLOG_TEMPERATURE_SOC, "ADL_PMLOG_TEMPERATURE_SOC", sensor->supported, sensor->value); if (sensor->supported) { *result.temp = sensor->value; FF_DEBUG("Temperature: %.1f°C (SOC)", *result.temp); } else { FF_DEBUG("No supported temp sensor found, temp detection failed"); } } } } if (result.coreUsage) { ADLSingleSensorData* activity = &pmLogDataOutput.sensors[ADL_PMLOG_INFO_ACTIVITY_GFX]; FF_DEBUG("Sensor %d: %s, supported: %d, value: %d", ADL_PMLOG_INFO_ACTIVITY_GFX, "ADL_PMLOG_INFO_ACTIVITY_GFX", activity->supported, activity->value); if (activity->supported) { *result.coreUsage = activity->value; FF_DEBUG("Core usage: %.1f%%", *result.coreUsage); } else { FF_DEBUG("Sensor %d not supported, GPU usage detection failed", ADL_PMLOG_INFO_ACTIVITY_GFX); } } } else { FF_DEBUG("Failed to get temperature / GPU activity"); } } } else if (odVersion == 7) { FF_DEBUG("Using OverdriveN API (odVersion=%d)", odVersion); if (result.frequency) { // https://github.com/MaynardMiner/odvii/blob/master/OverdriveN.cpp#L176 ADLODNCapabilitiesX2 odCapabilities = {}; int status = adlData.ffADL2_OverdriveN_CapabilitiesX2_Get(adlData.apiHandle, device->iAdapterIndex, &odCapabilities); FF_DEBUG("ADL2_OverdriveN_CapabilitiesX2_Get returned %s (%d)", ffAdlStatusToString(status), status); if (status == ADL_OK) { if (odCapabilities.iMaximumNumberOfPerformanceLevels == 0) { FF_DEBUG("ADL2_OverdriveN_CapabilitiesX2_Get: no performance levels available"); } else { FF_DEBUG("ODN Capabilities - MaxPerformanceLevels: %d, GPU Clock Range: [%d - %d]", odCapabilities.iMaximumNumberOfPerformanceLevels, odCapabilities.sEngineClockRange.iMin, odCapabilities.sEngineClockRange.iMax); size_t size = sizeof(ADLODNPerformanceLevelsX2) + sizeof(ADLODNPerformanceLevelX2) * ((unsigned) odCapabilities.iMaximumNumberOfPerformanceLevels - 1); FF_AUTO_FREE ADLODNPerformanceLevelsX2* odPerfLevels = calloc(size, 1); odPerfLevels->iSize = (int) size; odPerfLevels->iNumberOfPerformanceLevels = odCapabilities.iMaximumNumberOfPerformanceLevels; odPerfLevels->iMode = ODNControlType_Current; int status = adlData.ffADL2_OverdriveN_SystemClocksX2_Get(adlData.apiHandle, device->iAdapterIndex, odPerfLevels); FF_DEBUG("ADL2_OverdriveN_SystemClocksX2_Get returned %s (%d), levels: %d", ffAdlStatusToString(status), status, odPerfLevels->iNumberOfPerformanceLevels); if (status != ADL_OK) { FF_DEBUG("Failed to get frequency information"); } else { // lowest to highest for (int i = odPerfLevels->iNumberOfPerformanceLevels - 1; i >= 0 ; i--) { ADLODNPerformanceLevelX2* level = &odPerfLevels->aLevels[i]; FF_DEBUG("Performance level %d: enabled: %d, engine clock = %d", i, level->iEnabled, level->iClock); if (level->iEnabled) { *result.frequency = (uint32_t) level->iClock / 100; // in 10 kHz FF_DEBUG("Got max engine clock: %u MHz", *result.frequency); break; } } } } } else { FF_DEBUG("Failed to get frequency information"); } } if (result.coreUsage) { ADLODNPerformanceStatus performanceStatus = {}; int status = adlData.ffADL2_OverdriveN_PerformanceStatus_Get(adlData.apiHandle, device->iAdapterIndex, &performanceStatus); FF_DEBUG("ADL2_OverdriveN_PerformanceStatus_Get returned %s (%d)", ffAdlStatusToString(status), status); if (status == ADL_OK) { FF_DEBUG("Performance Status - Activity: %d%%, CoreClock: %dMHz, MemoryClock: %dMHz", performanceStatus.iGPUActivityPercent, performanceStatus.iCoreClock, performanceStatus.iMemoryClock); *result.coreUsage = performanceStatus.iGPUActivityPercent; FF_DEBUG("Got GPU activity: %d%%", performanceStatus.iGPUActivityPercent); } else { FF_DEBUG("Failed to get GPU activity"); } } if (result.temp) { int milliDegrees = 0; int status = adlData.ffADL2_OverdriveN_Temperature_Get(adlData.apiHandle, device->iAdapterIndex, 1, &milliDegrees); FF_DEBUG("ADL2_OverdriveN_Temperature_Get returned %s (%d)", ffAdlStatusToString(status), status); if (status == ADL_OK) { *result.temp = milliDegrees / 1000.0; FF_DEBUG("Temperature: %.1f°C (raw: %d milliC)", *result.temp, milliDegrees); } else { FF_DEBUG("Failed to get temperature"); } } } else if (odVersion == 6) { FF_DEBUG("Using Overdrive6 API (odVersion=%d)", odVersion); if (result.frequency) { FF_AUTO_FREE ADLOD6StateInfo* stateInfo = calloc(sizeof(ADLOD6StateInfo) + sizeof(ADLOD6PerformanceLevel), 1); stateInfo->iNumberOfPerformanceLevels = 2; int status = adlData.ffADL2_Overdrive6_StateInfo_Get(adlData.apiHandle, device->iAdapterIndex, ADL_OD6_GETSTATEINFO_CUSTOM_PERFORMANCE, stateInfo); FF_DEBUG("ADL2_Overdrive6_StateInfo_Get returned %s (%d), performance levels: %d", ffAdlStatusToString(status), status, stateInfo->iNumberOfPerformanceLevels); if (status == ADL_OK) { // OD6 uses clock ranges instead of discrete performance levels. // iNumberOfPerformanceLevels is always 2. // The 1st level indicates the minimum clocks in the range. // The 2nd level indicates the maximum clocks in the range. if (stateInfo->iNumberOfPerformanceLevels != 2) { FF_DEBUG("ADL2_Overdrive6_StateInfo_Get: unexpected number of performance levels: %d", stateInfo->iNumberOfPerformanceLevels); } else { FF_DEBUG("OD6 Settings - MinPerformanceLevels: %d, MaxPerformanceLevels: %d", stateInfo->aLevels[0].iEngineClock, stateInfo->aLevels[1].iEngineClock); *result.frequency = (uint32_t) stateInfo->aLevels[1].iEngineClock / 100; // in 10 kHz FF_DEBUG("Got max engine clock: %u MHz", *result.frequency); } } else { FF_DEBUG("Failed to get frequency information"); } } if (result.coreUsage) { ADLOD6CurrentStatus status = {}; int apiStatus = adlData.ffADL2_Overdrive6_CurrentStatus_Get(adlData.apiHandle, device->iAdapterIndex, &status); FF_DEBUG("ADL2_Overdrive6_CurrentStatus_Get returned %s (%d)", ffAdlStatusToString(apiStatus), apiStatus); if (apiStatus == ADL_OK) { *result.coreUsage = status.iActivityPercent; FF_DEBUG("Got GPU activity: %d%%", status.iActivityPercent); } else { FF_DEBUG("Failed to get GPU activity"); } } if (result.temp) { int milliDegrees = 0; int status = adlData.ffADL2_Overdrive6_Temperature_Get(adlData.apiHandle, device->iAdapterIndex, &milliDegrees); FF_DEBUG("ADL2_Overdrive6_Temperature_Get returned %s (%d), temperature: %d milliC", ffAdlStatusToString(status), status, milliDegrees); if (status == ADL_OK) { *result.temp = milliDegrees / 1000.0; FF_DEBUG("Temperature: %.1f°C", *result.temp); } else { FF_DEBUG("Failed to get temperature"); } } } else { FF_DEBUG("Unknown Overdrive version: %d", odVersion); return "Unknown Overdrive version"; } FF_DEBUG("AMD GPU detection complete - returning success"); return NULL; } ================================================ FILE: src/detection/gpu/gpu_android.c ================================================ #include "gpu.h" #include "common/io.h" #include "common/stringUtils.h" #include static double parseTZDir(int dfd, FFstrbuf* buffer) { if (!ffReadFileBufferRelative(dfd, "type", buffer) || !ffStrbufStartsWithS(buffer, "gpu")) return FF_GPU_TEMP_UNSET; if (!ffReadFileBufferRelative(dfd, "temp", buffer)) return FF_GPU_TEMP_UNSET; double value = ffStrbufToDouble(buffer, FF_GPU_TEMP_UNSET);// millidegree Celsius if (value == FF_GPU_TEMP_UNSET) return FF_GPU_TEMP_UNSET; return value / 1000.; } double ffGPUDetectTempFromTZ(void) { FF_AUTO_CLOSE_DIR DIR* dirp = opendir("/sys/class/thermal/"); if(dirp) { FF_STRBUF_AUTO_DESTROY buffer = ffStrbufCreate(); int dfd = dirfd(dirp); struct dirent* entry; while((entry = readdir(dirp)) != NULL) { if(entry->d_name[0] == '.') continue; if(!ffStrStartsWith(entry->d_name, "thermal_zone")) continue; FF_AUTO_CLOSE_FD int subfd = openat(dfd, entry->d_name, O_RDONLY | O_DIRECTORY | O_CLOEXEC); if(subfd < 0) continue; double result = parseTZDir(subfd, &buffer); if (result != FF_GPU_TEMP_UNSET) return result; } } return FF_GPU_TEMP_UNSET; } const char* ffDetectGPUImpl(const FFGPUOptions* options, FFlist* gpus) { FF_UNUSED(options, gpus); return "No permission. Fallbacks to Vulkan, OpenCL or OpenGL instead"; } ================================================ FILE: src/detection/gpu/gpu_apple.c ================================================ #include "gpu.h" #include "common/apple/cf_helpers.h" #include "common/apple/smc_temps.h" #include const char* ffGpuDetectMetal(FFlist* gpus); const char* ffGpuDetectDriverVersion(FFlist* gpus); static double detectGpuTemp(const FFstrbuf* gpuName) { double result = 0; const char* error = NULL; if (ffStrbufStartsWithS(gpuName, "Apple M")) { switch (strtol(gpuName->chars + strlen("Apple M"), NULL, 10)) { case 0: error = "Invalid Apple Silicon GPU"; break; case 1: error = ffDetectSmcTemps(FF_TEMP_GPU_M1X, &result); break; case 2: error = ffDetectSmcTemps(FF_TEMP_GPU_M2X, &result); break; case 3: error = ffDetectSmcTemps(FF_TEMP_GPU_M3X, &result); break; case 4: error = ffDetectSmcTemps(FF_TEMP_GPU_M4X, &result); break; default: error = "Unsupported Apple Silicon GPU"; break; } } else if (ffStrbufStartsWithS(gpuName, "Intel")) error = ffDetectSmcTemps(FF_TEMP_GPU_INTEL, &result); else if (ffStrbufStartsWithS(gpuName, "Radeon") || ffStrbufStartsWithS(gpuName, "AMD")) error = ffDetectSmcTemps(FF_TEMP_GPU_AMD, &result); else error = ffDetectSmcTemps(FF_TEMP_GPU_UNKNOWN, &result); if (error) return FF_GPU_TEMP_UNSET; return result; } #ifdef __aarch64__ #include "common/apple/cf_helpers.h" #include static const char* detectFrequency(FFGPUResult* gpu) { // https://github.com/giampaolo/psutil/pull/2222/files FF_IOOBJECT_AUTO_RELEASE io_registry_entry_t entryDevice = IOServiceGetMatchingService(MACH_PORT_NULL, IOServiceNameMatching("pmgr")); if (!entryDevice) return "IOServiceGetMatchingServices() failed"; if (!IOObjectConformsTo(entryDevice, "AppleARMIODevice")) return "\"pmgr\" should conform to \"AppleARMIODevice\""; FF_CFTYPE_AUTO_RELEASE CFDataRef freqProperty = (CFDataRef) IORegistryEntryCreateCFProperty(entryDevice, CFSTR("voltage-states9-sram"), kCFAllocatorDefault, kNilOptions); if (!freqProperty || CFGetTypeID(freqProperty) != CFDataGetTypeID()) return "\"voltage-states9-sram\" in \"pmgr\" is not found"; // voltage-states9-sram stores supported pairs of gpu from the lowest to the highest CFIndex propLength = CFDataGetLength(freqProperty); if (propLength == 0 || propLength % (CFIndex) sizeof(uint32_t) * 2 != 0) return "Invalid \"voltage-states9-sram\" length"; uint32_t* pStart = (uint32_t*) CFDataGetBytePtr(freqProperty); uint32_t pMax = *pStart; for (CFIndex i = 2; i < propLength / (CFIndex) sizeof(uint32_t) && pStart[i] > 0; i += 2 /* skip voltage */) pMax = pMax > pStart[i] ? pMax : pStart[i]; if (pMax > 0) { // While this is not necessary for now (seems), we add this logic just in case. See cpu_apple.c if (pMax > 100000000) // Assume that pMax is in Hz gpu->frequency = pMax / 1000 / 1000; else // Assume that pMax is in kHz gpu->frequency = pMax / 1000; } return NULL; } #endif const char* ffDetectGPUImpl(const FFGPUOptions* options, FFlist* gpus) { FF_IOOBJECT_AUTO_RELEASE io_iterator_t iterator = IO_OBJECT_NULL; { CFMutableDictionaryRef matches = IOServiceMatching(kIOAcceleratorClassName); CFDictionaryAddValue(matches, CFSTR("IOMatchCategory"), CFSTR(kIOAcceleratorClassName)); if (IOServiceGetMatchingServices(MACH_PORT_NULL, matches, &iterator) != kIOReturnSuccess) return "IOServiceGetMatchingServices() failed"; } io_registry_entry_t registryEntry; while ((registryEntry = IOIteratorNext(iterator)) != IO_OBJECT_NULL) { CFMutableDictionaryRef properties; if(IORegistryEntryCreateCFProperties(registryEntry, &properties, kCFAllocatorDefault, kNilOptions) != kIOReturnSuccess) { IOObjectRelease(registryEntry); continue; } FFGPUResult* gpu = ffListAdd(gpus); gpu->index = FF_GPU_INDEX_UNSET; ffStrbufInit(&gpu->memoryType); gpu->dedicated.total = gpu->dedicated.used = gpu->shared.total = gpu->shared.used = FF_GPU_VMEM_SIZE_UNSET; gpu->type = FF_GPU_TYPE_UNKNOWN; gpu->frequency = FF_GPU_FREQUENCY_UNSET; IORegistryEntryGetRegistryEntryID(registryEntry, &gpu->deviceId); ffStrbufInitStatic(&gpu->platformApi, "IOKit"); ffStrbufInit(&gpu->driver); // Ok for both Apple and Intel ffCfDictGetString(properties, CFSTR("CFBundleIdentifier"), &gpu->driver); if(ffCfDictGetInt(properties, CFSTR("gpu-core-count"), &gpu->coreCount) != NULL) // For Apple gpu->coreCount = FF_GPU_CORE_COUNT_UNSET; gpu->coreUsage = FF_GPU_CORE_USAGE_UNSET; CFDictionaryRef perfStatistics = NULL; uint64_t vramUsed = 0, vramTotal = 0; if (ffCfDictGetDict(properties, CFSTR("PerformanceStatistics"), &perfStatistics) == NULL) { int64_t utilization; if (ffCfDictGetInt64(perfStatistics, CFSTR("Device Utilization %"), &utilization) == NULL) gpu->coreUsage = (double) utilization; else if (ffCfDictGetInt64(perfStatistics, CFSTR("GPU Core Utilization"), &utilization) == NULL) gpu->coreUsage = (double) utilization / 10000000.; // Nvidia? if (ffCfDictGetInt64(perfStatistics, CFSTR("Alloc system memory"), (int64_t*) &vramTotal) == NULL) { if (ffCfDictGetInt64(perfStatistics, CFSTR("In use system memory"), (int64_t*) &vramUsed) != NULL) vramTotal = 0; } else if (ffCfDictGetInt64(perfStatistics, CFSTR("vramFreeBytes"), (int64_t*) &vramTotal) == NULL) { if (ffCfDictGetInt64(perfStatistics, CFSTR("vramUsedBytes"), (int64_t*) &vramUsed) == NULL) vramTotal += vramUsed; else vramTotal = 0; } } ffStrbufInit(&gpu->name); //IOAccelerator returns model / vendor-id properties for Apple Silicon, but not for Intel Iris GPUs. //Still needs testing for AMD's if(ffCfDictGetString(properties, CFSTR("model"), &gpu->name) != NULL) { CFRelease(properties); properties = NULL; FF_IOOBJECT_AUTO_RELEASE io_registry_entry_t parentEntry = 0; if(IORegistryEntryGetParentEntry(registryEntry, kIOServicePlane, &parentEntry) != kIOReturnSuccess || IORegistryEntryCreateCFProperties(parentEntry, &properties, kCFAllocatorDefault, kNilOptions) != kIOReturnSuccess) { IOObjectRelease(registryEntry); continue; } ffCfDictGetString(properties, CFSTR("model"), &gpu->name); } ffStrbufInit(&gpu->vendor); int vendorId; if(ffCfDictGetInt(properties, CFSTR("vendor-id"), &vendorId) == NULL) { const char* vendorStr = ffGPUGetVendorString((unsigned) vendorId); ffStrbufAppendS(&gpu->vendor, vendorStr); if (vendorStr == FF_GPU_VENDOR_NAME_APPLE || vendorStr == FF_GPU_VENDOR_NAME_INTEL) gpu->type = FF_GPU_TYPE_INTEGRATED; else if (vendorStr == FF_GPU_VENDOR_NAME_NVIDIA || vendorStr == FF_GPU_VENDOR_NAME_AMD) gpu->type = FF_GPU_TYPE_DISCRETE; #ifdef __aarch64__ if (vendorStr == FF_GPU_VENDOR_NAME_APPLE) detectFrequency(gpu); #endif if (gpu->type == FF_GPU_TYPE_INTEGRATED) { gpu->shared.total = vramTotal; gpu->shared.used = vramUsed; } else if (gpu->type == FF_GPU_TYPE_DISCRETE) { gpu->dedicated.total = vramTotal; gpu->dedicated.used = vramUsed; } } gpu->temperature = options->temp ? detectGpuTemp(&gpu->name) : FF_GPU_TEMP_UNSET; CFRelease(properties); IOObjectRelease(registryEntry); } ffGpuDetectMetal(gpus); if (instance.config.general.detectVersion) ffGpuDetectDriverVersion(gpus); return NULL; } ================================================ FILE: src/detection/gpu/gpu_apple.m ================================================ #include "gpu.h" #import #import #ifndef MAC_OS_VERSION_26_0 #define MTLGPUFamilyMetal4 ((MTLGPUFamily) 5002) #endif #ifndef MAC_OS_VERSION_13_0 #define MTLGPUFamilyMetal3 ((MTLGPUFamily) 5001) #endif #ifndef MAC_OS_X_VERSION_10_15 #define MTLFeatureSet_macOS_GPUFamily1_v4 ((MTLFeatureSet) 10004) #define MTLFeatureSet_macOS_GPUFamily2_v1 ((MTLFeatureSet) 10005) #endif const char* ffGpuDetectDriverVersion(FFlist* gpus) { if (@available(macOS 10.7, *)) { NSMutableArray* arr = NSMutableArray.new; FF_LIST_FOR_EACH(FFGPUResult, x, *gpus) [arr addObject:@(x->driver.chars)]; NSDictionary* dict = CFBridgingRelease(KextManagerCopyLoadedKextInfo((__bridge CFArrayRef)arr, (__bridge CFArrayRef)@[@"CFBundleVersion"])); FF_LIST_FOR_EACH(FFGPUResult, x, *gpus) { NSString* version = dict[@(x->driver.chars)][@"CFBundleVersion"]; if (version) { ffStrbufAppendC(&x->driver, ' '); ffStrbufAppendS(&x->driver, version.UTF8String); } } return NULL; } return "Unsupported macOS version"; } const char* ffGpuDetectMetal(FFlist* gpus) { if (@available(macOS 10.13, *)) { for (id device in MTLCopyAllDevices()) { FFGPUResult* gpu = NULL; FF_LIST_FOR_EACH(FFGPUResult, x, *gpus) { if (x->deviceId == device.registryID) { gpu = x; break; } } if (!gpu) continue; #ifndef MAC_OS_X_VERSION_10_15 if ([device supportsFeatureSet:MTLFeatureSet_macOS_GPUFamily2_v1]) ffStrbufSetStatic(&gpu->platformApi, "Metal Feature Set 2"); else if ([device supportsFeatureSet:MTLFeatureSet_macOS_GPUFamily1_v1]) ffStrbufSetStatic(&gpu->platformApi, "Metal Feature Set 1"); #else // MAC_OS_X_VERSION_10_15 #pragma clang diagnostic push #pragma clang diagnostic ignored "-Wunguarded-availability-new" if ([device supportsFamily:MTLGPUFamilyMetal4]) ffStrbufSetStatic(&gpu->platformApi, "Metal 4"); else if ([device supportsFamily:MTLGPUFamilyMetal3]) ffStrbufSetStatic(&gpu->platformApi, "Metal 3"); #pragma clang diagnostic pop else if ([device supportsFamily:MTLGPUFamilyCommon3]) ffStrbufSetStatic(&gpu->platformApi, "Metal Common 3"); else if ([device supportsFamily:MTLGPUFamilyCommon2]) ffStrbufSetStatic(&gpu->platformApi, "Metal Common 2"); else if ([device supportsFamily:MTLGPUFamilyCommon1]) ffStrbufSetStatic(&gpu->platformApi, "Metal Common 1"); gpu->type = device.hasUnifiedMemory ? FF_GPU_TYPE_INTEGRATED : FF_GPU_TYPE_DISCRETE; gpu->index = (uint32_t) device.locationNumber; if (device.hasUnifiedMemory && device.recommendedMaxWorkingSetSize > 0) gpu->shared.total = device.recommendedMaxWorkingSetSize; #endif } return NULL; } return "Metal API is not supported by this macOS version"; } ================================================ FILE: src/detection/gpu/gpu_bsd.c ================================================ #include "gpu_driver_specific.h" #include "common/io.h" #include "common/mallocHelper.h" #include #include #if __has_include() #include // FreeBSD #else #include // DragonFly #endif static void fillGPUTypeGeneric(FFGPUResult* gpu) { if (gpu->type == FF_GPU_TYPE_UNKNOWN) { if (gpu->vendor.chars == FF_GPU_VENDOR_NAME_NVIDIA) { if (ffStrbufStartsWithIgnCaseS(&gpu->name, "GeForce") || ffStrbufStartsWithIgnCaseS(&gpu->name, "Quadro") || ffStrbufStartsWithIgnCaseS(&gpu->name, "Tesla")) gpu->type = FF_GPU_TYPE_DISCRETE; } else if (gpu->vendor.chars == FF_GPU_VENDOR_NAME_MTHREADS) { if (ffStrbufStartsWithIgnCaseS(&gpu->name, "MTT ")) gpu->type = FF_GPU_TYPE_DISCRETE; } else if (gpu->vendor.chars == FF_GPU_VENDOR_NAME_INTEL) { // 0000:00:02.0 is reserved for Intel integrated graphics gpu->type = gpu->deviceId == ffGPUPciAddr2Id(0, 0, 2, 0) ? FF_GPU_TYPE_INTEGRATED : FF_GPU_TYPE_DISCRETE; } } } #if FF_HAVE_DRM #include "common/library.h" #include "common/stringUtils.h" #include static const char* detectByDrm(const FFGPUOptions* options, FFlist* gpus) { FF_LIBRARY_LOAD_MESSAGE(libdrm, "libdrm" FF_LIBRARY_EXTENSION, 2) FF_LIBRARY_LOAD_SYMBOL_MESSAGE(libdrm, drmGetDevices) FF_LIBRARY_LOAD_SYMBOL_MESSAGE(libdrm, drmFreeDevices) drmDevicePtr devices[64]; int nDevices = ffdrmGetDevices(devices, ARRAY_SIZE(devices)); if (nDevices < 0) return "drmGetDevices() failed"; for (int iDev = 0; iDev < nDevices; ++iDev) { drmDevice* dev = devices[iDev]; if (!(dev->available_nodes & (1 << DRM_NODE_PRIMARY))) continue; const char* path = dev->nodes[DRM_NODE_PRIMARY]; FFGPUResult* gpu = (FFGPUResult*)ffListAdd(gpus); ffStrbufInit(&gpu->vendor); ffStrbufInit(&gpu->name); ffStrbufInit(&gpu->driver); ffStrbufInitS(&gpu->platformApi, path); ffStrbufInit(&gpu->memoryType); gpu->index = FF_GPU_INDEX_UNSET; gpu->temperature = FF_GPU_TEMP_UNSET; gpu->coreCount = FF_GPU_CORE_COUNT_UNSET; gpu->coreUsage = FF_GPU_CORE_USAGE_UNSET; gpu->type = FF_GPU_TYPE_UNKNOWN; gpu->dedicated.total = gpu->dedicated.used = gpu->shared.total = gpu->shared.used = FF_GPU_VMEM_SIZE_UNSET; gpu->deviceId = 0; gpu->frequency = FF_GPU_FREQUENCY_UNSET; switch (dev->bustype) { case DRM_BUS_PCI: ffStrbufInitStatic(&gpu->vendor, ffGPUGetVendorString(dev->deviceinfo.pci->vendor_id)); gpu->deviceId = ffGPUPciAddr2Id(dev->businfo.pci->domain, dev->businfo.pci->bus, dev->businfo.pci->dev, dev->businfo.pci->func); break; case DRM_BUS_HOST1X: ffStrbufSetS(&gpu->name, dev->deviceinfo.host1x->compatible[0]); gpu->type = FF_GPU_TYPE_INTEGRATED; break; case DRM_BUS_PLATFORM: ffStrbufSetS(&gpu->name, dev->deviceinfo.platform->compatible[0]); gpu->type = FF_GPU_TYPE_INTEGRATED; break; case DRM_BUS_USB: ffStrbufSetF(&gpu->name, "USB Device (%u-%u)", dev->deviceinfo.usb->vendor, dev->deviceinfo.usb->product); gpu->type = FF_GPU_TYPE_DISCRETE; break; } FF_AUTO_CLOSE_FD int fd = open(path, O_RDONLY | O_CLOEXEC); if (fd < 0) continue; char driverName[64]; driverName[0] = '\0'; struct drm_version ver = { .name = driverName, .name_len = ARRAY_SIZE(driverName), }; if (ioctl(fd, DRM_IOCTL_VERSION, &ver) == 0) { driverName[ver.name_len] = '\0'; ffStrbufSetF(&gpu->driver, "%s %d.%d.%d", ver.name, ver.version_major, ver.version_minor, ver.version_patchlevel); } if (ffStrStartsWith(driverName, "i915")) ffDrmDetectI915(gpu, fd); else if (ffStrStartsWith(driverName, "amdgpu")) ffDrmDetectAmdgpu(options, gpu, dev->nodes[DRM_NODE_RENDER]); else if (ffStrStartsWith(driverName, "radeon")) ffDrmDetectRadeon(options, gpu, dev->nodes[DRM_NODE_RENDER]); else if (ffStrStartsWith(driverName, "xe")) ffDrmDetectXe(gpu, fd); else if (ffStrStartsWith(driverName, "asahi")) ffDrmDetectAsahi(gpu, fd); else if (ffStrStartsWith(driverName, "nouveau")) ffDrmDetectNouveau(gpu, fd); else if (dev->bustype == DRM_BUS_PCI) { ffGPUDetectDriverSpecific(options, gpu, (FFGpuDriverPciBusId) { .domain = (uint32_t) dev->businfo.pci->domain, .bus = dev->businfo.pci->bus, .device = dev->businfo.pci->dev, .func = dev->businfo.pci->func, }); } if (gpu->name.length == 0) { if (gpu->vendor.chars == FF_GPU_VENDOR_NAME_AMD) ffGPUQueryAmdGpuName(dev->deviceinfo.pci->device_id, dev->deviceinfo.pci->revision_id, gpu); if (gpu->name.length == 0) ffGPUFillVendorAndName(0, dev->deviceinfo.pci->vendor_id, dev->deviceinfo.pci->device_id, gpu); } fillGPUTypeGeneric(gpu); } ffdrmFreeDevices(devices, nDevices); return NULL; } #endif static const char* detectByPci(const FFGPUOptions* options, FFlist* gpus) { FF_AUTO_CLOSE_FD int fd = open("/dev/pci", O_RDONLY | O_CLOEXEC); if (fd < 0) return "open(\"/dev/pci\", O_RDONLY | O_CLOEXEC, 0) failed"; struct pci_conf confs[128]; struct pci_match_conf match = { .pc_class = PCIC_DISPLAY, .flags = PCI_GETCONF_MATCH_CLASS, }; struct pci_conf_io pcio = { .pat_buf_len = sizeof(match), .num_patterns = 1, .patterns = &match, .match_buf_len = sizeof(confs), .matches = confs, }; if (ioctl(fd, PCIOCGETCONF, &pcio) < 0) return "ioctl(fd, PCIOCGETCONF, &pc) failed"; if (pcio.status == PCI_GETCONF_ERROR) return "ioctl(fd, PCIOCGETCONF, &pc) returned error"; for (uint32_t i = 0; i < pcio.num_matches; ++i) { struct pci_conf* pc = &confs[i]; if (pc->pc_sel.pc_func > 0 && pc->pc_subclass == 0x80 /*PCI_CLASS_DISPLAY_OTHER*/) continue; // Likely an auxiliary display controller (#2034) FFGPUResult* gpu = (FFGPUResult*)ffListAdd(gpus); ffStrbufInitStatic(&gpu->vendor, ffGPUGetVendorString(pc->pc_vendor)); ffStrbufInit(&gpu->name); ffStrbufInitS(&gpu->driver, pc->pd_name); ffStrbufInitStatic(&gpu->platformApi, "/dev/pci"); ffStrbufInit(&gpu->memoryType); gpu->index = FF_GPU_INDEX_UNSET; gpu->temperature = FF_GPU_TEMP_UNSET; gpu->coreCount = FF_GPU_CORE_COUNT_UNSET; gpu->coreUsage = FF_GPU_CORE_USAGE_UNSET; gpu->type = FF_GPU_TYPE_UNKNOWN; gpu->dedicated.total = gpu->dedicated.used = gpu->shared.total = gpu->shared.used = FF_GPU_VMEM_SIZE_UNSET; gpu->deviceId = ffGPUPciAddr2Id(pc->pc_sel.pc_domain, pc->pc_sel.pc_bus, pc->pc_sel.pc_dev, pc->pc_sel.pc_func); gpu->frequency = FF_GPU_FREQUENCY_UNSET; ffGPUDetectDriverSpecific(options, gpu, (FFGpuDriverPciBusId) { .domain = (uint32_t) pc->pc_sel.pc_domain, .bus = pc->pc_sel.pc_bus, .device = pc->pc_sel.pc_dev, .func = pc->pc_sel.pc_func, }); if (gpu->name.length == 0) { if (gpu->vendor.chars == FF_GPU_VENDOR_NAME_AMD) ffGPUQueryAmdGpuName(pc->pc_device, pc->pc_revid, gpu); if (gpu->name.length == 0) ffGPUFillVendorAndName(pc->pc_subclass, pc->pc_vendor, pc->pc_device, gpu); } fillGPUTypeGeneric(gpu); } return NULL; } const char* ffDetectGPUImpl(const FFGPUOptions* options, FFlist* gpus) { #if FF_HAVE_DRM if (options->detectionMethod == FF_GPU_DETECTION_METHOD_AUTO) { detectByDrm(options, gpus); if (gpus->length > 0) return NULL; } #endif return detectByPci(options, gpus); } ================================================ FILE: src/detection/gpu/gpu_driver_specific.h ================================================ #pragma once #include "gpu.h" typedef enum __attribute__((__packed__)) FFGpuDriverConditionType { FF_GPU_DRIVER_CONDITION_TYPE_BUS_ID = 1 << 0, FF_GPU_DRIVER_CONDITION_TYPE_DEVICE_ID = 1 << 1, FF_GPU_DRIVER_CONDITION_TYPE_LUID = 1 << 2, FF_GPU_DRIVER_CONDITION_TYPE_FORCE_UNSIGNED = UINT8_MAX, } FFGpuDriverConditionType; typedef struct FFGpuDriverPciDeviceId { uint32_t deviceId; uint32_t vendorId; uint32_t subSystemId; uint32_t revId; } FFGpuDriverPciDeviceId; // Use pciBusId if not NULL; use pciDeviceId otherwise typedef struct FFGpuDriverCondition { FFGpuDriverConditionType type; FFGpuDriverPciBusId pciBusId; FFGpuDriverPciDeviceId pciDeviceId; uint64_t luid; } FFGpuDriverCondition; // detect x if not NULL typedef struct FFGpuDriverResult { uint32_t* index; double* temp; FFGPUMemory* memory; FFstrbuf* memoryType; FFGPUMemory* sharedMemory; uint32_t* coreCount; double* coreUsage; FFGPUType* type; uint32_t* frequency; FFstrbuf* name; } FFGpuDriverResult; const char* ffDetectNvidiaGpuInfo(const FFGpuDriverCondition* cond, FFGpuDriverResult result, const char* soName); const char* ffDetectIntelGpuInfo(const FFGpuDriverCondition* cond, FFGpuDriverResult result, const char* soName); const char* ffDetectAmdGpuInfo(const FFGpuDriverCondition* cond, FFGpuDriverResult result, const char* soName); const char* ffDetectMthreadsGpuInfo(const FFGpuDriverCondition* cond, FFGpuDriverResult result, const char* soName); FF_MAYBE_UNUSED static inline bool getDriverSpecificDetectionFn(const char* vendor, __typeof__(&ffDetectNvidiaGpuInfo)* pDetectFn, const char** pDllName) { if (vendor == FF_GPU_VENDOR_NAME_NVIDIA) { *pDetectFn = ffDetectNvidiaGpuInfo; #ifdef _WIN32 *pDllName = "nvml.dll"; #else *pDllName = "libnvidia-ml.so"; #endif } else if (vendor == FF_GPU_VENDOR_NAME_MTHREADS) { *pDetectFn = ffDetectMthreadsGpuInfo; #ifdef _WIN32 *pDllName = "mtml.dll"; #else *pDllName = "libmtml.so"; #endif } #ifdef _WIN32 else if (vendor == FF_GPU_VENDOR_NAME_INTEL) { *pDetectFn = ffDetectIntelGpuInfo; #ifdef _WIN64 *pDllName = "ControlLib.dll"; #else *pDllName = "ControlLib32.dll"; #endif } else if (vendor == FF_GPU_VENDOR_NAME_AMD) { *pDetectFn = ffDetectAmdGpuInfo; #ifdef _WIN64 *pDllName = "atiadlxx.dll"; #else *pDllName = "atiadlxy.dll"; #endif } #endif else { *pDetectFn = NULL; *pDllName = NULL; return false; } return true; } ================================================ FILE: src/detection/gpu/gpu_drm.c ================================================ #include "gpu.h" #if FF_HAVE_DRM #include #include #include #include "common/io.h" #include "common/library.h" #include "common/mallocHelper.h" #include "common/stringUtils.h" #include "intel_drm.h" #include "asahi_drm.h" #include #include const char* ffDrmDetectRadeon(const FFGPUOptions* options, FFGPUResult* gpu, const char* renderPath) { FF_AUTO_CLOSE_FD int fd = open(renderPath, O_RDONLY | O_CLOEXEC); if (fd < 0) return "Failed to open DRM render device"; uint32_t value; // https://github.com/torvalds/linux/blob/fb4d33ab452ea254e2c319bac5703d1b56d895bf/drivers/gpu/drm/radeon/radeon_kms.c#L231 if (ioctl(fd, DRM_IOCTL_RADEON_INFO, &(struct drm_radeon_info) { .request = RADEON_INFO_ACTIVE_CU_COUNT, .value = (uintptr_t) &value, }) >= 0) gpu->coreCount = (int32_t) value; if (options->temp) { if (ioctl(fd, DRM_IOCTL_RADEON_INFO, &(struct drm_radeon_info) { .request = RADEON_INFO_CURRENT_GPU_TEMP, // millidegrees C .value = (uintptr_t) &value, }) >= 0 && value != 0) // 0 means unavailable gpu->temperature = (double) value / 1000.0; } if (ioctl(fd, DRM_IOCTL_RADEON_INFO, &(struct drm_radeon_info) { .request = RADEON_INFO_MAX_SCLK, // MHz .value = (uintptr_t) &value, }) >= 0) gpu->frequency = (uint32_t) (value / 1000u); if (options->driverSpecific) { struct drm_radeon_gem_info gemInfo; if (ioctl(fd, DRM_IOCTL_RADEON_GEM_INFO, &gemInfo) >= 0) { // vram_usage can be bigger than vram_usage, so we use vram_size here gpu->dedicated.total = gemInfo.vram_size; gpu->shared.total = gemInfo.gart_size; uint64_t memSize; if (ioctl(fd, DRM_IOCTL_RADEON_INFO, &(struct drm_radeon_info) { .request = RADEON_INFO_VRAM_USAGE, // uint64_t .value = (uintptr_t) &memSize, }) >= 0) gpu->dedicated.used = memSize; if (ioctl(fd, DRM_IOCTL_RADEON_INFO, &(struct drm_radeon_info) { .request = RADEON_INFO_GTT_USAGE, // uint64_t .value = (uintptr_t) &memSize, }) >= 0) gpu->shared.used = memSize; } } return NULL; } #ifdef FF_HAVE_DRM_AMDGPU #include #include const char* ffDrmDetectAmdgpu(const FFGPUOptions* options, FFGPUResult* gpu, const char* renderPath) { #if FF_HAVE_DRM_AMDGPU FF_LIBRARY_LOAD_MESSAGE(libdrm, "libdrm_amdgpu" FF_LIBRARY_EXTENSION, 1) FF_LIBRARY_LOAD_SYMBOL_MESSAGE(libdrm, amdgpu_device_initialize) FF_LIBRARY_LOAD_SYMBOL_MESSAGE(libdrm, amdgpu_get_marketing_name) FF_LIBRARY_LOAD_SYMBOL_MESSAGE(libdrm, amdgpu_query_gpu_info) FF_LIBRARY_LOAD_SYMBOL_MESSAGE(libdrm, amdgpu_query_sensor_info) FF_LIBRARY_LOAD_SYMBOL_MESSAGE(libdrm, amdgpu_query_heap_info) FF_LIBRARY_LOAD_SYMBOL_MESSAGE(libdrm, amdgpu_device_deinitialize) FF_AUTO_CLOSE_FD int fd = open(renderPath, O_RDONLY | O_CLOEXEC); if (fd < 0) return "Failed to open DRM render device"; amdgpu_device_handle handle; uint32_t majorVersion, minorVersion; if (ffamdgpu_device_initialize(fd, &majorVersion, &minorVersion, &handle) < 0) return "Failed to initialize AMDGPU device"; uint32_t value; if (options->temp) { if (ffamdgpu_query_sensor_info(handle, AMDGPU_INFO_SENSOR_GPU_TEMP, sizeof(value), &value) >= 0) gpu->temperature = value / 1000.; } ffStrbufSetS(&gpu->name, ffamdgpu_get_marketing_name(handle)); struct amdgpu_gpu_info gpuInfo; if (ffamdgpu_query_gpu_info(handle, &gpuInfo) >= 0) { gpu->coreCount = (int32_t) gpuInfo.cu_active_number; gpu->frequency = (uint32_t) (gpuInfo.max_engine_clk / 1000u); gpu->index = FF_GPU_INDEX_UNSET; gpu->type = gpuInfo.ids_flags & AMDGPU_IDS_FLAGS_FUSION ? FF_GPU_TYPE_INTEGRATED : FF_GPU_TYPE_DISCRETE; #define FF_VRAM_CASE(name, value) case value /* AMDGPU_VRAM_TYPE_ ## name */: ffStrbufSetStatic(&gpu->memoryType, #name); break switch (gpuInfo.vram_type) { FF_VRAM_CASE(UNKNOWN, 0); FF_VRAM_CASE(GDDR1, 1); FF_VRAM_CASE(DDR2, 2); FF_VRAM_CASE(GDDR3, 3); FF_VRAM_CASE(GDDR4, 4); FF_VRAM_CASE(GDDR5, 5); FF_VRAM_CASE(HBM, 6); FF_VRAM_CASE(DDR3, 7); FF_VRAM_CASE(DDR4, 8); FF_VRAM_CASE(GDDR6, 9); FF_VRAM_CASE(DDR5, 10); FF_VRAM_CASE(LPDDR4, 11); FF_VRAM_CASE(LPDDR5, 12); default: ffStrbufAppendF(&gpu->memoryType, "Unknown (%u)", gpuInfo.vram_type); break; } struct amdgpu_heap_info heapInfo; if (ffamdgpu_query_heap_info(handle, AMDGPU_GEM_DOMAIN_VRAM, 0, &heapInfo) >= 0) { gpu->dedicated.total = heapInfo.heap_size; gpu->dedicated.used = heapInfo.heap_usage; } if (ffamdgpu_query_heap_info(handle, AMDGPU_GEM_DOMAIN_GTT, 0, &heapInfo) >= 0) { gpu->shared.total = heapInfo.heap_size; gpu->shared.used = heapInfo.heap_usage; } } if (ffamdgpu_query_sensor_info(handle, AMDGPU_INFO_SENSOR_GPU_LOAD, sizeof(value), &value) >= 0) gpu->coreUsage = value; ffamdgpu_device_deinitialize(handle); return NULL; #else FF_UNUSED(options, gpu, renderPath); return "Fastfetch is compiled without libdrm support"; #endif } #endif const char* ffDrmDetectI915(FFGPUResult* gpu, int fd) { { int value; drm_i915_getparam_t getparam = { .param = I915_PARAM_EU_TOTAL, .value = &value }; if (ioctl(fd, DRM_IOCTL_I915_GETPARAM, &getparam) >= 0) gpu->coreCount = value; } { struct drm_i915_query_item queryItem = { .query_id = DRM_I915_QUERY_MEMORY_REGIONS, }; struct drm_i915_query query = { .items_ptr = (uintptr_t) &queryItem, .num_items = 1, }; if (ioctl(fd, DRM_IOCTL_I915_QUERY, &query) >= 0 ) { FF_AUTO_FREE uint8_t* buffer = calloc(1, (size_t) queryItem.length); queryItem.data_ptr = (uintptr_t) buffer; if (ioctl(fd, DRM_IOCTL_I915_QUERY, &query) >= 0) { gpu->dedicated.total = gpu->shared.total = gpu->dedicated.used = gpu->shared.used = 0; struct drm_i915_query_memory_regions* regionInfo = (void*) buffer; for (uint32_t i = 0; i < regionInfo->num_regions; i++) { struct drm_i915_memory_region_info* region = regionInfo->regions + i; switch (region->region.memory_class) { case I915_MEMORY_CLASS_SYSTEM: gpu->shared.total += region->probed_size; gpu->shared.used += region->probed_size - region->unallocated_size; break; case I915_MEMORY_CLASS_DEVICE: gpu->dedicated.total += region->probed_size; gpu->dedicated.used += region->probed_size - region->unallocated_size; break; } } } } } return NULL; } static inline int popcountBytes(uint8_t* bytes, uint32_t length) { int count = 0; while (length >= 8) { count += __builtin_popcountll(*(uint64_t*) bytes); bytes += 8; length -= 8; } if (length >= 4) { count += __builtin_popcountl(*(uint32_t*) bytes); bytes += 4; length -= 4; } if (length >= 2) { count += __builtin_popcountl(*(uint16_t*) bytes); bytes += 2; length -= 2; } if (length) { count += __builtin_popcountl(*(uint8_t*) bytes); } return count; } const char* ffDrmDetectXe(FFGPUResult* gpu, int fd) { bool flag = false; { struct drm_xe_device_query query = { .query = DRM_XE_DEVICE_QUERY_GT_TOPOLOGY, }; if (ioctl(fd, DRM_IOCTL_XE_DEVICE_QUERY, &query) >= 0) { FF_AUTO_FREE uint8_t* buffer = malloc(query.size); query.data = (uintptr_t) buffer; if (ioctl(fd, DRM_IOCTL_XE_DEVICE_QUERY, &query) >= 0) { int dssCount = 0, euPerDssCount = 0; for (struct drm_xe_query_topology_mask* topo = (void*) buffer; (uint8_t*) topo < buffer + query.size; topo = (void*) (topo->mask + topo->num_bytes) ) { switch (topo->type) { case DRM_XE_TOPO_DSS_COMPUTE: case DRM_XE_TOPO_DSS_GEOMETRY: dssCount += popcountBytes(topo->mask, topo->num_bytes); break; case DRM_XE_TOPO_EU_PER_DSS: euPerDssCount += popcountBytes(topo->mask, topo->num_bytes); break; } } gpu->coreCount = dssCount * euPerDssCount; flag = true; } } } { struct drm_xe_device_query query = { .query = DRM_XE_DEVICE_QUERY_MEM_REGIONS, }; if (ioctl(fd, DRM_IOCTL_XE_DEVICE_QUERY, &query) >= 0) { FF_AUTO_FREE uint8_t* buffer = malloc(query.size); query.data = (uintptr_t) buffer; if (ioctl(fd, DRM_IOCTL_XE_DEVICE_QUERY, &query) >= 0) { gpu->dedicated.total = gpu->shared.total = gpu->dedicated.used = gpu->shared.used = 0; struct drm_xe_query_mem_regions* regionInfo = (void*) buffer; for (uint32_t i = 0; i < regionInfo->num_mem_regions; i++) { struct drm_xe_mem_region* region = regionInfo->mem_regions + i; switch (region->mem_class) { case DRM_XE_MEM_REGION_CLASS_SYSMEM: gpu->shared.total += region->total_size; gpu->shared.used += region->used; break; case DRM_XE_MEM_REGION_CLASS_VRAM: gpu->dedicated.total += region->total_size; gpu->dedicated.used += region->used; break; } } flag = true; } } } return flag ? NULL : "Failed to query Xe GPU information"; } const char* ffDrmDetectAsahi(FFGPUResult* gpu, int fd) { struct drm_asahi_params_global paramsGlobal = {}; if (ioctl(fd, DRM_IOCTL_ASAHI_GET_PARAMS, &(struct drm_asahi_get_params) { .param_group = DRM_ASAHI_GET_PARAMS, .pointer = (uintptr_t) ¶msGlobal, .size = sizeof(paramsGlobal), }) >= 0) { // They removed `unstable_uabi_version` from the struct. Hopefully they won't introduce new ABI changes. gpu->coreCount = (int32_t) (paramsGlobal.num_clusters_total * paramsGlobal.num_cores_per_cluster); gpu->frequency = paramsGlobal.max_frequency_khz / 1000; gpu->deviceId = ffGPUGeneral2Id(paramsGlobal.chip_id); if (!gpu->name.length) { const char* variant = " Unknown"; switch (paramsGlobal.gpu_variant) { case 'G': variant = ""; break; case 'S': variant = " Pro"; break; case 'C': variant = " Max"; break; case 'D': variant = " Ultra"; break; } ffStrbufSetF(&gpu->name, "Apple M%d%s (G%d%c %02X)", paramsGlobal.gpu_generation - 12, variant, paramsGlobal.gpu_generation, paramsGlobal.gpu_variant, paramsGlobal.gpu_revision + 0xA0); } return NULL; } return "Failed to query Asahi GPU information"; } #ifndef DRM_IOCTL_NOUVEAU_GETPARAM #define DRM_IOCTL_NOUVEAU_GETPARAM DRM_IOWR(DRM_COMMAND_BASE + DRM_NOUVEAU_GETPARAM, struct drm_nouveau_getparam) #endif const char* ffDrmDetectNouveau(FFGPUResult* gpu, int fd) { struct drm_nouveau_getparam getparam = { }; getparam.param = NOUVEAU_GETPARAM_FB_SIZE; if (ioctl(fd, DRM_IOCTL_NOUVEAU_GETPARAM, &getparam) == 0) gpu->dedicated.total = getparam.value; getparam.param = NOUVEAU_GETPARAM_AGP_SIZE; if (ioctl(fd, DRM_IOCTL_NOUVEAU_GETPARAM, &getparam) == 0) gpu->shared.total = getparam.value; getparam.param = NOUVEAU_GETPARAM_GRAPH_UNITS; if (ioctl(fd, DRM_IOCTL_NOUVEAU_GETPARAM, &getparam) == 0 && getparam.value < INT32_MAX) gpu->coreCount = (int32_t) getparam.value; return NULL; } #endif // FF_HAVE_DRM #include "gpu_driver_specific.h" const char* ffGPUDetectDriverSpecific(const FFGPUOptions* options, FFGPUResult* gpu, FFGpuDriverPciBusId pciBusId) { __typeof__(&ffDetectNvidiaGpuInfo) detectFn; const char* soName; if (getDriverSpecificDetectionFn(gpu->vendor.chars, &detectFn, &soName) && (options->temp || options->driverSpecific)) { return detectFn(&(FFGpuDriverCondition) { .type = FF_GPU_DRIVER_CONDITION_TYPE_BUS_ID, .pciBusId = pciBusId, }, (FFGpuDriverResult) { .index = &gpu->index, .temp = options->temp ? &gpu->temperature : NULL, .memory = options->driverSpecific ? &gpu->dedicated : NULL, .coreCount = options->driverSpecific ? (uint32_t*) &gpu->coreCount : NULL, .coreUsage = options->driverSpecific ? &gpu->coreUsage : NULL, .type = &gpu->type, .frequency = options->driverSpecific ? &gpu->frequency : NULL, .name = &gpu->name, }, soName); } return "No driver-specific detection function found for the GPU vendor"; } ================================================ FILE: src/detection/gpu/gpu_gnu.c ================================================ #include "gpu.h" #include "common/io.h" #include #include #include enum { PCI_VENDOR_ID = 0x00, PCI_DEVICE_ID = 0x02, PCI_REVISION_ID = 0x08, PCI_CLASS_PROG = 0x09, PCI_SUBCLASS = 0x0a, PCI_CLASS_DEVICE = 0x0b, PCI_CONF_SIZE = 0x40, }; const char* ffDetectGPUImpl(FF_MAYBE_UNUSED const FFGPUOptions* options, FFlist* gpus) { int dDomainFd = open(_SERVERS_BUS "/pci/0000", O_RDONLY | O_CLOEXEC); if (dDomainFd < 0) return "open(_SERVERS_BUS \"/pci/0000\") failed"; FF_AUTO_CLOSE_DIR DIR* dirDomain = fdopendir(dDomainFd); if (dirDomain == NULL) return "fdopendir(domain) failed"; struct dirent* busEntry; while ((busEntry = readdir(dirDomain)) != NULL) { if (busEntry->d_type != DT_DIR || busEntry->d_name[0] == '.') continue; char* endptr; uint16_t pciBus = (uint16_t) strtoul(busEntry->d_name, &endptr, 16); if (*endptr != '\0') continue; int dBusFd = openat(dDomainFd, busEntry->d_name, O_RDONLY | O_CLOEXEC); if (dBusFd < 0) continue; FF_AUTO_CLOSE_DIR DIR* dirBus = fdopendir(dBusFd); if (dirBus == NULL) continue; struct dirent* devEntry; while ((devEntry = readdir(dirBus)) != NULL) { if (devEntry->d_type != DT_DIR || devEntry->d_name[0] == '.') continue; uint8_t pciDev = (uint8_t) strtoul(devEntry->d_name, &endptr, 16); if (*endptr != '\0') continue; int dDevFd = openat(dBusFd, devEntry->d_name, O_RDONLY | O_CLOEXEC); if (dDevFd < 0) continue; FF_AUTO_CLOSE_DIR DIR* dirDev = fdopendir(dDevFd); if (dirDev == NULL) continue; struct dirent* funcEntry; while ((funcEntry = readdir(dirDev)) != NULL) { if (funcEntry->d_type != DT_DIR || funcEntry->d_name[0] == '.') continue; uint8_t pciFunc = (uint8_t) strtoul(funcEntry->d_name, &endptr, 16); if (*endptr != '\0') continue; char subpath[PATH_MAX]; snprintf(subpath, ARRAY_SIZE(subpath), "%s/%s/%s/%s/config", _SERVERS_BUS "/pci/0000", busEntry->d_name, devEntry->d_name, funcEntry->d_name); mach_port_t devicePort = file_name_lookup(subpath, 0, 0); if (devicePort == MACH_PORT_NULL) continue; mach_msg_type_number_t nread = 0; uint8_t data[PCI_CONF_SIZE]; data_t pData = (data_t) data; kern_return_t kr = pci_conf_read(devicePort, 0, &pData, &nread, PCI_CONF_SIZE); mach_port_deallocate(mach_task_self(), devicePort); if (kr != KERN_SUCCESS || nread < PCI_CONF_SIZE) continue; if (pData != (data_t) data) { memcpy(data, pData, PCI_CONF_SIZE); vm_deallocate(mach_task_self(), (vm_address_t)pData, nread); } uint8_t classBase = data[PCI_CLASS_DEVICE]; if (classBase != 0x03 /*PCI_BASE_CLASS_DISPLAY*/) continue; uint8_t classSub = data[PCI_SUBCLASS]; if (pciFunc > 0 && classSub == 0x80 /*PCI_CLASS_DISPLAY_OTHER*/) // Likely an auxiliary display controller (#2034) continue; uint8_t revision = data[PCI_REVISION_ID]; uint16_t vendorId = data[PCI_VENDOR_ID] | (data[PCI_VENDOR_ID + 1] << 8); uint16_t deviceId = data[PCI_DEVICE_ID] | (data[PCI_DEVICE_ID + 1] << 8); FFGPUResult* gpu = (FFGPUResult*)ffListAdd(gpus); ffStrbufInitStatic(&gpu->vendor, ffGPUGetVendorString(vendorId)); ffStrbufInit(&gpu->name); ffStrbufInit(&gpu->driver); ffStrbufInitStatic(&gpu->platformApi, "/servers/bus/pci"); ffStrbufInit(&gpu->memoryType); gpu->temperature = FF_GPU_TEMP_UNSET; gpu->coreCount = FF_GPU_CORE_COUNT_UNSET; gpu->coreUsage = FF_GPU_CORE_USAGE_UNSET; gpu->type = FF_GPU_TYPE_UNKNOWN; gpu->dedicated.total = gpu->dedicated.used = gpu->shared.total = gpu->shared.used = FF_GPU_VMEM_SIZE_UNSET; gpu->deviceId = ffGPUPciAddr2Id(0, pciBus, pciDev, pciFunc); gpu->frequency = FF_GPU_FREQUENCY_UNSET; if (gpu->vendor.chars == FF_GPU_VENDOR_NAME_AMD) ffGPUQueryAmdGpuName(deviceId, revision, gpu); if (gpu->name.length == 0) ffGPUFillVendorAndName(classSub, vendorId, deviceId, gpu); } } } return NULL; } ================================================ FILE: src/detection/gpu/gpu_haiku.c ================================================ #include "gpu.h" #include "common/io.h" #include const char* ffDetectGPUImpl(FF_MAYBE_UNUSED const FFGPUOptions* options, FFlist* gpus) { FF_AUTO_CLOSE_FD int pokefd = open(POKE_DEVICE_FULLNAME, O_RDWR | O_CLOEXEC); if (pokefd < 0) return "open(POKE_DEVICE_FULLNAME) failed"; pci_info dev; pci_info_args cmd = { .signature = POKE_SIGNATURE, .info = &dev, }; for (cmd.index = 0; ioctl(pokefd, POKE_GET_NTH_PCI_INFO, &cmd, sizeof(cmd)) == B_OK && cmd.status == B_OK; ++cmd.index) { if (dev.class_base != 0x03 /*PCI_BASE_CLASS_DISPLAY*/) continue; if (dev.function > 0 && dev.class_sub == 0x80 /*PCI_CLASS_DISPLAY_OTHER*/) continue; // Likely an auxiliary display controller (#2034) FFGPUResult* gpu = (FFGPUResult*)ffListAdd(gpus); ffStrbufInitStatic(&gpu->vendor, ffGPUGetVendorString(dev.vendor_id)); ffStrbufInit(&gpu->name); ffStrbufInit(&gpu->driver); ffStrbufInitStatic(&gpu->platformApi, POKE_DEVICE_FULLNAME); ffStrbufInit(&gpu->memoryType); gpu->temperature = FF_GPU_TEMP_UNSET; gpu->coreCount = FF_GPU_CORE_COUNT_UNSET; gpu->coreUsage = FF_GPU_CORE_USAGE_UNSET; gpu->type = FF_GPU_TYPE_UNKNOWN; gpu->dedicated.total = gpu->dedicated.used = gpu->shared.total = gpu->shared.used = FF_GPU_VMEM_SIZE_UNSET; gpu->deviceId = ffGPUPciAddr2Id(0, dev.bus, dev.device, dev.function); gpu->frequency = FF_GPU_FREQUENCY_UNSET; if (gpu->vendor.chars == FF_GPU_VENDOR_NAME_AMD) ffGPUQueryAmdGpuName(dev.device_id, dev.revision, gpu); if (gpu->name.length == 0) ffGPUFillVendorAndName(dev.class_sub, dev.vendor_id, dev.device_id, gpu); } return NULL; } ================================================ FILE: src/detection/gpu/gpu_intel.c ================================================ #include "gpu_driver_specific.h" #include "common/library.h" #include "common/mallocHelper.h" #include "igcl.h" struct FFIgclData { FF_LIBRARY_SYMBOL(ctlClose) FF_LIBRARY_SYMBOL(ctlEnumerateDevices) FF_LIBRARY_SYMBOL(ctlGetDeviceProperties) FF_LIBRARY_SYMBOL(ctlEnumTemperatureSensors) FF_LIBRARY_SYMBOL(ctlTemperatureGetProperties) FF_LIBRARY_SYMBOL(ctlEnumMemoryModules) FF_LIBRARY_SYMBOL(ctlMemoryGetProperties) FF_LIBRARY_SYMBOL(ctlMemoryGetState) FF_LIBRARY_SYMBOL(ctlEnumFrequencyDomains) FF_LIBRARY_SYMBOL(ctlFrequencyGetProperties) bool inited; ctl_api_handle_t apiHandle; } igclData; static void shutdownIgcl() { if (igclData.apiHandle) { igclData.ffctlClose(igclData.apiHandle); igclData.apiHandle = NULL; } } const char* ffDetectIntelGpuInfo(const FFGpuDriverCondition* cond, FFGpuDriverResult result, const char* soName) { if (!igclData.inited) { igclData.inited = true; FF_LIBRARY_LOAD(libigcl, "dlopen igcl (ControlLib) failed", soName , 1); FF_LIBRARY_LOAD_SYMBOL_MESSAGE(libigcl, ctlInit) FF_LIBRARY_LOAD_SYMBOL_VAR_MESSAGE(libigcl, igclData, ctlClose) FF_LIBRARY_LOAD_SYMBOL_VAR_MESSAGE(libigcl, igclData, ctlEnumerateDevices) FF_LIBRARY_LOAD_SYMBOL_VAR_MESSAGE(libigcl, igclData, ctlGetDeviceProperties) FF_LIBRARY_LOAD_SYMBOL_VAR_MESSAGE(libigcl, igclData, ctlEnumTemperatureSensors) FF_LIBRARY_LOAD_SYMBOL_VAR_MESSAGE(libigcl, igclData, ctlTemperatureGetProperties) FF_LIBRARY_LOAD_SYMBOL_VAR_MESSAGE(libigcl, igclData, ctlEnumMemoryModules) FF_LIBRARY_LOAD_SYMBOL_VAR_MESSAGE(libigcl, igclData, ctlMemoryGetProperties) FF_LIBRARY_LOAD_SYMBOL_VAR_MESSAGE(libigcl, igclData, ctlMemoryGetState) FF_LIBRARY_LOAD_SYMBOL_VAR_MESSAGE(libigcl, igclData, ctlEnumFrequencyDomains) FF_LIBRARY_LOAD_SYMBOL_VAR_MESSAGE(libigcl, igclData, ctlFrequencyGetProperties) if (ffctlInit(&(ctl_init_args_t) { .AppVersion = CTL_IMPL_VERSION, .flags = CTL_INIT_FLAG_USE_LEVEL_ZERO, .Size = sizeof(ctl_init_args_t), .Version = 0, }, &igclData.apiHandle) != CTL_RESULT_SUCCESS) return "loading igcl library failed"; atexit(shutdownIgcl); libigcl = NULL; // don't close igcl } if (!igclData.apiHandle) return "loading igcl library failed"; uint32_t deviceCount = 0; if (igclData.ffctlEnumerateDevices(igclData.apiHandle, &deviceCount, NULL)) return "ctlEnumerateDevices(NULL) failed"; if (deviceCount == 0) return "No Intel graphics adapter found"; FF_AUTO_FREE ctl_device_adapter_handle_t* devices = malloc(deviceCount * sizeof(*devices)); if (igclData.ffctlEnumerateDevices(igclData.apiHandle, &deviceCount, devices)) return "ctlEnumerateDevices(devices) failed"; ctl_device_adapter_handle_t device = NULL; uint64_t /* LUID */ deviceId = 0; ctl_device_adapter_properties_t properties = { .Size = sizeof(properties), .pDeviceID = &deviceId, .device_id_size = sizeof(deviceId), .Version = 2, }; for (uint32_t iDev = 0; iDev < deviceCount; iDev++) { if (igclData.ffctlGetDeviceProperties(devices[iDev], &properties) != CTL_RESULT_SUCCESS) continue; if (properties.device_type != CTL_DEVICE_TYPE_GRAPHICS) continue; if (cond->type & FF_GPU_DRIVER_CONDITION_TYPE_BUS_ID) { if (cond->pciBusId.bus == properties.adapter_bdf.bus && cond->pciBusId.device == properties.adapter_bdf.device && cond->pciBusId.func == properties.adapter_bdf.function) { device = devices[iDev]; break; } } else if (cond->type & FF_GPU_DRIVER_CONDITION_TYPE_LUID) { if (cond->luid == deviceId) { device = devices[iDev]; break; } } else if (cond->type & FF_GPU_DRIVER_CONDITION_TYPE_DEVICE_ID) { if ( cond->pciDeviceId.deviceId == properties.pci_device_id && cond->pciDeviceId.vendorId == properties.pci_vendor_id && cond->pciDeviceId.subSystemId == (uint32_t) ((properties.pci_subsys_id << 16u) | properties.pci_subsys_vendor_id) && cond->pciDeviceId.revId == properties.rev_id) { device = devices[iDev]; break; } } } if (!device) return "Device not found"; if (result.coreCount) *result.coreCount = properties.num_slices * properties.num_sub_slices_per_slice * properties.num_eus_per_sub_slice; if (result.memory) { ctl_mem_handle_t memoryModules[16]; uint32_t memoryCount = ARRAY_SIZE(memoryModules); if (igclData.ffctlEnumMemoryModules(device, &memoryCount, memoryModules) == CTL_RESULT_SUCCESS && memoryCount > 0) { result.memory->used = 0; result.memory->total = 0; for (uint32_t iMem = 0; iMem < memoryCount; iMem++) { ctl_mem_properties_t memoryProperties = { .Size = sizeof(memoryProperties), .Version = 0, }; if (igclData.ffctlMemoryGetProperties(memoryModules[iMem], &memoryProperties) == CTL_RESULT_SUCCESS) { if (memoryProperties.location == CTL_MEM_LOC_DEVICE && result.memoryType) { switch (memoryProperties.type) { #define FF_ICTL_MEM_TYPE_CASE(type) case CTL_MEM_TYPE_##type: ffStrbufSetStatic(result.memoryType, #type); break FF_ICTL_MEM_TYPE_CASE(HBM); FF_ICTL_MEM_TYPE_CASE(DDR); FF_ICTL_MEM_TYPE_CASE(DDR3); FF_ICTL_MEM_TYPE_CASE(DDR4); FF_ICTL_MEM_TYPE_CASE(DDR5); FF_ICTL_MEM_TYPE_CASE(LPDDR); FF_ICTL_MEM_TYPE_CASE(LPDDR3); FF_ICTL_MEM_TYPE_CASE(LPDDR4); FF_ICTL_MEM_TYPE_CASE(LPDDR5); FF_ICTL_MEM_TYPE_CASE(GDDR4); FF_ICTL_MEM_TYPE_CASE(GDDR5); FF_ICTL_MEM_TYPE_CASE(GDDR5X); FF_ICTL_MEM_TYPE_CASE(GDDR6); FF_ICTL_MEM_TYPE_CASE(GDDR6X); FF_ICTL_MEM_TYPE_CASE(GDDR7); #undef FF_ICTL_MEM_TYPE_CASE default: ffStrbufSetF(result.memoryType, "Unknown (%u)", memoryProperties.type); break; } } ctl_mem_state_t memoryState = { .Size = sizeof(ctl_mem_state_t), .Version = 0, }; if (igclData.ffctlMemoryGetState(memoryModules[iMem], &memoryState) == CTL_RESULT_SUCCESS) { if (memoryProperties.location == CTL_MEM_LOC_DEVICE) { result.memory->total += memoryState.size; result.memory->used += memoryState.size - memoryState.free; } else if (result.sharedMemory && memoryProperties.location == CTL_MEM_LOC_SYSTEM) { result.sharedMemory->total += memoryState.size; result.sharedMemory->used += memoryState.size - memoryState.free; } } } } } } if (result.type) { *result.type = properties.graphics_adapter_properties & CTL_ADAPTER_PROPERTIES_FLAG_INTEGRATED ? FF_GPU_TYPE_INTEGRATED : FF_GPU_TYPE_DISCRETE; } if (result.temp) { ctl_temp_handle_t sensors[16]; uint32_t sensorCount = ARRAY_SIZE(sensors); if (igclData.ffctlEnumTemperatureSensors(device, &sensorCount, sensors) == CTL_RESULT_SUCCESS && sensorCount > 0) { for (uint32_t iSensor = 0; iSensor < sensorCount; iSensor++) { ctl_temp_properties_t props = { .Size = sizeof(props) }; // The official sample code does not set Version // https://github.com/intel/drivers.gpu.control-library/blob/1bbacbf3814f2fd0d2b930cdf42fad83f3628db9/Samples/Telemetry_Samples/Sample_TelemetryAPP.cpp#L256 if (igclData.ffctlTemperatureGetProperties(sensors[iSensor], &props) == CTL_RESULT_SUCCESS) { if (props.type == CTL_TEMP_SENSORS_GPU) { *result.temp = props.maxTemperature; break; } } } } } if (result.frequency) { ctl_freq_handle_t domains[16]; uint32_t domainCount = ARRAY_SIZE(domains); if (igclData.ffctlEnumFrequencyDomains(device, &domainCount, domains) == CTL_RESULT_SUCCESS && domainCount > 0) { double maxValue = 0; ctl_freq_properties_t props = { .Size = sizeof(props), .Version = 0 }; for (uint32_t iDomain = 0; iDomain < domainCount; iDomain++) { if (igclData.ffctlFrequencyGetProperties(domains[iDomain], &props) == CTL_RESULT_SUCCESS) { if (props.type == CTL_FREQ_DOMAIN_GPU && props.max > maxValue) maxValue = props.max; } } *result.frequency = (uint32_t) (maxValue + 0.5); } } if (result.name) ffStrbufSetS(result.name, properties.name); return NULL; } ================================================ FILE: src/detection/gpu/gpu_linux.c ================================================ #include "detection/gpu/gpu.h" #include "detection/vulkan/vulkan.h" #include "detection/cpu/cpu.h" #include "detection/gpu/gpu_driver_specific.h" #include "common/io.h" #include "common/library.h" #include "common/FFstrbuf.h" #include "common/stringUtils.h" #include "common/mallocHelper.h" #include "modules/gpu/option.h" #include #include #ifdef FF_HAVE_DRM_AMDGPU #include #include #include #endif #ifdef FF_HAVE_DRM #include "intel_drm.h" #include #include #endif #if defined(FF_HAVE_DRM) && defined(__aarch64__) // https://github.com/alyssarosenzweig/linux/blob/agx-uapi-v7/include/uapi/drm/asahi_drm.h // Found in kernel-headers-6.14.4-400.asahi.fc42.aarch64 #if __has_include() #include #else #include "asahi_drm.h" #endif #define FF_HAVE_DRM_ASAHI 1 #endif static bool pciDetectDriver(FFstrbuf* result, FFstrbuf* pciDir, FFstrbuf* buffer, FF_MAYBE_UNUSED const char* drmKey) { uint32_t pciDirLength = pciDir->length; ffStrbufAppendS(pciDir, "/driver"); char pathBuf[PATH_MAX]; ssize_t resultLength = readlink(pciDir->chars, pathBuf, ARRAY_SIZE(pathBuf)); if(resultLength <= 0) return false; const char* slash = memrchr(pathBuf, '/', (size_t) resultLength); if (slash) { slash++; ffStrbufSetNS(result, (uint32_t) (resultLength - (slash - pathBuf)), slash); } if (ffStrbufEqualS(result, "nvidia")) { if (ffReadFileBuffer("/proc/driver/nvidia/version", buffer)) { if (ffStrbufContainS(buffer, " Open ")) ffStrbufAppendS(result, " (open source)"); else ffStrbufAppendS(result, " (proprietary)"); } } if (instance.config.general.detectVersion) { ffStrbufAppendS(pciDir, "/module/version"); if (ffReadFileBuffer(pciDir->chars, buffer)) { ffStrbufTrimRightSpace(buffer); ffStrbufAppendC(result, ' '); ffStrbufAppend(result, buffer); } else if (ffStrbufEqualS(result, "zx")) { ffStrbufSubstrBefore(pciDir, pciDirLength); ffStrbufAppendS(pciDir, "/zx_info/driver_version"); if (ffReadFileBuffer(pciDir->chars, buffer)) { ffStrbufTrimRightSpace(buffer); ffStrbufAppendC(result, ' '); ffStrbufAppend(result, buffer); } } } return true; } FF_MAYBE_UNUSED static const char* drmFindRenderFromCard(const char* drmCardKey, FFstrbuf* result) { char path[PATH_MAX]; sprintf(path, "/sys/class/drm/%s/device/drm", drmCardKey); FF_AUTO_CLOSE_DIR DIR* dirp = opendir(path); if (!dirp) return "Failed to open `/sys/class/drm/{drmCardKey}/device/drm`"; struct dirent* entry; while ((entry = readdir(dirp)) != NULL) { if (ffStrStartsWith(entry->d_name, "render")) { ffStrbufSetS(result, "/dev/dri/"); ffStrbufAppendS(result, entry->d_name); return NULL; } } return "Failed to find render device"; } static const char* drmDetectAmdSpecific(const FFGPUOptions* options, FFGPUResult* gpu, const char* drmKey, FFstrbuf* buffer) { #if FF_HAVE_DRM const char* error = drmFindRenderFromCard(drmKey, buffer); if (error) return error; if (ffStrbufEqualS(&gpu->driver, "radeon")) return ffDrmDetectRadeon(options, gpu, buffer->chars); else { #if FF_HAVE_DRM_AMDGPU return ffDrmDetectAmdgpu(options, gpu, buffer->chars); #else FF_UNUSED(options, gpu, drmKey, buffer); return "Fastfetch is not compiled with libdrm_amdgpu support"; #endif } #else FF_UNUSED(options, gpu, drmKey, buffer); return "Fastfetch is not compiled with drm support"; #endif } static void pciDetectAmdSpecific(const FFGPUOptions* options, FFGPUResult* gpu, FFstrbuf* pciDir, FFstrbuf* buffer) { // https://www.kernel.org/doc/html/v5.10/gpu/amdgpu.html#mem-info-vis-vram-total const uint32_t pciDirLen = pciDir->length; ffStrbufAppendS(pciDir, "/hwmon/"); FF_AUTO_CLOSE_DIR DIR* dirp = opendir(pciDir->chars); if (!dirp) return; struct dirent* entry; while ((entry = readdir(dirp)) != NULL) { if (entry->d_name[0] == '.') continue; break; } if (!entry) return; ffStrbufAppendS(pciDir, entry->d_name); ffStrbufAppendC(pciDir, '/'); const uint32_t hwmonLen = pciDir->length; uint64_t value = 0; if (options->temp) { ffStrbufAppendS(pciDir, "temp1_input"); // The on die GPU temperature in millidegrees Celsius if (ffReadFileBuffer(pciDir->chars, buffer) && (value = ffStrbufToUInt(buffer, 0))) gpu->temperature = (double) value / 1000; } if (ffStrbufEqualS(&gpu->driver, "amdgpu")) // Ancient radeon drivers don't have these files { ffStrbufSubstrBefore(pciDir, hwmonLen); ffStrbufAppendS(pciDir, "in1_input"); // Northbridge voltage in millivolts (APUs only) if (ffPathExists(pciDir->chars, FF_PATHTYPE_ANY)) gpu->type = FF_GPU_TYPE_INTEGRATED; else gpu->type = FF_GPU_TYPE_DISCRETE; if (options->driverSpecific) { ffStrbufSubstrBefore(pciDir, pciDirLen); ffStrbufAppendS(pciDir, "/mem_info_vis_vram_total"); if (ffReadFileBuffer(pciDir->chars, buffer) && (value = ffStrbufToUInt(buffer, 0))) { if (gpu->type == FF_GPU_TYPE_DISCRETE) gpu->dedicated.total = value; else gpu->shared.total = value; ffStrbufSubstrBefore(pciDir, pciDir->length - (uint32_t) strlen("/mem_info_vis_vram_total")); ffStrbufAppendS(pciDir, "/mem_info_vis_vram_used"); if (ffReadFileBuffer(pciDir->chars, buffer) && (value = ffStrbufToUInt(buffer, 0))) { if (gpu->type == FF_GPU_TYPE_DISCRETE) gpu->dedicated.used = value; else gpu->shared.used = value; } } ffStrbufSubstrBefore(pciDir, pciDirLen); ffStrbufAppendS(pciDir, "/gpu_busy_percent"); if (ffReadFileBuffer(pciDir->chars, buffer) && (value = ffStrbufToUInt(buffer, 0))) gpu->coreUsage = (double) value; } } } static void pciDetectIntelSpecific(const FFGPUOptions* options, FFGPUResult* gpu, FFstrbuf* pciDir, FFstrbuf* buffer, const char* drmKey) { // Works for Intel GPUs // https://patchwork.kernel.org/project/intel-gfx/patch/1422039866-11572-3-git-send-email-ville.syrjala@linux.intel.com/ // 0000:00:02.0 is reserved for Intel integrated graphics gpu->type = gpu->deviceId == ffGPUPciAddr2Id(0, 0, 2, 0) ? FF_GPU_TYPE_INTEGRATED : FF_GPU_TYPE_DISCRETE; if (!drmKey) return; const uint32_t pciDirLen = pciDir->length; bool isXE = ffStrbufEqualS(&gpu->driver, "xe"); if (isXE) ffStrbufAppendS(pciDir, "/tile0/gt0/freq0/max_freq"); else ffStrbufAppendF(pciDir, "/drm/%s/gt_max_freq_mhz", drmKey); if (ffReadFileBuffer(pciDir->chars, buffer)) gpu->frequency = (uint32_t) ffStrbufToUInt(buffer, 0); ffStrbufSubstrBefore(pciDir, pciDirLen); if (options->temp) { ffStrbufAppendS(pciDir, "/hwmon/"); FF_AUTO_CLOSE_DIR DIR* dirp = opendir(pciDir->chars); if (dirp) { struct dirent* entry; while ((entry = readdir(dirp)) != NULL) { if (entry->d_name[0] == '.') continue; ffStrbufSubstrBefore(pciDir, pciDirLen + strlen("/hwmon/")); ffStrbufAppendS(pciDir, entry->d_name); // https://github.com/Syllo/nvtop/blob/73291884d926445e499d6b9b71cb7a9bdbc7c393/src/extract_gpuinfo_intel.c#L279-L281 ffStrbufAppendS(pciDir, isXE ? "/temp2_input" : "/temp1_input"); if (ffReadFileBuffer(pciDir->chars, buffer)) { uint64_t value = ffStrbufToUInt(buffer, 0); if (value > 0) { gpu->temperature = (double) value / 1000; break; } } } } ffStrbufSubstrBefore(pciDir, pciDirLen); } } static const char* drmDetectIntelSpecific(FFGPUResult* gpu, const char* drmKey, FFstrbuf* buffer) { #if FF_HAVE_DRM ffStrbufSetS(buffer, "/dev/dri/"); ffStrbufAppendS(buffer, drmKey); FF_AUTO_CLOSE_FD int fd = open(buffer->chars, O_RDONLY | O_CLOEXEC); if (fd < 0) return "Failed to open drm device"; if (ffStrbufEqualS(&gpu->driver, "xe")) return ffDrmDetectXe(gpu, fd); else if (ffStrbufEqualS(&gpu->driver, "i915")) return ffDrmDetectI915(gpu, fd); return "Unknown Intel GPU driver"; #else FF_UNUSED(gpu, drmKey, buffer); return "Fastfetch is not compiled with drm support"; #endif } static const char* pciDetectTempGeneral(const FFGPUOptions* options, FFGPUResult* gpu, FFstrbuf* pciDir, FFstrbuf* buffer) { if (options->temp) { const uint32_t pciDirLen = pciDir->length; ffStrbufAppendS(pciDir, "/hwmon/"); FF_AUTO_CLOSE_DIR DIR* dirp = opendir(pciDir->chars); if (dirp) { struct dirent* entry; while ((entry = readdir(dirp))) { if (entry->d_name[0] == '.') continue; ffStrbufAppendS(pciDir, entry->d_name); ffStrbufAppendS(pciDir, "/temp1_input"); if (ffReadFileBuffer(pciDir->chars, buffer)) { uint64_t value = ffStrbufToUInt(buffer, 0); if (value > 0) gpu->temperature = (double) value / 1000.0; } break; } } ffStrbufSubstrBefore(pciDir, pciDirLen); } return NULL; } static const char* drmDetectNouveauSpecific(FFGPUResult* gpu, const char* drmKey, FFstrbuf* buffer) { #if FF_HAVE_DRM ffStrbufSetS(buffer, "/dev/dri/"); ffStrbufAppendS(buffer, drmKey); FF_AUTO_CLOSE_FD int fd = open(buffer->chars, O_RDONLY | O_CLOEXEC); if (fd < 0) return "Failed to open drm device"; return ffDrmDetectNouveau(gpu, fd); #else FF_UNUSED(gpu, drmKey, buffer); return "Fastfetch is not compiled with drm support"; #endif } static const char* pciDetectZxSpecific(const FFGPUOptions* options, FFGPUResult* gpu, FFstrbuf* pciDir, FFstrbuf* buffer) { gpu->type = FF_GPU_TYPE_INTEGRATED; const uint32_t pciDirLen = pciDir->length; ffStrbufAppendS(pciDir, "/zx_info/eclk"); if (ffReadFileBuffer(pciDir->chars, buffer)) gpu->frequency = (uint32_t) ffStrbufToUInt(buffer, FF_GPU_FREQUENCY_UNSET); ffStrbufSubstrBefore(pciDir, pciDirLen); if (options->driverSpecific) { ffStrbufAppendS(pciDir, "/zx_info/engine_3d_usage"); if (ffReadFileBuffer(pciDir->chars, buffer)) gpu->coreUsage = ffStrbufToDouble(buffer, FF_GPU_CORE_USAGE_UNSET); ffStrbufSubstrBefore(pciDir, pciDirLen); ffStrbufAppendS(pciDir, "/zx_info/fb_size"); if (ffReadFileBuffer(pciDir->chars, buffer)) gpu->shared.total = ffStrbufToUInt(buffer, FF_GPU_VMEM_SIZE_UNSET); ffStrbufSubstrBefore(pciDir, pciDirLen); if (gpu->shared.total != FF_GPU_VMEM_SIZE_UNSET) { gpu->shared.total *= 1024 * 1024; ffStrbufAppendS(pciDir, "/zx_info/free_fb_mem"); if (ffReadFileBuffer(pciDir->chars, buffer)) gpu->shared.used = ffStrbufToUInt(buffer, FF_GPU_VMEM_SIZE_UNSET); ffStrbufSubstrBefore(pciDir, pciDirLen); if (gpu->shared.used != FF_GPU_VMEM_SIZE_UNSET) { gpu->shared.used *= 1024 * 1024; gpu->shared.used = gpu->shared.total - gpu->shared.used; } } } return NULL; } static const char* detectPci(const FFGPUOptions* options, FFlist* gpus, FFstrbuf* buffer, FFstrbuf* deviceDir, const char* drmKey) { const uint32_t drmDirPathLength = deviceDir->length; uint32_t vendorId, deviceId, subVendorId, subDeviceId; uint8_t classId, subclassId; if (sscanf(buffer->chars + strlen("pci:"), "v%8" SCNx32 "d%8" SCNx32 "sv%8" SCNx32 "sd%8" SCNx32 "bc%2" SCNx8 "sc%2" SCNx8, &vendorId, &deviceId, &subVendorId, &subDeviceId, &classId, &subclassId) != 6) return "Failed to parse pci modalias"; if (classId != 0x03 /*PCI_BASE_CLASS_DISPLAY*/) return "Not a GPU device"; char pciPath[PATH_MAX]; const char* pPciPath = NULL; if (drmKey) { ssize_t pathLength = readlink(deviceDir->chars, pciPath, ARRAY_SIZE(pciPath) - 1); if (pathLength <= 0) return "Unable to get PCI device path"; pciPath[pathLength] = '\0'; pPciPath = strrchr(pciPath, '/'); if (__builtin_expect(pPciPath != NULL, true)) pPciPath++; else pPciPath = pciPath; } else { pPciPath = memrchr(deviceDir->chars, '/', deviceDir->length); assert(pPciPath); pPciPath++; } uint32_t pciDomain, pciBus, pciDevice, pciFunc; if (sscanf(pPciPath, "%" SCNx32 ":%" SCNx32 ":%" SCNx32 ".%" SCNx32, &pciDomain, &pciBus, &pciDevice, &pciFunc) != 4) return "Invalid PCI device path"; if (pciFunc > 0 && subclassId == 0x80 /*PCI_CLASS_DISPLAY_OTHER*/) return "Likely an auxiliary display controller"; // #2034 FFGPUResult* gpu = (FFGPUResult*)ffListAdd(gpus); ffStrbufInitStatic(&gpu->vendor, ffGPUGetVendorString((uint16_t) vendorId)); ffStrbufInit(&gpu->name); ffStrbufInit(&gpu->driver); ffStrbufInit(&gpu->platformApi); ffStrbufInit(&gpu->memoryType); gpu->index = FF_GPU_INDEX_UNSET; gpu->temperature = FF_GPU_TEMP_UNSET; gpu->coreUsage = FF_GPU_CORE_USAGE_UNSET; gpu->coreCount = FF_GPU_CORE_COUNT_UNSET; gpu->type = FF_GPU_TYPE_UNKNOWN; gpu->dedicated.total = gpu->dedicated.used = gpu->shared.total = gpu->shared.used = FF_GPU_VMEM_SIZE_UNSET; gpu->deviceId = ffGPUPciAddr2Id(pciDomain, pciBus, pciDevice, pciFunc); gpu->frequency = FF_GPU_FREQUENCY_UNSET; char drmKeyBuffer[8]; if (!drmKey) { ffStrbufAppendS(deviceDir, "/drm"); FF_AUTO_CLOSE_DIR DIR* dirp = opendir(deviceDir->chars); if (dirp) { struct dirent* entry; while ((entry = readdir(dirp)) != NULL) { if (ffStrStartsWith(entry->d_name, "card")) { ffStrCopy(drmKeyBuffer, entry->d_name, ARRAY_SIZE(drmKeyBuffer)); drmKey = drmKeyBuffer; break; } } } ffStrbufSubstrBefore(deviceDir, drmDirPathLength); } if (drmKey) ffStrbufSetF(&gpu->platformApi, "DRM (%s)", drmKey); pciDetectDriver(&gpu->driver, deviceDir, buffer, drmKey); ffStrbufSubstrBefore(deviceDir, drmDirPathLength); if (gpu->vendor.chars == FF_GPU_VENDOR_NAME_AMD) { bool ok = false; if (drmKey && options->driverSpecific) ok = drmDetectAmdSpecific(options, gpu, drmKey, buffer) == NULL; if (!ok) { pciDetectAmdSpecific(options, gpu, deviceDir, buffer); ffStrbufSubstrBefore(deviceDir, drmDirPathLength); ffStrbufAppendS(deviceDir, "/revision"); if (ffReadFileBuffer(deviceDir->chars, buffer)) { char* pend; uint64_t revision = strtoul(buffer->chars, &pend, 16); if (pend != buffer->chars) ffGPUQueryAmdGpuName((uint16_t) deviceId, (uint8_t) revision, gpu); } ffStrbufSubstrBefore(deviceDir, drmDirPathLength); } } else if (gpu->vendor.chars == FF_GPU_VENDOR_NAME_INTEL) { pciDetectIntelSpecific(options, gpu, deviceDir, buffer, drmKey); ffStrbufSubstrBefore(deviceDir, drmDirPathLength); if (options->driverSpecific && drmKey) drmDetectIntelSpecific(gpu, drmKey, buffer); } else if (gpu->vendor.chars == FF_GPU_VENDOR_NAME_NVIDIA && ffStrbufEqualS(&gpu->driver, "nouveau")) { pciDetectTempGeneral(options, gpu, deviceDir, buffer); if (options->driverSpecific && drmKey) drmDetectNouveauSpecific(gpu, drmKey, buffer); } else if (gpu->vendor.chars == FF_GPU_VENDOR_NAME_ZHAOXIN && ffStrbufStartsWithS(&gpu->driver, "zx")) { pciDetectTempGeneral(options, gpu, deviceDir, buffer); pciDetectZxSpecific(options, gpu, deviceDir, buffer); } else { ffGPUDetectDriverSpecific(options, gpu, (FFGpuDriverPciBusId) { .domain = pciDomain, .bus = pciBus, .device = pciDevice, .func = pciFunc, }); } if (gpu->name.length == 0) ffGPUFillVendorAndName(subclassId, (uint16_t) vendorId, (uint16_t) deviceId, gpu); if (gpu->type == FF_GPU_TYPE_UNKNOWN) { if (gpu->vendor.chars == FF_GPU_VENDOR_NAME_NVIDIA) { if (ffStrbufStartsWithIgnCaseS(&gpu->name, "GeForce") || ffStrbufStartsWithIgnCaseS(&gpu->name, "Quadro") || ffStrbufStartsWithIgnCaseS(&gpu->name, "Tesla")) gpu->type = FF_GPU_TYPE_DISCRETE; } else if (gpu->vendor.chars == FF_GPU_VENDOR_NAME_MTHREADS) { if (ffStrbufStartsWithIgnCaseS(&gpu->name, "MTT ")) gpu->type = FF_GPU_TYPE_DISCRETE; } } return NULL; } #if __aarch64__ FF_MAYBE_UNUSED static const char* drmDetectAsahiSpecific(FFGPUResult* gpu, const char* name, FF_MAYBE_UNUSED FFstrbuf* buffer, FF_MAYBE_UNUSED const char* drmKey) { if (sscanf(name, "agx-t%lu", &gpu->deviceId) == 1) ffStrbufSetStatic(&gpu->name, ffCPUAppleCodeToName((uint32_t) gpu->deviceId)); ffStrbufSetStatic(&gpu->vendor, FF_GPU_VENDOR_NAME_APPLE); #if FF_HAVE_DRM_ASAHI ffStrbufSetS(buffer, "/dev/dri/"); ffStrbufAppendS(buffer, drmKey); FF_AUTO_CLOSE_FD int fd = open(buffer->chars, O_RDONLY | O_CLOEXEC); if (fd >= 0) return ffDrmDetectAsahi(gpu, fd); #endif return NULL; } #endif static const char* detectOf(FFlist* gpus, FFstrbuf* buffer, FFstrbuf* drmDir, const char* drmKey) { char compatible[256]; // vendor,model-name if (sscanf(buffer->chars + strlen("of:"), "NgpuT%*[^C]C%255[^C]", compatible) != 1) return "Failed to parse of modalias or not a GPU device"; char* name = strchr(compatible, ','); if (name) { *name = '\0'; ++name; } FFGPUResult* gpu = (FFGPUResult*)ffListAdd(gpus); gpu->index = FF_GPU_INDEX_UNSET; gpu->deviceId = 0; ffStrbufInit(&gpu->name); ffStrbufInit(&gpu->vendor); ffStrbufInit(&gpu->driver); ffStrbufInit(&gpu->memoryType); ffStrbufInitF(&gpu->platformApi, "DRM (%s)", drmKey); gpu->temperature = FF_GPU_TEMP_UNSET; gpu->coreCount = FF_GPU_CORE_COUNT_UNSET; gpu->coreUsage = FF_GPU_CORE_USAGE_UNSET; gpu->type = FF_GPU_TYPE_INTEGRATED; gpu->dedicated.total = gpu->dedicated.used = gpu->shared.total = gpu->shared.used = FF_GPU_VMEM_SIZE_UNSET; gpu->frequency = FF_GPU_FREQUENCY_UNSET; pciDetectDriver(&gpu->driver, drmDir, buffer, drmKey); #ifdef __aarch64__ if (ffStrbufEqualS(&gpu->driver, "asahi")) drmDetectAsahiSpecific(gpu, name, buffer, drmKey); #endif if (!gpu->name.length) { ffStrbufSetS(&gpu->name, name ?: compatible); ffStrbufTrimRightSpace(&gpu->name); } if (!gpu->vendor.length && name) { if (ffStrEquals(compatible, "brcm")) ffStrbufSetStatic(&gpu->vendor, "Broadcom"); // Raspberry Pi else { ffStrbufSetS(&gpu->vendor, compatible); gpu->vendor.chars[0] = (char) toupper(compatible[0]); } } return NULL; } static const char* drmDetectGPUs(const FFGPUOptions* options, FFlist* gpus) { FF_STRBUF_AUTO_DESTROY drmDir = ffStrbufCreateA(64); ffStrbufAppendS(&drmDir, "/sys/class/drm/"); const uint32_t drmDirLength = drmDir.length; FF_AUTO_CLOSE_DIR DIR* dir = opendir(drmDir.chars); if(dir == NULL) return "Failed to open `/sys/class/drm/`"; FF_STRBUF_AUTO_DESTROY buffer = ffStrbufCreate(); struct dirent* entry; while ((entry = readdir(dir)) != NULL) { if (!ffStrStartsWith(entry->d_name, "card") || strchr(entry->d_name + 4, '-') != NULL) continue; ffStrbufAppendS(&drmDir, entry->d_name); ffStrbufAppendS(&drmDir, "/device/modalias"); if (!ffReadFileBuffer(drmDir.chars, &buffer)) continue; ffStrbufSubstrBefore(&drmDir, drmDir.length - (uint32_t) strlen("/modalias")); if (ffStrbufStartsWithS(&buffer, "pci:")) detectPci(options, gpus, &buffer, &drmDir, entry->d_name); else if (ffStrbufStartsWithS(&buffer, "of:")) // Open Firmware detectOf(gpus, &buffer, &drmDir, entry->d_name); ffStrbufSubstrBefore(&drmDir, drmDirLength); } return NULL; } static const char* pciDetectGPUs(const FFGPUOptions* options, FFlist* gpus) { //https://www.kernel.org/doc/Documentation/ABI/testing/sysfs-bus-pci const char* pciDirPath = "/sys/bus/pci/devices/"; FF_AUTO_CLOSE_DIR DIR* dirp = opendir(pciDirPath); if(dirp == NULL) return "Failed to open `/sys/bus/pci/devices/`"; FF_STRBUF_AUTO_DESTROY pciDir = ffStrbufCreateA(64); ffStrbufAppendS(&pciDir, pciDirPath); const uint32_t pciBaseDirLength = pciDir.length; FF_STRBUF_AUTO_DESTROY buffer = ffStrbufCreate(); struct dirent* entry; while((entry = readdir(dirp)) != NULL) { if(entry->d_name[0] == '.') continue; ffStrbufSubstrBefore(&pciDir, pciBaseDirLength); ffStrbufAppendS(&pciDir, entry->d_name); const uint32_t pciDevDirLength = pciDir.length; ffStrbufAppendS(&pciDir, "/modalias"); if (!ffReadFileBuffer(pciDir.chars, &buffer)) continue; ffStrbufSubstrBefore(&pciDir, pciDevDirLength); assert(ffStrbufStartsWithS(&buffer, "pci:")); detectPci(options, gpus, &buffer, &pciDir, NULL); ffStrbufSubstrBefore(&pciDir, pciBaseDirLength); } return NULL; } const char* ffDetectGPUImpl(const FFGPUOptions* options, FFlist* gpus) { #ifdef FF_HAVE_DIRECTX_HEADERS const char* ffGPUDetectByDirectX(const FFGPUOptions* options, FFlist* gpus); if (ffGPUDetectByDirectX(options, gpus) == NULL) return NULL; #endif if (options->detectionMethod == FF_GPU_DETECTION_METHOD_AUTO) { if (drmDetectGPUs(options, gpus) == NULL && gpus->length > 0) return NULL; } return pciDetectGPUs(options, gpus); } ================================================ FILE: src/detection/gpu/gpu_mthreads.c ================================================ #include "gpu_driver_specific.h" #include "common/library.h" #include "mtml.h" struct FFMtmlData { FF_LIBRARY_SYMBOL(mtmlDeviceCountGpuCores) FF_LIBRARY_SYMBOL(mtmlDeviceGetBrand) FF_LIBRARY_SYMBOL(mtmlDeviceGetIndex) FF_LIBRARY_SYMBOL(mtmlDeviceGetName) FF_LIBRARY_SYMBOL(mtmlDeviceGetPciInfo) FF_LIBRARY_SYMBOL(mtmlDeviceGetUUID) FF_LIBRARY_SYMBOL(mtmlDeviceInitGpu) FF_LIBRARY_SYMBOL(mtmlDeviceInitMemory) FF_LIBRARY_SYMBOL(mtmlGpuGetMaxClock) FF_LIBRARY_SYMBOL(mtmlGpuGetTemperature) FF_LIBRARY_SYMBOL(mtmlGpuGetUtilization) FF_LIBRARY_SYMBOL(mtmlLibraryCountDevice) FF_LIBRARY_SYMBOL(mtmlLibraryInitDeviceByIndex) FF_LIBRARY_SYMBOL(mtmlLibraryInitDeviceByPciSbdf) FF_LIBRARY_SYMBOL(mtmlLibraryInitSystem) FF_LIBRARY_SYMBOL(mtmlMemoryGetTotal) FF_LIBRARY_SYMBOL(mtmlMemoryGetUsed) FF_LIBRARY_SYMBOL(mtmlMemoryGetUtilization) FF_LIBRARY_SYMBOL(mtmlLibraryShutDown) bool inited; MtmlLibrary *lib; MtmlSystem *sys; } mtmlData; FF_MAYBE_UNUSED static void shutdownMtml(void) { mtmlData.ffmtmlLibraryShutDown(mtmlData.lib); } const char *ffDetectMthreadsGpuInfo(const FFGpuDriverCondition *cond, FFGpuDriverResult result, const char *soName) { #ifndef FF_DISABLE_DLOPEN if (!mtmlData.inited) { mtmlData.inited = true; FF_LIBRARY_LOAD(libmtml, "dlopen mtml failed", soName, 1); FF_LIBRARY_LOAD_SYMBOL_MESSAGE(libmtml, mtmlLibraryInit) FF_LIBRARY_LOAD_SYMBOL_VAR_MESSAGE(libmtml, mtmlData, mtmlDeviceCountGpuCores) FF_LIBRARY_LOAD_SYMBOL_VAR_MESSAGE(libmtml, mtmlData, mtmlDeviceGetBrand) FF_LIBRARY_LOAD_SYMBOL_VAR_MESSAGE(libmtml, mtmlData, mtmlDeviceGetIndex) FF_LIBRARY_LOAD_SYMBOL_VAR_MESSAGE(libmtml, mtmlData, mtmlDeviceGetName) FF_LIBRARY_LOAD_SYMBOL_VAR_MESSAGE(libmtml, mtmlData, mtmlDeviceGetPciInfo) FF_LIBRARY_LOAD_SYMBOL_VAR_MESSAGE(libmtml, mtmlData, mtmlDeviceGetUUID) FF_LIBRARY_LOAD_SYMBOL_VAR_MESSAGE(libmtml, mtmlData, mtmlDeviceInitGpu) FF_LIBRARY_LOAD_SYMBOL_VAR_MESSAGE(libmtml, mtmlData, mtmlDeviceInitMemory) FF_LIBRARY_LOAD_SYMBOL_VAR_MESSAGE(libmtml, mtmlData, mtmlGpuGetMaxClock) FF_LIBRARY_LOAD_SYMBOL_VAR_MESSAGE(libmtml, mtmlData, mtmlGpuGetTemperature) FF_LIBRARY_LOAD_SYMBOL_VAR_MESSAGE(libmtml, mtmlData, mtmlGpuGetUtilization) FF_LIBRARY_LOAD_SYMBOL_VAR_MESSAGE(libmtml, mtmlData, mtmlLibraryCountDevice) FF_LIBRARY_LOAD_SYMBOL_VAR_MESSAGE(libmtml, mtmlData, mtmlLibraryInitDeviceByIndex) FF_LIBRARY_LOAD_SYMBOL_VAR_MESSAGE(libmtml, mtmlData, mtmlLibraryInitDeviceByPciSbdf) FF_LIBRARY_LOAD_SYMBOL_VAR_MESSAGE(libmtml, mtmlData, mtmlLibraryInitSystem) FF_LIBRARY_LOAD_SYMBOL_VAR_MESSAGE(libmtml, mtmlData, mtmlMemoryGetTotal) FF_LIBRARY_LOAD_SYMBOL_VAR_MESSAGE(libmtml, mtmlData, mtmlMemoryGetUsed) FF_LIBRARY_LOAD_SYMBOL_VAR_MESSAGE(libmtml, mtmlData, mtmlMemoryGetUtilization) FF_LIBRARY_LOAD_SYMBOL_VAR_MESSAGE(libmtml, mtmlData, mtmlLibraryShutDown) if (ffmtmlLibraryInit(&mtmlData.lib) != MTML_SUCCESS) { mtmlData.ffmtmlLibraryInitSystem = NULL; return "mtmlLibraryInit failed"; } if (mtmlData.ffmtmlLibraryInitSystem(mtmlData.lib, &mtmlData.sys) != MTML_SUCCESS) { mtmlData.ffmtmlLibraryShutDown(mtmlData.lib); mtmlData.ffmtmlLibraryInitSystem = NULL; return "mtmlLibraryInitSystem failed"; } atexit(shutdownMtml); libmtml = NULL; // don't close mtml } if (mtmlData.ffmtmlLibraryInitSystem == NULL) return "loading mtml library failed"; MtmlDevice *device = NULL; if (cond->type & FF_GPU_DRIVER_CONDITION_TYPE_BUS_ID) { char pciBusIdStr[32]; snprintf(pciBusIdStr, ARRAY_SIZE(pciBusIdStr) - 1, "%04x:%02x:%02x.%d", cond->pciBusId.domain, cond->pciBusId.bus, cond->pciBusId.device, cond->pciBusId.func); MtmlReturn ret = mtmlData.ffmtmlLibraryInitDeviceByPciSbdf(mtmlData.lib, pciBusIdStr, &device); if (ret != MTML_SUCCESS) return "mtmlLibraryInitDeviceByPciSbdf() failed"; } else if (cond->type & FF_GPU_DRIVER_CONDITION_TYPE_DEVICE_ID) { uint32_t count; if (mtmlData.ffmtmlLibraryCountDevice(mtmlData.lib, &count) != MTML_SUCCESS) return "mtmlLibraryCountDevice() failed"; for (uint32_t i = 0; i < count; i++, device = NULL) { if (mtmlData.ffmtmlLibraryInitDeviceByIndex(mtmlData.lib, i, &device) != MTML_SUCCESS) continue; MtmlPciInfo pciInfo; if (mtmlData.ffmtmlDeviceGetPciInfo(device, &pciInfo) != MTML_SUCCESS) continue; if (pciInfo.pciDeviceId != ((cond->pciDeviceId.deviceId << 16u) | cond->pciDeviceId.vendorId) || pciInfo.pciSubsystemId != cond->pciDeviceId.subSystemId) continue; break; } if (!device) return "Device not found"; } else { return "Unknown condition type"; } MtmlBrandType brand; if (mtmlData.ffmtmlDeviceGetBrand(device, &brand) == MTML_SUCCESS) { switch (brand) { case MTML_BRAND_MTT: *result.type = FF_GPU_TYPE_DISCRETE; break; default: break; } } if (result.index) { unsigned int value; if (mtmlData.ffmtmlDeviceGetIndex(device, &value) == MTML_SUCCESS) *result.index = value; } if (result.temp) { MtmlGpu *gpu = NULL; if (mtmlData.ffmtmlDeviceInitGpu(device, &gpu) == MTML_SUCCESS) { uint32_t value; if (mtmlData.ffmtmlGpuGetTemperature(gpu, &value) == MTML_SUCCESS) *result.temp = value; } } if (result.memory) { MtmlMemory *mem = NULL; if (mtmlData.ffmtmlDeviceInitMemory(device, &mem) == MTML_SUCCESS) { unsigned long long total; if (mtmlData.ffmtmlMemoryGetTotal(mem, &total) == MTML_SUCCESS) result.memory->total = total; unsigned long long used; if (mtmlData.ffmtmlMemoryGetUsed(mem, &used) == MTML_SUCCESS) result.memory->used = used; } } if (result.coreCount) mtmlData.ffmtmlDeviceCountGpuCores(device, result.coreCount); if (result.frequency) { MtmlGpu *gpu = NULL; if (mtmlData.ffmtmlDeviceInitGpu(device, &gpu) == MTML_SUCCESS) { uint32_t clockMHz; if (mtmlData.ffmtmlGpuGetMaxClock(gpu, &clockMHz) == MTML_SUCCESS) *result.frequency = clockMHz; } } if (result.coreUsage) { MtmlGpu *gpu = NULL; if (mtmlData.ffmtmlDeviceInitGpu(device, &gpu) == MTML_SUCCESS) { unsigned int utilization; if (mtmlData.ffmtmlGpuGetUtilization(gpu, &utilization) == MTML_SUCCESS) *result.coreUsage = utilization; } } if (result.name) { char name[MTML_DEVICE_NAME_BUFFER_SIZE]; if (mtmlData.ffmtmlDeviceGetName(device, name, ARRAY_SIZE(name)) == MTML_SUCCESS) ffStrbufSetS(result.name, name); } return NULL; #else FF_UNUSED(cond, result, soName); return "dlopen is disabled"; #endif } ================================================ FILE: src/detection/gpu/gpu_nbsd.c ================================================ #include "gpu.h" #include "common/io.h" #include #include #include #include #include #include static inline int pciReadConf(int fd, uint32_t bus, uint32_t device, uint32_t func, uint32_t reg, uint32_t* result) { struct pciio_bdf_cfgreg bdfr = { .bus = bus, .device = device, .function = func, .cfgreg = { .reg = reg, }, }; if (ioctl(fd, PCI_IOC_BDF_CFGREAD, &bdfr) == -1) return -1; *result = bdfr.cfgreg.val; return 0; } const char* ffDetectGPUImpl(FF_MAYBE_UNUSED const FFGPUOptions* options, FFlist* gpus) { char pciDevPath[] = "/dev/pciXXX"; for (uint32_t idev = 0; idev <= 255; idev++) { snprintf(pciDevPath + strlen("/dev/pci"), 4, "%u", idev); FF_AUTO_CLOSE_FD int pcifd = open(pciDevPath, O_RDONLY | O_CLOEXEC); if (pcifd < 0) { if (errno == ENOENT) break; // No more /dev/pciN devices return "open(\"/dev/pciN\", O_RDONLY | O_CLOEXEC) failed"; } struct pciio_businfo businfo; if (ioctl(pcifd, PCI_IOC_BUSINFO, &businfo) != 0) continue; uint32_t bus = businfo.busno; for (uint32_t dev = 0; dev < businfo.maxdevs; dev++) { uint32_t maxfuncs = 0; for (uint32_t func = 0; func <= maxfuncs; func++) { uint32_t pciid, pciclass; if (pciReadConf(pcifd, bus, dev, func, PCI_ID_REG, &pciid) != 0) continue; if (PCI_VENDOR(pciid) == PCI_VENDOR_INVALID || PCI_VENDOR(pciid) == 0) continue; if (pciReadConf(pcifd, bus, dev, func, PCI_CLASS_REG, &pciclass) != 0) continue; if (func == 0) { // For some reason, pciReadConf returns success even for non-existing devices. // So we need to check for `PCI_VENDOR(pciid) == PCI_VENDOR_INVALID` above to filter them out. uint32_t bhlcr; if (pciReadConf(pcifd, bus, dev, 0, PCI_BHLC_REG, &bhlcr) != 0) continue; if (PCI_HDRTYPE_MULTIFN(bhlcr)) maxfuncs = 7; } if (PCI_CLASS(pciclass) != PCI_CLASS_DISPLAY) continue; if (func > 0 && PCI_SUBCLASS(pciclass) == PCI_SUBCLASS_DISPLAY_MISC) continue; // Likely an auxiliary display controller (#2034) FFGPUResult* gpu = (FFGPUResult*)ffListAdd(gpus); ffStrbufInitStatic(&gpu->vendor, ffGPUGetVendorString(PCI_VENDOR(pciid))); ffStrbufInit(&gpu->name); ffStrbufInit(&gpu->driver); ffStrbufInitS(&gpu->platformApi, pciDevPath); ffStrbufInit(&gpu->memoryType); gpu->index = FF_GPU_INDEX_UNSET; gpu->temperature = FF_GPU_TEMP_UNSET; gpu->coreCount = FF_GPU_CORE_COUNT_UNSET; gpu->coreUsage = FF_GPU_CORE_USAGE_UNSET; gpu->type = FF_GPU_TYPE_UNKNOWN; gpu->dedicated.total = gpu->dedicated.used = gpu->shared.total = gpu->shared.used = FF_GPU_VMEM_SIZE_UNSET; gpu->deviceId = ffGPUPciAddr2Id(0, bus, dev, func); gpu->frequency = FF_GPU_FREQUENCY_UNSET; if (gpu->vendor.chars == FF_GPU_VENDOR_NAME_AMD) ffGPUQueryAmdGpuName(PCI_PRODUCT(pciid), PCI_REVISION(pciid), gpu); if (gpu->name.length == 0) ffGPUFillVendorAndName(PCI_SUBCLASS(pciclass), PCI_VENDOR(pciid), PCI_PRODUCT(pciid), gpu); struct pciio_drvname drvname = { .device = dev, .function = func, }; if (ioctl(pcifd, PCI_IOC_DRVNAME, &drvname) == 0) ffStrbufInitS(&gpu->driver, drvname.name); } } } return NULL; } ================================================ FILE: src/detection/gpu/gpu_nosupport.c ================================================ #include "gpu.h" const char* ffDetectGPUImpl(const FFGPUOptions* options, FFlist* gpus) { FF_UNUSED(options, gpus); return "Not supported on this platform"; } ================================================ FILE: src/detection/gpu/gpu_nvidia.c ================================================ #include "gpu_driver_specific.h" #include "common/library.h" #include "nvml.h" struct FFNvmlData { FF_LIBRARY_SYMBOL(nvmlDeviceGetCount_v2) FF_LIBRARY_SYMBOL(nvmlDeviceGetHandleByIndex_v2) FF_LIBRARY_SYMBOL(nvmlDeviceGetHandleByPciBusId_v2) FF_LIBRARY_SYMBOL(nvmlDeviceGetPciInfo_v3) FF_LIBRARY_SYMBOL(nvmlDeviceGetTemperature) FF_LIBRARY_SYMBOL(nvmlDeviceGetMemoryInfo_v2) FF_LIBRARY_SYMBOL(nvmlDeviceGetMemoryInfo) FF_LIBRARY_SYMBOL(nvmlDeviceGetNumGpuCores) FF_LIBRARY_SYMBOL(nvmlDeviceGetMaxClockInfo) FF_LIBRARY_SYMBOL(nvmlDeviceGetUtilizationRates) FF_LIBRARY_SYMBOL(nvmlDeviceGetBrand) FF_LIBRARY_SYMBOL(nvmlDeviceGetIndex) FF_LIBRARY_SYMBOL(nvmlDeviceGetName) bool inited; } nvmlData; #if defined(_WIN32) && !defined(FF_DISABLE_DLOPEN) #include "nvapi.h" struct FFNvapiData { FF_LIBRARY_SYMBOL(nvapi_Unload) FF_LIBRARY_SYMBOL(nvapi_EnumPhysicalGPUs) FF_LIBRARY_SYMBOL(nvapi_GPU_GetRamType) FF_LIBRARY_SYMBOL(nvapi_GPU_GetGPUType) bool inited; } nvapiData; static const char* detectMoreByNvapi(FFGpuDriverResult* result) { if (!nvapiData.inited) { nvapiData.inited = true; FF_LIBRARY_LOAD_MESSAGE(libnvapi, #ifdef _WIN64 "nvapi64.dll" #else "nvapi.dll" #endif , 1); FF_LIBRARY_LOAD_SYMBOL_MESSAGE(libnvapi, nvapi_QueryInterface) #define FF_NVAPI_INTERFACE(iName, iOffset) \ __typeof__(&iName) ff ## iName = ffnvapi_QueryInterface(iOffset); \ if (ff ## iName == NULL) return "nvapi_QueryInterface " #iName " failed"; FF_NVAPI_INTERFACE(nvapi_Initialize, NVAPI_INTERFACE_OFFSET_INITIALIZE) FF_NVAPI_INTERFACE(nvapi_Unload, NVAPI_INTERFACE_OFFSET_UNLOAD) FF_NVAPI_INTERFACE(nvapi_EnumPhysicalGPUs, NVAPI_INTERFACE_OFFSET_ENUM_PHYSICAL_GPUS) FF_NVAPI_INTERFACE(nvapi_GPU_GetRamType, NVAPI_INTERFACE_OFFSET_GPU_GET_RAM_TYPE) FF_NVAPI_INTERFACE(nvapi_GPU_GetGPUType, NVAPI_INTERFACE_OFFSET_GPU_GET_GPU_TYPE) #undef FF_NVAPI_INTERFACE if (ffnvapi_Initialize() < 0) return "NvAPI_Initialize() failed"; nvapiData.ffnvapi_EnumPhysicalGPUs = ffnvapi_EnumPhysicalGPUs; nvapiData.ffnvapi_GPU_GetRamType = ffnvapi_GPU_GetRamType; nvapiData.ffnvapi_GPU_GetGPUType = ffnvapi_GPU_GetGPUType; nvapiData.ffnvapi_Unload = ffnvapi_Unload; atexit((void*) ffnvapi_Unload); libnvapi = NULL; // don't close nvapi } if (nvapiData.ffnvapi_EnumPhysicalGPUs == NULL) return "loading nvapi library failed"; NvPhysicalGpuHandle handles[32]; int gpuCount = 0; if (nvapiData.ffnvapi_EnumPhysicalGPUs(handles, &gpuCount) < 0) return "NvAPI_EnumPhysicalGPUs() failed"; uint32_t gpuIndex = *result->index; if (gpuIndex >= (uint32_t) gpuCount) return "GPU index out of range"; // Not very sure. Need to check in multi-GPU system NvPhysicalGpuHandle gpuHandle = handles[gpuIndex]; NvApiGPUMemoryType memType; if (result->memoryType && nvapiData.ffnvapi_GPU_GetRamType(gpuHandle, &memType) == 0) { switch (memType) { #define FF_NVAPI_MEMORY_TYPE(type) \ case NVAPI_GPU_MEMORY_TYPE_##type: \ ffStrbufSetStatic(result->memoryType, #type); \ break; FF_NVAPI_MEMORY_TYPE(UNKNOWN) FF_NVAPI_MEMORY_TYPE(SDRAM) FF_NVAPI_MEMORY_TYPE(DDR1) FF_NVAPI_MEMORY_TYPE(DDR2) FF_NVAPI_MEMORY_TYPE(GDDR2) FF_NVAPI_MEMORY_TYPE(GDDR3) FF_NVAPI_MEMORY_TYPE(GDDR4) FF_NVAPI_MEMORY_TYPE(DDR3) FF_NVAPI_MEMORY_TYPE(GDDR5) FF_NVAPI_MEMORY_TYPE(LPDDR2) FF_NVAPI_MEMORY_TYPE(GDDR5X) FF_NVAPI_MEMORY_TYPE(LPDDR3) FF_NVAPI_MEMORY_TYPE(LPDDR4) FF_NVAPI_MEMORY_TYPE(LPDDR5) FF_NVAPI_MEMORY_TYPE(GDDR6) FF_NVAPI_MEMORY_TYPE(GDDR6X) FF_NVAPI_MEMORY_TYPE(GDDR7) #undef FF_NVAPI_MEMORY_TYPE default: ffStrbufSetF(result->memoryType, "Unknown (%d)", memType); break; } } NvApiGPUType gpuType; if (result->type && nvapiData.ffnvapi_GPU_GetGPUType(gpuHandle, &gpuType) == 0) { switch (gpuType) { case NV_SYSTEM_TYPE_IGPU: *result->type = FF_GPU_TYPE_INTEGRATED; break; case NV_SYSTEM_TYPE_DGPU: *result->type = FF_GPU_TYPE_DISCRETE; break; default: *result->type = FF_GPU_TYPE_UNKNOWN; break; } } return NULL; } #endif const char* ffDetectNvidiaGpuInfo(const FFGpuDriverCondition* cond, FFGpuDriverResult result, const char* soName) { #ifndef FF_DISABLE_DLOPEN if (!nvmlData.inited) { nvmlData.inited = true; FF_LIBRARY_LOAD(libnvml, "dlopen nvml failed", soName , 1); FF_LIBRARY_LOAD_SYMBOL_MESSAGE(libnvml, nvmlInit_v2) FF_LIBRARY_LOAD_SYMBOL_MESSAGE(libnvml, nvmlShutdown) FF_LIBRARY_LOAD_SYMBOL_VAR_MESSAGE(libnvml, nvmlData, nvmlDeviceGetCount_v2) FF_LIBRARY_LOAD_SYMBOL_VAR_MESSAGE(libnvml, nvmlData, nvmlDeviceGetHandleByIndex_v2) FF_LIBRARY_LOAD_SYMBOL_VAR_MESSAGE(libnvml, nvmlData, nvmlDeviceGetHandleByPciBusId_v2) FF_LIBRARY_LOAD_SYMBOL_VAR_MESSAGE(libnvml, nvmlData, nvmlDeviceGetPciInfo_v3) FF_LIBRARY_LOAD_SYMBOL_VAR_MESSAGE(libnvml, nvmlData, nvmlDeviceGetTemperature) FF_LIBRARY_LOAD_SYMBOL_VAR_MESSAGE(libnvml, nvmlData, nvmlDeviceGetMemoryInfo_v2) FF_LIBRARY_LOAD_SYMBOL_VAR_MESSAGE(libnvml, nvmlData, nvmlDeviceGetMemoryInfo) FF_LIBRARY_LOAD_SYMBOL_VAR_MESSAGE(libnvml, nvmlData, nvmlDeviceGetNumGpuCores) FF_LIBRARY_LOAD_SYMBOL_VAR_MESSAGE(libnvml, nvmlData, nvmlDeviceGetMaxClockInfo) FF_LIBRARY_LOAD_SYMBOL_VAR_MESSAGE(libnvml, nvmlData, nvmlDeviceGetUtilizationRates) FF_LIBRARY_LOAD_SYMBOL_VAR_MESSAGE(libnvml, nvmlData, nvmlDeviceGetBrand) FF_LIBRARY_LOAD_SYMBOL_VAR_MESSAGE(libnvml, nvmlData, nvmlDeviceGetIndex) FF_LIBRARY_LOAD_SYMBOL_VAR_MESSAGE(libnvml, nvmlData, nvmlDeviceGetName) if (ffnvmlInit_v2() != NVML_SUCCESS) { nvmlData.ffnvmlDeviceGetNumGpuCores = NULL; return "nvmlInit_v2() failed"; } atexit((void*) ffnvmlShutdown); libnvml = NULL; // don't close nvml } if (nvmlData.ffnvmlDeviceGetNumGpuCores == NULL) return "loading nvml library failed"; nvmlDevice_t device = NULL; if (cond->type & FF_GPU_DRIVER_CONDITION_TYPE_BUS_ID) { char pciBusIdStr[32]; snprintf(pciBusIdStr, ARRAY_SIZE(pciBusIdStr), "%04x:%02x:%02x.%d", cond->pciBusId.domain, cond->pciBusId.bus, cond->pciBusId.device, cond->pciBusId.func); nvmlReturn_t ret = nvmlData.ffnvmlDeviceGetHandleByPciBusId_v2(pciBusIdStr, &device); if (ret != NVML_SUCCESS) return "nvmlDeviceGetHandleByPciBusId_v2() failed"; } else if (cond->type & FF_GPU_DRIVER_CONDITION_TYPE_DEVICE_ID) { uint32_t count; if (nvmlData.ffnvmlDeviceGetCount_v2(&count) != NVML_SUCCESS) return "nvmlDeviceGetCount_v2() failed"; for (uint32_t i = 0; i < count; i++, device = NULL) { if (nvmlData.ffnvmlDeviceGetHandleByIndex_v2(i, &device) != NVML_SUCCESS) continue; nvmlPciInfo_t pciInfo; if (nvmlData.ffnvmlDeviceGetPciInfo_v3(device, &pciInfo) != NVML_SUCCESS) continue; if (pciInfo.pciDeviceId != ((cond->pciDeviceId.deviceId << 16u) | cond->pciDeviceId.vendorId) || pciInfo.pciSubSystemId != cond->pciDeviceId.subSystemId) continue; break; } } if (!device) return "Device not found"; if (result.type) { nvmlBrandType_t brand; if (nvmlData.ffnvmlDeviceGetBrand(device, &brand) == NVML_SUCCESS) { switch (brand) { case NVML_BRAND_NVIDIA_RTX: case NVML_BRAND_QUADRO_RTX: case NVML_BRAND_GEFORCE: case NVML_BRAND_TITAN: case NVML_BRAND_TESLA: case NVML_BRAND_QUADRO: *result.type = FF_GPU_TYPE_DISCRETE; break; default: break; } } } if (result.index) { unsigned int value; if (nvmlData.ffnvmlDeviceGetIndex(device, &value) == NVML_SUCCESS) { *result.index = value; #ifdef _WIN32 // Don't bother loading nvapi for GPU type detection only if (result.memoryType) detectMoreByNvapi(&result); #endif } } if (result.temp) { uint32_t value; if (nvmlData.ffnvmlDeviceGetTemperature(device, NVML_TEMPERATURE_GPU, &value) == NVML_SUCCESS) *result.temp = value; } if (result.memory) { nvmlMemory_v2_t memory = { .version = nvmlMemory_v2 }; if (nvmlData.ffnvmlDeviceGetMemoryInfo_v2(device, &memory) == NVML_SUCCESS) { result.memory->total = memory.used + memory.free; result.memory->used = memory.used; } else { nvmlMemory_t memory_v1; if (nvmlData.ffnvmlDeviceGetMemoryInfo(device, &memory_v1) == NVML_SUCCESS) { result.memory->total = memory_v1.total; result.memory->used = memory_v1.used; } } } if (result.coreCount) nvmlData.ffnvmlDeviceGetNumGpuCores(device, result.coreCount); if (result.frequency) nvmlData.ffnvmlDeviceGetMaxClockInfo(device, NVML_CLOCK_GRAPHICS, result.frequency); if (result.coreUsage) { nvmlUtilization_t utilization; if (nvmlData.ffnvmlDeviceGetUtilizationRates(device, &utilization) == NVML_SUCCESS) *result.coreUsage = utilization.gpu; } if (result.name) { char name[NVML_DEVICE_NAME_V2_BUFFER_SIZE]; if (nvmlData.ffnvmlDeviceGetName(device, name, ARRAY_SIZE(name)) == NVML_SUCCESS) ffStrbufSetS(result.name, name); } return NULL; #else FF_UNUSED(cond, result, soName); return "dlopen is disabled"; #endif } ================================================ FILE: src/detection/gpu/gpu_obsd.c ================================================ #include "gpu.h" #include "common/io.h" #include #include #include #include #include #include static inline int pciReadConf(int fd, uint32_t bus, uint32_t device, uint32_t func, uint32_t reg, uint32_t* result) { struct pci_io bdfr = { .pi_sel = { .pc_bus = bus, .pc_dev = device, .pc_func = func, }, .pi_reg = reg, .pi_width = 4, }; if (ioctl(fd, PCIOCREAD, &bdfr) == -1) return -1; *result = bdfr.pi_data; return 0; } const char* ffDetectGPUImpl(FF_MAYBE_UNUSED const FFGPUOptions* options, FFlist* gpus) { char pciDevPath[] = "/dev/pci0"; FF_AUTO_CLOSE_FD int pcifd = open(pciDevPath, O_RDONLY | O_CLOEXEC); if (pcifd < 0) return "open(\"/dev/pci0\", O_RDONLY | O_CLOEXEC) failed"; for (uint32_t bus = 0; bus <= 255; bus++) { for (uint32_t dev = 0; dev < 32; dev++) { uint32_t maxfuncs = 0; for (uint32_t func = 0; func <= maxfuncs; func++) { uint32_t pciid, pciclass; if (pciReadConf(pcifd, bus, dev, func, PCI_ID_REG, &pciid) != 0) continue; if (PCI_VENDOR(pciid) == PCI_VENDOR_INVALID || PCI_VENDOR(pciid) == 0) continue; if (pciReadConf(pcifd, bus, dev, func, PCI_CLASS_REG, &pciclass) != 0) continue; if (func == 0) { // For some reason, pciReadConf returns success even for non-existing devices. // So we need to check for `PCI_VENDOR(pciid) == PCI_VENDOR_INVALID` above to filter them out. uint32_t bhlcr; if (pciReadConf(pcifd, bus, dev, 0, PCI_BHLC_REG, &bhlcr) != 0) continue; if (PCI_HDRTYPE_MULTIFN(bhlcr)) maxfuncs = 7; } if (PCI_CLASS(pciclass) != PCI_CLASS_DISPLAY) continue; if (func > 0 && PCI_SUBCLASS(pciclass) == PCI_SUBCLASS_DISPLAY_MISC) continue; // Likely an auxiliary display controller (#2034) FFGPUResult* gpu = (FFGPUResult*)ffListAdd(gpus); ffStrbufInitStatic(&gpu->vendor, ffGPUGetVendorString(PCI_VENDOR(pciid))); ffStrbufInit(&gpu->name); ffStrbufInit(&gpu->driver); ffStrbufInitS(&gpu->platformApi, "/dev/pci0"); ffStrbufInit(&gpu->memoryType); gpu->index = FF_GPU_INDEX_UNSET; gpu->temperature = FF_GPU_TEMP_UNSET; gpu->coreCount = FF_GPU_CORE_COUNT_UNSET; gpu->coreUsage = FF_GPU_CORE_USAGE_UNSET; gpu->type = FF_GPU_TYPE_UNKNOWN; gpu->dedicated.total = gpu->dedicated.used = gpu->shared.total = gpu->shared.used = FF_GPU_VMEM_SIZE_UNSET; gpu->deviceId = ffGPUPciAddr2Id(0, bus, dev, func); gpu->frequency = FF_GPU_FREQUENCY_UNSET; if (gpu->vendor.chars == FF_GPU_VENDOR_NAME_AMD) ffGPUQueryAmdGpuName(PCI_PRODUCT(pciid), PCI_REVISION(pciid), gpu); if (gpu->name.length == 0) ffGPUFillVendorAndName(PCI_SUBCLASS(pciclass), PCI_VENDOR(pciid), PCI_PRODUCT(pciid), gpu); } } } return NULL; } ================================================ FILE: src/detection/gpu/gpu_pci.c ================================================ #include "gpu.h" #include "common/io.h" #include "common/properties.h" #include "common/memrchr.h" #include #ifdef __FreeBSD__ #include #ifndef _PATH_LOCALBASE #define _PATH_LOCALBASE "/usr/local" #endif #elif __OpenBSD__ #define _PATH_LOCALBASE "/usr/local" #elif __NetBSD__ #define _PATH_LOCALBASE "/usr/pkg" #endif #if FF_HAVE_EMBEDDED_PCIIDS #include "fastfetch_pciids.c.inc" #endif #if FF_HAVE_EMBEDDED_AMDGPUIDS #include "fastfetch_amdgpuids.c.inc" #endif #define FF_STR_INDIR(x) #x #define FF_STR(x) FF_STR_INDIR(x) static const FFstrbuf* loadPciIds() { static FFstrbuf pciids; if (pciids.chars) return &pciids; ffStrbufInit(&pciids); #ifdef FF_CUSTOM_PCI_IDS_PATH ffReadFileBuffer(FF_STR(FF_CUSTOM_PCI_IDS_PATH), &pciids); #else // FF_CUSTOM_PCI_IDS_PATH #if __linux__ ffReadFileBuffer(FASTFETCH_TARGET_DIR_USR "/share/hwdata/pci.ids", &pciids); if (pciids.length == 0) { ffReadFileBuffer(FASTFETCH_TARGET_DIR_USR "/share/misc/pci.ids", &pciids); // debian? if (pciids.length == 0) ffReadFileBuffer(FASTFETCH_TARGET_DIR_USR "/local/share/hwdata/pci.ids", &pciids); } #elif __OpenBSD__ || __FreeBSD__ || __NetBSD__ ffReadFileBuffer(_PATH_LOCALBASE "/share/hwdata/pci.ids", &pciids); if (pciids.length == 0) ffReadFileBuffer(_PATH_LOCALBASE "/share/pciids/pci.ids", &pciids); #elif __sun ffReadFileBuffer(FASTFETCH_TARGET_DIR_ROOT "/usr/share/hwdata/pci.ids", &pciids); #elif __HAIKU__ ffReadFileBuffer(FASTFETCH_TARGET_DIR_ROOT "/system/data/hwdata/pci.ids", &pciids); #endif #endif // FF_CUSTOM_PCI_IDS_PATH return &pciids; } static void parsePciIdsFile(const FFstrbuf* content, uint8_t subclass, uint16_t vendor, uint16_t device, FFGPUResult* gpu) { if (content->length) { char buffer[32]; // Search for vendor uint32_t len = (uint32_t) snprintf(buffer, ARRAY_SIZE(buffer), "\n%04x ", vendor); char* start = (char*) memmem(content->chars, content->length, buffer, len); char* end = content->chars + content->length; if (start) { start += len; end = memchr(start, '\n', (uint32_t) (end - start)); if (!end) end = content->chars + content->length; if (!gpu->vendor.length) ffStrbufSetNS(&gpu->vendor, (uint32_t) (end - start), start); start = end; // point to '\n' of vendor end = start + 1; // point to start of devices // find the start of next vendor while (end[0] == '\t' || end[0] == '#') { end = strchr(end, '\n'); if (!end) { end = content->chars + content->length; break; } else end++; } // Search for device len = (uint32_t) snprintf(buffer, ARRAY_SIZE(buffer), "\n\t%04x ", device); start = memmem(start, (size_t) (end - start), buffer, len); if (start) { start += len; end = memchr(start, '\n', (uint32_t) (end - start)); if (!end) end = content->chars + content->length; char* closingBracket = end - 1; if (*closingBracket == ']') { char* openingBracket = memrchr(start, '[', (size_t) (closingBracket - start)); if (openingBracket) { openingBracket++; ffStrbufSetNS(&gpu->name, (uint32_t) (closingBracket - openingBracket), openingBracket); } } if (!gpu->name.length) ffStrbufSetNS(&gpu->name, (uint32_t) (end - start), start); } } } if (!gpu->name.length) { const char* subclassStr; switch (subclass) { case 0 /*PCI_CLASS_DISPLAY_VGA*/: subclassStr = " (VGA compatible)"; break; case 1 /*PCI_CLASS_DISPLAY_XGA*/: subclassStr = " (XGA compatible)"; break; case 2 /*PCI_CLASS_DISPLAY_3D*/: subclassStr = " (3D)"; break; default: subclassStr = ""; break; } ffStrbufSetF(&gpu->name, "%s Device %04X%s", gpu->vendor.length ? gpu->vendor.chars : "Unknown", device, subclassStr); } } #if FF_HAVE_EMBEDDED_PCIIDS static inline int pciDeviceCmp(const uint16_t* key, const FFPciDevice* element) { return (int) *key - (int) element->id; } static bool loadPciidsInc(uint8_t subclass, uint16_t vendor, uint16_t device, FFGPUResult* gpu) { for (const FFPciVendor* pvendor = ffPciVendors; pvendor->name; pvendor++) { if (pvendor->id != vendor) continue; if (!gpu->vendor.length) ffStrbufSetS(&gpu->vendor, pvendor->name); const FFPciDevice* pdevice = (const FFPciDevice*) bsearch(&device, pvendor->devices, pvendor->nDevices, sizeof(*pdevice), (void*) pciDeviceCmp); if (pdevice) { uint32_t nameLen = (uint32_t) strlen(pdevice->name); const char* closingBracket = pdevice->name + nameLen - 1; if (*closingBracket == ']') { const char* openingBracket = memrchr(pdevice->name, '[', nameLen - 1); if (openingBracket) { openingBracket++; ffStrbufSetNS(&gpu->name, (uint32_t) (closingBracket - openingBracket), openingBracket); } } if (!gpu->name.length) ffStrbufSetNS(&gpu->name, nameLen, pdevice->name); return true; } if (!gpu->name.length) { const char* subclassStr; switch (subclass) { case 0 /*PCI_CLASS_DISPLAY_VGA*/: subclassStr = " (VGA compatible)"; break; case 1 /*PCI_CLASS_DISPLAY_XGA*/: subclassStr = " (XGA compatible)"; break; case 2 /*PCI_CLASS_DISPLAY_3D*/: subclassStr = " (3D)"; break; default: subclassStr = ""; break; } ffStrbufSetF(&gpu->name, "%s Device %04X%s", gpu->vendor.length ? gpu->vendor.chars : "Unknown", device, subclassStr); } return true; } return false; } #endif void ffGPUFillVendorAndName(uint8_t subclass, uint16_t vendor, uint16_t device, FFGPUResult* gpu) { #if FF_HAVE_EMBEDDED_PCIIDS bool ok = loadPciidsInc(subclass, vendor, device, gpu); if (ok) return; #endif return parsePciIdsFile(loadPciIds(), subclass, vendor, device, gpu); } #if FF_HAVE_EMBEDDED_AMDGPUIDS static inline int amdGpuCmp(const uint32_t* key, const FFArmGpuProduct* element) { // Maximum value of *key is 0x00FFFFFF. `(int) *key` should never overflow return (int) *key - (int) element->id; } static bool loadAmdGpuIdsInc(uint16_t deviceId, uint8_t revision, FFGPUResult* gpu) { uint32_t key = (deviceId << 8u) | revision; FFArmGpuProduct* product = bsearch(&key, ffAmdGpuProducts, ARRAY_SIZE(ffAmdGpuProducts), sizeof(*ffAmdGpuProducts), (void*) amdGpuCmp); if (product) { ffStrbufSetS(&gpu->name, product->name); return true; } return false; } #endif static void parseAmdGpuIdsFile(uint16_t deviceId, uint8_t revision, FFGPUResult* gpu) { char query[32]; snprintf(query, ARRAY_SIZE(query), "%X,\t%X,", (unsigned) deviceId, (unsigned) revision); #ifdef FF_CUSTOM_AMDGPU_IDS_PATH ffParsePropFile(FF_STR(FF_CUSTOM_AMDGPU_IDS_PATH), query, &gpu->name); #else ffParsePropFileData("libdrm/amdgpu.ids", query, &gpu->name); #endif } void ffGPUQueryAmdGpuName(uint16_t deviceId, uint8_t revisionId, FFGPUResult* gpu) { #if FF_HAVE_EMBEDDED_AMDGPUIDS bool ok = loadAmdGpuIdsInc(deviceId, revisionId, gpu); if (ok) return; #endif return parseAmdGpuIdsFile(deviceId, revisionId, gpu); } ================================================ FILE: src/detection/gpu/gpu_sunos.c ================================================ #include "gpu.h" #include "common/stringUtils.h" #include static int walkDevTree(di_node_t node, FF_MAYBE_UNUSED di_minor_t minor, FFlist* gpus) { int* vendorId; int* deviceId; if (di_prop_lookup_ints(DDI_DEV_T_ANY, node, "vendor-id", &vendorId) > 0 && di_prop_lookup_ints(DDI_DEV_T_ANY, node, "device-id", &deviceId) > 0) { FFGPUResult* gpu = (FFGPUResult*)ffListAdd(gpus); ffStrbufInitS(&gpu->vendor, ffGPUGetVendorString((uint16_t) *vendorId)); ffStrbufInit(&gpu->name); ffStrbufInitS(&gpu->driver, di_driver_name(node)); ffStrbufInitStatic(&gpu->platformApi, "libdevinfo"); ffStrbufInit(&gpu->memoryType); gpu->index = FF_GPU_INDEX_UNSET; gpu->temperature = FF_GPU_TEMP_UNSET; gpu->coreCount = FF_GPU_CORE_COUNT_UNSET; gpu->coreUsage = FF_GPU_CORE_USAGE_UNSET; gpu->type = FF_GPU_TYPE_UNKNOWN; gpu->dedicated.total = gpu->dedicated.used = gpu->shared.total = gpu->shared.used = FF_GPU_VMEM_SIZE_UNSET; gpu->deviceId = strtoul(di_bus_addr(node), NULL, 16); gpu->frequency = FF_GPU_FREQUENCY_UNSET; if (gpu->vendor.chars == FF_GPU_VENDOR_NAME_AMD) { int* revId; if (di_prop_lookup_ints(DDI_DEV_T_ANY, node, "revision-id", &revId) > 0) ffGPUQueryAmdGpuName((uint16_t) *deviceId, (uint8_t) *revId, gpu); } if (gpu->name.length == 0) { uint8_t subclass = 0; // assume VGA int* classCode; if (di_prop_lookup_ints(DDI_DEV_T_ANY, node, "class-code", &classCode) > 0) subclass = (uint8_t) (*classCode & 0xFFFF); ffGPUFillVendorAndName(subclass, (uint16_t) *vendorId, (uint16_t) *deviceId, gpu); } } return DI_WALK_CONTINUE; } const char* ffDetectGPUImpl(FF_MAYBE_UNUSED const FFGPUOptions* options, FFlist* gpus) { di_node_t rootNode = di_init("/", DINFOCPYALL); if (rootNode == DI_NODE_NIL) return "di_init() failed"; di_walk_minor(rootNode, DDI_NT_DISPLAY, DI_WALK_CLDFIRST, gpus, (void*) walkDevTree); di_fini(rootNode); return NULL; } ================================================ FILE: src/detection/gpu/gpu_windows.c ================================================ #include "gpu.h" #include "detection/gpu/gpu_driver_specific.h" #include "common/windows/unicode.h" #include "common/windows/registry.h" #include "common/mallocHelper.h" #include "common/debug.h" #include "common/windows/nt.h" #include #include #define FF_EMPTY_GUID_STR L"{00000000-0000-0000-0000-000000000000}" enum { FF_GUID_STRLEN = sizeof(FF_EMPTY_GUID_STR) / sizeof(wchar_t) - 1 }; wchar_t regDirectxKey[] = L"SOFTWARE\\Microsoft\\DirectX\\" FF_EMPTY_GUID_STR; const uint32_t regDirectxKeyPrefixLength = (uint32_t) __builtin_strlen("SOFTWARE\\Microsoft\\DirectX\\"); wchar_t regDriverKey[] = L"SYSTEM\\CurrentControlSet\\Control\\Class\\" FF_EMPTY_GUID_STR L"\\0000"; const uint32_t regDriverKeyPrefixLength = (uint32_t) __builtin_strlen("SYSTEM\\CurrentControlSet\\Control\\Class\\"); #define GUID_DEVCLASS_DISPLAY_STRING L"{4d36e968-e325-11ce-bfc1-08002be10318}" // Found in static inline void wrapRegCloseKey(HKEY* phKey) { if(*phKey) RegCloseKey(*phKey); } #define FF_HKEY_AUTO_DESTROY __attribute__((__cleanup__(wrapRegCloseKey))) const char* ffDetectGPUImpl(FF_MAYBE_UNUSED const FFGPUOptions* options, FFlist* gpus) { FF_DEBUG("Starting GPU detection"); ULONG devIdListSize = 0; if (CM_Get_Device_ID_List_SizeW(&devIdListSize, GUID_DEVCLASS_DISPLAY_STRING, CM_GETIDLIST_FILTER_CLASS | CM_GETIDLIST_FILTER_PRESENT) != CR_SUCCESS || devIdListSize <= 1) { FF_DEBUG("No display devices found, list size: %lu", devIdListSize); return "No display devices found"; } FF_DEBUG("Found device ID list size: %lu", devIdListSize); FF_AUTO_FREE DEVINSTID_W devIdList = malloc(devIdListSize * sizeof(*devIdList)); if (CM_Get_Device_ID_ListW(GUID_DEVCLASS_DISPLAY_STRING, devIdList, devIdListSize, CM_GETIDLIST_FILTER_CLASS | CM_GETIDLIST_FILTER_PRESENT) != CR_SUCCESS) { FF_DEBUG("CM_Get_Device_ID_ListW failed"); return "CM_Get_Device_ID_ListW failed"; } FF_MAYBE_UNUSED int deviceCount = 0; for (wchar_t* devId = devIdList; *devId; devId += wcslen(devId) + 1) { FF_DEBUG("Processing device ID: %ls", devId); DEVINST devInst = 0; if (CM_Locate_DevNodeW(&devInst, devId, CM_LOCATE_DEVNODE_NORMAL) != CR_SUCCESS) { FF_DEBUG("Failed to get device instance ID or locate device node"); continue; } FF_DEBUG("Device instance ID: %lu", devInst); for (wchar_t* p = devId; *p; p++) { if (*p >= L'a' && *p <= L'z') *p -= L'a' - L'A'; } FFGPUResult* gpu = (FFGPUResult*)ffListAdd(gpus); deviceCount++; FF_DEBUG("Added GPU #%d to list", deviceCount); ffStrbufInit(&gpu->vendor); ffStrbufInit(&gpu->name); ffStrbufInit(&gpu->driver); ffStrbufInit(&gpu->memoryType); ffStrbufInitStatic(&gpu->platformApi, "CM API"); gpu->index = FF_GPU_INDEX_UNSET; gpu->temperature = FF_GPU_TEMP_UNSET; gpu->coreCount = FF_GPU_CORE_COUNT_UNSET; gpu->coreUsage = FF_GPU_CORE_USAGE_UNSET; gpu->type = FF_GPU_TYPE_UNKNOWN; gpu->dedicated.total = gpu->dedicated.used = gpu->shared.total = gpu->shared.used = FF_GPU_VMEM_SIZE_UNSET; gpu->deviceId = 0; gpu->frequency = FF_GPU_FREQUENCY_UNSET; unsigned vendorId = 0, deviceId = 0, subSystemId = 0, revId = 0; if (swscanf(devId, L"PCI\\VEN_%x&DEV_%x&SUBSYS_%x&REV_%x", &vendorId, &deviceId, &subSystemId, &revId) == 4) { FF_DEBUG("Parsed PCI IDs - Vendor: 0x%x, Device: 0x%x, SubSystem: 0x%x, Rev: 0x%x", vendorId, deviceId, subSystemId, revId); ffStrbufSetStatic(&gpu->vendor, ffGPUGetVendorString(vendorId)); } else { FF_DEBUG("Failed to parse PCI device information from instance ID"); } uint32_t pciBus = 0, pciAddr = 0, pciDev = 0, pciFunc = 0; ULONG pciBufLen = sizeof(pciBus); if (CM_Get_DevNode_Registry_PropertyW(devInst, CM_DRP_BUSNUMBER, NULL, &pciBus, &pciBufLen, 0) == CR_SUCCESS) { pciBufLen = sizeof(pciAddr); if (CM_Get_DevNode_Registry_PropertyW(devInst, CM_DRP_ADDRESS, NULL, &pciAddr, &pciBufLen, 0) == CR_SUCCESS) { pciDev = (pciAddr >> 16) & 0xFFFF; pciFunc = pciAddr & 0xFFFF; gpu->deviceId = ffGPUPciAddr2Id(0, pciBus, pciDev, pciFunc); pciAddr = 1; // Set to 1 to indicate that the device is a PCI device FF_DEBUG("PCI location - Bus: %u, Device: %u, Function: %u, DeviceID: %llu", pciBus, pciDev, pciFunc, gpu->deviceId); } else { FF_DEBUG("Failed to get PCI address"); } } else { FF_DEBUG("Failed to get PCI bus number"); } uint64_t adapterLuid = 0; FF_HKEY_AUTO_DESTROY HKEY hVideoIdKey = NULL; wchar_t buffer[256]; ULONG bufferLen = 0; FF_DEBUG("Get device description as device name"); bufferLen = sizeof(buffer); if (CM_Get_DevNode_Registry_PropertyW(devInst, CM_DRP_DEVICEDESC, NULL, buffer, &bufferLen, 0) == CR_SUCCESS) { ffStrbufSetWS(&gpu->name, buffer); FF_DEBUG("Found device description: %s", gpu->name.chars); } else { FF_DEBUG("Failed to get device description"); } if (wcsncmp(devId, L"SWD\\", 4) == 0 || wcsncmp(devId, L"ROOT\\DISPLAY\\", 13) == 0) { FF_DEBUG("Skipping virtual devices to avoid duplicates"); continue; } if (CM_Open_DevNode_Key(devInst, KEY_QUERY_VALUE, 0, RegDisposition_OpenExisting, &hVideoIdKey, CM_REGISTRY_HARDWARE) == CR_SUCCESS) { FF_DEBUG("Opened device node registry key"); bufferLen = sizeof(buffer); if (RegGetValueW(hVideoIdKey, NULL, L"VideoID", RRF_RT_REG_SZ, NULL, buffer, &bufferLen) == ERROR_SUCCESS && bufferLen == (FF_GUID_STRLEN + 1) * sizeof(wchar_t)) { FF_DEBUG("Found VideoID: %ls", buffer); wmemcpy(regDirectxKey + regDirectxKeyPrefixLength, buffer, FF_GUID_STRLEN); FF_AUTO_CLOSE_FD HANDLE hDirectxKey = NULL; if (ffRegOpenKeyForRead(HKEY_LOCAL_MACHINE, regDirectxKey, &hDirectxKey, NULL)) { FF_DEBUG("Opened DirectX registry key"); if (gpu->vendor.length == 0) { uint32_t vendorId = 0; if(ffRegReadUint(hDirectxKey, L"VendorId", &vendorId, NULL) && vendorId) { FF_DEBUG("Found vendor ID from DirectX registry: 0x%x", vendorId); ffStrbufSetStatic(&gpu->vendor, ffGPUGetVendorString(vendorId)); } } if (gpu->name.length == 0) { FF_DEBUG("Trying to get GPU name from DirectX registry"); if (ffRegReadStrbuf(hDirectxKey, L"Description", &gpu->name, NULL)) FF_DEBUG("Found GPU description: %s", gpu->name.chars); } if (ffRegReadUint64(hDirectxKey, L"DedicatedVideoMemory", &gpu->dedicated.total, NULL)) FF_DEBUG("Found dedicated video memory: %llu bytes", gpu->dedicated.total); if (ffRegReadUint64(hDirectxKey, L"DedicatedSystemMemory", &gpu->shared.total, NULL)) { FF_DEBUG("Found dedicated system memory: %llu bytes", gpu->shared.total); uint64_t sharedSystemMemory = 0; if (ffRegReadUint64(hDirectxKey, L"SharedSystemMemory", &sharedSystemMemory, NULL)) { gpu->shared.total += sharedSystemMemory; FF_DEBUG("Added shared system memory: %llu bytes, total shared: %llu bytes", sharedSystemMemory, gpu->shared.total); } } if (ffRegReadUint64(hDirectxKey, L"AdapterLuid", &adapterLuid, NULL)) { FF_DEBUG("Found adapter LUID: %llu", adapterLuid); if (!gpu->deviceId) gpu->deviceId = ffGPUGeneral2Id(adapterLuid); } uint32_t featureLevel = 0; if(ffRegReadUint(hDirectxKey, L"MaxD3D12FeatureLevel", &featureLevel, NULL) && featureLevel) { FF_DEBUG("Found D3D12 feature level: 0x%x", featureLevel); ffStrbufSetF(&gpu->platformApi, "Direct3D 12.%u", (featureLevel & 0x0F00) >> 8); } else if(ffRegReadUint(hDirectxKey, L"MaxD3D11FeatureLevel", &featureLevel, NULL) && featureLevel) { FF_DEBUG("Found D3D11 feature level: 0x%x", featureLevel); ffStrbufSetF(&gpu->platformApi, "Direct3D 11.%u", (featureLevel & 0x0F00) >> 8); } uint64_t driverVersion = 0; if(ffRegReadUint64(hDirectxKey, L"DriverVersion", &driverVersion, NULL) && driverVersion) { FF_DEBUG("Found driver version: %llu", driverVersion); ffStrbufSetF(&gpu->driver, "%u.%u.%u.%u", (unsigned) (driverVersion >> 48) & 0xFFFF, (unsigned) (driverVersion >> 32) & 0xFFFF, (unsigned) (driverVersion >> 16) & 0xFFFF, (unsigned) (driverVersion >> 0) & 0xFFFF ); } } else { FF_DEBUG("Failed to open DirectX registry key"); } } else { FF_DEBUG("Failed to get VideoID or invalid buffer length"); } } else { FF_DEBUG("Failed to open device node registry key"); } if (gpu->vendor.length == 0 || gpu->name.length == 0 || gpu->driver.length == 0 || gpu->dedicated.total == FF_GPU_VMEM_SIZE_UNSET) { FF_DEBUG("Trying fallback registry method for vendor/name etc."); bufferLen = sizeof(buffer); if (CM_Get_DevNode_Registry_PropertyW(devInst, CM_DRP_DRIVER, NULL, buffer, &bufferLen, 0) == CR_SUCCESS && bufferLen == (FF_GUID_STRLEN + strlen("\\0000") + 1) * 2) { FF_DEBUG("Found driver GUID: %ls", buffer); wmemcpy(regDriverKey + regDriverKeyPrefixLength, buffer, FF_GUID_STRLEN + strlen("\\0000")); FF_AUTO_CLOSE_FD HANDLE hRegDriverKey = NULL; if (ffRegOpenKeyForRead(HKEY_LOCAL_MACHINE, regDriverKey, &hRegDriverKey, NULL)) { FF_DEBUG("Opened driver registry key"); if (gpu->vendor.length == 0 && ffRegReadStrbuf(hRegDriverKey, L"ProviderName", &gpu->vendor, NULL)) { FF_DEBUG("Found provider name: %s", gpu->vendor.chars); if (ffStrbufContainS(&gpu->vendor, "Intel")) ffStrbufSetStatic(&gpu->vendor, FF_GPU_VENDOR_NAME_INTEL); else if (ffStrbufContainS(&gpu->vendor, "NVIDIA")) ffStrbufSetStatic(&gpu->vendor, FF_GPU_VENDOR_NAME_NVIDIA); else if (ffStrbufContainS(&gpu->vendor, "AMD") || ffStrbufContainS(&gpu->vendor, "ATI")) ffStrbufSetStatic(&gpu->vendor, FF_GPU_VENDOR_NAME_AMD); } if (gpu->name.length == 0 && ffRegReadStrbuf(hRegDriverKey, L"DriverDesc", &gpu->name, NULL)) FF_DEBUG("Found driver description: %s", gpu->name.chars); if (gpu->driver.length == 0 && ffRegReadStrbuf(hRegDriverKey, L"DriverVersion", &gpu->driver, NULL)) FF_DEBUG("Found driver version: %s", gpu->driver.chars); if (gpu->dedicated.total == FF_GPU_VMEM_SIZE_UNSET) { if (!ffRegReadUint64(hRegDriverKey, L"HardwareInformation.qwMemorySize", &gpu->dedicated.total, NULL)) { uint32_t memorySize = 0; if (ffRegReadUint(hRegDriverKey, L"HardwareInformation.MemorySize", &memorySize, NULL)) { gpu->dedicated.total = memorySize; FF_DEBUG("Found memory size from hardware info: %u bytes", memorySize); } } else { FF_DEBUG("Found qwMemorySize from hardware info: %llu bytes", gpu->dedicated.total); } } } else { FF_DEBUG("Failed to open driver registry key"); } } else { FF_DEBUG("Failed to get driver GUID or invalid buffer length"); } } __typeof__(&ffDetectNvidiaGpuInfo) detectFn; const char* dllName; if (options->driverSpecific && getDriverSpecificDetectionFn(gpu->vendor.chars, &detectFn, &dllName)) { FF_DEBUG("Calling driver-specific detection function for vendor: %s, DLL: %s", gpu->vendor.chars, dllName); detectFn( &(FFGpuDriverCondition) { .type = (deviceId > 0 ? FF_GPU_DRIVER_CONDITION_TYPE_DEVICE_ID : 0) | (adapterLuid > 0 ? FF_GPU_DRIVER_CONDITION_TYPE_LUID : 0) | (pciAddr > 0 ? FF_GPU_DRIVER_CONDITION_TYPE_BUS_ID : 0), .pciDeviceId = { .deviceId = deviceId, .vendorId = vendorId, .subSystemId = subSystemId, .revId = revId, }, .pciBusId = { .domain = 0, .bus = pciBus, .device = pciDev, .func = pciFunc, }, .luid = adapterLuid, }, (FFGpuDriverResult){ .index = &gpu->index, .temp = options->temp ? &gpu->temperature : NULL, .memory = options->driverSpecific ? &gpu->dedicated : NULL, .sharedMemory = options->driverSpecific ? &gpu->shared : NULL, .memoryType = options->driverSpecific ? &gpu->memoryType : NULL, .coreCount = options->driverSpecific ? (uint32_t*) &gpu->coreCount : NULL, .coreUsage = options->driverSpecific ? &gpu->coreUsage : NULL, .type = &gpu->type, .name = &gpu->name, .frequency = options->driverSpecific ? &gpu->frequency : NULL, }, dllName ); FF_DEBUG("Driver-specific detection completed"); } else if (options->driverSpecific) { FF_DEBUG("No driver-specific detection function found for vendor: %s", gpu->vendor.chars); } if (gpu->type == FF_GPU_TYPE_UNKNOWN && adapterLuid > 0) { FF_DEBUG("Trying to determine GPU type using D3DKMT APIs"); D3DKMT_OPENADAPTERFROMLUID openAdapterFromLuid = { .AdapterLuid = *(LUID*)&adapterLuid }; if (NT_SUCCESS(D3DKMTOpenAdapterFromLuid(&openAdapterFromLuid))) { FF_DEBUG("Successfully opened adapter from LUID"); D3DKMT_ADAPTERTYPE adapterType = {}; D3DKMT_QUERYADAPTERINFO queryAdapterInfo = { .hAdapter = openAdapterFromLuid.hAdapter, .Type = KMTQAITYPE_ADAPTERTYPE, // Windows 8 and later .pPrivateDriverData = &adapterType, .PrivateDriverDataSize = sizeof(adapterType), }; if (NT_SUCCESS(D3DKMTQueryAdapterInfo(&queryAdapterInfo))) { FF_DEBUG("Queried adapter type - HybridDiscrete: %d, HybridIntegrated: %d", adapterType.HybridDiscrete, adapterType.HybridIntegrated); if (adapterType.HybridDiscrete) gpu->type = FF_GPU_TYPE_DISCRETE; else if (adapterType.HybridIntegrated) gpu->type = FF_GPU_TYPE_INTEGRATED; } else { FF_DEBUG("Failed to query adapter type"); } if (gpu->frequency == FF_GPU_FREQUENCY_UNSET && ffIsWindows11OrGreater()) { FF_DEBUG("Trying to get GPU frequency information"); for (ULONG nodeIdx = 0; ; nodeIdx++) { D3DKMT_NODEMETADATA nodeMetadata = { .NodeOrdinalAndAdapterIndex = (0 << 16) | nodeIdx, }; queryAdapterInfo = (D3DKMT_QUERYADAPTERINFO) { .hAdapter = openAdapterFromLuid.hAdapter, .Type = KMTQAITYPE_NODEMETADATA, // Windows 10 and later .pPrivateDriverData = &nodeMetadata, .PrivateDriverDataSize = sizeof(nodeMetadata), }; if (!NT_SUCCESS(D3DKMTQueryAdapterInfo(&queryAdapterInfo))) { FF_DEBUG("No more nodes to query (index %lu)", nodeIdx); break; } if (nodeMetadata.NodeData.EngineType != DXGK_ENGINE_TYPE_3D) { FF_DEBUG("Skipping node %lu (not 3D engine)", nodeIdx); continue; } D3DKMT_QUERYSTATISTICS queryStatistics = { .Type = D3DKMT_QUERYSTATISTICS_NODE2, // Windows 11 (22H2) and later .AdapterLuid = *(LUID*)&adapterLuid, .QueryNode2 = { .PhysicalAdapterIndex = 0, .NodeOrdinal = (UINT16) nodeIdx }, }; if (NT_SUCCESS(D3DKMTQueryStatistics(&queryStatistics))) { gpu->frequency = (uint32_t) (queryStatistics.QueryResult.NodeInformation.NodePerfData.MaxFrequency / 1000 / 1000); FF_DEBUG("Found GPU frequency: %u MHz", gpu->frequency); break; } else { FF_DEBUG("Failed to query node statistics for node %lu", nodeIdx); } } } D3DKMT_CLOSEADAPTER closeAdapter = { .hAdapter = openAdapterFromLuid.hAdapter }; (void) D3DKMTCloseAdapter(&closeAdapter); openAdapterFromLuid.hAdapter = 0; FF_DEBUG("Closed adapter handle"); } else { FF_DEBUG("Failed to open adapter from LUID"); } if (options->temp && gpu->temperature == FF_GPU_TEMP_UNSET && ffIsWindows10OrGreater()) { FF_DEBUG("Trying to get GPU temperature"); D3DKMT_QUERYSTATISTICS queryStatistics = { .Type = D3DKMT_QUERYSTATISTICS_PHYSICAL_ADAPTER, // Windows 10 (1803) and later .AdapterLuid = *(LUID*)&adapterLuid, .QueryPhysAdapter = { .PhysicalAdapterIndex = 0 }, }; if (NT_SUCCESS(D3DKMTQueryStatistics(&queryStatistics)) && queryStatistics.QueryResult.PhysAdapterInformation.AdapterPerfData.Temperature != 0) { gpu->temperature = queryStatistics.QueryResult.PhysAdapterInformation.AdapterPerfData.Temperature / 10.0; FF_DEBUG("Found GPU temperature: %.1f°C", gpu->temperature); } else { FF_DEBUG("Failed to get GPU temperature or temperature is 0"); } } } if (gpu->type == FF_GPU_TYPE_UNKNOWN) { FF_DEBUG("Using fallback GPU type detection"); if (gpu->vendor.chars == FF_GPU_VENDOR_NAME_INTEL) { gpu->type = gpu->deviceId == ffGPUPciAddr2Id(0, 0, 2, 0) ? FF_GPU_TYPE_INTEGRATED : FF_GPU_TYPE_DISCRETE; FF_DEBUG("Intel GPU type determined: %s", gpu->type == FF_GPU_TYPE_INTEGRATED ? "Integrated" : "Discrete"); } else if (gpu->dedicated.total != FF_GPU_VMEM_SIZE_UNSET) { gpu->type = gpu->dedicated.total >= 1024 * 1024 * 1024 ? FF_GPU_TYPE_DISCRETE : FF_GPU_TYPE_INTEGRATED; FF_DEBUG("GPU type determined by memory size (%llu bytes): %s", gpu->dedicated.total, gpu->type == FF_GPU_TYPE_DISCRETE ? "Discrete" : "Integrated"); } else { FF_DEBUG("Unable to determine GPU type"); } } FF_DEBUG("Completed processing GPU #%d - Vendor: %s, Name: %s, Type: %d", deviceCount, gpu->vendor.chars, gpu->name.chars, gpu->type); } FF_DEBUG("GPU detection completed, found %d devices", deviceCount); return NULL; } ================================================ FILE: src/detection/gpu/gpu_wsl.cpp ================================================ #ifdef FF_HAVE_DIRECTX_HEADERS #define INITGUID extern "C" { #include "common/library.h" #include "detection/gpu/gpu.h" #include "detection/gpu/gpu_driver_specific.h" } #include "common/windows/util.hpp" #include #include #include #include #include #include #pragma GCC diagnostic ignored "-Wmissing-field-initializers" extern "C" const char* ffGPUDetectByDirectX(FF_MAYBE_UNUSED const FFGPUOptions* options, FFlist* gpus) { FF_LIBRARY_LOAD_MESSAGE(libdxcore, "/usr/lib/wsl/lib/libdxcore" FF_LIBRARY_EXTENSION, 4) // DXCoreCreateAdapterFactory is a reloaded function, so we can't use FF_LIBRARY_LOAD_SYMBOL_MESSAGE here typedef HRESULT (*DXCoreCreateAdapterFactory_t)(REFIID riid, void** ppvFactory); #ifndef FF_DISABLE_DLOPEN auto ffDXCoreCreateAdapterFactory = (DXCoreCreateAdapterFactory_t) dlsym(libdxcore, "DXCoreCreateAdapterFactory"); if(ffDXCoreCreateAdapterFactory == nullptr) return "dlsym DXCoreCreateAdapterFactory failed"; #else auto ffDXCoreCreateAdapterFactory = (DXCoreCreateAdapterFactory_t) DXCoreCreateAdapterFactory; #endif IDXCoreAdapterFactory *factory = nullptr; if (FAILED(ffDXCoreCreateAdapterFactory(IID_PPV_ARGS(&factory)))) return "DXCoreCreateAdapterFactory(IID_PPV_ARGS(&factory)) failed"; on_scope_exit destroyFactory([&] { factory->Release(); }); IDXCoreAdapterList *list = NULL; if (FAILED(factory->CreateAdapterList(1, &DXCORE_ADAPTER_ATTRIBUTE_D3D11_GRAPHICS, &list))) return "factory->CreateAdapterList(1, &DXCORE_ADAPTER_ATTRIBUTE_D3D11_GRAPHICS, &list) failed"; on_scope_exit destroyList([&] { list->Release(); }); for (uint32_t i = 0, count = list->GetAdapterCount(); i < count; i++) { IDXCoreAdapter *adapter = nullptr; if (FAILED(list->GetAdapter(i, &adapter))) continue; on_scope_exit destroyAdapter([&] { adapter->Release(); }); // https://learn.microsoft.com/en-us/windows/win32/api/dxcore_interface/ne-dxcore_interface-dxcoreadapterproperty { bool value = 0; if (SUCCEEDED(adapter->GetProperty(DXCoreAdapterProperty::IsHardware, sizeof(value), &value)) && !value) continue; } char desc[256]; if (FAILED(adapter->GetProperty(DXCoreAdapterProperty::DriverDescription, sizeof(desc), desc))) continue; FFGPUResult* gpu = (FFGPUResult*) ffListAdd(gpus); ffStrbufInitS(&gpu->name, desc); ffStrbufInit(&gpu->memoryType); gpu->index = FF_GPU_INDEX_UNSET; gpu->coreCount = FF_GPU_CORE_COUNT_UNSET; gpu->coreUsage = FF_GPU_CORE_USAGE_UNSET; gpu->temperature = FF_GPU_TEMP_UNSET; gpu->frequency = FF_GPU_FREQUENCY_UNSET; gpu->deviceId = 0; ffStrbufInitStatic(&gpu->platformApi, "DXCore"); LUID luid; if (SUCCEEDED(adapter->GetProperty(DXCoreAdapterProperty::InstanceLuid, sizeof(luid), &luid))) { static_assert(sizeof(luid) == sizeof(uint64_t), "LUID size mismatch"); gpu->deviceId = ffGPUGeneral2Id((uint64_t) luid.HighPart << 32 | (uint64_t) luid.LowPart); } ffStrbufInit(&gpu->driver); uint64_t value = 0; if (SUCCEEDED(adapter->GetProperty(DXCoreAdapterProperty::DriverVersion, sizeof(value), &value))) { ffStrbufSetF(&gpu->driver, "%" PRIu64 ".%" PRIu64 ".%" PRIu64 ".%" PRIu64, (value >> 48) & 0xFFFF, (value >> 32) & 0xFFFF, (value >> 16) & 0xFFFF, (value >> 0) & 0xFFFF); } gpu->dedicated.used = gpu->shared.used = gpu->dedicated.total = gpu->shared.total = FF_GPU_VMEM_SIZE_UNSET; if (SUCCEEDED(adapter->GetProperty(DXCoreAdapterProperty::DedicatedAdapterMemory, sizeof(value), &value))) gpu->dedicated.total = value; if (SUCCEEDED(adapter->GetProperty(DXCoreAdapterProperty::SharedSystemMemory, sizeof(value), &value))) gpu->shared.total = value; gpu->type = FF_GPU_TYPE_UNKNOWN; bool integrated; if (SUCCEEDED(adapter->GetProperty(DXCoreAdapterProperty::IsIntegrated, sizeof(integrated), &integrated))) gpu->type = integrated ? FF_GPU_TYPE_INTEGRATED : FF_GPU_TYPE_DISCRETE; ffStrbufInit(&gpu->vendor); DXCoreHardwareID hardwareId; if (SUCCEEDED(adapter->GetProperty(DXCoreAdapterProperty::HardwareID, sizeof(hardwareId), &hardwareId))) { const char* vendorStr = ffGPUGetVendorString((unsigned) hardwareId.vendorID); ffStrbufSetStatic(&gpu->vendor, vendorStr); if (vendorStr == FF_GPU_VENDOR_NAME_NVIDIA && (options->driverSpecific || options->temp)) { FFGpuDriverCondition cond = { .type = FF_GPU_DRIVER_CONDITION_TYPE_DEVICE_ID, .pciDeviceId = { .deviceId = hardwareId.deviceID, .vendorId = hardwareId.vendorID, .subSystemId = hardwareId.subSysID, .revId = hardwareId.revision, }, }; ffDetectNvidiaGpuInfo(&cond, (FFGpuDriverResult){ .index = &gpu->index, .temp = options->temp ? &gpu->temperature : NULL, .memory = options->driverSpecific ? &gpu->dedicated : NULL, .coreCount = options->driverSpecific ? (uint32_t*) &gpu->coreCount : NULL, .coreUsage = options->driverSpecific ? &gpu->coreUsage : NULL, .type = &gpu->type, .frequency = options->driverSpecific ? &gpu->frequency : NULL, .name = options->driverSpecific ? &gpu->name : NULL, }, "/usr/lib/wsl/lib/libnvidia-ml.so"); } } } return NULL; } #endif ================================================ FILE: src/detection/gpu/igcl.h ================================================ #pragma once // DISCLAIMER: // THIS FILE IS CREATED FROM SCRATCH, BY READING THE OFFICIAL IGCL API // DOCUMENTATION REFERENCED BELOW, IN ORDER TO MAKE FASTFETCH MIT COMPLIANT. #include // https://intel.github.io/drivers.gpu.control-library/Control/api.html#_CPPv412ctl_result_t typedef enum ctl_result_t { CTL_RESULT_SUCCESS = 0, } ctl_result_t; // https://intel.github.io/drivers.gpu.control-library/Control/api.html#_CPPv420ctl_application_id_t typedef struct ctl_application_id_t { uint32_t Data1; uint16_t Data2; uint16_t Data3; uint8_t Data4[8]; } ctl_application_id_t; #define CTL_IMPL_VERSION (( 1 /*major*/ << 16 )|( 1 /*minor*/ & 0x0000ffff)) // https://intel.github.io/drivers.gpu.control-library/Control/api.html#_CPPv415ctl_init_flag_t typedef enum ctl_init_flag_t { CTL_INIT_FLAG_USE_LEVEL_ZERO = 1, CTL_INIT_FLAG_MAX } ctl_init_flag_t; typedef uint32_t ctl_version_info_t; // https://intel.github.io/drivers.gpu.control-library/Control/api.html#_CPPv415ctl_init_args_t typedef struct ctl_init_args_t { uint32_t Size; uint8_t Version; ctl_version_info_t AppVersion; ctl_init_flag_t flags; ctl_version_info_t SupportedVersion; ctl_application_id_t ApplicationUID; } ctl_init_args_t; typedef struct ctl_api_handle_t* ctl_api_handle_t; // https://intel.github.io/drivers.gpu.control-library/Control/api.html#_CPPv47ctlInitP15ctl_init_args_tP16ctl_api_handle_t extern ctl_result_t ctlInit(ctl_init_args_t *pInitDesc, ctl_api_handle_t *phAPIHandle); // https://intel.github.io/drivers.gpu.control-library/Control/api.html#ctlclose extern ctl_result_t ctlClose(ctl_api_handle_t hAPIHandle); typedef struct ctl_device_adapter_handle_t* ctl_device_adapter_handle_t; // https://intel.github.io/drivers.gpu.control-library/Control/api.html#_CPPv419ctlEnumerateDevices16ctl_api_handle_tP8uint32_tP27ctl_device_adapter_handle_t extern ctl_result_t ctlEnumerateDevices(ctl_api_handle_t hAPIHandle, uint32_t *pCount, ctl_device_adapter_handle_t* phDevices); // https://intel.github.io/drivers.gpu.control-library/Control/api.html#_CPPv417ctl_device_type_t typedef enum ctl_device_type_t { CTL_DEVICE_TYPE_GRAPHICS = 1, CTL_DEVICE_TYPE_SYSTEM = 2, CTL_DEVICE_TYPE_MAX } ctl_device_type_t; // https://intel.github.io/drivers.gpu.control-library/Control/api.html#_CPPv422ctl_firmware_version_t typedef struct ctl_firmware_version_t { uint64_t major_version; uint64_t minor_version; uint64_t build_number; } ctl_firmware_version_t; // https://intel.github.io/drivers.gpu.control-library/Control/api.html#_CPPv417ctl_adapter_bdf_t typedef struct ctl_adapter_bdf_t { uint8_t bus; uint8_t device; uint8_t function; } ctl_adapter_bdf_t; #define IGCL_CTL_MAX_DEVICE_NAME_LEN 100 #define IGCL_CTL_MAX_RESERVED_SIZE 112 typedef enum ctl_adapter_properties_flag_t { CTL_ADAPTER_PROPERTIES_FLAG_INTEGRATED = 1, } ctl_adapter_properties_flag_t; // https://intel.github.io/drivers.gpu.control-library/Control/api.html#_CPPv431ctl_device_adapter_properties_t typedef struct ctl_device_adapter_properties_t { uint32_t Size; uint8_t Version; void* pDeviceID; uint32_t device_id_size; ctl_device_type_t device_type; uint32_t /*ctl_supported_functions_flags_t*/ supported_subfunction_flags; uint64_t driver_version; ctl_firmware_version_t firmware_version; uint32_t pci_vendor_id; uint32_t pci_device_id; uint32_t rev_id; uint32_t num_eus_per_sub_slice; uint32_t num_sub_slices_per_slice; uint32_t num_slices; char name[IGCL_CTL_MAX_DEVICE_NAME_LEN]; ctl_adapter_properties_flag_t graphics_adapter_properties; uint32_t Frequency; uint16_t pci_subsys_id; uint16_t pci_subsys_vendor_id; ctl_adapter_bdf_t adapter_bdf; char reserved[IGCL_CTL_MAX_RESERVED_SIZE]; } ctl_device_adapter_properties_t; // https://intel.github.io/drivers.gpu.control-library/Control/api.html#_CPPv422ctlGetDeviceProperties27ctl_device_adapter_handle_tP31ctl_device_adapter_properties_t extern ctl_result_t ctlGetDeviceProperties(ctl_device_adapter_handle_t hDAhandle, ctl_device_adapter_properties_t* pProperties); typedef struct ctl_temp_handle_t* ctl_temp_handle_t; // https://intel.github.io/drivers.gpu.control-library/Control/api.html#ctlenumtemperaturesensors extern ctl_result_t ctlEnumTemperatureSensors(ctl_device_adapter_handle_t hDAhandle, uint32_t* pCount, ctl_temp_handle_t* phTemperature); // https://intel.github.io/drivers.gpu.control-library/Control/api.html#_CPPv427ctlTemperatureGetProperties17ctl_temp_handle_tP21ctl_temp_properties_t typedef enum ctl_temp_sensors_t { CTL_TEMP_SENSORS_GLOBAL = 0, CTL_TEMP_SENSORS_GPU = 1, CTL_TEMP_SENSORS_MEMORY = 2, CTL_TEMP_SENSORS_GLOBAL_MIN = 3, CTL_TEMP_SENSORS_GPU_MIN = 4, CTL_TEMP_SENSORS_MEMORY_MIN = 5, CTL_TEMP_SENSORS_MAX } ctl_temp_sensors_t; typedef struct _ctl_temp_properties_t { uint32_t Size; uint8_t Version; ctl_temp_sensors_t type; double maxTemperature; } ctl_temp_properties_t; extern ctl_result_t ctlTemperatureGetProperties(ctl_temp_handle_t hTemperature, ctl_temp_properties_t* pTemperature); // https://intel.github.io/drivers.gpu.control-library/Control/api.html#_CPPv420ctlEnumMemoryModules27ctl_device_adapter_handle_tP8uint32_tP16ctl_mem_handle_t typedef struct ctl_mem_handle_t* ctl_mem_handle_t; // https://intel.github.io/drivers.gpu.control-library/Control/api.html#_CPPv420ctlEnumMemoryModules27ctl_device_adapter_handle_tP8uint32_tP16ctl_mem_handle_t extern ctl_result_t ctlEnumMemoryModules(ctl_device_adapter_handle_t hDAhandle, uint32_t *pCount, ctl_mem_handle_t* phMemory); // https://intel.github.io/drivers.gpu.control-library/Control/api.html#_CPPv415ctl_mem_state_t typedef struct ctl_mem_state_t { uint32_t Size; uint8_t Version; uint64_t free; uint64_t size; } ctl_mem_state_t; // https://intel.github.io/drivers.gpu.control-library/Control/api.html#_CPPv417ctlMemoryGetState16ctl_mem_handle_tP15ctl_mem_state_t extern ctl_result_t ctlMemoryGetState(ctl_mem_handle_t hMemory, ctl_mem_state_t *pState); // https://intel.github.io/drivers.gpu.control-library/Control/api.html#_CPPv414ctl_mem_type_t typedef enum ctl_mem_type_t { CTL_MEM_TYPE_HBM = 0, CTL_MEM_TYPE_DDR = 1, CTL_MEM_TYPE_DDR3 = 2, CTL_MEM_TYPE_DDR4 = 3, CTL_MEM_TYPE_DDR5 = 4, CTL_MEM_TYPE_LPDDR = 5, CTL_MEM_TYPE_LPDDR3 = 6, CTL_MEM_TYPE_LPDDR4 = 7, CTL_MEM_TYPE_LPDDR5 = 8, CTL_MEM_TYPE_GDDR4 = 9, CTL_MEM_TYPE_GDDR5 = 10, CTL_MEM_TYPE_GDDR5X = 11, CTL_MEM_TYPE_GDDR6 = 12, CTL_MEM_TYPE_GDDR6X = 13, CTL_MEM_TYPE_GDDR7 = 14, CTL_MEM_TYPE_MAX } ctl_mem_type_t; // https://intel.github.io/drivers.gpu.control-library/Control/api.html#_CPPv413ctl_mem_loc_t typedef enum ctl_mem_loc_t { CTL_MEM_LOC_SYSTEM = 0, CTL_MEM_LOC_DEVICE = 1, CTL_MEM_LOC_MAX } ctl_mem_loc_t; // https://intel.github.io/drivers.gpu.control-library/Control/api.html#_CPPv420ctl_mem_properties_t typedef struct ctl_mem_properties_t { uint32_t Size; uint8_t Version; ctl_mem_type_t type; ctl_mem_loc_t location; uint64_t physicalSize; int32_t busWidth; int32_t numChannels; } ctl_mem_properties_t; // https://intel.github.io/drivers.gpu.control-library/Control/api.html#_CPPv422ctlMemoryGetProperties16ctl_mem_handle_tP20ctl_mem_properties_t extern ctl_result_t ctlMemoryGetProperties(ctl_mem_handle_t hMemory, ctl_mem_properties_t *pProperties); typedef struct ctl_freq_handle_t* ctl_freq_handle_t; // https://intel.github.io/drivers.gpu.control-library/Control/api.html#ctlenumfrequencydomains extern ctl_result_t ctlEnumFrequencyDomains(ctl_device_adapter_handle_t hDAhandle, uint32_t* pCount, ctl_freq_handle_t* phFrequency); // https://intel.github.io/drivers.gpu.control-library/Control/api.html#_CPPv417ctl_freq_domain_t typedef enum ctl_freq_domain_t { CTL_FREQ_DOMAIN_GPU = 0, CTL_FREQ_DOMAIN_MEMORY = 1, CTL_FREQ_DOMAIN_MAX } ctl_freq_domain_t; // https://intel.github.io/drivers.gpu.control-library/Control/api.html#_CPPv421ctl_freq_properties_t typedef struct ctl_freq_properties_t { uint32_t Size; uint8_t Version; ctl_freq_domain_t type; bool canControl; double min; double max; } ctl_freq_properties_t; // https://intel.github.io/drivers.gpu.control-library/Control/api.html#_CPPv425ctlFrequencyGetProperties17ctl_freq_handle_tP21ctl_freq_properties_t extern ctl_result_t ctlFrequencyGetProperties(ctl_freq_handle_t hFrequency, ctl_freq_properties_t* pProperties); ================================================ FILE: src/detection/gpu/intel_drm.h ================================================ #pragma once /* SPDX-License-Identifier: MIT */ #include // xe_drm.h /* * Copyright © 2023 Intel Corporation */ #define DRM_XE_DEVICE_QUERY 0x00 #define DRM_IOCTL_XE_DEVICE_QUERY DRM_IOWR(DRM_COMMAND_BASE + DRM_XE_DEVICE_QUERY, struct drm_xe_device_query) enum drm_xe_memory_class { DRM_XE_MEM_REGION_CLASS_SYSMEM = 0, DRM_XE_MEM_REGION_CLASS_VRAM }; struct drm_xe_mem_region { __u16 mem_class; __u16 instance; __u32 min_page_size; __u64 total_size; __u64 used; __u64 cpu_visible_size; __u64 cpu_visible_used; __u64 reserved[6]; }; struct drm_xe_query_mem_regions { __u32 num_mem_regions; __u32 pad; struct drm_xe_mem_region mem_regions[]; }; struct drm_xe_query_topology_mask { __u16 gt_id; #define DRM_XE_TOPO_DSS_GEOMETRY 1 #define DRM_XE_TOPO_DSS_COMPUTE 2 #define DRM_XE_TOPO_EU_PER_DSS 4 __u16 type; __u32 num_bytes; __u8 mask[]; }; struct drm_xe_device_query { __u64 extensions; #define DRM_XE_DEVICE_QUERY_MEM_REGIONS 1 #define DRM_XE_DEVICE_QUERY_GT_TOPOLOGY 5 __u32 query; __u32 size; __u64 data; __u64 reserved[2]; }; // i915_drm.h /* * Copyright 2003 Tungsten Graphics, Inc., Cedar Park, Texas. * All Rights Reserved. */ #define DRM_IOCTL_I915_GETPARAM DRM_IOWR(DRM_COMMAND_BASE + DRM_I915_GETPARAM, drm_i915_getparam_t) struct drm_i915_getparam { __s32 param; int *value; }; typedef struct drm_i915_getparam drm_i915_getparam_t; #define DRM_I915_GETPARAM 0x06 #define DRM_I915_QUERY 0x39 #define DRM_I915_QUERY_MEMORY_REGIONS 4 #define DRM_IOCTL_I915_QUERY DRM_IOWR(DRM_COMMAND_BASE + DRM_I915_QUERY, struct drm_i915_query) #define I915_PARAM_EU_TOTAL 34 struct drm_i915_query_item { __u64 query_id; #define DRM_I915_QUERY_MEMORY_REGIONS 4 __s32 length; __u32 flags; __u64 data_ptr; }; struct drm_i915_query { __u32 num_items; __u32 flags; __u64 items_ptr; }; enum drm_i915_gem_memory_class { I915_MEMORY_CLASS_SYSTEM = 0, I915_MEMORY_CLASS_DEVICE, }; struct drm_i915_gem_memory_class_instance { __u16 memory_class; __u16 memory_instance; }; struct drm_i915_memory_region_info { struct drm_i915_gem_memory_class_instance region; __u32 rsvd0; __u64 probed_size; __u64 unallocated_size; union { __u64 rsvd1[8]; struct { __u64 probed_cpu_visible_size; __u64 unallocated_cpu_visible_size; }; }; }; struct drm_i915_query_memory_regions { __u32 num_regions; __u32 rsvd[3]; struct drm_i915_memory_region_info regions[]; }; ================================================ FILE: src/detection/gpu/mtml.h ================================================ #pragma once // DISCLAIMER: // THIS FILE IS CREATED FROM SCRATCH, BY READING THE OFFICIAL MTML API // DOCUMENTATION REFERENCED BELOW, IN ORDER TO MAKE FASTFETCH MIT COMPLIANT. #define MTML_DEVICE_PCI_SBDF_BUFFER_SIZE 32 #define MTML_DEVICE_NAME_BUFFER_SIZE 32 /** * Return values for MTML API calls. */ typedef enum { MTML_SUCCESS = 0, } MtmlReturn; /** * The brand of the device. */ typedef enum { MTML_BRAND_MTT = 0, //!< MTT series. } MtmlBrandType; typedef struct MtmlLibrary MtmlLibrary; typedef struct MtmlSystem MtmlSystem; typedef struct MtmlDevice MtmlDevice; typedef struct MtmlGpu MtmlGpu; typedef struct MtmlMemory MtmlMemory; /** * PCI information about a device. */ typedef struct { char sbdf[MTML_DEVICE_PCI_SBDF_BUFFER_SIZE]; //!< The tuple segment:bus:device.function PCI identifier (& NULL terminator). unsigned int segment; //!< The PCI segment group(domain) on which the device's bus resides, 0 to 0xffffffff. unsigned int bus; //!< The bus on which the device resides, 0 to 0xff. unsigned int device; //!< The device ID on the bus, 0 to 31. unsigned int pciDeviceId; //!< The combined 16-bit device ID and 16-bit vendor ID. unsigned int pciSubsystemId; //!< The 32-bit sub system device ID. unsigned int busWidth; //!< @deprecated This value set to zero. float pciMaxSpeed; //!< The maximum link speed (transfer rate per lane) of the device. The unit is GT/s. float pciCurSpeed; //!< The current link speed (transfer rate per lane) of the device. The unit is GT/s. unsigned int pciMaxWidth; //!< The maximum link width of the device. unsigned int pciCurWidth; //!< The current link width of the device. unsigned int pciMaxGen; //!< The maximum supported generation of the device. unsigned int pciCurGen; //!< The current generation of the device. int rsvd[6]; //!< Reserved for future extension. } MtmlPciInfo; // Retrieves the number of cores of a device. MtmlReturn mtmlDeviceCountGpuCores(const MtmlDevice* device, unsigned int* numCores); // Retrieves the brand of a device. MtmlReturn mtmlDeviceGetBrand(const MtmlDevice *dev, MtmlBrandType *type); // Retrieves the index associated with the specified device. MtmlReturn mtmlDeviceGetIndex(const MtmlDevice *dev, unsigned int *index); // Retrieves the name of a device. MtmlReturn mtmlDeviceGetName(const MtmlDevice *dev, char *name, unsigned int length); // Retrieves the PCI attributes of a device. MtmlReturn mtmlDeviceGetPciInfo(const MtmlDevice *dev, MtmlPciInfo *pci); /** * Retrieves the UUID of a specified device. The UUID is a hexadecimal string in the * form of xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx, where each 'x' is an ASCII character that represents a hexadecimal * digit. The UUID is globally unique for every single device thus can be used to identify different devices * physically. */ MtmlReturn mtmlDeviceGetUUID(const MtmlDevice *dev, char *uuid, unsigned int length); // Initializes a GPU opaque object to represent a specific graphic core on the target device that is designated by its index. MtmlReturn mtmlDeviceInitGpu(const MtmlDevice *dev, MtmlGpu **gpu); // Initializes a memory opaque object to represent the memory on the target device. MtmlReturn mtmlDeviceInitMemory(const MtmlDevice *dev, MtmlMemory **mem); // Retrieves the maximum supported clock speed for the device's graphic core. MtmlReturn mtmlGpuGetMaxClock(const MtmlGpu *gpu, unsigned int *clockMhz); // Retrieves the current temperature readings for the device's graphic core, in degrees Celsius. MtmlReturn mtmlGpuGetTemperature(const MtmlGpu *gpu, unsigned int *temp); // Retrieves the current utilization rate for the device's graphic core. MtmlReturn mtmlGpuGetUtilization(const MtmlGpu *gpu, unsigned int *utilization); // Retrieves the number of devices that can be accessed by the library opaque object. MtmlReturn mtmlLibraryCountDevice(const MtmlLibrary *lib, unsigned int *count); /** * Initializes a device opaque object to represent a device that is designated by its index. * The index ranges from (0) to (deviceCount - 1), where deviceCount is retrieved from \ref mtmlLibraryCountDevice(). */ MtmlReturn mtmlLibraryInit(MtmlLibrary **lib); /** * Initializes a device opaque object to represent a device that is designated by its index. * The index ranges from (0) to (deviceCount - 1), where deviceCount is retrieved from \ref mtmlLibraryCountDevice(). */ MtmlReturn mtmlLibraryInitDeviceByIndex(const MtmlLibrary *lib, unsigned int index, MtmlDevice **dev); /** * Initializes a device opaque object to represent a device that is designated by its PCI Sbdf. * The PCI Sbdf format like 00000000:3a:00.0 refer to \ref MtmlPciInfo::sbdf. */ MtmlReturn mtmlLibraryInitDeviceByPciSbdf(const MtmlLibrary *lib, const char *pciSbdf, MtmlDevice **dev); // Initializes a MtmlSystem opaque pointer that is bound to a library opaque object. MtmlReturn mtmlLibraryInitSystem(const MtmlLibrary *lib, MtmlSystem **sys); /** * Shuts down the library opaque object that is previously initialized by \ref mtmlLibraryInit() and releases its resources. * The \a lib pointer cannot be used anymore after this function returns. */ MtmlReturn mtmlLibraryShutDown(MtmlLibrary *lib); // Retrieves the amount of total memory available on the device, in bytes. MtmlReturn mtmlMemoryGetTotal(const MtmlMemory *mem, unsigned long long *total); // Retrieves the amount of used memory on the device, in bytes. MtmlReturn mtmlMemoryGetUsed(const MtmlMemory *mem, unsigned long long *used); // Retrieves the current memory utilization rate for the device. MtmlReturn mtmlMemoryGetUtilization(const MtmlMemory *mem, unsigned int *utilization); ================================================ FILE: src/detection/gpu/nvapi.h ================================================ // References: // https://github.com/NVIDIA/nvapi (MIT License) // https://github.com/deathcamp/NVOC/blob/master/nvoc.c (Public Domain) typedef enum NvApiGPUMemoryType { NVAPI_GPU_MEMORY_TYPE_UNKNOWN = 0, NVAPI_GPU_MEMORY_TYPE_SDRAM, NVAPI_GPU_MEMORY_TYPE_DDR1, NVAPI_GPU_MEMORY_TYPE_DDR2, NVAPI_GPU_MEMORY_TYPE_GDDR2, NVAPI_GPU_MEMORY_TYPE_GDDR3, NVAPI_GPU_MEMORY_TYPE_GDDR4, NVAPI_GPU_MEMORY_TYPE_DDR3, NVAPI_GPU_MEMORY_TYPE_GDDR5, NVAPI_GPU_MEMORY_TYPE_LPDDR2, NVAPI_GPU_MEMORY_TYPE_GDDR5X, NVAPI_GPU_MEMORY_TYPE_LPDDR3, NVAPI_GPU_MEMORY_TYPE_LPDDR4, NVAPI_GPU_MEMORY_TYPE_LPDDR5, NVAPI_GPU_MEMORY_TYPE_GDDR6, NVAPI_GPU_MEMORY_TYPE_GDDR6X, NVAPI_GPU_MEMORY_TYPE_GDDR7, } NvApiGPUMemoryType; typedef enum { NV_SYSTEM_TYPE_GPU_UNKNOWN = 0, NV_SYSTEM_TYPE_IGPU = 1, // Integrated NV_SYSTEM_TYPE_DGPU = 2, // Discrete } NvApiGPUType; typedef int NvAPI_Status; // 0 = success; < 0 = error typedef struct NvPhysicalGpuHandle* NvPhysicalGpuHandle; typedef enum { NVAPI_INTERFACE_OFFSET_INITIALIZE = 0x0150E828, NVAPI_INTERFACE_OFFSET_UNLOAD = 0xD22BDD7E, NVAPI_INTERFACE_OFFSET_ENUM_PHYSICAL_GPUS = 0xE5AC921F, NVAPI_INTERFACE_OFFSET_GPU_GET_RAM_TYPE = 0x57F7CAAC, NVAPI_INTERFACE_OFFSET_GPU_GET_GPU_TYPE = 0xC33BAEB1, NVAPI_INTERFACE_OFFSET_FORCE_UINT32 = 0xFFFFFFFF } NvApiInterfaceOffsets; extern void* nvapi_QueryInterface(NvApiInterfaceOffsets offset); extern NvAPI_Status nvapi_Initialize(void); extern NvAPI_Status nvapi_Unload(void); extern NvAPI_Status nvapi_EnumPhysicalGPUs(NvPhysicalGpuHandle* handles, int* count); extern NvAPI_Status nvapi_GPU_GetRamType(NvPhysicalGpuHandle handle, NvApiGPUMemoryType* memtype); extern NvAPI_Status nvapi_GPU_GetGPUType(NvPhysicalGpuHandle handle, NvApiGPUType* gpuType); ================================================ FILE: src/detection/gpu/nvml.h ================================================ #pragma once // DISCLAIMER: // THIS FILE IS CREATED FROM SCRATCH, BY READING THE OFFICIAL NVML API // DOCUMENTATION REFERENCED BELOW, IN ORDER TO MAKE FASTFETCH MIT COMPLIANT. // https://docs.nvidia.com/deploy/nvml-api/group__nvmlDeviceStructs.html #define NVML_DEVICE_PCI_BUS_ID_BUFFER_SIZE 32 #define NVML_DEVICE_PCI_BUS_ID_BUFFER_V2_SIZE 16 #define NVML_DEVICE_NAME_V2_BUFFER_SIZE 96 typedef enum { NVML_SUCCESS = 0 } nvmlReturn_t; typedef struct nvmlDevice_t* nvmlDevice_t; // https://docs.nvidia.com/deploy/nvml-api/structnvmlPciInfo__t.html // PCI information about a GPU device typedef struct { // The legacy tuple domain:bus:device.function PCI identifier (& NULL terminator) char busIdLegacy[NVML_DEVICE_PCI_BUS_ID_BUFFER_V2_SIZE]; // The PCI domain on which the device's bus resides, 0 to 0xffffffff unsigned int domain; // The bus on which the device resides, 0 to 0xff unsigned int bus; // The device's id on the bus, 0 to 31 unsigned int device; // The combined 16-bit device id and 16-bit vendor id unsigned int pciDeviceId; // The 32-bit Sub System Device ID unsigned int pciSubSystemId; // The tuple domain:bus:device.function PCI identifier (& NULL terminator) char busId[NVML_DEVICE_PCI_BUS_ID_BUFFER_SIZE]; } nvmlPciInfo_t; // https://docs.nvidia.com/deploy/nvml-api/group__nvmlDeviceEnumvs.html#group__nvmlDeviceEnumvs_1g2650b526841fa38b8f293c2d509a1de0 // Temperature sensors typedef enum { // Temperature sensor for the GPU die NVML_TEMPERATURE_GPU = 0, NVML_TEMPERATURE_COUNT, } nvmlTemperatureSensors_t; // https://docs.nvidia.com/deploy/nvml-api/structnvmlMemory__v2__t.html#structnvmlMemory__v2__t // Memory allocation information for a device (v2) typedef struct { // Structure format version (must be 2) unsigned int version; // Total physical device memory (in bytes) unsigned long long total; // Device memory (in bytes) reserved for system use (driver or firmware) unsigned long long reserved; // Unallocated device memory (in bytes) unsigned long long free; // Allocated device memory (in bytes) unsigned long long used; } nvmlMemory_v2_t; // https://github.com/NVIDIA/nvidia-settings/issues/78#issuecomment-1012837988 enum { nvmlMemory_v2 = (unsigned int)(sizeof(nvmlMemory_v2_t) | (2 << 24U)) }; // https://docs.nvidia.com/deploy/nvml-api/structnvmlMemory__t.html#structnvmlMemory__t // Memory allocation information for a device (v1) typedef struct { // Total physical device memory (in bytes) unsigned long long total; // Unallocated device memory (in bytes) unsigned long long free; // Sum of Reserved and Allocated device memory (in bytes) unsigned long long used; } nvmlMemory_t; // https://docs.nvidia.com/deploy/nvml-api/group__nvmlDeviceEnumvs.html#group__nvmlDeviceEnumvs_1g805c0647be9996589fc5e3f6ff680c64 // Clock types typedef enum { // Graphics clock domain NVML_CLOCK_GRAPHICS = 0, // SM clock domain NVML_CLOCK_SM = 1, // Memory clock domain NVML_CLOCK_MEM = 2, // Video encoder/decoder clock domain NVML_CLOCK_VIDEO = 3, // Count of clock types NVML_CLOCK_COUNT, } nvmlClockType_t; // https://docs.nvidia.com/deploy/nvml-api/group__nvmlDeviceEnumvs.html#group__nvmlDeviceEnumvs_1gfa6b01990b212f7b49089b7158eafd2b // The Brand of the GPU typedef enum { NVML_BRAND_UNKNOWN = 0, NVML_BRAND_QUADRO = 1, NVML_BRAND_TESLA = 2, NVML_BRAND_NVS = 3, NVML_BRAND_GRID = 4, NVML_BRAND_GEFORCE = 5, NVML_BRAND_TITAN = 6, NVML_BRAND_NVIDIA_VAPPS = 7, NVML_BRAND_NVIDIA_VPC = 8, NVML_BRAND_NVIDIA_VCS = 9, NVML_BRAND_NVIDIA_VWS = 10, NVML_BRAND_NVIDIA_CLOUD_GAMING = 11, NVML_BRAND_NVIDIA_VGAMING = NVML_BRAND_NVIDIA_CLOUD_GAMING, NVML_BRAND_QUADRO_RTX = 12, NVML_BRAND_NVIDIA_RTX = 13, NVML_BRAND_NVIDIA = 14, NVML_BRAND_GEFORCE_RTX = 15, NVML_BRAND_TITAN_RTX = 16, NVML_BRAND_COUNT, } nvmlBrandType_t; // https://docs.nvidia.com/deploy/nvml-api/structnvmlUtilization__t.html#structnvmlUtilization__t // Utilization information for a device. typedef struct { // Percent of time over the past second during which one or more kernels was executing on the GPU unsigned int gpu; // Percent of time over the past second during which global (device) memory was being read or written unsigned int memory; } nvmlUtilization_t; // https://docs.nvidia.com/deploy/nvml-api/group__nvmlInitializationAndCleanup.html#group__nvmlInitializationAndCleanup // Initialize NVML, but don't initialize any GPUs yet nvmlReturn_t nvmlInit_v2(void); // Shut down NVML by releasing all GPU resources previously allocated with nvmlInit_v2() nvmlReturn_t nvmlShutdown(void); // https://docs.nvidia.com/deploy/nvml-api/group__nvmlDeviceQueries.html // Retrieves the number of compute devices in the system. A compute device is a single GPU extern nvmlReturn_t nvmlDeviceGetCount_v2(unsigned int* deviceCount); // Acquire the handle for a particular device, based on its index extern nvmlReturn_t nvmlDeviceGetHandleByIndex_v2(unsigned int index, nvmlDevice_t* device); // Acquire the handle for a particular device, based on its PCI bus id extern nvmlReturn_t nvmlDeviceGetHandleByPciBusId_v2(const char* pciBusId, nvmlDevice_t* device); // Retrieves the PCI attributes of this device extern nvmlReturn_t nvmlDeviceGetPciInfo_v3(nvmlDevice_t device, nvmlPciInfo_t* pci); // Retrieves the current temperature readings for the device, in degrees C extern nvmlReturn_t nvmlDeviceGetTemperature(nvmlDevice_t device, nvmlTemperatureSensors_t sensorType, unsigned int* temp); // Retrieves the amount of used, free, reserved and total memory available on the device, in bytes. The reserved amount is supported on version 2 only extern nvmlReturn_t nvmlDeviceGetMemoryInfo_v2(nvmlDevice_t device, nvmlMemory_v2_t* memory); // Retrieves the amount of used, free, total memory available on the device, in bytes. extern nvmlReturn_t nvmlDeviceGetMemoryInfo(nvmlDevice_t device, nvmlMemory_t *memory); // Gets the device's core count extern nvmlReturn_t nvmlDeviceGetNumGpuCores(nvmlDevice_t device, unsigned int* numCores); // Retrieves the maximum clock speeds for the device extern nvmlReturn_t nvmlDeviceGetMaxClockInfo(nvmlDevice_t device, nvmlClockType_t type, unsigned int* clock); // Retrieves the brand of this device extern nvmlReturn_t nvmlDeviceGetBrand(nvmlDevice_t device, nvmlBrandType_t* type); // Retrieves the current utilization rates for the device extern nvmlReturn_t nvmlDeviceGetUtilizationRates(nvmlDevice_t device, nvmlUtilization_t *utilization); // Retrieves the globally unique immutable UUID associated with this device, as a 5 part hexadecimal string, that augments the immutable, board serial identifier. extern nvmlReturn_t nvmlDeviceGetIndex(nvmlDevice_t device, unsigned int *index); // Retrieves the name of this device. extern nvmlReturn_t nvmlDeviceGetName(nvmlDevice_t device, char *name, unsigned int length); ================================================ FILE: src/detection/gtk_qt/gtk.c ================================================ #include "fastfetch.h" #include "common/properties.h" #include "common/thread.h" #include "common/settings.h" #include "detection/gtk_qt/gtk_qt.h" #include "detection/displayserver/displayserver.h" static inline bool allPropertiesSet(FFGTKResult* result) { return result->theme.length > 0 && result->icons.length > 0 && result->font.length > 0; } static inline void applyGTKSettings(FFGTKResult* result, const char* themeName, const char* iconsName, const char* fontName, const char* cursorTheme, int cursorSize, const char* wallpaper) { if(result->theme.length == 0) ffStrbufAppendS(&result->theme, themeName); if(result->icons.length == 0) ffStrbufAppendS(&result->icons, iconsName); if(result->font.length == 0) ffStrbufAppendS(&result->font, fontName); if(result->cursor.length == 0) ffStrbufAppendS(&result->cursor, cursorTheme); if(result->cursorSize.length == 0 && cursorSize > 0) ffStrbufAppendF(&result->cursorSize, "%i", cursorSize); if(result->wallpaper.length == 0) ffStrbufAppendS(&result->wallpaper, wallpaper); } static bool testXfconfWallpaperPropKey(FF_MAYBE_UNUSED void* data, const char* key) { int count = 0; sscanf(key, "/backdrop/screen0/monitor%*[^/]/workspace0/last-image%n", &count); return count == 0; } static void detectGTKFromSettings(FFGTKResult* result) { static const char* themeName = NULL; static const char* iconsName = NULL; static const char* fontName = NULL; static const char* cursorTheme = NULL; static int cursorSize = 0; static const char* wallpaper = NULL; static bool init = false; if(init) { applyGTKSettings(result, themeName, iconsName, fontName, cursorTheme, cursorSize, wallpaper); return; } init = true; const FFDisplayServerResult* wmde = ffConnectDisplayServer(); if(ffStrbufIgnCaseEqualS(&wmde->dePrettyName, FF_DE_PRETTY_XFCE4)) { themeName = ffSettingsGetXFConf("xsettings", "/Net/ThemeName", FF_VARIANT_TYPE_STRING).strValue; iconsName = ffSettingsGetXFConf("xsettings", "/Net/IconThemeName", FF_VARIANT_TYPE_STRING).strValue; fontName = ffSettingsGetXFConf("xsettings", "/Gtk/FontName", FF_VARIANT_TYPE_STRING).strValue; cursorTheme = ffSettingsGetXFConf("xsettings", "/Gtk/CursorThemeName", FF_VARIANT_TYPE_STRING).strValue; cursorSize = ffSettingsGetXFConf("xsettings", "/Gtk/CursorThemeSize", FF_VARIANT_TYPE_INT).intValue; wallpaper = ffSettingsGetXFConfFirstMatch("xfce4-desktop", "/backdrop/screen0", FF_VARIANT_TYPE_STRING, NULL, testXfconfWallpaperPropKey).strValue; } else if(ffStrbufIgnCaseEqualS(&wmde->dePrettyName, FF_DE_PRETTY_CINNAMON)) { themeName = ffSettingsGetGnome("/org/cinnamon/desktop/interface/gtk-theme", "org.cinnamon.desktop.interface", NULL, "gtk-theme", FF_VARIANT_TYPE_STRING).strValue; iconsName = ffSettingsGetGnome("/org/cinnamon/desktop/interface/icon-theme", "org.cinnamon.desktop.interface", NULL, "icon-theme", FF_VARIANT_TYPE_STRING).strValue; fontName = ffSettingsGetGnome("/org/cinnamon/desktop/interface/font-name", "org.cinnamon.desktop.interface", NULL, "font-name", FF_VARIANT_TYPE_STRING).strValue; cursorTheme = ffSettingsGetGnome("/org/cinnamon/desktop/interface/cursor-theme", "org.cinnamon.desktop.interface", NULL, "cursor-theme", FF_VARIANT_TYPE_STRING).strValue; cursorSize = ffSettingsGetGnome("/org/cinnamon/desktop/interface/cursor-size", "org.cinnamon.desktop.interface", NULL, "cursor-size", FF_VARIANT_TYPE_INT).intValue; wallpaper = ffSettingsGetGnome("/org/cinnamon/desktop/background/picture-uri", "org.cinnamon.desktop.background", NULL, "picture-uri", FF_VARIANT_TYPE_STRING).strValue; } else if(ffStrbufIgnCaseEqualS(&wmde->dePrettyName, FF_DE_PRETTY_MATE)) { themeName = ffSettingsGetGnome("/org/mate/interface/gtk-theme", "org.mate.interface", NULL, "gtk-theme", FF_VARIANT_TYPE_STRING).strValue; iconsName = ffSettingsGetGnome("/org/mate/interface/icon-theme", "org.mate.interface", NULL, "icon-theme", FF_VARIANT_TYPE_STRING).strValue; fontName = ffSettingsGetGnome("/org/mate/interface/font-name", "org.mate.interface", NULL, "font-name", FF_VARIANT_TYPE_STRING).strValue; cursorTheme = ffSettingsGetGnome("/org/mate/peripherals-mouse/cursor-theme", "org.mate.peripherals-mouse", NULL, "cursor-theme", FF_VARIANT_TYPE_STRING).strValue; cursorSize = ffSettingsGetGnome("/org/mate/peripherals-mouse/cursor-size", "org.mate.peripherals-mouse", NULL, "cursor-size", FF_VARIANT_TYPE_INT).intValue; wallpaper = ffSettingsGetGnome("/org/mate/desktop/background", "org.mate.background", NULL, "picture-filename", FF_VARIANT_TYPE_STRING).strValue; } else if( 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) ) { 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; 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; } applyGTKSettings(result, themeName, iconsName, fontName, cursorTheme, cursorSize, wallpaper); } static void detectGTKFromConfigFile(const char* filename, FFGTKResult* result) { ffParsePropFileValues(filename, 5, (FFpropquery[]) { {"gtk-theme-name =", &result->theme}, {"gtk-icon-theme-name =", &result->icons}, {"gtk-font-name =", &result->font}, {"gtk-cursor-theme-name =", &result->cursor}, {"gtk-cursor-theme-size =", &result->cursorSize} }); } static void detectGTKFromConfigDir(FFstrbuf* configDir, const char* version, FFGTKResult* result) { uint32_t configDirLength = configDir->length; // /gtk-.0/settings.ini ffStrbufAppendS(configDir, "gtk-"); ffStrbufAppendS(configDir, version); ffStrbufAppendS(configDir, ".0/settings.ini"); detectGTKFromConfigFile(configDir->chars, result); ffStrbufSubstrBefore(configDir, configDirLength); if(allPropertiesSet(result)) return; // /gtk-.0/gtkrc ffStrbufAppendS(configDir, "gtk-"); ffStrbufAppendS(configDir, version); ffStrbufAppendS(configDir, ".0/gtkrc"); detectGTKFromConfigFile(configDir->chars, result); ffStrbufSubstrBefore(configDir, configDirLength); if(allPropertiesSet(result)) return; // /gtkrc-.0 ffStrbufAppendS(configDir, "gtkrc-"); ffStrbufAppendS(configDir, version); ffStrbufAppendS(configDir, ".0"); detectGTKFromConfigFile(configDir->chars, result); ffStrbufSubstrBefore(configDir, configDirLength); if(allPropertiesSet(result)) return; // /.gtkrc-.0 ffStrbufAppendS(configDir, ".gtkrc-"); ffStrbufAppendS(configDir, version); ffStrbufAppendS(configDir, ".0"); detectGTKFromConfigFile(configDir->chars, result); ffStrbufSubstrBefore(configDir, configDirLength); } static void detectGTK(const char* version, FFGTKResult* result) { //Mate, Cinnamon, GNOME, Unity, Budgie use dconf to save theme config //On other DEs, this will do nothing detectGTKFromSettings(result); if(allPropertiesSet(result)) return; //We need to do this because we use multiple threads on configDirs FF_STRBUF_AUTO_DESTROY baseDir = ffStrbufCreateA(64); FF_LIST_FOR_EACH(FFstrbuf, configDir, instance.state.platform.configDirs) { ffStrbufSet(&baseDir, configDir); detectGTKFromConfigDir(&baseDir, version, result); if(allPropertiesSet(result)) break; } } #define FF_DETECT_GTK_IMPL(version) \ static FFGTKResult result; \ static bool init = false; \ if(init) \ return &result; \ init = true; \ ffStrbufInit(&result.theme); \ ffStrbufInit(&result.icons); \ ffStrbufInit(&result.font); \ ffStrbufInit(&result.cursor); \ ffStrbufInit(&result.cursorSize); \ ffStrbufInit(&result.wallpaper); \ detectGTK(#version, &result); \ return &result; const FFGTKResult* ffDetectGTK2(void) { FF_DETECT_GTK_IMPL(2) } const FFGTKResult* ffDetectGTK3(void) { FF_DETECT_GTK_IMPL(3) } const FFGTKResult* ffDetectGTK4(void) { FF_DETECT_GTK_IMPL(4) } #undef FF_CALCULATE_GTK_IMPL ================================================ FILE: src/detection/gtk_qt/gtk_qt.h ================================================ #pragma once #include "fastfetch.h" typedef struct FFGTKResult { FFstrbuf theme; FFstrbuf icons; FFstrbuf font; FFstrbuf cursor; FFstrbuf cursorSize; FFstrbuf wallpaper; } FFGTKResult; typedef struct FFQtResult { FFstrbuf widgetStyle; FFstrbuf colorScheme; FFstrbuf icons; FFstrbuf font; FFstrbuf wallpaper; } FFQtResult; const FFGTKResult* ffDetectGTK2(void); const FFGTKResult* ffDetectGTK4(void); const FFGTKResult* ffDetectGTK3(void); const FFQtResult* ffDetectQt(void); ================================================ FILE: src/detection/gtk_qt/qt.c ================================================ #include "fastfetch.h" #include "common/properties.h" #include "common/thread.h" #include "common/stringUtils.h" #include "detection/gtk_qt/gtk_qt.h" #include "detection/displayserver/displayserver.h" #include #include static inline bool allValuesSet(const FFQtResult* result) { return result->widgetStyle.length > 0 && result->colorScheme.length > 0 && result->icons.length > 0 && result->font.length > 0 && result->wallpaper.length > 0; } typedef enum __attribute__((__packed__)) PlasmaCategory { PLASMA_CATEGORY_GENERAL, PLASMA_CATEGORY_KDE, PLASMA_CATEGORY_ICONS, PLASMA_CATEGORY_OTHER } PlasmaCategory; static bool detectPlasmaFromFile(const char* filename, FFQtResult* result) { FILE* kdeglobals = fopen(filename, "r"); if(kdeglobals == NULL) return false; char* line = NULL; size_t len = 0; PlasmaCategory category = PLASMA_CATEGORY_OTHER; while(getline(&line, &len, kdeglobals) != -1) { if(line[0] == '[') { char categoryName[32]; sscanf(line, "[%31[^]]", categoryName); if(ffStrEqualsIgnCase(categoryName, "General")) category = PLASMA_CATEGORY_GENERAL; else if(ffStrEqualsIgnCase(categoryName, "KDE")) category = PLASMA_CATEGORY_KDE; else if(ffStrEqualsIgnCase(categoryName, "Icons")) category = PLASMA_CATEGORY_ICONS; else category = PLASMA_CATEGORY_OTHER; continue; } if(category == PLASMA_CATEGORY_KDE && result->widgetStyle.length == 0) ffParsePropLine(line, "widgetStyle =", &result->widgetStyle); else if(category == PLASMA_CATEGORY_ICONS && result->icons.length == 0) ffParsePropLine(line, "Theme =", &result->icons); else if(category == PLASMA_CATEGORY_GENERAL) { if(result->colorScheme.length == 0) ffParsePropLine(line, "ColorScheme =", &result->colorScheme); if(result->font.length == 0) ffParsePropLine(line, "font =", &result->font); //Before plasma 5.23, "Font" was the key instead of "font". Since a lot of distros ship older versions, we test for both. if(result->font.length == 0) ffParsePropLine(line, "Font =", &result->font); } } free(line); fclose(kdeglobals); return true; } static void detectPlasma(FFQtResult* result) { bool foundAFile = false; //We need to do this because we use multiple threads on configDirs FF_STRBUF_AUTO_DESTROY baseDir = ffStrbufCreateA(64); FF_LIST_FOR_EACH(FFstrbuf, configDir, instance.state.platform.configDirs) { ffStrbufSet(&baseDir, configDir); ffStrbufAppendS(&baseDir, "kdeglobals"); if(detectPlasmaFromFile(baseDir.chars, result)) foundAFile = true; ffStrbufSet(&baseDir, configDir); ffStrbufAppendS(&baseDir, "plasma-org.kde.plasma.desktop-appletsrc"); ffParsePropFile(baseDir.chars, "Image=", &result->wallpaper); if(allValuesSet(result)) return; } if(!foundAFile) return; //In Plasma the default value is never set in the config file, but the whole key-value is discarded. ///We must set these values by our self if the file exists (it always does here) if(result->widgetStyle.length == 0) ffStrbufAppendS(&result->widgetStyle, "Breeze"); if(result->colorScheme.length == 0) ffStrbufAppendS(&result->colorScheme, "BreezeLight"); if(result->icons.length == 0) ffStrbufAppendS(&result->icons, "Breeze"); if(result->font.length == 0) ffStrbufAppendS(&result->font, "Noto Sans, 10"); } static void detectLXQt(FFQtResult* result) { ffParsePropFileConfigValues("lxqt/lxqt.conf", 3, (FFpropquery[]) { {"style = ", &result->widgetStyle}, {"icon_theme = ", &result->icons}, {"font = ", &result->font} }); ffParsePropFileConfig("pcmanfm-qt/lxqt/settings.conf", "Wallpaper=", &result->wallpaper); } static void detectQtCt(char qver, FFQtResult* result) { // qt5ct and qt6ct are technically separate applications, but they're both // by the same author and qt6ct understands qt5ct in qt6 applications as well. char file[] = "qtXct/qtXct.conf"; file[2] = file[8] = qver; FF_STRBUF_AUTO_DESTROY font = ffStrbufCreate(); ffParsePropFileConfigValues(file, 3, (FFpropquery[]) { {"style=", &result->widgetStyle}, {"icon_theme=", &result->icons}, {"general=", &font} }); if (ffStrbufStartsWithC(&font, '@')) { // See QVariant notes on https://doc.qt.io/qt-5/qsettings.html and // https://github.com/fastfetch-cli/fastfetch/issues/1053#issuecomment-2197254769 // Thankfully, newer versions use the more common font encoding. ffStrbufSetNS(&font, 5, file); } else if (qver == '5') { // #1864 const char *p = font.chars; while (*p) { if (p[0] == '\\' && p[1] == 'x' && isxdigit(p[2]) && isxdigit(p[3]) && isxdigit(p[4]) && isxdigit(p[5])) { uint32_t codepoint = (uint32_t)strtoul((char[]) { p[2], p[3], p[4], p[5], '\0' }, NULL, 16); ffStrbufAppendUtf32CodePoint(&result->font, codepoint); p += 6; } else { ffStrbufAppendC(&result->font, *p++); } } } else { ffStrbufDestroy(&result->font); ffStrbufInitMove(&result->font, &font); } } static void detectKvantum(FFQtResult* result) { ffParsePropFileConfigValues("Kvantum/kvantum.kvconfig", 1, (FFpropquery[]) { {"theme=", &result->widgetStyle}, }); } const FFQtResult* ffDetectQt(void) { static FFQtResult result; static bool init = false; if(init) return &result; init = true; ffStrbufInit(&result.widgetStyle); ffStrbufInit(&result.colorScheme); ffStrbufInit(&result.icons); ffStrbufInit(&result.font); ffStrbufInit(&result.wallpaper); const FFDisplayServerResult* wmde = ffConnectDisplayServer(); if(ffStrbufIgnCaseEqualS(&wmde->dePrettyName, FF_DE_PRETTY_PLASMA)) detectPlasma(&result); else if(ffStrbufIgnCaseEqualS(&wmde->dePrettyName, FF_DE_PRETTY_LXQT)) detectLXQt(&result); else { const char *qPlatformTheme = getenv("QT_QPA_PLATFORMTHEME"); if(qPlatformTheme && (ffStrEquals(qPlatformTheme, "qt5ct") || ffStrEquals(qPlatformTheme, "qt6ct"))) detectQtCt(qPlatformTheme[2], &result); } if(ffStrbufEqualS(&result.widgetStyle, "kvantum") || ffStrbufEqualS(&result.widgetStyle, "kvantum-dark")) { ffStrbufClear(&result.widgetStyle); detectKvantum(&result); } return &result; } ================================================ FILE: src/detection/host/host.h ================================================ #pragma once #include "fastfetch.h" #include "modules/host/option.h" typedef struct FFHostResult { FFstrbuf family; FFstrbuf name; FFstrbuf version; FFstrbuf sku; FFstrbuf serial; FFstrbuf uuid; FFstrbuf vendor; } FFHostResult; const char* ffHostGetMacProductNameWithHwModel(const FFstrbuf* hwModel); #if __x86_64__ bool ffHostDetectMac(FFHostResult* host); #endif const char* ffDetectHost(FFHostResult* host); ================================================ FILE: src/detection/host/host_android.c ================================================ #include "host.h" #include "common/settings.h" #include const char* ffDetectHost(FFHostResult* host) { // http://newandroidbook.com/ddb/ ffSettingsGetAndroidProperty("ro.product.device", &host->family); ffSettingsGetAndroidProperty("ro.product.marketname", &host->name) || ffSettingsGetAndroidProperty("ro.vendor.product.display", &host->name) || ffSettingsGetAndroidProperty("ro.vivo.market.name", &host->name) || ffSettingsGetAndroidProperty("ro.product.oppo_model", &host->name) || ffSettingsGetAndroidProperty("ro.oppo.market.name", &host->name) || ffSettingsGetAndroidProperty("ro.vendor.oplus.market.enname", &host->name) || ffSettingsGetAndroidProperty("ro.config.devicename", &host->name) || ffSettingsGetAndroidProperty("ro.config.marketing_name", &host->name) || ffSettingsGetAndroidProperty("ro.product.vendor.model", &host->name) || ffSettingsGetAndroidProperty("ro.product.brand", &host->name); if (ffSettingsGetAndroidProperty("ro.product.model", &host->version)) { if (ffStrbufStartsWithIgnCase(&host->version, &host->name)) { ffStrbufSubstrAfter(&host->version, host->name.length); ffStrbufTrimLeft(&host->version, ' '); } } ffSettingsGetAndroidProperty("ro.product.manufacturer", &host->vendor); if(host->vendor.length && !ffStrbufStartsWithIgnCase(&host->name, &host->vendor)) { ffStrbufPrependS(&host->name, " "); ffStrbufPrepend(&host->name, &host->vendor); } return NULL; } ================================================ FILE: src/detection/host/host_apple.c ================================================ #include "host.h" #include "common/sysctl.h" #include "common/apple/cf_helpers.h" #include const char* getProductNameWithIokit(FFstrbuf* result) { FF_IOOBJECT_AUTO_RELEASE io_registry_entry_t registryEntry = IORegistryEntryFromPath(MACH_PORT_NULL, "IODeviceTree:/product"); if (!registryEntry) return "IOServiceGetMatchingService() failed"; FF_CFTYPE_AUTO_RELEASE CFStringRef productName = IORegistryEntryCreateCFProperty(registryEntry, CFSTR("product-name"), kCFAllocatorDefault, kNilOptions); if (!productName) return "IORegistryEntryCreateCFProperty() failed"; return ffCfStrGetString(productName, result); } const char* getOthersByIokit(FFHostResult* host) { FF_IOOBJECT_AUTO_RELEASE io_registry_entry_t registryEntry = IOServiceGetMatchingService(MACH_PORT_NULL, IOServiceMatching("IOPlatformExpertDevice")); if (!registryEntry) return "IOServiceGetMatchingService() failed"; FF_CFTYPE_AUTO_RELEASE CFStringRef serialNumber = IORegistryEntryCreateCFProperty(registryEntry, CFSTR(kIOPlatformSerialNumberKey), kCFAllocatorDefault, kNilOptions); if (serialNumber) ffCfStrGetString(serialNumber, &host->serial); FF_CFTYPE_AUTO_RELEASE CFStringRef uuid = IORegistryEntryCreateCFProperty(registryEntry, CFSTR(kIOPlatformUUIDKey), kCFAllocatorDefault, kNilOptions); if (uuid) ffCfStrGetString(uuid, &host->uuid); FF_CFTYPE_AUTO_RELEASE CFStringRef manufacturer = IORegistryEntryCreateCFProperty(registryEntry, CFSTR("manufacturer"), kCFAllocatorDefault, kNilOptions); if (manufacturer) ffCfStrGetString(manufacturer, &host->vendor); FF_CFTYPE_AUTO_RELEASE CFStringRef version = IORegistryEntryCreateCFProperty(registryEntry, CFSTR("version"), kCFAllocatorDefault, kNilOptions); if (version) ffCfStrGetString(version, &host->version); return NULL; } const char* ffDetectHost(FFHostResult* host) { const char* error = ffSysctlGetString("hw.product", &host->family); if (error) error = ffSysctlGetString("hw.model", &host->family); if (error) return error; ffStrbufSetStatic(&host->name, ffHostGetMacProductNameWithHwModel(&host->family)); if (host->name.length == 0) getProductNameWithIokit(&host->name); if (host->name.length == 0) ffStrbufSet(&host->name, &host->family); getOthersByIokit(host); return NULL; } ================================================ FILE: src/detection/host/host_bsd.c ================================================ #include "host.h" #include "common/settings.h" #include "common/smbiosHelper.h" const char* ffDetectHost(FFHostResult* host) { ffSettingsGetFreeBSDKenv("smbios.system.product", &host->name); ffCleanUpSmbiosValue(&host->name); ffSettingsGetFreeBSDKenv("smbios.system.family", &host->family); ffCleanUpSmbiosValue(&host->family); ffSettingsGetFreeBSDKenv("smbios.system.version", &host->version); ffCleanUpSmbiosValue(&host->version); ffSettingsGetFreeBSDKenv("smbios.system.sku", &host->sku); ffCleanUpSmbiosValue(&host->sku); ffSettingsGetFreeBSDKenv("smbios.system.serial", &host->serial); ffCleanUpSmbiosValue(&host->serial); ffSettingsGetFreeBSDKenv("smbios.system.uuid", &host->uuid); ffCleanUpSmbiosValue(&host->uuid); ffSettingsGetFreeBSDKenv("smbios.system.maker", &host->vendor); ffCleanUpSmbiosValue(&host->vendor); #ifdef __x86_64__ ffHostDetectMac(host); #endif return NULL; } ================================================ FILE: src/detection/host/host_linux.c ================================================ #include "host.h" #include "common/io.h" #include "common/processing.h" #include "common/smbiosHelper.h" #include static bool getHostProductName(FFstrbuf* name) { if (ffReadFileBuffer("/sys/firmware/devicetree/base/model", name) || ffReadFileBuffer("/sys/firmware/devicetree/base/banner-name", name)) { ffStrbufTrimRight(name, '\0'); return true; } if (ffReadFileBuffer("/tmp/sysinfo/model", name)) { ffStrbufTrimRightSpace(name); ffStrbufTrimRight(name, '\0'); if(ffIsSmbiosValueSet(name)) return true; } return false; } static bool getHostSerialNumber(FFstrbuf* serial) { if (ffReadFileBuffer("/sys/firmware/devicetree/base/smbios/smbios/system/serial", serial) || ffReadFileBuffer("/sys/firmware/devicetree/base/serial-number", serial)) { ffStrbufTrimRight(serial, '\0'); return true; } return false; } static bool getHostProductFamily(FFstrbuf* family) { if (ffReadFileBuffer("/sys/firmware/devicetree/base/smbios/smbios/system/family", family) || ffReadFileBuffer("/sys/firmware/devicetree/base/smbios/smbios/system/product", family)) { ffStrbufTrimRight(family, '\0'); return true; } return false; } static bool getHostVendor(FFstrbuf* vendor) { if (ffReadFileBuffer("/sys/firmware/devicetree/base/smbios/smbios/system/manufacturer", vendor)) { ffStrbufTrimRight(vendor, '\0'); return true; } return false; } const char* ffDetectHost(FFHostResult* host) { // This is a hack for Asahi Linux, whose product_family is empty if (ffGetSmbiosValue("/sys/devices/virtual/dmi/id/product_family", "/sys/class/dmi/id/product_family", &host->family)) { ffGetSmbiosValue("/sys/devices/virtual/dmi/id/product_name", "/sys/class/dmi/id/product_name", &host->name); ffGetSmbiosValue("/sys/devices/virtual/dmi/id/product_version", "/sys/class/dmi/id/product_version", &host->version); ffGetSmbiosValue("/sys/devices/virtual/dmi/id/product_sku", "/sys/class/dmi/id/product_sku", &host->sku); ffGetSmbiosValue("/sys/devices/virtual/dmi/id/product_serial", "/sys/class/dmi/id/product_serial", &host->serial); ffGetSmbiosValue("/sys/devices/virtual/dmi/id/sys_vendor", "/sys/class/dmi/id/sys_vendor", &host->vendor); } else { getHostProductFamily(&host->family); getHostProductName(&host->name); getHostSerialNumber(&host->serial); getHostVendor(&host->vendor); } #ifdef __x86_64__ ffHostDetectMac(host); #endif //KVM/Qemu virtual machine if(ffStrbufStartsWithS(&host->name, "Standard PC")) ffStrbufPrependS(&host->name, "KVM/QEMU "); if(host->family.length == 0 && host->name.length == 0) { const char* wslDistroName = getenv("WSL_DISTRO_NAME"); //On WSL, the real host can't be detected. Instead use WSL as host. if(wslDistroName != NULL || getenv("WSL_DISTRO") != NULL || getenv("WSL_INTEROP") != NULL) { ffStrbufSetStatic(&host->name, "Windows Subsystem for Linux"); if (wslDistroName) ffStrbufAppendF(&host->name, " - %s", wslDistroName); ffStrbufSetStatic(&host->family, "WSL"); ffStrbufSetStatic(&host->vendor, "Microsoft Corporation"); if (instance.config.general.detectVersion) { ffProcessAppendStdOut(&host->version, (char* const[]){ "wslinfo", "--wsl-version", "-n", NULL, }); // supported in 2.2.3 and later } } else if (ffStrbufStartsWithS(&instance.state.platform.sysinfo.version, "FreeBSD ")) { ffStrbufSetStatic(&host->name, "Linux Binary Compatibility on FreeBSD"); ffStrbufSetStatic(&host->family, "FreeBSD"); ffStrbufSetStatic(&host->vendor, "FreeBSD Foundation"); if (instance.config.general.detectVersion) { ffStrbufSetS(&host->version, instance.state.platform.sysinfo.version.chars + strlen("FreeBSD ")); ffStrbufSubstrBeforeFirstC(&host->version, ' '); } } } return NULL; } ================================================ FILE: src/detection/host/host_mac.c ================================================ #include "host.h" #include "common/stringUtils.h" const char* ffHostGetMacProductNameWithHwModel(const FFstrbuf* hwModel) { // Macbook Pro: https://support.apple.com/en-us/HT201300 // Macbook Air: https://support.apple.com/en-us/HT201862 // Mac mini: https://support.apple.com/en-us/HT201894 // iMac: https://support.apple.com/en-us/HT201634 // Mac Pro: https://support.apple.com/en-us/HT202888 // Mac Studio: https://support.apple.com/en-us/HT213073 if(ffStrbufStartsWithS(hwModel, "MacBookPro")) { const char* version = hwModel->chars + strlen("MacBookPro"); if(ffStrEquals(version, "18,3") || ffStrEquals(version, "18,4")) return "MacBook Pro (14-inch, 2021)"; if(ffStrEquals(version, "18,1") || ffStrEquals(version, "18,2")) return "MacBook Pro (16-inch, 2021)"; if(ffStrEquals(version, "17,1")) return "MacBook Pro (13-inch, M1, 2020)"; if(ffStrEquals(version, "16,3")) return "MacBook Pro (13-inch, 2020, Two Thunderbolt 3 ports)"; if(ffStrEquals(version, "16,2")) return "MacBook Pro (13-inch, 2020, Four Thunderbolt 3 ports)"; if(ffStrEquals(version, "16,4") || ffStrEquals(version, "16,1")) return "MacBook Pro (16-inch, 2019)"; if(ffStrEquals(version, "15,4")) return "MacBook Pro (13-inch, 2019, Two Thunderbolt 3 ports)"; if(ffStrEquals(version, "15,3")) return "MacBook Pro (15-inch, 2019)"; if(ffStrEquals(version, "15,2")) return "MacBook Pro (13-inch, 2018/2019, Four Thunderbolt 3 ports)"; if(ffStrEquals(version, "15,1")) return "MacBook Pro (15-inch, 2018/2019)"; if(ffStrEquals(version, "14,3")) return "MacBook Pro (15-inch, 2017)"; if(ffStrEquals(version, "14,2")) return "MacBook Pro (13-inch, 2017, Four Thunderbolt 3 ports)"; if(ffStrEquals(version, "14,1")) return "MacBook Pro (13-inch, 2017, Two Thunderbolt 3 ports)"; if(ffStrEquals(version, "13,3")) return "MacBook Pro (15-inch, 2016)"; if(ffStrEquals(version, "13,2")) return "MacBook Pro (13-inch, 2016, Four Thunderbolt 3 ports)"; if(ffStrEquals(version, "13,1")) return "MacBook Pro (13-inch, 2016, Two Thunderbolt 3 ports)"; if(ffStrEquals(version, "12,1")) return "MacBook Pro (Retina, 13-inch, Early 2015)"; if(ffStrEquals(version, "11,4") || ffStrEquals(version, "11,5")) return "MacBook Pro (Retina, 15-inch, Mid 2015)"; if(ffStrEquals(version, "11,2") || ffStrEquals(version, "11,3")) return "MacBook Pro (Retina, 15-inch, Late 2013/Mid 2014)"; if(ffStrEquals(version, "11,1")) return "MacBook Pro (Retina, 13-inch, Late 2013/Mid 2014)"; if(ffStrEquals(version, "10,2")) return "MacBook Pro (Retina, 13-inch, Late 2012/Early 2013)"; if(ffStrEquals(version, "10,1")) return "MacBook Pro (Retina, 15-inch, Mid 2012/Early 2013)"; if(ffStrEquals(version, "9,2")) return "MacBook Pro (13-inch, Mid 2012)"; if(ffStrEquals(version, "9,1")) return "MacBook Pro (15-inch, Mid 2012)"; if(ffStrEquals(version, "8,3")) return "MacBook Pro (17-inch, 2011)"; if(ffStrEquals(version, "8,2")) return "MacBook Pro (15-inch, 2011)"; if(ffStrEquals(version, "8,1")) return "MacBook Pro (13-inch, 2011)"; if(ffStrEquals(version, "7,1")) return "MacBook Pro (13-inch, Mid 2010)"; if(ffStrEquals(version, "6,2")) return "MacBook Pro (15-inch, Mid 2010)"; if(ffStrEquals(version, "6,1")) return "MacBook Pro (17-inch, Mid 2010)"; if(ffStrEquals(version, "5,5")) return "MacBook Pro (13-inch, Mid 2009)"; if(ffStrEquals(version, "5,3")) return "MacBook Pro (15-inch, Mid 2009)"; if(ffStrEquals(version, "5,2")) return "MacBook Pro (17-inch, Mid/Early 2009)"; if(ffStrEquals(version, "5,1")) return "MacBook Pro (15-inch, Late 2008)"; if(ffStrEquals(version, "4,1")) return "MacBook Pro (17/15-inch, Early 2008)"; } else if(ffStrbufStartsWithS(hwModel, "MacBookAir")) { const char* version = hwModel->chars + strlen("MacBookAir"); if(ffStrEquals(version, "10,1")) return "MacBook Air (M1, 2020)"; if(ffStrEquals(version, "9,1")) return "MacBook Air (Retina, 13-inch, 2020)"; if(ffStrEquals(version, "8,2")) return "MacBook Air (Retina, 13-inch, 2019)"; if(ffStrEquals(version, "8,1")) return "MacBook Air (Retina, 13-inch, 2018)"; if(ffStrEquals(version, "7,2")) return "MacBook Air (13-inch, Early 2015/2017)"; if(ffStrEquals(version, "7,1")) return "MacBook Air (11-inch, Early 2015)"; if(ffStrEquals(version, "6,2")) return "MacBook Air (13-inch, Mid 2013/Early 2014)"; if(ffStrEquals(version, "6,1")) return "MacBook Air (11-inch, Mid 2013/Early 2014)"; if(ffStrEquals(version, "5,2")) return "MacBook Air (13-inch, Mid 2012)"; if(ffStrEquals(version, "5,1")) return "MacBook Air (11-inch, Mid 2012)"; if(ffStrEquals(version, "4,2")) return "MacBook Air (13-inch, Mid 2011)"; if(ffStrEquals(version, "4,1")) return "MacBook Air (11-inch, Mid 2011)"; if(ffStrEquals(version, "3,2")) return "MacBook Air (13-inch, Late 2010)"; if(ffStrEquals(version, "3,1")) return "MacBook Air (11-inch, Late 2010)"; if(ffStrEquals(version, "2,1")) return "MacBook Air (Mid 2009)"; } else if(ffStrbufStartsWithS(hwModel, "Macmini")) { const char* version = hwModel->chars + strlen("Macmini"); if(ffStrEquals(version, "9,1")) return "Mac mini (M1, 2020)"; if(ffStrEquals(version, "8,1")) return "Mac mini (2018)"; if(ffStrEquals(version, "7,1")) return "Mac mini (Mid 2014)"; if(ffStrEquals(version, "6,1") || ffStrEquals(version, "6,2")) return "Mac mini (Late 2012)"; if(ffStrEquals(version, "5,1") || ffStrEquals(version, "5,2")) return "Mac mini (Mid 2011)"; if(ffStrEquals(version, "4,1")) return "Mac mini (Mid 2010)"; if(ffStrEquals(version, "3,1")) return "Mac mini (Early/Late 2009)"; } else if(ffStrbufStartsWithS(hwModel, "MacBook")) { const char* version = hwModel->chars + strlen("MacBook"); if(ffStrEquals(version, "10,1")) return "MacBook (Retina, 12-inch, 2017)"; if(ffStrEquals(version, "9,1")) return "MacBook (Retina, 12-inch, Early 2016)"; if(ffStrEquals(version, "8,1")) return "MacBook (Retina, 12-inch, Early 2015)"; if(ffStrEquals(version, "7,1")) return "MacBook (13-inch, Mid 2010)"; if(ffStrEquals(version, "6,1")) return "MacBook (13-inch, Late 2009)"; if(ffStrEquals(version, "5,2")) return "MacBook (13-inch, Early/Mid 2009)"; } else if(ffStrbufStartsWithS(hwModel, "MacPro")) { const char* version = hwModel->chars + strlen("MacPro"); if(ffStrEquals(version, "7,1")) return "Mac Pro (2019)"; if(ffStrEquals(version, "6,1")) return "Mac Pro (Late 2013)"; if(ffStrEquals(version, "5,1")) return "Mac Pro (Mid 2010 - Mid 2012)"; if(ffStrEquals(version, "4,1")) return "Mac Pro (Early 2009)"; } else if(ffStrbufStartsWithS(hwModel, "Mac")) { const char* version = hwModel->chars + strlen("Mac"); if(ffStrEquals(version, "17,2")) return "MacBook Pro (14-inch, M5)"; if(ffStrEquals(version, "16,13")) return "MacBook Air (15-inch, M4, 2025)"; if(ffStrEquals(version, "16,12")) return "MacBook Air (13-inch, M4, 2025)"; if(ffStrEquals(version, "16,11") || ffStrEquals(version, "16,10")) return "Mac Mini (2024)"; if(ffStrEquals(version, "16,9")) return "Mac Studio (M4 Max, 2025)"; if(ffStrEquals(version, "16,3")) return "iMac (24-inch, 2024, Four Thunderbolt / USB 4 ports)"; if(ffStrEquals(version, "16,2")) return "iMac (24-inch, 2024, Two Thunderbolt / USB 4 ports)"; if(ffStrEquals(version, "16,1")) return "MacBook Pro (14-inch, 2024, Three Thunderbolt 4 ports)"; if(ffStrEquals(version, "16,6") || ffStrEquals(version, "16,8")) return "MacBook Pro (14-inch, 2024, Three Thunderbolt 5 ports)"; if(ffStrEquals(version, "16,7") || ffStrEquals(version, "16,5")) return "MacBook Pro (16-inch, 2024, Three Thunderbolt 5 ports)"; if(ffStrEquals(version, "15,14")) return "Mac Studio (M3 Ultra, 2025)"; if(ffStrEquals(version, "15,13")) return "MacBook Air (15-inch, M3, 2024)"; if(ffStrEquals(version, "15,12")) return "MacBook Air (13-inch, M3, 2024)"; if(ffStrEquals(version, "15,3")) return "MacBook Pro (14-inch, Nov 2023, Two Thunderbolt / USB 4 ports)"; if(ffStrEquals(version, "15,4")) return "iMac (24-inch, 2023, Two Thunderbolt / USB 4 ports)"; if(ffStrEquals(version, "15,5")) return "iMac (24-inch, 2023, Two Thunderbolt / USB 4 ports, Two USB 3 ports)"; if(ffStrEquals(version, "15,6") || ffStrEquals(version, "15,8") || ffStrEquals(version, "15,10")) return "MacBook Pro (14-inch, Nov 2023, Three Thunderbolt 4 ports)"; if(ffStrEquals(version, "15,7") || ffStrEquals(version, "15,9") || ffStrEquals(version, "15,11")) return "MacBook Pro (16-inch, Nov 2023, Three Thunderbolt 4 ports)"; if(ffStrEquals(version, "14,15")) return "MacBook Air (15-inch, M2, 2023)"; if(ffStrEquals(version, "14,14")) return "Mac Studio (M2 Ultra, 2023, Two Thunderbolt 4 front ports)"; if(ffStrEquals(version, "14,13")) return "Mac Studio (M2 Max, 2023, Two USB-C front ports)"; if(ffStrEquals(version, "14,8")) return "Mac Pro (2023)"; if(ffStrEquals(version, "14,6") || ffStrEquals(version, "14,10")) return "MacBook Pro (16-inch, 2023)"; if(ffStrEquals(version, "14,5") || ffStrEquals(version, "14,9")) return "MacBook Pro (14-inch, 2023)"; if(ffStrEquals(version, "14,3")) return "Mac mini (M2, 2023, Two Thunderbolt 4 ports)"; if(ffStrEquals(version, "14,12")) return "Mac mini (M2 Pro, 2023, Four Thunderbolt 4 ports)"; if(ffStrEquals(version, "14,7")) return "MacBook Pro (13-inch, M2, 2022)"; if(ffStrEquals(version, "14,2")) return "MacBook Air (M2, 2022)"; if(ffStrEquals(version, "13,1")) return "Mac Studio (M1 Max, 2022, Two USB-C front ports)"; if(ffStrEquals(version, "13,2")) return "Mac Studio (M1 Ultra, 2022, Two Thunderbolt 4 front ports)"; } else if(ffStrbufStartsWithS(hwModel, "iMac")) { const char* version = hwModel->chars + strlen("iMac"); if(ffStrEquals(version, "21,1")) return "iMac (24-inch, M1, 2021, Two Thunderbolt / USB 4 ports, Two USB 3 ports)"; if(ffStrEquals(version, "21,2")) return "iMac (24-inch, M1, 2021, Two Thunderbolt / USB 4 ports)"; if(ffStrEquals(version, "20,1") || ffStrEquals(version, "20,2")) return "iMac (Retina 5K, 27-inch, 2020)"; if(ffStrEquals(version, "19,1")) return "iMac (Retina 5K, 27-inch, 2019)"; if(ffStrEquals(version, "19,2")) return "iMac (Retina 4K, 21.5-inch, 2019)"; if(ffStrEquals(version, "Pro1,1")) return "iMac Pro (2017)"; if(ffStrEquals(version, "18,3")) return "iMac (Retina 5K, 27-inch, 2017)"; if(ffStrEquals(version, "18,2")) return "iMac (Retina 4K, 21.5-inch, 2017)"; if(ffStrEquals(version, "18,1")) return "iMac (21.5-inch, 2017)"; if(ffStrEquals(version, "17,1")) return "iMac (Retina 5K, 27-inch, Late 2015)"; if(ffStrEquals(version, "16,2")) return "iMac (Retina 4K, 21.5-inch, Late 2015)"; if(ffStrEquals(version, "16,1")) return "iMac (21.5-inch, Late 2015)"; if(ffStrEquals(version, "15,1")) return "iMac (Retina 5K, 27-inch, Late 2014 - Mid 2015)"; if(ffStrEquals(version, "14,4")) return "iMac (21.5-inch, Mid 2014)"; if(ffStrEquals(version, "14,2")) return "iMac (27-inch, Late 2013)"; if(ffStrEquals(version, "14,1")) return "iMac (21.5-inch, Late 2013)"; if(ffStrEquals(version, "13,2")) return "iMac (27-inch, Late 2012)"; if(ffStrEquals(version, "13,1")) return "iMac (21.5-inch, Late 2012)"; if(ffStrEquals(version, "12,2")) return "iMac (27-inch, Mid 2011)"; if(ffStrEquals(version, "12,1")) return "iMac (21.5-inch, Mid 2011)"; if(ffStrEquals(version, "11,3")) return "iMac (27-inch, Mid 2010)"; if(ffStrEquals(version, "11,2")) return "iMac (21.5-inch, Mid 2010)"; if(ffStrEquals(version, "10,1")) return "iMac (27/21.5-inch, Late 2009)"; if(ffStrEquals(version, "9,1")) return "iMac (24/20-inch, Early 2009)"; } return NULL; } #ifdef __x86_64__ bool ffHostDetectMac(FFHostResult* host) { if (ffStrbufStartsWithS(&host->family, "Mac") && ffStrbufEqualS(&host->vendor, "Apple Inc.")) { const char* productName = ffHostGetMacProductNameWithHwModel(&host->name); if (productName) { ffStrbufDestroy(&host->family); ffStrbufInitMove(&host->family, &host->name); ffStrbufSetStatic(&host->name, productName); return true; } } return false; } #endif ================================================ FILE: src/detection/host/host_nbsd.c ================================================ #include "host.h" #include "common/sysctl.h" #include "common/smbiosHelper.h" const char* ffDetectHost(FFHostResult* host) { const char* error = NULL; if ((error = ffSysctlGetString("machdep.dmi.system-product", &host->name))) return error; ffCleanUpSmbiosValue(&host->name); if (ffSysctlGetString("machdep.dmi.system-vendor", &host->vendor) == NULL) ffCleanUpSmbiosValue(&host->vendor); if (ffSysctlGetString("machdep.dmi.system-version", &host->version) == NULL) ffCleanUpSmbiosValue(&host->version); if (ffSysctlGetString("machdep.dmi.system-serial", &host->serial) == NULL) ffCleanUpSmbiosValue(&host->serial); if (ffSysctlGetString("machdep.dmi.system-uuid", &host->uuid) == NULL) ffCleanUpSmbiosValue(&host->uuid); return NULL; } ================================================ FILE: src/detection/host/host_nosupport.c ================================================ #include "host.h" const char* ffDetectHost(FF_MAYBE_UNUSED FFHostResult* host) { return "Not supported on this platform"; } ================================================ FILE: src/detection/host/host_obsd.c ================================================ #include "host.h" #include "common/sysctl.h" #include "common/smbiosHelper.h" const char* ffDetectHost(FFHostResult* host) { const char* error = NULL; if ((error = ffSysctlGetString(CTL_HW, HW_PRODUCT, &host->name))) return error; ffCleanUpSmbiosValue(&host->name); if (ffSysctlGetString(CTL_HW, HW_VENDOR, &host->vendor) == NULL) ffCleanUpSmbiosValue(&host->vendor); if (ffSysctlGetString(CTL_HW, HW_VERSION, &host->version) == NULL) ffCleanUpSmbiosValue(&host->version); if (ffSysctlGetString(CTL_HW, HW_SERIALNO, &host->serial) == NULL) ffCleanUpSmbiosValue(&host->serial); return NULL; } ================================================ FILE: src/detection/host/host_windows.c ================================================ #include "host.h" #include "common/smbiosHelper.h" typedef struct FFSmbiosSystemInfo { FFSmbiosHeader Header; uint8_t Manufacturer; // string uint8_t ProductName; // string uint8_t Version; // string uint8_t SerialNumber; // string // 2.1+ struct { uint32_t TimeLow; uint16_t TimeMid; uint16_t TimeHighAndVersion; uint8_t ClockSeqHiAndReserved; uint8_t ClockSeqLow; uint8_t Node[6]; } __attribute__((__packed__)) UUID; // varies uint8_t WakeUpType; // enum // 2.4+ uint8_t SKUNumber; // string uint8_t Family; // string } __attribute__((__packed__)) FFSmbiosSystemInfo; static_assert(offsetof(FFSmbiosSystemInfo, Family) == 0x1A, "FFSmbiosSystemInfo: Wrong struct alignment"); const char* ffDetectHost(FFHostResult* host) { const FFSmbiosHeaderTable* smbiosTable = ffGetSmbiosHeaderTable(); if (!smbiosTable) return "Failed to get SMBIOS data"; const FFSmbiosSystemInfo* data = (const FFSmbiosSystemInfo*) (*smbiosTable)[FF_SMBIOS_TYPE_SYSTEM_INFO]; if (!data) return "System information is not found in SMBIOS data"; const char* strings = (const char*) data + data->Header.Length; ffStrbufSetStatic(&host->vendor, ffSmbiosLocateString(strings, data->Manufacturer)); ffCleanUpSmbiosValue(&host->vendor); ffStrbufSetStatic(&host->name, ffSmbiosLocateString(strings, data->ProductName)); ffCleanUpSmbiosValue(&host->name); ffStrbufSetStatic(&host->version, ffSmbiosLocateString(strings, data->Version)); ffCleanUpSmbiosValue(&host->version); ffStrbufSetStatic(&host->serial, ffSmbiosLocateString(strings, data->SerialNumber)); ffCleanUpSmbiosValue(&host->serial); static_assert(offsetof(FFSmbiosSystemInfo, UUID) == 0x08, "FFSmbiosSystemInfo.UUID offset is wrong"); if (data->Header.Length > offsetof(FFSmbiosSystemInfo, UUID)) { ffStrbufSetF(&host->uuid, "%08X-%04X-%04X-%02X%02X-%02X%02X%02X%02X%02X%02X", data->UUID.TimeLow, data->UUID.TimeMid, data->UUID.TimeHighAndVersion, data->UUID.ClockSeqHiAndReserved, data->UUID.ClockSeqLow, data->UUID.Node[0], data->UUID.Node[1], data->UUID.Node[2], data->UUID.Node[3], data->UUID.Node[4], data->UUID.Node[5]); } static_assert(offsetof(FFSmbiosSystemInfo, SKUNumber) == 0x19, "FFSmbiosSystemInfo.SKUNumber offset is wrong"); if (data->Header.Length > offsetof(FFSmbiosSystemInfo, SKUNumber)) { ffStrbufSetStatic(&host->sku, ffSmbiosLocateString(strings, data->SKUNumber)); ffCleanUpSmbiosValue(&host->sku); } if (data->Header.Length > offsetof(FFSmbiosSystemInfo, Family)) { ffStrbufSetStatic(&host->family, ffSmbiosLocateString(strings, data->Family)); ffCleanUpSmbiosValue(&host->family); } #if _WIN64 && __x86_64__ // aarch64 also defines _WIN64 ffHostDetectMac(host); #endif return NULL; } ================================================ FILE: src/detection/icons/icons.h ================================================ #pragma once #include "fastfetch.h" #include "modules/icons/option.h" typedef struct FFIconsResult { FFstrbuf icons1; FFstrbuf icons2; } FFIconsResult; const char* ffDetectIcons(FFIconsResult* result); ================================================ FILE: src/detection/icons/icons_linux.c ================================================ #include "icons.h" #include "common/parsing.h" #include "detection/gtk_qt/gtk_qt.h" #include "detection/displayserver/displayserver.h" const char* ffDetectIcons(FFIconsResult* result) { const FFDisplayServerResult* wmde = ffConnectDisplayServer(); if(ffStrbufIgnCaseEqualS(&wmde->wmProtocolName, FF_WM_PROTOCOL_TTY)) return "Icons aren't supported in TTY"; const FFstrbuf* plasma = &ffDetectQt()->icons; const FFstrbuf* gtk2 = &ffDetectGTK2()->icons; const FFstrbuf* gtk3 = &ffDetectGTK3()->icons; const FFstrbuf* gtk4 = &ffDetectGTK4()->icons; if(plasma->length == 0 && gtk2->length == 0 && gtk3->length == 0 && gtk4->length == 0) return "No icons could be found"; ffParseGTK(&result->icons2, gtk2, gtk3, gtk4); if(plasma->length > 0) { ffStrbufAppend(&result->icons1, plasma); ffStrbufAppendS(&result->icons1, " [Qt]"); } return NULL; } ================================================ FILE: src/detection/icons/icons_nosupport.c ================================================ #include "icons.h" const char* ffDetectIcons(FF_MAYBE_UNUSED FFIconsResult* result) { return "Not supported on this platform"; } ================================================ FILE: src/detection/icons/icons_windows.c ================================================ #include "icons.h" #include "common/windows/registry.h" const char* ffDetectIcons(FFIconsResult* result) { FF_AUTO_CLOSE_FD HANDLE hKey = NULL; if(!ffRegOpenKeyForRead(HKEY_CURRENT_USER, L"Software\\Microsoft\\Windows\\CurrentVersion\\Explorer\\HideDesktopIcons\\NewStartPanel", &hKey, NULL) && !ffRegOpenKeyForRead(HKEY_CURRENT_USER, L"Software\\Microsoft\\Windows\\CurrentVersion\\Explorer\\HideDesktopIcons\\ClassicStartMenu", &hKey, NULL)) return "ffRegOpenKeyForRead(Software\\Microsoft\\Windows\\CurrentVersion\\Explorer\\HideDesktopIcons\\{NewStartPanel|ClassicStartMenu}) failed"; // Whether these icons are hidden uint32_t ThisPC = 1, UsersFiles = 1, RemoteNetwork = 1, RecycleBin = 0 /* Shown by default */, ControlPanel = 1; ffRegReadUint(hKey, L"{20D04FE0-3AEA-1069-A2D8-08002B30309D}", &ThisPC, NULL); ffRegReadUint(hKey, L"{59031a47-3f72-44a7-89c5-5595fe6b30ee}", &UsersFiles, NULL); ffRegReadUint(hKey, L"{F02C1A0D-BE21-4350-88B0-7367FC96EF3C}", &RemoteNetwork, NULL); ffRegReadUint(hKey, L"{645FF040-5081-101B-9F08-00AA002F954E}", &RecycleBin, NULL); ffRegReadUint(hKey, L"{5399E694-6CE5-4D6C-8FCE-1D8870FDCBA0}", &ControlPanel, NULL); if (ThisPC && UsersFiles && RemoteNetwork && RecycleBin && ControlPanel) return "All icons are hidden"; if (!ThisPC) ffStrbufAppendS(&result->icons1, "This PC, "); if (!UsersFiles) ffStrbufAppendS(&result->icons1, "User's Files"); ffStrbufTrimRight(&result->icons1, ' '); ffStrbufTrimRight(&result->icons1, ','); if (!RemoteNetwork) ffStrbufAppendS(&result->icons2, "Remote Network, "); if (!RecycleBin) ffStrbufAppendS(&result->icons2, "Recycle Bin, "); if (!ControlPanel) ffStrbufAppendS(&result->icons2, "Control Panel"); ffStrbufTrimRight(&result->icons2, ' '); ffStrbufTrimRight(&result->icons2, ','); return NULL; } ================================================ FILE: src/detection/initsystem/initsystem.h ================================================ #pragma once #include "fastfetch.h" #include "modules/initsystem/option.h" typedef struct FFInitSystemResult { FFstrbuf name; FFstrbuf exe; FFstrbuf version; uint32_t pid; } FFInitSystemResult; const char* ffDetectInitSystem(FFInitSystemResult* result); ================================================ FILE: src/detection/initsystem/initsystem_haiku.c ================================================ #include "initsystem.h" #include "common/stringUtils.h" #include "common/haiku/version.h" #include "common/io.h" #include #include const char* ffDetectInitSystem(FFInitSystemResult* result) { // Since it runs first, registrar does not know about it, // so we can't query be_roster for it. const char* path = "/boot/system/servers/launch_daemon"; if (!ffPathExists(path, FF_PATHTYPE_FILE)) return "launch_daemon is not found"; ffStrbufSetStatic(&result->exe, path); ffStrbufSetStatic(&result->name, "launch_daemon"); result->pid = 0; team_info teamInfo; int32 cookie = 0; while (get_next_team_info(&cookie, &teamInfo) == B_OK) { if (ffStrEquals(teamInfo.args, path)) { result->pid = (uint32_t) teamInfo.team; break; } } if (instance.config.general.detectVersion) ffGetFileVersion(path, &result->version); return NULL; } ================================================ FILE: src/detection/initsystem/initsystem_linux.c ================================================ #include "initsystem.h" #include "common/processing.h" #include "common/binary.h" #include "common/stringUtils.h" #include #include FF_MAYBE_UNUSED static bool extractSystemdVersion(const char* str, uint32_t len, void* userdata) { if (!ffStrStartsWith(str, "systemd ")) return true; const char* pstart = str + strlen("systemd "); const char* pend = memmem(pstart, len - strlen("systemd "), " running in ", strlen(" running in ")); if (!pend) return true; ffStrbufSetNS((FFstrbuf*) userdata, (uint32_t) (pend - pstart), pstart); return false; } const char* ffDetectInitSystem(FFInitSystemResult* result) { const char* error = ffProcessGetBasicInfoLinux((int) result->pid, &result->name, NULL, NULL); if (error) { #ifdef __ANDROID__ if (access("/system/bin/init", F_OK) == 0) { ffStrbufSetStatic(&result->exe, "/system/bin/init"); ffStrbufSetStatic(&result->name, "init"); return NULL; } #endif return error; } const char* _; // In linux /proc/1/exe is not readable ffProcessGetInfoLinux((int) result->pid, &result->name, &result->exe, &_, NULL); if (result->exe.chars[0] == '/') { // In some old system, /sbin/init is a symlink char buf[PATH_MAX]; if (realpath(result->exe.chars, buf)) { ffStrbufSetS(&result->exe, buf); ffStrbufSetS(&result->name, basename(result->exe.chars)); } } if (instance.config.general.detectVersion) { #if (defined(__linux__) && !defined(__ANDROID__)) || defined(__GNU__) if (ffStrbufEqualS(&result->name, "systemd")) { ffBinaryExtractStrings(result->exe.chars, extractSystemdVersion, &result->version, (uint32_t) strlen("systemd 0.0 running in x")); if (result->version.length == 0) { if (ffProcessAppendStdOut(&result->version, (char* const[]) { ffStrbufEndsWithS(&result->exe, "/systemd") ? result->exe.chars : "systemctl", // use exe path in case users have another systemd installed "--version", NULL, }) == NULL && result->version.length) { uint32_t iStart = ffStrbufFirstIndexC(&result->version, '('); if (iStart < result->version.length) { uint32_t iEnd = ffStrbufNextIndexC(&result->version, iStart + 1, ')'); ffStrbufSubstrBefore(&result->version, iEnd); ffStrbufSubstrAfter(&result->version, iStart); } } } } else if (ffStrbufEqualS(&result->name, "dinit")) { if (ffProcessAppendStdOut(&result->version, (char* const[]) { ffStrbufEndsWithS(&result->exe, "/dinit") ? result->exe.chars : "dinit", "--version", NULL, }) == NULL && result->version.length) { // Dinit version 0.18.0. ffStrbufSubstrBeforeFirstC(&result->version, '\n'); ffStrbufTrimRight(&result->version, '.'); ffStrbufSubstrAfterLastC(&result->version, ' '); } } else if (ffStrbufEqualS(&result->name, "shepherd")) { if (ffProcessAppendStdOut(&result->version, (char* const[]) { ffStrbufEndsWithS(&result->exe, "/shepherd") ? result->exe.chars : "shepherd", "--version", NULL, }) == NULL && result->version.length) { // shepherd (GNU Shepherd) 1.0.6 // The first line in the output might not contain the version if (!ffStrbufStartsWithS(&result->version, "shepherd")) ffStrbufSubstrAfterFirstC(&result->version, '\n'); ffStrbufSubstrBeforeFirstC(&result->version, '\n'); ffStrbufSubstrAfterLastC(&result->version, ' '); } } #elif __APPLE__ if (ffStrbufEqualS(&result->name, "launchd")) { if (ffProcessAppendStdOut(&result->version, (char* const[]) { "/bin/launchctl", "version", NULL, }) == NULL && result->version.length) { uint32_t iStart = ffStrbufFirstIndexS(&result->version, "Version "); if (iStart < result->version.length) { iStart += (uint32_t) strlen("Version"); uint32_t iEnd = ffStrbufNextIndexC(&result->version, iStart + 1, ':'); ffStrbufSubstrBefore(&result->version, iEnd); ffStrbufSubstrAfter(&result->version, iStart); } } } #endif } return NULL; } ================================================ FILE: src/detection/initsystem/initsystem_nosupport.c ================================================ #include "initsystem.h" const char* ffDetectInitSystem(FF_MAYBE_UNUSED FFInitSystemResult* result) { return "Not supported on this platform"; } ================================================ FILE: src/detection/keyboard/keyboard.h ================================================ #include "fastfetch.h" typedef struct FFKeyboardDevice { FFstrbuf serial; FFstrbuf name; } FFKeyboardDevice; const char* ffDetectKeyboard(FFlist* devices /* List of FFKeyboardDevice */); ================================================ FILE: src/detection/keyboard/keyboard_apple.c ================================================ #include "keyboard.h" #include "common/apple/cf_helpers.h" #include "common/mallocHelper.h" #include #include static void enumSet(IOHIDDeviceRef value, FFlist* results) { FFKeyboardDevice* device = (FFKeyboardDevice*) ffListAdd(results); ffStrbufInit(&device->serial); ffStrbufInit(&device->name); CFStringRef product = IOHIDDeviceGetProperty(value, CFSTR(kIOHIDProductKey)); ffCfStrGetString(product, &device->name); CFStringRef serialNumber = IOHIDDeviceGetProperty(value, CFSTR(kIOHIDSerialNumberKey)); ffCfStrGetString(serialNumber, &device->serial); } const char* ffDetectKeyboard(FFlist* devices /* List of FFKeyboardDevice */) { IOHIDManagerRef FF_CFTYPE_AUTO_RELEASE manager = IOHIDManagerCreate(kCFAllocatorDefault, kIOHIDOptionsTypeNone); if (IOHIDManagerOpen(manager, kIOHIDOptionsTypeNone) != kIOReturnSuccess) return "IOHIDManagerOpen() failed"; CFDictionaryRef FF_CFTYPE_AUTO_RELEASE matching1 = CFDictionaryCreate(kCFAllocatorDefault, (const void **)(CFStringRef[]){ CFSTR(kIOHIDDeviceUsagePageKey), CFSTR(kIOHIDDeviceUsageKey) }, (const void **)(CFNumberRef[]){ ffCfCreateInt(kHIDPage_GenericDesktop), ffCfCreateInt(kHIDUsage_GD_Keyboard) }, 2, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks); IOHIDManagerSetDeviceMatching(manager, matching1); CFSetRef FF_CFTYPE_AUTO_RELEASE set = IOHIDManagerCopyDevices(manager); if (set) CFSetApplyFunction(set, (CFSetApplierFunction) &enumSet, devices); IOHIDManagerClose(manager, kIOHIDOptionsTypeNone); return NULL; } ================================================ FILE: src/detection/keyboard/keyboard_bsd.c ================================================ #include "keyboard.h" #include "common/io.h" #include #include #include #include #if __has_include() #include // FreeBSD #else #include // DragonFly #endif static const char* detectByIoctl(FFlist* devices) { keyboard_info_t kbdInfo; if (ioctl(STDIN_FILENO, KDGKBINFO, &kbdInfo) != 0) return "ioctl(KDGKBINFO) failed"; FFKeyboardDevice* device = (FFKeyboardDevice*) ffListAdd(devices); switch (kbdInfo.kb_type) { case KB_84: ffStrbufInitS(&device->name, "AT 84-key keyboard"); break; case KB_101: ffStrbufInitS(&device->name, "AT 101/102-key keyboard"); break; default: ffStrbufInitS(&device->name, "Unknown keyboard"); break; } ffStrbufAppendF(&device->name, " (kbd%d)", kbdInfo.kb_index); ffStrbufInit(&device->serial); return NULL; } #define MAX_UHID_KBDS 64 static const char* detectByUsbhid(FFlist* devices) { char path[16]; for (int i = 0; i < MAX_UHID_KBDS; i++) { snprintf(path, ARRAY_SIZE(path), "/dev/uhid%d", i); FF_AUTO_CLOSE_FD int fd = open(path, O_RDONLY | O_CLOEXEC); if (fd < 0) { if (errno == ENOENT) break; // No more devices continue; // Device not found } report_desc_t repDesc = hid_get_report_desc(fd); if (!repDesc) continue; int reportId = hid_get_report_id(fd); struct hid_data* hData = hid_start_parse(repDesc, 0, reportId); if (hData) { struct hid_item hItem; while (hid_get_item(hData, &hItem) > 0) { if (HID_PAGE(hItem.usage) != 1 || HID_USAGE(hItem.usage) != 6) continue; struct usb_device_info di; if (ioctl(fd, USB_GET_DEVICEINFO, &di) != -1) { FFKeyboardDevice* device = (FFKeyboardDevice*) ffListAdd(devices); ffStrbufInitS(&device->serial, di.udi_serial); ffStrbufInitS(&device->name, di.udi_product); } } hid_end_parse(hData); } hid_dispose_report_desc(repDesc); } return NULL; } const char* ffDetectKeyboard(FFlist* devices /* List of FFKeyboardDevice */) { detectByUsbhid(devices); if (devices->length > 0) return NULL; return detectByIoctl(devices); } ================================================ FILE: src/detection/keyboard/keyboard_haiku.cpp ================================================ extern "C" { #include "keyboard.h" } #include #include const char* ffDetectKeyboard(FFlist* devices /* List of FFKeyboardDevice */) { BList list; if (get_input_devices(&list) != B_OK) return "get_input_devices() failed"; for (int32 i = 0, n = list.CountItems(); i < n; i++) { BInputDevice *device = (BInputDevice *) list.ItemAt(i); if (device->Type() != B_KEYBOARD_DEVICE || !device->IsRunning()) continue; FFKeyboardDevice* item = (FFKeyboardDevice*) ffListAdd(devices); ffStrbufInit(&item->serial); ffStrbufInitS(&item->name, device->Name()); } return NULL; } ================================================ FILE: src/detection/keyboard/keyboard_linux.c ================================================ #include "keyboard.h" #include "common/io.h" #include "common/stringUtils.h" #include const char* ffDetectKeyboard(FFlist* devices /* List of FFKeyboardDevice */) { // Parse /proc/bus/input/devices to find keyboards with a "kbd" handler. // This detects both wired and Bluetooth keyboards uniformly. FF_STRBUF_AUTO_DESTROY content = ffStrbufCreate(); if (!ffAppendFileBuffer("/proc/bus/input/devices", &content)) return "ffAppendFileBuffer(\"/proc/bus/input/devices\") == NULL"; FFstrbuf kbd = ffStrbufCreateStatic("kbd"); FFKeyboardDevice device = { .name = ffStrbufCreate(), .serial = ffStrbufCreate() }; char* line = NULL; size_t len = 0; while (ffStrbufGetline(&line, &len, &content)) { switch (line[0]) { case 'N': { const uint32_t prefixLen = strlen("N: Name="); if (__builtin_expect(len <= prefixLen, false)) continue; const char* name = line + prefixLen; const uint32_t nameLen = (uint32_t) len - prefixLen; ffStrbufSetNS(&device.name, nameLen, name); ffStrbufTrim(&device.name, '"'); continue; } case 'H': { const uint32_t prefixLen = strlen("H: Handlers="); if (__builtin_expect(len <= prefixLen, false)) continue; const char* handlers = line + prefixLen; const uint32_t handlersLen = (uint32_t) len - prefixLen; if (!ffStrbufMatchSeparatedNS(&kbd, handlersLen, handlers, ' ')) goto skipDevice; continue; } case 'B': { const char* bits = line + strlen("B: "); if (ffStrStartsWith(bits, "EV=")) { // Check EV_REP (auto-repeat, bit 20) to filter pseudo-keyboards (Power Button, PC Speaker). const char* evBits = bits + strlen("EV="); uint64_t val = strtoull(evBits, NULL, 16); if (!(val & (1ULL << EV_REP))) goto skipDevice; } else if (ffStrStartsWith(bits, "KEY=")) { // Check KEY_A (bit 30) to filter media remotes and headset controls. // The key capability bitmap is space-separated hex longs, MSB first; // KEY_A falls in the last (least significant) word on all architectures. const char* keyBits = bits + strlen("KEY="); const char* lastWord = memrchr(keyBits, ' ', len - (size_t) (keyBits - line)); lastWord = lastWord ? lastWord + 1 : keyBits; uint64_t val = strtoull(lastWord, NULL, 16); if (!(val & (1ULL << KEY_A))) goto skipDevice; } continue; } case 'U': { const uint32_t prefixLen = strlen("U: Uniq="); if (__builtin_expect(len <= prefixLen, false)) continue; const char* uniq = line + prefixLen; const uint32_t uniqLen = (uint32_t) len - prefixLen; ffStrbufSetNS(&device.serial, uniqLen, uniq); continue; } case '\0': // End of device entry; add to list if it has a name. if (device.name.length > 0) { FFKeyboardDevice* added = (FFKeyboardDevice*) ffListAdd(devices); ffStrbufInitMove(&added->name, &device.name); ffStrbufInitMove(&added->serial, &device.serial); } continue; default: continue; } skipDevice: // Skip to the end of the current device entry. while (line[0] != '\0' && ffStrbufGetline(&line, &len, &content)) ; // Despite the fn name, it resets the string buffer to initial state ffStrbufDestroy(&device.name); ffStrbufDestroy(&device.serial); } return NULL; } ================================================ FILE: src/detection/keyboard/keyboard_nosupport.c ================================================ #include "keyboard.h" const char* ffDetectKeyboard(FF_MAYBE_UNUSED FFlist* devices /* List of FFKeyboardDevice */) { return "No mouse support on this platform"; } ================================================ FILE: src/detection/keyboard/keyboard_windows.c ================================================ #define INITGUID #include "keyboard.h" #include "common/io.h" #include "common/mallocHelper.h" #include "common/windows/unicode.h" #include #include #include #include const char* ffDetectKeyboard(FFlist* devices /* List of FFKeyboardDevice */) { UINT nDevices = 0; if (GetRawInputDeviceList(NULL, &nDevices, sizeof(RAWINPUTDEVICELIST))) return "GetRawInputDeviceList(NULL) failed"; if (nDevices == 0) return "No HID devices found"; RAWINPUTDEVICELIST* FF_AUTO_FREE pRawInputDeviceList = (RAWINPUTDEVICELIST*) malloc(sizeof(RAWINPUTDEVICELIST) * nDevices); if ((nDevices = GetRawInputDeviceList(pRawInputDeviceList, &nDevices, sizeof(RAWINPUTDEVICELIST))) == (UINT) -1) return "GetRawInputDeviceList(pRawInputDeviceList) failed"; for (UINT i = 0; i < nDevices; ++i) { if (pRawInputDeviceList[i].dwType != RIM_TYPEKEYBOARD) continue; HANDLE hDevice = pRawInputDeviceList[i].hDevice; RID_DEVICE_INFO rdi; UINT rdiSize = sizeof(rdi); if (GetRawInputDeviceInfoW(hDevice, RIDI_DEVICEINFO, &rdi, &rdiSize) == (UINT) -1) continue; WCHAR devName[MAX_PATH]; UINT nameSize = MAX_PATH; if (GetRawInputDeviceInfoW(hDevice, RIDI_DEVICENAME, devName, &nameSize) == (UINT) -1) continue; FFKeyboardDevice* device = (FFKeyboardDevice*) ffListAdd(devices); ffStrbufInit(&device->serial); ffStrbufInit(&device->name); wchar_t buffer[MAX_PATH]; HANDLE FF_AUTO_CLOSE_FD hHidFile = CreateFileW(devName, 0 /* must be 0 instead of GENERIC_READ */, FILE_SHARE_READ, NULL, OPEN_EXISTING, 0, NULL); if (hHidFile != INVALID_HANDLE_VALUE) { if (HidD_GetProductString(hHidFile, buffer, (ULONG) sizeof(buffer))) ffStrbufSetWS(&device->name, buffer); if (HidD_GetSerialNumberString(hHidFile, buffer, sizeof(buffer))) ffStrbufSetWS(&device->serial, buffer); } if (!device->name.length) { // https://stackoverflow.com/a/64321096/9976392 DEVPROPTYPE propertyType; ULONG propertySize = sizeof(buffer); if (CM_Get_Device_Interface_PropertyW(devName, &DEVPKEY_Device_InstanceId, &propertyType, (PBYTE) buffer, &propertySize, 0) == CR_SUCCESS) { DEVINST devInst; if (CM_Locate_DevNodeW(&devInst, buffer, CM_LOCATE_DEVNODE_NORMAL) == CR_SUCCESS) { propertySize = sizeof(buffer); if (CM_Get_DevNode_PropertyW(devInst, &DEVPKEY_NAME, &propertyType, (PBYTE) buffer, &propertySize, 0) == CR_SUCCESS) ffStrbufSetWS(&device->name, buffer); } } } if (!device->name.length) ffStrbufSetF(&device->name, "Unknown device %04X-%04X", (unsigned) rdi.hid.dwVendorId, (unsigned) rdi.hid.dwProductId); } return NULL; } ================================================ FILE: src/detection/libc/libc.h ================================================ #pragma once #include "fastfetch.h" typedef struct FFLibcResult { const char* name; const char* version; } FFLibcResult; const char* ffDetectLibc(FFLibcResult* result); ================================================ FILE: src/detection/libc/libc_android.c ================================================ #include "libc.h" #define FF_STR_INDIR(x) #x #define FF_STR(x) FF_STR_INDIR(x) #include const char* ffDetectLibc(FFLibcResult* result) { #if __ANDROID_NDK__ result->name = "ndk-bionic"; result->version = FF_STR(__NDK_MAJOR__) "." FF_STR(__NDK_MINOR__) "." FF_STR(__NDK_BUILD__) #if __NDK_BETA__ "-beta" FF_STR(__NDK_BETA__) #elif __NDK_CANARY__ "-canary" #endif ; return NULL; #else return "Unknown Android libc"; #endif } ================================================ FILE: src/detection/libc/libc_apple.c ================================================ #include "libc.h" const char* ffDetectLibc(FFLibcResult* result) { result->name = "libSystem"; #ifdef FF_LIBSYSTEM_VERSION result->version = FF_LIBSYSTEM_VERSION; #else result->version = NULL; #endif return NULL; } ================================================ FILE: src/detection/libc/libc_bsd.c ================================================ #include "libc.h" const char* ffDetectLibc(FFLibcResult* result) { result->name = "Unknown"; result->version = NULL; #ifdef __DragonFly__ // We define `__FreeBSD__` on DragonFly BSD for simplification result->name = "DF"; #ifdef FF_DF_VERSION result->version = FF_DF_VERSION; #endif #elif __FreeBSD__ result->name = "FBSD"; #ifdef FF_FBSD_VERSION result->version = FF_FBSD_VERSION; #endif #endif return NULL; } ================================================ FILE: src/detection/libc/libc_linux.c ================================================ #include "libc.h" #define FF_STR_INDIR(x) #x #define FF_STR(x) FF_STR_INDIR(x) #include const char* ffDetectLibc(FFLibcResult* result) { #ifdef __UCLIBC__ result->name = "uClibc"; result->version = FF_STR(__UCLIBC_MAJOR__) "." FF_STR(__UCLIBC_MINOR__) "." FF_STR(__UCLIBC_SUBLEVEL__); #elif defined(__GNU_LIBRARY__) result->name = "glibc"; result->version = FF_STR(__GLIBC__) "." FF_STR(__GLIBC_MINOR__); #else result->name = "musl"; #ifdef FF_MUSL_VERSION result->version = FF_MUSL_VERSION; #else result->version = NULL; #endif #endif return NULL; } ================================================ FILE: src/detection/libc/libc_nosupport.c ================================================ #include "libc.h" const char* ffDetectLibc(FFLibcResult* result) { result->name = "Unknown"; result->version = NULL; return NULL; } ================================================ FILE: src/detection/libc/libc_windows.cpp ================================================ extern "C" { #include "libc.h" } #ifdef __MINGW32__ #include <_mingw.h> #endif template class version_t { constexpr static auto buflen() noexcept { unsigned int len = 2; // "." if (Major == 0) len++; else for (auto n = Major; n; len++, n /= 10); if (Minor == 0) len++; else for (auto n = Minor; n; len++, n /= 10); return len; } char buf[buflen()] = {}; public: constexpr version_t() noexcept { auto ptr = buf + buflen(); *--ptr = '\0'; if (Minor == 0) { *--ptr = '0'; } else { for (auto n = Minor; n; n /= 10) *--ptr = "0123456789"[n % 10]; } *--ptr = '.'; if (Major == 0) { *--ptr = '0'; } else { for (auto n = Major; n; n /= 10) *--ptr = "0123456789"[n % 10]; } } constexpr operator const char *() const { return buf; } }; template constexpr version_t version; extern "C" const char* ffDetectLibc(FFLibcResult* result) { #ifdef _UCRT result->name = "ucrt"; #else result->name = "msvcrt"; #endif result->version = version<(__MSVCRT_VERSION__ >> 8), (__MSVCRT_VERSION__ & 8)>; return NULL; } ================================================ FILE: src/detection/lm/lm.h ================================================ #pragma once #include "fastfetch.h" #include "modules/lm/option.h" typedef struct FFLMResult { FFstrbuf service; FFstrbuf type; FFstrbuf version; } FFLMResult; const char* ffDetectLM(FFLMResult* result); ================================================ FILE: src/detection/lm/lm_linux.c ================================================ #include "lm.h" #include "common/properties.h" #include "common/dbus.h" #include "common/processing.h" #include "detection/displayserver/displayserver.h" #include #define FF_SYSTEMD_SESSIONS_PATH "/run/systemd/sessions/" #define FF_SYSTEMD_USERS_PATH "/run/systemd/users/" static const char* getGdmVersion(FFstrbuf* version) { const char* error = ffProcessAppendStdOut(version, (char* const[]) { "gdm", "--version", NULL }); if (error || version->length == 0) { error = ffProcessAppendStdOut(version, (char* const[]) { "gdm3", "--version", NULL }); if (error || version->length == 0) return "Failed to get GDM version"; } // GDM 44.1 ffStrbufSubstrAfterFirstC(version, ' '); return NULL; } static const char* getSshdVersion(FFstrbuf* version) { const char* error = ffProcessAppendStdErr(version, (char* const[]) { "sshd", "-V", NULL }); if (error) return error; // OpenSSH_9.0p1, OpenSSL 3.0.9 30 May 2023... ffStrbufSubstrBeforeFirstC(version, ','); ffStrbufSubstrAfterFirstC(version, '_'); return NULL; } #ifdef FF_HAVE_ZLIB #include "common/library.h" #include #include static const char* getSddmVersion(FFstrbuf* version) { FF_LIBRARY_LOAD_MESSAGE(zlib, "libz" FF_LIBRARY_EXTENSION, 2) FF_LIBRARY_LOAD_SYMBOL_MESSAGE(zlib, gzopen) FF_LIBRARY_LOAD_SYMBOL_MESSAGE(zlib, gzread) FF_LIBRARY_LOAD_SYMBOL_MESSAGE(zlib, gzerror) FF_LIBRARY_LOAD_SYMBOL_MESSAGE(zlib, gztell) FF_LIBRARY_LOAD_SYMBOL_MESSAGE(zlib, gzrewind) FF_LIBRARY_LOAD_SYMBOL_MESSAGE(zlib, gzclose) gzFile file = ffgzopen(FASTFETCH_TARGET_DIR_USR "/share/man/man1/sddm.1.gz", "rb"); if (file == Z_NULL) return "ffgzopen(\"/usr/share/man/man1/sddm.1.gz\", \"rb\") failed"; ffStrbufEnsureFree(version, 2047); memset(version->chars, 0, version->allocated); int size = ffgzread(file, version->chars, version->allocated - 1); ffgzclose(file); if (size <= 0) return "ffgzread(file, version->chars, version->length) failed"; version->length = (uint32_t) size; uint32_t index = ffStrbufFirstIndexS(version, ".TH "); if (index == version->length) { ffStrbufClear(version); return ".TH is not found"; } ffStrbufSubstrBefore(version, ffStrbufNextIndexC(version, index, '\n')); ffStrbufSubstrAfter(version, index + (uint32_t) strlen(".TH ")); // "SDDM" 1 "May 2014" "sddm 0.20.0" "sddm" ffStrbufSubstrBeforeLastC(version, ' '); ffStrbufTrimRight(version, '"'); ffStrbufSubstrAfterLastC(version, ' '); return NULL; } #else static const char* getSddmVersion(FF_MAYBE_UNUSED FFstrbuf* version) { return "Fastfetch is built without libz support"; } #endif static const char* getXfwmVersion(FFstrbuf* version) { const char* error = ffProcessAppendStdOut(version, (char* const[]) { "xfwm4", "--version", NULL }); if (error) return error; // This is xfwm4 version 4.18.0 (revision 7e7473c5b) for Xfce 4.18... ffStrbufSubstrAfterFirstS(version, "version "); ffStrbufSubstrBeforeFirstC(version, ' '); return NULL; } static const char* getLightdmVersion(FFstrbuf* version) { const char* error = ffProcessAppendStdErr(version, (char* const[]) { "lightdm", "--version", NULL }); if (error) return error; // lightdm 1.30.0 ffStrbufSubstrAfterFirstC(version, ' '); ffStrbufTrimRight(version, '\n'); return NULL; } const char* ffDetectLM(FFLMResult* result) { FF_STRBUF_AUTO_DESTROY path = ffStrbufCreate(); FF_STRBUF_AUTO_DESTROY sessionId = ffStrbufCreateS(getenv("XDG_SESSION_ID")); if (sessionId.length == 0) { // On some incorrectly configured systems, $XDG_SESSION_ID is not set. Try finding it ourself // WARNING: This is private data. Do not parse ffStrbufSetF(&path, FF_SYSTEMD_USERS_PATH "%d", instance.state.platform.uid); // This is actually buggy, and assumes current user is using DE // `sd_pid_get_session` can be a better option, but we need to find a pid to use if (!ffParsePropFileValues(path.chars, 1, (FFpropquery[]) { {"DISPLAY=", &sessionId}, })) return "Failed to get $XDG_SESSION_ID"; } ffStrbufSetS(&path, FF_SYSTEMD_SESSIONS_PATH); ffStrbufAppend(&path, &sessionId); // WARNING: This is private data. Do not parse if (!ffParsePropFileValues(path.chars, 2, (FFpropquery[]) { {"SERVICE=", &result->service}, {"TYPE=", &result->type}, })) return "Failed to parse " FF_SYSTEMD_SESSIONS_PATH "$XDG_SESSION_ID"; if (instance.config.general.detectVersion) { if (ffStrbufStartsWithS(&result->service, "gdm")) getGdmVersion(&result->version); else if (ffStrbufStartsWithS(&result->service, "sddm")) getSddmVersion(&result->version); else if (ffStrbufStartsWithS(&result->service, "xfwm")) getXfwmVersion(&result->version); else if (ffStrbufStartsWithS(&result->service, "lightdm")) getLightdmVersion(&result->version); else if (ffStrbufStartsWithS(&result->service, "sshd")) getSshdVersion(&result->version); } // Correct char cases if (ffStrbufIgnCaseEqualS(&result->type, FF_WM_PROTOCOL_WAYLAND)) ffStrbufSetS(&result->type, FF_WM_PROTOCOL_WAYLAND); else if (ffStrbufIgnCaseEqualS(&result->type, FF_WM_PROTOCOL_X11)) ffStrbufSetS(&result->type, FF_WM_PROTOCOL_X11); else if (ffStrbufIgnCaseEqualS(&result->type, FF_WM_PROTOCOL_TTY)) ffStrbufSetS(&result->type, FF_WM_PROTOCOL_TTY); return NULL; } ================================================ FILE: src/detection/lm/lm_nosupport.c ================================================ #include "lm.h" const char* ffDetectLM(FF_MAYBE_UNUSED FFLMResult* result) { return "Not supported on this platform"; } ================================================ FILE: src/detection/loadavg/loadavg.h ================================================ #pragma once #include "fastfetch.h" const char* ffDetectLoadavg(double result[3]); ================================================ FILE: src/detection/loadavg/loadavg_bsd.c ================================================ #include "detection/loadavg/loadavg.h" #include #if __FreeBSD__ || __OpenBSD__ || __NetBSD__ #include #include #if __FreeBSD__ #include #endif #endif const char* ffDetectLoadavg(double result[3]) { struct loadavg load; size_t size = sizeof(load); if (sysctl((int []) { CTL_VM, VM_LOADAVG }, 2, &load, &size, NULL, 0) < 0) return "sysctl({CTL_VM, VM_LOADAVG}) failed"; for (int i = 0; i < 3; i++) result[i] = (double) load.ldavg[i] / (double) load.fscale; return NULL; } ================================================ FILE: src/detection/loadavg/loadavg_linux.c ================================================ #include "detection/loadavg/loadavg.h" #include "common/io.h" #include const char* ffDetectLoadavg(double result[3]) { #ifndef __ANDROID__ // cat: /proc/loadavg: Permission denied // Don't use syscall for container compatibility. #620 char buf[64]; ssize_t nRead = ffReadFileData("/proc/loadavg", sizeof(buf) - 1, buf); if (nRead > 0) { buf[nRead] = '\0'; if (sscanf(buf, "%lf%lf%lf", &result[0], &result[1], &result[2]) == 3) return NULL; } #endif #ifndef __GNU__ // getloadavg requires higher ANDROID_API version struct sysinfo si; if (sysinfo(&si) < 0) return "sysinfo() failed"; for (int i = 0; i < 3; i++) result[i] = (double) si.loads[i] / (1 << SI_LOAD_SHIFT); #endif return NULL; } ================================================ FILE: src/detection/loadavg/loadavg_nosupport.c ================================================ #include "detection/loadavg/loadavg.h" const char* ffDetectLoadavg(FF_MAYBE_UNUSED double result[3]) { return "Not supported on this platform"; } ================================================ FILE: src/detection/loadavg/loadavg_sunos.c ================================================ #include "detection/loadavg/loadavg.h" #include const char* ffDetectLoadavg(double result[3]) { return getloadavg(result, 3) == 3 ? NULL : "getloadavg() failed"; } ================================================ FILE: src/detection/locale/locale.h ================================================ #pragma once #include "fastfetch.h" const char* ffDetectLocale(FFstrbuf* result); ================================================ FILE: src/detection/locale/locale_linux.c ================================================ #include "detection/locale/locale.h" #include const char* ffDetectLocale(FFstrbuf* result) { ffStrbufAppendS(result, getenv("LC_ALL")); if(result->length > 0) return NULL; ffStrbufAppendS(result, getenv("LANG")); if(result->length > 0) return NULL; ffStrbufAppendS(result, setlocale(LC_TIME, NULL)); if(result->length > 0) return NULL; return "Failed to detect locale"; } ================================================ FILE: src/detection/locale/locale_windows.c ================================================ #include "detection/locale/locale.h" #include "common/windows/unicode.h" #include const char* ffDetectLocale(FFstrbuf* result) { wchar_t name[LOCALE_NAME_MAX_LENGTH]; int size = GetUserDefaultLocaleName(name, LOCALE_NAME_MAX_LENGTH); if (size <= 1) // including '\0' return "GetUserDefaultLocaleName() failed"; ffStrbufSetNWS(result, (uint32_t)size - 1, name); return NULL; } ================================================ FILE: src/detection/localip/localip.h ================================================ #pragma once #include "fastfetch.h" #include "modules/localip/option.h" #ifndef IN6_IS_ADDR_GLOBAL /* Global Unicast: 2000::/3 (001...) */ #define IN6_IS_ADDR_GLOBAL(a) (((a)->s6_addr[0] & 0xE0) == 0x20) #endif #ifndef IN6_IS_ADDR_UNIQUE_LOCAL /* Unique Local: fc00::/7 (1111 110...) */ #define IN6_IS_ADDR_UNIQUE_LOCAL(a) (((a)->s6_addr[0] & 0xFE) == 0xFC) #endif #ifndef IN6_IS_ADDR_LINKLOCAL /* Link-Local: fe80::/10 (1111 1110 10...) */ #define IN6_IS_ADDR_LINKLOCAL(a) (((a)->s6_addr[0] == 0xFE) && (((a)->s6_addr[1] & 0xC0) == 0x80)) #endif typedef struct FFLocalIpResult { FFstrbuf name; FFstrbuf ipv4; FFstrbuf ipv6; FFstrbuf mac; FFstrbuf flags; int32_t mtu; int32_t speed; // in Mbps FFLocalIpType defaultRoute; } FFLocalIpResult; typedef struct FFLocalIpNIFlag { uint32_t flag; const char *name; } FFLocalIpNIFlag; static inline void ffLocalIpFillNIFlags(FFstrbuf *buf, uint64_t flag, const FFLocalIpNIFlag names[]) { for (const FFLocalIpNIFlag *nf = names; flag && nf->name; ++nf) { if (flag & nf->flag) { if (buf->length > 0) ffStrbufAppendC(buf, ','); ffStrbufAppendS(buf, nf->name); flag &= ~nf->flag; } } } const char* ffDetectLocalIps(const FFLocalIpOptions* options, FFlist* results); ================================================ FILE: src/detection/localip/localip_linux.c ================================================ #include "localip.h" #include "common/io.h" #include "common/netif.h" #include "common/stringUtils.h" #include "common/debug.h" #include #include #include #include #include #include #include #include #include #include #ifdef __linux__ #include #include #include #include #endif #if __has_include() #include #endif #if defined(__FreeBSD__) || defined(__OpenBSD__) || defined(__APPLE__) || defined(__NetBSD__) || defined(__HAIKU__) #include #include #elif !defined(__GNU__) #include #endif #if defined(__sun) || defined(__HAIKU__) #include #endif #if defined(__sun) #include static inline void kstatFreeWrap(kstat_ctl_t** pkc) { assert(pkc); if (*pkc) kstat_close(*pkc); } #endif #define FF_LOCALIP_NIFLAG(name) { IFF_##name, #name } static const FFLocalIpNIFlag niFlagOptions[] = { FF_LOCALIP_NIFLAG(UP), FF_LOCALIP_NIFLAG(BROADCAST), #ifdef IFF_DEBUG FF_LOCALIP_NIFLAG(DEBUG), #endif FF_LOCALIP_NIFLAG(LOOPBACK), FF_LOCALIP_NIFLAG(POINTOPOINT), #ifdef IFF_RUNNING FF_LOCALIP_NIFLAG(RUNNING), #endif FF_LOCALIP_NIFLAG(NOARP), FF_LOCALIP_NIFLAG(PROMISC), FF_LOCALIP_NIFLAG(ALLMULTI), #ifdef IFF_INTELLIGENT FF_LOCALIP_NIFLAG(INTELLIGENT), #endif FF_LOCALIP_NIFLAG(MULTICAST), #ifdef IFF_NOTRAILERS FF_LOCALIP_NIFLAG(NOTRAILERS), #endif #if defined( __linux__) || defined (__GNU__) FF_LOCALIP_NIFLAG(MASTER), FF_LOCALIP_NIFLAG(SLAVE), FF_LOCALIP_NIFLAG(PORTSEL), FF_LOCALIP_NIFLAG(AUTOMEDIA), FF_LOCALIP_NIFLAG(DYNAMIC), #endif #ifdef __linux__ FF_LOCALIP_NIFLAG(LOWER_UP), FF_LOCALIP_NIFLAG(DORMANT), FF_LOCALIP_NIFLAG(ECHO), #endif #if defined(__FreeBSD__) || defined(__APPLE__) || defined(__OpenBSD__) || defined(__NetBSD__) FF_LOCALIP_NIFLAG(OACTIVE), FF_LOCALIP_NIFLAG(SIMPLEX), FF_LOCALIP_NIFLAG(LINK0), FF_LOCALIP_NIFLAG(LINK1), FF_LOCALIP_NIFLAG(LINK2), #endif #ifdef IFF_ALTPHYS FF_LOCALIP_NIFLAG(ALTPHYS), #endif #ifdef IFF_CANTCONFIG FF_LOCALIP_NIFLAG(CANTCONFIG), #endif #ifdef __HAIKU__ FF_LOCALIP_NIFLAG(AUTOUP), FF_LOCALIP_NIFLAG(SIMPLEX), FF_LOCALIP_NIFLAG(LINK), FF_LOCALIP_NIFLAG(AUTO_CONFIGURED), FF_LOCALIP_NIFLAG(CONFIGURING), #endif #ifdef __sun FF_LOCALIP_NIFLAG(MULTI_BCAST), FF_LOCALIP_NIFLAG(UNNUMBERED), FF_LOCALIP_NIFLAG(DHCPRUNNING), FF_LOCALIP_NIFLAG(PRIVATE), #endif // sentinel {}, }; static FFLocalIpIpv6Type getIpv6Type(struct ifaddrs* ifa) { struct sockaddr_in6* addr = (struct sockaddr_in6*) ifa->ifa_addr; FF_DEBUG("Checking IPv6 type for interface %s", ifa->ifa_name); FFLocalIpIpv6Type result = FF_LOCALIP_IPV6_TYPE_NONE; if (IN6_IS_ADDR_GLOBAL(&addr->sin6_addr)) { result = FF_LOCALIP_IPV6_TYPE_GUA_BIT; FF_DEBUG("Interface %s has Global Unicast Address", ifa->ifa_name); } else if (IN6_IS_ADDR_UNIQUE_LOCAL(&addr->sin6_addr)) { result = FF_LOCALIP_IPV6_TYPE_ULA_BIT; FF_DEBUG("Interface %s has Unique Local Address", ifa->ifa_name); } else if (IN6_IS_ADDR_LINKLOCAL(&addr->sin6_addr)) { result = FF_LOCALIP_IPV6_TYPE_LLA_BIT; FF_DEBUG("Interface %s has Link-Local Address", ifa->ifa_name); } else { FF_DEBUG("Interface %s has unknown IPv6 address type", ifa->ifa_name); return FF_LOCALIP_IPV6_TYPE_UNKNOWN_BIT; } #ifdef SIOCGIFAFLAG_IN6 static int sockfd = 0; if (sockfd == 0) { sockfd = socket(AF_INET6, SOCK_DGRAM #ifdef SOCK_CLOEXEC | SOCK_CLOEXEC #endif , 0); #ifndef SOCK_CLOEXEC if (sockfd > 0) fcntl(sockfd, F_SETFD, FD_CLOEXEC); #endif } if (sockfd < 0) return result; struct in6_ifreq ifr6 = {}; ffStrCopy(ifr6.ifr_name, ifa->ifa_name, IFNAMSIZ); ifr6.ifr_addr = *addr; if (ioctl(sockfd, SIOCGIFAFLAG_IN6, &ifr6) != 0) return result; #ifdef IN6_IFF_PREFER_SOURCE if (ifr6.ifr_ifru.ifru_flags6 & IN6_IFF_PREFER_SOURCE) result |= FF_LOCALIP_IPV6_TYPE_PREFERRED_BIT; #endif if (ifr6.ifr_ifru.ifru_flags6 & (IN6_IFF_DEPRECATED | IN6_IFF_TEMPORARY | IN6_IFF_TENTATIVE | IN6_IFF_DUPLICATED #ifdef IN6_IFF_OPTIMISTIC | IN6_IFF_OPTIMISTIC #endif )) result |= FF_LOCALIP_IPV6_TYPE_SECONDARY_BIT; return result; #elif __linux__ static FFlist addresses = {}; if (addresses.elementSize == 0) { ffListInit(&addresses, sizeof(struct in6_addr)); FF_STRBUF_AUTO_DESTROY buffer = ffStrbufCreate(); if (!ffReadFileBuffer("/proc/net/if_inet6", &buffer)) return result; char* line = NULL; size_t len = 0; while (ffStrbufGetline(&line, &len, &buffer)) { struct in6_addr* entry = (struct in6_addr*) ffListAdd(&addresses); uint8_t flags; if (sscanf(line, "%2" SCNx8 "%2" SCNx8 "%2" SCNx8 "%2" SCNx8 "%2" SCNx8 "%2" SCNx8 "%2" SCNx8 "%2" SCNx8 "%2" SCNx8 "%2" SCNx8 "%2" SCNx8 "%2" SCNx8 "%2" SCNx8 "%2" SCNx8 "%2" SCNx8 "%2" SCNx8 " %*s %*s %*s %" SCNx8 " %*s", &entry->s6_addr[0], &entry->s6_addr[1], &entry->s6_addr[2], &entry->s6_addr[3], &entry->s6_addr[4], &entry->s6_addr[5], &entry->s6_addr[6], &entry->s6_addr[7], &entry->s6_addr[8], &entry->s6_addr[9], &entry->s6_addr[10], &entry->s6_addr[11], &entry->s6_addr[12], &entry->s6_addr[13], &entry->s6_addr[14], &entry->s6_addr[15], &flags) != 17 || (!IN6_IS_ADDR_GLOBAL(entry) && !IN6_IS_ADDR_UNIQUE_LOCAL(entry)) || (flags & (IFA_F_DEPRECATED | IFA_F_TEMPORARY | IFA_F_TENTATIVE | IFA_F_DADFAILED | IFA_F_OPTIMISTIC)) ) --addresses.length; } } if (addresses.capacity == 0) return result; FF_LIST_FOR_EACH(struct in6_addr, entry, addresses) { if (memcmp(&addr->sin6_addr, entry, sizeof(struct in6_addr)) == 0) return result; } result |= FF_LOCALIP_IPV6_TYPE_SECONDARY_BIT; return result; #elif __sun if (ifa->ifa_flags & IFF_PREFERRED) result |= FF_LOCALIP_IPV6_TYPE_PREFERRED_BIT; if (ifa->ifa_flags & (IFF_DEPRECATED | IFF_TEMPORARY | IFF_DUPLICATE)) result |= FF_LOCALIP_IPV6_TYPE_SECONDARY_BIT; return result; #else return result; #endif } typedef struct { struct ifaddrs* mac; FFlist /**/ ipv4; FFlist /**/ ipv6; } FFAdapter; static void appendIpv4(const FFLocalIpOptions* options, FFstrbuf* buffer, const struct ifaddrs* ifa) { struct sockaddr_in* ipv4 = (struct sockaddr_in*) ifa->ifa_addr; char addressBuffer[INET_ADDRSTRLEN + 16]; inet_ntop(AF_INET, &ipv4->sin_addr, addressBuffer, INET_ADDRSTRLEN); FF_DEBUG("Adding IPv4 address %s for interface %s", addressBuffer, ifa->ifa_name); if (options->showType & FF_LOCALIP_TYPE_PREFIX_LEN_BIT) { struct sockaddr_in* netmask = (struct sockaddr_in*) ifa->ifa_netmask; int cidr = __builtin_popcount(netmask->sin_addr.s_addr); if (cidr != 0) { size_t len = strlen(addressBuffer); snprintf(addressBuffer + len, 16, "/%d", cidr); } } if (buffer->length) ffStrbufAppendC(buffer, ','); ffStrbufAppendS(buffer, addressBuffer); } static void appendIpv6(const FFLocalIpOptions* options, FFstrbuf* buffer, const struct ifaddrs* ifa) { struct sockaddr_in6* ipv6 = (struct sockaddr_in6*) ifa->ifa_addr; char addressBuffer[INET6_ADDRSTRLEN + 16]; inet_ntop(AF_INET6, &ipv6->sin6_addr, addressBuffer, INET6_ADDRSTRLEN); FF_DEBUG("Adding IPv6 address %s for interface %s", addressBuffer, ifa->ifa_name); if (options->showType & FF_LOCALIP_TYPE_PREFIX_LEN_BIT) { struct sockaddr_in6* netmask = (struct sockaddr_in6*) ifa->ifa_netmask; int cidr = 0; static_assert(sizeof(netmask->sin6_addr) % sizeof(uint64_t) == 0, ""); for (uint32_t i = 0; i < sizeof(netmask->sin6_addr) / sizeof(uint64_t); ++i) cidr += __builtin_popcountll(((uint64_t*) &netmask->sin6_addr)[i]); if (cidr != 0) { size_t len = strlen(addressBuffer); snprintf(addressBuffer + len, 16, "/%d", cidr); } } if (buffer->length) ffStrbufAppendC(buffer, ','); ffStrbufAppendS(buffer, addressBuffer); } const char* ffDetectLocalIps(const FFLocalIpOptions* options, FFlist* results) { FF_DEBUG("Starting local IP detection with showType=0x%x, namePrefix='%s'", options->showType, options->namePrefix.chars); struct ifaddrs* ifAddrStruct = NULL; if(getifaddrs(&ifAddrStruct) < 0) { FF_DEBUG("getifaddrs() failed"); return "getifaddrs(&ifAddrStruct) failed"; } FF_DEBUG("Successfully retrieved interface addresses"); FF_LIST_AUTO_DESTROY adapters = ffListCreate(sizeof(FFAdapter)); for (struct ifaddrs* ifa = ifAddrStruct; ifa; ifa = ifa->ifa_next) { if (!ifa->ifa_addr) { FF_DEBUG("Skipping interface %s (no address)", ifa->ifa_name); continue; } #ifdef IFF_RUNNING if (!(ifa->ifa_flags & IFF_RUNNING)) { FF_DEBUG("Skipping interface %s (not running)", ifa->ifa_name); continue; } #endif if ((ifa->ifa_flags & IFF_LOOPBACK) && !(options->showType & FF_LOCALIP_TYPE_LOOP_BIT)) { FF_DEBUG("Skipping loopback interface %s", ifa->ifa_name); continue; } if (options->namePrefix.length && strncmp(ifa->ifa_name, options->namePrefix.chars, options->namePrefix.length) != 0) { FF_DEBUG("Skipping interface %s (doesn't match prefix '%s')", ifa->ifa_name, options->namePrefix.chars); continue; } if (!(options->showType & FF_LOCALIP_TYPE_MAC_BIT) && ifa->ifa_addr->sa_family != AF_INET && ifa->ifa_addr->sa_family != AF_INET6) { FF_DEBUG("Skipping interface %s (unsupported address family %d)", ifa->ifa_name, ifa->ifa_addr->sa_family); continue; } if (options->showType & FF_LOCALIP_TYPE_DEFAULT_ROUTE_ONLY_BIT) { // If the interface is not the default route for either IPv4 or IPv6, skip it if (!((options->showType & FF_LOCALIP_TYPE_IPV4_BIT) && ffStrEquals(ffNetifGetDefaultRouteV4()->ifName, ifa->ifa_name)) && !((options->showType & FF_LOCALIP_TYPE_IPV6_BIT) && ffStrEquals(ffNetifGetDefaultRouteV6()->ifName, ifa->ifa_name))) { FF_DEBUG("Skipping interface %s (not default route interface)", ifa->ifa_name); continue; } } FF_DEBUG("Processing interface %s (family=%d, flags=0x%lx)", ifa->ifa_name, ifa->ifa_addr->sa_family, (unsigned long) ifa->ifa_flags); FFAdapter* adapter = NULL; FF_LIST_FOR_EACH(FFAdapter, x, adapters) { if (ffStrEquals(x->mac->ifa_name, ifa->ifa_name)) { adapter = x; break; } } if (!adapter) { adapter = ffListAdd(&adapters); *adapter = (FFAdapter) { .mac = ifa, .ipv4 = ffListCreate(sizeof(struct ifaddrs*)), .ipv6 = ffListCreate(sizeof(struct ifaddrs*)), }; FF_DEBUG("Created new adapter entry for interface %s", ifa->ifa_name); } switch (ifa->ifa_addr->sa_family) { case AF_INET: if (options->showType & FF_LOCALIP_TYPE_IPV4_BIT) { *FF_LIST_ADD(struct ifaddrs*, adapter->ipv4) = ifa; FF_DEBUG("Added IPv4 entry for interface %s", ifa->ifa_name); } break; case AF_INET6: if (options->showType & FF_LOCALIP_TYPE_IPV6_BIT) { *FF_LIST_ADD(struct ifaddrs*, adapter->ipv6) = ifa; FF_DEBUG("Added IPv6 entry for interface %s", ifa->ifa_name); } break; #if __FreeBSD__ || __OpenBSD__ || __APPLE__ || __NetBSD__ || __HAIKU__ case AF_LINK: adapter->mac = ifa; FF_DEBUG("Updated MAC entry for interface %s", ifa->ifa_name); break; #elif !__sun && !__GNU__ case AF_PACKET: adapter->mac = ifa; FF_DEBUG("Updated MAC entry for interface %s", ifa->ifa_name); break; #endif } } FF_DEBUG("Found %u network adapters", adapters.length); FF_LIST_FOR_EACH(FFAdapter, adapter, adapters) { FF_DEBUG("Processing adapter %s (IPv4 entries: %u, IPv6 entries: %u)", adapter->mac->ifa_name, adapter->ipv4.length, adapter->ipv6.length); if (adapter->ipv4.length == 0 && adapter->ipv6.length == 0 && !(options->showType & FF_LOCALIP_TYPE_MAC_BIT) ) { FF_DEBUG("Skipping interface %s (no IP addresses)", adapter->mac->ifa_name); continue; } FFLocalIpResult* item = FF_LIST_ADD(FFLocalIpResult, *results); ffStrbufInitS(&item->name, adapter->mac->ifa_name); ffStrbufInit(&item->ipv4); ffStrbufInit(&item->ipv6); ffStrbufInit(&item->mac); ffStrbufInit(&item->flags); item->defaultRoute = FF_LOCALIP_TYPE_NONE; item->mtu = -1; item->speed = -1; if (options->showType & FF_LOCALIP_TYPE_FLAGS_BIT) { ffLocalIpFillNIFlags(&item->flags, adapter->mac->ifa_flags, niFlagOptions); FF_DEBUG("Added flags for interface %s: %s", adapter->mac->ifa_name, item->flags.chars); } if ((options->showType & FF_LOCALIP_TYPE_IPV4_BIT)) { const FFNetifDefaultRouteResult* defaultRouteV4 = ffNetifGetDefaultRouteV4(); bool isDefaultRouteIf = ffStrEquals(defaultRouteV4->ifName, adapter->mac->ifa_name); if (isDefaultRouteIf) { item->defaultRoute |= FF_LOCALIP_TYPE_IPV4_BIT; FF_DEBUG("Interface %s is IPv4 default route", adapter->mac->ifa_name); } if (options->showType & FF_LOCALIP_TYPE_DEFAULT_ROUTE_ONLY_BIT) { if (!isDefaultRouteIf) { FF_DEBUG("Skipping IPv4 for interface %s (not default route)", adapter->mac->ifa_name); goto v6; } } if (!(options->showType & FF_LOCALIP_TYPE_ALL_IPS_BIT)) { struct ifaddrs* ifa = NULL; if (isDefaultRouteIf && defaultRouteV4->preferredSourceAddrV4 != 0) { FF_LIST_FOR_EACH(struct ifaddrs*, pifa, adapter->ipv4) { struct sockaddr_in* ipv4 = (struct sockaddr_in*) (*pifa)->ifa_addr; if (ipv4->sin_addr.s_addr == defaultRouteV4->preferredSourceAddrV4) { ifa = *pifa; FF_DEBUG("Found preferred IPv4 source address for interface %s", adapter->mac->ifa_name); break; } } } if (ifa) appendIpv4(options, &item->ipv4, ifa); else if (adapter->ipv4.length > 0) { appendIpv4(options, &item->ipv4, *FF_LIST_FIRST(struct ifaddrs*, adapter->ipv4)); FF_DEBUG("Using first IPv4 address for interface %s", adapter->mac->ifa_name); } } else { FF_DEBUG("Adding all IPv4 addresses for interface %s", adapter->mac->ifa_name); FF_LIST_FOR_EACH(struct ifaddrs*, pifa, adapter->ipv4) appendIpv4(options, &item->ipv4, *pifa); } } v6: if ((options->showType & FF_LOCALIP_TYPE_IPV6_BIT)) { const FFNetifDefaultRouteResult* defaultRouteV6 = ffNetifGetDefaultRouteV6(); bool isDefaultRouteIf = ffStrEquals(defaultRouteV6->ifName, adapter->mac->ifa_name); if (isDefaultRouteIf) { item->defaultRoute |= FF_LOCALIP_TYPE_IPV6_BIT; FF_DEBUG("Interface %s is IPv6 default route", adapter->mac->ifa_name); } if (options->showType & FF_LOCALIP_TYPE_DEFAULT_ROUTE_ONLY_BIT) { if (!isDefaultRouteIf) { FF_DEBUG("Skipping IPv6 for interface %s (not default route)", adapter->mac->ifa_name); goto mac; } } if (options->ipv6Type == FF_LOCALIP_IPV6_TYPE_AUTO) { if (!(options->showType & FF_LOCALIP_TYPE_ALL_IPS_BIT)) { struct ifaddrs* selected = NULL; struct ifaddrs* secondary = NULL; FF_LIST_FOR_EACH(struct ifaddrs*, pifa, adapter->ipv6) { FFLocalIpIpv6Type type = getIpv6Type(*pifa); if (type & FF_LOCALIP_IPV6_TYPE_PREFERRED_BIT) { selected = *pifa; FF_DEBUG("Found preferred IPv6 address for interface %s", adapter->mac->ifa_name); break; } else if ((type & FF_LOCALIP_IPV6_TYPE_GUA_BIT) && !(type & FF_LOCALIP_IPV6_TYPE_SECONDARY_BIT) && !selected) { selected = *pifa; FF_DEBUG("Found GUA IPv6 address for interface %s", adapter->mac->ifa_name); } else if ((type & FF_LOCALIP_IPV6_TYPE_ULA_BIT) && !(type & FF_LOCALIP_IPV6_TYPE_SECONDARY_BIT) && !secondary) { secondary = *pifa; FF_DEBUG("Found ULA IPv6 address for interface %s", adapter->mac->ifa_name); } } if (!selected) selected = secondary; if (selected) appendIpv6(options, &item->ipv6, selected); else if (adapter->ipv6.length > 0) { appendIpv6(options, &item->ipv6, *FF_LIST_FIRST(struct ifaddrs*, adapter->ipv6)); FF_DEBUG("Using first IPv6 address for interface %s", adapter->mac->ifa_name); } } else { FF_DEBUG("Adding all IPv6 addresses for interface %s", adapter->mac->ifa_name); FF_LIST_FOR_EACH(struct ifaddrs*, pifa, adapter->ipv6) appendIpv6(options, &item->ipv6, *pifa); } } else { FF_LIST_FOR_EACH(struct ifaddrs*, pifa, adapter->ipv6) { FFLocalIpIpv6Type type = getIpv6Type(*pifa); if (type & options->ipv6Type) { if ((options->showType & FF_LOCALIP_TYPE_ALL_IPS_BIT) || !(type & FF_LOCALIP_IPV6_TYPE_SECONDARY_BIT)) { appendIpv6(options, &item->ipv6, *pifa); if (!(options->showType & FF_LOCALIP_TYPE_ALL_IPS_BIT)) break; } } } } } mac: #if !defined( __sun) && !defined(__GNU__) if (options->showType & FF_LOCALIP_TYPE_MAC_BIT) { if (adapter->mac->ifa_addr) { #if __FreeBSD__ || __OpenBSD__ || __APPLE__ || __NetBSD__ || __HAIKU__ uint8_t* ptr = (uint8_t*) LLADDR((struct sockaddr_dl *)adapter->mac->ifa_addr); #else uint8_t* ptr = ((struct sockaddr_ll *)adapter->mac->ifa_addr)->sll_addr; #endif ffStrbufSetF(&item->mac, "%02x:%02x:%02x:%02x:%02x:%02x", ptr[0], ptr[1], ptr[2], ptr[3], ptr[4], ptr[5]); FF_DEBUG("Added MAC address %s for interface %s", item->mac.chars, adapter->mac->ifa_name); } else { FF_DEBUG("No MAC address available for interface %s", adapter->mac->ifa_name); } } #else (void) adapter; #endif } FF_LIST_FOR_EACH(FFAdapter, adapter, adapters) { ffListDestroy(&adapter->ipv4); ffListDestroy(&adapter->ipv6); } if (ifAddrStruct) { freeifaddrs(ifAddrStruct); ifAddrStruct = NULL; FF_DEBUG("Cleaned up interface address structures"); } if ((options->showType & FF_LOCALIP_TYPE_MTU_BIT) || (options->showType & FF_LOCALIP_TYPE_SPEED_BIT) #ifdef __sun || (options->showType & FF_LOCALIP_TYPE_MAC_BIT) #endif ) { FF_DEBUG("Retrieving additional interface properties (MTU/Speed/MAC)"); FF_AUTO_CLOSE_FD int sockfd = socket(AF_INET, SOCK_DGRAM, 0); if (sockfd > 0) { FF_LIST_FOR_EACH(FFLocalIpResult, iface, *results) { struct ifreq ifr = {}; ffStrCopy(ifr.ifr_name, iface->name.chars, IFNAMSIZ); if (options->showType & FF_LOCALIP_TYPE_MTU_BIT) { if (ioctl(sockfd, SIOCGIFMTU, &ifr) == 0) { iface->mtu = (int32_t) ifr.ifr_mtu; FF_DEBUG("Interface %s MTU: %d", iface->name.chars, iface->mtu); } else { FF_DEBUG("Failed to get MTU for interface %s", iface->name.chars); } } if (options->showType & FF_LOCALIP_TYPE_SPEED_BIT) { #ifdef __linux__ struct ethtool_cmd edata = { .cmd = ETHTOOL_GSET }; ifr.ifr_data = (void*) &edata; if (ioctl(sockfd, SIOCETHTOOL, &ifr) == 0) { iface->speed = (edata.speed_hi << 16) | edata.speed; FF_DEBUG("Interface %s speed: %d Mbps", iface->name.chars, iface->speed); } else { // ethtool_cmd_speed is not available on Android FF_DEBUG("Failed to get speed for interface %s via ethtool", iface->name.chars); } #elif __FreeBSD__ || __APPLE__ || __OpenBSD__ || __NetBSD__ struct ifmediareq ifmr = {}; ffStrCopy(ifmr.ifm_name, iface->name.chars, IFNAMSIZ); if (ioctl(sockfd, SIOCGIFMEDIA, &ifmr) == 0 && (IFM_TYPE(ifmr.ifm_active) & IFM_ETHER)) { FF_DEBUG("Interface %s media type: 0x%x", iface->name.chars, IFM_SUBTYPE(ifmr.ifm_active)); switch (IFM_SUBTYPE(ifmr.ifm_active)) { #ifdef IFM_HPNA_1 case IFM_HPNA_1: #endif iface->speed = 1; break; #ifdef IFM_1000_CX case IFM_1000_CX: #endif #ifdef IFM_1000_CX_SGMII case IFM_1000_CX_SGMII: #endif #ifdef IFM_1000_KX case IFM_1000_KX: #endif #ifdef IFM_1000_LX case IFM_1000_LX: #endif #ifdef IFM_1000_SGMII case IFM_1000_SGMII: #endif #ifdef IFM_1000_SX case IFM_1000_SX: #endif #ifdef IFM_1000_T case IFM_1000_T: #endif iface->speed = 1000; break; #ifdef IFM_100G_AUI2 case IFM_100G_AUI2: #endif #ifdef IFM_100G_AUI2_AC case IFM_100G_AUI2_AC: #endif #ifdef IFM_100G_AUI4 case IFM_100G_AUI4: #endif #ifdef IFM_100G_AUI4_AC case IFM_100G_AUI4_AC: #endif #ifdef IFM_100G_CAUI2 case IFM_100G_CAUI2: #endif #ifdef IFM_100G_CAUI2_AC case IFM_100G_CAUI2_AC: #endif #ifdef IFM_100G_CAUI4 case IFM_100G_CAUI4: #endif #ifdef IFM_100G_CAUI4_AC case IFM_100G_CAUI4_AC: #endif #ifdef IFM_100G_CP2 case IFM_100G_CP2: #endif #ifdef IFM_100G_CR4 case IFM_100G_CR4: #endif #ifdef IFM_100G_CR_PAM4 case IFM_100G_CR_PAM4: #endif #ifdef IFM_100G_DR case IFM_100G_DR: #endif #ifdef IFM_100G_KR2_PAM4 case IFM_100G_KR2_PAM4: #endif #ifdef IFM_100G_KR4 case IFM_100G_KR4: #endif #ifdef IFM_100G_KR_PAM4 case IFM_100G_KR_PAM4: #endif #ifdef IFM_100G_LR4 case IFM_100G_LR4: #endif #ifdef IFM_100G_SR2 case IFM_100G_SR2: #endif #ifdef IFM_100G_SR4 case IFM_100G_SR4: #endif iface->speed = 100000; break; #ifdef IFM_100_FX case IFM_100_FX: #endif #ifdef IFM_100_SGMII case IFM_100_SGMII: #endif #ifdef IFM_100_T case IFM_100_T: #endif #ifdef IFM_100_T2 case IFM_100_T2: #endif #ifdef IFM_100_T4 case IFM_100_T4: #endif #ifdef IFM_100_TX case IFM_100_TX: #endif #ifdef IFM_100_VG case IFM_100_VG: #endif iface->speed = 100; break; #ifdef IFM_10G_AOC case IFM_10G_AOC: #endif #ifdef IFM_10G_CR1 case IFM_10G_CR1: #endif #ifdef IFM_10G_CX4 case IFM_10G_CX4: #endif #ifdef IFM_10G_ER case IFM_10G_ER: #endif #ifdef IFM_10G_KR case IFM_10G_KR: #endif #ifdef IFM_10G_KX4 case IFM_10G_KX4: #endif #ifdef IFM_10G_LR case IFM_10G_LR: #endif #ifdef IFM_10G_LRM case IFM_10G_LRM: #endif #ifdef IFM_10G_SFI case IFM_10G_SFI: #endif #ifdef IFM_10G_SR case IFM_10G_SR: #endif #ifdef IFM_10G_T case IFM_10G_T: #endif #ifdef IFM_10G_TWINAX case IFM_10G_TWINAX: #endif #ifdef IFM_10G_TWINAX_LONG case IFM_10G_TWINAX_LONG: #endif iface->speed = 10000; break; #ifdef IFM_10_2 case IFM_10_2: #endif #ifdef IFM_10_5 case IFM_10_5: #endif #ifdef IFM_10_FL case IFM_10_FL: #endif #ifdef IFM_10_STP case IFM_10_STP: #endif #ifdef IFM_10_T case IFM_10_T: #endif iface->speed = 10; break; #ifdef IFM_200G_AUI4 case IFM_200G_AUI4: #endif #ifdef IFM_200G_AUI4_AC case IFM_200G_AUI4_AC: #endif #ifdef IFM_200G_AUI8 case IFM_200G_AUI8: #endif #ifdef IFM_200G_AUI8_AC case IFM_200G_AUI8_AC: #endif #ifdef IFM_200G_CR4_PAM4 case IFM_200G_CR4_PAM4: #endif #ifdef IFM_200G_DR4 case IFM_200G_DR4: #endif #ifdef IFM_200G_FR4 case IFM_200G_FR4: #endif #ifdef IFM_200G_KR4_PAM4 case IFM_200G_KR4_PAM4: #endif #ifdef IFM_200G_LR4 case IFM_200G_LR4: #endif #ifdef IFM_200G_SR4 case IFM_200G_SR4: #endif iface->speed = 200000; break; #ifdef IFM_20G_KR2 case IFM_20G_KR2: #endif iface->speed = 20000; break; #ifdef IFM_2500_KX case IFM_2500_KX: #endif #ifdef IFM_2500_SX case IFM_2500_SX: #endif #ifdef IFM_2500_T case IFM_2500_T: #endif #ifdef IFM_2500_X case IFM_2500_X: #endif iface->speed = 2500; break; #ifdef IFM_25G_ACC case IFM_25G_ACC: #endif #ifdef IFM_25G_AOC case IFM_25G_AOC: #endif #ifdef IFM_25G_AUI case IFM_25G_AUI: #endif #ifdef IFM_25G_CR case IFM_25G_CR: #endif #ifdef IFM_25G_CR1 case IFM_25G_CR1: #endif #ifdef IFM_25G_CR_S case IFM_25G_CR_S: #endif #ifdef IFM_25G_KR case IFM_25G_KR: #endif #ifdef IFM_25G_KR1 case IFM_25G_KR1: #endif #ifdef IFM_25G_KR_S case IFM_25G_KR_S: #endif #ifdef IFM_25G_LR case IFM_25G_LR: #endif #ifdef IFM_25G_PCIE case IFM_25G_PCIE: #endif #ifdef IFM_25G_SR case IFM_25G_SR: #endif #ifdef IFM_25G_T case IFM_25G_T: #endif iface->speed = 25000; break; #ifdef IFM_400G_AUI8 case IFM_400G_AUI8: #endif #ifdef IFM_400G_AUI8_AC case IFM_400G_AUI8_AC: #endif #ifdef IFM_400G_DR4 case IFM_400G_DR4: #endif #ifdef IFM_400G_FR8 case IFM_400G_FR8: #endif #ifdef IFM_400G_LR8 case IFM_400G_LR8: #endif iface->speed = 400000; break; #ifdef IFM_40G_CR4 case IFM_40G_CR4: #endif #ifdef IFM_40G_ER4 case IFM_40G_ER4: #endif #ifdef IFM_40G_KR4 case IFM_40G_KR4: #endif #ifdef IFM_40G_LR4 case IFM_40G_LR4: #endif #ifdef IFM_40G_SR4 case IFM_40G_SR4: #endif #ifdef IFM_40G_XLAUI case IFM_40G_XLAUI: #endif #ifdef IFM_40G_XLAUI_AC case IFM_40G_XLAUI_AC: #endif #ifdef IFM_40G_XLPPI case IFM_40G_XLPPI: #endif #ifdef IFM_40G_LM4 case IFM_40G_LM4: #endif iface->speed = 40000; break; #ifdef IFM_5000_KR case IFM_5000_KR: #endif #ifdef IFM_5000_KR1 case IFM_5000_KR1: #endif #ifdef IFM_5000_KR_S case IFM_5000_KR_S: #endif #ifdef IFM_5000_T case IFM_5000_T: #endif iface->speed = 5000; break; #ifdef IFM_50G_AUI1 case IFM_50G_AUI1: #endif #ifdef IFM_50G_AUI1_AC case IFM_50G_AUI1_AC: #endif #ifdef IFM_50G_AUI2 case IFM_50G_AUI2: #endif #ifdef IFM_50G_AUI2_AC case IFM_50G_AUI2_AC: #endif #ifdef IFM_50G_CP case IFM_50G_CP: #endif #ifdef IFM_50G_CR2 case IFM_50G_CR2: #endif #ifdef IFM_50G_FR case IFM_50G_FR: #endif #ifdef IFM_50G_KR2 case IFM_50G_KR2: #endif #ifdef IFM_50G_KR_PAM4 case IFM_50G_KR_PAM4: #endif #ifdef IFM_50G_LAUI2 case IFM_50G_LAUI2: #endif #ifdef IFM_50G_LAUI2_AC case IFM_50G_LAUI2_AC: #endif #ifdef IFM_50G_LR case IFM_50G_LR: #endif #ifdef IFM_50G_LR2 case IFM_50G_LR2: #endif #ifdef IFM_50G_PCIE case IFM_50G_PCIE: #endif #ifdef IFM_50G_SR case IFM_50G_SR: #endif #ifdef IFM_50G_SR2 case IFM_50G_SR2: #endif #ifdef IFM_50G_KR4 case IFM_50G_KR4: #endif iface->speed = 50000; break; #ifdef IFM_56G_R4 case IFM_56G_R4: #endif iface->speed = 56000; break; default: iface->speed = -1; FF_DEBUG("Unknown media subtype for interface %s", iface->name.chars); break; } if (iface->speed > 0) FF_DEBUG("Interface %s speed: %d Mbps", iface->name.chars, iface->speed); } else { FF_DEBUG("Failed to get media info for interface %s", iface->name.chars); } #endif } #if __sun || __GNU__ if ((options->showType & FF_LOCALIP_TYPE_MAC_BIT) && ioctl(sockfd, SIOCGIFHWADDR, &ifr) == 0) { const uint8_t* ptr = (uint8_t*) ifr.ifr_addr.sa_data; // NOT ifr_enaddr ffStrbufSetF(&iface->mac, "%02x:%02x:%02x:%02x:%02x:%02x", ptr[0], ptr[1], ptr[2], ptr[3], ptr[4], ptr[5]); FF_DEBUG("Added MAC address %s for interface %s (Solaris/GNU)", iface->mac.chars, iface->name.chars); } #endif #if __sun if (options->showType & FF_LOCALIP_TYPE_SPEED_BIT) { __attribute__((__cleanup__(kstatFreeWrap))) kstat_ctl_t* kc = kstat_open(); for (kstat_t* ks = kc->kc_chain; ks; ks = ks->ks_next) { if (!ffStrEquals(ks->ks_class, "net") || !ffStrEquals(ks->ks_module, "link")) continue; if (ffStrbufEqualS(&iface->name, ks->ks_name)) { if (kstat_read(kc, ks, NULL) >= 0) { kstat_named_t* ifspeed = (kstat_named_t*) kstat_data_lookup(ks, "ifspeed"); if (ifspeed) { iface->speed = (int32_t) (ifspeed->value.ui64 / 1000 / 1000); FF_DEBUG("Interface %s speed: %d Mbps (kstat)", iface->name.chars, iface->speed); } } break; } } } #endif } } else { FF_DEBUG("Failed to create socket for interface property retrieval"); } } FF_DEBUG("Local IP detection completed, found %u interfaces", results->length); return NULL; } ================================================ FILE: src/detection/localip/localip_windows.c ================================================ #include #include #include "common/netif.h" #include "common/mallocHelper.h" #include "common/windows/unicode.h" #include "common/debug.h" #include "localip.h" #define FF_LOCALIP_NIFLAG(name) { IP_ADAPTER_##name, #name } static const FFLocalIpNIFlag niFlagOptions[] = { FF_LOCALIP_NIFLAG(DDNS_ENABLED), FF_LOCALIP_NIFLAG(REGISTER_ADAPTER_SUFFIX), FF_LOCALIP_NIFLAG(DHCP_ENABLED), FF_LOCALIP_NIFLAG(RECEIVE_ONLY), FF_LOCALIP_NIFLAG(NO_MULTICAST), FF_LOCALIP_NIFLAG(IPV6_OTHER_STATEFUL_CONFIG), FF_LOCALIP_NIFLAG(NETBIOS_OVER_TCPIP_ENABLED), FF_LOCALIP_NIFLAG(IPV4_ENABLED), FF_LOCALIP_NIFLAG(IPV6_ENABLED), FF_LOCALIP_NIFLAG(IPV6_MANAGE_ADDRESS_CONFIG), // sentinel {}, }; const char* ffDetectLocalIps(const FFLocalIpOptions* options, FFlist* results) { FF_DEBUG("Starting local IP detection with showType=0x%X, namePrefix='%.*s'", options->showType, (int)options->namePrefix.length, options->namePrefix.chars); IP_ADAPTER_ADDRESSES* FF_AUTO_FREE adapter_addresses = NULL; // Multiple attempts in case interfaces change while // we are in the middle of querying them. DWORD adapter_addresses_buffer_size = 0; for (int attempts = 0;; ++attempts) { FF_DEBUG("Attempt %d to get adapter addresses, buffer size: %lu", attempts + 1, adapter_addresses_buffer_size); if (adapter_addresses_buffer_size) { adapter_addresses = (IP_ADAPTER_ADDRESSES*)realloc(adapter_addresses, adapter_addresses_buffer_size); assert(adapter_addresses); } DWORD family = options->showType & FF_LOCALIP_TYPE_IPV4_BIT ? options->showType & FF_LOCALIP_TYPE_IPV6_BIT ? AF_UNSPEC : AF_INET : AF_INET6; FF_DEBUG("Calling GetAdaptersAddresses with family=%u, flags=0x%X", (unsigned)family, GAA_FLAG_SKIP_ANYCAST | GAA_FLAG_SKIP_MULTICAST | GAA_FLAG_SKIP_DNS_SERVER); DWORD error = GetAdaptersAddresses( family, GAA_FLAG_SKIP_ANYCAST | GAA_FLAG_SKIP_MULTICAST | GAA_FLAG_SKIP_DNS_SERVER, NULL, adapter_addresses, &adapter_addresses_buffer_size); if (error == ERROR_SUCCESS) { FF_DEBUG("GetAdaptersAddresses succeeded on attempt %d", attempts + 1); break; } else if (ERROR_BUFFER_OVERFLOW == error && attempts < 4) { FF_DEBUG("Buffer overflow, need %lu bytes, retrying", adapter_addresses_buffer_size); continue; } else { FF_DEBUG("GetAdaptersAddresses failed with error %lu after %d attempts", error, attempts + 1); return "GetAdaptersAddresses() failed"; } } FF_MAYBE_UNUSED int adapterCount = 0, processedCount = 0; // Iterate through all of the adapters for (IP_ADAPTER_ADDRESSES* adapter = adapter_addresses; adapter; adapter = adapter->Next) { adapterCount++; FF_DEBUG("Processing adapter %d: IfIndex=%u, IfType=%u, OperStatus=%u", adapterCount, (unsigned)adapter->IfIndex, (unsigned)adapter->IfType, (unsigned)adapter->OperStatus); if (adapter->OperStatus != IfOperStatusUp) { FF_DEBUG("Skipping adapter %u (not operational, status=%d)", (unsigned)adapter->IfIndex, adapter->OperStatus); continue; } bool isLoop = adapter->IfType == IF_TYPE_SOFTWARE_LOOPBACK; FF_DEBUG("Adapter %u: isLoopback=%s", (unsigned)adapter->IfIndex, isLoop ? "true" : "false"); if (isLoop && !(options->showType & FF_LOCALIP_TYPE_LOOP_BIT)) { FF_DEBUG("Skipping loopback adapter %u (loopback not requested)", (unsigned)adapter->IfIndex); continue; } FF_STRBUF_AUTO_DESTROY name = ffStrbufCreateWS(adapter->FriendlyName); FF_DEBUG("Adapter %u name: '%s'", (unsigned)adapter->IfIndex, name.chars); if (options->namePrefix.length && !ffStrbufStartsWith(&name, &options->namePrefix)) { FF_DEBUG("Skipping adapter %u (name doesn't match prefix '%.*s')", (unsigned)adapter->IfIndex, (int)options->namePrefix.length, options->namePrefix.chars); continue; } if (options->showType & FF_LOCALIP_TYPE_DEFAULT_ROUTE_ONLY_BIT) { if (!((options->showType & FF_LOCALIP_TYPE_IPV4_BIT) && ffNetifGetDefaultRouteV4()->ifIndex == adapter->IfIndex) && !((options->showType & FF_LOCALIP_TYPE_IPV6_BIT) && ffNetifGetDefaultRouteV6()->ifIndex == adapter->IfIndex)) { FF_DEBUG("Skipping interface %u (not default route interface)", (unsigned)adapter->IfIndex); continue; } } processedCount++; FF_DEBUG("Creating result item for adapter %u ('%s')", (unsigned)adapter->IfIndex, name.chars); FFLocalIpResult* item = (FFLocalIpResult*) ffListAdd(results); ffStrbufInitMove(&item->name, &name); ffStrbufInit(&item->ipv4); ffStrbufInit(&item->ipv6); ffStrbufInit(&item->mac); ffStrbufInit(&item->flags); item->defaultRoute = FF_LOCALIP_TYPE_NONE; item->speed = -1; item->mtu = -1; uint32_t typesToAdd = options->showType & (FF_LOCALIP_TYPE_IPV4_BIT | FF_LOCALIP_TYPE_IPV6_BIT | FF_LOCALIP_TYPE_ALL_IPS_BIT); FF_DEBUG("Types to add for adapter %u: 0x%X", (unsigned)adapter->IfIndex, typesToAdd); FF_MAYBE_UNUSED int ipv4Count = 0, ipv6Count = 0; for (IP_ADAPTER_UNICAST_ADDRESS* ifa = adapter->FirstUnicastAddress; ifa; ifa = ifa->Next) { FF_DEBUG("Processing unicast address: prefix origin=%d, suffix origin=%d, family=%d, DadState=%d", ifa->PrefixOrigin, ifa->SuffixOrigin, ifa->Address.lpSockaddr->sa_family, ifa->DadState); if (!(options->showType & FF_LOCALIP_TYPE_ALL_IPS_BIT)) { if (ifa->DadState != IpDadStatePreferred) { FF_DEBUG("Skipping address (not preferred)"); continue; } if (ifa->SuffixOrigin == IpSuffixOriginRandom) { FF_DEBUG("Skipping temporary address (random suffix)"); continue; } // MIB_UNICASTIPADDRESS_ROW::SkipAsSource } if (ifa->Address.lpSockaddr->sa_family == AF_INET) { if (!(typesToAdd & (FF_LOCALIP_TYPE_IPV4_BIT | FF_LOCALIP_TYPE_ALL_IPS_BIT))) { FF_DEBUG("Skipping IPv4 address (not requested in typesToAdd=0x%X)", typesToAdd); continue; } bool isDefaultRoute = ((options->showType & FF_LOCALIP_TYPE_IPV4_BIT) && ffNetifGetDefaultRouteV4()->ifIndex == adapter->IfIndex); if ((options->showType & FF_LOCALIP_TYPE_DEFAULT_ROUTE_ONLY_BIT) && !isDefaultRoute) { FF_DEBUG("Skipping IPv4 address (not on default route interface)"); continue; } SOCKADDR_IN* ipv4 = (SOCKADDR_IN*) ifa->Address.lpSockaddr; char addressBuffer[INET_ADDRSTRLEN + 10]; char* end = RtlIpv4AddressToStringA(&ipv4->sin_addr, addressBuffer); if ((options->showType & FF_LOCALIP_TYPE_PREFIX_LEN_BIT) && ifa->OnLinkPrefixLength) end += snprintf(end, 10, "/%u", (unsigned) ifa->OnLinkPrefixLength); FF_DEBUG("Adding IPv4 address: %s (isDefaultRoute=%s)", addressBuffer, isDefaultRoute ? "true" : "false"); if (item->ipv4.length) ffStrbufAppendC(&item->ipv4, ','); ffStrbufAppendNS(&item->ipv4, (uint32_t) (end - addressBuffer), addressBuffer); if (isDefaultRoute) item->defaultRoute |= FF_LOCALIP_TYPE_IPV4_BIT; ipv4Count++; typesToAdd &= ~(unsigned) FF_LOCALIP_TYPE_IPV4_BIT; if (typesToAdd == 0) break; } else if (ifa->Address.lpSockaddr->sa_family == AF_INET6) { if (!(typesToAdd & (FF_LOCALIP_TYPE_IPV6_BIT | FF_LOCALIP_TYPE_ALL_IPS_BIT))) { FF_DEBUG("Skipping IPv6 address (not requested in typesToAdd=0x%X)", typesToAdd); continue; } SOCKADDR_IN6* ipv6 = (SOCKADDR_IN6*) ifa->Address.lpSockaddr; FFLocalIpIpv6Type ipv6Type = FF_LOCALIP_IPV6_TYPE_NONE; if (IN6_IS_ADDR_GLOBAL(&ipv6->sin6_addr)) ipv6Type |= FF_LOCALIP_IPV6_TYPE_GUA_BIT; else if (IN6_IS_ADDR_UNIQUE_LOCAL(&ipv6->sin6_addr)) ipv6Type |= FF_LOCALIP_IPV6_TYPE_ULA_BIT; else if (IN6_IS_ADDR_LINKLOCAL(&ipv6->sin6_addr)) ipv6Type |= FF_LOCALIP_IPV6_TYPE_LLA_BIT; else ipv6Type |= FF_LOCALIP_IPV6_TYPE_UNKNOWN_BIT; if (!(options->ipv6Type & ipv6Type)) { FF_DEBUG("Skipping IPv6 address (doesn't match requested type 0x%X)", options->ipv6Type); continue; } bool isDefaultRoute = ((options->showType & FF_LOCALIP_TYPE_IPV6_BIT) && ffNetifGetDefaultRouteV6()->ifIndex == adapter->IfIndex); if ((options->showType & FF_LOCALIP_TYPE_DEFAULT_ROUTE_ONLY_BIT) && !isDefaultRoute) { FF_DEBUG("Skipping IPv6 address (not on default route interface)"); continue; } char addressBuffer[INET6_ADDRSTRLEN + 10]; char* end = RtlIpv6AddressToStringA(&ipv6->sin6_addr, addressBuffer); if ((options->showType & FF_LOCALIP_TYPE_PREFIX_LEN_BIT) && ifa->OnLinkPrefixLength) end += snprintf(end, 10, "/%u", (unsigned) ifa->OnLinkPrefixLength); FF_DEBUG("Adding IPv6 address: %s (isDefaultRoute=%s)", addressBuffer, isDefaultRoute ? "true" : "false"); if (item->ipv6.length) ffStrbufAppendC(&item->ipv6, ','); ffStrbufAppendNS(&item->ipv6, (uint32_t) (end - addressBuffer), addressBuffer); if (isDefaultRoute) item->defaultRoute |= FF_LOCALIP_TYPE_IPV6_BIT; ipv6Count++; typesToAdd &= ~(unsigned) FF_LOCALIP_TYPE_IPV6_BIT; if (typesToAdd == 0) break; } } FF_DEBUG("Adapter %u: collected %d IPv4 and %d IPv6 addresses", (unsigned)adapter->IfIndex, ipv4Count, ipv6Count); if (options->showType & FF_LOCALIP_TYPE_SPEED_BIT) { item->speed = (int32_t) (adapter->ReceiveLinkSpeed / 1000000); FF_DEBUG("Adapter %u speed: %d Mbps (raw: %llu)", (unsigned)adapter->IfIndex, item->speed, adapter->ReceiveLinkSpeed); } if (options->showType & FF_LOCALIP_TYPE_MTU_BIT) { item->mtu = (int32_t) adapter->Mtu; FF_DEBUG("Adapter %u MTU: %d", (unsigned)adapter->IfIndex, item->mtu); } if (options->showType & FF_LOCALIP_TYPE_FLAGS_BIT) { ffLocalIpFillNIFlags(&item->flags, adapter->Flags, niFlagOptions); FF_DEBUG("Adapter %u flags: 0x%lX -> '%s'", (unsigned)adapter->IfIndex, adapter->Flags, item->flags.chars); } if (options->showType & FF_LOCALIP_TYPE_MAC_BIT && adapter->PhysicalAddressLength == 6) { uint8_t* ptr = adapter->PhysicalAddress; ffStrbufSetF(&item->mac, "%02x:%02x:%02x:%02x:%02x:%02x", ptr[0], ptr[1], ptr[2], ptr[3], ptr[4], ptr[5]); FF_DEBUG("Adapter %u MAC: %s", (unsigned)adapter->IfIndex, item->mac.chars); } } FF_DEBUG("Local IP detection completed: scanned %d adapters, processed %d, results count: %u", adapterCount, processedCount, results->length); return NULL; } ================================================ FILE: src/detection/media/media.c ================================================ #include "media.h" #include "common/io.h" void ffDetectMediaImpl(FFMediaResult* media, bool saveCover); static FFMediaResult result; static void removeMediaCoverFile(void) { if (result.cover.length > 0) { ffRemoveFile(result.cover.chars); ffStrbufDestroy(&result.cover); } } const FFMediaResult* ffDetectMedia(bool saveCover) { if (result.error.chars == NULL) { ffStrbufInit(&result.error); ffStrbufInit(&result.playerId); ffStrbufInit(&result.player); ffStrbufInit(&result.song); ffStrbufInit(&result.artist); ffStrbufInit(&result.album); ffStrbufInit(&result.url); ffStrbufInit(&result.status); ffStrbufInit(&result.cover); result.removeCoverAfterUse = false; ffDetectMediaImpl(&result, saveCover); if(result.song.length == 0 && result.error.length == 0) ffStrbufAppendS(&result.error, "No media found"); ffStrbufTrimRightSpace(&result.song); ffStrbufTrimRightSpace(&result.artist); ffStrbufTrimRightSpace(&result.album); ffStrbufTrimRightSpace(&result.player); if (saveCover && result.removeCoverAfterUse) atexit(removeMediaCoverFile); } return &result; } ================================================ FILE: src/detection/media/media.h ================================================ #pragma once #include "fastfetch.h" #include "modules/media/option.h" typedef struct FFMediaResult { FFstrbuf error; FFstrbuf playerId; // Bus name on Linux, app bundle name on macOS. e.g. plasma-browser-integration FFstrbuf player; // e.g. Google Chrome FFstrbuf song; FFstrbuf artist; FFstrbuf album; FFstrbuf url; FFstrbuf status; FFstrbuf cover; bool removeCoverAfterUse; } FFMediaResult; const FFMediaResult* ffDetectMedia(bool saveCover); ================================================ FILE: src/detection/media/media_apple.m ================================================ #include "fastfetch.h" #include "common/processing.h" #include "common/apple/cf_helpers.h" #include "detection/media/media.h" #import #import #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)) __attribute__((weak_import)); extern void MRMediaRemoteGetNowPlayingApplicationIsPlaying(dispatch_queue_t queue, void (^callback)(BOOL playing)) __attribute__((weak_import)); extern void MRMediaRemoteGetNowPlayingApplicationDisplayID(dispatch_queue_t queue, void (^callback)(_Nullable CFStringRef displayID)) __attribute__((weak_import)); extern void MRMediaRemoteGetNowPlayingApplicationDisplayName(int unknown, dispatch_queue_t queue, void (^callback)(_Nullable CFStringRef name)) __attribute__((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 dispatch_group_t group = dispatch_group_create(); dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0); dispatch_group_enter(group); __block const char* error = NULL; MRMediaRemoteGetNowPlayingInfo(queue, ^(_Nullable CFDictionaryRef info) { if(info != nil) { ffCfDictGetString(info, CFSTR("kMRMediaRemoteNowPlayingInfoTitle"), &result->song); ffCfDictGetString(info, CFSTR("kMRMediaRemoteNowPlayingInfoArtist"), &result->artist); ffCfDictGetString(info, CFSTR("kMRMediaRemoteNowPlayingInfoAlbum"), &result->album); if (saveCover) { NSData* artworkData = (__bridge NSData*) CFDictionaryGetValue(info, CFSTR("kMRMediaRemoteNowPlayingInfoArtworkData")); 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"]]; if ([artworkData writeToFile:path atomically:NO]) ffStrbufSetS(&result->cover, path.UTF8String); } } } else error = "MRMediaRemoteGetNowPlayingInfo() failed"; dispatch_group_leave(group); }); dispatch_group_enter(group); MRMediaRemoteGetNowPlayingApplicationIsPlaying(queue, ^(BOOL playing) { ffStrbufSetStatic(&result->status, playing ? "Playing" : "Paused"); dispatch_group_leave(group); }); if (MRMediaRemoteGetNowPlayingApplicationDisplayID) { dispatch_group_enter(group); MRMediaRemoteGetNowPlayingApplicationDisplayID(queue, ^(_Nullable CFStringRef displayID) { ffCfStrGetString(displayID, &result->playerId); dispatch_group_leave(group); }); } if (MRMediaRemoteGetNowPlayingApplicationDisplayName) { dispatch_group_enter(group); MRMediaRemoteGetNowPlayingApplicationDisplayName(0, queue, ^(_Nullable CFStringRef name) { ffCfStrGetString(name, &result->player); dispatch_group_leave(group); }); } dispatch_group_wait(group, DISPATCH_TIME_FOREVER); // Don't dispatch_release because we are using ARC if(result->song.length > 0) return NULL; return error; } __attribute__((visibility("default"), used)) int ffPrintMediaByMediaRemote(bool saveCover) { FFMediaResult media = { .status = ffStrbufCreate(), .song = ffStrbufCreate(), .artist = ffStrbufCreate(), .album = ffStrbufCreate(), .playerId = ffStrbufCreate(), .player = ffStrbufCreate(), .cover = ffStrbufCreate(), }; 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); ffStrbufDestroy(&media.status); ffStrbufDestroy(&media.song); ffStrbufDestroy(&media.artist); ffStrbufDestroy(&media.album); ffStrbufDestroy(&media.playerId); ffStrbufDestroy(&media.player); ffStrbufDestroy(&media.cover); return 0; } 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"; // status\ntitle\nartist\nalbum\nbundleName\nappName FFstrbuf* const varList[] = { &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); return NULL; } void ffDetectMediaImpl(FFMediaResult* media, bool saveCover) { const char* error; if (@available(macOS 15.4, *)) error = getMediaByAuthorizedProcess(media, saveCover); else error = getMediaByMediaRemote(media, saveCover); if (error) ffStrbufAppendS(&media->error, error); else if (media->player.length == 0 && media->playerId.length > 0) { ffStrbufSet(&media->player, &media->playerId); if (ffStrbufStartsWithIgnCaseS(&media->player, "com.")) ffStrbufSubstrAfter(&media->player, strlen("com.") - 1); ffStrbufReplaceAllC(&media->player, '.', ' '); if (media->cover.length > 0) media->removeCoverAfterUse = true; } } ================================================ FILE: src/detection/media/media_linux.c ================================================ #include "fastfetch.h" #include "common/io.h" #include "common/stringUtils.h" #include "common/memrchr.h" #include "detection/media/media.h" #include #define FF_DBUS_MPRIS_PREFIX "org.mpris.MediaPlayer2." #ifdef FF_HAVE_DBUS #include "common/dbus.h" #define FF_DBUS_ITER_CONTINUE(dbus, iterator) \ { \ if(!(dbus)->lib->ffdbus_message_iter_next(iterator)) \ break; \ continue; \ } static bool parseMprisMetadata(FFDBusData* data, DBusMessageIter* rootIterator, FFMediaResult* result) { DBusMessageIter arrayIterator; if (data->lib->ffdbus_message_iter_get_arg_type(rootIterator) == DBUS_TYPE_VARIANT) { DBusMessageIter variantIterator; data->lib->ffdbus_message_iter_recurse(rootIterator, &variantIterator); if(data->lib->ffdbus_message_iter_get_arg_type(&variantIterator) != DBUS_TYPE_ARRAY) return false; data->lib->ffdbus_message_iter_recurse(&variantIterator, &arrayIterator); } else { data->lib->ffdbus_message_iter_recurse(rootIterator, &arrayIterator); } while(true) { if(data->lib->ffdbus_message_iter_get_arg_type(&arrayIterator) != DBUS_TYPE_DICT_ENTRY) FF_DBUS_ITER_CONTINUE(data, &arrayIterator) DBusMessageIter dictIterator; data->lib->ffdbus_message_iter_recurse(&arrayIterator, &dictIterator); if(data->lib->ffdbus_message_iter_get_arg_type(&dictIterator) != DBUS_TYPE_STRING) FF_DBUS_ITER_CONTINUE(data, &arrayIterator) if(!data->lib->ffdbus_message_iter_has_next(&dictIterator)) FF_DBUS_ITER_CONTINUE(data, &arrayIterator) const char* key; data->lib->ffdbus_message_iter_get_basic(&dictIterator, &key); data->lib->ffdbus_message_iter_next(&dictIterator); if(ffStrStartsWith(key, "xesam:")) { const char* xesam = key + strlen("xesam:"); if(ffStrEquals(xesam, "title")) ffDBusGetString(data, &dictIterator, &result->song); else if(ffStrEquals(xesam, "album")) ffDBusGetString(data, &dictIterator, &result->album); else if(ffStrEquals(xesam, "artist")) ffDBusGetString(data, &dictIterator, &result->artist); else if(ffStrEquals(xesam, "url")) ffDBusGetString(data, &dictIterator, &result->url); if(result->song.length > 0 && result->artist.length > 0 && result->album.length > 0 && result->url.length > 0) break; } else if (ffStrStartsWith(key, "mpris:")) { const char* xesam = key + strlen("mpris:"); if(ffStrEquals(xesam, "artUrl")) { FF_STRBUF_AUTO_DESTROY path = ffStrbufCreate(); ffDBusGetString(data, &dictIterator, &path); if (ffStrbufStartsWithS(&path, "file:///")) { ffStrbufEnsureFree(&result->cover, path.length - (uint32_t) strlen("file://")); for (uint32_t i = (uint32_t) strlen("file://"); i < path.length; ++i) { if (path.chars[i] == '%') { if (i + 2 >= path.length) break; char str[] = { path.chars[i + 1], path.chars[i + 2], 0 }; char* end = NULL; const char decodedChar = (char) strtoul(str, &end, 16); if (end == &str[2]) { i += 2; ffStrbufAppendC(&result->cover, decodedChar); } else ffStrbufAppendC(&result->cover, '%'); } else { ffStrbufAppendC(&result->cover, path.chars[i]); } } } } } FF_DBUS_ITER_CONTINUE(data, &arrayIterator) } return true; } static bool getBusProperties(FFDBusData* data, const char* busName, FFMediaResult* result) { // Get all properties at once to reduce the number of IPCs DBusMessage* reply = ffDBusGetAllProperties(data, busName, "/org/mpris/MediaPlayer2", "org.mpris.MediaPlayer2.Player"); if(reply == NULL) return false; DBusMessageIter rootIterator; if(!data->lib->ffdbus_message_iter_init(reply, &rootIterator) && data->lib->ffdbus_message_iter_get_arg_type(&rootIterator) != DBUS_TYPE_ARRAY) { data->lib->ffdbus_message_unref(reply); return false; } DBusMessageIter arrayIterator; data->lib->ffdbus_message_iter_recurse(&rootIterator, &arrayIterator); while(true) { if(data->lib->ffdbus_message_iter_get_arg_type(&arrayIterator) != DBUS_TYPE_DICT_ENTRY) FF_DBUS_ITER_CONTINUE(data, &arrayIterator) DBusMessageIter dictIterator; data->lib->ffdbus_message_iter_recurse(&arrayIterator, &dictIterator); const char* key; data->lib->ffdbus_message_iter_get_basic(&dictIterator, &key); data->lib->ffdbus_message_iter_next(&dictIterator); if(ffStrEquals(key, "Metadata")) parseMprisMetadata(data, &dictIterator, result); else if(ffStrEquals(key, "PlaybackStatus")) ffDBusGetString(data, &dictIterator, &result->status); FF_DBUS_ITER_CONTINUE(data, &arrayIterator) } if(result->song.length == 0) { if(result->url.length) { const char* fileName = memrchr(result->url.chars, '/', result->url.length); assert(fileName); ++fileName; ffStrbufEnsureFixedLengthFree(&result->song, result->url.length - (uint32_t) (fileName - result->url.chars)); for(; *fileName && *fileName != '?'; ++fileName) { if (*fileName != '%') { ffStrbufAppendC(&result->song, *fileName); } else { if (fileName[1] == 0 || fileName[2] == 0) break; char str[] = { fileName[1], fileName[2], 0 }; ffStrbufAppendC(&result->song, (char) strtoul(str, NULL, 16)); fileName += 2; } } } else { ffStrbufClear(&result->artist); ffStrbufClear(&result->album); ffStrbufClear(&result->url); ffStrbufClear(&result->status); data->lib->ffdbus_message_unref(reply); return false; } } //Set short bus name ffStrbufAppendS(&result->playerId, busName + sizeof(FF_DBUS_MPRIS_PREFIX) - 1); //We found a song, get the player name if (ffStrbufStartsWithS(&result->playerId, "musikcube.instance")) { // dbus calls are EXTREMELY slow on musikcube, so we set the player name manually ffStrbufSetStatic(&result->player, "musikcube"); } else { ffDBusGetPropertyString(data, busName, "/org/mpris/MediaPlayer2", "org.mpris.MediaPlayer2", "Identity", &result->player); if(result->player.length == 0) ffDBusGetPropertyString(data, busName, "/org/mpris/MediaPlayer2", "org.mpris.MediaPlayer2", "DesktopEntry", &result->player); if(result->player.length == 0) ffStrbufAppend(&result->player, &result->playerId); } data->lib->ffdbus_message_unref(reply); return true; } static void getCustomBus(FFDBusData* data, const FFstrbuf* playerName, FFMediaResult* result) { if(ffStrbufStartsWithS(playerName, FF_DBUS_MPRIS_PREFIX)) { getBusProperties(data, playerName->chars, result); return; } FF_STRBUF_AUTO_DESTROY busName = ffStrbufCreateS(FF_DBUS_MPRIS_PREFIX); ffStrbufAppend(&busName, playerName); getBusProperties(data, busName.chars, result); } static void getBestBus(FFDBusData* data, FFMediaResult* result) { if( getBusProperties(data, FF_DBUS_MPRIS_PREFIX "spotify", result) || getBusProperties(data, FF_DBUS_MPRIS_PREFIX "vlc", result) || getBusProperties(data, FF_DBUS_MPRIS_PREFIX "plasma-browser-integration", result) ) return; DBusMessage* reply = ffDBusGetMethodReply(data, "org.freedesktop.DBus", "/org/freedesktop/DBus", "org.freedesktop.DBus", "ListNames", NULL, NULL); if(reply == NULL) return; DBusMessageIter rootIterator; if(!data->lib->ffdbus_message_iter_init(reply, &rootIterator) || data->lib->ffdbus_message_iter_get_arg_type(&rootIterator) != DBUS_TYPE_ARRAY) return; DBusMessageIter arrayIterator; data->lib->ffdbus_message_iter_recurse(&rootIterator, &arrayIterator); while(true) { if(data->lib->ffdbus_message_iter_get_arg_type(&arrayIterator) != DBUS_TYPE_STRING) FF_DBUS_ITER_CONTINUE(data, &arrayIterator) const char* busName; data->lib->ffdbus_message_iter_get_basic(&arrayIterator, &busName); if(!ffStrStartsWith(busName, FF_DBUS_MPRIS_PREFIX)) FF_DBUS_ITER_CONTINUE(data, &arrayIterator) if(getBusProperties(data, busName, result)) break; FF_DBUS_ITER_CONTINUE(data, &arrayIterator) } data->lib->ffdbus_message_unref(reply); } static const char* getMedia(FFMediaResult* result) { FF_DBUS_AUTO_DESTROY_DATA FFDBusData data = {}; const char* error = ffDBusLoadData(DBUS_BUS_SESSION, &data); if(error != NULL) return error; // FIXME: This is shared for both player and media module. // However it uses an option in one specific module if(instance.config.general.playerName.length > 0) getCustomBus(&data, &instance.config.general.playerName, result); else getBestBus(&data, result); return NULL; } #endif void ffDetectMediaImpl(FFMediaResult* media, bool saveCover) { FF_UNUSED(saveCover); // We don't save the cover to a file for Mpris implementation #ifdef FF_HAVE_DBUS const char* error = getMedia(media); ffStrbufAppendS(&media->error, error); #else ffStrbufAppendS(&media->error, "Fastfetch was compiled without DBus support"); #endif } ================================================ FILE: src/detection/media/media_nosupport.c ================================================ #include "media.h" void ffDetectMediaImpl(FFMediaResult* media) { ffStrbufAppendS(&media->error, "Not supported on this platform"); } ================================================ FILE: src/detection/media/media_windows.c ================================================ #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); } ================================================ FILE: src/detection/media/media_windows.dll.cpp ================================================ #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" ================================================ FILE: src/detection/media/media_windows.dll.h ================================================ #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); ================================================ FILE: src/detection/memory/memory.h ================================================ #pragma once #include "fastfetch.h" #include "modules/memory/option.h" typedef struct FFMemoryResult { uint64_t bytesUsed; uint64_t bytesTotal; } FFMemoryResult; const char* ffDetectMemory(FFMemoryResult* ram); ================================================ FILE: src/detection/memory/memory_apple.c ================================================ #include "memory.h" #include "common/debug.h" #include #include #include #include const char* ffDetectMemory(FFMemoryResult* ram) { size_t length = sizeof(ram->bytesTotal); #if FF_APPLE_MEMSIZE_USABLE if (sysctlbyname("hw.memsize_usable", &ram->bytesTotal, &length, NULL, 0) != 0) return "Failed to read hw.memsize_usable"; #else if (sysctl((int[]){ CTL_HW, HW_MEMSIZE }, 2, &ram->bytesTotal, &length, NULL, 0) != 0) return "Failed to read hw.memsize"; #endif mach_msg_type_number_t count = HOST_VM_INFO64_COUNT; vm_statistics64_data_t vmstat; if(host_statistics64(mach_host_self(), HOST_VM_INFO64, (host_info64_t) (&vmstat), &count) != KERN_SUCCESS) return "Failed to read host_statistics64"; // https://github.com/st3fan/osx-10.9/blob/34e34a6a539b5a822cda4074e56a7ced9b57da71/system_cmds-597.1.1/vm_stat.tproj/vm_stat.c#L139 uint64_t pagesFree = vmstat.free_count - vmstat.speculative_count; uint64_t pagesFileBacked = vmstat.external_page_count; // Cached files ram->bytesUsed = ram->bytesTotal - (pagesFree + pagesFileBacked) * instance.state.platform.sysinfo.pageSize; return NULL; } ================================================ FILE: src/detection/memory/memory_bsd.c ================================================ #include "memory.h" #include "common/sysctl.h" const char* ffDetectMemory(FFMemoryResult* ram) { size_t length = sizeof(ram->bytesTotal); if (sysctl((int[]){ CTL_HW, HW_PHYSMEM }, 2, &ram->bytesTotal, &length, NULL, 0)) return "Failed to read hw.physmem"; // vm.stats.vm.* are int values int32_t pagesFree = ffSysctlGetInt("vm.stats.vm.v_free_count", 0) + ffSysctlGetInt("vm.stats.vm.v_inactive_count", 0) + ffSysctlGetInt("vm.stats.vm.v_cache_count", 0); ram->bytesUsed = ram->bytesTotal - (uint64_t) pagesFree * instance.state.platform.sysinfo.pageSize; return NULL; } ================================================ FILE: src/detection/memory/memory_haiku.c ================================================ #include "memory.h" #include const char* ffDetectMemory(FFMemoryResult* ram) { system_info info; if (get_system_info(&info) != B_OK) return "Error getting system info"; uint32_t pageSize = instance.state.platform.sysinfo.pageSize; ram->bytesTotal = pageSize * info.max_pages; ram->bytesUsed = pageSize * info.used_pages; return NULL; } ================================================ FILE: src/detection/memory/memory_linux.c ================================================ #include "memory.h" #include "common/io.h" #include "common/mallocHelper.h" #include const char* ffDetectMemory(FFMemoryResult* ram) { char buf[PROC_FILE_BUFFSIZ]; ssize_t nRead = ffReadFileData("/proc/meminfo", ARRAY_SIZE(buf) - 1, buf); if(nRead < 0) return "ffReadFileData(\"/proc/meminfo\", ARRAY_SIZE(buf)-1, buf)"; buf[nRead] = '\0'; uint64_t memTotal = 0, memAvailable = 0, shmem = 0, memFree = 0, buffers = 0, cached = 0, sReclaimable = 0; char *token = NULL; if((token = strstr(buf, "MemTotal:")) != NULL) memTotal = strtoul(token + strlen("MemTotal:"), NULL, 10); else return "MemTotal not found in /proc/meminfo"; if((token = strstr(buf, "MemAvailable:")) != NULL) memAvailable = strtoul(token + strlen("MemAvailable:"), NULL, 10); if (memAvailable == 0 || memAvailable >= memTotal) // MemAvailable can be unreasonable. #1988 { if((token = strstr(buf, "MemFree:")) != NULL) memFree = strtoul(token + strlen("MemFree:"), NULL, 10); if((token = strstr(buf, "Buffers:")) != NULL) buffers = strtoul(token + strlen("Buffers:"), NULL, 10); if((token = strstr(buf, "Cached:")) != NULL) cached = strtoul(token + strlen("Cached:"), NULL, 10); if((token = strstr(buf, "Shmem:")) != NULL) shmem = strtoul(token + strlen("Shmem:"), NULL, 10); if((token = strstr(buf, "SReclaimable:")) != NULL) sReclaimable = strtoul(token + strlen("SReclaimable:"), NULL, 10); memAvailable = memFree + buffers + cached + sReclaimable - shmem; } ram->bytesTotal = memTotal * 1024lu; ram->bytesUsed = (memTotal - memAvailable) * 1024lu; return NULL; } ================================================ FILE: src/detection/memory/memory_nbsd.c ================================================ #include "memory.h" #include "common/sysctl.h" #include #include const char* ffDetectMemory(FFMemoryResult* ram) { struct uvmexp_sysctl buf; size_t length = sizeof(buf); if (sysctl((int[]){ CTL_VM, VM_UVMEXP2 }, 2, &buf, &length, NULL, 0) < 0) return "sysctl(CTL_VM, VM_UVMEXP2) failed"; ram->bytesTotal = (uint64_t) buf.npages * instance.state.platform.sysinfo.pageSize; ram->bytesUsed = ((uint64_t) buf.active + (uint64_t) buf.inactive + (uint64_t) buf.wired) * instance.state.platform.sysinfo.pageSize; return NULL; } ================================================ FILE: src/detection/memory/memory_nosupport.c ================================================ #include "memory.h" const char* ffDetectMemory(FF_MAYBE_UNUSED FFMemoryResult* ram) { return "Not supported on this platform"; } ================================================ FILE: src/detection/memory/memory_obsd.c ================================================ #include "memory.h" #include "common/sysctl.h" #include #include const char* ffDetectMemory(FFMemoryResult* ram) { struct uvmexp buf; size_t length = sizeof(buf); if (sysctl((int[]){ CTL_VM, VM_UVMEXP }, 2, &buf, &length, NULL, 0) < 0) return "sysctl(CTL_VM, VM_UVMEXP) failed"; ram->bytesTotal = (uint64_t) buf.npages * instance.state.platform.sysinfo.pageSize; ram->bytesUsed = ((uint64_t) buf.active + (uint64_t) buf.inactive + (uint64_t) buf.wired) * instance.state.platform.sysinfo.pageSize; return NULL; } ================================================ FILE: src/detection/memory/memory_sunos.c ================================================ #include "memory.h" #include const char* ffDetectMemory(FFMemoryResult* ram) { ram->bytesTotal = (uint64_t) sysconf(_SC_PHYS_PAGES) * instance.state.platform.sysinfo.pageSize; ram->bytesUsed = ram->bytesTotal - (uint64_t) sysconf(_SC_AVPHYS_PAGES) * instance.state.platform.sysinfo.pageSize; return NULL; } ================================================ FILE: src/detection/memory/memory_windows.c ================================================ #include "memory.h" #include const char* ffDetectMemory(FFMemoryResult* ram) { MEMORYSTATUSEX statex = { .dwLength = sizeof(statex), }; // GlobalMemoryStatusEx() internally uses // NtQuerySystemInformation(SystemBasicPerformanceInformation) in Win 7, and // NtQuerySystemInformation(SystemMemoryUsageInformation) in Win 10 if (!GlobalMemoryStatusEx(&statex)) return "GlobalMemoryStatusEx() failed"; ram->bytesTotal = statex.ullTotalPhys; ram->bytesUsed = statex.ullTotalPhys - statex.ullAvailPhys; return NULL; } ================================================ FILE: src/detection/mouse/mouse.h ================================================ #include "fastfetch.h" typedef struct FFMouseDevice { FFstrbuf serial; FFstrbuf name; } FFMouseDevice; const char* ffDetectMouse(FFlist* devices /* List of FFMouseDevice */); ================================================ FILE: src/detection/mouse/mouse_apple.c ================================================ #include "mouse.h" #include "common/apple/cf_helpers.h" #include "common/mallocHelper.h" #include #include static void enumSet(IOHIDDeviceRef value, FFlist* results) { FFMouseDevice* device = (FFMouseDevice*) ffListAdd(results); ffStrbufInit(&device->serial); ffStrbufInit(&device->name); CFStringRef product = IOHIDDeviceGetProperty(value, CFSTR(kIOHIDProductKey)); ffCfStrGetString(product, &device->name); CFStringRef serialNumber = IOHIDDeviceGetProperty(value, CFSTR(kIOHIDSerialNumberKey)); ffCfStrGetString(serialNumber, &device->serial); } const char* ffDetectMouse(FFlist* devices /* List of FFMouseDevice */) { IOHIDManagerRef FF_CFTYPE_AUTO_RELEASE manager = IOHIDManagerCreate(kCFAllocatorDefault, kIOHIDOptionsTypeNone); if (IOHIDManagerOpen(manager, kIOHIDOptionsTypeNone) != kIOReturnSuccess) return "IOHIDManagerOpen() failed"; CFDictionaryRef FF_CFTYPE_AUTO_RELEASE matching1 = CFDictionaryCreate(kCFAllocatorDefault, (const void **)(CFStringRef[]){ CFSTR(kIOHIDDeviceUsagePageKey), CFSTR(kIOHIDDeviceUsageKey) }, (const void **)(CFNumberRef[]){ ffCfCreateInt(kHIDPage_GenericDesktop), ffCfCreateInt(kHIDUsage_GD_Mouse) }, 2, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks); IOHIDManagerSetDeviceMatching(manager, matching1); CFSetRef FF_CFTYPE_AUTO_RELEASE set = IOHIDManagerCopyDevices(manager); if (set) CFSetApplyFunction(set, (CFSetApplierFunction) &enumSet, devices); IOHIDManagerClose(manager, kIOHIDOptionsTypeNone); return NULL; } ================================================ FILE: src/detection/mouse/mouse_bsd.c ================================================ #include "mouse.h" #include "common/io.h" #include #include #include #if __has_include() #include // FreeBSD #else #include // DragonFly #endif #define MAX_UHID_MICE 64 const char* ffDetectMouse(FFlist* devices /* List of FFMouseDevice */) { char path[16]; for (int i = 0; i < MAX_UHID_MICE; i++) { snprintf(path, ARRAY_SIZE(path), "/dev/uhid%d", i); FF_AUTO_CLOSE_FD int fd = open(path, O_RDONLY | O_CLOEXEC); if (fd < 0) { if (errno == ENOENT) break; // No more devices continue; // Device not found } report_desc_t repDesc = hid_get_report_desc(fd); if (!repDesc) continue; int reportId = hid_get_report_id(fd); struct hid_data* hData = hid_start_parse(repDesc, 0, reportId); if (hData) { struct hid_item hItem; while (hid_get_item(hData, &hItem) > 0) { if (HID_PAGE(hItem.usage) != 1 || HID_USAGE(hItem.usage) != 2) continue; struct usb_device_info di; if (ioctl(fd, USB_GET_DEVICEINFO, &di) != -1) { FFMouseDevice* device = (FFMouseDevice*) ffListAdd(devices); ffStrbufInitS(&device->serial, di.udi_serial); ffStrbufInitS(&device->name, di.udi_product); } } hid_end_parse(hData); } hid_dispose_report_desc(repDesc); } return NULL; } ================================================ FILE: src/detection/mouse/mouse_haiku.cpp ================================================ extern "C" { #include "mouse.h" } #include #include const char* ffDetectMouse(FFlist* devices /* List of FFMouseDevice */) { BList list; if (get_input_devices(&list) != B_OK) return "get_input_devices() failed"; for (int32 i = 0, n = list.CountItems(); i < n; i++) { BInputDevice *device = (BInputDevice *) list.ItemAt(i); if (device->Type() != B_POINTING_DEVICE || !device->IsRunning()) continue; FFMouseDevice* item = (FFMouseDevice*) ffListAdd(devices); ffStrbufInit(&item->serial); ffStrbufInitS(&item->name, device->Name()); } return NULL; } ================================================ FILE: src/detection/mouse/mouse_linux.c ================================================ #include "mouse.h" #include "common/io.h" #include "common/stringUtils.h" const char* ffDetectMouse(FFlist* devices /* List of FFMouseDevice */) { FF_AUTO_CLOSE_DIR DIR* dirp = opendir("/sys/class/input/"); if (dirp == NULL) return "opendir(\"/sys/class/input/\") == NULL"; FF_STRBUF_AUTO_DESTROY path = ffStrbufCreateS("/sys/class/input/"); uint32_t baseLen = path.length; struct dirent* entry; while ((entry = readdir(dirp)) != NULL) { if (!ffStrStartsWith(entry->d_name, "mouse")) continue; if (!ffCharIsDigit(entry->d_name[strlen("mouse")])) continue; ffStrbufAppendS(&path, entry->d_name); ffStrbufAppendS(&path, "/device/name"); FF_STRBUF_AUTO_DESTROY name = ffStrbufCreate(); if (ffAppendFileBuffer(path.chars, &name)) { ffStrbufTrimRightSpace(&name); ffStrbufSubstrBefore(&path, path.length - 4); FFMouseDevice* device = (FFMouseDevice*) ffListAdd(devices); ffStrbufInitMove(&device->name, &name); ffStrbufInit(&device->serial); ffStrbufAppendS(&path, "uniq"); if (ffAppendFileBuffer(path.chars, &device->serial)) ffStrbufTrimRightSpace(&device->serial); } ffStrbufSubstrBefore(&path, baseLen); } return NULL; } ================================================ FILE: src/detection/mouse/mouse_nosupport.c ================================================ #include "mouse.h" const char* ffDetectMouse(FF_MAYBE_UNUSED FFlist* devices /* List of FFMouseDevice */) { return "No mouse support on this platform"; } ================================================ FILE: src/detection/mouse/mouse_windows.c ================================================ #define INITGUID #include "mouse.h" #include "common/io.h" #include "common/mallocHelper.h" #include "common/windows/unicode.h" #include #include #include #include const char* ffDetectMouse(FFlist* devices /* List of FFMouseDevice */) { UINT nDevices = 0; if (GetRawInputDeviceList(NULL, &nDevices, sizeof(RAWINPUTDEVICELIST))) return "GetRawInputDeviceList(NULL) failed"; if (nDevices == 0) return "No HID devices found"; RAWINPUTDEVICELIST* FF_AUTO_FREE pRawInputDeviceList = (RAWINPUTDEVICELIST*) malloc(sizeof(RAWINPUTDEVICELIST) * nDevices); if ((nDevices = GetRawInputDeviceList(pRawInputDeviceList, &nDevices, sizeof(RAWINPUTDEVICELIST))) == (UINT) -1) return "GetRawInputDeviceList(pRawInputDeviceList) failed"; for (UINT i = 0; i < nDevices; ++i) { if (pRawInputDeviceList[i].dwType != RIM_TYPEMOUSE) continue; HANDLE hDevice = pRawInputDeviceList[i].hDevice; RID_DEVICE_INFO rdi; UINT rdiSize = sizeof(rdi); if (GetRawInputDeviceInfoW(hDevice, RIDI_DEVICEINFO, &rdi, &rdiSize) == (UINT) -1) continue; WCHAR devName[MAX_PATH]; UINT nameSize = MAX_PATH; if (GetRawInputDeviceInfoW(hDevice, RIDI_DEVICENAME, devName, &nameSize) == (UINT) -1) continue; FFMouseDevice* device = (FFMouseDevice*) ffListAdd(devices); ffStrbufInit(&device->serial); ffStrbufInit(&device->name); wchar_t buffer[MAX_PATH]; HANDLE FF_AUTO_CLOSE_FD hHidFile = CreateFileW(devName, 0 /* must be 0 instead of GENERIC_READ */, FILE_SHARE_READ, NULL, OPEN_EXISTING, 0, NULL); if (hHidFile != INVALID_HANDLE_VALUE) { if (HidD_GetProductString(hHidFile, buffer, (ULONG) sizeof(buffer))) ffStrbufSetWS(&device->name, buffer); if (HidD_GetSerialNumberString(hHidFile, buffer, sizeof(buffer))) ffStrbufSetWS(&device->serial, buffer); } if (!device->name.length) { // https://stackoverflow.com/a/64321096/9976392 DEVPROPTYPE propertyType; ULONG propertySize = sizeof(buffer); if (CM_Get_Device_Interface_PropertyW(devName, &DEVPKEY_Device_InstanceId, &propertyType, (PBYTE) buffer, &propertySize, 0) == CR_SUCCESS) { DEVINST devInst; if (CM_Locate_DevNodeW(&devInst, buffer, CM_LOCATE_DEVNODE_NORMAL) == CR_SUCCESS) { propertySize = sizeof(buffer); if (CM_Get_DevNode_PropertyW(devInst, &DEVPKEY_NAME, &propertyType, (PBYTE) buffer, &propertySize, 0) == CR_SUCCESS) ffStrbufSetWS(&device->name, buffer); } } } if (!device->name.length) ffStrbufSetF(&device->name, "Unknown device %04X-%04X", (unsigned) rdi.hid.dwVendorId, (unsigned) rdi.hid.dwProductId); } return NULL; } ================================================ FILE: src/detection/netio/netio.c ================================================ #include "netio.h" #include "common/time.h" static FFlist ioCounters1; static uint64_t time1; void ffPrepareNetIO(FFNetIOOptions* options) { if (options->detectTotal) return; if (time1 != 0) return; // Already prepared ffListInit(&ioCounters1, sizeof(FFNetIOResult)); ffNetIOGetIoCounters(&ioCounters1, options); time1 = ffTimeGetNow(); } const char* ffDetectNetIO(FFlist* result, FFNetIOOptions* options) { const char* error = NULL; if (options->detectTotal) { error = ffNetIOGetIoCounters(result, options); if (error) return error; return NULL; } if (time1 == 0) { ffListInit(&ioCounters1, sizeof(FFNetIOResult)); error = ffNetIOGetIoCounters(&ioCounters1, options); if (error) return error; time1 = ffTimeGetNow(); } if (ioCounters1.length == 0) return "No network interfaces found"; uint64_t time2 = ffTimeGetNow(); while (time2 - time1 < options->waitTime) { ffTimeSleep((uint32_t) (options->waitTime - (time2 - time1))); time2 = ffTimeGetNow(); } error = ffNetIOGetIoCounters(result, options); if (error) return error; if (result->length != ioCounters1.length) return "Different number of network interfaces. Network change?"; for (uint32_t i = 0; i < result->length; ++i) { FFNetIOResult* icPrev = FF_LIST_GET(FFNetIOResult, ioCounters1, i); FFNetIOResult* icCurr = FF_LIST_GET(FFNetIOResult, *result, i); if (!ffStrbufEqual(&icPrev->name, &icCurr->name)) return "Network interface name changed"; static_assert(sizeof(FFNetIOResult) - offsetof(FFNetIOResult, txBytes) == sizeof(uint64_t) * 8, "Unexpected struct FFNetIOResult layout"); for (size_t off = offsetof(FFNetIOResult, txBytes); off < sizeof(FFNetIOResult); off += sizeof(uint64_t)) { uint64_t* prevValue = (uint64_t*) ((uint8_t*) icPrev + off); uint64_t* currValue = (uint64_t*) ((uint8_t*) icCurr + off); uint64_t temp = *currValue; *currValue -= *prevValue; *currValue /= (time2 - time1) / 1000 /* seconds */; *prevValue = temp; } } time1 = time2; return NULL; } ================================================ FILE: src/detection/netio/netio.h ================================================ #pragma once #include "fastfetch.h" #include "modules/netio/option.h" typedef struct FFNetIOResult { FFstrbuf name; bool defaultRoute; uint64_t txBytes; uint64_t rxBytes; uint64_t txPackets; uint64_t rxPackets; uint64_t rxErrors; uint64_t txErrors; uint64_t rxDrops; uint64_t txDrops; } FFNetIOResult; const char* ffDetectNetIO(FFlist* result, FFNetIOOptions* options); const char* ffNetIOGetIoCounters(FFlist* result, FFNetIOOptions* options); ================================================ FILE: src/detection/netio/netio_apple.c ================================================ #include "netio.h" #include "common/netif.h" #include "common/mallocHelper.h" #include #include #include const char* ffNetIOGetIoCounters(FFlist* result, FFNetIOOptions* options) { int mib[] = {CTL_NET, PF_LINK, NETLINK_GENERIC, options->defaultRouteOnly ? IFMIB_IFDATA : IFMIB_IFALLDATA, options->defaultRouteOnly ? (int) ffNetifGetDefaultRouteV4()->ifIndex : 0, IFDATA_GENERAL}; size_t bufSize = 0; if (sysctl(mib, ARRAY_SIZE(mib), NULL, &bufSize, 0, 0) < 0) return "sysctl(mib, ARRAY_SIZE(mib), NULL, &bufSize, 0, 0) failed"; assert(bufSize % sizeof(struct ifmibdata) == 0); FF_AUTO_FREE struct ifmibdata* buf = (struct ifmibdata*) malloc(bufSize); if (sysctl(mib, ARRAY_SIZE(mib), buf, &bufSize, 0, 0) < 0) return "sysctl(mib, ARRAY_SIZE(mib), buf, &bufSize, 0, 0) failed"; size_t ifCount = bufSize / sizeof(struct ifmibdata); const char* defaultRouteIfName = ffNetifGetDefaultRouteV4()->ifName; for (size_t i = 0; i < ifCount; i++) { struct ifmibdata* mibdata = &buf[i]; if (!(mibdata->ifmd_flags & IFF_RUNNING) || (mibdata->ifmd_flags & IFF_NOARP)) continue; if (options->namePrefix.length && strncmp(mibdata->ifmd_name, options->namePrefix.chars, options->namePrefix.length) != 0) continue; FFNetIOResult* counters = (FFNetIOResult*) ffListAdd(result); *counters = (FFNetIOResult) { .name = ffStrbufCreateS(mibdata->ifmd_name), .txBytes = mibdata->ifmd_data.ifi_obytes, .rxBytes = mibdata->ifmd_data.ifi_ibytes, .txPackets = mibdata->ifmd_data.ifi_opackets, .rxPackets = mibdata->ifmd_data.ifi_ipackets, .txErrors = mibdata->ifmd_data.ifi_oerrors, .rxErrors = mibdata->ifmd_data.ifi_ierrors, .txDrops = mibdata->ifmd_snd_drops, .rxDrops = mibdata->ifmd_data.ifi_iqdrops, .defaultRoute = strncmp(mibdata->ifmd_name, defaultRouteIfName, IFNAMSIZ) == 0, }; } return NULL; } ================================================ FILE: src/detection/netio/netio_bsd.c ================================================ #include "netio.h" #include "common/netif.h" #include "common/mallocHelper.h" #include #include #include #include #include #include const char* ffNetIOGetIoCounters(FFlist* result, FFNetIOOptions* options) { uint32_t defaultRouteIfIndex = ffNetifGetDefaultRouteV4()->ifIndex; size_t bufSize = 0; if (sysctl((int[]) { CTL_NET, PF_ROUTE, 0, 0, NET_RT_IFLIST, (options->defaultRouteOnly ? (int) defaultRouteIfIndex : 0) }, 6, NULL, &bufSize, 0, 0) < 0) return "sysctl({ CTL_NET, PF_ROUTE, 0, 0, NET_RT_IFLIST, ifIndex }, 6, NULL, &bufSize, 0, 0) failed"; FF_AUTO_FREE struct if_msghdr* buf = (struct if_msghdr*) malloc(bufSize); if (sysctl((int[]) { CTL_NET, PF_ROUTE, 0, 0, NET_RT_IFLIST, (options->defaultRouteOnly ? (int) defaultRouteIfIndex : 0) }, 6, buf, &bufSize, 0, 0) < 0) return "sysctl({ CTL_NET, PF_ROUTE, 0, 0, NET_RT_IFLIST, ifIndex }, 6, buf, &bufSize, 0, 0) failed"; for (struct if_msghdr* ifm = buf; ifm < (struct if_msghdr*) ((uint8_t*) buf + bufSize); ifm = (struct if_msghdr*) ((uint8_t*) ifm + ifm->ifm_msglen)) { if (ifm->ifm_type != RTM_IFINFO || !(ifm->ifm_flags & IFF_RUNNING) || (ifm->ifm_flags & IFF_NOARP)) continue; struct sockaddr_dl* sdl = (struct sockaddr_dl*) (ifm + 1); assert(sdl->sdl_family == AF_LINK); sdl->sdl_data[sdl->sdl_nlen] = 0; if (options->namePrefix.length && strncmp(sdl->sdl_data, options->namePrefix.chars, options->namePrefix.length) != 0) continue; FFNetIOResult* counters = (FFNetIOResult*) ffListAdd(result); *counters = (FFNetIOResult) { .name = ffStrbufCreateNS(sdl->sdl_nlen, sdl->sdl_data), .txBytes = ifm->ifm_data.ifi_obytes, .rxBytes = ifm->ifm_data.ifi_ibytes, .txPackets = ifm->ifm_data.ifi_opackets, .rxPackets = ifm->ifm_data.ifi_ipackets, .txErrors = ifm->ifm_data.ifi_oerrors, .rxErrors = ifm->ifm_data.ifi_ierrors, #ifdef FF_HAVE_IFI_OQDROPS .txDrops = ifm->ifm_data.ifi_oqdrops, #else .txDrops = 0, // unsupported #endif .rxDrops = ifm->ifm_data.ifi_iqdrops, .defaultRoute = sdl->sdl_index == defaultRouteIfIndex, }; } return NULL; } ================================================ FILE: src/detection/netio/netio_haiku.cpp ================================================ extern "C" { #include "netio.h" #include "common/netif.h" } #include #include const char* ffNetIOGetIoCounters(FFlist* result, FFNetIOOptions* options) { BNetworkRoster& roster = BNetworkRoster::Default(); BNetworkInterface interface; uint32 cookie = 0; uint32_t defaultRouteIfIndex = ffNetifGetDefaultRouteV4()->ifIndex; while (roster.GetNextInterface(&cookie, interface) == B_OK) { if (!interface.Exists()) continue; bool defaultRoute = interface.Index() == defaultRouteIfIndex; if (options->defaultRouteOnly && !defaultRoute) continue; if (options->namePrefix.length && strncmp(interface.Name(), options->namePrefix.chars, options->namePrefix.length) != 0) continue; ifreq_stats stats = {}; if (interface.GetStats(stats) != B_OK) continue; FFNetIOResult* counters = (FFNetIOResult*) ffListAdd(result); *counters = (FFNetIOResult) { .name = ffStrbufCreateS(interface.Name()), .defaultRoute = defaultRoute, .txBytes = stats.send.bytes, .rxBytes = stats.receive.bytes, .txPackets = stats.send.packets, .rxPackets = stats.receive.packets, .rxErrors = stats.receive.errors, .txErrors = stats.send.errors, .rxDrops = stats.receive.dropped, .txDrops = stats.send.dropped }; } return NULL; } ================================================ FILE: src/detection/netio/netio_linux.c ================================================ #include "netio.h" #include "common/io.h" #include "common/netif.h" #include "common/stringUtils.h" #include #include static void getData(FFstrbuf* buffer, const char* ifName, bool isDefaultRoute, int basefd, FFlist* result) { FF_AUTO_CLOSE_FD int dfd = openat(basefd, ifName, O_RDONLY | O_DIRECTORY); if (dfd < 0) return; char operstate; if(!ffReadFileDataRelative(dfd, "operstate", 1, &operstate) || operstate != 'u' /* up or unknown */) return; FFNetIOResult* counters = (FFNetIOResult*) ffListAdd(result); ffStrbufInitS(&counters->name, ifName); counters->defaultRoute = isDefaultRoute; if (ffReadFileBufferRelative(dfd, "statistics/rx_bytes", buffer)) counters->rxBytes = ffStrbufToUInt(buffer, 0); if (ffReadFileBufferRelative(dfd, "statistics/tx_bytes", buffer)) counters->txBytes = ffStrbufToUInt(buffer, 0); if (ffReadFileBufferRelative(dfd, "statistics/rx_packets", buffer)) counters->rxPackets = ffStrbufToUInt(buffer, 0); if (ffReadFileBufferRelative(dfd, "statistics/tx_packets", buffer)) counters->txPackets = ffStrbufToUInt(buffer, 0); if (ffReadFileBufferRelative(dfd, "statistics/rx_errors", buffer)) counters->rxErrors = ffStrbufToUInt(buffer, 0); if (ffReadFileBufferRelative(dfd, "statistics/tx_errors", buffer)) counters->txErrors = ffStrbufToUInt(buffer, 0); if (ffReadFileBufferRelative(dfd, "statistics/rx_dropped", buffer)) counters->rxDrops = ffStrbufToUInt(buffer, 0); if (ffReadFileBufferRelative(dfd, "statistics/tx_dropped", buffer)) counters->txDrops = ffStrbufToUInt(buffer, 0); } const char* ffNetIOGetIoCounters(FFlist* result, FFNetIOOptions* options) { FF_AUTO_CLOSE_DIR DIR* dirp = opendir("/sys/class/net"); if (!dirp) return "opendir(\"/sys/class/net\") == NULL"; FF_STRBUF_AUTO_DESTROY buffer = ffStrbufCreate(); const char* defaultRouteIfName = ffNetifGetDefaultRouteV4()->ifName; if (options->defaultRouteOnly) { if (options->namePrefix.length && strncmp(defaultRouteIfName, options->namePrefix.chars, options->namePrefix.length) != 0) return NULL; getData(&buffer, defaultRouteIfName, true, dirfd(dirp), result); } else { struct dirent* entry; while((entry = readdir(dirp)) != NULL) { const char* ifName = entry->d_name; if(ifName[0] == '.') continue; if (options->namePrefix.length && strncmp(ifName, options->namePrefix.chars, options->namePrefix.length) != 0) continue; getData(&buffer, ifName, ffStrEquals(ifName, defaultRouteIfName), dirfd(dirp), result); } } return NULL; } ================================================ FILE: src/detection/netio/netio_nosupport.c ================================================ #include "netio.h" const char* ffNetIOGetIoCounters(FF_MAYBE_UNUSED FFlist* result, FF_MAYBE_UNUSED FFNetIOOptions* options) { return "Not supported on this platform"; } ================================================ FILE: src/detection/netio/netio_sunos.c ================================================ #include "netio.h" #include "common/netif.h" #include "common/stringUtils.h" #include static inline void kstatFreeWrap(kstat_ctl_t** pkc) { assert(pkc); if (*pkc) kstat_close(*pkc); } const char* ffNetIOGetIoCounters(FFlist* result, FFNetIOOptions* options) { __attribute__((__cleanup__(kstatFreeWrap))) kstat_ctl_t* kc = kstat_open(); if (!kc) return "kstat_open() failed"; const char* defaultRouteIfName = ffNetifGetDefaultRouteV4()->ifName; for (kstat_t* ks = kc->kc_chain; ks; ks = ks->ks_next) { if (!ffStrEquals(ks->ks_class, "net") || !ffStrEquals(ks->ks_module, "link")) continue; if (options->namePrefix.length && strncmp(ks->ks_name, options->namePrefix.chars, options->namePrefix.length) != 0) continue; bool isDefaultRoute = ffStrEquals(ks->ks_name, defaultRouteIfName); if (options->defaultRouteOnly && !isDefaultRoute) continue; if (kstat_read(kc, ks, NULL) < 0) continue; FFNetIOResult* counters = (FFNetIOResult*) ffListAdd(result); kstat_named_t* wbytes = (kstat_named_t*) kstat_data_lookup(ks, "obytes64"); kstat_named_t* rbytes = (kstat_named_t*) kstat_data_lookup(ks, "rbytes64"); kstat_named_t* wpkts = (kstat_named_t*) kstat_data_lookup(ks, "opackets64"); kstat_named_t* rpkts = (kstat_named_t*) kstat_data_lookup(ks, "ipackets64"); kstat_named_t* werrs = (kstat_named_t*) kstat_data_lookup(ks, "oerrors"); kstat_named_t* rerrs = (kstat_named_t*) kstat_data_lookup(ks, "ierrors"); *counters = (FFNetIOResult) { .name = ffStrbufCreateS(ks->ks_name), .txBytes = wbytes->value.ui64, .rxBytes = rbytes->value.ui64, .txPackets = wpkts->value.ui64, .rxPackets = rpkts->value.ui64, .txErrors = werrs->value.ui64, .rxErrors = rerrs->value.ui64, .txDrops = 0, // unsupported .rxDrops = 0, .defaultRoute = isDefaultRoute, }; if (options->defaultRouteOnly) break; } return NULL; } ================================================ FILE: src/detection/netio/netio_windows.c ================================================ #include "netio.h" #include "common/netif.h" #include "common/mallocHelper.h" #include "common/windows/unicode.h" #include #include const char* ffNetIOGetIoCounters(FFlist* result, FFNetIOOptions* options) { IP_ADAPTER_ADDRESSES* FF_AUTO_FREE adapter_addresses = NULL; // Multiple attempts in case interfaces change while // we are in the middle of querying them. DWORD adapter_addresses_buffer_size = 0; for (int attempts = 0;; ++attempts) { if (adapter_addresses_buffer_size) { adapter_addresses = (IP_ADAPTER_ADDRESSES*)realloc(adapter_addresses, adapter_addresses_buffer_size); assert(adapter_addresses); } DWORD error = GetAdaptersAddresses( AF_UNSPEC, GAA_FLAG_SKIP_ANYCAST | GAA_FLAG_SKIP_MULTICAST | GAA_FLAG_SKIP_DNS_SERVER, NULL, adapter_addresses, &adapter_addresses_buffer_size); if (error == ERROR_SUCCESS) break; else if (ERROR_BUFFER_OVERFLOW == error && attempts < 4) continue; else return "GetAdaptersAddresses() failed"; } uint32_t defaultRouteIfIndex = ffNetifGetDefaultRouteV4()->ifIndex; // Iterate through all of the adapters for (IP_ADAPTER_ADDRESSES* adapter = adapter_addresses; adapter; adapter = adapter->Next) { bool isDefaultRoute = adapter->IfIndex == defaultRouteIfIndex; if (options->defaultRouteOnly && !isDefaultRoute) continue; FF_STRBUF_AUTO_DESTROY name = ffStrbufCreateWS(adapter->FriendlyName); if (options->namePrefix.length && !ffStrbufStartsWith(&name, &options->namePrefix)) continue; MIB_IF_ROW2 ifRow = { .InterfaceIndex = adapter->IfIndex }; if (GetIfEntry2(&ifRow) == NO_ERROR) { FFNetIOResult* counters = (FFNetIOResult*) ffListAdd(result); *counters = (FFNetIOResult) { .name = ffStrbufCreateMove(&name), .txBytes = ifRow.OutOctets, .rxBytes = ifRow.InOctets, .txPackets = (ifRow.OutUcastPkts + ifRow.OutNUcastPkts), .rxPackets = (ifRow.InUcastPkts + ifRow.InNUcastPkts), .rxErrors = ifRow.InErrors, .txErrors = ifRow.OutErrors, .rxDrops = ifRow.InDiscards, .txDrops = ifRow.OutDiscards, .defaultRoute = isDefaultRoute, }; } } return NULL; } ================================================ FILE: src/detection/opencl/opencl.c ================================================ #include "detection/opencl/opencl.h" #include "detection/gpu/gpu.h" #if !defined(FF_HAVE_OPENCL) && defined(__APPLE__) && defined(MAC_OS_X_VERSION_10_15) #define FF_HAVE_OPENCL 1 #endif #ifdef FF_HAVE_OPENCL #include "common/library.h" #include "common/parsing.h" #include "common/stringUtils.h" #include #define CL_TARGET_OPENCL_VERSION 110 #ifndef __APPLE__ #include #include #else #include #include #endif typedef struct OpenCLData { FF_LIBRARY_SYMBOL(clGetPlatformIDs) FF_LIBRARY_SYMBOL(clGetPlatformInfo) FF_LIBRARY_SYMBOL(clGetDeviceInfo) FF_LIBRARY_SYMBOL(clGetDeviceIDs) } OpenCLData; static const char* openCLHandleData(OpenCLData* data, FFOpenCLResult* result) { cl_platform_id platforms[32]; cl_uint numPlatforms = 0; cl_int ret = data->ffclGetPlatformIDs(ARRAY_SIZE(platforms), platforms, &numPlatforms); if (ret != CL_SUCCESS) { switch (ret) { #ifdef CL_PLATFORM_NOT_FOUND_KHR // not available on macOS case CL_PLATFORM_NOT_FOUND_KHR: return "clGetPlatformIDs() failed: CL_PLATFORM_NOT_FOUND_KHR"; #endif case CL_INVALID_VALUE: return "clGetPlatformIDs() failed: CL_INVALID_VALUE"; case CL_OUT_OF_HOST_MEMORY: return "clGetPlatformIDs() failed: CL_OUT_OF_HOST_MEMORY"; default: return "clGetPlatformIDs() failed: unknown error"; } } if (numPlatforms == 0) return "clGetPlatformIDs returned 0 platforms"; char buffer[1024]; for (cl_uint iplat = 0; iplat < numPlatforms; ++iplat) { if (data->ffclGetPlatformInfo(platforms[iplat], CL_PLATFORM_VERSION, sizeof(buffer), buffer, NULL) != CL_SUCCESS) return "clGetPlatformInfo() failed"; // Use the newest supported OpenCL version if (ffStrbufCompS(&result->version, buffer) < 0) { const char* versionPretty = buffer; if(ffStrStartsWithIgnCase(buffer, "OpenCL ")) versionPretty = buffer + strlen("OpenCL "); ffStrbufSetS(&result->version, versionPretty); ffStrbufTrim(&result->version, ' '); if (data->ffclGetPlatformInfo(platforms[iplat], CL_PLATFORM_NAME, sizeof(buffer), buffer, NULL) == CL_SUCCESS) ffStrbufSetS(&result->name, buffer); if (data->ffclGetPlatformInfo(platforms[iplat], CL_PLATFORM_VENDOR, sizeof(buffer), buffer, NULL) == CL_SUCCESS) ffStrbufSetS(&result->vendor, buffer); } cl_device_id deviceIDs[32]; cl_uint numDevices = (cl_uint) ARRAY_SIZE(deviceIDs); if (data->ffclGetDeviceIDs(platforms[iplat], CL_DEVICE_TYPE_GPU, numDevices, deviceIDs, &numDevices) != CL_SUCCESS) continue; for (cl_uint idev = 0; idev < numDevices; ++idev) { cl_device_id deviceID = deviceIDs[idev]; if (data->ffclGetDeviceInfo(deviceID, CL_DEVICE_NAME, sizeof(buffer), buffer, NULL) != CL_SUCCESS) continue; FFGPUResult* gpu = ffListAdd(&result->gpus); ffStrbufInitS(&gpu->name, buffer); ffStrbufInit(&gpu->vendor); ffStrbufInit(&gpu->driver); ffStrbufInit(&gpu->platformApi); ffStrbufInit(&gpu->memoryType); gpu->index = FF_GPU_INDEX_UNSET; gpu->temperature = FF_GPU_TEMP_UNSET; gpu->coreCount = FF_GPU_CORE_COUNT_UNSET; gpu->type = FF_GPU_TYPE_UNKNOWN; gpu->dedicated.total = gpu->dedicated.used = gpu->shared.total = gpu->shared.used = FF_GPU_VMEM_SIZE_UNSET; gpu->deviceId = (size_t) deviceID; gpu->frequency = FF_GPU_FREQUENCY_UNSET; gpu->coreUsage = FF_GPU_CORE_USAGE_UNSET; if (data->ffclGetDeviceInfo(deviceID, CL_DEVICE_VERSION, sizeof(buffer), buffer, NULL) == CL_SUCCESS) { ffStrbufSetS(&gpu->platformApi, buffer); ffStrbufTrimRight(&gpu->platformApi, ' '); } else ffStrbufSetStatic(&gpu->platformApi, "OpenCL"); { cl_uint vendorId; if (data->ffclGetDeviceInfo(deviceID, CL_DEVICE_VENDOR_ID, sizeof(vendorId), &vendorId, NULL) == CL_SUCCESS) ffStrbufSetStatic(&gpu->vendor, ffGPUGetVendorString(vendorId)); if (gpu->vendor.length == 0 && data->ffclGetDeviceInfo(deviceID, CL_DEVICE_VENDOR, sizeof(buffer), buffer, NULL) == CL_SUCCESS) ffStrbufSetS(&gpu->vendor, buffer); } if (data->ffclGetDeviceInfo(deviceID, CL_DRIVER_VERSION, sizeof(buffer), buffer, NULL) == CL_SUCCESS) { const char* versionPretty = strchr(buffer, ' '); if (versionPretty && *versionPretty) ffStrbufSetS(&gpu->driver, versionPretty + 1); else ffStrbufSetS(&gpu->driver, buffer); } { cl_uint value; if (data->ffclGetDeviceInfo(deviceID, CL_DEVICE_MAX_COMPUTE_UNITS, sizeof(value), &value, NULL) == CL_SUCCESS) gpu->coreCount = (int32_t) value; } { cl_uint value; if (data->ffclGetDeviceInfo(deviceID, CL_DEVICE_MAX_CLOCK_FREQUENCY, sizeof(value), &value, NULL) == CL_SUCCESS) gpu->frequency = value; } { cl_bool value; if (data->ffclGetDeviceInfo(deviceID, CL_DEVICE_HOST_UNIFIED_MEMORY, sizeof(value), &value, NULL) == CL_SUCCESS) { gpu->type = value ? FF_GPU_TYPE_INTEGRATED : FF_GPU_TYPE_DISCRETE; cl_ulong memSize; if (data->ffclGetDeviceInfo(deviceID, CL_DEVICE_GLOBAL_MEM_SIZE, sizeof(memSize), &memSize, NULL) == CL_SUCCESS) { if (gpu->type == FF_GPU_TYPE_INTEGRATED) gpu->shared.total = memSize; else gpu->dedicated.total = memSize; } } } } } return NULL; } static const char* detectOpenCL(FFOpenCLResult* result) { OpenCLData data; #ifndef __APPLE__ FF_LIBRARY_LOAD_MESSAGE(opencl, #ifdef _WIN32 "OpenCL" FF_LIBRARY_EXTENSION, -1, #endif "libOpenCL" FF_LIBRARY_EXTENSION, 1 ); FF_LIBRARY_LOAD_SYMBOL_VAR_MESSAGE(opencl, data, clGetPlatformIDs); FF_LIBRARY_LOAD_SYMBOL_VAR_MESSAGE(opencl, data, clGetPlatformInfo); FF_LIBRARY_LOAD_SYMBOL_VAR_MESSAGE(opencl, data, clGetDeviceIDs); FF_LIBRARY_LOAD_SYMBOL_VAR_MESSAGE(opencl, data, clGetDeviceInfo); return openCLHandleData(&data, result); #else data.ffclGetPlatformIDs = clGetPlatformIDs; data.ffclGetPlatformInfo = clGetPlatformInfo; data.ffclGetDeviceIDs = clGetDeviceIDs; data.ffclGetDeviceInfo = clGetDeviceInfo; return openCLHandleData(&data, result); #endif } #endif // defined(FF_HAVE_OPENCL) FFOpenCLResult* ffDetectOpenCL(void) { static FFOpenCLResult result; if (result.gpus.elementSize == 0) { ffStrbufInit(&result.version); ffStrbufInit(&result.name); ffStrbufInit(&result.vendor); ffListInit(&result.gpus, sizeof(FFGPUResult)); #ifdef FF_HAVE_OPENCL result.error = detectOpenCL(&result); #else result.error = "fastfetch was compiled without OpenCL support"; #endif } return &result; } ================================================ FILE: src/detection/opencl/opencl.h ================================================ #pragma once #include "fastfetch.h" #include "modules/opencl/option.h" typedef struct FFOpenCLResult { FFstrbuf version; FFstrbuf name; FFstrbuf vendor; FFlist gpus; //List of FFGPUResult, see detection/gpu/gpu.h const char* error; } FFOpenCLResult; FFOpenCLResult* ffDetectOpenCL(); ================================================ FILE: src/detection/opengl/opengl.h ================================================ #pragma once #include "fastfetch.h" #include "modules/opengl/option.h" typedef struct FFOpenGLResult { FFstrbuf version; FFstrbuf renderer; FFstrbuf vendor; FFstrbuf slv; FFstrbuf library; } FFOpenGLResult; #define FF_OPENGL_BUFFER_WIDTH 1 #define FF_OPENGL_BUFFER_HEIGHT 1 const char* ffDetectOpenGL(FFOpenGLOptions* options, FFOpenGLResult* result); ================================================ FILE: src/detection/opengl/opengl_apple.c ================================================ #include "fastfetch.h" #include "opengl.h" #define GL_SILENCE_DEPRECATION #include #include // This brings in CGL, not GL void ffOpenGLHandleResult(FFOpenGLResult* result, __typeof__(&glGetString) ffglGetString); static const char* cglHandleContext(FFOpenGLResult* result, CGLContextObj context) { if(CGLSetCurrentContext(context) != kCGLNoError) return "CGLSetCurrentContext() failed"; ffOpenGLHandleResult(result, &glGetString); GLint major, minor; CGLGetVersion(&major, &minor); ffStrbufSetF(&result->library, "CGL %d.%d", major, minor); return NULL; } static const char* cglHandlePixelFormat(FFOpenGLResult* result, CGLPixelFormatObj pixelFormat) { CGLContextObj context; if(CGLCreateContext(pixelFormat, NULL, &context) != kCGLNoError) return "CGLCreateContext() failed"; const char* error = cglHandleContext(result, context); CGLDestroyContext(context); return error; } const char* cglDetectOpenGL(FFOpenGLResult* result) { CGLPixelFormatObj pixelFormat; CGLPixelFormatAttribute attrs[] = { kCGLPFAOpenGLProfile, (CGLPixelFormatAttribute) kCGLOGLPVersion_3_2_Core, kCGLPFAAccelerated, 0 }; GLint num; if (CGLChoosePixelFormat(attrs, &pixelFormat, &num) != kCGLNoError) return "CGLChoosePixelFormat() failed"; const char* error = cglHandlePixelFormat(result, pixelFormat); CGLDestroyPixelFormat(pixelFormat); return error; } const char* ffDetectOpenGL(FFOpenGLOptions* options, FFOpenGLResult* result) { if (options->library == FF_OPENGL_LIBRARY_AUTO) return cglDetectOpenGL(result); else if (options->library == FF_OPENGL_LIBRARY_EGL) { #if __has_include() const char* ffOpenGLDetectByEGL(FFOpenGLResult* result); return ffOpenGLDetectByEGL(result); #else return "fastfetch was compiled without egl support"; #endif } else return "Unsupported OpenGL library"; } ================================================ FILE: src/detection/opengl/opengl_haiku.cpp ================================================ #include extern "C" { #include "opengl.h" #include "common/io.h" #if FF_HAVE_EGL const char* ffOpenGLDetectByEGL(FFOpenGLResult* result); #endif void ffOpenGLHandleResult(FFOpenGLResult* result, __typeof__(&glGetString) ffglGetString); } static const char* oglDetectOpenGL(FFOpenGLResult* result) { BApplication app("application/x-vnd.fastfetch-cli-fastfetch"); FF_SUPPRESS_IO(); BGLView glView(BRect(), "ff_ogl_view", B_FOLLOW_NONE, B_WILL_DRAW, BGL_RGB); auto ffglGetString = (decltype(&glGetString)) glView.GetGLProcAddress("glGetString"); if (!ffglGetString) return "glView.GetGLProcAddress() failed"; ffOpenGLHandleResult(result, ffglGetString); ffStrbufSetStatic(&result->library, "OpenGLKit"); return NULL; } const char* ffDetectOpenGL(FFOpenGLOptions* options, FFOpenGLResult* result) { if (options->library == FF_OPENGL_LIBRARY_AUTO) return oglDetectOpenGL(result); else if (options->library == FF_OPENGL_LIBRARY_EGL) { #if FF_HAVE_EGL return ffOpenGLDetectByEGL(result); #else return "fastfetch was compiled without egl support"; #endif } else return "Unsupported OpenGL library"; } ================================================ FILE: src/detection/opengl/opengl_linux.c ================================================ #include "fastfetch.h" #include "opengl.h" #include "common/io.h" #include #if __ANDROID__ && !defined(FF_HAVE_EGL) // On Android, installing OpenGL headers is enough (mesa-dev) #if __has_include() #define FF_HAVE_EGL 1 #endif #endif #if defined(FF_HAVE_EGL) || defined(FF_HAVE_GLX) #define FF_HAVE_GL 1 #include "common/library.h" #include void ffOpenGLHandleResult(FFOpenGLResult* result, __typeof__(&glGetString) ffglGetString); #endif // FF_HAVE_GL #ifdef FF_HAVE_GLX #include typedef struct GLXData { FF_LIBRARY_SYMBOL(glGetString) FF_LIBRARY_SYMBOL(glXGetProcAddress) FF_LIBRARY_SYMBOL(glXQueryVersion) FF_LIBRARY_SYMBOL(XOpenDisplay) FF_LIBRARY_SYMBOL(glXChooseVisual) FF_LIBRARY_SYMBOL(XCreatePixmap) FF_LIBRARY_SYMBOL(glXCreateGLXPixmap) FF_LIBRARY_SYMBOL(glXCreateContext) FF_LIBRARY_SYMBOL(glXMakeCurrent) FF_LIBRARY_SYMBOL(glXDestroyContext) FF_LIBRARY_SYMBOL(glXDestroyGLXPixmap) FF_LIBRARY_SYMBOL(XFreePixmap) FF_LIBRARY_SYMBOL(XCloseDisplay) FF_LIBRARY_SYMBOL(XFree) Display* display; XVisualInfo* visualInfo; Pixmap pixmap; GLXPixmap glxPixmap; GLXContext context; } GLXData; static const char* glxHandleContext(FFOpenGLResult* result, GLXData* data) { if(data->ffglXMakeCurrent(data->display, data->glxPixmap, data->context) != True) return "glXMakeCurrent returned False"; ffOpenGLHandleResult(result, data->ffglGetString); int major, minor; if (data->ffglXQueryVersion(data->display, &major, &minor)) ffStrbufSetF(&result->library, "GLX %d.%d", major, minor); else ffStrbufSetStatic(&result->library, "GLX"); return NULL; } static const char* glxHandleGLXPixmap(FFOpenGLResult* result, GLXData* data) { data->context = data->ffglXCreateContext(data->display, data->visualInfo, NULL, True); if(data->context == NULL) return "glXCreateContext returned NULL"; const char* error = glxHandleContext(result, data); data->ffglXDestroyContext(data->display, data->context); return error; } static const char* glxHandlePixmap(FFOpenGLResult* result, GLXData* data) { data->glxPixmap = data->ffglXCreateGLXPixmap(data->display, data->visualInfo, data->pixmap); if(data->glxPixmap == None) return "glXCreateGLXPixmap returned None"; const char* error = glxHandleGLXPixmap(result, data); data->ffglXDestroyGLXPixmap(data->display, data->glxPixmap); return error; } static const char* glxHandleVisualInfo(FFOpenGLResult* result, GLXData* data) { data->pixmap = data->ffXCreatePixmap(data->display, DefaultRootWindow(data->display), FF_OPENGL_BUFFER_WIDTH, FF_OPENGL_BUFFER_HEIGHT, (unsigned int) data->visualInfo->depth); if(data->pixmap == None) return "XCreatePixmap returned None"; const char* error = glxHandlePixmap(result, data); data->ffXFreePixmap(data->display, data->pixmap); return error; } static const char* glxHandleDisplay(FFOpenGLResult* result, GLXData* data) { data->visualInfo = data->ffglXChooseVisual(data->display, DefaultScreen(data->display), (int[]){None}); if(data->visualInfo == NULL) return "glXChooseVisual returned NULL"; const char* error = glxHandleVisualInfo(result, data); data->ffXFree(data->visualInfo); return error; } static const char* glxHandleData(FFOpenGLResult* result, GLXData* data) { data->ffglGetString = (__typeof__(data->ffglGetString)) data->ffglXGetProcAddress((const GLubyte*) "glGetString"); if(data->ffglGetString == NULL) return "glXGetProcAddress(glGetString) returned NULL"; data->display = data->ffXOpenDisplay(NULL); if(data->display == NULL) return "XOpenDisplay returned NULL"; const char* error = glxHandleDisplay(result, data); data->ffXCloseDisplay(data->display); return error; } static const char* detectByGlx(FFOpenGLResult* result) { GLXData data; FF_LIBRARY_LOAD_MESSAGE(glx, #if !__OpenBSD__ && !__NetBSD__ "libGLX" #else "libGL" #endif FF_LIBRARY_EXTENSION, 1); FF_LIBRARY_LOAD_SYMBOL_VAR_MESSAGE(glx, data, glXGetProcAddress); FF_LIBRARY_LOAD_SYMBOL_VAR_MESSAGE(glx, data, glXQueryVersion); FF_LIBRARY_LOAD_SYMBOL_VAR_MESSAGE(glx, data, XOpenDisplay); FF_LIBRARY_LOAD_SYMBOL_VAR_MESSAGE(glx, data, glXChooseVisual); FF_LIBRARY_LOAD_SYMBOL_VAR_MESSAGE(glx, data, XCreatePixmap); FF_LIBRARY_LOAD_SYMBOL_VAR_MESSAGE(glx, data, glXCreateGLXPixmap); FF_LIBRARY_LOAD_SYMBOL_VAR_MESSAGE(glx, data, glXCreateContext); FF_LIBRARY_LOAD_SYMBOL_VAR_MESSAGE(glx, data, glXMakeCurrent); FF_LIBRARY_LOAD_SYMBOL_VAR_MESSAGE(glx, data, glXDestroyContext); FF_LIBRARY_LOAD_SYMBOL_VAR_MESSAGE(glx, data, glXDestroyGLXPixmap); FF_LIBRARY_LOAD_SYMBOL_VAR_MESSAGE(glx, data, XFreePixmap); FF_LIBRARY_LOAD_SYMBOL_VAR_MESSAGE(glx, data, XCloseDisplay); FF_LIBRARY_LOAD_SYMBOL_VAR_MESSAGE(glx, data, XFree); FF_SUPPRESS_IO(); return glxHandleData(result, &data); } #endif //FF_HAVE_GLX const char* ffDetectOpenGL(FFOpenGLOptions* options, FFOpenGLResult* result) { #if FF_HAVE_GL if(options->library == FF_OPENGL_LIBRARY_GLX) { #ifdef FF_HAVE_GLX return detectByGlx(result); #else return "fastfetch was compiled without glx support"; #endif } if(options->library == FF_OPENGL_LIBRARY_EGL) { #ifdef FF_HAVE_EGL const char* ffOpenGLDetectByEGL(FFOpenGLResult* result); return ffOpenGLDetectByEGL(result); #else return "fastfetch was compiled without egl support"; #endif } const char* error = ""; // not NULL dummy value #ifdef FF_HAVE_EGL const char* ffOpenGLDetectByEGL(FFOpenGLResult* result); error = ffOpenGLDetectByEGL(result); #endif #ifdef FF_HAVE_GLX if(error != NULL) error = detectByGlx(result); #endif return error; #else FF_UNUSED(options, result); return "Fastfetch was built without gl support."; #endif //FF_HAVE_GL } ================================================ FILE: src/detection/opengl/opengl_shared.c ================================================ #include "opengl.h" #include "common/debug.h" #include "common/library.h" #if __has_include() #include #elif __APPLE__ #define GL_SILENCE_DEPRECATION 1 #include #else #define FF_HAVE_NO_GL 1 #endif #ifndef FF_HAVE_NO_GL #ifndef GL_SHADING_LANGUAGE_VERSION // For WGL #define GL_SHADING_LANGUAGE_VERSION 0x8B8C #endif void ffOpenGLHandleResult(FFOpenGLResult* result, __typeof__(&glGetString) ffglGetString) { ffStrbufAppendS(&result->version, (const char*) ffglGetString(GL_VERSION)); ffStrbufAppendS(&result->renderer, (const char*) ffglGetString(GL_RENDERER)); ffStrbufAppendS(&result->vendor, (const char*) ffglGetString(GL_VENDOR)); ffStrbufAppendS(&result->slv, (const char*) ffglGetString(GL_SHADING_LANGUAGE_VERSION)); } #if defined(FF_HAVE_EGL) || __has_include() #include "common/io.h" #define EGL_EGL_PROTOTYPES 1 #define EGL_EGLEXT_PROTOTYPES 1 #include #include typedef struct EGLData { FF_LIBRARY_SYMBOL(glGetString) FF_LIBRARY_SYMBOL(eglGetProcAddress) FF_LIBRARY_SYMBOL(eglGetDisplay) FF_LIBRARY_SYMBOL(eglQueryString) FF_LIBRARY_SYMBOL(eglInitialize) FF_LIBRARY_SYMBOL(eglBindAPI) FF_LIBRARY_SYMBOL(eglGetConfigs) FF_LIBRARY_SYMBOL(eglCreatePbufferSurface) FF_LIBRARY_SYMBOL(eglCreateContext) FF_LIBRARY_SYMBOL(eglMakeCurrent) FF_LIBRARY_SYMBOL(eglDestroyContext) FF_LIBRARY_SYMBOL(eglDestroySurface) FF_LIBRARY_SYMBOL(eglTerminate) EGLDisplay display; EGLConfig config; EGLSurface surface; EGLContext context; } EGLData; static const char* eglHandleContext(FFOpenGLResult* result, EGLData* data) { FF_DEBUG("Making EGL context current"); if(data->ffeglMakeCurrent(data->display, data->surface, data->surface, data->context) != EGL_TRUE) { FF_DEBUG("eglMakeCurrent() returned EGL_FALSE"); return "eglMakeCurrent returned EGL_FALSE"; } ffOpenGLHandleResult(result, data->ffglGetString); ffStrbufSetF(&result->library, "EGL %s", data->ffeglQueryString(data->display, EGL_VERSION)); FF_DEBUG("OpenGL via EGL detected: version='%s', renderer='%s', vendor='%s', slv='%s', library='%s'", result->version.chars, result->renderer.chars, result->vendor.chars, result->slv.chars, result->library.chars); return NULL; } static const char* eglHandleSurface(FFOpenGLResult* result, EGLData* data, bool gles) { FF_DEBUG("Creating EGL context (preferred API=%s, client version=%d)", gles ? "OpenGL ES" : "OpenGL", gles ? 2 : 1); data->context = data->ffeglCreateContext(data->display, data->config, EGL_NO_CONTEXT, (EGLint[]){ EGL_CONTEXT_CLIENT_VERSION, gles ? 2 : 1, // Try GLES 2.0+ first EGL_NONE }); if(data->context == EGL_NO_CONTEXT && gles) // Some ANGLE builds support GLES 1.1 only { FF_DEBUG("EGL context creation with GLES 2.x failed, retrying with default attributes (GLES 1.1 fallback)"); data->context = data->ffeglCreateContext(data->display, data->config, EGL_NO_CONTEXT, (EGLint[]){EGL_NONE}); } if(data->context == EGL_NO_CONTEXT) { FF_DEBUG("eglCreateContext() returned EGL_NO_CONTEXT"); return "eglCreateContext returned EGL_NO_CONTEXT"; } FF_DEBUG("EGL context created successfully"); const char* error = eglHandleContext(result, data); FF_DEBUG("eglHandleContext() returns: %s", error ?: "success"); FF_DEBUG("Releasing current EGL context"); data->ffeglMakeCurrent(data->display, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT); FF_DEBUG("Destroying EGL context"); data->ffeglDestroyContext(data->display, data->context); return error; } static const char* eglHandleDisplay(FFOpenGLResult* result, EGLData* data) { // try use OpenGL API. If failed, use the default API (usually OpenGL ES) bool gles = !data->ffeglBindAPI(EGL_OPENGL_API); FF_DEBUG("eglBindAPI(EGL_OPENGL_API) %s, effective API=%s", gles ? "failed" : "succeeded", gles ? "default (usually OpenGL ES)" : "OpenGL"); EGLint eglConfigCount; data->ffeglGetConfigs(data->display, &data->config, 1, &eglConfigCount); FF_DEBUG("eglGetConfigs() returned %d config(s)", eglConfigCount); if(eglConfigCount == 0) { FF_DEBUG("No EGL config is available"); return "eglGetConfigs returned 0 configs"; } FF_DEBUG("Creating EGL pbuffer surface (%dx%d)", FF_OPENGL_BUFFER_WIDTH, FF_OPENGL_BUFFER_HEIGHT); data->surface = data->ffeglCreatePbufferSurface(data->display, data->config, (EGLint[]){ EGL_WIDTH, FF_OPENGL_BUFFER_WIDTH, EGL_HEIGHT, FF_OPENGL_BUFFER_HEIGHT, EGL_NONE }); if(data->surface == EGL_NO_SURFACE) { FF_DEBUG("eglCreatePbufferSurface() returned EGL_NO_SURFACE"); return "eglCreatePbufferSurface returned EGL_NO_SURFACE"; } FF_DEBUG("EGL pbuffer surface created successfully"); const char* error = eglHandleSurface(result, data, gles); FF_DEBUG("eglHandleSurface() returns: %s", error ?: "success"); FF_DEBUG("Destroying EGL surface"); data->ffeglDestroySurface(data->display, data->surface); return error; } static const char* eglHandleData(FFOpenGLResult* result, EGLData* data) { FF_DEBUG("Resolving glGetString via eglGetProcAddress()"); data->ffglGetString = (__typeof__(&glGetString)) data->ffeglGetProcAddress("glGetString"); if(!data->ffglGetString) { FF_DEBUG("eglGetProcAddress('glGetString') returned NULL"); return "eglGetProcAddress(glGetString) returned NULL"; } #if EGL_VERSION_1_5 PFNEGLGETPLATFORMDISPLAYEXTPROC ffeglGetPlatformDisplay = (PFNEGLGETPLATFORMDISPLAYEXTPROC) data->ffeglGetProcAddress("eglGetPlatformDisplay"); if (ffeglGetPlatformDisplay) { FF_DEBUG("Trying eglGetPlatformDisplay(EGL_PLATFORM_SURFACELESS_MESA)"); data->display = ffeglGetPlatformDisplay(EGL_PLATFORM_SURFACELESS_MESA, NULL, NULL); FF_DEBUG("eglGetPlatformDisplay() %s", data->display == EGL_NO_DISPLAY ? "failed" : "succeeded"); } else FF_DEBUG("eglGetPlatformDisplay is unavailable, falling back to eglGetDisplay"); if(!ffeglGetPlatformDisplay || data->display == EGL_NO_DISPLAY) #endif { FF_DEBUG("Trying eglGetDisplay(EGL_DEFAULT_DISPLAY)"); data->display = data->ffeglGetDisplay(EGL_DEFAULT_DISPLAY); if(data->display == EGL_NO_DISPLAY) { FF_DEBUG("eglGetDisplay() returned EGL_NO_DISPLAY"); return "eglGetDisplay returned EGL_NO_DISPLAY"; } FF_DEBUG("eglGetDisplay() succeeded"); } EGLint major, minor; if(data->ffeglInitialize(data->display, &major, &minor) == EGL_FALSE) { FF_DEBUG("eglInitialize() returned EGL_FALSE"); return "eglInitialize returned EGL_FALSE"; } FF_DEBUG("EGL initialized successfully: %d.%d", major, minor); const char* error = eglHandleDisplay(result, data); FF_DEBUG("eglHandleDisplay() returns: %s", error ?: "success"); FF_DEBUG("Terminating EGL display connection"); data->ffeglTerminate(data->display); return error; } const char* ffOpenGLDetectByEGL(FFOpenGLResult* result) { FF_DEBUG("Starting OpenGL detection via EGL"); EGLData eglData; FF_LIBRARY_LOAD_MESSAGE(egl, "libEGL" FF_LIBRARY_EXTENSION, 1); FF_LIBRARY_LOAD_SYMBOL_VAR_MESSAGE(egl, eglData, eglGetProcAddress); FF_LIBRARY_LOAD_SYMBOL_VAR_MESSAGE(egl, eglData, eglGetDisplay); FF_LIBRARY_LOAD_SYMBOL_VAR_MESSAGE(egl, eglData, eglQueryString); FF_LIBRARY_LOAD_SYMBOL_VAR_MESSAGE(egl, eglData, eglInitialize); FF_LIBRARY_LOAD_SYMBOL_VAR_MESSAGE(egl, eglData, eglBindAPI); FF_LIBRARY_LOAD_SYMBOL_VAR_MESSAGE(egl, eglData, eglGetConfigs); FF_LIBRARY_LOAD_SYMBOL_VAR_MESSAGE(egl, eglData, eglCreatePbufferSurface); FF_LIBRARY_LOAD_SYMBOL_VAR_MESSAGE(egl, eglData, eglCreateContext); FF_LIBRARY_LOAD_SYMBOL_VAR_MESSAGE(egl, eglData, eglMakeCurrent); FF_LIBRARY_LOAD_SYMBOL_VAR_MESSAGE(egl, eglData, eglDestroyContext); FF_LIBRARY_LOAD_SYMBOL_VAR_MESSAGE(egl, eglData, eglDestroySurface); FF_LIBRARY_LOAD_SYMBOL_VAR_MESSAGE(egl, eglData, eglTerminate); FF_DEBUG("Loaded EGL library and required symbols"); FF_SUPPRESS_IO(); FF_DEBUG("Suppressed stdout/stderr during EGL probing"); const char* error = eglHandleData(result, &eglData); FF_DEBUG("OpenGL detection via EGL returns: %s", error ?: "success"); return error; } #endif //FF_HAVE_EGL #endif //FF_HAVE_NO_GL ================================================ FILE: src/detection/opengl/opengl_windows.c ================================================ #include "opengl.h" #include "common/library.h" #include "common/printing.h" #include "common/windows/nt.h" #include #include typedef struct WGLData { FF_LIBRARY_SYMBOL(glGetString) FF_LIBRARY_SYMBOL(wglMakeCurrent) FF_LIBRARY_SYMBOL(wglCreateContext) FF_LIBRARY_SYMBOL(wglDeleteContext) } WGLData; void ffOpenGLHandleResult(FFOpenGLResult* result, __typeof__(&glGetString) ffglGetString); static const char* wglHandleContext(WGLData* wglData, FFOpenGLResult* result, HDC hdc, HGLRC context) { if(wglData->ffwglMakeCurrent(hdc, context) == FALSE) return "wglMakeCurrent() failed"; ffOpenGLHandleResult(result, wglData->ffglGetString); ffStrbufSetStatic(&result->library, "WGL 1.0"); if(wglData->ffwglMakeCurrent(NULL, NULL) == FALSE) return "wglMakeCurrent(NULL, NULL) failed"; return NULL; } static const char* wglHandlePixelFormat(WGLData* wglData, FFOpenGLResult* result, HWND hWnd) { HDC hdc = GetDC(hWnd); if(hdc == NULL) return "GetDC() failed"; PIXELFORMATDESCRIPTOR pfd = { .nSize = sizeof(PIXELFORMATDESCRIPTOR), .nVersion = 1, .dwFlags = PFD_DRAW_TO_WINDOW | PFD_SUPPORT_OPENGL | PFD_DOUBLEBUFFER, .iPixelType = PFD_TYPE_RGBA, .cColorBits = 32, .cDepthBits = 24, .iLayerType = PFD_MAIN_PLANE }; int pixelFormat = ChoosePixelFormat(hdc, &pfd); if(pixelFormat == 0) { ReleaseDC(hWnd, hdc); return "ChoosePixelFormat() failed"; } if(SetPixelFormat(hdc, pixelFormat, &pfd) == FALSE) { ReleaseDC(hWnd, hdc); return "SetPixelFormat() failed"; } HGLRC context = wglData->ffwglCreateContext(hdc); if(context == NULL) { ReleaseDC(hWnd, hdc); return "wglCreateContext() failed"; } const char* error = wglHandleContext(wglData, result, hdc, context); wglData->ffwglDeleteContext(context); ReleaseDC(hWnd, hdc); return error; } static const char* wglDetectOpenGL(FFOpenGLResult* result) { FF_LIBRARY_LOAD_MESSAGE(opengl32, "opengl32" FF_LIBRARY_EXTENSION, 1); WGLData data = {}; FF_LIBRARY_LOAD_SYMBOL_VAR_MESSAGE(opengl32, data, wglMakeCurrent); FF_LIBRARY_LOAD_SYMBOL_VAR_MESSAGE(opengl32, data, wglCreateContext); FF_LIBRARY_LOAD_SYMBOL_VAR_MESSAGE(opengl32, data, wglDeleteContext); FF_LIBRARY_LOAD_SYMBOL_VAR_MESSAGE(opengl32, data, glGetString); HINSTANCE hInstance = ffGetPeb()->ImageBaseAddress; WNDCLASSW wc = { .lpfnWndProc = DefWindowProcW, .hInstance = hInstance, .hbrBackground = (HBRUSH)COLOR_BACKGROUND, .lpszClassName = L"ogl_version_check", .style = CS_OWNDC, }; if(!RegisterClassW(&wc)) return "RegisterClassW() failed"; HWND hWnd = CreateWindowW(wc.lpszClassName, L"ogl_version_check", 0, 0, 0, FF_OPENGL_BUFFER_WIDTH, FF_OPENGL_BUFFER_HEIGHT, NULL, NULL, hInstance, NULL); if(!hWnd) return "CreateWindowW() failed"; const char* error = wglHandlePixelFormat(&data, result, hWnd); DestroyWindow(hWnd); UnregisterClassW(wc.lpszClassName, hInstance); return error; } const char* ffDetectOpenGL(FFOpenGLOptions* options, FFOpenGLResult* result) { if (options->library == FF_OPENGL_LIBRARY_AUTO) return wglDetectOpenGL(result); else if (options->library == FF_OPENGL_LIBRARY_EGL) { #if __has_include() const char* ffOpenGLDetectByEGL(FFOpenGLResult* result); return ffOpenGLDetectByEGL(result); #else return "fastfetch was compiled without egl support"; #endif } else return "Unsupported OpenGL library"; } ================================================ FILE: src/detection/os/os.c ================================================ #include "os.h" void ffDetectOSImpl(FFOSResult* os); const FFOSResult* ffDetectOS(void) { static FFOSResult result; if (result.name.chars == NULL) { ffStrbufInit(&result.name); ffStrbufInit(&result.prettyName); ffStrbufInit(&result.id); ffStrbufInit(&result.version); ffStrbufInit(&result.versionID); ffStrbufInit(&result.codename); ffStrbufInit(&result.buildID); ffStrbufInit(&result.idLike); ffStrbufInit(&result.variant); ffStrbufInit(&result.variantID); ffDetectOSImpl(&result); } return &result; } ================================================ FILE: src/detection/os/os.h ================================================ #pragma once #include "fastfetch.h" #include "modules/os/option.h" typedef struct FFOSResult { FFstrbuf name; FFstrbuf prettyName; FFstrbuf id; FFstrbuf idLike; FFstrbuf variant; FFstrbuf variantID; FFstrbuf version; FFstrbuf versionID; FFstrbuf codename; FFstrbuf buildID; } FFOSResult; const FFOSResult* ffDetectOS(); ================================================ FILE: src/detection/os/os_android.c ================================================ #include "os.h" #include "common/settings.h" void ffDetectOSImpl(FFOSResult* os) { ffStrbufSetStatic(&os->name, "Android"); ffStrbufSetStatic(&os->id, "android"); ffSettingsGetAndroidProperty("ro.build.version.release", &os->version); ffSettingsGetAndroidProperty("ro.build.version.release", &os->versionID); ffSettingsGetAndroidProperty("ro.build.version.codename", &os->codename); ffSettingsGetAndroidProperty("ro.build.id", &os->buildID); } ================================================ FILE: src/detection/os/os_apple.m ================================================ #include "os.h" #include "common/io.h" #include "common/sysctl.h" #include "common/stringUtils.h" #include "common/mallocHelper.h" #include #include #import static bool parseSystemVersion(FFOSResult* os) { NSError* error; NSString* fileName = @"file:///System/Library/CoreServices/SystemVersion.plist"; NSDictionary* dict = [NSDictionary dictionaryWithContentsOfURL:[NSURL URLWithString:fileName] error:&error]; if(error) return false; NSString* value; if((value = dict[@"ProductName"])) ffStrbufInitS(&os->name, value.UTF8String); if((value = dict[@"ProductUserVisibleVersion"])) ffStrbufInitS(&os->version, value.UTF8String); if((value = dict[@"ProductBuildVersion"])) ffStrbufInitS(&os->buildID, value.UTF8String); if (ffStrbufStartsWithS(&os->version, "16.")) { // macOS 26 Tahoe. #1809 os->version.chars[0] = '2'; } return true; } static bool detectOSCodeName(FFOSResult* os) { // https://en.wikipedia.org/wiki/MacOS_version_history char* str_end; const char* version = os->version.chars; unsigned long num = strtoul(version, &str_end, 10); if (str_end == version) return false; switch (num) { case 26: case 16: ffStrbufSetStatic(&os->codename, "Tahoe"); return true; case 15: ffStrbufSetStatic(&os->codename, "Sequoia"); return true; case 14: ffStrbufSetStatic(&os->codename, "Sonoma"); return true; case 13: ffStrbufSetStatic(&os->codename, "Ventura"); return true; case 12: ffStrbufSetStatic(&os->codename, "Monterey"); return true; case 11: ffStrbufSetStatic(&os->codename, "Big Sur"); return true; case 10: { version = str_end + 1; num = strtoul(version, &str_end, 10); if (str_end == version) return false; switch (num) { case 16: ffStrbufSetStatic(&os->codename, "Big Sur"); return true; case 15: ffStrbufSetStatic(&os->codename, "Catalina"); return true; case 14: ffStrbufSetStatic(&os->codename, "Mojave"); return true; case 13: ffStrbufSetStatic(&os->codename, "High Sierra"); return true; case 12: ffStrbufSetStatic(&os->codename, "Sierra"); return true; case 11: ffStrbufSetStatic(&os->codename, "El Capitan"); return true; case 10: ffStrbufSetStatic(&os->codename, "Yosemite"); return true; case 9: ffStrbufSetStatic(&os->codename, "Mavericks"); return true; case 8: ffStrbufSetStatic(&os->codename, "Mountain Lion"); return true; case 7: ffStrbufSetStatic(&os->codename, "Lion"); return true; case 6: ffStrbufSetStatic(&os->codename, "Snow Leopard"); return true; case 5: ffStrbufSetStatic(&os->codename, "Leopard"); return true; case 4: ffStrbufSetStatic(&os->codename, "Tiger"); return true; case 3: ffStrbufSetStatic(&os->codename, "Panther"); return true; case 2: ffStrbufSetStatic(&os->codename, "Jaguar"); return true; case 1: ffStrbufSetStatic(&os->codename, "Puma"); return true; case 0: ffStrbufSetStatic(&os->codename, "Cheetah"); return true; } } } return false; } void ffDetectOSImpl(FFOSResult* os) { parseSystemVersion(os); ffStrbufSetStatic(&os->id, "macos"); if(__builtin_expect(os->name.length == 0, 0)) ffStrbufSetStatic(&os->name, "macOS"); if(__builtin_expect(os->version.length == 0, 0)) ffSysctlGetString("kern.osproductversion", &os->version); if(__builtin_expect(os->buildID.length == 0, 0)) ffSysctlGetString("kern.osversion", &os->buildID); ffStrbufAppend(&os->versionID, &os->version); detectOSCodeName(os); ffStrbufSetF(&os->prettyName, "%s %s %s (%s)", os->name.chars, os->codename.chars, os->version.chars, os->buildID.chars); } ================================================ FILE: src/detection/os/os_haiku.c ================================================ #include "os.h" #include #include void ffDetectOSImpl(FFOSResult* os) { ffStrbufSetStatic(&os->name, "Haiku"); ffStrbufSetStatic(&os->id, "haiku"); image_info image; int32 cookie = 0; while (get_next_image_info(B_SYSTEM_TEAM, &cookie, &image) == B_OK) { int32 ver = image.api_version; if (ver == 0) continue; // https://github.com/haiku/haiku/blob/e63683b2fb337d2034059a7e053c170eaf978142/headers/os/BeBuild.h#L36 if (ver < B_HAIKU_VERSION_1_ALPHA_1) { switch (ver) { case B_HAIKU_VERSION_BEOS: ffStrbufSetStatic(&os->version, "BEOS"); break; case B_HAIKU_VERSION_BONE: ffStrbufSetStatic(&os->version, "BONE"); break; case B_HAIKU_VERSION_DANO: ffStrbufSetStatic(&os->version, "DANO"); break; } } else { int32 relVer = ver / 0x10000; ver %= 0x10000; if (ver == 0) { ffStrbufSetF(&os->version, "R%d", relVer); } else { relVer++; bool isPre = !!(ver & 1); if (ver < B_HAIKU_VERSION_1_PRE_BETA_1) { int32 alphaVer = ver / 0x100; if (isPre) ffStrbufSetF(&os->version, "R%dA%d-", relVer, alphaVer + 1); else ffStrbufSetF(&os->version, "R%dA%d", relVer, alphaVer); } else if (ver < 0x00010000 /* B_HAIKU_VERSION_1 */) { int32 betaVer = (ver - B_HAIKU_VERSION_1_ALPHA_4) / 0x100; if (isPre) ffStrbufSetF(&os->version, "R%dB%d-", relVer, betaVer + 1); else ffStrbufSetF(&os->version, "R%dB%d", relVer, betaVer); } } } } if (!os->version.length) { system_info sys; if (get_system_info(&sys) == B_OK) ffStrbufAppendF(&os->version, "R%ldx", sys.kernel_version); } } ================================================ FILE: src/detection/os/os_linux.c ================================================ #include "os.h" #include "common/properties.h" #include "common/parsing.h" #include "common/io.h" #include "common/processing.h" #include "common/stringUtils.h" #include #include #define FF_STR_INDIR(x) #x #define FF_STR(x) FF_STR_INDIR(x) static bool parseLsbRelease(const char* fileName, FFOSResult* result) { return ffParsePropFileValues(fileName, 4, (FFpropquery[]) { {"DISTRIB_ID =", &result->id}, {"DISTRIB_DESCRIPTION =", &result->prettyName}, {"DISTRIB_RELEASE =", &result->version}, {"DISTRIB_CODENAME =", &result->codename}, }); } static bool parseOsRelease(const char* fileName, FFOSResult* result) { return ffParsePropFileValues(fileName, 11, (FFpropquery[]) { {"PRETTY_NAME =", &result->prettyName}, {"NAME =", &result->name}, {"ID =", &result->id}, {"ID_LIKE =", &result->idLike}, {"VARIANT =", &result->variant}, {"VARIANT_ID =", &result->variantID}, {"VERSION =", &result->version}, {"VERSION_ID =", &result->versionID}, {"VERSION_CODENAME =", &result->codename}, {"CODENAME =", &result->codename}, {"BUILD_ID =", &result->buildID}, }); } // Common logic for detecting Armbian image version FF_MAYBE_UNUSED static bool detectArmbianVersion(FFOSResult* result) { // Possible values `PRETTY_NAME` starts with on Armbian: // - `Armbian` for official releases // - `Armbian_community` for community releases // - `Armbian_Security` for images with kali repo added // - `Armbian-unofficial` for an unofficial image built from source, e.g. during development and testing if (ffStrbufStartsWithS(&result->prettyName, "Armbian")) ffStrbufSetStatic(&result->name, "Armbian"); else return false; ffStrbufSet(&result->idLike, &result->id); ffStrbufSetS(&result->id, "armbian"); ffStrbufClear(&result->versionID); uint32_t versionStart = ffStrbufFirstIndexC(&result->prettyName, ' ') + 1; uint32_t versionEnd = ffStrbufNextIndexC(&result->prettyName, versionStart, ' '); ffStrbufSetNS(&result->versionID, versionEnd - versionStart, result->prettyName.chars + versionStart); return true; } // Returns false if PrettyName should be updated by caller FF_MAYBE_UNUSED static bool getUbuntuFlavour(FFOSResult* result) { if (detectArmbianVersion(result)) return true; else if(ffStrbufStartsWithS(&result->prettyName, "Linux Lite ")) { ffStrbufSetStatic(&result->name, "Linux Lite"); ffStrbufSetStatic(&result->id, "linuxlite"); ffStrbufSetStatic(&result->idLike, "ubuntu"); ffStrbufSetS(&result->versionID, result->prettyName.chars + strlen("Linux Lite ")); return true; } else if(ffStrbufStartsWithS(&result->prettyName, "Rhino Linux ")) { ffStrbufSetStatic(&result->name, "Rhino Linux"); ffStrbufSetStatic(&result->id, "rhinolinux"); ffStrbufSetStatic(&result->idLike, "ubuntu"); ffStrbufSetS(&result->versionID, result->prettyName.chars + strlen("Rhino Linux ")); return true; } else if(ffStrbufStartsWithS(&result->prettyName, "VanillaOS ")) { ffStrbufSetStatic(&result->id, "vanilla"); ffStrbufSetStatic(&result->idLike, "ubuntu"); return true; } if (ffPathExists("/usr/bin/lliurex-version", FF_PATHTYPE_FILE)) { ffStrbufSetStatic(&result->name, "LliureX"); ffStrbufSetStatic(&result->id, "lliurex"); ffStrbufClear(&result->version); if (ffProcessAppendStdOut(&result->version, (char* const[]) { "/usr/bin/lliurex-version", NULL, }) == NULL) // 8.2.2 ffStrbufTrimRightSpace(&result->version); ffStrbufSetF(&result->prettyName, "LliureX %s", result->version.chars); ffStrbufSetStatic(&result->idLike, "ubuntu"); return true; } 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"); ffStrbufSetStatic(&result->idLike, "ubuntu"); return false; } if(ffStrContains(xdgConfigDirs, "xfce") || ffStrContains(xdgConfigDirs, "xubuntu")) { ffStrbufSetStatic(&result->name, "Xubuntu"); ffStrbufSetStatic(&result->id, "xubuntu"); ffStrbufSetStatic(&result->idLike, "ubuntu"); return false; } if(ffStrContains(xdgConfigDirs, "lxqt") || ffStrContains(xdgConfigDirs, "lubuntu")) { ffStrbufSetStatic(&result->name, "Lubuntu"); ffStrbufSetStatic(&result->id, "lubuntu"); ffStrbufSetStatic(&result->idLike, "ubuntu"); return false; } if(ffStrContains(xdgConfigDirs, "budgie")) { ffStrbufSetStatic(&result->name, "Ubuntu Budgie"); ffStrbufSetStatic(&result->id, "ubuntu-budgie"); ffStrbufSetStatic(&result->idLike, "ubuntu"); return false; } if(ffStrContains(xdgConfigDirs, "cinnamon")) { ffStrbufSetStatic(&result->name, "Ubuntu Cinnamon"); ffStrbufSetStatic(&result->id, "ubuntu-cinnamon"); ffStrbufSetStatic(&result->idLike, "ubuntu"); return false; } if(ffStrContains(xdgConfigDirs, "mate")) { ffStrbufSetStatic(&result->name, "Ubuntu MATE"); ffStrbufSetStatic(&result->id, "ubuntu-mate"); ffStrbufSetStatic(&result->idLike, "ubuntu"); return false; } if(ffStrContains(xdgConfigDirs, "studio")) { ffStrbufSetStatic(&result->name, "Ubuntu Studio"); ffStrbufSetStatic(&result->id, "ubuntu-studio"); ffStrbufSetStatic(&result->idLike, "ubuntu"); return false; } if(ffStrContains(xdgConfigDirs, "sway")) { ffStrbufSetStatic(&result->name, "Ubuntu Sway"); ffStrbufSetStatic(&result->id, "ubuntu-sway"); ffStrbufSetStatic(&result->idLike, "ubuntu"); return false; } if(ffStrContains(xdgConfigDirs, "touch")) { ffStrbufSetStatic(&result->name, "Ubuntu Touch"); ffStrbufSetStatic(&result->id, "ubuntu-touch"); ffStrbufSetStatic(&result->idLike, "ubuntu"); return false; } return false; } FF_MAYBE_UNUSED static void getDebianVersion(FFOSResult* result) { FF_STRBUF_AUTO_DESTROY debianVersion = ffStrbufCreate(); ffAppendFileBuffer("/etc/debian_version", &debianVersion); ffStrbufTrimRightSpace(&debianVersion); if (!debianVersion.length) return; ffStrbufDestroy(&result->versionID); ffStrbufInitMove(&result->versionID, &debianVersion); ffStrbufSetF(&result->prettyName, "%s %s (%s)", result->name.chars, result->versionID.chars, result->codename.chars); } FF_MAYBE_UNUSED static bool detectDebianDerived(FFOSResult* result) { if (detectArmbianVersion(result)) return true; else if (ffStrbufStartsWithS(&result->name, "Loc-OS")) { ffStrbufSetStatic(&result->id, "locos"); ffStrbufSetStatic(&result->idLike, "debian"); return true; } else if (ffStrbufEqualS(&result->name, "Parrot Security")) { // https://github.com/ParrotSec/base-files/blob/c06f6d42ddf8d79564882306576576eddab7d907/etc/os-release ffStrbufSetS(&result->id, "parrot"); ffStrbufSetS(&result->idLike, "debian"); return true; } else if (ffStrbufStartsWithS(&result->name, "Lilidog GNU/Linux")) { // https://github.com/fastfetch-cli/fastfetch/issues/1373 ffStrbufSetStatic(&result->id, "lilidog"); ffStrbufSetStatic(&result->idLike, "debian"); return true; } else if (access("/usr/bin/pveversion", X_OK) == 0) { ffStrbufSetStatic(&result->id, "pve"); ffStrbufSetStatic(&result->idLike, "debian"); ffStrbufSetStatic(&result->name, "Proxmox VE"); ffStrbufClear(&result->versionID); if (ffProcessAppendStdOut(&result->versionID, (char* const[]) { "/usr/bin/dpkg-query", "--showformat=${version}", "--show", "pve-manager", NULL, }) == NULL) // 8.2.2 ffStrbufTrimRightSpace(&result->versionID); ffStrbufSetF(&result->prettyName, "Proxmox VE %s", result->versionID.chars); return true; } else if (ffPathExists("/etc/rpi-issue", FF_PATHTYPE_FILE)) { // Raspberry Pi OS ffStrbufSetStatic(&result->id, "raspbian"); ffStrbufSetStatic(&result->idLike, "debian"); ffStrbufSetStatic(&result->name, "Raspberry Pi OS"); getDebianVersion(result); return true; } else if (ffPathExists("/boot/dietpi/.version", FF_PATHTYPE_FILE)) { // DietPi ffStrbufSetStatic(&result->id, "dietpi"); ffStrbufSetStatic(&result->name, "DietPi"); ffStrbufSetStatic(&result->prettyName, "DietPi"); ffStrbufSetStatic(&result->idLike, "debian"); FF_STRBUF_AUTO_DESTROY core = ffStrbufCreate(); FF_STRBUF_AUTO_DESTROY sub = ffStrbufCreate(); FF_STRBUF_AUTO_DESTROY rc = ffStrbufCreate(); if (ffParsePropFileValues("/boot/dietpi/.version", 3, (FFpropquery[]) { {"G_DIETPI_VERSION_CORE=", &core}, {"G_DIETPI_VERSION_SUB=", &sub}, {"G_DIETPI_VERSION_RC=", &rc}, })) ffStrbufAppendF(&result->prettyName, " %s.%s.%s", core.chars, sub.chars, rc.chars); return true; } else if (ffStrbufEndsWithS(&instance.state.platform.sysinfo.release, "+truenas")) { // TrueNAS Scale ffStrbufSetStatic(&result->id, "truenas-scale"); ffStrbufSetStatic(&result->idLike, "debian"); ffStrbufSetStatic(&result->name, "TrueNAS Scale"); ffStrbufSetStatic(&result->prettyName, "TrueNAS Scale"); return true; } else if (ffPathExists("/usr/bin/emmabuntus_config.sh", FF_PATHTYPE_FILE)) { // Emmabuntüs ffStrbufSetStatic(&result->id, "emmabuntus"); ffStrbufSetStatic(&result->idLike, "debian"); ffStrbufSetStatic(&result->name, "Emmabuntüs"); getDebianVersion(result); return true; } else { // Hack for MX Linux. See #847 FF_STRBUF_AUTO_DESTROY lsbRelease = ffStrbufCreate(); if (ffAppendFileBuffer("/etc/lsb-release", &lsbRelease) && ffStrbufContainS(&lsbRelease, "DISTRIB_ID=MX")) { ffStrbufSetStatic(&result->id, "mx"); ffStrbufSetStatic(&result->idLike, "debian"); ffStrbufSetStatic(&result->name, "MX"); ffStrbufClear(&result->version); ffParsePropLines(lsbRelease.chars, "DISTRIB_RELEASE=", &result->version); ffStrbufSet(&result->versionID, &result->version); ffStrbufClear(&result->codename); ffParsePropLines(lsbRelease.chars, "DISTRIB_CODENAME=", &result->codename); ffStrbufClear(&result->prettyName); ffParsePropLines(lsbRelease.chars, "DISTRIB_DESCRIPTION=", &result->prettyName); return true; } } return false; } FF_MAYBE_UNUSED static bool detectFedoraVariant(FFOSResult* result) { if (ffStrbufEqualS(&result->variantID, "coreos") || ffStrbufEqualS(&result->variantID, "kinoite") || ffStrbufEqualS(&result->variantID, "sericea") || ffStrbufEqualS(&result->variantID, "silverblue")) { ffStrbufAppendC(&result->id, '-'); ffStrbufAppend(&result->id, &result->variantID); ffStrbufSetStatic(&result->idLike, "fedora"); return true; } return false; } static bool detectBedrock(FFOSResult* os) { const char* bedrockRestrict = getenv("BEDROCK_RESTRICT"); if(bedrockRestrict && bedrockRestrict[0] == '1') return false; return parseOsRelease(FASTFETCH_TARGET_DIR_ROOT "/bedrock/strata/bedrock/etc/os-release", os); } static void detectOS(FFOSResult* os) { #ifdef FF_CUSTOM_OS_RELEASE_PATH parseOsRelease(FF_STR(FF_CUSTOM_OS_RELEASE_PATH), os); #ifdef FF_CUSTOM_LSB_RELEASE_PATH parseLsbRelease(FF_STR(FF_CUSTOM_LSB_RELEASE_PATH), os); #endif return; #endif if (detectBedrock(os)) return; // 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); if (os->id.length == 0 || os->name.length == 0 || os->prettyName.length == 0) parseOsRelease(FASTFETCH_TARGET_DIR_USR "/lib/os-release", os); if (os->id.length == 0 && os->name.length == 0 && os->prettyName.length == 0) { // HarmonyOS has no os-release file if (ffStrbufEqualS(&instance.state.platform.sysinfo.name, "HarmonyOS")) { ffStrbufSetS(&os->id, "harmonyos"); ffStrbufSetS(&os->idLike, "harmonyos"); ffStrbufSetS(&os->name, "HarmonyOS"); ffStrbufSetS(&os->prettyName, "HarmonyOS"); } } } void ffDetectOSImpl(FFOSResult* os) { detectOS(os); #if __linux__ || __GNU__ if(ffStrbufEqualS(&os->id, "ubuntu")) { if (!getUbuntuFlavour(os)) { if (!ffStrbufEndsWithS(&os->prettyName, " (development branch)")) ffStrbufSetF(&os->prettyName, "%s %s", os->name.chars, os->version.chars); // os->version contains code name } } else if(ffStrbufEqualS(&os->id, "debian")) { if (!detectDebianDerived(os)) getDebianVersion(os); } else if(ffStrbufEqualS(&os->id, "fedora")) detectFedoraVariant(os); else if(ffStrbufEqualS(&os->id, "linuxmint")) { if (ffStrbufEqualS(&os->name, "LMDE")) { ffStrbufSetS(&os->id, "lmde"); ffStrbufSetS(&os->idLike, "linuxmint"); } } #endif } ================================================ FILE: src/detection/os/os_nbsd.c ================================================ #include "os.h" void ffDetectOSImpl(FFOSResult* os) { ffStrbufSetStatic(&os->name, "NetBSD"); ffStrbufSet(&os->version, &instance.state.platform.sysinfo.release); } ================================================ FILE: src/detection/os/os_obsd.c ================================================ #include "os.h" void ffDetectOSImpl(FFOSResult* os) { ffStrbufSetStatic(&os->name, "OpenBSD"); ffStrbufSet(&os->version, &instance.state.platform.sysinfo.release); } ================================================ FILE: src/detection/os/os_sunos.c ================================================ #include "os.h" #include "common/io.h" void ffDetectOSImpl(FFOSResult* os) { if (!ffReadFileBuffer("/etc/release", &os->prettyName)) return; ffStrbufSubstrBeforeFirstC(&os->prettyName, '\n'); ffStrbufSubstrBeforeLastC(&os->prettyName, '('); ffStrbufTrim(&os->prettyName, ' '); // OpenIndiana Hipster 2024.04 uint32_t idx = ffStrbufFirstIndexC(&os->prettyName, ' '); ffStrbufSetNS(&os->id, idx, os->prettyName.chars); ffStrbufSetStatic(&os->idLike, "sunos"); } ================================================ FILE: src/detection/os/os_windows.c ================================================ #include "os.h" #include "common/library.h" #include "common/stringUtils.h" #include "common/windows/registry.h" #include "common/windows/unicode.h" #include PWSTR WINAPI BrandingFormatString(PCWSTR format); static bool getCodeName(FFOSResult* os) { FF_AUTO_CLOSE_FD HANDLE hKey = NULL; if(!ffRegOpenKeyForRead(HKEY_LOCAL_MACHINE, L"SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion", &hKey, NULL)) return false; if(!ffRegReadStrbuf(hKey, L"DisplayVersion", &os->codename, NULL)) { if (!ffRegReadStrbuf(hKey, L"CSDVersion", &os->codename, NULL)) // For Windows 7 and Windows 8 if (!ffRegReadStrbuf(hKey, L"ReleaseId", &os->codename, NULL)) // For old Windows 10 return false; } return true; } void ffDetectOSImpl(FFOSResult* os) { //https://dennisbabkin.com/blog/?t=how-to-tell-the-real-version-of-windows-your-app-is-running-on#ver_string const wchar_t* rawName = BrandingFormatString(L"%WINDOWS_LONG%"); ffStrbufSetWS(&os->variant, rawName); GlobalFree((HGLOBAL)rawName); ffStrbufSet(&os->prettyName, &os->variant); ffStrbufTrimRight(&os->variant, ' '); //WMI returns the "Microsoft" prefix while BrandingFormatString doesn't. Make them consistent. if(ffStrbufStartsWithS(&os->variant, "Microsoft ")) ffStrbufSubstrAfter(&os->variant, strlen("Microsoft ") - 1); if(os->variant.length == 0) // Windows PE? { FF_AUTO_CLOSE_FD HANDLE hKey = NULL; if(ffRegOpenKeyForRead(HKEY_LOCAL_MACHINE, L"SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion", &hKey, NULL)) ffRegReadStrbuf(hKey, L"ProductName", &os->variant, NULL); } ffStrbufSet(&os->prettyName, &os->variant); if(ffStrbufStartsWithS(&os->variant, "Windows ")) { ffStrbufAppendS(&os->name, "Windows"); ffStrbufSubstrAfter(&os->variant, strlen("Windows ") - 1); if(ffStrbufStartsWithS(&os->variant, "Server ")) { ffStrbufAppendS(&os->name, " Server"); ffStrbufSubstrAfter(&os->variant, strlen(" Server") - 1); } if(ffStrbufStartsWithIgnCaseS(&os->variant, "(TM) ")) ffStrbufSubstrAfter(&os->variant, strlen(" (TM)") - 1); uint32_t index = ffStrbufFirstIndexC(&os->variant, ' '); ffStrbufAppendNS(&os->version, index, os->variant.chars); ffStrbufSubstrAfter(&os->variant, index); // Windows Server 20xx Rx if(ffStrbufEndsWithC(&os->name, 'r')) { if(os->variant.chars[0] == 'R' && ffCharIsDigit(os->variant.chars[1]) && (os->variant.chars[2] == '\0' || os->variant.chars[2] == ' ')) { ffStrbufAppendF(&os->version, " R%c", os->variant.chars[1]); ffStrbufSubstrAfter(&os->variant, strlen("Rx ") - 1); } } } else { // Unknown Windows name, please report this ffStrbufAppend(&os->name, &os->variant); ffStrbufClear(&os->variant); } ffStrbufAppendF(&os->id, "%s %s", os->name.chars, os->version.chars); ffStrbufSetStatic(&os->idLike, "Windows"); if (getCodeName(os) && os->codename.length > 0) ffStrbufAppendF(&os->prettyName, " (%s)", os->codename.chars); } ================================================ FILE: src/detection/packages/packages.c ================================================ #include "packages.h" #include "common/io.h" #include "common/time.h" #include #include #ifdef __APPLE__ #define st_mtim st_mtimespec #endif void ffDetectPackagesImpl(FFPackagesResult* result, FFPackagesOptions* options); const char* ffDetectPackages(FFPackagesResult* result, FFPackagesOptions* options) { ffDetectPackagesImpl(result, options); for(uint32_t i = 0; i < offsetof(FFPackagesResult, all) / sizeof(uint32_t); ++i) result->all += ((uint32_t *)result)[i]; if (result->all == 0) return "No packages from known package managers found"; return NULL; } bool ffPackagesReadCache(FFstrbuf* cacheDir, FFstrbuf* cacheContent, const char* filePath, const char* packageId, uint32_t* result) { #ifndef _WIN32 struct stat st; if (stat(filePath, &st) < 0) // file doesn't exist or isn't accessible { *result = 0; return true; } if (__builtin_expect(st.st_mtim.tv_sec <= 0, false)) return false; uint64_t mtime_current = (uint64_t) st.st_mtim.tv_sec * 1000ull + (uint64_t) st.st_mtim.tv_nsec / 1000000ull; #else FF_AUTO_CLOSE_FD HANDLE handle = CreateFileA(filePath, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL); if (handle == INVALID_HANDLE_VALUE) // file doesn't exist or isn't accessible { *result = 0; return true; } uint64_t mtime_current; FILE_BASIC_INFORMATION fileInfo; IO_STATUS_BLOCK iosb; if (!NT_SUCCESS(NtQueryInformationFile(handle, &iosb, &fileInfo, sizeof(fileInfo), FileBasicInformation))) return false; mtime_current = ffFileTimeToUnixMs((uint64_t) fileInfo.LastWriteTime.QuadPart); #endif ffStrbufSet(cacheDir, &instance.state.platform.cacheDir); ffStrbufEnsureEndsWithC(cacheDir, '/'); ffStrbufAppendF(cacheDir, "fastfetch/packages/%s.txt", packageId); if (ffReadFileBuffer(cacheDir->chars, cacheContent)) { uint64_t mtime_cached; uint32_t num_cached; if (sscanf(cacheContent->chars, "%" SCNu64 " %" SCNu32, &mtime_cached, &num_cached) == 2 && mtime_cached == mtime_current && num_cached > 0) { *result = num_cached; return true; } } ffStrbufSetF(cacheContent, "%" PRIu64 " ", mtime_current); return false; } bool ffPackagesWriteCache(FFstrbuf* cacheDir, FFstrbuf* cacheContent, uint32_t num_elements) { if (__builtin_expect(cacheContent->length == 0, false)) return false; ffStrbufAppendF(cacheContent, "%" PRIu32, num_elements); return ffWriteFileBuffer(cacheDir->chars, cacheContent); } #ifndef _WIN32 uint32_t ffPackagesGetNumElements(const char* dirname, bool isdir) { FF_AUTO_CLOSE_DIR DIR* dirp = opendir(dirname); if(dirp == NULL) return 0; uint32_t num_elements = 0; struct dirent *entry; while((entry = readdir(dirp)) != NULL) { bool ok = false; if (entry->d_name[0] != '.') { #if !defined(__sun) && !defined(__HAIKU__) if(entry->d_type != DT_UNKNOWN && entry->d_type != DT_LNK) ok = entry->d_type == (isdir ? DT_DIR : DT_REG); else #endif { struct stat stbuf; if (fstatat(dirfd(dirp), entry->d_name, &stbuf, 0) == 0) ok = isdir ? S_ISDIR(stbuf.st_mode) : S_ISREG(stbuf.st_mode); } } if(ok) ++num_elements; } return num_elements; } #endif ================================================ FILE: src/detection/packages/packages.h ================================================ #pragma once #include "fastfetch.h" #include "modules/packages/option.h" typedef struct FFPackagesResult { uint32_t amSystem; uint32_t amUser; uint32_t apk; uint32_t brew; uint32_t brewCask; uint32_t choco; uint32_t dpkg; uint32_t emerge; uint32_t eopkg; uint32_t flatpakSystem; uint32_t flatpakUser; uint32_t guixHome; uint32_t guixSystem; uint32_t guixUser; uint32_t hpkgSystem; uint32_t hpkgUser; uint32_t kiss; uint32_t linglong; uint32_t lpkg; uint32_t lpkgbuild; uint32_t macports; uint32_t mport; uint32_t moss; uint32_t nixDefault; uint32_t nixSystem; uint32_t nixUser; uint32_t opkg; uint32_t pacman; uint32_t pacstall; uint32_t paludis; uint32_t pisi; uint32_t pkg; uint32_t pkgsrc; uint32_t pkgtool; uint32_t rpm; uint32_t scoopUser; uint32_t scoopGlobal; uint32_t snap; uint32_t soar; uint32_t sorcery; uint32_t winget; uint32_t xbps; uint32_t all; //Make sure this goes last FFstrbuf pacmanBranch; } FFPackagesResult; const char* ffDetectPackages(FFPackagesResult* result, FFPackagesOptions* options); bool ffPackagesReadCache(FFstrbuf* cacheDir, FFstrbuf* cacheContent, const char* filePath, const char* packageId, uint32_t* result); bool ffPackagesWriteCache(FFstrbuf* cacheDir, FFstrbuf* cacheContent, uint32_t num_elements); #if defined(__linux__) || defined(__APPLE__) || defined(__GNU__) uint32_t ffPackagesGetNix(FFstrbuf* baseDir, const char* dirname); #endif #ifndef _WIN32 uint32_t ffPackagesGetNumElements(const char* dirname, bool isdir); #endif ================================================ FILE: src/detection/packages/packages_apple.c ================================================ #include "packages.h" #include "common/io.h" #include "common/parsing.h" #include "common/processing.h" #include "common/stringUtils.h" static void countBrewPackages(FFstrbuf* baseDir, FFPackagesResult* result) { uint32_t baseDirLength = baseDir->length; ffStrbufAppendS(baseDir, "/Caskroom"); result->brewCask += ffPackagesGetNumElements(baseDir->chars, true); ffStrbufSubstrBefore(baseDir, baseDirLength); ffStrbufAppendS(baseDir, "/Cellar"); result->brew += ffPackagesGetNumElements(baseDir->chars, true); ffStrbufSubstrBefore(baseDir, baseDirLength); } static uint32_t getMacPortsPackages(FFstrbuf* baseDir) { ffStrbufAppendS(baseDir, "/var/macports/software"); return ffPackagesGetNumElements(baseDir->chars, true); } void ffDetectPackagesImpl(FFPackagesResult* result, FFPackagesOptions* options) { FF_STRBUF_AUTO_DESTROY baseDir = ffStrbufCreate(); if (!(options->disabled & FF_PACKAGES_FLAG_BREW_BIT)) { const char* prefix = getenv("HOMEBREW_PREFIX"); if (ffStrSet(prefix)) { ffStrbufSetS(&baseDir, prefix); } else { #ifdef __aarch64__ ffStrbufSetS(&baseDir, FASTFETCH_TARGET_DIR_ROOT "/opt/homebrew"); #else ffStrbufSetS(&baseDir, FASTFETCH_TARGET_DIR_USR "/local"); #endif } countBrewPackages(&baseDir, result); } if (!(options->disabled & FF_PACKAGES_FLAG_MACPORTS_BIT)) { const char* prefix = getenv("MACPORTS_PREFIX"); if (ffStrSet(prefix)) { ffStrbufSetS(&baseDir, prefix); } else { ffStrbufSetS(&baseDir, FASTFETCH_TARGET_DIR_ROOT "/opt/local"); } result->macports = getMacPortsPackages(&baseDir); } if (!(options->disabled & FF_PACKAGES_FLAG_NIX_BIT)) { ffStrbufSetS(&baseDir, FASTFETCH_TARGET_DIR_ROOT); result->nixDefault += ffPackagesGetNix(&baseDir, "/nix/var/nix/profiles/default"); result->nixSystem += ffPackagesGetNix(&baseDir, "/run/current-system"); ffStrbufSet(&baseDir, &instance.state.platform.homeDir); result->nixUser = ffPackagesGetNix(&baseDir, "/.nix-profile"); } } ================================================ FILE: src/detection/packages/packages_bsd.c ================================================ #include "packages.h" #include "common/settings.h" static uint32_t getSQLite3Int(const char* dbPath, const char* query, const char* packageId) { FF_STRBUF_AUTO_DESTROY cacheDir = ffStrbufCreate(); FF_STRBUF_AUTO_DESTROY cacheContent = ffStrbufCreate(); uint32_t num_elements; if (ffPackagesReadCache(&cacheDir, &cacheContent, dbPath, packageId, &num_elements)) return num_elements; num_elements = (uint32_t) ffSettingsGetSQLite3Int(dbPath, query); ffPackagesWriteCache(&cacheDir, &cacheContent, num_elements); return num_elements; } void ffDetectPackagesImpl(FFPackagesResult* result, FFPackagesOptions* options) { if (!(options->disabled & FF_PACKAGES_FLAG_PKG_BIT)) result->pkg = getSQLite3Int(FASTFETCH_TARGET_DIR_ROOT "/var/db/pkg/local.sqlite", "SELECT count(*) FROM packages", "pkg"); if (!(options->disabled & FF_PACKAGES_FLAG_MPORT_BIT)) result->mport = getSQLite3Int(FASTFETCH_TARGET_DIR_ROOT "/var/db/mport/master.db", "SELECT count(*) FROM packages", "mport"); } ================================================ FILE: src/detection/packages/packages_haiku.c ================================================ #include "packages.h" #include "common/io.h" void ffDetectPackagesImpl(FFPackagesResult* result, FFPackagesOptions* options) { // TODO: Use the Package Kit C++ API instead (would account for disabled packages) if (!(options->disabled & FF_PACKAGES_FLAG_HPKG_BIT)) { result->hpkgSystem = ffPackagesGetNumElements(FASTFETCH_TARGET_DIR_ROOT "/system/packages", false); result->hpkgUser = ffPackagesGetNumElements(FASTFETCH_TARGET_DIR_ROOT "/boot/home/config/packages", false); } } ================================================ FILE: src/detection/packages/packages_linux.c ================================================ #include "packages.h" #include "common/io.h" #include "common/parsing.h" #include "common/properties.h" #include "common/settings.h" #include "common/stringUtils.h" #include "detection/os/os.h" static uint32_t getNumElements(FFstrbuf* baseDir, const char* dirname, bool isdir) { uint32_t baseDirLength = baseDir->length; ffStrbufAppendS(baseDir, dirname); uint32_t num_elements = ffPackagesGetNumElements(baseDir->chars, isdir); ffStrbufSubstrBefore(baseDir, baseDirLength); return num_elements; } static uint32_t getNumStringsImpl(const char* filename, const char* needle) { FF_STRBUF_AUTO_DESTROY content = ffStrbufCreate(); if (!ffReadFileBuffer(filename, &content)) return 0; uint32_t count = 0; char *iter = content.chars; size_t needleLength = strlen(needle); while ((iter = memmem(iter, content.length - (size_t)(iter - content.chars), needle, needleLength)) != NULL) { ++count; iter += needleLength; } return count; } static uint32_t getNumStrings(FFstrbuf* baseDir, const char* filename, const char* needle, const char* packageId) { uint32_t baseDirLength = baseDir->length; ffStrbufAppendS(baseDir, filename); FF_STRBUF_AUTO_DESTROY cacheDir = ffStrbufCreate(); FF_STRBUF_AUTO_DESTROY cacheContent = ffStrbufCreate(); uint32_t num_elements; if (ffPackagesReadCache(&cacheDir, &cacheContent, baseDir->chars, packageId, &num_elements)) { ffStrbufSubstrBefore(baseDir, baseDirLength); return num_elements; } num_elements = getNumStringsImpl(baseDir->chars, needle); ffStrbufSubstrBefore(baseDir, baseDirLength); ffPackagesWriteCache(&cacheDir, &cacheContent, num_elements); return num_elements; } static uint32_t getSQLite3Int(FFstrbuf* baseDir, const char* dbPath, const char* query, const char* packageId) { uint32_t baseDirLength = baseDir->length; ffStrbufAppendS(baseDir, dbPath); FF_STRBUF_AUTO_DESTROY cacheDir = ffStrbufCreate(); FF_STRBUF_AUTO_DESTROY cacheContent = ffStrbufCreate(); uint32_t num_elements; if (ffPackagesReadCache(&cacheDir, &cacheContent, baseDir->chars, packageId, &num_elements)) { ffStrbufSubstrBefore(baseDir, baseDirLength); return num_elements; } num_elements = (uint32_t) ffSettingsGetSQLite3Int(baseDir->chars, query); ffStrbufSubstrBefore(baseDir, baseDirLength); ffPackagesWriteCache(&cacheDir, &cacheContent, num_elements); return num_elements; } static uint32_t countFilesRecursiveImpl(FFstrbuf* baseDirPath, const char* filename) { uint32_t baseDirPathLength = baseDirPath->length; ffStrbufAppendC(baseDirPath, '/'); ffStrbufAppendS(baseDirPath, filename); bool exists = ffPathExists(baseDirPath->chars, FF_PATHTYPE_FILE); ffStrbufSubstrBefore(baseDirPath, baseDirPathLength); if(exists) return 1; DIR* dirp = opendir(baseDirPath->chars); if(dirp == NULL) return 0; ffStrbufAppendC(baseDirPath, '/'); baseDirPathLength = baseDirPath->length; uint32_t sum = 0; struct dirent *entry; while((entry = readdir(dirp)) != NULL) { // According to the PMS, neither category nor package name can begin with '.', so no need to check for . or .. specifically if(entry->d_type != DT_DIR || entry->d_name[0] == '.') continue; ffStrbufAppendS(baseDirPath, entry->d_name); sum += countFilesRecursiveImpl(baseDirPath, filename); ffStrbufSubstrBefore(baseDirPath, baseDirPathLength); } closedir(dirp); return sum; } static uint32_t countFilesRecursive(FFstrbuf* baseDir, const char* dirname, const char* filename) { uint32_t baseDirLength = baseDir->length; ffStrbufAppendS(baseDir, dirname); uint32_t sum = countFilesRecursiveImpl(baseDir, filename); ffStrbufSubstrBefore(baseDir, baseDirLength); return sum; } static uint32_t getXBPSImpl(FFstrbuf* baseDir) { DIR* dir = opendir(baseDir->chars); if(dir == NULL) return 0; uint32_t result = 0; struct dirent *entry; while((entry = readdir(dir)) != NULL) { if(entry->d_type != DT_REG || !ffStrStartsWithIgnCase(entry->d_name, "pkgdb-")) continue; ffStrbufAppendC(baseDir, '/'); ffStrbufAppendS(baseDir, entry->d_name); result = getNumStringsImpl(baseDir->chars, "installed"); break; } closedir(dir); return result; } static uint32_t getXBPS(FFstrbuf* baseDir, const char* dirname) { uint32_t baseDirLength = baseDir->length; ffStrbufAppendS(baseDir, dirname); uint32_t result = getXBPSImpl(baseDir); ffStrbufSubstrBefore(baseDir, baseDirLength); return result; } static uint32_t getSnap(FFstrbuf* baseDir) { uint32_t result = getNumElements(baseDir, "/snap", true); if (result == 0) result = getNumElements(baseDir, "/var/lib/snapd/snap", true); //Accounting for the /snap/bin folder return result > 0 ? result - 1 : 0; } #ifdef FF_HAVE_RPM #include "common/library.h" #include #include #include #include static uint32_t getRpmFromLibrpm(void) { FF_LIBRARY_LOAD(rpm, 0, "librpm" FF_LIBRARY_EXTENSION, 12) FF_LIBRARY_LOAD_SYMBOL(rpm, rpmReadConfigFiles, 0) FF_LIBRARY_LOAD_SYMBOL(rpm, rpmtsCreate, 0) FF_LIBRARY_LOAD_SYMBOL(rpm, rpmtsInitIterator, 0) FF_LIBRARY_LOAD_SYMBOL(rpm, rpmdbGetIteratorCount, 0) FF_LIBRARY_LOAD_SYMBOL(rpm, rpmdbFreeIterator, 0) FF_LIBRARY_LOAD_SYMBOL(rpm, rpmtsFree, 0) FF_LIBRARY_LOAD_SYMBOL(rpm, rpmlogSetMask, 0) // Don't print any error messages ffrpmlogSetMask(RPMLOG_MASK(RPMLOG_EMERG)); if(ffrpmReadConfigFiles(NULL, NULL) != 0) return 0; rpmts ts = ffrpmtsCreate(); if(ts == NULL) return 0; rpmdbMatchIterator mi = ffrpmtsInitIterator(ts, RPMDBI_LABEL, NULL, 0); if(mi == NULL) { ffrpmtsFree(ts); return 0; } int count = ffrpmdbGetIteratorCount(mi); ffrpmdbFreeIterator(mi); ffrpmtsFree(ts); return count > 0 ? (uint32_t) count : 0; } #endif //FF_HAVE_RPM static uint32_t getAMPackages(FFstrbuf* baseDir) { uint32_t baseLength = baseDir->length; FF_AUTO_CLOSE_DIR DIR* dirp = opendir(baseDir->chars); if (!dirp) return 0; uint32_t result = 0; struct dirent *entry; while ((entry = readdir(dirp)) != NULL) { if (entry->d_name[0] == '.') continue; if (entry->d_type == DT_DIR) { ffStrbufAppendF(baseDir, "/%s/remove", entry->d_name); if (ffPathExists(baseDir->chars, FF_PATHTYPE_FILE)) ++result; ffStrbufSubstrBefore(baseDir, baseLength); } } return result; } static uint32_t getAMSystem(FFstrbuf* baseDir) { // #771 uint32_t baseDirLength = baseDir->length; ffStrbufAppendS(baseDir, "/opt"); uint32_t optDirLength = baseDir->length; uint32_t result = 0; ffStrbufAppendS(baseDir, "/am/APP-MANAGER"); if (ffPathExists(baseDir->chars, FF_PATHTYPE_FILE)) { ++result; // `am` itself is counted as a package too ffStrbufSubstrBefore(baseDir, optDirLength); result = getAMPackages(baseDir); } ffStrbufSubstrBefore(baseDir, baseDirLength); return result; } static uint32_t getAMUser(void) { if (instance.state.platform.configDirs.length == 0) return 0; // check if $XDG_CONFIG_HOME/appman/appman-config exists FFstrbuf* baseDir = FF_LIST_FIRST(FFstrbuf, instance.state.platform.configDirs); uint32_t baseLen = baseDir->length; ffStrbufAppendS(baseDir, "appman/appman-config"); FF_STRBUF_AUTO_DESTROY packagesPath = ffStrbufCreate(); if (ffReadFileBuffer(baseDir->chars, &packagesPath)) ffStrbufTrimRightSpace(&packagesPath); ffStrbufSubstrBefore(baseDir, baseLen); return packagesPath.length > 0 ? getAMPackages(&packagesPath) : 0; } static int compareHash(const void* a, const void* b) { return memcmp(a, b, 32); } static uint32_t getGuixPackagesImpl(char* filename) { FF_STRBUF_AUTO_DESTROY content = ffStrbufCreate(); if (!ffAppendFileBuffer(filename, &content)) return 0; // Count number of unique /gnu/store/ paths in PROFILE/manifest based on their hash value. // Contains packages explicitly installed and their propagated inputs. char* pend = content.chars; for (const char* pattern = content.chars; (pattern = strstr(pattern, "/gnu/store/")); pattern += 32) { pattern += strlen("/gnu/store/"); memmove(pend, pattern, 32); pend += 32; } if (pend == content.chars) return 0; qsort(content.chars, (size_t) (pend - content.chars) / 32, 32, compareHash); uint32_t count = 1; for (const char* p = content.chars + 32; p < pend; p += 32) count += compareHash(p - 32, p) != 0; return count; } static uint32_t getGuixPackages(FFstrbuf* baseDir, const char* dirname) { uint32_t baseDirLength = baseDir->length; ffStrbufAppendS(baseDir, dirname); ffStrbufAppendS(baseDir, "/manifest"); uint32_t num_elements = getGuixPackagesImpl(baseDir->chars); ffStrbufSubstrBefore(baseDir, baseDirLength); return num_elements; } static inline uint32_t getFlatpakRuntimePackagesArch(FFstrbuf* baseDir) { FF_AUTO_CLOSE_DIR DIR* dirp = opendir(baseDir->chars); if (dirp == NULL) return 0; uint32_t num_elements = 0; struct dirent *entry; while ((entry = readdir(dirp)) != NULL) { if(entry->d_type == DT_DIR && entry->d_name[0] != '.') { num_elements += getNumElements(baseDir, entry->d_name, true); } } return num_elements; } static inline uint32_t getFlatpakRuntimePackages(FFstrbuf* baseDir) { ffStrbufAppendS(baseDir, "runtime/"); FF_AUTO_CLOSE_DIR DIR* dirp = opendir(baseDir->chars); if (dirp == NULL) return 0; uint32_t runtimeDirLength = baseDir->length; uint32_t num_elements = 0; struct dirent *entry; while ((entry = readdir(dirp)) != NULL) { if(entry->d_type == DT_DIR && entry->d_name[0] != '.') { // `flatpak list` ignores `.Locale` and `.Debug` packages, and maybe others const char* dot = strrchr(entry->d_name, '.'); if (__builtin_expect(!dot, false)) continue; dot++; if (ffStrEquals(dot, "Locale") || ffStrEquals(dot, "Debug")) continue; ffStrbufAppendS(baseDir, entry->d_name); ffStrbufAppendC(baseDir, '/'); num_elements += getFlatpakRuntimePackagesArch(baseDir); ffStrbufSubstrBefore(baseDir, runtimeDirLength); } } return num_elements; } static inline uint32_t getFlatpakAppPackages(FFstrbuf* baseDir) { ffStrbufAppendS(baseDir, "app/"); FF_AUTO_CLOSE_DIR DIR* dirp = opendir(baseDir->chars); if (dirp == NULL) return 0; uint32_t appDirLength = baseDir->length; uint32_t num_elements = 0; struct dirent *entry; while ((entry = readdir(dirp)) != NULL) { if(entry->d_type == DT_DIR && entry->d_name[0] != '.') { ffStrbufAppendS(baseDir, entry->d_name); ffStrbufAppendS(baseDir, "/current"); if (ffPathExists(baseDir->chars, FF_PATHTYPE_ANY)) // Exclude deleted apps, #1856 ++num_elements; ffStrbufSubstrBefore(baseDir, appDirLength); } } return num_elements; } static uint32_t getFlatpakPackages(FFstrbuf* baseDir, const char* dirname) { uint32_t num_elements = 0; uint32_t baseDirLength = baseDir->length; ffStrbufAppendS(baseDir, dirname); ffStrbufAppendS(baseDir, "/flatpak/"); uint32_t flatpakDirLength = baseDir->length; num_elements += getFlatpakAppPackages(baseDir); ffStrbufSubstrBefore(baseDir, flatpakDirLength); num_elements += getFlatpakRuntimePackages(baseDir); ffStrbufSubstrBefore(baseDir, baseDirLength); return num_elements; } static uint32_t getPacmanPackages(FFstrbuf* baseDir) { FF_STRBUF_AUTO_DESTROY dbPath = ffStrbufCreate(); FF_STRBUF_AUTO_DESTROY rootDir = ffStrbufCreate(); // Get path to pacman.conf uint32_t baseDirLen = baseDir->length; ffStrbufAppendS(baseDir, "/etc/pacman.conf"); bool confFound = ffParsePropFileValues(baseDir->chars, 2, (FFpropquery[]){ { "DBPath =", &dbPath }, { "RootDir =", &rootDir }, }); ffStrbufSubstrBefore(baseDir, baseDirLen); if (confFound) { if (dbPath.length > 0) { // If DBPath is specified, use it ffStrbufEnsureEndsWithC(&dbPath, '/'); ffStrbufAppendS(&dbPath, "local"); } else if (rootDir.length > 0) { // ... otherwise, use RootDir ffStrbufDestroy(&dbPath); ffStrbufInitMove(&dbPath, &rootDir); ffStrbufEnsureEndsWithC(&dbPath, '/'); ffStrbufAppendS(&dbPath, "var/lib/pacman/local"); } } if (dbPath.length == 0) ffStrbufSetStatic(&dbPath, "/var/lib/pacman/local"); return getNumElements(baseDir, dbPath.chars, true); } static void getPackageCounts(FFstrbuf* baseDir, FFPackagesResult* packageCounts, FFPackagesOptions* options) { if (!(options->disabled & FF_PACKAGES_FLAG_APK_BIT)) packageCounts->apk += getNumStrings(baseDir, "/lib/apk/db/installed", "C:Q", "apk"); if (!(options->disabled & FF_PACKAGES_FLAG_DPKG_BIT)) packageCounts->dpkg += getNumStrings(baseDir, "/var/lib/dpkg/status", "Status: install ok installed", "dpkg"); if (!(options->disabled & FF_PACKAGES_FLAG_LPKG_BIT)) packageCounts->lpkg += getNumStrings(baseDir, "/opt/Loc-OS-LPKG/installed-lpkg/Listinstalled-lpkg.list", "\n", "lpkg"); if (!(options->disabled & FF_PACKAGES_FLAG_EMERGE_BIT)) packageCounts->emerge += countFilesRecursive(baseDir, "/var/db/pkg", "SIZE"); if (!(options->disabled & FF_PACKAGES_FLAG_EOPKG_BIT)) packageCounts->eopkg += getNumElements(baseDir, "/var/lib/eopkg/package", true); if (!(options->disabled & FF_PACKAGES_FLAG_FLATPAK_BIT)) packageCounts->flatpakSystem += getFlatpakPackages(baseDir, "/var/lib"); if (!(options->disabled & FF_PACKAGES_FLAG_KISS_BIT)) packageCounts->kiss += getNumElements(baseDir, "/var/db/kiss/installed", true); if (!(options->disabled & FF_PACKAGES_FLAG_NIX_BIT)) { packageCounts->nixDefault += ffPackagesGetNix(baseDir, "/nix/var/nix/profiles/default"); packageCounts->nixSystem += ffPackagesGetNix(baseDir, "/run/current-system"); } if (!(options->disabled & FF_PACKAGES_FLAG_PACMAN_BIT)) packageCounts->pacman += getPacmanPackages(baseDir); if (!(options->disabled & FF_PACKAGES_FLAG_LPKGBUILD_BIT)) packageCounts->lpkgbuild += getNumElements(baseDir, "/opt/Loc-OS-LPKG/lpkgbuild/remove", false); if (!(options->disabled & FF_PACKAGES_FLAG_PKGTOOL_BIT)) packageCounts->pkgtool += getNumElements(baseDir, "/var/log/packages", false); if (!(options->disabled & FF_PACKAGES_FLAG_RPM_BIT)) { // `Sigmd5` is the only table that doesn't contain the virtual `gpg-pubkey` package packageCounts->rpm += getSQLite3Int(baseDir, "/var/lib/rpm/rpmdb.sqlite", "SELECT count(*) FROM Sigmd5", "rpm"); } if (!(options->disabled & FF_PACKAGES_FLAG_SNAP_BIT)) packageCounts->snap += getSnap(baseDir); if (!(options->disabled & FF_PACKAGES_FLAG_XBPS_BIT)) packageCounts->xbps += getXBPS(baseDir, "/var/db/xbps"); if (!(options->disabled & FF_PACKAGES_FLAG_BREW_BIT)) { packageCounts->brewCask += getNumElements(baseDir, "/home/linuxbrew/.linuxbrew/Caskroom", true); packageCounts->brew += getNumElements(baseDir, "/home/linuxbrew/.linuxbrew/Cellar", true); } if (!(options->disabled & FF_PACKAGES_FLAG_PALUDIS_BIT)) packageCounts->paludis += countFilesRecursive(baseDir, "/var/db/paludis/repositories", "environment.bz2"); if (!(options->disabled & FF_PACKAGES_FLAG_OPKG_BIT)) packageCounts->opkg += getNumStrings(baseDir, "/usr/lib/opkg/status", "Package:", "opkg"); // openwrt if (!(options->disabled & FF_PACKAGES_FLAG_AM_BIT)) packageCounts->amSystem = getAMSystem(baseDir); if (!(options->disabled & FF_PACKAGES_FLAG_SORCERY_BIT)) packageCounts->sorcery += getNumStrings(baseDir, "/var/state/sorcery/packages", ":installed:", "sorcery"); if (!(options->disabled & FF_PACKAGES_FLAG_GUIX_BIT)) { packageCounts->guixSystem += getGuixPackages(baseDir, "/run/current-system/profile"); } if (!(options->disabled & FF_PACKAGES_FLAG_LINGLONG_BIT)) packageCounts->linglong += getNumElements(baseDir, "/var/lib/linglong/layers", true); if (!(options->disabled & FF_PACKAGES_FLAG_PACSTALL_BIT)) packageCounts->pacstall += getNumElements(baseDir, "/var/lib/pacstall/metadata", false); if (!(options->disabled & FF_PACKAGES_FLAG_PISI_BIT)) packageCounts->pisi += getNumElements(baseDir, "/var/lib/pisi/package", true); if (!(options->disabled & FF_PACKAGES_FLAG_PKGSRC_BIT)) packageCounts->pkgsrc += getNumElements(baseDir, "/usr/pkg/pkgdb", DT_DIR); 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"); } static void getPackageCountsRegular(FFstrbuf* baseDir, FFPackagesResult* packageCounts, FFPackagesOptions* options) { getPackageCounts(baseDir, packageCounts, options); if (!(options->disabled & FF_PACKAGES_FLAG_PACMAN_BIT)) { uint32_t baseDirLength = baseDir->length; ffStrbufAppendS(baseDir, FASTFETCH_TARGET_DIR_ETC "/pacman-mirrors.conf"); if(ffParsePropFile(baseDir->chars, "Branch =", &packageCounts->pacmanBranch) && packageCounts->pacmanBranch.length == 0) ffStrbufAppendS(&packageCounts->pacmanBranch, "stable"); ffStrbufSubstrBefore(baseDir, baseDirLength); } } static void getPackageCountsBedrock(FFstrbuf* baseDir, FFPackagesResult* packageCounts, FFPackagesOptions* options) { uint32_t baseDirLength = baseDir->length; ffStrbufAppendS(baseDir, "/bedrock/strata"); FF_AUTO_CLOSE_DIR DIR* dir = opendir(baseDir->chars); if(dir == NULL) { ffStrbufSubstrBefore(baseDir, baseDirLength); return; } ffStrbufAppendC(baseDir, '/'); uint32_t baseDirLength2 = baseDir->length; struct dirent* entry; while((entry = readdir(dir)) != NULL) { if(entry->d_type != DT_DIR) continue; if(entry->d_name[0] == '.') continue; ffStrbufAppendS(baseDir, entry->d_name); getPackageCounts(baseDir, packageCounts, options); ffStrbufSubstrBefore(baseDir, baseDirLength2); } ffStrbufSubstrBefore(baseDir, baseDirLength); } void ffDetectPackagesImpl(FFPackagesResult* result, FFPackagesOptions* options) { FF_STRBUF_AUTO_DESTROY baseDir = ffStrbufCreateA(512); ffStrbufAppendS(&baseDir, FASTFETCH_TARGET_DIR_ROOT); if(ffStrbufIgnCaseEqualS(&ffDetectOS()->id, "bedrock")) getPackageCountsBedrock(&baseDir, result, options); else getPackageCountsRegular(&baseDir, result, options); // If SQL failed, we can still try with librpm. // This is needed on openSUSE, which seems to use a proprietary database file // This method doesn't work on bedrock, so we do it here. #ifdef FF_HAVE_RPM if(!(options->disabled & FF_PACKAGES_FLAG_RPM_BIT) && result->rpm == 0) result->rpm = getRpmFromLibrpm(); #endif ffStrbufSet(&baseDir, &instance.state.platform.homeDir); if (!(options->disabled & FF_PACKAGES_FLAG_NIX_BIT)) { // Count packages from $HOME/.nix-profile result->nixUser += ffPackagesGetNix(&baseDir, ".nix-profile"); // Check in $XDG_STATE_HOME/nix/profile FF_STRBUF_AUTO_DESTROY stateHome = ffStrbufCreate(); const char* stateHomeEnv = getenv("XDG_STATE_HOME"); if (ffStrSet(stateHomeEnv)) { ffStrbufSetS(&stateHome, stateHomeEnv); ffStrbufEnsureEndsWithC(&stateHome, '/'); } else { ffStrbufSet(&stateHome, &instance.state.platform.homeDir); ffStrbufAppendS(&stateHome, ".local/state/"); } result->nixUser += ffPackagesGetNix(&stateHome, "nix/profile"); // Check in /etc/profiles/per-user/$USER FF_STRBUF_AUTO_DESTROY userPkgsDir = ffStrbufCreateStatic("/etc/profiles/per-user/"); result->nixUser += ffPackagesGetNix(&userPkgsDir, instance.state.platform.userName.chars); } if (!(options->disabled & FF_PACKAGES_FLAG_GUIX_BIT)) { result->guixUser += getGuixPackages(&baseDir, ".guix-profile"); result->guixHome += getGuixPackages(&baseDir, ".guix-home/profile"); } if (!(options->disabled & FF_PACKAGES_FLAG_FLATPAK_BIT)) result->flatpakUser = getFlatpakPackages(&baseDir, "/.local/share"); if (!(options->disabled & FF_PACKAGES_FLAG_AM_BIT)) result->amUser = getAMUser(); if (!(options->disabled & FF_PACKAGES_FLAG_SOAR_BIT)) result->soar += getSQLite3Int(&baseDir, ".local/share/soar/db/soar.db", "SELECT COUNT(DISTINCT pkg_id || pkg_name) FROM packages WHERE is_installed = true", "soar"); } ================================================ FILE: src/detection/packages/packages_nbsd.c ================================================ #include "packages.h" #include "common/io.h" void ffDetectPackagesImpl(FFPackagesResult* result, FFPackagesOptions* options) { if (!(options->disabled & FF_PACKAGES_FLAG_PKGSRC_BIT)) result->pkgsrc = ffPackagesGetNumElements(FASTFETCH_TARGET_DIR_ROOT "/usr/pkg/pkgdb", true); } ================================================ FILE: src/detection/packages/packages_nix.c ================================================ #include "packages.h" #include "common/io.h" #include "common/processing.h" #include "common/stringUtils.h" static bool isValidNixPkg(FFstrbuf* pkg) { if (!ffPathExists(pkg->chars, FF_PATHTYPE_DIRECTORY)) return false; ffStrbufSubstrAfterLastC(pkg, '/'); if ( ffStrbufStartsWithS(pkg, "nixos-system-nixos-") || ffStrbufEndsWithS(pkg, "-doc") || ffStrbufEndsWithS(pkg, "-man") || ffStrbufEndsWithS(pkg, "-info") || ffStrbufEndsWithS(pkg, "-dev") || ffStrbufEndsWithS(pkg, "-bin") ) return false; enum { START, DIGIT, DOT, MATCH } state = START; for (uint32_t i = 0; i < pkg->length; i++) { char c = pkg->chars[i]; switch (state) { case START: if (ffCharIsDigit(c)) state = DIGIT; break; case DIGIT: if (ffCharIsDigit(c)) continue; if (c == '.') state = DOT; else state = START; break; case DOT: if (ffCharIsDigit(c)) state = MATCH; else state = START; break; case MATCH: break; } } return state == MATCH; } static bool checkNixCache(FFstrbuf* cacheDir, FFstrbuf* hash, uint32_t* count) { if (!ffPathExists(cacheDir->chars, FF_PATHTYPE_FILE)) return false; FF_STRBUF_AUTO_DESTROY cacheContent = ffStrbufCreate(); if (!ffReadFileBuffer(cacheDir->chars, &cacheContent)) return false; // Format: \n uint32_t split = ffStrbufFirstIndexC(&cacheContent, '\n'); if (split == cacheContent.length) return false; ffStrbufSetNS(hash, split, cacheContent.chars); *count = (uint32_t)atoi(cacheContent.chars + split + 1); return true; } static bool writeNixCache(FFstrbuf* cacheDir, FFstrbuf* hash, uint32_t count) { FF_STRBUF_AUTO_DESTROY cacheContent = ffStrbufCreateCopy(hash); ffStrbufAppendF(&cacheContent, "\n%u", count); return ffWriteFileBuffer(cacheDir->chars, &cacheContent); } static uint32_t getNixPackagesImpl(char* path) { //Nix detection is kinda slow, so we only do it if the dir exists if(!ffPathExists(path, FF_PATHTYPE_DIRECTORY)) return 0; FF_STRBUF_AUTO_DESTROY cacheDir = ffStrbufCreateCopy(&instance.state.platform.cacheDir); ffStrbufEnsureEndsWithC(&cacheDir, '/'); ffStrbufAppendS(&cacheDir, "fastfetch/packages/nix"); ffStrbufAppendS(&cacheDir, path); //Check the hash first to determine if we need to recompute the count FF_STRBUF_AUTO_DESTROY hash = ffStrbufCreateA(64); FF_STRBUF_AUTO_DESTROY cacheHash = ffStrbufCreateA(64); uint32_t count = 0; ffProcessAppendStdOut(&hash, (char* const[]) { "nix-store", "--query", "--hash", path, NULL }); if (checkNixCache(&cacheDir, &cacheHash, &count) && ffStrbufEqual(&hash, &cacheHash)) return count; //Cache is invalid, recompute the count count = 0; //Implementation based on bash script from here: //https://github.com/fastfetch-cli/fastfetch/issues/195#issuecomment-1191748222 FF_STRBUF_AUTO_DESTROY output = ffStrbufCreateA(1024); ffProcessAppendStdOut(&output, (char* const[]) { "nix-store", "--query", "--requisites", path, NULL }); uint32_t lineLength = 0; for (uint32_t i = 0; i < output.length; i++) { if (output.chars[i] != '\n') { lineLength++; continue; } output.chars[i] = '\0'; FFstrbuf line = { .allocated = 0, .length = lineLength, .chars = output.chars + i - lineLength }; if (isValidNixPkg(&line)) count++; lineLength = 0; } writeNixCache(&cacheDir, &hash, count); return count; } uint32_t ffPackagesGetNix(FFstrbuf* baseDir, const char* dirname) { uint32_t baseDirLength = baseDir->length; ffStrbufAppendS(baseDir, dirname); uint32_t num_elements = getNixPackagesImpl(baseDir->chars); ffStrbufSubstrBefore(baseDir, baseDirLength); return num_elements; } ================================================ FILE: src/detection/packages/packages_nosupport.c ================================================ #include "packages.h" void ffDetectPackagesImpl(FF_MAYBE_UNUSED FFPackagesResult* result, FF_MAYBE_UNUSED FFPackagesOptions* options) { } ================================================ FILE: src/detection/packages/packages_obsd.c ================================================ #include "packages.h" #include "common/io.h" void ffDetectPackagesImpl(FFPackagesResult* result, FFPackagesOptions* options) { if (!(options->disabled & FF_PACKAGES_FLAG_PKG_BIT)) result->pkg = ffPackagesGetNumElements(FASTFETCH_TARGET_DIR_ROOT "/var/db/pkg", true); } ================================================ FILE: src/detection/packages/packages_sunos.c ================================================ #include "packages.h" #include void ffDetectPackagesImpl(FFPackagesResult* result, FFPackagesOptions* options) { if (!(options->disabled & FF_PACKAGES_FLAG_PKG_BIT)) { yyjson_doc* doc = yyjson_read_file(FASTFETCH_TARGET_DIR_ROOT "/var/pkg/state/installed/catalog.attrs", YYJSON_READ_NOFLAG, NULL, NULL); if (doc) { yyjson_val* packageCount = yyjson_obj_get(yyjson_doc_get_root(doc), "package-count"); if (packageCount) result->pkg = (uint32_t) yyjson_get_uint(packageCount); } } if (!(options->disabled & FF_PACKAGES_FLAG_PKGSRC_BIT)) result->pkgsrc = ffPackagesGetNumElements(FASTFETCH_TARGET_DIR_ROOT "/usr/pkg/pkgdb", true); } ================================================ FILE: src/detection/packages/packages_windows.c ================================================ #include "packages.h" #include "common/processing.h" #include "common/stringUtils.h" #include "common/path.h" #include "common/windows/unicode.h" #include "common/mallocHelper.h" #include "common/io.h" #include #include #include "common/windows/nt.h" #include #include static uint32_t getNumElements(const char* searchPath, DWORD type, const wchar_t* ignore) { FF_AUTO_CLOSE_FD HANDLE dfd = CreateFileA(searchPath, FILE_LIST_DIRECTORY | SYNCHRONIZE, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, NULL, OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS, NULL); if (dfd == INVALID_HANDLE_VALUE) return 0; bool flag = ignore == NULL; uint32_t counter = 0; alignas(8) uint8_t buffer[64 * 1024]; BOOLEAN firstScan = TRUE; size_t ignoreLen = ignore ? wcslen(ignore) : 0; while (true) { IO_STATUS_BLOCK ioStatus = {}; NTSTATUS status = NtQueryDirectoryFile( dfd, NULL, NULL, NULL, &ioStatus, buffer, ARRAY_SIZE(buffer), FileDirectoryInformation, FALSE, NULL, firstScan ); firstScan = FALSE; if (!NT_SUCCESS(status) && status != STATUS_BUFFER_OVERFLOW) break; for (FILE_DIRECTORY_INFORMATION* entry = (FILE_DIRECTORY_INFORMATION*) buffer; ; entry = (FILE_DIRECTORY_INFORMATION*) ((uint8_t*) entry + entry->NextEntryOffset)) { if (!(entry->FileAttributes & type)) continue; if (!flag && ignoreLen == entry->FileNameLength / sizeof(*entry->FileName) && _wcsnicmp(entry->FileName, ignore, ignoreLen) == 0) { flag = true; continue; } counter++; if (entry->NextEntryOffset == 0) break; } if (status == STATUS_SUCCESS) break; // No next page } if(type == FILE_ATTRIBUTE_DIRECTORY && counter >= 2) counter -= 2; // accounting for . and .. return counter; } static inline void wrapYyjsonFree(yyjson_doc** doc) { assert(doc); if (*doc) yyjson_doc_free(*doc); } static void detectScoop(FFPackagesResult* result) { FF_STRBUF_AUTO_DESTROY scoopPath = ffStrbufCreateA(MAX_PATH + 3); ffStrbufAppend(&scoopPath, &instance.state.platform.homeDir); ffStrbufAppendS(&scoopPath, ".config/scoop/config.json"); yyjson_val* root = NULL; yyjson_doc* __attribute__((__cleanup__(wrapYyjsonFree))) doc = yyjson_read_file(scoopPath.chars, 0, NULL, NULL); if (doc) { root = yyjson_doc_get_root(doc); if (!yyjson_is_obj(root)) root = NULL; } { ffStrbufClear(&scoopPath); if (root) ffStrbufSetJsonVal(&scoopPath, yyjson_obj_get(root, "root_path")); if (scoopPath.length == 0) { ffStrbufSet(&scoopPath, &instance.state.platform.homeDir); ffStrbufAppendS(&scoopPath, "/scoop"); } ffStrbufAppendS(&scoopPath, "/apps/"); result->scoopUser = getNumElements(scoopPath.chars, FILE_ATTRIBUTE_DIRECTORY, L"scoop"); } { ffStrbufClear(&scoopPath); if (root) ffStrbufSetJsonVal(&scoopPath, yyjson_obj_get(root, "global_path")); if (scoopPath.length == 0) { PWSTR pPath = NULL; if (SUCCEEDED(SHGetKnownFolderPath(&FOLDERID_ProgramData, KF_FLAG_DEFAULT, NULL, &pPath))) { ffStrbufSetWS(&scoopPath, pPath); CoTaskMemFree(pPath); } ffStrbufAppendS(&scoopPath, "/scoop"); } ffStrbufAppendS(&scoopPath, "/apps/"); result->scoopGlobal = getNumElements(scoopPath.chars, FILE_ATTRIBUTE_DIRECTORY, L"scoop"); } } static void detectChoco(FF_MAYBE_UNUSED FFPackagesResult* result) { const char* chocoInstall = getenv("ChocolateyInstall"); if(!chocoInstall || chocoInstall[0] == '\0') return; char chocoPath[MAX_PATH + 3]; char* pend = ffStrCopy(chocoPath, chocoInstall, ARRAY_SIZE(chocoPath)); ffStrCopy(pend, "/lib/", ARRAY_SIZE(chocoPath) - (size_t) (pend - chocoPath)); result->choco = getNumElements(chocoPath, FILE_ATTRIBUTE_DIRECTORY, L"choco"); } static void detectPacman(FFPackagesResult* result) { const char* msystemPrefix = getenv("MSYSTEM_PREFIX"); if(!msystemPrefix) return; // MSYS2 char pacmanPath[MAX_PATH + 3]; char* pend = ffStrCopy(pacmanPath, msystemPrefix, ARRAY_SIZE(pacmanPath)); ffStrCopy(pend, "/../var/lib/pacman/local/", ARRAY_SIZE(pacmanPath) - (size_t) (pend - pacmanPath)); result->pacman = getNumElements(pacmanPath, FILE_ATTRIBUTE_DIRECTORY, NULL); } static void detectWinget(FFPackagesResult* result) { FF_STRBUF_AUTO_DESTROY buffer = ffStrbufCreate(); if (ffProcessAppendStdOut(&buffer, (char* []) { "winget.exe", "list", "--disable-interactivity", NULL, })) return; uint32_t index = ffStrbufFirstIndexS(&buffer, "--\r\n"); // Ignore garbage and table headers if (index == buffer.length) return; uint32_t count = 0; for ( index += strlen("--\r\n"); (index = ffStrbufNextIndexC(&buffer, index, '\n')) < buffer.length; ++index ) ++count; if (buffer.chars[buffer.length - 1] != '\n') // count last line ++count; result->winget = count; } void ffDetectPackagesImpl(FFPackagesResult* result, FFPackagesOptions* options) { if (!(options->disabled & FF_PACKAGES_FLAG_SCOOP_BIT)) detectScoop(result); if (!(options->disabled & FF_PACKAGES_FLAG_CHOCO_BIT)) detectChoco(result); if (!(options->disabled & FF_PACKAGES_FLAG_PACMAN_BIT)) detectPacman(result); if (!(options->disabled & FF_PACKAGES_FLAG_WINGET_BIT)) detectWinget(result); } ================================================ FILE: src/detection/physicaldisk/physicaldisk.h ================================================ #include "fastfetch.h" #include "modules/physicaldisk/option.h" #define FF_PHYSICALDISK_TEMP_UNSET (-DBL_MAX) typedef enum __attribute__((__packed__)) FFPhysicalDiskType { FF_PHYSICALDISK_TYPE_NONE = 0, // If neither is set, it's unknown FF_PHYSICALDISK_TYPE_HDD = 1 << 0, FF_PHYSICALDISK_TYPE_SSD = 1 << 1, FF_PHYSICALDISK_TYPE_FIXED = 1 << 2, FF_PHYSICALDISK_TYPE_REMOVABLE = 1 << 3, FF_PHYSICALDISK_TYPE_READWRITE = 1 << 4, FF_PHYSICALDISK_TYPE_READONLY = 1 << 5, FF_PHYSICALDISK_TYPE_FORCE_UNSIGNED = UINT8_MAX, } FFPhysicalDiskType; static_assert(sizeof(FFPhysicalDiskType) == sizeof(uint8_t), ""); typedef struct FFPhysicalDiskResult { FFstrbuf name; FFstrbuf interconnect; FFstrbuf serial; FFstrbuf devPath; FFstrbuf revision; FFPhysicalDiskType type; uint64_t size; double temperature; } FFPhysicalDiskResult; const char* ffDetectPhysicalDisk(FFlist* result, FFPhysicalDiskOptions* options); ================================================ FILE: src/detection/physicaldisk/physicaldisk_apple.c ================================================ #include "physicaldisk.h" #include "common/apple/cf_helpers.h" #include #include #include #include #include #include #ifdef MAC_OS_X_VERSION_10_15 #include #endif #ifdef MAC_OS_X_VERSION_10_15 static inline void wrapIoDestroyPlugInInterface(IOCFPlugInInterface*** pluginInf) { assert(pluginInf); if (*pluginInf) IODestroyPlugInInterface(*pluginInf); } #endif static const char* detectSsdTemp(io_service_t entryPhysical, double* temp) { #ifdef MAC_OS_X_VERSION_10_15 __attribute__((__cleanup__(wrapIoDestroyPlugInInterface))) IOCFPlugInInterface** pluginInf = NULL; int32_t score; if (IOCreatePlugInInterfaceForService(entryPhysical, kIONVMeSMARTUserClientTypeID, kIOCFPlugInInterfaceID, &pluginInf, &score) != kIOReturnSuccess) return "IOCreatePlugInInterfaceForService() failed"; IONVMeSMARTInterface** smartInf = NULL; if ((*pluginInf)->QueryInterface(pluginInf, CFUUIDGetUUIDBytes(kIONVMeSMARTInterfaceID), (LPVOID) &smartInf) != kIOReturnSuccess) return "QueryInterface() failed"; NVMeSMARTData smartData; const char* error = NULL; if ((*smartInf)->SMARTReadData(smartInf, &smartData) == kIOReturnSuccess) *temp = smartData.TEMPERATURE - 273; else error = "SMARTReadData() failed"; (*pluginInf)->Release(smartInf); return error; #else return "No support for old MacOS version"; #endif } const char* ffDetectPhysicalDisk(FFlist* result, FFPhysicalDiskOptions* options) { FF_IOOBJECT_AUTO_RELEASE io_iterator_t iterator = 0; if (IOServiceGetMatchingServices(MACH_PORT_NULL, IOServiceMatching(kIOMediaClass), &iterator) != KERN_SUCCESS) return "IOServiceGetMatchingServices() failed"; io_registry_entry_t registryEntry; while ((registryEntry = IOIteratorNext(iterator)) != IO_OBJECT_NULL) { FF_IOOBJECT_AUTO_RELEASE io_registry_entry_t entryPartition = registryEntry; io_name_t deviceName; if (IORegistryEntryGetName(registryEntry, deviceName) != KERN_SUCCESS) continue; if (options->namePrefix.length && strncmp(deviceName, options->namePrefix.chars, options->namePrefix.length) != 0) continue; FF_IOOBJECT_AUTO_RELEASE io_registry_entry_t entryDriver = 0; if (IORegistryEntryGetParentEntry(entryPartition, kIOServicePlane, &entryDriver) != KERN_SUCCESS) continue; if (!IOObjectConformsTo(entryDriver, kIOBlockStorageDriverClass)) // physical disk only continue; FFPhysicalDiskResult* device = (FFPhysicalDiskResult*) ffListAdd(result); ffStrbufInit(&device->serial); ffStrbufInit(&device->revision); ffStrbufInitS(&device->name, deviceName); ffStrbufInit(&device->devPath); ffStrbufInit(&device->interconnect); device->type = FF_PHYSICALDISK_TYPE_NONE; device->size = 0; device->temperature = FF_PHYSICALDISK_TEMP_UNSET; FF_CFTYPE_AUTO_RELEASE CFBooleanRef removable = IORegistryEntryCreateCFProperty(entryPartition, CFSTR(kIOMediaRemovableKey), kCFAllocatorDefault, kNilOptions); if (removable) device->type |= CFBooleanGetValue(removable) ? FF_PHYSICALDISK_TYPE_REMOVABLE : FF_PHYSICALDISK_TYPE_FIXED; FF_CFTYPE_AUTO_RELEASE CFBooleanRef writable = IORegistryEntryCreateCFProperty(entryPartition, CFSTR(kIOMediaWritableKey), kCFAllocatorDefault, kNilOptions); if (writable) device->type |= CFBooleanGetValue(writable) ? FF_PHYSICALDISK_TYPE_READWRITE : FF_PHYSICALDISK_TYPE_READONLY; FF_CFTYPE_AUTO_RELEASE CFStringRef bsdName = IORegistryEntryCreateCFProperty(entryPartition, CFSTR(kIOBSDNameKey), kCFAllocatorDefault, kNilOptions); if (bsdName) { ffCfStrGetString(bsdName, &device->devPath); ffStrbufPrependS(&device->devPath, "/dev/"); } FF_CFTYPE_AUTO_RELEASE CFNumberRef mediaSize = IORegistryEntryCreateCFProperty(entryPartition, CFSTR(kIOMediaSizeKey), kCFAllocatorDefault, kNilOptions); if (mediaSize) ffCfNumGetInt64(mediaSize, (int64_t*) &device->size); else device->size = 0; FF_IOOBJECT_AUTO_RELEASE io_registry_entry_t entryPhysical = 0; if (IORegistryEntryGetParentEntry(entryDriver, kIOServicePlane, &entryPhysical) == KERN_SUCCESS) { FF_CFTYPE_AUTO_RELEASE CFDictionaryRef protocolCharacteristics = IORegistryEntryCreateCFProperty(entryPhysical, CFSTR(kIOPropertyProtocolCharacteristicsKey), kCFAllocatorDefault, kNilOptions); if (protocolCharacteristics) ffCfDictGetString(protocolCharacteristics, CFSTR(kIOPropertyPhysicalInterconnectTypeKey), &device->interconnect); FF_CFTYPE_AUTO_RELEASE CFDictionaryRef deviceCharacteristics = IORegistryEntryCreateCFProperty(entryPhysical, CFSTR(kIOPropertyDeviceCharacteristicsKey), kCFAllocatorDefault, kNilOptions); if (deviceCharacteristics) { ffCfDictGetString(deviceCharacteristics, CFSTR(kIOPropertyProductSerialNumberKey), &device->serial); ffStrbufTrimSpace(&device->serial); ffCfDictGetString(deviceCharacteristics, CFSTR(kIOPropertyProductRevisionLevelKey), &device->revision); ffStrbufTrimRightSpace(&device->revision); CFStringRef mediumType = (CFStringRef) CFDictionaryGetValue(deviceCharacteristics, CFSTR(kIOPropertyMediumTypeKey)); if (mediumType) { if (CFStringCompare(mediumType, CFSTR(kIOPropertyMediumTypeSolidStateKey), 0) == 0) device->type |= FF_PHYSICALDISK_TYPE_SSD; else if (CFStringCompare(mediumType, CFSTR(kIOPropertyMediumTypeRotationalKey), 0) == 0) device->type |= FF_PHYSICALDISK_TYPE_HDD; } } #ifdef MAC_OS_X_VERSION_10_15 if (options->temp) { FF_CFTYPE_AUTO_RELEASE CFBooleanRef nvmeSMARTCapable = IORegistryEntryCreateCFProperty(entryPhysical, CFSTR(kIOPropertyNVMeSMARTCapableKey), kCFAllocatorDefault, kNilOptions); if (nvmeSMARTCapable && CFBooleanGetValue(nvmeSMARTCapable)) detectSsdTemp(entryPhysical, &device->temperature); } #endif } } return NULL; } ================================================ FILE: src/detection/physicaldisk/physicaldisk_bsd.c ================================================ #include "physicaldisk.h" #if __has_include() #include "common/stringUtils.h" #include #include #include #include #include #include const char* ffDetectPhysicalDisk(FFlist* result, FFPhysicalDiskOptions* options) { struct gmesh geomTree; if (geom_gettree(&geomTree) < 0) return "geom_gettree() failed"; if (geom_stats_open() < 0) return "geom_stats_open() failed"; void* snap = geom_stats_snapshot_get(); struct devstat* snapIter; while ((snapIter = geom_stats_snapshot_next(snap)) != NULL) { if (snapIter->device_type & DEVSTAT_TYPE_PASS) continue; struct gident* geomId = geom_lookupid(&geomTree, snapIter->id); if (geomId == NULL) continue; if (geomId->lg_what != ISPROVIDER) continue; struct gprovider* provider = (struct gprovider*) geomId->lg_ptr; if (provider->lg_geom->lg_rank != 1) continue; // Should memory disk (MD) be considered as physical disk? //if (!ffStrEquals(provider->lg_geom->lg_class->lg_name, "DISK")) // continue; FF_STRBUF_AUTO_DESTROY name = ffStrbufCreateS(provider->lg_name); FF_STRBUF_AUTO_DESTROY identifier = ffStrbufCreate(); FFPhysicalDiskType type = FF_PHYSICALDISK_TYPE_NONE; for (struct gconfig* ptr = provider->lg_config.lh_first; ptr; ptr = ptr->lg_config.le_next) { if (ffStrEquals(ptr->lg_name, "descr")) ffStrbufSetS(&name, ptr->lg_val); else if (ffStrEquals(ptr->lg_name, "rotationrate") && !ffStrEquals(ptr->lg_val, "unknown")) type |= ffStrEquals(ptr->lg_val, "0") ? FF_PHYSICALDISK_TYPE_SSD : FF_PHYSICALDISK_TYPE_HDD; else if (ffStrEquals(ptr->lg_name, "ident")) ffStrbufSetS(&identifier, ptr->lg_val); else if (ffStrEquals(ptr->lg_name, "access")) { if (ffStrEquals(ptr->lg_val, "read-only")) type |= FF_PHYSICALDISK_TYPE_READONLY; else if (ffStrEquals(ptr->lg_val, "read-write")) type |= FF_PHYSICALDISK_TYPE_READWRITE; } } if (options->namePrefix.length && !ffStrbufStartsWith(&name, &options->namePrefix)) continue; FFPhysicalDiskResult* device = (FFPhysicalDiskResult*) ffListAdd(result); ffStrbufInitF(&device->devPath, "/dev/%s", provider->lg_name); ffStrbufInitMove(&device->serial, &identifier); ffStrbufTrimSpace(&device->serial); ffStrbufInit(&device->revision); ffStrbufInit(&device->interconnect); switch (snapIter->device_type & DEVSTAT_TYPE_IF_MASK) { case DEVSTAT_TYPE_IF_SCSI: ffStrbufAppendS(&device->interconnect, "SCSI"); break; case DEVSTAT_TYPE_IF_IDE: ffStrbufAppendS(&device->interconnect, "IDE"); break; case DEVSTAT_TYPE_IF_OTHER: ffStrbufAppendS(&device->interconnect, "OTHER"); break; // https://github.com/freebsd/freebsd-src/commit/d282baddb0b029ca8466d23ac51e95c918442535 case 0x040 /*DEVSTAT_TYPE_IF_NVME*/: ffStrbufAppendS(&device->interconnect, "NVMe"); break; } device->size = (uint64_t) provider->lg_mediasize; ffStrbufInitMove(&device->name, &name); if (!(device->type & FF_PHYSICALDISK_TYPE_READONLY) && !(device->type & FF_PHYSICALDISK_TYPE_READWRITE)) { int acr = 1, acw = 1; // Number of partitions mounted for reading or writing if (sscanf(provider->lg_mode, "r%dw%de%*d", &acr, &acw) == 2 && acr) type |= acw ? FF_PHYSICALDISK_TYPE_READWRITE : FF_PHYSICALDISK_TYPE_READONLY; } device->type = type; device->temperature = FF_PHYSICALDISK_TEMP_UNSET; } geom_stats_snapshot_free(snap); geom_stats_close(); return NULL; } #else const char* ffDetectPhysicalDisk(FFlist* result, FFPhysicalDiskOptions* options) { return "Fastfetch was compiled without libgeom support"; } #endif ================================================ FILE: src/detection/physicaldisk/physicaldisk_haiku.c ================================================ #include "physicaldisk.h" #include "common/io.h" #include "common/stringUtils.h" #include #include #include #include static const char* detectDisk(FFstrbuf* path, const char* diskType, FFlist* result) { FF_AUTO_CLOSE_FD int rawfd = open(path->chars, O_RDONLY | O_CLOEXEC); if (rawfd < 0) return "detectDisk: open(rawfd) failed"; device_geometry geometry; if (ioctl(rawfd, B_GET_GEOMETRY, &geometry, sizeof(geometry)) < 0) return "ioctl(B_GET_GEOMETRY) failed"; char name[B_OS_NAME_LENGTH]; if (ioctl(rawfd, B_GET_DEVICE_NAME, name, sizeof(name)) != 0) { // ioctl reports `not a tty` for NVME drives for some reason snprintf(name, sizeof(name), "Unknown %s drive", diskType); } FFPhysicalDiskResult* device = (FFPhysicalDiskResult*) ffListAdd(result); ffStrbufInitS(&device->name, name); ffStrbufInitCopy(&device->devPath, path); ffStrbufInit(&device->serial); ffStrbufInit(&device->revision); ffStrbufInitS(&device->interconnect, diskType); device->temperature = FF_PHYSICALDISK_TEMP_UNSET; device->type = FF_PHYSICALDISK_TYPE_NONE; device->type |= (geometry.read_only ? FF_PHYSICALDISK_TYPE_READONLY : FF_PHYSICALDISK_TYPE_READWRITE) | (geometry.removable ? FF_PHYSICALDISK_TYPE_REMOVABLE : FF_PHYSICALDISK_TYPE_FIXED); device->size = (uint64_t) geometry.cylinder_count * geometry.head_count * geometry.sectors_per_track * geometry.bytes_per_sector; return NULL; } static const char* searchRawDeviceFile(FFstrbuf* path, const char* diskType, FFlist* result) { FF_AUTO_CLOSE_DIR DIR* dir = opendir(path->chars); if (!dir) return "detectDiskType: opendir() failed"; uint32_t baseLen = path->length; struct dirent* entry; while((entry = readdir(dir))) { if (entry->d_name[0] == '.') continue; ffStrbufAppendC(path, '/'); ffStrbufAppendS(path, entry->d_name); struct stat st; if (stat(path->chars, &st) != 0) { ffStrbufSubstrBefore(path, baseLen); continue; } if (S_ISDIR(st.st_mode)) searchRawDeviceFile(path, diskType, result); else if (ffStrEquals(entry->d_name, "raw")) detectDisk(path, diskType, result); ffStrbufSubstrBefore(path, baseLen); } return NULL; } const char* ffDetectPhysicalDisk(FFlist* result, FF_MAYBE_UNUSED FFPhysicalDiskOptions* options) { FF_AUTO_CLOSE_DIR DIR* dir = opendir("/dev/disk"); if (!dir) return "opendir(/dev/disk) failed"; FF_STRBUF_AUTO_DESTROY path = ffStrbufCreateA(64); ffStrbufAppendS(&path, "/dev/disk/"); uint32_t baseLen = path.length; struct dirent* entry; while((entry = readdir(dir))) { if (entry->d_name[0] == '.' || ffStrEquals(entry->d_name, "virtual")) continue; ffStrbufAppendS(&path, entry->d_name); searchRawDeviceFile(&path, entry->d_name, result); ffStrbufSubstrBefore(&path, baseLen); } return NULL; } ================================================ FILE: src/detection/physicaldisk/physicaldisk_linux.c ================================================ #include "physicaldisk.h" #include "common/io.h" #include "common/properties.h" #include "common/stringUtils.h" #include #include #include #include static double detectNvmeTemp(int devfd) { char pathHwmon[] = "hwmon$/temp1_input"; for (char c = '0'; c <= '9'; c++) // hopefully there's only one digit { pathHwmon[strlen("hwmon")] = c; char buffer[64]; ssize_t size = ffReadFileDataRelative(devfd, pathHwmon, ARRAY_SIZE(buffer), buffer); if (size > 0) { buffer[size] = '\0'; double temp = strtod(buffer, NULL); return temp > 0 && temp < 10000000 /*VMware*/ ? temp / 1000 : FF_PHYSICALDISK_TEMP_UNSET; } } return FF_PHYSICALDISK_TEMP_UNSET; } static void parsePhysicalDisk(int dfd, const char* devName, FFPhysicalDiskOptions* options, FFlist* result) { int devfd = openat(dfd, "device", O_RDONLY | O_CLOEXEC | O_PATH | O_DIRECTORY); if (devfd < 0) return; // virtual device FF_STRBUF_AUTO_DESTROY name = ffStrbufCreate(); { if (ffAppendFileBufferRelative(devfd, "vendor", &name)) { ffStrbufTrimRightSpace(&name); if (name.length > 0) ffStrbufAppendC(&name, ' '); } ffAppendFileBufferRelative(devfd, "model", &name); ffStrbufTrimRightSpace(&name); if (name.length == 0) ffStrbufSetS(&name, devName); if (ffStrStartsWith(devName, "nvme")) { int devid, nsid; if (sscanf(devName, "nvme%dn%d", &devid, &nsid) == 2) { bool multiNs = nsid > 1; if (!multiNs) { char pathSysBlock[32]; snprintf(pathSysBlock, ARRAY_SIZE(pathSysBlock), "/dev/nvme%dn2", devid); multiNs = access(pathSysBlock, F_OK) == 0; } if (multiNs) { // In Asahi Linux, there are multiple namespaces for the same NVMe drive. ffStrbufAppendF(&name, " - %d", nsid); } } } if (options->namePrefix.length && !ffStrbufStartsWith(&name, &options->namePrefix)) return; } FFPhysicalDiskResult* device = (FFPhysicalDiskResult*) ffListAdd(result); device->type = FF_PHYSICALDISK_TYPE_NONE; ffStrbufInitMove(&device->name, &name); ffStrbufInitF(&device->devPath, "/dev/%s", devName); bool isVirtual = false; { ffStrbufInit(&device->interconnect); if (ffStrStartsWith(devName, "nvme")) ffStrbufSetStatic(&device->interconnect, "NVMe"); else if (ffStrStartsWith(devName, "mmcblk")) ffStrbufSetStatic(&device->interconnect, "MMC"); else if (ffStrStartsWith(devName, "md")) { ffStrbufSetStatic(&device->interconnect, "RAID"); isVirtual = true; } else { char pathSysDeviceLink[64]; snprintf(pathSysDeviceLink, ARRAY_SIZE(pathSysDeviceLink), "/sys/block/%s/device", devName); char pathSysDeviceReal[PATH_MAX]; if (realpath(pathSysDeviceLink, pathSysDeviceReal)) { if (strstr(pathSysDeviceReal, "/usb") != NULL) ffStrbufSetStatic(&device->interconnect, "USB"); else if (strstr(pathSysDeviceReal, "/ata") != NULL) ffStrbufSetStatic(&device->interconnect, "ATA"); else if (strstr(pathSysDeviceReal, "/scsi") != NULL) ffStrbufSetStatic(&device->interconnect, "SCSI"); else if (strstr(pathSysDeviceReal, "/nvme") != NULL) ffStrbufSetStatic(&device->interconnect, "NVMe"); else if (strstr(pathSysDeviceReal, "/virtio") != NULL) { ffStrbufSetStatic(&device->interconnect, "Virtual"); isVirtual = true; } else { if (ffAppendFileBufferRelative(devfd, "transport", &device->interconnect)) ffStrbufTrimRightSpace(&device->interconnect); } } } } if (!isVirtual) { char isRotationalChar = '1'; if (ffReadFileDataRelative(dfd, "queue/rotational", 1, &isRotationalChar) > 0) device->type |= isRotationalChar == '1' ? FF_PHYSICALDISK_TYPE_HDD : FF_PHYSICALDISK_TYPE_SSD; } { char blkSize[32]; ssize_t fileSize = ffReadFileDataRelative(dfd, "size", ARRAY_SIZE(blkSize) - 1, blkSize); if (fileSize > 0) { blkSize[fileSize] = 0; device->size = (uint64_t) strtoul(blkSize, NULL, 10) * 512; } else device->size = 0; } { char removableChar = '0'; if (ffReadFileDataRelative(dfd, "removable", 1, &removableChar) > 0) device->type |= removableChar == '1' ? FF_PHYSICALDISK_TYPE_REMOVABLE : FF_PHYSICALDISK_TYPE_FIXED; } { char roChar = '0'; if (ffReadFileDataRelative(dfd, "ro", 1, &roChar) > 0) device->type |= roChar == '1' ? FF_PHYSICALDISK_TYPE_READONLY : FF_PHYSICALDISK_TYPE_READWRITE; } { ffStrbufInit(&device->serial); if (ffReadFileBufferRelative(devfd, "serial", &device->serial)) ffStrbufTrimSpace(&device->serial); } { ffStrbufInit(&device->revision); if (ffReadFileBufferRelative(devfd, "firmware_rev", &device->revision)) ffStrbufTrimRightSpace(&device->revision); else { if (ffReadFileBufferRelative(devfd, "rev", &device->revision)) ffStrbufTrimRightSpace(&device->revision); } } if (options->temp) device->temperature = detectNvmeTemp(devfd); else device->temperature = FF_PHYSICALDISK_TEMP_UNSET; } const char* ffDetectPhysicalDisk(FFlist* result, FFPhysicalDiskOptions* options) { FF_AUTO_CLOSE_DIR DIR* sysBlockDirp = opendir("/sys/block/"); if(sysBlockDirp == NULL) return "opendir(\"/sys/block/\") == NULL"; struct dirent* sysBlockEntry; while ((sysBlockEntry = readdir(sysBlockDirp)) != NULL) { const char* const devName = sysBlockEntry->d_name; if (devName[0] == '.') continue; char pathSysBlock[sizeof("/sys/block/") + sizeof(sysBlockEntry->d_name)]; snprintf(pathSysBlock, ARRAY_SIZE(pathSysBlock), "/sys/block/%s", devName); int dfd = openat(dirfd(sysBlockDirp), devName, O_RDONLY | O_CLOEXEC | O_PATH | O_DIRECTORY); if (dfd > 0) parsePhysicalDisk(dfd, devName, options, result); } return NULL; } ================================================ FILE: src/detection/physicaldisk/physicaldisk_nosupport.c ================================================ #include "physicaldisk.h" const char* ffDetectPhysicalDisk(FF_MAYBE_UNUSED FFlist* result, FF_MAYBE_UNUSED FFPhysicalDiskOptions* options) { return "Not supported on this platform"; } ================================================ FILE: src/detection/physicaldisk/physicaldisk_sunos.c ================================================ #include "physicaldisk.h" #include "common/stringUtils.h" #include "sys/scsi/generic/inquiry.h" #include #include struct FFWalkTreeBundle { FFPhysicalDiskOptions* options; FFlist* disks; }; static int walkDevTree(di_node_t node, di_minor_t minor, struct FFWalkTreeBundle* bundle) { if (di_minor_spectype(minor) != S_IFCHR || !ffStrEquals(di_minor_name(minor), "a,raw")) return DI_WALK_CONTINUE; char* productId; char* vendorId; if (di_prop_lookup_strings(DDI_DEV_T_ANY, node, "inquiry-product-id", &productId) > 0 && di_prop_lookup_strings(DDI_DEV_T_ANY, node, "inquiry-vendor-id", &vendorId) > 0) { FF_STRBUF_AUTO_DESTROY name = ffStrbufCreateF("%s %s", vendorId, productId); if (bundle->options->namePrefix.length && !ffStrbufStartsWithIgnCase(&name, &bundle->options->namePrefix)) return DI_WALK_CONTINUE; FFPhysicalDiskResult* device = (FFPhysicalDiskResult*) ffListAdd(bundle->disks); ffStrbufInitMove(&device->name, &name); ffStrbufInitF(&device->devPath, "/devices%s", di_devfs_path(node)); ffStrbufInit(&device->serial); ffStrbufInit(&device->revision); ffStrbufInit(&device->interconnect); device->temperature = FF_PHYSICALDISK_TEMP_UNSET; device->type = FF_PHYSICALDISK_TYPE_NONE; device->size = 0; char* buf; if (di_prop_lookup_strings(DDI_DEV_T_ANY, node, "inquiry-serial-no", &buf) > 0) { ffStrbufSetS(&device->serial, buf); ffStrbufTrimSpace(&device->serial); } if (di_prop_lookup_strings(DDI_DEV_T_ANY, node, "inquiry-revision-id", &buf) > 0) { ffStrbufSetS(&device->revision, buf); ffStrbufTrimRightSpace(&device->revision); } if (di_prop_lookup_strings(DDI_DEV_T_ANY, node, "class", &buf) > 0) ffStrbufSetS(&device->interconnect, buf); device->type |= di_prop_find(DDI_DEV_T_ANY, node, "removable-media") ? FF_PHYSICALDISK_TYPE_REMOVABLE : FF_PHYSICALDISK_TYPE_FIXED; int* value; if (di_prop_lookup_ints(DDI_DEV_T_ANY, node, "device-solid-state", &value) > 0) device->type |= *value ? FF_PHYSICALDISK_TYPE_SSD : FF_PHYSICALDISK_TYPE_HDD; if (di_prop_lookup_ints(DDI_DEV_T_ANY, node, "inquiry-device-type", &value) > 0) device->type |= *value == DTYPE_DIRECT ? FF_PHYSICALDISK_TYPE_READWRITE : *value == DTYPE_RODIRECT ? FF_PHYSICALDISK_TYPE_READONLY : 0; int64_t* nblocks; if (di_prop_lookup_int64(DDI_DEV_T_ANY, node, "device-nblocks", &nblocks) > 0 && di_prop_lookup_ints(DDI_DEV_T_ANY, node, "device-blksize", &value) > 0) device->size = (uint64_t) ((uint64_t) *nblocks * (uint64_t) *value); } return DI_WALK_CONTINUE; } const char* ffDetectPhysicalDisk(FFlist* result, FFPhysicalDiskOptions* options) { di_node_t rootNode = di_init("/", DINFOCPYALL); if (rootNode == DI_NODE_NIL) return "di_init() failed"; di_walk_minor(rootNode, DDI_NT_BLOCK, DI_WALK_CLDFIRST, &(struct FFWalkTreeBundle) { options, result }, (void*) walkDevTree); di_fini(rootNode); return NULL; } ================================================ FILE: src/detection/physicaldisk/physicaldisk_windows.c ================================================ #include "physicaldisk.h" #include "common/io.h" #include "common/windows/unicode.h" #include #include #include static bool detectPhysicalDisk(const wchar_t* szDevice, FFlist* result, FFPhysicalDiskOptions* options) { FF_AUTO_CLOSE_FD HANDLE hDevice = CreateFileW(szDevice, FILE_READ_ATTRIBUTES, FILE_SHARE_READ, NULL, OPEN_EXISTING, 0, NULL); if (hDevice == INVALID_HANDLE_VALUE) return false; DWORD retSize; char sddBuffer[4096]; if(!DeviceIoControl( hDevice, IOCTL_STORAGE_QUERY_PROPERTY, &(STORAGE_PROPERTY_QUERY) { .PropertyId = StorageDeviceProperty, .QueryType = PropertyStandardQuery, }, sizeof(STORAGE_PROPERTY_QUERY), &sddBuffer, sizeof(sddBuffer), &retSize, NULL ) || retSize == 0) return true; FFPhysicalDiskResult* device = (FFPhysicalDiskResult*) ffListAdd(result); device->type = FF_PHYSICALDISK_TYPE_NONE; STORAGE_DEVICE_DESCRIPTOR* sdd = (STORAGE_DEVICE_DESCRIPTOR*) sddBuffer; ffStrbufInit(&device->name); if (sdd->VendorIdOffset != 0) { ffStrbufSetS(&device->name, (const char*) sddBuffer + sdd->VendorIdOffset); ffStrbufTrim(&device->name, ' '); } if (sdd->ProductIdOffset != 0) { if (device->name.length) ffStrbufAppendC(&device->name, ' '); ffStrbufAppendS(&device->name, (const char*) sddBuffer + sdd->ProductIdOffset); ffStrbufTrimRight(&device->name, ' '); } if (!device->name.length) ffStrbufSetWS(&device->name, szDevice); if (options->namePrefix.length && !ffStrbufStartsWith(&device->name, &options->namePrefix)) { ffStrbufDestroy(&device->name); result->length--; return true; } ffStrbufInitWS(&device->devPath, szDevice); ffStrbufInit(&device->serial); if (sdd->SerialNumberOffset != 0) { ffStrbufSetS(&device->serial, (const char*) sddBuffer + sdd->SerialNumberOffset); ffStrbufTrimSpace(&device->serial); } ffStrbufInit(&device->revision); if (sdd->ProductRevisionOffset != 0) { ffStrbufSetS(&device->revision, (const char*) sddBuffer + sdd->ProductRevisionOffset); ffStrbufTrimRightSpace(&device->revision); } device->type |= sdd->RemovableMedia ? FF_PHYSICALDISK_TYPE_REMOVABLE : FF_PHYSICALDISK_TYPE_FIXED; ffStrbufInit(&device->interconnect); switch (sdd->BusType) { case BusTypeUnknown: ffStrbufSetStatic(&device->interconnect, "Unknown"); break; case BusTypeScsi: ffStrbufSetStatic(&device->interconnect, "SCSI"); break; case BusTypeAtapi: ffStrbufSetStatic(&device->interconnect, "ATAPI"); break; case BusTypeAta: ffStrbufSetStatic(&device->interconnect, "ATA"); break; case BusType1394: ffStrbufSetStatic(&device->interconnect, "1394"); break; case BusTypeSsa: ffStrbufSetStatic(&device->interconnect, "SSA"); break; case BusTypeFibre: ffStrbufSetStatic(&device->interconnect, "Fibre"); break; case BusTypeUsb: ffStrbufSetStatic(&device->interconnect, "USB"); break; case BusTypeRAID: ffStrbufSetStatic(&device->interconnect, "RAID"); break; case BusTypeiScsi: ffStrbufSetStatic(&device->interconnect, "iSCSI"); break; case BusTypeSas: ffStrbufSetStatic(&device->interconnect, "SAS"); break; case BusTypeSata: ffStrbufSetStatic(&device->interconnect, "SATA"); break; case BusTypeSd: ffStrbufSetStatic(&device->interconnect, "SD"); break; case BusTypeMmc: ffStrbufSetStatic(&device->interconnect, "MMC"); break; case BusTypeVirtual: ffStrbufSetStatic(&device->interconnect, "Virtual"); break; case BusTypeFileBackedVirtual: ffStrbufSetStatic(&device->interconnect, "File Backed Virtual"); break; case BusTypeSpaces: ffStrbufSetStatic(&device->interconnect, "Spaces"); break; case BusTypeNvme: ffStrbufSetStatic(&device->interconnect, "NVMe"); break; case BusTypeSCM: ffStrbufSetStatic(&device->interconnect, "SCM"); break; case BusTypeUfs: ffStrbufSetStatic(&device->interconnect, "UFS"); break; default: ffStrbufSetF(&device->interconnect, "Unknown (%d)", (int) sdd->BusType); break; } { DEVICE_SEEK_PENALTY_DESCRIPTOR dspd = {}; if(DeviceIoControl( hDevice, IOCTL_STORAGE_QUERY_PROPERTY, &(STORAGE_PROPERTY_QUERY) { .PropertyId = StorageDeviceSeekPenaltyProperty, .QueryType = PropertyStandardQuery, }, sizeof(STORAGE_PROPERTY_QUERY), &dspd, sizeof(dspd), &retSize, NULL ) && retSize == sizeof(dspd)) device->type |= dspd.IncursSeekPenalty ? FF_PHYSICALDISK_TYPE_HDD : FF_PHYSICALDISK_TYPE_SSD; } { DISK_GEOMETRY_EX dge = {}; if(DeviceIoControl( hDevice, IOCTL_DISK_GET_DRIVE_GEOMETRY_EX, NULL, 0, &dge, sizeof(dge), &retSize, NULL)) device->size = (uint64_t) dge.DiskSize.QuadPart; else device->size = 0; } { alignas(GET_MEDIA_TYPES) uint8_t buffer[sizeof(GET_MEDIA_TYPES) + sizeof(DEVICE_MEDIA_INFO) * 7] = {}; GET_MEDIA_TYPES* gmt = (GET_MEDIA_TYPES*) buffer; if(DeviceIoControl( hDevice, IOCTL_STORAGE_GET_MEDIA_TYPES_EX, NULL, 0, gmt, sizeof(buffer), &retSize, NULL) && gmt->MediaInfoCount > 0 ) { // DiskInfo and RemovableDiskInfo have the same structures. TapeInfo doesn't. if (gmt->DeviceType != FILE_DEVICE_TAPE) { __auto_type diskInfo = &gmt->MediaInfo[0].DeviceSpecific.DiskInfo; if (diskInfo->MediaCharacteristics & MEDIA_READ_ONLY) device->type |= FF_PHYSICALDISK_TYPE_READONLY; else if (diskInfo->MediaCharacteristics & MEDIA_READ_WRITE) device->type |= FF_PHYSICALDISK_TYPE_READWRITE; if (device->size == 0) device->size = (uint64_t) diskInfo->NumberMediaSides * diskInfo->TracksPerCylinder * diskInfo->SectorsPerTrack * diskInfo->BytesPerSector; } else { __auto_type tapeInfo = &gmt->MediaInfo[0].DeviceSpecific.TapeInfo; if (tapeInfo->MediaCharacteristics & MEDIA_READ_ONLY) device->type |= FF_PHYSICALDISK_TYPE_READONLY; else if (tapeInfo->MediaCharacteristics & MEDIA_READ_WRITE) device->type |= FF_PHYSICALDISK_TYPE_READWRITE; } } } device->temperature = FF_PHYSICALDISK_TEMP_UNSET; if (options->temp) { STORAGE_TEMPERATURE_DATA_DESCRIPTOR stdd = {}; if(DeviceIoControl( hDevice, IOCTL_STORAGE_QUERY_PROPERTY, &(STORAGE_PROPERTY_QUERY) { .PropertyId = StorageDeviceTemperatureProperty, .QueryType = PropertyStandardQuery, }, sizeof(STORAGE_PROPERTY_QUERY), &stdd, sizeof(stdd), &retSize, NULL ) && retSize == sizeof(stdd)) device->temperature = stdd.TemperatureInfo[0].Temperature; } return true; } const char* ffDetectPhysicalDisk(FFlist* result, FFPhysicalDiskOptions* options) { { wchar_t szPhysicalDrive[32] = L"\\\\.\\PhysicalDrive"; wchar_t* pNum = szPhysicalDrive + strlen("\\\\.\\PhysicalDrive"); for (uint32_t idev = 0; ; ++idev) { _ultow(idev, pNum, 10); if (!detectPhysicalDisk(szPhysicalDrive, result, options)) break; } } { wchar_t szCdrom[32] = L"\\\\.\\CDROM"; wchar_t* pNum = szCdrom + strlen("\\\\.\\CDROM"); for (uint32_t idev = 0; ; ++idev) { _ultow(idev, pNum, 10); if (!detectPhysicalDisk(szCdrom, result, options)) break; } } return NULL; } ================================================ FILE: src/detection/physicalmemory/physicalmemory.c ================================================ #include "physicalmemory.h" static inline const char* getVendorString(unsigned vendorId) { switch (vendorId) { case 0x017A: return "Apacer"; case 0x0198: return "Kingston"; case 0x029E: return "Corsair"; case 0x04CB: return "A-DATA"; case 0x04CD: return "G-Skill"; case 0x059B: case 0x859B: return "Crucial"; case 0x00CE: case 0x80CE: case 0xCE00: return "Samsung"; case 0x014F: return "Transcend"; case 0x2C00: case 0x802C: return "Micron"; case 0xAD00: case 0x80AD: return "SK Hynix"; case 0x5105: case 0x8551: return "Qimonda"; case 0x02FE: return "Elpida"; case 0x0467: return "Ramaxel"; default: return NULL; } } void FFPhysicalMemoryUpdateVendorString(FFPhysicalMemoryResult* device) { if (device->vendor.length == 0) return; if (ffStrbufEqualS(&device->vendor, "Unknown")) { ffStrbufClear(&device->vendor); return; } char vendorIdStr[5]; if (ffStrbufStartsWithS(&device->vendor, "0x")) { if (device->vendor.length < 6) return; memcpy(vendorIdStr, device->vendor.chars + 2, 4); } else { if (device->vendor.length < 4) return; memcpy(vendorIdStr, device->vendor.chars, 4); } vendorIdStr[4] = '\0'; char* pEnd = NULL; uint32_t vendorId = (uint32_t) strtoul(vendorIdStr, &pEnd, 16); if (*pEnd != '\0') return; const char* vendorStr = getVendorString(vendorId); if (vendorStr) ffStrbufSetStatic(&device->vendor, vendorStr); } ================================================ FILE: src/detection/physicalmemory/physicalmemory.h ================================================ #pragma once #include "fastfetch.h" #include "modules/physicalmemory/option.h" typedef struct FFPhysicalMemoryResult { uint64_t size; // B uint32_t maxSpeed; // MT/s uint32_t runningSpeed; // MT/s bool installed; FFstrbuf type; FFstrbuf formFactor; FFstrbuf locator; FFstrbuf partNumber; FFstrbuf vendor; FFstrbuf serial; bool ecc; } FFPhysicalMemoryResult; const char* ffDetectPhysicalMemory(FFlist* result); // list of FFPhysicalMemoryResult void FFPhysicalMemoryUpdateVendorString(FFPhysicalMemoryResult* device); ================================================ FILE: src/detection/physicalmemory/physicalmemory_apple.m ================================================ #include "physicalmemory.h" #include "common/processing.h" #include "common/smbiosHelper.h" #include "common/stringUtils.h" #include "common/apple/cf_helpers.h" #import static void appendDevice( FFlist* result, NSString* type, NSString* vendor, NSString* size, // Intel only NSString* locator, NSString* serial, NSString* partNumber, NSString* speed, bool ecc) { FFPhysicalMemoryResult* device = ffListAdd(result); ffStrbufInitS(&device->type, type.UTF8String); ffStrbufInit(&device->formFactor); ffStrbufInitS(&device->locator, locator.UTF8String); ffStrbufInitS(&device->vendor, vendor.UTF8String); FFPhysicalMemoryUpdateVendorString(device); ffStrbufInitS(&device->serial, serial.UTF8String); ffCleanUpSmbiosValue(&device->serial); ffStrbufInitS(&device->partNumber, partNumber.UTF8String); ffCleanUpSmbiosValue(&device->partNumber); device->size = 0; device->maxSpeed = 0; device->runningSpeed = 0; device->installed = true; device->ecc = ecc; if (size) { char* unit = NULL; device->size = strtoul(size.UTF8String, &unit, 10); if (*unit == ' ') ++unit; switch (*unit) { case 'G': device->size *= 1024ULL * 1024 * 1024; break; case 'M': device->size *= 1024ULL * 1024; break; case 'K': device->size *= 1024ULL; break; case 'T': device->size *= 1024ULL * 1024 * 1024 * 1024; break; } } if (speed) { char* unit = NULL; device->maxSpeed = (uint32_t) strtoul(speed.UTF8String, &unit, 10); if (*unit == ' ') ++unit; switch (*unit) { case 'T': device->maxSpeed *= 1000 * 1000; break; case 'G': device->maxSpeed *= 1000; break; case 'K': device->maxSpeed /= 1000; break; } device->runningSpeed = device->maxSpeed; } } static const char* detectFromSystemProfiler(FFlist* result) { FF_STRBUF_AUTO_DESTROY buffer = ffStrbufCreate(); if (ffProcessAppendStdOut(&buffer, (char* const[]) { "system_profiler", "SPMemoryDataType", "-xml", "-detailLevel", "full", NULL }) != NULL) return "Starting `system_profiler SPMemoryDataType -xml -detailLevel full` failed"; NSArray* arr = [NSPropertyListSerialization propertyListWithData:[NSData dataWithBytes:buffer.chars length:buffer.length] options:NSPropertyListImmutable format:nil error:nil]; if (!arr || !arr.count) return "system_profiler SPMemoryDataType returned an empty array"; for (NSDictionary* data in arr[0][@"_items"]) { if (data[@"_items"]) { // for Intel for (NSDictionary* item in data[@"_items"]) { appendDevice(result, item[@"dimm_type"], item[@"dimm_manufacturer"], item[@"dimm_size"], item[@"_name"], item[@"dimm_serial_number"], item[@"dimm_part_number"], item[@"dimm_speed"], !![data[@"global_ecc_state"] isEqualToString:@"ecc_enabled"]); } } else { // for Apple Silicon appendDevice(result, data[@"dimm_type"], data[@"dimm_manufacturer"], data[@"SPMemoryDataType"], nil, nil, nil, nil, false); } } return NULL; } FF_MAYBE_UNUSED static const char* detectFromIokit(FFlist* result) { FF_IOOBJECT_AUTO_RELEASE io_registry_entry_t entryDevice = IORegistryEntryFromPath(MACH_PORT_NULL, "IODeviceTree:/chosen"); if (!entryDevice) return "IORegistryEntryFromPath() failed"; FF_CFTYPE_AUTO_RELEASE CFTypeRef dramType = IORegistryEntryCreateCFProperty(entryDevice, CFSTR("dram-type"), kCFAllocatorDefault, 0); FF_CFTYPE_AUTO_RELEASE CFTypeRef dramSize = IORegistryEntryCreateCFProperty(entryDevice, CFSTR("dram-size"), kCFAllocatorDefault, 0); FF_CFTYPE_AUTO_RELEASE CFTypeRef dramVendor = IORegistryEntryCreateCFProperty(entryDevice, CFSTR("dram-vendor"), kCFAllocatorDefault, 0); if (!dramType || !dramSize || !dramVendor) return "IORegistryEntryCreateCFProperty() failed"; FFPhysicalMemoryResult* device = ffListAdd(result); ffStrbufInit(&device->type); ffStrbufInit(&device->formFactor); ffStrbufInit(&device->locator); ffStrbufInit(&device->vendor); ffStrbufInit(&device->serial); ffStrbufInit(&device->partNumber); device->size = 0; device->maxSpeed = 0; device->runningSpeed = 0; device->installed = true; device->ecc = false; ffCfStrGetString(dramType, &device->type); ffCfStrGetString(dramVendor, &device->vendor); ffCfNumGetInt64(dramSize, (int64_t*) &device->size); return NULL; } const char* ffDetectPhysicalMemory(FFlist* result) { #if __aarch64__ if (detectFromIokit(result) == NULL) return NULL; #endif return detectFromSystemProfiler(result); } ================================================ FILE: src/detection/physicalmemory/physicalmemory_linux.c ================================================ #include "physicalmemory.h" #include "common/smbiosHelper.h" // 7.18 typedef struct FFSmbiosMemoryDevice { FFSmbiosHeader Header; // 2.1+ uint16_t PhysicalMemoryArrayHandle; // varies uint16_t MemoryErrorInformationHandle; //varies uint16_t TotalWidth; // varies uint16_t DataWidth; // varies uint16_t Size; // varies uint8_t FormFactor; // enum uint8_t DeviceSet; // varies uint8_t DeviceLocator; // string uint8_t BankLocator; // string uint8_t MemoryType; // enum uint16_t TypeDetail; // bit field // 2.3+ uint16_t Speed; // varies uint8_t Manufacturer; // string uint8_t SerialNumber; // string uint8_t AssetTag; // string uint8_t PartNumber; // string // 2.6+ uint8_t Attributes; // varies // 2.7+ uint32_t ExtendedSize; // varies uint16_t ConfiguredMemorySpeed; // varies // 2.8+ uint16_t MinimumVoltage; // varies uint16_t MaximumVoltage; // varies uint16_t ConfiguredVoltage; // varies // 3.2+ uint8_t MemoryTechnology; // varies uint16_t MemoryOperatingMode; // bit field uint8_t FirmwareVersion; // string uint16_t ModuleManufacturerID; // varies uint16_t ModuleProductID; // varies uint16_t MemorySubsystemControllerManufacturerID; // vaies uint16_t MemorySubsystemControllerProductID; // varies uint64_t NonVolatileSize; // varies uint64_t VolatileSize; // varies uint64_t CacheSize; // varies uint64_t LogicalSize; // varies // 3.3+ uint32_t ExtendedSpeed; // varies uint32_t ExtendedConfiguredSpeed; // varies // 3.7+ uint16_t Pmic0ManufacturerID; // varies uint16_t Pmic0RevisionNumber; // varies uint16_t RcdManufacturerID; // varies uint16_t RcdRevisionNumber; // varies } __attribute__((__packed__)) FFSmbiosMemoryDevice; static_assert(offsetof(FFSmbiosMemoryDevice, RcdRevisionNumber) == 0x62, "FFSmbiosMemoryDevice: Wrong struct alignment"); const char* ffDetectPhysicalMemory(FFlist* result) { const FFSmbiosHeaderTable* smbiosTable = ffGetSmbiosHeaderTable(); if (!smbiosTable) return "Failed to get SMBIOS data"; const FFSmbiosMemoryDevice* data = (const FFSmbiosMemoryDevice*) (*smbiosTable)[FF_SMBIOS_TYPE_MEMORY_DEVICE]; if (!data) return "Memory device is not found in SMBIOS data"; for (; data->Header.Type < FF_SMBIOS_TYPE_END_OF_TABLE; data = (const FFSmbiosMemoryDevice*) ffSmbiosNextEntry(&data->Header)) { if (data->Header.Type != FF_SMBIOS_TYPE_MEMORY_DEVICE) continue; const char* strings = (const char*) data + data->Header.Length; bool installed = data->Size != 0; FFPhysicalMemoryResult* device = ffListAdd(result); ffStrbufInit(&device->type); ffStrbufInit(&device->formFactor); ffStrbufInit(&device->locator); ffStrbufInit(&device->vendor); ffStrbufInit(&device->serial); ffStrbufInit(&device->partNumber); device->size = 0; device->maxSpeed = 0; device->runningSpeed = 0; device->installed = installed; device->ecc = false; if (installed && data->TotalWidth != 0xFFFF && data->DataWidth != 0xFFFF) device->ecc = data->TotalWidth > data->DataWidth; if (installed && data->Size != 0xFFFF) { if (data->Size == 0x7FFF) device->size = (data->ExtendedSize & ~(1ULL << 31)) * 1024ULL * 1024ULL; else if (data->Size & (1 << 15)) { // in kB device->size = (data->Size & ~(1ULL << 15)) * 1024ULL; } else { // in MB device->size = data->Size * 1024ULL * 1024ULL; } } // https://github.com/fastfetch-cli/fastfetch/issues/1051#issuecomment-2206687345 const char* lbank = ffSmbiosLocateString(strings, data->BankLocator); const char* ldevice = ffSmbiosLocateString(strings, data->DeviceLocator); if (lbank && ldevice) ffStrbufSetF(&device->locator, "%s/%s", lbank, ldevice); else if (lbank) ffStrbufSetS(&device->locator, lbank); else if (ldevice) ffStrbufSetS(&device->locator, ldevice); const char* formFactorNames[] = { NULL, // 0x00 (Placeholder for indexing) "Other", // 0x01 "Unknown", // 0x02 "SIMM", // 0x03 "SIP", // 0x04 "Chip", // 0x05 "DIP", // 0x06 "ZIP", // 0x07 "Proprietary Card",// 0x08 "DIMM", // 0x09 "TSOP", // 0x0A "Row of chips", // 0x0B "RIMM", // 0x0C "SODIMM", // 0x0D "SRIMM", // 0x0E "FBDIMM", // 0x0F "Die", // 0x10 }; if (data->FormFactor > 0 && data->FormFactor < ARRAY_SIZE(formFactorNames)) ffStrbufSetS(&device->formFactor, formFactorNames[data->FormFactor]); else ffStrbufSetF(&device->formFactor, "Unknown (%d)", (int) data->FormFactor); const char* memoryTypeNames[] = { NULL, // 0x00 (Placeholder for indexing) "Other", // 0x01 "Unknown", // 0x02 "DRAM", // 0x03 "EDRAM", // 0x04 "VRAM", // 0x05 "SRAM", // 0x06 "RAM", // 0x07 "ROM", // 0x08 "FLASH", // 0x09 "EEPROM", // 0x0A "FEPROM", // 0x0B "EPROM", // 0x0C "CDRAM", // 0x0D "3DRAM", // 0x0E "SDRAM", // 0x0F "SGRAM", // 0x10 "RDRAM", // 0x11 "DDR", // 0x12 "DDR2", // 0x13 "DDR2 FB-DIMM", // 0x14 "Reserved", // 0x15 "Reserved", // 0x16 "Reserved", // 0x17 "DDR3", // 0x18 "FBD2", // 0x19 "DDR4", // 0x1A "LPDDR", // 0x1B "LPDDR2", // 0x1C "LPDDR3", // 0x1D "LPDDR4", // 0x1E "Logical non-volatile device", // 0x1F "HBM", // 0x20 "HBM2", // 0x21 "DDR5", // 0x22 "LPDDR5", // 0x23 "HBM3", // 0x24 }; if (!installed) ffStrbufSetStatic(&device->type, "Empty"); else if (data->MemoryType > 0 && data->MemoryType < ARRAY_SIZE(memoryTypeNames)) ffStrbufSetStatic(&device->type, memoryTypeNames[data->MemoryType]); else ffStrbufSetF(&device->type, "Unknown (%d)", (int) data->MemoryType); if (installed && data->Header.Length > offsetof(FFSmbiosMemoryDevice, Speed)) // 2.3+ { if (data->Speed) device->maxSpeed = data->Speed == 0xFFFF ? data->ExtendedSpeed : data->Speed; ffStrbufSetStatic(&device->vendor, ffSmbiosLocateString(strings, data->Manufacturer)); ffCleanUpSmbiosValue(&device->vendor); FFPhysicalMemoryUpdateVendorString(device); ffStrbufSetStatic(&device->serial, ffSmbiosLocateString(strings, data->SerialNumber)); ffCleanUpSmbiosValue(&device->serial); ffStrbufSetStatic(&device->partNumber, ffSmbiosLocateString(strings, data->PartNumber)); ffCleanUpSmbiosValue(&device->partNumber); } if (installed && data->Header.Length > offsetof(FFSmbiosMemoryDevice, ConfiguredMemorySpeed)) // 2.7+ { if (data->ConfiguredMemorySpeed) device->runningSpeed = data->ConfiguredMemorySpeed == 0xFFFF ? data->ExtendedConfiguredSpeed : data->ConfiguredMemorySpeed; } } return NULL; } ================================================ FILE: src/detection/physicalmemory/physicalmemory_nosupport.c ================================================ #include "physicalmemory.h" const char* ffDetectPhysicalMemory(FF_MAYBE_UNUSED FFlist* result) { return "Not supported on this platform"; } ================================================ FILE: src/detection/poweradapter/poweradapter.h ================================================ #pragma once #include "fastfetch.h" #include "modules/poweradapter/option.h" typedef struct FFPowerAdapterResult { FFstrbuf description; FFstrbuf name; FFstrbuf modelName; FFstrbuf manufacturer; FFstrbuf serial; int watts; } FFPowerAdapterResult; const char* ffDetectPowerAdapter(FFlist* results); ================================================ FILE: src/detection/poweradapter/poweradapter_apple.c ================================================ #include "fastfetch.h" #include "poweradapter.h" #include "common/apple/cf_helpers.h" #include #include const char* ffDetectPowerAdapter(FFlist* results) { FF_CFTYPE_AUTO_RELEASE CFDictionaryRef details = IOPSCopyExternalPowerAdapterDetails(); if (details && CFDictionaryContainsKey(details, CFSTR(kIOPSPowerAdapterWattsKey))) { FFPowerAdapterResult* adapter = ffListAdd(results); ffStrbufInit(&adapter->name); ffStrbufInit(&adapter->description); ffStrbufInit(&adapter->manufacturer); ffStrbufInit(&adapter->modelName); ffStrbufInit(&adapter->serial); adapter->watts = 0; ffCfDictGetString(details, CFSTR(kIOPSNameKey), &adapter->name); if (ffCfDictGetString(details, CFSTR("Model"), &adapter->modelName) != NULL) { int adapterId; if (ffCfDictGetInt(details, CFSTR(kIOPSPowerAdapterIDKey), &adapterId) == 0) ffStrbufSetF(&adapter->modelName, "%d", adapterId); } ffCfDictGetString(details, CFSTR("Manufacturer"), &adapter->manufacturer); ffCfDictGetString(details, CFSTR("Description"), &adapter->description); if (ffCfDictGetString(details, CFSTR("SerialString"), &adapter->serial) != NULL) { int serialNumber; if (ffCfDictGetInt(details, CFSTR(kIOPSPowerAdapterSerialNumberKey), &serialNumber) == 0) ffStrbufSetF(&adapter->serial, "%X", serialNumber); } ffCfDictGetInt(details, CFSTR(kIOPSPowerAdapterWattsKey), &adapter->watts); } return NULL; } ================================================ FILE: src/detection/poweradapter/poweradapter_linux.c ================================================ #include "poweradapter.h" #include "common/io.h" #include "common/stringUtils.h" #include #include #include static void parsePowerAdapter(int dfd, FF_MAYBE_UNUSED const char* id, FFlist* results) { FF_STRBUF_AUTO_DESTROY tmpBuffer = ffStrbufCreate(); //type must exist and be "Mains" if (ffReadFileBufferRelative(dfd, "type", &tmpBuffer)) ffStrbufTrimRightSpace(&tmpBuffer); if(!ffStrbufIgnCaseEqualS(&tmpBuffer, "Mains")) return; //scope may not exist or must not be "Device" (?) if (ffReadFileBufferRelative(dfd, "scope", &tmpBuffer)) ffStrbufTrimRightSpace(&tmpBuffer); if(ffStrbufIgnCaseEqualS(&tmpBuffer, "Device")) return; char online = '\0'; ffReadFileDataRelative(dfd, "online", sizeof(online), &online); if (online != '1') return; //input_power_limit must exist and be not empty if (!ffReadFileBufferRelative(dfd, "input_power_limit", &tmpBuffer) || tmpBuffer.length == 0) return; FFPowerAdapterResult* result = ffListAdd(results); ffStrbufInit(&result->name); ffStrbufInit(&result->description); result->watts = (int) (ffStrbufToDouble(&tmpBuffer, 0) / 1e6 + 0.5); ffStrbufInit(&result->manufacturer); ffStrbufInit(&result->modelName); ffStrbufInit(&result->serial); if (ffReadFileBufferRelative(dfd, "manufacturer", &result->manufacturer)) ffStrbufTrimRightSpace(&result->manufacturer); else if (ffStrEquals(id, "macsmc-ac")) // asahi ffStrbufSetStatic(&result->manufacturer, "Apple Inc."); if (ffReadFileBufferRelative(dfd, "model_name", &result->modelName)) ffStrbufTrimRightSpace(&result->modelName); if (ffReadFileBufferRelative(dfd, "serial_number", &result->serial)) ffStrbufTrimRightSpace(&result->serial); } const char* ffDetectPowerAdapter(FFlist* results) { FF_AUTO_CLOSE_DIR DIR* dirp = opendir("/sys/class/power_supply/"); if(dirp == NULL) return "opendir(\"/sys/class/power_supply/\") == NULL"; struct dirent* entry; while((entry = readdir(dirp)) != NULL) { if(entry->d_name[0] == '.') continue; FF_AUTO_CLOSE_FD int dfd = openat(dirfd(dirp), entry->d_name, O_RDONLY | O_CLOEXEC); if (dfd > 0) parsePowerAdapter(dfd, entry->d_name, results); } return NULL; } ================================================ FILE: src/detection/poweradapter/poweradapter_nosupport.c ================================================ #include "poweradapter.h" const char* ffDetectPowerAdapter(FF_MAYBE_UNUSED FFlist* results) { return "Not supported on this platform"; } ================================================ FILE: src/detection/processes/processes.h ================================================ #pragma once #include "fastfetch.h" const char* ffDetectProcesses(uint32_t* result); ================================================ FILE: src/detection/processes/processes_bsd.c ================================================ #include "processes.h" #include #ifdef __FreeBSD__ #include #include #endif #ifndef KERN_PROC_PROC #define KERN_PROC_PROC KERN_PROC_ALL // Apple #endif const char* ffDetectProcesses(uint32_t* result) { int request[] = {CTL_KERN, KERN_PROC, KERN_PROC_PROC}; size_t length; if(sysctl(request, ARRAY_SIZE(request), NULL, &length, NULL, 0) != 0) return "sysctl({CTL_KERN, KERN_PROC, KERN_PROC_PROC}) failed"; *result = (uint32_t)(length / sizeof(struct kinfo_proc)); return NULL; } ================================================ FILE: src/detection/processes/processes_haiku.c ================================================ #include "processes.h" #include const char* ffDetectProcesses(uint32_t* result) { system_info info; if (get_system_info(&info) != B_OK) return "Error getting system info"; *result = info.used_teams; return NULL; } ================================================ FILE: src/detection/processes/processes_linux.c ================================================ #include "processes.h" #include "common/io.h" #include "common/stringUtils.h" const char* ffDetectProcesses(uint32_t* result) { FF_AUTO_CLOSE_DIR DIR* dir = opendir("/proc"); if(dir == NULL) return "opendir(\"/proc\") failed"; uint32_t num = 0; struct dirent* entry; while ((entry = readdir(dir)) != NULL) { if ( #ifdef _DIRENT_HAVE_D_TYPE (entry->d_type == DT_DIR || entry->d_type == DT_UNKNOWN) && #endif ffCharIsDigit(entry->d_name[0])) ++num; } *result = num; return NULL; } ================================================ FILE: src/detection/processes/processes_nbsd.c ================================================ #include "processes.h" #include const char* ffDetectProcesses(uint32_t* result) { int request[] = {CTL_KERN, KERN_PROC2, KERN_PROC_ALL, -1, sizeof(struct kinfo_proc2), 0}; size_t length = 0; if(sysctl(request, ARRAY_SIZE(request), NULL, &length, NULL, 0) != 0) return "sysctl({CTL_KERN, KERN_PROC2, KERN_PROC_ALL}) failed"; *result = (uint32_t)(length / sizeof(struct kinfo_proc2)); return NULL; } ================================================ FILE: src/detection/processes/processes_nosupport.c ================================================ #include "processes.h" const char* ffDetectProcesses(uint32_t* result) { return "Not supported on this platform"; } ================================================ FILE: src/detection/processes/processes_obsd.c ================================================ #include "processes.h" #include #include #include const char* ffDetectProcesses(uint32_t* result) { kvm_t* kd = kvm_open(NULL, NULL, NULL, KVM_NO_FILES, NULL); const void* ret = kvm_getprocs(kd, KERN_PROC_ALL, 0, 1, result); kvm_close(kd); if (!ret) return "kvm_getprocs() failed"; return NULL; } ================================================ FILE: src/detection/processes/processes_windows.c ================================================ #include "processes.h" #include "common/mallocHelper.h" #include #include const char* ffDetectProcesses(uint32_t* result) { SYSTEM_PROCESS_INFORMATION* FF_AUTO_FREE pstart = NULL; // Multiple attempts in case processes change while // we are in the middle of querying them. ULONG size = 0; for (int attempts = 0;; ++attempts) { if (size) { pstart = (SYSTEM_PROCESS_INFORMATION*)realloc(pstart, size); assert(pstart); } NTSTATUS status = NtQuerySystemInformation(SystemProcessInformation, pstart, size, &size); if(NT_SUCCESS(status)) break; else if(status == STATUS_INFO_LENGTH_MISMATCH && attempts < 4) size += sizeof(SYSTEM_PROCESS_INFORMATION) * 5; else return "NtQuerySystemInformation(SystemProcessInformation) failed"; } *result = 1; //Init with 1 because we test for ptr->NextEntryOffset for (SYSTEM_PROCESS_INFORMATION* ptr = pstart; ptr->NextEntryOffset; ptr = (SYSTEM_PROCESS_INFORMATION*)((uint8_t*)ptr + ptr->NextEntryOffset)) ++*result; return NULL; } ================================================ FILE: src/detection/publicip/publicip.c ================================================ #include "publicip.h" #include "common/networking.h" #define FF_UNITIALIZED ((const char*)(uintptr_t) -1) static FFNetworkingState states[2]; static const char* statuses[2] = { FF_UNITIALIZED, FF_UNITIALIZED }; void ffPreparePublicIp(FFPublicIPOptions* options) { FFNetworkingState* state = &states[options->ipv6]; const char** status = &statuses[options->ipv6]; if (*status != FF_UNITIALIZED) { fputs("Error: PublicIp module can only be used once due to internal limitations\n", stderr); exit(1); } state->timeout = options->timeout; state->ipv6 = options->ipv6; if (options->url.length == 0) { state->compression = true; state->tfo = true; *status = ffNetworkingSendHttpRequest(state, options->ipv6 ? "v6.ipinfo.io" : "ipinfo.io", "/json", NULL); } else { FF_STRBUF_AUTO_DESTROY host = ffStrbufCreateCopy(&options->url); uint32_t hostStartIndex = ffStrbufFirstIndexS(&host, "://"); if (hostStartIndex < host.length) { if (hostStartIndex != 4 || !ffStrbufStartsWithIgnCaseS(&host, "http")) { fputs("Error: only http: protocol is supported. Use `Command` module with `curl` if needed\n", stderr); exit(1); } ffStrbufSubstrAfter(&host, hostStartIndex + (uint32_t) (strlen("://") - 1)); } uint32_t pathStartIndex = ffStrbufFirstIndexC(&host, '/'); FF_STRBUF_AUTO_DESTROY path = ffStrbufCreate(); if(pathStartIndex != host.length) { ffStrbufAppendNS(&path, pathStartIndex, host.chars + (host.length - pathStartIndex)); host.length = pathStartIndex; host.chars[pathStartIndex] = '\0'; } *status = ffNetworkingSendHttpRequest(state, host.chars, path.length == 0 ? "/" : path.chars, NULL); } } static inline void wrapYyjsonFree(yyjson_doc** doc) { assert(doc); if (*doc) yyjson_doc_free(*doc); } const char* ffDetectPublicIp(FFPublicIPOptions* options, FFPublicIpResult* result) { FFNetworkingState* state = &states[options->ipv6]; const char** status = &statuses[options->ipv6]; if (*status == FF_UNITIALIZED) ffPreparePublicIp(options); if (*status != NULL) return *status; FF_STRBUF_AUTO_DESTROY response = ffStrbufCreateA(4096); const char* error = ffNetworkingRecvHttpResponse(state, &response); *state = (FFNetworkingState){}; *status = FF_UNITIALIZED; if (error == NULL) ffStrbufSubstrAfterFirstS(&response, "\r\n\r\n"); else return error; if (response.length == 0) return "Empty server response received"; if (options->url.length == 0) { yyjson_doc* __attribute__((__cleanup__(wrapYyjsonFree))) doc = yyjson_read_opts(response.chars, response.length, 0, NULL, NULL); if (doc) { yyjson_val* root = yyjson_doc_get_root(doc); ffStrbufAppendJsonVal(&result->ip, yyjson_obj_get(root, "ip")); ffStrbufDestroy(&result->location); ffStrbufInitF(&result->location, "%s, %s", yyjson_get_str(yyjson_obj_get(root, "city")), yyjson_get_str(yyjson_obj_get(root, "country"))); return NULL; } } ffStrbufDestroy(&result->ip); ffStrbufInitMove(&result->ip, &response); ffStrbufTrimRightSpace(&result->ip); return NULL; } ================================================ FILE: src/detection/publicip/publicip.h ================================================ #pragma once #include "fastfetch.h" #include "modules/publicip/option.h" typedef struct FFPublicIpResult { FFstrbuf ip; FFstrbuf location; } FFPublicIpResult; void ffPreparePublicIp(FFPublicIPOptions* options); const char* ffDetectPublicIp(FFPublicIPOptions* options, FFPublicIpResult* result); ================================================ FILE: src/detection/sound/audio_oss_sunos.h ================================================ /* * CDDL HEADER START * * The contents of this file are subject to the terms of the * Common Development and Distribution License (the "License"). * You may not use this file except in compliance with the License. * * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE * or http://www.opensolaris.org/os/licensing. * See the License for the specific language governing permissions * and limitations under the License. * * When distributing Covered Code, include this CDDL HEADER in each * file and include the License file at usr/src/OPENSOLARIS.LICENSE. * If applicable, add the following below this CDDL HEADER, with the * fields enclosed by brackets "[]" replaced with your own identifying * information: Portions Copyright [yyyy] [name of copyright owner] * * CDDL HEADER END */ /* * Copyright (C) 4Front Technologies 1996-2008. * * Copyright 2009 Sun Microsystems, Inc. All rights reserved. * Use is subject to license terms. */ #ifndef _SYS_AUDIO_OSS_H #define _SYS_AUDIO_OSS_H #include #include /* * These are the ioctl calls for all Solaris /dev/dsp and /dev/mixer audio * devices. * * Note that the contents of this file include definitions which exist * primarily for compatibility. Many of the defines here are not * actually implemented, but exist solely to facilitate compilation of * programs from other operating systems. Other definitions here may * not be fully supported or may otherwise be obsolete. There are many * things in this file which should not be used on SunOS. * * Please read the documentation to determine which portions of the * API are fully supported and recommended for use in new * applications. */ #ifdef __cplusplus extern "C" { #endif /* * Buffer status queries. * SNDCTL_DSP_GETOSPACE and SNDCTL_DSP_GETISPACE */ typedef struct audio_buf_info { int fragments; /* # of available fragments */ int fragstotal; /* Total # of fragments allocated */ int fragsize; /* Size of a fragment in bytes */ int bytes; /* Available space in bytes */ /* Note! 'bytes' could be more than fragments*fragsize */ } audio_buf_info; /* * Sync groups for audio devices. * SNDCTL_DSP_SYNCGROUP and SNDCTL_DSP_SYNCSTART */ typedef struct oss_syncgroup { int id; int mode; int filler[16]; } oss_syncgroup; /* * SNDCTL_DSP_GETERROR */ typedef struct audio_errinfo { int play_underruns; int rec_overruns; unsigned int play_ptradjust; unsigned int rec_ptradjust; int play_errorcount; int rec_errorcount; int play_lasterror; int rec_lasterror; int play_errorparm; int rec_errorparm; int filler[16]; } audio_errinfo; /* * SNDCTL_DSP_GETIPTR and SNDCTL_DSP_GETOPTR */ typedef struct count_info { unsigned int bytes; /* Total # of bytes processed */ int blocks; /* # of fragment transitions since last time */ int ptr; /* Current DMA pointer value */ } count_info; /* * SNDCTL_DSP_CURENT_IPTR and SNDCTL_DSP_CURRENT_OPTR */ typedef struct { long long samples; /* Total # of samples */ int fifo_samples; /* Samples in device FIFO */ int filler[32]; /* For future use */ } oss_count_t; /* * SNDCTL_DSP_GET_RECSRC_NAMES and SNDCTL_DSP_GET_PLAYTGT_NAMES */ #define OSS_ENUM_MAXVALUE 255 typedef struct oss_mixer_enuminfo { int dev; int ctrl; int nvalues; int version; short strindex[OSS_ENUM_MAXVALUE]; char strings[3000]; } oss_mixer_enuminfo; /* * Digital interface (S/PDIF) control interface * SNDCTL_DSP_READCTL and SNDCTL_DSP_WRITECTL */ typedef struct oss_digital_control { unsigned int caps; #define DIG_CBITIN_NONE 0x00000000 #define DIG_CBITIN_LIMITED 0x00000001 #define DIG_CBITIN_DATA 0x00000002 #define DIG_CBITIN_BYTE0 0x00000004 #define DIG_CBITIN_FULL 0x00000008 #define DIG_CBITIN_MASK 0x0000000f #define DIG_CBITOUT_NONE 0x00000000 #define DIG_CBITOUT_LIMITED 0x00000010 #define DIG_CBITOUT_BYTE0 0x00000020 #define DIG_CBITOUT_FULL 0x00000040 #define DIG_CBITOUT_DATA 0x00000080 #define DIG_CBITOUT_MASK 0x000000f0 #define DIG_UBITIN 0x00000100 #define DIG_UBITOUT 0x00000200 #define DIG_VBITOUT 0x00000400 #define DIG_OUTRATE 0x00000800 #define DIG_INRATE 0x00001000 #define DIG_INBITS 0x00002000 #define DIG_OUTBITS 0x00004000 #define DIG_EXACT 0x00010000 #define DIG_PRO 0x00020000 #define DIG_CONSUMER 0x00040000 #define DIG_PASSTHROUGH 0x00080000 #define DIG_OUTSEL 0x00100000 unsigned int valid; #define VAL_CBITIN 0x00000001 #define VAL_UBITIN 0x00000002 #define VAL_CBITOUT 0x00000004 #define VAL_UBITOUT 0x00000008 #define VAL_ISTATUS 0x00000010 #define VAL_IRATE 0x00000020 #define VAL_ORATE 0x00000040 #define VAL_INBITS 0x00000080 #define VAL_OUTBITS 0x00000100 #define VAL_REQUEST 0x00000200 #define VAL_OUTSEL 0x00000400 #define VAL_OUTMASK (VAL_CBITOUT|VAL_UBITOUT|VAL_ORATE|VAL_OUTBITS|VAL_OUTSEL) unsigned int request; unsigned int param; #define SPD_RQ_PASSTHROUGH 1 unsigned char cbitin[24]; unsigned char ubitin[24]; unsigned char cbitout[24]; unsigned char ubitout[24]; unsigned int outsel; #define OUTSEL_DIGITAL 1 #define OUTSEL_ANALOG 2 #define OUTSEL_BOTH (OUTSEL_DIGITAL|OUTSEL_ANALOG) int in_data; /* Audio/data if autodetectable by receiver */ #define IND_UNKNOWN 0 #define IND_AUDIO 1 #define IND_DATA 2 int in_locked; /* Receiver locked */ #define LOCK_NOT_INDICATED 0 #define LOCK_UNLOCKED 1 #define LOCK_LOCKED 2 int in_quality; /* Input signal quality */ #define IN_QUAL_NOT_INDICATED 0 #define IN_QUAL_POOR 1 #define IN_QUAL_GOOD 2 int in_vbit; int out_vbit; /* V bits */ #define VBIT_NOT_INDICATED 0 #define VBIT_OFF 1 #define VBIT_ON 2 unsigned int in_errors; /* Various input error conditions */ #define INERR_CRC 0x0001 #define INERR_QCODE_CRC 0x0002 #define INERR_PARITY 0x0004 #define INERR_BIPHASE 0x0008 int srate_in; int srate_out; int bits_in; int bits_out; int filler[32]; } oss_digital_control; /* * The "new" mixer API. * * This improved mixer API makes it possible to access every possible feature * of every possible device. However you should read the mixer programming * section of the OSS API Developer's Manual. There is no chance that you * could use this interface correctly just by examining this header. */ #define OSS_VERSION 0x040003 #define SOUND_VERSION OSS_VERSION typedef struct oss_sysinfo { char product[32]; /* E.g. SunOS Audio */ char version[32]; /* E.g. 4.0a */ int versionnum; /* See OSS_GETVERSION */ char options[128]; /* NOT SUPPORTED */ int numaudios; /* # of audio/dsp devices */ int openedaudio[8]; /* Mask of audio devices are busy */ int numsynths; /* NOT SUPPORTED, always 0 */ int nummidis; /* NOT SUPPORTED, always 0 */ int numtimers; /* NOT SUPPORTED, always 0 */ int nummixers; /* # of mixer devices */ int openedmidi[8]; /* Mask of midi devices are busy */ int numcards; /* Number of sound cards in the system */ int numaudioengines; /* Number of audio engines in the system */ char license[16]; /* E.g. "GPL" or "CDDL" */ char revision_info[256]; /* For internal use */ int filler[172]; /* For future expansion */ } oss_sysinfo; typedef struct oss_mixext { int dev; /* Mixer device number */ int ctrl; /* Extension number */ int type; /* Entry type */ #define MIXT_DEVROOT 0 /* Device root entry */ #define MIXT_GROUP 1 /* Controller group */ #define MIXT_ONOFF 2 /* OFF (0) or ON (1) */ #define MIXT_ENUM 3 /* Enumerated (0 to maxvalue) */ #define MIXT_MONOSLIDER 4 /* Mono slider (0 to 255) */ #define MIXT_STEREOSLIDER 5 /* Stereo slider (dual 0 to 255) */ #define MIXT_MESSAGE 6 /* (Readable) textual message */ #define MIXT_MONOVU 7 /* VU meter value (mono) */ #define MIXT_STEREOVU 8 /* VU meter value (stereo) */ #define MIXT_MONOPEAK 9 /* VU meter peak value (mono) */ #define MIXT_STEREOPEAK 10 /* VU meter peak value (stereo) */ #define MIXT_RADIOGROUP 11 /* Radio button group */ #define MIXT_MARKER 12 /* Separator between entries */ #define MIXT_VALUE 13 /* Decimal value entry */ #define MIXT_HEXVALUE 14 /* Hexadecimal value entry */ #define MIXT_MONODB 15 /* OBSOLETE */ #define MIXT_STEREODB 16 /* OBSOLETE */ #define MIXT_SLIDER 17 /* Slider (mono, 31 bit int range) */ #define MIXT_3D 18 #define MIXT_MONOSLIDER16 19 /* Mono slider (0-32767) */ #define MIXT_STEREOSLIDER16 20 /* Stereo slider (dual 0-32767) */ #define MIXT_MUTE 21 /* Mute=1, unmute=0 */ /* Possible value range (minvalue to maxvalue) */ /* Note that maxvalue may also be smaller than minvalue */ int maxvalue; int minvalue; int flags; #define MIXF_READABLE 0x00000001 /* Has readable value */ #define MIXF_WRITEABLE 0x00000002 /* Has writeable value */ #define MIXF_POLL 0x00000004 /* May change itself */ #define MIXF_HZ 0x00000008 /* Hertz scale */ #define MIXF_STRING 0x00000010 /* Use dynamic extensions for value */ #define MIXF_DYNAMIC 0x00000010 /* Supports dynamic extensions */ #define MIXF_OKFAIL 0x00000020 /* Interpret value as 1=OK, 0=FAIL */ #define MIXF_FLAT 0x00000040 /* NOT SUPPORTED */ #define MIXF_LEGACY 0x00000080 /* NOT SUPPORTED */ #define MIXF_CENTIBEL 0x00000100 /* Centibel (0.1 dB) step size */ #define MIXF_DECIBEL 0x00000200 /* Step size of 1 dB */ #define MIXF_MAINVOL 0x00000400 /* Main volume control */ #define MIXF_PCMVOL 0x00000800 /* PCM output volume control */ #define MIXF_RECVOL 0x00001000 /* PCM recording volume control */ #define MIXF_MONVOL 0x00002000 /* Input->output monitor volume */ #define MIXF_WIDE 0x00004000 /* NOT SUPPORTED */ #define MIXF_DESCR 0x00008000 /* NOT SUPPORTED */ #define MIXF_DISABLE 0x00010000 /* Control has been disabled */ char id[16]; /* Mnemonic ID (internal use) */ int parent; /* Entry# of parent (-1 if root) */ int dummy; /* NOT SUPPORTED */ int timestamp; char data[64]; /* Misc data (entry type dependent) */ unsigned char enum_present[32]; /* Mask of allowed enum values */ int control_no; /* NOT SUPPORTED, always -1 */ unsigned int desc; /* Scope flags, etc */ #define MIXEXT_SCOPE_MASK 0x0000003f #define MIXEXT_SCOPE_OTHER 0x00000000 #define MIXEXT_SCOPE_INPUT 0x00000001 #define MIXEXT_SCOPE_OUTPUT 0x00000002 #define MIXEXT_SCOPE_MONITOR 0x00000003 #define MIXEXT_SCOPE_RECSWITCH 0x00000004 char extname[32]; int update_counter; #ifdef _KERNEL int filler[6]; int enumbit; #else int filler[7]; #endif } oss_mixext; typedef struct oss_mixext_root { char id[16]; char name[48]; } oss_mixext_root; typedef struct oss_mixer_value { int dev; int ctrl; int value; int flags; /* Reserved for future use. Initialize to 0 */ int timestamp; /* Must be set to oss_mixext.timestamp */ int filler[8]; /* Reserved for future use. Initialize to 0 */ } oss_mixer_value; #define OSS_LONGNAME_SIZE 64 #define OSS_LABEL_SIZE 16 #define OSS_DEVNODE_SIZE 32 typedef char oss_longname_t[OSS_LONGNAME_SIZE]; typedef char oss_label_t[OSS_LABEL_SIZE]; typedef char oss_devnode_t[OSS_DEVNODE_SIZE]; typedef struct oss_audioinfo { int dev; /* Audio device number */ char name[64]; int busy; /* 0, OPEN_READ, OPEN_WRITE, OPEN_READWRITE */ int pid; /* Process ID, not used in SunOS */ int caps; /* PCM_CAP_INPUT, PCM_CAP_OUTPUT */ int iformats; /* Supported input formats */ int oformats; /* Supported output formats */ int magic; /* Internal use only */ char cmd[64]; /* Command using the device (if known) */ int card_number; int port_number; int mixer_dev; int legacy_device; /* Obsolete field. Replaced by devnode */ int enabled; /* 1=enabled, 0=device not ready */ int flags; /* internal use only - no practical meaning */ int min_rate; /* Minimum sample rate */ int max_rate; /* Maximum sample rate */ int min_channels; /* Minimum number of channels */ int max_channels; /* Maximum number of channels */ int binding; /* DSP_BIND_FRONT, etc. 0 means undefined */ int rate_source; char handle[32]; #define OSS_MAX_SAMPLE_RATES 20 /* Cannot be changed */ unsigned int nrates; /* Array of supported sample rates */ unsigned int rates[OSS_MAX_SAMPLE_RATES]; oss_longname_t song_name; /* Song name (if given) */ oss_label_t label; /* Device label (if given) */ int latency; /* In usecs, -1=unknown */ oss_devnode_t devnode; /* Device special file name (absolute path) */ int next_play_engine; int next_rec_engine; int filler[184]; } oss_audioinfo; typedef struct oss_mixerinfo { int dev; char id[16]; char name[32]; int modify_counter; int card_number; int port_number; char handle[32]; int magic; /* Reserved */ int enabled; /* Reserved */ int caps; #define MIXER_CAP_VIRTUAL 0x00000001 #define MIXER_CAP_LAYOUT_B 0x00000002 /* For internal use only */ #define MIXER_CAP_NARROW 0x00000004 /* Conserve horiz space */ int flags; /* Reserved */ int nrext; /* * The priority field can be used to select the default * (motherboard) mixer device. The mixer with the highest * priority is the most preferred one. -2 or less means that * this device cannot be used as the default mixer. */ int priority; oss_devnode_t devnode; /* Device special file name (absolute path) */ int legacy_device; int filler[245]; /* Reserved */ } oss_mixerinfo; typedef struct oss_card_info { int card; char shortname[16]; char longname[128]; int flags; char hw_info[400]; int intr_count; int ack_count; int filler[154]; } oss_card_info; typedef struct mixer_info { /* OBSOLETE */ char id[16]; char name[32]; int modify_counter; int card_number; int port_number; char handle[32]; } mixer_info; #define MAX_PEAK_CHANNELS 128 typedef unsigned short oss_peaks_t[MAX_PEAK_CHANNELS]; /* For use with SNDCTL_DSP_GET_CHNORDER */ #define CHID_UNDEF 0 #define CHID_L 1 #define CHID_R 2 #define CHID_C 3 #define CHID_LFE 4 #define CHID_LS 5 #define CHID_RS 6 #define CHID_LR 7 #define CHID_RR 8 #define CHNORDER_UNDEF 0x0000000000000000ULL #define CHNORDER_NORMAL 0x0000000087654321ULL #define OSSIOCPARM_MASK 0x1fff /* parameters must be < 8192 bytes */ #define OSSIOC_VOID 0x00000000 /* no parameters */ #define OSSIOC_OUT 0x20000000 /* copy out parameters */ #define OSSIOC_IN 0x40000000 /* copy in parameters */ #define OSSIOC_INOUT (OSSIOC_IN|OSSIOC_OUT) #define OSSIOC_SZ(t) ((sizeof (t) & OSSIOCPARM_MASK) << 16) #define OSSIOC_GETSZ(x) (((x) >> 16) & OSSIOCPARM_MASK) #define __OSSIO(x, y) ((int)(OSSIOC_VOID|(x<<8)|y)) #define __OSSIOR(x, y, t) ((int)(OSSIOC_OUT|OSSIOC_SZ(t)|(x<<8)|y)) #define __OSSIOW(x, y, t) ((int)(OSSIOC_IN|OSSIOC_SZ(t)|(x<<8)|y)) #define __OSSIOWR(x, y, t) ((int)(OSSIOC_INOUT|OSSIOC_SZ(t)|(x<<8)|y)) #define SNDCTL_SYSINFO __OSSIOR('X', 1, oss_sysinfo) #define OSS_SYSINFO SNDCTL_SYSINFO /* Old name */ #define SNDCTL_MIX_NRMIX __OSSIOR('X', 2, int) #define SNDCTL_MIX_NREXT __OSSIOWR('X', 3, int) #define SNDCTL_MIX_EXTINFO __OSSIOWR('X', 4, oss_mixext) #define SNDCTL_MIX_READ __OSSIOWR('X', 5, oss_mixer_value) #define SNDCTL_MIX_WRITE __OSSIOWR('X', 6, oss_mixer_value) #define SNDCTL_AUDIOINFO __OSSIOWR('X', 7, oss_audioinfo) #define SNDCTL_MIX_ENUMINFO __OSSIOWR('X', 8, oss_mixer_enuminfo) #define SNDCTL_MIDIINFO __OSSIO('X', 9) #define SNDCTL_MIXERINFO __OSSIOWR('X', 10, oss_mixerinfo) #define SNDCTL_CARDINFO __OSSIOWR('X', 11, oss_card_info) #define SNDCTL_ENGINEINFO __OSSIOWR('X', 12, oss_audioinfo) #define SNDCTL_AUDIOINFO_EX __OSSIOWR('X', 13, oss_audioinfo) #define SNDCTL_MIX_DESCRIPTION __OSSIOWR('X', 14, oss_mixer_enuminfo) /* ioctl codes 'X', 200-255 are reserved for internal use */ /* * Few more "globally" available ioctl calls. */ #define SNDCTL_SETSONG __OSSIOW('Y', 2, oss_longname_t) #define SNDCTL_GETSONG __OSSIOR('Y', 2, oss_longname_t) #define SNDCTL_SETNAME __OSSIOW('Y', 3, oss_longname_t) #define SNDCTL_SETLABEL __OSSIOW('Y', 4, oss_label_t) #define SNDCTL_GETLABEL __OSSIOR('Y', 4, oss_label_t) /* * IOCTL commands for /dev/dsp */ #define SNDCTL_DSP_HALT __OSSIO('P', 0) #define SNDCTL_DSP_RESET SNDCTL_DSP_HALT /* Old name */ #define SNDCTL_DSP_SYNC __OSSIO('P', 1) #define SNDCTL_DSP_SPEED __OSSIOWR('P', 2, int) #define SNDCTL_DSP_STEREO __OSSIOWR('P', 3, int) /* OBSOLETE */ #define SNDCTL_DSP_GETBLKSIZE __OSSIOWR('P', 4, int) #define SNDCTL_DSP_SAMPLESIZE SNDCTL_DSP_SETFMT #define SNDCTL_DSP_CHANNELS __OSSIOWR('P', 6, int) #define SNDCTL_DSP_POST __OSSIO('P', 8) #define SNDCTL_DSP_SUBDIVIDE __OSSIOWR('P', 9, int) #define SNDCTL_DSP_SETFRAGMENT __OSSIOWR('P', 10, int) #define SNDCTL_DSP_GETFMTS __OSSIOR('P', 11, int) /* Returns a mask */ #define SNDCTL_DSP_SETFMT __OSSIOWR('P', 5, int) /* Selects ONE fmt */ #define SNDCTL_DSP_GETOSPACE __OSSIOR('P', 12, audio_buf_info) #define SNDCTL_DSP_GETISPACE __OSSIOR('P', 13, audio_buf_info) #define SNDCTL_DSP_NONBLOCK __OSSIO('P', 14) /* Obsolete */ #define SNDCTL_DSP_GETCAPS __OSSIOR('P', 15, int) #define SNDCTL_DSP_GETTRIGGER __OSSIOR('P', 16, int) #define SNDCTL_DSP_SETTRIGGER __OSSIOW('P', 16, int) #define SNDCTL_DSP_GETIPTR __OSSIOR('P', 17, count_info) #define SNDCTL_DSP_GETOPTR __OSSIOR('P', 18, count_info) #define SNDCTL_DSP_SETSYNCRO __OSSIO('P', 21) #define SNDCTL_DSP_SETDUPLEX __OSSIO('P', 22) #define SNDCTL_DSP_PROFILE __OSSIOW('P', 23, int) /* OBSOLETE */ #define APF_NORMAL 0 /* Normal applications */ #define APF_NETWORK 1 /* Underruns caused by "external" delay */ #define APF_CPUINTENS 2 /* Underruns caused by "overheating" the CPU */ #define SNDCTL_DSP_GETODELAY __OSSIOR('P', 23, int) #define SNDCTL_DSP_GETPLAYVOL __OSSIOR('P', 24, int) #define SNDCTL_DSP_SETPLAYVOL __OSSIOWR('P', 24, int) #define SNDCTL_DSP_GETERROR __OSSIOR('P', 25, audio_errinfo) #define SNDCTL_DSP_READCTL __OSSIOWR('P', 26, oss_digital_control) #define SNDCTL_DSP_WRITECTL __OSSIOWR('P', 27, oss_digital_control) #define SNDCTL_DSP_SYNCGROUP __OSSIOWR('P', 28, oss_syncgroup) #define SNDCTL_DSP_SYNCSTART __OSSIOW('P', 29, int) #define SNDCTL_DSP_COOKEDMODE __OSSIOW('P', 30, int) #define SNDCTL_DSP_SILENCE __OSSIO('P', 31) #define SNDCTL_DSP_SKIP __OSSIO('P', 32) #define SNDCTL_DSP_HALT_INPUT __OSSIO('P', 33) #define SNDCTL_DSP_RESET_INPUT SNDCTL_DSP_HALT_INPUT /* Old name */ #define SNDCTL_DSP_HALT_OUTPUT __OSSIO('P', 34) #define SNDCTL_DSP_RESET_OUTPUT SNDCTL_DSP_HALT_OUTPUT /* Old name */ #define SNDCTL_DSP_LOW_WATER __OSSIOW('P', 34, int) #define SNDCTL_DSP_CURRENT_IPTR __OSSIOR('P', 35, oss_count_t) #define SNDCTL_DSP_CURRENT_OPTR __OSSIOR('P', 36, oss_count_t) #define SNDCTL_DSP_GET_RECSRC_NAMES __OSSIOR('P', 37, oss_mixer_enuminfo) #define SNDCTL_DSP_GET_RECSRC __OSSIOR('P', 38, int) #define SNDCTL_DSP_SET_RECSRC __OSSIOWR('P', 38, int) #define SNDCTL_DSP_GET_PLAYTGT_NAMES __OSSIOR('P', 39, oss_mixer_enuminfo) #define SNDCTL_DSP_GET_PLAYTGT __OSSIOR('P', 40, int) #define SNDCTL_DSP_SET_PLAYTGT __OSSIOWR('P', 40, int) #define SNDCTL_DSP_GETRECVOL __OSSIOR('P', 41, int) #define SNDCTL_DSP_SETRECVOL __OSSIOWR('P', 41, int) #define SNDCTL_DSP_GET_CHNORDER __OSSIOR('P', 42, unsigned long long) #define SNDCTL_DSP_SET_CHNORDER __OSSIOWR('P', 42, unsigned long long) #define SNDCTL_DSP_GETIPEAKS __OSSIOR('P', 43, oss_peaks_t) #define SNDCTL_DSP_GETOPEAKS __OSSIOR('P', 44, oss_peaks_t) #define SNDCTL_DSP_POLICY __OSSIOW('P', 45, int) /* See the manual */ #define SNDCTL_DSP_GETCHANNELMASK __OSSIOWR('P', 64, int) #define SNDCTL_DSP_BIND_CHANNEL __OSSIOWR('P', 65, int) /* * These definitions are here for the benefit of compiling application * code. Most of these are NOT implemented in the Solaris code, * however. This is the older 3.x OSS API, and only the master input and * output levels are actually supported. */ #define SOUND_MIXER_NRDEVICES 28 #define SOUND_MIXER_VOLUME 0 #define SOUND_MIXER_BASS 1 #define SOUND_MIXER_TREBLE 2 #define SOUND_MIXER_SYNTH 3 #define SOUND_MIXER_PCM 4 #define SOUND_MIXER_SPEAKER 5 #define SOUND_MIXER_LINE 6 #define SOUND_MIXER_MIC 7 #define SOUND_MIXER_CD 8 #define SOUND_MIXER_IMIX 9 /* Recording monitor */ #define SOUND_MIXER_ALTPCM 10 #define SOUND_MIXER_RECLEV 11 /* Recording level */ #define SOUND_MIXER_IGAIN 12 /* Input gain */ #define SOUND_MIXER_OGAIN 13 /* Output gain */ #define SOUND_MIXER_LINE1 14 /* Input source 1 (aux1) */ #define SOUND_MIXER_LINE2 15 /* Input source 2 (aux2) */ #define SOUND_MIXER_LINE3 16 /* Input source 3 (line) */ #define SOUND_MIXER_DIGITAL1 17 /* Digital I/O 1 */ #define SOUND_MIXER_DIGITAL2 18 /* Digital I/O 2 */ #define SOUND_MIXER_DIGITAL3 19 /* Digital I/O 3 */ #define SOUND_MIXER_PHONE 20 /* Phone */ #define SOUND_MIXER_MONO 21 /* Mono Output */ #define SOUND_MIXER_VIDEO 22 /* Video/TV (audio) in */ #define SOUND_MIXER_RADIO 23 /* Radio in */ #define SOUND_MIXER_DEPTH 24 /* Surround depth */ #define SOUND_MIXER_REARVOL 25 /* Rear/Surround speaker vol */ #define SOUND_MIXER_CENTERVOL 26 /* Center/LFE speaker vol */ #define SOUND_MIXER_SIDEVOL 27 /* Side-Surround (8speaker) vol */ #define SOUND_MIXER_SURRVOL SOUND_MIXER_SIDEVOL #define SOUND_ONOFF_MIN 28 #define SOUND_ONOFF_MAX 30 #define SOUND_MIXER_NONE 31 #define SOUND_MIXER_RECSRC 0xff /* Recording sources */ #define SOUND_MIXER_DEVMASK 0xfe /* Supported devices */ #define SOUND_MIXER_RECMASK 0xfd /* Recording sources */ #define SOUND_MIXER_CAPS 0xfc /* Mixer capabilities (do not use) */ #define SOUND_MIXER_STEREODEVS 0xfb /* Mixer channels supporting stereo */ #define SOUND_MIXER_OUTSRC 0xfa #define SOUND_MIXER_OUTMASK 0xf9 #define SOUND_MIXER_ENHANCE SOUND_MIXER_NONE #define SOUND_MIXER_MUTE SOUND_MIXER_NONE #define SOUND_MIXER_LOUD SOUND_MIXER_NONE #define SOUND_MASK_VOLUME (1 << SOUND_MIXER_VOLUME) #define SOUND_MASK_BASS (1 << SOUND_MIXER_BASS) #define SOUND_MASK_TREBLE (1 << SOUND_MIXER_TREBLE) #define SOUND_MASK_SYNTH (1 << SOUND_MIXER_SYNTH) #define SOUND_MASK_PCM (1 << SOUND_MIXER_PCM) #define SOUND_MASK_SPEAKER (1 << SOUND_MIXER_SPEAKER) #define SOUND_MASK_LINE (1 << SOUND_MIXER_LINE) #define SOUND_MASK_MIC (1 << SOUND_MIXER_MIC) #define SOUND_MASK_CD (1 << SOUND_MIXER_CD) #define SOUND_MASK_IMIX (1 << SOUND_MIXER_IMIX) #define SOUND_MASK_ALTPCM (1 << SOUND_MIXER_ALTPCM) #define SOUND_MASK_RECLEV (1 << SOUND_MIXER_RECLEV) #define SOUND_MASK_IGAIN (1 << SOUND_MIXER_IGAIN) #define SOUND_MASK_OGAIN (1 << SOUND_MIXER_OGAIN) #define SOUND_MASK_LINE1 (1 << SOUND_MIXER_LINE1) #define SOUND_MASK_LINE2 (1 << SOUND_MIXER_LINE2) #define SOUND_MASK_LINE3 (1 << SOUND_MIXER_LINE3) #define SOUND_MASK_DIGITAL1 (1 << SOUND_MIXER_DIGITAL1) #define SOUND_MASK_DIGITAL2 (1 << SOUND_MIXER_DIGITAL2) #define SOUND_MASK_DIGITAL3 (1 << SOUND_MIXER_DIGITAL3) #define SOUND_MASK_MONO (1 << SOUND_MIXER_MONO) #define SOUND_MASK_PHONE (1 << SOUND_MIXER_PHONE) #define SOUND_MASK_RADIO (1 << SOUND_MIXER_RADIO) #define SOUND_MASK_VIDEO (1 << SOUND_MIXER_VIDEO) #define SOUND_MASK_DEPTH (1 << SOUND_MIXER_DEPTH) #define SOUND_MASK_REARVOL (1 << SOUND_MIXER_REARVOL) #define SOUND_MASK_CENTERVOL (1 << SOUND_MIXER_CENTERVOL) #define SOUND_MASK_SIDEVOL (1 << SOUND_MIXER_SIDEVOL) #define SOUND_MASK_SURRVOL SOUND_MASK_SIDEVOL #define SOUND_MASK_MUTE (1 << SOUND_MIXER_MUTE) #define SOUND_MASK_ENHANCE (1 << SOUND_MIXER_ENHANCE) #define SOUND_MASK_LOUD (1 << SOUND_MIXER_LOUD) /* * Again, DO NOT USE the following two macros. They are here for SOURCE * COMPATIBILITY ONLY. */ #define SOUND_DEVICE_LABELS { \ "Vol ", "Bass ", "Treble", "Synth", "Pcm ", "Speaker ", "Line ", \ "Mic ", "CD ", "Mix ", "Pcm2 ", "Rec ", "IGain", "OGain", \ "Aux1", "Aux2", "Aux3", "Digital1", "Digital2", "Digital3", \ "Phone", "Mono", "Video", "Radio", "Depth", \ "Rear", "Center", "Side" } #define SOUND_DEVICE_NAMES { \ "vol", "bass", "treble", "synth", "pcm", "speaker", "line", \ "mic", "cd", "mix", "pcm2", "rec", "igain", "ogain", \ "aux1", "aux2", "aux3", "dig1", "dig2", "dig3", \ "phone", "mono", "video", "radio", "depth", \ "rear", "center", "side" } #define MIXER_READ(dev) __OSSIOR('M', dev, int) #define MIXER_WRITE(dev) __OSSIOWR('M', dev, int) #define SOUND_MIXER_INFO __OSSIOR('M', 101, mixer_info) #define OSS_GETVERSION __OSSIOR('M', 118, int) /* * These macros are useful for some applications. They are implemented * as soft values for the application, and do not affect real hardware. */ #define SOUND_MIXER_READ_VOLUME MIXER_READ(SOUND_MIXER_VOLUME) #define SOUND_MIXER_READ_OGAIN MIXER_READ(SOUND_MIXER_OGAIN) #define SOUND_MIXER_READ_PCM MIXER_READ(SOUND_MIXER_PCM) #define SOUND_MIXER_READ_IGAIN MIXER_READ(SOUND_MIXER_IGAIN) #define SOUND_MIXER_READ_RECLEV MIXER_READ(SOUND_MIXER_RECLEV) #define SOUND_MIXER_READ_RECSRC MIXER_READ(SOUND_MIXER_RECSRC) #define SOUND_MIXER_READ_DEVMASK MIXER_READ(SOUND_MIXER_DEVMASK) #define SOUND_MIXER_READ_RECMASK MIXER_READ(SOUND_MIXER_RECMASK) #define SOUND_MIXER_READ_CAPS MIXER_READ(SOUND_MIXER_CAPS) #define SOUND_MIXER_READ_STEREODEVS MIXER_READ(SOUND_MIXER_STEREODEVS) #define SOUND_MIXER_READ_RECGAIN __OSSIOR('M', 119, int) #define SOUND_MIXER_READ_MONGAIN __OSSIOR('M', 120, int) #define SOUND_MIXER_WRITE_VOLUME MIXER_WRITE(SOUND_MIXER_VOLUME) #define SOUND_MIXER_WRITE_OGAIN MIXER_WRITE(SOUND_MIXER_OGAIN) #define SOUND_MIXER_WRITE_PCM MIXER_WRITE(SOUND_MIXER_PCM) #define SOUND_MIXER_WRITE_IGAIN MIXER_WRITE(SOUND_MIXER_IGAIN) #define SOUND_MIXER_WRITE_RECLEV MIXER_WRITE(SOUND_MIXER_RECLEV) #define SOUND_MIXER_WRITE_RECSRC MIXER_WRITE(SOUND_MIXER_RECSRC) #define SOUND_MIXER_WRITE_RECGAIN __OSSIOWR('M', 119, int) #define SOUND_MIXER_WRITE_MONGAIN __OSSIOWR('M', 120, int) /* * These macros are here for source compatibility. They intentionally don't * map to any real hardware. NOT SUPPORTED! */ #define SOUND_MIXER_READ_BASS MIXER_READ(SOUND_MIXER_BASS) #define SOUND_MIXER_READ_TREBLE MIXER_READ(SOUND_MIXER_TREBLE) #define SOUND_MIXER_READ_SYNTH MIXER_READ(SOUND_MIXER_SYNTH) #define SOUND_MIXER_READ_SPEAKER MIXER_READ(SOUND_MIXER_SPEAKER) #define SOUND_MIXER_READ_LINE MIXER_READ(SOUND_MIXER_LINE) #define SOUND_MIXER_READ_MIC MIXER_READ(SOUND_MIXER_MIC) #define SOUND_MIXER_READ_CD MIXER_READ(SOUND_MIXER_CD) #define SOUND_MIXER_READ_IMIX MIXER_READ(SOUND_MIXER_IMIX) #define SOUND_MIXER_READ_ALTPCM MIXER_READ(SOUND_MIXER_ALTPCM) #define SOUND_MIXER_READ_LINE1 MIXER_READ(SOUND_MIXER_LINE1) #define SOUND_MIXER_READ_LINE2 MIXER_READ(SOUND_MIXER_LINE2) #define SOUND_MIXER_READ_LINE3 MIXER_READ(SOUND_MIXER_LINE3) #define SOUND_MIXER_WRITE_BASS MIXER_WRITE(SOUND_MIXER_BASS) #define SOUND_MIXER_WRITE_TREBLE MIXER_WRITE(SOUND_MIXER_TREBLE) #define SOUND_MIXER_WRITE_SYNTH MIXER_WRITE(SOUND_MIXER_SYNTH) #define SOUND_MIXER_WRITE_SPEAKER MIXER_WRITE(SOUND_MIXER_SPEAKER) #define SOUND_MIXER_WRITE_LINE MIXER_WRITE(SOUND_MIXER_LINE) #define SOUND_MIXER_WRITE_MIC MIXER_WRITE(SOUND_MIXER_MIC) #define SOUND_MIXER_WRITE_CD MIXER_WRITE(SOUND_MIXER_CD) #define SOUND_MIXER_WRITE_IMIX MIXER_WRITE(SOUND_MIXER_IMIX) #define SOUND_MIXER_WRITE_ALTPCM MIXER_WRITE(SOUND_MIXER_ALTPCM) #define SOUND_MIXER_WRITE_LINE1 MIXER_WRITE(SOUND_MIXER_LINE1) #define SOUND_MIXER_WRITE_LINE2 MIXER_WRITE(SOUND_MIXER_LINE2) #define SOUND_MIXER_WRITE_LINE3 MIXER_WRITE(SOUND_MIXER_LINE3) /* * Audio encoding types (Note! U8=8 and S16_LE=16 for compatibility) */ #define AFMT_QUERY 0x00000000 /* Return current fmt */ #define AFMT_MU_LAW 0x00000001 #define AFMT_A_LAW 0x00000002 #define AFMT_IMA_ADPCM 0x00000004 #define AFMT_U8 0x00000008 #define AFMT_S16_LE 0x00000010 #define AFMT_S16_BE 0x00000020 #define AFMT_S8 0x00000040 #define AFMT_U16_LE 0x00000080 #define AFMT_U16_BE 0x00000100 #define AFMT_MPEG 0x00000200 /* NOT SUPPORTED: MPEG (2) audio */ #define AFMT_AC3 0x00000400 /* NOT SUPPORTED: AC3 compressed */ #define AFMT_VORBIS 0x00000800 /* NOT SUPPORTED: Ogg Vorbis */ #define AFMT_S32_LE 0x00001000 #define AFMT_S32_BE 0x00002000 #define AFMT_FLOAT 0x00004000 /* NOT SUPPORTED: IEEE double float */ #define AFMT_S24_LE 0x00008000 /* LSB aligned in 32 bit word */ #define AFMT_S24_BE 0x00010000 /* LSB aligned in 32 bit word */ #define AFMT_SPDIF_RAW 0x00020000 /* NOT SUPPORTED: Raw S/PDIF frames */ #define AFMT_S24_PACKED 0x00040000 /* 24 bit packed little endian */ /* * Some big endian/little endian handling macros (native endian and * opposite endian formats). */ #if defined(_BIG_ENDIAN) #define AFMT_S16_NE AFMT_S16_BE #define AFMT_U16_NE AFMT_U16_BE #define AFMT_S32_NE AFMT_S32_BE #define AFMT_S24_NE AFMT_S24_BE #define AFMT_S16_OE AFMT_S16_LE #define AFMT_S32_OE AFMT_S32_LE #define AFMT_S24_OE AFMT_S24_LE #else #define AFMT_S16_NE AFMT_S16_LE #define AFMT_U16_NE AFMT_U16_LE #define AFMT_S32_NE AFMT_S32_LE #define AFMT_S24_NE AFMT_S24_LE #define AFMT_S16_OE AFMT_S16_BE #define AFMT_S32_OE AFMT_S32_BE #define AFMT_S24_OE AFMT_S24_BE #endif /* * SNDCTL_DSP_GETCAPS bits */ #define PCM_CAP_REVISION 0x000000ff /* Revision level (0 to 255) */ #define PCM_CAP_DUPLEX 0x00000100 /* Full duplex rec/play */ #define PCM_CAP_REALTIME 0x00000200 /* NOT SUPPORTED */ #define PCM_CAP_BATCH 0x00000400 /* NOT SUPPORTED */ #define PCM_CAP_COPROC 0x00000800 /* NOT SUPPORTED */ #define PCM_CAP_TRIGGER 0x00001000 /* Supports SETTRIGGER */ #define PCM_CAP_MMAP 0x00002000 /* Supports mmap() */ #define PCM_CAP_MULTI 0x00004000 /* Supports multiple open */ #define PCM_CAP_BIND 0x00008000 /* Supports channel binding */ #define PCM_CAP_INPUT 0x00010000 /* Supports recording */ #define PCM_CAP_OUTPUT 0x00020000 /* Supports playback */ #define PCM_CAP_VIRTUAL 0x00040000 /* Virtual device */ #define PCM_CAP_ANALOGOUT 0x00100000 /* NOT SUPPORTED */ #define PCM_CAP_ANALOGIN 0x00200000 /* NOT SUPPORTED */ #define PCM_CAP_DIGITALOUT 0x00400000 /* NOT SUPPORTED */ #define PCM_CAP_DIGITALIN 0x00800000 /* NOT SUPPORTED */ #define PCM_CAP_ADMASK 0x00f00000 /* NOT SUPPORTED */ #define PCM_CAP_SHADOW 0x01000000 /* "Shadow" device */ #define PCM_CAP_CH_MASK 0x06000000 /* See DSP_CH_MASK below */ #define PCM_CAP_HIDDEN 0x08000000 /* NOT SUPPORTED */ #define PCM_CAP_FREERATE 0x10000000 #define PCM_CAP_MODEM 0x20000000 /* NOT SUPPORTED */ #define PCM_CAP_DEFAULT 0x40000000 /* "Default" device */ /* * Preferred channel usage. These bits can be used to give * recommendations to the application. Used by few drivers. For * example if ((caps & DSP_CH_MASK) == DSP_CH_MONO) means that the * device works best in mono mode. However it doesn't necessarily mean * that the device cannot be used in stereo. These bits should only be * used by special applications such as multi track hard disk * recorders to find out the initial setup. However the user should be * able to override this selection. * * To find out which modes are actually supported the application * should try to select them using SNDCTL_DSP_CHANNELS. */ #define DSP_CH_MASK 0x06000000 /* Mask */ #define DSP_CH_ANY 0x00000000 /* No preferred mode */ #define DSP_CH_MONO 0x02000000 #define DSP_CH_STEREO 0x04000000 #define DSP_CH_MULTI 0x06000000 /* More than two channels */ /* * The PCM_CAP_* capability names used to be known as DSP_CAP_*, so * it's necessary to define the older names too. */ #define DSP_CAP_ADMASK PCM_CAP_ADMASK #define DSP_CAP_ANALOGIN PCM_CAP_ANALOGIN #define DSP_CAP_ANALOGOUT PCM_CAP_ANALOGOUT #define DSP_CAP_BATCH PCM_CAP_BATCH #define DSP_CAP_BIND PCM_CAP_BIND #define DSP_CAP_COPROC PCM_CAP_COPROC #define DSP_CAP_DEFAULT PCM_CAP_DEFAULT #define DSP_CAP_DIGITALIN PCM_CAP_DIGITALIN #define DSP_CAP_DIGITALOUT PCM_CAP_DIGITALOUT #define DSP_CAP_DUPLEX PCM_CAP_DUPLEX #define DSP_CAP_FREERATE PCM_CAP_FREERATE #define DSP_CAP_HIDDEN PCM_CAP_HIDDEN #define DSP_CAP_INPUT PCM_CAP_INPUT #define DSP_CAP_MMAP PCM_CAP_MMAP #define DSP_CAP_MODEM PCM_CAP_MODEM #define DSP_CAP_MULTI PCM_CAP_MULTI #define DSP_CAP_OUTPUT PCM_CAP_OUTPUT #define DSP_CAP_REALTIME PCM_CAP_REALTIME #define DSP_CAP_REVISION PCM_CAP_REVISION #define DSP_CAP_SHADOW PCM_CAP_SHADOW #define DSP_CAP_TRIGGER PCM_CAP_TRIGGER #define DSP_CAP_VIRTUAL PCM_CAP_VIRTUAL /* * SNDCTL_DSP_GETTRIGGER and SNDCTL_DSP_SETTRIGGER */ #define PCM_ENABLE_INPUT 0x00000001 #define PCM_ENABLE_OUTPUT 0x00000002 /* * SNDCTL_DSP_BIND_CHANNEL */ #define DSP_BIND_QUERY 0x00000000 #define DSP_BIND_FRONT 0x00000001 #define DSP_BIND_SURR 0x00000002 #define DSP_BIND_CENTER_LFE 0x00000004 #define DSP_BIND_HANDSET 0x00000008 #define DSP_BIND_MIC 0x00000010 #define DSP_BIND_MODEM1 0x00000020 #define DSP_BIND_MODEM2 0x00000040 #define DSP_BIND_I2S 0x00000080 #define DSP_BIND_SPDIF 0x00000100 #define DSP_BIND_REAR 0x00000200 /* * SOUND_MIXER_READ_CAPS */ #define SOUND_CAP_EXCL_INPUT 0x00000001 #define SOUND_CAP_NOLEGACY 0x00000004 #define SOUND_CAP_NORECSRC 0x00000008 /* * The following ioctl is for internal use only -- it is used to * coordinate /dev/sndstat numbering with file names in /dev/sound. * Applications must not use it. (This is duplicated in sys/audioio.h * as well.) */ #define SNDCTL_SUN_SEND_NUMBER __OSSIOW('X', 200, int) #ifdef __cplusplus } #endif #endif /* _SYS_AUDIO_OSS_H */ ================================================ FILE: src/detection/sound/sound.h ================================================ #pragma once #include "fastfetch.h" #define FF_SOUND_VOLUME_UNKNOWN 255 typedef struct FFSoundDevice { FFstrbuf identifier; FFstrbuf name; FFstrbuf platformApi; uint8_t volume; // 0-100% bool main; bool active; } FFSoundDevice; const char* ffDetectSound(FFlist* devices /* List of FFSoundDevice */); ================================================ FILE: src/detection/sound/sound_apple.c ================================================ #include "sound.h" #include "common/apple/cf_helpers.h" #include #include #ifndef MAC_OS_VERSION_12_0 #define kAudioObjectPropertyElementMain kAudioObjectPropertyElementMaster #endif const char* ffDetectSound(FFlist* devices /* List of FFSoundDevice */) { AudioDeviceID mainDeviceId; UInt32 dataSize = sizeof(mainDeviceId); if(AudioObjectGetPropertyData(kAudioObjectSystemObject, &(AudioObjectPropertyAddress){ kAudioHardwarePropertyDefaultOutputDevice, kAudioObjectPropertyScopeOutput, kAudioObjectPropertyElementMain }, 0, NULL, &dataSize, &mainDeviceId) != kAudioHardwareNoError) return "AudioObjectGetPropertyData(kAudioHardwarePropertyDefaultOutputDevice) failed"; AudioObjectID deviceIds[32] = {}; dataSize = sizeof(deviceIds); if(AudioObjectGetPropertyData(kAudioObjectSystemObject, &(AudioObjectPropertyAddress){ kAudioHardwarePropertyDevices, kAudioObjectPropertyScopeOutput, kAudioObjectPropertyElementMain }, 0, NULL, &dataSize, &deviceIds) != kAudioHardwareNoError) return "AudioObjectGetPropertyData(kAudioHardwarePropertyDevices) failed"; for(uint32_t index = 0, length = dataSize / sizeof(*deviceIds); index < length; ++index) { AudioDeviceID deviceId = deviceIds[index]; // Ignore input devices if(AudioObjectGetPropertyDataSize(deviceId, &(AudioObjectPropertyAddress){ kAudioDevicePropertyStreams, kAudioObjectPropertyScopeInput, kAudioObjectPropertyElementMain }, 0, NULL, &dataSize) == kAudioHardwareNoError && dataSize > 0) continue; uint32_t dataSource; dataSize = sizeof(dataSource); if(AudioObjectGetPropertyData(deviceId, &(AudioObjectPropertyAddress){ kAudioDevicePropertyDataSource, kAudioObjectPropertyScopeOutput, kAudioObjectPropertyElementMain }, 0, NULL, &dataSize, &dataSource) == kAudioHardwareNoError && dataSource == 'hdpn') { uint32_t connected; dataSize = sizeof(connected); if(AudioObjectGetPropertyData(deviceId, &(AudioObjectPropertyAddress){ kAudioDevicePropertyJackIsConnected, kAudioObjectPropertyScopeOutput, kAudioObjectPropertyElementMain }, 0, NULL, &dataSize, &connected) == kAudioHardwareNoError) if (!connected) continue; } FFSoundDevice* device = (FFSoundDevice*) ffListAdd(devices); device->main = deviceId == mainDeviceId; device->active = false; device->volume = FF_SOUND_VOLUME_UNKNOWN; ffStrbufInit(&device->identifier); ffStrbufInit(&device->name); ffStrbufInitStatic(&device->platformApi, "Core Audio"); FF_CFTYPE_AUTO_RELEASE CFStringRef uid = NULL; dataSize = sizeof(uid); if(AudioObjectGetPropertyData(deviceId, &(AudioObjectPropertyAddress) { kAudioDevicePropertyDeviceUID, kAudioObjectPropertyScopeOutput, kAudioObjectPropertyElementMain }, 0, NULL, &dataSize, &uid) == kAudioHardwareNoError) ffCfStrGetString(uid, &device->identifier); else ffStrbufAppendF(&device->identifier, "ID-%u", (unsigned) deviceId); FF_CFTYPE_AUTO_RELEASE CFStringRef name = NULL; dataSize = sizeof(name); if(AudioObjectGetPropertyData(deviceId, &(AudioObjectPropertyAddress){ kAudioObjectPropertyName, kAudioObjectPropertyScopeOutput, kAudioObjectPropertyElementMain }, 0, NULL, &dataSize, &name) == kAudioHardwareNoError) ffCfStrGetString(name, &device->name); else ffStrbufSet(&device->name, &device->identifier); uint32_t muted; dataSize = sizeof(muted); if(AudioObjectGetPropertyData(deviceId, &(AudioObjectPropertyAddress){ kAudioDevicePropertyMute, kAudioObjectPropertyScopeOutput, kAudioObjectPropertyElementMain }, 0, NULL, &dataSize, &muted) != kAudioHardwareNoError) muted = false; // Device may not support volume control uint32_t active; dataSize = sizeof(active); if(AudioObjectGetPropertyData(deviceId, &(AudioObjectPropertyAddress){ kAudioDevicePropertyDeviceIsAlive, kAudioObjectPropertyScopeOutput, kAudioObjectPropertyElementMain }, 0, NULL, &dataSize, &active) == kAudioHardwareNoError) device->active = !!active; if (muted) device->volume = 0; else { float volume; dataSize = sizeof(volume); if(AudioObjectGetPropertyData(deviceId, &(AudioObjectPropertyAddress){ kAudioDevicePropertyVolumeScalar, kAudioObjectPropertyScopeOutput, kAudioObjectPropertyElementMain }, 0, NULL, &dataSize, &volume) == kAudioHardwareNoError) device->volume = (uint8_t) (volume * 100 + 0.5); else { // Try detecting volume from channels uint32_t channels[2]; dataSize = sizeof(channels); if (AudioObjectGetPropertyData(deviceId, &(AudioObjectPropertyAddress){ kAudioDevicePropertyPreferredChannelsForStereo, kAudioObjectPropertyScopeOutput, kAudioObjectPropertyElementMain }, 0, NULL, &dataSize, channels) == kAudioHardwareNoError) { dataSize = sizeof(volume); if (AudioObjectGetPropertyData(deviceId, &(AudioObjectPropertyAddress){ kAudioDevicePropertyVolumeScalar, kAudioObjectPropertyScopeOutput, channels[0] }, 0, NULL, &dataSize, &volume) == kAudioHardwareNoError) { float temp; if (AudioObjectGetPropertyData(deviceId, &(AudioObjectPropertyAddress){ kAudioDevicePropertyVolumeScalar, kAudioObjectPropertyScopeOutput, channels[1] }, 0, NULL, &dataSize, &temp) == kAudioHardwareNoError) device->volume = (uint8_t) ((volume + temp) / 2 * 100 + 0.5); } } } } } return NULL; } ================================================ FILE: src/detection/sound/sound_bsd.c ================================================ #include "sound.h" #include "common/io.h" #include "common/sysctl.h" #include #include #include const char* ffDetectSound(FFlist* devices) { #ifndef __NetBSD__ int defaultDev = ffSysctlGetInt("hw.snd.default_unit", -1); if (defaultDev == -1) return "sysctl(hw.snd.default_unit) failed"; #else int defaultDev; { char mixerp[12]; ssize_t plen = readlink("/dev/mixer", mixerp, ARRAY_SIZE(mixerp)); if (plen < 6) return "readlink(/dev/mixer) failed"; defaultDev = mixerp[plen - 1] - '0'; if (defaultDev < 0 || defaultDev > 9) return "Invalid mixer device"; } #endif char path[] = "/dev/mixer0"; struct oss_sysinfo info = { .nummixers = 9 }; for (int idev = 0; idev <= info.nummixers; ++idev) { path[strlen("/dev/mixer")] = (char) ('0' + idev); FF_AUTO_CLOSE_FD int fd = open(path, O_RDWR | O_CLOEXEC); if (fd < 0) break; if (idev == 0) { if (ioctl(fd, SNDCTL_SYSINFO, &info) != 0) return "ioctl(SNDCTL_SYSINFO) failed"; } uint32_t devmask = 0; if (ioctl(fd, SOUND_MIXER_READ_DEVMASK, &devmask) < 0) continue; if (!(devmask & SOUND_MASK_VOLUME)) continue; #if defined(SOUND_MIXER_MUTE) && (SOUND_MIXER_MUTE != SOUND_MIXER_NONE) #define FF_SOUND_HAVE_MIXER_MUTE 1 uint32_t mutemask = 0; ioctl(fd, SOUND_MIXER_READ_MUTE, &mutemask); #endif struct oss_card_info ci = { .card = idev }; if (ioctl(fd, SNDCTL_CARDINFO, &ci) < 0) continue; uint32_t volume; if (ioctl(fd, SOUND_MIXER_READ_VOLUME, &volume) < 0) continue; FFSoundDevice* device = ffListAdd(devices); ffStrbufInitS(&device->identifier, path); ffStrbufInitF(&device->name, "%s %s", ci.longname, ci.hw_info); ffStrbufTrimRightSpace(&device->name); ffStrbufInitF(&device->platformApi, "%s %s", info.product, info.version); device->volume = #ifdef FF_SOUND_HAVE_MIXER_MUTE mutemask & SOUND_MASK_VOLUME ? 0 : #endif ((uint8_t) volume /*left*/ + (uint8_t) (volume >> 8) /*right*/) / 2; device->active = true; device->main = defaultDev == idev; } return NULL; } ================================================ FILE: src/detection/sound/sound_haiku.cpp ================================================ extern "C" { #include "sound.h" #include "common/stringUtils.h" } #include #include #include #include const char* ffDetectSound(FFlist* devices /* List of FFSoundDevice */) { BMediaRoster* roster = BMediaRoster::Roster(); media_node mediaNode; live_node_info liveInfo; dormant_node_info dormantInfo; if (roster->GetAudioOutput(&mediaNode) != B_OK) return NULL; FFSoundDevice* device = (FFSoundDevice*)ffListAdd(devices); ffStrbufInit(&device->identifier); if (roster->GetDormantNodeFor(mediaNode, &dormantInfo) == B_OK) ffStrbufAppendS(&device->identifier, dormantInfo.name); ffStrbufInit(&device->name); if (roster->GetLiveNodeInfo(mediaNode, &liveInfo) == B_OK) { ffStrbufAppendS(&device->name, liveInfo.name); ffStrbufTrimRightSpace(&device->name); } ffStrbufInitStatic(&device->platformApi, "MediaKit"); // We'll check the Mixer actually device->volume = 0; device->active = true; device->main = true; roster->ReleaseNode(mediaNode); media_node mixer; if (roster->GetAudioMixer(&mixer) != B_OK) return NULL; BParameterWeb *web; status_t status = roster->GetParameterWebFor(mixer, &web); roster->ReleaseNode(mixer); // the web is all we need :-) if (status != B_OK) return NULL; BContinuousParameter *gain = NULL; BParameter *mute = NULL; BParameter *parameter; for (int32 index = 0; (parameter = web->ParameterAt(index)) != NULL; index++) { // assume the mute preceding master gain control if (ffStrEquals(parameter->Kind(), B_MUTE)) mute = parameter; if (ffStrEquals(parameter->Kind(), B_MASTER_GAIN)) { // Can not use dynamic_cast due to fno-rtti //gain = dynamic_cast(parameter); gain = (BContinuousParameter *)(parameter); break; } } if (gain == NULL) return NULL; bigtime_t when; size_t size; if (mute) { int32 isMute = false; size = sizeof(isMute); if (mute->GetValue(&isMute, &size, &when) == B_OK && isMute) return NULL; } float volume = 0.0; size = sizeof(volume); if (gain->GetValue(&volume, &size, &when) == B_OK) device->volume = (uint8_t) (100 * (volume - gain->MinValue()) / (gain->MaxValue() - gain->MinValue())); return NULL; } ================================================ FILE: src/detection/sound/sound_linux.c ================================================ #include "sound.h" #ifdef FF_HAVE_PULSE #include "common/library.h" #include static void paSinkInfoCallback(pa_context *c, const pa_sink_info *i, int eol, void *userdata) { FF_UNUSED(c); if(eol > 0 || !i) return; FFSoundDevice* device = ffListAdd(userdata); ffStrbufInitS(&device->identifier, i->name); ffStrbufInitStatic(&device->platformApi, "PulseAudio"); ffStrbufTrimRightSpace(&device->identifier); ffStrbufInitS(&device->name, i->description); ffStrbufTrimRightSpace(&device->name); ffStrbufTrimLeft(&device->name, ' '); device->volume = i->mute ? 0 : (uint8_t) ((i->volume.values[0] * 100 + PA_VOLUME_NORM / 2 /*round*/) / PA_VOLUME_NORM); device->active = i->active_port && i->active_port->available != PA_PORT_AVAILABLE_NO; device->main = false; } static void paServerInfoCallback(FF_MAYBE_UNUSED pa_context *c, const pa_server_info *i, void *userdata) { if(!i) return; FF_STRBUF_AUTO_DESTROY api = ffStrbufCreate(); const char* realServer = strstr(i->server_name, "(on "); if (realServer) { ffStrbufSetS(&api, realServer + strlen("(on ")); ffStrbufTrimRight(&api, ')'); } else ffStrbufSetF(&api, "%s %s", i->server_name, i->server_version); FF_LIST_FOR_EACH(FFSoundDevice, device, *(FFlist*)userdata) { device->main = ffStrbufEqualS(&device->identifier, i->default_sink_name); ffStrbufSet(&device->platformApi, &api); } } static const char* detectSound(FFlist* devices) { FF_LIBRARY_LOAD_MESSAGE(pulse, "libpulse" FF_LIBRARY_EXTENSION, 0) FF_LIBRARY_LOAD_SYMBOL_MESSAGE(pulse, pa_mainloop_new) FF_LIBRARY_LOAD_SYMBOL_MESSAGE(pulse, pa_mainloop_get_api) FF_LIBRARY_LOAD_SYMBOL_MESSAGE(pulse, pa_mainloop_iterate) FF_LIBRARY_LOAD_SYMBOL_MESSAGE(pulse, pa_mainloop_free) FF_LIBRARY_LOAD_SYMBOL_MESSAGE(pulse, pa_context_new) FF_LIBRARY_LOAD_SYMBOL_MESSAGE(pulse, pa_context_connect) FF_LIBRARY_LOAD_SYMBOL_MESSAGE(pulse, pa_context_get_state) FF_LIBRARY_LOAD_SYMBOL_MESSAGE(pulse, pa_context_get_sink_info_list) FF_LIBRARY_LOAD_SYMBOL_MESSAGE(pulse, pa_context_get_server_info) FF_LIBRARY_LOAD_SYMBOL_MESSAGE(pulse, pa_context_unref) FF_LIBRARY_LOAD_SYMBOL_MESSAGE(pulse, pa_operation_get_state) FF_LIBRARY_LOAD_SYMBOL_MESSAGE(pulse, pa_operation_unref) pa_mainloop* mainloop = ffpa_mainloop_new(); if(!mainloop) return "Failed to create pulseaudio mainloop"; pa_mainloop_api* mainloopApi = ffpa_mainloop_get_api(mainloop); if(!mainloopApi) { ffpa_mainloop_free(mainloop); return "Failed to get pulseaudio mainloop api"; } pa_context* context = ffpa_context_new(mainloopApi, "fastfetch"); if(!context) { ffpa_mainloop_free(mainloop); return "Failed to create pulseaudio context"; } if(ffpa_context_connect(context, NULL, PA_CONTEXT_NOFLAGS, NULL) < 0) { ffpa_context_unref(context); ffpa_mainloop_free(mainloop); return "Failed to connect to pulseaudio context"; } pa_context_state_t state; while((state = ffpa_context_get_state(context)) != PA_CONTEXT_READY) { if(!PA_CONTEXT_IS_GOOD(state)) { ffpa_context_unref(context); ffpa_mainloop_free(mainloop); return "Failed to get pulseaudio context state"; } ffpa_mainloop_iterate(mainloop, 1, NULL); } pa_operation* operation = ffpa_context_get_sink_info_list(context, paSinkInfoCallback, devices); if(!operation) { ffpa_context_unref(context); ffpa_mainloop_free(mainloop); return "Failed to get pulseaudio sink info list"; } while(ffpa_operation_get_state(operation) == PA_OPERATION_RUNNING) ffpa_mainloop_iterate(mainloop, 1, NULL); ffpa_operation_unref(operation); operation = ffpa_context_get_server_info(context, paServerInfoCallback, devices); if(operation) { while(ffpa_operation_get_state(operation) == PA_OPERATION_RUNNING) ffpa_mainloop_iterate(mainloop, 1, NULL); ffpa_operation_unref(operation); } ffpa_context_unref(context); ffpa_mainloop_free(mainloop); return NULL; } #endif // FF_HAVE_PULSE const char* ffDetectSound(FFlist* devices) { #ifdef FF_HAVE_PULSE return detectSound(devices); #else FF_UNUSED(devices); return "Fastfetch was built without libpulse support"; #endif } ================================================ FILE: src/detection/sound/sound_nbsd.c ================================================ #include "sound.h" #include "common/io.h" #include #include #include #include #include const char* ffDetectSound(FFlist* devices) { int defaultDev; { char audiop[12]; ssize_t plen = readlink("/dev/audio", audiop, ARRAY_SIZE(audiop)); if (plen < (ssize_t) strlen("audioN")) return "readlink(/dev/audio) failed"; defaultDev = audiop[plen - 1] - '0'; if (defaultDev < 0 || defaultDev > 9) return "Invalid audio device"; } char path[] = "/dev/audio0"; for (int idev = 0; idev < 9; ++idev) { path[strlen("/dev/audio")] = (char) ('0' + idev); FF_AUTO_CLOSE_FD int fd = open(path, O_RDWR | O_CLOEXEC); if (fd < 0) break; audio_device_t ad; if (ioctl(fd, AUDIO_GETDEV, &ad) < 0) continue; audio_info_t ai; if (ioctl(fd, AUDIO_GETINFO, &ai) < 0) continue; FFSoundDevice* device = ffListAdd(devices); ffStrbufInitS(&device->identifier, path); ffStrbufInitS(&device->name, ad.name); ffStrbufTrimRightSpace(&device->name); ffStrbufInitF(&device->platformApi, "%s", "SunAudio"); device->volume = (uint8_t) ((ai.play.gain * 100 + AUDIO_MAX_GAIN / 2) / AUDIO_MAX_GAIN); device->active = true; device->main = defaultDev == idev; } return NULL; } ================================================ FILE: src/detection/sound/sound_nosupport.c ================================================ #include "sound.h" const char* ffDetectSound(FF_MAYBE_UNUSED FFlist* devices /* List of FFSoundDevice */) { return "Not supported on this platform"; } ================================================ FILE: src/detection/sound/sound_obsd.c ================================================ #include "sound.h" #include "common/stringUtils.h" #include #include static void close_hdl(struct sioctl_hdl** phdl) { assert(phdl); if (*phdl) sioctl_close(*phdl); } enum { MAX_CHANNEL_NUM = 8 }; typedef struct FFSoundDeviceBundle { char name[SIOCTL_DISPLAYMAX]; double level[MAX_CHANNEL_NUM]; uint8_t iLevel; bool mute[MAX_CHANNEL_NUM]; uint8_t iMute; } FFSoundDeviceBundle; static void enumerate_props(FFSoundDeviceBundle* bundle, struct sioctl_desc* desc, int val) { if (!desc) return; if (desc->type == SIOCTL_SEL) { if (desc->display[0] != '\0' && ffStrEquals(desc->node0.name, "server")) ffStrCopy(bundle->name, desc->display, SIOCTL_DISPLAYMAX); return; } if (desc->type != SIOCTL_NUM && desc->type != SIOCTL_SW) return; if (!ffStrEquals(desc->node0.name, "output")) return; if (ffStrEquals(desc->func, "level")) { if (__builtin_expect(bundle->iLevel == MAX_CHANNEL_NUM, false)) return; bundle->level[bundle->iLevel] = (double) val / (double) desc->maxval; ++bundle->iLevel; } else if (ffStrEquals(desc->func, "mute")) { if (__builtin_expect(bundle->iMute == MAX_CHANNEL_NUM, false)) return; bundle->mute[bundle->iMute] = !!val; ++bundle->iMute; } } const char* ffDetectSound(FFlist* devices) { __attribute__((__cleanup__(close_hdl))) struct sioctl_hdl* hdl = sioctl_open(SIO_DEVANY, SIOCTL_READ, 0); if (!hdl) return "sio_open() failed"; FFSoundDeviceBundle bundle = {}; if (sioctl_ondesc(hdl, (void*) enumerate_props, &bundle) == 0) return "sioctl_ondesc() failed"; if (bundle.iLevel != bundle.iMute || bundle.iLevel == 0) return "Unexpected sioctl_ondesc() result"; FFSoundDevice* device = ffListAdd(devices); ffStrbufInitS(&device->name, bundle.name); ffStrbufInitS(&device->identifier, SIO_DEVANY); ffStrbufInitStatic(&device->platformApi, "sndio"); device->active = true; device->main = true; device->volume = 0; double totalLevel = 0; for (uint8_t i = 0; i < bundle.iLevel; ++i) { if (!bundle.mute[i]) totalLevel += bundle.level[i]; } device->volume = (uint8_t) ((totalLevel * 100 + bundle.iLevel / 2) / bundle.iLevel); return NULL; } ================================================ FILE: src/detection/sound/sound_sunos.c ================================================ #include "sound.h" #include "common/io.h" #include "common/stringUtils.h" #include #include #if __has_include() #include #else // Strangely, they don't provide this file on default installation #include "audio_oss_sunos.h" #endif const char* ffDetectSound(FFlist* devices) { int defaultDev; { char mixerp[12]; ssize_t plen = readlink("/dev/audio", mixerp, ARRAY_SIZE(mixerp)); if (plen < 6) return "readlink(/dev/audio) failed"; defaultDev = mixerp[plen - 1] - '0'; if (defaultDev < 0 || defaultDev > 9) return "Invalid mixer device"; } char path[] = "/dev/mixer0"; FF_STRBUF_AUTO_DESTROY sndstat = ffStrbufCreate(); struct oss_sysinfo info = { .nummixers = 9 }; // The implementation is very different from *BSD's. They call it OSS4 for (int idev = 0; idev < info.nummixers; ++idev) { path[strlen("/dev/mixer")] = (char) ('0' + idev); FF_AUTO_CLOSE_FD int fd = open(path, O_RDWR | O_CLOEXEC); if (fd < 0) break; if (idev == 0) { if (ioctl(fd, SNDCTL_SYSINFO, &info) != 0) return "ioctl(SNDCTL_SYSINFO) failed"; if (ffAppendFDBuffer(fd, &sndstat)) ffStrbufSubstrAfterFirstS(&sndstat, "\nMixers:"); } struct oss_mixerinfo mi = {}; if (ioctl(fd, SNDCTL_MIXERINFO, &mi) < 0) continue; int volume = -1; for (int iext = 0; iext < mi.nrext; ++iext) { struct oss_mixext me = { .dev = mi.dev, .ctrl = iext }; if (ioctl(fd, SNDCTL_MIX_EXTINFO, &me) < 0) continue; if (me.flags & MIXF_PCMVOL) { struct oss_mixer_value mv = { .dev = mi.dev, .ctrl = iext, .timestamp = me.timestamp }; if (ioctl(fd, SNDCTL_MIX_READ, &mv) >= 0) { mv.value -= me.minvalue; me.maxvalue -= me.minvalue; volume = (uint8_t) ((mv.value * 100 + me.maxvalue / 2) / me.maxvalue); } break; } } if (volume == -1) continue; FFSoundDevice* device = ffListAdd(devices); ffStrbufInitS(&device->identifier, path); char buf[16]; int bufLen = snprintf(buf, ARRAY_SIZE(buf), "\n%d: ", mi.dev); assert(bufLen > 3); const char* pLine = memmem(sndstat.chars, sndstat.length, buf, (size_t) bufLen); if (pLine) { pLine += bufLen; const char* pEnd = strchr(pLine, '\n'); if (!pEnd) pEnd = sndstat.chars + sndstat.length; ffStrbufInitNS(&device->name, (uint32_t) (pEnd - pLine), pLine); } else ffStrbufInitS(&device->name, mi.name); ffStrbufTrimRightSpace(&device->name); ffStrbufInitF(&device->platformApi, "%s %s", info.product, info.version); device->volume = (uint8_t) volume; device->active = !!mi.enabled; device->main = defaultDev == idev; } return NULL; } ================================================ FILE: src/detection/sound/sound_windows.cpp ================================================ extern "C" { #include "sound.h" #include "common/windows/unicode.h" } #include "common/windows/com.hpp" #include #include #include #include static void ffCoTaskMemFreeWrapper(void* pptr) { assert(pptr != NULL); void* ptr = *(void**)pptr; if (ptr) CoTaskMemFree(ptr); } #define FF_COTASK_AUTO_FREE __attribute__((__cleanup__(ffCoTaskMemFreeWrapper))) const char* ffDetectSound(FFlist* devices /* List of FFSoundDevice */) { const char* error = ffInitCom(); if (error) return error; IMMDeviceEnumerator* FF_AUTO_RELEASE_COM_OBJECT pEnum = NULL; if (FAILED(CoCreateInstance(CLSID_MMDeviceEnumerator, NULL, CLSCTX_ALL, IID_IMMDeviceEnumerator, (void **)&pEnum))) return "CoCreateInstance(CLSID_MMDeviceEnumerator) failed"; LPWSTR FF_COTASK_AUTO_FREE mainDeviceId = NULL; { IMMDevice* FF_AUTO_RELEASE_COM_OBJECT pDefaultDevice = NULL; if (FAILED(pEnum->GetDefaultAudioEndpoint(eRender, eMultimedia, &pDefaultDevice))) return "GetDefaultAudioEndpoint() failed"; if (FAILED(pDefaultDevice->GetId(&mainDeviceId))) return "pDefaultDevice->GetId() failed"; } IMMDeviceCollection* FF_AUTO_RELEASE_COM_OBJECT pDevices = NULL; if (FAILED(pEnum->EnumAudioEndpoints(eRender, DEVICE_STATE_ACTIVE | DEVICE_STATE_DISABLED, &pDevices))) return "EnumAudioEndpoints() failed"; uint32_t deviceCount; if (FAILED(pDevices->GetCount(&deviceCount))) return "pDevices->GetCount() failed"; for (uint32_t deviceIdx = 0; deviceIdx < deviceCount; ++deviceIdx) { IMMDevice* FF_AUTO_RELEASE_COM_OBJECT immDevice = NULL; if (FAILED(pDevices->Item(deviceIdx, &immDevice))) continue; LPWSTR FF_COTASK_AUTO_FREE immDeviceId = NULL; if (FAILED(immDevice->GetId(&immDeviceId))) continue; IPropertyStore* FF_AUTO_RELEASE_COM_OBJECT immPropStore; if (FAILED(immDevice->OpenPropertyStore(STGM_READ, &immPropStore))) continue; DWORD immState; if (FAILED(immDevice->GetState(&immState))) continue; FFSoundDevice* device = (FFSoundDevice*) ffListAdd(devices); device->main = wcscmp(mainDeviceId, immDeviceId) == 0; device->active = !!(immState & DEVICE_STATE_ACTIVE); device->volume = FF_SOUND_VOLUME_UNKNOWN; ffStrbufInitWS(&device->identifier, immDeviceId); ffStrbufInit(&device->name); ffStrbufInitStatic(&device->platformApi, "Core Audio APIs"); { PROPVARIANT __attribute__((__cleanup__(PropVariantClear))) friendlyName; PropVariantInit(&friendlyName); if (SUCCEEDED(immPropStore->GetValue(PKEY_Device_FriendlyName, &friendlyName))) ffStrbufSetWS(&device->name, friendlyName.pwszVal); } IAudioEndpointVolume* FF_AUTO_RELEASE_COM_OBJECT immEndpointVolume; if(SUCCEEDED(immDevice->Activate(IID_IAudioEndpointVolume, CLSCTX_ALL, NULL, (void**) &immEndpointVolume))) { BOOL muted; if (FAILED(immEndpointVolume->GetMute(&muted)) || !muted) { FLOAT volume; if (SUCCEEDED(immEndpointVolume->GetMasterVolumeLevelScalar(&volume))) device->volume = (uint8_t) (volume * 100 + 0.5); } } } return NULL; } ================================================ FILE: src/detection/swap/swap.h ================================================ #pragma once #include "fastfetch.h" #include "modules/swap/option.h" typedef struct FFSwapResult { FFstrbuf name; uint64_t bytesUsed; uint64_t bytesTotal; } FFSwapResult; const char* ffDetectSwap(FFlist* result /* List of FFSwapResult */); ================================================ FILE: src/detection/swap/swap_apple.c ================================================ #include "swap.h" #include #include const char* ffDetectSwap(FFlist* result) { struct xsw_usage xsw; size_t size = sizeof(xsw); if(sysctl((int[]){ CTL_VM, VM_SWAPUSAGE }, 2, &xsw, &size, NULL, 0) != 0) return "Failed to read vm.swapusage"; FFSwapResult* swap = ffListAdd(result); ffStrbufInitStatic(&swap->name, xsw.xsu_encrypted ? "Encrypted" : "Normal"); swap->bytesTotal = xsw.xsu_total; swap->bytesUsed = xsw.xsu_used; return NULL; } ================================================ FILE: src/detection/swap/swap_bsd.c ================================================ #include "swap.h" #include "common/sysctl.h" #include #include #include static void addSwapEntry(FFlist* result, struct xswdev* xsw, uint32_t pageSize) { if (xsw->xsw_nblks == 0) // DFBSD reports some /dev/wdog devices with nblks == 0 return; FFSwapResult* swap = ffListAdd(result); if (xsw->xsw_dev == NODEV) ffStrbufInitStatic(&swap->name, "[NFS]"); else ffStrbufInitF(&swap->name, "/dev/%s", devname(xsw->xsw_dev, S_IFCHR)); swap->bytesUsed = (uint64_t) xsw->xsw_used * pageSize; swap->bytesTotal = (uint64_t) xsw->xsw_nblks * pageSize; } #if __DragonFly__ const char* ffDetectSwap(FFlist* result) { struct xswdev xsws[32]; size_t size = sizeof(xsws); if (sysctlbyname("vm.swap_info_array", xsws, &size, NULL, 0) < 0) return "sysctlbyname(\"vm.swap_info_array\") failed"; uint32_t pageSize = instance.state.platform.sysinfo.pageSize; size_t count = size / sizeof(struct xswdev); if (count == 0) return NULL; if (xsws->xsw_version != XSWDEV_VERSION) return "xswdev version mismatch"; for (uint32_t i = 0; i < count; ++i) addSwapEntry(result, &xsws[i], pageSize); return NULL; } #elif __FreeBSD__ const char* ffDetectSwap(FFlist* result) { int mib[16]; size_t mibsize = ARRAY_SIZE(mib); if (sysctlnametomib("vm.swap_info", mib, &mibsize) < 0) return "sysctlnametomib(\"vm.swap_info\") failed"; uint32_t pageSize = instance.state.platform.sysinfo.pageSize; for (int n = 0; ; ++n) { mib[mibsize] = n; struct xswdev xsw; size_t size = sizeof(xsw); if (sysctl(mib, (uint32_t) (mibsize + 1), &xsw, &size, NULL, 0) < 0) break; if (xsw.xsw_version != XSWDEV_VERSION) return "xswdev version mismatch"; addSwapEntry(result, &xsw, pageSize); } return NULL; } #endif ================================================ FILE: src/detection/swap/swap_haiku.c ================================================ #include "swap.h" #include #include const char* ffDetectSwap(FFlist* result) { system_info info; if (get_system_info(&info) != B_OK) return "Error getting system info"; uint32_t pageSize = instance.state.platform.sysinfo.pageSize; FFSwapResult* swap = ffListAdd(result); ffStrbufInitStatic(&swap->name, "System"); void* kvms = load_driver_settings("virtual_memory"); // /boot/home/config/settings/kernel/drivers/virtual_memory if (kvms) { const char* swapAuto = get_driver_parameter(kvms, "swap_auto", NULL, NULL); if (swapAuto) ffStrbufSetStatic(&swap->name, swapAuto[0] == 'y' ? "Auto" : "Manual"); unload_driver_settings(kvms); } swap->bytesTotal = pageSize * (uint64_t) info.max_swap_pages; swap->bytesUsed = pageSize * (uint64_t) (info.max_swap_pages - info.free_swap_pages); return NULL; } ================================================ FILE: src/detection/swap/swap_linux.c ================================================ #include "swap.h" #include "common/io.h" #include "common/mallocHelper.h" #include static const char* detectByProcMeminfo(FFlist* result) { // For Android // Ref: #620 char buf[PROC_FILE_BUFFSIZ]; ssize_t nRead = ffReadFileData("/proc/meminfo", ARRAY_SIZE(buf) - 1, buf); if(nRead < 0) return "ffReadFileData(\"/proc/meminfo\", ARRAY_SIZE(buf)-1, buf)"; buf[nRead] = '\0'; uint64_t swapTotal = 0, swapFree = 0; char *token = NULL; if ((token = strstr(buf, "SwapTotal:")) != NULL) swapTotal = strtoul(token + strlen("SwapTotal:"), NULL, 10); if ((token = strstr(buf, "SwapFree:")) != NULL) swapFree = strtoul(token + strlen("SwapFree:"), NULL, 10); FFSwapResult* swap = ffListAdd(result); ffStrbufInitStatic(&swap->name, "Total"); swap->bytesTotal = swapTotal * 1024lu; swap->bytesUsed = (swapTotal - swapFree) * 1024lu; return NULL; } static const char* detectByProcSwaps(FFlist* result) { // Ref: #620 char buf[PROC_FILE_BUFFSIZ]; ssize_t nRead = ffReadFileData("/proc/swaps", ARRAY_SIZE(buf) - 1, buf); if(nRead <= 0) return "ffReadFileData(\"/proc/swaps\", ARRAY_SIZE(buf)-1, buf) failed"; buf[nRead] = '\0'; // Skip header char* line = memchr(buf, '\n', (size_t) nRead); while(line && *++line) { uint64_t total, used; char name[256]; if(sscanf(line, "%255s %*[^\t]%" SCNu64 "%" SCNu64, name, &total, &used) != 3) return "Invalid /proc/swaps format found"; uint32_t nameLen = (uint32_t) strnlen(name, sizeof(name)); FFSwapResult* swap = ffListAdd(result); ffStrbufInitA(&swap->name, nameLen); for (size_t i = 0; i < nameLen; ++i) { if(name[i] == '\\') { char octal[4] = { name[i + 1], name[i + 2], name[i + 3], '\0' }; ffStrbufAppendC(&swap->name, (char) strtol(octal, NULL, 8)); i += 3; } else ffStrbufAppendC(&swap->name, name[i]); } swap->bytesTotal = total * 1024u; swap->bytesUsed = used * 1024u; line = memchr(line, '\n', (size_t) (nRead - (line - buf))); } return NULL; } const char* ffDetectSwap(FFlist* result) { if (detectByProcSwaps(result) == NULL) return NULL; return detectByProcMeminfo(result); } ================================================ FILE: src/detection/swap/swap_nosupport.c ================================================ #include "swap.h" const char* ffDetectSwap(FFSwapResult* swap) { return "Not supported on this platform"; } ================================================ FILE: src/detection/swap/swap_obsd.c ================================================ #include "swap.h" #include "common/FFlist.h" #include "common/mallocHelper.h" #include #include #include #include const char* ffDetectSwap(FFlist* result) { int nswap = swapctl(SWAP_NSWAP, 0, 0); if (nswap < 0) return "swapctl(SWAP_NSWAP) failed"; if (nswap == 0) return NULL; FF_AUTO_FREE struct swapent* swdev = malloc((uint32_t) nswap * sizeof(*swdev)); if (swapctl(SWAP_STATS, swdev, nswap) < 0) return "swapctl(SWAP_STATS) failed"; for (int i = 0; i < nswap; i++) { if (swdev[i].se_flags & SWF_ENABLE) { FFSwapResult* swap = ffListAdd(result); ffStrbufInitS(&swap->name, swdev[i].se_path); swap->bytesUsed = (uint64_t) swdev[i].se_inuse * DEV_BSIZE; swap->bytesTotal = (uint64_t) swdev[i].se_nblks * DEV_BSIZE; } } return NULL; } ================================================ FILE: src/detection/swap/swap_sunos.c ================================================ #include "swap.h" #include #include #include #include enum { FFMaxNSwap = 8 }; const char* ffDetectSwap(FFlist* result) { char strings[FFMaxNSwap][PATH_MAX]; alignas(swaptbl_t) uint8_t buffer[sizeof(swaptbl_t) + sizeof(swapent_t) * (FFMaxNSwap - 1)] = {}; swaptbl_t* table = (swaptbl_t*) buffer; table->swt_n = FFMaxNSwap; for (int i = 0; i < FFMaxNSwap; ++i) table->swt_ent[i].ste_path = strings[i]; int size = swapctl(SC_LIST, table); if (size < 0) return "swapctl() failed"; uint32_t pageSize = instance.state.platform.sysinfo.pageSize; for (int i = 0; i < size; ++i) { FFSwapResult* swap = ffListAdd(result); ffStrbufInitS(&swap->name, table->swt_ent[i].ste_path); swap->bytesTotal = (uint64_t) table->swt_ent[i].ste_pages * pageSize; swap->bytesUsed = swap->bytesTotal - (uint64_t) table->swt_ent[i].ste_free * pageSize; } return NULL; } ================================================ FILE: src/detection/swap/swap_windows.c ================================================ #include "swap.h" #include "common/windows/unicode.h" #include #include #include #include const char* ffDetectSwap(FFlist* result) { alignas(SYSTEM_PAGEFILE_INFORMATION) uint8_t buffer[4096]; ULONG size = sizeof(buffer); SYSTEM_PAGEFILE_INFORMATION* pstart = (SYSTEM_PAGEFILE_INFORMATION*) buffer; if(!NT_SUCCESS(NtQuerySystemInformation(SystemPagefileInformation, pstart, size, &size))) return "NtQuerySystemInformation(SystemPagefileInformation, size) failed"; uint32_t pageSize = instance.state.platform.sysinfo.pageSize; for (SYSTEM_PAGEFILE_INFORMATION* current = pstart; ; current = (SYSTEM_PAGEFILE_INFORMATION*)((uint8_t*) current + current->NextEntryOffset)) { FFSwapResult* swap = ffListAdd(result); ffStrbufInitNWS(&swap->name, current->FileName.Length / sizeof(wchar_t), current->FileName.Buffer); if (ffStrbufStartsWithS(&swap->name, "\\??\\")) ffStrbufSubstrAfter(&swap->name, strlen("\\??\\") - 1); swap->bytesUsed = (uint64_t) current->TotalUsed * pageSize; swap->bytesTotal = (uint64_t) current->CurrentSize * pageSize; if (current->NextEntryOffset == 0) break; } return NULL; } ================================================ FILE: src/detection/terminalfont/terminalfont.c ================================================ #include "terminalfont.h" #include "common/io.h" #include "common/properties.h" #include "common/processing.h" #include "common/debug.h" #include "common/stringUtils.h" #include "detection/terminalshell/terminalshell.h" static void detectAlacritty(FFTerminalFontResult* terminalFont) { // Maybe using a toml parser to read the config file is better? // https://github.com/cktan/tomlc17 // Doc: https://alacritty.org/config-alacritty.html#s26 FF_STRBUF_AUTO_DESTROY fontNormal = ffStrbufCreate(); FF_STRBUF_AUTO_DESTROY fontFamily = ffStrbufCreate(); FF_STRBUF_AUTO_DESTROY fontStyle = ffStrbufCreate(); FF_STRBUF_AUTO_DESTROY fontSize = ffStrbufCreate(); do { FFpropquery fontQueryToml[] = { {"normal =", &fontNormal}, {"size =", &fontSize}, }; // alacritty parses config files in this order if(ffParsePropFileConfigValues("alacritty/alacritty.toml", 2, fontQueryToml)) break; if(ffParsePropFileConfigValues("alacritty.toml", 2, fontQueryToml)) break; if(ffParsePropFileConfigValues(".alacritty.toml", 2, fontQueryToml)) break; } while (false); if(fontNormal.length > 0) { // { family = "Fira Code", style = "Medium" } ffStrbufTrimSpace(&fontNormal); ffStrbufTrimRight(&fontNormal, '}'); ffStrbufTrimLeft(&fontNormal, '{'); ffStrbufTrimSpace(&fontNormal); // family = "Fira Code", style = "Medium" ffStrbufReplaceAllC(&fontNormal, ',', '\n'); // Assume no commas in font names ffParsePropLines(fontNormal.chars, "family =", &fontFamily); ffParsePropLines(fontNormal.chars, "style =", &fontStyle); } if (fontFamily.length == 0) { #if __APPLE__ ffStrbufSetStatic(&fontFamily, "Menlo"); #elif _WIN32 ffStrbufSetStatic(&fontFamily, "Consolas"); #else ffStrbufSetStatic(&fontFamily, "monospace"); #endif } if (fontStyle.length == 0) ffStrbufSetStatic(&fontStyle, "Regular"); if(fontSize.length == 0) ffStrbufSetStatic(&fontSize, "11.25"); ffFontInitMoveValues(&terminalFont->font, &fontFamily, &fontSize, &fontStyle); } static void detectGhostty(const FFstrbuf* exe, FFTerminalFontResult* terminalFont) { FF_DEBUG("detectGhostty: start"); FF_STRBUF_AUTO_DESTROY configPath = ffStrbufCreate(); FF_STRBUF_AUTO_DESTROY fontName = ffStrbufCreate(); FF_STRBUF_AUTO_DESTROY fontNameFallback = ffStrbufCreate(); FF_STRBUF_AUTO_DESTROY fontSize = ffStrbufCreate(); // Try ghostty +show-config first FF_STRBUF_AUTO_DESTROY buffer = ffStrbufCreate(); const char* error = ffProcessAppendStdOut(&buffer, (char* const[]){ exe->chars, "+show-config", NULL, }); if(error != NULL) { FF_DEBUG("`ghostty +show-config` failed: %s", error); return; } char* line = NULL; size_t len = 0; while (ffStrbufGetline(&line, &len, &buffer)) { if (!fontName.length || !fontNameFallback.length) { if (ffStrStartsWith(line, "font-family = ")) { FF_DEBUG("found %s", line); ffStrbufSetNS( !fontName.length ? &fontName : &fontNameFallback, (uint32_t) (len - strlen("font-family = ")), line + strlen("font-family = ")); continue; } } if (!fontSize.length) { if (ffStrStartsWith(line, "font-size = ")) { FF_DEBUG("found fallback %s", line); ffStrbufSetNS( &fontSize, (uint32_t) (len - strlen("font-size = ")), line + strlen("font-size = ")); continue; } } } if (fontName.length == 0) { ffStrbufAppendS(&fontName, "JetBrainsMono Nerd Font"); FF_DEBUG("using default family='%s'", fontName.chars); } if (fontSize.length == 0) { ffStrbufAppendS(&fontSize, #if __APPLE__ "13" #else "12" #endif ); FF_DEBUG("using default size='%s'", fontSize.chars); } ffFontInitValues(&terminalFont->font, fontName.chars, fontSize.chars); if (fontNameFallback.length > 0) { FF_DEBUG("applying fallback family='%s'", fontNameFallback.chars); ffFontInitValues(&terminalFont->fallback, fontNameFallback.chars, NULL); } FF_DEBUG("result family='%s' size='%s'%s", fontName.chars, fontSize.chars, fontNameFallback.length ? " (with fallback)" : ""); FF_DEBUG("detectGhostty: end"); } FF_MAYBE_UNUSED static void detectTTY(FFTerminalFontResult* terminalFont) { FF_STRBUF_AUTO_DESTROY fontName = ffStrbufCreate(); ffParsePropFile(FASTFETCH_TARGET_DIR_ETC"/vconsole.conf", "Font =", &fontName); if(fontName.length == 0) { ffStrbufAppendS(&fontName, "VGA default kernel font "); ffProcessAppendStdOut(&fontName, (char* const[]){ "showconsolefont", "--info", NULL }); ffStrbufTrimRight(&fontName, ' '); } if(fontName.length > 0) ffFontInitCopy(&terminalFont->font, fontName.chars); else ffStrbufAppendS(&terminalFont->error, "Couldn't find Font in "FASTFETCH_TARGET_DIR_ETC"/vconsole.conf"); } FF_MAYBE_UNUSED static bool detectKitty(const FFstrbuf* exe, FFTerminalFontResult* result) { FF_STRBUF_AUTO_DESTROY fontName = ffStrbufCreate(); FF_STRBUF_AUTO_DESTROY fontSize = ffStrbufCreate(); char fontHex[512] = "", sizeHex[512] = ""; // https://github.com/fastfetch-cli/fastfetch/discussions/1030#discussioncomment-9845233 if (ffGetTerminalResponse( "\eP+q6b697474792d71756572792d666f6e745f66616d696c79;6b697474792d71756572792d666f6e745f73697a65\e\\", // kitty-query-font_family;kitty-query-font_size 2, "\eP1+r%*[^=]=%511[^\e]\e\\\eP1+r%*[^=]=%511[^\e]\e\\", fontHex, sizeHex) == NULL && *fontHex && *sizeHex) { // decode hex string for (const char* p = fontHex; p[0] && p[1]; p += 2) { unsigned value; if (sscanf(p, "%2x", &value) == 1) ffStrbufAppendC(&fontName, (char) value); } for (const char* p = sizeHex; p[0] && p[1]; p += 2) { unsigned value; if (sscanf(p, "%2x", &value) == 1) ffStrbufAppendC(&fontSize, (char) value); } } else { FF_STRBUF_AUTO_DESTROY buf = ffStrbufCreate(); if(!ffProcessAppendStdOut(&buf, (char* const[]){ exe->chars, "+kitten", "query-terminal", NULL, })) { ffParsePropLines(buf.chars, "font_family: ", &fontName); ffParsePropLines(buf.chars, "font_size: ", &fontSize); } else { FFpropquery fontQuery[] = { {"font_family ", &fontName}, {"font_size ", &fontSize}, }; ffParsePropFileConfigValues("kitty/kitty.conf", 2, fontQuery); if(fontName.length == 0) ffStrbufSetS(&fontName, "monospace"); if(fontSize.length == 0) ffStrbufSetS(&fontSize, "11.0"); } } ffFontInitValues(&result->font, fontName.chars, fontSize.chars); return true; } static bool detectWezterm(const FFstrbuf* exe, FFTerminalFontResult* result) { FF_STRBUF_AUTO_DESTROY cli = ffStrbufCreateCopy(exe); ffStrbufSubstrBeforeLastC(&cli, '-'); #ifdef _WIN32 ffStrbufAppendS(&cli, ".exe"); #endif FF_STRBUF_AUTO_DESTROY fontName = ffStrbufCreate(); ffStrbufSetS(&result->error, ffProcessAppendStdOut(&fontName, (char* const[]){ cli.chars, "ls-fonts", "--text", "a", NULL })); if(result->error.length) return false; //LeftToRight // 0 a \u{61} x_adv=7 cells=1 glyph=a,180 wezterm.font("JetBrains Mono", {weight="Regular", stretch="Normal", style="Normal"}) // , BuiltIn ffStrbufSubstrAfterFirstC(&fontName, '"'); ffStrbufSubstrBeforeFirstC(&fontName, '"'); if(!fontName.length) return false; ffFontInitCopy(&result->font, fontName.chars); return true; } static bool detectTabby(FFTerminalFontResult* result) { FF_STRBUF_AUTO_DESTROY fontName = ffStrbufCreate(); FF_STRBUF_AUTO_DESTROY fontSize = ffStrbufCreate(); FFpropquery fontQuery[] = { {"font: ", &fontName}, {"fontSize: ", &fontSize}, }; if(!ffParsePropFileConfigValues("tabby/config.yaml", 2, fontQuery)) return false; if(fontName.length == 0) ffStrbufSetS(&fontName, "monospace"); if(fontSize.length == 0) ffStrbufSetS(&fontSize, "14"); ffFontInitValues(&result->font, fontName.chars, fontSize.chars); return true; } static bool detectContour(const FFstrbuf* exe, FFTerminalFontResult* result) { FF_STRBUF_AUTO_DESTROY buf = ffStrbufCreate(); if(ffProcessAppendStdOut(&buf, (char* const[]){ exe->chars, "font-locator", NULL })) { ffStrbufAppendS(&result->error, "`contour font-locator` failed"); return false; } //[error] Missing key .logging.enabled. Using default: false. //[error] ... //Matching fonts using : Fontconfig //Font description : (family=Sarasa Term SC Nerd weight=Regular slant=Roman spacing=Monospace, strict_spacing=yes) //Number of fonts found : 49 // path /usr/share/fonts/google-noto/NotoSansMono-Regular.ttf Regular Roman // path ... uint32_t index = ffStrbufFirstIndexS(&buf, "Font description : (family="); if(index >= buf.length) return false; index += (uint32_t) strlen("Font description : (family="); ffStrbufSubstrBefore(&buf, ffStrbufNextIndexS(&buf, index, " weight=")); ffFontInitCopy(&result->font, buf.chars + index); return true; } static bool detectRio(FFTerminalFontResult* terminalFont) { FF_STRBUF_AUTO_DESTROY fontName = ffStrbufCreate(); FF_STRBUF_AUTO_DESTROY fontSize = ffStrbufCreate(); FFpropquery fontQueryToml[] = { {"family =", &fontName}, {"size =", &fontSize}, }; ffParsePropFileConfigValues("rio/config.toml", 2, fontQueryToml); if(fontName.length == 0) ffStrbufAppendS(&fontName, "Cascadia Code"); if(fontSize.length == 0) ffStrbufAppendS(&fontSize, "18"); ffFontInitValues(&terminalFont->font, fontName.chars, fontSize.chars); return true; } bool ffDetectTerminalFontPlatform(const FFTerminalResult* terminal, FFTerminalFontResult* terminalFont); static bool detectTerminalFontCommon(const FFTerminalResult* terminal, FFTerminalFontResult* terminalFont) { if(ffStrbufStartsWithIgnCaseS(&terminal->processName, "alacritty")) detectAlacritty(terminalFont); else if(ffStrbufStartsWithIgnCaseS(&terminal->processName, "wezterm-gui")) detectWezterm(&terminal->exe, terminalFont); else if(ffStrbufStartsWithIgnCaseS(&terminal->processName, "tabby")) detectTabby(terminalFont); else if(ffStrbufStartsWithIgnCaseS(&terminal->processName, "contour")) detectContour(&terminal->exe, terminalFont); else if(ffStrbufStartsWithIgnCaseS(&terminal->processName, "ghostty")) detectGhostty(&terminal->exe, terminalFont); else if(ffStrbufStartsWithIgnCaseS(&terminal->processName, "rio")) detectRio(terminalFont); #ifndef _WIN32 else if(ffStrbufStartsWithIgnCaseS(&terminal->exe, "/dev/pts/")) ffStrbufAppendS(&terminalFont->error, "Terminal font detection is not supported on PTS"); else if(ffStrbufIgnCaseEqualS(&terminal->processName, "kitty")) detectKitty(&terminal->exe, terminalFont); else if(ffStrbufStartsWithIgnCaseS(&terminal->exe, "/dev/tty")) detectTTY(terminalFont); #endif else return false; return true; } bool ffDetectTerminalFont(FFTerminalFontResult* result) { const FFTerminalResult* terminal = ffDetectTerminal(); if(terminal->processName.length == 0) ffStrbufAppendS(&result->error, "Terminal font needs successful terminal detection"); else if(!detectTerminalFontCommon(terminal, result)) ffDetectTerminalFontPlatform(terminal, result); if(result->error.length == 0 && result->font.pretty.length == 0) ffStrbufAppendF(&result->error, "Unknown terminal: %s", terminal->processName.chars); return result->error.length == 0; } ================================================ FILE: src/detection/terminalfont/terminalfont.h ================================================ #pragma once #include "fastfetch.h" #include "common/font.h" #include "modules/font/option.h" typedef struct FFTerminalFontResult { FFstrbuf error; FFfont font; FFfont fallback; } FFTerminalFontResult; bool ffDetectTerminalFont(FFTerminalFontResult* result); ================================================ FILE: src/detection/terminalfont/terminalfont_android.c ================================================ #include "fastfetch.h" #include "terminalfont.h" #include "detection/terminalshell/terminalshell.h" #include "common/io.h" #ifdef FF_HAVE_FREETYPE #include "common/library.h" #include #include FT_FREETYPE_H #endif #define FF_TERMUX_FONT_PATH FASTFETCH_TARGET_DIR_HOME "/.termux/font.ttf" const char* detectTermux(FFTerminalFontResult* terminalFont) { if(!ffPathExists(FF_TERMUX_FONT_PATH, FF_PATHTYPE_FILE)) { ffFontInitCopy(&terminalFont->font, "monospace"); return NULL; } #ifdef FF_HAVE_FREETYPE FF_LIBRARY_LOAD_MESSAGE(freetype, "libfreetype" FF_LIBRARY_EXTENSION, 2) FF_LIBRARY_LOAD_SYMBOL_MESSAGE(freetype, FT_Init_FreeType); FF_LIBRARY_LOAD_SYMBOL_MESSAGE(freetype, FT_New_Face); FF_LIBRARY_LOAD_SYMBOL_MESSAGE(freetype, FT_Done_Face); FF_LIBRARY_LOAD_SYMBOL_MESSAGE(freetype, FT_Done_FreeType); FT_Library library = NULL; FT_Face face = NULL; const char* error = NULL; if(ffFT_Init_FreeType(&library)) { error = "FT_Init_FreeType() failed"; goto exit; } if(ffFT_New_Face(library, FF_TERMUX_FONT_PATH, 0, &face)) { error = "FT_NEW_Face(" FF_TERMUX_FONT_PATH ") failed"; goto exit; } ffFontInitCopy(&terminalFont->font, face->family_name); exit: if(face) ffFT_Done_Face(face); if(library) ffFT_Done_FreeType(library); return error; #else FF_UNUSED(terminalFont); return "Fastfetch was built without freetype2 support"; #endif } bool ffDetectTerminalFontPlatform(const FFTerminalResult* terminal, FFTerminalFontResult* terminalFont) { if(ffStrbufEqualS(&terminal->processName, "com.termux")) ffStrbufSetS(&terminalFont->error, detectTermux(terminalFont)); else { bool ffDetectTerminalFontPlatformLinux(const FFTerminalResult* terminal, FFTerminalFontResult* terminalFont); return ffDetectTerminalFontPlatformLinux(terminal, terminalFont); } return true; } ================================================ FILE: src/detection/terminalfont/terminalfont_apple.m ================================================ #include "terminalfont.h" #include "common/font.h" #include "detection/terminalshell/terminalshell.h" #include "common/apple/osascript.h" #include #include #import static void detectIterm2(FFTerminalFontResult* terminalFont) { const char* profile = getenv("ITERM_PROFILE"); if (profile == NULL) { ffStrbufAppendS(&terminalFont->error, "environment variable ITERM_PROFILE not set"); return; } NSError* error; NSString* fileName = [NSString stringWithFormat:@"file://%s/Library/Preferences/com.googlecode.iterm2.plist", instance.state.platform.homeDir.chars]; NSDictionary* dict = [NSDictionary dictionaryWithContentsOfURL:[NSURL URLWithString:fileName] error:&error]; if(error) { ffStrbufAppendS(&terminalFont->error, error.localizedDescription.UTF8String); return; } for(NSDictionary* bookmark in dict[@"New Bookmarks"]) { if(![bookmark[@"Name"] isEqualToString:@(profile)]) continue; NSString* normalFont = bookmark[@"Normal Font"]; if(!normalFont) { ffStrbufAppendF(&terminalFont->error, "`Normal Font` key in profile `%s` doesn't exist", profile); return; } ffFontInitWithSpace(&terminalFont->font, normalFont.UTF8String); NSNumber* useNonAsciiFont = bookmark[@"Use Non-ASCII Font"]; if(useNonAsciiFont.boolValue) { NSString* nonAsciiFont = bookmark[@"Non Ascii Font"]; if (nonAsciiFont) ffFontInitWithSpace(&terminalFont->fallback, nonAsciiFont.UTF8String); } return; } ffStrbufAppendF(&terminalFont->error, "find profile `%s` bookmark failed", profile); } static void detectAppleTerminal(FFTerminalFontResult* terminalFont) { FF_STRBUF_AUTO_DESTROY font = ffStrbufCreate(); ffOsascript("tell application \"Terminal\" to font name of window frontmost & \" \" & font size of window frontmost", &font); if(font.length == 0) { ffStrbufAppendS(&terminalFont->error, "executing osascript failed"); return; } ffFontInitWithSpace(&terminalFont->font, font.chars); } static void detectWarpTerminal(FFTerminalFontResult* terminalFont) { NSError* error; NSString* fileName = [NSString stringWithFormat:@"file://%s/Library/Preferences/dev.warp.Warp-Stable.plist", instance.state.platform.homeDir.chars]; NSDictionary* dict = [NSDictionary dictionaryWithContentsOfURL:[NSURL URLWithString:fileName] error:&error]; if(error) { ffStrbufAppendS(&terminalFont->error, error.localizedDescription.UTF8String); return; } NSString* fontName = dict[@"FontName"]; if(!fontName) fontName = @"Hack"; else fontName = [fontName stringByTrimmingCharactersInSet:[NSCharacterSet characterSetWithCharactersInString:@"\""]]; NSString* fontSize = dict[@"FontSize"]; if(!fontSize) fontSize = @"13"; ffFontInitValues(&terminalFont->font, fontName.UTF8String, fontSize.UTF8String); } bool ffDetectTerminalFontPlatform(const FFTerminalResult* terminal, FFTerminalFontResult* terminalFont) { if(ffStrbufIgnCaseEqualS(&terminal->processName, "iterm.app") || ffStrbufStartsWithIgnCaseS(&terminal->processName, "iTermServer-")) detectIterm2(terminalFont); else if(ffStrbufIgnCaseEqualS(&terminal->processName, "Apple_Terminal")) detectAppleTerminal(terminalFont); else if(ffStrbufIgnCaseEqualS(&terminal->processName, "WarpTerminal")) detectWarpTerminal(terminalFont); else return false; return true; } ================================================ FILE: src/detection/terminalfont/terminalfont_linux.c ================================================ #include "common/font.h" #include "terminalfont.h" #include "common/settings.h" #include "common/properties.h" #include "common/parsing.h" #include "common/io.h" #include "common/processing.h" #include "common/mallocHelper.h" #include "common/stringUtils.h" #include "common/binary.h" #include "detection/terminalshell/terminalshell.h" #include "detection/displayserver/displayserver.h" static const char* getSystemMonospaceFont(void) { const FFDisplayServerResult* wmde = ffConnectDisplayServer(); if(ffStrbufIgnCaseEqualS(&wmde->dePrettyName, "Cinnamon")) { const char* systemMonospaceFont = ffSettingsGetGnome("/org/cinnamon/desktop/interface/monospace-font-name", "org.cinnamon.desktop.interface", NULL, "monospace-font-name", FF_VARIANT_TYPE_STRING).strValue; if(ffStrSet(systemMonospaceFont)) return systemMonospaceFont; } else if(ffStrbufIgnCaseEqualS(&wmde->dePrettyName, "Mate")) { const char* systemMonospaceFont = ffSettingsGetGnome("/org/mate/interface/monospace-font-name", "org.mate.interface", NULL, "monospace-font-name", FF_VARIANT_TYPE_STRING).strValue; if(ffStrSet(systemMonospaceFont)) return systemMonospaceFont; } return ffSettingsGetGnome("/org/gnome/desktop/interface/monospace-font-name", "org.gnome.desktop.interface", NULL, "monospace-font-name", FF_VARIANT_TYPE_STRING).strValue; } static void detectKgx(FFTerminalFontResult* terminalFont) { // kgx (gnome console) doesn't support profiles if(!ffSettingsGetGnome("/org/gnome/Console/use-system-font", "org.gnome.Console", NULL, "use-system-font", FF_VARIANT_TYPE_BOOL).boolValue) { FF_AUTO_FREE const char* fontName = ffSettingsGetGnome("/org/gnome/Console/custom-font", "org.gnome.Console", NULL, "custom-font", FF_VARIANT_TYPE_STRING).strValue; if(ffStrSet(fontName)) ffFontInitPango(&terminalFont->font, fontName); else ffStrbufAppendF(&terminalFont->error, "Couldn't get terminal font from GSettings (org.gnome.Console::custom-font)"); } else { FF_AUTO_FREE const char* fontName = getSystemMonospaceFont(); if(ffStrSet(fontName)) ffFontInitPango(&terminalFont->font, fontName); else ffStrbufAppendS(&terminalFont->error, "Couldn't get system monospace font name from GSettings / DConf"); } } static void detectPtyxis(FFTerminalFontResult* terminalFont) { if(!ffSettingsGetGnome("/org/gnome/Ptyxis/use-system-font", "org.gnome.Ptyxis", NULL, "use-system-font", FF_VARIANT_TYPE_BOOL).boolValue) { FF_AUTO_FREE const char* fontName = ffSettingsGetGnome("/org/gnome/Ptyxis/font-name", "org.gnome.Ptyxis", NULL, "font-name", FF_VARIANT_TYPE_STRING).strValue; if(ffStrSet(fontName)) ffFontInitPango(&terminalFont->font, fontName); else ffStrbufAppendF(&terminalFont->error, "Couldn't get terminal font from GSettings (org.gnome.Ptyxis::font-name)"); } else { FF_AUTO_FREE const char* fontName = getSystemMonospaceFont(); if(ffStrSet(fontName)) ffFontInitPango(&terminalFont->font, fontName); else ffStrbufAppendS(&terminalFont->error, "Couldn't get system monospace font name from GSettings / DConf"); } } static void detectFromGSettings(const char* profilePath, const char* profileList, const char* profile, const char* defaultProfileKey, FFTerminalFontResult* terminalFont) { FF_AUTO_FREE const char* defaultProfile = ffSettingsGetGSettings(profileList, NULL, defaultProfileKey, FF_VARIANT_TYPE_STRING).strValue; if(!ffStrSet(defaultProfile)) { ffStrbufAppendF(&terminalFont->error, "Could not get default profile from gsettings: %s", profileList); return; } FF_STRBUF_AUTO_DESTROY path = ffStrbufCreateA(128); ffStrbufAppendS(&path, profilePath); ffStrbufAppendS(&path, defaultProfile); ffStrbufAppendC(&path, '/'); if(!ffSettingsGetGSettings(profile, path.chars, "use-system-font", FF_VARIANT_TYPE_BOOL).boolValue) { FF_AUTO_FREE const char* fontName = ffSettingsGetGSettings(profile, path.chars, "font", FF_VARIANT_TYPE_STRING).strValue; if(ffStrSet(fontName)) ffFontInitPango(&terminalFont->font, fontName); else ffStrbufAppendF(&terminalFont->error, "Couldn't get terminal font from GSettings (%s::%s::font)", profile, path.chars); } else { FF_AUTO_FREE const char* fontName = getSystemMonospaceFont(); if(ffStrSet(fontName)) ffFontInitPango(&terminalFont->font, fontName); else ffStrbufAppendS(&terminalFont->error, "Couldn't get system monospace font name from GSettings / DConf"); } } static void detectFromConfigFile(const char* configFile, const char* start, FFTerminalFontResult* terminalFont) { FF_STRBUF_AUTO_DESTROY fontName = ffStrbufCreate(); ffParsePropFileConfig(configFile, start, &fontName); if(fontName.length == 0) ffStrbufAppendF(&terminalFont->error, "Couldn't find %s in .config/%s", start, configFile); else ffFontInitPango(&terminalFont->font, fontName.chars); } static void detectKonsole(FFTerminalFontResult* terminalFont, const char* rcFile) { FF_STRBUF_AUTO_DESTROY profile = ffStrbufCreate(); if(!ffParsePropFileConfig(rcFile, "DefaultProfile =", &profile)) { ffStrbufAppendF(&terminalFont->error, "Configuration \".config/%s\" doesn't exist", rcFile); return; } if(profile.length == 0) { ffStrbufAppendS(&terminalFont->error, "Built-in profile is used"); return; } FF_STRBUF_AUTO_DESTROY profilePath = ffStrbufCreateA(32); ffStrbufAppendS(&profilePath, "konsole/"); ffStrbufAppend(&profilePath, &profile); FF_STRBUF_AUTO_DESTROY fontName = ffStrbufCreate(); ffParsePropFileData(profilePath.chars, "Font =", &fontName); if(fontName.length == 0) ffStrbufAppendF(&terminalFont->error, "Couldn't find \"Font=%%[^\\n]\" in \"%s\"", profilePath.chars); else ffFontInitQt(&terminalFont->font, fontName.chars); } static void detectXFCETerminal(FFTerminalFontResult* terminalFont) { FF_STRBUF_AUTO_DESTROY useSysFont = ffStrbufCreate(); FF_STRBUF_AUTO_DESTROY fontName = ffStrbufCreate(); const char* path = "xfce4/xfconf/xfce-perchannel-xml/xfce4-terminal.xml"; bool configFound = ffParsePropFileConfigValues(path, 2, (FFpropquery[]) { {"error, "Couldn't find FontName in %s", path); else ffFontInitPango(&terminalFont->font, fontName.chars); } else { const char* systemFontName = ffSettingsGetXFConf("xsettings", "/Gtk/MonospaceFontName", FF_VARIANT_TYPE_STRING).strValue; if(ffStrSet(systemFontName)) ffFontInitPango(&terminalFont->font, systemFontName); else ffStrbufAppendS(&terminalFont->error, "Couldn't find xsettings::/Gtk/MonospaceFontName in XFConf"); } } static void detectDeepinTerminal(FFTerminalFontResult* terminalFont) { FF_STRBUF_AUTO_DESTROY fontName = ffStrbufCreate(); FF_STRBUF_AUTO_DESTROY fontSize = ffStrbufCreate(); FF_STRBUF_AUTO_DESTROY profile = ffStrbufCreateA(64); ffSearchUserConfigFile(&instance.state.platform.configDirs, "deepin/deepin-terminal/config.conf", &profile); FILE* file = fopen(profile.chars, "r"); if(file) { char* line = NULL; size_t len = 0; for(int count = 0; getline(&line, &len, file) != -1 && count < 2;) { if(ffStrEquals(line, "[basic.interface.font]\n")) { if(getline(&line, &len, file) != -1) ffParsePropLine(line, "value=", &fontName); ++count; } else if(ffStrEquals(line, "[basic.interface.font_size]\n")) { if(getline(&line, &len, file) != -1) ffParsePropLine(line, "value=", &fontSize); ++count; } } free(line); fclose(file); } if(fontName.length == 0) ffStrbufAppendS(&fontName, "Noto Sans Mono"); if(fontSize.length == 0) ffStrbufAppendS(&fontSize, "11"); ffFontInitValues(&terminalFont->font, fontName.chars, fontSize.chars); } static void detectFootTerminal(FFTerminalFontResult* terminalFont) { FF_STRBUF_AUTO_DESTROY font = ffStrbufCreate(); if (!ffParsePropFileConfig("foot/foot.ini", "font=", &font) || !ffStrSet(font.chars)) { ffFontInitValues(&terminalFont->font, "monospace", "8"); return; } //Sarasa Term SC Nerd:size=8 uint32_t colon = ffStrbufFirstIndexC(&font, ':'); if(colon == font.length) { ffFontInitValues(&terminalFont->font, font.chars, "8"); return; } uint32_t equal = ffStrbufNextIndexS(&font, colon, "size="); font.chars[colon] = '\0'; if (equal == font.length) { ffFontInitValues(&terminalFont->font, font.chars, "8"); return; } uint32_t size = equal + (uint32_t) strlen("size="); uint32_t comma = ffStrbufNextIndexC(&font, size, ','); if (comma < font.length) font.chars[comma] = '\0'; ffFontInitValues(&terminalFont->font, font.chars, &font.chars[size]); if (comma < font.length) ffFontInitValues(&terminalFont->fallback, &font.chars[comma + 1], NULL); } static void detectQTerminal(FFTerminalFontResult* terminalFont) { FF_STRBUF_AUTO_DESTROY fontName = ffStrbufCreate(); FF_STRBUF_AUTO_DESTROY fontSize = ffStrbufCreate(); ffParsePropFileConfigValues("qterminal.org/qterminal.ini", 2, (FFpropquery[]) { {"fontFamily=", &fontName}, {"fontSize=", &fontSize}, }); if (fontName.length == 0) ffStrbufAppendS(&fontName, "monospace"); if (fontSize.length == 0) ffStrbufAppendS(&fontSize, "12"); ffFontInitValues(&terminalFont->font, fontName.chars, fontSize.chars); } static void detectXterm(FFTerminalFontResult* terminalFont) { FF_STRBUF_AUTO_DESTROY fontName = ffStrbufCreate(); FF_STRBUF_AUTO_DESTROY fontSize = ffStrbufCreate(); ffParsePropFileHomeValues(".Xresources", 2, (FFpropquery[]) { {"xterm*faceName:", &fontName}, {"xterm*faceSize:", &fontSize}, }); if (fontName.length == 0) { ffParsePropFileHomeValues(".Xresources", 2, (FFpropquery[]) { {"xterm.vt100.faceName:", &fontName}, {"xterm.vt100.faceSize:", &fontSize}, }); } if (fontName.length == 0) ffStrbufAppendS(&fontName, "fixed"); if (fontSize.length == 0) ffStrbufAppendS(&fontSize, "8.0"); ffFontInitValues(&terminalFont->font, fontName.chars, fontSize.chars); } static bool extractStTermFont(const char* str, FF_MAYBE_UNUSED uint32_t len, void* userdata) { if (!ffStrContains(str, "size=")) return true; ffStrbufSetNS((FFstrbuf*) userdata, len, str); return false; } static void detectSt(FFTerminalFontResult* terminalFont, const FFTerminalResult* terminal) { FF_STRBUF_AUTO_DESTROY size = ffStrbufCreateF("/proc/%u/cmdline", terminal->pid); FF_STRBUF_AUTO_DESTROY font = ffStrbufCreate(); if (!ffAppendFileBuffer(size.chars, &font)) { ffStrbufAppendF(&terminalFont->error, "Failed to open %s", size.chars); return; } const char* p = memmem(font.chars, font.length, "\0-f", sizeof("\0-f")); // find parameter of `-f` if (p) { // st was executed with `-f` parameter ffStrbufSubstrAfter(&font, (uint32_t) (p + (sizeof("\0-f") - 1) - font.chars)); ffStrbufRecalculateLength(&font); } else { ffStrbufClear(&font); const char* error = ffBinaryExtractStrings(terminal->exePath.chars, extractStTermFont, &font, (uint32_t) strlen("size=0")); if (error) { ffStrbufAppendS(&terminalFont->error, error); return; } if (font.length == 0) { ffStrbufAppendS(&terminalFont->error, "No font config found in st binary"); return; } } // JetBrainsMono Nerd Font Mono:pixelsize=12:antialias=true:autohint=true uint32_t index = ffStrbufFirstIndexC(&font, ':'); if (index != font.length) { uint32_t sIndex = ffStrbufNextIndexS(&font, index + 1, "size="); if (sIndex != font.length) { sIndex += (uint32_t) strlen("size="); uint32_t sIndexEnd = ffStrbufNextIndexC(&font, sIndex, ':'); ffStrbufSetNS(&size, sIndexEnd - sIndex, font.chars + sIndex); } ffStrbufSubstrBefore(&font, index); } else ffStrbufClear(&size); ffFontInitValues(&terminalFont->font, font.chars, size.chars); } static void detectWarp(FFTerminalFontResult* terminalFont) { FF_STRBUF_AUTO_DESTROY baseDir = ffStrbufCreateA(64); FF_LIST_FOR_EACH(FFstrbuf, dirPrefix, instance.state.platform.configDirs) { //We need to copy the dir each time, because it used by multiple threads, so we can't directly write to it. ffStrbufSet(&baseDir, dirPrefix); ffStrbufAppendS(&baseDir, "warp-terminal/user_preferences.json"); yyjson_doc* doc = yyjson_read_file(baseDir.chars, YYJSON_READ_INSITU | YYJSON_READ_ALLOW_TRAILING_COMMAS | YYJSON_READ_ALLOW_COMMENTS, NULL, NULL); if (!doc) continue; yyjson_val* prefs = yyjson_obj_get(yyjson_doc_get_root(doc), "prefs"); if (yyjson_is_obj(prefs)) { const char* fontName = yyjson_get_str(yyjson_obj_get(prefs, "FontName")); if (!fontName) fontName = "Hack"; const char* fontSize = yyjson_get_str(yyjson_obj_get(prefs, "FontSize")); if (!fontSize) fontSize = "13"; ffFontInitValues(&terminalFont->font, fontName, fontSize); } yyjson_doc_free(doc); return; } } static void detectTerminator(FFTerminalFontResult* result) { FF_STRBUF_AUTO_DESTROY useSystemFont = ffStrbufCreate(); FF_STRBUF_AUTO_DESTROY fontName = ffStrbufCreate(); if(!ffParsePropFileConfigValues("terminator/config", 2, (FFpropquery[]) { {"use_system_font =", &useSystemFont}, {"font =", &fontName}, }) || ffStrbufIgnCaseEqualS(&useSystemFont, "True")) { FF_AUTO_FREE const char* fontName = getSystemMonospaceFont(); if(ffStrSet(fontName)) ffFontInitPango(&result->font, fontName); else ffStrbufAppendS(&result->error, "Couldn't get system monospace font name from GSettings / DConf"); return; } if(fontName.length == 0) ffFontInitValues(&result->font, "Mono", "10"); else ffFontInitPango(&result->font, fontName.chars); } static void detectWestonTerminal(FFTerminalFontResult* terminalFont) { FF_STRBUF_AUTO_DESTROY font = ffStrbufCreate(); FF_STRBUF_AUTO_DESTROY size = ffStrbufCreate(); ffParsePropFileConfigValues("weston.ini", 2, (FFpropquery[]) { {"font=", &font}, {"font-size=", &size}, }); if (!font.length) ffStrbufSetStatic(&font, "DejaVu Sans Mono"); if (!size.length) ffStrbufSetStatic(&size, "14"); ffFontInitValues(&terminalFont->font, font.chars, size.chars); } static void detectUrxvt(FFTerminalFontResult* terminalFont) { FF_STRBUF_AUTO_DESTROY buffer = ffStrbufCreate(); if (!(ffParsePropFileHomeValues(".Xresources", 1, (FFpropquery[]) { {"URxvt.font:", &buffer}, }) || ffParsePropFileHomeValues(".Xdefaults", 1, (FFpropquery[]) { {"URxvt.font:", &buffer}, }))) { ffStrbufAppendS(&terminalFont->error, "Could not find URxvt.font in .Xresources or .Xdefaults"); return; } uint32_t index = 0; char* line = NULL; size_t len = 0; while (ffStrbufGetdelim(&line, &len, ',', &buffer)) { FFfont* font = index == 0 ? &terminalFont->font : &terminalFont->fallback; if (line[0] == '-') ffFontInitXlfd(font, line); else if (ffStrStartsWith(line, "xft:")) ffFontInitXft(font, line + 4); else { ffStrbufAppendF(&terminalFont->error, "Unknown URxvt font format: %s", line); continue; } index++; if (index > 1) break; } } static bool detectCosmicTerm(FFTerminalFontResult* terminalFont) { FF_STRBUF_AUTO_DESTROY fontName = ffStrbufCreate(); FF_STRBUF_AUTO_DESTROY fontSize = ffStrbufCreate(); FF_STRBUF_AUTO_DESTROY path = ffStrbufCreateCopy(&instance.state.platform.homeDir); ffStrbufAppendS(&path, ".config/cosmic/com.system76.CosmicTerm/v1/"); uint32_t baseLen = path.length; ffStrbufAppendS(&path, "font_name"); ffReadFileBuffer(path.chars, &fontName); ffStrbufTrim(&path, '"'); ffStrbufSubstrBefore(&path, baseLen); if (fontName.length == 0) ffStrbufSetStatic(&fontName, "Noto Sans Mono"); ffStrbufAppendS(&path, "font_size"); if (ffReadFileBuffer(path.chars, &fontSize)) ffStrbufAppendS(&fontSize, "px"); ffStrbufSubstrBefore(&path, baseLen); if (fontSize.length == 0) ffStrbufSetStatic(&fontSize, "14px"); ffFontInitValues(&terminalFont->font, fontName.chars, fontSize.chars); return true; } #ifdef __HAIKU__ static void detectHaikuTerminal(FFTerminalFontResult* terminalFont) { FF_STRBUF_AUTO_DESTROY font = ffStrbufCreate(); FF_STRBUF_AUTO_DESTROY size = ffStrbufCreate(); ffParsePropFileConfigValues("Terminal/Default", 2, (FFpropquery[]) { {"\"Half Font Family\" , ", &font}, {"\"Half Font Size\" , ", &size}, }); if (!font.length) ffStrbufSetStatic(&font, "Noto Sans Mono"); if (!size.length) ffStrbufSetStatic(&size, "12"); ffFontInitValues(&terminalFont->font, font.chars, size.chars); } #endif bool #ifdef __ANDROID__ ffDetectTerminalFontPlatformLinux #else ffDetectTerminalFontPlatform #endif (const FFTerminalResult* terminal, FFTerminalFontResult* terminalFont) { if(ffStrbufIgnCaseEqualS(&terminal->processName, "konsole")) detectKonsole(terminalFont, "konsolerc"); else if(ffStrbufIgnCaseEqualS(&terminal->processName, "yakuake")) detectKonsole(terminalFont, "yakuakerc"); else if(ffStrbufIgnCaseEqualS(&terminal->processName, "xfce4-terminal")) detectXFCETerminal(terminalFont); else if(ffStrbufIgnCaseEqualS(&terminal->processName, "lxterminal")) detectFromConfigFile("lxterminal/lxterminal.conf", "fontname =", terminalFont); else if(ffStrbufIgnCaseEqualS(&terminal->processName, "tilix")) detectFromGSettings("/com/gexperts/Tilix/profiles/", "com.gexperts.Tilix.ProfilesList", "com.gexperts.Tilix.Profile", "default", terminalFont); else if(ffStrbufStartsWithIgnCaseS(&terminal->processName, "gnome-terminal")) detectFromGSettings("/org/gnome/terminal/legacy/profiles:/:", "org.gnome.Terminal.ProfilesList", "org.gnome.Terminal.Legacy.Profile", "default", terminalFont); else if(ffStrbufStartsWithIgnCaseS(&terminal->processName, "ptyxis-agent")) detectPtyxis(terminalFont); else if(ffStrbufIgnCaseEqualS(&terminal->processName, "kgx")) detectKgx(terminalFont); else if(ffStrbufIgnCaseEqualS(&terminal->processName, "mate-terminal")) detectFromGSettings("/org/mate/terminal/profiles/", "org.mate.terminal.global", "org.mate.terminal.profile", "default-profile", terminalFont); else if(ffStrbufIgnCaseEqualS(&terminal->processName, "deepin-terminal")) detectDeepinTerminal(terminalFont); else if(ffStrbufIgnCaseEqualS(&terminal->processName, "foot")) detectFootTerminal(terminalFont); else if(ffStrbufIgnCaseEqualS(&terminal->processName, "qterminal")) detectQTerminal(terminalFont); else if(ffStrbufIgnCaseEqualS(&terminal->processName, "xterm")) detectXterm(terminalFont); else if(ffStrbufIgnCaseEqualS(&terminal->processName, "st")) detectSt(terminalFont, terminal); else if(ffStrbufIgnCaseEqualS(&terminal->processName, "warp")) detectWarp(terminalFont); else if(ffStrbufIgnCaseEqualS(&terminal->processName, "weston-terminal")) detectWestonTerminal(terminalFont); else if(ffStrbufStartsWithIgnCaseS(&terminal->processName, "terminator")) detectTerminator(terminalFont); else if(ffStrbufStartsWithIgnCaseS(&terminal->processName, "sakura")) detectFromConfigFile("sakura/sakura.conf", "font=", terminalFont); else if(ffStrbufStartsWithIgnCaseS(&terminal->processName, "cosmic-term")) detectCosmicTerm(terminalFont); #ifdef __HAIKU__ else if(ffStrbufStartsWithIgnCaseS(&terminal->processName, "Terminal")) detectHaikuTerminal(terminalFont); #endif else if(ffStrbufStartsWithIgnCaseS(&terminal->processName, "termite")) detectFromConfigFile("termite/config", "font =", terminalFont); else if(ffStrbufIgnCaseEqualS(&terminal->processName, "rxvt") || ffStrbufIgnCaseEqualS(&terminal->processName, "urxvt") || ffStrbufIgnCaseEqualS(&terminal->processName, "urxvtd")) detectUrxvt(terminalFont); else return false; return true; } ================================================ FILE: src/detection/terminalfont/terminalfont_windows.c ================================================ #include "common/library.h" #include "common/io.h" #include "common/path.h" #include "common/processing.h" #include "common/properties.h" #include "common/windows/unicode.h" #include "common/windows/registry.h" #include "common/stringUtils.h" #include "detection/terminalshell/terminalshell.h" #include "terminalfont.h" #include #include #include static const char* detectWTProfile(yyjson_val* profile, FFstrbuf* name, double* size) { yyjson_val* font = yyjson_obj_get(profile, "font"); if (!font) return "yyjson_obj_get(profile, \"font\"); failed"; if (!yyjson_is_obj(font)) return "yyjson_is_obj(font) returns false"; if (name->length == 0) { ffStrbufAppendJsonVal(name, yyjson_obj_get(font, "face")); } if (*size < 0) { yyjson_val* psize = yyjson_obj_get(font, "size"); if (yyjson_is_num(psize)) *size = unsafe_yyjson_get_num(psize); } return NULL; } static inline void wrapYyjsonFree(yyjson_doc** doc) { assert(doc); if (*doc) yyjson_doc_free(*doc); } static const char* detectFromWTImpl(FFstrbuf* content, FFstrbuf* name, double* size) { yyjson_doc* __attribute__((__cleanup__(wrapYyjsonFree))) doc = yyjson_read_opts(content->chars, content->length, YYJSON_READ_ALLOW_COMMENTS | YYJSON_READ_ALLOW_TRAILING_COMMAS, NULL, NULL); if (!doc) return "Failed to parse WT JSON config file"; yyjson_val* const root = yyjson_doc_get_root(doc); assert(root); yyjson_val* profiles = yyjson_obj_get(root, "profiles"); if (!profiles) return "yyjson_obj_get(root, \"profiles\") failed"; FF_STRBUF_AUTO_DESTROY wtProfileId = ffStrbufCreateS(getenv("WT_PROFILE_ID")); ffStrbufTrim(&wtProfileId, '\''); if (wtProfileId.length > 0) { yyjson_val* list = yyjson_obj_get(profiles, "list"); if (yyjson_is_arr(list)) { yyjson_val* profile; size_t idx, max; yyjson_arr_foreach(list, idx, max, profile) { yyjson_val* guid = yyjson_obj_get(profile, "guid"); if(ffStrbufEqualS(&wtProfileId, yyjson_get_str(guid))) { detectWTProfile(profile, name, size); break; } } } } yyjson_val* defaults = yyjson_obj_get(profiles, "defaults"); if (defaults) detectWTProfile(defaults, name, size); if(name->length == 0) ffStrbufSetS(name, "Cascadia Mono"); if(*size < 0) *size = 12; return NULL; } static void detectFromWindowsTerminal(const FFstrbuf* terminalExe, FFTerminalFontResult* terminalFont) { //https://learn.microsoft.com/en-us/windows/terminal/install#settings-json-file FF_STRBUF_AUTO_DESTROY json = ffStrbufCreate(); const char* error = NULL; if(terminalExe && ffIsAbsolutePath(terminalExe->chars)) { FF_STRBUF_AUTO_DESTROY jsonPath = ffStrbufCreateA(MAX_PATH); ffStrbufAppendNS(&jsonPath, ffStrbufLastIndexC(terminalExe, '\\') + 1, terminalExe->chars); ffStrbufAppendS(&jsonPath, ".portable"); if(ffPathExists(jsonPath.chars, FF_PATHTYPE_ANY)) { ffStrbufSubstrBefore(&jsonPath, jsonPath.length - strlen(".portable")); ffStrbufAppendS(&jsonPath, "settings\\settings.json"); if(!ffAppendFileBuffer(jsonPath.chars, &json)) error = "Error reading Windows Terminal portable settings JSON file"; } else { PWSTR localAppDataW = NULL; if(SUCCEEDED(SHGetKnownFolderPath(&FOLDERID_LocalAppData, KF_FLAG_DEFAULT, NULL, &localAppDataW))) { ffStrbufSetWS(&jsonPath, localAppDataW); CoTaskMemFree(localAppDataW); if(ffStrbufContainIgnCaseS(terminalExe, "_8wekyb3d8bbwe\\")) { // Microsoft Store version if(ffStrbufContainIgnCaseS(terminalExe, ".WindowsTerminalPreview_")) { // Preview version ffStrbufAppendS(&jsonPath, "\\Packages\\Microsoft.WindowsTerminalPreview_8wekyb3d8bbwe\\LocalState\\settings.json"); if(!ffAppendFileBuffer(jsonPath.chars, &json)) error = "Error reading Windows Terminal Preview settings JSON file"; } else { // Stable version ffStrbufAppendS(&jsonPath, "\\Packages\\Microsoft.WindowsTerminal_8wekyb3d8bbwe\\LocalState\\settings.json"); if(!ffAppendFileBuffer(jsonPath.chars, &json)) error = "Error reading Windows Terminal settings JSON file"; } } else { ffStrbufAppendS(&jsonPath, "\\Microsoft\\Windows Terminal\\settings.json"); if(!ffAppendFileBuffer(jsonPath.chars, &json)) error = "Error reading Windows Terminal settings JSON file"; } } } } if(!error && json.length == 0) { error = ffProcessAppendStdOut(&json, (char* const[]) { "cmd.exe", "/c", //print the file content directly, so we don't need to handle the difference of Windows and POSIX path "if exist %LOCALAPPDATA%\\Packages\\Microsoft.WindowsTerminal_8wekyb3d8bbwe\\LocalState\\settings.json " "( type %LOCALAPPDATA%\\Packages\\Microsoft.WindowsTerminal_8wekyb3d8bbwe\\LocalState\\settings.json ) " "else if exist %LOCALAPPDATA%\\Packages\\Microsoft.WindowsTerminalPreview_8wekyb3d8bbwe\\LocalState\\settings.json " "( type %LOCALAPPDATA%\\Packages\\Microsoft.WindowsTerminalPreview_8wekyb3d8bbwe\\LocalState\\settings.json ) " "else if exist \"%LOCALAPPDATA%\\Microsoft\\Windows Terminal\\settings.json\" " "( type %LOCALAPPDATA%\\Microsoft\\Windows Terminal\\settings.json ) " "else ( call )", NULL }); } if(error) { ffStrbufAppendS(&terminalFont->error, error); return; } ffStrbufTrimRight(&json, '\n'); if(json.length == 0) { ffStrbufAppendS(&terminalFont->error, "Cannot find file \"settings.json\""); return; } FF_STRBUF_AUTO_DESTROY name = ffStrbufCreate(); double size = -1; error = detectFromWTImpl(&json, &name, &size); if(error) ffStrbufAppendS(&terminalFont->error, error); else { char sizeStr[16]; snprintf(sizeStr, ARRAY_SIZE(sizeStr), "%g", size); ffFontInitValues(&terminalFont->font, name.chars, sizeStr); } } static void detectMintty(FFTerminalFontResult* terminalFont) { FF_STRBUF_AUTO_DESTROY fontName = ffStrbufCreate(); FF_STRBUF_AUTO_DESTROY fontSize = ffStrbufCreate(); if(!ffParsePropFileConfigValues("mintty/config", 2, (FFpropquery[]) { {"Font=", &fontName}, {"FontHeight=", &fontSize} })) ffParsePropFileConfigValues(".minttyrc", 2, (FFpropquery[]) { {"Font=", &fontName}, {"FontHeight=", &fontSize} }); if(fontName.length == 0) ffStrbufAppendS(&fontName, "Lucida Console"); if(fontSize.length == 0) ffStrbufAppendC(&fontSize, '9'); ffFontInitValues(&terminalFont->font, fontName.chars, fontSize.chars); } static void detectConhost(FFTerminalFontResult* terminalFont) { CONSOLE_FONT_INFOEX cfi = { .cbSize = sizeof(cfi) }; if(!GetCurrentConsoleFontEx(GetStdHandle(STD_OUTPUT_HANDLE), FALSE, &cfi)) { ffStrbufAppendS(&terminalFont->error, "GetCurrentConsoleFontEx() failed"); return; } FF_STRBUF_AUTO_DESTROY fontName = ffStrbufCreateWS(cfi.FaceName); char fontSize[16]; _ultoa((unsigned long)(cfi.dwFontSize.Y), fontSize, 10); ffFontInitValues(&terminalFont->font, fontName.chars, fontSize); } static void detectConEmu(FFTerminalFontResult* terminalFont) { //https://conemu.github.io/en/ConEmuXml.html#search-sequence FF_STRBUF_AUTO_DESTROY path = ffStrbufCreate(); FF_STRBUF_AUTO_DESTROY fontName = ffStrbufCreate(); FF_STRBUF_AUTO_DESTROY fontSize = ffStrbufCreate(); const char* paths[] = { "ConEmuDir", "ConEmuBaseDir", "APPDATA" }; for (uint32_t i = 0; i < ARRAY_SIZE(paths); ++i) { ffStrbufSetS(&path, getenv(paths[i])); if(path.length > 0) { ffStrbufAppendS(&path, "/ConEmu.xml"); if(ffParsePropFileValues(path.chars, 2, (FFpropquery[]){ {"error, "Failed to parse ConEmu.xml"); return; } if(fontName.length > 0) ffStrbufSubstrBeforeLastC(&fontName, '"'); else ffStrbufAppendS(&fontName, "Consola"); if(fontSize.length > 0) ffStrbufSubstrBeforeLastC(&fontSize, '"'); else ffStrbufAppendS(&fontSize, "14"); ffFontInitValues(&terminalFont->font, fontName.chars, fontSize.chars); } static void detectWarp(FFTerminalFontResult* terminalFont) { FF_AUTO_CLOSE_FD HANDLE key = NULL; if (!ffRegOpenKeyForRead(HKEY_CURRENT_USER, L"Software\\Warp.dev\\Warp", &key, &terminalFont->error)) return; FF_STRBUF_AUTO_DESTROY fontName = ffStrbufCreate(); FF_STRBUF_AUTO_DESTROY fontSize = ffStrbufCreate(); if (ffRegReadValues(key, 2, (FFRegValueArg[]) { FF_ARG(fontName, L"FontName"), FF_ARG(fontSize, L"FontSize") }, &terminalFont->error)) { ffStrbufTrim(&fontName, '"'); ffStrbufAppendS(&fontSize, "px"); } else { ffStrbufSetS(&fontName, "Hack"); ffStrbufSetS(&fontSize, "13.0px"); } ffFontInitValues(&terminalFont->font, fontName.chars, fontSize.chars); FFstrbuf* fontWeight = (FFstrbuf*) ffListAdd(&terminalFont->font.styles); ffStrbufInit(fontWeight); if (ffRegReadStrbuf(key, L"FontWeight", fontWeight, NULL)) ffStrbufTrim(fontWeight, '"'); else ffStrbufSetStatic(fontWeight, "Normal"); } bool ffDetectTerminalFontPlatform(const FFTerminalResult* terminal, FFTerminalFontResult* terminalFont) { if(ffStrbufIgnCaseEqualS(&terminal->processName, "Windows Terminal") || ffStrbufIgnCaseEqualS(&terminal->processName, "WindowsTerminal.exe")) detectFromWindowsTerminal(&terminal->exe, terminalFont); else if(ffStrbufIgnCaseEqualS(&terminal->processName, "mintty")) detectMintty(terminalFont); else if(ffStrbufIgnCaseEqualS(&terminal->processName, "conhost.exe")) detectConhost(terminalFont); else if(ffStrbufStartsWithIgnCaseS(&terminal->processName, "ConEmu")) detectConEmu(terminalFont); else if(ffStrbufStartsWithIgnCaseS(&terminal->processName, "warp")) detectWarp(terminalFont); else return false; return true; } ================================================ FILE: src/detection/terminalshell/terminalshell.c ================================================ #include "fastfetch.h" #include "common/io.h" #include "common/processing.h" #include "common/properties.h" #include "common/path.h" #include "common/stringUtils.h" #include "common/binary.h" #include #include #ifdef __FreeBSD__ #include #ifndef _PATH_LOCALBASE #define _PATH_LOCALBASE "/usr/local" #endif #elif __OpenBSD__ #define _PATH_LOCALBASE "/usr/local" #elif __NetBSD__ #define _PATH_LOCALBASE "/usr/pkg" #elif _WIN32 #include "common/windows/version.h" #include static bool getFileVersion(const FFstrbuf* exePath, const wchar_t* stringName, FFstrbuf* version) { wchar_t exePathW[PATH_MAX + 1]; if (!NT_SUCCESS(RtlUTF8ToUnicodeN(exePathW, (ULONG) sizeof(exePathW), NULL, exePath->chars, (ULONG)exePath->length + 1))) return false; return ffGetFileVersion(exePathW, stringName, version); } #elif __HAIKU__ #include "common/haiku/version.h" #endif static bool getExeVersionRaw(FFstrbuf* exe, FFstrbuf* version) { return ffProcessAppendStdOut(version, (char* const[]) { exe->chars, "--version", NULL }) == NULL; } static bool getExeVersionGeneral(FFstrbuf* exe, FFstrbuf* version) { if(!getExeVersionRaw(exe, version)) return false; ffStrbufSubstrAfterFirstC(version, ' '); ffStrbufSubstrBeforeFirstC(version, ' '); return true; } static bool extractBashVersion(const char* line, FF_MAYBE_UNUSED uint32_t len, void *userdata) { if (!ffStrStartsWith(line, "@(#)Bash version ")) return true; const char* start = line + strlen("@(#)Bash version "); const char* end = strchr(start, '('); if (!end) return true; ffStrbufSetNS((FFstrbuf*) userdata, (uint32_t) (end - start), start); return false; } static bool getShellVersionBash(FFstrbuf* exe, FFstrbuf* version) { ffBinaryExtractStrings(exe->chars, extractBashVersion, version, (uint32_t) strlen("@(#)Bash version 0.0.0(0) release GNU")); if (version->length > 0) return true; if(!getExeVersionRaw(exe, version)) return false; // GNU bash, version 5.1.16(1)-release (x86_64-pc-msys)\nCopyright... ffStrbufSubstrBeforeFirstC(version, '('); // GNU bash, version 5.1.16 ffStrbufSubstrAfterLastC(version, ' '); // 5.1.16 return true; } static bool getShellVersionFish(FFstrbuf* exe, FFstrbuf* version) { if(!getExeVersionRaw(exe, version)) return false; //fish, version 4.0.2-1 (Built by MSYS2 project) // version can be localized if LC_ALL is set if (version->length < strlen("fish, v") || !ffStrbufStartsWithS(version, "fish")) return false; uint32_t index = ffStrbufNextIndexC(version, strlen("fish, "), ' '); ffStrbufSubstrAfter(version, index); ffStrbufSubstrBeforeFirstC(version, ' '); return true; } static bool getShellVersionPwsh(FFstrbuf* exe, FFstrbuf* version) { // Requires manually setting $POWERSHELL_VERSION // $env:POWERSHELL_VERSION = $PSVersionTable.PSVersion.ToString(); fastfetch.exe const char* env = getenv("POWERSHELL_VERSION"); if (env) { ffStrbufSetS(version, env); return true; } #ifdef _WIN32 if(getFileVersion(exe, NULL, version)) { ffStrbufSubstrBeforeLastC(version, '.'); return true; } #endif if(!getExeVersionRaw(exe, version)) return false; ffStrbufSubstrAfterLastC(version, ' '); return true; } static bool getShellVersionKsh(FFstrbuf* exe, FFstrbuf* version) { if(ffProcessAppendStdErr(version, (char* const[]) { exe->chars, "--version", NULL }) == NULL && ffStrbufSubstrAfterFirstS(version, " (AT&T Research) ")) { // version sh (AT&T Research) 93u+ 2012-08-01 ffStrbufSubstrBeforeFirstC(version, ' '); return true; } ffStrbufClear(version); if(ffProcessAppendStdOut(version, (char* const[]) { exe->chars, "-c", "echo $KSH_VERSION", NULL }) == NULL && ffStrbufSubstrAfterFirstS(version, " KSH ")) { // OKSH: @(#)PD KSH v5.2.14 99/07/13.2 // MKSH: @(#)MIRBSD KSH R59 2025/04/26 +Debian // $OKSH_VERSION doesn't exist on OpenBSD ffStrbufSubstrBeforeFirstC(version, ' '); ffStrbufTrimLeft(version, 'v'); return true; } return false; } static bool getShellVersionOksh(FFstrbuf* exe, FFstrbuf* version) { // Homebrew version if(ffProcessAppendStdOut(version, (char* const[]) { exe->chars, "-c", "echo $OKSH_VERSION", NULL }) != NULL) return false; //oksh 7.3 ffStrbufSubstrAfterFirstC(version, ' '); return true; } static bool getShellVersionOils(FFstrbuf* exe, FFstrbuf* version) { if(ffProcessAppendStdOut(version, (char* const[]) { exe->chars, "--version", NULL }) != NULL) return false; // Oils 0.18.0 https://www.oilshell.org/... ffStrbufSubstrAfterFirstC(version, ' '); ffStrbufSubstrBeforeFirstC(version, '\t'); return true; } static bool getShellVersionNushell(FFstrbuf* exe, FFstrbuf* version) { ffStrbufSetS(version, getenv("NU_VERSION")); if (version->length) return true; return getExeVersionRaw(exe, version); //0.73.0 } static bool getShellVersionAsh(FFstrbuf* exe, FFstrbuf* version) { if(ffProcessAppendStdErr(version, (char* const[]) { exe->chars, "--help", NULL }) != NULL) return false; // BusyBox v1.36.1 (2023-11-07 18:53:09 UTC) multi-call binary... ffStrbufSubstrAfterFirstC(version, ' '); ffStrbufSubstrBeforeFirstC(version, ' '); ffStrbufTrimLeft(version, 'v'); return true; } static bool getShellVersionXonsh(FF_MAYBE_UNUSED FFstrbuf* exe, FFstrbuf* version) { ffStrbufSetS(version, getenv("XONSH_VERSION")); if (version->length) return true; // exe is python here if(ffProcessAppendStdErr(version, (char* const[]) { "xonsh", "--version", NULL }) != NULL) return false; // xonsh/0.14.1 ffStrbufSubstrAfterFirstC(version, '/'); return true; } static bool extractZshVersion(const char* line, FF_MAYBE_UNUSED uint32_t len, void *userdata) { if (!ffStrStartsWith(line, "zsh-")) return true; const char* start = line + strlen("zsh-"); const char* end = strchr(start, '-'); if (!end) return true; ffStrbufSetNS((FFstrbuf*) userdata, (uint32_t) (end - start), start); return false; } static bool getShellVersionZsh(FFstrbuf* exe, FFstrbuf* version) { ffBinaryExtractStrings(exe->chars, extractZshVersion, version, (uint32_t) strlen("zsh-0.0-0")); if (version->length) return true; return getExeVersionGeneral(exe, version); //zsh 5.9 (arm-apple-darwin21.3.0) } #ifdef _WIN32 static bool getShellVersionWinPowerShell(FFstrbuf* exe, FFstrbuf* version) { const char* env = getenv("POWERSHELL_VERSION"); if (env) { ffStrbufSetS(version, env); return true; } return ffProcessAppendStdOut(version, (char* const[]) { exe->chars, "-NoLogo", "-NoProfile", "-Command", "$PSVersionTable.PSVersion.ToString()", NULL }) == NULL; } #endif bool fftsGetShellVersion(FFstrbuf* exe, const char* exeName, FFstrbuf* version) { if (!instance.config.general.detectVersion) return false; if(ffStrEqualsIgnCase(exeName, "sh")) // #849 return false; if(ffStrEqualsIgnCase(exeName, "bash")) return getShellVersionBash(exe, version); if(ffStrEqualsIgnCase(exeName, "zsh")) return getShellVersionZsh(exe, version); if(ffStrEqualsIgnCase(exeName, "fish")) return getShellVersionFish(exe, version); if(ffStrEqualsIgnCase(exeName, "pwsh")) return getShellVersionPwsh(exe, version); if(ffStrEqualsIgnCase(exeName, "csh") || ffStrEqualsIgnCase(exeName, "tcsh")) return getExeVersionGeneral(exe, version); //tcsh 6.24.07 (Astron) 2022-12-21 (aarch64-apple-darwin) options wide,nls,dl,al,kan,sm,rh,color,filec if(ffStrEqualsIgnCase(exeName, "nu")) return getShellVersionNushell(exe, version); if(ffStrEqualsIgnCase(exeName, "ksh") || ffStrEqualsIgnCase(exeName, "mksh")) return getShellVersionKsh(exe, version); if(ffStrEqualsIgnCase(exeName, "oksh")) return getShellVersionOksh(exe, version); if(ffStrEqualsIgnCase(exeName, "oil.ovm")) return getShellVersionOils(exe, version); if(ffStrEqualsIgnCase(exeName, "elvish")) return getExeVersionRaw(exe, version); if(ffStrEqualsIgnCase(exeName, "ash")) return getShellVersionAsh(exe, version); if(ffStrEqualsIgnCase(exeName, "xonsh")) return getShellVersionXonsh(exe, version); if(ffStrEqualsIgnCase(exeName, "brush")) return getExeVersionGeneral(exe, version); // brush 0.2.23 (git:2835487) #ifdef _WIN32 if(ffStrEqualsIgnCase(exeName, "powershell") || ffStrEqualsIgnCase(exeName, "powershell_ise")) return getShellVersionWinPowerShell(exe, version); return getFileVersion(exe, NULL, version); #endif return false; } FF_MAYBE_UNUSED static bool getTerminalVersionTermux(FFstrbuf* version) { ffStrbufSetS(version, getenv("TERMUX_VERSION")); return version->length > 0; } static bool extractGeneralVersion(const char *str, FF_MAYBE_UNUSED uint32_t len, void *userdata) { if (!ffCharIsDigit(str[0])) return true; int count = 0; sscanf(str, "%*d.%*d.%*d%n", &count); if (count == 0) return true; ffStrbufSetS((FFstrbuf*) userdata, str); return false; } FF_MAYBE_UNUSED static bool getTerminalVersionGnome(FFstrbuf* exe, FFstrbuf* version) { if (ffIsAbsolutePath(exe->chars)) { ffBinaryExtractStrings(exe->chars, extractGeneralVersion, version, (uint32_t) strlen("0.0.0")); if (version->length) return true; } if(ffProcessAppendStdOut(version, (char* const[]){ "gnome-terminal", "--version", NULL })) return false; //# GNOME Terminal 3.46.7 using VTE 0.70.2 +BIDI +GNUTLS +ICU +SYSTEMD ffStrbufSubstrAfterFirstS(version, "Terminal "); ffStrbufSubstrBeforeFirstC(version, ' '); return true; } FF_MAYBE_UNUSED static bool getTerminalVersionXfce4Terminal(FFstrbuf* exe, FFstrbuf* version) { if (ffIsAbsolutePath(exe->chars)) { ffBinaryExtractStrings(exe->chars, extractGeneralVersion, version, (uint32_t) strlen("0.0.0")); if (version->length) return true; } return getExeVersionGeneral(exe, version);//xfce4-terminal 1.0.4 (Xfce 4.18)... } FF_MAYBE_UNUSED static bool getTerminalVersionKgx(FFstrbuf* version) { if(ffProcessAppendStdOut(version, (char* const[]){ "kgx", "--version", NULL })) return false; //# KGX 45.0 using VTE 0.74.0 +BIDI +GNUTLS +ICU +SYSTEMD ffStrbufSubstrAfterFirstS(version, "KGX "); ffStrbufSubstrBeforeFirstC(version, ' '); return true; } FF_MAYBE_UNUSED static bool getTerminalVersionKonsole(FFstrbuf* exe, FFstrbuf* version) { const char* konsoleVersion = getenv("KONSOLE_VERSION"); if(konsoleVersion) { //221201 long major = strtol(konsoleVersion, NULL, 10); if (major >= 0) { long patch = major % 100; major /= 100; long minor = major % 100; major /= 100; ffStrbufSetF(version, "%ld.%ld.%ld", major, minor, patch); return true; } } return getExeVersionGeneral(exe, version); } FF_MAYBE_UNUSED static bool getTerminalVersionFoot(FFstrbuf* exe, FFstrbuf* version) { uint32_t major = 0, minor = 0, patch = 0; if (ffGetTerminalResponse("\e[>c", 3, "\e[>1;%2u%2u%2u;0c", &major, &minor, &patch) == NULL) { ffStrbufSetF(version, "%u.%u.%u", major, minor, patch); return true; } if(!getExeVersionRaw(exe, version)) return false; //foot version: 1.13.1 -pgo +ime -graphemes -assertions ffStrbufSubstrAfterFirstS(version, "version: "); ffStrbufSubstrBeforeFirstC(version, ' '); return true; } FF_MAYBE_UNUSED static bool getTerminalVersionMateTerminal(FFstrbuf* exe, FFstrbuf* version) { ffBinaryExtractStrings(exe->chars, extractGeneralVersion, version, (uint32_t) strlen("0.0.0")); if (version->length > 0) return true; if(!getExeVersionRaw(exe, version)) return false; //MATE Terminal 1.26.1 ffStrbufSubstrAfterLastC(version, ' '); return version->length > 0; } FF_MAYBE_UNUSED static bool getTerminalVersionCockpit(FFstrbuf* exe, FFstrbuf* version) { if(!getExeVersionRaw(exe, version)) return false; //Version: 295\n... ffStrbufSubstrBeforeFirstC(version, '\n'); ffStrbufSubstrAfterFirstC(version, ' '); return version->length > 0; } FF_MAYBE_UNUSED static bool getTerminalVersionXterm(FFstrbuf* exe, FFstrbuf* version) { ffStrbufSetS(version, getenv("XTERM_VERSION")); if (!version->length) { if(ffProcessAppendStdOut(version, (char* const[]){ exe->chars, "-v", NULL })) return false; } //xterm(273) ffStrbufTrimRight(version, ')'); ffStrbufSubstrAfterFirstC(version, '('); return version->length > 0; } FF_MAYBE_UNUSED static bool getTerminalVersionBlackbox(FFstrbuf* exe, FFstrbuf* version) { if(ffProcessAppendStdOut(version, (char* const[]){ exe->chars, "--version", NULL })) return false; //BlackBox version 0.14.0 (flatpak) ffStrbufSubstrAfterFirstS(version, "version "); ffStrbufSubstrBeforeFirstC(version, ' '); return version->length > 0; } FF_MAYBE_UNUSED static bool getTerminalVersionUrxvt(FF_MAYBE_UNUSED FFstrbuf* exe, FFstrbuf* version) { if(ffProcessAppendStdErr(version, (char* const[]){ "urxvt", // Don't use exe because of urxvtd "-invalid", NULL })) return false; //urxvt: "invalid": unknown or malformed option. //rxvt-unicode (urxvt) v9.31 - released: 2023-01-02 ffStrbufSubstrAfterFirstS(version, "(urxvt) v"); ffStrbufSubstrBeforeFirstC(version, ' '); return version->length > 0; } FF_MAYBE_UNUSED static bool getTerminalVersionSt(FF_MAYBE_UNUSED FFstrbuf* exe, FFstrbuf* version) { if(ffProcessAppendStdErr(version, (char* const[]){ exe->chars, "-v", NULL })) return false; //st 0.9 ffStrbufSubstrAfterFirstC(version, ' '); return version->length > 0; } FF_MAYBE_UNUSED static bool getTerminalVersionLxterminal(FFstrbuf* exe, FFstrbuf* version) { if(!getExeVersionRaw(exe, version)) return false; // lxterminal 0.3.2 ffStrbufSubstrAfterFirstC(version, ' '); return version->length > 0; } FF_MAYBE_UNUSED static bool getTerminalVersionWeston(FF_MAYBE_UNUSED FFstrbuf* exe, FFstrbuf* version) { // weston-terminal doesn't report a version, use weston version instead if(ffProcessAppendStdOut(version, (char* const[]){ "weston", "--version", NULL })) return false; //weston 8.0.0 ffStrbufSubstrAfterFirstC(version, ' '); return version->length > 0; } static bool getTerminalVersionContour(FFstrbuf* exe, FFstrbuf* version) { const char* env = getenv("TERMINAL_VERSION_STRING"); if (env) { ffStrbufAppendS(version, env); return true; } if(!getExeVersionRaw(exe, version)) return false; // Contour Terminal Emulator 0.3.12.262 ffStrbufSubstrAfterLastC(version, ' '); return version->length > 0; } static bool getTerminalVersionScreen(FFstrbuf* exe, FFstrbuf* version) { if(!getExeVersionRaw(exe, version)) return false; // Screen version 4.09.01 (GNU) 20-Aug-23 ffStrbufSubstrAfter(version, (uint32_t) strlen("Screen version ") - 1); ffStrbufSubstrBeforeFirstC(version, ' '); return version->length > 0; } static bool getTerminalVersionTmux(FFstrbuf* exe, FFstrbuf* version) { if (ffProcessAppendStdOut(version, (char* const[]) { exe->chars, "-V", NULL }) != NULL) return false; // tmux 3.4 ffStrbufSubstrAfterFirstC(version, ' '); return version->length > 0; } static bool getTerminalVersionZellij(FFstrbuf* exe, FFstrbuf* version) { if(!getExeVersionRaw(exe, version)) return false; // zellij 0.39.2 ffStrbufSubstrAfterFirstC(version, ' '); return version->length > 0; } static bool getTerminalVersionZed(FFstrbuf* exe, FFstrbuf* version) { FF_STRBUF_AUTO_DESTROY cli = ffStrbufCreateCopy(exe); ffStrbufSubstrBeforeLastC(&cli, '/'); ffStrbufAppendS(&cli, "/cli" #ifdef _WIN32 ".exe" #endif ); if(ffProcessAppendStdOut(version, (char* const[]) { cli.chars, "--version", NULL }) != NULL) return false; // Zed 0.142.6 – /Applications/Zed.app ffStrbufSubstrAfterFirstC(version, ' '); ffStrbufSubstrBeforeFirstC(version, ' '); return true; } static bool extractSshdVersion(const char *str, FF_MAYBE_UNUSED uint32_t len, void *userdata) { if (!ffStrStartsWith(str, "OpenSSH_") || !ffCharIsDigit(str[strlen("OpenSSH_")])) return true; str += strlen("OpenSSH_"); int count = 0; sscanf(str, "%*d.%*dp%*d%n", &count); if (count == 0) return true; ffStrbufSetS((FFstrbuf*) userdata, str); return false; } static bool getTerminalVersionSshd(FFstrbuf* exe, FFstrbuf* version) { FF_STRBUF_AUTO_DESTROY exePath = ffStrbufCreate(); if (ffIsAbsolutePath(exe->chars)) ffStrbufSet(&exePath, exe); else if (ffFindExecutableInPath("sshd", &exePath) != NULL) return false; ffBinaryExtractStrings(exePath.chars, extractSshdVersion, version, (uint32_t) strlen("OpenSSH0.0")); if (version->length) return true; if(ffProcessAppendStdOut(version, (char* const[]) { exePath.chars, "-V", NULL }) != NULL) return false; if (ffStrbufStartsWithS(version, "unknown ")) // `unknown option -- V` (ancient OpenSSH version) ffStrbufSubstrAfterFirstC(version, '\n'); // OpenSSH_10.0p2 Ubuntu-5ubuntu5, OpenSSL 3.5.3 16 Sep 2025 ffStrbufSubstrAfterFirstC(version, '_'); ffStrbufSubstrBeforeFirstC(version, ','); return true; } #ifndef _WIN32 static bool getTerminalVersionKitty(FFstrbuf* exe, FFstrbuf* version) { #if defined(__linux__) || defined(__FreeBSD__) || defined(__OpenBSD__) || defined(__NetBSD__) || defined(__GNU__) char buffer[1024] = {}; if ( #if __linux__ || __GNU__ ffReadFileData(FASTFETCH_TARGET_DIR_USR "/lib64/kitty/kitty/constants.py", ARRAY_SIZE(buffer) - 1, buffer) || ffReadFileData(FASTFETCH_TARGET_DIR_USR "/lib/kitty/kitty/constants.py", ARRAY_SIZE(buffer) - 1, buffer) #else ffReadFileData(_PATH_LOCALBASE "/share/kitty/kitty/constants.py", ARRAY_SIZE(buffer) - 1, buffer) #endif ) { // Starts from version 0.17.0 // https://github.com/kovidgoyal/kitty/blob/master/kitty/constants.py#L25 const char* p = memmem(buffer, ARRAY_SIZE(buffer) - 1, "version: Version = Version(", strlen("version: Version = Version(")); if (p) { p += strlen("version: Version = Version("); int major, minor, patch; if (sscanf(p, "%d,%d,%d", &major, &minor, &patch) == 3) { ffStrbufSetF(version, "%d.%d.%d", major, minor, patch); return true; } } } #elif __APPLE__ if (ffStrbufEndsWithS(exe, "/kitty.app/Contents/MacOS/kitty")) { ffStrbufSet(version, exe); ffStrbufSubstrBeforeLastC(version, '/'); ffStrbufSubstrBeforeLastC(version, '/'); ffStrbufAppendS(version, "/Info.plist"); char buf[4096]; ssize_t size = ffReadFileData(version->chars, ARRAY_SIZE(buf) - 1, buf); if (size > 0) { buf[size] = '\0'; const char* p = strstr(buf, "CFBundleShortVersionString"); if (p) { p += strlen("CFBundleShortVersionString"); p = strchr(p, '>'); if (p) { p++; const char* end = strchr(p, '<'); if (end) { ffStrbufSetNS(version, (uint32_t) (end - p), p); return true; } } } } ffStrbufClear(version); } #endif char versionHex[64]; // https://github.com/fastfetch-cli/fastfetch/discussions/1030#discussioncomment-9845233 if (ffGetTerminalResponse( "\eP+q6b697474792d71756572792d76657273696f6e\e\\", // kitty-query-version 1, "\eP1+r%*[^=]=%63[^\e]\e\\\\", versionHex) == NULL) { // decode hex string for (const char* p = versionHex; p[0] && p[1]; p += 2) { unsigned value; if (sscanf(p, "%2x", &value) == 1) ffStrbufAppendC(version, (char) value); } return true; } //kitty 0.21.2 created by Kovid Goyal return getExeVersionGeneral(exe, version); } FF_MAYBE_UNUSED static bool getTerminalVersionPtyxis(FF_MAYBE_UNUSED FFstrbuf* exe, FFstrbuf* version) { if(ffProcessAppendStdOut(version, (char* const[]) { "ptyxis", "--version", NULL }) != NULL) return false; ffStrbufSubstrBeforeFirstC(version, '\n'); ffStrbufSubstrAfterFirstC(version, ' '); return true; } FF_MAYBE_UNUSED static bool getTerminalVersionTilix(FFstrbuf* exe, FFstrbuf* version) { if (ffIsAbsolutePath(exe->chars)) { ffBinaryExtractStrings(exe->chars, extractGeneralVersion, version, (uint32_t) strlen("0.0.0")); if (version->length) return true; } if(ffProcessAppendStdOut(version, (char* const[]) { exe->chars, "--version", NULL }) != NULL) return false; uint32_t index = ffStrbufFirstIndexS(version, "Tilix version: "); if (index == version->length) return false; index += (uint32_t) strlen("Tilix version:"); uint32_t end = ffStrbufNextIndexC(version, index, '\n'); ffStrbufSubstrBefore(version, end); ffStrbufSubstrAfter(version, index); return true; } FF_MAYBE_UNUSED static bool getTerminalVersionSakura(FFstrbuf* exe, FFstrbuf* version) { if(ffProcessAppendStdErr(version, (char* const[]) { exe->chars, "--version", NULL }) != NULL) // sakura version is 3.8.8 return false; ffStrbufSubstrAfterLastC(version, ' '); return true; } FF_MAYBE_UNUSED static bool getTerminalVersionTermite(FFstrbuf* exe, FFstrbuf* version) { if(ffProcessAppendStdOut(version, (char* const[]) { exe->chars, "--version", NULL }) != NULL) // termite v16.9\nvte 0.78.1 +BIDI +GNUTLS +ICU +SYSTEMD return false; ffStrbufSubstrBeforeFirstC(version, '\n'); ffStrbufSubstrAfterLastC(version, 'v'); return true; } #endif #ifdef _WIN32 static bool getTerminalVersionWindowsTerminal(FFstrbuf* exe, FFstrbuf* version) { FF_STRBUF_AUTO_DESTROY buildInfoPath; ffStrbufInitNS(&buildInfoPath, ffStrbufLastIndexC(exe, '\\') + 1, exe->chars); ffStrbufAppendS(&buildInfoPath, "BuildInfo.xml"); if(ffParsePropFile(buildInfoPath.chars, "StoreVersion=\"", version)) { ffStrbufTrimRight(version, '"'); return true; } return getFileVersion(exe, NULL, version); } static bool getTerminalVersionConEmu(FFstrbuf* exe, FFstrbuf* version) { ffStrbufSetS(version, getenv("ConEmuBuild")); if(version->length) return true; return getFileVersion(exe, NULL, version); } #endif bool fftsGetTerminalVersion(FFstrbuf* processName, FF_MAYBE_UNUSED FFstrbuf* exe, FFstrbuf* version) { if (!instance.config.general.detectVersion) return false; #ifdef __ANDROID__ if(ffStrbufEqualS(processName, "com.termux")) return getTerminalVersionTermux(version); #endif #if defined(__linux__) || defined(__FreeBSD__) || defined(__OpenBSD__) || defined(__sun) || defined(__NetBSD__) || defined(__HAIKU__) || defined(__GNU__) if(ffStrbufStartsWithIgnCaseS(processName, "gnome-terminal")) return getTerminalVersionGnome(exe, version); if(ffStrbufIgnCaseEqualS(processName, "konsole")) return getTerminalVersionKonsole(exe, version); if(ffStrbufIgnCaseEqualS(processName, "yakuake")) return getTerminalVersionKonsole(exe, version); // yakuake shares code with konsole if(ffStrbufIgnCaseEqualS(processName, "xfce4-terminal")) return getTerminalVersionXfce4Terminal(exe, version); if(ffStrbufIgnCaseEqualS(processName, "terminator")) return getExeVersionGeneral(exe, version);//terminator 2.1.3 if(ffStrbufIgnCaseEqualS(processName, "deepin-terminal")) return getExeVersionGeneral(exe, version);//deepin-terminal 5.4.36 if(ffStrbufIgnCaseEqualS(processName, "foot")) return getTerminalVersionFoot(exe, version); if(ffStrbufIgnCaseEqualS(processName, "qterminal")) return getExeVersionRaw(exe, version); //1.2.0 if(ffStrbufIgnCaseEqualS(processName, "mate-terminal")) return getTerminalVersionMateTerminal(exe, version); if(ffStrbufIgnCaseEqualS(processName, "cockpit-bridge")) return getTerminalVersionCockpit(exe, version); if(ffStrbufIgnCaseEqualS(processName, "xterm")) return getTerminalVersionXterm(exe, version); if(ffStrbufIgnCaseEqualS(processName, "blackbox")) return getTerminalVersionBlackbox(exe, version); if(ffStrbufIgnCaseEqualS(processName, "st")) return getTerminalVersionSt(exe, version); if(ffStrbufIgnCaseEqualS(processName, "lxterminal")) return getTerminalVersionLxterminal(exe, version); if(ffStrbufIgnCaseEqualS(processName, "weston-terminal")) return getTerminalVersionWeston(exe, version); if(ffStrbufIgnCaseEqualS(processName, "urxvt") || ffStrbufIgnCaseEqualS(processName, "urxvtd") || ffStrbufIgnCaseEqualS(processName, "rxvt") || ffStrbufIgnCaseEqualS(processName, "rxvt-unicode") ) return getTerminalVersionUrxvt(exe, version); if(ffStrbufIgnCaseEqualS(processName, "ptyxis-agent")) return getTerminalVersionPtyxis(exe, version); if(ffStrbufIgnCaseEqualS(processName, "tilix")) return getTerminalVersionTilix(exe, version); if(ffStrbufIgnCaseEqualS(processName, "sakura")) return getTerminalVersionSakura(exe, version); if(ffStrbufIgnCaseEqualS(processName, "termite")) return getTerminalVersionTermite(exe, version); if(ffStrbufIgnCaseEqualS(processName, "cosmic-term")) return getTerminalVersionTmux(exe, version); #endif #ifdef _WIN32 if(ffStrbufIgnCaseEqualS(processName, "WindowsTerminal.exe")) return getTerminalVersionWindowsTerminal(exe, version); if(ffStrbufStartsWithIgnCaseS(processName, "ConEmu")) return getTerminalVersionConEmu(exe, version); if(ffStrbufIgnCaseEqualS(processName, "warp.exe")) return getFileVersion(exe, L"ProductVersion", version); #endif #ifndef _WIN32 if(ffStrbufIgnCaseEqualS(processName, "kitty")) return getTerminalVersionKitty(exe, version); if (ffStrbufIgnCaseEqualS(processName, "Tabby") && getExeVersionRaw(exe, version)) return true; #endif if(ffStrbufStartsWithIgnCaseS(processName, "alacritty")) return getExeVersionGeneral(exe, version); if(ffStrbufStartsWithIgnCaseS(processName, "contour")) return getTerminalVersionContour(exe, version); if(ffStrbufStartsWithIgnCaseS(processName, "screen")) return getTerminalVersionScreen(exe, version); if(ffStrbufStartsWithIgnCaseS(processName, "zellij")) return getTerminalVersionZellij(exe, version); if(ffStrbufStartsWithIgnCaseS(processName, "zed")) return getTerminalVersionZed(exe, version); #if __HAIKU__ if(ffStrbufEqualS(processName, "Terminal")) return ffGetFileVersion(exe->chars, version); #endif const char* termProgramVersion = getenv("TERM_PROGRAM_VERSION"); if(termProgramVersion) { const char* termProgram = getenv("TERM_PROGRAM"); if(termProgram) { if(ffStrbufStartsWithIgnCaseS(processName, termProgram) || // processName ends with `.exe` on Windows (ffStrEquals(termProgram, "vscode") && ffStrbufStartsWithIgnCaseS(processName, "code")) || #ifdef __APPLE__ (ffStrEquals(termProgram, "iTerm.app") && ffStrbufStartsWithIgnCaseS(processName, "iTermServer-")) || #elif defined(__linux__) (ffStrEquals(termProgram, "WarpTerminal") && ffStrbufEqualS(processName, "warp")) || #endif false ) { ffStrbufSetS(version, termProgramVersion); return true; } } } termProgramVersion = getenv("LC_TERMINAL_VERSION"); if(termProgramVersion) { const char* termProgram = getenv("LC_TERMINAL"); if(termProgram) { if(ffStrbufStartsWithIgnCaseS(processName, termProgram) || // processName ends with `.exe` on Windows (ffStrEquals(termProgram, "vscode") && ffStrbufStartsWithIgnCaseS(processName, "code")) || (ffStrStartsWith(termProgram, "iTerm") && ffStrbufStartsWithIgnCaseS(processName, "iTermServer-")) ) { ffStrbufSetS(version, termProgramVersion); return true; } } } if(ffStrbufStartsWithIgnCaseS(processName, "tmux")) return getTerminalVersionTmux(exe, version); if(ffStrbufIgnCaseEqualS(processName, "sshd") || ffStrbufStartsWithIgnCaseS(processName, "sshd-")) return getTerminalVersionSshd(exe, version); #ifdef _WIN32 return getFileVersion(exe, NULL, version); #else return false; #endif } ================================================ FILE: src/detection/terminalshell/terminalshell.h ================================================ #pragma once #include "fastfetch.h" #include "modules/terminal/option.h" #include "modules/shell/option.h" typedef struct FFShellResult { FFstrbuf processName; FFstrbuf exe; //Actually arg0 in *nix const char* exeName; //pointer to a char in exe FFstrbuf exePath; //Full real path to executable file FFstrbuf prettyName; FFstrbuf version; uint32_t pid; uint32_t ppid; int32_t tty; } FFShellResult; typedef struct FFTerminalResult { FFstrbuf processName; FFstrbuf exe; FFstrbuf prettyName; const char* exeName; //pointer to a char in exe FFstrbuf exePath; //Full real path to executable file FFstrbuf version; FFstrbuf tty; uint32_t pid; uint32_t ppid; } FFTerminalResult; const FFShellResult* ffDetectShell(); const FFTerminalResult* ffDetectTerminal(); ================================================ FILE: src/detection/terminalshell/terminalshell_linux.c ================================================ #include "terminalshell.h" #include "common/io.h" #include "common/parsing.h" #include "common/processing.h" #include "common/thread.h" #include "common/stringUtils.h" #include #include #include static void setExeName(FFstrbuf* exe, const char** exeName) { assert(exe->length > 0); uint32_t lastSlashIndex = ffStrbufLastIndexC(exe, '/'); if(lastSlashIndex < exe->length) *exeName = exe->chars + lastSlashIndex + 1; } static pid_t getShellInfo(FFShellResult* result, pid_t pid) { pid_t ppid = 0; int32_t tty = -1; const char* userShellName = NULL; { uint32_t index = ffStrbufLastIndexC(&instance.state.platform.userShell, '/'); if (index == instance.state.platform.userShell.length) userShellName = instance.state.platform.userShell.chars; else userShellName = instance.state.platform.userShell.chars + index + 1; } while (pid > 1 && ffProcessGetBasicInfoLinux(pid, &result->processName, &ppid, &tty) == NULL) { if (!ffStrbufEqualS(&result->processName, userShellName)) { //Common programs that are between terminal and own process, but are not the shell if( // tty < 0 || //A shell should connect to a tty pid == 1 || // init/systemd ffStrbufEqualS(&result->processName, "sh") || //This prevents us from detecting things like pipes and redirects, i hope nobody uses plain `sh` as shell ffStrbufEqualS(&result->processName, "sudo") || ffStrbufEqualS(&result->processName, "su") || ffStrbufEqualS(&result->processName, "strace") || ffStrbufEqualS(&result->processName, "gdb") || ffStrbufEqualS(&result->processName, "lldb") || ffStrbufEqualS(&result->processName, "lldb-mi") || ffStrbufEqualS(&result->processName, "login") || ffStrbufEqualS(&result->processName, "ltrace") || ffStrbufEqualS(&result->processName, "perf") || ffStrbufEqualS(&result->processName, "guake-wrapped") || ffStrbufEqualS(&result->processName, "time") || ffStrbufEqualS(&result->processName, "clifm") || //https://github.com/leo-arch/clifm/issues/289 ffStrbufEqualS(&result->processName, "valgrind") || ffStrbufEqualS(&result->processName, "fastfetch") || //#994 ffStrbufEqualS(&result->processName, "flashfetch") || ffStrbufEqualS(&result->processName, "proot") || ffStrbufEqualS(&result->processName, "script") || #ifdef __linux__ ffStrbufEqualS(&result->processName, "run-parts") || #endif ffStrbufContainS(&result->processName, "debug") || ffStrbufContainS(&result->processName, "command-not-") || ffStrbufEndsWithS(&result->processName, ".sh") ) { pid = ppid; ffStrbufClear(&result->processName); continue; } } result->pid = (uint32_t) pid; result->ppid = (uint32_t) ppid; result->tty = tty; ffProcessGetInfoLinux(pid, &result->processName, &result->exe, &result->exeName, &result->exePath); break; } return pid > 1 ? ppid : 0; } static pid_t getTerminalInfo(FFTerminalResult* result, pid_t pid) { pid_t ppid = 0; while (pid > 1 && ffProcessGetBasicInfoLinux(pid, &result->processName, &ppid, NULL) == NULL) { //Known shells if ( pid == 1 || // init/systemd ffStrbufEqualS(&result->processName, "sudo") || ffStrbufEqualS(&result->processName, "su") || ffStrbufEqualS(&result->processName, "sh") || ffStrbufEqualS(&result->processName, "ash") || ffStrbufEqualS(&result->processName, "bash") || ffStrbufEqualS(&result->processName, "zsh") || ffStrbufEqualS(&result->processName, "ksh") || ffStrbufEqualS(&result->processName, "mksh") || ffStrbufEqualS(&result->processName, "oksh") || ffStrbufEqualS(&result->processName, "csh") || ffStrbufEqualS(&result->processName, "tcsh") || ffStrbufEqualS(&result->processName, "fish") || ffStrbufEqualS(&result->processName, "dash") || ffStrbufEqualS(&result->processName, "pwsh") || ffStrbufEqualS(&result->processName, "nu") || ffStrbufEqualS(&result->processName, "git-shell") || ffStrbufEqualS(&result->processName, "elvish") || ffStrbufEqualS(&result->processName, "oil.ovm") || ffStrbufEqualS(&result->processName, "xonsh") || // works in Linux but not in macOS because kernel returns `Python` in this case ffStrbufEqualS(&result->processName, "login") || ffStrbufEqualS(&result->processName, "clifm") || // https://github.com/leo-arch/clifm/issues/289 ffStrbufEqualS(&result->processName, "chezmoi") || // #762 ffStrbufEqualS(&result->processName, "proot") || ffStrbufEqualS(&result->processName, "script") || #ifdef __linux__ ffStrbufStartsWithS(&result->processName, "Relay(") || // Unknown process in WSL2 ffStrbufStartsWithS(&result->processName, "flatpak-") || // #707 ffStrbufEqualS(&result->processName, "run-parts") || // #2048 #endif ffStrbufEndsWithS(&result->processName, ".sh") ) { pid = ppid; ffStrbufClear(&result->processName); continue; } #ifdef __APPLE__ // https://github.com/fastfetch-cli/fastfetch/discussions/501 const char* pLeft = strstr(result->processName.chars, " ("); if (pLeft) { pLeft += 2; const char* pRight = strstr(pLeft, "term)"); if (pRight && pRight[5] == '\0') { for (; pLeft < pRight; ++pLeft) if (*pLeft < 'a' || *pLeft > 'z') break; if (pLeft == pRight && ffProcessGetBasicInfoLinux(ppid, &result->processName, &ppid, NULL) != NULL) return 0; } } #endif result->pid = (uint32_t) pid; result->ppid = (uint32_t) ppid; ffProcessGetInfoLinux(pid, &result->processName, &result->exe, &result->exeName, &result->exePath); break; } return pid > 1 ? ppid : 0; } static bool getTerminalInfoByPidEnv(FFTerminalResult* result, const char* pidEnv) { const char* envStr = getenv(pidEnv); if (envStr == NULL) return false; pid_t pid = (pid_t) strtol(envStr, NULL, 10); result->pid = (uint32_t) pid; if (ffProcessGetBasicInfoLinux(pid, &result->processName, (pid_t*) &result->ppid, NULL) == NULL) { ffProcessGetInfoLinux(pid, &result->processName, &result->exe, &result->exeName, &result->exePath); return true; } return false; } static void getTerminalFromEnv(FFTerminalResult* result) { if (result->processName.length > 0) { if (!ffStrbufStartsWithS(&result->processName, "login") && !ffStrbufEqualS(&result->processName, "(login)") && #ifdef __APPLE__ !ffStrbufEqualS(&result->processName, "launchd") && #else !ffStrbufEqualS(&result->processName, "systemd") && !ffStrbufEqualS(&result->processName, "init") && !ffStrbufEqualS(&result->processName, "(init)") && !ffStrbufEqualS(&result->processName, "SessionLeader") && // #750 #endif !ffStrbufEqualS(&result->processName, "0") ) return; ffStrbufClear(&result->processName); ffStrbufClear(&result->exe); result->exeName = result->exe.chars; ffStrbufClear(&result->exePath); result->pid = result->ppid = 0; } const char* term = NULL; //SSH if( getenv("SSH_TTY") != NULL ) term = getenv("SSH_TTY"); else if( getenv("KITTY_PID") != NULL || getenv("KITTY_INSTALLATION_DIR") != NULL ) { if (getTerminalInfoByPidEnv(result, "KITTY_PID")) return; term = "kitty"; } #ifdef __linux__ // WSL //Windows Terminal else if( getenv("WT_SESSION") != NULL || getenv("WT_PROFILE_ID") != NULL ) term = "Windows Terminal"; //ConEmu else if( getenv("ConEmuPID") != NULL ) term = "ConEmu"; #endif //Alacritty else if( getenv("ALACRITTY_SOCKET") != NULL || getenv("ALACRITTY_LOG") != NULL || getenv("ALACRITTY_WINDOW_ID") != NULL ) term = "Alacritty"; #ifdef __ANDROID__ //Termux else if( getenv("TERMUX_VERSION") != NULL || getenv("TERMUX_MAIN_PACKAGE_FORMAT") != NULL ) { if (getTerminalInfoByPidEnv(result, "TERMUX_APP__PID")) return; term = "com.termux"; } #endif #if defined(__linux__) || defined(__FreeBSD__) || defined(__NetBSD__) || defined(__GNU__) //Konsole else if( getenv("KONSOLE_VERSION") != NULL ) term = "konsole"; else if( getenv("GNOME_TERMINAL_SCREEN") != NULL || getenv("GNOME_TERMINAL_SERVICE") != NULL ) term = "gnome-terminal"; #endif //MacOS, mintty else if(getenv("TERM_PROGRAM") != NULL) term = getenv("TERM_PROGRAM"); else if(getenv("LC_TERMINAL") != NULL) term = getenv("LC_TERMINAL"); //Normal Terminal else { term = getenv("TERM"); //TTY if(!ffStrSet(term) || ffStrEquals(term, "linux")) term = ttyname(STDIN_FILENO); } if(ffStrSet(term)) { ffStrbufSetS(&result->processName, term); ffStrbufSetS(&result->exe, term); setExeName(&result->exe, &result->exeName); } } static void getUserShellFromEnv(FFShellResult* result) { //If shell detection via processes failed if(result->processName.length == 0 && instance.state.platform.userShell.length > 0) { ffStrbufSet(&result->exe, &instance.state.platform.userShell); setExeName(&result->exe, &result->exeName); ffStrbufAppendS(&result->processName, result->exeName); } } bool fftsGetShellVersion(FFstrbuf* exe, const char* exeName, FFstrbuf* version); bool fftsGetTerminalVersion(FFstrbuf* processName, FFstrbuf* exe, FFstrbuf* version); static void setShellInfoDetails(FFShellResult* result) { ffStrbufClear(&result->version); fftsGetShellVersion(result->exePath.length > 0 ? &result->exePath : &result->exe, result->exeName, &result->version); if(ffStrbufEqualS(&result->processName, "pwsh")) ffStrbufInitStatic(&result->prettyName, "PowerShell"); else if(ffStrbufEqualS(&result->processName, "nu")) ffStrbufInitStatic(&result->prettyName, "nushell"); else if(ffStrbufEqualS(&result->processName, "oil.ovm")) ffStrbufInitStatic(&result->prettyName, "Oils"); else { // https://github.com/fastfetch-cli/fastfetch/discussions/280#discussioncomment-3831734 ffStrbufInitS(&result->prettyName, result->exeName); } } static void setTerminalInfoDetails(FFTerminalResult* result) { if(ffStrbufStartsWithC(&result->processName, '.') && ffStrbufContainS(&result->processName, "-wrap")) { // For NixOS. Ref: #510 and https://github.com/NixOS/nixpkgs/pull/249428 // We use processName when detecting version and font, overriding it for simplification ffStrbufSubstrBeforeLastC(&result->processName, '-'); ffStrbufSubstrAfter(&result->processName, 0); } if(ffStrbufEqualS(&result->processName, "wezterm-gui")) ffStrbufInitStatic(&result->prettyName, "WezTerm"); else if(ffStrbufStartsWithS(&result->processName, "tmux:")) ffStrbufInitStatic(&result->prettyName, "tmux"); else if(ffStrbufStartsWithS(&result->processName, "screen-")) ffStrbufInitStatic(&result->prettyName, "screen"); else if(ffStrbufEqualS(&result->processName, "sshd") || ffStrbufStartsWithS(&result->processName, "sshd-")) { if (result->tty.length) ffStrbufInitCopy(&result->prettyName, &result->tty); else ffStrbufSetStatic(&result->prettyName, "sshd"); } #if defined(__ANDROID__) else if(ffStrbufEqualS(&result->processName, "com.termux")) ffStrbufInitStatic(&result->prettyName, "Termux"); #elif defined(__linux__) || defined(__FreeBSD__) || defined(__NetBSD__) || defined(__GNU__) else if(ffStrbufStartsWithS(&result->processName, "gnome-terminal")) ffStrbufInitStatic(&result->prettyName, "GNOME Terminal"); else if(ffStrbufStartsWithS(&result->processName, "kgx")) ffStrbufInitStatic(&result->prettyName, "GNOME Console"); else if(ffStrbufEqualS(&result->processName, "urxvt") || ffStrbufEqualS(&result->processName, "urxvtd") || ffStrbufEqualS(&result->processName, "rxvt") ) ffStrbufInitStatic(&result->prettyName, "rxvt-unicode"); else if(ffStrbufStartsWithS(&result->processName, "ptyxis-agent")) ffStrbufInitStatic(&result->prettyName, "Ptyxis"); #elif defined(__APPLE__) else if(ffStrbufEqualS(&result->processName, "iTerm.app") || ffStrbufStartsWithS(&result->processName, "iTermServer-")) ffStrbufInitStatic(&result->prettyName, "iTerm"); else if(ffStrbufEndsWithS(&result->exePath, "Terminal.app/Contents/MacOS/Terminal")) { ffStrbufSetStatic(&result->processName, "Apple_Terminal"); // $TERM_PROGRAM, for terminal font detection ffStrbufInitStatic(&result->prettyName, "Apple Terminal"); } else if(ffStrbufEqualS(&result->processName, "Apple_Terminal")) ffStrbufInitStatic(&result->prettyName, "Apple Terminal"); else if(ffStrbufEndsWithS(&result->exePath, "Warp.app/Contents/MacOS/stable")) { ffStrbufSetStatic(&result->processName, "WarpTerminal"); // $TERM_PROGRAM, for terminal font detection ffStrbufInitStatic(&result->prettyName, "Warp"); } else if(ffStrbufEqualS(&result->processName, "WarpTerminal")) ffStrbufInitStatic(&result->prettyName, "Warp"); #elif defined(__HAIKU__) else if(ffStrbufEqualS(&result->processName, "Terminal")) ffStrbufInitStatic(&result->prettyName, "Haiku Terminal"); #endif else if(strncmp(result->exeName, result->processName.chars, result->processName.length) == 0) // if exeName starts with processName, print it. Otherwise print processName ffStrbufInitS(&result->prettyName, result->exeName); else ffStrbufInitCopy(&result->prettyName, &result->processName); fftsGetTerminalVersion(&result->processName, result->exePath.length > 0 ? &result->exePath : &result->exe, &result->version); } #if defined(MAXPATH) #define FF_EXE_PATH_LEN MAXPATH #elif defined(PATH_MAX) #define FF_EXE_PATH_LEN PATH_MAX #else #define FF_EXE_PATH_LEN 260 #endif const FFShellResult* ffDetectShell() { static FFShellResult result; static bool init = false; if(init) return &result; init = true; ffStrbufInit(&result.processName); ffStrbufInitA(&result.exe, FF_EXE_PATH_LEN); result.exeName = result.exe.chars; ffStrbufInit(&result.exePath); ffStrbufInit(&result.version); result.pid = 0; result.ppid = 0; result.tty = -1; pid_t ppid = getppid(); const char* ignoreParent = getenv("FFTS_IGNORE_PARENT"); if (ignoreParent && ffStrEquals(ignoreParent, "1")) { FF_STRBUF_AUTO_DESTROY _ = ffStrbufCreate(); ffProcessGetBasicInfoLinux(ppid, &_, &ppid, NULL); } ppid = getShellInfo(&result, ppid); getUserShellFromEnv(&result); setShellInfoDetails(&result); return &result; } const FFTerminalResult* ffDetectTerminal() { static FFTerminalResult result; static bool init = false; if(init) return &result; init = true; ffStrbufInit(&result.processName); ffStrbufInitA(&result.exe, FF_EXE_PATH_LEN); result.exeName = result.exe.chars; ffStrbufInit(&result.exePath); ffStrbufInit(&result.version); ffStrbufInitS(&result.tty, ttyname(STDOUT_FILENO)); result.pid = 0; result.ppid = 0; pid_t ppid = (pid_t) ffDetectShell()->ppid; if (ppid) ppid = getTerminalInfo(&result, ppid); getTerminalFromEnv(&result); setTerminalInfoDetails(&result); return &result; } ================================================ FILE: src/detection/terminalshell/terminalshell_windows.c ================================================ #include "terminalshell.h" #include "common/io.h" #include "common/processing.h" #include "common/thread.h" #include "common/mallocHelper.h" #include "common/windows/registry.h" #include "common/windows/unicode.h" #include "common/windows/version.h" #include "common/windows/nt.h" #include "common/stringUtils.h" #include #include #include #include #include #include #include bool fftsGetShellVersion(FFstrbuf* exe, const char* exeName, FFstrbuf* version); static uint32_t getShellInfo(FFShellResult* result, uint32_t pid) { uint32_t ppid = 0; bool gui = false; while (pid != 0 && ffProcessGetInfoWindows(pid, &ppid, &result->processName, &result->exe, &result->exeName, &result->exePath, &gui)) { ffStrbufSet(&result->prettyName, &result->processName); if (ffStrbufEndsWithIgnCaseS(&result->prettyName, ".exe")) ffStrbufSubstrBefore(&result->prettyName, result->prettyName.length - 4); //Common programs that are between terminal and own process, but are not the shell if ( !gui && ( ffStrbufIgnCaseEqualS(&result->prettyName, "sudo") || ffStrbufIgnCaseEqualS(&result->prettyName, "su") || ffStrbufIgnCaseEqualS(&result->prettyName, "gdb") || ffStrbufIgnCaseEqualS(&result->prettyName, "lldb") || ffStrbufIgnCaseEqualS(&result->prettyName, "lldb-dap") || ffStrbufIgnCaseEqualS(&result->prettyName, "python") || // python on windows generates shim executables ffStrbufIgnCaseEqualS(&result->prettyName, "fastfetch") || // scoop warps the real binaries with a "shim" exe ffStrbufIgnCaseEqualS(&result->prettyName, "flashfetch") || ffStrbufContainIgnCaseS(&result->prettyName, "debug") || ffStrbufContainIgnCaseS(&result->prettyName, "time") || ffStrbufStartsWithIgnCaseS(&result->prettyName, "ConEmuC") // https://github.com/fastfetch-cli/fastfetch/issues/488#issuecomment-1619982014 )) { ffStrbufClear(&result->processName); ffStrbufClear(&result->prettyName); ffStrbufClear(&result->exe); result->exeName = NULL; pid = ppid; continue; } result->pid = pid; if (gui) { // Started without shell // In this case, terminal process will be created by fastfetch itself. ppid = 0; if (ffStrbufIgnCaseEqualS(&result->prettyName, "explorer")) ffStrbufSetS(&result->prettyName, "Windows Explorer"); } else { result->ppid = ppid; } break; } return ppid; } static void setShellInfoDetails(FFShellResult* result) { if(ffStrbufIgnCaseEqualS(&result->prettyName, "pwsh")) ffStrbufSetS(&result->prettyName, "PowerShell"); else if(ffStrbufIgnCaseEqualS(&result->prettyName, "powershell")) ffStrbufSetS(&result->prettyName, "Windows PowerShell"); else if(ffStrbufIgnCaseEqualS(&result->prettyName, "powershell_ise")) ffStrbufSetS(&result->prettyName, "Windows PowerShell ISE"); else if(ffStrbufIgnCaseEqualS(&result->prettyName, "cmd")) { ffStrbufSetS(&result->prettyName, "CMD"); if (instance.config.general.detectVersion) { FF_AUTO_CLOSE_FD HANDLE snapshot = NULL; while(!(snapshot = CreateToolhelp32Snapshot(TH32CS_SNAPMODULE, result->pid)) && GetLastError() == ERROR_BAD_LENGTH) {} if(snapshot) { MODULEENTRY32W module; module.dwSize = sizeof(module); for(BOOL success = Module32FirstW(snapshot, &module); success; success = Module32NextW(snapshot, &module)) { if(wcsncmp(module.szModule, L"clink_dll_", strlen("clink_dll_")) == 0) { FF_STRBUF_AUTO_DESTROY clinkVersion = ffStrbufCreate(); if (ffGetFileVersion(module.szExePath, NULL, &clinkVersion)) ffStrbufAppendF(&result->prettyName, " (with Clink %s)", clinkVersion.chars); else ffStrbufAppendS(&result->prettyName, " (with Clink)"); break; } } } } } else if(ffStrbufIgnCaseEqualS(&result->prettyName, "nu")) ffStrbufSetS(&result->prettyName, "nushell"); else if(ffStrbufIgnCaseEqualS(&result->prettyName, "explorer")) ffStrbufSetS(&result->prettyName, "Windows Explorer"); } static bool getTerminalFromEnv(FFTerminalResult* result) { if( result->processName.length > 0 && ffStrbufIgnCaseCompS(&result->processName, "explorer") != 0 ) return false; const char* term = getenv("ConEmuPID"); if(term) { //ConEmu uint32_t pid = (uint32_t) strtoul(term, NULL, 10); result->pid = pid; if(ffProcessGetInfoWindows(pid, NULL, &result->processName, &result->exe, &result->exeName, &result->exePath, NULL)) { ffStrbufSet(&result->prettyName, &result->processName); if(ffStrbufEndsWithIgnCaseS(&result->prettyName, ".exe")) ffStrbufSubstrBefore(&result->prettyName, result->prettyName.length - 4); return true; } else { term = "ConEmu"; } } //SSH if(getenv("SSH_TTY") != NULL) term = getenv("SSH_TTY"); //Windows Terminal if(!term && ( getenv("WT_SESSION") != NULL || getenv("WT_PROFILE_ID") != NULL )) term = "WindowsTerminal"; //Alacritty if(!term && ( getenv("ALACRITTY_SOCKET") != NULL || getenv("ALACRITTY_LOG") != NULL || getenv("ALACRITTY_WINDOW_ID") != NULL )) term = "Alacritty"; if(!term) term = getenv("TERM_PROGRAM"); //Normal Terminal if(!term) term = getenv("TERM"); if(term) { ffStrbufSetS(&result->processName, term); ffStrbufSetS(&result->prettyName, term); ffStrbufSetS(&result->exe, term); result->exeName = ""; return true; } return false; } static bool detectDefaultTerminal(FFTerminalResult* result) { wchar_t regPath[128] = L"SOFTWARE\\Classes\\PackagedCom\\ClassIndex\\"; wchar_t* uuid = regPath + strlen("SOFTWARE\\Classes\\PackagedCom\\ClassIndex\\"); DWORD bufSize = 80; if (RegGetValueW(HKEY_CURRENT_USER, L"Console\\%%Startup", L"DelegationTerminal", RRF_RT_REG_SZ, NULL, uuid, &bufSize) == ERROR_SUCCESS) { if(wcscmp(uuid, L"{00000000-0000-0000-0000-000000000000}") == 0 || // Let Windows decide wcscmp(uuid, L"{B23D10C0-E52E-411E-9D5B-C09FDF709C7D}") == 0) // Conhost { goto conhost; } FF_AUTO_CLOSE_FD HANDLE hKey = NULL; if(ffRegOpenKeyForRead(HKEY_LOCAL_MACHINE, regPath, &hKey, NULL)) { FF_STRBUF_AUTO_DESTROY path = ffStrbufCreate(); if(ffRegGetSubKey(hKey, 0, &path, NULL)) { if (ffStrbufStartsWithS(&path, "Microsoft.WindowsTerminal")) { ffStrbufSetS(&result->processName, "WindowsTerminal.exe"); ffStrbufSetS(&result->prettyName, "WindowsTerminal"); PWSTR programFiles = NULL; if (SUCCEEDED(SHGetKnownFolderPath(&FOLDERID_ProgramFiles, KF_FLAG_DEFAULT, NULL, &programFiles))) { ffStrbufSetWS(&result->exe, programFiles); CoTaskMemFree(programFiles); programFiles = NULL; ffStrbufAppendS(&result->exe, "\\WindowsApps\\"); ffStrbufAppend(&result->exe, &path); ffStrbufAppendS(&result->exe, "\\WindowsTerminal.exe"); if(ffPathExists(result->exe.chars, FF_PATHTYPE_FILE)) { result->exeName = result->exe.chars + ffStrbufLastIndexC(&result->exe, '\\') + 1; ffStrbufSet(&result->exePath, &result->exe); } else { ffStrbufDestroy(&result->exe); ffStrbufInitMove(&result->exe, &path); result->exeName = ""; } } return true; } } } } conhost:; ULONG_PTR conhostPid = 0; ULONG size; if(NT_SUCCESS(NtQueryInformationProcess(NtCurrentProcess(), ProcessConsoleHostProcess, &conhostPid, sizeof(conhostPid), &size)) && conhostPid != 0) { // For Windows Terminal, it reports the PID of OpenConsole if(ffProcessGetInfoWindows((uint32_t) conhostPid, NULL, &result->processName, &result->exe, &result->exeName, &result->exePath, NULL)) { ffStrbufSet(&result->prettyName, &result->processName); if(ffStrbufEndsWithIgnCaseS(&result->prettyName, ".exe")) ffStrbufSubstrBefore(&result->prettyName, result->prettyName.length - 4); return true; } } ffStrbufClear(&result->exe); return false; } static uint32_t getTerminalInfo(FFTerminalResult* result, uint32_t pid) { if (getenv("MSYSTEM")) { // Don't try to detect terminals in MSYS shell // It won't work because MSYS doesn't follow process tree of native Windows programs return 0; } uint32_t ppid = 0; bool gui; while (pid != 0 && ffProcessGetInfoWindows(pid, &ppid, &result->processName, &result->exe, &result->exeName, &result->exePath, &gui)) { if (!gui) { //We are in nested shell ffStrbufClear(&result->processName); ffStrbufClear(&result->prettyName); ffStrbufClear(&result->exe); ffStrbufClear(&result->exePath); result->exeName = ""; pid = ppid; continue; } ffStrbufSet(&result->prettyName, &result->processName); if(ffStrbufEndsWithIgnCaseS(&result->prettyName, ".exe")) ffStrbufSubstrBefore(&result->prettyName, result->prettyName.length - 4); if(ffStrbufIgnCaseEqualS(&result->prettyName, "sihost") || ffStrbufIgnCaseEqualS(&result->prettyName, "explorer") || ffStrbufIgnCaseEqualS(&result->prettyName, "wininit") ) { // A CUI program created by Windows Explorer will spawn a conhost as its child. // However the conhost process is just a placeholder; // The true terminal can be Windows Terminal or others. ffStrbufClear(&result->processName); ffStrbufClear(&result->prettyName); ffStrbufClear(&result->exe); ffStrbufClear(&result->exePath); result->exeName = ""; return 0; } else { result->pid = pid; result->ppid = ppid; } break; } return ppid; } static void setTerminalInfoDetails(FFTerminalResult* result) { if(ffStrbufIgnCaseEqualS(&result->prettyName, "WindowsTerminal")) ffStrbufSetStatic(&result->prettyName, ffStrbufContainIgnCaseS(&result->exe, ".WindowsTerminalPreview_") ? "Windows Terminal Preview" : "Windows Terminal" ); else if(ffStrbufIgnCaseEqualS(&result->prettyName, "conhost")) ffStrbufSetStatic(&result->prettyName, "Windows Console"); else if(ffStrbufIgnCaseEqualS(&result->prettyName, "Code")) ffStrbufSetStatic(&result->prettyName, "Visual Studio Code"); else if(ffStrbufIgnCaseEqualS(&result->prettyName, "explorer")) ffStrbufSetStatic(&result->prettyName, "Windows Explorer"); else if(ffStrbufEqualS(&result->prettyName, "wezterm-gui")) ffStrbufSetStatic(&result->prettyName, "WezTerm"); else if(ffStrbufIgnCaseEqualS(&result->prettyName, "sshd") || ffStrbufStartsWithIgnCaseS(&result->prettyName, "sshd-")) { const char* tty = getenv("SSH_TTY"); if (tty) ffStrbufSetS(&result->prettyName, tty); } } bool fftsGetTerminalVersion(FFstrbuf* processName, FFstrbuf* exe, FFstrbuf* version); const FFShellResult* ffDetectShell(void) { static FFShellResult result; static bool init = false; if(init) return &result; init = true; ffStrbufInit(&result.processName); ffStrbufInitA(&result.exe, MAX_PATH); result.exeName = ""; ffStrbufInit(&result.exePath); ffStrbufInit(&result.prettyName); ffStrbufInit(&result.version); result.pid = 0; result.ppid = 0; result.tty = -1; uint32_t ppid; if(!ffProcessGetInfoWindows(0, &ppid, NULL, NULL, NULL, NULL, NULL)) return &result; const char* ignoreParent = getenv("FFTS_IGNORE_PARENT"); if (ignoreParent && ffStrEquals(ignoreParent, "1")) ffProcessGetInfoWindows(ppid, &ppid, NULL, NULL, NULL, NULL, NULL); ppid = getShellInfo(&result, ppid); if (result.processName.length > 0) { setShellInfoDetails(&result); char tmp[MAX_PATH]; strcpy(tmp, result.exeName); char* ext = strrchr(tmp, '.'); if (ext) *ext = '\0'; fftsGetShellVersion(result.exePath.length > 0 ? &result.exePath : &result.exe, tmp, &result.version); } return &result; } const FFTerminalResult* ffDetectTerminal(void) { static FFTerminalResult result; static bool init = false; if(init) return &result; init = true; ffStrbufInit(&result.processName); ffStrbufInitA(&result.exe, MAX_PATH); result.exeName = ""; ffStrbufInit(&result.exePath); ffStrbufInit(&result.prettyName); ffStrbufInit(&result.version); ffStrbufInit(&result.tty); result.pid = 0; result.ppid = 0; uint32_t ppid = ffDetectShell()->ppid; if(ppid) getTerminalInfo(&result, ppid); if(result.processName.length == 0) getTerminalFromEnv(&result); if(result.processName.length == 0) detectDefaultTerminal(&result); if(result.processName.length > 0) { setTerminalInfoDetails(&result); fftsGetTerminalVersion(&result.processName, result.exePath.length > 0 ? &result.exePath : &result.exe, &result.version); } return &result; } ================================================ FILE: src/detection/terminalsize/terminalsize.h ================================================ #pragma once #include "fastfetch.h" #include "modules/terminalsize/option.h" typedef struct FFTerminalSizeResult { uint16_t rows; uint16_t columns; uint16_t width; uint16_t height; } FFTerminalSizeResult; bool ffDetectTerminalSize(FFTerminalSizeResult* result); ================================================ FILE: src/detection/terminalsize/terminalsize_linux.c ================================================ #include "terminalsize.h" #include "common/io.h" #include #include #include #ifdef __sun #include #endif bool ffDetectTerminalSize(FFTerminalSizeResult* result) { struct winsize winsize = {}; static int ttyfd = STDOUT_FILENO; if (!isatty(ttyfd)) ttyfd = open("/dev/tty", O_RDWR | O_NOCTTY | O_CLOEXEC); ioctl(ttyfd, TIOCGWINSZ, &winsize); if (winsize.ws_row == 0 || winsize.ws_col == 0) ffGetTerminalResponse("\e[18t", 2, "\e[8;%hu;%hut", &winsize.ws_row, &winsize.ws_col); if (winsize.ws_ypixel == 0 || winsize.ws_xpixel == 0) ffGetTerminalResponse("\e[14t", 2, "\e[4;%hu;%hut", &winsize.ws_ypixel, &winsize.ws_xpixel); if (winsize.ws_row == 0 && winsize.ws_col == 0) return false; result->rows = winsize.ws_row; result->columns = winsize.ws_col; result->width = winsize.ws_xpixel; result->height = winsize.ws_ypixel; return true; } ================================================ FILE: src/detection/terminalsize/terminalsize_windows.c ================================================ #include "terminalsize.h" #include "common/io.h" #include bool ffDetectTerminalSize(FFTerminalSizeResult* result) { HANDLE hOutput = GetStdHandle(STD_OUTPUT_HANDLE); FF_AUTO_CLOSE_FD HANDLE hConout = INVALID_HANDLE_VALUE; { DWORD outputMode; if (!GetConsoleMode(hOutput, &outputMode)) { hConout = CreateFileW(L"CONOUT$", GENERIC_READ | GENERIC_WRITE, FILE_SHARE_WRITE, 0, OPEN_EXISTING, 0, NULL); hOutput = hConout; } } { CONSOLE_SCREEN_BUFFER_INFO csbi; if (GetConsoleScreenBufferInfo(hOutput, &csbi)) { result->columns = (uint16_t) (csbi.srWindow.Right - csbi.srWindow.Left + 1); result->rows = (uint16_t) (csbi.srWindow.Bottom - csbi.srWindow.Top + 1); } else { // Windows Terminal doesn't report `\e` for some reason ffGetTerminalResponse("\e[18t", 2, "%*[^;];%hu;%hut", &result->rows, &result->columns); } } if (result->columns == 0 && result->rows == 0) return false; { CONSOLE_FONT_INFOEX cfi; if(GetCurrentConsoleFontEx(hOutput, FALSE, &cfi)) // Only works for ConHost { result->width = result->columns * (uint16_t) cfi.dwFontSize.X; result->height = result->rows * (uint16_t) cfi.dwFontSize.Y; } if (result->width == 0 || result->height == 0) { // Windows Terminal doesn't report `\e` for some reason ffGetTerminalResponse("\e[14t", 2, "%*[^;];%hu;%hut", &result->height, &result->width); } } return result->columns > 0 && result->rows > 0; } ================================================ FILE: src/detection/terminaltheme/terminaltheme.c ================================================ #include "terminaltheme.h" #include "common/io.h" #include "common/stringUtils.h" #include static bool detectByEscapeCode(FFTerminalThemeResult* result) { // Windows Terminal removes all `\e`s in its output if (ffGetTerminalResponse("\e]10;?\e\\" /*fg*/ "\e]11;?\e\\" /*bg*/, 6, "%*[^0-9]10;rgb:%" SCNx16 "/%" SCNx16 "/%" SCNx16 /*"\e\\"*/ "%*[^0-9]11;rgb:%" SCNx16 "/%" SCNx16 "/%" SCNx16 /*"\e\\"*/, &result->fg.r, &result->fg.g, &result->fg.b, &result->bg.r, &result->bg.g, &result->bg.b) == NULL) { if (result->fg.r > 0x0100 || result->fg.g > 0x0100 || result->fg.b > 0x0100) result->fg.r /= 0x0100, result->fg.g /= 0x0100, result->fg.b /= 0x0100; if (result->bg.r > 0x0100 || result->bg.g > 0x0100 || result->bg.b > 0x0100) result->bg.r /= 0x0100, result->bg.g /= 0x0100, result->bg.b /= 0x0100; } else return false; return true; } static FFTerminalThemeColor fgbgToColor(int num) { // https://github.com/dalance/termbg/blob/13c478a433fa182e65c401d26a1e7792a7f7f453/src/lib.rs#L251 switch (num) { case 0: return (FFTerminalThemeColor){ 0, 0, 0, false}; // black case 1: return (FFTerminalThemeColor){205, 0, 0, false}; // red case 2: return (FFTerminalThemeColor){ 0, 205, 0, false}; // green case 3: return (FFTerminalThemeColor){205, 205, 0, false}; // yellow case 4: return (FFTerminalThemeColor){ 0, 0, 238, false}; // blue case 5: return (FFTerminalThemeColor){205, 0, 205, false}; // magenta case 6: return (FFTerminalThemeColor){ 0, 205, 205, false}; // cyan case 7: return (FFTerminalThemeColor){229, 229, 229, false}; // white case 8: return (FFTerminalThemeColor){127, 127, 127, false}; // bright black case 9: return (FFTerminalThemeColor){255, 0, 0, false}; // bright red case 10: return (FFTerminalThemeColor){ 0, 255, 0, false}; // bright green case 11: return (FFTerminalThemeColor){255, 255, 0, false}; // bright yellow case 12: return (FFTerminalThemeColor){ 92, 92, 255, false}; // bright blue case 13: return (FFTerminalThemeColor){255, 0, 255, false}; // bright magenta case 14: return (FFTerminalThemeColor){ 0, 255, 255, false}; // bright cyan case 15: return (FFTerminalThemeColor){255, 255, 255, false}; // bright white default: return (FFTerminalThemeColor){ 0, 0, 0, false}; // invalid } } static bool detectByEnv(FFTerminalThemeResult* result) { const char* color = getenv("COLORFGBG"); // 7;0 if (!ffStrSet(color)) return false; int f, g; if (sscanf(color, "%d;%d", &f, &g) != 2) return false; result->fg = fgbgToColor(f); result->bg = fgbgToColor(g); return true; } static inline bool detectColor(FFTerminalThemeResult* result, bool forceEnv) { if (!forceEnv && detectByEscapeCode(result)) return true; return detectByEnv(result); } bool ffDetectTerminalTheme(FFTerminalThemeResult* result, bool forceEnv) { if (!detectColor(result, forceEnv)) return false; result->fg.dark = result->fg.r * 299 + result->fg.g * 587 + result->fg.b * 114 < 128000; result->bg.dark = result->bg.r * 299 + result->bg.g * 587 + result->bg.b * 114 < 128000; return true; } ================================================ FILE: src/detection/terminaltheme/terminaltheme.h ================================================ #pragma once #include "fastfetch.h" #include "modules/terminaltheme/option.h" typedef struct FFTerminalThemeColor { uint16_t r; uint16_t g; uint16_t b; bool dark; } FFTerminalThemeColor; typedef struct FFTerminalThemeResult { FFTerminalThemeColor fg; FFTerminalThemeColor bg; } FFTerminalThemeResult; bool ffDetectTerminalTheme(FFTerminalThemeResult* result, bool forceEnv); ================================================ FILE: src/detection/theme/theme.h ================================================ #pragma once #include "fastfetch.h" #include "modules/theme/option.h" typedef struct FFThemeResult { FFstrbuf theme1; FFstrbuf theme2; } FFThemeResult; const char* ffDetectTheme(FFThemeResult* result); ================================================ FILE: src/detection/theme/theme_apple.c ================================================ #include "theme.h" #include "detection/os/os.h" const char* ffDetectTheme(FFThemeResult* result) { const FFOSResult* os = ffDetectOS(); char* str_end; const char* version = os->version.chars; unsigned long osNum = strtoul(version, &str_end, 10); if (str_end != version) { if (osNum > 15) { // Tahoe ffStrbufSetStatic(&result->theme1, "Liquid Glass"); } else if (osNum < 10) { ffStrbufSetStatic(&result->theme1, "Platinum"); } else { ffStrbufSetStatic(&result->theme1, "Aqua"); } } return NULL; } ================================================ FILE: src/detection/theme/theme_linux.c ================================================ #include "theme.h" #include "common/parsing.h" #include "detection/gtk_qt/gtk_qt.h" #include "detection/displayserver/displayserver.h" const char* ffDetectTheme(FFThemeResult* result) { const FFDisplayServerResult* wmde = ffConnectDisplayServer(); if(ffStrbufIgnCaseEqualS(&wmde->wmProtocolName, FF_WM_PROTOCOL_TTY)) return "Theme isn't supported in TTY"; const FFQtResult* plasma = ffDetectQt(); const FFstrbuf* gtk2 = &ffDetectGTK2()->theme; const FFstrbuf* gtk3 = &ffDetectGTK3()->theme; const FFstrbuf* gtk4 = &ffDetectGTK4()->theme; if(plasma->widgetStyle.length == 0 && plasma->colorScheme.length == 0 && gtk2->length == 0 && gtk3->length == 0 && gtk4->length == 0) return "No themes found"; ffParseGTK(&result->theme2, gtk2, gtk3, gtk4); FF_STRBUF_AUTO_DESTROY plasmaColorPretty = ffStrbufCreate(); if(ffStrbufStartsWithIgnCase(&plasma->colorScheme, &plasma->widgetStyle)) ffStrbufAppendNS(&plasmaColorPretty, plasma->colorScheme.length - plasma->widgetStyle.length, &plasma->colorScheme.chars[plasma->widgetStyle.length]); else ffStrbufAppend(&plasmaColorPretty, &plasma->colorScheme); ffStrbufTrim(&plasmaColorPretty, ' '); if(plasma->widgetStyle.length > 0) { ffStrbufAppend(&result->theme1, &plasma->widgetStyle); if(plasma->colorScheme.length > 0) { ffStrbufAppendS(&result->theme1, " ("); if(plasmaColorPretty.length > 0) ffStrbufAppend(&result->theme1, &plasmaColorPretty); else ffStrbufAppend(&result->theme1, &plasma->colorScheme); ffStrbufAppendC(&result->theme1, ')'); } } else if(plasma->colorScheme.length > 0) { if(plasmaColorPretty.length > 0) ffStrbufAppend(&result->theme1, &plasmaColorPretty); else ffStrbufAppend(&result->theme1, &plasma->colorScheme); } if(plasma->widgetStyle.length > 0 || plasma->colorScheme.length > 0) ffStrbufAppendS(&result->theme1, " [Qt]"); return NULL; } ================================================ FILE: src/detection/theme/theme_nosupport.c ================================================ #include "theme.h" const char* ffDetectTheme(FF_MAYBE_UNUSED FFThemeResult* result) { return "Not supported on this platform"; } ================================================ FILE: src/detection/theme/theme_windows.c ================================================ #include "theme.h" #include "detection/os/os.h" const char* ffDetectTheme(FFThemeResult* result) { const FFOSResult* os = ffDetectOS(); uint32_t ver = (uint32_t) ffStrbufToUInt(&os->version, 0); if (ver > 1000) { // Windows Server if (ver >= 2016) ffStrbufSetStatic(&result->theme1, "Fluent"); else if (ver >= 2012) ffStrbufSetStatic(&result->theme1, "Metro"); else ffStrbufSetStatic(&result->theme1, "Aero"); } else { if (ver >= 10) ffStrbufSetStatic(&result->theme1, "Fluent"); else if (ver >= 8) ffStrbufSetStatic(&result->theme1, "Metro"); else ffStrbufSetStatic(&result->theme1, "Aero"); } return NULL; } ================================================ FILE: src/detection/tpm/tpm.h ================================================ #pragma once #include "fastfetch.h" #include "modules/tpm/option.h" typedef struct FFTPMResult { FFstrbuf version; FFstrbuf description; } FFTPMResult; const char* ffDetectTPM(FFTPMResult* result); ================================================ FILE: src/detection/tpm/tpm_apple.c ================================================ #include "tpm.h" #ifndef __aarch64__ #include "common/apple/cf_helpers.h" #include #endif const char* ffDetectTPM(FFTPMResult* result) { #ifdef __aarch64__ ffStrbufSetStatic(&result->version, "2.0"); ffStrbufSetStatic(&result->description, "Apple Silicon Security"); return NULL; #else FF_IOOBJECT_AUTO_RELEASE io_service_t t2Service = IOServiceGetMatchingService( MACH_PORT_NULL, IOServiceMatching("AppleT2")); if (t2Service) { ffStrbufSetStatic(&result->version, "2.0"); ffStrbufSetStatic(&result->description, "Apple T2 Security Chip"); return NULL; } #endif return "No Apple Security hardware detected"; } ================================================ FILE: src/detection/tpm/tpm_bsd.c ================================================ #include "tpm.h" #include "common/sysctl.h" #include "common/kmod.h" const char* ffDetectTPM(FFTPMResult* result) { if (ffSysctlGetString("dev.tpmcrb.0.%desc", &result->description) != NULL) { if (!ffKmodLoaded("tpm")) return "`tpm` kernel module is not loaded"; return "TPM device is not found"; } if (ffStrbufContainS(&result->description, "2.0")) ffStrbufSetStatic(&result->version, "2.0"); else if (ffStrbufContainS(&result->description, "1.2")) ffStrbufSetStatic(&result->version, "1.2"); else ffStrbufSetStatic(&result->version, "unknown"); return NULL; } ================================================ FILE: src/detection/tpm/tpm_linux.c ================================================ #include "tpm.h" #include "common/io.h" const char* ffDetectTPM(FFTPMResult* result) { if (!ffPathExists("/sys/class/tpm/tpm0/", FF_PATHTYPE_DIRECTORY)) { if (!ffPathExists("/sys/class/tpm/", FF_PATHTYPE_DIRECTORY)) return "TPM is not supported by kernel"; return "TPM device is not found"; } if (ffReadFileBuffer("/sys/class/tpm/tpm0/tpm_version_major", &result->version)) { ffStrbufTrimRightSpace(&result->version); if (ffStrbufEqualS(&result->version, "2")) ffStrbufSetStatic(&result->version, "2.0"); } if (ffReadFileBuffer("/sys/class/tpm/tpm0/device/description", &result->description)) ffStrbufTrimRightSpace(&result->description); return NULL; } ================================================ FILE: src/detection/tpm/tpm_nosupport.c ================================================ #include "tpm.h" const char* ffDetectTPM(FF_MAYBE_UNUSED FFTPMResult* result) { return "Not supported on this platform"; } ================================================ FILE: src/detection/tpm/tpm_windows.c ================================================ #include "tpm.h" #include "common/library.h" #include #include #include const char* ffDetectTPM(FFTPMResult* result) { FF_LIBRARY_LOAD_MESSAGE(tbs, "TBS" FF_LIBRARY_EXTENSION, -1) FF_LIBRARY_LOAD_SYMBOL_MESSAGE(tbs, Tbsi_GetDeviceInfo) TPM_DEVICE_INFO deviceInfo = {}; TBS_RESULT code = ffTbsi_GetDeviceInfo(sizeof(deviceInfo), &deviceInfo); if (code != TBS_SUCCESS) return code == (TBS_RESULT) TBS_E_TPM_NOT_FOUND ? "TPM device is not found" : "Tbsi_GetDeviceInfo() failed"; switch (deviceInfo.tpmVersion) { case TPM_VERSION_12: ffStrbufSetStatic(&result->version, "1.2"); break; case TPM_VERSION_20: ffStrbufSetStatic(&result->version, "2.0"); break; default: ffStrbufSetStatic(&result->version, "unknown"); break; } switch (deviceInfo.tpmInterfaceType) { case TPM_IFTYPE_1: ffStrbufSetF(&result->description, "I/O-port or MMIO TPM %s", result->version.chars); break; case TPM_IFTYPE_TRUSTZONE: ffStrbufSetF(&result->description, "Trustzone TPM %s", result->version.chars); break; case TPM_IFTYPE_HW: ffStrbufSetF(&result->description, "HW TPM %s", result->version.chars); break; case TPM_IFTYPE_EMULATOR: ffStrbufSetF(&result->description, "SW-emulator TPM %s", result->version.chars); break; case TPM_IFTYPE_SPB: ffStrbufSetF(&result->description, "SPB attached TPM %s", result->version.chars); break; default: break; } return NULL; } ================================================ FILE: src/detection/uptime/uptime.h ================================================ #pragma once #include "fastfetch.h" #include "modules/uptime/option.h" typedef struct FFUptimeResult { uint64_t bootTime; uint64_t uptime; } FFUptimeResult; const char* ffDetectUptime(FFUptimeResult* result); ================================================ FILE: src/detection/uptime/uptime_bsd.c ================================================ #include "uptime.h" #include "common/time.h" #include #include const char* ffDetectUptime(FFUptimeResult* result) { #if __NetBSD__ struct timespec bootTime; #else struct timeval bootTime; #endif size_t bootTimeSize = sizeof(bootTime); if(sysctl( (int[]) {CTL_KERN, KERN_BOOTTIME}, 2, &bootTime, &bootTimeSize, NULL, 0 ) != 0) return "sysctl({CTL_KERN, KERN_BOOTTIME}) failed"; #if __NetBSD__ result->bootTime = (uint64_t) bootTime.tv_sec * 1000 + (uint64_t) bootTime.tv_nsec / 1000000; #else result->bootTime = (uint64_t) bootTime.tv_sec * 1000 + (uint64_t) bootTime.tv_usec / 1000; #endif result->uptime = ffTimeGetNow() - result->bootTime; return NULL; } ================================================ FILE: src/detection/uptime/uptime_haiku.c ================================================ #include "uptime.h" #include "common/time.h" const char* ffDetectUptime(FFUptimeResult* result) { result->uptime = (uint64_t) system_time() / 1000; result->bootTime = ffTimeGetNow() - result->uptime; return NULL; } ================================================ FILE: src/detection/uptime/uptime_linux.c ================================================ #include "uptime.h" #include "common/time.h" #include "common/io.h" #include const char* ffDetectUptime(FFUptimeResult* result) { #ifndef __ANDROID__ // cat: /proc/uptime: Permission denied // #620 char buf[64]; ssize_t nRead = ffReadFileData("/proc/uptime", ARRAY_SIZE(buf) - 1, buf); if(nRead > 0) { buf[nRead] = '\0'; char *err = NULL; double sec = strtod(buf, &err); if(err != buf) { result->uptime = (uint64_t) (sec * 1000); result->bootTime = ffTimeGetNow() - result->uptime; return NULL; } } #endif #ifndef __GNU__ struct timespec uptime; if (clock_gettime(CLOCK_BOOTTIME, &uptime) != 0) return "clock_gettime(CLOCK_BOOTTIME) failed"; result->uptime = (uint64_t) uptime.tv_sec * 1000 + (uint64_t) uptime.tv_nsec / 1000000; result->bootTime = ffTimeGetNow() - result->uptime; return NULL; #else return "read(/proc/uptime) failed"; #endif } ================================================ FILE: src/detection/uptime/uptime_sunos.c ================================================ #include "uptime.h" #include "common/time.h" #include const char* ffDetectUptime(FFUptimeResult* result) { struct utmpx* ut; setutxent(); while (NULL != (ut = getutxent())) { if (ut->ut_type == BOOT_TIME) { result->bootTime = (uint64_t) ut->ut_tv.tv_sec * 1000 + (uint64_t) ut->ut_tv.tv_usec / 1000000; result->uptime = ffTimeGetNow() - result->bootTime; break; } } endutxent(); return NULL; } ================================================ FILE: src/detection/uptime/uptime_windows.c ================================================ #include "uptime.h" #include "common/time.h" #include "common/windows/nt.h" const char* ffDetectUptime(FFUptimeResult* result) { // GetInterruptTime with Win7 support uint64_t interruptTime = ffKSystemTimeToUInt64(&SharedUserData->InterruptTime); result->uptime = interruptTime / 10000; // Convert from 100-nanosecond intervals to milliseconds result->bootTime = ffTimeGetNow() - result->uptime; // Alternatively, `NtQuerySystemInformation(SystemTimeOfDayInformation)` reports the boot time directly, // whose result exactly equals what WMI `Win32_OperatingSystem` reports // with much lower accuracy (0.5 seconds) return NULL; } ================================================ FILE: src/detection/users/users.h ================================================ #pragma once #include "fastfetch.h" #include "modules/users/option.h" typedef struct FFUserResult { FFstrbuf name; FFstrbuf hostName; FFstrbuf clientIp; FFstrbuf sessionName; uint64_t loginTime; // ms } FFUserResult; const char* ffDetectUsers(FFUsersOptions* options, FFlist* users); ================================================ FILE: src/detection/users/users_linux.c ================================================ #include "common/io.h" #include "common/properties.h" #include "fastfetch.h" #include "users.h" #include #if FF_HAVE_UTMPX #include #else //for Android compatibility #include #define utmpx utmp #define setutxent setutent #define getutxent getutent #endif #if __linux__ || __GNU__ #include #include #endif #if __linux__ bool detectUserBySystemd(const FFstrbuf* pathUsers, FFlist* users) { FF_STRBUF_AUTO_DESTROY state = ffStrbufCreate(); FF_STRBUF_AUTO_DESTROY userName = ffStrbufCreate(); FF_STRBUF_AUTO_DESTROY loginTime = ffStrbufCreate(); FF_STRBUF_AUTO_DESTROY sessions = ffStrbufCreate(); // WARNING: This is private data. Do not parse if (!ffParsePropFileValues(pathUsers->chars, 4, (FFpropquery[]) { {"NAME=", &userName}, {"STATE=", &state}, {"REALTIME=", &loginTime}, {"ONLINE_SESSIONS=", &sessions}, }) || !ffStrbufEqualS(&state, "active")) return false; FFUserResult* user = FF_LIST_ADD(FFUserResult, *users); ffStrbufInitMove(&user->name, &userName); ffStrbufInit(&user->hostName); ffStrbufInit(&user->sessionName); ffStrbufInit(&user->clientIp); ffStrbufSubstrBefore(&loginTime, loginTime.length - 3); // converts us to ms user->loginTime = ffStrbufToUInt(&loginTime, 0); FF_STRBUF_AUTO_DESTROY pathSessions = ffStrbufCreateS("/run/systemd/sessions/"); const uint32_t pathSessionsBaseLen = pathSessions.length; FF_STRBUF_AUTO_DESTROY tty = ffStrbufCreate(); FF_STRBUF_AUTO_DESTROY remoteHost = ffStrbufCreate(); FF_STRBUF_AUTO_DESTROY service = ffStrbufCreate(); char* token = NULL; size_t n = 0; while (ffStrbufGetdelim(&token, &n, ' ', &sessions)) { ffStrbufSubstrBefore(&pathSessions, pathSessionsBaseLen); ffStrbufAppendS(&pathSessions, token); ffStrbufClear(&remoteHost); ffStrbufClear(&service); ffStrbufClear(&tty); ffStrbufClear(&loginTime); // WARNING: This is private data. Do not parse if (ffParsePropFileValues(pathSessions.chars, 4, (FFpropquery[]) { {"REMOTE_HOST=", &remoteHost}, {"TTY=", &tty}, {"SERVICE=", &service}, {"REALTIME=", &loginTime}, }) && !ffStrbufEqualS(&service, "systemd-user")) { if (remoteHost.length) { ffStrbufTrimRight(&remoteHost, ']'); ffStrbufTrimLeft(&remoteHost, '['); ffStrbufInitMove(&user->hostName, &remoteHost); } else ffStrbufSetStatic(&user->hostName, "localhost"); ffStrbufInitMove(&user->sessionName, tty.length ? &tty : &service); if (loginTime.length) { ffStrbufSubstrBefore(&loginTime, loginTime.length - 3); // converts us to ms user->loginTime = ffStrbufToUInt(&loginTime, 0); } break; } } return true; } const char* detectBySystemd(FFUsersOptions* options, FFlist* users) { // For some reason, debian/ubuntu no longer updates `/var/run/utmp` (#2064) // Query systemd instead FF_STRBUF_AUTO_DESTROY pathUsers = ffStrbufCreateS("/run/systemd/users/"); if (options->myselfOnly) { ffStrbufAppendUInt(&pathUsers, instance.state.platform.uid); detectUserBySystemd(&pathUsers, users); } else { const uint32_t pathUsersBaseLen = pathUsers.length; FF_AUTO_CLOSE_DIR DIR* dirp = opendir(pathUsers.chars); if (!dirp) return "opendir(\"/run/systemd/users/\") failed"; struct dirent* entry; while ((entry = readdir(dirp))) { if (entry->d_type != DT_REG) continue; ffStrbufAppendS(&pathUsers, entry->d_name); detectUserBySystemd(&pathUsers, users); ffStrbufSubstrBefore(&pathUsers, pathUsersBaseLen); } } return NULL; } #endif #if __linux__ || __GNU__ static void fillUtmpIpAddr(FFUserResult* user, struct utmpx* n) { bool isIpv6 = false; for (int i = 1; i < 4; ++i) { if (n->ut_addr_v6[i] != 0) { isIpv6 = true; break; } } if (isIpv6) { char ipv6_str[INET6_ADDRSTRLEN]; if (inet_ntop(AF_INET6, n->ut_addr_v6, ipv6_str, INET6_ADDRSTRLEN) != NULL) { ffStrbufSetS(&user->clientIp, ipv6_str); } } else if (n->ut_addr_v6[0] != 0) { char ipv4_str[INET_ADDRSTRLEN]; if (inet_ntop(AF_INET, n->ut_addr_v6, ipv4_str, INET_ADDRSTRLEN) != NULL) { ffStrbufSetS(&user->clientIp, ipv4_str); } } } #else static void fillUtmpIpAddr(FF_MAYBE_UNUSED FFUserResult* user, FF_MAYBE_UNUSED struct utmpx* n) { } #endif const char* detectByUtmp(FFUsersOptions* options, FFlist* users) { struct utmpx* n = NULL; setutxent(); next: while ((n = getutxent())) { if (n->ut_type != USER_PROCESS) continue; if (options->myselfOnly && !ffStrbufEqualS(&instance.state.platform.userName, n->ut_user)) continue; FF_LIST_FOR_EACH(FFUserResult, user, *users) { if(ffStrbufEqualS(&user->name, n->ut_user)) { uint64_t newLoginTime = (uint64_t) n->ut_tv.tv_sec * 1000 + (uint64_t) n->ut_tv.tv_usec / 1000; if (newLoginTime > user->loginTime) { ffStrbufSetS(&user->hostName, n->ut_host); ffStrbufSetS(&user->sessionName, n->ut_line); fillUtmpIpAddr(user, n); user->loginTime = newLoginTime; } goto next; } } FFUserResult* user = FF_LIST_ADD(FFUserResult, *users); ffStrbufInitS(&user->name, n->ut_user); ffStrbufInitS(&user->hostName, n->ut_host); ffStrbufInitS(&user->sessionName, n->ut_line); ffStrbufInit(&user->clientIp); fillUtmpIpAddr(user, n); user->loginTime = (uint64_t) n->ut_tv.tv_sec * 1000 + (uint64_t) n->ut_tv.tv_usec / 1000; } endutxent(); return NULL; } const char* ffDetectUsers(FFUsersOptions* options, FFlist* users) { const char* err = detectByUtmp(options, users); if (err) return err; #if __linux__ if (users->length == 0) detectBySystemd(options, users); #endif return NULL; } ================================================ FILE: src/detection/users/users_nosupport.c ================================================ #include "fastfetch.h" #include "users.h" const char* ffDetectUsers(FFUsersOptions* options, FFlist* users) { return "Not supported on this platform"; } ================================================ FILE: src/detection/users/users_obsd.c ================================================ #include "fastfetch.h" #include "users.h" #include "common/io.h" #include const char* ffDetectUsers(FF_MAYBE_UNUSED FFUsersOptions* options, FFlist* users) { FF_AUTO_CLOSE_FILE FILE* fp = fopen(_PATH_UTMP, "r"); if (!fp) return "fopen(_PATH_UTMP, r) failed"; struct utmp n; next: while (fread(&n, sizeof(n), 1, fp) == 1) { if (!n.ut_name[0]) continue; if (options->myselfOnly && !ffStrbufEqualS(&instance.state.platform.userName, n.ut_name)) continue; FF_LIST_FOR_EACH(FFUserResult, user, *users) { if (ffStrbufEqualS(&user->name, n.ut_name)) goto next; } FFUserResult* user = (FFUserResult*) ffListAdd(users); ffStrbufInitS(&user->name, n.ut_name); ffStrbufInitS(&user->hostName, n.ut_host); ffStrbufInitS(&user->sessionName, n.ut_line); ffStrbufInit(&user->clientIp); user->loginTime = (uint64_t) n.ut_time * 1000; } return NULL; } ================================================ FILE: src/detection/users/users_windows.c ================================================ #include "users.h" #include "common/windows/unicode.h" #include "common/time.h" #include #include #include const char* ffDetectUsers(FFUsersOptions* options, FFlist* users) { WTS_SESSION_INFO_1W* sessionInfo; DWORD sessionCount; DWORD level = 1; if (!WTSEnumerateSessionsExW(WTS_CURRENT_SERVER_HANDLE, &level, 0, &sessionInfo, &sessionCount)) return "WTSEnumerateSessionsW(WTS_CURRENT_SERVER_HANDLE) failed"; for (DWORD i = 0; i < sessionCount; i++) { WTS_SESSION_INFO_1W* session = &sessionInfo[i]; if (session->State != WTSActive) continue; FF_STRBUF_AUTO_DESTROY userName = ffStrbufCreateWS(session->pUserName); if (options->myselfOnly && !ffStrbufEqual(&instance.state.platform.userName, &userName)) continue; FFUserResult* user = (FFUserResult*) ffListAdd(users); ffStrbufInitMove(&user->name, &userName); ffStrbufInitWS(&user->hostName, session->pHostName); ffStrbufInitWS(&user->sessionName, session->pSessionName); ffStrbufInit(&user->clientIp); user->loginTime = 0; DWORD bytes = 0; PWTS_CLIENT_ADDRESS address = NULL; if (WTSQuerySessionInformationW(WTS_CURRENT_SERVER_HANDLE, session->SessionId, WTSClientAddress, (LPWSTR *) &address, &bytes)) { if (address->AddressFamily == AF_INET) ffStrbufSetF(&user->clientIp, "%u.%u.%u.%u", address->Address[2], address->Address[3], address->Address[4], address->Address[5]); else if (address->AddressFamily == AF_INET6) { char ipStr[INET6_ADDRSTRLEN]; const char* end = RtlIpv6AddressToStringA((const IN6_ADDR *) address->Address, ipStr); ffStrbufSetNS(&user->clientIp, (uint32_t) (end - ipStr), ipStr); } WTSFreeMemory(address); } bytes = 0; PWTSINFOW wtsInfo = NULL; if (WTSQuerySessionInformationW(WTS_CURRENT_SERVER_HANDLE, session->SessionId, WTSSessionInfo, (LPWSTR *) &wtsInfo, &bytes)) { user->loginTime = ffFileTimeToUnixMs((uint64_t) wtsInfo->LogonTime.QuadPart); WTSFreeMemory(wtsInfo); } } WTSFreeMemoryExW(WTSTypeSessionInfoLevel1, sessionInfo, 1); return NULL; } ================================================ FILE: src/detection/version/version.c ================================================ #include "version.h" #if defined(__x86_64__) #define FF_ARCHITECTURE "x86_64" #elif defined(__i386__) #define FF_ARCHITECTURE "i386" #elif defined(__ia64__) #define FF_ARCHITECTURE "ia64" #elif defined(__aarch64__) #define FF_ARCHITECTURE "aarch64" #elif defined(__arm__) #define FF_ARCHITECTURE "arm" #elif defined(__mips__) #define FF_ARCHITECTURE "mips" #elif defined(__powerpc__) || defined(__powerpc) #define FF_ARCHITECTURE "powerpc" #elif defined(__riscv__) || defined(__riscv) #define FF_ARCHITECTURE "riscv" #elif defined(__s390x__) #define FF_ARCHITECTURE "s390x" #elif defined(__loongarch__) #define FF_ARCHITECTURE "loongarch" #elif defined(__sparc__) #define FF_ARCHITECTURE "sparc" #elif defined(__alpha__) #define FF_ARCHITECTURE "alpha" #elif defined(__hppa__) #define FF_ARCHITECTURE "hppa" #elif defined(__sh__) #define FF_ARCHITECTURE "sh" #elif defined(__m68k__) #define FF_ARCHITECTURE "m68k" #else #define FF_ARCHITECTURE "Unknown" #endif #if defined(__ANDROID__) #define FF_SYSNAME "Android" #elif defined(__linux__) #define FF_SYSNAME "Linux" #elif defined(__DragonFly__) // We define `__FreeBSD__` on DragonFly BSD for simplification #define FF_SYSNAME "DragonFly" #elif defined(__MidnightBSD__) #define FF_SYSNAME "MidnightBSD" #elif defined(__FreeBSD__) #define FF_SYSNAME "FreeBSD" #elif defined(__APPLE__) #define FF_SYSNAME "macOS" #elif defined(_WIN32) #define FF_SYSNAME "Windows" #elif defined(__sun) #define FF_SYSNAME "SunOS" #elif defined(__OpenBSD__) #define FF_SYSNAME "OpenBSD" #elif defined(__NetBSD__) #define FF_SYSNAME "NetBSD" #elif defined(__HAIKU__) #define FF_SYSNAME "Haiku" #elif defined(__GNU__) #define FF_SYSNAME "GNU" #else #define FF_SYSNAME "Unknown" #endif #define FF_STR_INDIR(x) #x #define FF_STR(x) FF_STR_INDIR(x) FFVersionResult ffVersionResult = { .projectName = FASTFETCH_PROJECT_NAME, .sysName = FF_SYSNAME, .architecture = FF_ARCHITECTURE, .version = FASTFETCH_PROJECT_VERSION, .versionTweak = FASTFETCH_PROJECT_VERSION_TWEAK, .versionGit = FASTFETCH_PROJECT_VERSION_GIT, .cmakeBuiltType = FASTFETCH_PROJECT_CMAKE_BUILD_TYPE, .compileTime = __DATE__ ", " __TIME__, .compiler = #ifdef __clang__ #ifdef _MSC_VER "clang-cl " ; #elif defined(__APPLE__) && defined(__apple_build_version__) "Apple clang " #else "clang " #endif FF_STR(__clang_major__) "." FF_STR(__clang_minor__) "." FF_STR(__clang_patchlevel__) #if defined(__APPLE__) && defined(__apple_build_version__) " (" FF_STR(__apple_build_version__) ")" #endif , #elif defined(__GNUC__) "gcc " FF_STR(__GNUC__) "." FF_STR(__GNUC_MINOR__) "." FF_STR(__GNUC_PATCHLEVEL__), #elif defined(_MSC_VER) "msvc " FF_STR(_MSC_VER), #else "unknown", #endif .debugMode = #ifndef NDEBUG true, #else false, #endif }; ================================================ FILE: src/detection/version/version.h ================================================ #pragma once #include "fastfetch.h" #include "modules/version/option.h" typedef struct FFVersionResult { const char* projectName; const char* sysName; const char* architecture; const char* version; const char* versionTweak; const char* versionGit; const char* cmakeBuiltType; const char* compileTime; const char* compiler; bool debugMode; } FFVersionResult; extern FFVersionResult ffVersionResult; ================================================ FILE: src/detection/vulkan/vulkan.c ================================================ #include "fastfetch.h" #include "common/debug.h" #include "detection/gpu/gpu.h" #include "detection/vulkan/vulkan.h" #ifdef FF_HAVE_VULKAN #include "common/library.h" #include "common/io.h" #include "common/parsing.h" #include "common/stringUtils.h" #include #include static inline void applyVulkanVersion(uint32_t vulkanVersion, FFVersion* ffVersion) { ffVersion->major = VK_VERSION_MAJOR(vulkanVersion); ffVersion->minor = VK_VERSION_MINOR(vulkanVersion); ffVersion->patch = VK_VERSION_PATCH(vulkanVersion); } static void applyDriverName(VkPhysicalDeviceDriverPropertiesKHR* properties, FFstrbuf* result) { if(!ffStrSet(properties->driverName)) return; ffStrbufAppendS(result, properties->driverName); /* * Some drivers (android for example) expose a multiline string as driver info. * It contains too much info anyways, so we just don't append it. */ if(!ffStrSet(properties->driverInfo) || strchr(properties->driverInfo, '\n') != NULL) return; ffStrbufAppendS(result, " ["); ffStrbufAppendS(result, properties->driverInfo); ffStrbufAppendC(result, ']'); } static const char* detectVulkan(FFVulkanResult* result) { FF_DEBUG("Starting Vulkan detection"); FF_LIBRARY_LOAD_MESSAGE(vulkan, #if __APPLE__ "libMoltenVK" FF_LIBRARY_EXTENSION, -1 #elif _WIN32 "vulkan-1" FF_LIBRARY_EXTENSION, -1 #else "libvulkan" FF_LIBRARY_EXTENSION, 2 #endif ) FF_LIBRARY_LOAD_SYMBOL_MESSAGE(vulkan, vkGetInstanceProcAddr) FF_LIBRARY_LOAD_SYMBOL_MESSAGE(vulkan, vkCreateInstance) FF_LIBRARY_LOAD_SYMBOL_MESSAGE(vulkan, vkDestroyInstance) FF_LIBRARY_LOAD_SYMBOL_MESSAGE(vulkan, vkEnumeratePhysicalDevices) //Some drivers (nvdc) print messages to stdout //and that is the best way I found to disable that FF_SUPPRESS_IO(); FF_DEBUG("Suppressed stdout/stderr during Vulkan probing to avoid noisy drivers"); FFVersion instanceVersion = FF_VERSION_INIT; //We need to get the function pointer this way, because it is only provided by vulkan 1.1 and higher. //a dlsym would fail on 1.0 implementations PFN_vkEnumerateInstanceVersion ffvkEnumerateInstanceVersion = (PFN_vkEnumerateInstanceVersion) ffvkGetInstanceProcAddr(NULL, "vkEnumerateInstanceVersion"); if(ffvkEnumerateInstanceVersion != NULL) { uint32_t version; if(ffvkEnumerateInstanceVersion(&version) == VK_SUCCESS) { applyVulkanVersion(version, &instanceVersion); FF_DEBUG("Detected Vulkan instance version: %u.%u.%u", instanceVersion.major, instanceVersion.minor, instanceVersion.patch); } else FF_DEBUG("vkEnumerateInstanceVersion() is available but returned a non-success status"); } else FF_DEBUG("vkEnumerateInstanceVersion() is unavailable (likely Vulkan 1.0 runtime)"); const uint32_t projectVersion = VK_MAKE_VERSION( FASTFETCH_PROJECT_VERSION_MAJOR, FASTFETCH_PROJECT_VERSION_MINOR, FASTFETCH_PROJECT_VERSION_PATCH ); VkInstance vkInstance; FF_DEBUG("Creating Vulkan instance with requested API version %s", instanceVersion.minor >= 1 ? "1.1" : "1.0"); VkResult res = ffvkCreateInstance(&(VkInstanceCreateInfo) { .sType = VK_STRUCTURE_TYPE_INSTANCE_CREATE_INFO, .pNext = NULL, .pApplicationInfo = &(VkApplicationInfo) { .sType = VK_STRUCTURE_TYPE_APPLICATION_INFO, .pNext = NULL, .pApplicationName = FASTFETCH_PROJECT_NAME, .applicationVersion = projectVersion, .pEngineName = "vulkanPrintGPUs", .engineVersion = projectVersion, // We need to request 1.1 to get physicalDeviceDriverProperties .apiVersion = instanceVersion.minor >= 1 ? VK_API_VERSION_1_1 : VK_API_VERSION_1_0 }, .enabledLayerCount = 0, .ppEnabledLayerNames = NULL, .enabledExtensionCount = 0, .ppEnabledExtensionNames = NULL, .flags = 0 }, NULL, &vkInstance); if(res != VK_SUCCESS) { FF_DEBUG("ffvkCreateInstance() failed with VkResult=%d", res); switch (res) { case VK_ERROR_OUT_OF_HOST_MEMORY: return "ffvkCreateInstance() failed: VK_ERROR_OUT_OF_HOST_MEMORY"; case VK_ERROR_OUT_OF_DEVICE_MEMORY: return "ffvkCreateInstance() failed: VK_ERROR_OUT_OF_DEVICE_MEMORY"; case VK_ERROR_INITIALIZATION_FAILED: return "ffvkCreateInstance() failed: VK_ERROR_INITIALIZATION_FAILED"; case VK_ERROR_LAYER_NOT_PRESENT: return "ffvkCreateInstance() failed: VK_ERROR_LAYER_NOT_PRESENT"; case VK_ERROR_EXTENSION_NOT_PRESENT: return "ffvkCreateInstance() failed: VK_ERROR_EXTENSION_NOT_PRESENT"; case VK_ERROR_INCOMPATIBLE_DRIVER: return "ffvkCreateInstance() failed: VK_ERROR_INCOMPATIBLE_DRIVER"; default: return "ffvkCreateInstance() failed: unknown error"; } } FF_DEBUG("Vulkan instance created successfully"); //if instance creation succeeded, but vkEnumerateInstanceVersion didn't, this means we are running against a vulkan 1.0 implementation //explicitly set this version, if no device is found, so we still have at least this info if(instanceVersion.major == 0 && instanceVersion.minor == 0 && instanceVersion.patch == 0) { instanceVersion.major = 1; FF_DEBUG("Falling back to Vulkan instance version 1.0 due to unavailable enumerate call"); } VkPhysicalDevice physicalDevices[128]; uint32_t physicalDeviceCount = (uint32_t) ARRAY_SIZE(physicalDevices); res = ffvkEnumeratePhysicalDevices(vkInstance, &physicalDeviceCount, physicalDevices); if(res != VK_SUCCESS) { FF_DEBUG("ffvkEnumeratePhysicalDevices() failed with VkResult=%d", res); ffvkDestroyInstance(vkInstance, NULL); switch (res) { case VK_ERROR_OUT_OF_HOST_MEMORY: return "ffvkEnumeratePhysicalDevices() failed: VK_ERROR_OUT_OF_HOST_MEMORY"; case VK_ERROR_OUT_OF_DEVICE_MEMORY: return "ffvkEnumeratePhysicalDevices() failed: VK_ERROR_OUT_OF_DEVICE_MEMORY"; case VK_ERROR_INITIALIZATION_FAILED: return "ffvkEnumeratePhysicalDevices() failed: VK_ERROR_INITIALIZATION_FAILED"; case VK_INCOMPLETE: return "ffvkEnumeratePhysicalDevices() failed: VK_INCOMPLETE"; default: return "ffvkEnumeratePhysicalDevices() failed"; } } FF_DEBUG("Enumerated %u Vulkan physical device(s)", physicalDeviceCount); PFN_vkGetPhysicalDeviceProperties ffvkGetPhysicalDeviceProperties = NULL; PFN_vkGetPhysicalDeviceProperties2 ffvkGetPhysicalDeviceProperties2 = (PFN_vkGetPhysicalDeviceProperties2) ffvkGetInstanceProcAddr(vkInstance, "vkGetPhysicalDeviceProperties2"); // 1.1 if(!ffvkGetPhysicalDeviceProperties2) ffvkGetPhysicalDeviceProperties = (PFN_vkGetPhysicalDeviceProperties) ffvkGetInstanceProcAddr(vkInstance, "vkGetPhysicalDeviceProperties"); FF_DEBUG("Using %s for querying physical device properties", ffvkGetPhysicalDeviceProperties2 ? "vkGetPhysicalDeviceProperties2" : "vkGetPhysicalDeviceProperties"); PFN_vkGetPhysicalDeviceMemoryProperties ffvkGetPhysicalDeviceMemoryProperties = (PFN_vkGetPhysicalDeviceMemoryProperties) ffvkGetInstanceProcAddr(vkInstance, "vkGetPhysicalDeviceMemoryProperties"); if(!ffvkGetPhysicalDeviceMemoryProperties) { FF_DEBUG("vkGetPhysicalDeviceMemoryProperties is unavailable"); ffvkDestroyInstance(vkInstance, NULL); return "vkGetPhysicalDeviceMemoryProperties is not available"; } FFVersion maxDeviceApiVersion = FF_VERSION_INIT; FFVersion maxDeviceConformanceVersion = FF_VERSION_INIT; for(uint32_t i = 0; i < physicalDeviceCount; i++) { //Get device properties. //On VK 1.1 and up, we use vkGetPhysicalDeviceProperties2, so we can put VkPhysicalDeviceDriverProperties in the pNext chain. //This is required to get the driver name and conformance version. VkPhysicalDeviceDriverPropertiesKHR driverProperties = { .sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_DRIVER_PROPERTIES_KHR, }; VkPhysicalDeviceProperties2 physicalDeviceProperties = { .sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_PROPERTIES_2, .pNext = &driverProperties, }; if(ffvkGetPhysicalDeviceProperties2 != NULL) ffvkGetPhysicalDeviceProperties2(physicalDevices[i], &physicalDeviceProperties); else ffvkGetPhysicalDeviceProperties(physicalDevices[i], &physicalDeviceProperties.properties); FF_DEBUG("Processing Vulkan device #%u: name='%s', vendorId=0x%04X, deviceId=0x%04X, type=%u", i, physicalDeviceProperties.properties.deviceName, physicalDeviceProperties.properties.vendorID, physicalDeviceProperties.properties.deviceID, physicalDeviceProperties.properties.deviceType); //We don't want software rasterizers to show up as physical gpu if(physicalDeviceProperties.properties.deviceType == VK_PHYSICAL_DEVICE_TYPE_CPU) { FF_DEBUG("Skipping CPU Vulkan device '%s'", physicalDeviceProperties.properties.deviceName); continue; } //If the device api version is higher than the current highest device api version, overwrite it //In this case, also use the current device driver name as the shown driver name FFVersion deviceAPIVersion = FF_VERSION_INIT; applyVulkanVersion(physicalDeviceProperties.properties.apiVersion, &deviceAPIVersion); if(ffVersionCompare(&deviceAPIVersion, &maxDeviceApiVersion) > 0) { maxDeviceApiVersion = deviceAPIVersion; applyDriverName(&driverProperties, &result->driver); FF_DEBUG("Updated max Vulkan device API version to %u.%u.%u (driver='%s')", maxDeviceApiVersion.major, maxDeviceApiVersion.minor, maxDeviceApiVersion.patch, result->driver.chars); } //If the device conformance version is higher than the current highest device conformance version, overwrite it if(ffvkGetPhysicalDeviceProperties2) { FFVersion deviceConformanceVersion = { .major = driverProperties.conformanceVersion.major, .minor = driverProperties.conformanceVersion.minor, .patch = driverProperties.conformanceVersion.patch, }; if(ffVersionCompare(&deviceConformanceVersion, &maxDeviceConformanceVersion) > 0) { maxDeviceConformanceVersion = deviceConformanceVersion; FF_DEBUG("Updated max Vulkan conformance version to %u.%u.%u", maxDeviceConformanceVersion.major, maxDeviceConformanceVersion.minor, maxDeviceConformanceVersion.patch); } } //Add the device to the list of devices shown by the GPU module // #456 FF_LIST_FOR_EACH(FFGPUResult, gpu, result->gpus) { if (gpu->deviceId == physicalDeviceProperties.properties.deviceID) { FF_DEBUG("Skipping duplicate Vulkan GPU entry for deviceId=0x%04X", physicalDeviceProperties.properties.deviceID); goto next; } } FFGPUResult* gpu = ffListAdd(&result->gpus); ffStrbufInitF(&gpu->platformApi, "Vulkan %u.%u.%u", deviceAPIVersion.major, deviceAPIVersion.minor, deviceAPIVersion.patch); gpu->deviceId = physicalDeviceProperties.properties.deviceID; ffStrbufInitS(&gpu->name, physicalDeviceProperties.properties.deviceName); gpu->type = physicalDeviceProperties.properties.deviceType == VK_PHYSICAL_DEVICE_TYPE_DISCRETE_GPU ? FF_GPU_TYPE_DISCRETE : FF_GPU_TYPE_INTEGRATED; ffStrbufInitS(&gpu->vendor, ffGPUGetVendorString(physicalDeviceProperties.properties.vendorID)); ffStrbufInitS(&gpu->driver, driverProperties.driverInfo); ffStrbufInit(&gpu->memoryType); FF_DEBUG("Added Vulkan GPU '%s' (vendor='%s', type=%s)", gpu->name.chars, gpu->vendor.chars, gpu->type == FF_GPU_TYPE_DISCRETE ? "discrete" : "integrated"); VkPhysicalDeviceMemoryProperties memoryProperties = {}; ffvkGetPhysicalDeviceMemoryProperties(physicalDevices[i], &memoryProperties); gpu->dedicated.total = gpu->shared.total = 0; gpu->dedicated.used = gpu->shared.used = FF_GPU_VMEM_SIZE_UNSET; for(uint32_t index = 0; index < memoryProperties.memoryHeapCount; ++index) { const VkMemoryHeap* heap = &memoryProperties.memoryHeaps[index]; FFGPUMemory* vmem = gpu->type == FF_GPU_TYPE_DISCRETE && (heap->flags & VK_MEMORY_HEAP_DEVICE_LOCAL_BIT) ? &gpu->dedicated : &gpu->shared; vmem->total += heap->size; } FF_DEBUG("Computed memory for '%s': dedicatedTotal=%llu, sharedTotal=%llu", gpu->name.chars, (unsigned long long) gpu->dedicated.total, (unsigned long long) gpu->shared.total); //No way to detect those using vulkan gpu->index = FF_GPU_INDEX_UNSET; gpu->coreCount = FF_GPU_CORE_COUNT_UNSET; gpu->temperature = FF_GPU_TEMP_UNSET; gpu->frequency = FF_GPU_FREQUENCY_UNSET; gpu->coreUsage = FF_GPU_CORE_USAGE_UNSET; next: continue; } ffVersionToPretty(&instanceVersion, &result->instanceVersion); ffVersionToPretty(&maxDeviceApiVersion, &result->apiVersion); ffVersionToPretty(&maxDeviceConformanceVersion, &result->conformanceVersion); FF_DEBUG("Vulkan detection finished: instanceVersion=%s, apiVersion=%s, conformanceVersion=%s, gpuCount=%u", result->instanceVersion.chars, result->apiVersion.chars, result->conformanceVersion.chars, result->gpus.length); ffvkDestroyInstance(vkInstance, NULL); FF_DEBUG("Destroyed Vulkan instance"); return NULL; } #endif FFVulkanResult* ffDetectVulkan(void) { static FFVulkanResult result; if (result.gpus.elementSize == 0) { FF_DEBUG("Initializing Vulkan detection cache"); ffStrbufInit(&result.driver); ffStrbufInit(&result.apiVersion); ffStrbufInit(&result.conformanceVersion); ffStrbufInit(&result.instanceVersion); ffListInit(&result.gpus, sizeof(FFGPUResult)); #ifdef FF_HAVE_VULKAN result.error = detectVulkan(&result); if (result.error) FF_DEBUG("Vulkan detection returned error: %s", result.error); #else result.error = "fastfetch was compiled without vulkan support"; FF_DEBUG("Vulkan support is disabled at compile time"); #endif } else FF_DEBUG("Reusing cached Vulkan detection result"); return &result; } ================================================ FILE: src/detection/vulkan/vulkan.h ================================================ #pragma once #include "fastfetch.h" #include "modules/vulkan/option.h" typedef struct FFVulkanResult { FFstrbuf driver; FFstrbuf apiVersion; FFstrbuf conformanceVersion; FFstrbuf instanceVersion; FFlist gpus; //List of FFGPUResult, see detection/gpu/gpu.h const char* error; } FFVulkanResult; FFVulkanResult* ffDetectVulkan(); ================================================ FILE: src/detection/wallpaper/wallpaper.h ================================================ #pragma once #include "fastfetch.h" const char* ffDetectWallpaper(FFstrbuf* result); ================================================ FILE: src/detection/wallpaper/wallpaper_apple.m ================================================ #include "wallpaper.h" #include "common/settings.h" #include "common/apple/osascript.h" #import const char* ffDetectWallpaper(FFstrbuf* result) { { // For Sonoma // https://github.com/JohnCoates/Aerial/issues/1332 NSError* error; NSString* fileName = [NSString stringWithFormat:@"file://%s/Library/Application Support/com.apple.wallpaper/Store/Index.plist", instance.state.platform.homeDir.chars]; NSDictionary* dict = [NSDictionary dictionaryWithContentsOfURL:[NSURL URLWithString:fileName] error:&error]; if (!error) { NSArray* choices = [dict valueForKeyPath:@"SystemDefault.Desktop.Content.Choices"]; if (choices.count > 0) { NSDictionary* choice = choices[0]; NSArray* files = choice[@"Files"]; if (files.count > 0) { NSString* file = files[0][@"relative"]; ffStrbufAppendS(result, [NSURL URLWithString:file].path.UTF8String); } else { NSString* provider = choice[@"Provider"]; NSString* builtinPrefix = @"com.apple.wallpaper.choice."; if ([provider hasPrefix:builtinPrefix]) provider = [provider substringFromIndex:builtinPrefix.length]; if ([provider isEqualToString:@"sonoma"]) ffStrbufSetStatic(result, "macOS Sonoma"); else if ([provider isEqualToString:@"aerials"]) // Most builtin aerial wallpapers are private ffStrbufSetStatic(result, "Built-in aerial photography"); else ffStrbufAppendF(result, "Built-in %s wallpaper", provider.UTF8String); } } if (result->length > 0) return NULL; } } #ifdef FF_HAVE_SQLITE3 { // For Ventura // https://stackoverflow.com/questions/301215/getting-desktop-background-on-mac FF_STRBUF_AUTO_DESTROY path = ffStrbufCreateCopy(&instance.state.platform.homeDir); ffStrbufAppendS(&path, "Library/Application Support/Dock/desktoppicture.db"); if (ffSettingsGetSQLite3String(path.chars, "SELECT value\n" "FROM preferences\n" "JOIN data ON preferences.data_id=data.ROWID\n" "JOIN pictures ON preferences.picture_id=pictures.ROWID\n" "JOIN displays ON pictures.display_id=displays.ROWID\n" "JOIN spaces ON pictures.space_id=spaces.ROWID\n" "WHERE display_id=1 AND space_id=1 AND key=1", result) ) return NULL; } #endif if (ffOsascript("tell application \"Finder\" to get POSIX path of (get desktop picture as alias)", result)) return NULL; return "All detection methods failed"; } ================================================ FILE: src/detection/wallpaper/wallpaper_linux.c ================================================ #include "wallpaper.h" #include "common/settings.h" #include "detection/gtk_qt/gtk_qt.h" const char* ffDetectWallpaper(FFstrbuf* result) { const FFstrbuf* wallpaper = NULL; const FFGTKResult* gtk = ffDetectGTK4(); if (gtk->wallpaper.length) wallpaper = >k->wallpaper; else { const FFQtResult* qt = ffDetectQt(); if (qt->wallpaper.length) wallpaper = &qt->wallpaper; } if (!wallpaper) return "Failed to detect the current wallpaper path"; if (ffStrbufStartsWithS(wallpaper, "file:///")) ffStrbufAppendS(result, wallpaper->chars + strlen("file://")); else ffStrbufAppend(result, wallpaper); return NULL; } ================================================ FILE: src/detection/wallpaper/wallpaper_nosupport.c ================================================ #include "wallpaper.h" const char* ffDetectWallpaper(FF_MAYBE_UNUSED FFstrbuf* result) { return "Not supported on this platform"; } ================================================ FILE: src/detection/wallpaper/wallpaper_windows.c ================================================ #include "wallpaper.h" #include "common/windows/registry.h" const char* ffDetectWallpaper(FFstrbuf* result) { FF_AUTO_CLOSE_FD HANDLE hKey = NULL; if(!ffRegOpenKeyForRead(HKEY_CURRENT_USER, L"Control Panel\\Desktop", &hKey, NULL)) return "ffRegOpenKeyForRead(Control Panel\\Desktop) failed"; if(!ffRegReadStrbuf(hKey, L"WallPaper", result, NULL)) return "ffRegReadStrbuf(WallPaper) failed"; return NULL; } ================================================ FILE: src/detection/weather/weather.c ================================================ #include "weather.h" #include "common/networking.h" #define FF_UNITIALIZED ((const char*)(uintptr_t) -1) static FFNetworkingState state; static const char* status = FF_UNITIALIZED; void ffPrepareWeather(FFWeatherOptions* options) { if (status != FF_UNITIALIZED) { fputs("Error: Weather module can only be used once due to internal limitations\n", stderr); exit(1); } state.timeout = options->timeout; FF_STRBUF_AUTO_DESTROY path = ffStrbufCreateS("/"); if (options->location.length) ffStrbufAppend(&path, &options->location); ffStrbufAppendS(&path, "?format="); ffStrbufAppend(&path, &options->outputFormat); switch (instance.config.display.tempUnit) { case FF_TEMPERATURE_UNIT_CELSIUS: ffStrbufAppendS(&path, "&m"); break; case FF_TEMPERATURE_UNIT_FAHRENHEIT: ffStrbufAppendS(&path, "&u"); break; default: break; } status = ffNetworkingSendHttpRequest(&state, "wttr.in", path.chars, "User-Agent: curl/0.0.0\r\n"); } const char* ffDetectWeather(FFWeatherOptions* options, FFstrbuf* result) { if(status == FF_UNITIALIZED) ffPrepareWeather(options); if(status != NULL) return status; ffStrbufEnsureFree(result, 4095); const char* error = ffNetworkingRecvHttpResponse(&state, result); state = (FFNetworkingState){}; status = FF_UNITIALIZED; if (error == NULL) { ffStrbufSubstrAfterFirstS(result, "\r\n\r\n"); ffStrbufTrimRightSpace(result); } else return error; if(result->length == 0) return "Empty server response received"; return NULL; } ================================================ FILE: src/detection/weather/weather.h ================================================ #pragma once #include "fastfetch.h" #include "modules/weather/option.h" void ffPrepareWeather(FFWeatherOptions* options); const char* ffDetectWeather(FFWeatherOptions* options, FFstrbuf* result); ================================================ FILE: src/detection/wifi/wifi.h ================================================ #pragma once #include "fastfetch.h" #include "modules/wifi/option.h" struct FFWifiInterface { FFstrbuf description; FFstrbuf status; }; struct FFWifiConnection { FFstrbuf status; FFstrbuf ssid; FFstrbuf bssid; FFstrbuf protocol; FFstrbuf security; double signalQuality; // Percentage double rxRate; double txRate; uint16_t channel; uint16_t frequency; // MHz }; typedef struct FFWifiResult { struct FFWifiInterface inf; struct FFWifiConnection conn; } 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? if (frequency == 2484) return 14; else if (frequency < 2484) return (uint16_t) ((frequency - 2407) / 5); else return (uint16_t) ((frequency / 5) - 1000); } ================================================ FILE: src/detection/wifi/wifi_android.c ================================================ #include "wifi.h" #include "common/processing.h" #include "common/properties.h" #define FF_TERMUX_API_PATH FASTFETCH_TARGET_DIR_ROOT "/libexec/termux-api" #define FF_TERMUX_API_PARAM "WifiConnectionInfo" static inline void wrapYyjsonFree(yyjson_doc** doc) { assert(doc); if (*doc) yyjson_doc_free(*doc); } const char* ffDetectWifi(FFlist* result) { FF_STRBUF_AUTO_DESTROY buffer = ffStrbufCreate(); if(ffProcessAppendStdOut(&buffer, (char* const[]){ FF_TERMUX_API_PATH, FF_TERMUX_API_PARAM, NULL })) return "Starting `" FF_TERMUX_API_PATH " " FF_TERMUX_API_PARAM "` failed"; yyjson_doc* __attribute__((__cleanup__(wrapYyjsonFree))) doc = yyjson_read_opts(buffer.chars, buffer.length, 0, NULL, NULL); if (!doc) return "Failed to parse wifi connection info"; yyjson_val* root = yyjson_doc_get_root(doc); if (!yyjson_is_obj(root)) return "Wifi info result is not a JSON object"; FFWifiResult* item = (FFWifiResult*)ffListAdd(result); ffStrbufInit(&item->inf.description); ffStrbufInit(&item->inf.status); ffStrbufInit(&item->conn.status); ffStrbufInit(&item->conn.ssid); ffStrbufInit(&item->conn.bssid); ffStrbufInit(&item->conn.protocol); ffStrbufInit(&item->conn.security); item->conn.signalQuality = -DBL_MAX; item->conn.rxRate = -DBL_MAX; item->conn.txRate = -DBL_MAX; item->conn.channel = 0; item->conn.frequency = 0; ffStrbufAppendJsonVal(&item->inf.status, yyjson_obj_get(root, "supplicant_state")); if(!item->inf.status.length) { ffStrbufAppendS(&item->inf.status, "Unknown"); return NULL; } if(!ffStrbufEqualS(&item->inf.status, "COMPLETED")) return NULL; double rssi = yyjson_get_num(yyjson_obj_get(root, "rssi")); item->conn.signalQuality = rssi >= -50 ? 100 : rssi <= -100 ? 0 : (rssi + 100) * 2; ffStrbufAppendJsonVal(&item->inf.description, yyjson_obj_get(root, "ip")); ffStrbufAppendJsonVal(&item->conn.bssid, yyjson_obj_get(root, "bssid")); ffStrbufAppendJsonVal(&item->conn.ssid, yyjson_obj_get(root, "ssid")); item->conn.frequency = (uint16_t) yyjson_get_int(yyjson_obj_get(root, "frequency_mhz")); item->conn.txRate = yyjson_get_num(yyjson_obj_get(root, "link_speed_mbps")); item->conn.channel = ffWifiFreqToChannel(item->conn.frequency); return NULL; } ================================================ FILE: src/detection/wifi/wifi_apple.m ================================================ #include "wifi.h" #include "common/processing.h" #include "common/stringUtils.h" #import static inline double rssiToSignalQuality(int rssi) { return (double) (rssi >= -50 ? 100 : rssi <= -100 ? 0 : (rssi + 100) * 2); } @interface CWNetworkProfile() @property(readonly, retain, nullable) NSArray *bssidList; @end const char* ffDetectWifi(FFlist* result) { NSArray* interfaces = CWWiFiClient.sharedWiFiClient.interfaces; if (!interfaces) return "CWWiFiClient.sharedWiFiClient.interfaces is nil"; for (CWInterface* inf in interfaces) { FFWifiResult* item = (FFWifiResult*) ffListAdd(result); ffStrbufInit(&item->inf.description); ffStrbufInit(&item->inf.status); ffStrbufInit(&item->conn.status); ffStrbufInit(&item->conn.ssid); ffStrbufInit(&item->conn.bssid); ffStrbufInit(&item->conn.protocol); ffStrbufInit(&item->conn.security); item->conn.signalQuality = -DBL_MAX; item->conn.rxRate = -DBL_MAX; item->conn.txRate = -DBL_MAX; item->conn.channel = 0; item->conn.frequency = 0; ffStrbufAppendS(&item->inf.description, inf.interfaceName.UTF8String); ffStrbufSetStatic(&item->inf.status, inf.powerOn ? "Power On" : "Power Off"); if(!inf.powerOn) continue; ffStrbufSetStatic(&item->conn.status, inf.interfaceMode != kCWInterfaceModeNone ? "Active" : "Inactive"); if(inf.interfaceMode == kCWInterfaceModeNone) continue; FF_STRBUF_AUTO_DESTROY ipconfig = ffStrbufCreate(); CWNetworkProfile* networkProfile = inf.configuration.networkProfiles.firstObject; if (inf.ssid) // https://developer.apple.com/forums/thread/732431 ffStrbufAppendS(&item->conn.ssid, inf.ssid.UTF8String); else if (networkProfile.ssid) ffStrbufSetStatic(&item->conn.ssid, inf.configuration.networkProfiles.firstObject.ssid.UTF8String); else ffStrbufSetStatic(&item->conn.ssid, ""); // https://developer.apple.com/forums/thread/732431 if (inf.bssid) ffStrbufAppendS(&item->conn.bssid, inf.bssid.UTF8String); else if (networkProfile.bssidList) ffStrbufSetStatic(&item->conn.bssid, [networkProfile.bssidList.firstObject[@"BSSID"] UTF8String]); else ffStrbufSetStatic(&item->conn.bssid, ""); switch(inf.activePHYMode) { case kCWPHYModeNone: ffStrbufSetStatic(&item->conn.protocol, "none"); break; case kCWPHYMode11a: ffStrbufSetStatic(&item->conn.protocol, "802.11a"); break; case kCWPHYMode11b: ffStrbufSetStatic(&item->conn.protocol, "802.11b"); break; case kCWPHYMode11g: ffStrbufSetStatic(&item->conn.protocol, "802.11g"); break; case kCWPHYMode11n: ffStrbufSetStatic(&item->conn.protocol, "802.11n (Wi-Fi 4)"); break; case kCWPHYMode11ac: ffStrbufSetStatic(&item->conn.protocol, "802.11ac (Wi-Fi 5)"); break; case 6 /*kCWPHYMode11ax*/: ffStrbufSetStatic(&item->conn.protocol, "802.11ax (Wi-Fi 6)"); break; case 7 /*kCWPHYMode11be?*/: ffStrbufSetStatic(&item->conn.protocol, "802.11be (Wi-Fi 7)"); break; default: if (inf.activePHYMode < 8) ffStrbufAppendF(&item->conn.protocol, "Unknown (%ld)", inf.activePHYMode); break; } item->conn.signalQuality = rssiToSignalQuality((int) inf.rssiValue); item->conn.txRate = inf.transmitRate; switch(inf.security) { case kCWSecurityNone: ffStrbufSetStatic(&item->conn.security, "Insecure"); break; case kCWSecurityWEP: ffStrbufSetStatic(&item->conn.security, "WEP"); break; case kCWSecurityWPAPersonal: ffStrbufSetStatic(&item->conn.security, "WPA Personal"); break; case kCWSecurityWPAPersonalMixed: ffStrbufSetStatic(&item->conn.security, "WPA Persional Mixed"); break; case kCWSecurityWPA2Personal: ffStrbufSetStatic(&item->conn.security, "WPA2 Personal"); break; case kCWSecurityPersonal: ffStrbufSetStatic(&item->conn.security, "Personal"); break; case kCWSecurityDynamicWEP: ffStrbufSetStatic(&item->conn.security, "Dynamic WEP"); break; case kCWSecurityWPAEnterprise: ffStrbufSetStatic(&item->conn.security, "WPA Enterprise"); break; case kCWSecurityWPAEnterpriseMixed: ffStrbufSetStatic(&item->conn.security, "WPA Enterprise Mixed"); break; case kCWSecurityWPA2Enterprise: ffStrbufSetStatic(&item->conn.security, "WPA2 Enterprise"); break; case kCWSecurityEnterprise: ffStrbufSetStatic(&item->conn.security, "Enterprise"); break; case 11 /*kCWSecurityWPA3Personal*/: ffStrbufSetStatic(&item->conn.security, "WPA3 Personal"); break; case 12 /*kCWSecurityWPA3Enterprise*/: ffStrbufSetStatic(&item->conn.security, "WPA3 Enterprise"); break; case 13 /*kCWSecurityWPA3Transition*/: ffStrbufSetStatic(&item->conn.security, "WPA3 Transition"); break; case 14 /*kCWSecurityOWE*/: ffStrbufSetStatic(&item->conn.security, "OWE"); break; case 15 /*kCWSecurityOWETransition*/: ffStrbufSetStatic(&item->conn.security, "OWE Transition"); break; default: ffStrbufAppendF(&item->conn.security, "Unknown (%ld)", inf.security); break; } item->conn.channel = (uint16_t) inf.wlanChannel.channelNumber; switch (inf.wlanChannel.channelBand) { case kCWChannelBand2GHz: item->conn.frequency = 2400; break; case kCWChannelBand5GHz: item->conn.frequency = 5400; break; case 3 /*kCWChannelBand6GHz*/: item->conn.frequency = 6400; break; default: item->conn.frequency = 0; break; } } return NULL; } ================================================ FILE: src/detection/wifi/wifi_bsd.c ================================================ #include "wifi.h" #include "common/io.h" #include "common/stringUtils.h" #include #include #include #include #include const char* ffDetectWifi(FFlist* result) { struct if_nameindex* infs = if_nameindex(); if(!infs) { return "if_nameindex() failed"; } FF_AUTO_CLOSE_FD int sock = socket(AF_INET, SOCK_DGRAM, 0); if(sock < 0) { if_freenameindex(infs); return "socket() failed"; } for(struct if_nameindex* i = infs; !(i->if_index == 0 && i->if_name == NULL); ++i) { if (!ffStrStartsWith(i->if_name, "wlan")) { continue; } FFWifiResult* item = (FFWifiResult*) ffListAdd(result); ffStrbufInitS(&item->inf.description, i->if_name); ffStrbufInit(&item->inf.status); ffStrbufInit(&item->conn.status); ffStrbufInit(&item->conn.ssid); ffStrbufInit(&item->conn.bssid); ffStrbufInit(&item->conn.protocol); ffStrbufInit(&item->conn.security); item->conn.signalQuality = -DBL_MAX; item->conn.rxRate = -DBL_MAX; item->conn.txRate = -DBL_MAX; item->conn.channel = 0; item->conn.frequency = 0; char ssid[IEEE80211_NWID_LEN + 1] = {}; struct ieee80211req ireq = {}; strlcpy(ireq.i_name, i->if_name, sizeof(ireq.i_name)); ireq.i_type = IEEE80211_IOC_SSID; ireq.i_data = ssid; ireq.i_len = sizeof(ssid) - 1; if (ioctl(sock, SIOCG80211, &ireq) < 0 || ireq.i_len == 0) { struct ifreq ifr; strlcpy(ifr.ifr_name, i->if_name, sizeof(ifr.ifr_name)); if (ioctl(sock, SIOCGIFFLAGS, &ifr) < 0) { ffStrbufSetStatic(&item->inf.status, "Unknown"); } else { ffStrbufSetStatic(&item->inf.status, ifr.ifr_flags & IFF_UP ? "Up" : "Down"); } ffStrbufAppendS(&item->conn.status, "Not associated"); continue; } ffStrbufSetStatic(&item->inf.status, "Up"); ffStrbufSetStatic(&item->conn.status, "Associated"); ffStrbufAppendNS(&item->conn.ssid, ireq.i_len, ssid); uint8_t bssid[IEEE80211_ADDR_LEN] = {}; ireq.i_type = IEEE80211_IOC_BSSID; ireq.i_data = bssid; ireq.i_len = sizeof(bssid); if (ioctl(sock, SIOCG80211, &ireq) >= 0) { ffStrbufSetF(&item->conn.bssid, "%02X:%02X:%02X:%02X:%02X:%02X", bssid[0], bssid[1], bssid[2], bssid[3], bssid[4], bssid[5]); } struct ieee80211_channel curchan = {}; ireq.i_type = IEEE80211_IOC_CURCHAN; ireq.i_data = &curchan; ireq.i_len = sizeof(curchan); if (ioctl(sock, SIOCG80211, &ireq) >= 0) { item->conn.channel = curchan.ic_ieee; item->conn.frequency = curchan.ic_freq; #ifdef IEEE80211_IS_CHAN_HE // for future use if (IEEE80211_IS_CHAN_HE(&curchan)) ffStrbufSetStatic(&item->conn.protocol, "802.11ax (Wi-Fi 6)"); else #endif if (IEEE80211_IS_CHAN_VHT(&curchan)) ffStrbufSetStatic(&item->conn.protocol, "802.11ac (Wi-Fi 5)"); else if (IEEE80211_IS_CHAN_HT(&curchan)) ffStrbufSetStatic(&item->conn.protocol, "802.11n (Wi-Fi 4)"); else if (IEEE80211_IS_CHAN_ANYG(&curchan)) ffStrbufSetStatic(&item->conn.protocol, "802.11g"); else if (IEEE80211_IS_CHAN_B(&curchan)) ffStrbufSetStatic(&item->conn.protocol, "802.11b"); else if (IEEE80211_IS_CHAN_A(&curchan)) ffStrbufSetStatic(&item->conn.protocol, "802.11a"); else if (IEEE80211_IS_CHAN_FHSS(&curchan)) ffStrbufSetStatic(&item->conn.protocol, "802.11 (FHSS)"); } union { struct ieee80211req_sta_req req; uint8_t buf[1024]; } stareq = {}; memcpy(stareq.req.is_u.macaddr, bssid, sizeof(bssid)); ireq.i_type = IEEE80211_IOC_STA_INFO; ireq.i_data = &stareq; ireq.i_len = sizeof(stareq); if (ioctl(sock, SIOCG80211, &ireq) >= 0) { struct ieee80211req_sta_info* sta = stareq.req.info; if (sta->isi_len != 0) { item->conn.signalQuality = (sta->isi_rssi >= -50 ? 100 : sta->isi_rssi <= -100 ? 0 : (sta->isi_rssi + 100) * 2); item->conn.rxRate = sta->isi_txmbps * 0.5; } } ireq.i_type = IEEE80211_IOC_AUTHMODE; ireq.i_data = NULL; ireq.i_len = 0; if (ioctl(sock, SIOCG80211, &ireq) >= 0) { switch (ireq.i_val) { case IEEE80211_AUTH_NONE: ffStrbufSetStatic(&item->conn.security, "Insecure"); break; case IEEE80211_AUTH_OPEN: ffStrbufSetStatic(&item->conn.security, "Open"); break; case IEEE80211_AUTH_SHARED: ffStrbufSetStatic(&item->conn.security, "Shared"); break; case IEEE80211_AUTH_8021X: ffStrbufSetStatic(&item->conn.security, "8021X"); break; case IEEE80211_AUTH_AUTO: ffStrbufSetStatic(&item->conn.security, "Auto"); break; case IEEE80211_AUTH_WPA: ffStrbufSetStatic(&item->conn.security, "WPA"); break; default: ffStrbufSetF(&item->conn.security, "Unknown (%d)", ireq.i_val); break; } } } if_freenameindex(infs); return NULL; } ================================================ FILE: src/detection/wifi/wifi_linux.c ================================================ #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 #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"; } 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); } 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"); } 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); if (!item->conn.status.length) { ffStrbufSetStatic(&item->conn.status, "connected"); FF_DEBUG("Setting connection status to 'connected'"); } 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"; } 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"; } DBusMessageIter arrayIterator; dbus.lib->ffdbus_message_iter_recurse(&rootIterator, &arrayIterator); NM80211ApFlags flags; NM80211ApSecurityFlags wpaFlags, rsnFlags; int flagCount = 0; FF_DEBUG("Parsing access point properties"); while(true) { if(dbus.lib->ffdbus_message_iter_get_arg_type(&arrayIterator) != DBUS_TYPE_DICT_ENTRY) FF_DBUS_ITER_CONTINUE(dbus, &arrayIterator) 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); 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); } } 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); } } 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); } } } 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); } } } } 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) } 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"); } if (!item->conn.security.length) { ffStrbufAppendS(&item->conn.security, "Insecure"); FF_DEBUG("No security detected, marking as 'Insecure'"); } else { ffStrbufTrimRight(&item->conn.security, '/'); FF_DEBUG("Final security string: %s", item->conn.security.chars); } if (wpaFlags & NM_802_11_AP_SEC_PAIR_TKIP || rsnFlags & NM_802_11_AP_SEC_PAIR_TKIP) { FF_DEBUG("Detected TKIP encryption"); } if (wpaFlags & NM_802_11_AP_SEC_PAIR_CCMP || rsnFlags & NM_802_11_AP_SEC_PAIR_CCMP) { FF_DEBUG("Detected CCMP/AES encryption"); } } FF_DEBUG("NetworkManager wifi detection completed successfully"); dbus.lib->ffdbus_message_unref(reply); return NULL; } #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; } if(output.length == 0) { FF_DEBUG("iw command output is empty"); return "iw command execution failed"; } if(!ffParsePropLines(output.chars, "Connected to ", &item->conn.bssid)) { FF_DEBUG("Not connected to any access point"); ffStrbufAppendS(&item->conn.status, "disconnected"); return NULL; } 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(ffParsePropLines(output.chars, "SSID: ", &item->conn.ssid)) FF_DEBUG("SSID: %s", item->conn.ssid.chars); 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); } } 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"); } } 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); } FF_DEBUG("iw wifi detection completed successfully"); return NULL; } #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: %m"); 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: %m"); // 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); FF_DEBUG("Protocol: %s", item->conn.protocol.chars); } else FF_DEBUG("Failed to get protocol name via ioctl: %m"); // 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]); ffStrbufTrimRight(&item->conn.bssid, ':'); FF_DEBUG("BSSID: %s", item->conn.bssid.chars); } else FF_DEBUG("Failed to get BSSID via ioctl: %m"); // 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: %m"); // 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++; } 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: %m"); // 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; 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: %m"); // 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: %m"); FF_DEBUG("ioctl wifi detection completed"); return NULL; } #endif // FF_HAVE_LINUX_WIRELESS const char* ffDetectWifi(FF_MAYBE_UNUSED FFlist* result) { FF_DEBUG("Starting wifi detection"); struct if_nameindex* infs = if_nameindex(); if(!infs) { FF_DEBUG("if_nameindex() failed: %m"); return "if_nameindex() failed"; } FF_STRBUF_AUTO_DESTROY buffer = ffStrbufCreate(); for(struct if_nameindex* i = infs; !(i->if_index == 0 && i->if_name == NULL); ++i) { FF_DEBUG("Checking interface: %s (index: %u)", i->if_name, i->if_index); ffStrbufSetF(&buffer, "/sys/class/net/%s/phy80211/", i->if_name); if(!ffPathExists(buffer.chars, FF_PATHTYPE_DIRECTORY)) { FF_DEBUG("Not a wifi interface (no phy80211 directory)"); continue; } FF_DEBUG("Found wifi interface: %s", i->if_name); FFWifiResult* item = (FFWifiResult*)ffListAdd(result); ffStrbufInitS(&item->inf.description, i->if_name); ffStrbufInit(&item->inf.status); ffStrbufInit(&item->conn.status); ffStrbufInit(&item->conn.ssid); ffStrbufInit(&item->conn.bssid); ffStrbufInit(&item->conn.protocol); ffStrbufInit(&item->conn.security); item->conn.signalQuality = -DBL_MAX; item->conn.rxRate = -DBL_MAX; item->conn.txRate = -DBL_MAX; item->conn.channel = 0; item->conn.frequency = 0; 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"); continue; } FF_DEBUG("Connection status: %c", operstate); if (operstate != 'u') { FF_DEBUG("Skipping interface as it's not up"); 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); 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); FF_DEBUG("Wifi detection completed, found %u wifi interfaces", result->length); return NULL; } ================================================ FILE: src/detection/wifi/wifi_nbsd.c ================================================ #include "wifi.h" #include "common/io.h" #include "common/stringUtils.h" #define COMPAT_FREEBSD_NET80211 1 #include #include #include #include #include #include // ieee80211 header of NetBSD is full of mess. Add compatibility macros from FreeBSD #undef IEEE80211_IS_CHAN_ANYG #define IEEE80211_IS_CHAN_ANYG(x) (IEEE80211_IS_CHAN_PUREG(x) || IEEE80211_IS_CHAN_G(x)) #undef IEEE80211_IS_CHAN_HT #define IEEE80211_IS_CHAN_HT(x) (((x)->ic_flags & IEEE80211_CHAN_HT) != 0) #undef IEEE80211_IS_CHAN_VHT #define IEEE80211_IS_CHAN_VHT(x) (((x)->ic_flags & IEEE80211_CHAN_VHT) != 0) const char* ffDetectWifi(FFlist* result) { struct if_nameindex* infs = if_nameindex(); if(!infs) { return "if_nameindex() failed"; } FF_AUTO_CLOSE_FD int sock = socket(AF_INET, SOCK_DGRAM, 0); if(sock < 0) { if_freenameindex(infs); return "socket() failed"; } for(struct if_nameindex* i = infs; !(i->if_index == 0 && i->if_name == NULL); ++i) { if (!ffStrStartsWith(i->if_name, "iwm")) { continue; } FFWifiResult* item = (FFWifiResult*) ffListAdd(result); ffStrbufInitS(&item->inf.description, i->if_name); ffStrbufInit(&item->inf.status); ffStrbufInit(&item->conn.status); ffStrbufInit(&item->conn.ssid); ffStrbufInit(&item->conn.bssid); ffStrbufInit(&item->conn.protocol); ffStrbufInit(&item->conn.security); item->conn.signalQuality = -DBL_MAX; item->conn.rxRate = -DBL_MAX; item->conn.txRate = -DBL_MAX; item->conn.channel = 0; item->conn.frequency = 0; char ssid[IEEE80211_NWID_LEN + 1] = {}; struct ieee80211req ireq = {}; strlcpy(ireq.i_name, i->if_name, sizeof(ireq.i_name)); ireq.i_type = IEEE80211_IOC_SSID; ireq.i_data = ssid; ireq.i_len = sizeof(ssid) - 1; if (ioctl(sock, SIOCG80211, &ireq) < 0 || ireq.i_len == 0) { struct ifreq ifr; strlcpy(ifr.ifr_name, i->if_name, sizeof(ifr.ifr_name)); if (ioctl(sock, SIOCGIFFLAGS, &ifr) < 0) { ffStrbufSetStatic(&item->inf.status, "Unknown"); } else { ffStrbufSetStatic(&item->inf.status, ifr.ifr_flags & IFF_UP ? "Up" : "Down"); } ffStrbufAppendS(&item->conn.status, "Not associated"); continue; } ffStrbufSetStatic(&item->inf.status, "Up"); ffStrbufSetStatic(&item->conn.status, "Associated"); ffStrbufAppendNS(&item->conn.ssid, ireq.i_len, ssid); uint8_t bssid[IEEE80211_ADDR_LEN] = {}; ireq.i_type = IEEE80211_IOC_BSSID; ireq.i_data = bssid; ireq.i_len = sizeof(bssid); if (ioctl(sock, SIOCG80211, &ireq) >= 0) { ffStrbufSetF(&item->conn.bssid, "%02X:%02X:%02X:%02X:%02X:%02X", bssid[0], bssid[1], bssid[2], bssid[3], bssid[4], bssid[5]); } struct ieee80211_channel curchan = {}; ireq.i_type = IEEE80211_IOC_CHANNEL; ireq.i_data = &curchan; ireq.i_len = sizeof(curchan); if (ioctl(sock, SIOCG80211, &ireq) >= 0) { // item->conn.channel = curchan.ic_ieee; // No ic_ieee in NetBSD item->conn.channel = ffWifiFreqToChannel(curchan.ic_freq); item->conn.frequency = curchan.ic_freq; #ifdef IEEE80211_IS_CHAN_HE // for future use if (IEEE80211_IS_CHAN_HE(&curchan)) ffStrbufSetStatic(&item->conn.protocol, "802.11ax (Wi-Fi 6)"); else #endif if (IEEE80211_IS_CHAN_VHT(&curchan)) ffStrbufSetStatic(&item->conn.protocol, "802.11ac (Wi-Fi 5)"); else if (IEEE80211_IS_CHAN_HT(&curchan)) ffStrbufSetStatic(&item->conn.protocol, "802.11n (Wi-Fi 4)"); else if (IEEE80211_IS_CHAN_ANYG(&curchan)) ffStrbufSetStatic(&item->conn.protocol, "802.11g"); else if (IEEE80211_IS_CHAN_B(&curchan)) ffStrbufSetStatic(&item->conn.protocol, "802.11b"); else if (IEEE80211_IS_CHAN_A(&curchan)) ffStrbufSetStatic(&item->conn.protocol, "802.11a"); else if (IEEE80211_IS_CHAN_FHSS(&curchan)) ffStrbufSetStatic(&item->conn.protocol, "802.11 (FHSS)"); } union { struct ieee80211req_sta_req req; uint8_t buf[1024]; } stareq = {}; memcpy(stareq.req.is_u.macaddr, bssid, sizeof(bssid)); ireq.i_type = IEEE80211_IOC_STA_INFO; ireq.i_data = &stareq; ireq.i_len = sizeof(stareq); if (ioctl(sock, SIOCG80211, &ireq) >= 0) { struct ieee80211req_sta_info* sta = stareq.req.info; if (sta->isi_len != 0) { int8_t rssi = (int8_t) sta->isi_rssi; // Strangely, `sta->isi_rssi` is unsigned item->conn.signalQuality = (rssi >= -50 ? 100 : rssi <= -100 ? 0 : (rssi + 100) * 2); if (sta->isi_txrate) { item->conn.txRate = (double)sta->isi_txrate / 2.0; } } } ireq.i_type = IEEE80211_IOC_AUTHMODE; ireq.i_data = NULL; ireq.i_len = 0; if (ioctl(sock, SIOCG80211, &ireq) >= 0) { switch (ireq.i_val) { case IEEE80211_AUTH_NONE: ffStrbufSetStatic(&item->conn.security, "Insecure"); break; case IEEE80211_AUTH_OPEN: ffStrbufSetStatic(&item->conn.security, "Open"); break; case IEEE80211_AUTH_SHARED: ffStrbufSetStatic(&item->conn.security, "Shared"); break; case IEEE80211_AUTH_8021X: ffStrbufSetStatic(&item->conn.security, "8021X"); break; case IEEE80211_AUTH_AUTO: ffStrbufSetStatic(&item->conn.security, "Auto"); break; case IEEE80211_AUTH_WPA: ffStrbufSetStatic(&item->conn.security, "WPA"); break; default: ffStrbufSetF(&item->conn.security, "Unknown (%d)", ireq.i_val); break; } } } if_freenameindex(infs); return NULL; } ================================================ FILE: src/detection/wifi/wifi_nosupport.c ================================================ #include "wifi.h" const char* ffDetectWifi(FF_MAYBE_UNUSED FFlist* result) { return "Not support on this platform"; } ================================================ FILE: src/detection/wifi/wifi_obsd.c ================================================ #include "wifi.h" #include "common/io.h" #include "common/stringUtils.h" #include #include #include #include #include #include const char* ffDetectWifi(FFlist* result) { struct if_nameindex* infs = if_nameindex(); if(!infs) { return "if_nameindex() failed"; } FF_AUTO_CLOSE_FD int sock = socket(AF_INET, SOCK_DGRAM, 0); if(sock < 0) { return "socket() failed"; } for(struct if_nameindex* i = infs; !(i->if_index == 0 && i->if_name == NULL); ++i) { if (!ffStrStartsWith(i->if_name, "iwm")) { continue; } FFWifiResult* item = (FFWifiResult*) ffListAdd(result); ffStrbufInitS(&item->inf.description, i->if_name); ffStrbufInit(&item->inf.status); ffStrbufInit(&item->conn.status); ffStrbufInit(&item->conn.ssid); ffStrbufInit(&item->conn.bssid); ffStrbufInit(&item->conn.protocol); ffStrbufInit(&item->conn.security); item->conn.signalQuality = -DBL_MAX; item->conn.rxRate = -DBL_MAX; item->conn.txRate = -DBL_MAX; item->conn.channel = 0; item->conn.frequency = 0; struct ieee80211_nodereq nr = {}; strlcpy(nr.nr_ifname, i->if_name, sizeof(nr.nr_ifname)); struct ifreq ifr = {}; strlcpy(ifr.ifr_name, i->if_name, sizeof(ifr.ifr_name)); if (ioctl(sock, SIOCGIFFLAGS, &ifr) < 0) { ffStrbufSetStatic(&item->inf.status, "Unknown"); } else { ffStrbufSetStatic(&item->inf.status, ifr.ifr_flags & IFF_UP ? "Up" : "Down"); } if (ioctl(sock, SIOCG80211NODE, &nr) < 0) { ffStrbufSetStatic(&item->conn.status, "Not associated"); continue; } if (nr.nr_nwid_len > 0) { ffStrbufSetStatic(&item->conn.status, "Associated"); ffStrbufAppendNS(&item->conn.ssid, nr.nr_nwid_len, (char*)nr.nr_nwid); } else { ffStrbufSetStatic(&item->conn.status, "Not associated"); continue; } ffStrbufSetF(&item->conn.bssid, "%02X:%02X:%02X:%02X:%02X:%02X", nr.nr_bssid[0], nr.nr_bssid[1], nr.nr_bssid[2], nr.nr_bssid[3], nr.nr_bssid[4], nr.nr_bssid[5]); item->conn.channel = nr.nr_channel; if (nr.nr_max_rssi) { item->conn.signalQuality = ((float)nr.nr_rssi / nr.nr_max_rssi) * 100.0; } if (nr.nr_flags & IEEE80211_NODEREQ_HT) { ffStrbufSetStatic(&item->conn.protocol, "802.11n (Wi-Fi 4)"); } else if (nr.nr_flags & IEEE80211_NODEREQ_VHT) { ffStrbufSetStatic(&item->conn.protocol, "802.11ac (Wi-Fi 5)"); } else if (nr.nr_chan_flags & IEEE80211_CHANINFO_5GHZ) { ffStrbufSetStatic(&item->conn.protocol, "802.11a"); } else if (nr.nr_chan_flags & IEEE80211_CHANINFO_2GHZ) { ffStrbufSetStatic(&item->conn.protocol, "802.11g"); } struct ieee80211_wpaparams wpa = {}; strlcpy(wpa.i_name, i->if_name, sizeof(wpa.i_name)); if (ioctl(sock, SIOCG80211WPAPARMS, &wpa) >= 0 && wpa.i_enabled) { if (wpa.i_protos & IEEE80211_WPA_PROTO_WPA2) ffStrbufSetStatic(&item->conn.security, "WPA2"); else if (wpa.i_protos & IEEE80211_WPA_PROTO_WPA1) ffStrbufSetStatic(&item->conn.security, "WPA"); } else { struct ieee80211_nwkey nwkey = {}; strlcpy(nwkey.i_name, i->if_name, sizeof(nwkey.i_name)); if (ioctl(sock, SIOCG80211NWKEY, &nwkey) >= 0) { if (nwkey.i_wepon) ffStrbufSetStatic(&item->conn.security, "WEP"); else ffStrbufSetStatic(&item->conn.security, "Open"); } } } if_freenameindex(infs); return NULL; } ================================================ FILE: src/detection/wifi/wifi_windows.c ================================================ #include "wifi.h" #include "common/library.h" #include "common/windows/unicode.h" #include #include #pragma GCC diagnostic push #pragma GCC diagnostic ignored "-Wswitch" static void convertIfStateToString(WLAN_INTERFACE_STATE state, FFstrbuf* result) { switch (state) { case wlan_interface_state_not_ready: ffStrbufAppendS(result, "Not ready"); break; case wlan_interface_state_connected: ffStrbufAppendS(result, "Connected"); break; case wlan_interface_state_ad_hoc_network_formed: ffStrbufAppendS(result, "Ad hoc network formed"); break; case wlan_interface_state_disconnecting: ffStrbufAppendS(result, "Disconnecting"); break; case wlan_interface_state_disconnected: ffStrbufAppendS(result, "Disconnected"); break; case wlan_interface_state_associating: ffStrbufAppendS(result, "Associating"); break; case wlan_interface_state_discovering: ffStrbufAppendS(result, "Discovering"); break; case wlan_interface_state_authenticating: ffStrbufAppendS(result, "Authenticating"); break; default: ffStrbufAppendS(result, "Unknown"); break; } } const char* ffDetectWifi(FFlist* result) { FF_LIBRARY_LOAD_MESSAGE(wlanapi, "wlanapi" FF_LIBRARY_EXTENSION, 1) FF_LIBRARY_LOAD_SYMBOL_MESSAGE(wlanapi, WlanOpenHandle) FF_LIBRARY_LOAD_SYMBOL_MESSAGE(wlanapi, WlanEnumInterfaces) FF_LIBRARY_LOAD_SYMBOL_MESSAGE(wlanapi, WlanQueryInterface) FF_LIBRARY_LOAD_SYMBOL_MESSAGE(wlanapi, WlanFreeMemory) FF_LIBRARY_LOAD_SYMBOL_MESSAGE(wlanapi, WlanCloseHandle) FF_LIBRARY_LOAD_SYMBOL_MESSAGE(wlanapi, WlanGetNetworkBssList) DWORD curVersion; HANDLE hClient = NULL; WLAN_INTERFACE_INFO_LIST* ifList = NULL; const char* error = NULL; if(ffWlanOpenHandle(2, NULL, &curVersion, &hClient) != ERROR_SUCCESS) { error = "WlanOpenHandle() failed"; goto exit; } if(ffWlanEnumInterfaces(hClient, NULL, &ifList) != ERROR_SUCCESS) { error = "WlanEnumInterfaces() failed"; goto exit; } for(uint32_t index = 0; index < ifList->dwNumberOfItems; ++index) { WLAN_INTERFACE_INFO* ifInfo = (WLAN_INTERFACE_INFO*)&ifList->InterfaceInfo[index]; FFWifiResult* item = (FFWifiResult*)ffListAdd(result); ffStrbufInitWS(&item->inf.description, ifInfo->strInterfaceDescription); ffStrbufInit(&item->inf.status); ffStrbufInit(&item->conn.status); ffStrbufInit(&item->conn.ssid); ffStrbufInit(&item->conn.bssid); ffStrbufInit(&item->conn.protocol); ffStrbufInit(&item->conn.security); item->conn.signalQuality = -DBL_MAX; item->conn.rxRate = -DBL_MAX; item->conn.txRate = -DBL_MAX; item->conn.channel = 0; item->conn.frequency = 0; convertIfStateToString(ifInfo->isState, &item->inf.status); if(ifInfo->isState != wlan_interface_state_connected) continue; WLAN_CONNECTION_ATTRIBUTES* connInfo = NULL; DWORD bufSize = sizeof(*connInfo); WLAN_OPCODE_VALUE_TYPE opCode = wlan_opcode_value_type_query_only; if(ffWlanQueryInterface(hClient, &ifInfo->InterfaceGuid, wlan_intf_opcode_current_connection, NULL, &bufSize, (PVOID*)&connInfo, &opCode) != ERROR_SUCCESS ) continue; convertIfStateToString(connInfo->isState, &item->conn.status); ffStrbufAppendNS(&item->conn.ssid, connInfo->wlanAssociationAttributes.dot11Ssid.uSSIDLength, (const char *)connInfo->wlanAssociationAttributes.dot11Ssid.ucSSID); for (size_t i = 0; i < sizeof(connInfo->wlanAssociationAttributes.dot11Bssid); i++) ffStrbufAppendF(&item->conn.bssid, "%.2X:", connInfo->wlanAssociationAttributes.dot11Bssid[i]); ffStrbufTrimRight(&item->conn.bssid, ':'); switch (connInfo->wlanAssociationAttributes.dot11PhyType) { case dot11_phy_type_fhss: ffStrbufAppendS(&item->conn.protocol, "802.11 (FHSS)"); break; case dot11_phy_type_dsss: ffStrbufAppendS(&item->conn.protocol, "802.11 (DSSS)"); break; case dot11_phy_type_irbaseband: ffStrbufAppendS(&item->conn.protocol, "802.11 (IR)"); break; case dot11_phy_type_ofdm: ffStrbufAppendS(&item->conn.protocol, "802.11a"); break; case dot11_phy_type_hrdsss: ffStrbufAppendS(&item->conn.protocol, "802.11b"); break; case dot11_phy_type_erp: ffStrbufAppendS(&item->conn.protocol, "802.11g"); break; case dot11_phy_type_ht: ffStrbufAppendS(&item->conn.protocol, "802.11n (Wi-Fi 4)"); break; case dot11_phy_type_vht: ffStrbufAppendS(&item->conn.protocol, "802.11ac (Wi-Fi 5)"); break; case dot11_phy_type_dmg: ffStrbufAppendS(&item->conn.protocol, "802.11ad (WiGig)"); break; case dot11_phy_type_he: ffStrbufAppendS(&item->conn.protocol, "802.11ax (Wi-Fi 6)"); break; case dot11_phy_type_eht: ffStrbufAppendS(&item->conn.protocol, "802.11be (Wi-Fi 7)"); break; default: ffStrbufAppendF(&item->conn.protocol, "Unknown (%u)", (unsigned)connInfo->wlanAssociationAttributes.dot11PhyType); break; } item->conn.signalQuality = connInfo->wlanAssociationAttributes.wlanSignalQuality; item->conn.rxRate = connInfo->wlanAssociationAttributes.ulRxRate / 1000.; item->conn.txRate = connInfo->wlanAssociationAttributes.ulTxRate / 1000.; if(connInfo->wlanSecurityAttributes.bSecurityEnabled) { switch (connInfo->wlanSecurityAttributes.dot11AuthAlgorithm) { case DOT11_AUTH_ALGO_80211_OPEN: ffStrbufAppendS(&item->conn.security, "802.11 Open"); break; case DOT11_AUTH_ALGO_80211_SHARED_KEY: ffStrbufAppendS(&item->conn.security, "802.11 Shared"); break; case DOT11_AUTH_ALGO_WPA: ffStrbufAppendS(&item->conn.security, "WPA"); break; case DOT11_AUTH_ALGO_WPA_PSK: ffStrbufAppendS(&item->conn.security, "WPA-PSK"); break; case DOT11_AUTH_ALGO_WPA_NONE: ffStrbufAppendS(&item->conn.security, "WPA-None"); break; case DOT11_AUTH_ALGO_RSNA: ffStrbufAppendS(&item->conn.security, "WPA2"); break; case DOT11_AUTH_ALGO_RSNA_PSK: ffStrbufAppendS(&item->conn.security, "WPA2-PSK"); break; case DOT11_AUTH_ALGO_WPA3: ffStrbufAppendS(&item->conn.security, "WPA3"); break; case DOT11_AUTH_ALGO_WPA3_SAE: ffStrbufAppendS(&item->conn.security, "WPA3-SAE"); break; case 10 /* DOT11_AUTH_ALGO_OWE */: ffStrbufAppendS(&item->conn.security, "OWE"); break; case 11 /* DOT11_AUTH_ALGO_WPA3_ENT */: ffStrbufAppendS(&item->conn.security, "WPA3-ENT"); break; default: ffStrbufAppendF(&item->conn.security, "Unknown (%u)", (unsigned)connInfo->wlanSecurityAttributes.dot11AuthAlgorithm); break; } if(connInfo->wlanSecurityAttributes.bOneXEnabled) ffStrbufAppendS(&item->conn.security, " 802.11X"); } else ffStrbufAppendS(&item->conn.security, "Insecure"); WLAN_BSS_LIST* bssList = NULL; if (ffWlanGetNetworkBssList(hClient, &ifInfo->InterfaceGuid, &connInfo->wlanAssociationAttributes.dot11Ssid, connInfo->wlanAssociationAttributes.dot11BssType, connInfo->wlanSecurityAttributes.bSecurityEnabled, NULL, &bssList) == ERROR_SUCCESS && bssList->dwNumberOfItems > 0 ) { item->conn.frequency = (uint16_t) (bssList->wlanBssEntries[0].ulChCenterFrequency / 1000); ffWlanFreeMemory(bssList); } ffWlanFreeMemory(connInfo); ULONG* channelNumber = 0; bufSize = sizeof(*channelNumber); if(ffWlanQueryInterface(hClient, &ifInfo->InterfaceGuid, wlan_intf_opcode_channel_number, NULL, &bufSize, (PVOID*)&channelNumber, &opCode) == ERROR_SUCCESS ) { item->conn.channel = (uint16_t) *channelNumber; ffWlanFreeMemory(channelNumber); } } exit: if(ifList) ffWlanFreeMemory(ifList); if(hClient) ffWlanCloseHandle(hClient, NULL); return error; } #pragma GCC diagnostic pop ================================================ FILE: src/detection/wm/wm.h ================================================ #pragma once #include "fastfetch.h" #include "modules/wm/wm.h" const char* ffDetectWMPlugin(FFstrbuf* pluginName); const char* ffDetectWMVersion(const FFstrbuf* wmName, FFstrbuf* result, FFWMOptions* options); ================================================ FILE: src/detection/wm/wm_apple.m ================================================ #include "wm.h" #include "common/sysctl.h" #include "common/mallocHelper.h" #include "common/stringUtils.h" #include #include #import const char* ffDetectWMPlugin(FFstrbuf* pluginName) { int request[] = {CTL_KERN, KERN_PROC, KERN_PROC_ALL}; u_int requestLength = ARRAY_SIZE(request); size_t length = 0; FF_AUTO_FREE struct kinfo_proc* processes = ffSysctlGetData(request, requestLength, &length); if(processes == NULL) return "sysctl(CTL_KERN, KERN_PROC, KERN_PROC_ALL) failed"; assert(length % sizeof(struct kinfo_proc) == 0); for(size_t i = 0; i < length / sizeof(struct kinfo_proc); i++) { const struct kinfo_proc* proc = &processes[i]; if (proc->kp_eproc.e_ppid != 1) continue; const char* comm = proc->kp_proc.p_comm; if( !ffStrEqualsIgnCase(comm, "rectangle") && // 28.6k !ffStrEqualsIgnCase(comm, "yabai") && // 28.4k !ffStrEqualsIgnCase(comm, "aerospace") && // 19.6k !ffStrEqualsIgnCase(comm, "amethyst") && // 16k !ffStrEqualsIgnCase(comm, "glazewm") && // 11.6k #if 0 // Unmaintained !ffStrEqualsIgnCase(comm, "spectacle") && // 13.6k !ffStrEqualsIgnCase(comm, "chunkwm") && // repo deleted; was https://github.com/koekeishiya/chunkwm !ffStrEqualsIgnCase(comm, "kwm") && // repo deleted; was https://github.com/koekeishiya/kwm #endif true ) continue; if (instance.config.general.detectVersion) { char buf[PROC_PIDPATHINFO_MAXSIZE]; int length = proc_pidpath(proc->kp_proc.p_pid, buf, ARRAY_SIZE(buf) - strlen("Info.plist")); if (length > 0) { char* lastSlash = strrchr(buf, '/'); if (lastSlash) { *lastSlash = '\0'; if (ffStrEndsWith(buf, ".app/Contents/MacOS")) { lastSlash -= strlen("MacOS"); strcpy(lastSlash, "Info.plist"); // X.app/Contents/Info.plist NSError* error; NSDictionary* dict = [NSDictionary dictionaryWithContentsOfURL:[NSURL fileURLWithPath:@(buf)] error:&error]; if (dict) { NSString* name = dict[@"CFBundleDisplayName"] ?: dict[@"CFBundleName"]; ffStrbufSetS(pluginName, name.UTF8String ?: comm); NSString* version = dict[@"CFBundleShortVersionString"]; if (version) { ffStrbufAppendC(pluginName, ' '); ffStrbufAppendS(pluginName, version.UTF8String); } break; } } } } } ffStrbufAppendS(pluginName, comm); pluginName->chars[0] = (char) toupper(pluginName->chars[0]); break; } return NULL; } const char* ffDetectWMVersion(const FFstrbuf* wmName, FFstrbuf* result, FF_MAYBE_UNUSED FFWMOptions* options) { if (!wmName) return "No WM detected"; if (ffStrbufEqualS(wmName, "WindowServer")) { NSError* error; NSDictionary* dict = [NSDictionary dictionaryWithContentsOfURL:[NSURL fileURLWithPath:@"/System/Library/PrivateFrameworks/SkyLight.framework/Resources/version.plist" isDirectory:NO] error:&error]; if (!dict) { dict = [NSDictionary dictionaryWithContentsOfURL:[NSURL fileURLWithPath:@"/System/Library/Frameworks/ApplicationServices.framework/Frameworks/CoreGraphics.framework/Resources/version.plist" isDirectory:NO] error:&error]; } if (dict) ffStrbufSetS(result, ((NSString*) dict[@"CFBundleShortVersionString"]).UTF8String); } return NULL; } ================================================ FILE: src/detection/wm/wm_linux.c ================================================ #include "wm.h" #include "common/processing.h" #include "common/io.h" #include "common/binary.h" #include "common/path.h" #include "common/stringUtils.h" #include "common/debug.h" #include "detection/displayserver/displayserver.h" const char* ffDetectWMPlugin(FF_MAYBE_UNUSED FFstrbuf* pluginName) { return "Not supported on this platform"; } static bool extractCommonWmVersion(const char* line, FF_MAYBE_UNUSED uint32_t len, void *userdata) { int count = 0; sscanf(line, "%*d.%*d.%*d%n", &count); if (count == 0) return true; ffStrbufSetNS((FFstrbuf*) userdata, len, line); return false; } #if !__ANDROID__ static bool extractHyprlandVersion(const char* line, uint32_t len, void *userdata) { if (line[0] != 'v') return true; ++line; --len; int count = 0; sscanf(line, "%*d.%*d.%*d%n", &count); if (count == 0) return true; ffStrbufSetNS((FFstrbuf*) userdata, len, line); return false; } static const char* getHyprland(FFstrbuf* result) { FF_DEBUG("Detecting Hyprland version"); FF_STRBUF_AUTO_DESTROY buffer = ffStrbufCreate(); FF_DEBUG("Checking for " FASTFETCH_TARGET_DIR_USR "/include/hyprland/src/version.h" " file"); if (ffReadFileBuffer(FASTFETCH_TARGET_DIR_USR "/include/hyprland/src/version.h", result)) { FF_DEBUG("Found version.h file, extracting version"); if (ffStrbufSubstrAfterFirstS(result, "\n#define GIT_TAG ")) { ffStrbufSubstrAfterFirstC(result, '"'); ffStrbufSubstrBeforeFirstC(result, '"'); ffStrbufTrimLeft(result, 'v'); FF_DEBUG("Extracted version from version.h: %s", result->chars); return NULL; } FF_DEBUG("Failed to extract version from version.h"); ffStrbufClear(result); } else { FF_DEBUG("version.h file not found, trying Hyprland executable"); } const char* error = ffFindExecutableInPath("Hyprland", &buffer); if (error) { FF_DEBUG("Error finding Hyprland executable: %s", error); return "Failed to find Hyprland executable path"; } FF_DEBUG("Found Hyprland executable at: %s", buffer.chars); ffBinaryExtractStrings(buffer.chars, extractHyprlandVersion, result, (uint32_t) strlen("v0.0.0")); if (result->length > 0) { FF_DEBUG("Extracted version from binary strings: %s", result->chars); return NULL; } FF_DEBUG("Failed to extract version from binary strings, trying --version option"); if (ffProcessAppendStdOut(result, (char* const[]){ buffer.chars, "--version", NULL }) == NULL) { // Hyprland 0.48.1 built from branch at commit 29e2e59... // Date: ... // Tag: v0.48.1, commits: 5937 // ... FF_DEBUG("Raw version output: %s", result->chars); // Use tag if available if (ffStrbufSubstrAfterFirstS(result, "\nTag: v")) { ffStrbufSubstrBeforeFirstC(result, ','); FF_DEBUG("Extracted version from Tag: %s", result->chars); } else { ffStrbufSubstrAfterFirstC(result, ' '); ffStrbufSubstrBeforeFirstC(result, ' '); FF_DEBUG("Extracted version from output: %s", result->chars); } return NULL; } FF_DEBUG("Failed to run Hyprland --version command"); return "Failed to run command `Hyprland --version`"; } static bool extractSwayVersion(const char* line, FF_MAYBE_UNUSED uint32_t len, void *userdata) { if (!ffStrStartsWith(line, "sway version ")) return true; FFstrbuf* result = (FFstrbuf*) userdata; ffStrbufSetNS(result, len - (uint32_t) strlen("sway version "), line + strlen("sway version ")); ffStrbufTrimRightSpace(result); return false; } static const char* getSway(FFstrbuf* result) { FF_STRBUF_AUTO_DESTROY path = ffStrbufCreate(); const char* error = ffFindExecutableInPath("sway", &path); if (error) return "Failed to find sway executable path"; ffBinaryExtractStrings(path.chars, extractSwayVersion, result, (uint32_t) strlen("v0.0.0")); if (result->length > 0) return NULL; if (ffProcessAppendStdOut(result, (char* const[]){ path.chars, "--version", NULL }) == NULL) { // sway version 1.10 ffStrbufSubstrAfterLastC(result, ' '); ffStrbufTrimRightSpace(result); return NULL; } return "Failed to run command `sway --version`"; } static const char* getLabwc(FFstrbuf* result) { FF_STRBUF_AUTO_DESTROY path = ffStrbufCreate(); const char* error = ffFindExecutableInPath("labwc", &path); if (error) return "Failed to find labwc executable path"; ffBinaryExtractStrings(path.chars, extractCommonWmVersion, result, (uint32_t) strlen("0.0.0")); if (result->length > 0) return NULL; if (ffProcessAppendStdOut(result, (char* const[]){ path.chars, "--version", NULL }) == NULL) { // labwc 0.9.0 (+xwayland +nls +rsvg +libsfdo) ffStrbufSubstrAfterFirstC(result, ' '); ffStrbufSubstrBeforeFirstC(result, ' '); return NULL; } return "Failed to run command `labwc --version`"; } static const char* getNiri(FFstrbuf* result) { if (ffProcessAppendStdOut(result, (char* const[]){ "niri", "--version", NULL }) == NULL) { // niri 25.11 (commit b35bcae) ffStrbufSubstrAfterFirstC(result, ' '); ffStrbufSubstrBeforeLastC(result, '('); ffStrbufTrimRightSpace(result); return NULL; } return "Failed to run command `niri --version`"; } #ifdef __linux__ static const char* getWslg(FFstrbuf* result) { if (!ffAppendFileBuffer("/mnt/wslg/versions.txt", result)) return "Failed to read /mnt/wslg/versions.txt"; if (!ffStrbufStartsWithS(result, "WSLg ")) return "Failed to find WSLg version"; ffStrbufSubstrBeforeFirstC(result, '\n'); ffStrbufSubstrBeforeFirstC(result, '+'); ffStrbufSubstrAfterFirstC(result, ':'); ffStrbufTrimLeft(result, ' '); return NULL; } #endif #endif // !__ANDROID__ static bool extractI3Version(const char* line, FF_MAYBE_UNUSED uint32_t len, void *userdata) { int count = 0; sscanf(line, "%*d.%*d%n", &count); if (count == 0) return true; ffStrbufSetNS((FFstrbuf*) userdata, len, line); return false; } static const char* getI3(FFstrbuf* result) { FF_STRBUF_AUTO_DESTROY path = ffStrbufCreate(); const char* error = ffFindExecutableInPath("i3", &path); if (error) return "Failed to find i3 executable path"; ffBinaryExtractStrings(path.chars, extractI3Version, result, (uint32_t) strlen("0.0")); if (result->length > 0) return NULL; if (ffProcessAppendStdOut(result, (char* const[]){ path.chars, "--version", NULL }) == NULL) { // i3 version 1.10 C 2009... ffStrbufSubstrAfterFirstS(result, "version "); ffStrbufSubstrBeforeFirstC(result, ' '); return NULL; } return "Failed to run command `i3 --version`"; } static const char* getCtwm(FFstrbuf* result) { FF_STRBUF_AUTO_DESTROY path = ffStrbufCreate(); const char* error = ffFindExecutableInPath("ctwm", &path); if (error) return "Failed to find ctwm executable path"; ffBinaryExtractStrings(path.chars, extractCommonWmVersion, result, (uint32_t) strlen("0.0.0")); if (result->length > 0) return NULL; if (ffProcessAppendStdOut(result, (char* const[]){ path.chars, "--version", NULL }) == NULL) { // ctwm version 4.0.1\n... ffStrbufSubstrBeforeFirstC(result, '\n'); ffStrbufSubstrAfterLastC(result, ' '); return NULL; } return "Failed to run command `ctwm --version`"; } static const char* getFvwm(FFstrbuf* result) { FF_STRBUF_AUTO_DESTROY path = ffStrbufCreate(); const char* error = ffFindExecutableInPath("fvwm", &path); if (error) return "Failed to find fvwm executable path"; ffBinaryExtractStrings(path.chars, extractCommonWmVersion, result, (uint32_t) strlen("0.0.0")); if (result->length > 0) return NULL; if (ffProcessAppendStdOut(result, (char* const[]){ path.chars, "-version", NULL }) == NULL) { // [FVWM][main]: fvwm Version 2.2.5\n... ffStrbufSubstrBeforeFirstC(result, '\n'); ffStrbufSubstrAfterLastC(result, ' '); return NULL; } return "Failed to run command `fvwm -version`"; } static const char* getOpenbox(FFstrbuf* result) { FF_STRBUF_AUTO_DESTROY path = ffStrbufCreate(); const char* error = ffFindExecutableInPath("openbox", &path); if (error) return "Failed to find openbox executable path"; ffBinaryExtractStrings(path.chars, extractCommonWmVersion, result, (uint32_t) strlen("0.0.0")); if (result->length > 0) return NULL; if (ffProcessAppendStdOut(result, (char* const[]){ path.chars, "--version", NULL }) == NULL) { // Openbox 3.6.1\n... ffStrbufSubstrBeforeFirstC(result, '\n'); ffStrbufSubstrAfterLastC(result, ' '); return NULL; } return "Failed to run command `openbox --version`"; } const char* ffDetectWMVersion(const FFstrbuf* wmName, FFstrbuf* result, FF_MAYBE_UNUSED FFWMOptions* options) { if (!wmName) return "No WM detected"; #if !__ANDROID__ // Wayland compositors if (ffStrbufIgnCaseEqualS(wmName, "Hyprland")) return getHyprland(result); if (ffStrbufEqualS(wmName, "sway")) return getSway(result); if (ffStrbufEqualS(wmName, "labwc")) return getLabwc(result); if (ffStrbufEqualS(wmName, "niri")) return getNiri(result); #if __linux__ if (ffStrbufEqualS(wmName, "WSLg")) return getWslg(result); #endif #endif // X11 WMs if (ffStrbufEqualS(wmName, "i3")) return getI3(result); if (ffStrbufEqualS(wmName, "ctwm")) return getCtwm(result); if (ffStrbufEqualS(wmName, "fvwm")) return getFvwm(result); if (ffStrbufEqualS(wmName, "Openbox")) return getOpenbox(result); return "Unsupported WM"; } ================================================ FILE: src/detection/wm/wm_nosupport.c ================================================ #include "wm.h" const char* ffDetectWMPlugin(FF_MAYBE_UNUSED FFstrbuf* pluginName) { return "Not supported on this platform"; } const char* ffDetectWMVersion(FF_MAYBE_UNUSED const FFstrbuf* wmName, FF_MAYBE_UNUSED FFstrbuf* result, FF_MAYBE_UNUSED FFWMOptions* options) { return "Not supported on this platform"; } ================================================ FILE: src/detection/wm/wm_windows.c ================================================ #include "wm.h" #include "common/mallocHelper.h" #include "common/io.h" #include "common/library.h" #include "common/processing.h" #include "common/windows/nt.h" #include "common/windows/unicode.h" #include "common/windows/version.h" #include #include #include #include #include typedef enum { FF_PROCESS_TYPE_NONE, FF_PROCESS_TYPE_SIGNED = 1 << 0, FF_PROCESS_TYPE_WINDOWS_STORE = 1 << 1, FF_PROCESS_TYPE_GUI = 1 << 2, FF_PROCESS_TYPE_CUI = 1 << 3, } FFProcessType; static bool verifySignature(const wchar_t* filePath) { FF_LIBRARY_LOAD(wintrust, true, "wintrust" FF_LIBRARY_EXTENSION, -1) FF_LIBRARY_LOAD_SYMBOL(wintrust, WinVerifyTrustEx, true) WINTRUST_FILE_INFO fileInfo = { .cbStruct = sizeof(fileInfo), .pcwszFilePath = filePath, }; GUID actionID = WINTRUST_ACTION_GENERIC_VERIFY_V2; WINTRUST_DATA trustData = { .cbStruct = sizeof(trustData), .dwUIChoice = WTD_UI_NONE, .fdwRevocationChecks = WTD_REVOKE_NONE, .dwUnionChoice = WTD_CHOICE_FILE, .pFile = &fileInfo, .dwStateAction = WTD_STATEACTION_VERIFY, .dwProvFlags = WTD_SAFER_FLAG, }; LONG status = ffWinVerifyTrustEx(NULL, &actionID, &trustData); trustData.dwStateAction = WTD_STATEACTION_CLOSE; ffWinVerifyTrustEx(NULL, &actionID, &trustData); return status == ERROR_SUCCESS; } static bool isProcessTrusted(DWORD processId, FFProcessType processType, UNICODE_STRING* buffer, size_t bufSize) { FF_AUTO_CLOSE_FD HANDLE hProcess = NULL; if (!NT_SUCCESS(NtOpenProcess(&hProcess, PROCESS_QUERY_LIMITED_INFORMATION, &(OBJECT_ATTRIBUTES) { .Length = sizeof(OBJECT_ATTRIBUTES), }, &(CLIENT_ID) { .UniqueProcess = (HANDLE)(uintptr_t) processId }))) return false; ULONG size; if(!NT_SUCCESS(NtQueryInformationProcess(hProcess, ProcessImageFileNameWin32, buffer, (ULONG) bufSize, &size)) || buffer->Length == 0) return false; assert(buffer->MaximumLength >= buffer->Length + 2); // NULL terminated if (processType & FF_PROCESS_TYPE_WINDOWS_STORE) { static wchar_t windowsAppsPath[MAX_PATH]; static uint32_t windowsAppsPathLen; if (windowsAppsPathLen == 0) { PWSTR pPath = NULL; if(SUCCEEDED(SHGetKnownFolderPath(&FOLDERID_ProgramFiles, KF_FLAG_DEFAULT, NULL, &pPath))) { windowsAppsPathLen = (uint32_t) wcslen(pPath); memcpy(windowsAppsPath, pPath, windowsAppsPathLen * sizeof(wchar_t)); memcpy(windowsAppsPath + windowsAppsPathLen, L"\\WindowsApps\\", sizeof(L"\\WindowsApps\\")); windowsAppsPathLen += strlen("\\WindowsApps\\"); } else { windowsAppsPathLen = -1u; } CoTaskMemFree(pPath); } if (windowsAppsPathLen != -1u && (buffer->Length <= windowsAppsPathLen * sizeof(wchar_t) || // Path is too short to be in WindowsApps _wcsnicmp(buffer->Buffer, windowsAppsPath, windowsAppsPathLen) != 0) // Path does not start with WindowsApps ) return false; } if (processType & FF_PROCESS_TYPE_SIGNED) { if (!verifySignature(buffer->Buffer)) return false; } if (processType & (FF_PROCESS_TYPE_GUI | FF_PROCESS_TYPE_CUI)) { SECTION_IMAGE_INFORMATION info = {}; if(!NT_SUCCESS(NtQueryInformationProcess(hProcess, ProcessImageInformation, &info, sizeof(info), &size)) || size != sizeof(info)) return false; if ((processType & FF_PROCESS_TYPE_GUI) && info.SubSystemType != IMAGE_SUBSYSTEM_WINDOWS_GUI) return false; if ((processType & FF_PROCESS_TYPE_CUI) && info.SubSystemType != IMAGE_SUBSYSTEM_WINDOWS_CUI) return false; } return true; } #define ffStrEqualNWS(str, compareTo) (_wcsnicmp(str, L ## compareTo, sizeof(compareTo) - 1) == 0) const char* ffDetectWMPlugin(FFstrbuf* pluginName) { alignas(UNICODE_STRING) uint8_t buffer[4096]; UNICODE_STRING* filePath = (UNICODE_STRING*) buffer; SYSTEM_PROCESS_INFORMATION* FF_AUTO_FREE pstart = NULL; // Multiple attempts in case processes change while // we are in the middle of querying them. ULONG size = 0; for (int attempts = 0;; ++attempts) { if (size) { pstart = (SYSTEM_PROCESS_INFORMATION*)realloc(pstart, size); assert(pstart); } NTSTATUS status = NtQuerySystemInformation(SystemProcessInformation, pstart, size, &size); if(NT_SUCCESS(status)) break; else if(status == STATUS_INFO_LENGTH_MISMATCH && attempts < 4) size += sizeof(SYSTEM_PROCESS_INFORMATION) * 5; else return "NtQuerySystemInformation(SystemProcessInformation) failed"; } for (SYSTEM_PROCESS_INFORMATION* ptr = pstart; ; ptr = (SYSTEM_PROCESS_INFORMATION*)((uint8_t*)ptr + ptr->NextEntryOffset)) { assert(ptr->ImageName.Length == 0 || ptr->ImageName.MaximumLength >= ptr->ImageName.Length + 2); // NULL terminated if (ptr->ImageName.Length == strlen("FancyWM-GUI.exe") * sizeof(wchar_t) && ffStrEqualNWS(ptr->ImageName.Buffer, "FancyWM-GUI.exe") && isProcessTrusted((DWORD) (uintptr_t) ptr->UniqueProcessId, FF_PROCESS_TYPE_WINDOWS_STORE | FF_PROCESS_TYPE_GUI, filePath, sizeof(buffer)) ) { if (instance.config.general.detectVersion && ffGetFileVersion(filePath->Buffer, NULL, pluginName)) ffStrbufPrependS(pluginName, "FancyWM "); else ffStrbufSetStatic(pluginName, "FancyWM"); break; } else if (ptr->ImageName.Length == strlen("glazewm-watcher.exe") * sizeof(wchar_t) && ffStrEqualNWS(ptr->ImageName.Buffer, "glazewm-watcher.exe") && isProcessTrusted((DWORD) (uintptr_t) ptr->UniqueProcessId, FF_PROCESS_TYPE_SIGNED | FF_PROCESS_TYPE_GUI, filePath, sizeof(buffer)) ) { if (instance.config.general.detectVersion && ffGetFileVersion(filePath->Buffer, NULL, pluginName)) ffStrbufPrependS(pluginName, "GlazeWM "); else ffStrbufSetStatic(pluginName, "GlazeWM"); break; } else if (ptr->ImageName.Length == strlen("komorebi.exe") * sizeof(wchar_t) && ffStrEqualNWS(ptr->ImageName.Buffer, "komorebi.exe") && isProcessTrusted((DWORD) (uintptr_t) ptr->UniqueProcessId, FF_PROCESS_TYPE_CUI, filePath, sizeof(buffer)) ) { if (instance.config.general.detectVersion) { FF_STRBUF_AUTO_DESTROY path = ffStrbufCreateNWS(filePath->Length / sizeof(wchar_t), filePath->Buffer); if (ffProcessAppendStdOut(pluginName, (char *const[]) { path.chars, "--version", NULL, }) == NULL) ffStrbufSubstrBeforeFirstC(pluginName, '\n'); } if (pluginName->length == 0) ffStrbufSetStatic(pluginName, "Komorebi"); break; } if (ptr->NextEntryOffset == 0) break; } return NULL; } const char* ffDetectWMVersion(const FFstrbuf* wmName, FFstrbuf* result, FF_MAYBE_UNUSED FFWMOptions* options) { if (!wmName) return "No WM detected"; if (ffStrbufEqualS(wmName, "dwm.exe")) { PWSTR pPath = NULL; if(SUCCEEDED(SHGetKnownFolderPath(&FOLDERID_System, KF_FLAG_DEFAULT, NULL, &pPath))) { wchar_t fullPath[MAX_PATH]; wcscpy(fullPath, pPath); wcscat(fullPath, L"\\dwm.exe"); ffGetFileVersion(fullPath, NULL, result); } CoTaskMemFree(pPath); return NULL; } return "Not supported on this platform"; } ================================================ FILE: src/detection/wmtheme/wmtheme.h ================================================ #pragma once #include "fastfetch.h" bool ffDetectWmTheme(FFstrbuf* themeOrError); ================================================ FILE: src/detection/wmtheme/wmtheme_apple.m ================================================ #include "fastfetch.h" #include "wmtheme.h" #import bool ffDetectWmTheme(FFstrbuf* themeOrError) { NSError* error; NSString* fileName = [NSString stringWithFormat:@"file://%s/Library/Preferences/.GlobalPreferences.plist", instance.state.platform.homeDir.chars]; NSDictionary* dict = [NSDictionary dictionaryWithContentsOfURL:[NSURL URLWithString:fileName] error:&error]; if(error) { ffStrbufAppendS(themeOrError, error.localizedDescription.UTF8String); return false; } NSNumber* wmThemeColor = dict[@"AppleAccentColor"]; if(!wmThemeColor) ffStrbufAppendS(themeOrError, "Multicolor"); else { switch(wmThemeColor.intValue) { case -1: ffStrbufAppendS(themeOrError, "Graphite"); break; case 0: ffStrbufAppendS(themeOrError, "Red"); break; case 1: ffStrbufAppendS(themeOrError, "Orange"); break; case 2: ffStrbufAppendS(themeOrError, "Yellow"); break; case 3: ffStrbufAppendS(themeOrError, "Green"); break; case 4: ffStrbufAppendS(themeOrError, "Blue"); break; case 5: ffStrbufAppendS(themeOrError, "Purple"); break; case 6: ffStrbufAppendS(themeOrError, "Pink"); break; default: ffStrbufAppendS(themeOrError, "Unknown"); break; } } NSString* wmTheme = dict[@"AppleInterfaceStyle"]; ffStrbufAppendF(themeOrError, " (%s)", wmTheme ? wmTheme.UTF8String : "Light"); return true; } ================================================ FILE: src/detection/wmtheme/wmtheme_linux.c ================================================ #include "wmtheme.h" #include "common/io.h" #include "common/properties.h" #include "common/parsing.h" #include "common/settings.h" #include "common/stringUtils.h" #include "common/mallocHelper.h" #include "detection/gtk_qt/gtk_qt.h" #include "detection/displayserver/displayserver.h" static bool detectWMThemeFromConfigFile(const char* configFile, const char* themeRegex, const char* defaultValue, FFstrbuf* themeOrError) { if(!ffParsePropFileConfig(configFile, themeRegex, themeOrError)) { ffStrbufAppendF(themeOrError, "Config file %s doesn't exist", configFile); return false; } if(themeOrError->length == 0) { if(defaultValue == NULL) { ffStrbufAppendF(themeOrError, "Couldn't find WM theme in %s", configFile); return false; } ffStrbufAppendS(themeOrError, defaultValue); return true; } // Remove Plasma-generated prefixes uint32_t idx = 0; idx = ffStrbufFirstIndexS(themeOrError, "qml_"); if(idx != themeOrError->length) ffStrbufSubstrAfter(themeOrError, idx + 3); idx = ffStrbufFirstIndexS(themeOrError, "svg__"); if(idx != themeOrError->length) ffStrbufSubstrAfter(themeOrError, idx + 4); return true; } static bool detectWMThemeFromSettings(const char* dconfKey, const char* gsettingsSchemaName, const char* gsettingsPath, const char* gsettingsKey, FFstrbuf* themeOrError) { const char* theme = ffSettingsGetGnome(dconfKey, gsettingsSchemaName, gsettingsPath, gsettingsKey, FF_VARIANT_TYPE_STRING).strValue; if(!ffStrSet(theme)) { ffStrbufAppendS(themeOrError, "Couldn't find WM theme in DConf or GSettings"); return false; } ffStrbufAppendS(themeOrError, theme); return true; } static bool detectGTKThemeAsWMTheme(FFstrbuf* themeOrError) { const FFGTKResult* gtk = ffDetectGTK4(); if(gtk->theme.length > 0) goto ok; gtk = ffDetectGTK3(); if(gtk->theme.length > 0) goto ok; gtk = ffDetectGTK2(); if(gtk->theme.length > 0) goto ok; ffStrbufAppendS(themeOrError, "Couldn't detect GTK4/3/2 theme"); return false; ok: ffStrbufAppend(themeOrError, >k->theme); return true; } static bool detectMutter(FFstrbuf* themeOrError) { const char* theme = ffSettingsGetGnome("/org/gnome/shell/extensions/user-theme/name", "org.gnome.shell.extensions.user-theme", NULL, "name", FF_VARIANT_TYPE_STRING).strValue; if(ffStrSet(theme)) { ffStrbufAppendS(themeOrError, theme); return true; } return detectGTKThemeAsWMTheme(themeOrError); } static bool detectMuffin(FFstrbuf* themeOrError) { FF_AUTO_FREE const char* name = ffSettingsGetGnome("/org/cinnamon/theme/name", "org.cinnamon.theme", NULL, "name", FF_VARIANT_TYPE_STRING).strValue; FF_AUTO_FREE const char* theme = ffSettingsGetGnome("/org/cinnamon/desktop/wm/preferences/theme", "org.cinnamon.desktop.wm.preferences", NULL, "theme", FF_VARIANT_TYPE_STRING).strValue; if(name == NULL && theme == NULL) { ffStrbufAppendS(themeOrError, "Couldn't find muffin theme in GSettings / DConf"); return false; } if(name == NULL) { ffStrbufAppendS(themeOrError, theme); return true; } if(theme == NULL) { ffStrbufAppendS(themeOrError, name); return true; } ffStrbufAppendF(themeOrError, "%s (%s)", name, theme); return true; } static bool detectXFWM4(FFstrbuf* themeOrError) { const char* theme = ffSettingsGetXFConf("xfwm4", "/general/theme", FF_VARIANT_TYPE_STRING).strValue; if(theme == NULL) { ffStrbufAppendS(themeOrError, "Couldn't find xfwm4::/general/theme in XFConf"); return false; } ffStrbufAppendS(themeOrError, theme); return true; } static bool detectOpenbox(const FFstrbuf* dePrettyName, FFstrbuf* themeOrError) { FF_STRBUF_AUTO_DESTROY absolutePath = ffStrbufCreateA(64); const char *configFileSubpath = "openbox/rc.xml"; if (ffStrbufIgnCaseEqualS(dePrettyName, "LXQt")) configFileSubpath = "openbox/lxqt-rc.xml"; else if (ffStrbufIgnCaseEqualS(dePrettyName, "LXDE")) configFileSubpath = "openbox/lxde-rc.xml"; if (!ffSearchUserConfigFile(&instance.state.platform.configDirs, configFileSubpath, &absolutePath)) { ffStrbufAppendF(themeOrError, "Couldn't find config file \"%s\"", configFileSubpath); return false; } FF_STRBUF_AUTO_DESTROY content = ffStrbufCreate(); if (!ffReadFileBuffer(absolutePath.chars, &content)) { ffStrbufAppendF(themeOrError, "Couldn't read \"%s\"", absolutePath.chars); return false; } const char *themeStart = strstr(content.chars, ""); if (themeStart == NULL) goto theme_not_found; const char *themeEnd = strstr(themeStart, ""); if (__builtin_expect(themeEnd == NULL, false)) // very rare case goto theme_not_found; const char *nameStart = strstr(themeStart, ""); if (nameStart == NULL) goto name_not_found; const char *nameEnd = strstr(nameStart, ""); if (nameEnd == NULL || nameEnd > themeEnd) // (nameEnd > themeEnd) means name is not a theme's child goto name_not_found; nameStart += strlen(""); ffStrbufAppendNS(themeOrError, (uint32_t)(nameEnd - nameStart), nameStart); ffStrbufTrim(themeOrError, ' '); if(themeOrError->length == 0) goto name_not_found; return true; theme_not_found: ffStrbufAppendF(themeOrError, "Couldn't find theme node in \"%s\"", absolutePath.chars); return false; name_not_found: ffStrbufAppendF(themeOrError, "Couldn't find theme name in \"%s\"", absolutePath.chars); return false; } bool ffDetectWmTheme(FFstrbuf* themeOrError) { const FFDisplayServerResult* wm = ffConnectDisplayServer(); if(wm->wmPrettyName.length == 0) { ffStrbufAppendS(themeOrError, "WM Theme needs successful WM detection"); return false; } if(ffStrbufIgnCaseEqualS(&wm->wmPrettyName, FF_WM_PRETTY_KWIN)) return detectWMThemeFromConfigFile("kwinrc", "theme =", "Breeze", themeOrError); if( ffStrbufIgnCaseEqualS(&wm->wmPrettyName, FF_WM_PRETTY_XFWM4) || (ffStrbufIgnCaseEqualS(&wm->wmPrettyName, "labwc") && ffStrbufIgnCaseEqualS(&wm->dePrettyName, FF_DE_PRETTY_XFCE4)) ) return detectXFWM4(themeOrError); if(ffStrbufIgnCaseEqualS(&wm->wmPrettyName, FF_WM_PRETTY_MUTTER)) { if( ffStrbufIgnCaseEqualS(&wm->dePrettyName, FF_DE_PRETTY_GNOME) || ffStrbufIgnCaseEqualS(&wm->dePrettyName, FF_DE_PRETTY_GNOME_CLASSIC) ) return detectMutter(themeOrError); else return detectGTKThemeAsWMTheme(themeOrError); } if(ffStrbufIgnCaseEqualS(&wm->wmPrettyName, FF_WM_PRETTY_MUFFIN)) return detectMuffin(themeOrError); if(ffStrbufIgnCaseEqualS(&wm->wmPrettyName, FF_WM_PRETTY_MARCO)) return detectWMThemeFromSettings("/org/mate/Marco/general/theme", "org.mate.Marco.general", NULL, "theme", themeOrError); if(ffStrbufIgnCaseEqualS(&wm->wmPrettyName, FF_WM_PRETTY_OPENBOX)) return detectOpenbox(&wm->dePrettyName, themeOrError); ffStrbufAppendS(themeOrError, "Unknown WM: "); ffStrbufAppend(themeOrError, &wm->wmPrettyName); return false; } ================================================ FILE: src/detection/wmtheme/wmtheme_nosupport.c ================================================ #include "fastfetch.h" #include "wmtheme.h" bool ffDetectWmTheme(FFstrbuf* themeOrError) { ffStrbufAppendS(themeOrError, "Not supported on this platform"); return false; } ================================================ FILE: src/detection/wmtheme/wmtheme_windows.c ================================================ #include "fastfetch.h" #include "wmtheme.h" #include "common/windows/registry.h" const char* colorHexToString(DWORD hex) { switch(hex) { case 0x696cc3: return "Yellow gold"; case 0xff8c00: return "Gold"; case 0xf7630c: return "Orange bright"; case 0xca5010: return "Orange dark"; case 0xda3b01: return "Rust"; case 0xef6950: return "Pale rust"; case 0xd13438: return "Brick red"; case 0xff4343: return "Mod red"; case 0xe74856: return "Pale red"; case 0xe81123: return "Red"; case 0xea005e: return "Rose bright"; case 0xc30052: return "Rose"; case 0xe3008c: return "Plum light"; case 0xbf0077: return "Plum"; case 0xc239b3: return "Orchid light"; case 0x9a0089: return "Orchid"; case 0x0078d4: return "Blue"; case 0x0063b1: return "Navy blue"; case 0x8d8bd7: return "Purple shadow"; case 0x6b69d6: return "Purple shadow dark"; case 0x8764b8: return "Iris pastel"; case 0x744da9: return "Iris Spring"; case 0xb146c2: return "Violet red light"; case 0x881798: return "Violet red"; case 0x0099bc: return "Cool blue bright"; case 0x2d7d9a: return "Cool blue"; case 0x00b7c3: return "Seafoam"; case 0x038387: return "Seafoam teal"; case 0x00b294: return "Mint light"; case 0x018574: return "Mint dark"; case 0x00cc6a: return "Turf green"; case 0x10893e: return "Sport green"; case 0x7a7574: return "Gray"; case 0x5d5a58: return "Gray brown"; case 0x68768a: return "Steel blue"; case 0x515c6b: return "Metal blue"; case 0x567c73: return "Pale moss"; case 0x486860: return "Moss"; case 0x498205: return "Meadow green"; case 0x107c10: return "Green"; case 0x767676: return "Overcast"; case 0x4c4a48: return "Storm"; case 0x69797e: return "Blue gray"; case 0x4a5459: return "Gray dark"; case 0x647c64: return "Liddy green"; case 0x4c574e: return "Sage"; case 0x807143: return "Camouflage desert"; case 0x766c59: return "Camouflage"; case 0x000000: return "Black"; case 0xFFFFFF: return "White"; default: return NULL; } } bool ffDetectWmTheme(FFstrbuf* themeOrError) { { FF_AUTO_CLOSE_FD HANDLE hKey = NULL; if (ffRegOpenKeyForRead(HKEY_CURRENT_USER, L"SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Themes", &hKey, NULL)) { FF_STRBUF_AUTO_DESTROY theme = ffStrbufCreate(); if (ffRegReadStrbuf(hKey, L"CurrentTheme", &theme, NULL)) { ffStrbufSubstrBeforeLastC(&theme, '.'); ffStrbufSubstrAfterLastC(&theme, '\\'); if(isalpha(theme.chars[0])) theme.chars[0] = (char)toupper(theme.chars[0]); ffStrbufAppend(themeOrError, &theme); } } } do { uint32_t rgbColor; uint32_t bgrColor; FF_AUTO_CLOSE_FD HANDLE hKey = NULL; if (ffRegOpenKeyForRead(HKEY_CURRENT_USER, L"Software\\Microsoft\\Windows\\DWM", &hKey, NULL)) { if (ffRegReadUint(hKey, L"AccentColor", &bgrColor, NULL)) rgbColor = ((bgrColor & 0xFF) << 16) | (bgrColor & 0xFF00) | ((bgrColor >> 16) & 0xFF); else if (ffRegReadUint(hKey, L"ColorizationColor", &rgbColor, NULL)) rgbColor &= 0xFFFFFF; else break; } else break; if (themeOrError->length > 0) ffStrbufAppendS(themeOrError, " - "); const char* text = colorHexToString(rgbColor); if (text) ffStrbufAppendS(themeOrError, text); else ffStrbufAppendF(themeOrError, "#%06lX", (long)rgbColor); } while (false); { FF_AUTO_CLOSE_FD HANDLE hKey = NULL; if (ffRegOpenKeyForRead(HKEY_CURRENT_USER, L"SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Themes\\Personalize", &hKey, NULL)) { uint32_t system = 1, apps = 1; if (ffRegReadValues(hKey, 2, (FFRegValueArg[]) { FF_ARG(system, L"SystemUsesLightTheme"), FF_ARG(apps, L"AppsUseLightTheme"), }, NULL)) { bool paren = themeOrError->length > 0; if (paren) ffStrbufAppendS(themeOrError, " ("); ffStrbufAppendF(themeOrError, "System: %s, Apps: %s", system ? "Light" : "Dark", apps ? "Light" : "Dark"); if (paren) ffStrbufAppendC(themeOrError, ')'); } } } if(themeOrError->length == 0) { ffStrbufSetStatic(themeOrError, "Failed to find current theme"); return false; } return true; } ================================================ FILE: src/detection/zpool/libzfs_simplified.h ================================================ #pragma once #include "fastfetch.h" // From https://github.com/openzfs/zfs/blob/master/include/libzfs.h /* * CDDL HEADER START * * The contents of this file are subject to the terms of the * Common Development and Distribution License (the "License"). * You may not use this file except in compliance with the License. * * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE * or https://opensource.org/licenses/CDDL-1.0. * See the License for the specific language governing permissions * and limitations under the License. * * When distributing Covered Code, include this CDDL HEADER in each * file and include the License file at usr/src/OPENSOLARIS.LICENSE. * If applicable, add the following below this CDDL HEADER, with the * fields enclosed by brackets "[]" replaced with your own identifying * information: Portions Copyright [yyyy] [name of copyright owner] * * CDDL HEADER END */ // zpool_prop_t and zprop_source_t were previously enums in upstream OpenZFS. // However, the enum values for these types vary greatly between different platforms, // making it unsafe to use the enum values directly. To ensure portability, // we define them as simple int typedefs and use zpool_name_to_prop to look up // the correct value for a property at runtime. typedef int zpool_prop_t; typedef int zprop_source_t; typedef int boolean_t; typedef struct libzfs_handle libzfs_handle_t; typedef struct zpool_handle zpool_handle_t; typedef int (*zpool_iter_f)(zpool_handle_t *, void *); extern libzfs_handle_t *libzfs_init(void); extern void libzfs_fini(libzfs_handle_t *); extern int zpool_iter(libzfs_handle_t *, zpool_iter_f, void *); extern zpool_prop_t zpool_name_to_prop(const char *); // https://github.com/openzfs/zfs/blob/06c73cffabc30b61a695988ec8e290f43cb3768d/lib/libzfs/libzfs_pool.c#L300 extern uint64_t zpool_get_prop_int(zpool_handle_t *zhp, zpool_prop_t prop, zprop_source_t *srctype); extern int zpool_get_prop(zpool_handle_t *zhp, zpool_prop_t prop, char *buf, size_t len, zprop_source_t *srctype, boolean_t literal); extern void zpool_close(zpool_handle_t *); ================================================ FILE: src/detection/zpool/zpool.c ================================================ #include "zpool.h" #if FF_HAVE_LIBZFS #include "common/kmod.h" #ifdef __sun #include #ifndef __illumos__ // On Solaris 11, zpool_get_prop has only 5 arguments. #2173 #define ffzpool_get_prop(zhp, prop, buf, len, srctype, literal) \ ffzpool_get_prop(zhp, prop, buf, len, srctype) #endif #else #include "libzfs_simplified.h" #endif #include "common/library.h" typedef struct FFZfsData { FF_LIBRARY_SYMBOL(libzfs_fini) FF_LIBRARY_SYMBOL(zpool_get_prop_int) FF_LIBRARY_SYMBOL(zpool_get_prop) FF_LIBRARY_SYMBOL(zpool_close) // The fields in this struct store property IDs returned by `zpool_name_to_prop`, // not the property values themselves. struct { int name; int health; int guid; int size; int free; int allocated; int fragmentation; int readonly; } props; libzfs_handle_t* handle; FFlist* result; } FFZfsData; static inline void cleanLibzfs(FFZfsData* data) { if (data->fflibzfs_fini && data->handle) { data->fflibzfs_fini(data->handle); data->handle = NULL; } } static int enumZpoolCallback(zpool_handle_t* zpool, void* param) { FFZfsData* data = (FFZfsData*) param; zprop_source_t source; FFZpoolResult* item = ffListAdd(data->result); char buf[1024]; if (data->ffzpool_get_prop(zpool, data->props.name, buf, ARRAY_SIZE(buf), &source, false) == 0) ffStrbufInitS(&item->name, buf); else ffStrbufInitStatic(&item->name, "unknown"); if (data->ffzpool_get_prop(zpool, data->props.health, buf, ARRAY_SIZE(buf), &source, false) == 0) ffStrbufInitS(&item->state, buf); else ffStrbufInitStatic(&item->state, "unknown"); item->guid = data->ffzpool_get_prop_int(zpool, data->props.guid, &source); item->total = data->ffzpool_get_prop_int(zpool, data->props.size, &source); item->used = item->total - data->ffzpool_get_prop_int(zpool, data->props.free, &source); item->allocated = data->ffzpool_get_prop_int(zpool, data->props.allocated, &source); uint64_t fragmentation = data->ffzpool_get_prop_int(zpool, data->props.fragmentation, &source); item->fragmentation = fragmentation == UINT64_MAX ? -DBL_MAX : (double) fragmentation; item->readOnly = (bool) data->ffzpool_get_prop_int(zpool, data->props.readonly, &source); data->ffzpool_close(zpool); return 0; } const char* ffDetectZpool(FFlist* result /* list of FFZpoolResult */) { FF_LIBRARY_LOAD_MESSAGE(libzfs, "libzfs" FF_LIBRARY_EXTENSION, 6); FF_LIBRARY_LOAD_SYMBOL_MESSAGE(libzfs, libzfs_init); libzfs_handle_t* handle = fflibzfs_init(); if (!handle) { if (!ffKmodLoaded("zfs")) return "`zfs` kernel module is not loaded"; return "libzfs_init() failed"; } __attribute__((__cleanup__(cleanLibzfs))) FFZfsData data = { .handle = handle, .result = result, }; FF_LIBRARY_LOAD_SYMBOL_MESSAGE(libzfs, zpool_name_to_prop); #define FF_QUERY_ZPOOL_PROP_FROM_NAME(prop_name) do { \ data.props.prop_name = ffzpool_name_to_prop(#prop_name); \ if (data.props.prop_name < 0) \ return "Failed to query prop: " #prop_name; \ } while (false) FF_QUERY_ZPOOL_PROP_FROM_NAME(name); FF_QUERY_ZPOOL_PROP_FROM_NAME(health); FF_QUERY_ZPOOL_PROP_FROM_NAME(guid); FF_QUERY_ZPOOL_PROP_FROM_NAME(size); FF_QUERY_ZPOOL_PROP_FROM_NAME(free); FF_QUERY_ZPOOL_PROP_FROM_NAME(allocated); FF_QUERY_ZPOOL_PROP_FROM_NAME(fragmentation); FF_QUERY_ZPOOL_PROP_FROM_NAME(readonly); #undef FF_QUERY_ZPOOL_PROP_FROM_NAME FF_LIBRARY_LOAD_SYMBOL_MESSAGE(libzfs, zpool_iter); FF_LIBRARY_LOAD_SYMBOL_VAR_MESSAGE(libzfs, data, libzfs_fini); FF_LIBRARY_LOAD_SYMBOL_VAR_MESSAGE(libzfs, data, zpool_get_prop_int); FF_LIBRARY_LOAD_SYMBOL_VAR_MESSAGE(libzfs, data, zpool_get_prop); FF_LIBRARY_LOAD_SYMBOL_VAR_MESSAGE(libzfs, data, zpool_close); if (ffzpool_iter(handle, enumZpoolCallback, &data) < 0) return "zpool_iter() failed"; return NULL; } #else const char* ffDetectZpool(FF_MAYBE_UNUSED FFlist* result) { return "fastfetch was compiled without libzfs support"; } #endif ================================================ FILE: src/detection/zpool/zpool.h ================================================ #pragma once #include "fastfetch.h" #include "modules/zpool/option.h" typedef struct FFZpoolResult { FFstrbuf name; FFstrbuf state; uint64_t guid; uint64_t used; uint64_t total; uint64_t allocated; double fragmentation; bool readOnly; } FFZpoolResult; const char* ffDetectZpool(FFlist* result /* list of FFZpoolResult */); ================================================ FILE: src/fastfetch.c ================================================ #include "fastfetch.h" #include "common/ffdata.h" #include "detection/version/version.h" #include "logo/logo.h" #include "common/commandoption.h" #include "common/init.h" #include "common/io.h" #include "common/jsonconfig.h" #include "common/time.h" #include "common/stringUtils.h" #include "common/mallocHelper.h" #include "fastfetch_datatext.h" #include #include #include #ifdef _WIN32 #include "common/windows/getline.h" #endif static void printCommandFormatHelpJson(void) { yyjson_mut_doc* doc = yyjson_mut_doc_new(NULL); yyjson_mut_val* root = yyjson_mut_obj(doc); yyjson_mut_doc_set_root(doc, root); for (uint32_t i = 0; i <= 'Z' - 'A'; ++i) { for (FFModuleBaseInfo** modules = ffModuleInfos[i]; *modules; ++modules) { FFModuleBaseInfo* baseInfo = *modules; if (!baseInfo->formatArgs.count) continue; FF_STRBUF_AUTO_DESTROY type = ffStrbufCreateS(baseInfo->name); ffStrbufLowerCase(&type); ffStrbufAppendS(&type, "Format"); yyjson_mut_val* obj = yyjson_mut_obj(doc); if (yyjson_mut_obj_add(root, yyjson_mut_strbuf(doc, &type), obj)) { FF_STRBUF_AUTO_DESTROY content = ffStrbufCreateF("Output format of the module `%s`. See Wiki for formatting syntax\n", baseInfo->name); for (unsigned i = 0; i < baseInfo->formatArgs.count; i++) { const FFModuleFormatArg* arg = &baseInfo->formatArgs.args[i]; ffStrbufAppendF(&content, " %u. {%s}: %s\n", i + 1, arg->name, arg->desc); } ffStrbufTrimRight(&content, '\n'); yyjson_mut_obj_add_strbuf(doc, obj, "description", &content); yyjson_mut_obj_add_str(doc, obj, "type", "string"); } } } yyjson_mut_write_fp(stdout, doc, YYJSON_WRITE_PRETTY, NULL, NULL); putchar('\n'); yyjson_mut_doc_free(doc); } static void printCommandFormatHelp(const char* command) { FF_STRBUF_AUTO_DESTROY type = ffStrbufCreateNS((uint32_t) (strlen(command) - strlen("-format")), command); ffStrbufLowerCase(&type); for (FFModuleBaseInfo** modules = ffModuleInfos[toupper(command[0]) - 'A']; *modules; ++modules) { FFModuleBaseInfo* baseInfo = *modules; if (ffStrbufIgnCaseEqualS(&type, baseInfo->name)) { if (baseInfo->formatArgs.count > 0) { FF_STRBUF_AUTO_DESTROY variable = ffStrbufCreate(); printf("-- In config file: { \"type\": \"%s\", \"format\": \"{}\" }\n", type.chars); printf("Sets the format string for %s output.\n", baseInfo->name); puts("To see how a format string is constructed, take a look at https://github.com/fastfetch-cli/fastfetch/wiki/Format-String-Guide."); puts("The following variables are passed:"); for (unsigned i = 0; i < baseInfo->formatArgs.count; i++) { const FFModuleFormatArg* arg = &baseInfo->formatArgs.args[i]; ffStrbufSetF(&variable, "{%s}", arg->name); printf("%20s: %s\n", variable.chars, arg->desc); } } else fprintf(stderr, "Error: Module '%s' doesn't support output formatting\n", baseInfo->name); return; } } fprintf(stderr, "Error: Module '%s' is not supported\n", type.chars); } static void printFullHelp() { fputs("Fastfetch is a neofetch-like tool for fetching system information and displaying them in a pretty way\n\n", stdout); if (!instance.config.display.pipe) fputs("\e[1;4mUsage:\e[m \e[1mfastfetch\e[m \e[3m\e[m\n\n", stdout); else fputs("Usage: fastfetch \n\n", stdout); yyjson_doc* doc = yyjson_read(FASTFETCH_DATATEXT_JSON_HELP, strlen(FASTFETCH_DATATEXT_JSON_HELP), YYJSON_READ_NOFLAG); assert(doc); yyjson_val *groupKey, *flagArr; size_t groupIdx, groupMax; yyjson_obj_foreach(yyjson_doc_get_root(doc), groupIdx, groupMax, groupKey, flagArr) { if (!instance.config.display.pipe) fputs("\e[1;4m", stdout); printf("%s options:", yyjson_get_str(groupKey)); if (!instance.config.display.pipe) fputs("\e[m", stdout); putchar('\n'); yyjson_val* flagObj; size_t flagIdx, flagMax; yyjson_arr_foreach(flagArr, flagIdx, flagMax, flagObj) { yyjson_val* shortKey = yyjson_obj_get(flagObj, "short"); if (shortKey) { fputs(" ", stdout); if (!instance.config.display.pipe) fputs("\e[1m", stdout); printf("-%s", yyjson_get_str(shortKey)); if (!instance.config.display.pipe) fputs("\e[m", stdout); fputs(", ", stdout); } else { fputs(" ", stdout); } yyjson_val* longKey = yyjson_obj_get(flagObj, "long"); assert(longKey); if (!instance.config.display.pipe) fputs("\e[1m", stdout); printf("--%s", yyjson_get_str(longKey)); if (!instance.config.display.pipe) fputs("\e[m", stdout); yyjson_val* argObj = yyjson_obj_get(flagObj, "arg"); if (argObj) { yyjson_val* typeKey = yyjson_obj_get(argObj, "type"); assert(typeKey); yyjson_val* optionalKey = yyjson_obj_get(argObj, "optional"); bool optional = optionalKey && yyjson_get_bool(optionalKey); putchar(' '); if (!instance.config.display.pipe) fputs("\e[3m", stdout); printf("<%s%s>", optional ? "?" : "", yyjson_get_str(typeKey)); if (!instance.config.display.pipe) fputs("\e[m", stdout); } yyjson_val* descKey = yyjson_obj_get(flagObj, "desc"); assert(descKey); if (yyjson_is_arr(descKey)) { if (instance.config.display.pipe) putchar(':'); yyjson_val* descStr; size_t descIdx, descMax; yyjson_arr_foreach(descKey, descIdx, descMax, descStr) { if (!instance.config.display.pipe) printf("\e[46G%s\n", yyjson_get_str(descStr)); else printf(" %s", yyjson_get_str(descStr)); } if (instance.config.display.pipe) putchar('\n'); } else { if (!instance.config.display.pipe) fputs("\e[46G", stdout); else fputs(": ", stdout); puts(yyjson_get_str(descKey)); } } putchar('\n'); } yyjson_doc_free(doc); puts("\n\ Command flags are not case sensitive. E.g. `--print-logos` is equal to `--Print-Logos`\n\ If a value starts with a ?, it is optional. An optional boolean value defaults to true if not specified.\n\ More detailed help messages for each options can be printed with `-h `\n\ For detailed information on logo options, module configuration, and formatting, visit:\n\ https://github.com/fastfetch-cli/fastfetch/wiki/Configuration"); } static bool printSpecificCommandHelp(const char* command) { yyjson_doc* doc = yyjson_read(FASTFETCH_DATATEXT_JSON_HELP, strlen(FASTFETCH_DATATEXT_JSON_HELP), YYJSON_READ_NOFLAG); assert(doc); yyjson_val *groupKey, *flagArr; size_t groupIdx, groupMax; yyjson_obj_foreach(yyjson_doc_get_root(doc), groupIdx, groupMax, groupKey, flagArr) { yyjson_val* flagObj; size_t flagIdx, flagMax; yyjson_arr_foreach(flagArr, flagIdx, flagMax, flagObj) { yyjson_val* pseudo = yyjson_obj_get(flagObj, "pseudo"); if (pseudo && yyjson_get_bool(pseudo)) continue; yyjson_val* longKey = yyjson_obj_get(flagObj, "long"); assert(longKey); if (ffStrEqualsIgnCase(command, yyjson_get_str(longKey))) { puts(yyjson_get_str(yyjson_obj_get(flagObj, "desc"))); printf("%10s: ", "Usage"); yyjson_val* shortKey = yyjson_obj_get(flagObj, "short"); if (shortKey) { if (!instance.config.display.pipe) fputs("\e[1m", stdout); printf("-%s", yyjson_get_str(shortKey)); if (!instance.config.display.pipe) fputs("\e[m", stdout); fputs(", ", stdout); } if (!instance.config.display.pipe) fputs("\e[1m", stdout); printf("--%s", yyjson_get_str(longKey)); if (!instance.config.display.pipe) fputs("\e[m", stdout); yyjson_val* argObj = yyjson_obj_get(flagObj, "arg"); if (argObj) { yyjson_val* typeKey = yyjson_obj_get(argObj, "type"); assert(typeKey); yyjson_val* optionalKey = yyjson_obj_get(argObj, "optional"); bool optional = optionalKey && yyjson_get_bool(optionalKey); putchar(' '); if (!instance.config.display.pipe) fputs("\e[3m", stdout); printf("<%s%s>", optional ? "?" : "", yyjson_get_str(typeKey)); if (!instance.config.display.pipe) fputs("\e[m", stdout); putchar('\n'); yyjson_val* defaultKey = yyjson_obj_get(argObj, "default"); if (defaultKey) { if (ffStrEqualsIgnCase(yyjson_get_str(typeKey), "structure")) printf("%10s: %s\n", "Default", FASTFETCH_DATATEXT_STRUCTURE); else if (yyjson_is_bool(defaultKey)) printf("%10s: %s\n", "Default", yyjson_get_bool(defaultKey) ? "true" : "false"); else if (yyjson_is_num(defaultKey)) printf("%10s: %d\n", "Default", yyjson_get_int(defaultKey)); else if (yyjson_is_str(defaultKey)) printf("%10s: %s\n", "Default", yyjson_get_str(defaultKey)); else printf("%10s: Unknown\n", "Default"); } yyjson_val* enumKey = yyjson_obj_get(argObj, "enum"); if (enumKey) { printf("%10s:\n", "Options"); yyjson_val *optKey, *optVal; size_t optIdx, optMax; yyjson_obj_foreach(enumKey, optIdx, optMax, optKey, optVal) printf("%12s: %s\n", yyjson_get_str(optKey), yyjson_get_str(optVal)); } } else putchar('\n'); yyjson_val* remarkKey = yyjson_obj_get(flagObj, "remark"); if (remarkKey) { if (yyjson_is_str(remarkKey)) printf("%10s: %s\n", "Remark", yyjson_get_str(remarkKey)); else if (yyjson_is_arr(remarkKey) && yyjson_arr_size(remarkKey) > 0) { yyjson_val* remarkStr; size_t remarkIdx, remarkMax; yyjson_arr_foreach(remarkKey, remarkIdx, remarkMax, remarkStr) { if (remarkIdx == 0) printf("%10s: %s\n", "Remark", yyjson_get_str(remarkStr)); else printf(" %s\n", yyjson_get_str(remarkStr)); } } } yyjson_doc_free(doc); return true; } } } yyjson_doc_free(doc); return false; } static void printCommandHelp(const char* command) { if(command == NULL) printFullHelp(); else if(ffStrEqualsIgnCase(command, "format-json")) printCommandFormatHelpJson(); else if(ffCharIsEnglishAlphabet(command[0]) && ffStrEndsWithIgnCase(command, "-format")) // -format printCommandFormatHelp(command); else if(!printSpecificCommandHelp(command)) fprintf(stderr, "Error: No specific help for command '%s' provided\n", command); } static void listAvailablePresets(bool pretty) { FF_LIST_FOR_EACH(FFstrbuf, path, instance.state.platform.dataDirs) { ffStrbufAppendS(path, "fastfetch/presets/"); ffListFilesRecursively(path->chars, pretty); } if (instance.state.platform.exePath.length) { FF_STRBUF_AUTO_DESTROY absolutePath = ffStrbufCreateCopy(&instance.state.platform.exePath); ffStrbufSubstrBeforeLastC(&absolutePath, '/'); ffStrbufAppendS(&absolutePath, "/presets/"); ffListFilesRecursively(absolutePath.chars, pretty); } } static void listAvailableLogos(void) { FF_LIST_FOR_EACH(FFstrbuf, path, instance.state.platform.dataDirs) { ffStrbufAppendS(path, "fastfetch/logos/"); ffListFilesRecursively(path->chars, true); } } static void listConfigPaths(void) { FF_LIST_FOR_EACH(FFstrbuf, folder, instance.state.platform.configDirs) { bool exists = false; uint32_t length = folder->length + (uint32_t) strlen("fastfetch") + 1 /* trailing slash */; ffStrbufAppendS(folder, "fastfetch/config.jsonc"); exists = ffPathExists(folder->chars, FF_PATHTYPE_FILE); ffStrbufSubstrBefore(folder, length); printf("%s%s\n", folder->chars, exists ? " (*)" : ""); } } static void listDataPaths(void) { FF_LIST_FOR_EACH(FFstrbuf, folder, instance.state.platform.dataDirs) { ffStrbufAppendS(folder, "fastfetch/"); puts(folder->chars); } } static void listModules(bool pretty) { unsigned count = 0; for (int i = 0; i <= 'Z' - 'A'; ++i) { for (FFModuleBaseInfo** modules = ffModuleInfos[i]; *modules; ++modules) { ++count; if (pretty) printf("%d)%s%-14s: %s\n", count, count > 9 ? " " : " ", (*modules)->name, (*modules)->description); else printf("%s:%s\n", (*modules)->name, (*modules)->description); } } } static bool parseJsoncFile(FFdata* data, const char* path, yyjson_read_flag flg) { assert(!data->configDoc); { yyjson_read_err error; data->configDoc = path ? yyjson_read_file(path, flg, NULL, &error) : yyjson_read_fp(stdin, flg, NULL, &error); if (!data->configDoc) { if (error.code != YYJSON_READ_ERROR_FILE_OPEN) { if (path) { size_t row = 0, col = error.pos; FF_STRBUF_AUTO_DESTROY content = ffStrbufCreate(); if (ffAppendFileBuffer(path, &content)) yyjson_locate_pos(content.chars, content.length, error.pos, &row, &col, NULL); fprintf(stderr, "Error: failed to parse JSON config file `%s` at (%zu, %zu): %s\n", path, row, col, error.msg); } else fprintf(stderr, "Error: failed to parse JSON from stdin at %zu: %s\n", error.pos, error.msg); exit(477); } return false; } } { const char* error = NULL; yyjson_val* const root = yyjson_doc_get_root(data->configDoc); if (!yyjson_is_obj(root)) error = "Invalid JSON config format. Root value must be an object"; if ( error || (error = ffOptionsParseLogoJsonConfig(&instance.config.logo, root)) || (error = ffOptionsParseGeneralJsonConfig(&instance.config.general, root)) || (error = ffOptionsParseDisplayJsonConfig(&instance.config.display, root)) || false ) { fprintf(stderr, "JsonConfig Error: %s\n", error); exit(477); } } return true; } static void generateConfigFile(FFdata* data, bool force, const char* filePath, bool fullConfig) { if (data->resultDoc) { fprintf(stderr, "Error: duplicated `--gen-config` or `--format json` flags found\n"); exit(477); } if (!filePath) { if (instance.state.platform.configDirs.length == 0) { fprintf(stderr, "Error: No config directory found to generate config file in. Use --gen-config to specify a path\n"); exit(477); } FFstrbuf* configDir = FF_LIST_FIRST(FFstrbuf, instance.state.platform.configDirs); ffStrbufEnsureFixedLengthFree(&data->genConfigPath, configDir->length + strlen("fastfetch/config.jsonc")); ffStrbufSet(&data->genConfigPath, configDir); ffStrbufAppendS(&data->genConfigPath, "fastfetch/config.jsonc"); } else { ffStrbufSetS(&data->genConfigPath, filePath); } if (!force && ffPathExists(data->genConfigPath.chars, FF_PATHTYPE_ANY)) { fprintf(stderr, "Error: file `%s` exists. Use `--gen-config%s-force` to overwrite\n", data->genConfigPath.chars, fullConfig ? "-full" : ""); exit(477); } data->docType = fullConfig ? FF_RESULT_DOC_TYPE_CONFIG_FULL : FF_RESULT_DOC_TYPE_CONFIG; data->resultDoc = yyjson_mut_doc_new(NULL); } static void optionParseConfigFile(FFdata* data, const char* key, const char* value) { if (data->configLoaded) { fprintf(stderr, "Error: only one config file can be loaded\n"); exit(413); } data->configLoaded = true; if(value == NULL) { fprintf(stderr, "Error: usage: %s \n", key); exit(413); } if (value[0] == '\0' || ffStrEqualsIgnCase(value, "none")) return; if (value[0] == '-' && value[1] == '\0') { parseJsoncFile(data, NULL, false); return; } //Try to load as an absolute path FF_STRBUF_AUTO_DESTROY absolutePath = ffStrbufCreateA(128); ffStrbufSetS(&absolutePath, value); bool strictJson = ffStrbufEndsWithIgnCaseS(&absolutePath, ".json"); bool jsonc = !strictJson && ffStrbufEndsWithIgnCaseS(&absolutePath, ".jsonc"); bool json5 = !strictJson && !jsonc && ffStrbufEndsWithIgnCaseS(&absolutePath, ".json5"); bool needExtension = !strictJson && !jsonc && !json5; if (needExtension) ffStrbufAppendS(&absolutePath, ".jsonc"); yyjson_read_flag flag = strictJson ? 0 : jsonc ? YYJSON_READ_ALLOW_COMMENTS | YYJSON_READ_ALLOW_TRAILING_COMMAS : YYJSON_READ_JSON5; if (parseJsoncFile(data, absolutePath.chars, flag)) return; //Try to load as a relative path with the config directory FF_LIST_FOR_EACH(FFstrbuf, path, instance.state.platform.configDirs) { ffStrbufSet(&absolutePath, path); ffStrbufAppendS(&absolutePath, "fastfetch/"); ffStrbufAppendS(&absolutePath, value); if (needExtension) ffStrbufAppendS(&absolutePath, ".jsonc"); if (parseJsoncFile(data, absolutePath.chars, flag)) return; } //Try to load as a preset FF_LIST_FOR_EACH(FFstrbuf, path, instance.state.platform.dataDirs) { ffStrbufSet(&absolutePath, path); ffStrbufAppendS(&absolutePath, "fastfetch/presets/"); ffStrbufAppendS(&absolutePath, value); if (needExtension) ffStrbufAppendS(&absolutePath, ".jsonc"); if (parseJsoncFile(data, absolutePath.chars, flag)) return; } //Try to load as a relative path with the directory of fastfetch binary, for Windows support if (instance.state.platform.exePath.length) { uint32_t lastSlash = ffStrbufLastIndexC(&instance.state.platform.exePath, '/') + 1; assert(lastSlash < instance.state.platform.exePath.length); // Try {exePath}/ ffStrbufSetNS(&absolutePath, lastSlash, instance.state.platform.exePath.chars); ffStrbufAppendS(&absolutePath, value); if (needExtension) ffStrbufAppendS(&absolutePath, ".jsonc"); if (parseJsoncFile(data, absolutePath.chars, flag)) return; // Try {exePath}/presets/ ffStrbufSubstrBefore(&absolutePath, lastSlash); ffStrbufAppendS(&absolutePath, "presets/"); ffStrbufAppendS(&absolutePath, value); if (needExtension) ffStrbufAppendS(&absolutePath, ".jsonc"); if (parseJsoncFile(data, absolutePath.chars, flag)) return; } //File not found fprintf(stderr, "Error: couldn't find config: %s\n", value); exit(414); } static void printVersion() { FFVersionResult* result = &ffVersionResult; printf("%s %s%s%s (%s)\n", result->projectName, result->version, result->versionTweak, result->debugMode ? "-debug" : "", result->architecture); } static void enableJsonOutput(FFdata* data) { if (data->resultDoc) { fprintf(stderr, "Error: duplicated `--gen-config` or `--format json` flags found\n"); exit(477); } data->resultDoc = yyjson_mut_doc_new(NULL); data->docType = FF_RESULT_DOC_TYPE_JSON; yyjson_mut_doc_set_root(data->resultDoc, yyjson_mut_arr(data->resultDoc)); } static void parseCommand(FFdata* data, char* key, char* value) { if(ffStrEqualsIgnCase(key, "-h") || ffStrEqualsIgnCase(key, "--help")) { printCommandHelp(value); exit(0); } if(ffStrEqualsIgnCase(key, "--help-raw")) { puts(FASTFETCH_DATATEXT_JSON_HELP); exit(0); } else if(ffStrEqualsIgnCase(key, "-v") || ffStrEqualsIgnCase(key, "--version")) { printVersion(); exit(0); } else if(ffStrEqualsIgnCase(key, "--version-raw")) { puts(FASTFETCH_PROJECT_VERSION); exit(0); } else if(ffStrStartsWithIgnCase(key, "--print-")) { const char* subkey = key + strlen("--print-"); if(ffStrEndsWithIgnCase(subkey, "structure")) puts(FASTFETCH_DATATEXT_STRUCTURE); else if(ffStrEqualsIgnCase(subkey, "logos")) ffLogoBuiltinPrint(); else { fprintf(stderr, "Error: unsupported print option: %s\n", key); exit(415); } exit(0); } else if(ffStrStartsWithIgnCase(key, "--list-")) { const char* subkey = key + strlen("--list-"); if(ffStrEqualsIgnCase(subkey, "modules")) listModules(!value || !ffStrEqualsIgnCase(value, "autocompletion")); else if(ffStrEqualsIgnCase(subkey, "presets")) listAvailablePresets(!value || !ffStrEqualsIgnCase(value, "autocompletion")); else if(ffStrEqualsIgnCase(subkey, "config-paths")) listConfigPaths(); else if(ffStrEqualsIgnCase(subkey, "data-paths")) listDataPaths(); else if(ffStrEqualsIgnCase(subkey, "features")) ffListFeatures(); else if(ffStrEqualsIgnCase(subkey, "logos")) { if (value) { if (ffStrEqualsIgnCase(value, "autocompletion")) ffLogoBuiltinListAutocompletion(); else if (ffStrEqualsIgnCase(value, "builtin")) ffLogoBuiltinList(); else if (ffStrEqualsIgnCase(value, "custom")) listAvailableLogos(); else { fprintf(stderr, "Error: unsupported logo type: %s\n", value); exit(415); } } else { puts("Builtin logos:"); ffLogoBuiltinList(); puts("\nCustom logos:"); listAvailableLogos(); } } else { fprintf(stderr, "Error: unsupported list option: %s\n", key); exit(415); } exit(0); } else if(ffStrEqualsIgnCase(key, "--gen-config")) generateConfigFile(data, false, value, false); else if(ffStrEqualsIgnCase(key, "--gen-config-force")) generateConfigFile(data, true, value, false); else if(ffStrEqualsIgnCase(key, "--gen-config-full")) generateConfigFile(data, false, value, true); else if(ffStrEqualsIgnCase(key, "--gen-config-full-force")) generateConfigFile(data, true, value, true); else if(ffStrEqualsIgnCase(key, "-c") || ffStrEqualsIgnCase(key, "--config")) optionParseConfigFile(data, key, value); else if(ffStrEqualsIgnCase(key, "-j") || ffStrEqualsIgnCase(key, "--json")) { if (ffOptionParseBoolean(value)) enableJsonOutput(data); } else if(ffStrEqualsIgnCase(key, "--format")) { if (!!ffOptionParseEnum(key, value, (FFKeyValuePair[]) { { "default", false}, { "json", true }, {}, })) enableJsonOutput(data); } else if(ffStrEqualsIgnCase(key, "--dynamic-interval")) instance.state.dynamicInterval = ffOptionParseUInt32(key, value); // seconds to milliseconds else return; // Don't parse it again in parseOption. // This is necessary because parseOption doesn't understand this option and will result in an unknown option error. key[0] = '\0'; if (value) value[0] = '\0'; } static void parseOption(FFdata* data, const char* key, const char* value) { if(ffStrEqualsIgnCase(key, "-s") || ffStrEqualsIgnCase(key, "--structure")) ffOptionParseString(key, value, &data->structure); else if( ffOptionsParseGeneralCommandLine(&instance.config.general, key, value) || ffOptionsParseLogoCommandLine(&instance.config.logo, key, value) || ffOptionsParseDisplayCommandLine(&instance.config.display, key, value) || ffParseModuleOptions(key, value) ) {} else if(ffStrEqualsIgnCase(key, "--structure-disabled")) ffOptionParseString(key, value, &data->structureDisabled); else { fprintf(stderr, "Error: unknown option: %s\n", key); exit(400); } } static void parseConfigFiles(FFdata* data) { if (__builtin_expect(data->genConfigPath.length == 0, true)) { FF_LIST_FOR_EACH(FFstrbuf, dir, instance.state.platform.configDirs) { uint32_t dirLength = dir->length; ffStrbufAppendS(dir, "fastfetch/config.jsonc"); bool success = parseJsoncFile(data, dir->chars, YYJSON_READ_ALLOW_COMMENTS | YYJSON_READ_ALLOW_TRAILING_COMMAS); ffStrbufSubstrBefore(dir, dirLength); if (success) return; ffStrbufAppendS(dir, "fastfetch/config.json5"); success = parseJsoncFile(data, dir->chars, YYJSON_READ_JSON5); ffStrbufSubstrBefore(dir, dirLength); if (success) return; } } } static void parseArguments(FFdata* data, int argc, char** argv, void (*parser)(FFdata* data, char* key, char* value)) { for(int i = 1; i < argc; i++) { const char* key = argv[i]; if(*key == '\0') continue; // has been handled by parseCommand if(*key != '-') { fprintf(stderr, "Error: invalid option: %s. An option must start with `-`\n", key); exit(400); } if(i == argc - 1 || ( argv[i + 1][0] == '-' && argv[i + 1][1] != '\0' && // `-` is used as an alias for `/dev/stdin` !ffStrEqualsIgnCase(argv[i], "--separator-string") // Separator string can start with a - )) { parser(data, argv[i], NULL); } else { parser(data, argv[i], argv[i + 1]); ++i; } } } static void run(FFdata* data) { const bool useJsonConfig = data->structure.length == 0 && data->configDoc; if (useJsonConfig) ffPrintJsonConfig(data, true /* prepare */); else { //If we don't have a custom structure, use the default one if(data->structure.length == 0) ffStrbufAppendS(&data->structure, FASTFETCH_DATATEXT_STRUCTURE); // Cannot use `ffStrbufSetStatic` here because we will modify the string ffPrepareCommandOption(data); } ffStart(); if (!data->resultDoc) ffLogoPrint(); #if defined(_WIN32) if (!instance.config.display.noBuffer) fflush(stdout); #endif while (true) { if (useJsonConfig) ffPrintJsonConfig(data, false); else ffPrintCommandOption(data); if (instance.state.dynamicInterval > 0) { fflush(stdout); ffTimeSleep(instance.state.dynamicInterval); fputs("\e[H", stdout); } else break; if (useJsonConfig) ffPrintJsonConfig(data, true /* prepare */); else ffPrepareCommandOption(data); } if (data->resultDoc) yyjson_mut_write_fp(stdout, data->resultDoc, YYJSON_WRITE_INF_AND_NAN_AS_NULL | YYJSON_WRITE_PRETTY_TWO_SPACES | YYJSON_WRITE_NEWLINE_AT_END, NULL, NULL); else { if (instance.config.logo.printRemaining) ffLogoPrintRemaining(); ffFinish(); } } static void writeConfigFile(FFdata* data) { const FFstrbuf* filename = &data->genConfigPath; yyjson_mut_doc* doc = data->resultDoc; yyjson_mut_val* root = yyjson_mut_obj(doc); yyjson_mut_doc_set_root(doc, root); yyjson_mut_obj_add_str(doc, root, "$schema", "https://github.com/fastfetch-cli/fastfetch/raw/master/doc/json_schema.json"); if (data->docType == FF_RESULT_DOC_TYPE_CONFIG_FULL) { ffOptionsGenerateLogoJsonConfig(data, &instance.config.logo); ffOptionsGenerateDisplayJsonConfig(data, &instance.config.display); ffOptionsGenerateGeneralJsonConfig(data, &instance.config.general); } ffMigrateCommandOptionToJsonc(data); if (ffStrbufEqualS(filename, "-")) yyjson_mut_write_fp(stdout, doc, YYJSON_WRITE_INF_AND_NAN_AS_NULL | YYJSON_WRITE_PRETTY_TWO_SPACES | YYJSON_WRITE_NEWLINE_AT_END, NULL, NULL); else { size_t len; FF_AUTO_FREE const char* str = yyjson_mut_write(doc, YYJSON_WRITE_INF_AND_NAN_AS_NULL | YYJSON_WRITE_PRETTY_TWO_SPACES | YYJSON_WRITE_NEWLINE_AT_END, &len); if (!str) { printf("Error: failed to generate config file\n"); exit(1); } if (ffWriteFileData(filename->chars, len, str)) { printf("✓ Configuration file generated: `%s`\n" "* Tip: Use a JSON schema-aware editor for better editing experience\n" "* Documentation: https://github.com/fastfetch-cli/fastfetch/wiki/Configuration\n", filename->chars); } else { printf("Error: failed to write file in `%s`\n", filename->chars); exit(1); } } } int main(int argc, char** argv) { ffInitInstance(); atexit(ffDestroyInstance); //Data stores things only needed for the configuration of fastfetch FFdata data = { .structure = ffStrbufCreate(), .structureDisabled = ffStrbufCreate(), .genConfigPath = ffStrbufCreate(), }; parseArguments(&data, argc, argv, parseCommand); if(instance.state.dynamicInterval && data.resultDoc) { fprintf(stderr, "Error: --dynamic-interval cannot be used with --json\n"); exit(400); } if(!data.configLoaded && !getenv("NO_CONFIG")) parseConfigFiles(&data); parseArguments(&data, argc, argv, (void*) parseOption); if (__builtin_expect(data.genConfigPath.length == 0, true)) run(&data); else writeConfigFile(&data); ffStrbufDestroy(&data.structure); ffStrbufDestroy(&data.structureDisabled); yyjson_doc_free(data.configDoc); yyjson_mut_doc_free(data.resultDoc); ffStrbufDestroy(&data.genConfigPath); } ================================================ FILE: src/fastfetch.h ================================================ #pragma once #include "fastfetch_config.h" #include #include #ifdef _MSC_VER #define __attribute__(x) #endif #include "common/arrayUtils.h" #include "common/FFstrbuf.h" #include "common/FFlist.h" #include "common/FFPlatform.h" #include "common/unused.h" #include "options/logo.h" #include "options/display.h" #include "options/general.h" typedef struct FFconfig { FFOptionsLogo logo; FFOptionsDisplay display; FFOptionsGeneral general; } FFconfig; typedef struct FFstate { uint32_t logoWidth; uint32_t logoHeight; uint32_t keysHeight; bool terminalLightTheme; bool titleFqdn; uint32_t dynamicInterval; FFPlatform platform; } FFstate; typedef struct FFinstance { FFconfig config; FFstate state; } FFinstance; extern FFinstance instance; // Defined in `common/init.c` extern FFModuleBaseInfo** ffModuleInfos[]; ================================================ FILE: src/fastfetch_config.h.in ================================================ #pragma once #define FASTFETCH_PROJECT_NAME "@PROJECT_NAME@" #define FASTFETCH_PROJECT_VERSION "@PROJECT_VERSION@" #define FASTFETCH_PROJECT_VERSION_MAJOR @PROJECT_VERSION_MAJOR@ #define FASTFETCH_PROJECT_VERSION_MINOR @PROJECT_VERSION_MINOR@ #define FASTFETCH_PROJECT_VERSION_PATCH @PROJECT_VERSION_PATCH@ #define FASTFETCH_PROJECT_VERSION_GIT "@PROJECT_VERSION_GIT@" #define FASTFETCH_PROJECT_VERSION_TWEAK "@PROJECT_VERSION_TWEAK@" #define FASTFETCH_PROJECT_VERSION_TWEAK_NUM @PROJECT_VERSION_TWEAK_NUM@ #define FASTFETCH_PROJECT_CMAKE_BUILD_TYPE "@CMAKE_BUILD_TYPE@" #define FASTFETCH_PROJECT_HOMEPAGE_URL "@PROJECT_HOMEPAGE_URL@" #define FASTFETCH_PROJECT_DESCRIPTION "@PROJECT_DESCRIPTION@" #define FASTFETCH_PROJECT_LICENSE "@PROJECT_LICENSE@" #define FASTFETCH_TARGET_DIR_ROOT "@TARGET_DIR_ROOT@" #define FASTFETCH_TARGET_DIR_USR "@TARGET_DIR_USR@" #define FASTFETCH_TARGET_DIR_HOME "@TARGET_DIR_HOME@" #define FASTFETCH_TARGET_DIR_ETC "@TARGET_DIR_ETC@" #define FASTFETCH_TARGET_DIR_INSTALL_SYSCONF "@CMAKE_INSTALL_SYSCONFDIR@" ================================================ FILE: src/fastfetch_datatext.h.in ================================================ #pragma once #define FASTFETCH_DATATEXT_JSON_HELP @DATATEXT_JSON_HELP@ #define FASTFETCH_DATATEXT_STRUCTURE @DATATEXT_STRUCTURE@ ================================================ FILE: src/flashfetch.c ================================================ #include "fastfetch.h" #include "common/init.h" #include "logo/logo.h" #include "modules/modules.h" // A dirty replicate of neofetch int main(void) { ffInitInstance(); // Init everything // Modify global config here if needed instance.config.display.sizeMaxPrefix = 2; // MB instance.config.display.sizeNdigits = 0; instance.config.display.freqNdigits = 3; instance.config.display.freqSpaceBeforeUnit = FF_SPACE_BEFORE_UNIT_NEVER; instance.config.display.sizeSpaceBeforeUnit = FF_SPACE_BEFORE_UNIT_NEVER; // Some preparation stuff ffStart(); // Print logo ffLogoPrint(); // Print all modules { __attribute__((cleanup(ffDestroyTitleOptions))) FFTitleOptions options; ffInitTitleOptions(&options); ffPrintTitle(&options); } { __attribute__((cleanup(ffDestroySeparatorOptions))) FFSeparatorOptions options; ffInitSeparatorOptions(&options); ffPrintSeparator(&options); } { __attribute__((cleanup(ffDestroyOSOptions))) FFOSOptions options; ffInitOSOptions(&options); ffPrintOS(&options); } { __attribute__((cleanup(ffDestroyHostOptions))) FFHostOptions options; ffInitHostOptions(&options); ffPrintHost(&options); } { __attribute__((cleanup(ffDestroyKernelOptions))) FFKernelOptions options; ffInitKernelOptions(&options); ffStrbufSetStatic(&options.moduleArgs.outputFormat, "{release}"); ffPrintKernel(&options); } { __attribute__((cleanup(ffDestroyUptimeOptions))) FFUptimeOptions options; ffInitUptimeOptions(&options); ffPrintUptime(&options); } { __attribute__((cleanup(ffDestroyPackagesOptions))) FFPackagesOptions options; ffInitPackagesOptions(&options); options.combined = true; ffPrintPackages(&options); } { __attribute__((cleanup(ffDestroyShellOptions))) FFShellOptions options; ffInitShellOptions(&options); ffPrintShell(&options); } { __attribute__((cleanup(ffDestroyDisplayOptions))) FFDisplayOptions options; ffInitDisplayOptions(&options); options.compactType = FF_DISPLAY_COMPACT_TYPE_ORIGINAL_BIT; ffStrbufSetStatic(&options.moduleArgs.key, "Resolution"); ffPrintDisplay(&options); } { __attribute__((cleanup(ffDestroyDEOptions))) FFDEOptions options; ffInitDEOptions(&options); ffPrintDE(&options); } { instance.config.general.detectVersion = false; __attribute__((cleanup(ffDestroyWMOptions))) FFWMOptions options; ffInitWMOptions(&options); options.detectPlugin = true; ffPrintWM(&options); instance.config.general.detectVersion = true; } { __attribute__((cleanup(ffDestroyWMThemeOptions))) FFWMThemeOptions options; ffInitWMThemeOptions(&options); ffPrintWMTheme(&options); } { __attribute__((cleanup(ffDestroyThemeOptions))) FFThemeOptions options; ffInitThemeOptions(&options); ffPrintTheme(&options); } { __attribute__((cleanup(ffDestroyIconsOptions))) FFIconsOptions options; ffInitIconsOptions(&options); ffPrintIcons(&options); } { __attribute__((cleanup(ffDestroyTerminalOptions))) FFTerminalOptions options; ffInitTerminalOptions(&options); ffPrintTerminal(&options); } { __attribute__((cleanup(ffDestroyTerminalFontOptions))) FFTerminalFontOptions options; ffInitTerminalFontOptions(&options); ffStrbufSetStatic(&options.moduleArgs.outputFormat, "{/name}{-}{/}{name}{?size} {size}{?}"); ffPrintTerminalFont(&options); } { __attribute__((cleanup(ffDestroyCPUOptions))) FFCPUOptions options; ffInitCPUOptions(&options); ffPrintCPU(&options); } { __attribute__((cleanup(ffDestroyGPUOptions))) FFGPUOptions options; ffInitGPUOptions(&options); ffStrbufSetStatic(&options.moduleArgs.key, "GPU"); ffStrbufSetStatic(&options.moduleArgs.outputFormat, "{name}"); ffPrintGPU(&options); } { __attribute__((cleanup(ffDestroyMemoryOptions))) FFMemoryOptions options; ffInitMemoryOptions(&options); ffStrbufSetStatic(&options.moduleArgs.outputFormat, "{} / {}"); ffPrintMemory(&options); } { __attribute__((cleanup(ffDestroyBreakOptions))) FFBreakOptions options; ffInitBreakOptions(&options); ffPrintBreak(&options); } { __attribute__((cleanup(ffDestroyColorsOptions))) FFColorsOptions options; ffInitColorsOptions(&options); ffPrintColors(&options); } ffLogoPrintRemaining(); ffFinish(); ffDestroyInstance(); return 0; } ================================================ FILE: src/logo/ascii/adelie.txt ================================================ $3 ,-^-___ $3 /\\\/// $2refined.$1 /\\\\// $2reliable.$1 /\\\/// $2ready.$1 /\\/////\ __///\\\\/////\ $3 _//////\\\\\\\//// $1 ///////$3\\\\\\\\\\// //////$1\\\\\/ /////\\\\\/ /////$3\\\\/ /\\///\\\/ /\\\/$1\\/ /\\\\// ////// /// $3\\\\\ ================================================ FILE: src/logo/ascii/aeon.txt ================================================ ⣾⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣷ ⣿⡇ ⢸⣿ ⣿⡇ ⢀⣀ ⣀⡀ ⢸⣿ ⣿⣇ ⠸⣿⣄ ⣠⣿⠇ ⣸⣿ ⢹⣿⡄ ⠙⠻⠿⠿⠟⠋ ⢠⣿⡏ ⠹⣿⣦⡀ ⢀⣴⣿⠏ ⠈⠛⢿⣶⣤⣄ ⣠⣤⣶⡿⠛⠁ $2 ⣠⣴⡿⠿⠛ ⠛⠿⢿⣦⣄ ⣠⣾⠟⠉ ⠉⠻⣷⣄ ⢰⣿⠏ ⢀⣤⣶⣶⣤⡀ ⠹⣿⡆ ⣿⡟ ⢰⣿⠏⠁⠈⠹⣿⡆ ⢿⣿ ⣿⡇ ⠈⠋ ⠙⠁ ⢸⣿ ⣿⡇ ⢸⣿ ⣿⣷⣶⣶⣶⣶⣶⣶⣶⣶⣶⣶⣶⣶⣶⣶⣾⣿ ================================================ FILE: src/logo/ascii/aeros.txt ================================================ ooo OOO OOO ooo oOO OOo oOO OOo oOO OOo oOO OOo oOO OOo oOO OOo OOo OOo OOo OOo OOo OOo oOO OOo oOO OOo oOO OOo oOO OOo oO OOo oOO OOo oOO OOo ooo OOO OOO ooo ================================================ FILE: src/logo/ascii/aerynos.txt ================================================ ;llll. 0MMMMMM: NMMMMMMMMd .@ .cccccccccoWMMMMMMMMMM0 @@ .MMMMMMMMMMMMMMMMMMMMMMMN OMMMMMMMMMMMMMMMW. .@ .MMMMMMMMMMMMMMMMMMM. .@ dMMMMMMMMMMMMMMMMMMl OMMMMMMMMk .OWMMMMM; dMMMMMMMMMk .MMMMMMMMMk @@ ooooooooooooooooooo .MMMMMMMMMN .@ oooooMMMMMMMMMMMMP NMMMMMMMMW. KMMMMMMMMMM. 0MMMMMMMMM: NMMMMMMMMM. dMMMMMMMMMd .WMMMMMMMW. XMMMMMMMMO .MMMMMMMk. xMMMMMMMMX ================================================ FILE: src/logo/ascii/afterglow.txt ================================================ $2. $1. $2.{! $1.L! $2J@||* $1gJJJJL` $2g@FFS" $1,@FFFJF`$2_g@@LLP` $1_@FFFFF`$2_@@@@@P` $4. $1J@@@LLF $2_@@@@@P` $4.J! $1g@@@@@" $2_@@@@@P`$3. $4.L|||* $1g@@@@M" $2"VP`$3.L! $4<@JJJJ` $1"@N" $3:||||! $4JFFFFS" $3.{JJ||F`$4_gFFFF@' $3.@FJJJF`$4,@LFFFF` $3_@FFFFF $4VLLLP` $3J@@LL@" $4`" $3V@@" ================================================ FILE: src/logo/ascii/aix.txt ================================================ `:+ssssossossss+-` .oys///oyhddddhyo///sy+. /yo:+hNNNNNNNNNNNNNNNNh+:oy/ :h/:yNNNNNNNNNNNNNNNNNNNNNNy-+h: `ys.yNNNNNNNNNNNNNNNNNNNNNNNNNNy.ys `h+-mNNNNNNNNNNNNNNNNNNNNNNNNNNNNm-oh h+-NNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNN.oy /d`mNNNNNNN/::mNNNd::m+:/dNNNo::dNNNd`m: h//NNNNNNN: . .NNNh mNo od. -dNNNNN:+y N.sNNNNNN+ -N/ -NNh mNNd. sNNNNNNNo-m N.sNNNNNs +oo /Nh mNNs` ` /mNNNNNNo-m h//NNNNh ossss` +h md- .hm/ `sNNNNN:+y :d`mNNN+/yNNNNNd//y//h//oNNNNy//sNNNd`m- yo-NNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNm.ss `h+-mNNNNNNNNNNNNNNNNNNNNNNNNNNNNm-oy sy.yNNNNNNNNNNNNNNNNNNNNNNNNNNs.yo :h+-yNNNNNNNNNNNNNNNNNNNNNNs-oh- :ys:/yNNNNNNNNNNNNNNNmy/:sy: .+ys///osyhhhhys+///sy+. -/osssossossso/- ================================================ FILE: src/logo/ascii/almalinux.txt ================================================ $1 'c:. $1 lkkkx, .. $2.. ,cc, $1 okkkk:ckkx' $2.lxkkx.okkkkd $1 .:llcokkx' $2:kkkxkko:xkkd, $1 .xkkkkdood: $2;kx, .lkxlll; $1 xkkx. $2xk' xkkkkk: $1 'xkx. $2xd .....,. $3 .. $1:xkl' $2:c ..''.. $3 .dkx' $1.:ldl:'. $2' $4':lollldkkxo; $3 .''lkkko' $4ckkkx. $3'xkkkd:kkd. .. $5;' $4:kkxo. $3,xkkkd;kk' ,d; $5ld. $4':dkd::cc, $3 .,,.;xkko'.';lxo. $5dx, $4:kkk'xkkkkc $3 'dkkkkkxo:. $5;kx $4.kkk:;xkkd. $3 ..... $5.;dk:. $5lkk. $4:;, $5:kkkkkkkdoxkkx ,c,,;;;:xkkd. ;kkkkl... ;kkkkl ,od; ================================================ FILE: src/logo/ascii/alpine.txt ================================================ .hddddddddddddddddddddddh. :dddddddddddddddddddddddddd: /dddddddddddddddddddddddddddd/ +dddddddddddddddddddddddddddddd+ `sdddddddddddddddddddddddddddddddds` `ydddddddddddd++hdddddddddddddddddddy` .hddddddddddd+` `+ddddh:-sdddddddddddh. hdddddddddd+` `+y: .sddddddddddh ddddddddh+` `//` `.` -sddddddddd ddddddh+` `/hddh/` `:s- -sddddddd ddddh+` `/+/dddddh/` `+s- -sddddd ddd+` `/o` :dddddddh/` `oy- .yddd hdddyo+ohddyosdddddddddho+oydddy++ohdddh .hddddddddddddddddddddddddddddddddddddh. `yddddddddddddddddddddddddddddddddddy` `sdddddddddddddddddddddddddddddddds` +dddddddddddddddddddddddddddddd+ /dddddddddddddddddddddddddddd/ :dddddddddddddddddddddddddd: .hddddddddddddddddddddddh. ================================================ FILE: src/logo/ascii/alpine2.txt ================================================ .:::::::::::::::::::::. .:::::::::::::::::::::::. .:::::::::::::::::::::::::. .:::::::::::::::::::::::::::. .:::::::::$2,db,$1::::::::::::::::. .::::::::$2,d%%%%b,$1::$2,db,$1:::::::::. .:::::::$2,%%%%P'%%%b,d%%%b,$1::::::::. .::::::$2,%%%%P,$1:::$2`%%%b'^q%%b,$1:::::::. '::::$2,%%%%P,d|$1:::::$2`%%%b:'^%%b,$1:::::' '::$2`%%%'$1:$2'q$|$1:::::::$2'q%%b'`q%%b'$1::' ':::::::::::::::::::::::::::::::' ':::::::::::::::::::::::::::::' ':::::::::::::::::::::::::::' ':::::::::::::::::::::::::' ':::::::::::::::::::::::' ':::::::::::::::::::::' ================================================ FILE: src/logo/ascii/alpine2_small.txt ================================================ $1 /\ /\ /$2/ $1\ \ /$2// $1\ \ /$2// $1\ \ $2// $1\ \ \ ================================================ FILE: src/logo/ascii/alpine3_small.txt ================================================ ,db, ,d%%%%b, ,db, ,%%%%P'%%%b,d%%%b, ,%%%%P, `%%%b'^q%%b, ,%%%%P,d| `%%%b '^%%b, `%%%' 'q$| 'q%%b'`q%%b ================================================ FILE: src/logo/ascii/alpine_small.txt ================================================ /\ /\ // \ \ // \ \ /// \ \ // \ \ \ ================================================ FILE: src/logo/ascii/alter.txt ================================================ %, ^WWWw 'wwwwww !wwwwwwww #`wwwwwwwww @wwwwwwwwwwww wwwwwwwwwwwwwww wwwwwwwwwwwwwwwww wwwwwwwwwwwwwwwwwww wwwwwwwwwwwwwwwwwwww, w~1i.wwwwwwwwwwwwwwwww, 3~:~1lli.wwwwwwwwwwwwwwww. :~~:~?ttttzwwwwwwwwwwwwwwww #<~:~~~~?llllltO-.wwwwwwwwwww #~:~~:~:~~?ltlltlttO-.wwwwwwwww @~:~~:~:~:~~(zttlltltlOda.wwwwwww @~:~~: ~:~~:~:(zltlltlO a,wwwwww 8~~:~~:~~~~:~~~~_1ltltu ,www 5~~:~~:~~:~~:~~:~~~_1ltq N,, g~:~~:~~~:~~:~~:~:~~~~1q N, ================================================ FILE: src/logo/ascii/altlinux.txt ================================================ ############## ###################### ########################## ##+$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+###$3####$1##########$2%#*$1####### ##########$2###*$3######$2+#+$1#####$2+##*$1###### #########$2##%$3#####$2:%#####$1###$2###*$1##### ########$2%#+$3######$2#############$1#### #####$2##%:$3######$2:############$1## ##$2+##*$3########$2############$1 $2###$3#########$2##########$1 $3########$2###### ================================================ FILE: src/logo/ascii/amazon.txt ================================================ `-/oydNNdyo:.` `.:+shmMMMMMMMMMMMMMMmhs+:.` -+hNNMMMMMMMMMMMMMMMMMMMMMMNNho- .`` -/+shmNNMMMMMMNNmhs+/- ``. dNmhs+:. `.:/oo/:.` .:+shmNd dMMMMMMMNdhs+:.. ..:+shdNMMMMMMMd dMMMMMMMMMMMMMMNds odNMMMMMMMMMMMMMMd dMMMMMMMMMMMMMMMMh yMMMMMMMMMMMMMMMMd dMMMMMMMMMMMMMMMMh yMMMMMMMMMMMMMMMMd dMMMMMMMMMMMMMMMMh yMMMMMMMMMMMMMMMMd dMMMMMMMMMMMMMMMMh yMMMMMMMMMMMMMMMMd dMMMMMMMMMMMMMMMMh yMMMMMMMMMMMMMMMMd dMMMMMMMMMMMMMMMMh yMMMMMMMMMMMMMMMMd dMMMMMMMMMMMMMMMMh yMMMMMMMMMMMMMMMMd dMMMMMMMMMMMMMMMMh yMMMMMMMMMMMMMMMMd dMMMMMMMMMMMMMMMMh yMMMMMMMMMMMMMMMMd .:+ydNMMMMMMMMMMMh yMMMMMMMMMMMNdy+:. `.:+shNMMMMMh yMMMMMNhs+:`` `-+shy shs+:` ================================================ FILE: src/logo/ascii/amazon_linux.txt ================================================ , $2#_$1 ~\_ $2####_$1 ~~ \_$2#####\$1 ~~ \$2###|$1 ~~ \$2#/$1 ___ ~~ V~' '-> ~~~ / ~~._. _/ _/ _/ _/m/' ================================================ FILE: src/logo/ascii/amiga.txt ================================================ ----.--- :-==.-==- $2 -=== ===: ===-:===. $3 -+++:=++= =+++ +++. $6 .+++-=++= $4.::. ::: $6 :**+ +**- $4 :--:.---. $6 .+**=-***. $5 :--- ---::$6***:***+ $5 .---::-$6=##*.*##- $7 ----+$6##=:##*. $7 :-$6###-*##+ ================================================ FILE: src/logo/ascii/amogos.txt ================================================ ___________ / \ / $2______$1 \ / $2/ \$1 \ | $2( )$1 \ / $2\______/$1 | | | / \ | | | | / | | | | _______ | ____/ / \ | / | | | | / ____/ | \_________/ / | \ __/ \_______/ ================================================ FILE: src/logo/ascii/anarchy.txt ================================================ $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#########+ :###$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 ================================================ FILE: src/logo/ascii/android.txt ================================================ -o o- +hydNNNNdyh+ +mMMMMMMMMMMMMm+ `dMM$2m:$1NMMMMMMN$2:m$1MMd` hMMMMMMMMMMMMMMMMMMh .. yyyyyyyyyyyyyyyyyyyy .. .mMMm`MMMMMMMMMMMMMMMMMMMM`mMMm. :MMMM-MMMMMMMMMMMMMMMMMMMM-MMMM: :MMMM-MMMMMMMMMMMMMMMMMMMM-MMMM: :MMMM-MMMMMMMMMMMMMMMMMMMM-MMMM: :MMMM-MMMMMMMMMMMMMMMMMMMM-MMMM: -MMMM-MMMMMMMMMMMMMMMMMMMM-MMMM- +yy+ MMMMMMMMMMMMMMMMMMMM +yy+ mMMMMMMMMMMMMMMMMMMm `/++MMMMh++hMMMM++/` MMMMo oMMMM MMMMo oMMMM oNMm- -mMNs ================================================ FILE: src/logo/ascii/android_small.txt ================================================ ;, ,; ';,.-----.,;' ,' ', / O O \ | | '-----------------' ================================================ FILE: src/logo/ascii/anduinos.txt ================================================ $1+++++++++++ +++++++++++++++++++++ +++++++++++++++++++++++++++++++ =+++++++++++++++++++++++++++++++++++++++= +++++++++++++++++++++++++++++++++++++++++++++ =++++++++++++++++++++++++++++++++++++++++++++ ==+++++++++++++++++++++++++++++++++== +++++++++++++++++++++++++++ $2**** $1++=+++++++++++=++ $2**** ********** $1+++++++ $2********** ************** ************** **************** **************** *********************** ****** ************* ****** *********** *** *********** *************** *************** *************** *************** ********************* *********** ================================================ FILE: src/logo/ascii/antergos.txt ================================================ $2 `.-/::/-`` .-/osssssssso/. :osyysssssssyyys+- `.+yyyysssssssssyyyyy+. `/syyyyyssssssssssyyyyys-` `/yhyyyyysss$1++$2ssosyyyyhhy/` .ohhhyyyys$1o++/+o$2so$1+$2syy$1+$2shhhho. .shhhhys$1oo++//+$2sss$1+++$2yyy$1+s$2hhhhs. -yhhhhs$1+++++++o$2ssso$1+++$2yyy$1s+o$2hhddy: -yddhhy$1o+++++o$2syyss$1++++$2yyy$1yooy$2hdddy- .yddddhs$1o++o$2syyyyys$1+++++$2yyhh$1sos$2hddddy` `odddddhyosyhyyyyyy$1++++++$2yhhhyosddddddo .dmdddddhhhhhhhyyyo$1+++++$2shhhhhohddddmmh. ddmmdddddhhhhhhhso$1++++++$2yhhhhhhdddddmmdy dmmmdddddddhhhyso$1++++++$2shhhhhddddddmmmmh -dmmmdddddddhhys$1o++++o$2shhhhdddddddmmmmd- .smmmmddddddddhhhhhhhhhdddddddddmmmms. `+ydmmmdddddddddddddddddddmmmmdy/. `.:+ooyyddddddddddddyyso+:.` ================================================ FILE: src/logo/ascii/antix.txt ================================================ \ , - ~ ^ ~ - \ / , ' \ ' , / , \ '/ , \ / , ,___, \/ , / | _ _ _|_ o /\ , |, | / |/ | | | / \ , \,_/\_/ | |_/|_/|_/_/ \, , / ,\ , / , ' \ ' - , _ _ _ , ' ================================================ FILE: src/logo/ascii/anushos.txt ================================================ $4####################### $4# $2##### $4# $4# $2####### $4# $4# $2##$5O$2#$5O$2## $4# $4# $2#$3#####$2# $4# $4# $2##$1##$3###$1##$2## $4# $4# $2#$1##########$2## $4# $4# $2#$1############$2## $4# $4# $2#$1######$5A_O$1####$2### $4# $4# $3##$2#$1############$2##$3## $4# $4#$3######$2#$1#######$2#$3######$4# $4#$3#######$2#$1#####$2#$3#######$4# $4# $3#####$2#######$3##### $4# $4####################### $4#$5╔═╗╔╗╔╦ ╦╔═╗╦ ╦╔═╗╔═╗$4# $4#$5╠═╣║║║║ ║╚═╗╠═╣║ ║╚═╗$4# $4#$5╩ ╩╝╚╝╚═╝╚═╝╩ ╩╚═╝╚═╝$4# $4####################### $4# $3WWW.ANUSHOS.ORG $4# $4####################### ================================================ FILE: src/logo/ascii/aoscos.txt ================================================ $2__ $2gpBBBBBBBBBP $2_gBBBBBBBBBRP $24BBBBBBBBRP $4,_____ $2`"" $4_g@@@@@@@@@@@@@%g> $4__@@@@@@@@@@@@@@@@P" $1___ $4_g@@@@@@@@@@@@@@@N"` $1_gN@@@@@N^ $4_w@@@@@@@@@@@@@@@@P" $1_g@@@@@@@P" $4_g@@@@@@@@@@@@@@@N"` $1VMNN@NNNM^` $4^MMM@@@@@@@@@@@MP" $3,ggppww__ $4`""""" $3_wNNNNNNNNNNNNNNNNNNN $3_gBNNNNNNNNNNNNNNNNNP" $3_wNNNNNNNNNNNNNNNNNNMP` $3_gBNNNNNNNNNNNNNNNNNP" $3_wNNNNNNNNNNNNNNNNNNNM^ $3""Y^^MNNNNNNNNNNNNP` $3`""""""" ================================================ FILE: src/logo/ascii/aoscos_old.txt ================================================ .:+syhhhhys+:. .ohNMMMMMMMMMMMMMMNho. `+mMMMMMMMMMMmdmNMMMMMMMMm+` +NMMMMMMMMMMMM/ `./smMMMMMN+ .mMMMMMMMMMMMMMMo -yMMMMMm. :NMMMMMMMMMMMMMMMs .hMMMMN: .NMMMMhmMMMMMMMMMMm+/- oMMMMN. dMMMMs ./ymMMMMMMMMMMNy. sMMMMd -MMMMN` oMMMMMMMMMMMN: `NMMMM- /MMMMh NMMMMMMMMMMMMm hMMMM/ /MMMMh NMMMMMMMMMMMMm hMMMM/ -MMMMN` :MMMMMMMMMMMMy. `NMMMM- dMMMMs .yNMMMMMMMMMMMNy/. sMMMMd .NMMMMo -/+sMMMMMMMMMMMmMMMMN. :NMMMMh. .MMMMMMMMMMMMMMMN: .mMMMMMy- NMMMMMMMMMMMMMm. +NMMMMMms/.` mMMMMMMMMMMMN+ `+mMMMMMMMMNmddMMMMMMMMMMm+` .ohNMMMMMMMMMMMMMMNho. .:+syhhhhys+:. ================================================ FILE: src/logo/ascii/aoscosretro.txt ================================================ $2 ......... ................... .....................$1################$2 .............. ....$1################$2 .............. ...$1################$2 ............. ..$1****************$2 ............ . .$1****************$2 ........... ... $1................$2 .......... ..... $1...............$2 ......... ....... ... .$3...... $2. $3..... .....$2.... $4........... $3.... ......$2. $4........... $3... ....... $4........... $3................ $4*********** $3................ $4########### $3**************** $3################ ================================================ FILE: src/logo/ascii/aoscosretro_small.txt ================================================ $2 _____ $1_____$2 -' '-$1| |$2 / ___ $1| |$2 | / _ \$1|_____|$2 ' / /_\ \ \ / _____ \$4___ $3|$2/_/ $3| $4| | $3| | $4|___| $3|_____| ================================================ FILE: src/logo/ascii/aperture.txt ================================================ .,-:;//;:=, $8. $1:H@@@MM@M#H/.$2,+%;, $8,/X+ $1+M@@M@MM%=,$2-%HMMM@X/, $8-+@MM; $1$$M@@MH+-,$2;XMMMM@MMMM@+- $8;@M@@M- $1XM@X;. $2-+XXXXXHHH@M@M#@/. $8,%MM@@MH $1,@%= $2.---=-=:=,. $8=@#@@@MX., $3-%HX$$$$%%%:; $7=-$8./@M@M$$ $3.;@MMMM@MM: $7X@/$8 -$$MM/ $3. +MM@@@M$$ $7,@M@H:$8 :@: $4. $3=X#@@@@- $7,@@@MMX,$8 . $4/H- $3;@M@M= $7.H@@@@M@+, $4%MM+. $3%#$. $7/MMMM@MMH/. $4XM@MH; $3=; $7/%+%$$XHH@$$= $5, $4.H@@@@MX, $6=----------. $5-%H.$4,@@@@@MX, $6.%MM@@@HHHXX$$$$$$%+- $5.:$$MMX$4 =M@@MM%. $6=XMMM@MM@MM#H;$5,-+HMM@M+$4 /MMMX= $6=%@M@M#@$$-$5.=$@MM@@@M;$4 %M%= $6,:+$$+-$5,/H#MMMMMMM@=$4 =, $5=++%%%%+/:-. ================================================ FILE: src/logo/ascii/apricity.txt ================================================ ./o- ``...`` `:. -/: `-+ymNMMMMMNmho-` :sdNNm/ `+dMMMMMMMMMMMMMMMmo` sh:.:::- /mMMMMMMMMMMMMMMMMMMMm/`sNd/ oMMMMMMMMMMMMMMMMMMMMMMMs -` :MMMMMMMMMMMMMMMMMMMMMMMMM/ NMMMMMMMMMMMMMMMMMMMMMMMMMd MMMMMMMmdmMMMMMMMMMMMMMMMMd MMMMMMy` .mMMMMMMMMMMMmho:` MMMMMMNo/sMMMMMMMNdy+-.`-/ MMMMMMMMMMMMNdy+:.`.:ohmm: MMMMMMMmhs+-.`.:+ymNMMMy. MMMMMM/`.-/ohmNMMMMMMy- MMMMMMNmNNMMMMMMMMmo. MMMMMMMMMMMMMMMms:` MMMMMMMMMMNds/. dhhyys+/-` ================================================ FILE: src/logo/ascii/arch.txt ================================================ -` .o+` `ooo/ `+oooo: `+oooooo: -+oooooo+: `/:-:++oooo+: `/++++/+++++++: `/++++++++++++++: `/+++o$2oooooooo$1oooo/` ./$2ooosssso++osssssso$1+` $2 .oossssso-````/ossssss+` -osssssso. :ssssssso. :osssssss/ osssso+++. /ossssssss/ +ssssooo/- `/ossssso+/:- -:/+osssso+- `+sso+:-` `.-/+oso: `++:. `-/+/ .` `/ ================================================ FILE: src/logo/ascii/arch2.txt ================================================ ▄ ▟█▙ ▟███▙ ▟█████▙ ▟███████▙ ▂▔▀▜██████▙ ▟██▅▂▝▜█████▙ ▟█████████████▙ ▟███████████████▙ ▟█████████████████▙ ▟███████████████████▙ $2 ▟█████████▛▀▀▜████████▙ ▟████████▛ ▜███████▙ ▟█████████ ████████▙ ▟██████████ █████▆▅▄▃▂ ▟██████████▛ ▜█████████▙ ▟██████▀▀▀ ▀▀██████▙ ▟███▀▘ ▝▀███▙ ▟▛▀ ▀▜▙ ================================================ FILE: src/logo/ascii/arch3.txt ================================================ . / \ / \ / \ / \ />, \ / `*. \ / ` \ / \ / \ $2 / ,.-+-.. \ / ,/' `\. \ / .|' `|. _ \ / :|. ,|; `+.\ / .\: ;/, "<\ / __,--+" "+--.__ \ / _,+'" "'+._ \ /,-' `-.\ ' ' ================================================ FILE: src/logo/ascii/arch_old.txt ================================================ __ _=(SDGJT=_ _GTDJHGGFCVS) ,GTDJGGDTDFBGX0 $1 JDJDIJHRORVFSBSVL$2-=+=,_ $1 IJFDUFHJNXIXCDXDSV,$2 "DEBL $1 [LKDSDJTDU=OUSCSBFLD.$2 '?ZWX, $1 ,LMDSDSWH' `DCBOSI$2 DRDS], $1 SDDFDFH' !YEWD,$2 )HDROD $1 !KMDOCG &GSU|$2\_GFHRGO\' $1 HKLSGP'$2 __$1\TKM0$2\GHRBV)' $1JSNRVW'$2 __+MNAEC$1\IOI,$2\BN' $1HELK['$2 __,=OFFXCBGHC$1\FD) $1?KGHE $2\_-#DASDFLSV='$1 'EF 'EHTI !H `0F' '! ================================================ FILE: src/logo/ascii/arch_small.txt ================================================ /\ / \ / \ / \ $2 / ,, \ / | | \ /_-'' ''-_\ ================================================ FILE: src/logo/ascii/archbox.txt ================================================ ...:+oh/:::.. ..-/oshhhhhh` `::::-. .:/ohhhhhhhhhhhh` `-::::. .+shhhhhhhhhhhhhhhhh` `.::-. /`-:+shhhhhhhhhhhhhh` .-/+shh / .:/ohhhhhhhhh` .:/ohhhhhhhh / `-:+shhh` ..:+shhhhhhhhhhhh / .:ohhhhhhhhhhhhhhhhhhh / `hhhhhhhhhhhhhhhhhhhh / `hhhhhhhhhhhhhhhhhhhh / `hhhhhhhhhhhhhhhhhhhh / `hhhhhhhhhhhhhhhhhhhh / .+o+ `hhhhhhhhhhhhhhhhhhhh / -hhhhh `hhhhhhhhhhhhhhhhhhhh / ohhhhho `hhhhhhhhhhhhhhhhhhhh /:::+`hhhhoos` `hhhhhhhhhhhhhhhhhs+` `--/:` /: `hhhhhhhhhhhho/- -/:. `hhhhhhs+:-` ::::/ho/-` ================================================ FILE: src/logo/ascii/archcraft.txt ================================================ $1⠄⠄⠄⠄⠄⠄⠄⠄⠄⠄⠄⠄⠄⠄⠄⠄⠄⠄⠄$1⢰⡆$1⠄⠄⠄⠄⠄⠄⠄⠄⠄⠄⠄⠄⠄⠄⠄⠄⠄⠄⠄ $2⠄⠄⠄⠄⠄⠄⠄⠄⠄⠄⠄⠄⠄⠄⠄⠄⠄⠄$1⢠⣿⣿⡄$2⠄⠄⠄⠄⠄⠄⠄⠄⠄⠄⠄⠄⠄⠄⠄⠄⠄⠄ $3⠄⠄⠄⠄⠄⠄⠄⠄⠄⠄⠄⠄⠄⠄⠄⠄⠄$1⢀⣾⣿⣿⣿⡀$3⠄⠄⠄⠄⠄⠄⠄⠄⠄⠄⠄⠄⠄⠄⠄⠄⠄ $4⠄⠄⠄⠄⠄⠄⠄⠄⠄⠄⠄⠄⠄⠄⠄⠄⠄$1⣼⣿⣿⣿⣿⣷⡀$4⠄⠄⠄⠄⠄⠄⠄⠄⠄⠄⠄⠄⠄⠄⠄⠄ $5⠄⠄⠄⠄⠄⠄⠄⠄⠄⠄⠄⠄⠄⠄⠄⠄$1⣼⣿⣿⣿⣿⣿⣿⣷$5⠄⠄⠄⠄⠄⠄⠄⠄⠄⠄⠄⠄⠄⠄⠄⠄ $6⠄⠄⠄⠄⠄⠄⠄⠄⠄⠄⠄⠄⠄⠄⠄$1⢼⣿⣿⣿⣿⣿⣿⣿⣿⣧$6⠄⠄⠄⠄⠄⠄⠄⠄⠄⠄⠄⠄⠄⠄⠄ $1⠄⠄⠄⠄⠄⠄⠄⠄⠄⠄⠄⠄⠄⠄$1⣰⣤⣈⠻⢿⣿⣿⣿⣿⣿⣿⣧$1⠄⠄⠄⠄⠄⠄⠄⠄⠄⠄⠄⠄⠄⠄ $2⠄⠄⠄⠄⠄⠄⠄⠄⠄⠄⠄⠄⠄$1⣰⣿⣿⣿⣿⣮⣿⣿⣿⣿⣿⣿⣿⣧$2⠄⠄⠄⠄⠄⠄⠄⠄⠄⠄⠄⠄⠄ $3⠄⠄⠄⠄⠄⠄⠄⠄⠄⠄⠄⠄$1⣰⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣧$3⠄⠄⠄⠄⠄⠄⠄⠄⠄⠄⠄⠄ $4⠄⠄⠄⠄⠄⠄⠄⠄⠄⠄⠄$1⣰⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣧$4⠄⠄⠄⠄⠄⠄⠄⠄⠄⠄⠄ $5⠄⠄⠄⠄⠄⠄⠄⠄⠄⠄$1⣼⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣧$5⠄⠄⠄⠄⠄⠄⠄⠄⠄⠄ $6⠄⠄⠄⠄⠄⠄⠄⠄⠄$1⣼⣿⣿⣿⣿⣿⡿⣿⣿⡟$6⠄⠄$1⠸⣿⣿⡿⣿⣿⣿⣿⣿⣷⡀$6⠄⠄⠄⠄⠄⠄⠄⠄ $1⠄⠄⠄⠄⠄⠄⠄⠄$1⣼⣿⣿⣿⣿⣿⡏$1⠄⠄⠄⠄⠄⠄⠄⠄⠄⠄$1⠈⣿⣿⣿⣿⣿⣷⡀$1⠄⠄⠄⠄⠄⠄⠄ $2⠄⠄⠄⠄⠄⠄$1⢀⣼⣿⣿⣿⣿⣿⣿⡗$2⠄⠄⠄$1⢀⣠⣤⣀⠄⠄⠄$1⠸⣿⣿⣿⣿⣿⣿⣷⡀$2⠄⠄⠄⠄⠄⠄ $3⠄⠄⠄⠄⠄$1⢀⣾⣿⣿⣿⣿⣿⡏⠁$3⠄⠄⠄$1⢠⣿⣿⣿⣿⡇$3⠄⠄⠄⠄$1⢙⣿⣿⣻⠿⣿⣷⡀$3⠄⠄⠄⠄⠄ $4⠄⠄⠄⠄$1⢀⣾⣿⣿⣿⣿⣿⣿⣷⣤⡀$4⠄⠄⠄$1⠻⣿⣿⡿⠃$4⠄⠄⠄$1⢀⣼⣿⣿⣿⣿⣦⣌⠙⠄$4⠄⠄⠄⠄ $5⠄⠄⠄$1⢠⣾⣿⣿⣿⣿⣿⣿⣿⣿⣿⠏$5⠄⠄⠄⠄⠄⠄⠄⠄⠄⠄⠄$1⢿⣿⣿⣿⣿⣿⣿⣿⣿⣦⡀$5⠄⠄⠄ $6⠄⠄$1⢠⣿⣿⣿⣿⣿⣿⣿⡿⠟⠋⠁$6⠄⠄⠄⠄⠄⠄⠄⠄⠄⠄⠄⠄⠄$1⠙⠻⣿⣿⣿⣿⣿⣿⣿⣿⡄$6⠄⠄ $1⠄$1⣠⣿⣿⣿⣿⠿⠛⠋⠁$1⠄⠄⠄⠄⠄⠄⠄⠄⠄⠄⠄⠄⠄⠄⠄⠄⠄⠄⠄⠄$1⠉⠙⠻⢿⣿⣿⣿⣿⣆$1⠄ $1⡰⠟⠛⠉⠁$2⠄⠄⠄⠄⠄⠄⠄⠄⠄⠄⠄⠄⠄⠄⠄⠄⠄⠄⠄⠄⠄⠄⠄⠄⠄⠄⠄⠄⠄⠄$1⠉⠙⠛⠿⢆ ================================================ FILE: src/logo/ascii/archcraft2.txt ================================================ -o\ :ooo: .ooooo. ooooooo. +oooooooo. -oooooooooo. --:-+oooooooo. yooo+=+sooooooo. yoooooosooooooooo. y+ooooooooooooooooo. yoooooooooooooooooooo` yoooooo+oo= :oo++ooooo` :oooooo. +ooooo- -ooooooo. .::. +ooosoo= -oooooo` .oooo` +os-=o= =ooooooo=: `oo+ :=ooo=--`. +ooooooooos. .=sooooooo+- .+osossos+-` `-+osososs+. :sss+=-:` `:-=+ssss: :=-:` `-=+: ================================================ FILE: src/logo/ascii/archlabs.txt ================================================ 'c' 'kKk, .dKKKx. .oKXKXKd. .l0XXXXKKo. c0KXXXXKX0l. :0XKKOxxOKX0l. :OXKOc. .c0XX0l. :OK0o. $2...$1'dKKX0l. :OX0c $2;xOx'$1'dKXX0l. :0KKo.$2.o0XXKd'.$1lKXX0l. c0XKd.$2.oKXXXXKd..$1oKKX0l. .c0XKk;$2.l0K0OO0XKd..$1oKXXKo. .l0XXXk:$2,dKx,.'l0XKo.$1.kXXXKo. .o0XXXX0d,$2:x; .oKKx'$1.dXKXXKd. .oKXXXXKK0c.$2;. :00c'$1cOXXXXXKd. .dKXXXXXXXXk,$2. cKx'$1'xKXXXXXXKx' 'xKXXXXK0kdl:. $2.ok; $1.cdk0KKXXXKx' 'xKK0koc,.. $2'c, $1 ..,cok0KKk, ,xko:'. $2.. $1 .':okx; .,'. .',. ================================================ FILE: src/logo/ascii/archstrike.txt ================================================ $1 * **. **** ****** ******* ** ******* **** ******* $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******, ================================================ FILE: src/logo/ascii/arco.txt ================================================ /- ooo: yoooo/ yooooooo yooooooooo yooooooooooo .yooooooooooooo .oooooooooooooooo .oooooooarcoooooooo .ooooooooo-oooooooooo .ooooooooo- oooooooooo :ooooooooo. :ooooooooo :ooooooooo. :ooooooooo :oooarcooo .oooarcooo :ooooooooy .ooooooooo $1:ooooooooo $2/ooooooooooooooooooo $1:ooooooooo $2.-ooooooooooooooooo. $1ooooooooo- $2-ooooooooooooo. $1ooooooooo- $2.-oooooooooo. $1ooooooooo. $2-ooooooooo ================================================ FILE: src/logo/ascii/arco_small.txt ================================================ A ooo ooooo ooooooo ooooooooo ooooo ooooo ooooo ooooo ooooo ooooo ooooo $2$1 ooooo $2$1 ooooo $2 ================================================ FILE: src/logo/ascii/arkane.txt ================================================ .:.. ..:::...... $2.$1 .$2.$1..... $2+=$1...$2==$1.... ......:.$3:-$2:$1..$3+*$2=$1.... $2:----::$1...... $2.=***##*+=: $1.. $2=$3***######*$2= $2.$3-*######+ $2:+$3###%%%###$1: $2-+*$3########+$1. $2=++*$3#######$1- $2-+=+**$3*####$1= $1.$2-=++==***$3##*$1- $2-++++++==++++= .-+++**+++=+===$1. $2:---===++++=-=--$1. $2-===============$1-==--: $2.-==+++***++*$3*#########$1=:::. $2.-=++++*++++**$3#######%%###$1= $2.:==++++++**$3#############$1: $2.$1-+*++*+++==$3###$1+ -$3*+$1: ================================================ FILE: src/logo/ascii/armbian.txt ================================================ .. `:]x**j-,' .,+t***********z\<" ?******************; '*n` .'`^,;;,^`'. ,cc. -<. .[l // ^^ ^^ \\ !^ $2^^$1 ": 'tt}` $2!~]rj_$1 ")t/. Itttt?' $2~~]rr]$1 `{tttt, \tttttt!""I$2_]r($1"""~tttttt1 '_tttttttttttt$2)f$1tttttttttttti. \*ztttttttttttttttttttttttttf**[ l**c)tttttttttttttttttttttttt(z**, .z*x.`tttttttttttttttttttttttt.`u*n >` (tttttttttttttttttttttt] "I ,tttttttttttttttttttttt` ./ttttt$2f$1tttttttt$2f$1ttttt( 'I)$2))(\()($1tt$2))|\()($1{;' $2.~~~~~~~|)~~~~~~~<$1 '$2[)))))1$1|($2)))))))$1? $2",,," ",,,^ ================================================ FILE: src/logo/ascii/armbian2.txt ================================================ █ █ █ █ █ █ █ █ █ █ █ ███████████████████████ ▄▄██ ██▄▄ ▄▄██ ███████████ ██▄▄ ▄▄██ ██ ██ ██▄▄ ▄▄██ ██ ██ ██▄▄ ▄▄██ ██ ██ ██▄▄ ▄▄██ █████████████ ██▄▄ ▄▄██ ██ ██ ██▄▄ ▄▄██ ██ ██ ██▄▄ ▄▄██ ██ ██ ██▄▄ ▄▄██ ██▄▄ ███████████████████████ █ █ █ █ █ █ █ █ █ █ █ ================================================ FILE: src/logo/ascii/arselinux.txt ================================================ ⣀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀ ⠀⣶⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢸⣿⠀⠀⠀⠀⠀⠀⣴⣶⠀⠀⠀⠀⠀ ⢸⣿⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢸⣿⣄⠀⠀⠀⠀⣼⠟⠁⠀⠀⢀⣀⠀ ⢸⣿⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢀⡀⠀⢀⣤⡀⠀⠀⠀⠉⢻⣷⡄⠀⠀⠁⠀⢀⣤⣾⡿⠟⠀ ⢸⣿⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠙⢿⣷⣿⠏⠀⠀⠀⠀⠀⠀⠹⣿⡄⠀⠀⠀⠙⠉⠁⠀⠀⠀ ⢸⣿⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠹⣿⡄⠀⠀⠀⠀⠀⠀⠀⢹⣿⠀⠀⠀⠀⠠⣶⣶⣶⡶ ⢸⣿⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢹⣿⠀⠀⠀⠀⠀⠀⠀⠀⣿⡇⠀⠀⠀⠀⠀⠀⠀⠀ ⢸⣿⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢸⣿⠀⠀⠀⠀⠀⠀⠀⢠⣿⠁⠀⠀⠀⠀⠀⠀⠀⠀ ⢸⣿⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢸⣿⠂⠀⠀⠀⠀⠀⢀⣾⡏⠀⠀⠀⠀⠀⠀⠀⠀⠀ ⢸⣿⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢠⣿⠇⠀⠀⠀⠀⠀⣠⣾⠟⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀ ⢸⣿⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⣠⣴⣿⣇⣀⣀⣀⣠⣴⣾⣿⡏⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀ ⢸⣿⠀⠀⠀⠀⠀⣤⣤⣴⣶⣾⠿⠟⣿⡏⠙⠛⠛⠛⠋⠉⢀⣿⠁⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀ ⠀⣿⡄⠀⠀⠀⠀⠈⠉⠉⠀⠀⠀⠀⣿⡇⠀⠀⠀⠀⠀⠀⢸⣿⡀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀ ⠀⣿⡇⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢿⠇⠀⠀⠀⠀⠀⠀⠘⠿⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀ ⠀⠈⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀ ================================================ FILE: src/logo/ascii/artix.txt ================================================ ' 'o' 'ooo' 'ooxoo' 'ooxxxoo' 'oookkxxoo' 'oiioxkkxxoo' ':;:iiiioxxxoo' `'.;::ioxxoo' '-. `':;jiooo' 'oooio-.. `'i:io' 'ooooxxxxoio:,. `'-;' 'ooooxxxxxkkxoooIi:-. `' 'ooooxxxxxkkkkxoiiiiiji' 'ooooxxxxxkxxoiiii:'` .i' 'ooooxxxxxoi:::'` .;ioxo' 'ooooxooi::'` .:iiixkxxo' 'ooooi:'` `'';ioxxo' 'i:'` '':io' '` `' ================================================ FILE: src/logo/ascii/artix2.txt ================================================ .: .==: .====: .======: .========: .==========: .============: .-===========: .:-========: .-:. .:======: .======:.. :-===. .============:.. .:=. .==================:.. .=======================: .====================::. . ================::. :-=== ============-:. .:======== ========-:. :-========== =====:. ..:-===== =::. ..:: ================================================ FILE: src/logo/ascii/artix2_small.txt ================================================ /\ / \ /`'.,\ / ', / ,`\ / ,.'`. \ /.,'` `'.\ ================================================ FILE: src/logo/ascii/artix_small.txt ================================================ ' 'A' 'ooo' 'ookxo' `ookxxo' '. `ooko' 'ooo`. `oo' 'ooxxxoo`. `' 'ookxxxkooo.` . 'ookxxkoo'` .'oo' 'ooxoo'` .:ooxxo' 'io'` `'oo' '` `' ================================================ FILE: src/logo/ascii/arya.txt ================================================ $1 `oyyy/$2-yyyyyy+ $1 -syyyy/$2-yyyyyy+ $1 .syyyyy/$2-yyyyyy+ $1 :yyyyyy/$2-yyyyyy+ $1 `/ :yyyyyy/$2-yyyyyy+ $1 .+s :yyyyyy/$2-yyyyyy+ $1 .oys :yyyyyy/$2-yyyyyy+ $1 -oyys :yyyyyy/$2-yyyyyy+ $1 :syyys :yyyyyy/$2-yyyyyy+ $1 /syyyys :yyyyyy/$2-yyyyyy+ $1 +yyyyyys :yyyyyy/$2-yyyyyy+ $1 .oyyyyyyo. :yyyyyy/$2-yyyyyy+ --------- $1 .syyyyyy+` :yyyyyy/$2-yyyyy+-+syyyyyyyy $1 -syyyyyy/ :yyyyyy/$2-yyys:.syyyyyyyyyy $1:syyyyyy/ :yyyyyy/$2-yyo.:syyyyyyyyyyy ================================================ FILE: src/logo/ascii/asahi.txt ================================================ ## $2** $1*####$2****. $1###$2, $3...,$1/#$3,,,.. $3/*,,,,,,,,$1*$3,........$4,, $3,((((((//*,,,,,,,,$4,...... $3(((((((((((((($5%..$4.......... $3,((((((((((((((($5@@($4............ $3((((((((((((((((($5@@@@/$4............ $3,(((((((((((((((((($5@@@@@&*$4........... $3(((((((((((((((((((($5@@@@@@@&$4,........... $3((((((((((((((((((((($5@@@$6&%&$5@@@%$4,.......... $3/((((((((((((((((((($5@@@$6&%%&$5@@@@($4........ $3,(((((((((((((((($5@@@$6&&$5@@&/&@@@/$4.. $3/(((((((((((($5@@@@@@/$4.../&& $3.((((((((($5@@@@($4.... $3/((((($5@@#$4... $3.(($4&, ================================================ FILE: src/logo/ascii/asahi2.txt ================================================ $1_wwM $2_ww $1MMM$2MMMM $1MM $3_ww##############yy_ $4wMMMMM$3###########$4MMMMm $4,MMMMMMMMM$3######$4MMMMMMMM0_ $4wMMMMMMMMMMMMM$5MM$60MMMMMMMMMMm $4,MMMMMMMMMMMMMMM$5MMMM$60MMMMMMMMM0, $4wMMMMMMMMMMMMMMMM$5MMMMM0$6MMMMMMMMMMb $4_MMMMMMMMMMMMMMMMMM$5MMMMMMM0$6MMMMMMMMM0, $4_MMMMMMMMMMMMMMMMMMM$5MMMM$7M$5MMMW$6MMMMMMMMMM_ $4_MMMMMMMMMMMMMMMMMMMM$5MMMM$7M0$5MMMW$6MMMMMMMMMM_ $4~MMMMMMMMMMMMMMMMM$5MMMM$7M$5MMMW$6MMMMM00~ $4~MMMMMMMMMMMMM$5MMMWMMM$60MM$5MMM$6~ $4~MMMMMMMMMM$5MMMMM$60MMM~~ $4~MMMMMM$5MMM$60MM~ $4~MM$5M$6@~ $4M ================================================ FILE: src/logo/ascii/aster.txt ================================================ ...''... .;oOXWMWNXXXNMMN0d:. .oXMWOo;.. ..:oO; ;KMWx, co, 'KMNl dMMW. oMMx xMMMMk xMM: dMMMMMM; cMMl dMMMMMMMW NMK xMMMx::dXMx ,MMl xMMN' .o. cMM; dMMW' ;MMc oMMW, WMK dMMW, ccccccc. lMMl oMMM; ooooooo. OMMc ... xMMx ;XMN: ,. ================================================ FILE: src/logo/ascii/asteroidos.txt ================================================ $1 *** $1 ***** $1 ********** $1 *************** $1 *///****////****////. $2 (/////// /////// ///////( $2 /(((((//* //, //((((((. $2 ((((((((((( ((( (((((((( $2 *((((((((((((((((((((((( (((((((( $3 (((((#(((((((#((((( ((#((((( $3 (#(#(#####(#(#, ####(#(# $3 ######### ######## $3 /######## ######## $4 #######%####### $4 (#%%%%%%%# $4 %%%%% $4 %%% ================================================ FILE: src/logo/ascii/astos.txt ================================================ oQA#$%UMn H 9 G # 6 % ?#M#%KW3" // \\ // \\ // \\ // \\ n%@$DK&ML .0O3#@&M_ P # 8 W H U G # B N O @ C&&#%HNAR 'WS3QMHB" // \\ \\ // \\ \\ // \\ \\ // \\ \\ uURF$##Bv nKWB$%ABc aM@3R@D@b 8 M @ O # % % & G U @ @ & @ # % % # !HGN@MNCf t&S9#%HQr ?@G#6S@QP ================================================ FILE: src/logo/ascii/astra_linux.txt ================================================ AA AaaA Aa$2/\$1aA $1 Aa$2/$1aa$2\$1aA $1 Aa$2/$1aAAa$2\$1aA $1 aA$2/$1aaAAaa$2\$1Aa $1 aA$2/$1aaAAAAaa$2\$1Aa $1 aaaaaaAAAAa$2/$1aaAAAAAAaa$2\$1aAAAAaaaaa $1aAAa$2-----$1aaaaaAAAAAAAAAAaaaaa$2-----$1aAAa $1 aAA$2\ $1aAAAAAAAAAAAAAAAAAAAAAAa$2 /$1AAa $1 aAa$2\$1aAAA$2\$1AAAA$2\$1AAAA$2\$1AAA$2\$1AAa$2/$1aAa $1 aAa$2\$1aA$2\\$1AAA$2\\$1AAA$2\\$1AA$2\\/$1aAa $1 aAA$2\$1aA$2\\$1AAA$2\\$1AAA$2\\$1Aa$2/$1AAa $1 aA$2\$1aA$2\\$1AAA$2\\$1AAA$2\\/$1Aa $1 aA$2/$1AA$2\\\$1AA$2\\\$1AA$2\\\$1Aa $1 aA$2/\$1AAa$2\\\$1Aa$2\\\$1Aa$2\\\$1Aa $1 aA$2/\\$1AAa$2\\/\$1a$2\\\$1Aa$2\\$1Aa $1 aA$2/$1a$2\\\$1Aa$2\/$1AA$2\\\\\$1Aa$2\\$1Aa $1 aA$2/$1aA$2\\/$1aAa aAa$2\\\$1Aa$2\$1Aa $1 aA$2/\$1A$2\/$1Aa aA$2\\$1A$2\\$1Aa $1 A$2|/$1aaAa aAaa$2\|$1A $1 aAaa aaAa ================================================ FILE: src/logo/ascii/athenaos.txt ================================================ u. ..:Y .Y..1 ..::i::.. .br7S. .::::...::::. 5dDr: ..:::.. .:i::.. Y7: ..:::::.. ..::::. ...:i:i:i:.. $2... $1:.:..:i:::... ::i:. $2.USL Yr $1..i::. .:::. $2:K :u. $1.::. :... $2jB. .:vUi: 77 KB: $1..:: ...: $2iBR vP::5X5PgSKXvLjS. iBB $1.::. :i:: $2BBQr jgDIuj1UuJjXbbi 2BBP $1:::; 'i:: $2sQBg: i2S55121XSSU. YBQZ: $1.::: r::: $2r:JgBMu. Q :BqEqZEB: Q :qBBK7i: $1:i:i .rir. $2IBqPDgQBDr P rB :q 1QBRgEqgB $1.::i. :i:i $2:JsXPDDRQQPBL P qQXBQRDDPIYY $1rir: :iii $25qvLUDDMgMgBB5 :DBQZRgMDdJ7Ygi $1iir: iri: $2MBRgdZZggQgRBMIBQQRRggdZdMBBr $1riri irii $2IBQgdZbDDRRRgQgQQMZDdZdgBB. $1iiri i7rr $2.BBQZZbEEMQQDBQMEZdDDBBP $1.rir: :7ir. $27BBQMDMQSbB5ZBggQBBQ. $1ivr7. :r7r. $27BBBB:rBbQ.1BBBM. $1:r7vi ::rLrr. $2.b:rBQ7QB uU. $1:7vLi. .BB .iLYv: $1idL B1 $1:r77r. LBB :vrvvr rvu7r: ZB. ':7v177Lsvurr:' .d2 ''::r:'' :' ================================================ FILE: src/logo/ascii/athenaos_old.txt ================================================ . .. $1 :####: ####. $1 .################ $1 :################## $1.###################. $1######## ####### $1####### $2####$1 ##### $1:#######. #### $1 ######### $2#$1 ## # $1 ####### $2##$1 #### $1######## $2####$1 ####### $1######## $2#####$1 ######## $1######## $2#######$1 ####### $1 ####### $2########$1 ####### $1 ######## $2#########$1 ###### $1 ######## $2#########$1 ##### $1 ####### $2#########$1 #### $1 ####### $2#########$1 ## $1 ####### $2########$1 ## $1 ###### $2########$1 # $1 ### $2#######$1 $1 $2######$1 $1 $2####$1 $1 $2##$1 ================================================ FILE: src/logo/ascii/aurora.txt ================================================ +++++++++ +++++$4+$1++++++$4+$1++ ++++++$4+$1+++ +++++++++++ +++++++++++ $2+$1 +++++$4+$1+++++ ++++++$4+$1++++ $4+++$1 +++++++++++ ++++++++++$4 + +++++ ++ $2+++++$4+$2++++ +++$4+$1++++$4 + + + +++++++ + + $2+++++$4+$2++ ++++++$4 $2+$4 +++ +++++++++ +++ $2++++++++ +++$4+$1++$4 + +++++ +++++ + ++++++$4 $2+$4 +++++ +++++ +++++ ++$4+$2+++$4 + +++++ ++++++ ++++++++++ ++++++$4 +++++ +++++ + +++++++++++++++++++ ++++++$4 + +++++ + +++++++++++++++ +++$4++$2+$4 ++++++ +++++++++++++ ++++++$4 + ++++++ +++++++++++++ $2+++ $4++ ++++++$4 ++++++++++++++++ $2++$4+$2+ $4++ $1+ ++++++$4 ++++++++++++++ $3+ $2++++ ++++++$4 +++++++++++ + $4+ $3++++++ ++$4+$3+++$4 ++++++++++ +++ $3+++++++++++++ +++++ $4+++++++++ + $3+++++++++++++ +++ $4++++++++ $2++$4 + $3++++++++++++++ ++++ ++ $4++++++++ $3+ ++++++++++++++ +++++++ $4++++++ $2+ $3+++++++++++++ +++++++$2++ $4++++ $3+++++++++++++ ++++++$2+++ +++++++++++ ++++++$2+++ +++++ ++++++$2+++ ++++++$2++ +++++ ================================================ FILE: src/logo/ascii/axos.txt ================================================ ▂🬭𜴧⎻𜷗𜴗𜴦𜵎⎻𜴧🬭▂ ▁▂𜴧━𜵼𜵶𜶮▂🬭🬭𜴐🭗 🬁𜴜🬭🬭▂𜶮▁𜵁━𜴧▂▁ 𜷓𜵐🬂🬂🮂𜶮𜴸𜴪━𜴆🮂▔ ▔🮂𜴆━𜴩𜵳𜴸🮂🬂🬂𜶚🬿 🭄🬨▍ 🬜🮂▔ ▁▂▂🬭🬭▂▂▁ ▔🮂🬪 🮈🬕𜶿 🭄🭙▐▎𜵫 🭃██████████🭎 🭢𜶡🮇▌🭤𜶿 🭄🭙 ▐🭄🭙 🭋█████🭜𜴦█████🭀 🭤🭏▌ 🭤🭏 𜵫🭗 🮉🭛 🭃███🭝🭙 🭥🭒███🭎 🭦▋ 🭢𜶡 ▟🬮𜴧━🭷🭘 🭋████▄▂ ▂▄████🭀 🭣𜴇━𜴧🬯🭏 🭕𜴸𜴆╾🭺🬽 🭃██🭡 🭖██🭐 🭈𜵡╼𜴆🬡🭠 𜴤🬼 🮉🭀 🭋███΄ 🭤███🭀 🭋▊ 🭇𜵐 𜴠🬾 ▐𜴢🬾▁▁ 🭃██𜴍 𜶘██🭐 ▁▁🭉🭫▌ 🭉🭠 𜴠🬾🮈▎𜶚 ▔🮂🬂▀🬎🬎 🮅🬎▀🬂🮂▔ 𜵐🮇▌🭉𜴏 𜴢🭿▍ 𜴬▂▁ ▁▂🬜΄🮈🬲𜴏 🭥𜶡🬭🬭▂𜶭▁𜵁━𜴧▂▁ ▁▂𜴧━𜶷𜶭𜶮▂🬭🬭𜵫🭚 ▔⎺𜴆━𜴩▔𜶮🮂🬂🬂𜴜🬼 🭇𜴐🬂🬂🮂𜶮▔𜴨━𜴆⎺▔ ⠈🮂🭷𜴆╾𜶦𜷞🭄𜴬╼𜴆🬂🮂΄ ================================================ FILE: src/logo/ascii/azos.txt ================================================ $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 ((((( ================================================ FILE: src/logo/ascii/bedrock.txt ================================================ -------------------------------------- -------------------------------------- -------------------------------------- ---$2\\\\\\\\\\\\$1----------------------- ----$2\\\ \\\$1---------------------- -----$2\\\ \\\$1--------------------- ------$2\\\ \\\\\\\\\\\\\\\\\$1------ -------$2\\\ \\\$1----- --------$2\\\ \\\$1---- ---------$2\\\ ______ \\\$1--- ----------$2\\\ ///$1--- -----------$2\\\ ///$1---- ------------$2\\\ ///$1----- -------------$2\\\////////////////$1------ -------------------------------------- -------------------------------------- -------------------------------------- ================================================ FILE: src/logo/ascii/bedrock_small.txt ================================================ _________ | $2__ $1 | | $2\ \___ $1 | | $2 \ _ \$1 | | $2 \___/$1 | |_________| ================================================ FILE: src/logo/ascii/biglinux.txt ================================================ ... :OWMMMNd. :NMMMMMMMMWc okkl. kMMMMMW0xdOWMl : xMMMMMW. kMMMMNc lW. :x NMMMMMO ,MMMM0. 'l Xx "lkk" kMMMX .okx, $2.MX .cc;. .xXKx. KMMM: .OMMMMMl :MM' 'KMMMMWK: 0MMMMk xMMM. lWMMMMMMM' cMMN:;xMMMMk::MMO oMMMMX .XMM. .KMMMWOOMMMd 'MMMMMMMMN, NMMx OMMMMl .kM0OMMMMk. ;MMd xMMMMMMd .MMMW :NMMMd .ckKKx' KMc dWMNd. oMMMN lkNMX, oM. ;. ;MMMMx "MM:. cO $3 .X. oMMMMW. l. dMk:..;xWMMMMW, kMMMMMMMMMMX. :XMMMMMMK: ':MM:" Made in Brazil ================================================ FILE: src/logo/ascii/bitrig.txt ================================================ `hMMMMN+ -MMo-dMd` oMN- oMN` yMd /NM: .mMmyyhMMs :NMMMhsmMh +MNhNNoyMm- hMd.-hMNMN: mMmsssmMMMo .MMdyyhNMMMd oMN.`/dMddMN` yMm/hNm+./MM/ .dMMMmo.``.NMo :NMMMNmmmmmMMh /MN/-------oNN: hMd. .dMh sm/ /ms ================================================ FILE: src/logo/ascii/blackarch.txt ================================================ $3 00 11 ====$1 .$3//$1 `o$3//$1: `+o$3//$1o: `+oo$3//$1oo: -+oo$3//$1oo+: `/:-:+$3//$1ooo+: `/+++++$3//$1+++++: `/++++++$3//$1++++++: `/+++o$2ooo$3//$2ooo$1oooo/` $2 $1./$2ooosssso$3//$2osssssso$1+` $2 .oossssso-`$3//$1`/ossssss+` -osssssso. $3//$1 :ssssssso. :osssssss/ $3//$1 osssso+++. /ossssssss/ $3//$1 +ssssooo/- `/ossssso+/:- $3//$1 -:/+osssso+- `+sso+:-` $3//$1 `.-/+oso: `++:. $3//$1 `-/+/ .` $3/$1 `/ ================================================ FILE: src/logo/ascii/blackmesa.txt ================================================ .-;+$$XHHHHHHX$$+;- ,:X@@X%/;=----=:\%X@@X:, =$$@@%=. .=+H@X: -XMX: =XMX= /@@: =H@+ %@X. .$$@$$ +@X, $$@% /@@, .@@\ %@% +@$$ H@: :@H H@: :HHHHHHHHHHHHHHHHHHX, =@H %@% ;@M@@@@@@@@@@@@@@@@@H- +@$$ =@@, :@@@@@@@@@@@@@@@@@@@@@= .@@: =@X :@@@@@@@@@@@@@@@@@@@@@@:%@% $$@$$, ;@@@@@@@@@@@@@@@@@M@@@@@@$$ +@@HHHHHHH@@@@@@@@@@@@@@@@@@@@@@@ =X@@@@@@@@@@@@@@@@@@@@@@@@@@@X= :$$@@@@@@@@@@@@@@@@@@M@@@@$$: \$$@@@@@@@@@@@@@@@@@@X/- .-;+$$XXHHHHHX$$+;-. ================================================ FILE: src/logo/ascii/blackpanther.txt ================================================ $3 ........ .,»╔╗╗╬▄▄╫█▀▓▄▄╬╗╗g≈,. ,j╗╬╣▓▓███████▌;»╙▀▀▀▀█▄▄╗j, .≈╗╬▓██▀▀▀▀▀╠╙░░»»;:`$2``>$1▄ $3▐ ▓╫╗⌂, .j╬▓█▀▒░░░░░░░░░»»»;:```` ╙▀█▌╬░, ;╗▓█▄▄███████▀░░»»»»;```` ╓▄▄█▄▄φ ██▌Ñ>. .j╣█████▀▀░░░░░░░░»»╓▄▄¿``▄███████/▄████▓╬U. .j╣▓██▀ÜÑ╦╦░░░░░░▐█@▄████⌐▐███████████████▓╬H. «╫▓█▀░ÑÑ╩╦░░░░░░░░▀██████M"▀███████████████▓╫░ :]╣█▌ÑÑÑÑ▄▄██▀░░░░»»██████████████████████████Ñ~ »╫▓█╫ÑÑ▄███▀░░░░░»»▐██████████████████████████▌░ `j╣█▌Ñ╬████░░░░░░░»»▐████████████████████████▌▐█U` `/╫█▌▄███▌░░░░░░░»»»;▀██████████████▀████████w▐█░` ;╟█▌███▌░░░░░░░▄▄»»;:`▀▀████████▀Ü▄████████▌ ▐▌>` `]▓████░░░░░░░░██⌂;:````╓▄▄µp╓▄▄██████████▀ ,█M` "╠╣██▌░░░░░░░»██▌;```` ╙▀██████████████M █▀" "╟╣█░░░░░░░░»███⌂``` ▐▀████████▀░ █▌░` "╩█▄░░░░░░»»▀███ `` └└` ,█▀"` `░▀█▄░░░»»»»████@ .▄█Ü` `╙▀█▄@»»»;`▀███▌¿ ,▄▀Ñ"` `"╨▀█▄▄▄░`▐█████▄, ,▄▄▀▀░` `"╙╩▀▀▀▀████████▓▌▌▌▀▀▀╨"`` ``""░╚╨╝╝╝╝╨╨░""`` ================================================ FILE: src/logo/ascii/blag.txt ================================================ d ,MK: xMMMX: .NMMMMMX; lMMMMMMMM0clodkO0KXWW: KMMMMMMMMMMMMMMMMMMX' .;d0NMMMMMMMMMMMMMMMMMMK. .;dONMMMMMMMMMMMMMMMMMMMMMMx 'dKMMMMMMMMMMMMMMMMMMMMMMMMl .:xKWMMMMMMMMMMMMMMMMMMM0. .:xNMMMMMMMMMMMMMMMMMK. lMMMMMMMMMMMMMMMMMMK. ,MMMMMMMMWkOXWMMMMMM0 .NMMMMMNd. `':ldko OMMMK: oWk, ;: ================================================ FILE: src/logo/ascii/blankon.txt ================================================ $2 `./ohdNMMMMNmho+.` $1 .+oo:` $2 -smMMMMMMMMMMMMMMMMmy-` $1`yyyyy+ $2 `:dMMMMMMMMMMMMMMMMMMMMMMd/` $1`yyyyys $2 .hMMMMMMMNmhso/++symNMMMMMMMh- $1`yyyyys $2 -mMMMMMMms-` -omMMMMMMN-$1.yyyyys $2.mMMMMMMy. .yMMMMMMm:$1yyyyys $2sMMMMMMy `sMMMMMMh$1yyyyys $2NMMMMMN: .NMMMMMN$1yyyyys $2MMMMMMm. NMMMMMN$1yyyyys $2hMMMMMM+ /MMMMMMN$1yyyyys $2:NMMMMMN: :mMMMMMM+$1yyyyys $2 oMMMMMMNs- .sNMMMMMMs.$1yyyyys $2 +MMMMMMMNho:.` `.:ohNMMMMMMNo $1`yyyyys $2 -hMMMMMMMMNNNmmNNNMMMMMMMMh- $1`yyyyys $2 :yNMMMMMMMMMMMMMMMMMMNy:` $1`yyyyys $2 .:sdNMMMMMMMMMMNds/. $1`yyyyyo $2 `.:/++++/:.` $1:oys+. ================================================ FILE: src/logo/ascii/bluelight.txt ================================================ oMMNMMMMMMMMMMMMMMMMMMMMMM oMMMMMMMMMMMMMMMMMMMMMMMMM oMMMMMMMMMMMMMMMMMMMMMMMMM oMMMMMMMMMMMMMMMMMMMMMMMMM -+++++++++++++++++++++++mM$2 ```````````````````````..$1dM$2 ```````````````````````....$1dM$2 ```````````````````````......$1dM$2 ```````````````````````........$1dM$2 ```````````````````````..........$1dM$2 ```````````````````````............$1dM$2 .::::::::::::::::::::::-..............$1dM$2 `-+yyyyyyyyyyyyyyyyyyyo............$1+mMM$2 -+yyyyyyyyyyyyyyyyo..........$1+mMMMM$2 ./syyyyyyyyyyyyo........$1+mMMMMMM$2 ./oyyyyyyyyyo......$1+mMMMMMMMM$2 omdyyyyyyo....$1+mMMMMMMMMMM$2 $1oMMM$2mdhyyo..$1+mMMMMMMMMMMMM oNNNNNNm$2dso$1mMMMMMMMMMMMMMM ================================================ FILE: src/logo/ascii/bodhi.txt ================================================ $1| $2,,mmKKKKKKKKWm,, $1' $2,aKKP$1LL**********|L*$2TKp, $1t $2aKP$1L**``` ```**L$2*Kp IX$1EL$3L,wwww, $1``*||$2Kp ,#P$1L|$3KKKpPP@IPPTKmw, $1`*||$2K ,K$1LL*$3{KKKKKKPPb$KPhpKKPKp $1`||$2K #$1PL $3!KKKKKKPhKPPP$KKEhKKKKp $1`||$2K !H$1L* $31KKKKKKKphKbPKKKKKK$KKp $1`|I$2W $$$$1bL $3KKKKKKKKBQKhKbKKKKKKKK $1|I$2N $$$$1bL $3!KKKKKKKKKKNKKKKKKKPP` $1|I$2b TH$1L* $3TKKKKKK##KKKN@KKKK^ $1|I$2M K@$1L $3*KKKKKKKKKKKEKE5 $1||$2K `NL$1L $3`KKKKKKKKKK"```|L $1||$2#P `K@$1LL $3`"**"` $1'. :||$2#P Yp$1LL $1' |L$2$M` `Tp$1pLL, ,|||$2p'L "Kpp$1LL++,., ,,|||$$$$2#K* $1'. $2`"MKWpppppppp#KM"` $1`h, ================================================ FILE: src/logo/ascii/bonsai.txt ================================================ $2 ,####, $2#######, $2,#####, $2#####',# $2'###### $2''###'$3';,,,'$2###' $3 ,; '''' $3 ;;; $2,#####, $3 ;;;' ,,;$2;;### $3 ';;;;''$2'####' $3 ;;; $3 ,.;;';'',,, $3 ' ' $1 # # O ##, ,##,',##, ,## ,#, , # # # # #''# #,, # # # '#' '##' # # ,,# '##;, # ================================================ FILE: src/logo/ascii/bredos.txt ================================================ ████████████████ ██████░░░░░░████░░░░██ ████ ████░░░░████░░░░░░██░░░░░░██░░░░██ ██░░░░░░░░░░██░░░░░░██░░░░░░░░██░░░░██ ██░░░░░░░░░░██░░░░░░░░░░░░░░░░██░░░░░░██ ██▓▓░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░▓▓██ ██▓▓▓▓░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░▓▓▓▓██ ██▓▓▓▓▓▓▓▓░░░░░░░░░░░░░░░░░░░░▓▓▓▓▓▓▓▓██ ██████████████████████████ ================================================ FILE: src/logo/ascii/bsd.txt ================================================ $1 , , /( )` \ \___ / | /- _ `-/ ' ($2/\/ \ $1\ /\ $2/ / | ` $1\ $3O O $2) $1/ | $2`-^--'$1`< ' (_.) _ ) / `.___/` / `-----' / $4<----. __ / __ \ $4<----|====$1O)))$4==$1) \) /$4====| <----' $1`--' `.__,' \ | | \ / /\ $5______$1( (_ / \______/ $5,' ,-----' | `--{__________) ================================================ FILE: src/logo/ascii/bunsenlabs.txt ================================================ `++ -yMMs `yMMMMN` -NMMMMMMm. :MMMMMMMMMN- .NMMMMMMMMMMM/ yMMMMMMMMMMMMM/ `MMMMMMNMMMMMMMN. -MMMMN+ /mMMMMMMy -MMMm` `dMMMMMM `MMN. .NMMMMM. hMy yMMMMM` -Mo +MMMMN /o +MMMMs +MMMN` hMMM: `NMM/ +MN: mh. -/ ================================================ FILE: src/logo/ascii/cachyos.txt ================================================ $3.$1-------------------------: .$2+=$1========================. :$2++$1===$2++===$1===============- :$2++$1- :$2*++$1====$2+++++==$1===========- .==: -$2*+++$1=====$2+***++=$1=========: =$2*++++=$1=======------------: =$2*+++++=$1====- $3...$1 .$2+*+++++$1=-===: .$2=+++=$1: :$2++++$1=====-==: -***$2**$1+ :$2++=$1=======-=. .=+**+$3.$1 .$2+$1==========-. $3.$1 :$2+++++++$1====- $3.$1--==-$3.$1 :$2++$1==========. $3:$2+++++++$1$3: $1.-===========. =*****+*+ $1.-===========: .+*****+: $1-=======$2++++$1:::::::::::::::::::::::::-: $3.$1---: :======$2++++$1====$2+++******************=. $1:=====$2+++$1==========$2++++++++++++++*- $1.====$2++$1==============$2++++++++++*- $1.===$2+$1==================$2+++++++: $1.-=======================$2+++: $3.......................... ================================================ FILE: src/logo/ascii/cachyos_small.txt ================================================ /''''''''''''/ /''''''''''''/ /''''''/ /''''''/ \......\ \......\ \.............../ \............./ ================================================ FILE: src/logo/ascii/calculate.txt ================================================ ...... ,,+++++++,. .,,,....,,,$2+**+,,.$1 ............,$2++++,,,$1 ............... ......,,,........ .....+*#####+,,,*+. .....,*###############,..,,,,,,.. ......,*#################*..,,,,,..,,,.. .,,....*####################+***+,,,,...,++, .,,..,..*#####################*, ,+,.+*..*#######################. ,+,,+*+..,########################* .,++++++. ..+##**###################+ ..... ..+##***#################*. .,.*#*****##############*. ..,,*********#####****+. $2.,++*****+++$1*****************$2+++++,.$1 $2,++++++**+++++$1***********$2+++++++++,$1 $2.,,,,++++,.. .,,,,,.....,+++,.,,$1 ================================================ FILE: src/logo/ascii/calinixos.txt ================================================ ⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⣀⣠⠤⠔⠒⠒⠋⠉⠉⠉⠉⠓⠒⠒⠦⠤⣄⡀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀ ⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⣀⠤⠒⠉⣁⣠⣤⣶⣶⣿⣿⣿⣿⣿⣿⣿⣿⣶⣶⣤⣄⣈⠙⠲⢤⣀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀ ⠀⠀⠀⠀⠀⠀⠀⠀⠀⣀⠴⠋⢁⣤⣶⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣶⣤⡈⠑⢦⡀⠀⠀⠀⠀⠀⠀⠀⠀⠀ ⠀⠀⠀⠀⠀⠀⠀⣠⠞⢁⣠⣾⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣷⡄⠈⠢⡀⠀⠀⠀⠀⠀⠀⠀ ⠀⠀⠀⠀⠀⢀⠞⠁⣴⣿⣿⣿⣿⣿⣿⣿⣿⣿⠿⠛⠋⠉⠁⠀⠀⠀⠀⠈⠉⠙⠛⠿⣿⣿⣿⣿⣿⣿⠏⠀⠀⠀⠈⢢⡀⠀⠀⠀⠀⠀ ⠀⠀⠀⠀⡰⠃⣠⣾⣿⣿⣿⣿⣿⣿⡿⠛⠉⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠉⠻⢿⡿⠁⠀⠀⠀⠀⠀⠀⠙⣄⠀⠀⠀⠀ ⠀⠀⠀⡼⠁⣴⣿⣿⣿⣿⣿⣿⡿⠋⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠈⢆⠀⠀⠀ ⠀⠀⡼⠀⣼⣿⣿⣿⣿⣿⣿⠏⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠈⣆⠀⠀ ⠀⣰⠁⣸⣿⣿⣿⣿⣿⣿⠃⠀⠀⠀⠀⠀⠀⠉⠻⣿⣿⣿⣿⣿⣿⣷⣄⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠘⡄⠀ ⢀⡇⢠⣿⣿⣿⣿⣿⣿⠃⠀⠀⠀⠀⠀⠀⠀⠀⠀⠈⠛⢿⣿⣿⣿⣿⣿⣷⣦⡀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢳⠀ ⢸⠀⣸⣿⣿⣿⣿⣿⡟⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠙⢿⣿⣿⣿⣿⣿⣿⣦⣄⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠘⡄ ⣼⠀⣿⣿⣿⣿⣿⣿⠇⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠈⠻⣿⣿⣿⣿⣿⣿⣷⣤⡀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⡇ ⡇⠀⣿⣿⣿⣿⣿⣿⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠈⢛⣿⣿⣿⣿⣿⣿⣿⡦⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⡇ ⢻⠀⣿⣿⣿⣿⣿⣿⡆⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⣠⣶⣿⣿⣿⣿⣿⣿⡿⠋⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⡇ ⢸⡀⢹⣿⣿⣿⣿⣿⣧⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢀⣠⣾⣿⣿⣿⣿⣿⣿⠟⠁⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢰⠃ ⠀⣇⠘⣿⣿⣿⣿⣿⣿⡄⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢀⣴⣿⣿⣿⣿⣿⣿⡿⠋⠁⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⡼⠀ ⠀⠸⡄⢹⣿⣿⣿⣿⣿⣿⡄⠀⠀⠀⠀⠀⠀⠀⣠⣶⣿⣿⣿⣿⣿⣿⠟⠋⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢰⠃⠀ ⠀⠀⢳⡀⢻⣿⣿⣿⣿⣿⣿⣆⠀⠀⠀⠀⠀⠈⠉⠉⠉⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢠⠏⠀⠀ ⠀⠀⠀⠳⡀⠻⣿⣿⣿⣿⣿⣿⣷⣄⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⣠⣾⣷⣄⡀⠀⠀⠀⠀⢠⠏⠀⠀⠀ ⠀⠀⠀⠀⠙⣄⠙⢿⣿⣿⣿⣿⣿⣿⣷⣦⣀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⣀⣴⣾⣿⣿⣿⣿⣿⣦⡀⠀⡰⠃⠀⠀⠀⠀ ⠀⠀⠀⠀⠀⠈⠢⡈⠻⣿⣿⣿⣿⣿⣿⣿⣿⣷⣶⣤⣄⣀⡀⠀⠀⠀⠀⢀⣀⣠⣤⣶⣿⣿⣿⣿⣿⣿⣿⣿⣿⠟⣠⠞⠁⠀⠀⠀⠀⠀ ⠀⠀⠀⠀⠀⠀⠀⠈⠢⡈⠙⢿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⡿⠋⣡⠞⠁⠀⠀⠀⠀⠀⠀⠀ ⠀⠀⠀⠀⠀⠀⠀⠀⠀⠈⠓⢤⡈⠛⠿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⠿⠛⣁⠴⠊⠁⠀⠀⠀⠀⠀⠀⠀⠀⠀ ⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠈⠑⠢⢄⣉⠙⠛⠿⠿⣿⣿⣿⣿⣿⣿⣿⣿⠿⠿⠛⠋⣉⡤⠖⠋⠁⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀ ⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠈⠉⠓⠒⠢⠤⠤⠤⠤⠤⠤⠤⠤⠖⠒⠋⠉⠀ ================================================ FILE: src/logo/ascii/calinixos_small.txt ================================================ ⠀⠀⠀⠀⠀⠀⠀⠀⣀⠤⠐⣂⣈⣩⣭⣭⣍⣀⣐⠀⠄⡀⠀⠀⠀⠀⠀⠀⠀⠀ ⠀⠀⠀⠀⠀⡀⠔⣨⣴⣾⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣷⣦⣅⠢⡀⠀⠀⠀⠀⠀ ⠀⠀⠀⠠⢊⣴⣾⣿⣿⣿⣿⠿⠟⠛⠛⠛⠛⠻⠿⣿⣿⣿⣿⠃⠀⠠⡀⠀⠀⠀ ⠀⠀⡐⢡⣾⣿⣿⣿⠟⠉⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠉⠛⠁⠀⠀⠀⠈⢆⠀⠀ ⠀⡘⢰⣿⣿⣿⡟⠁⠀⠀⢀⣀⣀⣀⣀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢂⠀ ⢠⢠⣿⣿⣿⡟⠀⠀⠀⠀⠀⠙⠿⣿⣿⣷⣦⡀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠈⡀ ⡄⢸⣿⣿⣿⠁⠀⠀⠀⠀⠀⠀⠀⠈⠻⣿⣿⣿⣦⣄⠀⠀⠀⠀⠀⠀⠀⠀⠀⠁ ⡇⣿⣿⣿⣿⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠈⣹⣿⣿⣿⣷⠄⠀⠀⠀⠀⠀⠀⠀⠀ ⠃⢸⣿⣿⣿⡀⠀⠀⠀⠀⠀⠀⠀⠀⣠⣾⣿⣿⡿⠛⠁⠀⠀⠀⠀⠀⠀⠀⠀⡀ ⠘⡘⣿⣿⣿⣧⠀⠀⠀⠀⠀⢀⣴⣿⣿⣿⠿⠋⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢀⠁ ⠀⠡⠸⣿⣿⣿⣧⡀⠀⠀⠀⠉⠉⠉⠉⠁⠀⠀⠀⠀⠀⠀⢀⠀⠀⠀⠀⢀⠆⠀ ⠀⠀⠡⡘⢿⣿⣿⣿⣦⣀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⣀⣴⣿⣷⣦⡀⢀⠊⠀⠀ ⠀⠀⠀⠈⠊⡻⢿⣿⣿⣿⣿⣶⣤⣤⣤⣤⣤⣤⣶⣿⣿⣿⣿⡿⢟⠕⠁⠀⠀⠀ ⠀⠀⠀⠀⠀⠈⠢⢙⠻⢿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⡿⠟⡩⠐⠁⠀⠀⠀⠀⠀ ⠀⠀⠀⠀⠀⠀⠀⠀⠈⠐⠂⠭⠉⠙⣛⣛⠋⠉⠭⠐⠂⠁⠀⠀⠀⠀ ================================================ FILE: src/logo/ascii/carbs.txt ================================================ .......... ..,;:ccccccc:;'.. ..,clllc:;;;;;:cllc,. .,cllc,... ..';;'. .;lol;.. .. .,lol;. .coo:. .'lol,. .,lol,. .,lol,. 'col;. .:ooc'. .'col:. .'cllc'.. .''. ..:lolc,'.......',cll,. ..;cllllccccclllc;'. ...',;;;;;;,,... ..... ================================================ FILE: src/logo/ascii/cbl_mariner.txt ================================================ . :- . :==. .=: :===: -==: :-===: .====: :-====- -=====: -====== :=======: -======. .=========: -======: -==========. -======- -===========. :======- :===========. :=======. .-==========. :=======: -==========. :=======- :==========. :=======- .-========- :--------. :========- ..:::--=========- ..::---================-=- ================================================ FILE: src/logo/ascii/celos.txt ================================================ `-:/++++/:-` -/syyyyyyyyyyyyy+- :ssssyyyyyyyyyyyyyyyy/ .osy$2mmmmmmmmmmmmmmmNNNNNmmhy+ $1.sssshhhhhhhddddddddddddddds- $1`osssssssyyyyyyyyyyyyyyyyyyhy` $1:ssssssyyyyyyyyyyyyyyyyyyyyhh/ $2sMMMMMMMMMMMMMMMMMMMMMMMh$1yyyyyyhho :sssssssyyyyyyyyyyyyyyyyyyyhh/ `ssssssssyyyyyyyyyyyyyyyyyyhy. -sssssyddddddddddddddddddddy -ssss$2hmmmmmmmmmmmmmmmmmmmyssss- $1`/ssssyyyyyyyyyyyyyyyy+` $1`:osyyyyyyyyyyyyys/` $1`.:/+ooooo+:-` ================================================ FILE: src/logo/ascii/center.txt ================================================ . o, . d, . ';' ..d;.. .cl' .:; 'oldO,.oo. ..,:,xKXxoo;'. ,;;;;;ldxkONMMMXxkxc;;;;;. .....':oddXWMNOxlcl:...... .:dlxk0c;:. . :d:.,xcld,.,:. ;l, .l; ';' .o; l, ================================================ FILE: src/logo/ascii/centos.txt ================================================ .. .PLTJ. <><><><> $2KKSSV' 4KKK $1LJ$4 KKKL.'VSSKK $2KKV' 4KKKKK $1LJ$4 KKKKAL 'VKK $2V' ' 'VKKKK $1LJ$4 KKKKV' ' 'V $2.4MA.' 'VKK $1LJ$4 KKV' '.4Mb. $4. $2KKKKKA.' 'V $1LJ$4 V' '.4KKKKK $3. $4.4D $2KKKKKKKA.'' $1LJ$4 ''.4KKKKKKK $3FA. $4 '$4VD $3KKKKKKKK'.. $2LJ $1..'KKKKKKKK $3FV $4' $3VKKKKK'. .4 $2LJ $1K. .'KKKKKV $3' $3'VK'. .4KK $2LJ $1KKA. .'KV' $3A. . .4KKKK $2LJ $1KKKKA. . .4 $3KKA. 'KKKKK $2LJ $1KKKKK' .4KK $3KKSSA. VKKK $2LJ $1KKKV .4SSKK $2<><><><> $2'MKKM' $2'' ================================================ FILE: src/logo/ascii/centos_small.txt ================================================ $2____$1^$4____ $2|\ $1|$4 /| $2| \ $1|$4 / | $4<---- $3----> $3| / $2|$1 \ | $3|/__$2|$1__\| $2v ================================================ FILE: src/logo/ascii/cereus.txt ================================================ .. '::,..... .,;:llll;. $4 ... $2...... $1''''.$4 ':::;. $2 ..,::lll::;,..$4 ::::;. $2 ':llllllllllll:' $4 ;:::; $2 .;llllllllllllllll' $4 ;::;. $3 .... $2 .;lllllllllllllllll:. $4 ;::;. $3 .;::::, $2 ,lllllllllllllllllll. $4 . $3 .::::::.$2 .lllllllllllllllllll:. $3 .::::::. $2 ,lllllllllllllllllll:.$3 ...... $3 .;:::::'$2 .;lllllllllllllllllll,$3 .,::::::,. $3 .;::::;.$2.:lllllllllllllllllll.$3 .,::::::::;. $3 .,;::;$2;lllllllllllllllllll;$3 .:::::::::;. $3 ...$2,lllllllllllllllllll.$3 ':::::::::,. $2 .:lllllllllllllllll, $3 .;::::::::;. $2 .:llllllllllllllll:.$3 .;::::::::,. $2 ,llllllllllllllll'$3 .';:::::::;. $5 ... $2 .llllllllllllllll;$3,,,,;:::::::;,. $5.:l:. $2 ,llllllllllllll;$3,,:::::::;,'.. $5.;::, $2 .;lllllllllllll,$3 ''''''''''' $5 .:::. $2 ':lllllllllll:. $5 .;::' $2 .,:llllllllll, $5 .,::;. $2 .';lllllllll' $5 .,::;'.. $2 .';lllllll,. $5 ..,;::;,. $2 ...,;lll:. $5 ...''. $2 ..',;'. ================================================ FILE: src/logo/ascii/chakra.txt ================================================ _ _ _ "kkkkkkkk. ,kkkkkkkk., 'kkkkkkkkk, ,kkkkkkkkkkkk., 'kkkkkkkkk. ,kkkkkkkkkkkkkkkk,'kkkkkkkk, ,kkkkkkkkkkkkkkkkkkk'kkkkkkk. "''"''',;::,,"''kkk''kkkkk; __ ,kkkkkkkkkk, "k''kkkkk' ,kkkk ,kkkkkkk' ., ' .: 'kkkk',kkkkkk ,kkkkkkkk'.k' , ,kkkk;kkkkkkkkk ,kkkkkkkk';kk 'k "'k',kkkkkkkkkkkk .kkkkkkkkk.kkkk.'kkkkkkkkkkkkkkkkkk' ;kkkkkkkk''kkkkkk;'kkkkkkkkkkkkk'' 'kkkkkkk; 'kkkkkkkk.,""''"''"" ''kkkk; 'kkkkkkkkkk., ';' 'kkkkkkkkkkkk., ';kkkkkkkkkk' ';kkkkkk' "''" ================================================ FILE: src/logo/ascii/chaletos.txt ================================================ `.//+osso+/:`` `/sdNNmhyssssydmNNdo:` :hNmy+-` .-+hNNs- /mMh/` `+:` `+dMd: .hMd- -sNNMNo. /yyy /mMs` -NM+ `/dMd/--omNh::dMM `yMd` .NN+ .sNNs:/dMNy:/hNmo/s yMd` hMs `/hNd+-smMMMMMMd+:omNy- `dMo :NM. .omMy:/hNMMMMMMMMMMNy:/hMd+` :Md` /Md` `sm+.omMMMMMMMMMMMMMMMMd/-sm+ .MN: /Md` MMMMMMMMMMMMMMMMMMMN .MN: :NN. MMMMMMm....--NMMMMMN -Mm. `dMo MMMMMMd mMMMMMN hMs -MN: MMMMMMd mMMMMMN oMm` :NM: MMMMMMd mMMMMMN +Mm- -mMy. mmmmmmh dmmmmmh -hMh. oNNs- :yMm/ .+mMdo:` `:smMd/` -ohNNmhsoo++osshmNNh+. `./+syyhhyys+:`` ================================================ FILE: src/logo/ascii/chapeau.txt ================================================ .-/-. ////////. ////////$2y+$1//. ////////$2mMN$1/////. ////////$2mMN+$1////////. ////////////////////////. /////////+$2shhddhyo$1+////////. ////////$2ymMNmdhhdmNNdo$1///////. ///////+$2mMms$1////////$2hNMh$1///////. ///////$2NMm+$1//////////$2sMMh$1/////// //////$2oMMNmmmmmmmmmmmmMMm$1/////// //////$2+MMmssssssssssssss+$1/////// `//////$2yMMy$1//////////////////// `//////$2smMNhso++oydNm$1//////// `///////$2ohmNMMMNNdy+$1/////// `//////////$2++$1////////// `////////////////. -////////- ================================================ FILE: src/logo/ascii/chimera_linux.txt ================================================ $3ddddddddddddddc $1,cc: $3ddddddddddddddc $1,cc: $3ddddddddddddddd $1,cc: $3ddddddddddddl:' $1,cc: $3dddddddddl' $1..;cc: $3dddddddo. $1,:cccccc: $3ddddddl $1,ccc:''''' $3dddddo. $1;ccc. ............ .ccc. cccccccccccc $2...... $1.ccc. .ccc''''''''' $2OOOOOk. $1;ccc. .ccc; ...... $2OOOOOOd $1'ccc:,....,:ccc' $4coooooo $2OOOOOOOx. $1':cccccccc:' $4.looooooo $2OOOOOOOOOd, $1`'''` $4.coooooooo $2OOOOOOOOOOOOdc,. $4..,coooooooooooo $2OOOOOOOOOOOOOOOO' $4.oooooooooooooooo $2OOOOOOOOOOOOOOOO' $4.oooooooooooooooo $2OOOOOOOOOOOOOOOO' $4.oooooooooooooooo ================================================ FILE: src/logo/ascii/chonkysealos.txt ================================================ .-/-. .:-=++****++=-:. .:=+*##%%%%%%%%%%##*+=:. :=*#%%%%%%%%%%%%%%%%%%%%#*=: :=*#%%%%%%%%%%%%%%%%%%%%%%%%#*=. -+#%%%%%%%%%%%%%%%%%%%%%%%%%%%%#+- =+#%%%%@@@@@@@%%%%%%%@@@@@@@%%%%%#+= =+#@%%%%*+=-==*%%%%%%%#+====*%%%%%@#+= :+*%%%%@* +@%%%@# -@%%%%%*+: =+#%%%%%%#+====*###%%##*=--=+*%%%%%%%#+= +*%%%%%%%@@##%%%%*=::=#%%%##%@%%%%%%%%*+ +*%%%%%%%@**@%%%%%@==@%%%%%@+#%%%%%%%%*+ =+#%%%%%%@#*@%%%%%%**%%%%%@%+%%%%%%%%#+= :+*%%%%%%%@#*####**###*####*%@%%%%%%%*+: =+#@%%%%%%@%%%%%%%@@%%%%%%%%%%%%%%@#+= =+#%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%#+= -+#%%%%%%%%%%%%%%%%%%%%%%%%%%%%*+- .=*#%%%%%%%%%%%%%%%%%%%%%%%%#*=. :=*##%%%%%%%%%%%%%%%%%%##*=: .:=+*##%%%%%%%%%%##*+=:. .:-=++****++=-:. ================================================ FILE: src/logo/ascii/chrom.txt ================================================ $2 .,:loool:,. .,coooooooooooooc,. .,lllllllllllllllllllll,. ;ccccccccccccccccccccccccc; $1 '$2ccccccccccccccccccccccccccccc. $1 ,oo$2c::::::::okO$5000$30OOkkkkkkkkkkk: $1.ooool$2;;;;:x$5K0$4kxxxxxk$50X$3K0000000000. $1:oooool$2;,;O$5K$4ddddddddddd$5KX$3000000000d $1lllllool$2;l$5N$4dllllllllllld$5N$3K000000000 $1lllllllll$2o$5M$4dccccccccccco$5W$3K000000000 $1;cllllllllX$5X$4c:::::::::c$50X$3000000000d $1.ccccllllllO$5Nk$4c;,,,;cx$5KK$30000000000. $1 .cccccclllllxOO$5OOO$1Okx$3O0000000000; $1 .:ccccccccllllllllo$3O0000000OOO, $1 ,:ccccccccclllcd$30000OOOOOOl. $1 '::ccccccccc$3dOOOOOOOkx:. $1 ..,::cccc$3xOOOkkko;. $1 ..,:$3dOkxl:. ================================================ FILE: src/logo/ascii/cleanjaro.txt ================================================ ███████▌ ████████████████ ███████▌ ████████████████ ███████▌ ████████████████ ███████▌ ███████▌ ███████▌ ███████▌ ███████▌ █████████████████████████ █████████████████████████ █████████████████████████ ▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀ ================================================ FILE: src/logo/ascii/cleanjaro_small.txt ================================================ █████ ██████████ █████ ██████████ █████ █████ █████ ████████████████ ████████████████ ================================================ FILE: src/logo/ascii/clear_linux.txt ================================================ BBB BBBBBBBBB BBBBBBBBBBBBBBB BBBBBBBBBBBBBBBBBBBB BBBBBBBBBBB BBB BBBBBBBB$2YYYYY $1 BBBBBBBB$2YYYYYY $1 BBBBBBBB$2YYYYYYY $1 BBBBBBBBB$2YYYYY$3W $4 GG$1BBBBBBBY$2YYYY$3WWW $4 GGG$1BBBBBBB$2YY$3WWWWWWWW $4 GGGGGG$1BBBBBB$3WWWWWWWW $4 GGGGGGGG$1BBBB$3WWWWWWWW $4GGGGGGGGGGG$1BBB$3WWWWWWW $4GGGGGGGGGGGGG$1B$3WWWWWW $4GGGGGGGG$3WWWWWWWWWWW $4GG$3WWWWWWWWWWWWWWWW WWWWWWWWWWWWWWWW WWWWWWWWWW WWW ================================================ FILE: src/logo/ascii/clearos.txt ================================================ `.--::::::--.` .-:////////////////:-. `-////////////////////////-` -////////////////////////////- `//////////////-..-//////////////` ./////////////: ://///////////. `//////:..-////: :////-..-//////` ://////` -///:.``.:///-` ://///: `///////:. -////////-` `:///////` .//:--////:. -////-` `:////--://. ./: .////:. --` `:////- :/. `//-` .////:. `:////- `-//` :///-` .////:. `:////- `-///: `/////-` -///: :///- `-/////` `//////- `///: :///` .//////` `:////: `///: :///` -////:` .://: `///: :///` -//:. .:: `///: :///` -:. `///: :///` `... ...` ================================================ FILE: src/logo/ascii/clover.txt ================================================ `omo``omo` `oNMMMNNMMMNo` `oNMMMMMMMMMMMMNo` oNMMMMMMMMMMMMMMMMNo `sNMMMMMMMMMMMMMMNs` `omo` `sNMMMMMMMMMMNs` `omo` `oNMMMNo` `sNMMMMMMNs` `oNMMMNo` `oNMMMMMMMNo` `oNMMNs` `oNMMMMMMMNo` oNMMMMMMMMMMMNo` `sy` `oNMMMMMMMMMMMNo `sNMMMMMMMMMMMMNo.$2oNNs$1.oNMMMMMMMMMMMMNs` `oNMMMMMMMMMMMMNs.$2oNNs$1.oNMMMMMMMMMMMMNo` oNMMMMMMMMMMMNs` `sy` `oNMMMMMMMMMMMNo `oNMMMMMMMNs` `oNMMNo` `oNMMMMMMMNs` `oNMMMNs` `sNMMMMMMNs` `oNMMMNs` `oNs` `sNMMMMMMMMMMNs` `oNs` `sNMMMMMMMMMMMMMMNs` +NMMMMMMMMMMMMMMMMNo `oNMMMMMMMMMMMMNo` `oNMMMNNMMMNs` `omo``oNs` ================================================ FILE: src/logo/ascii/cobalt.txt ================================================ $1 /// $1 ,////////////// $1 /////////////////////////////// $1 ///////////////$5***********$1////// ////$5***********************$1///// /////$5***********************$1//// //////$5,,,,,,,,,,,,,,,,,,,,,,$1/// //////$5,,,,,,,,,,,,,,,,,,,,,,,,,$1///// /////$5,,,,,,,,,,,,,,,,,,,,,,,,,,,,$1///// $4 *****$3,,,,,,,,,,,,,,,,,,,,,,,,,,,,,$4***** ******$3,,,,,,,,,,,,,,,,,,,,,,,,,,,,$4***** *******$3,,,,,,,,,,,,,,,,,,,,,,,,,$4****** *******$3......................$4******* ******$3....$4*********************** **************************** ***** ================================================ FILE: src/logo/ascii/codex.txt ================================================ #: :+#@@@@%# @@#: .*@@@@@@@@@ @@@@#- .@@@@@@@@@@@ @@@@@@%- %@@@@@@@@@@@ @@@@@@@@%= :@@@@%%%%@@@@ @@@@@@@@@@%==*-: .:=# :*@@@@@@@@@@= :*@@@@@@@@= .*@@@@@@= .+@@@@= .+%@= += ================================================ FILE: src/logo/ascii/condres.txt ================================================ $1syyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy+$3.+. $1`oyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy+$3:++. $2/o$1+oyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy/$3oo++. $2/y+$1syyyyyyyyyyyyyyyyyyyyyyyyyyyyy$3+ooo++. $2/hy+$1oyyyhhhhhhhhhhhhhhyyyyyyyyy$3+oo+++++. $2/hhh+$1shhhhhdddddhhhhhhhyyyyyyy$3+oo++++++. $2/hhdd+$1oddddddddddddhhhhhyyyys$3+oo+++++++. $2/hhddd+$1odmmmdddddddhhhhyyyy$3+ooo++++++++. $2/hhdddmo$1odmmmdddddhhhhhyyy$3+oooo++++++++. $2/hdddmmms$1/dmdddddhhhhyyys$3+oooo+++++++++. $2/hddddmmmy$1/hdddhhhhyyyyo$3+oooo++++++++++: $2/hhdddmmmmy$1:yhhhhyyyyy+$3+oooo+++++++++++: $2/hhddddddddy$1-syyyyyys+$3ooooo++++++++++++: $2/hhhddddddddy$1-+yyyy+$3/ooooo+++++++++++++: $2/hhhhhdddddhhy$1./yo:$3+oooooo+++++++++++++/ $2/hhhhhhhhhhhhhy$1:-.$3+sooooo+++++++++++///: $2:sssssssssssso++$1$3`:/:--------.```````` ================================================ FILE: src/logo/ascii/cosmic.txt ================================================ .xMMMMMMMMMMMMMMMMMMMMMMx. JDMMMMMMMMMMMMMMMMMMMMMMMMOL IMMMY' 'YMMMI MMMM MMMM MMMM MMMM IMMMb dMMMI 'YMMMMMMMMb. .dMMMMMMMMK' 'OMMMMMMMP' 'YMMMMMMMP' $2.x76767$36767676$476767$5676x. $2'*76767$36767676$476767$5676*' ================================================ FILE: src/logo/ascii/crux.txt ================================================ $1odddd oddxkkkxxdoo ddcoddxxxdoool xdclodod olol xoc xdd olol xdc $2k00$1Okdlol xxd$2kOKKKOkd$1ldd xdco$2xOkdlo$1dldd ddc:cl$2lll$1oooodo odxxdd$3xkO000kx$1ooxdo oxddx$30NMMMMMMWW0o$1dkkxo oooxd$30WMMMMMMMMMW0o$1dxkx docldkXW$3MMMMMMMWWN$1Odolco xx$2dx$1kxxOKN$3WMMWN$10xdoxo::c $2xOkkO$10oo$3odOW$2WW$1XkdodOxc:l $2dkkkxkkk$3OKX$2NNNX0Oxx$1xc:cd $2odxxdx$3xllo$2dddooxx$1dc:ldo $2lodd$1dolccc$2ccox$1xoloo ================================================ FILE: src/logo/ascii/crux_small.txt ================================================ ___ ($3.· $1| ($2<> $1| / $3__ $1\ ( $3/ \ $1/| $2_$1/\ $3__)$1/$2_$1) $2\/$1-____$2\/ ================================================ FILE: src/logo/ascii/crystal.txt ================================================ mysssym mysssym mysssym mysssym mysssyd mysssyd N mysssyd mysym mysssyd dysssym mysssyd dysssym mysssyd dysssym mysssyd dysssym mysssyd dysssym mysssyd dysssym mysym dysssym N dysssym dysssym dysssym dysssym dysssym dysssym ================================================ FILE: src/logo/ascii/cucumber.txt ================================================ `.-://++++++//:-.` `:/+//$2::--------$1:://+/:` -++/:$2----..........----$1:/++- .++:$2---...........-......---$1:++. /+:$2---....-::/:/--//:::-....---$1:+/ `++:$2--.....:---::/--/::---:.....--$1:++` /+:$2--.....--.--::::-/::--.--.....--$1:+/ -o:$2--.......-:::://--/:::::-.......--$1:o- /+:$2--...-:-::---:::..:::---:--:-...--$1:+/ o/:$2-...-:.:.-/:::......::/:.--.:-...-$1:/o o/$2--...::-:/::/:-......-::::::-/-...-$1:/o /+:$2--..-/:/:::--:::..:::--::////-..--$1:+/ -o:$2--...----::/:::/--/:::::-----...--$1:o- /+:$2--....://:::.:/--/:.::://:....--$1:+/ `++:$2--...-:::.--.:..:.--.:/:-...--$1:++` /+:$2---....----:-..-:----....---$1:+/ .++:$2---..................---$1:++. -/+/:$2----..........----$1:/+/- `:/+//$2::--------:::$1/+/:` `.-://++++++//:-.` ================================================ FILE: src/logo/ascii/cuerdos.txt ================================================ ⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢀⣀⡀ ⠀⠀⠀⠀⠀⠀⠀⠀⢀⣴⣶⣾⣿⣿⣿⣶⣦⣄⡀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢠⣶⣿⣿⣿⡇ ⠀⠀⡄⢀⣤⣤⣤⣤⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣶⣤⣤⣤⣤⣤⣤⣄⠀⣿⣿⣿⣿⣿⡇ ⠀⠀⡇⣾⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣷⣿⣿⣿⣿⣿⠁ ⠀⠀⣧⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⠃ ⠀⠀⡿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⠟⠁ ⠀⠀⡇⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⡿⠟⠋⠁ ⠀⠀⠇⠘⠿⢿⣿⣿⣿⣿⣿⣿⣿⣿⣿⡿⠿⠿⠿⠿⠿⠛⠛⠋⠉ ⠀⠀⠀⠀⠀⠀⠀⠀⣿⣿⡇ ⠀⠀⠀⠀⠀⠀⠀⠀⠈⠉ ____________________________________ | Optimizado hasta el último pixel | ************************************ ================================================ FILE: src/logo/ascii/cutefishos.txt ================================================ ___ww___ _ _wwMMM@M^^^^MMMMww_ M0w_ _wMMM~~ ~~MMm_ ~MMy _ww0M~ ~MMy ~MMMM~ o "MM $3 jw0M~~MMMw_ _wMM' wMM~ ~~MMmw__ __w0M~ ~ ~~MM0MmwwwwwwwwwMMM~ ~~~~^^~~~ ================================================ FILE: src/logo/ascii/cuteos.txt ================================================ $31ua$2 $3MMM1ua$2 $1MM$2EE $3 MMMMM1uazE$2 $1MM $2EEEE $3M1MM1uazzEn $2EEEE MME EEEEE $3MMM uazEno $2EEEE EEEEE$1MMMMMMEno~; $2EE E$2 EE $1MMMMMMMM~;;E $2MMMMM M $2 E $1MMMMMMMMM $2 E E $2 $1MMMMMMMMMMM $1MMMMMMMMM $2EE $1 MM1MMMM $2EEE $1 MMMMM MMM M ================================================ FILE: src/logo/ascii/cyberos.txt ================================================ $3 !M$EEEEEEEEEEEP .MMMMM000000Nr. $3&MMMMMM$2MMMMMMMMMMMMM9 $3~MMM$1MMMM$2MMMMMMMMMMMMC $1" $3M$1MMMMMMM$2MMMMMMMMMMs $1iM$2MMM&&$1MMMMMMMM$2MMMMMMMMs $1BMMM$2MMMMM$1MMMMMMM$2MMMMMM$3" $19MMMMM$2MMMMMMM$1MMMM$2MMMM$3MMMf- $2sMMMMMMMM$1MM$2M$3MMMMMMMMM3_ $2+ffffffff$1P$3MMMMMMMMMMMM0 $2CMMMMMMMMMMM }MMMMMMMMM ~MMMMMMM "RMMMM .PMB ================================================ FILE: src/logo/ascii/cycledream.txt ================================================ .:ox00000kdc, ;; 'cdO. dxl,. 00 'oO' 0x; 00ko ,Oc x00Okxx lO' .x, .0c ;c .O00' :: '0: . O0000. o0. .0c ,,;;:clO00000Olc:;;,, .0l x0 ;00000000000000000c 00 00 0000000000000. 0O x0. ... 00000000000. ... '0: .0c ,00000000000o .o o0. O000; '0000 0k .0 O O' lO. ''..... .0o d000 Oo' ,dO O0 ;Oo;. .;oO, 00 xkdocc:ccodko ;k0k, ================================================ FILE: src/logo/ascii/dahlia.txt ================================================ .#. *%@@@%* .,,,,,(&@@@@@@@&/,,,,,. ,#@@@@@@@@@@@@@@@@@@@@@#. ,#@@@@@@@&#///#&@@@@@@@#. ,/%&@@@@@%/, .,(%@@@@@&#/. *#&@@@@@@#,. .*#@@@@@@&#, .&@@@@@@@@@( .(@@@@@@@@@&&. #@@@@@@@@@@( )@@@@@@@@@@@# °@@@@@@@@@@( .(@@@@@@@@@@@° *%@@@@@@@(. ,#@@@@@@@%* ,(&@@@@@@%*. ./%@@@@@@%(, ,#@@@@@@@&(***(&@@@@@@@#. ,#@@@@@@@@@@@@@@@@@@@@@#. ,*****#&@@@@@@@&(*****, ,/%@@@%/. ,#, ================================================ FILE: src/logo/ascii/darkos.txt ================================================ $3⠀⠀⠀⠀ ⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢠⠢⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀ $1⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢀⣶⠋⡆⢹⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀ $5⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢀⡆⢀⣤⢛⠛⣠⣿⠀⡏⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀ $6⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢀⣶⣿⠟⣡⠊⣠⣾⣿⠃⣠⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀ $2⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⣴⣯⣿⠀⠊⣤⣿⣿⣿⠃⣴⣧⣄⣀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀ $1⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢀⣤⣶⣿⣿⡟⣠⣶⣿⣿⣿⢋⣤⠿⠛⠉⢁⣭⣽⠋⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀ $4 ⠀⠀⠀⠀⠀⠀ ⠀⣠⠖⡭⢉⣿⣯⣿⣯⣿⣿⣿⣟⣧⠛⢉⣤⣶⣾⣿⣿⠋⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀ $5⠀⠀⠀⠀⠀⠀⠀⠀⣴⣫⠓⢱⣯⣿⢿⠋⠛⢛⠟⠯⠶⢟⣿⣯⣿⣿⣿⣿⣿⣿⣦⣄⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀ $2⠀⠀⠀⠀⠀⠀⢀⡮⢁⣴⣿⣿⣿⠖⣠⠐⠉⠀⠀⠀⠀⠀⠀⠀⠀⠀⠉⠉⠉⠛⠛⠛⢿⣶⣄⠀⠀⠀⠀⠀⠀⠀ $3⠀⠀⠀⠀⢀⣤⣷⣿⣿⠿⢛⣭⠒⠉⠀⠀⠀⣀⣀⣄⣤⣤⣴⣶⣶⣶⣿⣿⣿⣿⣿⠿⠋⠁⠀⠀⠀⠀⠀⠀⠀⠀ $1⠀⢀⣶⠏⠟⠝⠉⢀⣤⣿⣿⣶⣾⣿⣿⣿⣿⣿⣿⣟⢿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣧⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀ $6⢴⣯⣤⣶⣿⣿⣿⣿⣿⡿⣿⣯⠉⠉⠉⠉⠀⠀⠀⠈⣿⡀⣟⣿⣿⢿⣿⣿⣿⣿⣿⣦⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀ $5⠀⠀⠀⠉⠛⣿⣧⠀⣆⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⣿⠃⣿⣿⣯⣿⣦⡀⠀⠉⠻⣿⣦⠀⠀⠀⠀⠀⠀⠀⠀⠀ $3⠀⠀⠀⠀⠀⠀⠉⢿⣮⣦⠀⠀⠀⠀⠀⠀⠀⠀⠀⣼⣿⠀⣯⠉⠉⠛⢿⣿⣷⣄⠀⠈⢻⣆⠀⠀⠀⠀⠀⠀⠀⠀ $2⠀⠀⠀⠀⠀⠀⠀⠀⠀⠉⠢⠀⠀⠀⠀⠀⠀⠀⢀⢡⠃⣾⣿⣿⣦⠀⠀⠀⠙⢿⣿⣤⠀⠙⣄⠀⠀⠀⠀⠀⠀⠀ $6⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢀⢋⡟⢠⣿⣿⣿⠋⢿⣄⠀⠀⠀⠈⡄⠙⣶⣈⡄⠀⠀⠀⠀⠀⠀ $1⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠐⠚⢲⣿⠀⣾⣿⣿⠁⠀⠀⠉⢷⡀⠀⠀⣇⠀⠀⠈⠻⡀⠀⠀⠀⠀⠀ $4⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢢⣀⣿⡏⠀⣿⡿⠀⠀⠀⠀⠀⠀⠙⣦⠀⢧⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀ $3⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢸⠿⣧⣾⣿⠀⠀⠀⠀⠀⠀⠀⠀⠀⠙⣮⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀ $5⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠉⠙⠛⠀⠀⠀⠀⠀⠀ ================================================ FILE: src/logo/ascii/debian.txt ================================================ $2_,met$$$$$$$$$$gg. ,g$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$P. ,g$$$$P"" """Y$$$$.". ,$$$$P' `$$$$$$. ',$$$$P ,ggs. `$$$$b: `d$$$$' ,$P"' $1.$2 $$$$$$ $$$$P d$' $1,$2 $$$$P $$$$: $$$. $1-$2 ,d$$$$' $$$$; Y$b._ _,d$P' Y$$$$. $1`.$2`"Y$$$$$$$$P"' `$$$$b $1"-.__ $2`Y$$$$b `Y$$$$. `$$$$b. `Y$$$$b. `"Y$$b._ `"""" ================================================ FILE: src/logo/ascii/debian_small.txt ================================================ _____ / __ \ | / | | \___- -_ --_ ================================================ FILE: src/logo/ascii/deepin.txt ================================================ ............ .';;;;;. .,;,. .,;;;;;;;. ';;;;;;;. .;::::::::' .,::;;,''''',. ,'.:::::::: .;;'. '; ;' 'cccccc, ,' :: '.. .: ,, :ccccc. ;: .c, '' :. ,; .l. cllll' ., .lc :; .l' l. .c :lllc ;cl: .l' .ll. :' .l 'looc. . ,o: 'oo' c, .o. .:ool::coc' .ooo' o. :: ..... .;dddo ;c l:... .';lddddo. ,o lxxxxxdoolllodxxxxxxxxxc :l ,dxxxxxxxxxxxxxxxxxxl. 'o, ,dkkkkkkkkkkkkko;. .;o; .;okkkkkdl;. .,cl:. .,:cccccccc:,. ================================================ FILE: src/logo/ascii/desaos.txt ================================================ ███████████████████████ ███████████████████████ ███████████████████████ ███████████████████████ ████████ ███████ ████████ ███████ ████████ ███████ ████████ ███████ ████████ ███████ ████████ ███████ ████████ ███████ ██████████████████████████████ ██████████████████████████████ ████████████████████████ ████████████████████████ ████████████████████████ ================================================ FILE: src/logo/ascii/devuan.txt ================================================ ..,,;;;::;,.. `':ddd;:,. `'dPPd:,. `:b$$$$b`. 'P$$$$$d` .$$$$$$$$$` ;$$$$$$$$$P .:P$$$$$$$$$$$$` .,:b$$$$$$$$$$$$$;' .,:dP$$$$$$$$$$$$$$$$b:' .,:;db$$$$$$$$$$$$$$$$$$$$Pd'` ,db$$$$$$$$$$$$$$$$$$$$$$$$$$$$b:'` :$$$$$$$$$$$$$$$$$$$$$$$$b:'` `$$$$$$$$$bd:''` `'''` ================================================ FILE: src/logo/ascii/devuan_small.txt ================================================ ..:::. ..-==- .+#: =@@ :+%@#: .:=+#@@%*: #@@@#=: ================================================ FILE: src/logo/ascii/dietpi.txt ================================================ :=+******+- -+******+=: =#-::-::::-=#:-#=-::::-::-#= :%-::--==-::-%%-::-==--::-%: +#-:::::=+++$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@%@@@@@@@@+ #@@@@@@@@@@@@@@@@@@@@@@# -#@@@@@@@@@@@@@@@@@@#- -*%@@@@@@@@@@%*- .+%@@@@%+. ================================================ FILE: src/logo/ascii/dracos.txt ================================================ `-:/- -os: -os/` :sy+-` `/yyyy+. `+yyyyo- `/yyyys: `:osssoooo++- +yyyyyy/` ./yyyyyyo yo`:syyyy+. -oyyy+ +- :yyyyyo- `:sy: `. `/yyyyys: ./o/.` .oyyso+oo:` :+oo+//::::///:-.` `.` ================================================ FILE: src/logo/ascii/dragonfly.txt ================================================ $2,--, $1| $2,--, $2| `-, $1,^, $2,-' | $2 `, `-, $1(/ \) $2,-' ,' $2 `-, `-,$1/ \$2,-' ,-' $2 `------$1( )$2------' $2 ,----------$1( )$2----------, $2 | _,-$1( )$2-,_ | $2 `-,__,-' $1\ /$2 `-,__,-' $1 | | | | | | | | | | | | `|' ================================================ FILE: src/logo/ascii/dragonfly_old.txt ================================================ .-. $3 ()$1I$3() $1 "==.__:-:__.==" "==.__/~|~\__.==" "==._( Y )_.==" $2.-'~~""~=--...,__$1\/|\/$2__,...--=~""~~'-. ( ..=$1\=$1/$2=.. ) `'-. ,.-"`;$1/=\$2;"-.,_ .-'` `~"-=-~` .-~` $1|=|$2 `~-. `~-=-"~` .-~` /$1|=|$2\ `~-. .~` / $1|=|$2 \ `~. .-~` .' $1|=|$2 `. `~-. (` _,.-="` $1 |=|$2 `"=-.,_ `) `~"~"` $1 |=|$2 `"~"~` $1 /=\ \=/ ^ ================================================ FILE: src/logo/ascii/dragonfly_small.txt ================================================ $2 ,$1_$2, ('-_$1|$2_-') >--$1|$2--< (_-'$1|$2'-_) $1| | | ================================================ FILE: src/logo/ascii/drauger.txt ================================================ -``- `:+``+:` `/++``++/. .++/. ./++. :++/` `/++: `/++: :++/` ./+/- -/+/. -++/. ./++- :++:` `:++: `/++- -++/` ./++. ./+/. -++/` `/++- :++:` `:++: `/++- -++/` .:-.`..............................`.-:. `.-/++++++++++++++++++++++++++++++++/-.` ================================================ FILE: src/logo/ascii/droidian.txt ================================================ $2 _,met$$$$$$$$$$gg. ,g$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$P. ,$$$$P' `$$$$$$. ',$$$$P ,ggs. `$$$$b: `d$$$$' ,$$P"' $1.$2 $$$$$$ $$$$P d$$' $1,$2 $$$$P $$$$: $$$$. $1-$2 ,d$$$$' $$$$; Y$$b._ _,d$$P' Y$$$$. $1`.$2`"Y$$$$$$$$P"' $2 `$$$$b $1"-.__ $2 `Y$$$$ `Y$$$$. `$$$$b. `Y$$$$b. `"Y$$b._ ================================================ FILE: src/logo/ascii/elbrus.txt ================================================ ▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄ ██▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀██ ██ ██ ██ ███████ ███████ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ███████ ███████ ██ ██ ██ ██ ██▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄██ ██ ▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀██ ██ ██ ███████████████████████████ ================================================ FILE: src/logo/ascii/elementary.txt ================================================ eeeeeeeeeeeeeeeee eeeeeeeeeeeeeeeeeeeeeee eeeee eeeeeeeeeeee eeeee eeee eeeee eee eeee eeee eeee eee eeee eee eee eee eee eee eee eee eee ee eee eeee eeee ee eee eeeee eeeeee ee eee eeeee eeeee ee eee eeee eeeeee eeeee eee eee eeeeeeeeee eeeeee eee eeeeeeeeeeeeeeeeeeeeeeee eeeee eeeeeeee eeeeeeeeeeee eeee eeeee eeeee eeeeeee eeeeeee eeeeeeeeeeeeeeeee ================================================ FILE: src/logo/ascii/elementary_small.txt ================================================ _______ / ____ \ / | / /\ |__\ / / | \ /__/ / \_______/ ================================================ FILE: src/logo/ascii/elive.txt ================================================ *@$2,,&(%%%..%*. $1(@$2&%/##############((/$1*, $2@$1@&$2#########$1*..../$2########%$1*.. $2@$1&$2#%%%%%. $3,.$1,$2%%%%%%. /%$2(%%%%. $1($2%%%%#. /$1*$2%%##,. .,%%###, ,####. ,$1*$2#%$1#$3/,(/ $2/$1#$2###, ((###/ ,,##########$1($3/(# $2%####, %#((($1. .$1./$2(((((((((((((($1($2#/$3*.. $3*.$2((($1/ $2%#///$1. $3***$2.*///////////// $3#$2#////* $3***$2.*/////. $3($2(***** $3*** $2,*****.. ..$1*$2*****.. *$1%$2/****. .,,*******,$3,,../##($2%&$1&$2#******$1,$2. ,*$1,$2,,,,,,,,,,,,,,,,,,,$1,$2.. *//$1/,,$2,,,,,,,$1,..$2 ================================================ FILE: src/logo/ascii/emmabuntus.txt ================================================ _~~_ nmmmmmmm/$2/**\$1\ nmHhHMMMHh\$2\__/$1/ nm zot $2__$1 t*~~*n m b $2_+*´cc`*+_$1 p m m & $2/%cc,;;,cc%\$1 & m _~~_ & $2c__ +cc$1 & n /$2/**\$1\& $2cc;$1 & m \$2\__/$1/& $2c~~ +cc´$1 & n *~~* & $2\cc%*--*%cc/$1 & m m b $2`+.cccc.+´$1 p m nm zo o_~~_ nmHhHMMMHhH/$2/**\$1\ nmmmmmmmm\$2\__/$1/ *~~* ================================================ FILE: src/logo/ascii/emperoros.txt ================================================ !! !!!! llllll llllll IIIIIIIIII IIIIIIIIIIIIIIIIIIII ;;;;;;;;;;;;;;;;;;;;;;;; ;;;;;;;;;;;;;;;;;;;; :;;::;:;:: :::::: ,,,,,, ,,,, "" ================================================ FILE: src/logo/ascii/encryptos.txt ================================================ ******* *** **. ** ** ** ** ***************** ,,,,,,,,,,,,,,,,*** ,,,,,,, ,,,,,,, ,,,,,,, ,,,,,,, ,,,,,,, ,,,,,,, ,,,,,,, ,,,,,,, ,,,,,,,,,,,,,,,,,,, ,,,,,,,,,,,,. ================================================ FILE: src/logo/ascii/endeavouros.txt ================================================ $2./$1o$3. $2./$1sssso$3- $2`:$1osssssss+$3- $2`:+$1sssssssssso$3/. $2`-/o$1ssssssssssssso$3/. $2`-/+$1sssssssssssssssso$3+:` $2`-:/+$1sssssssssssssssssso$3+/. $2`.://o$1sssssssssssssssssssso$3++- $2.://+$1ssssssssssssssssssssssso$3++: $2.:///o$1ssssssssssssssssssssssssso$3++: $2`:////$1ssssssssssssssssssssssssssso$3+++. $2`-////+$1ssssssssssssssssssssssssssso$3++++- $2`..-+$1oosssssssssssssssssssssssso$3+++++/` $3./++++++++++++++++++++++++++++++/:. `:::::::::::::::::::::::::------`` ================================================ FILE: src/logo/ascii/endeavouros_small.txt ================================================ /$2o$3. $1 /$2sssso$3- $1 /$2ossssssso$3: $1 /$2ssssssssssso$3+ $1 /$2ssssssssssssssso$3+ $1//$2osssssssssssssso$3+- `+++++++++++++++-` ================================================ FILE: src/logo/ascii/endless.txt ================================================ `:+yhmNMMMMNmhy+:` -odMMNhso//////oshNMMdo- /dMMh+. .+hMMd/ /mMNo` `oNMm: `yMMo` `oMMy` `dMN- -NMd` hMN. .NMh /MM/ -os` /MM/ dMm `smNmmhs/- `:sNMd+ `` mMd MMy oMd--:+yMMMMMNo.:ohmMMMNy` yMM MMy -NNyyhmMNh+oNMMMMMy:. dMo yMM dMm `/++/-``/yNNh+/sdNMNddMm- mMd /MM/ `dNy: `-::- /MM/ hMN. .NMh `dMN- -NMd` `yMMo` `oMMy` /mMNo` `oNMm/ /dMMh+. .+hMMd/ -odMMNhso//////oshNMMdo- `:+yhmNMMMMNmhy+:` ================================================ FILE: src/logo/ascii/enso.txt ================================================ .:--==--:. :=*#############*+-. .+##################*##*: .*##########+==-==++*####*##- =########=: .-+**#***. *#######- ++*#**. +######+ -*+#** :######* .*+**= *######: --#*# ####### +++#. #######. ++=*. *######+ .-+*+ :#######- -:*+: =#######*. :.*+- +########*- :*=- =###########+=: =+=: .+#############. .-==: .=###########= ..:--:. .-+######+ ================================================ FILE: src/logo/ascii/eshanizedos.txt ================================================ .:-==++++++++++++++++++++++-. .:=+##############################+ .=*###################################- :+######################################: :+######################################*- .+#############*+--:::::::::::::::::::::. :*##########*=: -###########= :##########+. *#########= =*********+ .::::::::::::::::. **********. -*###################+: :********** =#######################= -*********+ +************************ :*********+ :***********************: .********** .=******************+- +*********- .**********. =**********: +**********=. =***********=: -*************+-. :+****************+++++++++++++++++++++==: :+**************************************+. -+************************************- .-+*********************************. :-==+*************************=. ================================================ FILE: src/logo/ascii/eurolinux.txt ================================================ __ -wwwWWWWWWWWWwww- -WWWWWWWWWWWWWWWWWWw- \WWWWWWWWWWWWWWWWWWW- _Ww `WWWWWWWWWWWWWWWWWWWw -W$2E$1Www -WWWWWWWWW- _WW$2U$1WWWW- _WWWWWWWW _WW$2R$1WWWWWWWWWWWWWWWWWWWWWWWWWWWWWW- wWW$2O$1WWWWWWWWWWWWWWWWWWWWWWWWWWWWWWW WWW$2L$1WWWWWWWWWWWWWWWWWWWWWWWWWWWWWWw WWW$2I$1WWWWWWWWWWWWWWWWWWWWWWWWWWWWww- wWW$2N$1WWWWw WW$2U$1WWWWWWw wW$2X$1WWWWWWWWww wWWWWWWWWWWWWWWWw wWWWWWWWWWWWWWWWw WWWWWWWWWWWWWw wWWWWWWWw ================================================ FILE: src/logo/ascii/evolutionos.txt ================================================ $2.':ldxxxxdo:,. .lXMMMMMMMMMMMMMMMMNo' dWMMMMMMMMMMMMMMMMMMMMMMWk .OMMMMWWWWWWWWWWWWWWWWWWWMMMMM0; kMMMMM$1XxxxxkkkkkkkkkkkkkkkK$2WMMMMMK. .kMMMMMM$1Xddd0KKKKKKKKKKKKKKKN$2MMMMMMMN. KMMMMMMM$1XdddX$2MMMMMMMMMMMMMMMMMMMMMMMMX. cMMMMMMMM$1XdddX$2MMMMMMMMMMMMMMMMMMMMMMMMMo KMMMMMMMM$1XdddX$2MMMMMMMMMMMMMMMMMMMMMMMMMN XMMMMMMMM$1XdddX$2WO$1kkkkkkkK$2WK$1OOOX$2MMMMMMMMMW XMMMMMMMM$1XdddX$2WO$1kkkkkkkK$2WK$1OOOX$2MMMMMMMMMW 0MMMMMMMM$1XdddX$2MWWWWWWWWWMWWWWWMMMMMMMMMN cMMMMMMMM$1XdddX$2MMMMMMMMMMMMMMMMMMMMMMMMMd .kMMMMMMM$1XdddX$2MMMMMMMMMMMMMMMMMMMMMMMM0. .kMMMMMM$1XxxxN$2W0$1OOOOOOOOOOOOOK$2MMMMMMMK. oMMMMM$1XxxxN$2W0$1OOOOOOOOOOOOOK$2WMMMMMk. '0MMMWNNNWMWWWWWWWWWWWWWWWMMMMX; cWMMMMMMMMMMMMMMMMMMMMMMWd :KWMMMMMMMMMMMMMMWXo. .cdO0KK00xl' .. ================================================ FILE: src/logo/ascii/evolutionos_old.txt ================================================ dddddddddddddddddddddddd .dddd''''''''''''''''''''''dddd. dd: dddddddddddddddddddd; dd: dd: ldl:'''''''''''''''' dd: dd: ldl: dd: dd: ldl: dd: dd: ldl: dd: dd: ldl: dd: dd: ldl: ddddddd; ddddd; dd: dd: ldl: ''''''' ''''' dd: dd: ldl: dd: dd: ldl: dd: dd: ldl: dd: dd: ldl: dd: dd: ldl: ddddddddddddddd; dd: dddd:.''' ''''''''''''''' dddd: dddddddddddddddddddddddddd;;' ''''''''''''''''''''''''' ================================================ FILE: src/logo/ascii/evolutionos_small.txt ================================================ $2,coddoc' 'cddddddddddc' 'ddd$1OWWXXXXXXK$2ddo. .dddd$1OMX$2ddddddddddd. odddd$1OMX$2k$100O$2k$1OO$2ddddo .dddd$1OMX$2kOOOxOkdddd. .ddd$1OWW$2X$1XXXXXK$2ddd' 'dddddddddddd' 'cddddd, ================================================ FILE: src/logo/ascii/eweos.txt ================================================ $2 #####%%% $2 ##%%$3////$2%%%%%$3/// $2 #%%%%$3////((((////$2% $1 *@@@@@@@$3/$5,,,$3/////$5,,,$2%$1@@@@@@@ .@@@@@@@@@@@$3////////$2%%%$1@@@@@@@@@@@@ @@@$4...$1@@@@@@$3////$2%%$3////$1@@@@@@@@@@@@@@@@ @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ @@@@@@@@@@@@@@@@@@@@@@@@@@@@@ @@@@@@@@@@@@@@@@@@@@@@@@@@@ @@@@@@@@@@@@@@@@@@@@@@@ @@@@@@ @@@@@@ @@@ @@@ ================================================ FILE: src/logo/ascii/exherbo.txt ================================================ $2 , OXo. NXdX0: .cok0KXNNXXK0ko:. KX '0XdKMMK;.xMMMk, .0MMMMMXx; ... 'NO..xWkMMx kMMM cMMMMMX,NMWOxOXd. cNMk NK .oXM. OMMMMO. 0MMNo kW. lMc o: ., .oKNk; ;NMMWlxW' ;Mc .. .,,' .0M$1g;$2WMN'dWMMMMMMO XX ,WMMMMW. cM$1cfli$2WMKlo. .kMk .Mo .WM$1GD$2MW. XM$1WO0$2MMk oMl ,M: ,XMMWx::,''oOK0x; NM. 'Ml ,kNKOxxxxxkkO0XXKOd:. oMk NK .0Nxc$3:::::::::::::::$2fkKNk, .MW ,Mo .NXc$3::$2qXWXb$3::::::::::$2oo$3::$2lNK. .MW ;Wo oMd$3:::$2oNMNP$3::::::::$2oWMMMx$3:$2c0M; lMO 'NO;W0c$3:::::::::::::::$2dMMMMO$3::$2lMk .WM' xWONXdc$3::::::::::::::$2oOOo$3::$2lXN. ,WMd 'KWWNXXK0Okxxo,$3:::::::$2,lkKNo xMMO :XMNxl,';:lodxkOO000Oxc. .oWMMo 'dXMMXkl;,. .,o0MMNo' ':d0XWMMMMWNNNNMMMNOl' ':okKXWNKkl' ================================================ FILE: src/logo/ascii/exodia_predator.txt ================================================ - : +: :+ ++. .++ +++ : . +++ +++= .+ + =+++ ++++- ++ += -++++ ++++++- -++ ++- -++++++ ++++++++: .+++ +++. :++++++++ ++++++++++: ++++ ++++ :++++++++++ +++++++++++==++++ ++++=++++++=+++++ +++++.:++++++++++ ++++++++++:.+++++ +++++. .+++++++++ +++++++++. .+++++ +++++: ++++++++ ++++++++ :+++++ ++++++- =+++++++ +++++++= -++++++ :+++++= =+++++++ +++++++= =+++++: :+++= =+++++++ +++++++= =+++: -+= =+++++++ +++++++= ++- : =++++++- -++++++= : =++++- -++++= =++= =++= =++ ++= =+. .+= =- -= : : ================================================ FILE: src/logo/ascii/fastfetch.txt ================================================ ╭───────────────────────╮ │ $2● $3● $4● $5FASTFETCH $1│ ├───────────────────────┤ │ │ │ $2 /\ $7►►►►►►► $1│ │ $2 /--\ $7►►►►►► $1│ │ $2/----\ $7►►►►► $1│ │ $6|xx| $7►►►► $1│ │ $6|xx| $7►►► $1│ │ $3^^^^ $1│ ╰───────────────────────╯ ================================================ FILE: src/logo/ascii/fedora.txt ================================================ .',;::::;,'. .';:cccccccccccc:;,. .;cccccccccccccccccccccc;. .:cccccccccccccccccccccccccc:. .;ccccccccccccc;$2.:dddl:.$1;ccccccc;. .:ccccccccccccc;$2OWMKOOXMWd$1;ccccccc:. .:ccccccccccccc;$2KMMc$1;cc;$2xMMc$1;ccccccc:. ,cccccccccccccc;$2MMM.$1;cc;$2;WW:$1;cccccccc, :cccccccccccccc;$2MMM.$1;cccccccccccccccc: :ccccccc;$2oxOOOo$1;$2MMM000k.$1;cccccccccccc: cccccc;$20MMKxdd:$1;$2MMMkddc.$1;cccccccccccc; ccccc;$2XMO'$1;cccc;$2MMM.$1;cccccccccccccccc' ccccc;$2MMo$1;ccccc;$2MMW.$1;ccccccccccccccc; ccccc;$20MNc.$1ccc$2.xMMd$1;ccccccccccccccc; cccccc;$2dNMWXXXWM0:$1;cccccccccccccc:, cccccccc;$2.:odl:.$1;cccccccccccccc:,. ccccccccccccccccccccccccccccc:'. :ccccccccccccccccccccccc:;,.. ':cccccccccccccccc::;,. ================================================ FILE: src/logo/ascii/fedora2_small.txt ================================================ __ / \ __ |_ / | \__/ ================================================ FILE: src/logo/ascii/fedora_coreos.txt ================================================ ..... .';:cccccccc:;'. ':ccccclc$3lllllllll$1cc:. .;cccccccc$3lllllllllllllll$1c, ;clllccccc$3llllllllllllllllll$1c, .cllclccccc$3lllll$2lll$3llllllllllll$1c: ccclclcccc$3cllll$2kWMMNKk$3llllllllll$1c: :ccclclcccc$3llll$2oWMMMMMMWO$3lllllllll$1c, .ccllllllccc$3clll$2OMMMMMMMMM0$3lllllllll$1c .lllllclcccc$3llll$2KMMMMMMMMMMo$3llllllll$1c. .lllllllcccc$3clll$2KMMMMMMMMN0$3lllllllll$1c. .cclllllcccc$3lllld$2xkkxxdo$3llllllllllc$1lc :cccllllllcccc$3lllccllllcclccc$1cccccc; .ccclllllllcccccccc$3lll$1ccccclccccccc .cllllllllllclcccclccclccllllcllc :cllllllllccclcllllllllllllcc; .cccccccccccccclcccccccccc:. .;cccclccccccllllllccc,. .';ccccclllccc:;.. ..... ================================================ FILE: src/logo/ascii/fedora_kinoite.txt ================================================ ,clll:.$2 .,::::::::::::' $1:ooooooo$2 .;:::::::::::::: $1looooooo$2 ,:::::::::::::::' $1looooooo$2 .:::::::::::::::: $1looooooo$2 ;:::::::::::::::. $1looooooo$2 .:::::::::::::::: $1looooool$2;;;;,:::::::::::::::: $1looool$2::, .:::::::::::::: $1looooc$2:: ;:: $1looooc$2::;. .::; $1loooool$2:::::::::::. $1looooooo$2. .::::::' $1looooooo$2 .::::::,;,.. $1looooooo$2 :::;' ';:;. $1looooooo$2 ::: ::: $1cooooooo$2 .::' '::. $1 .ooooc $2 ::, ,:: '''' ================================================ FILE: src/logo/ascii/fedora_old.txt ================================================ /:-------------:\ :-------------------:: :-----------$2/shhOHbmp$1---:\ /-----------$2omMMMNNNMMD$1 ---: :-----------$2sMMMMNMNMP$1. ---: :-----------$2:MMMdP$1------- ---\ ,------------$2:MMMd$1-------- ---: :------------$2:MMMd$1------- .---: :---- $2oNMMMMMMMMMNho$1 .----: :-- .$2+shhhMMMmhhy++$1 .------/ :- -------$2:MMMd$1--------------: :- --------$2/MMMd$1-------------; :- ------$2/hMMMy$1------------: :--$2 :dMNdhhdNMMNo$1------------; :---$2:sdNMMMMNds:$1------------: :------$2:://:$1-------------:: :---------------------:// ================================================ FILE: src/logo/ascii/fedora_sericea.txt ================================================ :oooo, .',' .';;;.;oooooooolooooo' coooooooooooooooooooooooolc' .':oooooooooooo$2ll$1ooooooooooooool .oooooooooooooooo$2ll$1oooooooooooo$2l$1ool ooooooooooooooooo$2ll$1ooooooooooo$2ll$1oo' oooo$2l$1oooooooooo$2lll$1ooooooooo$2lll$1oo .ooooo$2lll$1ooooo$2lll$1ooooooooo$2lll$1ool .ooooooo$2lll$1oo$2llll$1oooo$2lllll$1ooooo: 'oooooooo$2llllllllllll$1oooooooo' .c,.oo$2lllll$1oooooooo.$2 'll; 'll. lll lll ;ll, .l: ================================================ FILE: src/logo/ascii/fedora_silverblue.txt ================================================ .;ooooooooooooooooooooooooooo. ,dddddddddddddddddddddddddddddd'$3; $1 lddddddddddddddddddddddddddddd'$3;;; $1ddddd$2,XXX.$1ddddd$2,XXX.$1dddd'$2,XXX.$3;;;;; $1ddddd$2XX$1x$2XX$1ddddd$2XX$1x$2XX$1ddd'$2,XX$3x$2XX$3;;;;; $1ddddd$2'XXX'$1ddddd$2'XXX'$1dd'$2XXXXXX'$3;;;;; $1dddddd$2;X;$1ddddddd$2;X:$1d'$2XXX$3;;;;;;;;;;; $1dddddd$2;X;$1ddddddd$2;X:$2XXX$3;;;;;;;;;;;;; $1dddddd$2;X;$1dddddd'$2;XXX,,,,,,XXX.$3;;;;; $1dddddd$2;X;$1dddd'$2XXXX$2XXXXXXXXX$3x$2XX$3;;;;; $1dddddd$2;X;$1dd'$2XXX$3;;;;;;;;;;;$2XXX$3;;;;;; $1dddddd$2;X;$1'$2XXX$3;;;;;;;;;;;;;;;;;;;;;; $1dddddd$2;XXXXX,,,,,,,,,,,,,;XXX:$3;;;;; $1dddddd$2:XXXXXXXXXXXXXXXXXXXX$3x$2XX$3;;;;; $1ddddd'$3;;;;;;;;;;;;;;;;;;;$2'XXX'$3;;;;' $1ddd'$3;;;;;;;;;;;;;;;;;;;;;;;;;;;;; $1o'$3;;;;;;;;;;;;;;;;;;;;;;;;;;;;' ================================================ FILE: src/logo/ascii/fedora_small.txt ================================================ ,'''''. | ,. | | | '_' ,....| |.. .' ,_;| ..' | | | | | ',_,' | '. ,' ''''' ================================================ FILE: src/logo/ascii/femboyos.txt ================================================ MMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM MMMMWKkxkKWMMMMMMMMMMMMMMMMMMMMWKkxkKWMM MMMMXo. .;xKWMMMMMMMMMMMMMMMMMMXo. .oXMM MMWXx,..'..oXMMMMMMMMMMMMMMMMWKx, .lXMM MMNo. .cOc.,xKWMMMMMMMMMMMMWXx;.....cXMM MMXl..;kKl. .oXMMMMMMMMMMWKx;..,ok:.'o0W WKx,.cKWNk;..lXMMMMMMMMWKx;..,o0NXl. .oN No. .lXMMWKc.,dKWMMMMMMNo..;d0NWMNx,..lX Nk:,:kNMMMNk:,ckNMMMMMMNxcxXWMMMMMN0ockN MWNNNWMMMMMWNNNWMMMMMMMMWWWMMMMMMMMMWWWM MMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM MMMMMMMNXKXNWMMMMMMMMMMMWNKOKWMMMMMMMMMM MMMMMMWKdccxXMMMMMMMMMMW0o'.oXMMMMMMMMMM MMMMMMMNO:.'o0NKkkkkkOXXo. .lXMMMMMMMMMM MMMMMMMMNx,..;o;. .:o,..;kNMMMMMMMMMM MMMMMMMMMNO: ... .cKWMMMMMMMMMMM MMMMMMMMMMNx,. .;dk:. .;kNMMMMMMMMMMMM MMMMMMMMMMMN0ocxXWNkl:,:xXWMMMMMMMMMMMMM MMMMMMMMMMMMMWNWMMMWWNNNWMMMMMMMMMMMMMMM MMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM ================================================ FILE: src/logo/ascii/feren.txt ================================================ `----------` :+ooooooooo+. -o+oooooooooo+- ..`/+++++++++++/...````````````````` .++++++++++++++++++++++++++/////- ++++++++++++++++++++++++++++++++//:` -++++++++++++++++++++++++++++++/-` ++++++++++++++++++++++++++++:. -++++++++++++++++++++++++/. +++++++++++++++++++++/-` -++++++++++++++++++//-` .:+++++++++++++//////- .:++++++++//////////- `-++++++---:::://///. `.:///+++. ` `......... ================================================ FILE: src/logo/ascii/filotimo.txt ================================================ O ' lk: ;xX. .0K ' lodk ;lldK' .lld0O :okX; lloo0 ;lllld0: .lllllxKx :clllkKk llllo, ;llllloox .clllllloxK :cclllllx0N lllll, ;llllllll .cclllllllo' :cccclllllld cllll, ;cclllllc .ccccllllll' :cccccclllll cllll, ;cccllllc ccccccllll' ::cccccclllc ccccl, ;cccccllc :cccccccll' :::ccccccclc ccccc, ;cccccccc ::cccccccl' :::::ccccccc :cccc, ;:ccccccc ::::cccccc' :::::::ccccc :cccc, ;:::ccccc ::::::cccc' :;::::::ccc: :::cc, ;:::::ccc ;:::::::cc' :;;:::::::c: :::::, ;:::::::c ;;:::::::c' :;;;;::::::: :::::, ;;::::::c ;;;;::::::' :;;;;;:::::: ;::::, ,;;;::::c ,;;;;;::::' ';;;;;;:::: ';;::, ;;;;::: .;;;;;::' .;;;;;:: ;;;, ;;;;: .;;;:' ;;;: ,;, ,;: ';' ., ================================================ FILE: src/logo/ascii/finnix.txt ================================================ ,,:;;;;:,, ,;*%S########S%*;, ;?#################S?: :%######################?: +##########################; +############################; :#############.**,#############, *###########+ +###########+ ?########## $3Finnix$1 ##########* *###########, ,###########+ :#############%..%#############, *############################+ *##########################+ ;S######################%: ,+%##################%; :+?S##########S?+: ,:;++++;:, ================================================ FILE: src/logo/ascii/floflis.txt ================================================ ,▄▄▄▌▓▓███▓▓▌▄▄▄, ,▄▒▓███████████████████▓▄▄ ▄▓███████████████████████████▌ ▓███████████████████████████████ , ╙▓████████████████████████████▀ ▄ ╓█▓▄ ╙▀▓████████████████████▀▀` ,▄██▓ ╓█████▌▄, '▀▀▀▀▓▓▓▓▓▓▀▀Å╙` ▄▄▓█████▌ ██████████▓▌▄ , ▄▓███████████▄ ╢████████████▓ ║████▓▓███▌ ╣█████████████▓ ▓█████████████ ▐█████████▀ ▓██████████████ ▓█████████████ ▐█████████▄ ███████████████ ▀████████████▌ ║█████████▌ ▀█████████████▌ ████████████M ▓██████████ ▐█████████████⌐ ▀██████████▌ ▐███████████▌ ▀███████████▌ ╙▓█████▓ ▓██████████████▄ ▀███████▀ ╝▓██▀ ╓▓████████████████▓ ▀▓██▀ ,▄████████████████████▌, ╝▀████████████████████▓▀' `╙▀▀▓▓███████▓▀▀╩' ================================================ FILE: src/logo/ascii/freebsd.txt ================================================ ``` $2` $1` `.....---...$2....--.``` -/ $1+o .--` $2/y:` +. $1yo`:. $2:o `+- $1y/ $2-/` -o/ $1.- $2::/sy+:. $1/ $2`-- / $1`: $2:` $1`: $2:` $1/ $2/ $1.- $2-. $1-- $2-. $1`:` $2`:` .-- `--. .---.....----. ================================================ FILE: src/logo/ascii/freebsd_small.txt ================================================ $1/\,-'''''-,/\ \_) (_/ | | | | ; ; '-_____-' ================================================ FILE: src/logo/ascii/freemint.txt ================================================ ## ## ######### #### ## #### #### ## #### #### ## ## #### #### ## ## #### #### ## ## ## #### ###### ###### ## ## #### #### ################ #### ## #### ## #### ###### ## ## #### #### ## ## ## ## ## #### #### ## ## ## ================================================ FILE: src/logo/ascii/frugalware.txt ================================================ `++/::-.` /o+++++++++/::-.` `o+++++++++++++++o++/::-.` /+++++++++++++++++++++++oo++/:-.`` .o+ooooooooooooooooooosssssssso++oo++/:-` ++osoooooooooooosssssssssssssyyo+++++++o: -o+ssoooooooooooosssssssssssssyyo+++++++s` o++ssoooooo++++++++++++++sssyyyyo++++++o: :o++ssoooooo$2/-------------$1+syyyyyo+++++oo `o+++ssoooooo$2/-----$1+++++ooosyyyyyyo++++os: /o+++ssoooooo$2/-----$1ooooooosyyyyyyyo+oooss .o++++ssooooos$2/------------$1syyyyyyhsosssy- ++++++ssooooss$2/-----$1+++++ooyyhhhhhdssssso -s+++++syssssss$2/-----$1yyhhhhhhhhhhhddssssy. sooooooyhyyyyyh$2/-----$1hhhhhhhhhhhddddyssy+ :yooooooyhyyyhhhyyyyyyhhhhhhhhhhdddddyssy` yoooooooyhyyhhhhhhhhhhhhhhhhhhhddddddysy/ -ysooooooydhhhhhhhhhhhddddddddddddddddssy .-:/+osssyyyysyyyyyyyyyyyyyyyyyyyyyyssy: ``.-/+oosysssssssssssssssssssssss ``.:/+osyysssssssssssssh. `-:/+osyyssssyo .-:+++` ================================================ FILE: src/logo/ascii/funtoo.txt ================================================ .dKXXd . :XXl;:. .OXo .'OXO'' .''''''''''''''''''''':XNd..'oco.lco, xXXXXXX, cXXXNNNXXXXNNXXXXXXXXNNNNKOOK; d0O .k kXX xXo KNNN0 KNN. 'xXNo :c; 'cc. kXX xNo KNNN0 KNN. :xxxx. 'NNo kXX xNo loooc KNN. oNNNN. 'NNo kXX xN0:. KNN' oNNNX' ,XNk kXX xNNXNNNNNNNNXNNNNNNNNXNNOxXNX0Xl ... ......................... .;cc;. ================================================ FILE: src/logo/ascii/furreto.txt ================================================ .$2xOOko $1.$2odd, oX$1WW$2KOOOO. 'ON$1WW$20kkk. $1.$2k0XKOOOOOOcOON$1W$2NOOOOO. xOOOOOOOOOkkOOOOOOOOO; $1.$2O0OkkocxO000000kcdk0OO0OOkx k$1W$1M$2Xkkkkloxkkkx; :dxxxxddc... 'kO0OOOOOkc .cl:..kk0KK0Okc ;kOOO0000xd. dO00000Oo .xkO$1NMM$2XOOOO .dddxkOOOkddc.kKN$1WW$2N000000l.ddk0000OOOO. 'dd:;ddddd;.dK$1MMMW$2K00KKK0O::ddxkO00Oko .okxkOKK0kkOO00KKOxxlodddddddl .00OOkkkkkkkkOOO00OOOO0O; .dddl 'kkkkkxxkkkkkkkOOkxdxkxxddd. cddddddddxxkkkkkxddddddddddo 'ddddddodddddddddddddddddddc $1.$2ddddddodddddddddodddddddc .odddo. $1.$2kOOkkk; lkK$1WN$2kkkxc kkxkkkkkkx. ,,..xxx. ================================================ FILE: src/logo/ascii/galliumos.txt ================================================ sooooooooooooooooooooooooooooooooooooo+: yyooooooooooooooooooooooooooooooooo+/::: yyysoooooooooooooooooooooooooooo+/:::::: yyyyyoooooooooooooooooooooooo+/::::::::: yyyyyysoooooooooooooooooo++/:::::::::::: yyyyyyysoooooooooooooo++/::::::::::::::: yyyyyyyyysoooooo$2sydddys$1+/::::::::::::::: yyyyyyyyyysooo$2smMMMMMMMNd$1+:::::::::::::: yyyyyyyyyyyyo$2sMMMMMMMMMMMN$1/::::::::::::: yyyyyyyyyyyyy$2dMMMMMMMMMMMM$1o//::::::::::: yyyyyyyyyyyyy$2hMMMMMMMMMMMm$1--//:::::::::: yyyyyyyyyyyyyy$2hmMMMMMMMNy$1:..-://:::::::: yyyyyyyyyyyyyyy$2yyhhyys+:$1......://::::::: yyyyyyyyyyyyyyys+:--...........-///::::: yyyyyyyyyyyys+:--................://:::: yyyyyyyyyo+:-.....................-//::: yyyyyyo+:-..........................://: yyyo+:-..............................-// o/:-...................................: ================================================ FILE: src/logo/ascii/garuda.txt ================================================ .%;888:8898898: x;XxXB%89b8:b8%b88: .8Xxd 8X:. .8Xx; 8x:. .tt8x .d x88; .@8x8; .db: xx@; ,tSXX° .bbbbbbbbbbbbbbbbbbbB8x@; .SXxx bBBBBBBBBBBBBBBBBBBBbSBX8; ,888S pd! 8X88/ q 8X88/ GBB. x%88 d888@8@X@X@X88X@@XX@@X@8@X. dxXd dB8b8b8B8B08bB88b998888b88x. dxx8o .@@;. dx88 .t@x. d:SS@8ba89aa67a853Sxxad. .d988999889889899dd. ================================================ FILE: src/logo/ascii/garuda_dragon.txt ================================================ .:--=========--:.. .:-=+++++===-----=======-:. :=++++-:.. ..:-===-:. .+++=:. .-=---. . :-: :---: := . :---: .=-: :-. .---: :-===--::. .::::. ---: .::--====-===+=-----=-: ---: :: :-++++====--=-:=====--=--: .--- +**=:+++===++++++==- -===== -==-. ---: *****+==++==--::::::======== . ===-. :--- ****==++-::.:::::-.::--======-.-=== .--- ***+=+-::::--:. -==:-==--=======-::. . :--- =**++-::::== -+===-==: :::.:-=-==--- :--: .*+*-:::-+= ...::==== .:: .=-:--. .. -*++:::=+-: .:=- -- .: . ... =*++:.++-+: .:. =-.: :-: :: :. -*++-=+=-+=-=++===-. .====: .::::. :++++++-:::--.-:=== --. .=+++++- .= +.=:= .:: :=+++++:. .: : . :---- .-++++=-:.. .:--===-. .-=+++++===-----=======-:. ================================================ FILE: src/logo/ascii/garuda_small.txt ================================================ .----. .' , '. .' '-----| '. -----, '.____.' ================================================ FILE: src/logo/ascii/gentoo.txt ================================================ -/oyddmdhs+:. -o$2dNMMMMMMMMNNmhy+$1-` -y$2NMMMMMMMMMMMNNNmmdhy$1+- `o$2mMMMMMMMMMMMMNmdmmmmddhhy$1/` om$2MMMMMMMMMMMN$1hhyyyo$2hmdddhhhd$1o` .y$2dMMMMMMMMMMd$1hs++so/s$2mdddhhhhdm$1+` oy$2hdmNMMMMMMMN$1dyooy$2dmddddhhhhyhN$1d. :o$2yhhdNNMMMMMMMNNNmmdddhhhhhyym$1Mh .:$2+sydNMMMMMNNNmmmdddhhhhhhmM$1my /m$2MMMMMMNNNmmmdddhhhhhmMNh$1s: `o$2NMMMMMMMNNNmmmddddhhdmMNhs$1+` `s$2NMMMMMMMMNNNmmmdddddmNMmhs$1/. /N$2MMMMMMMMNNNNmmmdddmNMNdso$1:` +M$2MMMMMMNNNNNmmmmdmNMNdso$1/- yM$2MNNNNNNNmmmmmNNMmhs+/$1-` /h$2MMNNNNNNNNMNdhs++/$1-` `/$2ohdmmddhys+++/:$1.` `-//////:--. ================================================ FILE: src/logo/ascii/gentoo_small.txt ================================================ _-----_ ( \ \ 0 \ $2\ ) / _/ ( _- \____- ================================================ FILE: src/logo/ascii/ghostbsd.txt ================================================ ,gggggg. ,agg9* .g) .agg* ._.,gg* ,gga* (ggg*' ,ga* ,ga* ,ga' .ag* ,ga' .agga' 9g' .agg'g*,a 'gggg*',gga' .gg*' .gga* .gga* (ga* ================================================ FILE: src/logo/ascii/ghostfreak.txt ================================================ xSSSSSSSSSSSx: XSSSSSSSSSSSSSSSSSSSSX xSSSSSSSSSSSSSSSSSSSSSSSxSSX xSSSSSSSSSSSSSSSSSSSSSSSSSSSSXSS SSSSSSSSSSSSSSSSSSSSSSSSSSSSSSXXXS; +SSSSSSx++SSSSSSSSSSSSSSSSSSSSSSSSSXSX SSSSS:::::::SSSSSSSSSSSSSXSSSSSSSSSS+S+ XSSSX::::::::XxSSSSSSSSXSx;::xSSSSSSSSSS ;SSSS:::::::::XSSSSSSSSS+:::::::xSSSSSSSS; +SSSSx::::::::SSSSSSSSS;:::::::::XSSSSSSSX SSSSXSx:::::SSSSxXSSSS::::::::::XSSSSSSSX xSSSSSSSSSSSS:::::SSSX::::::::::XSSSSSSS; +SSSSSSSSSSSSSSX:SSSSS;:::::::SxSSSSSSS +SSSSSSSSSSSSSSSSSSSSSSSSSXSSxSSSSSS xxSSSSSSSSSSSSSSSSSSSSSSSSSSSSSx SSSSSSSSSSSSSSSSSSSSSSSSSSSX xXSSSSSSSSSSSSX SSS +SSSxSSSSS ;x xSSx ================================================ FILE: src/logo/ascii/glaucus.txt ================================================ ,, ,d88P ,d8P ,ad8888* ,888P d88888* ,,ad8888P* d d888P a88888P* ,ad8888888* .d8 d8888: d888888* ,d888888P* .888; 88888b d8888888b8888888P d8888J888888a88888888888888P* ,d 88888888888888888888888888P ,,d8* 888888888888888888888888888888888* *8888888888888888888888888888888* Y888888888P* `*``*888888888888* *^888^* *Y888P** ================================================ FILE: src/logo/ascii/gnewsense.txt ================================================ ..,,,,.. .oocchhhhhhhhhhccoo. .ochhlllllllc hhhhhh ollllllhhco. ochlllllllllll hhhllllllhhh lllllllllllhco .cllllllllllllll hlllllo +hllh llllllllllllllc. ollllllllllhco'' hlllllo +hllh ``ochllllllllllo hllllllllc' hllllllllllllh `cllllllllh ollllllh +llllllllllll+ hllllllo `cllllh. ohllllllho .hllllc' ochllc. ++++ .cllhco `+occooo+. .+ooocco+' `+oo++++ ++++oo+' ================================================ FILE: src/logo/ascii/gnome.txt ================================================ ,@@@@@@@@, @@@@@@ @@@@@@@@@@@@ ,@@. @@@@@@@ *@@@@@@@@@@@@ @@@@@% @@@@@@( @@@@@@@@@@@& @@@@@@ @@@@* @@@@@@@@@# @@@@* @@@@, *@@@@@% @@@@@. @@@@# @@@@@@@@@@@@@@@@ ,@@@@@@@@@@@@@@@@@@@@@@@, ,@@@@@@@@@@@@@@@@@@@@@@@@@@& .@@@@@@@@@@@@@@@@@@@@@@@@@@@@ @@@@@@@@@@@@@@@@@@@@@@@@@@@ @@@@@@@@@@@@@@@@@@@@@@@@( @@@@@@@@@@@@@@@@@@@@% @@@@@@@@@@@@@@@@ @@@@@@@@@@@@* @@@@@@@@/ &@@@@@@@@@@ @@@@@@@@@* @@@@@@@@@@@, @@@@@@@@@* ,@@@@@@@@@@@@@@@@@@@@& &@@@@@@@@@@@@@@ ... ================================================ FILE: src/logo/ascii/gnu.txt ================================================ _-`````-, ,- '- . .' .- - | | - -. `. /.' / `. \ :/ : _... ..._ `` : :: : /._ .`:'_.._\. || : :: `._ ./ ,` : \ . _.'' . `:. / | -. \-. \\_ / \:._ _/ .' .@) \@) ` `\ ,.' _/,--' .- .\,-.`--`. ,'/'' (( \ ` ) /'/' \ `-' ( '/'' `._,-----' ''/' .,---' ''/' ;: ''/'' ''/ ''/''/'' '/'/' `; ================================================ FILE: src/logo/ascii/gobolinux.txt ================================================ _____ _ / ____| | | | | __ ___ | |__ ___ | | |_ |/ _ \| '_ \ / _ \ | |__| | (_) | |_) | (_) | \_____|\___/|_.__/ \___/ ================================================ FILE: src/logo/ascii/goldendoglinux.txt ================================================ .:^~!!^. .:: ^7777!!!777!77Y5? :??!!~~~77!~~~~~!7Y. :J7~~!?!~!~~~~~~~!?? ??~~~~7J7~~~~~~~7?!. J7~~~~~!J7~~~~!J7. !J~~~~!!?Y~~~~?7 ~?7777?Y7~~~!Y^ !J???7!~~~~!J! ~?7~~~~~~~~~~~7J .~?7~~~~~~~~~~~~~7J .~?7~~~~~~~~~~~~~~~7J ^??~~~~~~~~~~~~~~~~!Y^ !J!~~~~~~!7777!!~~~~7J !J!~~~~~!!!!!!77??~~~7J :Y!~~~~~~~~~~~~~!!J?~~7J :. !J!~~~~~~~~~~~~~!!JJ~~7Y. JJ!!Y!~~~~~~~~~~~~!!JY!~~!J~ .....~YJ?J!~~~~~~~~~~~~!JYJJJ7~!J^^:::.... ================================================ FILE: src/logo/ascii/grapheneos.txt ================================================ B? G~ G~& G!^:^?# &^.:::.J &PG& #G5JJ7~^~?JY5B& #PG B5JJPGJ77YG5JYP# && &B5JYPGJ7?YG5JYP# &Y..::.:P& &?..::.:G #!::::? B~::::J B~J# B!?# !P 75 !P 75 !5 7Y &Y~:^!P &J~:^!P P..::.:B Y..::.:# #PYJJ~^^!JJYP# &B5YJ?~^^!JJYG# &YYG# && #PYJ5G5??JGGYJ5G& && #PYP B^.::..7& J::::^G #Y^G& B~ G! # ================================================ FILE: src/logo/ascii/grombyang.txt ================================================ eeeeeeeeeeee eeeeeeeeeeeeeeeee eeeeeeeeeeeeeeeeeeeeeee eeeee $2.o+ $1eeee eeee $2`ooo/ $1eeee eeee $2`+oooo: $1eeee eee $2`+oooooo: $1eee eee $2-+oooooo+: $1eee ee $2`/:oooooooo+: $1ee ee $2`/+ +++ +: $1ee ee $2+o+\ $1ee eee $2+o+\ $1eee eee $2// \ooo/ \\ $1eee eee $2//++++oooo++++\\ $1eee eeee $2::::++oooo+::::: $1eeee eeeee $3Grombyang OS $1 eeee eeeeeeeeeeeeeeeeeeeeeee eeeeeeeeeeeeeeeee ================================================ FILE: src/logo/ascii/guix.txt ================================================ .. `. `--..```..` `..```..--` .-:///-:::. `-:::///:-. ````.:::` `:::.```` -//:` -::- ://: -::- `///- .:::` -+++-:::. :+/:::- `-....` ================================================ FILE: src/logo/ascii/guix_small.txt ================================================ |.__ __.| |__ \ / __| \ \ / / \ \ / / \ \ / / \ \/ / \__/ ================================================ FILE: src/logo/ascii/gxde.txt ================================================ ################ ######################## ##########-- --########## #######* ****** *####### ###### --*######--- ###### ##### --- *#### ##### ##### *-- #*#*#* * #### ##### #####* ##- ##*-#* ### ### *##### ##### ###* -##*#* *# *# *### ##### ##### ###* -*##*-*## *#* ### ##### ##### #### - --#### - ##* ### ##### ##### *####* * - ###- ##* ##### #####* #######- *###- ### *##### #####* ***#####****--- ### *##### ###### ----------- - ### ###### ###### ----- ### ###### ####### ####### ##########*----*########## ###################### ############## ================================================ FILE: src/logo/ascii/haiku.txt ================================================ MMMM MMMM MMMM MMMM MMMM MMMM MMMM MMMM MMMM$2 .ciO| /YMMMMM*" $1 MMMM$2 .cOMMMMM|/MMMMM/` , ,iMM|/MMMMMMMMMMMMMMM* `*.__,-cMMMMMMMMMMMMMMMMM/`$1.MMM MM$2MMMMMMM/`:MMM/ $1MMMM MMMM MMMM MMMM MMMM """" """" ================================================ FILE: src/logo/ascii/haiku2.txt ================================================ $2 :dc' 'l:;'$1,$2'ck. .;dc:. co $1..$2k. .;; ':o. co $1..$2k. ol $1.$20. co $1..$2k. oc $1..$20. co $1..$2k. oc $1..$20. .Ol,. co $1...''$2Oc;kkodxOdddOoc,. ';lxxlxOdxkxk0kd$1oooll$2dl$1ccc:$2clxd; ..$1oOolllllccccccc:::::$2od; cx:ooc$1:::::::;$2cooolcX. cd$1.$2''cloxdoollc' $1...$20. cd$1......$2k;$1.$2xl$1.... .$20. .::c$1;..$2cx;$1.$2xo$1..... .$20. '::c'$1...$2do$1..... .$2K, cd,.$1....:$2O,$1 ':clod:'$1 $1 ================================================ FILE: src/logo/ascii/haiku_small.txt ================================================ ,^, / \ *--_ ; ; _--* \ '" "' / '. .' .-'" "'-. '-.__. .__.-' |_| ================================================ FILE: src/logo/ascii/hamonikr.txt ================================================ cO0Ox. .ldddddddo. .lddddddddddo 'lddddddddddddc ,oddddddddddddd; 'ldddddddddddddo. .oddddddddddddddc. ,dddddddddddddddo. ,ccoooooooocoddooo: ,cooooooooooooooooop $3 c000x. $1.cooooooooooooooopcllll$3 .cddddddo. $1coooooooooooooop' .qlll.$3 .ddoooooooo; $1cooooooooooc; $3'qlllp. .ddoooooooooo; $1.cooooooc; $3'lllbc...coooooooooooo; $1 .cooc' $3.llllcoooooooooooooo. .coooooooooooooop: .coooooooooooooop' .cooooooooooooop. .cooooooooooooop. .coooooooooooop. .cooooooooooop. .cooooooooop. .cooooop' ================================================ FILE: src/logo/ascii/hardclanz.txt ================================================ $1 ........::::.... ::################::.. :########################:. :######**###################: :###$2&&&&^$1############ $2&$1#######: :#$2&&&&&$1.:##############:$2^&o$1`:###: :#$2&&&&$1.:#################:.$2&&&$1`###: :##$2&^$1:######################:$2^&&$1::##: :#############################:$2&$1:##:: :##########$2@@$1###########$2@@$1#####:.###: :#########$2@@$3o$2@@$1#########$2@@$3o$2@@$1########: :#######:$2@@$3o$50$3o$2@@@@$1###$2@@@@$3o$50$3o$2@@$1######: : :######:$2@@@$3o$2@@@@@@$1V$2@@@@@@$3o$2@@@$1######: :#####:$2@@@@@@@@@@@@@@@@@@@$1:####; :####:.$2@@@@@@@@@@@@@@@@$1:#####: `:####:.$2@@@@@@@@@@@@@@$1:#####: ``:##:.$2@@@@@@@@@@@@$1^## # : : ## $2\@@@;@@@/ $1:: # : $2 VVV ================================================ FILE: src/logo/ascii/harmonyos.txt ================================================ ....-----.... .-+##############+-.. .+######################+.. .########+- -++#######-. .+######- -######+. .######. .######.. .+####+ +#####.. .-####+. +####+. .+####+ -####+.. ..+####+ -####+... $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... ================================================ FILE: src/logo/ascii/hash.txt ================================================ + ###### + ### ###### ### ##### ###### ##### ###### ###### ###### ####### '"###### '"######## ####### ###### ######## ####### ###### ######## ###### '"###### '"###### ##### ###### ##### ### ###### ### ~ ###### ~ ================================================ FILE: src/logo/ascii/hce.txt ================================================ ti jGGGGj tGGGGGGt ,LGGLGGGGGGGG, jGGGGjGGGGGGGGGj GGGGGGGGGGGGGGGGGGGG iGGGGGGt tLLLLLLi ,GGGGGi iGGGGG, ,jGGGG LGGGG, ,LjGGj LLLL, ,LLGG. iiii. ,LLj ,LLj jLL, ,LLLL. GGLL, ,LLLLt GGLL, ,LGGGG LGGGj, ,GGGGGi iGGGGG, iGGGGGGj tGGGGGG, GGGGGGGGGGGGGGGGGGGG jGGGGGGGGGjGGGGj ,LGGGGGGGGGGG, tGGGGGGt jGGGGj it ================================================ FILE: src/logo/ascii/heliumos.txt ================================================ ,,╥╥╥╦╦╦╥╥,, ,╓╦Ñ╨^`_,,,,,,. `"╨╩Nw ,╥Ñ^`,╥╦╫╫╫╫╫╫╫╫╫╫╫╫ÑN≥,`╙Ñ╦_ ,j╩_,╦Ñ╙╙╩╫╫╫╫╫╫╫╫╫╫╫╫╫Ñ╨╨╩N,`╨N, j╩_╓╫╫Ñ ]N ]╫╫╫╫╫╫╫╫╫╫╫╫_]N ]╫Ñw_╩N ,╫^ ]╫╫╫Ñ ╫╫ ]╫╫╫╫╫╫╫╫╫╫╫╫ ╫╫ ]╫╫╫N_╙╫ _╫`,╫╫╫╫╫Ñ ╫╫ ]╫╫╫╫╫╫╫╫╫╫╫╫ ╫╫ ]╫╫╫╫╫ `╫ ÑH ╫╫╫╫╫╫Ñ ╫╫ ]╫╫╫╫╫╫╫╫╫╫╫╫ ╫╫ ]╫╫╫╫╫╫_]Ñ j╫ ]╫╫╫╫╫╫Ñ ╫╫ ]╫╫╫╫╫╫╫╫╫╫╫╫ ╫╫ ]╫╫╫╫╫╫H ╫H j╫ ╟╫╫╫╫╫╫Ñ ╫╫,,,,,,,,,,,,,,,╫╫ ]╫╫╫╫╫╫╫ ╠N j╫ ]╫╫╫╫╫╫Ñ ╫╫```````````````╫╫ ]╫╫╫╫╫╫╫ 1╡ ╫_j╫╫╫╫╫╫Ñ ╫╫ ]╫╫╫╫╫╫╫╫╫╫╫╫ ╫╫ ]╫╫╫╫╫╫N ╫H ╟H_╫╫╫╫╫╫Ñ ╫╫ ]╫╫╫╫╫╫╫╫╫╫╫╫ ╫╫ ]╫╫╫╫╫╫ jÑ _╫╕ ╫╫╫╫╫Ñ ╫╫ ]╫╫╫╫╫╫╫╫╫╫╫╫ ╫╫ ]╫╫╫╫╫ ,╫ ╫╥ ╩╫╫╫Ñ ╫╫ ]╫╫╫╫╫╫╫╫╫╫╫╫ ╫╫ ]╫╫╫Ñ ╓Ñ _╚N_`╫╫Ñ ╩╩ ]╫╫╫╫╫╫╫╫╫╫╫╫ ╩╩ 1╫Ñ`,jÑ_ `╩N,`╨N╦╦Ñ╫╫╫╫╫╫╫╫╫╫╫╫╫N╦╥]╩`,╦╩` _╙Ñ╦,`"╩Ñ╫╫╫╫╫╫╫╫╫╫╫Ñ╩^`,╥Ñ^_ _"╨N╦w,_ `````_ ,╓╦N╩^ __`"^╙╙╨╙╙^^`__ ================================================ FILE: src/logo/ascii/huayra.txt ================================================ $2 ` . . ` `` - . . `.` -` `. - `` .` ..`-`-` + - / .` ``` .--.+--`+:- :/.` .-``.` -+/so::h:.d-`./:`.` :hNhyMomy:os-...-. ```` .dhsshNmNhoo+:-``.``` $1`ohy:-$2NMds+::-.`` ````$1.hNN+`$2mMNho/:-....```` ````` `../dmNhoo+/:..`` ```` .dh++o/:....` .+s/` `/s-.-.:.`` ```` ::` `::`..` .` `.. `` ================================================ FILE: src/logo/ascii/hybrid.txt ================================================ $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######## ================================================ FILE: src/logo/ascii/hydroos.txt ================================================ _ _ _ ____ _____ | | | | | | / __ \ / ____| | |__| |_ _ __| |_ __ ___ | | | | (___ | __ | | | |/ _` | '__/ _ \| | | |\___ \ | | | | |_| | (_| | | | (_) | |__| |____) | |_| |_|\__, |\__,_|_| \___/ \____/|_____/ __/ | |___/ ================================================ FILE: src/logo/ascii/hyperbola.txt ================================================ WW KX W WO0W NX0O NOO0NW WNXK0OOKW W0OOOOOOOOOOOOKN N0OOOOOOO0KXW WNXXXNW NXK00000KN WNK0OOOOOOOOOO0W NK0OOOOOOOOOOOOOO0W X0OOOOOOO00KK00OOOOOK X0OOOO0KNWW WX0OO0W X0OO0XNW KOOW N00KNW KOW NKXN W0W WW W ================================================ FILE: src/logo/ascii/hyperbola_small.txt ================================================ |`__.`/ \____/ .--. / \ / ___ \ / .` `.\ /.` `.\ ================================================ FILE: src/logo/ascii/hypros.txt ================================================ ___ ,adZZEEEE#&$2>=x. $1,zAP*~'$4_,-$2'~*VM$2N&x. $1,%&P^`$4<$3,.$4<<$2,-===--.N>x $1.%M7$4//$3,%^$2,x<3#$13EEbo$2<&>&b $1&#/$4/$3.<^$4/$2x<>^$4-.`$1`+&WW$2<&N&; $1/#/$4//$34$4//$2/W/ $4^+.`$1`###$2NM\ $1##'$4|$3.l$4|$2,&/ $4`.',$1I#I$2HI# $1#I$4||$3`I$4|$2(#( $3)`'$1)##$2H~^ $1@\$4|||$3\$4\$2`X\ $3///$1,##%V$3'/ $4\\\\\\$3Y,$2*@b, $3.-+//$1/&#%#/$3,' $4`\\\\$2,.$4\$3<$2`*$3^`x<$1,z<#&#x"$3,' $3`x<<$2`Xx,$3`<_`$1+{##&@P^$4'>$3' $3`<_<<$2^<\-.$3`*`>$1<^'$4,-' $3`<_=-$2^\Xx$1XX\.$3+<. $3`^<_-$2^BWWmm$2 $1$BBBB .kBBBBB"$2 ,mp. $1%BBBB {BBBBBF$2 '%BBBBmm $1$BBBR,$BBBBB`$2 `TBBB"` $1$BBBBBBBBBF$2 kBF $1%BBBRRBBBBB.$2 ,mmWBBF $1kBBBB `%BBBBm$2 T%BBBB $1%BBBB %BBBBBn$2 `"T%m $1$BBBB "%BBBBm$2 TBm $1"*""" $2,m.$1"F""`$2 {BBBBm,. ,zmBBBm. !%BBBP"T%BBBBBB""R%BBB `` %BBBF `` !BBP ================================================ FILE: src/logo/ascii/kdeneon.txt ================================================ `..---+/---..` `---.`` `` `.---.` .--.` `` `-:-. `:/: `.----//----.` :/- .:. `---` `--.` .:` .:` `--` .:- `:. `/ `:. `.-::-.` -:` `/` /. /. `:++++++++:` .: .: `/ .: `+++++++++++/ /` `+` /+` -- .++++++++++++` :. .+: `/ .: `+++++++++++/ /` `+` /` /. `:++++++++:` .: .: ./ `:. `.:::-.` -:` `/` .:` `--` .:- `:. .:. `---` `--.` .:` `:/: `.----//----.` :/- .-:.` `` `-:-. `---.`` `` `.---.` `..---+/---..` ================================================ FILE: src/logo/ascii/kernelos.txt ================================================ .''''....''''. .''... ...''. ''.. ..'' '.. ..'' .'. .,,'.. .',,,' ..'. .'. .,,,,' .,,,,,' .'. .'. .,,,,' .,,,,,. .'. '.. .,,,,'',,,,;. .'' .'. .,,,,,,,;;. ..' .'. .,,,,;;;;;$2, $1..'. '.. .,;;;,$2';;;;;, $1..' .'. .;;$2;;' .;;:::: $1.,. '.. $2.;;;;' .::::::. $1.,' '.. $2.;;;;' .::::::. $1.,$2'$1 .'.. $2.',. $1',.. $2.',' $1.,,... $2..',,. ..,,''........',,,. ...... ================================================ FILE: src/logo/ascii/kibojoe.txt ================================================ $3 ./+oooooo+/. -/+ooooo+/:.` $1`$3yyyo$2+++/++$3osss$1. $1+NMN$3yssssssssssss$1. $1.dMMMMN$3sssssssssssy$1Ns` +MMMMMMMm$3sssssssssssh$1MNo` `hMMMMMNNNMd$3sssssssssssd$1MMN/ .$3syyyssssssy$1NNmmmmd$3sssss$1hMMMMd: -NMmh$3yssssssssyhhhhyssyh$1mMMMMMMMy` -NMMMMMNN$3mdhyyyyyyyhdm$1NMMMMMMMMMMMN+ `NMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMd. ods+/:-----://+oyydmNMMMMMMMMMMMMMMMMMN- ` .-:+osyhhdmmNNNmdo ================================================ FILE: src/logo/ascii/kiss.txt ================================================ $3 ___ ($2.· $3| ($1<> $3| / $2__$3 \ ( $1/ \ $3/| $1_$3/\ $2__)$3/$1_$3) $1\/$3-____$1\/$2 ================================================ FILE: src/logo/ascii/kiss2.txt ================================================ ██████ ██████ ██$2██████$1████$2██████$1██ ██$3████$2████████████████$1██ ██$2██$3████$2██████████████████$1██ ██$2██$3██$2████$3████████████$2████████$1██ ██$2██████$3████████████████$2██████$1██ ██$2████████████████████████████$1██ ██$2████████████████████████$1██ ██$2██████$3████████$2██████$1██ ████$2████████████$1████ ████████████ ================================================ FILE: src/logo/ascii/kogaion.txt ================================================ ;; ,; ;;; ,;; ,;;;; ;;;; ,;;;;;;;; ;;;; ;;;;;;;;;;; ;;;;; ,;;;;;;;;;;;; ';;;;;, ;;;;;;;;;;;;;;, ';;;;;;; ;;;;;;;;;;;;;;;;;, ';;;;; ; ';;;;;;;;;;;;;;;;;;, ;;; ;;;, ';;;;;;;;;;;;;;;;;;;,;; ;;;;;, ';;;;;;;;;;;;;;;;;;, ;;;;;;;;, ';;;;;;;;;;;;;;;;, ;;;;;;;;;;;;, ';;;;;;;;;;;;;; ';;;;;;;;;;;;; ';;;;;;;;;;;;; ';;;;;;;;;;;;;, ';;;;;;;;;;; ';;;;;;;;;;;;; ;;;;;;;;;; ';;;;;;;;;;;; ;;;;;;;; ';;;;;;;; ;;;;;; ';;;;; ;;;; ';;; ;; ================================================ FILE: src/logo/ascii/korora.txt ================================================ $2 ____________ _add55555555554$1: _w?'$1``````````'$2)k$1: _Z'$1`$2 ]k$1: m($1`$2 )k$1: _.ss$1`$2m[$1`$2, ]e$1: .uY"^`$1`$2Xc$1`$2?Ss. d($1` jF'$1`$2 `@. $1`$2Sc .jr$1` jr$1`$2 `?n_ $1`$2$; _a2"$1` .m$1:$2 `~M$1`$21k$1`$25?!`$1` :#$1:$2 $1`$2)e$1``` :m$1:$2 ,#'$1` :#$1:$2 .s2'$1` :m,________.aa7^$1` :#baaaaaaas!J'$1` ``````````` ================================================ FILE: src/logo/ascii/krassos.txt ================================================ $2**@@@@@@@@@@@* $2,@@@@%$1((((((((((((($2%@@@@, $2#@@&$1((((((((((((((((((((((($2&@@% $2@@&$1((((((((((((((((((((((((((((($2@@@ $2@@&$1((((((((((((((((((((((((((((((((($2&@@ $2.@@$1((((((((((((((((((((((((((((((((((((($2@@. $2@@$1((((((((((((((((((((((((((((((((((((((($2@@ $2@@#$1((((((((((((((((((((((((((((($2%@@@@@@@#$1(#$2@@ $2.@@$1(((((((((((((((($2#%@@@@@@@@@&%#$1(((($2%@&$1(((($2@@. $2.@@$1(((((((/($2&@@@@@@%$1(/(((((((((((((($2@@/$1((((($2@@. $2.@@$1(///////////////////////////////$2@$1(///////$2@@ $2%@#$1/////////////////////////////($2#$1////////$2%@% $2 @@$1(///////////////////////////$2%$1/////////($2@@ $2@@#$1***********************************$2%@@ $2*@@$1********************************$2/@@/ $2,@@#$1***************************$2%@@* $2@@@&$1********************$2/@@@@ $2&@@@@&$1(//***//($2&@@@@& $2**@@@@@@@@@@@* ================================================ FILE: src/logo/ascii/kslinux.txt ================================================ K K U U RRRR ooo K K U U R R o o KKK U U RRRR o o K K U U R R o o K K UUU R R ooo $2 SSS AAA W W AAA S A A W W A A SSS AAAAA W W W AAAAA S A A WW WW A A SSS A A W W A A ================================================ FILE: src/logo/ascii/kubuntu.txt ================================================ $1 `.:/ossyyyysso/:. .:oyyyyyyyyyyyyyyyyyyo:` -oyyyyyyyo$2dMMy$1yyyyyyysyyyyo- -syyyyyyyyyy$2dMMy$1oyyyy$2dmMMy$1yyyys- oyyys$2dMy$1syyyy$2dMMMMMMMMMMMMMy$1yyyyyyo `oyyyy$2dMMMMy$1syysoooooo$2dMMMMy$1yyyyyyyyo` oyyyyyy$2dMMMMy$1yyyyyyyyyyys$2dMMy$1sssssyyyo -yyyyyyyy$2dMy$1syyyyyyyyyyyyyys$2dMMMMMy$1syyy- oyyyysoo$2dMy$1yyyyyyyyyyyyyyyyyy$2dMMMMy$1syyyo yyys$2dMMMMMy$1yyyyyyyyyyyyyyyyyysosyyyyyyyy yyys$2dMMMMMy$1yyyyyyyyyyyyyyyyyyyyyyyyyyyyy oyyyyysos$2dy$1yyyyyyyyyyyyyyyyyy$2dMMMMy$1syyyo -yyyyyyyy$2dMy$1syyyyyyyyyyyyyys$2dMMMMMy$1syyy- oyyyyyy$2dMMMy$1syyyyyyyyyyys$2dMMy$1oyyyoyyyo `oyyyy$2dMMMy$1syyyoooooo$2dMMMMy$1oyyyyyyyyo oyyysyyoyyyys$2dMMMMMMMMMMMy$1yyyyyyyo -syyyyyyyyy$2dMMMy$1syyy$2dMMMy$1syyyys- -oyyyyyyy$2dMMy$1yyyyyysosyyyyo- ./oyyyyyyyyyyyyyyyyyyo/. `.:/oosyyyysso/:.` ================================================ FILE: src/logo/ascii/kylin.txt ================================================ $1++ $2* *** $2******* $2*******$1++ $2********$1+$3=== $2******$1#$2*$1+$3====== $2*******$1#$2***$1++$3=== $2*********$1+$2**$3=== $1##$2*********$1##$3== $3====$1+ $4%$1##$2****$1++$3==$1+$2****$1# $2***$1+$3=$2** $1#$2****$1++$3=====$1++$2****$1## $2******** $1####$2*$1+$3=======$1+$2******$3===$1- $2***$1#$2*$1##$2**** $1#$4%$1#$2******$1+$2**$1+$3==$1++$2**$3=======$1+$2*$1+$2*$1#$4% $4%$1#$2****$1+$2******$3=====$1+$3====$1+++$3==$2** $1#$2*********$1##$2***$1###$2********$1##$4% $2***$1##$4%$1#$4%%%%$2*****$1#$2*$1###$2*****$1#$4% $2*$1#$4%%%$1#$4%%%%%$2**** $4%%$2*******$1#$4% $1#$4%%%%%% $4%%%$1##$2***$1###$4%% $2**$1#$4% $4%%%%$1#$4% $2**$1#$2**$1## $1###$4%% $4%%%%%$1 $4%%%$2*** $4%%%%%% $4%%%%%%%$1 $4%%%%$1#$2* $4%%%%%% $4%%%%%%$1+$3= ================================================ FILE: src/logo/ascii/lainos.txt ================================================ $2 /==\ \==/ $1 · · · · · · · · · · · · · · · · · · · · $2.-======-.$1· · · · $2 .::.$1 ·$2.-============-.$1· $2.::. .:==:$1· $2.:===:'$1. ·· .$2':===:.$1 ·$2:==:. .:===:$1 · $2:===.$1 · $3.--.$1 · $2.===:$1 · $2:===:. :===:$1· · $2:===.$1 · $3.:====:.$1 · $2.===:$1 · ·$2:===: (===:$1· · $2:===-$1 · $3:======:$1 · $2-===:$1 · ·$2:===) :===:$1· · $2:===.$1 · $3':====:'$1 · $2.===:$1 · ·$2:===: ':===:$1 · $2:===.$1 · $3'--'$1 · $2.===:$1 · $2:===:' ':==:$1· $2':===:.$1' ·· '$2.:===:'$1 ·$2:==:' '::'$1 · $2'===-. .-==='$1 · $2'::' $2/==\$1 · · · $2:=== ===:$1 · · · $2/==\ \==/$1 · · ·$2:===$1 ·$2===:$1· · · $2\==/$2 .-. $1· $2:===$1· $2===:$1 ·$2 $2.-. .===. .=== ===. .===. .======== ========. ''''' ''''' ================================================ FILE: src/logo/ascii/langitketujuh.txt ================================================ $2. $1'7L7L7L7L7L7L7L7L7L7L7L7L7L7L7L7L7L7 $2L7. $1'7L7L7L7L7L7L7L7L7L7L7L7L7L7L7L7 $2L7L7L $17L7L7L7L7L7L7L7L7L7L7L7L7L7 $2L7L7L7 $1L7L7L7 $2L7L7L7 $1'L7L7L7L7L7L7L7L7L7L7 $2L7L7L7 $1'L7L7L7L7L7L7L7L7 $2L7L7L7 $1'L7L7L7L7L7L7 $2L7L7L7 $1L7L7L7 $2L7L7L7L7L7L7L7L7L7L7LL7L7L7. $1'7L7L7 $2L7L7L7L7L7L7L7L7L7L7L7L7L7L7L7L. $1'L7 $2L7L7L7L7L7L7L7L7L7L7L7L7L7L7L7L7L7. $1' ================================================ FILE: src/logo/ascii/laxeros.txt ================================================ /. `://:- `//////: .////////:` -//////////:` -/////////////` :///////////////. `://////.```-//////- `://///:` .//////- `//////: `//////: .//////- `://///:` -//////- `://///:` -//////. ://////` ://////` -//////. `/////:` ./////: .-::-` .:::-` .:://////////////////////////////////::. //////////////////////////////////////// .:////////////////////////////////////:. ================================================ FILE: src/logo/ascii/lede.txt ================================================ _________ / /\ / LE / \ / DE / \ /________/ LE \ \ \ DE / \ LE \ / \ DE \ / \________\/ ================================================ FILE: src/logo/ascii/lfs.txt ================================================ $2 :@@@@@@@: $2 @@@@@@@@@- $2 .:%. @@@@@@@@@+. @% $2 *@@@%+: :@@@@@@@%=: .=%@@@@@@= $2 :@@@@@@##@@@@@@@@@%*+%@%+@@@@@@@+ $2 @@#$1####$2+@@@@@@@%:$1######$2=@@@@@@@@@- $2 *@%$1######$2.@@@@@#$1#########$2-@@@@@@@@#. $2 %@-$1#$2.@$1=$2:$1##$2+@@@@-$1###$2%@$1:$2=$1###$2*@#*+=-+#: $2 @@.$1#$2@@*$1=$2:$1#$2-%%**-$1##$2%@@%$1*$2*$1###$2#=- $2 @@-$1#$2@@@@+.-$3...$2:=.$1#$2%@@@@%$1###$2#- $2 %@%$1##$2*#:$3.o.....o...$2-%@+$1###$2#@+ -: $2 +@@*$1#$3....................$2+@@@@@@@@+ $2 @%:$3....................._:$2@@@@@@@=. $2 .=:$3...............__*-=`.$2=@@@@@@#=. $2 :+:$3....:==*__*-=`:..==-:$2#@@@@@%+: $2 .--=-: $3+..::.....-: $2=%@*=: $2 :........- $2 .:...--. ================================================ FILE: src/logo/ascii/libreelec.txt ================================================ $1 :+ooo/. $2./ooo+: $1 :+ooooooo/. $2./ooooooo+: $1 :+ooooooooooo:$2:ooooooooooo+: $1 :+ooooooooooo+- $2-+ooooooooooo+: $1 :+ooooooooooo+- $3-- $2-+ooooooooooo+: $1.+ooooooooooo+- $3:+oo+: $2-+ooooooooooo+- $1-+ooooooooo+- $3:+oooooo+: $2-+oooooooooo- $1 :+ooooo+- $3:+oooooooooo+: $2-+oooooo: $1 :+o+- $3:+oooooooooooooo+: $2-+oo: $4 ./ $3:oooooooooooooooooo: $5/. $4 ./oo+: $3-+oooooooooooooo+- $5:+oo/. $4 ./oooooo+: $3-+oooooooooo+- $5:+oooooo/. $4-oooooooooo+: $3-+oooooo+- $5:+oooooooooo- $4.+ooooooooooo+: $3-+oo+- $5:+ooooooooooo+. $4 -+ooooooooooo+: $3.. $5:+ooooooooooo+- $4 -+ooooooooooo+: $5:+ooooooooooo+- $4 -+oooooooooo+:$5:+oooooooooo+- $4 -+oooooo+: $5:+oooooo+- $4 -+oo+: $5:+oo+- $4 .. $5.. ================================================ FILE: src/logo/ascii/lilidog.txt ================================================ +DDDL+ +LDDDDD+ LD++++D+D+::::::+D D+-::::=D+::::::::+LLDD+ D=::::::=L=-::::::=LL++=+D D-:::::=LDD=-::-=+L=-::::=D L+--==+++===++LDD+:::::::-D DL+++==:::::::::-+D-:::::::=D D=:::::::::::::::::++:::::-+D D+::::::::::::::::::+D++==+LD+ D=:::::::::::::::::+LD+=--:-+D L+=-::::::::::::::L+-::::::=D DLL++-::::::::::-D:::::::-+D DL+:::::::::+D=-:::-=+D D=:::::::-D +LDLLD+ L+--::-=D +LDLDD+ ================================================ FILE: src/logo/ascii/lingmo.txt ================================================ ..',;;;;;;,,.. ..,;;;;;;;;;;;;;;;;,.. .';;;;;;;;;;;;;;;;;;;;;;;;,. .;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;' .;;;;;;;;;$2clodddol:$1;;;;;;;;;;;;;;;;. ';;;;;;;$2ckXWWWNNWWWN0d:$1;;;;;;;;;;;;;;' ';;;;;;$2cOWWKxl:$1;;;:$2okXWXx$1;;;;;;;;;;;;;;, ;;;;;;$2:XWWo$1;;;;;;;;;;;$2ONNx$1;;;;;;;;;;;;;;. ,;;;;;;$2xWMk$1;;;;;;;;;;;;;$20NX:$1;;;;;;;;;;;;;; ;;;;;;;$2xMMk$1;;;;;;;;;;;;;$2oXNdcloooolc:$1;;;;; ,;;;;;;$2:NMWl$1;;;;;;;;;;;;$2ckodKK00000Oc$1;;;;; .;;;;;;;$2cKMWOo:$1;;;;;$2cox0Xccdl:$1;;;;;:;;;;;' ;;;;;;;;;$2o0WWNK000KXXKkodKKo$1;;;;;;;;;;;; ,;;;;;;;;;$2:loxxxxdlc$1;;;;$2xKKxc$1;;;;;;;;; ,;;;;;;;;;;;;;;;;;;;;;;;$2ckK0o;$1;;;;;; ;;;;;;;;;;;;;;;;;;;;;;;;$2:l$1;;;;;;. ;;;;;;;;;;;;;;;;;;;;;;;;;;;;. ;;;;;;;;;;;;;;;;;;;;;;. ';;;;;;;;;;;;, ';;'. ================================================ FILE: src/logo/ascii/linspire.txt ================================================ $2 __^ $2 __/ \ $2 MMy dMy __/ \ $2 dMMy MMy $1MM$2 \ $2 MMMy ,, $1dMMMMn $2\ $2 dMMy dMM dMMMMMMy $1dMM MM dMMMMMy dMM MM.nMMM dMMMMMM $1MMM $2MMy MMy MMy $1dMM MMy MMy MMy MMy dy dMy $1MMM $2dMM dMM MMy $1dMMMMy dMM dMM dMM dMM dMMMMMMM $2 dMMy MMy MMy MMy $1dMMy MM MMy MMy MMy dMM $2dMMy dMM dMM dMM $1dMM MMy dMMMMMy dMM dMM MMy MM $2MMMMMMMMMM MMy MMy MMy $1dMMMyyy MMy MMy MMy dMMMMMMy $2 $1dy ================================================ FILE: src/logo/ascii/linux.txt ================================================ $2##### $2####### $2##$1O$2#$1O$2## $2#$3#####$2# $2##$1##$3###$1##$2## $2#$1##########$2## $2#$1############$2## $2#$1############$2### $3##$2#$1###########$2##$3# $3######$2#$1#######$2#$3###### $3#######$2#$1#####$2#$3####### $3#####$2#######$3##### ================================================ FILE: src/logo/ascii/linux_small.txt ================================================ $1___ ($2.. $1\ ($3<> $1| /$2/ \ $1\ ( $2| | $1/| $3_$1/\ $2__)$1/$3_$1) $3\/$1-____$3\/ ================================================ FILE: src/logo/ascii/linuxlite.txt ================================================ ,xXc .l0MMMMMO .kNMMMMM$2W$1MMMN, KMMMMMM$2K$1MMMMMMo 'MMMMMMN$2K$1MMMMMM: kMMMMMM$2O$1MMMMMMO .MMMMMM$20$1XMMMMMW. oMMMMM$2x$1MMMMMMM: WMMMMM$2x$1MMMMMMO :MMMMMM$2O$1XMMMMW .0MMMMM$2x$1MMMMM; :;cKMMW$2x$1MMMMO 'MMWMMX$2O$1MMMMl kMMMMK$2O$1MMMMMX: .WMMMMK$2O$1WMMM0c lMMMMMW$2O$1WMNd:' oollXMK$2o$1Xxl;. ':. .:$2 .$1' $2.. . ================================================ FILE: src/logo/ascii/linuxlite_small.txt ================================================ /\ / \ / $2/ $1/ / $2/ $1/ \ $2\ $1\ \_$2\$1_\ $2 \ ================================================ FILE: src/logo/ascii/linuxmint.txt ================================================ $2_.-ppOOOOOOqq-._ .oOOOOPPPPPPPPPPOOOOo. .oOOOO$1.=oOOOOOOOOOOo=.$2OOOOo. .:OOO$1.=oOOOOOOOOOOOOOOOOo=.$2OOO:. .OOO$1.OOOOOOOOOOOOOOOOOOOOOOOO.$2OOO. .OOO$1.OO OOO:´ `::´ `:OOO.$2OO: .OOO$1.OOO OO OOO.$2OOO: OOO$1.OOOO OO oo oo OOOO.$2OOO :OOO$1:OOOO OO OO OO OOOO:$2OOO: :OOO$1:OOOO OO OO OO OOOO:$2OOO: 'OOO$1'OOOO OO OO OO OOOO'$2OOO' OOO$1'OOOO OO____OO____OO OOOO'$2OOO' 'OOO$1'OOO 'OOOOOOOOOOOO' OOOO'$2OOO 'OOO$1'OOO .OOO'$2OOO' 'OOO$1'OOOO:ooooooooooooooo:OOOO'$2OOO' ':OOOo$1'=OOOOOOOOOOOOOOOOO='$2oOOO:' ':OOOOo$1'=OOOOOOOOOOO='$2oOOOO:' ``-OOOOooooooooooOOOO-´´ ```-=:OOOO:=-´´´ ================================================ FILE: src/logo/ascii/linuxmint2.txt ================================================ $2...-:::::-... .-MMMMMMMMMMMMMMM-. .-MMMM$1`..-:::::::-..`$2MMMM-. .:MMMM$1.:MMMMMMMMMMMMMMM:.$2MMMM:. -MMM$1-M---MMMMMMMMMMMMMMMMMMM.$2MMM- :MMM$1:MM` :MMMM:....::-...-MMMM:$2MMM: :MMM$1:MMM` :MM:` `` `` `:MMM:$2MMM: .MMM$1.MMMM` :MM. -MM. .MM- `MMMM.$2MMM. :MMM$1:MMMM` :MM. -MM- .MM: `MMMM-$2MMM: :MMM$1:MMMM` :MM. -MM- .MM: `MMMM:$2MMM: :MMM$1:MMMM` :MM. -MM- .MM: `MMMM-$2MMM: .MMM$1.MMMM` :MM:--:MM:--:MM: `MMMM.$2MMM. :MMM$1:MMM- `-MMMMMMMMMMMM-` -MMM-$2MMM: :MMM$1:MMM:` `:MMM:$2MMM: .MMM$1.MMMM:--------------:MMMM.$2MMM. '-MMMM$1.-MMMMMMMMMMMMMMM-.$2MMMM-' '.-MMMM$1``--:::::--``$2MMMM-.' '-MMMMMMMMMMMMM-' ``-:::::-`` ================================================ FILE: src/logo/ascii/linuxmint_old.txt ================================================ MMMMMMMMMMMMMMMMMMMMMMMMMmds+. MMm----::-://////////////oymNMd+` MMd $2/++ $1-sNMd: MMNso/` $2dMM `.::-. .-::.` $1.hMN: ddddMMh $2dMM :hNMNMNhNMNMNh: $1`NMm NMm $2dMM .NMN/-+MMM+-/NMN` $1dMM NMm $2dMM -MMm `MMM dMM. $1dMM NMm $2dMM -MMm `MMM dMM. $1dMM NMm $2dMM .mmd `mmm yMM. $1dMM NMm $2dMM` ..` ... ydm. $1dMM hMM- $2+MMd/-------...-:sdds $1dMM -NMm- $2:hNMNNNmdddddddddy/` $1dMM -dMNs-$2``-::::-------.`` $1dMM `/dMNmy+/:-------------:/yMMM ./ydNMMMMMMMMMMMMMMMMMMMMM .MMMMMMMMMMMMMMMMMMM ================================================ FILE: src/logo/ascii/linuxmint_small.txt ================================================ __________ |_ \ | $2| _____ $1| | $2| | | | $1| | $2| | | | $1| | $2\__$2___/ $1| \_________/ ================================================ FILE: src/logo/ascii/live_raizo.txt ================================================ `......` -+shmNMMMMMMNmhs/. :smMMMMMmmhyyhmmMMMMMmo- -hMMMMd+:. `----` .:odMMMMh- `hMMMN+. .odNMMMMMMNdo. .yMMMMs` hMMMd. -dMMMMmdhhdNMMMNh` .mMMMh oMMMm` :MMMNs.:sddy:-sMMMN- `NMMM+ mMMMs dMMMo sMMMMMMd yMMMd sMMMm ----` .---` oNMMMMMh `---. .---- .sMMy: /MM/ +dMMms. hMMMMMMN `dMMMMMMm: .+ss+sMNysMMoomMd+ss+. +MMMMMMN` +MM/ hMMMMMNs sMMMMMMm-hNMMMd-hMMMMMMd :yddh+`hMMMMMMN :yddy/` .hMMMMd: `..` ================================================ FILE: src/logo/ascii/lliurex.txt ================================================ ~. ........ ::~~ +=: ~:========:~ ..~:+oo===oo:.~+oooooooooooo+~ .:+==ooooooooooo==ooooooooooooooo: .:=oooooooooooooooooooooooooooooooo= .. .=oooooooooooooooooooooooooooooooooo= ==. .+ooooooooooooooooooooooooooooooooooo= ~+:~:oooooooooooooooooooooooooooooooooooo: ~ooooooooooooooooooooooooooooooooooooo=: ~oooooooooooooooooooooooooooooooooo==:. .+oooooooooooooooooooooooooo=~::::~.. ~+oooooooooooooooooooooooo=: .:=ooooooooooooooooooooo=: .:==ooooooooooooooo==:. ~~:===oooooo==+:~ ...~~:~::.. ================================================ FILE: src/logo/ascii/lmde.txt ================================================ $2 `.-::---.. $1 .:++++ooooosssoo:. .+o++::. `.:oos+. $1 :oo:.` -+oo$2: $1 $2`$1+o/` .$2::::::$1-. .++-$2` $1$2`$1/s/ .yyyyyyyyyyo: +o-$2` $1$2`$1so .ss ohyo` :s-$2: $1$2`$1s/ .ss h m myy/ /s`$2` $1`s: `oo s m Myy+-o:` `oo :+sdoohyoydyso/. :o. .:////////++: $1 `/++ $2-:::::- $1 $2`$1++- $1 $2`$1/+- $1 $2.$1+/. $1 $2.$1:+-. `--.`` ================================================ FILE: src/logo/ascii/locos.txt ================================================ $3..;:::::::;. .0X'$1''''''''''''$3'N: :Xd$1,.'''''''''''''''$3lKx .0o$1'.'''''''''''''''''''$3:K; .O$1;.'$3okOx:$1'''''''''$3,dOOx:$1''$3O; xc$1.'$3xXl$1 $3kX;$1''$2:cc;$1''$3kXl$1 $3OX;$1''$3k. 'x$1..'$3:0X0Xx$1',$2OOOOOd$1,:$30X0Xx$1'''$3o: ::$1..'$2,clollxOOOOOOOOdlddlc$1''''$3d l'$1..$2':kkkkOOOOOOOOOOOOkkkx,$1'''$3x o$1...''''',$2codxkkxxxdl$1:''''''''$3d. d$1...''''''''''''''''''''''''''$3o. d$1..''''$3,loc,$1''''''''$3;os:,$1'''''$3d' :d$1..'$3,dKXXXXX0kxxxk0XXXXXX0l$1'''$3,O. '0$1..'$3cKXXXXXXXXXXXXXXXXXXXXXXk$1'''$3ck '0$1'.'$3cXXXXXXXXXXXXXXXXXXXXXXXXXO$1'''$3od .0$1,.''$30XXXXXXXXXXXXXXXXXXXXXXXXXXo$1'''$3k; cc$1..''$3XXXXXXXXXXXXXXXXXXXXXXXXXXXk$1''''$3k l$1...',$3XXXXXXXXXXXXXXXXXXXXXXXXXXXO$1''''$3d. ================================================ FILE: src/logo/ascii/lubuntu.txt ================================================ `.:/ossyyyysso/:. `.:yyyyyyyyyyyyyyyyyy:.` .:yyyyyyyyyyyyyyyyyyyyyyyy:. .:yyyyyyyyyyyyyyyyyyyyyyyyyyyy:. -yyyyyyyyyyyyyy$2+hNMMMNh+$1yyyyyyyyy- :yy$2mNy+$1yyyyyyyy$2+Nmso++smMdhyysoo+$1yy: -yy$2+MMMmmy$1yyyyyy$2hh$1yyyyyyyyyyyyyyyyyyy- .yyyy$2NMN$1yy$2shhs$1yyy$2+o$1yyyyyyyyyyyyyyyyyyyy. :yyyy$2oNM+$1yyyy$2+sso$1yyyyyyy$2ss$1yyyyyyyyyyyyy: :yyyyy$2+dNs$1yyyyyyy$2++$1yyyyy$2oN+$1yyyyyyyyyyyy: :yyyyy$2oMMmhysso$1yyyyyyyyyy$2mN+$1yyyyyyyyyyy: :yyyyyy$2hMm$1yyyyy$2+++$1yyyyyyy$2+MN$1yyyyyyyyyyy: .yyyyyyy$2ohmy+$1yyyyyyyyyyyyy$2NMh$1yyyyyyyyyy. -yyyyyyyyyy$2++$1yyyyyyyyyyyy$2MMh$1yyyyyyyyy- :yyyyyyyyyyyyyyyyyyyyy$2+mMN+$1yyyyyyyy: -yyyyyyyyyyyyyyyyy$2+sdMMd+$1yyyyyyyy- .:yyyyyyyyy$2hmdmmNMNdy+$1yyyyyyyy:. .:yyyyyyy$2my$1yyyyyyyyyyyyyyy:. `.:yyyy$2s$1yyyyyyyyyyyyy:.` `.:/oosyyyysso/:.` ================================================ FILE: src/logo/ascii/lunar.txt ================================================ `-. `-. -ohys/-` `:+shy/` -omNNdyo/` :+shmNNy/` $3 - /mMmo hMMMN` .NMMs $1 -:+oooo+//: $3/MN$1. -///oooo+/-` /:.` $3/$1 `.:/` $3 __ | | _ _ ___ ___ ___ | |__| | | | .'| _| |_____|___|_|_|__,|_| ================================================ FILE: src/logo/ascii/macaronios.txt ================================================ .::-::::. :======---:. .-=++++++++==-:. .:-++===--=**+=: .=. ...:::::--=****+++=-:. -- -: $2.-==-.$1 .::. :: . $2:*%@@@@@#. .:::. :#%%%%%@%#+. :*##%%%%#+. *%%%%%%*. =. +*#%%%%%%%#: *%%%%%%- +%%%%%%#@= :%%%%%%#-. .:+%%%%%%%%+ :*%%%%%%+. .-+*###*=: :=+=-. ================================================ FILE: src/logo/ascii/macos.txt ================================================ ..' ,xNMM. .OMMMMo lMM" .;loddo:. .olloddol;. cKMMMMMMMMMMNWMMMMMMMMMM0: $2.KMMMMMMMMMMMMMMMMMMMMMMMWd. XMMMMMMMMMMMMMMMMMMMMMMMX. $3;MMMMMMMMMMMMMMMMMMMMMMMM: :MMMMMMMMMMMMMMMMMMMMMMMM: $4.MMMMMMMMMMMMMMMMMMMMMMMMX. kMMMMMMMMMMMMMMMMMMMMMMMMWd. $5'XMMMMMMMMMMMMMMMMMMMMMMMMMMk 'XMMMMMMMMMMMMMMMMMMMMMMMMK. $6kMMMMMMMMMMMMMMMMMMMMMMd ;KMMMMMMMWXXWMMMMMMMk. "cooc*" "*coo'" ================================================ FILE: src/logo/ascii/macos2.txt ================================================ ..' ,xN . .O o l " .;loddo:. .olloddol;. cK NW 0: $2.K Wd. X X. $3; : : : $4. X. k Wd. $5'X k 'X K. $6k d ;K WXXW k. "cooc*" "*coo'" ================================================ FILE: src/logo/ascii/macos2_small.txt ================================================ $1 .:' __ :'__ $2 .'` `-' ``. $3: .-' $4: : : `-; $5 `.__.-.__.' ================================================ FILE: src/logo/ascii/macos3.txt ================================================ -:+:. :++++. /+++/. .:-::- .+/:-\`\`.::- .:/++++++/::::/++++++/::` $2.:///////////////////////::` /////////////////////////` $3+++++++++++++++++++++++++` ++++++++++++++++++++++++ $4ssssssssssssssssssssssss. :ssssssssssssssssssssssss- $5osssssssssssssssssssssssss` `syyyyyyyyyyyyyyyyyyyyyyyyys` $6`ossssssssssssssssssssssss :ooooooooooooooooooo+. `:++oo+/:- -:/+o+/- ================================================ FILE: src/logo/ascii/macos_small.txt ================================================ $1 .:' __ :'__ $2 .'`__`-'__``. $3:__________.-' $4:_________: :_________`-; $5 `.__.-.__.' ================================================ FILE: src/logo/ascii/mageia.txt ================================================ .°°. °° .°°. .°°°. °° . . °°° .°°°. .°°°. '___' $2 .$1'___' $2 . :dkxc;'. ..,cxkd; .dkk. kkkkkkkkkk .kkd. .dkk. ';cloolc;. .kkd ckk. .kk; xO: cOd xO: lOd lOO. .OO: .k00. .00x .k00; ;00O. .lO0Kc;,,,,,,;c0KOc. ;d00KKKKKK00d; .,KKKK,. ================================================ FILE: src/logo/ascii/mageia_small.txt ================================================ * * ** $2 /\__/\ / \ \ / \____/ ================================================ FILE: src/logo/ascii/magix.txt ================================================ $2@ @@--=====@@ @@--==@@ @@====+@ @-@@ @==@ @=@ @=@$1 @=@ @-==== @=@$2 @=@$1 @-===@==++@===+@$2 @=@$1 @--====@@=====+@$2 -=@$1 @--==========++@$2 ==$1 @--==========++@$2 @=@ @==$1 @--=======@==++@$2 @=+@ @==$1 @-==========++$2 @=@ @==$1 @-=======@=%$2 @=@ @==$1 @@@@@@$2 @=@ @====@@@ @@===+% @@=====@@==++++@@ =#@=@ @==@++@ @@@ ================================================ FILE: src/logo/ascii/magpieos.txt ================================================ ;00000 :000Ol .x00kk00: O0kk00k; l00: :00. o0k :O0k. .k0k. x$2d$dddd$1k' .d00; k0k. $2.dddddl $1o00, o00. $2':cc:. $1d0O .00l ,00. l00. d0x k0O .:k0o O0k ;dO0000d. k0O .O0O$2xxxxk$100: o00. k0O$2dddddd$1occ '00l x0O$2dddddo$3;..$1 x00. .x00$2kxxd$3:..$1 .O0x .:oxxx$4Okl.$1 .x0d $4,xx,$1 .:o. $4.xd ckd$1 .. $4dxl .xx; :xxolldxd' ;oxdl. ================================================ FILE: src/logo/ascii/mainsailos.txt ================================================ - *%: :%%%# =%%%%%- *%%%%%%# :#%%%%%%%#. -%%%%%%%%+ *%%%%%%%%- : .#%%%%%%%#. *%= -%%%%%%%%+ :#%%%* +%%%%%%%%- =%%%%%%#. .#%%%%%%%#. *%%%%%%%%: -%%%%%%%%* :#%%%%%%%#. +%%%%%%%%- =%%%%%%%%+ :%*. .#%%%%%%%#: *%%%%%%%%- +%%%%*: :%%%%%%%%* :#%%%%%%%#. .*%%%%%%%* +%%%%%%%%= -%%%%%%%%+ :%%%%%%%%* .#%%%%%%%%: *%%%%%%%%- =%%%%%%%%= ================================================ FILE: src/logo/ascii/mainsailos_small.txt ================================================ -: +%* .#%%+ -%%%: += +%%#..#%%- .#%%+ -%%%- += -%%%- +%%#..#%%+ ================================================ FILE: src/logo/ascii/mandriva.txt ================================================ $2 `` `-. $1 ` $2.--- $1 -/ $2-::--` $1 `++ $2`----...```-:::::. $1 `os. $2.::::::::::::::-``` ` ` $1 +s+ $2.::::::::::::::::---...--` $1-ss: $2`-::::::::::::::::-.``.`` $1/ss- $2.::::::::::::-.`` ` $1+ss: $2.::::::::::::- $1/sso $2.::::::-::::::- $1.sss/ $2-:::-.` .::::: $1 /sss+. $2..`$1 `--` $2.::: $1 -ossso+/:://+/-` $2.:` $1 -/+ooo+/-. $2` ================================================ FILE: src/logo/ascii/manjaro.txt ================================================ ██████████████████ ████████ ██████████████████ ████████ ██████████████████ ████████ ██████████████████ ████████ ████████ ████████ ████████ ████████ ████████ ████████ ████████ ████████ ████████ ████████ ████████ ████████ ████████ ████████ ████████ ████████ ████████ ████████ ████████ ████████ ████████ ████████ ████████ ████████ ████████ ████████ ████████ ████████ ████████ ================================================ FILE: src/logo/ascii/manjaro_small.txt ================================================ ||||||||| |||| ||||||||| |||| |||| |||| |||| |||| |||| |||| |||| |||| |||| |||| |||| |||| |||| |||| ================================================ FILE: src/logo/ascii/massos.txt ================================================ -+++/+++osyyhdmNNMMMMNdy/ /MMMMMMMMMMMMMMMMMMMMMMMMm. /MMMMMMMMMMMMMMMMMMMMMMMMMm /MMMMMMMMMMMMMMMMMMMMMMMMMM: :ddddddddhddddddddhdddddddd/ /NNNNNNNm:NNNNNNNN-NNNNNNNNo /MMMMMMMN:MMMMMMMM-MMMMMMMMs /MMMMMMMN:MMMMMMMM-MMMMMMMMs /MMMMMMMN:MMMMMMMM-MMMMMMMMs /MMMMMMMN:MMMMMMMM-MMMMMMMMs /MMMMMMMN:MMMMMMMM-MMMMMMMMs /MMMMMMMN:MMMMMMMM-MMMMMMMMs /MMMMMMMN:MMMMMMMM-MMMMMMMMs /MMMMMMMN:MMMMMMMM-MMMMMMMMs :MMMMMMMN:MMMMMMMM-MMMMMMMMs dMMMMMMN:MMMMMMMM-MMMMMMMMs `yNMMMMN:MMMMMMMM-MMMMMMMMs `:+oss.ssssssss.ssssssss/ ================================================ FILE: src/logo/ascii/matuusos.txt ================================================ ░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░ ░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░ ░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░ ░░░░░░░░░░░░░░░░▒▓▓████▓▒▒░░░░░░░░░░░░░░░░ ░░░░░░░░░░░░░▒▓████████████▓░░░░░░░░░░░░░░ ░░░░░░░░░░░░▓████████████████▒░░░░░░░░░░░░ ░░░░░░░░░░░▓██████████████████▒░░░░░░░░░░░ ░░░░░░░░░░▓████▒▓███████▓▓█████░░░░░░░░░░░ ░░░░░░░░░░██████▓░▓████▒░██████▓░░░░░░░░░░ ░░░░░░░░░▒███████▓░▒▓▒░░████████░░░░░░░░░░ ░░░░░░░░░▒█████████▒░░░█████████░░░░░░░░░░ ░░░░░░░░░░██████████▓▒██████████░░░░░░░░░░ ░░░░░░░░░░▓████████████████████▒░░░░░░░░░░ ░░░░░░░░░░░███████████████████▓░░░░░░░░░░░ ░░░░░░░░░░░░█████████████████▓░░░░░░░░░░░░ ░░░░░░░░░░░░░▓██████████████▒░░░░░░░░░░░░░ ░░░░░░░░░░░░░░░▒▓████████▓░░░░░░░░░░░░░░░░ ░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░ ░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░ ░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░ ================================================ FILE: src/logo/ascii/maui.txt ================================================ `.-://////:--` .:/oooooooooooooooo+:. `:+ooooooooooooooooooooooo:` `:oooooooooooooooooooooooooooo/` ..```-oooooo/-`` `:oooooo+:.` `-- :. +oo+-` /ooo/` -/ -o. `o+- +o/` -o: `oo` ::` :o/ `+. .+o` /oo. /o+ . -+oo- ` /oo/ `ooo/ +o- /ooo+` .+ooo. :ooo+ ++ .+oooo: -oooo+ `oooo+ :. .oooooo` :ooooo- :oooo: ` .oooooo: :ooooo+ `ooo+-` .+oooooo` -oooooo: `o/- +oooooo: .ooooooo. /ooooooo` /ooooooo/ .. `:oooooooo/:::/ooooooooo+:--:/:` `:+oooooooooooooooooooooo+:` .:+oooooooooooooooo+:. `.-://////:-.` ================================================ FILE: src/logo/ascii/mauna.txt ================================================ .. :-=++++=-: .-+*+ -********* **= =***= +******+ =---=+*. +**** +****+ :-=++*++=-- =****= **** :+***********+: +****+ *** $2.-- $1+******- =*****: *- $2:+=: $1=*****: $2. $1+*****: : $2-+++ $1:****= $2-+ $1:*****+ $2- ++++ $1:***- $2-++=: $1=+****: $2:++ +++++ $1**+ $2 +++++= $1-====-$2++++: +++++ $1*+ $2 .=++++++++++++++: $2+++++= $1. $2 .=+++++++++= :=+++++= .:::--- -+++++++- :-==++==-. ================================================ FILE: src/logo/ascii/meowix.txt ================================================ $1 #$2% $3&$4* $1 ##$2%% $3&&$4** $1 ## $2%% $3&& $4** $1 ## $2%% $3&& $4** $1 ## $2%% $3&& $4** $1 ## $2%% $3&& $4** $1 ## $2%%$3&& $4** $1 ## $2%% $4** $1 ## $4** $1## $4** ================================================ FILE: src/logo/ascii/mer.txt ================================================ dMs .-` `y`-o+` ``NMMy .--`:++. .hNNNNs /MMMMMN `ommmd/ +/ ```` +/ `:+sssso/-` .-::. `-::-` `smNMNmdmNMNd/ .://-` .ymNMNNdmNMMNm+` -dMMh:.....+dMMs `sNNMMNo dMN+::NMMy::hMM+ mMMo `ohhy/ `dMM+ yMMy::- MMm yMM- :MMs NMN` `:::::--sMMh dMM` MMm yMM- -MMs mMM+ `ymmdsymMMMs dMM` NNd sNN- -NNs -mMNs-.--..:dMMh` dNN --- .--` `--. .smMMmdddmMNdo` .-- ./ohddds+:` +h- `.:-. ./`.dMMMN+ +MMMMMd `+dmmy- ``` .+` .dMNo-y. `hmm/ .:` dMs ================================================ FILE: src/logo/ascii/midnightbsd.txt ================================================ ..:::'''':::.. .:'''` `''':. .:'` .::` `':: :' .::' ':. .:` .:::' `:. :` :::: `: :: ::::: :: : ::::: :: : : :::::: .:::' : :: ::::::. : .:::::: :: :. :::::::. : :::::::' .: `:. `':::::::.'.:::::': .:' `:. '':::::::::::::.: .:` ':. `''::::::::''' .:' '':.. ``'`` ..:'' ''''::::::'''' ================================================ FILE: src/logo/ascii/midos.txt ================================================ ▁▂▃▄▅▆▇▇████▇▇▆▅▄▃▂▁ ▃▅▇████████████████████▇▅▃ ▂▅████████████████████████████▅▁ ▗▟██████████████▛▀▀▜██████████████▙▖ ▗▟████████████████▌ ▐████████████████▙▖ ▗██████████████████▌ ▐██████████████████▖ ▗███████████████████▌ ▐███████████████████▖ ▕███████████████▀▘ ██▌ ▐██ ▝▀███████████████▏ ▐██████████▛▀▀ ▂██▌ ▐██▂ ▀▀▜██████████▌ ███████▀▀ ▁▄▆████▌ ▐████▆▄▁ ▀▀███████ █████ ▂▄▂ ▔▀▜████▌ ▐████▛▀▔ ▂▄▂ █████ ▐████▄▄▆█████▄▂ ▔▀██▌ ▐██▀▔ ▂▄█████▆▄▄████▌ ▕██████████████▇▄▂ ██▌ ▐██ ▂▄▇██████████████▏ ▝███████████████████▌ ▐███████████████████▘ ▝██████████████████▌ ▐██████████████████▘ ▝▜████████████████▌ ▐████████████████▛▘ ▝▜██████████████▙▄▄▟██████████████▛▘ ▔▀▜██████████████████████████▛▀▔ ▔▀▜████████████████████▛▀▔ ▔▔▀▀▀▜██████▛▀▀▀▔▔ ================================================ FILE: src/logo/ascii/midos_old.txt ================================================ .:=+*#%%@@@@@@%%#*+=:. .=*%@@@@@@@@@@@@@@@@@@@@@@%*=: .=%@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@%+. :*@@@@@@@@@@@@@@@@@$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%@@@@@@@@@@@@@@@@#-. .+%@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@%+: :=*%@@@@@@@@@@@@@@@@@@@@@@%*=:. .-=+*#%%@@@@@@%%#*+=-. ================================================ FILE: src/logo/ascii/minimal.txt ================================================ ##### ##### ##### ###### ###### ##### ##### ##### ###### ##### ###### ##### ###### ###### ##### ###### ##### ##### ##### ###### ###### ###### ##### ##### ================================================ FILE: src/logo/ascii/minix.txt ================================================ $2 -sdhyo+:-` -/syymm: sdyooymmNNy. `` .smNmmdysNd odyoso+syNNmysoyhhdhsoomNmm+/osdm/ :hhy+-/syNNmddhddddddmNMNo:sdNd: `smNNdNmmNmddddddddddmmmmmmmy` `ohhhhdddddmmNNdmddNmNNmdddddmdh- odNNNmdyo/:/-/hNddNy-`..-+ydNNNmd: `+mNho:` smmd/ sNNh :dmms` -+ymmo. -od/ -m$1mm$2mo -NN+ +m$1mm$2m- yms: +sms -.` :so: .NN+ :os/ .-`mNh: .-hyh+:////- -sNNd:` .--://ohNs- `:hNNNNNNNMMd/sNMmhsdMMh/ymmNNNmmNNy/ -+sNNNNMMNNNsmNMo: :NNmymNNNNMMMms: //oydNMMMMydMMNysNMMmsMMMMMNyo/` ../-yNMMy--/::/-.sMMmos+.` -+oyhNsooo+omy/``` `::ohdmds-` ================================================ FILE: src/logo/ascii/miracle_linux.txt ================================================ ,A .### .#' .#### .#. r##: :#### ####. +###; :#### ######C :####: #### ,######".#. .# :####. :### #####'.#####. ##: `####. ### ###'.########+. #### `####::## ##'.#######+' ####+.`###i## #',####:' `+###MI`##### 'l###:' `+####+`### ;#E' `+###:## #' `:### ' '## '; ================================================ FILE: src/logo/ascii/mos.txt ================================================ :--==========================--: .-=================================. -==================================- ==================================== =======-....:==========:....:======= =======: -======-. .======= =======: :====- .======= =======: :==: .======= =======: .. .======= =======: .: .: .======= =======: .=- :=: .======= =======: .===. .-==: .======= =======: .==========: .======= =======: :==========: :======= ==================================== -=================================== .==================================: :--===========================-:. ================================================ FILE: src/logo/ascii/msys2.txt ================================================ $2 ... 5GB###GJ. !YPGGGG 7@@@@@@@B. :G@@@@@@@ 7@@@@@@@@Y ~&@@@@@@@@$3YJYY5YY?L $2!@@@@@@@@@@^ ^&@@@@@@@$3#PP555555PBY $2~&@@@@@@@@@@? ^&@@@@@@$3#5YY5YYYYYYYY#7 $2^&@@@@@@@@@@@B :#@@@@@@@$3G5BBYGPYYYYYY#J $2^#@@@&J#@@@@@@@~ .B@@@@@@@@@@@P $3?#YYYYYPB. $2:#@@@@7 G@@@@@@@J P@@@#!&@@@@@@G$3.GGYYYYGB^ $2:#@@@@J Y@@@@@@@B 5@@@&:.&@@@@@@&$3BBYYY5B5. $2:#@@@@Y !@@@@@@@@!Y@@@&~ .#@@@@@@$3GYYYYYBP JP~ $2:#@@@@P :&@@@@@@@@@@@&~ B@@@@@$3#5YYYYYPGPGPGG $2^#@@@@G. P@@@@@@@@@@@! P@@@@$3GYYYYYYYYYYYYBY $2^#@@@@B: ^@@@@@@@@@@7 !@@@$3#GGGGGGGPPPP5GB: $2!&@@@@B: Y@@@@@@@@? P@@@@@@@@@&? $3^PY: $27&@@@@5. P@@@@@@? P@@@@@@@@@B Y@@@&P! 5@@@@7 7G@@@@@&P~ .JJ?~: ^JY~ ^!5J!^: $1:@P5#B. #G 7&^ :@P5#B. !&P^. ?@~ #P !&P^. .?BG! #G5@~ .?BG! :.B@. 7@@5 :.B@. !PYY5Y :&@^ !PYY5Y ~@Y !5: ================================================ FILE: src/logo/ascii/mx.txt ================================================ MMMMMMMMMMMMMMMMMMMMMMMMMMMMMMNMMMMMMMMM MMMMMMMMMMNs..yMMMMMMMMMMMMMm: +NMMMMMMM MMMMMMMMMN+ :mMMMMMMMMMNo` -dMMMMMMMM MMMMMMMMMMMs. `oNMMMMMMh- `sNMMMMMMMMM MMMMMMMMMMMMN/ -hMMMN+ :dMMMMMMMMMMM MMMMMMMMMMMMMMh- +ms. .sMMMMMMMMMMMMM MMMMMMMMMMMMMMMN+` ` +NMMMMMMMMMMMMMM MMMMMMMMMMMMMMNMMd: .dMMMMMMMMMMMMMMM MMMMMMMMMMMMm/-hMd- `sNMMMMMMMMMMMMM MMMMMMMMMMNo` -` :h/ -dMMMMMMMMMMMM MMMMMMMMMd: /NMMh- `+NMMMMMMMMMM MMMMMMMNo` :mMMN+` `-hMMMMMMMM MMMMMMh. `oNMMd: `/mMMMMMM MMMMm/ -hMd- `sNMMMM MMNs` - :dMMM Mm: `oMM MMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM ================================================ FILE: src/logo/ascii/mx2.txt ================================================ @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ @@@@@@@@@@@@%*+--:------=+*%@@@@@@@@@@@@ @@@@@@@@@#=. .-+#%@@@@@%#*+--=#@@@@@@@@@ @@@@@@@+. .=%@@@@@@@@@@@@@@@@*-:+@@@@@@@ @@@@@*. *@@@@@@@@@@@@@@@@@@@@@%-.*@@@@@ @@@@- -@@@@@@@@@@@@@@@@@@@@@@@#: -@@@@ @@@: -@@@@@@@=.*@@@@@@@@@@@@%- = :@@@ @@= .@@@@@@@@%- :%@@@@@@@@@+ -%@# =@@ @% +@@@@@@@@@@#. =@@@@@@*. .*@@@@. %@ @+ *@@@@@@*..*@@+ *@@%- =@@@@@@- +@ @= *@@@@%- -%@@- := -%@@@@@@@: +@ @+ :@@@= +@@= .#@@@@@@@@% *@ @% +*. .: *@@#: +@@: @@ @@+ :%@- :- :: +@@ @@@- .=@@= -@@@ @@+. . +@@ %=..:.................::...........:..-% @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ ================================================ FILE: src/logo/ascii/mx_small.txt ================================================ \\ / \\/ \\ /\/ \\ / \ /\ / \/ \ /__________\ ================================================ FILE: src/logo/ascii/namib.txt ================================================ .:+shysyhhhhysyhs+:. -/yyys syyy/- -shy yhs- -yhs shy- +hy yh+ +ds sd+ /ys so sy/ sh smMMNdyo hs yo ymMMMMNNMMNho oy N ydMMMNNMMMMMMMMMmy N N shmMMMMNNMMMMMMMMMMMMMNy N yo ooshmNMMMNNNNMMMMMMMMMMMMMMMMMms oy sd yyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy ds /ys sy/ +ds sd+ +hy yh+ -yhs shy- -shy yhs- -/yyys syyy/- .:+shysyhyhhysyhs+:. ================================================ FILE: src/logo/ascii/nekos.txt ================================================ @@@@@ @@@@@@@@@. @@@@@@@@ @@@ @@@@@@@@@@@ @@. @@@@@@@@@@@@@ . @@@@@@@@@@@@@@@@@ , @@@@@@@@@@@@@@@@@@@ @@@@@$2///$1@@@@@@@$2///$1@@@ @@@@$2/***$1@@@@@@@$2**//$1@@@@ @@@@@@@@@@@@@@@@@@@@@@@@@@@@ @@@@@@@@@@@@@@@@@@@@@@@ @@@/ /@@@@@@@@@/ /@@@ @@@@@@ @@@$3██$1@@@@ @@@@@@ @@@@@@/ /@$2██$3██$2██$1@@/ /@@@@@@ @@@@@@@@@@@@@@@@@@@@@@@@@@@@ ##########%%%% ##########%% %% @ @@@#######@@%%% @@@ @@@@####@@@@ % @@@ @@@@@@@#@@@@@@@ @@@ @@@@@@@@@@@@@@@ @@@@ @@@@@@@@@@@@@@@@@ @@@@@@@@@@@@@@@@@@@@@@@@ ================================================ FILE: src/logo/ascii/neptune.txt ================================================ ./+sydddddddys/-. .+ymNNdyooo/:+oooymNNmy/` `/hNNh/.` `-+dNNy:` /mMd/. .++.:oy/ .+mMd- `sMN/ oMMmdy+. `oNNo `hMd. `/ymy/. :NMo oMN- `/dMd: /MM- `mMy -dMN+` mMs .MMo -NMM/ yMs dMh mMMMo:` `NMo /MM/ /ymMMMm- sMN. +Mm: .hMMd` oMN/ +mNs. `yNd/` -dMm- .yMNs: `/.` `/yNNo` .odNNy+-` .:ohNNd/. -+ymNNmdyyyyyyydmNNmy+. `-//sssssss//. ================================================ FILE: src/logo/ascii/netbsd.txt ================================================ $1 `-/oshdmNMNdhyo+:-` $2y$1/s+:-`` `.-:+oydNMMMMNhs/-`` $2-m+$1NMMMMMMMMMMMMMMMMMMMNdhmNMMMmdhs+/-` $2-m+$1NMMMMMMMMMMMMMMMMMMMMmy+:` $2-N/$1dMMMMMMMMMMMMMMMds:` $2-N/$1hMMMMMMMMMmho:` $2-N/$1-:/++/:.` $2 :M+ :Mo :Ms :Ms :Ms :Ms :Ms :Ms :Ms :Ms ================================================ FILE: src/logo/ascii/netbsd2.txt ================================================ __,gnnnOCCCCCOObaau,_ $2 _._ $1__,gnnCCCCCCCCOPF"'' $2 (N\\$1XCbngg,._____.,gnnndCCCCCCCCCCCCF"___,,,,___ $2 \N\\$1XCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCOOOOPYvv. $2 \N\\$1XCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCPF"'' $2 \N\\$1XCCCCCCCCCCCCCCCCCCCCCCCCCOF"' $2 \N\\$1XCCCCCCCCCCCCCCCCCCCCOF"' $2 \N\\$1XCCCCCCCCCCCCCCCPF"' $2 \N\\$1\"PCOCCCOCCFP"" $2 \N\\ \N\\ \N\\ \NN\ \NN\ \NNA. \NNA, \NNN, \NNN\ \NNN\ \NNNA ================================================ FILE: src/logo/ascii/netbsd_small.txt ================================================ $2 \\$1`-______,----__ $2 \\ $1 __,---`_ $2 \\ $1 `.____ $2 \\$1-______,----`- $2 \\ $2 \\ $2 \\ $2 \\ ================================================ FILE: src/logo/ascii/nethydra.txt ================================================ ⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠰⣶⣶⣦⣤⣄⡀⠀ ⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢹⣦⣤⣌⣙⠻⢶⣄⠀ ⠀⠀⠀⠀⠀⠀⠀⢀⣀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢀⡴⠊⣤⣶⣶⣶⣦⣤⣼⡧⢤⡈⠙⠳⣦⡙⢷⣄⠀ ⠀⠀⣠⣴⣶⣿⣿⣿⣿⣿⣿⣶⣦⣀⠀⠀⠀⠀⠀⠀⠀⢀⣴⡿⢾⡷⣢⢄⣠⣬⣭⣝⣿⣿⣄⠈⠓⢄⠈⠳⡄⠙⢷ ⢀⣾⣿⡿⠛⠉⠀⠀⠀⠀⠉⠛⠿⣿⣷⣦⡀⠀⠀⠀⡖⠉⣠⠶⢛⣼⣡⡍⢉⠍⠉⠙⠻⣿⣿⣧⠀⠀⠁⠀⠀⠀⠀⠳⡀ ⢸⣿⡟⠁⠀⠀⠀⠀⠀⠀⠀⠀⠀⠈⠙⢿⣿⣦⣄⠀⠀⠈⠁⡠⠟⣫⣵⡇⠀⠀⠀⠀⠀⢈⣿⣿ ⢸⣿⡇⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠙⢿⣿⣷⣄⠀⠚⠀⠞⠋⠀⠀⠀⠀⠀⠀⢀⣼⣿⡟ ⠸⣿⣿⡀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⣀⠀⠀⠙⢿⣿⣷⣤⡀⠀⠀⠀⠀⠀⠀⢀⣠⣿⣿⠏ ⠀⠹⣿⣷⣄⠀⠀⠀⠀⠀⠀⠀⣀⣴⡞⠁⠀⠀⠀⠀⠈⠛⠿⣿⣿⣶⣶⣶⣶⣾⡿⠟⠋ ⠀⠀⠙⠿⣿⣷⣶⣦⣴⣶⣾⣿⠿⠋⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠉⠉⠉⠉⠉⠁ ⠀⠀⠀⠀⠈⠉⠛⠛⠛⠛⠉ ================================================ FILE: src/logo/ascii/netrunner.txt ================================================ .:oydmMMMMMMmdyo:` -smMMMMMMMMMMMMMMMMMMds- +mMMMMMMMMMMMMMMMMMMMMMMMMd+ /mMMMMMMMMMMMMMMMMMMMMMMMMMMMMm/ `hMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMy` .mMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMd` dMMMMMMMMMMMMMMMMMMMMMMNdhmMMMMMMMMMMh +MMMMMMMMMMMMMNmhyo+/-. -MMMMMMMMMMMM/ mMMMMMMMMd+:.` `mMMMMMMMMMMMMd MMMMMMMMMMMdy/. yMMMMMMMMMMMMMM MMMMMMMMMMMMMMMNh+` +MMMMMMMMMMMMMMM mMMMMMMMMMMMMMMMMMs -NMMMMMMMMMMMMMMd +MMMMMMMMMMMMMMMMMN. `mMMMMMMMMMMMMMMM/ dMMMMMMMMMMMMMMMMMy hMMMMMMMMMMMMMMMh `dMMMMMMMMMMMMMMMMM-+MMMMMMMMMMMMMMMd` `hMMMMMMMMMMMMMMMMmMMMMMMMMMMMMMMMy /mMMMMMMMMMMMMMMMMMMMMMMMMMMMMm: +dMMMMMMMMMMMMMMMMMMMMMMMMd/ -odMMMMMMMMMMMMMMMMMMdo- `:+ydmNMMMMNmhy+-` ================================================ FILE: src/logo/ascii/nexalinux.txt ================================================ **** ********** ****************** ************************ ****************************** *************** *************** ************ **** #*********** ********* ********** ********* ****** #****************# #***** * ************************ *# ****************************** #************** **************# ************ **** ************ ********* ********** ********* *** **************** *** ********************** ******************** ***** ***** ================================================ FILE: src/logo/ascii/nitrux.txt ================================================ `:/. `/yo `/yo `/yo .+:. `/yo .sys+:.` `/yo `-/sys+:.` `/yo ./sss+:.` `/yo .:oss+:-` `/yo ./o///:-` `/yo `.-:///////:` `/yo `.://///++//-`` `/yo `.-:////++++/-` `/yo `-://///++o+/-` `/yo `-/+o+++ooo+/-` `/s+:+oooossso/.` `//+sssssso:. `+syyyy+:` :+s+- ================================================ FILE: src/logo/ascii/nixos.txt ================================================ $1▗▄▄▄ $2▗▄▄▄▄ ▄▄▄▖ $1▜███▙ $2▜███▙ ▟███▛ $1▜███▙ $2▜███▙▟███▛ $1▜███▙ $2▜██████▛ $1▟█████████████████▙ $2▜████▛ $3▟▙ $1▟███████████████████▙ $2▜███▙ $3▟██▙ $6▄▄▄▄▖ $2▜███▙ $3▟███▛ $6▟███▛ $2▜██▛ $3▟███▛ $6▟███▛ $2▜▛ $3▟███▛ $6▟███████████▛ $3▟██████████▙ $6▜██████████▛ $3▟███████████▛ $6▟███▛ $5▟▙ $3▟███▛ $6▟███▛ $5▟██▙ $3▟███▛ $6▟███▛ $5▜███▙ $3▝▀▀▀▀ $6▜██▛ $5▜███▙ $4▜██████████████████▛ $6▜▛ $5▟████▙ $4▜████████████████▛ $5▟██████▙ $4▜███▙ $5▟███▛▜███▙ $4▜███▙ $5▟███▛ ▜███▙ $4▜███▙ $5▝▀▀▀ ▀▀▀▀▘ $4▀▀▀▘ ================================================ FILE: src/logo/ascii/nixos_old.txt ================================================ $1 ____ $2_______ ____ $1 /####\ $2\######\ /####\ $1 ######\ $2\######\ /#####/ $1 \######\ $2\######\ /#####/ $1 \######\ $2\######\/#####/ $1/\ $1 \######\ $2\###########/ $1/##\ $1 ________\######\______$2\#########/ $1/####\ $1 /#######################$2\#######/ $1/###### $1 /#########################$2\######\ $1/######/ $1 /###########################$2\######\ $1/######/ $1 ¯¯¯¯¯¯¯¯¯¯¯¯$2/######/$1¯¯¯¯¯¯¯¯¯$2\######$1/######/ $2 /######/ $2\####$1/######/________ $2 _____________/######/ $2\##$1/################\ $2 /###################/ $2\$1/##################\ $2 \##################/$1\ /###################/ $2 \################/$1##\ /######/¯¯¯¯¯¯¯¯¯¯¯¯¯ $2 ¯¯¯¯¯¯¯¯/######/$1####\ /######/ $2 /######/$1######\$2_________$1/######/$2____________ $2 /######/ $1\######\$2###########################/ $2 /######/ $1\######\$2#########################/ $2 ######/ $1/#######\$2#######################/ $2 \####/ $1/#########\$2¯¯¯¯¯¯\######\¯¯¯¯¯¯¯¯ $2 \##/ $1/###########\$2 \######\ $2 \/ $1/#####/\######\$2 \######\ $1 $1/#####/ \######\$2 \######\ $1 $1/#####/ \######\$2 \###### $1 $1\####/ \######\$2 \####/ $1 $1¯¯¯¯ ¯¯¯¯¯¯¯$2 ¯¯¯¯ ================================================ FILE: src/logo/ascii/nixos_old_small.txt ================================================ $1 \\ $2\\ // $1 ==\\__$2\\/ $1// $2 // $2\\$1// $2==// $1//== $2 //$1\\$2___$1// $2// $1/\\ $2\\== $1// \\ $2\\ ================================================ FILE: src/logo/ascii/nixos_small.txt ================================================ $1 ▗▄ $2▗▄ ▄▖ $1 ▄▄🬸█▄▄▄$2🬸█▛ $3▃ $6 ▟▛ ▜$3▃▟🬕 $6🬋🬋🬫█ $3█🬛🬋🬋 $6 🬷▛🮃$5▙ $4▟▛ $6 🮃 $5▟█🬴$4▀▀▀█🬴▀▀ $5▝▀ ▀▘ $4▀▘ ================================================ FILE: src/logo/ascii/nobara.txt ================================================ ⢀⣤⣴⣶⣶⣶⣦⣤⡀⠀⣀⣠⣤⣴⣶⣶⣶⣶⣶⣶⣶⣶⣤⣤⣀⡀ ⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣶⣤⡀ ⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣷⣄ ⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣷⣄ ⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣧ ⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⠟⠋⠉⠁⠀⠀⠉⠉⠛⠿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣧ ⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⠟⠁⠀⠀⠀⢀⣀⣀⡀⠀⠀⠀⠈⢻⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⡇ ⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⡏⠀⠀⠀⢠⣾⣿⣿⣿⣿⣷⡄⠀⠀⠀⠻⠿⢿⣿⣿⣿⣿⣿⣿⣿⣿⣿ ⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⠁⠀⠀⠀⣿⣿⣿⣿⣿⣿⣿⡇⠀⠀⠀⠀⠀⣀⣀⣬⣽⣿⣿⣿⣿⣿⣿ ⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⠀⠀⠀⠀⠈⠻⢿⣿⣿⡿⠟⠁⠀⠀⠀⢸⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿ ⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣇⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢸⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿ ⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣷⣤⣤⣄⣀⡀⠀⠀⠀⠀⠀⠀⠀⠀⢸⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿ ⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣷⣄⠀⠀⠀⠀⠀⠀⢸⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿ ⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣇⠀⠀⠀⠀⠀⢸⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿ ⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⡿⠟⠛⠉⠉⠛⠛⢿⣿⣿⠀⠀⠀⠀⠀⠸⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⡿ ⠘⢿⣿⣿⣿⣿⣿⣿⣿⡿⠋⠀⠀⠀⠀⠀⠀⠀⠀⠈⢿⠀⠀⠀⠀⠀⠀⠙⢿⣿⣿⣿⣿⣿⣿⣿⠟⠁ ⠈⠙⠛⠛⠛⠋⠁⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠉⠛⠛⠛⠛⠉⠁ ================================================ FILE: src/logo/ascii/nomadbsd.txt ================================================ _======__ (===============\ (===================\ _ _---__ (= ====- (= ====== (== ====== (== ====== (==\ \=-_ _=/ /====/ (==\ \========/ /====/ /====-_ (==\ \=====/ /==/ /===-- /================/ /===- \===========/ ================================================ FILE: src/logo/ascii/nuros.txt ================================================ ___╓╓___ _▄▄▓▓▀▀╜╜╨▀▓▓▓╗_ ╓▓▓▀² `╙▓▓╖ ╣▓▀ _▄▓▓▓▓▓▓W_ ╙▓▓ ╣▓╜ ,▓▓▓▓▓▓▓▓▓▓▓▓_ ²▓▓ ╒▓▌ ▓▓▓▓▓▓▓▓▓▓▓▓▓▓ ║▓m ╞▓▓ í▓▓▓▓▓▓▓▓▓▓▓▓▓▓h ╞▓╡ ²▓▓ ▓▓▓▓▓▓▓▓▓▓▓▓▓▓ ║▓h ║▓▄ ╙▓▓▓▓▓▓▓▓▓▓╜ ƒ▓▓ ╙▓▓_ ⁿ╙╨╝╝╝╜² _╢▓╜ ╙▓▓╗__ _╗▓▓╜ `╙╝▓▓▓▓▓▓▓▓╝╙ ================================================ FILE: src/logo/ascii/nurunner.txt ================================================ ,xc ;00cxXl ;K0, .xNo. :KO' .lXx. cXk. ;xl cXk. cXk. ;k:.,xo. cXk. .lXx. :x::0MNl,dd. :KO, .xNx. cx;:KMMMMMNo'dx. ;KK; .dNl. cd,cXMMMMMMMMMWd,ox' 'OK: ;WK. 'K,.KMMMMMMMMMMMMMWc.Kx lMO 'OK: 'dl'xWMMMMMMMMMM0::x: 'OK: .kNo .xo'xWMMMMMM0;:O: ;KK; .dXd. .do,oNMMO;ck: ;00, oNd. .dx,;'cO; ;K0, oNx. okk; ;K0, lXx. :KO' cKk' cXk. ;00:lXx. ,kd. ================================================ FILE: src/logo/ascii/nutyx.txt ================================================ . . ... ... .... .........--. ..-++-----....--++++++---. .-++++++-. .-++++++++++++-----.. .--... .++..-+++--.....-++++++++++--.. . .-+-. .**- .... ..-+----.. .+++. .*+. + -++-----. .+++++- ++. .*+. .....-+++-----. -+++-++. .+. .-+***++***++--++++. . -+-. -- -. -*- ...... ..--. .-. .+- . -+. . .+- +. -- -- -+----. .- -++-.+. . .++. -- +. ----. . .+. .. - . . ================================================ FILE: src/logo/ascii/obarun.txt ================================================ ,;::::; ;cooolc;, ,coool; ,loool, loooo; :ooool cooooc ,:ccc; looooc :oooooool cooooo ;oooooooooo, :ooooo; :ooooooooooo oooooo oooooooooooc :oooooo :ooooooooool loooooo ;oooooooool looooooc .coooooooc cooooooo: ,;co; ,ooooooool; ,:loc cooooooooooooloooooc ;ooooooooooooool; ;looooooolc; ================================================ FILE: src/logo/ascii/obrevenge.txt ================================================ __ __ _@@@@ @@@g_ _@@@@@@ @@@@@@ _@@@@@@M W@@@@@@_ j@@@@P ^W@@@@ @@@@L____ _____Q@@@@ Q@@@@@@@@@@j@@@@@@@@@@ @@@@@ T@j@ T@@@@@ @@@@@ ___Q@J@ _@@@@@ @@@@@fMMM@@j@jggg@@@@@@ @@@@@ j@j@^MW@P @@@@ Q@@@@@ggg@@f@ @@@@@@L ^@@@@WWMMP ^ Q@@@@ @@@@@_ _@@@@l W@@@@@g_____g@@@@@P @@@@@@@@@@@@@@@@l ^W@@@@@@@@@@@P ^TMMMMTll ================================================ FILE: src/logo/ascii/obsidianos.txt ================================================ $2 *+++++++# $2#*******###$3 @ $2#*******###$3 @ $2#*******###$3 @ @ $2#*******##%$3 @ $3@@@@@@@@@ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @@@@@@@@@ @$1#*******##%$3 @$1 ##########%$3 @$1 ##########%$3 @$1 ##########% ########% ================================================ FILE: src/logo/ascii/omnios.txt ================================================ ____ __ __ _ _ _ / __ \ | \/ || \ | || | | | | || || \| || | | |__| || |\/| || , `$2_$1||$2_$1| $2____$1 \____/ |_| |_||_|\$2/ __ \ / ___| | | | ||(__ $3community$2 | |__| | ___)| $3edition$2 \____/ |____/ ================================================ FILE: src/logo/ascii/openbsd.txt ================================================ $3 _ (_) $1 | . $1 . |L /| . $3 _ $1 _ . |\ _| \--+._/| . $3(_) $1 / ||\| Y J ) / |/| ./ J |)'( | ` F`.'/ $3 _ $1 -<| F __ .-< $3(_) $1 | / .-'$3. $1`. /$3-. $1L___ J \ < $3\ $1 | | $5O$3\$1|.-' $3 _ $1 _J \ .- \$3/ $5O $3| $1| \ |$1F $3(_) $1 '-F -<_. \ .-' `-' L__ __J _ _. >-' $1)$4._. $1|-' $1 `-|.' /_. $4\_| $1 F /.- . _.< /' /.' .' `\ /L /' |/ _.-'-\ /'J ___.---'\| |\ .--' V | `. ` |/`. `-. `._) / .-.\ \ ( `\ `.\ ================================================ FILE: src/logo/ascii/openbsd_small.txt ================================================ _____ \- -/ \_/ \ | $2O O$1 | |_ < ) 3 ) / \ / /-_____-\ ================================================ FILE: src/logo/ascii/openeuler.txt ================================================ `.cc.` ``.cccccccc..` `.cccccccccccccccc.` ``.cccccccccccccccccccccc.`` `..cccccccccccccccccccccccccccc..` `.ccccccccccccccc$2/++/$1ccccccccccccccccc.` .ccccccccccccccc$2mNMMNdo+oso+$1ccccccccccc. .cccccccccc$2/++odms+//+mMMMMm/:+syso/$1cccc .ccccccccc$2yNNMMMs:::/::+o+/:$1c$2dMMMMMm$1cccc .ccccccc$2:+NmdyyhNNmNNNd:$1ccccc$1$2:oyyyo:$1cccc .ccc$2:ohdmMs:$1cccc$2+mNMNmy$1ccccccccccccccccc .cc$2/NMMMMMo////:$1c$2:///:$1cccccccccccccccccc .cc$2:syysyNMNNNMNy$1ccccccccccccccccccccccc .cccccccc$2+MMMMMNy$1c$2:/+++/$1cccccccccccccccc .ccccccccc$2ohhhs/$1c$2omMMMMNh$1ccccccccccccccc .ccccccccccccccc$2:MMMMMMMM/$1cccccccccccccc .cccccccccccccccc$2sNNNNNd+$1cccccccccccccc. `..cccccccccccccccc$2/+/:$1cccccccccccccc..` ``.cccccccccccccccccccccccccccc.`` `.cccccccccccccccccccccc.` ``.cccccccccccccc.`` `.cccccccc.` `....` ================================================ FILE: src/logo/ascii/openindiana.txt ================================================ $2 .sy/ .yh+ $1-+syyyo+- $2 /+. $1+ddo/---/sdh/ $2 ym- $1`hm+ `sms$2 ym-```````.-. $1sm+ sm/ $2 ym- +s $1hm. /mo $2 ym- /h $1omo ym: $2 ym- `os` $1smo` .ym+ $2 ym- .os- `` $1:ymy+///oyms- $2 ym- .+s+. ..` $1`:+oo+/-` $2 -//oyo- -:` .:oys/. +- `./oyys/. h+` `.-:+oyyyo/-` `/ossssysso+/-.` ================================================ FILE: src/logo/ascii/openkylin.txt ================================================ /KKK] KKKKKKK` ]KKKK\ KKKKK/ /KKKKKKKKK\ KKKK/ ,KKKKKKKKKKKK^ ,]KKK =KKK` /KKKKKKOOOOOO` ,KKKKKK =KK /` [\OOOOOOO\ \KKKKK =K ,OOOOOOO` ,KKKKK =^ \OOOOOO ,KKKK ^ OOOOOO^ *KKK^ =OOOOO^ OOKK^ OOOOOO^ \OOOK\ /OOOOOO` OOOOOO] ,OOOOOOO^ ,OOOOOOOO\] ,[OOOOOOOOO/ \OOOOOOOOOOOOOOOOOOOOO` [OOOOOOOOOOOOOOOO/` ,[OOOOOOOOO] ================================================ FILE: src/logo/ascii/openmamba.txt ================================================ ````` .-/+ooooooooo+/:-` ./ooooooooooooooooooo+:. -+oooooooooooooooooooooooo+- .+ooooooooo+/:---::/+ooooooooo+. :oooooooo/-` `-/oo$2s´$1oooo.$2s´$1 :ooooooo/` `$2sNds$1ooo$2sNds$1 -ooooooo- $2:dmy$1ooo$2:dmy$1 +oooooo: :oooooo- .ooooooo .://:` :oooooo+ ./+o+:` -ooooooo` `oooooo+ `ooooooo: /oooooo+ -ooooooo: :ooooooo. :ooooooo+. .+ooooooo: :oooooooo+-` `-+oooooooo: .+ooooooooo+/::::://oooooooooo+. -+oooooooooooooooooooooooo+- .:ooooooooooooooooooo+:. `-:/ooooooooo+/:.` `````` ================================================ FILE: src/logo/ascii/openmandriva.txt ================================================ `````` `-:/+++++++//:-.` .:+++oooo+/:.`` `` `:+ooooooo+:. `-:/++++++/:.` -+oooooooo:` `-++o+/::::://+o+/- `/ooooooooo- -+oo/.` `-/oo+. `+ooooooooo. :os/` .+so: +sssssssss/ :ss/ `+ss- :ssssssssss` sss` .sso ossssssssss `yyo sys `sssssssssss` `yys `yys `sssssssssss: +yy/ +yy: oyyyyyyyyyys. `oyy/` `+yy+ :yyyyyyyyyyyo. `+yhs:. `./shy/ oyyyyyyyyyyys:` .oyhys+:----/+syhy+. ` `syyyyyyyyyyyyo-` .:osyhhhhhyys+:``.:` `oyyyyyyyyyyyyys+-`` `.----.```./oo. /yhhhhhhhhhhhhhhyso+//://+osyhy/` `/yhhhhhhhhhhhhhhhhhhhhhhhhy/` `:oyhhhhhhhhhhhhhhhhhhyo:` .:+syhhhhhhhhys+:-` ``....`` ================================================ FILE: src/logo/ascii/openstage.txt ================================================ /(/ .(((((((, /(((((((((/ .(((((/,/(((((, *(((((* ,(((((/ (((((* .*/(( *((((/ (//(/* /((((* ((((((((((, . /((((* (((((((((((((. ((. *((((/ ,(((((((( ,(((/ (((((/ ** ,((((((* /(((((. .(((((/ //(((* *(((((/ .(((((, ((/ .(((((/. .(((((, /((((* ,(((((((/ ,((((( /(((((((((((((((((((/. /(((((((((/ /(((((((((((((((((, /(((((((((((/ */(((((//*. */((/(/(/* ================================================ FILE: src/logo/ascii/opensuse.txt ================================================ $2.;ldkO0000Okdl;. .;d00xl:^''''''^:ok00d;. .d00l' 'o00d. .d0Kd'$1 Okxol:;,. $2:O0d .OK$1KKK0kOKKKKKKKKKKOxo:, $2lKO. ,0K$1KKKKKKKKKKKKKKK0P^$2,,,$1^dx:$2 ;00, .OK$1KKKKKKKKKKKKKKKk'$2.oOPPb.$1'0k.$2 cKO. :KK$1KKKKKKKKKKKKKKK: $2kKx..dd $1lKd$2 'OK: dKK$1KKKKKKKKKOx0KKKd $2^0KKKO' $1kKKc$2 dKd dKK$1KKKKKKKKKK;.;oOKx,..$2^$1..;kKKK0.$2 dKd :KK$1KKKKKKKKKK0o;...^cdxxOK0O/^^' $2.0K: kKK$1KKKKKKKKKKKKK0x;,,......,;od $2lKk '0K$1KKKKKKKKKKKKKKKKKKKK00KKOo^ $2c00' 'kK$1KKOxddxkOO00000Okxoc;'' $2.dKk' l0Ko. .c00l' 'l0Kk:. .;xK0l' 'lkK0xl:;,,,,;:ldO0kl' '^:ldxkkkkxdl:^' ================================================ FILE: src/logo/ascii/opensuse_leap.txt ================================================ ==== ====== ==== ====+ +==== +==== +===+ ==== ==== ==== +=== +==== ==== +==== ===== ==== +===+ ===== ==+ ===== +===+ === ==== ==== ===== ===== ==== ======= ==== ==== === ==== ====+ ==== ==== ===== ====== == ================================================ FILE: src/logo/ascii/opensuse_leap_old.txt ================================================ .-++:. ./oooooo/- `:oooooooooooo:. -+oooooooooooooooo+-` ./oooooooooooooooooooooo/- :oooooooooooooooooooooooooo: ` `-+oooooooooooooooooooo/- ` `:oo/- .:ooooooooooooooo+:` `-+oo/. `/oooooo:. -/oooooooooo/. ./oooooo/. `:+ooooo+-` `:+oooo+- `:oooooo+:` .:oooooo/. .::` -+oooooo/. -/oooooo:. ./oooooo+- `:+ooooo+-:+oooooo:` ./oooooooooo/. -/oooo+:` `:/. ================================================ FILE: src/logo/ascii/opensuse_microos.txt ================================================ ⣀⣠⣴⣶⣶⣿⣿⣿⣿⣶⣶⣦⣄⣀ ⢀⣴⣾⣿⠿⠛⠉⠉ ⠉⠉⠛⠿⣿⣷⣦⡀ ⣴⣿⡿⠋ ⠙⢿⣿⣦ ⣾⣿⡟ ⣠⣴⣶⣿⣿⣶⣦⣄ ⢻⣿⣷ ⣠⣤⣤⣤⣤⣤⣤⣼⣿⣿ ⣼⣿⡟⠉ ⠉⢻⣿⣧ ⣿⣿⣧⣤⣤⣤⣤⣤⣤⣄ ⠙⠛⠛⠛⠛⠛⠛⢻⣿⣿ ⢻⣿⣧⡀ ⢀⣼⣿⡟ ⣿⣿⡟⠛⠛⠛⠛⠛⠛⠋ ⢿⣿⣇ ⠙⠿⣿⣿⣿⣿⠿⠋ ⣸⣿⡿ ⠈⢻⣿⣧⣀ ⣀⣾⣿⡟⠁ ⠙⠻⣿⣷⣦⣄⣀ ⣀⣠⣴⣾⣿⠟⠋ ⠉⠛⠿⢿⣿⣿⣿⣿⣿⣿⡿⠿⠛⠉ ================================================ FILE: src/logo/ascii/opensuse_slowroll.txt ================================================ _,......,_ _aaQQQQQQWQQQQQQQaa. ajQQQ??^' '^??QQQw. ,aQQP^ ?4QQa wQQP ^4QQa ,QQP' ,aaaaa. ^$Q6, ,QQP ajQQQWWWWQQQw. 4QQ QQP aQQP? ?$QQ. 4Q6 jQQ jQQ' ?WQr 'QQb QQf ]QQ' ]QQ QQ6 QQf ]QQ ]QQ) QQQ QQ6 'QQ6 ]QQ QQf ]QQ. ^4QQaa .QQP ,QQ' $QQ. ^?QQQQf _yQP ,QQP ^WQQ6. aQQ? _QQP ^4QQQ6a. ajQQP' aQQP ?QQQQQWQaaaaaaajQWQP? aQQP' ?QQQQP????????^' saWQP? ^?QQQgaaa._ __aaaQQQP?' '^?QWQQQQQQQQ@??^' ================================================ FILE: src/logo/ascii/opensuse_small.txt ================================================ _______ __| __ \ / .\ \ \__/ | _______| \_______ __________/ ================================================ FILE: src/logo/ascii/opensuse_tumbleweed.txt ================================================ ,..., .,:lloooooc;. ,ool' oo,;oo: .lo' oo. oo: .oo. oo. oo: :ol oo. 'oo :oo .oo. .oo. .oooooooooooooo. .oo. ;oo. .oo. 'oo, .oo. "ooc,',,,,,,,,,,:ooc,,,,,,,,,,, ':cooooooooooooooooooooooooool;. .oo. .oo; .oo. .oo. .oo. 'oooooooooo:ooo. .oo. 'oo. col .oo' 'oo col coo 'oo oo' coc 'oo .lo, `oo, 'oo .:oo 'ooooc,, ,:lol `''"clc"' ================================================ FILE: src/logo/ascii/opensuse_tumbleweed2.txt ================================================ ⣀⣤⣤⣶⣶⣶⣶⣶⣤⣄⡀ ⣠⣾⣿⣿⡿⠿⠛⠛⣿⣿⣿⣿⣿⣶⣄ ⢠⣾⣿⣿⠛⠁ ⣿⣿⣿⠉⠻⣿⣿⣦ ⢀⣿⣿⡿⠁ ⣿⣿⣿ ⠙⣿⣿⣧ ⢸⣿⣿⠃ ⣿⣿⣿ ⢹⣿⣿⡀ ⢸⣿⣿⣤⣤⣤⣤⣤⣤⣤⣤⣿⣿⣿ ⢸⣿⣿⡇ ⠘⣿⣿⣿⠿⠿⠿⠿⠿⠿⠿⠿⠿⠿ ⢸⣿⣿⡇ ⠹⣿⣿⣷⡀ ⢸⣿⣿⡇ ⠙⢿⣿⣿⣷⣤⣄⣀⣀⣀⣀⣀⣀⣀⣀⣸⣿⣿⣇⣀⣀⣀⣀⣀⣀⣀⣀ ⠉⠻⠿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣶⣤⡀ ⠈⠉⠉⠉⠉⠉⠉⠉⠉⢹⣿⣿⡏⠉⠉⠉⠉⠉⠉⠉⠉⠙⠛⢿⣿⣿⣷⡀ ⢸⣿⣿⡇ ⠈⢻⣿⣿⣆ ⢸⣿⣿⡇ ⢰⣶⣶⣶⣶⣶⣶⣶⣶⣶⣿⣿⣿⡄ ⢸⣿⣿⡇ ⢸⣿⣿⠛⠛⠛⠛⠛⠛⠛⠛⣿⣿⡇ ⠘⣿⣿⡇ ⢸⣿⣿ ⢰⣿⣿⡇ ⢻⣿⣿⡄ ⢸⣿⣿ ⢀⣾⣿⡿⠁ ⢻⣿⣿⣦⣀⢸⣿⣿ ⢀⣴⣿⣿⡿⠁ ⠙⢿⣿⣿⣿⣿⣿⣤⣴⣶⣾⣿⣿⡿⠋ ⠈⠙⠻⠿⠿⠿⠿⠿⠛⠋⠁ ================================================ FILE: src/logo/ascii/opensuse_tumbleweed_old.txt ================================================ ...... .,cdxxxoc,. .:kKMMMNWMMMNk:. cKMMN0OOOKWMMXo. A ;0MWk:' ':OMMk. ;WMK;' 'lKMMNM, :NMK' 'OMW; cMW; WMMMN ,XMK' oMM. .MMc ''^*~l. xMN: KM0 'MM. .NMO oMM .MM, .kMMl xMN KM0 .kMM0' .dl>~,. .WMd 'XM0. ,OMMK' OMMM7' .XMK *WMO:. .;xNMMk' NNNMKl. .xWMx ^ONMMNXMMMKx; V 'xNMWKkxllox0NMWk' ''''' ':dOOXXKOxl' ================================================ FILE: src/logo/ascii/opensuse_tumbleweed_small.txt ================================================ .oooo. o o o ooooo oo o oo 'oooooooooooo. oo o oo ooooo o o o 'oooo' ================================================ FILE: src/logo/ascii/openwrt.txt ================================================ _______ | |.-----.-----.-----. | - || _ | -__| | |_______|| __|_____|__|__| |__| ________ __ | | | |.----.| |_ | | | || _|| _| |________||__| |____| ================================================ FILE: src/logo/ascii/opnsense.txt ================================================ .''''''''''''''''''''''''''''''''''' oocc:::::::::::::::::::::::::::::::cox ;00; o0O .,:' .;;;;;;;;;;;;;;;;;;;;;;;;;; ;:, .',;;cxOOOOOOOOOOOOOOOOOOOOOOOkd:;;,.. .,cll:' ':llc,. ,;;:okxdxd: :dxdxko:;;, .xxxx0XNNK0O. .O0KNNX0xxxx. ,$2cc:$1,. .,$2:cc$1, ........;$2ccc:$1;. .;$2:ccc$1;........ $2ccccccccccccccc ccccccccccccccc$1 ........;$2ccc:$1;. .;$2:ccc$1;........ ,$2cc:$1,. .,$2:cc$1, .xxxx0XNNK0O. .O0KNNX0xxxx. ,;;:okxdxd: :dxdxko:;;, .,cll:' ':llc,. .,,;,ckOOOOOOOOOOOOOOOOOOOOOOOOx;,;,'. .:l' ........................... ;:; lOk' cdd ;lccccccccccccccccccccccccccccccccccc:. ================================================ FILE: src/logo/ascii/oracle.txt ================================================ `-/+++++++++++++++++/-.` `/syyyyyyyyyyyyyyyyyyyyyyys/. :yyyyo/-...............-/oyyyy/ /yyys- .oyyy+ .yyyy` `syyy- :yyyo /yyy/ .yyyy` `syyy- /yyys. .oyyyo /yyyyo:-...............-:oyyyy/` `/syyyyyyyyyyyyyyyyyyyyyyys+. `.:/+ooooooooooooooo+/:.` ================================================ FILE: src/logo/ascii/orchid.txt ================================================ $2 .==. .-$3#$1@@$3#$2-. .-$3##$1@@@@$3##$2-. .-$3##$1@@@@@@@@$3##$2-. :*$1@@@@@$3####$1@@@@@$2*: ..:*$1@@@@$2==--==$1@@@@$2*:.. .-*$1%%$3#$2==$3#$1@@$3#$2====$3#$1@@$3#$2==$3#$1%%$2*-. .-$3#$1@@@@@$3##$2==$3#$1@@$2++$1@@$3##$2==$3#$1@@@@@$3#$2-. .-$3#$1@@@@@$2#$1@@@$3#$2++#====$3#$2++#$1@@@$2#$1@@@@@$3#$2-. .-$3#$1@@@@@$3#$2-==**$3###$2+:--:+$3###$2**==-$3#$1@@@@@$3#$2-. .-$3#$1@@@@@$3#$2-==**$3###$2+:--:+$3###$2**==-$3#$1@@@@@$3#$2-. .-$3#$1@@@@@$2#$1@@@$3#$2++#====$3#$2++#$1@@@$2#$1@@@@@$3#$2-. .-$3#$1@@@@@$3##$2==$3#$1@@$2++$1@@$3##$2==$3#$1@@@@@$3#$2-. .-*$1%%$3#$2==$3#$1@@$3#$2====$3#$1@@$3#$2==$3#$1%%$2*-. ..:*$1@@@@$2==--==$1@@@@$2*:.. :*$1@@@@@$3####$1@@@@@$2*: .-$3##$1@@@@@@@@$3##$2-. .-$3##$1@@@@$3##$2-. .-$3#$1@@$3#$2-. .==. ================================================ FILE: src/logo/ascii/orchid_small.txt ================================================ $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#- :##: ================================================ FILE: src/logo/ascii/oreon.txt ================================================ @@@@@@@@@@ @@@@@@@@@@@@@@@@@@ @@@@@@@@@@@@@@@@@@@@@@@@@@ @@@@@@@@@@ @@@@@@@@@@@ @@@@@@@@ @@@@@@@@@ @@@@@@@@ @@@@@@@@ @@@@@@@ @@@@@@@ @@@@@@@@ @@@@@@@ @@@@@@@@ @@@@@@ @@@@@@@@@ @@@@@@ @@@@@@@@@@ @@@@@@ @@@@@@@@@@ @@@@@@ @@@@@@@@@ @@@@@@ @@@@@@@@ @@@@@@ @@@@@@@@ @@@@@@@ @@@@@@@ @@@@@@@ @@@@@@@@ @@@@@@@@ @@@@@@@@ @@@@@@@@ @@@@@@@@@@ @@@@@@@@@@@ @@@@@@@@@@@@@@@@@@@@@@@@ @@@@@@@@@@@@@@@@@@ @@@@@@@@@@ ================================================ FILE: src/logo/ascii/os2warp.txt ================================================ .-==: . . -+:::+= * *- = -. -%* ** *# :%- . :.*=: -%% %@: -@% =%@%%*: - :. -%= -@@: :@@- -+#%%+ .: :*. -@@: :@@-- -% = .* .%@- -%% ++ # . -: . #* ** =.:*: +- = #%%%%#+-*: -=====- .:- .. $2 =*+ -+*: .*+ :*+= -+*++**- +*-++++ -++-:. .:-=++.=*+. =*- -***:-+:*++=*++*: +*+=:-**+=**++- :- :***==*+.=++ +*+=++*=.=*+. .=-=*+***+*= +*+ :**+=++ . -*+ +*+ .=: ================================================ FILE: src/logo/ascii/os_elbrus.txt ================================================ ▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄ ██▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀██ ██ ██ ██ ███████ ███████ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ███████ ███████ ██ ██ ██ ██ ██▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄██ ██ ▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀██ ██ ██ ███████████████████████████ ================================================ FILE: src/logo/ascii/osmc.txt ================================================ -+shdmNNNNmdhs+- .+hMNho/:..``..:/ohNMh+. :hMdo. .odMh: -dMy- -yMd- sMd- -dMs hMy +. .+ yMh yMy dMs. .sMd yMy :Mm dMNMs` `sMNMd `mM: yM+ dM//mNs``sNm//Md +My mM- dM: +NNNN+ :Md -Mm mM- dM: `oNN+ :Md -Mm yM+ dM/+NNo` :Md +My :Mm` dMMNs` :Md `mM: yMy dMs` -ms yMy hMy +. yMh sMd- -dMs -dMy- -yMd- :hMdo. .odMh: .+hMNho/:..``..:/ohNMh+. -+shdmNNNNmdhs+- ================================================ FILE: src/logo/ascii/pacbsd.txt ================================================ :+sMs. `:ddNMd- -o--` -sMMMMh: `+N+`` yMMMMMs` .....-/-... `mNh/ yMMMMMmh+-`:sdmmmmmmMmmmmddy+-``./ddNMMm yNMMNMMMMNdyyNNMMMMMMMMMMMMMMMhyshNmMMMm :yMMMMMMMMMNdooNMMMMMMMMMMMMMMMMNmy:mMMd +MMMMMMMMMmy:sNMMMMMMMMMMMMMMMMMMMmshs- :hNMMMMMMN+-+MMMMMMMMMMMMMMMMMMMMMMMs. .omysmNNhy/+yNMMMMMMMMMMNMMMMMMMMMNdNNy- /hMM:::::/hNMMMMMMMMMMMm/-yNMMMMMMN.mMNh` .hMMMMdhdMMMMMMMMMMMMMMmo `sMMMMMMN mMMm- :dMMMMMMMMMMMMMMMMMMMMMdo+ oMMMMMMN`smMNo` /dMMMMMMMMMMMMMMMMMMMMMNd/` :yMMMMMN:-hMMM. :dMMMMMMMMMMMMMMMMMMMMMNh` oMMMMMMNo/dMNN` :hMMMMMMMMMMMMMMMMMMMMMMNs--sMMMMMMMNNmy++` sNMMMMMMMMMMMMMMMMMMMMMMMmmNMMMMMMNho::o. :yMMMMMMMMMMMMMNho+sydNNNNNNNmysso/` -// /dMMMMMMMMMMMMMs- ````````..`` .oMMMMMMMMMMMMNs` ./y:` +dNMMNMMMMMMMmy` ``./ys. `/hMMMMMMMMMMMNo-`` `.+yy+-` `-/hmNMNMMMMMMmmddddhhy/-` `-+oooyMMMdsoo+/:. ================================================ FILE: src/logo/ascii/panwah.txt ================================================ HHH HAAAH HHH HAAAAH HAAAH HAAAAAAH HAAAAH HAAAAAAH HAAAAAH HAAAAAAAAH$2WWWWWWWWWWWWWWWW $1HAAAAAH HAAAAAAAAH$2WWWWWWWWWWWWWWWWWWWW$1 HAAAAAH HAA$2WWWWWWWWWWWWWWWWWWWWWWWWWWWWW$1AAAAAH$2 WWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWW$1WAH$2 WWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWW WWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWW WWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWW WWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWW WWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWW WWWWWWW$1AAA$2WWWW WWWWWWWWWWWWWWWWWWWWWWWWWWW WWWW$1AAA$2WWWWW WWWWWWW WWWWWWWWWWWWWWW WW$1AAA$2WWWWWWWWWWWWWWWWW WWWWW$1AAA$2WWWWWWWW $1AAA$2WWWWW$1OOOOOOOOOOO$2WWWWWWWWWWW$1AAA$2WWWWWW $1OOOO$3GGGGGGG$1OOOO$2WWWWWWWWWW$1AAA$2WWWW $1OOO$3GGGGGGG$1OOO$2WWWWWWWWWWWW$1AAA$2W $1OOOOOOOOO ================================================ FILE: src/logo/ascii/parabola.txt ================================================ `.-. `. `.` `:++. `-+o+. `` `:+/. `:+/. `-+oooo+ ``-::-.:+/. `:+/. `-+oooooo+ `.-:///- ..` .-. `-+oooooooo- `..-..` `+ooooooooo: `` :oooooooo/ `ooooooo: `oooooo: -oooo+. +ooo/` -ooo- `+o/. /+- //` -. ================================================ FILE: src/logo/ascii/parabola_small.txt ================================================ __ __ __ _ .`_//_//_/ / `. / .` / .` /.` /` ================================================ FILE: src/logo/ascii/parch.txt ================================================ ,:lodddd. .:clooood. ;clllooooc ;cclllllloo .cccccllllll . ,cccclllll ':::;; ccccclll; .:::cccccccccccll; ;::::ccccllllllcll: .;::::cccclllloool::; ;;;::::cccclllolc::::;. ;;;::::cccclllccc:::::;. ;;;::::cccclccccc::::::;. ;;;;::::::llcccccc:::::' ;;;;:; ,clllccccccc:: .;; .cllllllcccccc::;::::' .'''''''''',:lddoooolll '.....'''',cdddooooollll ........':oddddoooolllllc ....';ldddddooooolllllc: ,cdddddddooooollllccc :ddddddoooolllllccc ;ddooooolllllcc. :ooollllc. c' ================================================ FILE: src/logo/ascii/pardus.txt ================================================ .smNdy+- `.:/osyyso+:.` -+ydmNs. /Md- -/ymMdmNNdhso/::/oshdNNmdMmy/. :dM/ mN. oMdyy- -y `-dMo .Nm .mN+` sMy hN+ -: yMs `+Nm. `yMMddMs.dy `+` sMddMMy` +MMMo .` . oMMM+ `NM/ `````.` `.````` +MN` yM+ `.-:yhomy ymohy:-.` +My yM: yo oy :My +Ms .N` `N. +h sM+ `MN - -::::::- : :o:+`NM` yM/ sh -dMMMMd- ho +y+My .dNhsohMh-//: /mm/ ://-yMyoshNd` `-ommNMm+:/. oo ./:+mMNmmo:` `/o+.-somNh- :yy: -hNmos-.+o/` ./` .s/`s+sMdd+``+ddMs+s`/s. `/. : -y. -hNmddmNy. .y- : -+ `..` +- ================================================ FILE: src/logo/ascii/parrot.txt ================================================ `:oho/-` `mMMMMMMMMMMMNmmdhy- dMMMMMMMMMMMMMMMMMMs` +MMsohNMMMMMMMMMMMMMm/ .My .+dMMMMMMMMMMMMMh. + :NMMMMMMMMMMMMNo `yMMMMMMMMMMMMMm: /NMMMMMMMMMMMMMy` .hMMMMMMMMMMMMMN+ ``-NMMMMMMMMMd- /MMMMMMMMMMMs` mMMMMMMMsyNMN/ +MMMMMMMo :sNh. `NMMMMMMm -o/ oMMMMMMM. `NMMMMMM+ +MMd/NMh mMm -mN` /MM `h: dM` . :M- d: -+ - ================================================ FILE: src/logo/ascii/parsix.txt ================================================ $2-/+/:. $2.syssssys. $1.--. $2ssssssssso$1 ..--. :++++++: $2+ssssssss+$1 ./++/+++: /+++++++++.$2.yssooooy`$1-+///////o- /++++++++++.$2+soooos:$1:+////////+- :+++++////o-$2oooooo-$1+/////////- `-/++//++-$4.-----.-$1:+/////:- $3-://::--$1-:/:$4.--.````.--.$1:::-$3--::::::. $3-/:::::::://:$4.:-` `-:$3`:/:::::::--/- $3/::::::::::/-$4--. .-.$3-/://///::::/ $3-/:::::::::/:$4`:-. .-:$3`:///////////- `$3-::::--$1.-://.$4---....---$1`:+/:-$3--::::-` $1-/+///+o/-$4.----.$1.:oo+++o+. $1-+/////+++o:$2syyyyy.$1o+++++++++: $1.+////+++++-$2+sssssy+$1.++++++++++\ $1.+:/++++++.$2.yssssssy-$1`+++++++++: $1:/+++++- $2+sssssssss $1-++++++- $1`--` $2+sssssssso $1`--` $2+sssssy+` $2`.::-` ================================================ FILE: src/logo/ascii/pcbsd.txt ================================================ .. s. +y yN -MN `. :NMs `m .yMMm` `No `-/+++sdMMMNs+-`+Ms `:oo+-` .yMMMMy` `-+oNMh -oo- +NMMMM/ oMMh- .s+` ` oMMMMM/ - oMMMhy. +s`- :: :MMMMMd -o `mMMMy`s+ y+ h .Ny+oNMMMMMN/ sh+NMMMMo +y s+ .ds -NMMMMMMMMMMNdhdNMMMMMMh` +s -h .NM` `hMMMMMMMMMMMMMMNMMNy: h- y- hMN` hMMmMMMMMMMMMNsdMNs. -y m` mMMy` oMMNoNMMMMMMo` sMMMo `m m` :NMMMdyydMMMMo+MdMMMs sMMMd` `m h- `+ymMMMMMMMM--M+hMMN/ +MMMMy -h :y `.sMMMMM/ oMM+.yMMNddNMMMMMm y: y: `s dMMN- .MMMM/ :MMMMMMMMMMh :y `h: `mdmMMM/ yMMMMs sMMMMMMMMN- :h` so -NMMMN /mmd+ `dMMMMMMMm- os :y: `yMMM` `+NMMMMMMNo`:y: /s+`.omy /NMMMMMNh/.+s: .+oo:-. /mdhs+::oo+. -/o+++++++++++/- ================================================ FILE: src/logo/ascii/pclinuxos.txt ================================================ mhhhyyyyhhhdN dyssyhhhhhhhhhhhssyhN Nysyhhyo/:-.....-/oyhhhssd Nsshhy+. `/shhysm dohhy/ -shhsy dohhs` /hhys N+hho $2+ssssss+- .+syhys+ $1/hhsy ohhh` $2ymmo++hmm+`smmy/::+y` $1shh+ +hho $2ymm- /mmy+mms $1:hhod /hh+ $2ymmhhdmmh.smm/ $1.hhsh +hhs $2ymm+::-` /mmy` ` $1/hh+m yyhh- $2ymm- /dmdyosyd` $1`yhh+ ohhy` $2://` -/+++/- $1ohhom N+hhy- `shhoh sshho. `+hhyom dsyhhs/. `:ohhhoy dysyhhhso///://+syhhhssh dhyssyhhhhhhyssyyhN mddhdhdmN ================================================ FILE: src/logo/ascii/pearos.txt ================================================ .+yh sMMMo sMMN+ +o: $2 ./oyyys+. :dMMMMMMMMMm/ :MMMMMMMMMMMMMy yMMMMMMMMMMMMMN $3 mMMMMMMMMMMMMs` yMMMMMMMMMMMMo -mMMMMMMMMMMMMM` oMMMMMMMMMMMMMMM` $4 oMMMMMMMMMMMMMMMMy .MMMMMMMMMMMMMMMMMMy` +MMMMMMMMMMMMMMMMMMMMy/` /MMMMMMMMMMMMMMMMMMMMMMMNds $5 `mMMMMMMMMMMMMMMMMMMMMMMMM/ .mMMMMMMMMMMMMMMMMMMMMMM+ `oNMMMMMMMMMMMMMMMMMMd- `+hMMMMMMMMMMMMMms- -/osyhhyso:. ================================================ FILE: src/logo/ascii/pengwin.txt ================================================ $3 ...` $3 `-///:-` $3 .+$2ssys$3/ $3 +$2yyyyy$3o $2 $2 -yyyyyy: $2 `.:/+ooo+/:` -yyyyyy+ $2 `:oyyyyyys+:-.`syyyyyy: $2 .syyyyyyo-` .oyyyyyyo $2`syyyyyy `-+yyyyyyy/` $2/yyyyyy+ -/osyyyyyyo/. $2+yyyyyy- `.-:::-.` $2.yyyyyy- $3 :$2yyyyy$3o $3 .+$2ooo$3+ $3 `.::/:. ================================================ FILE: src/logo/ascii/pentoo.txt ================================================ $2 `:oydNNMMMMNNdyo:` :yNMMMMMMMMMMMMMMMMNy: :dMMMMMMMMMMMMMMMMMMMMMMd: oMMMMMMMho/-....-/ohMMMMMMMo oMMMMMMy. .yMMMMMMo .MMMMMMo oMMMMMM. +MMMMMm mMMMMM+ oMMMMMh hMMMMMo //hMMMMMm//$1`$2 $1`$2////mMMMMMh// MMMMMMMMMMM$1/$2 $1/o/`$2 $1.$2smMMMMMMMMMMM MMMMMMMMMMm $1`NMN:$2 $1.$2yMMMMMMMMMM MMMMMMMMMMMh$1:.$2 dMMMMMMMMM MMMMMMMMMMMMMy$1.$2 $1-$2NMMMMMMMMM MMMMMMMMMMMd:$1`$2 $1-$2yNMMMMMMMMMM MMMMMMMMMMh$1`$2 $1./$2hNMMMMMMMMMMMM MMMMMMMMMM$1s$2 $1.:$2ymMMMMMMMMMMMMMMM MMMMMMMMMMN$1s:..-/$2ohNMMMMMMMMMMMMMMMMMM MMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM MMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM MMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM ================================================ FILE: src/logo/ascii/peppermint.txt ================================================ $1 PPPPPPPPPPPPPP $1 PPPP$2MMMMMMM$1PPPPPPPPPPP $1 PPPP$2MMMMMMMMMM$1PPPPPPPP$2MM$1PP $1 PPPPPPPP$2MMMMMMM$1PPPPPPPP$2MMMMM$1PP $1 PPPPPPPPPPPP$2MMMMMM$1PPPPPPP$2MMMMMMM$1PP $1 PPPPPPPPPPPP$2MMMMMMM$1PPPP$2M$1P$2MMMMMMMMM$1PP $1 PP$2MMMM$1PPPPPPPPPP$2MMM$1PPPPP$2MMMMMMM$1P$2MM$1PPPP $1 P$2MMMMMMMMMM$1PPPPPP$2MM$1PPPPP$2MMMMMM$1PPPPPPPP $1P$2MMMMMMMMMMMM$1PPPPP$2MM$1PP$2M$1P$2MM$1P$2MM$1PPPPPPPPPPP $1P$2MMMMMMMMMMMMMMMM$1PP$2M$1P$2MMM$1PPPPPPPPPPPPPPPP $1P$2MMM$1PPPPPPPPPPPPPPPPPPPPPPPPPPPPPP$2MMMMM$1P $1PPPPPPPPPPPPPPPP$2MMM$1P$2M$1P$2MMMMMMMMMMMMMMMM$1PP $1PPPPPPPPPPP$2MM$1P$2MM$1PPPP$2MM$1PPPPP$2MMMMMMMMMMM$1PP $1 PPPPPPPP$2MMMMMM$1PPPPP$2MM$1PPPPPP$2MMMMMMMMM$1PP $1 PPPP$2MM$1P$2MMMMMMM$1PPPPPP$2MM$1PPPPPPPPPP$2MMMM$1PP $1 PP$2MMMMMMMMM$1P$2M$1PPPP$2MMMMMM$1PPPPPPPPPPPPP $1 PP$2MMMMMMM$1PPPPPPP$2MMMMMM$1PPPPPPPPPPPP $1 PP$2MMMM$1PPPPPPPPP$2MMMMMMM$1PPPPPPPP $1 PP$2MM$1PPPPPPPP$2MMMMMMMMMM$1PPPP $1 PPPPPPPPPP$2MMMMMMMM$1PPPP $1 PPPPPPPPPPPPPP ================================================ FILE: src/logo/ascii/peropesis.txt ================================================ #### #### #### ### #### #### #### # #### # # # # # # # # # # # # # #### ### #### # # #### ### # # # # # # # # # # # # # # # #### # # ### # #### #### # #### ================================================ FILE: src/logo/ascii/phyos.txt ================================================ .^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^.^^^^^. :777777777777777777777777777777^~7777: .~~~~~~~~~~~~~~~~~~~~~^~7777!:!777!. ~7!!!!!!!!!!!!!!!!!^:!777~^!777~ ^77777!!!!!!!!!7!^^7777^^7777^ ^7777~.~~~~^. .~7777^~7777: :!777~^!777~. !777!:~777!: .!777!:~777!:~77~:!777!. ~777!^~7777:^~^!777~ ^7777^^7777^^7777^ :7777~^!7777777: .!777!:!7777!. .~777!:~77~. ~7777^~~ ^7777. :77: .. ================================================ FILE: src/logo/ascii/pikaos.txt ================================================ - ---------------- ------------------ ------------------- -- -----------=*-=----##### -----------------------------####--=###### ------------------------------++---+#### =============----------------=+=----- =================-----------####+---- ==================----------*###=---- ===================----------------- ==================----------------- =================---------------- ===============------------:::: ============-::::::::::::::: =======--::::::::::::::: .::::::::::::::::. ::::::: ================================================ FILE: src/logo/ascii/pisi.txt ================================================ \Fv/!- `:?lzC $1 Q!::=zFx! $2`;v6WBCicl;` $1,vCC\!::#. $1 ,%:::,'` $2+#%@@FQ@@. ,cF%i$1``-',::a? $1 +m:,'```$2}3,/@@Q\@@ "af-$1 `-'"7f =o'.` $2/m' :Q@:Qg ,kl$1 `.|o :k` '$2$+ 'Narm >d,$1 ii #`$2!p. `C , 'd+$1 %' $2 !0m `6Kv =a m+ !A !\L|: :|L\! $: .8` Q''%Q#' '#Q%''Q `0- :6 E|.6QQu uQQ6.|E p: i{ \jts9? ?9stj\ u\ |a` -''. `e> ,m+ $1'^ !`$2s@@@@a$1'"`+`$2 >e' !3|$1`|=>>r- $2'U%:$1 '>>>=:`\3! 'xopE| $2`'$1 `ledoz- `;=>>+`$2`^llci/|==|/iclc;`$1'>>>>: `^`+~ $2````$1 !!-^ ================================================ FILE: src/logo/ascii/pnm_linux.txt ================================================ ``.---..` `--` ``.---........-:.$2-::`$1 $2./::-$1........$2--::.````$1 $2.:://:::$1----$2::::-..$1 ..$2--:::::--::::++-$1.` $2`-:-`$1 .-ohy+::$2-:::$1/sdmdd:.$2 `-:- .-:::$1...$3sNNmds$y$1o/+$3sy+NN$m$1d+.`$2-:::-. `.-:-$1./$3dN$1()$3yyooosd$1()$3$m$1dy$2-.::-.`$1 $2`.$1-...-$3+hNdyyyyyydmy$1:......$2`$1 ``..--.....-$3yNNm$4hssssh$3mmdo$1.........``` `-:://:.....$3hNNNNN$4mddm$3NNNmds$1.....//::--` ```.:-...$3oNNNNNNNNNNNNNNmd/$1...:-.``` .....$3hNNNNNNNNNNNNNNmds$1....` --...$3hNNNNNNNNNNNNNNmdo$1..... .:...$3/NNNNNNNNNNNNNNdd$1:....` `-...$3+mNNNNNNNNNNNmh$1:...-. $4.:+o+/:-$1:+oo+///++o+/:-$4:/+ooo/:. $4+oo/:o- +oooooso.` $4.` ` `/ .-//- ================================================ FILE: src/logo/ascii/pop.txt ================================================ ///////////// ///////////////////// ///////$2*767$1//////////////// //////$27676767676*$1////////////// /////$276767$1//$27676767$1////////////// /////$2767676$1///$2*76767$1/////////////// ///////$2767676$1///$276767$1.///$27676*$1/////// /////////$2767676$1//$276767$1///$2767676$1//////// //////////$276767676767$1////$276767$1///////// ///////////$276767676$1//////$27676$1////////// ////////////,$27676$1,///////$2767$1/////////// /////////////*$27676$1///////$276$1//////////// ///////////////$27676$1//////////////////// ///////////////$27676$1///$2767$1//////////// //////////////////////$2'$1//////////// //////$2.7676767676767676767,$1////// /////$2767676767676767676767$1///// /////////////////////////// ///////////////////// ///////////// ================================================ FILE: src/logo/ascii/pop_small.txt ================================================ ______ \ _ \ __ \ \ \ \ / / \ \_\ \ / / \ ___\ /_/ \ \ _ __\_\__(_)_ (___________)` ================================================ FILE: src/logo/ascii/porteus.txt ================================================ `.-:::-.` -+ydmNNNNNNNmdy+- .+dNmdhs+//////+shdmdo. .smmy+-` ./sdy: `omdo. `.-/+osssso+/-` `+dy. `yms. `:shmNmdhsoo++osyyo-``oh. hm/ .odNmds/.` ``.....:::-+s /m: `+dNmy:` `./oyhhhhyyooo++so ys `yNmy- .+hmmho:-.` ``` s: yNm+` .smNd+. `` /Nm: +dNd+` yN+ `smNy. dm oNNy` hy -mNm. +y oNNo `y` sNN: `: +NN: ` .mNo /mm` /my` .sy` .+: ` ================================================ FILE: src/logo/ascii/postmarketos.txt ================================================ /\ / \ / \ / \ / \ / \ \ \ /\ \____ \ / \____ \ \ / / \ \ / / \ ___\ / / \ / ____ / / \/ / \ / / __________/ \ / \ \ \ / \ \ \ / / / \ /___________/ /____________________\ ================================================ FILE: src/logo/ascii/postmarketos_small.txt ================================================ /\ / \ / \ \__ \ /\__ \ _\ / / \/ __ / / ____/ \ / \ \ \ /_____/ /________\ ================================================ FILE: src/logo/ascii/prismlinux.txt ================================================ ⣤⣤ ⢀⡄ ⢀⣾⣦ ⠙⠛ ⢀⣴⣿⣷ ⢠⣤ ⣤⡄ ⣸⣿⣿⣷⣄ ⣠⣶⣿⣿⣿⣿ ⠈⠉ ⢀⣤⡀ ⠉⠁ ⢠⣿⣿⣿⣿⣿⣦⡈⢿⣿⣿⣿⣿⣿⡇⢀⣀⣤⣤⣤⣤⣴⣶⠆⠘⠿⠃ ⠉ ⡀ ⢤⣄⡀ ⣾⣿⣿⣿⣿⣿⣿⣷⡄⠻⣿⣿⣿⣿⡇⢸⣿⣿⣿⣿⣿⣿⡏ ⡇ ⠘⣿⣿⣿⣿⣶⣤⣈⣉⠛⠿⢿⣿⣿⣿⣿⣦⠘⢿⣿⣿⣷⠈⣿⣿⣿⣿⣿⠏ ⠐⠁ ⢿⣿⣿⣿⣿⣿⣿⣿⣿⣶⣦⣤⣉⡛⠻⢿⣷⣄⠻⣿⣿ ⣿⣿⣿⣿⠟⢠⣀ ⡐⠁ ⢰⣶ ⠘⣿⣿⣿⣿⣿⣿⣿⠿⠿⠛⠛⠉⠉ ⠈⠉ ⠘⣿ ⣿⣿⣿⡏⢠⣿⣿⣿⣶⣦⣄⡀ ⠂ ⠈⠁ ⠙⠋⣉⣉⣤⣤⣶⣶⠾⠉ ⠈ ⢹⣿⠏⢠⣿⣿⣿⣿⣿⣿⣿⣿⠟ ⢀⣠⣤⣴⣶⣿⣿⣿⣿⣿⣿⣿⠟⠁ ⢸⠟⣰⣿⣿⣿⣿⣿⡿⠟⠉ ⠈⠛⢿⣿⣿⣿⣿⣿⣿⣿⠟⢁⣴⡏ ⠈⣰⣿⣿⣿⠿⠛⣁⡀ ⢀⡒ ⢤⡀ ⠙⢿⣿⣿⣿⠟⢁⣴⣿⣿ ⣰⡿⠟⢉⣠⣴⣾⣿⣿⣆ ⠛⠁ ⠔ ⠈⠛⢁⣴⣿⣿⣿⡇⢰⡆ ⠊⣁⣤⣾⣿⣿⣿⣿⣿⣿⣿⣷⡀ ⡠⠂ ⢀⣴⣿⣿⣿⣿⣿⠃⣾⣿⡄⢢⡀ ⠤⠶⠿⠿⠿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣷⡄ ⢀⠈ ⢀⣴⣿⣿⣿⣿⣿⣿⡿⢠⣿⣿⣷ ⢿⣷⣄⠑⢶⣶⣶⣶⣶⣶⣤⣤⣤⣤⣤⣤ ⠠⠁ ⠚⠛⠿⠿⢿⣿⣿⣿⡇⢸⣿⣿⣿⣧⠈⢿⣿⣿⣦⣌⠙⢿⣿⣿⣿⣿⣿⣿⣿⣿⡄ ⢰⣶ ⡆ ⣼⣿⣿⣿⣿⣇⠘⣿⣿⣿⣿⣿⣦⣈⠛⢿⣿⣿⣿⣿⣿⡇ ⢁ ⣀ ⠄⠂⠁⢀⣿⣿⣿⣿⣿⣿⡆⠹⣿⣿⣿⣿⣿⣿⠗ ⠈⠛⢿⣿⣿⡇ ⠁⠂ ⠒⠒⠈⠁ ⢸⣿⣿⣿⠿⠛⠉ ⢻⣿⣿⣿⣿⠟ ⠈⠙⠃ ⠼⠛⠉⠁ ⢻⣿⣿⠏ ⢀⣄ ⠿⠇ ⠈⠿⠁ ⠘⠋ ================================================ FILE: src/logo/ascii/prismlinux_small.txt ================================================ ⢠⣦⡀⠘⠁⢀⣴⣾ ⢀⡄ ⠛ ⣸⣿⣿⣄⢰⣿⣿⣿⡄⢀⣀⣀⣀⡀⢾⠆⠐⠂⢄ ⠰⣤⣄⡀ ⢿⣿⣿⣿⣧⡹⣿⣿⡇⣿⣿⣿⡿⠁ ⢻⣿⣿⣿⣶⣶⣭⣝⡻⠷⣌⢿⡇⢸⣿⡿⢁⣀ ⠘⠛ ⠸⠿⠟⣛⣋⡭⠅ ⠻⢸⡿⢡⣿⣿⣿⣶⣤⠁ ⠠⣤⣶⣶⣿⣿⡿⢋ ⠸⣡⣿⣿⡿⠟⠉ ⢠ ⠈⠻⣿⡿⢋⣴⡟ ⢰⠟⣋⣥⣾⣦⡀ ⠁ ⠈⠠⠊ ⢈⣴⣿⣿⣇⣧⢀ ⢀⣠⣴⣿⣿⣿⣿⣿⣷⡄ ⣰⣿⣿⣿⣿⢹⣿⣆⢳⣦⣐⠶⣶⣦⣤⣬⣭⣭⠉⢉⠉⠉ ⡀ ⠈⠉⠉⢉⢸⣿⣿⣆⢻⣿⣷⣮⡙⢿⣿⣿⣿ ⠉ ⣀⣀ ⠄⠐⠈ ⣾⣿⣿⠿ ⣿⣿⣿⡿ ⠉⠻⢿ ⠛⠉ ⢀⡀⠘⣿⠟ ⢠⡀ ⠈⠁ ⠁ ================================================ FILE: src/logo/ascii/proxmox.txt ================================================ $1 .://:` `://:. `hMMMMMMd/ /dMMMMMMh` `sMMMMMMMd: :mMMMMMMMs` $2`-/+oo+/:$1`.yMMMMMMMh- -hMMMMMMMy.`$2:/+oo+/-` `:oooooooo/$1`-hMMMMMMMyyMMMMMMMh-`$2/oooooooo:` `/oooooooo:$1`:mMMMMMMMMMMMMm:`$2:oooooooo/` ./ooooooo+-$1 +NMMMMMMMMN+ $2-+ooooooo/. .+ooooooo+-$1`oNMMMMNo`$2-+ooooooo+. -+ooooooo/.$1`sMMs`$2./ooooooo+- :oooooooo/$1`..`$2/oooooooo: :oooooooo/`$1..$2`/oooooooo: -+ooooooo/.`$1sMMs$2`./ooooooo+- .+ooooooo+-`$1oNMMMMNo$2`-+ooooooo+. ./ooooooo+-$1 +NMMMMMMMMN+ $2-+ooooooo/. `/oooooooo:`$1:mMMMMMMMMMMMMm:$2`:oooooooo/` `:oooooooo/`$1-hMMMMMMMyyMMMMMMMh-$2`/oooooooo:` `-/+oo+/:`$1.yMMMMMMMh- -hMMMMMMMy.$2`:/+oo+/-` $1 `sMMMMMMMm: :dMMMMMMMs` `hMMMMMMd/ /dMMMMMMh` `://:` `://:` ================================================ FILE: src/logo/ascii/puffos.txt ================================================ _,..._,m, ,/' '""; / ". ,'mmmMMMMmm. \ _/-"^^^^^"""%#%mm, ; ,m,_,' "###) ;, (###% \#/ ;##mm. ^#/ __ ___ ; (######) ; //.\\ //.\\ ; \####/ _; (#\"// \\"/#) ; ,/ @##\ \##/ = `"=" ,;mm/ `\##>.____,...,____,<####@ ================================================ FILE: src/logo/ascii/puppy.txt ================================================ `-/osyyyysosyhhhhhyys+- -ohmNNmh+/hMMMMMMMMNNNNd+dMMMMNM+ yMMMMNNmmddo/NMMMNNNNNNNNNo+NNNNNy .NNNNNNmmmddds:MMNNNNNNNNNNNh:mNNN/ -NNNdyyyhdmmmd`dNNNNNmmmmNNmdd/os/ .Nm+shddyooo+/smNNNNmmmmNh. :mmd. NNNNy:` ./hmmmmmmmNNNN: hNMh NMN- -++- +NNNNNNNNNNm+..-sMMMM- .MMo oNNNNo hNNNNNNNNmhdNNNMMMMM+ .MMs /NNNN/ dNmhs+:-` yMMMMMMMM+ mMM+ .. `sNN+. hMMMMhhMMM- +MMMmo:...:sNMMMMMms:` hMMMMm.hMMy yMMMMMMMMMMMNdMMMMMM::/+o+//dMMd` sMMMMMMMMMMN+:oyyo:sMMMNNMMMNy` :mMMMMMMMMMMMmddNMMMMMMMMmh/ /dMMMMMMMMMMMMMMMMMMNdy/` .+hNMMMMMMMMMNmdhs/. .:/+ooo+/:-. ================================================ FILE: src/logo/ascii/pureos.txt ================================================ dmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmd dNm//////////////////////////////////mNd dNd dNd dNd dNd dNd dNd dNd dNd dNd dNd dNd dNd dNd dNd dNd dNd dNm//////////////////////////////////mNd dmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmd ================================================ FILE: src/logo/ascii/pureos_small.txt ================================================ _____________ | _________ | | | | | | | | | | |_________| | |_____________| ================================================ FILE: src/logo/ascii/q4os.txt ================================================ .:***** :=====. .:******** :========. .*********** :===========. .:************ :============- .************** :============== :*************** :=============== :**************. :=============== .*************: .=============. *************. .============: $1:############. $2:==: $1:##############. $2:======: $1:################ $2.==========: $1:############### $2.===========: $1:############## $2.===========: $1:############# $2.=========: $1:########### $2.=====: $1.######### $2.=: $1.##### ================================================ FILE: src/logo/ascii/qts.txt ================================================ $1###########################- $1############################### $1=#########**************######### $1########## ######## $1########## ######## $1########## ######## $1########## ######## $1########## ######## $1########## $2. $1######## $1########## $2+=. $1:##### $1+#########+=========- $2%%%* $1=# $1#####################* $2%%%%# $1-#####################* $2%%%%%. $2``````` ================================================ FILE: src/logo/ascii/qubes.txt ================================================ `..--..` `.----------.` `..----------------..` `.------------------------.`` `..-------------....-------------..` .::----------..`` ``..----------:+: :////:----..` `..---:/ossso :///////:` `/osssssso :///////: /ssssssso :///////: /ssssssso :///////: /ssssssso :///////: /ssssssso :///////: /ssssssso :////////-` .:sssssssso :///////////-.` `-/osssssssssso `//////////////:-```.:+ssssssssssssso- .-://////////////sssssssssssssso/-` `.:///////////sssssssssssssso:. .-:///////ssssssssssssssssss/` `.:////ssss+/+ssssssssssss. `--//- `-/osssso/. ================================================ FILE: src/logo/ascii/qubyt.txt ================================================ $1 ########################$2($3ooo $1 ########################$2($3ooo $1###$2($3ooo $1###$2($3ooo $1###$2($3ooo $1###$2($3ooo $1###$2($3ooo $1###$2($3ooo $1###$2($3ooo $1###$2($3ooo $1###$2($3ooo $1###$2($3ooo $1###$2($3ooo $1###$2($3ooo $1###$2($3ooo $1##$3o $2(((($3ooo $1###$2($3ooo o$2(($1### $3oooooo $1###$2($3ooo oo$2(($1###$3o $1###$2($3ooo ooo$2(($1### $1################$2($3oo oo$2(((($3o $2((((((((((((((((($3ooo ooooo oooooooooooooooooo o ================================================ FILE: src/logo/ascii/quibian.txt ================================================ `.--::::::::--.` `.-:::-..`` ``..-::-.` .::::-` .$2+$1:`` `.-::.` .::::.` -::::::-` `.::. `-:::-` -:::::::::--..`` .::` `::::- .$2oy$1:::::::---.```.: `::` -:::: `.-:::::::::::-.``` `:: .::::.`-:::::::::::::. `:. -::::.::::::::::::::: -: ::::::::::::::::::::` `: :::::::::::::::::::- `: ::::::::::::::::::: -- .:::::::::::::::::` `:` `::::::::::::::::: -` .:::::::::::::::- -` `::::::::::::::- `.` .::::::::::::- `` `.--:::::-. ================================================ FILE: src/logo/ascii/quirinux.txt ================================================ $2 @=++++++++++=@ $2 =++++++++++++++++++= $2 *++++++++++++++++++++++* $2 =++++++++++++++++++++++++++= $2 *++++++++$1-..........-$2++++++++* $2 =++++++++$1..............$2++++++++= $2@++++++++$1:.....$2:++$1:.....:$2++++++++@ $2=++++++++$1:.....$2++++$1.....:$2++++++++= $2=++++++++$1:.....$2++++$1.....:$2++++++++= $2#++++++++$1:.....$2++++$1.....:$2++++++++# $2 +++++++++$1......$2--$1......$2+++++++++ $2 @++++++++$1:............:$2++++++++@ $2 @+++++++++++$1-....-$2+++++++++++@ $2 *++++++++++$1::::$2++++++++++* $2 *++++++++++++++++++++* $2 @*++++++++++++++*@ $2 @#====#@ ================================================ FILE: src/logo/ascii/radix.txt ================================================ .:oyhdmNo `/yhyoosdms` -o+/ohmmho- ..`.:/:-` `.--:::-.``$2 .+ydNMMMMMMNmhs:` `omMMMMMMMMMMMMMMNh- oNMMMNmddhhyyhhhddmy. mMMMMNmmddhhysoo+/:-` yMMMMMMMMMMMMMMMMNNh. -dmmmmmNNMMMMMMMMMMs` -+oossyhmMMMMMMMMd- `sNMMMMMMMMMMMMMm: `yMMMMMMNmdhhhh: `sNMMMMMNmmho. `+mMMMMMMMy. .yNMMMm+` `:yd+. ================================================ FILE: src/logo/ascii/raspbian.txt ================================================ $2`.::///+:/-. --///+//-:` `+oooooooooooo: `+oooooooooooo: /oooo++//ooooo: ooooo+//+ooooo. `+ooooooo:-:oo- +o+::/ooooooo: `:oooooooo+`` `.oooooooo+- `:++ooo/. :+ooo+/.`$1 ...` `.----.` ``.. .::::-``:::::::::.`-:::-` -:::-` .:::::::-` `-:::- `::. `.--.` `` `.---.``.::` .::::::::` -::::::::` ` .::` .:::::::::- `::::::::::``::. -:::` ::::::::::. ::::::::::.`:::- :::: -::::::::. `-:::::::: :::: -::- .-:::-.``....``.-::-. -::- .. `` .::::::::. `..`.. -:::-` -::::::::::` .:::::` :::::::` -::::::::::` :::::::. .::::::: -::::::::. :::::::: `-:::::` ..--.` ::::::. `...` `...--..` `...` .:::::::::: `.-::::-` ================================================ FILE: src/logo/ascii/raspbian_small.txt ================================================ $2.~~. .~~. '. \ ' ' / .'$1 .~ .~~~..~. : .~.'~'.~. : ~ ( ) ( ) ~ ( : '~'.~.'~' : ) ~ .~ ( ) ~. ~ ( : '~' : ) '~ .~~~. ~' '~' ================================================ FILE: src/logo/ascii/ravynos.txt ================================================ ..oooo.. .o$$$$$$$$$$$$$$$$$$$$$$$$$$$$o. od$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$o o$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$o .$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$. d$$$$$$$$$$$$$$$$$********$$$$$$$$$$$$$$$$$$$$$$$$$$$$$b d$$$$$$$$$$$$$* °****?$$$$$$$$$$$$$$$$b $$$$$$$$$$$$* °$$$$$$$$$$$$$ d$$$$** .oo$$$$$$$$$$$$$$$$b *° o$$$$$$$$$$$$$$$$$$$$$$$$$$$$ o$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$ o$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$P *$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$ ?$$$$$$$$$$$$$$$$$$$$$$$$$$$$P $$$$$$$$$$$$$$$$$$$$$$$$$P $$$$$$$$$$$$$$$$$$$$$$$$P ?$$$$$$$$$$$$$$$$$$$$* $$$$$$$$$$$$$*° d$$$$$$$$*° ° ================================================ FILE: src/logo/ascii/rebornos.txt ================================================ $1.======================. $1.#$2#*********$1%%$2*********#$1%: $1:%$2#**********$1%%$2**********#$1%- $1-%$2************$1%%$2************$1%= $1+%$2******$1%%#####$1%%#####%%$2******$1%+ $1*%%#$2****$1%#$3+=====$1%%$3=====+$1#%$2****$1#%%* $1*%$2*#$1#%%#%#$3====+++$1%%$3+++====$1#%#%%#$2#*$1##. $1.##$2*****$1#%%%#$3*++$1%######%$3*+*$1#%%%#$2*****$1#%. $1:%#$2*****$1#%$3*=+*$1#%%$3*++++++*$1%%#$3*+=*$1%#$2*****$1#%: $1-%#$2*****$1#%$3+====*$1%$3*++++++++*$1%#$3====+$1%#$2******$1%- $1-%#$2*****$1#%$3+====*$1%$3*++++++++*$1%#$3====+$1%#$2******$1%= $1:%#$2*****$1#%$3*=+*$1#%%$3*++++++*$1%%#$3*+=*$1%#$2*****$1#%- $1.##$2*****$1#%%%#$3*+*$1%######%$3*+*$1#%%%#$2*****$1#%: $1.##$2**$1#%%#%#$3====+++$1%%$3+++====$1#%#%%#$2#*$1##. $1*%%#$2****$1%#$3+=====$1%%$3=====+$1#%$2****$1#%%* $1+%$2******$1%%#####%%#####%%$2******$1%* $1-%$2************$1%%$2************$1%= $1:%$2#**********$1%%$2**********#$1%- $1:%$2#*********$1%%$2*********#$1%: $1.======================. ================================================ FILE: src/logo/ascii/rebornos_small.txt ================================================ _______ /\_____/\ / /\___/\ \ /_/_/ \_\_\ \ \ \___/ / / \ \/___\/ / \/_____\/ ================================================ FILE: src/logo/ascii/redcore.txt ================================================ RRRRRRRRR RRRRRRRRRRRRR RRRRRRRRRR RRRRR RRRRRRRRRRRRRRRRRRRRRRRRRRR RRRRRRR RRR RRR RRRRRRRR RRRRR RR RRRRRRRRR RRRR RR RRRRRRRR RR RRRRRR RRRR R RRRRRRRRRRRRRR RR RRRRR RRRR R RRRRRRRRRRRRRRRRRR R RRRRR RRRR RRRRRRRRRRRRRRRRRRR R RRRR RRR RRRRRRRRRRRRRRRRRRRR R RRRR RRR RRRRRRRRRRRRRRRRRRRR RRRR RR RRRRRRRRRRRRRRRRRRR RRR RR RRRRRRRRRRRRRRRRR RRR RR RRRRRRRRRRRRRR RR R RRRR RR ================================================ FILE: src/logo/ascii/redos.txt ================================================ ╭───────────────╮ ╭───────────────╮ │###############│ │###############│ │#+++++++++++++#│ │#+++++++++++++#│ │#+++++++++++++#│ │#+++++++++++++#│ │#+++++++++++++#│ │#+++++++++++++#│ │#+++++++++++++#│ │#+++++++++++++#│ │#+++++++++++++#│ │#+++++++++++++#│ │###############│ │###############│ ╰───────────────╯ ╰───────────────╯ ╭───────────────╮ │###############│ │#+++++++++++++#│ │#+++++++++++++#│ │#+++++++++++++#│ │#+++++++++++++#│ │#+++++++++++++#│ │###############│ ╰───────────────╯ ================================================ FILE: src/logo/ascii/redos_small.txt ================================================ ╭─────╮ ╭─────╮ │ │ │ │ │ │ │ │ ╰─────╯ ╰─────╯ ╭─────╮ │ │ │ │ ╰─────╯ ================================================ FILE: src/logo/ascii/redstar.txt ================================================ .. .oK0l :0KKKKd. .xKO0KKKKd ,Od' .d0000l .c;. .'''... ..'. .,:cloddxxxkkkkOOOOkkkkkkkkxxxxxxxxxkkkx: ;kOOOOOOOkxOkc'...',;;;;,,,'',;;:cllc:,. .okkkkd,.lko .......',;:cllc:;,,'''''. .cdo. :xd' cd:. ..';'',,,'',,;;;,'. . .ddl.;doooc'..;oc;'..';::;,'. coo;.oooolllllllcccc:'. . .ool''lllllccccccc:::::;. ;lll. .':cccc:::::::;;;;' :lcc:'',..';::::;;;;;;;,,. :cccc::::;...';;;;;,,,,,,. ,::::::;;;,'. ..',,,,'''. ........ ...... ================================================ FILE: src/logo/ascii/refracta.txt ================================================ A VW VVW\ .yWWW\ ,;,,u,;yy;;v;uyyyyyyy ,WWWWW^ *WWWWWWWWWWWWWWWW/ $VWWWWw , ^*%WWWWWWVWWX $WWWW** ,yy , "**WWW/' **' ,yy/WWW*` &WWWWwy `*` <,ywWW%VWWW* yWWWWWWWWWW* ., "**WW%W ,&WWWWWM*"` ,y/ &WWWww ^* XWWX*^ ,yWWWW09 .WWWWWWWWwy, *` &WWWWWM WWWWWWWWWWWWWww, (WWWWW` /#####WWW*********** ^WWWW VWW Wh. V/ ================================================ FILE: src/logo/ascii/regata.txt ================================================ ddhso+++++osydd dho/.`hh$2.:/+/:.$1hhh`:+yd do-hhhhhh$2/sssssss+`$1hhhhh./yd h/`hhhhhhh$2-sssssssss:$1hhhhhhhh-yd do`hhhhhhhhh$2`ossssssso.$1hhhhhhhhhh/d d/hhhhhhhhhhhh$2`/ossso/.$1hhhhhhhhhhhh.h /hhhhhhhhhhhh$3`-/osyso/-`$1hhhhhhhhhhhh.h shh$4-/ooo+-$1hhh$3:syyso+osyys/`$1hhh$5`+oo`$1hhh/ h$4`ohhhhhhho`$3+yyo.$1hhhhh$3.+yyo`$5.sssssss.$1h`h s$4:hhhhhhhhho$3yys`$1hhhhhhh$3.oyy/$5ossssssso-$1hs s$4.yhhhhhhhy/$3yys`$1hhhhhhh$3.oyy/$5ossssssso-$1hs hh$4./syyys+.$1 $3+yy+.$1hhhhh$3.+yyo`$5.ossssso/$1h`h shhh$4``.`$1hhh$3`/syyso++oyys/`$1hhh$5`+++-`$1hh:h d/hhhhhhhhhhhh$3`-/osyso+-`$1hhhhhhhhhhhh.h d/hhhhhhhhhhhh$6`/ossso/.$1hhhhhhhhhhhh.h do`hhhhhhhhh$6`ossssssso.$1hhhhhhhhhh:h h/`hhhhhhh$6-sssssssss:$1hhhhhhhh-yd h+.hhhhhh$6+sssssss+$1hhhhhh`/yd dho:.hhh$6.:+++/.$1hhh`-+yd ddhso+++++osyhd ================================================ FILE: src/logo/ascii/regolith.txt ================================================ ``....``` `.:/++++++/::-.` -/+++++++:.` -++++++++:` `/++++++++- `/++++++++. -/+/ /++++++++/ `` .:+++:. -+++++++++/ ./++++:+++/-` :+++++++++/ `+++++++/-` :++++++++++` .-/+++++++` `:++++++++++/``.-/++++:-:::-` ` `:+++++++++++++++++/:.` ./` :++/-:+++++++++/:-.. -/+. +++++++++/::-...:/+++/-..````..-/+++. `......``.::/+++++++++++++++++++++/. -/+++++++++++++++++++++/. .:/+++++++++++++++/-` `.-:://////:-. ================================================ FILE: src/logo/ascii/rengeos.txt ================================================ .. .:~!. ...::^~!!7?J?: ..::^^~~!!!7?JYYYYYYY?. .::::..::^!7?JJYYYJYY! :~~~:.. .:~!?JJYYYYYYYJ!?J^ :!?JJ?7!!7?JYYYYYYYYYYYJ~.~?: :~?YYYYYYYYYYYYYYY?~. ^7. .~?YYYYYYYYYY?^ .~ ..:~!7?JYYYYYYYY?^ .^~!7777777JYYYYY7: ^YYYJ7: :YYJ!: :JJ!. .7~. .. ================================================ FILE: src/logo/ascii/rhaymos.txt ================================================ ### ##### ####### /######## ############# ########### ,########### #### ####(.. #### #### ####* ########## #### ##### ##### (#### #### ########### ########### #### ######### ########## ################################### ##################################### ####################################### ================================================ FILE: src/logo/ascii/rhel.txt ================================================ .MMM..:MMMMMMM MMMMMMMMMMMMMMMMMM MMMMMMMMMMMMMMMMMMMM. MMMMMMMMMMMMMMMMMMMMMM ,MMMMMMMMMMMMMMMMMMMMMM: MMMMMMMMMMMMMMMMMMMMMMMM .MMMM' MMMMMMMMMMMMMMMMMMMMMM MMMMMM `MMMMMMMMMMMMMMMMMMMM. MMMMMMMM MMMMMMMMMMMMMMMMMM . MMMMMMMMM. `MMMMMMMMMMMMM' MM. MMMMMMMMMMM. MMMM `MMMMMMMMMMMMM. ,MMMMM. `MMMMMMMMMMMMMMMMM. ,MMMMMMMM. MMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM MMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM: MMMMMMMMMMMMMMMMMMMMMMMMMMMMMM `MMMMMMMMMMMMMMMMMMMMMMMM: ``MMMMMMMMMMMMMMMMM' ================================================ FILE: src/logo/ascii/rhel_old.txt ================================================ `.-..........` `////////::.`-/. -: ....-////////. //:-::///////////` `--::: `-://////////////: //////- ``.-:///////// .` `://////:-.` :///////::///:` .-/////////:---/////////////: .-://////////////////////. $2 yMN+`.-$1::///////////////-` $2 .-`:NMMNMs` `..-------..` MN+/mMMMMMhoooyysshsss MMM MMMMMMMMMMMMMMyyddMMM+ MMMM MMMMMMMMMMMMMNdyNMMh` hyhMMM MMMMMMMMMMMMMMMMyoNNNMMM+. MMMMMMMM MMNMMMNNMMMMMNM+ mhsMNyyyyMNMMMMsMM ================================================ FILE: src/logo/ascii/rhel_small.txt ================================================ .M.:MMM MMMMMMMMMM. ,MMMMMMMMMMM .MM MMMMMMMMMMM MMMM MMMMMMMMM MMMMMM MM MMMMMMMMM ,MMMM MMMMMMMMMMMMMMMM: `MMMMMMMMMMMM ================================================ FILE: src/logo/ascii/rhino.txt ================================================ $1 .;:;,. .: $1 'coooooooo:oo.';. $1 ,oooooooooooooooo ; $1 clllcccllloooooooo;c:'o $1.$4;$3';:::::::::$1cclooooooo' $4''',$3::::::::::::::$1ccclc. $4.'''$3;::::::::::$2l$3::::::: $4 ''''$3,:::::::::$2kd$3. $4 .'''''$3,;::$2ck:$2oW$3; $4 ''''''''$2kXOM. $4 .,,:$2dXMK $4 $2:k ================================================ FILE: src/logo/ascii/rocky.txt ================================================ __wgliliiligw_, _williiiiiiliilililw, _%iiiiiilililiiiiiiiiiii_ .Qliiiililiiiiiiililililiilm. _iiiiiliiiiiililiiiiiiiiiiliil, .lililiiilililiiiilililililiiiii, _liiiiiiliiiiiiiliiiiiF{iiiiiilili, jliililiiilililiiili@` ~ililiiiiiL iiiliiiiliiiiiiili>` ~liililii liliiiliiilililii` -9liiiil iiiiiliiliiiiii~ "4lili 4ililiiiiilil~| -w, )4lf -liiiiililiF' _liig, )' )iiiliii@` _QIililig, )iiii>` .Qliliiiililw )<>~ .mliiiiiliiiiiil, _gllilililiililii~ giliiiiiiiiiiiiT` -^~$ililili@~~' ================================================ FILE: src/logo/ascii/rocky_small.txt ================================================ `-/+++++++++/-.` `-+++++++++++++++++-` .+++++++++++++++++++++. -+++++++++++++++++++++++. +++++++++++++++/-/+++++++ +++++++++++++/. ./+++++ +++++++++++:. ./+++ +++++++++:` `:/:` .:/ -++++++:` .:+++++:` .+++-` ./+++++++++:` `-` ./+++++++++++- -+++++++++:-.` ================================================ FILE: src/logo/ascii/rosa.txt ================================================ $2 ⢀⣀⣤⣤⣤⣤⣤⣤⣤⣤⣤⣤⣤⣤⣤⣤⣤⣤⣤⣤⣤⣄ $2 ⠈⠻⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⡇ $1 ⢀⣤⣄ $2⣿⣿⣿⡇ $1 ⢀⣴⣿⣿⣿⠗ $2⣿⣿⣿⡇ $1 ⢠⣾⣿⣿⡿⠁ $2⣿⣿⣿⡇ $1 ⢠⣿⣿⣿⡟ $2⣿⣿⣿⡇ $1⢀⣿⣿⣿⡿ $2⣿⣿⣿⡇ $1⢸⣿⣿⣿⠃ $2⣿⣿⣿⡇ $1⢸⣿⣿⣿ $2⣿⣿⣿⡇ $1⢸⣿⣿⣿ $2⣿⣿⣿⡇ $1⢸⣿⣿⣿⡄ $2⠙⢿⡿ $1⠘⣿⣿⣿⣷ ⠘⣿⣿⣿⣦ ⠙⣿⣿⣿⣷⡀ ⢀⡀ ⠘⢿⣿⣿⣿⣦⣄ ⣠⣴⣿⣿⡦ ⠙⢿⣿⣿⣿⣷⣦⣀⣀⡀ ⢀⣀⣠⣴⣾⣿⣿⣿⠟⠁ ⠈⠙⢿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⠟⠋⠁ ⠈⠙⠛⠿⠿⠿⠿⠿⠿⠟⠛⠋⠁ ================================================ FILE: src/logo/ascii/sabayon.txt ================================================ ........... .. .. .. .. .. $2o $1.. .. $2:W' $1.. .. $2.d. $1.. :. $2.KNO $1.: :. $2cNNN. $1.: : $2dXXX, $1: : $2. dXXX, .cd, $1: : $2'kc .. dKKK. ,ll;:' $1: : $2.xkkxc;..dkkkc',cxkkl $1: :. $2.,cdddddddddddddo:. $1.: .. $2:lllllll: $1.. .. $2',,,,, $1.. .. .. .. .. ............... ================================================ FILE: src/logo/ascii/sabotage.txt ================================================ .|'''.| | '||''|. ..|''|| ||.. ' ||| || || .|' || ''|||. | || ||'''|. || || . '|| .''''|. || || '|. || |'....|' .|. .||. .||...|' ''|...|' |''||''| | ..|'''.| '||''''| || ||| .|' ' || . || | || || .... ||''| || .''''|. '|. || || .||. .|. .||. ''|...'| .||.....| ================================================ FILE: src/logo/ascii/sailfish.txt ================================================ _a@b _#b (b _@@ @_ _, _#^@ _#*^^*gg,aa@^^ #- @@^ _a@^^ @_ *g#b ^@_ ^@_ ^@_ @ @(b (b #b(b#^ _@_#@^ _a@a*^ ,a@*^ ================================================ FILE: src/logo/ascii/salentos.txt ================================================ ``..`` .-:+oshdNMMMMMMNdhyo+:-.` -oydmMMMMMMMMMMMMMMMMMMMMMMMMMMNdhs/ $4 +hdddm$1NMMMMMMMMMMMMMMMMMMMMMMMMN$4mdddh+` $2`MMMMMN$4mdddddm$1MMMMMMMMMMMM$4mdddddm$3NMMMMM- $2 mMMMMMMMMMMMN$4ddddhyyhhddd$3NMMMMMMMMMMMM` $2 dMMMMMMMMMMMMMMMMM$4oo$3MMMMMMMMMMMMMMMMMN` $2 yMMMMMMMMMMMMMMMMM$4hh$3MMMMMMMMMMMMMMMMMd $2 +MMMMMMMMMMMMMMMMM$4hh$3MMMMMMMMMMMMMMMMMy $2 :MMMMMMMMMMMMMMMMM$4hh$3MMMMMMMMMMMMMMMMMo $2 .MMMMMMMMMMMMMMMMM$4hh$3MMMMMMMMMMMMMMMMM/ $2 `NMMMMMMMMMMMMMMMM$4hh$3MMMMMMMMMMMMMMMMM- $2 mMMMMMMMMMMMMMMMM$4hh$3MMMMMMMMMMMMMMMMN` $2 hMMMMMMMMMMMMMMMM$4hh$3MMMMMMMMMMMMMMMMm $2 /MMMMMMMMMMMMMMMM$4hh$3MMMMMMMMMMMMMMMMy $2 .+hMMMMMMMMMMMMM$4hh$3MMMMMMMMMMMMMms: $2 `:smMMMMMMMMM$4hh$3MMMMMMMMMNh+. $2 .+hMMMMMM$4hh$3MMMMMMdo: $2 `:smMM$4yy$3MMNy/` $2.- $4`$3:. ================================================ FILE: src/logo/ascii/salientos.txt ================================================ 00xxxx0 00xxxxxx0 0xxxxxxxxx 000000 0xxxxxxxxxx xxxxxxxxxx0 0xxxxxxxxxxx0 xxxxxxxxxxxxx0 0xxxxxxxxxxxx0 0xxxxxxxxxxxxxx0 0xxxxxxxxxxxxx0 0xxxxxxxxxxxxxxx0 0xxxxxxxxxxxxxxx xxxxxxxxxxxxxxxx0 xxxxxxxxxxxxxxxx0 0xxxxxxxxxxxxxxxx xxxxxxxxxxxxxxxxx 0xxxxxxxxxxxxxxx xxxxxxxxxxxxxxxxxx xxxxxxxxxxxxxxx xxxxxxxxxxxxxxxxxx0 xxxxxxxxxxxxxx 0xxxxxxxxxxxxxxxxxx0 0xxxxxxxxxxxx0 0xxxxxxxxxxxxxxxxxx xxxxxxxxxxx0 0xxxxxxxxxxxxxxxxx xxxxxxxxxx0 0xxxxxxxxxxxxxxxx xxxxxxxxx0 0xxxxxxxxxxxx0 0xxxxxxx0 0xxxxxxx0 xxxxxx0 0xxx00 x00 ================================================ FILE: src/logo/ascii/salix.txt ================================================ __s_aaaaaaaaauuoXSSSSSSSS: ._xSSSSSSSSSSSSSSSSSSSSSSSSSS: _aSSSSSSSSSSSSSSSSSSSSSSSSSSSSS: _xSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSS: nSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSS} nSSSSSSSSSSSSSSSSSSSSSSSSSSSSSS}` XSSSSSSSSSSSSSSSSSSSSSSSSSSSS"` SSSSSSSSSSSSSSSSSSSSSSSSS!"` -""""""""""""""""""""""` ================================================ FILE: src/logo/ascii/sambabox.txt ================================================ # *////##### /////////#########( .((((((///// ,####(#((((( /#######(((* (#(((((((((. //((#(#(#, ((##( ,((((((// ////// #(##########( ////// ////// ((#(#(#(#(##########(///////// /////( (((((((#########(##((((((///// /(((#( ((((/ ####(# ((### #########(((/////////(((((((((, (#(#( ########( /////////(((((((* ##### ####///, *////((( ((((((( ./////////// .//((((((((( ///////////, *(/////((((* ,/(((((((((##########/. .((((((####### ((##* ================================================ FILE: src/logo/ascii/sasanqua.txt ================================================ __,_ _╕⌐≡µ,√* º≡, ñ "' ░ ╞) _, ▒ __ _,,,_ _Ñ╜^≡µ ≡' 1µ╕º^el "%µ ∩' K Yµ& 1l ╞) ▒ √" ^Ü 1" `1µ Γ ║h _¿▒∞√;, ^≡, K ^u_ ⌐* ╙¥ ╓Ñ ⌠ º≡u,, ║I Å Ü _∩" ║µ_¿╝" Yu_ ▒ ╙º≡_ ║l1µ ║l , Y∞µ___≡ª Γl ╓hⁿ╖I 1l Ñ ╓Ñ Ñ ¥,___≡1l ╓Ñ ¿╕ª Ü ╙L ¿¿∩ª ╓P ª≡,__ *ⁿ┤ⁿÑⁿ^µ √ª ⁿ≡,,__√╝* "ⁿⁿ*" ================================================ FILE: src/logo/ascii/scientific.txt ================================================ =/;;/- +: // /; /; -X H. .//;;;:;;-, X= :+ .-;:=;:;#;. M- ,=;;;#:, ,:#;;:=, ,@ :# :#.=/++++/=.$= #= ,#; #/:+/;,,/++:+/ ;+. ,+/. ,;@+, ,#H;, ,/+, ;+;;/= @. $3.H$2#$3#X $1-X :///+; ;+=;;;.@, $2.X$3M$2@$. $1=X.//;=#/. ,;: :@#= =$H: .+#- ,#= #;-///==///-// =#, ;+ :#-;;;:;;;;-X- +: @- .-;;;;M- =M/;;;-. -X :;;::;;-. #- :+ ,-;;-;:== ,X H. ;/ #= // +; '////' ================================================ FILE: src/logo/ascii/secureblue.txt ================================================ ==++++++++++ :========++++++++++++: ===============+++++++++++ ====================++++++++++ :=============$3#%@@@%$1=====++++++++- -============$3%@%$1====$3%@@$1========+++++ -============$3%@#$1======$3@@$1==========+++- .=============$3%@+$1======$3@@$1==============. $2--$1=========$3+@@@@@@@@@@@@@@@%+$1==========- $2------$1=====$3%@@@@@@@@@@@@@@@@*$1=========== $2---------$1==$3%@@@@@@@%%@@@@@@@*$1=========== $2:----------$3%@@@@@#$1===$3+%@@@@@*$1==========- $2----------$3%@@@@@%$1===$3*@@@@@@*$1==========. $2:---------$3%@@@@@@@@@@@@@@@@*$1=========- $2:--------$3%@@@@@@@@@@@@@@@@*$1========- $2:--------$3+##############+$1========: $2-------------------------$1====- $2-------------------------- .--------------------. ------------ ================================================ FILE: src/logo/ascii/semc.txt ================================================ /\ ______/ \ / |()| $2E M C $1 | (-- | | \ \ | | .----) | |__| |_______/ / $3"$1 \ $3" " ================================================ FILE: src/logo/ascii/septor.txt ================================================ ssssssssssssssssssssssssssssssssssssssss ssssssssssssssssssssssssssssssssssssssss ssssssssssssssssssssssssssssssssssssssss ssssssssssssssssssssssssssssssssssssssss ssssssssss$2;okOOOOOOOOOOOOOOko;$1ssssssssss sssssssss$2oNWWWWWWWWWWWWWWWWWWNo$1sssssssss ssssssss$2:WWWWWWWWWWWWWWWWWWWWWW:$1ssssssss ssssssss$2lWWWWWk$1ssssssssss$2lddddd:$1ssssssss ssssssss$2cWWWWWNKKKKKKKKKKKKOx:$1ssssssssss $3yy$1sssssss$2OWWWWWWWWWWWWWWWWWWWWx$1sssssss$3yy yyyyyyyyyy$2:kKNNNNNNNNNNNNWWWWWW:$3yyyyyyyy yyyyyyyy$2sccccc;$3yyyyyyyyyy$2kWWWWW:$3yyyyyyyy yyyyyyyy$2:WWWWWWNNNNNNNNNNWWWWWW;$3yyyyyyyy yyyyyyyy$2.dWWWWWWWWWWWWWWWWWWWNd$3yyyyyyyyy yyyyyyyyyy$2sdO0KKKKKKKKKKKK0Od;$3yyyyyyyyyy yyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy yyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy yyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy yyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy yyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy ================================================ FILE: src/logo/ascii/serene.txt ================================================ __---''''''---__ . . : : - _______----_- s __----''' __---- __h_ _-' _-' h '-._''--.._ ; _-' y : ''-._ '-._/ _-' : y ':_ _--'' y m .--'' '-._.;' m m : : m y '.._ '-__ y : '--._ '''----___ : y '--._ ''-- _ y h '--._ : h s __'; vs - __..--'' - :_..--'' : . _ . `''---______---''-`` ================================================ FILE: src/logo/ascii/serpent_os.txt ================================================ ,dKNWWNKOxo;. ;xKXOdoloOWMWX0d:. .dNNd' cNMMMMNKOx;. .dWNo .:oxO000KXKl. .OMNl ..... oNMXd,. .lKWMNKkdollccc:::;;;,'.. .;ox0KNWWMMMMMMMMWWWWNK0xl,. ..,;:ccloodxO0KNWMMMMNOc. .';lxKWMMWk' ..,:ccc:,. .oXMMMO .:d0NWWWWWMWXOo,. .,cc:;,,'..oWMMW .c0WW0xl:;,;cd0NMNO:'..ldl;. .'.:XMMM xWMKc. .;OWMWX0xl. .dWMMK WMWd. .oKWMMXx, .dNMMXc NMMK:. ..:oxo:oOXMMNOdc:;;cxKWMWO; ;kXWN0xooodkOOko;. .lONMMMMWWMMWKx;. ,okKXXXKOxc'. .:kKWMMWXOl' ================================================ FILE: src/logo/ascii/sharklinux.txt ================================================ `:shd/ `:yNMMMMs `-smMMMMMMN. .+dNMMMMMMMMs .smNNMMMMMMMMm` .sNNNNNNNMMMMMM/ `omNNNNNNNMMMMMMm /dNNNNNNNNMMMMMMM+ .yNNNNNNNNNMMMMMMMN` +mNNNNNNNNNMMMMMMMMh .hNNNNNNNNNNMMMMMMMMMs +mMNNNNNNNNMMMMMMMMMMMs .hNMMNNNNMMMMMMMMMMMMMMMd .oNNNNNNNNNNMMMMMMMMMMMMMMMo `:+syyssoo++++ooooossssssssssso: ================================================ FILE: src/logo/ascii/shastraos.txt ================================================ ..,;;,'. ':oo. ;o: :o, ol .oo ..';co: ooo',;:looo; .;lddl cx .xl .c:' dd xx xx ,d; .xd cx. xx dd. cx: .xo xx ,x: 'xl xx cx' .xl xd, xx .xd dx. .xo:xx xx .xx 'c xx:.'lx: ..,;cxxxo .';:codxxl lxo cd. 'xo :o, 'ld .oc'...';lo ================================================ FILE: src/logo/ascii/shebang.txt ================================================ ' '#' '#!#' '#!#!#' '#!#!#!#' '#!#!#!#!#' '#!#!#!#!#!#' '#!#!#!#!#!#!#' #!#!#!#!#!#!' '#! #!#!#!#!#' '#!#!#!#! #!#!#!' '#!#!#!#!#!#!#! #!#!' '#!#!#!#!#!#!#!#!#!#! #' '#!#!#!#!#!#!#!#!#!#!#!#! '#!#!#!#!#!#!#!#!#!#! #!' '#!#!#!#!#!#!#!#! #!#!#!' '#!#!#!#!#!#! #!#!#!#!#!' '#!#!#!#! #!#!#!#!#' '#!#! #!#!#' '# #!' ================================================ FILE: src/logo/ascii/siduction.txt ================================================ _aass, jQh: =$w QWmwawQW )$QQQQ@( .. _a_a. ~??^ syDY?Sa, _mW>-<$c jWmi imm. ]QQwayQE 4QQmgwmQQ` ?WWQWP' -9QQQQQ@'._aas, _a%is. .adYYs,. -"?!` aQB*~^3$c _Qh;.nm .QWc. {QL ]QQp;..vmQ/ "QQmmQ@ -QQQggmQP ]QQWmggmQQ( -???" "$WQQQY` __, ?QQQQQQW! _yZ!?q, - .yWY!!Sw, "???^ .QQa_=qQ mQm>..vmm $QQWQQP $QQQgmQQ@ "???" _aa, -9WWQQWY` _mB>~)$a -~~ mQms_vmQ. ]WQQQQQP -?T??" ================================================ FILE: src/logo/ascii/skiffos.txt ================================================ $2 ,@@@@@@@@@@@w,_ $2====~~~,,.$2A@@@@@@@@@@@@@@@@@W,_ $1`||||||||||||||L{$2"@$@@@@@@@@B" $1`|||||||||||||||||||||L{$2"$D $2@@@@@@@@@@@@@@@@@@@@@$1_||||}==, $2*@@@@@@@@@@@@@@@@@@@@@@@@@p$1||||==, $1`'||LLL{{""$2@$B@@@@@@@@@@@@@@@p$1|| $1`~=|||||||||||L"$2$@@@@@@@@@@@ $1````'"""""""$2'"""""""" ================================================ FILE: src/logo/ascii/slackel.txt ================================================ _aawmmmmmwwaaaaas,,,_. .ammmmm###mmmmmmm###BQmm##mws .am###mmBmBmBmBmBmBmmmmm#mmmm#2 7'$2ZZZZ$1,%" /7'$2ZZZZ$1/>' ,//$2ZZZZ$1,// ,%<$2ZZZZ$1,%< >%^$2ZZZZ$1x%^_____ 4>'$2ZZZZ$1---------9b H$2EEEEEEEEEEEEEEEE$1H H$2EEEEEEEEEEEEEEEE$1H Yq$2^************^$1pY `````````````` ================================================ FILE: src/logo/ascii/sleeperos_small.txt ================================================ _____ [$2BBBBB$1] `^^7$2&$1/` ,/$2&$1/` ,/$2&$1Z__ [$2BBBBB$1] `^^^^^` ================================================ FILE: src/logo/ascii/slitaz.txt ================================================ @ @( @ @@ @@ @ @/ @@ @@ @@ @@ @@ %@@ @@ @@ @@ %@@@ @@@@@. @@@@ @@ @@@ @@@@ @@@@@@@ &@@@ @@@ @@@@@@@ %@@@@@@@@@@@@ &@@@% @@@@@@@/ ,@@@@@@@@@@@@@@@@@@@@@@@@@ .@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@/ @@@@@@. @@@@@@@@@@@@@@@@@@@@@ /@@@@@@ @@ @@@@@ @@@@@@@@@@@@, @@@@@ @@@ @@ @@@@. @@@@@@@@@@@@@% #@@@@ @@. @@ ,@@ @@@@@@@@@@@@@ @@@ @@ @ @@. @@@@@@@@@@@@@ @@@ *@ @ @@ @@@@@@@@@@@@ @@ @ @ @@@@@@@@@. #@ @ ,@@@@@ @ ================================================ FILE: src/logo/ascii/smartos.txt ================================================ yyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy yyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy yyyys oyyyyyyyyyyyyyyyy yyyys yyyyyyyyy oyyyyyyyyyyyyyyyy yyyys yyyyyyyyy oyyyyyyyyyyyyyyyy yyyys yyyyyyyyy oyyyyyyyyyyyyyyyy yyyys yyyyyyyyy oyyyyyyyyyyyyyyyy yyyys yyyyyyyyyyyyyyyyyyyyyyyyyyyy yyyyy syyyy yyyyyyyyyyyyyyyyyyyyyyyyyyyy syyyy yyyyyyyyyyyyyyyy syyyyyyyyy syyyy yyyyyyyyyyyyyyyy oyyyyyyyyy syyyy yyyyyyyyyyyyyyyy oyyyyyyyyy syyyy yyyyyyyyyyyyyyyy syyyyyyyyy syyyy yyyyyyyyyyyyyyyy yyyyy yyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy yyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy ================================================ FILE: src/logo/ascii/snigdhaos.txt ================================================ WK0OO0X WKOxk0XWNXkxN WXOxk0N kk WKkxOXW NxO NX0ddk00OOOOkkkkkkkkk0 WKkxO0dOXXNNNNWWW WXKK NxkN kxW XOxxdN KoX WkxX WkxK Xd0 WxkW XkkX kkW NkxX W0dK XxOWkkW XxkN NkxX XoKWkxK Kd0 XkkN WOdN NkxX NxO OxNKxOW XdO kk kdk0W XoK WXkxN WNNXKXXXXKKKK000OOdxkk0X KdOO000KKKKXXXXX0xx0W XoK N0kx0N Nd0 NKOxOKW Nkk0K0kxOKN WXXXN ================================================ FILE: src/logo/ascii/soda.txt ================================================ $2@&&&&$1 *'*, $2@@@@@@@&&&$1 ** '*, $2@@@@@@@@@@@&&&& @&@@@@@@@@@@@@@@@&&& $1******$2@@@@@@@@@@@@@@@&&&& $1************$2@@@@@@@@@@@@@@ $1*****************$2@@@@@@@@@ $1**********************$2@@@ @@@@@$1******************** $2@@@@@@@@@$1*************** $2@@@@@@@@@@@@@@@$1********* $2@@@@@@@@@@@@@@@@@@$1**** $2@@@@@@@@@@@@@@@@@@ @@@@@@@@@@@@ @@@@@@@ ================================================ FILE: src/logo/ascii/solaris.txt ================================================ `- ` `-- `+- .: .+: `++: -/+- . `.::` -++/``:::`./+/ `.-/. `++/-`.` ` /++:` `` ./:` .: `..`.- ``./+/:- -+++:- -/+` :. ================================================ FILE: src/logo/ascii/solaris_small.txt ================================================ . .; . . :; :: ;: . .;. .. .. .;. .. .. .. .. .;, ,;. ================================================ FILE: src/logo/ascii/solus.txt ================================================ $2 -``````````` `-+/------------.` .---:mNo---------------. .-----yMMMy:---------------. `------oMMMMMm/----------------` .------/MMMMMMMN+----------------. .------/NMMMMMMMMm-+/--------------. `------/NMMMMMMMMMN-:mh/-------------` .-----/NMMMMMMMMMMM:-+MMd//oso/:-----. -----/NMMMMMMMMMMMM+--mMMMh::smMmyo:-- ----+NMMMMMMMMMMMMMo--yMMMMNo-:yMMMMd/. .--oMMMMMMMMMMMMMMMy--yMMMMMMh:-yMMMy-` `-sMMMMMMMMMMMMMMMMh--dMMMMMMMd:/Ny+y. `-/+osyhhdmmNNMMMMMm-/MMMMMMMmh+/ohm+ .------------:://+-/++++++$1oshddys: -hhhhyyyyyyyyyyyhhhhddddhysssso- `:ossssssyysssssssssssssssso:` `:+ssssssssssssssssssss+- `-/+ssssssssssso+/-` `.-----..` ================================================ FILE: src/logo/ascii/source_mage.txt ================================================ :ymNMNho. .+sdmNMMMMMMMMMMy` .-::/yMMMMMMMMMMMm- sMMMMMMMMMMMm/ /NMMMMMMMMMMMMMm: .MMMMMMMMMMMMMMMMM: `MMMMMMMMMMMMMMMMMN. NMMMMMMMMMMMMMMMMMd mMMMMMMMMMMMMMMMMMMo hhMMMMMMMMMMMMMMMMMM. .`/MMMMMMMMMMMMMMMMMs :mMMMMMMMMMMMMMMMN` `sMMMMMMMMMMMMMMM+ /NMMMMMMMMMMMMMN` oMMMMMMMMMMMMM+ ./sd.-hMMMMMMMMmmN` ./+oyyyh- `MMMMMMMMMmNh sMMMMMMMMMmmo `NMMMMMMMMMd: -dMMMMMMMMMo -shmNMMms. ================================================ FILE: src/logo/ascii/sparky.txt ================================================ . `-:-` .o` .-///-` `oo` .:/++:. os+` -/+++:` ``.........``` /ys+`./+++/-.-::::::----......`` `syyo`++o+--::::-::/+++/-`` -yyy+.+o+`:/:-:sdmmmmmmmmdy+-` ::-` :yyy/-oo.-+/`ymho++++++oyhdmdy/` `/yy+-`.syyo`+o..o--h..osyhhddhs+//osyy/` -ydhs+-oyy/.+o.-: ` ` :/::+ydhy+```-os- .sdddy::syo--/:. `.:dy+-ohhho ./: :yddds/:+oo+//:-`- /+ +hy+.shhy: `` `:ydmmdysooooooo-.ss`/yss--oyyo `./ossyyyyo+:-/oo:.osso- .oys ``..-------::////.-oooo/ :so `...----::::::::--.`/oooo: .o: ``````` ++o+:` `:` ./+/-` ` `-:-. `` ================================================ FILE: src/logo/ascii/spoinkos.txt ================================================ @..@ (----) ( >__< ) ^^ ~~ ^^ | Spoink! | ================================================ FILE: src/logo/ascii/star.txt ================================================ ./ `yy- `y.`y` `` s- .y ` +h//:..` +/ /o ``..:/so /o``.-::/:/+ o/://::-.`+o` :s` `. .` `s/ .y. .s- `y- :s` .-//. /+:. .:/:. .:/:. -+o:. .:+:. -///++///:::` .-::::///+so- ``..o/ d-....``` s. `/. d h .+o-+o- h. h -o/` `/o: s: -s/o:` `:o/+/ /s- -yo ================================================ FILE: src/logo/ascii/steamdeck.txt ================================================ $2 .xXK0kdc'.. .OMMMMWNK0Odc'. .OMMMMMMMMMMWNOl'. .OMMMMMMMMMMMMMWXx;. .:ddk0XWMMMMMMMMMMNx' ..;oxONMMMMMMMWKc. $1 ..,;::::;,'.$2 .;xXMMMMMMMNo. $1 .':looooooooool:,..$2 .;OWMMMMMMNl. $1 .:oooooooooooooooooc'$2 .xWMMMMMMK: $1 'coooooooooooooooooool,$2 'OMMMMMMWx. $1.coooooooooooooooooooool.$2 cNMMMMMM0, $1'loooooooooooooooooooooo,$2 ,KMMMMMMX: $1'loooooooooooooooooooooo,$2 ,KMMMMMMX: $1.:oooooooooooooooooooooc.$2 lNMMMMMM0, $1 .coooooooooooooooooool'$2 ,0MMMMMMWd. $1 .:looooooooooooooolc.$2 'OWMMMMMMK; $1 ..;cloooooooooc;'..$2 .c0WMMMMMMXc. $1 ..',;;;;,'..$2 ..cONMMMMMMMXc. ..,cxOKWMMMMMMMW0;. .cxk0KNWMMMMMMMMMWXo. .OMMMMMMMMMMMMMWKo' .OMMMMMMMMMMWKx:. .OMMMWNX0Oxl;. .o0Oxoc,.. ================================================ FILE: src/logo/ascii/steamdeck_small.txt ================================================ $2__ \ $1## $2\ $1## $2/ __/ ================================================ FILE: src/logo/ascii/steamos.txt ================================================ $1 .,,,,. .,'onNMMMMMNNnn',. .'oNMANKMMMMMMMMMMMNNn'. .'ANMMMMMMMXKNNWWWPFFWNNMNn. ;NNMMMMMMMMMMNWW'' ,.., 'WMMM, ;NMMMMV+##+VNWWW' .+;'':+, 'WMW, ,VNNWP+$2######$1+WW, $2+: $1:+, +MMM, '$2+#############, +. ,+' $1+NMMM $2 '*#########*' '*,,*' $1.+NMMMM. $2 `'*###*' ,.,;###$1+WNM, $2 .,;;, .;##########$1+W $2,',. '; ,+##############' '###+. :,. .,; ,###############' '####.. `'' .,###############' '#####+++################' '*##################*' ''*##########*'' '''''' ================================================ FILE: src/logo/ascii/stock_linux.txt ================================================ #G5J5G# &BPYJJJJJJJYPB& &#G5JJJJJJY5YJJJJJJ5G#& #G5YJJJJJY5G#& &#G5YJJJJJY5G# BPYJJJJJJJ5B& &BPYJJJJJJYPB JJJJJJJJJJY5G#& &BPYJJJJJ JJJJJJJJJJJJJJY5G# &JJJJJ PYJJJJJJJJJJJJJJJJYPB& GYJJJ &BPYJJJJJJJJJJJJJJJJ5PB& &BP #G5YJJJJJJJJJJJJJJJY5G# PB& &BP5JJJJJJJJJJJJJJJJYPB& JJJYG &BPYJJJJJJJJJJJJJJJJYP JJJJJ& #G5YJJJJJJJJJJJJJJ JJJJJYPB& &#G5YJJJJJJJJJJ BPYJJJJJJYPB& &B5JJJJJJJYPB #G5YJJJJJY5G#& &#G5YJJJJJY5G# &#G5JJJJJJY5YJJJJJJ5G#& &BPYJJJJJJJYPB& #G5J5G# ================================================ FILE: src/logo/ascii/sulin.txt ================================================ /\ /\ ( \\ // ) \ \\ // / \_\\||||//_/ \/ _ _ \ \/|(O)(O)| \/ | | ___________________\/ \ / // // |____| // || / \ //| \| \ 0 0 / // \ ) V / \____/ // \ / ( / \ /_________| |_/ / /\ / | || / / / / \ || | | | | | || | | | | | || |_| |_| |_|| \_\ \_\ \_\ ================================================ FILE: src/logo/ascii/summitos.txt ================================================ :~^ ~5PPJ :5555P7 .Y55555P~ ?P5555555^ 7P55555555Y. ~5555555555PJ$2 7YJ^ $1^555555555555P7$2 7GGGP^ $1.Y5555555555555P!$2 ~PPPPP5. $1JP555555555555555^$2 ^PPPPPPGY $17P5555555555555555Y$2^5PPPPPPPG? $1~P55555555555555555$2^PPPPPPPPPPP! $1^555555555555555555$2^PPPPPPPPPPPPP^ $1.Y555555555555P$3G$1P55$2^PPPPPPPPPPPPPP5: $1JP55555555555P$3GGG$1P$2^PPPPPPPPPPPPPPPGY $17P555555555555$3GGGGG$2PPPPPPPPPPPPPPPPPG? $1!P555555555555$3GGGGGGG$2PPPPPPPPPPPPPPPPPG! $1^5555555555555$3PGGGGGGGG$2PPPPPPPPPPPPPPPPPP^ $1:Y555555555555$3PGGGGGGGGGG$2PPPPPPPPPPPPPPPPP5: $1.JP55555555555$3PGGGGGGGGGGGG$2PPPPPPPPPPPPPPPPGY. $1?PPPPPPPPPPPP$3GGBBBBBBBBBBBBB$2GGGGGGGGGGGGGGGGGJ $1^~~~~~~~~~~~$3!!!!!!!!!!!!!!!!!$2!!!!!!!!!!!!!!!!~ ================================================ FILE: src/logo/ascii/suse.txt ================================================ kKKKKKd' Okxol:;,. kKKKKKKK0kOKKKKKKKKKKOxo:, KKKKKKKKKKKKKKKKKKK0P^,,,^dx: KKKKKKKKKKKKKKKKKKk'.oOPPb.'0k. KKKKKKKKKKKKKKKKKK: kKx..dd lKd KKKKKKKKKKKKOx0KKKd ^0KKKO' kKKc KKKKKKKKKKKKK;.;oOKx,..^..;kKKK0. KKKKKKKKKKKKK0o;...^cdxxOK0O/^^' KKKKKKKKKKKKKKKKK0x;,,......,;od kKKKKKKKKKKKKKKKKKKKKKKK00KKOo^ kKKKKKOxddxkOO00000Okxoc;'' ================================================ FILE: src/logo/ascii/swagarch.txt ================================================ $2 .;ldkOKXXNNNNXXK0Oxoc,. ,lkXMMNK0OkkxkkOKWMMMMMMMMMM; 'K0xo ..,;:c:. `'lKMMMMM0 .lONMMMMMM' `lNMk' $2 ;WMMMMMMMMMO. $1....::... $2 OMMMMMMMMMMMMKl. $1.,;;;;;ccccccc, $2 `0MMMMMMMMMMMMMM0: $1.. .ccccccc. $2 'kWMMMMMMMMMMMMMNo. $1.,:' .ccccccc. $2 `c0MMMMMMMMMMMMMN,$1,:c; :cccccc: $2 ckl. `lXMMMMMMMMMX$1occcc:.. ;ccccccc. $2dMMMMXd, `OMMMMMMWk$1ccc;:''` ,ccccccc: $2XMMMMMMMWKkxxOWMMMMMNo$1ccc; .cccccccc. $2 `':ldxO0KXXXXXK0Okdo$1cccc. :cccccccc. :ccc:' `cccccccc:, '' ================================================ FILE: src/logo/ascii/t2.txt ================================================ $3//$2 $1TTTTTTTTTTTTTTT$2 $3//$2 $1TTTTTTTTTTTTTTT$2 $3//$2 $1..::$2 $4:-:.$2 $4ttt$2 $3//$2 $1.::.$2 $4.:-:$2 $4ttt$2 $3//$2 $1.:.$2 $4--$2 $4ttt$2 $3//$2 $4:-:$2 $4ttt$2 $3//$2 $4.:-:$2 $4ttt$2 $3//$2 $4:-:.$2 $4ttt$2 $3//$2 $4.--.$2 $4ttt$2 $3//$2 $4.:-:$2 $4ttt$2 $3//$2 $4...$2 $4ttt$2 $3//$2 $3//$2 $3.:::::::::::.$2 $3//$2 ================================================ FILE: src/logo/ascii/t2_small.txt ================================================ $2TTTTTTTTTT tt $1222$2 tt $12 2$2 tt $12$2 tt $12$2 tt $122222$2 ================================================ FILE: src/logo/ascii/tails.txt ================================================ `` ./yhNh syy/Nshh `:o/ N:dsNshh █ `ohNMMd N-/+Nshh `yMMMMd N-yhMshh yMMMMd N-s:hshh █ yMMMMd so//. N-oyNsyh yMMMMd d Mms. N:hohhhd:. yMMMMd syMMM+ Nsyh+-..+y+- yMMMMd :mMM+ +hy- -ss/`yMMMM `+d+ :sy/. ./yNMMMMm `` .+ys- `:+hNMMMMMMy/` `hNmmMMMMMMMMMMMMdo. dMMMMMMMMMMMMMMMMMNh: +hMMMMMMMMMMMMMMMMMmy. -oNMMMMMMMMMMmy+.` `:yNMMMds/.` .//` ================================================ FILE: src/logo/ascii/tatra.txt ================================================ . .:. . .:. .^^.!.:::. .^!?J?^ .:^.^!!!~:~^. .7??77!~~^. .~^.!??77?!.^~: ..:^^7JJJ7~~^. .^~.^7???7~.~~. .7??????????! .:^:^^~^^:!^ ^: .......^!:... .!7~.::.!.::. ~BG~ :^ ^~: :!!~ ~. ?BBBB! ^?J!. .!~. :!. .JBY. .Y#BBBY?~!???J7. :^^. .. :5#B#P~P#BBP?7?55J?J7: ^P#BBBBBBBB5?7J5555J!..... !BBBBBBGBBGJ77::Y555J?77777^ ?BBBBG5JJ5PJ?!: .?Y??????77?~. .YBGPYJ??????Y?^^^^~7?????????7?!. .^^:..::::::::.....:::::::::::..:. ================================================ FILE: src/logo/ascii/tearch.txt ================================================ @@@@@@@@@@@@@@ @@@@@@@@@ @@@@@@ @@@@@ @@@@@ @@ @@ @% @@ @ @ @@@@@@@@@@@@@@@@@@@@@@@@ @@ .@@@@@@@@@@@@/@@@@@@@@@@@@ @@@@@@@@@@@@///@@@@@@@@@@@@ @@@@@@@@@@@@@((((@@@@@@@@@@@@ @@@@@@@@@@@#(((((((#@@@@@@@@@@@ @@@@@@@@@@@#//////////@@@@@@@@@@& @@@@@@@@@@////@@@@@////@@@@@@@@@@ @@@@@@@@//////@@@@@/////@@@@@@@@@ @@@@@@@//@@@@@@@@@@@@@@@//@@@@@@@ @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ @@ .@@@@@@@@@@@@@@@@@@@@@@@@@ @ @@@@@@ @@@. @@@@@@@ @@@@@@@&@@@@@@@# #@@@@@@@@@@@@@@@@ @@@@@@@@@@@@@@@@@@@@@@@@@@@@@ @@@@@@@@@@@@@@@@@@@@@ ================================================ FILE: src/logo/ascii/templeos.txt ================================================ $1aooooo$2, $1aooooo$2`` $1aooooo$2`` $3(((((( $1aooooo$2``$3(((((( $1oooo$3@@ @C@*;,./k@@ @@ @$1oooo$2 $1oooooo$3@@ @@@@@@@@ @@k$1oooooo$2`` $1oooooooo$3\@ @/@@ @@$1ooooooooo$2`` `````````$3'*@*/'$2```````````` $3@@@( $1aooooo$2`` $3@@@@ @@ @ $1aooooo$2`` $3@ @@ @@@ $1aooooo$2`` $3@@@, @@ $1aooooo$2`` $3 @@ @ $1aooooo$2`` $3 (@ $1aooooo$2`` $1aooooo$2`` $1aooooo$2`` $1aooooo$2`` $1aooooo$2`` ``````` ````` ================================================ FILE: src/logo/ascii/tileos.txt ================================================ $2 ,. $2 ########## $2 ###################. $2 ############################ $2 #################################### $2 ###################################### $1,(/$2 *############################( $3.%$1 $1((((((($2 (#################### $3%%####$1 $1(((((((((((/$2 ############ $3%#########$1 $1(((((((((((((((($2 ##/ $3(##############$1 $1(((((((((((((((((((($2 $3###################$1 $1((((((((((((((((((((($2 $3####################$1 $1((((((((((((((((((((($2 $3####################$1 $1((((((((((((((((((((($2 $3####################$1 $1((((((((((((((((((((($2 $3####################$1 $1 (((((((((((((((((((($2 $3###################%$1 $1 (((((((((((((((($2 $3################/$1 $1 (((((((((((($2 $3###########%$1 $1 ((((((($2 $3#######*$1 $1 ((($2 $3##%*$1 ================================================ FILE: src/logo/ascii/torizoncore.txt ================================================ $2 `.::-. `-://////:-. `:////////////:. `.-.` `-:///////-. ` `.://///:.` `-::-. `-://:.` .:///////////:. `-:////////:.` `.://///////:-` .:////////////:` `.-/:-` `.:///:-` $3`-+o/.$2 `.://////-. `.` `.:///////:-` `.` $3`-+sssssso/.$2 `.--. `-:///:-` -/////////////: $3`+sssssssssssso-$2 `-://///////:-` .-///////:.` `` $3-/sssssssso/.$2 ` .:///////////-. .-::.` `-://:-. $3-/sso/.$2 `.:/:-. `.://///-. `-:////////:-. $3`$2 `.:////////-. `.-. $1o-$2 -:///////////:` .:////////////:` $1-o$2 $1hdho-$2 `-://///:.` `..` `-://////:.` $1-ohdh$2 $1/ydddho-$2 `--.` `.:////:.` `-::.` $1-ohdddy/$2 $1./ydddho:`$2 `.://////////:. $1`:ohdddy/.$2 ` $1`/shddho:`$2 `.://////////:. $1`:ohddds/`$2 ` ::-` $1`/shddhs:`$2 `.:////:.` $1`:shddhs/`$2 `-:: :///:-` $1`:shddhs/`$2 `..` $1`/shddhs:`$2 `-:///- `.:///:-` $1`:ohddhs/`$2 $1`/shddho:`$2 `-:///:.` `.:///:-. $1-ohdddy/.$2 $1./ydddho-$2 .-:///-.` `.:///:-. $1-+hdddy+//+ydddh+-$2 .-://:-. `.-///:-. $1-+yddddddy+-$2 .-://:-. .-://:-. $1./++/.$2 .-///:-. .-:///:.` `.:///:-` `-:///:.````.:///:-` `-:////////:-` `-::::-` ================================================ FILE: src/logo/ascii/trisquel.txt ================================================ ,o$$$$$$o. ,o$$$$Y"""Y$$$$b ,o$$$$$$$$$$$$o. ,$$$$' , Y$$$$b ,o$$$$$$$$$$$$$$$$$$$$$$$$o. :$$ b Y$$$$. ,$$$$"' "Y$$$$$$$$o. 'b. ,b d$$$$$$ $$$$' .d$$$$$$$$b '$$$$$$$$o 'Y$$$$$$Y d$$$$$$' $$$$' q' 'b '$$$$$$$$$$o._ _.o$$$$$$$$' .$$$$,_ _,d$$ ,$$$$$$$$$$$$$$$$$$$$$$$$$2$$$$$$$$Y' $1 '$$$$$$$$aaa$$$$$$' .$$$$$$$$$$$$$2$$$$$$$$$$$$$$$$' $1 """" $2d$$$$$$$$"' d$$$$$$' .d$$$$b. $$$$$$$$ .$$" 'a$$. $$$$$$$$ $$b $$$$. '$$$$$$. '$$b,,. $$$$$$ '$$$$$$. .$$$$' 'a$$$$$$o._.o$$$$a' 'a$$$$$$$$a' ================================================ FILE: src/logo/ascii/truenas_scale.txt ================================================ ++ $2++ $1++++++ $2++++++ $1+++++++++ $2+++++++++ $1+++++++ $2+++++++ $1+++ $2+++ $1+ $3--------$1 $2+ $1+++ $2+++ $1++++++ $2++++++ $3------$1 ++++++ $2++++++ $1++++++++ $2++++++++++ $1++++++++++ $2++++++++ $1+++++++ $3--$1 $2+++++++ $1+++++++ $3--$1 $2+++++++ $2+++ $1+ $3--------$1 $2+ $1+++ $2+++ $1+ $3--------$1 $2+ $1+++ $2++++++ $3--------$1 ++++++ $2++++++ $3--------$1 ++++++ $2++++++++++ $1+++++++++ $2+++++++++ $1++++++++++ $2+++++++++ $1+++++++ $3--$1 $2+++++++ $1+++++++++ $2+++++ +++ $3--------$1 +++ +++++ $2++ +++++++ $3------$1 +++++++ ++ $2++++++++++ $1++++++++++ $2++++++++ $1++++++++ $2+++++ $1+++++ $2++ $1++ ================================================ FILE: src/logo/ascii/tuxedo_os.txt ================================================ #############################+ .%#######################%@@@+ . :#@%- .#@%%%- =@@#. *@%@@= +@@+ =@@%@+.#@%- -@@%@@@#: .#@%%@- :#@@%@@= =@@+=@@%@+ .#@@= -@@%@# -%@#: :%@%@%: *@@+ .#%%%%- :%@%- . +@@@%#%%%%%%%%%%%%%%%%%%%%%%%. +######################******* ================================================ FILE: src/logo/ascii/twister.txt ================================================ $3.......................$4.......$5.,:ccllllcc:.$4... $3.......................$5.,;cldxkO0KXNNNWNNNX0x: $3.................$5.,:ldkOOKK0OkkkkkOKWMMMMMMMNo $3.............$5,;coxO0Oxo::'$4.........$5,oXMMMMMM0' $3.........$5,:ldkOOkc;'$3...$4.............$5;OMMMMW0:$4. $3.......$5;ldkOxol:'$3......$4............$5,dNMMX0''$4.. $3....$5;:oxkxl:'$3..........$4..........$5,lOWWXx:.,'$4.. $3..$5,ldxkkd:'$3............$5.:c$4....$5':lkNMNOc,cdl'$4.. $3.$5;oxxkkkc'$3.........$5.:clc;'.';lkKNNKk;;ck00l$4... $5.lxxxkkkxoooooddxkOOko'..cok0KOd':::o0NXd;'$4... $3.$5:odxxkkOOOOOOOkdoi'..:ddxdoc:::od0NWW0c'$4..... $3...$5':;gggggg;::''.......::::x0XWMMMNO.::'$4..... $3..............$5;......,;od0KNWMWNK0O:::do'$4..... $3...............$5'cclgggggggxdll":::'XKoo,$4...... $3.................$5',,,,,,,::::;ooNWMWOc'$4....... $3..................$5,:loxxxO0KXNNXK0ko:'$4........ $3..................$5';::;oTcoggcoF":::'$4......... $3..................$5':o,.:::::::::,p'$4........... $3..................$5;'ccdxkOOOdxlf'$4............. $3.................$5,l;;XgggggXP:'$4............... $3................$5;lco;::::::'$4.................. $3..............$5.';ggggol'`$4..................... $3.............$5':oo:''$3...$4....................... ================================================ FILE: src/logo/ascii/ublinux.txt ================================================ $1 UUU $1 UUUUUUUUUUU $1 UUUUUUUUUUUUUUUUU $1 UUUUUUUUUUUUUUUUUUUUUUUUU $1UUUUUUUUUUUUUUUUUUUUUUUUUUUUUUU$3#. $1UU$2BBBBB$1UUUUUU$2BBBBBBBBBBB.$1UUUUUU$3## $1UU$2BBBBB$1UUUUUU$2BBBBBBBBBBBBB:$1UUUU$3## $1UU$2BBBBB$1UUUUUU$2BBBBB$1UUUU$2BBBBB$1UUUU$3## $1UU$2BBBBB$1UUUUUU$2BBBBB$1UUUU$2BBBBB$1UUUU$3## $1UU$2BBBBB$1UUUUUU$2BBBBBBBBBBBB$1UUUUUU$3## $1UU$2BBBBB$1UUUUUU$2BBBBB$1UUUU,$2BBBB.$1UUU$3## $1UUU$2BBBBB$1UUUUU$2BBBBB$1UUUUU$2BBBBB$1UUU$3## $1UUU$2BBBBB$1UUUUU$2BBBBB$1UUUUU$2BBBBB$1UUU$3## $1UUU#$2BBBBBBBBBBBBBBBBBBBBBBB$1UUUU$3## $1UUUUUU$2'BBBBBBBBBBBBBBBB'$1UUUUUUU$3## $1 UUUUUUUUUUUUUUUUUUUUUUUUU$3##' $1 UUUUUUUUUUUUUUUUU$3###' $1 UUUUUUUUUUU$3###' $1 UUU$3###' ================================================ FILE: src/logo/ascii/ublinux_small.txt ================================================ $1 UUUU $1 UUUUUUUUUU $1 $2BBB$1UUUU$2BBBBBB$1UU$3# $1U$2BBB$1UUUU$2BBB$1UUU$2B$1UU$3# $1U$2BBB$1UUUU$2BBB$1UUU$2B$1UU$3# $1U$2BBB$1UUUU$2BBBBBB$1UUU$3# $1U$2BBB$1UUUU$2BBB$1UUU$2BB$1U$3# $1UU$2BBB$1UUU$2BBB$1UUU$2BB$1U$3# $1 UU$2BBBBBBBBBBB$1UU$3# $1 UUUUUUUUU$3# $1 UUU$3# ================================================ FILE: src/logo/ascii/ubuntu.txt ================================================ .... $2.',:clooo: $1.:looooo:. $2.;looooooooc $1.oooooooooo' $2.;looooool:,''. $1:ooooooooooc $2;looool;. $1'oooooooooo, $2;clool' $1.cooooooc. $2,, $2... $1...... $2.:oo, $1.;clol:,. $2.loooo' $1:ooooooooo, $2'ooool $1'ooooooooooo. $2loooo. $1'ooooooooool $2coooo. $1,loooooooc. $2.loooo. $1.,;;;'. $2;ooooc $2... $2,ooool. $2.cooooc. $1..',,'. $2.cooo. $2;ooooo:. $1;oooooooc. $2:l. $2.coooooc,.. $1coooooooooo. $2.:ooooooolc:. $1.ooooooooooo' $2.':loooooo; $1,oooooooooc $2..';::c' $1.;loooo:' ================================================ FILE: src/logo/ascii/ubuntu_budgie.txt ================================================ ./oydmMMMMMMmdyo/. :smMMMMMMMMMMMhs+:++yhs: `omMMMMMMMMMMMN+` `odo` /NMMMMMMMMMMMMN- `sN/ `hMMMMmhhmMMMMMMh sMh` .mMmo- /yMMMMm` `MMm. mN/ yMMMMMMMd- MMMm oN- oMMMMMMMMMms+//+o+: :MMMMo m/ +NMMMMMMMMMMMMMMMMm. :NMMMMm M` .NMMMMMMMMMMMMMMMNodMMMMMMM M- sMMMMMMMMMMMMMMMMMMMMMMMMM mm` mMMMMMMMMMNdhhdNMMMMMMMMMm oMm/ .dMMMMMMMMh: :dMMMMMMMo mMMNyo/:/sdMMMMMMMMM+ sMMMMMm .mMMMMMMMMMMMMMMMMMs `NMMMm. `hMMMMMMMMMMM.oo+. `MMMh` /NMMMMMMMMMo sMN/ `omMMMMMMMMy. :dmo` :smMMMMMMMh+-` `.:ohs: ./oydmMMMMMMdhyo/. ================================================ FILE: src/logo/ascii/ubuntu_cinnamon.txt ================================================ .-/+oooooooo+/-. `:+oooooooooooooooooo+:` -+oooooooooooooooooooooooo+- .ooooooooooooooooooo$2:ohNd$1oooooo. /oooooooooooo$2:/+oo++:/ohNd$1ooooooo/ +oooooooooo$2:osNdhyyhdNNh+:+$1oooooooo+ /ooooooooo$2/dN/$1ooooooooo$2/sNNo$1ooooooooo/ .ooooooooo$2oMd:$1oooooooooooo$2:yMy$1ooooooooo. +ooooo$2:+o/Md$1oooooo$2:sm/$1oo/ooo$2yMo$1oooooooo+ ooo$2:sdMdosMo$1ooooo$2oNMd$1//$2dMd+$1o$2:so$1ooooooooo oooo$2+ymdosMo$1ooo$2+mMm$1+/$2hMMMMMh+hs$1ooooooooo +oooooo$2:$1:$2/Nm:$1/$2hMNo$1:y$2MMMMMMMMMM+$1oooooooo+ .ooooooooo$2/NNMNy$1:o$2NMMMMMMMMMMo$1ooooooooo. /oooooooooo$2:yh:$1+m$2MMMMMMMMMMd/$1ooooooooo/ +oooooooooo$2+$1/h$2mMMMMMMNds//o$1oooooooo+ /oooooooooooo$2+:////:o/ymMd$1ooooooo/ .oooooooooooooooooooo$2/sdh$1oooooo. -+oooooooooooooooooooooooo+- `:+oooooooooooooooooo+:` .-/+oooooooo+/-. ================================================ FILE: src/logo/ascii/ubuntu_gnome.txt ================================================ $3 ./o. .oooooooo .oooo```soooo .oooo` `soooo .ooo` $4.o.$3 `\/ooo. :ooo $4:oooo.$3 `\/ooo. sooo $4`ooooo$3 \/oooo \/ooo $4`soooo$3 `ooooo `soooo $4`\/ooo$3 `soooo $4./oo $3`\/ooo $4`/oooo.$3 `/ooo $4`\/ooo. $3`/oooo. $4`/oooo.$3 `` $4 `\/ooo. $3/oooo $4/ooo` $4 `ooooo $3`` $4.oooo $4 `soooo. .oooo` `\/oooooooooo` ``\/oo`` ================================================ FILE: src/logo/ascii/ubuntu_kylin.txt ================================================ $1 .__=liiiiiii=__, ._=liiliii|i|i|iiilii=_. _=iiiii|ii|i|ii|i|inwwwzii=, .=liiii|ii|ii|wwww|i$23QWWWW$1ziii=, =lii|i|ii|i|$2QQQWWWWWm]QWWQD$1||iiii= =lii|iiivw$2Qm$1>3$2WWWWWQWQQwwQw$1cii|i|ii= =lii|ii|n$2QWWWQ$1|)i$2|i$1%i|]$2TQWWWWm$1|ii|i|i= .li|i|i|m$2WWWQV$1|ii$2wmD$1|iiii|$2TWWWWm$1|i|iiii, =iii$2www|$WQWk$1|i$2aWWWD$1|i|i|ii]$2QQWQk$1|ii|i|= iii$2QWWWQz$WW$1|i$2jQQWQm$1w|ii$2wW$1k|$2TTTTY$1i|i|iii iiI$2QWQWWtyQQ$1|i|$2$WWWWWQWk$1||i|i|ii||i|ii|i <|i|$2TTT|mQQWz$1|i$23D$1]C|$2nD$W$1|ii$2vWWWWk$1||ii|i> -|ii|i|i$2WWWQw$1|$2Tt$1|i3$2T$1|$2T$1|i|$2nQWQWDk$1|ii|ii` <|i|iii|$2VWQWWQ$1|i|i|||ii$2wmWWQWD$1||ii|ii+ <|ii|i|i]$2$W@$1tv$2QQQWQQQWWTTHT$11|iii|i|> <|i|ii|ii||v$2QWWWQWWW@vmWWWm$1|i|i|i> -<|i|ii|ii|i|$2TTTTTT$1|]$2QQWWWC$1|ii>` -<|i|ii|i|ii|i|i|ii3$2TTT$1t|i>` ~<|ii|ii|iiiii|i|||i>~ -~~<|ii|i||i>~~` ================================================ FILE: src/logo/ascii/ubuntu_mate.txt ================================================ $1 .:/+oossssoo+/:.` `:+ssssssssssssssssss+:` -+sssssssssssssss$2y$1ssssssss+- .osssssssssssss$2yy$1ss$2mMmh$1ssssssso. /sssssssss$2ydmNNNmmd$1s$2mMMMMNdy$1sssss/ `+ssssssss$2hNNdy$1sssssss$2mMMMMNdy$1ssssss+` +sssssss$2yNNh$1ss$2hmNNNNm$1s$2mMmh$1s$2ydy$1sssssss+ -sssss$2y$1ss$2Nm$1ss$2hNNh$1ssssss$2y$1s$2hh$1ss$2mMy$1sssssss- +ssss$2yMNdy$1ss$2hMd$1ssssssssss$2hMd$1ss$2NN$1sssssss+ sssss$2yMMMMMmh$1sssssssssssss$2NM$1ss$2dMy$1sssssss sssss$2yMMMMMmhy$1ssssssssssss$2NM$1ss$2dMy$1sssssss +ssss$2yMNdy$1ss$2hMd$1ssssssssss$2hMd$1ss$2NN$1sssssss+ -sssss$2y$1ss$2Nm$1ss$2hNNh$1ssssssss$2dh$1ss$2mMy$1sssssss- +sssssss$2yNNh$1ss$2hmNNNNm$1s$2mNmh$1s$2ymy$1sssssss+ +ssssssss$2hNNdy$1sssssss$2mMMMMmhy$1ssssss+ /sssssssss$2ydmNNNNmd$1s$2mMMMMNdh$1sssss/ .osssssssssssss$2yy$1ss$2mMmdy$1sssssso. -+sssssssssssssss$2y$1ssssssss+- `:+ssssssssssssssssss+:` .:/+oossssoo+/:. ================================================ FILE: src/logo/ascii/ubuntu_old.txt ================================================ --+oossssssoo+-- .:+ssssssssssssssssss+:. -+ssssssssssssssssssyyssss+- .ossssssssssssssssssd$2MMMNy$1sssso. /sssssssssss$2hdmmNNmmyNMMMMh$1ssssss/ +sssssssss$2hm$1yd$2MMMMMMMNddddy$1ssssssss+ /ssssssss$2hNMMM$1yh$2hyyyyhmNMMMNh$1ssssssss/ .ssssssss$2dMMMNh$1ssssssssss$2hNMMMd$1ssssssss. +ssss$2hhhyNMMNy$1ssssssssssss$2yNMMMy$1sssssss+ oss$2yNMMMNyMMh$1ssssssssssssss$2hmmmh$1ssssssso oss$2yNMMMNyMMh$1sssssssssssssshmmmhssssssso +ssss$2hhhyNMMNy$1ssssssssssss$2yNMMMy$1sssssss+ .ssssssss$2dMMMNh$1ssssssssss$2hNMMMd$1ssssssss. /ssssssss$2hNMMM$1yh$2hyyyyhdNMMMNh$1ssssssss/ +sssssssss$2dm$1yd$2MMMMMMMMddddy$1ssssssss+ /sssssssssss$2hdmNNNNmyNMMMMh$1ssssss/ .ossssssssssssssssss$2dMMMNy$1sssso. -+sssssssssssssssss$2yyy$1ssss+- `:+ssssssssssssssssss+:´ --+oossssssoo+-- ================================================ FILE: src/logo/ascii/ubuntu_old2.txt ================================================ ./+o+- $2 yyyyy- $1-yyyyyy+ $2 $2://+//////$1-yyyyyyo $3 .++ $2.:/++++++/-$1.+sss/` $3 .:++o: $2/++++++++/:--:/- $3 o:+o+:++.$2`..```.-/oo+++++/ $3 .:+o:+o/.$2 `+sssoo+/ $2 .++/+:$3+oo+o:`$2 /sssooo. $2/+++//+:$3`oo+o$2 /::--:. $2+/+o+++$3`o++o$1 ++////. $2 .++.o+$3++oo+:`$1 /dddhhh. $3 .+.o+oo:.$1 `oddhhhh+ $3 +.++o+o`$1`-````.:ohdhhhhh+ $3 `:o+++ $1`ohhhhhhhhyo++os: $3 .o:$1`.syhhhhhhh/$3.oo++o` $1 /osyyyyyyo$3++ooo+++/ $1 ````` $3+oo+++o: $3 `oo++. ================================================ FILE: src/logo/ascii/ubuntu_old2_small.txt ================================================ _ ---(_) _/ --- \ (_) | | \ --- _/ ---(_) ================================================ FILE: src/logo/ascii/ubuntu_small.txt ================================================ $2..;,; $1.,;,. $2.,lool: $1.ooooo, $2;oo;: $1.coool. $1.... $1''' $2,l; $1:oooo, $2'oo. $1looooc $2:oo' $1'::' $2,oo: $2,., $1.... $2co, $2lo:;. $1:oooo; $2. $2':ooo; $1cooooc $2''' $1'''' ================================================ FILE: src/logo/ascii/ubuntu_studio.txt ================================================ ..-::::::-.` `.:+++++++++++$2ooo$1++:.` ./+++++++++++++$2sMMMNdyo$1+/. .++++++++++++++++$2oyhmMMMMms$1++. `/+++++++++$2osyhddddhys$1+$2osdMMMh$1++/` `+++++++++$2ydMMMMNNNMMMMNds$1+$2oyyo$1++++` +++++++++$2dMMNhso$1++++$2oydNMMmo$1++++++++` :+$2odmy$1+++$2ooysoohmNMMNmyoohMMNs$1+++++++: ++$2dMMm$1+$2oNMd$1++$2yMMMmhhmMMNs+yMMNo$1+++++++ `++$2NMMy$1+$2hMMd$1+$2oMMMs$1++++$2sMMN$1++$2NMMs$1+++++++. `++$2NMMy$1+$2hMMd$1+$2oMMMo$1++++$2sMMN$1++$2mMMs$1+++++++. ++$2dMMd$1+$2oNMm$1++$2yMMNdhhdMMMs$1+y$2MMNo$1+++++++ :+$2odmy$1++$2oo$1+$2ss$1+$2ohNMMMMmho$1+$2yMMMs$1+++++++: +++++++++$2hMMmhs+ooo+oshNMMms$1++++++++ `++++++++$2oymMMMMNmmNMMMMmy+oys$1+++++` `/+++++++++$2oyhdmmmmdhso+sdMMMs$1++/ ./+++++++++++++++$2oyhdNMMMms$1++. ./+++++++++++++$2hMMMNdyo$1+/. `.:+++++++++++$2sso$1++:. ..-::::::-.. ================================================ FILE: src/logo/ascii/ubuntu_sway.txt ================================================ .-/+oossssoo+\-. ´:+ssssssssssssssssss+:` -+ssssssssssssssssss$2yy$1ssss+- .ossssssssssssssssss$2dMMMNyy$1ssso. /sssssssssss$2hdmmNNmmyNMMMMh$1ssssss\ +sssssssss$2hm$1ydMMMMMMMNdd$2ddy$1ssssssss+ /ssssssss$2hN$1MM$2M$1yh$2hyyyyhmNM$1MM$2Nh$1ssssssss\ .ssssssss$2dM$1MM$2Nh$1ssssssssss$2hN$1MM$2Md$1ssssssss. +sss$2yyyyyN$1MM$2Ny$1ssssssssssss$2yN$1MM$2My$1sssssss+ ossy$2NMMMNy$1MM$2h$1ssssssssssssss$2hm$1mm$2h$1ssssssso ossy$2NMMMNy$1MM$2h$1sssssssssssssshmmmh$1ssssssso +sss$2yyyyyN$1MM$2Ny$1ssssssssssss$2yN$1MM$2My$1sssssss+ .ssssssss$2dM$1MM$2Nh$1ssssssssss$2hN$1MM$2Md$1ssssssss. \ssssssss$2hN$1MM$2M$1yh$2hyyyyhdNM$1M$2MNh$1ssssssss/ +sssssssss$2dm$1ydMMMMMMMMdd$2ddy$1ssssssss+ \sssssssssss$2hdmNNNNmyNMMMMh$1ssssss/ .ossssssssssssssssss$2dMMMNyy$1ssso. -+sssssssssssssssss$2yy$1sss+- `:+ssssssssssssssssss+:` .-\+oossssoo+/-. ================================================ FILE: src/logo/ascii/ubuntu_touch.txt ================================================ ############### ## ## ## $2##$1 $2##$1 ## ## $2##$1 $2#$1 $2#$1 $2##$1 ## ## $2###$1 ## ## ## ############### ================================================ FILE: src/logo/ascii/ubuntu_unity.txt ================================================ .-/+ooosssssssooo+\-. ,:+sssssssssssssssssssss+:. -+ssssssssssssssssssss$2.....$1ssss+- .ssssss$2,$1ss$2:cloooooo:$1ss$2.:loooo.$1ssssss. .ssssssss$2loo$1ss$2oooooooc$1ss$2.ooooooooo$1ssssss. .ssssss$2.;looooool:,''.$1ss$2:ooooooooooc$1ssssss. +ssssss$2;loooool;.$1ssssssssss$2ooooooooo$1ssssssss+ /ssssss$2;clool'$1sssssssssssssss$2ooooooc$1ssss$2,,$1sssss. .sssssssssssssssssssssssssssssssssssss$2.:oo,$1sssss. .sssss$2.;clol:,.$1ssssssssssssssssssssss$2.loooo'$1ssss. +ssss$2:ooooooooo,$1ssssssssssssssssssssss$2'ooool$1ssss+ osss$2'ooooooooooo.$1ssssssssssssssssssssss$2loooo.$1ssso osss$2'ooooooooool$1sssssssssssssssssssssss$2co$1ssssssso ossss$2,looooooc.$1sssssssssssssssssssssss$2.loooo.$1ssso +ssssss$2.,;;;'.$1ssssssssssssssssssssssss$2;ooooc$1ssss+ .ssssssssssssssssssssssssssssssssssss$2,ooool.$1ssss. .sssssss$2.cooooc.$1sssssssssssss$2.,,,,.$1ss$2.cooo.$1sssss. `sssssss$2ooooo:.$1ssssssssssss$2oooooooc.$1ss$2:l.$1sssss' +ssssss$2.coooooc,..$1sssssss$2cooooooooo.$1ssssssss+ `sssssss$2.:oo$1s$2ooooolc:.$1s$2.ooooooooooo'$1ssssss' .sssssss$2.o$1ss$2`:loooo;$1sss$2,ooooooooc$1sssssss. `sssssssss$2;oooo';:c'$1ssss$2`:ooo:'$1sssssss' -+sssssssssssssssssssssssssssssss+- `:+sssssssssssssssssssssss+:' `-\+ooosssssssooo+/-' ================================================ FILE: src/logo/ascii/ultramarine.txt ================================================ .cd0NNNNNNNXOdc. .:xKNNNNNNNNNNNNNNNNKd;. ,dXNNNNNNNNNNNNNNNNNNNNNNNd, 'ONNNNNNNNNNNNNNNNNNNNNNNNNNNNO' .xNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNk. .0NNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNN0. .0NNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNN0. dNNNNNNNNNNNNWWWWWWWWNNNNNNNNNNNNNNNNNNd NNNNNNNNNNNNNW$2MMMMMMMM$1WWNNNNNNNNNNNNNNNN NNNNNNNNNNNNNNW$2MMMMMMMMM$1WWNNNNNNNNNNNNNN NNNNNNNNNNNNNNW$2MMMMMMMMMMMM$1WNNNNNNNNNNNN NNNNNNNNNNWWW$2MMMMMMMMMMMMMMMM$1WWWNNNNNNNX oNWWWW$2MMMMMMMMMMMMMMMMMMMMMMMMMMMM$1WWWNNo OW$2MMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM$1WO .OW$2MMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM$1WO. lNW$2MMMMMMMMMMMMMMMMMMMMMMMMMMMM$1WNl .dNW$2MMMMMMMMMMMMMMMMMMMMMMMM$1WNd. .cKW$2MMMMMMMMMMMMMMMMMMMM$1WKc. 'oOXWWW$2MMMMMMMM$1WWWXOl. ;lkXNNNNNNXkl' ================================================ FILE: src/logo/ascii/ultramarine_small.txt ================================================ @@@@@@@@@@@@ @@@@@@@@@@@@@@@@@@ @@@@@@@@@@@@@@@@@@@@@@ @@@@@@@@@@@@@@@@@@@@@@@@ @@@@@@@@@@@@@@@@@@@@@@@@@@ @@@@@@@@@*=+*%@@@@@@@@@@@@ @@@@@@@@@@: :#@@@@@@@@@@ @@@@@@@@@# -#@@@@@@@@ @@%###*=. :=*#@@@@ @@= =@@ @@= -@@ @@*: :*@@ @%*=: :=*@@ @@@%%##%%@@@ ================================================ FILE: src/logo/ascii/unifi.txt ================================================ ___ ___ .__________.__ | | |____ |__\_ ____/__| | | / \| || __) | | | | | | \ || \ | | |______|___| /__||__/ |__| |_/ ================================================ FILE: src/logo/ascii/univalent.txt ================================================ UUUUUUU UUUUUUU UUUUUUU UUUUUUU UUUUUUU A UUUUUUU UUUUUUU A|A UUUUUUU UUUUUUU A | A UUUUUUU UUUUUUU A | A UUUUUUU UUUUUUU A| | |A UUUUUUU UUUUUUU A | | | A UUUUUUU UUUUUUU A | | | A UUUUUUU UUUUUUU A | | | A UUUUUUU UUUUUUU A | | | A UUUUUUU UUUUUUU A | | | A UUUUUUU UUUUUUU A | | | A UUUUUUU UUUUUUU A | | | A UUUUUUU UUUUUUU A | | | A UUUUUUU UUUUUUUAAAAAAAAAAAUUUUUUU UUUUUUUUUUUUUUUUUUU UUUUUUUUUUUUU ================================================ FILE: src/logo/ascii/univention.txt ================================================ ./osssssssssssssssssssssso+- `ohhhhhhhhhhhhhhhhhhhhhhhhhhhhy: shhhhhhhhhhhhhhhhhhhhhhhhhhhhhhh- `-//$2sssss$1/hhhhhhhhhhhhhh+$2s$1.hhhhhhhhh+ .ohhhy$2sssss$1.hhhhhhhhhhhhhh.$2sss$1+hhhhhhh+ .yhhhhy$2sssss$1.hhhhhhhhhhhhhh.$2ssss$1:hhhhhh+ +hhhhhy$2sssss$1.hhhhhhhhhhhhhh.$2sssss$1yhhhhh+ +hhhhhy$2sssss$1.hhhhhhhhhhhhhh.$2sssss$1yhhhhh+ +hhhhhy$2sssss$1.hhhhhhhhhhhhhh.$2sssss$1yhhhhh+ +hhhhhy$2sssss$1.hhhhhhhhhhhhhh.$2sssss$1yhhhhh+ +hhhhhy$2sssss$1.hhhhhhhhhhhhhh.$2sssss$1yhhhhh+ +hhhhhy$2sssss$1.hhhhhhhhhhhhhh.$2sssss$1yhhhhh+ +hhhhhy$2sssss$1.hhhhhhhhhhhhhh.$2sssss$1yhhhhh+ +hhhhhy$2ssssss$1+yhhhhhhhhhhy/$2ssssss$1yhhhhh+ +hhhhhh:$2sssssss$1:hhhhhhh+$2.ssssssss$1yhhhhy. +hhhhhhh+`$2ssssssssssssssss$1hh$2sssss$1yhhho` +hhhhhhhhhs+$2ssssssssssss$1+hh+$2sssss$1/:-` -hhhhhhhhhhhhhhhhhhhhhhhhhhhhhhho :yhhhhhhhhhhhhhhhhhhhhhhhhhhhh+` -+ossssssssssssssssssssss+:` ================================================ FILE: src/logo/ascii/unknown.txt ================================================ ________ _jgN########Ngg_ _N##N@@"" ""9NN##Np_ d###P N####p "^^" T#### d###P _g###@F _gN##@P gN###F" d###F 0###F 0###F 0###F "NN@' ___ q###r "" ================================================ FILE: src/logo/ascii/uos.txt ================================================ ....... .............. ...................... ............................. uuuuuu uuuuuu ooooooooooo ssssssssss u::::u u::::u oo:::::::::::oo ss::::::::::s u::::u u::::u o:::::::::::::::oss:::::::::::::s u::::u u::::u o:::::ooooo:::::os::::::ssss:::::s u::::u u::::u o::::o o::::o s:::::s ssssss u::::u u::::u o::::o o::::o s::::::s u::::u u::::u o::::o o::::o s::::::s u:::::uuuu:::::u o::::o o::::ossssss s:::::s u:::::::::::::::uuo:::::ooooo:::::os:::::ssss::::::s u:::::::::::::::uo:::::::::::::::os::::::::::::::s uu::::::::uu:::u oo:::::::::::oo s:::::::::::ss uuuuuuuu uuuu ooooooooooo sssssssssss ............................. ..................... ............. ...... ================================================ FILE: src/logo/ascii/urukos.txt ================================================ $3:$4:::::::::::::: $5. $3=#$4*============. $5:#: $3=##%$4+----------. $5.###: $3=####. $5.####: $3=####. $5.####: $3=###*. .=$4--------. $5####: $3=####. .*#+$4======- $5####: $3=###*. .*###+$4====- $5####: $3=###*. .*#####+$4==- $5####: $3=###*. .*#######+$4: $5####: $3=###*. .*#######+$4: $5####: $3=###*. .*#####+$4==- $5####: $3=###*. .*###+$4====- $5####: $3=####. .*#+$4======- $5####: $3=###*. .=$4--------. $5.####: $3=####. $5.####: $3=####. $5.####: $3=###+$4--------------$5####: $3=#+$4=================-$5: $3:$4::::::::::::::::::. ================================================ FILE: src/logo/ascii/uwuntu.txt ================================================ && &&&&&&&& , *&&&&&& &&&&&&&&( &%%%%&&&& &&&&&&&&&&&& ,&&&&& %%$2%%%%&&$1&&& ,&&&&&&&&&&&&&, %&&&$&&&%%$%%%. &%%%$2%&&&&&$1&&# &, &&&&&&$2&&&&&&&%%%$1%% &%%&&$2&&&&$1&&&( &&&$2&&&&&&%$1%%% &&&&&$2&&&$1&% *&&$2&&&&&$1&&% &&&/ &&&&$3\$1& ,$3/$1*.** %&&&&&&&& &&&$3⟩$1., *.$3⟨$1 %&&&&&&&& &&$3/$1.. $3/ \$1 ..$3\$1(&&&&&& #&&&#%%%%.%%%( $3\_/\_/$1 (%%%.%%%%/ /%%%%%%%&&* ,&&&%%%%%%& &&&&&&&& &&&&&&&&&&& (&&&&& &&&&&&&&&&& $2%%$1 & &&&&&&&&&&&& &&&&&&& $2%%%$1 #&&&&&&# &&&&&&&&& $2%%%%% %%$1 #&&&&&( $2&%. %%%$1 $2%%%%%%% ================================================ FILE: src/logo/ascii/valhalla.txt ================================================ .:~: !55P&#: ^@G?G!&Y !GYYGP55P: Y5J777!^:. .GY?J#G!Y?5. ....P#7Y!##Y??57 .5JY5PPY5GBBBPYYPBBGBYYP5PJP5: ^#J?Y@5?J5@@&!GG!&@@YJ?5@PJ7B? 5PY55G5YY5P!55YY5P7P5JJY5YYJ5Y #G?J5@PJJP@~&55Y5@7@5JJG@5J?GB .:.^:^^:!.!:!!:!:!:^^:^.:. ================================================ FILE: src/logo/ascii/vanilla.txt ================================================ .----: .-------. :---::----: .----::-----. ......... :----::-----: ..:::-::::.. .-----------------::------------------: ----::-----------::----------::::---: -----:::--------::-------:::------- :------::::--::...:::::---------: .---------::.. ..:---------. .::-----::.. .::----::. .:------:.......:-------: .--------::::::::-:::-------. .-------::-----.:-----::------. -----::------: :------::----- :--::--------: .-------::---: :----------:: .:---------- :--------: :--------: ================================================ FILE: src/logo/ascii/vanilla2.txt ================================================ . x/A\x Z#@#?P`. /@$R/.:.', _ ($@`.:::.) _ _-=t'''`-.g$(.::::.!-aZ#$#Kko, V$#6..::::.~l.::.<&#p***q##$p' '9#$b,:::::::::.%P~'.:::.`~v' "<#$&b,.':::'./'.:::::::.>' `~*q@#&b+=- -=+x.,''_.' z+'.:::',).:.`~q@6x, /.:::::',Z!.:::.`*q&x i'.:::', $2 _j>!vC1,, $4,$2 ,CY$3O$2t$3O$21(l;" `$4~-{r(1I$2 ^$1/zmwJuc:$2I^ '$4?)|$1U$4/}-$2 ^$3f$1OCLLOw$3_$2,; ,$4i,``. $2",$3k%ooW@$d"$2I,' ' ;^$3u$$$$$$$$$$$$$$$$^<$2:^ ` .>>$3($$$$$5$@@@@$$$$$3$nl$2[:: `!}?$3B$$$5%&WMMW&%$$$3$1}-$2}": ^?j$3Z$$$5WMMWWWWMMW$$$3ofc$2;;` <~x&$3$$$5&MWWWWWWWWp$3-$5l>[< $1 'ljmwn$2~tk8$5MWWWWM8O$2X$1r$2+]nC$1[ !JZqwwdX$2:^C8$5#MMMM@$2X$1Odpdpq0< ^x00J(" ^" ================================================ FILE: src/logo/ascii/void.txt ================================================ __.;=====;.__ _.=+==++=++=+=+===;. -=+++=+===+=+=+++++=_ . -=:`` `--==+=++==. _vi, ` --+=++++: .uvnvi. _._ -==+==+. .vvnvnI` .;==|==;. :|=||=|. $2+QmQQm$1pvvnv;$2 _yYsyQQWUUQQQm #QmQ#$1:$2QQQWUV$QQm. $2-QQWQW$1pvvo$2wZ?.wQQQE$1==<$2QWWQ/QWQW.QQWW$1(:$2 jQWQE $2-$QQQQmmU' jQQQ$1@+=<$2QWQQ)mQQQ.mQQQC$1+;$2jWQQ@' $2-$WQ8Y$1nI:$2 QWQQwgQQWV$1`$2mWQQ.jQWQQgyyWW@! $1-1vvnvv. `~+++` ++|+++ +vnvnnv, `-|=== +vnvnvns. . :=- -Invnvvnsi..___..=sv=. ` +Invnvnvnnnnnnnnvvnn;. ~|Invnvnvvnvvvnnv}+` -~|{*l}*|~ ================================================ FILE: src/logo/ascii/void2.txt ================================================ .......... .::::::::::::::::::.. ..:::::::::::::::::::::::::. '::::::::::::::::::::::::::::. ':::::'' '':::::::::::::. $3 .. $1' '':::::::::. $3 .||. $1'::::::::: $3 .|||||. $1':::::::: $3 .|||||||: $1:::::::: $3 |||||||: $1.::::::::. :::::::: $2 ######$3||||||' $2##^ v##########v$1::. $2##### #############v $2 ######$3||||| $2##^ v####$1::::::$2####v$1::$2##### #####$1:::::$2##### $2 ######$3||$2##^ #####$1::::::$2#####$1::$2##### #####$1:::::$2###### $2 ######^$3|| $2#####$1:::::$2####^$1::$2##### #####$1:::::$2#####^ $2 ##^$3||||| $2^###########^$1:::$2##### ##############^ $3 |||||||: $1'::::::::' .:::::::: $3 '|||||||: $1.::::::::' $3 '|||||||:. $1':::::: $3 '||||||||:. $1'::: $3 ':|||||||||. . $1' $3 '|||||||||||:... ...:||||. ':||||||||||||||||||||||||||. ':|||||||||||||||||||||||'' '':||||||||||||||:'' '''''' ================================================ FILE: src/logo/ascii/void2_small.txt ================================================ _______ _ \______ - | \ ___ \ | | | / \ | | | | \___/ | | | \______ \_| -_______\ ================================================ FILE: src/logo/ascii/void_small.txt ================================================ ____ 'pfPfp.% // _._ \\ UU |===| UU \\ ^~^ // `0PpppP' ````` ================================================ FILE: src/logo/ascii/vzlinux.txt ================================================ $1.::::::::.$2 `/////` $1:zzzzzzzz$2 ://///- VVVVVu` $1-zzz`$2 /VVVVV. `dVVVVV $1.zzz`$2 :VVVVV: `VVVVVo $1zzz$2 -VVVVV/ .VVVVV\ $1zzz/$2 .VVVVV+ -VVVVV: $1zzzzzzzzz$2 .dVVVVs \VVVVV- $1`````````$2 VVVVVV +VVVVV. sVVVVu` oVVVVd` +VVVVd` VVVVVV /VVVVV. `uVVVVs -VVVVV- `dVVVV+ .VVVVV/ .VVVVV\ `dVVVV+ -VVVVV-`uVVVVo :VVVVVVVVVVV \VVVVVVVVu oVVVVVVd` sVVVVV. ----. ================================================ FILE: src/logo/ascii/wii_linux.txt ================================================ ''''''' `~;:` -'''''' ~kQ@@g\ ,EQ@@g/ h@@@@@@' o@@@@@9` `@@@@@@D `@@@@@@@= @@@@@@@? '@@@@@@X o@@@@@@@D v@@@@@@: R@@@@@@, D@@@@@@_ t@@@@@@' _@@@@@@@@@; `Q@@@@@U ;fmo/- ;fmo/- `Q@@@@@m d@@@@@@@@@N 7@@@@@@' L@@@@@@' :@@@@@&@@@@@| `Q@@@@@Z :]]]]]v :]]]]]v Q@@@@@X R@@@@Q`g@@@@Q f@@@@@Q- z@@@@@Q v@@@@@Q r@@@@@@~ ;@@@@@/ ;@@@@@L `@@@@@@/ z@@@@@Q v@@@@@Q d@@@@@q M@@@@# H@@@@Q ]@@@@@Q z@@@@@Q v@@@@@Q ,@@@@@@, >@@@@@; _@@@@@c `@@@@@@> z@@@@@Q v@@@@@Q X@@@@@U Q@@@@R Z@@@@Q`{@@@@@N z@@@@@Q v@@@@@Q .@@@@@@S@@@@@: -@@@@@e@@@@@@: z@@@@@Q v@@@@@Q {@@@@@@@@@@U t@@@@@@@@@@e z@@@@@Q v@@@@@Q `Q@@@@@@@@@' `Q@@@@@@@@@- z@@@@@Q v@@@@@Q :@@@@@@@@| ;@@@@@@@@= z@@@@@Q v@@@@@Q '2#@@Q6: ,eQ@@QZ~ /QQQQQg \QQQQQN ================================================ FILE: src/logo/ascii/windows.txt ================================================ $1 ,.=:!!t3Z3z., :tt:::tt333EE3 $1 Et:::ztt33EEEL$2 @Ee., .., $1 ;tt:::tt333EE7$2 ;EEEEEEttttt33# $1 :Et:::zt333EEQ.$2 $EEEEEttttt33QL $1 it::::tt333EEF$2 @EEEEEEttttt33F $1 ;3=*^```"*4EEV$2 :EEEEEEttttt33@. $3 ,.=::::!t=., $1`$2 @EEEEEEtttz33QF $3 ;::::::::zt33)$2 "4EEEtttji3P* $3 :t::::::::tt33.$4:Z3z..$2 ``$4 ,..g. $3 i::::::::zt33F$4 AEEEtttt::::ztF $3 ;:::::::::t33V$4 ;EEEttttt::::t3 $3 E::::::::zt33L$4 @EEEtttt::::z3F $3{3=*^```"*4E3)$4 ;EEEtttt:::::tZ` $3 `$4 :EEEEtttt::::z7 "VEzjt:;;z>*` ================================================ FILE: src/logo/ascii/windows_11.txt ================================================ $1///////////////// $2///////////////// $1///////////////// $2///////////////// $1///////////////// $2///////////////// $1///////////////// $2///////////////// $1///////////////// $2///////////////// $1///////////////// $2///////////////// $1///////////////// $2///////////////// $1///////////////// $2///////////////// $3///////////////// $4///////////////// $3///////////////// $4///////////////// $3///////////////// $4///////////////// $3///////////////// $4///////////////// $3///////////////// $4///////////////// $3///////////////// $4///////////////// $3///////////////// $4///////////////// $3///////////////// $4///////////////// ================================================ FILE: src/logo/ascii/windows_11_small.txt ================================================ $1lllllll $2lllllll $1lllllll $2lllllll $1lllllll $2lllllll $3lllllll $4lllllll $3lllllll $4lllllll $3lllllll $4lllllll ================================================ FILE: src/logo/ascii/windows_2025.txt ================================================ $1 ##%%%%%%%%% $2%%%%%%%%%## $1 ###%%%%%%%%%% $2%%%%%%%%%%### $1 ####%%%%%%%%%%% $2%%%%%%%%%%%#### $1 ##%%%%%%%%%%%%%% $2%%%%%%%%%%%%%%## $1#%%%%%%%%%%%%%%%% $2%%%%%%%%%%%%%%%%# $1%%%%%%%%%%%%%%%%% $2%%%%%%%%%%%%%%%%% $1%%%%%%%%%%%%%%%%% $2%%%%%%%%%%%%%%%%% $1%%%%%%%%%%%%%%%%% $2#%%%%%%%%%%%%%%%% $3%%%%%%%%%%%%%%%%% $4#%%%%%%%%%%%%%%%% $3%%%%%%%%%%%%%%%%% $4%%%%%%%%%%%%%%%%% $3%%%%%%%%%%%%%%%%% $4%%%%%%%%%%%%%%%%% $3%%%%%%%%%%%%%%%%% $4%%%%%%%%%%%%%%%%# $3 ###%%%%%%%%%%%%% $4%%%%%%%%%%%%%%%## $3 ####%%%%%%%%%%% $4%%%%%%%%%%%#%#### $3 ##%#%%%%%%%%% $4%%%%%%%%%%%###### $3 ##%%%%%%%%% $4%%%%%%%%%######## ================================================ FILE: src/logo/ascii/windows_8.txt ================================================ $2.., ....,,:;+ccllll $1 ...,,+:; $2cllllllllllllllllll $1,cclllllllllll $2lllllllllllllllllll $1llllllllllllll $2lllllllllllllllllll $1llllllllllllll $2lllllllllllllllllll $1llllllllllllll $2lllllllllllllllllll $1llllllllllllll $2lllllllllllllllllll $1llllllllllllll $2lllllllllllllllllll $3llllllllllllll $4lllllllllllllllllll $3llllllllllllll $4lllllllllllllllllll $3llllllllllllll $4lllllllllllllllllll $3llllllllllllll $4lllllllllllllllllll $3llllllllllllll $4lllllllllllllllllll $3`'ccllllllllll $4lllllllllllllllllll $3 `' \*:: $4:ccllllllllllllllll ````''*::cll `` ================================================ FILE: src/logo/ascii/windows_95.txt ================================================ $6 ___ .--=+++++=-:. . _ *%@@@@@@@@@@@@@@* *:+:.__ :+* @@@ @"$5_*&%$6@@$4%&&&*$6"@@@ "+.-#+ +%* " _ $5++&&&%$6@@$4%&&&&&#$6@@ $5" , $6%@@ $5&&&&&%$6@@$4%&&&&&#$6@@ $5 * oo *# $6" _ $5&&&&&%$6@@$4%&&&&&#$6@@ $5" , $6%@@ $5&&&&"$6@@@@#*$4"&&&$6@@ .$5 * oo *# $6" _ %@@@@@@@@@@@@@@@@ *:+:.__ :=* %@@ @"$1**&%$6@@$3%&&&*$6"@@@ "+.-#+ +%* " _ $1&&&&&%$6@@$3%&&&&&#$6@@ $1" , $6%@@ $1&&&&&%$6@@$3%&&&&&#$6@@ $1 * oo *# $6" _ $1&&&&&%$6@@$3%&&&&&#$6@@ $1" , $6%@@ $1&&*"$6%@@@@@@$3"*%&$6@@ .$1 * oo *# $6" _ @@@@@@@@@@@@@@@@@ *:+:.__ :+# @@@ @%#=+""""""+==%#@ "+.-#+ +%* %+" " ":@ " " ================================================ FILE: src/logo/ascii/wolfos.txt ================================================ __ __ _ __ ___ ____ \ \ / /__ | |/ _|/ _ \/ ___| \ \ /\ / / _ \| | |_| | | \___ \ \ V V / (_) | | _| |_| |___) | \_/\_/ \___/|_|_| \___/|____/ ================================================ FILE: src/logo/ascii/xcp_ng.txt ================================================ $3%$4# $1(((($3%%%%%$4&& $1#(((((((((($4&&&& $1((((((((((($2#####$4&& $1((((($5%.....$2%####### $1(((( (((((($5%.......$2%####, $1%((((((& (((((((($5%.....$2%#### $1(((((((((((((($2########### $1(((( ((((((($2########### $1( /(((($2########### $6,,*..$2########## $6,,,******$2## ###### $6,,,*,,,, $2## ###### $6. .,,,, $2### ## $6... ================================================ FILE: src/logo/ascii/xenia.txt ================================================ ** * ** */ $2@$1///// / *** *** **** ***/ //////////////**** ** * ** *** **/ ,, ,,//////////* ****** ** ** ** / , , ,,, /////******* ********* / , ,,,,, ** ** *********** ., ,,,**** ******* *** /// ,, *********** ** //// ,,,,, ******* //// // ,,,,,,. ///// // ,,, , /////// ,,,, ///// ,, / / / ================================================ FILE: src/logo/ascii/xenia_old.txt ================================================ $2 ,c. .c; $2 .$1KMMMk$2.... ....$1kMMMK$2. $2 .$1WMMMMMX$2..... .....$1KMMMMMW. $1 XMMMMMMM0$2..... ....$1OMMMMMMMN $1 dMMMMMMMMM;$2.... ..... ....,$1MMMMMMMMMd $1 WMMMMMMMMMl;$3okKKKKKKKKKOo$1;cMMMMMMMMMM $1 'MMMMMMMNX$2K0$3KKKKKKKKKKKKKKK$20K$1XNMMMMMMM; $1 oMMMMMMM$2Oxo$3KKKKKKKKKKKKKKKKK$2oxO$1MMMMMMMd $1 dMMMMMMM$2dxxx$3KKKKKKKKKKKKKKK$2xxxd$1NMMMMMMk $1 :MMMMX0$2xxxxxx$30KKKKKKKK0KK0$2xxxxxx0$1XMMMMc $1 MMMO$2xxxxxxxxdx$3kdd$20x0$3ddk$2xdxxxxxxxx$1OMMM $1 ;$2xxkxddxxxxdodxxxxdxdxxxxdodxxxxddxkxx$1; $1dxd$2KMMMWXo$1'.....'$2cdxxxdc$1'.....'$2lXWMMMX$1dxd $1cxd$2XMMMN$1,..........$2dxd$1'.........'$2XMMMN$1dxl $1 .xx$2WMMl$1...''....'.;k:.'....''...$2lMMW$1xx. $1..:kXMMx..'....''..kMk..''....'..xMMXkc.. $1 dMMMMMMd.....'...xMMMx...''....dMMMMMMx $1 kMMMMWOoc:coOkolllokOoc:coOWMMMMO $1 .MMMMMMMMl$2...$1lNMMMMMMM. $1 KMMMMMMX$2l$1KMMMMMMX $1 .MMMMMMMMM. ================================================ FILE: src/logo/ascii/xeroarch.txt ================================================ $5 ████ $5 ███$2▓▓$5███ $5 ███$2▓▓▓▓$5███ $5 ███$2▓▓▓▓▓▓$5███ $5 ███$2▓▓▓▓▓▓▓▓$5███ $5 ███$2▓▓▓▓▓▓▓▓▓▓$5███ $5 ███$2▓▓▓▓▓▓▓▓▓▓▓▓$5███ $5 ███$2▓▓▓▓▓▓$5██$2▓▓▓▓▓▓$5▓██ $5 ███$2▓▓▓▓▓▓$5████$2▓▓▓▓▓▓$5▓██ $5 ██▓$2▓▓▓▓▓▓$5█▓$3▓▓$5▓█$2▓▓▓▓▓▓$5▓██ $5 █████▓▒▒▒██$3▓$4▒▒$3▓$5██▒▒▒▓█████ $5 ███$6▓▓▓▓▓$5████$3▓$4▒▒▒▒$3▓$5▓███$6▓▓▓▓▓$5███ $5 ███$6▓▓▓▓▓▓▓▓▓$5███▓▓███$6▓▓▓▓▓▓▓▓▓$5███ $5 ███$6▓▓▓▓▓▓▓$2▓▓$6▓▓▓▓▓$5█$6▓▓▓▓▓▓▓▓▓▓▓▓▓$5███ $5 ███$6▓▓▓▓▓▓$2▓▓$5▒▒$6$2▓▓$6▓▓▓$5▓$6▓▓▓▓$4()$6▓▓▓▓▓▓▓▓$5███ $5 ███$6▓▓▓▓▓$2▓▓$5▒▒▒▒▒▒$6$2▓▓$6▓$5▓$6▓▓$3()$6▓▓$3()$6▓▓▓▓▓▓▓$5███ $5███$6▓▓▓▓▓▓▓▓$2▓▓$5▒▒$6$2▓▓$6▓▓▓$5▓$6▓▓▓▓$4()$6▓▓▓▓▓▓▓▓▓▓$5███ $5██$6▓▓▓▓▓▓▓▓▓▓▓$2▓▓$6▓▓▓▓▓$5▓$6▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓$5██ $5████████████████████████████████████████ ================================================ FILE: src/logo/ascii/xferience.txt ================================================ ``--:::::::-.` .-/+++ooooooooo+++:-` `-/+oooooooooooooooooo++:. -/+oooooo/+ooooooooo+/ooo++:` `/+oo++oo. .+oooooo+.-: +:-o+- `/+o/. -o. :oooooo+ ```:.+oo+- `:+oo- -/` :oooooo+ .`-`+oooo/. .+ooo+. .` `://///+-+..oooooo+:` -+ooo:` ``.-+oooooo+/` -+oo/` :+oooo/. .+oo: ..-/. . -+oo+/` `/++- -:::++::/. -+oo+- ./o: `:///+- `./ooo+:` .++- `` /-` -:/+oooo+:` .:+/:`` `-:ooooooo++- ./+o+//:...../+oooooooo++:` `:/++ooooooooooooo++/-` `.-//++++++//:-.` `````` ================================================ FILE: src/logo/ascii/xinux.txt ================================================ $1 * $2======= ===== ======= $1* $1 **** $2======= ======= ======= $1*** $1 ****** $2======= ======= ======= $1****** $1 ******* $2======== ======= ======= $1******* $1 ******* $2======== ============= $1******* $1 ******* $2======== ========== $1******* $1 ******* $2======== ======== $1******* $1 ******** $2======= ======= $1********* $1 *********** $2==== ======= $1*********** $1 ************* $2== ======= $1************* $1 ******* ******* $2======== $1******* ******* $1 ******* ******* $2====== $1******* ******* $1******* ******* $2==== $1******* ******* $1 ***** ******* $2== $1******* ***** ================================================ FILE: src/logo/ascii/xray_os.txt ================================================ $1rrrrrraaaaaaaaaaaayyyy $1xxrrrrrrrraaaaaaaaaaaayyyyyyyyy $1xxxxxrrrrrrrraaaaaaaaaaaayyyyyyy$3yyyyy$2yyyyyyyyyy $1xxxxxxxrrrrrrrraaaaa $2aaaaayyyyyyyyyyyyyyyyyyy $1xxxxxx$3xxx$1rrrrrrrraaaa $2aaaaaaayyyyyyyyyyyyyyyyy $1xxxxxx$3xxxxxr$1rrrrrrraa $2aaaaaaaaay$3yyyyyyyyy$2yyyy $1yy $1xxxxxxx$3xxx$1xxrrrrrrrra $2aaaaaaaaa$3ayyyyyyyyyyyy$1yyyyyy $1xxxxxxxxxxxxrrrrrrrr $2aaaaaaaaaaa$3yyyyyyyyyyyy$1yyyyyyy $1xxxxxxxxxxxxxrrrrrr $2raaaaaaaaaaaayyy$3yyyyyyyy$1yyyyyy$1yyy $1xxxxxxxxxxxxxrrrrr $2rraaaaaaaaaaaayyyyy$3yy$2yyyyyy $1yyyyyy $1xxxxxxxx$3xxxx$1xrrrrr$2rr$3raaaaaaa$2aaaaayyyyyyyyyy $1yyyyyyyyy $1xxxxxxx$3xxxx$1xxrrrrrrr$3raaaaaa$2aaaaaayyyyyyy $1yyyyyyyyyyyy $1xxxxxxx$3xxx$1xxxrrrrrrrr$3aaaaaa$2aaaaaayyyy $1yyyyyyyyyyyyyy $1xxxxxxxxxxxxrrrrrrrra $2aaaaaaaaaay $1yyyyyyyyyyyyyyyy $1xxxxxxxxxxxrrrrrrr $2aaaaaaaaaaaayyyy$1yyyyyyyyyyyyy $1xxxxxxx$3xxxrr$1rrrr $2raaaaaaaaaaaa $1yyyyyyyyyyyyyyy $1xxxxxxxxrrrr $2rrraaaaaaaaa $1aayyyyyyyyyyyyyy $1xxxxxxrrrrrrr $2aaaaaa $1aaaayyyyyyyyyyyy $1xxxrrrrrr $2raaa $1aaaaaaayyyyyyyyy $1rrrr $2rr $1aaaaaaaaaayyyyyy $2r $1aaaaaaaaaa ================================================ FILE: src/logo/ascii/xubuntu.txt ================================================ __ygg@@@@@@@@@ggy__ _yg@@@@@@@@@@@@@@@@@@@@@gy_ _a@@@@@@@@@@@@@@@@@@@@@@@@@@@@@y_ _a@@@@@@@@@@@@@@@@@@@@@@@$2#$1@@@@@@@@@g_ a@@@@@@@@@@@@@$2###$1@@@@@@@@$2##$1@@@@$2##$1@@@@@k g@@@@@@@$2###$1@@@$2#####$1@@@@@@@$2##$1@@$2###$1@@@@@@@k a@@@@@@@@$2#####$1@$2#####$1@@@@@@$2##$1@@$2###$1@@@@@@@@@k j@@@@@@@@@$2############$1@@@@@$2##$1@$2###$1@@@@@@@@@@@k g@@@@@@@@@$2#####################$1@@@@@@@@@@@@@@ @@@@@@@@@$2##########################$1@@@@@@@@@@ 0@@@@@@@@$2###########################$1@@@@@@@@@ ~@@@@@@@$2############################$1@@@@@@@@F 9@@@@@@$2##########################$1@@@@@@@@@P 4@@@@@@$2######################$1@@@@@@@@@@@P ~@@@@@@$2################$1@@@@@@@@@@@@@@@F `4@@@@@@$2#######$1@@@@@@@@@@@@@@@@@@@@P` `~@@@@@@@@@@@@@@@@@@@@@@@@@@@@@F` ~~4@@@@@@@@@@@@@@@@@@@@@P~~ `~~=R@@@@@@@@@P=~~~ ================================================ FILE: src/logo/ascii/yiffos.txt ================================================ NK NxKXW WOlcoXW 0olccloxNW XxllllllloxOKNW Nklllllllllllodk0XWW N0dllllllllllllllodxOKNW WKxlllllllllllllllllllooxOKN WKdllllllllllllllooooooooooooo Nxllllllllllllllloooooooooo$2oooo $1 XXNOolllllllllllloooooooooo$2oooooo $1 WX0xolllllllllllooooooooooo$2oooooooo $1XWN0xolllllllllloooooooo$2ooooooooooooo $1 Kkdolllllllooooddddd$2doooooooooooddo $1 K00XNW$2 WX0xdooooooddddddd WXOxdoooooodddd WXOxdddddddd NX0ddd WN0d ================================================ FILE: src/logo/ascii/zorin.txt ================================================ `osssssssssssssssssssso` .osssssssssssssssssssssso. .+oooooooooooooooooooooooo+. `::::::::::::::::::::::. .:` `+ssssssssssssssssss+:.` `.:+ssso` .ossssssssssssssso/. `-+ossssssso. ssssssssssssso/-` `-/osssssssssssss .ossssssso/-` .-/ossssssssssssssso. `+sss+:. `.:+ssssssssssssssssss+` `:. .::::::::::::::::::::::` .+oooooooooooooooooooooooo+. -osssssssssssssssssssssso- `osssssssssssssssssssso` ================================================ FILE: src/logo/ascii/zos.txt ================================================ // OOOOOOO SSSSSSS // OO OO SS zzzzzz // OO OO SS zz // OO OO SSSS zz // OO OO SS zz // OO OO SS zzzzzz // OOOOOOO SSSSSSS ================================================ FILE: src/logo/ascii/zraxyl.txt ================================================ __________ / __________ \ / / \ \ / / .-"````"-. \ \ | | |/ \| | | | | Z R A X Y L | | | | \ ¯¯¯¯¯¯¯¯¯¯¯¯ / | | \ \ `-.___..___.-' / / '.'._ _.' .' '-.| M - C | .-' ================================================ FILE: src/logo/builtin.c ================================================ #include "logo.h" #include "logo_builtin.h" #include "common/color.h" const FFlogo ffLogoUnknown = { .names = {"unknown"}, .lines = FASTFETCH_DATATEXT_LOGO_UNKNOWN, .colors = { FF_COLOR_FG_DEFAULT, }, }; static const FFlogo A[] = { // Adélie { .names = {"Adélie", "Adelie"}, .lines = FASTFETCH_DATATEXT_LOGO_ADELIE, .colors = { FF_COLOR_FG_BLUE, FF_COLOR_FG_WHITE, FF_COLOR_FG_CYAN, }, }, // AerOS { .names = {"aerOS"}, .lines = FASTFETCH_DATATEXT_LOGO_AEROS, .colors = { FF_COLOR_FG_CYAN, FF_COLOR_FG_CYAN, }, }, // Aeon { .names = {"Aeon"}, .lines = FASTFETCH_DATATEXT_LOGO_AEON, .colors = { FF_COLOR_FG_256 "36", FF_COLOR_FG_256 "36", }, }, // AerynOS { .names = {"AerynOS"}, .lines = FASTFETCH_DATATEXT_LOGO_AERYNOS, .colors = { FF_COLOR_FG_DEFAULT, FF_COLOR_FG_MAGENTA, }, }, // Afterglow { .names = {"Afterglow"}, .lines = FASTFETCH_DATATEXT_LOGO_AFTERGLOW, .colors = { FF_COLOR_FG_MAGENTA, FF_COLOR_FG_RED, FF_COLOR_FG_YELLOW, FF_COLOR_FG_BLUE, }, }, // AIX { .names = {"aix"}, .lines = FASTFETCH_DATATEXT_LOGO_AIX, .colors = { FF_COLOR_FG_GREEN, FF_COLOR_FG_WHITE, }, .colorKeys = FF_COLOR_FG_GREEN, .colorTitle = FF_COLOR_FG_DEFAULT, }, // AlmaLinux { .names = {"Almalinux"}, .lines = FASTFETCH_DATATEXT_LOGO_ALMALINUX, .colors = { FF_COLOR_FG_RED, FF_COLOR_FG_LIGHT_YELLOW, FF_COLOR_FG_BLUE, FF_COLOR_FG_LIGHT_GREEN, FF_COLOR_FG_CYAN, }, .colorKeys = FF_COLOR_FG_YELLOW, .colorTitle = FF_COLOR_FG_RED, }, // Alpine { .names = {"Alpine"}, .lines = FASTFETCH_DATATEXT_LOGO_ALPINE, .colors = { FF_COLOR_FG_BLUE, }, .colorKeys = FF_COLOR_FG_MAGENTA, .colorTitle = FF_COLOR_FG_BLUE, }, // Alpine2 { .names = {"Alpine2"}, .lines = FASTFETCH_DATATEXT_LOGO_ALPINE2, .type = FF_LOGO_LINE_TYPE_ALTER_BIT, .colors = { FF_COLOR_FG_BLUE, FF_COLOR_FG_WHITE, }, .colorKeys = FF_COLOR_FG_BLUE, .colorTitle = FF_COLOR_FG_BLUE, }, // AlpineSmall { .names = {"Alpine_small"}, .type = FF_LOGO_LINE_TYPE_SMALL_BIT, .lines = FASTFETCH_DATATEXT_LOGO_ALPINE_SMALL, .colors = { FF_COLOR_FG_BLUE, FF_COLOR_FG_WHITE, }, .colorKeys = FF_COLOR_FG_MAGENTA, .colorTitle = FF_COLOR_FG_BLUE, }, // Alpine2Small { .names = {"alpine2_small"}, .type = FF_LOGO_LINE_TYPE_SMALL_BIT | FF_LOGO_LINE_TYPE_ALTER_BIT, .lines = FASTFETCH_DATATEXT_LOGO_ALPINE2_SMALL, .colors = { FF_COLOR_FG_BLUE, FF_COLOR_FG_WHITE, }, .colorKeys = FF_COLOR_FG_MAGENTA, .colorTitle = FF_COLOR_FG_BLUE, }, // Alpine3Small { .names = {"alpine3_small"}, .type = FF_LOGO_LINE_TYPE_SMALL_BIT | FF_LOGO_LINE_TYPE_ALTER_BIT, .lines = FASTFETCH_DATATEXT_LOGO_ALPINE3_SMALL, .colors = { FF_COLOR_FG_DEFAULT, }, .colorKeys = FF_COLOR_FG_BLUE, .colorTitle = FF_COLOR_FG_BLUE, }, // Alter { .names = {"Alter"}, .lines = FASTFETCH_DATATEXT_LOGO_ALTER, .colors = { FF_COLOR_FG_CYAN, }, .colorKeys = FF_COLOR_FG_CYAN, .colorTitle = FF_COLOR_FG_CYAN, }, // ALTLinux { .names = {"ALTLinux"}, .lines = FASTFETCH_DATATEXT_LOGO_ALTLINUX, .colors = { FF_COLOR_FG_YELLOW, FF_COLOR_FG_BLACK, FF_COLOR_FG_WHITE, }, .colorKeys = FF_COLOR_FG_YELLOW, .colorTitle = FF_COLOR_FG_YELLOW, }, // Amazon { .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", } }, // 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, } }, // AmogOS { .names = {"AmogOS"}, .lines = FASTFETCH_DATATEXT_LOGO_AMOGOS, .colors = { FF_COLOR_FG_WHITE, FF_COLOR_FG_CYAN, }, .colorKeys = FF_COLOR_FG_DEFAULT, .colorTitle = FF_COLOR_FG_CYAN, }, // Anarchy { .names = {"Anarchy"}, .lines = FASTFETCH_DATATEXT_LOGO_ANARCHY, .colors = { FF_COLOR_FG_WHITE, FF_COLOR_FG_BLUE, }, .colorKeys = FF_COLOR_FG_DEFAULT, .colorTitle = FF_COLOR_FG_BLUE, }, // Android { .names = {"android"}, .lines = FASTFETCH_DATATEXT_LOGO_ANDROID, .colors = { FF_COLOR_FG_GREEN, FF_COLOR_FG_WHITE, }, .colorKeys = FF_COLOR_FG_GREEN, .colorTitle = FF_COLOR_FG_GREEN, }, // AndroidSmall { .names = {"android_small"}, .type = FF_LOGO_LINE_TYPE_SMALL_BIT, .lines = FASTFETCH_DATATEXT_LOGO_ANDROID_SMALL, .colors = { FF_COLOR_FG_GREEN, }, .colorKeys = FF_COLOR_FG_GREEN, .colorTitle = FF_COLOR_FG_GREEN, }, // AnduinOS { .names = {"anduinos"}, .lines = FASTFETCH_DATATEXT_LOGO_ANDUINOS, .colors = { FF_COLOR_FG_CYAN, FF_COLOR_FG_BLUE, }, .colorKeys = FF_COLOR_FG_BLUE, .colorTitle = FF_COLOR_FG_DEFAULT, }, // Antergos { .names = {"Antergos"}, .lines = FASTFETCH_DATATEXT_LOGO_ANTERGOS, .colors = { FF_COLOR_FG_BLUE, FF_COLOR_FG_CYAN, }, .colorKeys = FF_COLOR_FG_BLUE, .colorTitle = FF_COLOR_FG_CYAN, }, // Antix { .names = {"antiX"}, .lines = FASTFETCH_DATATEXT_LOGO_ANTIX, .colors = { FF_COLOR_FG_RED, }, .colorKeys = FF_COLOR_FG_RED, .colorTitle = FF_COLOR_FG_DEFAULT, }, // AnushOS { .names = {"AnushOS"}, .lines = FASTFETCH_DATATEXT_LOGO_ANUSHOS, .colors = { FF_COLOR_FG_WHITE, FF_COLOR_FG_BLACK, FF_COLOR_FG_YELLOW, FF_COLOR_FG_CYAN, FF_COLOR_FG_RED, }, .colorKeys = FF_COLOR_FG_DEFAULT, .colorTitle = FF_COLOR_FG_DEFAULT, }, // AoscOsRetro { .names = {"Aosc OS/Retro", "aoscosretro"}, .lines = FASTFETCH_DATATEXT_LOGO_AOSCOSRETRO, .colors = { FF_COLOR_FG_BLUE, FF_COLOR_FG_WHITE, }, .colorKeys = FF_COLOR_FG_BLUE, .colorTitle = FF_COLOR_FG_DEFAULT, }, // AoscOsRetro_small { .names = {"Aosc OS/Retro_small", "aoscosretro_small"}, .type = FF_LOGO_LINE_TYPE_SMALL_BIT, .lines = FASTFETCH_DATATEXT_LOGO_AOSCOSRETRO_SMALL, .colors = { FF_COLOR_FG_BLUE, FF_COLOR_FG_WHITE, }, .colorKeys = FF_COLOR_FG_BLUE, .colorTitle = FF_COLOR_FG_DEFAULT, }, // AoscOS { .names = {"Aosc OS", "aoscos"}, .lines = FASTFETCH_DATATEXT_LOGO_AOSCOS, .colors = { FF_COLOR_FG_BLUE, FF_COLOR_FG_BLACK, FF_COLOR_FG_RED, FF_COLOR_FG_YELLOW, }, }, // AoscOS_old { .names = {"Aosc OS_old", "aoscos_old"}, .lines = FASTFETCH_DATATEXT_LOGO_AOSCOS_OLD, .type = FF_LOGO_LINE_TYPE_ALTER_BIT, .colors = { FF_COLOR_FG_DEFAULT, }, .colorKeys = FF_COLOR_FG_BLUE, .colorTitle = FF_COLOR_FG_DEFAULT, }, // Aperture { .names = {"Aperture"}, .lines = FASTFETCH_DATATEXT_LOGO_APERTURE, .colors = { FF_COLOR_FG_CYAN, FF_COLOR_FG_CYAN, FF_COLOR_FG_CYAN, FF_COLOR_FG_CYAN, FF_COLOR_FG_CYAN, FF_COLOR_FG_CYAN, FF_COLOR_FG_CYAN, FF_COLOR_FG_CYAN, }, .colorKeys = FF_COLOR_FG_CYAN, .colorTitle = FF_COLOR_FG_DEFAULT, }, // Apple { .names = {"Apple"}, .lines = FASTFETCH_DATATEXT_LOGO_MACOS, .colors = { FF_COLOR_FG_GREEN, FF_COLOR_FG_YELLOW, FF_COLOR_FG_LIGHT_RED, FF_COLOR_FG_RED, FF_COLOR_FG_MAGENTA, FF_COLOR_FG_BLUE, }, .colorKeys = FF_COLOR_FG_YELLOW, .colorTitle = FF_COLOR_FG_GREEN, }, // AppleSmall { .names = {"Apple_small"}, .type = FF_LOGO_LINE_TYPE_SMALL_BIT, .lines = FASTFETCH_DATATEXT_LOGO_MACOS_SMALL, .colors = { FF_COLOR_FG_GREEN, FF_COLOR_FG_YELLOW, FF_COLOR_FG_RED, FF_COLOR_FG_MAGENTA, FF_COLOR_FG_BLUE, }, .colorKeys = FF_COLOR_FG_YELLOW, .colorTitle = FF_COLOR_FG_GREEN, }, // Apricity { .names = {"Apricity"}, .lines = FASTFETCH_DATATEXT_LOGO_APRICITY, .colors = { FF_COLOR_FG_DEFAULT, }, .colorKeys = FF_COLOR_FG_BLUE, .colorTitle = FF_COLOR_FG_DEFAULT, }, // ArchBox { .names = {"ArchBox"}, .lines = FASTFETCH_DATATEXT_LOGO_ARCHBOX, .colors = { FF_COLOR_FG_GREEN, }, .colorKeys = FF_COLOR_FG_GREEN, .colorTitle = FF_COLOR_FG_DEFAULT, }, // Archcraft { .names = {"Archcraft"}, .lines = FASTFETCH_DATATEXT_LOGO_ARCHCRAFT, .colors = { FF_COLOR_FG_CYAN, }, .colorKeys = FF_COLOR_FG_CYAN, .colorTitle = FF_COLOR_FG_RED, }, // Archcraft2 { .names = {"Archcraft2"}, .type = FF_LOGO_LINE_TYPE_ALTER_BIT, .lines = FASTFETCH_DATATEXT_LOGO_ARCHCRAFT2, .colors = { FF_COLOR_FG_CYAN, }, .colorKeys = FF_COLOR_FG_CYAN, .colorTitle = FF_COLOR_FG_RED, }, // Arch { .names = {"arch", "archmerge"}, .lines = FASTFETCH_DATATEXT_LOGO_ARCH, .colors = { FF_COLOR_FG_CYAN, FF_COLOR_FG_CYAN, }, }, // Arch2 { .names = {"arch2"}, .type = FF_LOGO_LINE_TYPE_ALTER_BIT, .lines = FASTFETCH_DATATEXT_LOGO_ARCH2, .colors = { FF_COLOR_FG_CYAN, FF_COLOR_FG_CYAN, }, }, // Arch3 { .names = {"arch3"}, .type = FF_LOGO_LINE_TYPE_ALTER_BIT, .lines = FASTFETCH_DATATEXT_LOGO_ARCH3, .colors = { FF_COLOR_FG_CYAN, FF_COLOR_FG_CYAN, }, }, // ArchSmall { .names = {"arch_small"}, .type = FF_LOGO_LINE_TYPE_SMALL_BIT, .lines = FASTFETCH_DATATEXT_LOGO_ARCH_SMALL, .colors = { FF_COLOR_FG_CYAN, FF_COLOR_FG_CYAN, }, }, // ArchOld { .names = {"arch_old"}, .type = FF_LOGO_LINE_TYPE_ALTER_BIT, .lines = FASTFETCH_DATATEXT_LOGO_ARCH_OLD, .colors = { FF_COLOR_FG_BLUE, FF_COLOR_FG_WHITE, }, .colorTitle = FF_COLOR_FG_DEFAULT, .colorKeys = FF_COLOR_FG_BLUE, }, // Archlabs { .names = {"ARCHlabs"}, .lines = FASTFETCH_DATATEXT_LOGO_ARCHLABS, .colors = { FF_COLOR_FG_CYAN, FF_COLOR_FG_RED, }, .colorKeys = FF_COLOR_FG_CYAN, .colorTitle = FF_COLOR_FG_RED, }, // ArchStrike { .names = {"ArchStrike"}, .lines = FASTFETCH_DATATEXT_LOGO_ARCHSTRIKE, .colors = { FF_COLOR_FG_CYAN, FF_COLOR_FG_BLACK, }, .colorKeys = FF_COLOR_FG_CYAN, .colorTitle = FF_COLOR_FG_CYAN, }, // Arkane { .names = {"Arkane", "Arkane Linux"}, .lines = FASTFETCH_DATATEXT_LOGO_ARKANE, .colors = { FF_COLOR_FG_256 "237", FF_COLOR_FG_256 "130", FF_COLOR_FG_WHITE, }, .colorKeys = FF_COLOR_FG_256 "130", .colorTitle = FF_COLOR_FG_DEFAULT, }, // Armbian { .names = {"Armbian"}, .lines = FASTFETCH_DATATEXT_LOGO_ARMBIAN, .colors = { FF_COLOR_FG_WHITE, FF_COLOR_FG_RED, }, .colorKeys = FF_COLOR_FG_DEFAULT, .colorTitle = FF_COLOR_FG_RED, }, // Armbian2 { .names = {"Armbian2"}, .type = FF_LOGO_LINE_TYPE_ALTER_BIT, .lines = FASTFETCH_DATATEXT_LOGO_ARMBIAN2, .colors = { FF_COLOR_FG_RED, }, .colorKeys = FF_COLOR_FG_DEFAULT, .colorTitle = FF_COLOR_FG_RED, }, // Artix { .names = {"artix"}, .lines = FASTFETCH_DATATEXT_LOGO_ARTIX, .colors = { FF_COLOR_FG_CYAN, }, .colorKeys = FF_COLOR_FG_CYAN, .colorTitle = FF_COLOR_FG_CYAN, }, // ArtixSmall { .names = {"artix_small"}, .type = FF_LOGO_LINE_TYPE_SMALL_BIT, .lines = FASTFETCH_DATATEXT_LOGO_ARTIX_SMALL, .colors = { FF_COLOR_FG_CYAN, }, .colorKeys = FF_COLOR_FG_CYAN, .colorTitle = FF_COLOR_FG_CYAN, }, // Artix2Small { .names = {"artix2_small"}, .type = FF_LOGO_LINE_TYPE_SMALL_BIT | FF_LOGO_LINE_TYPE_ALTER_BIT, .lines = FASTFETCH_DATATEXT_LOGO_ARTIX2_SMALL, .colors = { FF_COLOR_FG_CYAN, }, .colorKeys = FF_COLOR_FG_CYAN, .colorTitle = FF_COLOR_FG_CYAN, }, // ArcoLinux (Discontinued) { .names = {"arco", "arcolinux"}, // ID=arcolinux .lines = FASTFETCH_DATATEXT_LOGO_ARCO, .colors = { FF_COLOR_FG_BLUE, FF_COLOR_FG_WHITE, }, .colorKeys = FF_COLOR_FG_BLUE, .colorTitle = FF_COLOR_FG_BLUE, }, // ArcoLinuxSmall { .names = {"arco_small", "arcolinux_small"}, .type = FF_LOGO_LINE_TYPE_SMALL_BIT, .lines = FASTFETCH_DATATEXT_LOGO_ARCO_SMALL, .colors = { FF_COLOR_FG_BLUE, FF_COLOR_FG_WHITE, }, .colorKeys = FF_COLOR_FG_BLUE, .colorTitle = FF_COLOR_FG_BLUE, }, // ArseLinux { .names = {"arse", "arselinux", "arse-linux"}, .lines = FASTFETCH_DATATEXT_LOGO_ARSELINUX, .colors = { FF_COLOR_FG_BLUE, FF_COLOR_FG_WHITE, }, .colorKeys = FF_COLOR_FG_BLUE, .colorTitle = FF_COLOR_FG_DEFAULT, }, // Arya { .names = {"Arya"}, .lines = FASTFETCH_DATATEXT_LOGO_ARYA, .colors = { FF_COLOR_FG_GREEN, FF_COLOR_FG_RED, }, .colorKeys = FF_COLOR_FG_GREEN, .colorTitle = FF_COLOR_FG_RED, }, // Asahi { .names = {"asahi", "asahi-linux"}, .lines = FASTFETCH_DATATEXT_LOGO_ASAHI, .colors = { FF_COLOR_FG_YELLOW, FF_COLOR_FG_GREEN, FF_COLOR_FG_RED, FF_COLOR_FG_LIGHT_BLACK, FF_COLOR_FG_WHITE, FF_COLOR_FG_CYAN, FF_COLOR_FG_BLUE, }, .colorKeys = FF_COLOR_FG_YELLOW, .colorTitle = FF_COLOR_FG_GREEN, }, // Asahi2 { .names = {"asahi2", "asahi-linux2"}, .lines = FASTFETCH_DATATEXT_LOGO_ASAHI2, .type = FF_LOGO_LINE_TYPE_ALTER_BIT, .colors = { FF_COLOR_FG_LIGHT_YELLOW, FF_COLOR_FG_CYAN, FF_COLOR_FG_RED, FF_COLOR_FG_LIGHT_RED, FF_COLOR_FG_WHITE, FF_COLOR_FG_BLACK, FF_COLOR_FG_LIGHT_CYAN, }, .colorKeys = FF_COLOR_FG_YELLOW, .colorTitle = FF_COLOR_FG_GREEN, }, // Aster { .names = {"aster"}, .lines = FASTFETCH_DATATEXT_LOGO_ASTER, .colors = { FF_COLOR_FG_CYAN, }, .colorKeys = FF_COLOR_FG_CYAN, .colorTitle = FF_COLOR_FG_CYAN, }, // AsteroidOS { .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" }, .colorKeys = FF_COLOR_FG_256 "160", .colorTitle = FF_COLOR_FG_256 "208", }, // AstOS { .names = {"astOS"}, .lines = FASTFETCH_DATATEXT_LOGO_ASTOS, .colors = { FF_COLOR_FG_DEFAULT, }, .colorKeys = FF_COLOR_FG_DEFAULT, .colorTitle = FF_COLOR_FG_DEFAULT, }, // Astra { .names = {"Astra", "Astra Linux", "astralinux"}, .lines = FASTFETCH_DATATEXT_LOGO_ASTRA_LINUX, .colors = { FF_COLOR_FG_LIGHT_RED, FF_COLOR_FG_WHITE, }, .colorKeys = FF_COLOR_FG_RED, .colorTitle = FF_COLOR_FG_DEFAULT, }, // Ataraxia { .names = {"Ataraxia Linux", "Ataraxia"}, .lines = FASTFETCH_DATATEXT_LOGO_JANUSLINUX, .colors = { FF_COLOR_FG_BLUE, FF_COLOR_FG_MAGENTA, }, .colorKeys = FF_COLOR_FG_BLUE, .colorTitle = FF_COLOR_FG_MAGENTA, }, // AthenaOS { .names = {"AthenaOS"}, .lines = FASTFETCH_DATATEXT_LOGO_ATHENAOS, .colors = { FF_COLOR_FG_BLUE, FF_COLOR_FG_LIGHT_BLUE, }, .colorKeys = FF_COLOR_FG_BLUE, .colorTitle = FF_COLOR_FG_LIGHT_BLUE, }, // AthenaOS_old { .names = {"AthenaOS_old"}, .lines = FASTFETCH_DATATEXT_LOGO_ATHENAOS_OLD, .type = FF_LOGO_LINE_TYPE_ALTER_BIT, .colors = { FF_COLOR_FG_WHITE, FF_COLOR_FG_YELLOW, }, .colorKeys = FF_COLOR_FG_YELLOW, .colorTitle = FF_COLOR_FG_DEFAULT, }, // Aurora { .names = {"Aurora"}, .lines = FASTFETCH_DATATEXT_LOGO_AURORA, .colors = { FF_COLOR_FG_BLUE, FF_COLOR_FG_MAGENTA, FF_COLOR_FG_RED, FF_COLOR_FG_WHITE, }, }, // AxOS { .names = {"AxOS"}, .lines = FASTFETCH_DATATEXT_LOGO_AXOS, .colors = { FF_COLOR_FG_RGB "222;6;255", FF_COLOR_FG_RGB "222;6;255", }, }, // Azos { .names = {"Azos"}, .lines = FASTFETCH_DATATEXT_LOGO_AZOS, .colors = { FF_COLOR_FG_CYAN, FF_COLOR_FG_RED, } }, // LAST {}, }; static const FFlogo B[] = { // Bedrock { .names = {"bedrock"}, .lines = FASTFETCH_DATATEXT_LOGO_BEDROCK, .colors = { FF_COLOR_FG_LIGHT_BLACK, //grey FF_COLOR_FG_DEFAULT, }, .colorKeys = FF_COLOR_FG_LIGHT_BLACK, //grey .colorTitle = FF_COLOR_FG_DEFAULT, }, // BedrockSmall { .names = {"bedrock_small"}, .lines = FASTFETCH_DATATEXT_LOGO_BEDROCK_SMALL, .type = FF_LOGO_LINE_TYPE_SMALL_BIT, .colors = { FF_COLOR_FG_LIGHT_BLACK, //grey FF_COLOR_FG_DEFAULT, }, .colorKeys = FF_COLOR_FG_LIGHT_BLACK, //grey .colorTitle = FF_COLOR_FG_DEFAULT, }, // BigLinux { .names = {"BigLinux"}, .lines = FASTFETCH_DATATEXT_LOGO_BIGLINUX, .colors = { FF_COLOR_FG_CYAN, FF_COLOR_FG_YELLOW, FF_COLOR_FG_BLUE, }, .colorKeys = FF_COLOR_FG_YELLOW, .colorTitle = FF_COLOR_FG_CYAN, }, // Bitrig { .names = {"Bitrig"}, .lines = FASTFETCH_DATATEXT_LOGO_BITRIG, .colors = { FF_COLOR_FG_GREEN, }, .colorKeys = FF_COLOR_FG_DEFAULT, .colorTitle = FF_COLOR_FG_GREEN, }, // BlackArch { .names = {"Blackarch"}, .lines = FASTFETCH_DATATEXT_LOGO_BLACKARCH, .colors = { FF_COLOR_FG_RED, FF_COLOR_FG_LIGHT_RED, FF_COLOR_FG_BLACK, }, .colorKeys = FF_COLOR_FG_LIGHT_RED, .colorTitle = FF_COLOR_FG_RED, }, // BlackMesa { .names = {"BlackMesa", "black-mesa"}, .lines = FASTFETCH_DATATEXT_LOGO_BLACKMESA, .colors = { FF_COLOR_FG_BLACK, }, .colorKeys = FF_COLOR_FG_BLACK, .colorTitle = FF_COLOR_FG_DEFAULT, }, // BlackPanther { .names = {"BlackPanther"}, .lines = FASTFETCH_DATATEXT_LOGO_BLACKPANTHER, .colors = { FF_COLOR_FG_RED, FF_COLOR_FG_YELLOW, FF_COLOR_FG_LIGHT_BLUE, }, .colorKeys = FF_COLOR_FG_RED, .colorTitle = FF_COLOR_FG_YELLOW, }, // BLAG { .names = {"BLAG"}, .lines = FASTFETCH_DATATEXT_LOGO_BLAG, .colors = { FF_COLOR_FG_MAGENTA, }, .colorKeys = FF_COLOR_FG_MAGENTA, .colorTitle = FF_COLOR_FG_DEFAULT, }, // BlankOn { .names = {"BlankOn"}, .lines = FASTFETCH_DATATEXT_LOGO_BLANKON, .colors = { FF_COLOR_FG_RED, FF_COLOR_FG_WHITE, }, .colorKeys = FF_COLOR_FG_RED, .colorTitle = FF_COLOR_FG_DEFAULT, }, // BlueLight { .names = {"BlueLight"}, .lines = FASTFETCH_DATATEXT_LOGO_BLUELIGHT, .colors = { FF_COLOR_FG_WHITE, FF_COLOR_FG_BLUE, }, .colorKeys = FF_COLOR_FG_DEFAULT, .colorTitle = FF_COLOR_FG_BLUE, }, // Bodhi { .names = {"Bodhi"}, .lines = FASTFETCH_DATATEXT_LOGO_BODHI, .colors = { FF_COLOR_FG_WHITE, FF_COLOR_FG_LIGHT_YELLOW, FF_COLOR_FG_GREEN, }, .colorKeys = FF_COLOR_FG_YELLOW, .colorTitle = FF_COLOR_FG_GREEN, }, // Bonsai { .names = {"Bonsai"}, .lines = FASTFETCH_DATATEXT_LOGO_BONSAI, .colors = { FF_COLOR_FG_CYAN, FF_COLOR_FG_GREEN, FF_COLOR_FG_YELLOW, }, .colorKeys = FF_COLOR_FG_CYAN, .colorTitle = FF_COLOR_FG_GREEN, }, // BredOS { .names = {"Bredos"}, .lines = FASTFETCH_DATATEXT_LOGO_BREDOS, .colors = { FF_COLOR_FG_RGB "198;151;66", //grey }, .colorKeys = FF_COLOR_FG_RGB "198;151;66", .colorTitle = FF_COLOR_FG_RGB "198;151;66", }, // BSD { .names = {"BSD"}, .lines = FASTFETCH_DATATEXT_LOGO_BSD, .colors = { FF_COLOR_FG_RED, FF_COLOR_FG_WHITE, FF_COLOR_FG_BLUE, FF_COLOR_FG_YELLOW, FF_COLOR_FG_CYAN, }, .colorKeys = FF_COLOR_FG_RED, .colorTitle = FF_COLOR_FG_DEFAULT, }, // BunsenLabs { .names = {"BunsenLabs"}, .lines = FASTFETCH_DATATEXT_LOGO_BUNSENLABS, .colors = { FF_COLOR_FG_DEFAULT, }, .colorKeys = FF_COLOR_FG_DEFAULT, .colorTitle = FF_COLOR_FG_DEFAULT, }, // LAST {}, }; static const FFlogo C[] = { // CachyOS { .names = {"CachyOS"}, .lines = FASTFETCH_DATATEXT_LOGO_CACHYOS, .colors = { FF_COLOR_FG_CYAN, FF_COLOR_FG_GREEN, FF_COLOR_FG_BLACK, }, .colorKeys = FF_COLOR_FG_CYAN, .colorTitle = FF_COLOR_FG_CYAN, }, // CachyOSSmall { .names = {"CachyOS_small"}, .type = FF_LOGO_LINE_TYPE_SMALL_BIT, .lines = FASTFETCH_DATATEXT_LOGO_CACHYOS_SMALL, .colors = { FF_COLOR_FG_CYAN, }, .colorKeys = FF_COLOR_FG_CYAN, .colorTitle = FF_COLOR_FG_CYAN, }, // Calculate { .names = {"Calculate"}, .lines = FASTFETCH_DATATEXT_LOGO_CALCULATE, .colors = { FF_COLOR_FG_WHITE, FF_COLOR_FG_YELLOW, }, .colorKeys = FF_COLOR_FG_YELLOW, .colorTitle = FF_COLOR_FG_DEFAULT, }, // CalinixOS { .names = {"Calinix", "calinixos"}, .lines = FASTFETCH_DATATEXT_LOGO_CALINIXOS, .colors = { FF_COLOR_FG_MAGENTA, }, .colorKeys = FF_COLOR_FG_MAGENTA, .colorTitle = FF_COLOR_FG_BLUE, }, // CalinixOSSmall { .names = {"Calinix_small", "calinixos_small"}, .type = FF_LOGO_LINE_TYPE_SMALL_BIT, .lines = FASTFETCH_DATATEXT_LOGO_CALINIXOS_SMALL, .colors = { FF_COLOR_FG_MAGENTA, }, .colorKeys = FF_COLOR_FG_MAGENTA, .colorTitle = FF_COLOR_FG_BLUE, }, // Carbs { .names = {"Carbs"}, .lines = FASTFETCH_DATATEXT_LOGO_CARBS, .colors = { FF_COLOR_FG_MAGENTA, }, .colorKeys = FF_COLOR_FG_MAGENTA, .colorTitle = FF_COLOR_FG_BLUE, }, // CBL-Mariner { .names = {"CBL-Mariner"}, .lines = FASTFETCH_DATATEXT_LOGO_CBL_MARINER, .colors = { FF_COLOR_FG_CYAN, }, .colorKeys = FF_COLOR_FG_CYAN, .colorTitle = FF_COLOR_FG_CYAN, }, // CelOS { .names = {"Cel", "celos", "cel-linux", "celos-linux"}, .lines = FASTFETCH_DATATEXT_LOGO_CELOS, .colors = { FF_COLOR_FG_MAGENTA, FF_COLOR_FG_BLACK, }, .colorKeys = FF_COLOR_FG_CYAN, .colorTitle = FF_COLOR_FG_BLUE, }, // Center { .names = {"Center"}, .lines = FASTFETCH_DATATEXT_LOGO_CENTER, .colors = { FF_COLOR_FG_DEFAULT, }, .colorKeys = FF_COLOR_FG_DEFAULT, .colorTitle = FF_COLOR_FG_DEFAULT, }, // CentOS { .names = {"CentOS"}, .lines = FASTFETCH_DATATEXT_LOGO_CENTOS, .colors = { FF_COLOR_FG_YELLOW, FF_COLOR_FG_GREEN, FF_COLOR_FG_BLUE, FF_COLOR_FG_MAGENTA, FF_COLOR_FG_WHITE, }, .colorKeys = FF_COLOR_FG_GREEN, .colorTitle = FF_COLOR_FG_YELLOW, }, // CentOSSmall { .names = {"CentOS_small"}, .type = FF_LOGO_LINE_TYPE_SMALL_BIT, .lines = FASTFETCH_DATATEXT_LOGO_CENTOS_SMALL, .colors = { FF_COLOR_FG_YELLOW, FF_COLOR_FG_GREEN, FF_COLOR_FG_BLUE, FF_COLOR_FG_MAGENTA, }, .colorKeys = FF_COLOR_FG_GREEN, .colorTitle = FF_COLOR_FG_YELLOW, }, // Cereus { .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" }, .colorKeys = FF_COLOR_FG_256 "108", .colorTitle = FF_COLOR_MODE_BOLD FF_COLOR_FG_WHITE, }, // Chakra { .names = {"Chakra"}, .lines = FASTFETCH_DATATEXT_LOGO_CHAKRA, .colors = { FF_COLOR_FG_BLUE, }, .colorKeys = FF_COLOR_FG_BLUE, .colorTitle = FF_COLOR_FG_MAGENTA, }, // ChaletOS { .names = {"ChaletOS"}, .lines = FASTFETCH_DATATEXT_LOGO_CHALETOS, .colors = { FF_COLOR_FG_BLUE, FF_COLOR_FG_WHITE }, .colorKeys = FF_COLOR_FG_BLUE, .colorTitle = FF_COLOR_FG_DEFAULT, }, // Chapeau { .names = {"Chapeau"}, .lines = FASTFETCH_DATATEXT_LOGO_CHAPEAU, .colors = { FF_COLOR_FG_GREEN, FF_COLOR_FG_WHITE, }, .colorKeys = FF_COLOR_FG_GREEN, .colorTitle = FF_COLOR_FG_DEFAULT, }, // Chimera { .names = {"Chimera"}, .lines = FASTFETCH_DATATEXT_LOGO_CHIMERA_LINUX, .colors = { FF_COLOR_FG_RED, FF_COLOR_FG_MAGENTA, FF_COLOR_FG_BLUE, FF_COLOR_FG_RED, }, .colorKeys = FF_COLOR_FG_MAGENTA, .colorTitle = FF_COLOR_FG_RED, }, // ChonkySealOS { .names = {"ChonkySealOS"}, .lines = FASTFETCH_DATATEXT_LOGO_CHONKYSEALOS, .colors = { FF_COLOR_FG_DEFAULT, }, .colorKeys = FF_COLOR_FG_DEFAULT, .colorTitle = FF_COLOR_FG_DEFAULT, }, // Chrom { .names = {"Chrom", "ChromeOS"}, .lines = FASTFETCH_DATATEXT_LOGO_CHROM, .colors = { FF_COLOR_FG_GREEN, FF_COLOR_FG_RED, FF_COLOR_FG_YELLOW, FF_COLOR_FG_BLUE, FF_COLOR_FG_WHITE, }, .colorKeys = FF_COLOR_FG_GREEN, .colorTitle = FF_COLOR_FG_RED, }, // Cleanjaro { .names = {"Cleanjaro"}, .lines = FASTFETCH_DATATEXT_LOGO_CLEANJARO, .colors = { FF_COLOR_FG_DEFAULT, }, .colorKeys = FF_COLOR_FG_DEFAULT, .colorTitle = FF_COLOR_FG_DEFAULT, }, // CleanjaroSmall { .names = {"Cleanjaro_small"}, .type = FF_LOGO_LINE_TYPE_SMALL_BIT, .lines = FASTFETCH_DATATEXT_LOGO_CLEANJARO_SMALL, .colors = { FF_COLOR_FG_DEFAULT, }, .colorKeys = FF_COLOR_FG_DEFAULT, .colorTitle = FF_COLOR_FG_DEFAULT, }, // ClearLinux { .names = {"Clear Linux", "clearlinux", "Clear Linux OS"}, .lines = FASTFETCH_DATATEXT_LOGO_CLEAR_LINUX, .colors = { FF_COLOR_FG_BLUE, FF_COLOR_FG_YELLOW, }, .colorKeys = FF_COLOR_FG_BLUE, .colorTitle = FF_COLOR_FG_YELLOW, }, // ClearOS { .names = {"ClearOS"}, .lines = FASTFETCH_DATATEXT_LOGO_CLEAROS, .colors = { FF_COLOR_FG_GREEN, }, .colorKeys = FF_COLOR_FG_GREEN, .colorTitle = FF_COLOR_FG_GREEN, }, // Clover { .names = {"Clover"}, .lines = FASTFETCH_DATATEXT_LOGO_CLOVER, .colors = { FF_COLOR_FG_GREEN, FF_COLOR_FG_CYAN, }, .colorKeys = FF_COLOR_FG_GREEN, .colorTitle = FF_COLOR_FG_CYAN, }, // Cobalt { .names = {"Cobalt"}, .lines = FASTFETCH_DATATEXT_LOGO_COBALT, .colors = { FF_COLOR_FG_BLUE, FF_COLOR_FG_BLUE, FF_COLOR_FG_LIGHT_BLACK, FF_COLOR_FG_LIGHT_BLUE, FF_COLOR_FG_BLACK, }, .colorKeys = FF_COLOR_FG_BLUE, .colorTitle = FF_COLOR_FG_BLUE, }, // Codex Linux (reMarkable OS) { .names = {"Codex Linux"}, .lines = FASTFETCH_DATATEXT_LOGO_CODEX, .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 }, .colorKeys = FF_COLOR_FG_GREEN, .colorTitle = FF_COLOR_FG_YELLOW, }, // ContainerLinux { .names = {"ContainerLinux", "Container Linux", "Container Linux by CoreOS"}, .lines = FASTFETCH_DATATEXT_LOGO_FEDORA_COREOS, .colors = { FF_COLOR_FG_BLUE, FF_COLOR_FG_WHITE, FF_COLOR_FG_RED, }, .colorKeys = FF_COLOR_FG_BLUE, .colorTitle = FF_COLOR_FG_DEFAULT, }, // Common Torizon { .names = {"common-torizon"}, .lines = FASTFETCH_DATATEXT_LOGO_TORIZONCORE, .colors = { FF_COLOR_FG_LIGHT_WHITE, FF_COLOR_FG_YELLOW, FF_COLOR_FG_BLUE }, }, // Cosmic DE { .names = {"Cosmic"}, .lines = FASTFETCH_DATATEXT_LOGO_COSMIC, .colors = { FF_COLOR_FG_WHITE, FF_COLOR_FG_LIGHT_YELLOW, FF_COLOR_FG_YELLOW, FF_COLOR_FG_LIGHT_RED, FF_COLOR_FG_RED, }, .colorKeys = FF_COLOR_FG_LIGHT_RED, .colorTitle = FF_COLOR_FG_YELLOW, }, // CRUX { .names = {"CRUX"}, .lines = FASTFETCH_DATATEXT_LOGO_CRUX, .colors = { FF_COLOR_FG_BLUE, FF_COLOR_FG_MAGENTA, FF_COLOR_FG_WHITE, }, .colorKeys = FF_COLOR_FG_MAGENTA, .colorTitle = FF_COLOR_FG_BLUE, }, // CRUXSmall { .names = {"CRUX_small"}, .type = FF_LOGO_LINE_TYPE_SMALL_BIT, .lines = FASTFETCH_DATATEXT_LOGO_CRUX_SMALL, .colors = { FF_COLOR_FG_BLUE, FF_COLOR_FG_MAGENTA, FF_COLOR_FG_WHITE, }, .colorKeys = FF_COLOR_FG_MAGENTA, .colorTitle = FF_COLOR_FG_BLUE, }, // CrystalLinux { .names = {"Crystal", "Crystal", "crystal-linux", "Crystal-Linux"}, .lines = FASTFETCH_DATATEXT_LOGO_CRYSTAL, .colors = { FF_COLOR_FG_MAGENTA, }, .colorKeys = FF_COLOR_FG_MAGENTA, .colorTitle = FF_COLOR_FG_MAGENTA, }, // Cucumber { .names = {"Cucumber", "CucumberOS"}, .lines = FASTFETCH_DATATEXT_LOGO_CUCUMBER, .colors = { FF_COLOR_FG_GREEN, FF_COLOR_FG_YELLOW, }, .colorKeys = FF_COLOR_FG_GREEN, .colorTitle = FF_COLOR_FG_YELLOW, }, // CuerdOS { .names = {"CuerdOS", "CuerdOS GNU/Linux"}, .lines = FASTFETCH_DATATEXT_LOGO_CUERDOS, .colors = { FF_COLOR_FG_YELLOW, FF_COLOR_FG_GREEN, }, .colorKeys = FF_COLOR_FG_GREEN, .colorTitle = FF_COLOR_FG_YELLOW, }, // CutefishOS { .names = {"CutefishOS"}, .lines = FASTFETCH_DATATEXT_LOGO_CUTEFISHOS, .colors = { FF_COLOR_FG_CYAN, FF_COLOR_FG_WHITE, FF_COLOR_FG_BLUE, }, }, // CuteOS { .names = {"CuteOS"}, .lines = FASTFETCH_DATATEXT_LOGO_CUTEOS, .colors = { FF_COLOR_FG_BLUE, FF_COLOR_FG_CYAN, FF_COLOR_FG_256 "57", }, .colorKeys = FF_COLOR_FG_BLUE, .colorTitle = FF_COLOR_FG_CYAN, }, // CyberOS { .names = {"CyberOS"}, .lines = FASTFETCH_DATATEXT_LOGO_CYBEROS, .colors = { FF_COLOR_FG_BLUE, FF_COLOR_FG_CYAN, FF_COLOR_FG_256 "57", }, .colorKeys = FF_COLOR_FG_BLUE, .colorTitle = FF_COLOR_FG_CYAN, }, // cycledream { .names = {"cycledream"}, .lines = FASTFETCH_DATATEXT_LOGO_CYCLEDREAM, .colors = { FF_COLOR_FG_MAGENTA, }, .colorKeys = FF_COLOR_FG_MAGENTA, .colorTitle = FF_COLOR_FG_MAGENTA, }, // LAST {}, }; static const FFlogo D[] = { // DahliaOS { .names = {"dahliaOS"}, .lines = FASTFETCH_DATATEXT_LOGO_DAHLIA, .colors = { FF_COLOR_FG_RED, }, .colorKeys = FF_COLOR_FG_RED, .colorTitle = FF_COLOR_FG_DEFAULT, }, // DarkOS { .names = {"DarkOS"}, .lines = FASTFETCH_DATATEXT_LOGO_DARKOS, .colors = { FF_COLOR_FG_RED, FF_COLOR_FG_CYAN, FF_COLOR_FG_MAGENTA, FF_COLOR_FG_YELLOW, FF_COLOR_FG_GREEN, }, .colorKeys = FF_COLOR_FG_RED, .colorTitle = FF_COLOR_FG_CYAN, }, // Debian { .names = {"Debian"}, .lines = FASTFETCH_DATATEXT_LOGO_DEBIAN, .colors = { FF_COLOR_FG_RED, FF_COLOR_FG_WHITE, }, .colorKeys = FF_COLOR_FG_RED, .colorTitle = FF_COLOR_FG_RED, }, // DebianSmall { .names = {"Debian_small"}, .type = FF_LOGO_LINE_TYPE_SMALL_BIT, .lines = FASTFETCH_DATATEXT_LOGO_DEBIAN_SMALL, .colors = { FF_COLOR_FG_RED, }, .colorKeys = FF_COLOR_FG_RED, .colorTitle = FF_COLOR_FG_RED, }, // Deepin { .names = {"Deepin"}, .lines = FASTFETCH_DATATEXT_LOGO_DEEPIN, .colors = { FF_COLOR_FG_BLUE, }, .colorKeys = FF_COLOR_FG_BLUE, .colorTitle = FF_COLOR_FG_BLUE, }, // DesaOS { .names = {"DesaOS"}, .lines = FASTFETCH_DATATEXT_LOGO_DESAOS, .colors = { FF_COLOR_FG_GREEN, FF_COLOR_FG_WHITE, }, .colorKeys = FF_COLOR_FG_GREEN, .colorTitle = FF_COLOR_FG_DEFAULT, }, // Devuan { .names = {"Devuan"}, .lines = FASTFETCH_DATATEXT_LOGO_DEVUAN, .colors = { FF_COLOR_FG_MAGENTA, }, .colorKeys = FF_COLOR_FG_MAGENTA, .colorTitle = FF_COLOR_FG_MAGENTA, }, // DevuanSmall { .names = {"Devuan_small"}, .type = FF_LOGO_LINE_TYPE_SMALL_BIT, .lines = FASTFETCH_DATATEXT_LOGO_DEVUAN_SMALL, .colors = { FF_COLOR_FG_MAGENTA, }, .colorKeys = FF_COLOR_FG_MAGENTA, .colorTitle = FF_COLOR_FG_MAGENTA, }, // DietPi { .names = {"DietPi"}, .lines = FASTFETCH_DATATEXT_LOGO_DIETPI, .colors = { FF_COLOR_FG_GREEN, FF_COLOR_FG_LIGHT_BLACK, }, .colorKeys = FF_COLOR_FG_LIGHT_BLACK, .colorTitle = FF_COLOR_FG_GREEN, }, // DracOS { .names = {"DracOS"}, .lines = FASTFETCH_DATATEXT_LOGO_DRACOS, .colors = { FF_COLOR_FG_RED, FF_COLOR_FG_WHITE, }, .colorKeys = FF_COLOR_FG_RED, .colorTitle = FF_COLOR_FG_DEFAULT, }, // DragonFly { .names = {"DragonFly"}, .lines = FASTFETCH_DATATEXT_LOGO_DRAGONFLY, .colors = { FF_COLOR_FG_RED, FF_COLOR_FG_DEFAULT, }, .colorKeys = FF_COLOR_FG_RED, .colorTitle = FF_COLOR_FG_DEFAULT, }, // DragonFlySmall { .names = {"DragonFly_small"}, .type = FF_LOGO_LINE_TYPE_SMALL_BIT, .lines = FASTFETCH_DATATEXT_LOGO_DRAGONFLY_SMALL, .colors = { FF_COLOR_FG_RED, FF_COLOR_FG_DEFAULT, }, .colorKeys = FF_COLOR_FG_RED, .colorTitle = FF_COLOR_FG_DEFAULT, }, // DragonFlyOld { .names = {"DragonFly_old"}, .type = FF_LOGO_LINE_TYPE_ALTER_BIT, .lines = FASTFETCH_DATATEXT_LOGO_DRAGONFLY_OLD, .colors = { FF_COLOR_FG_RED, FF_COLOR_FG_DEFAULT, FF_COLOR_FG_YELLOW, }, .colorKeys = FF_COLOR_FG_RED, .colorTitle = FF_COLOR_FG_DEFAULT, }, // DraugerOS { .names = {"DraugerOS", "Drauger"}, .lines = FASTFETCH_DATATEXT_LOGO_DRAUGER, .colors = { FF_COLOR_FG_RED, FF_COLOR_FG_WHITE, }, .colorKeys = FF_COLOR_FG_RED, .colorTitle = FF_COLOR_FG_DEFAULT, }, // Droidian { .names = {"Droidian"}, .lines = FASTFETCH_DATATEXT_LOGO_DROIDIAN, .colors = { FF_COLOR_FG_GREEN, FF_COLOR_FG_LIGHT_GREEN, }, .colorKeys = FF_COLOR_FG_GREEN, .colorTitle = FF_COLOR_FG_LIGHT_GREEN, }, // LAST {}, }; static const FFlogo E[] = { // Elbrus { .names = {"elbrus"}, .lines = FASTFETCH_DATATEXT_LOGO_ELBRUS, .colors = { FF_COLOR_FG_BLUE, }, .colorKeys = FF_COLOR_FG_BLUE, .colorTitle = FF_COLOR_FG_BLUE, }, // Elementary { .names = {"Elementary"}, .lines = FASTFETCH_DATATEXT_LOGO_ELEMENTARY, .colors = { FF_COLOR_FG_DEFAULT, }, .colorKeys = FF_COLOR_FG_BLUE, .colorTitle = FF_COLOR_FG_DEFAULT, }, // ElementarySmall { .names = {"Elementary_small"}, .type = FF_LOGO_LINE_TYPE_SMALL_BIT, .lines = FASTFETCH_DATATEXT_LOGO_ELEMENTARY_SMALL, .colors = { FF_COLOR_FG_DEFAULT, }, .colorKeys = FF_COLOR_FG_BLUE, .colorTitle = FF_COLOR_FG_DEFAULT, }, // Elive { .names = {"Elive"}, .lines = FASTFETCH_DATATEXT_LOGO_ELIVE, .colors = { FF_COLOR_FG_WHITE, FF_COLOR_FG_LIGHT_CYAN, FF_COLOR_FG_CYAN, }, .colorKeys = FF_COLOR_FG_DEFAULT, .colorTitle = FF_COLOR_FG_CYAN, }, // Emmabuntüs { .names = {"Emmabuntus"}, .lines = FASTFETCH_DATATEXT_LOGO_EMMABUNTUS, .colors = { FF_COLOR_FG_BLUE, FF_COLOR_FG_YELLOW, }, }, // EmperorOS { .names = {"Emperor"}, .lines = FASTFETCH_DATATEXT_LOGO_EMPEROROS, .colors = { FF_COLOR_FG_YELLOW, FF_COLOR_FG_DEFAULT, }, }, // EncryptOS { .names = {"EncryptOS"}, .lines = FASTFETCH_DATATEXT_LOGO_ENCRYPTOS, .colors = { FF_COLOR_FG_MAGENTA, }, .colorKeys = FF_COLOR_FG_MAGENTA, .colorTitle = FF_COLOR_FG_GREEN, }, // EndeavourOS { .names = {"EndeavourOS"}, .lines = FASTFETCH_DATATEXT_LOGO_ENDEAVOUROS, .colors = { FF_COLOR_FG_MAGENTA, FF_COLOR_FG_RED, FF_COLOR_FG_BLUE, }, .colorKeys = FF_COLOR_FG_MAGENTA, .colorTitle = FF_COLOR_FG_RED, }, // EndeavourOSSmall { .names = {"EndeavourOS_small"}, .type = FF_LOGO_LINE_TYPE_SMALL_BIT, .lines = FASTFETCH_DATATEXT_LOGO_ENDEAVOUROS_SMALL, .colors = { FF_COLOR_FG_RED, FF_COLOR_FG_MAGENTA, FF_COLOR_FG_BLUE, }, }, // Endless { .names = {"Endless"}, .lines = FASTFETCH_DATATEXT_LOGO_ENDLESS, .colors = { FF_COLOR_FG_RED, FF_COLOR_FG_WHITE }, .colorKeys = FF_COLOR_FG_RED, .colorTitle = FF_COLOR_FG_DEFAULT, }, // Enso { .names = {"Enso"}, .lines = FASTFETCH_DATATEXT_LOGO_ENSO, .colors = { FF_COLOR_FG_DEFAULT, }, .colorKeys = FF_COLOR_FG_DEFAULT, .colorTitle = FF_COLOR_FG_DEFAULT, }, // EshanizedOS { .names = {"EshanizedOS"}, .lines = FASTFETCH_DATATEXT_LOGO_ESHANIZEDOS, .colors = { FF_COLOR_FG_RED, FF_COLOR_FG_WHITE, }, }, // EuroLinux { .names = {"EuroLinux"}, .lines = FASTFETCH_DATATEXT_LOGO_EUROLINUX, .colors = { FF_COLOR_FG_BLUE, FF_COLOR_FG_WHITE, }, .colorKeys = FF_COLOR_FG_BLUE, .colorTitle = FF_COLOR_FG_DEFAULT, }, // EvolutionOS { .names = {"EvolutionOS"}, .lines = FASTFETCH_DATATEXT_LOGO_EVOLUTIONOS, .colors = { FF_COLOR_FG_GREEN, FF_COLOR_FG_WHITE, }, }, // EvolutionOSSmall { .names = {"EvolutionOS_small"}, .type = FF_LOGO_LINE_TYPE_SMALL_BIT, .lines = FASTFETCH_DATATEXT_LOGO_EVOLUTIONOS_SMALL, .colors = { FF_COLOR_FG_GREEN, FF_COLOR_FG_WHITE, }, }, // EvolutionOS_old { .names = {"EvolutionOS_old"}, .type = FF_LOGO_LINE_TYPE_ALTER_BIT, .lines = FASTFETCH_DATATEXT_LOGO_EVOLUTIONOS_OLD, .colors = { FF_COLOR_FG_LIGHT_BLUE, FF_COLOR_FG_WHITE, }, }, // eweOS { .names = {"eweOS"}, .lines = FASTFETCH_DATATEXT_LOGO_EWEOS, .colors = { FF_COLOR_FG_WHITE, FF_COLOR_FG_LIGHT_YELLOW, FF_COLOR_FG_LIGHT_RED, FF_COLOR_FG_LIGHT_BLACK, FF_COLOR_FG_RED, }, }, // Exherbo { .names = {"Exherbo"}, .lines = FASTFETCH_DATATEXT_LOGO_EXHERBO, .colors = { FF_COLOR_FG_BLUE, FF_COLOR_FG_WHITE, FF_COLOR_FG_RED, }, .colorKeys = FF_COLOR_FG_BLUE, .colorTitle = FF_COLOR_FG_BLUE, }, // ExodiaOS { .names = {"Exodia"}, .lines = FASTFETCH_DATATEXT_LOGO_EXODIA_PREDATOR, .colors = { FF_COLOR_FG_MAGENTA, }, .colorKeys = FF_COLOR_FG_MAGENTA, .colorTitle = FF_COLOR_FG_MAGENTA, }, // LAST {}, }; static const FFlogo F[] = { // Fastfetch { .names = {"Fastfetch", "FF"}, .lines = FASTFETCH_DATATEXT_LOGO_FASTFETCH, .colors = { FF_COLOR_FG_BLUE, FF_COLOR_FG_RED, FF_COLOR_FG_YELLOW, FF_COLOR_FG_GREEN, FF_COLOR_FG_DEFAULT, FF_COLOR_FG_MAGENTA, FF_COLOR_FG_CYAN, }, .colorKeys = FF_COLOR_FG_BLUE, .colorTitle = FF_COLOR_FG_YELLOW, }, // Fedora { .names = {"Fedora"}, .lines = FASTFETCH_DATATEXT_LOGO_FEDORA, .colors = { FF_COLOR_FG_BLUE, FF_COLOR_FG_WHITE, }, .colorKeys = FF_COLOR_FG_BLUE, .colorTitle = FF_COLOR_FG_DEFAULT, }, // FedoraAsahiRemix { .names = {"fedora-asahi-remix"}, .lines = FASTFETCH_DATATEXT_LOGO_ASAHI, .colors = { FF_COLOR_FG_YELLOW, FF_COLOR_FG_GREEN, FF_COLOR_FG_RED, FF_COLOR_FG_LIGHT_BLACK, FF_COLOR_FG_WHITE, FF_COLOR_FG_CYAN, FF_COLOR_FG_BLUE, }, .colorKeys = FF_COLOR_FG_YELLOW, .colorTitle = FF_COLOR_FG_GREEN, }, // FedoraSmall { .names = {"Fedora_small"}, .type = FF_LOGO_LINE_TYPE_SMALL_BIT, .lines = FASTFETCH_DATATEXT_LOGO_FEDORA_SMALL, .colors = { FF_COLOR_FG_BLUE, }, .colorKeys = FF_COLOR_FG_BLUE, .colorTitle = FF_COLOR_FG_DEFAULT, }, { .names = {"Fedora2_small"}, .type = FF_LOGO_LINE_TYPE_SMALL_BIT | FF_LOGO_LINE_TYPE_ALTER_BIT, .lines = FASTFETCH_DATATEXT_LOGO_FEDORA2_SMALL, .colors = { FF_COLOR_FG_BLUE, }, .colorKeys = FF_COLOR_FG_BLUE, .colorTitle = FF_COLOR_FG_DEFAULT, }, // FedoraOld { .names = {"Fedora_old"}, .type = FF_LOGO_LINE_TYPE_ALTER_BIT, .lines = FASTFETCH_DATATEXT_LOGO_FEDORA_OLD, .colors = { FF_COLOR_FG_BLUE, FF_COLOR_FG_WHITE, }, .colorKeys = FF_COLOR_FG_BLUE, .colorTitle = FF_COLOR_FG_DEFAULT, }, // FedoraSilverblue { .names = {"Fedora-Silverblue"}, .lines = FASTFETCH_DATATEXT_LOGO_FEDORA_SILVERBLUE, .colors = { FF_COLOR_FG_BLUE, FF_COLOR_FG_WHITE, FF_COLOR_FG_CYAN, }, .colorKeys = FF_COLOR_FG_BLUE, .colorTitle = FF_COLOR_FG_DEFAULT, }, // FedoraKinoite { .names = {"Fedora-Kinoite"}, .lines = FASTFETCH_DATATEXT_LOGO_FEDORA_KINOITE, .colors = { FF_COLOR_FG_BLUE, FF_COLOR_FG_WHITE, }, .colorKeys = FF_COLOR_FG_BLUE, .colorTitle = FF_COLOR_FG_DEFAULT, }, // FedoraSericea { .names = {"Fedora-Sericea"}, .lines = FASTFETCH_DATATEXT_LOGO_FEDORA_SERICEA, .colors = { FF_COLOR_FG_BLUE, FF_COLOR_FG_WHITE, }, .colorKeys = FF_COLOR_FG_BLUE, .colorTitle = FF_COLOR_FG_DEFAULT, }, // FedoraCoreOS { .names = {"Fedora-CoreOS"}, .lines = FASTFETCH_DATATEXT_LOGO_FEDORA_COREOS, .colors = { FF_COLOR_FG_BLUE, FF_COLOR_FG_WHITE, FF_COLOR_FG_RED, }, .colorKeys = FF_COLOR_FG_BLUE, .colorTitle = FF_COLOR_FG_DEFAULT, }, // FemboyOS { .names = {"FemboyOS"}, .lines = FASTFETCH_DATATEXT_LOGO_FEMBOYOS, .colors = { FF_COLOR_FG_BLUE, }, .colorKeys = FF_COLOR_FG_BLUE, .colorTitle = FF_COLOR_FG_DEFAULT, }, // Feren { .names = {"Feren"}, .lines = FASTFETCH_DATATEXT_LOGO_FEREN, .colors = { FF_COLOR_FG_BLUE, }, .colorKeys = FF_COLOR_FG_BLUE, .colorTitle = FF_COLOR_FG_DEFAULT, }, // Filotimo { .names = {"filotimo"}, .lines = FASTFETCH_DATATEXT_LOGO_FILOTIMO, .colors = { FF_COLOR_FG_BLUE, }, .colorKeys = FF_COLOR_FG_BLUE, .colorTitle = FF_COLOR_FG_DEFAULT, }, // Finnix { .names = {"Finnix"}, .lines = FASTFETCH_DATATEXT_LOGO_FINNIX, .colors = { FF_COLOR_FG_BLUE, FF_COLOR_FG_WHITE, }, .colorKeys = FF_COLOR_FG_BLUE, .colorTitle = FF_COLOR_FG_DEFAULT, }, // Floflis { .names = {"Floflis"}, .lines = FASTFETCH_DATATEXT_LOGO_FLOFLIS, .colors = { FF_COLOR_FG_LIGHT_CYAN, }, .colorKeys = FF_COLOR_FG_RED, .colorTitle = FF_COLOR_FG_DEFAULT, }, // FreeBSD { .names = {"Freebsd"}, .lines = FASTFETCH_DATATEXT_LOGO_FREEBSD, .colors = { FF_COLOR_FG_WHITE, FF_COLOR_FG_RED, }, .colorKeys = FF_COLOR_FG_RED, .colorTitle = FF_COLOR_FG_RED, }, // FreeBSDSmall { .names = {"freebsd_small"}, .type = FF_LOGO_LINE_TYPE_SMALL_BIT, .lines = FASTFETCH_DATATEXT_LOGO_FREEBSD_SMALL, .colors = { FF_COLOR_FG_RED, }, .colorKeys = FF_COLOR_FG_RED, .colorTitle = FF_COLOR_FG_RED, }, // FreeMiNT { .names = {"FreeMiNT"}, .lines = FASTFETCH_DATATEXT_LOGO_FREEMINT, .colors = { FF_COLOR_FG_WHITE }, .colorKeys = FF_COLOR_FG_DEFAULT, .colorTitle = FF_COLOR_FG_DEFAULT, }, // Frugalware { .names = {"Frugalware", "frugalware-linux"}, .lines = FASTFETCH_DATATEXT_LOGO_FRUGALWARE, .colors = { FF_COLOR_FG_BLUE, FF_COLOR_FG_WHITE, }, .colorKeys = FF_COLOR_FG_BLUE, .colorTitle = FF_COLOR_FG_DEFAULT, }, // Funtoo { .names = {"Funtoo"}, .lines = FASTFETCH_DATATEXT_LOGO_FUNTOO, .colors = { FF_COLOR_FG_MAGENTA, FF_COLOR_FG_WHITE, }, .colorKeys = FF_COLOR_FG_MAGENTA, .colorTitle = FF_COLOR_FG_DEFAULT, }, // Furreto { .names = {"Furreto"}, .lines = FASTFETCH_DATATEXT_LOGO_FURRETO, .colors = { FF_COLOR_FG_WHITE, FF_COLOR_FG_LIGHT_MAGENTA, }, .colorKeys = FF_COLOR_FG_CYAN, .colorTitle = FF_COLOR_FG_CYAN, }, // LAST {}, }; static const FFlogo G[] = { // GalliumOS { .names = {"GalliumOS"}, .lines = FASTFETCH_DATATEXT_LOGO_GALLIUMOS, .colors = { FF_COLOR_FG_BLUE, FF_COLOR_FG_WHITE, }, .colorKeys = FF_COLOR_FG_BLUE, .colorTitle = FF_COLOR_FG_DEFAULT, }, // Garuda { .names = {"Garuda", "garuda-linux"}, .lines = FASTFETCH_DATATEXT_LOGO_GARUDA, .colors = { FF_COLOR_FG_RED, }, .colorKeys = FF_COLOR_FG_RED, .colorTitle = FF_COLOR_FG_RED, }, // GarudaDragon { .names = {"GarudaDragon", "garuda-dragon"}, .lines = FASTFETCH_DATATEXT_LOGO_GARUDA_DRAGON, .colors = { FF_COLOR_FG_RED, }, .colorKeys = FF_COLOR_FG_RED, .colorTitle = FF_COLOR_FG_RED, }, // GarudaSmall { .names = {"Garuda_small", "garuda-linux_small"}, .type = FF_LOGO_LINE_TYPE_SMALL_BIT, .lines = FASTFETCH_DATATEXT_LOGO_GARUDA_SMALL, .colors = { FF_COLOR_FG_RED, }, .colorKeys = FF_COLOR_FG_RED, .colorTitle = FF_COLOR_FG_RED, }, // Gentoo { .names = {"Gentoo"}, .lines = FASTFETCH_DATATEXT_LOGO_GENTOO, .colors = { FF_COLOR_FG_MAGENTA, FF_COLOR_FG_WHITE, }, .colorKeys = FF_COLOR_FG_MAGENTA, .colorTitle = FF_COLOR_FG_MAGENTA, }, // GentooSmall { .names = {"Gentoo_small"}, .type = FF_LOGO_LINE_TYPE_SMALL_BIT, .lines = FASTFETCH_DATATEXT_LOGO_GENTOO_SMALL, .colors = { FF_COLOR_FG_MAGENTA, FF_COLOR_FG_WHITE, }, .colorKeys = FF_COLOR_FG_MAGENTA, .colorTitle = FF_COLOR_FG_MAGENTA, }, // GhostBSD { .names = {"GhostBSD"}, .lines = FASTFETCH_DATATEXT_LOGO_GHOSTBSD, .colors = { FF_COLOR_FG_BLUE, }, .colorKeys = FF_COLOR_FG_BLUE, .colorTitle = FF_COLOR_FG_RED, }, // GhostFreak { .names = {"GhostFreak"}, .lines = FASTFETCH_DATATEXT_LOGO_GHOSTFREAK, .colors = { FF_COLOR_FG_DEFAULT, }, .colorKeys = FF_COLOR_FG_DEFAULT, .colorTitle = FF_COLOR_FG_BLUE, }, // Glaucus { .names = {"Glaucus"}, .lines = FASTFETCH_DATATEXT_LOGO_GLAUCUS, .colors = { FF_COLOR_FG_MAGENTA, }, .colorKeys = FF_COLOR_FG_MAGENTA, .colorTitle = FF_COLOR_FG_MAGENTA, }, // GNewSense { .names = {"gNewSense"}, .lines = FASTFETCH_DATATEXT_LOGO_GNEWSENSE, .colors = { FF_COLOR_FG_BLUE, }, .colorKeys = FF_COLOR_FG_BLUE, .colorTitle = FF_COLOR_FG_BLUE, }, // GNOME { .names = {"GNOME"}, .lines = FASTFETCH_DATATEXT_LOGO_GNOME, .colors = { FF_COLOR_FG_BLUE, }, .colorKeys = FF_COLOR_FG_BLUE, .colorTitle = FF_COLOR_FG_RED, }, // GNU { .names = {"GNU"}, .lines = FASTFETCH_DATATEXT_LOGO_GNU, .colors = { FF_COLOR_FG_DEFAULT, }, .colorKeys = FF_COLOR_FG_DEFAULT, .colorTitle = FF_COLOR_FG_RED, }, // GoboLinux { .names = {"GoboLinux", "Gobo"}, .lines = FASTFETCH_DATATEXT_LOGO_GOBOLINUX, .colors = { FF_COLOR_FG_MAGENTA, }, .colorKeys = FF_COLOR_FG_MAGENTA, .colorTitle = FF_COLOR_FG_MAGENTA, }, // GoldenDogLinux { .names = {"GoldenDog Linux", "GDL", "goldendoglinux"}, .lines = FASTFETCH_DATATEXT_LOGO_GOLDENDOGLINUX, .colors = { FF_COLOR_FG_YELLOW, }, .colorKeys = FF_COLOR_FG_MAGENTA, .colorTitle = FF_COLOR_FG_DEFAULT, }, // GrapheneOS { .names = {"GrapheneOS"}, .lines = FASTFETCH_DATATEXT_LOGO_GRAPHENEOS, .colors = { FF_COLOR_FG_DEFAULT, }, .colorKeys = FF_COLOR_FG_DEFAULT, .colorTitle = FF_COLOR_FG_BLUE, }, // Grombyang { .names = {"Grombyang"}, .lines = FASTFETCH_DATATEXT_LOGO_GROMBYANG, .colors = { FF_COLOR_FG_BLUE, FF_COLOR_FG_GREEN, FF_COLOR_FG_RED, }, .colorKeys = FF_COLOR_FG_BLUE, .colorTitle = FF_COLOR_FG_GREEN, }, // Guix { .names = {"Guix"}, .lines = FASTFETCH_DATATEXT_LOGO_GUIX, .colors = { FF_COLOR_FG_YELLOW, }, .colorKeys = FF_COLOR_FG_YELLOW, .colorTitle = FF_COLOR_FG_DEFAULT, }, // GuixSmall { .names = {"Guix_small"}, .type = FF_LOGO_LINE_TYPE_SMALL_BIT, .lines = FASTFETCH_DATATEXT_LOGO_GUIX_SMALL, .colors = { FF_COLOR_FG_YELLOW, }, .colorKeys = FF_COLOR_FG_YELLOW, .colorTitle = FF_COLOR_FG_DEFAULT, }, // GXDE { .names = {"GXDE"}, .lines = FASTFETCH_DATATEXT_LOGO_GXDE, .colors = { FF_COLOR_FG_RED, }, .colorKeys = FF_COLOR_FG_RED, .colorTitle = FF_COLOR_FG_RED, }, // LAST {}, }; static const FFlogo H[] = { // Haiku { .names = {"Haiku"}, .lines = FASTFETCH_DATATEXT_LOGO_HAIKU, .colors = { FF_COLOR_FG_DEFAULT, FF_COLOR_FG_GREEN, }, .colorKeys = FF_COLOR_FG_RED, .colorTitle = FF_COLOR_FG_YELLOW, }, // Haiku2 { .names = {"Haiku2"}, .lines = FASTFETCH_DATATEXT_LOGO_HAIKU2, .colors = { FF_COLOR_FG_GREEN, FF_COLOR_FG_DEFAULT, }, .colorKeys = FF_COLOR_FG_RED, .colorTitle = FF_COLOR_FG_YELLOW, }, // HaikuSmall { .names = {"Haiku_small"}, .type = FF_LOGO_LINE_TYPE_SMALL_BIT, .lines = FASTFETCH_DATATEXT_LOGO_HAIKU_SMALL, .colors = { FF_COLOR_FG_GREEN, }, .colorKeys = FF_COLOR_FG_GREEN, .colorTitle = FF_COLOR_FG_YELLOW, }, // HamoniKR { .names = {"HamoniKR"}, .lines = FASTFETCH_DATATEXT_LOGO_HAMONIKR, .colors = { FF_COLOR_FG_BLUE, FF_COLOR_FG_WHITE, FF_COLOR_FG_256 "99" }, .colorKeys = FF_COLOR_FG_BLUE, .colorTitle = FF_COLOR_FG_DEFAULT, }, // HarDClanZ { .names = {"HarDClanZ"}, .lines = FASTFETCH_DATATEXT_LOGO_HARDCLANZ, .colors = { FF_COLOR_FG_BLUE, FF_COLOR_FG_WHITE, FF_COLOR_FG_RED, }, .colorKeys = FF_COLOR_FG_BLUE, .colorTitle = FF_COLOR_FG_DEFAULT, }, // HardenedBSD { .names = {"HardenedBSD"}, .lines = FASTFETCH_DATATEXT_LOGO_FREEBSD, .colors = { FF_COLOR_FG_WHITE, FF_COLOR_FG_RED, }, .colorKeys = FF_COLOR_FG_RED, .colorTitle = FF_COLOR_FG_RED, }, // HarmonyOS { .names = {"HarmonyOS"}, .lines = FASTFETCH_DATATEXT_LOGO_HARMONYOS, .colors = { FF_COLOR_FG_WHITE, FF_COLOR_FG_BLUE, }, .colorKeys = FF_COLOR_FG_BLUE, .colorTitle = FF_COLOR_FG_BLUE, }, // Hash { .names = {"Hash"}, .lines = FASTFETCH_DATATEXT_LOGO_HASH, .colors = { FF_COLOR_FG_256 "123", FF_COLOR_FG_256 "123", }, }, // HeliumOS { .names = {"HeliumOS"}, .lines = FASTFETCH_DATATEXT_LOGO_HELIUMOS, .colors = { FF_COLOR_FG_256 "81", }, .colorKeys = FF_COLOR_FG_256 "81", .colorTitle = FF_COLOR_FG_DEFAULT, }, // Huawei Cloud EulerOS { .names = {"Huawei Cloud EulerOS", "hce"}, .lines = FASTFETCH_DATATEXT_LOGO_HCE, .colors = { FF_COLOR_FG_RED, }, .colorKeys = FF_COLOR_FG_RED, .colorTitle = FF_COLOR_FG_RED, }, // Huayra { .names = {"Huayra"}, .lines = FASTFETCH_DATATEXT_LOGO_HUAYRA, .colors = { FF_COLOR_FG_DEFAULT, }, .colorKeys = FF_COLOR_FG_DEFAULT, .colorTitle = FF_COLOR_FG_BLUE, }, // Hybrid { .names = {"Hybrid"}, .lines = FASTFETCH_DATATEXT_LOGO_HYBRID, .colors = { FF_COLOR_FG_BLUE, FF_COLOR_FG_LIGHT_BLUE, }, .colorKeys = FF_COLOR_FG_LIGHT_BLUE, .colorTitle = FF_COLOR_FG_BLUE, }, // HydroOS { .names = {"HydroOS"}, .lines = FASTFETCH_DATATEXT_LOGO_HYDROOS, .colors = { FF_COLOR_FG_RED, }, .colorKeys = FF_COLOR_FG_RED, .colorTitle = FF_COLOR_FG_GREEN, }, // HyprOS { .names = {"hypros"}, .lines = FASTFETCH_DATATEXT_LOGO_HYPROS, .colors = { FF_COLOR_FG_RED, FF_COLOR_FG_YELLOW, FF_COLOR_FG_CYAN, FF_COLOR_FG_BLUE, }, }, // Hyperbola { .names = {"Hyperbola"}, .lines = FASTFETCH_DATATEXT_LOGO_HYPERBOLA, .colors = { FF_COLOR_FG_LIGHT_BLACK, }, .colorKeys = FF_COLOR_FG_LIGHT_BLACK, .colorTitle = FF_COLOR_FG_DEFAULT, }, // HyperbolaSmall { .names = {"Hyperbola_small"}, .type = FF_LOGO_LINE_TYPE_SMALL_BIT, .lines = FASTFETCH_DATATEXT_LOGO_HYPERBOLA_SMALL, .colors = { FF_COLOR_FG_LIGHT_BLACK, }, .colorKeys = FF_COLOR_FG_LIGHT_BLACK, .colorTitle = FF_COLOR_FG_DEFAULT, }, // LAST {}, }; static const FFlogo I[] = { // Iglunix { .names = {"Iglunix", "Iglu"}, .lines = FASTFETCH_DATATEXT_LOGO_IGLUNIX, .colors = { FF_COLOR_FG_LIGHT_BLACK, }, .colorKeys = FF_COLOR_FG_LIGHT_BLACK, .colorTitle = FF_COLOR_FG_DEFAULT, }, // InstantOS { .names = {"InstantOS"}, .lines = FASTFETCH_DATATEXT_LOGO_INSTANTOS, .colors = { FF_COLOR_FG_BLUE, FF_COLOR_FG_CYAN, }, .colorKeys = FF_COLOR_FG_BLUE, .colorTitle = FF_COLOR_FG_CYAN, }, // Interix { .names = {"Interix"}, .lines = FASTFETCH_DATATEXT_LOGO_INTERIX, .colors = { FF_COLOR_FG_RED, FF_COLOR_FG_WHITE, FF_COLOR_FG_BLUE, FF_COLOR_FG_BLACK, FF_COLOR_FG_YELLOW, }, }, // IRIX { .names = {"IRIX"}, .lines = FASTFETCH_DATATEXT_LOGO_IRIX, .colors = { FF_COLOR_FG_BLUE, }, .colorKeys = FF_COLOR_FG_BLUE, .colorTitle = FF_COLOR_FG_DEFAULT, }, // Ironclad { .names = {"Ironclad"}, .lines = FASTFETCH_DATATEXT_LOGO_IRONCLAD, .colors = { FF_COLOR_FG_BLACK, }, .colorKeys = FF_COLOR_FG_DEFAULT, .colorTitle = FF_COLOR_FG_MAGENTA, }, // Itc { .names = {"Itc"}, .lines = FASTFETCH_DATATEXT_LOGO_ITC, .colors = { FF_COLOR_FG_RED, }, .colorKeys = FF_COLOR_FG_DEFAULT, .colorTitle = FF_COLOR_FG_RED, }, // LAST {}, }; static const FFlogo J[] = { // Januslinux { .names = {"januslinux", "janus"}, .lines = FASTFETCH_DATATEXT_LOGO_JANUSLINUX, .colors = { FF_COLOR_FG_BLUE, FF_COLOR_FG_MAGENTA, }, .colorKeys = FF_COLOR_FG_BLUE, .colorTitle = FF_COLOR_FG_MAGENTA, }, // LAST {}, }; static const FFlogo K[] = { // Kaisen { .names = {"Kaisen"}, .lines = FASTFETCH_DATATEXT_LOGO_KAISEN, .colors = { FF_COLOR_FG_RED, FF_COLOR_FG_WHITE, }, .colorKeys = FF_COLOR_FG_RED, .colorTitle = FF_COLOR_FG_DEFAULT, }, // Kali { .names = {"Kali"}, .lines = FASTFETCH_DATATEXT_LOGO_KALI, .colors = { FF_COLOR_FG_BLUE, FF_COLOR_FG_LIGHT_BLACK, }, .colorKeys = FF_COLOR_FG_BLUE, .colorTitle = FF_COLOR_FG_DEFAULT, }, // KaliSmall { .names = {"Kali_small"}, .type = FF_LOGO_LINE_TYPE_SMALL_BIT, .lines = FASTFETCH_DATATEXT_LOGO_KALI_SMALL, .colors = { FF_COLOR_FG_BLUE, FF_COLOR_FG_LIGHT_BLACK, }, .colorKeys = FF_COLOR_FG_BLUE, .colorTitle = FF_COLOR_FG_DEFAULT, }, // Kalpa Desktop { .names = {"kalpa-desktop"}, .lines = FASTFETCH_DATATEXT_LOGO_KALPA_DESKTOP, .colors = { FF_COLOR_FG_GREEN, }, .colorKeys = FF_COLOR_FG_GREEN, .colorTitle = FF_COLOR_FG_DEFAULT, }, // KaOS { .names = {"KaOS"}, .lines = FASTFETCH_DATATEXT_LOGO_KAOS, .colors = { FF_COLOR_FG_BLUE, FF_COLOR_FG_WHITE, }, .colorKeys = FF_COLOR_FG_BLUE, .colorTitle = FF_COLOR_FG_DEFAULT, }, // KernelOS { .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 } }, // KDE Neon { .names = {"KDE Neon"}, // Distro ID is "neon"; Distro name is "KDE Neon" .lines = FASTFETCH_DATATEXT_LOGO_KDENEON, .colors = { FF_COLOR_FG_GREEN, FF_COLOR_FG_DEFAULT, }, }, // Kibojoe { .names = {"Kibojoe"}, .lines = FASTFETCH_DATATEXT_LOGO_KIBOJOE, .colors = { FF_COLOR_FG_GREEN, FF_COLOR_FG_WHITE, FF_COLOR_FG_BLUE, }, .colorKeys = FF_COLOR_FG_GREEN, .colorTitle = FF_COLOR_FG_DEFAULT, }, // KISSLinux { .names = {"KISS", "kiss-linux", "kisslinux"}, .lines = FASTFETCH_DATATEXT_LOGO_KISS, .colors = { FF_COLOR_FG_MAGENTA, FF_COLOR_FG_WHITE, FF_COLOR_FG_BLUE, }, .colorKeys = FF_COLOR_FG_MAGENTA, .colorTitle = FF_COLOR_FG_BLUE, }, // KISSLinux2 { .names = {"kiss2"}, .lines = FASTFETCH_DATATEXT_LOGO_KISS2, .colors = { FF_COLOR_FG_BLACK, FF_COLOR_FG_RED, FF_COLOR_FG_WHITE, }, .colorKeys = FF_COLOR_FG_RED, .colorTitle = FF_COLOR_FG_RED, }, // Kogaion { .names = {"Kogaion"}, .lines = FASTFETCH_DATATEXT_LOGO_KOGAION, .colors = { FF_COLOR_FG_BLUE, FF_COLOR_FG_WHITE, }, .colorKeys = FF_COLOR_FG_BLUE, .colorTitle = FF_COLOR_FG_DEFAULT, }, // Korora { .names = {"Korora"}, .lines = FASTFETCH_DATATEXT_LOGO_KORORA, .colors = { FF_COLOR_FG_BLUE, FF_COLOR_FG_WHITE, }, .colorKeys = FF_COLOR_FG_BLUE, .colorTitle = FF_COLOR_FG_DEFAULT, }, // KrassOS { .names = {"KrassOS", "Krass"}, .lines = FASTFETCH_DATATEXT_LOGO_KRASSOS, .colors = { FF_COLOR_FG_BLUE, FF_COLOR_FG_WHITE, }, .colorKeys = FF_COLOR_FG_BLUE, .colorTitle = FF_COLOR_FG_DEFAULT, }, // KSLinux { .names = {"KSLinux"}, .lines = FASTFETCH_DATATEXT_LOGO_KSLINUX, .colors = { FF_COLOR_FG_BLUE, FF_COLOR_FG_WHITE, }, .colorKeys = FF_COLOR_FG_BLUE, .colorTitle = FF_COLOR_FG_DEFAULT, }, // Kubuntu { .names = {"Kubuntu", "kubuntu-linux", "kde-ubuntu"}, .lines = FASTFETCH_DATATEXT_LOGO_KUBUNTU, .colors = { FF_COLOR_FG_BLUE, FF_COLOR_FG_WHITE, }, .colorKeys = FF_COLOR_FG_BLUE, .colorTitle = FF_COLOR_FG_BLUE, }, // Kylin { .names = {"Kylin", "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, .colorTitle = FF_COLOR_FG_BLUE, }, // LAST {}, }; static const FFlogo L[] = { // LainOS { .names = {"LainOS"}, .lines = FASTFETCH_DATATEXT_LOGO_LAINOS, .colors = { FF_COLOR_FG_BLUE, FF_COLOR_FG_256 "14", FF_COLOR_FG_WHITE, }, }, // LangitKetujuh { .names = {"langitketujuh", "l7"}, .lines = FASTFETCH_DATATEXT_LOGO_LANGITKETUJUH, .colors = { FF_COLOR_FG_BLUE, FF_COLOR_FG_WHITE, }, .colorKeys = FF_COLOR_FG_BLUE, .colorTitle = FF_COLOR_FG_BLUE, }, // Laxeros { .names = {"Laxeros"}, .lines = FASTFETCH_DATATEXT_LOGO_LAXEROS, .colors = { FF_COLOR_FG_BLUE, FF_COLOR_FG_WHITE, }, .colorKeys = FF_COLOR_FG_BLUE, .colorTitle = FF_COLOR_FG_DEFAULT, }, // LEDE { .names = {"LEDE"}, .lines = FASTFETCH_DATATEXT_LOGO_LEDE, .colors = { FF_COLOR_FG_BLUE, FF_COLOR_FG_WHITE, }, .colorKeys = FF_COLOR_FG_BLUE, .colorTitle = FF_COLOR_FG_DEFAULT, }, // LibreELEC { .names = {"LibreELEC"}, .lines = FASTFETCH_DATATEXT_LOGO_LIBREELEC, .colors = { FF_COLOR_FG_GREEN, FF_COLOR_FG_YELLOW, FF_COLOR_FG_WHITE, FF_COLOR_FG_CYAN, FF_COLOR_FG_MAGENTA, }, .colorKeys = FF_COLOR_FG_GREEN, .colorTitle = FF_COLOR_FG_YELLOW, }, // Lilidog { .names = {"Lilidog"}, .lines = FASTFETCH_DATATEXT_LOGO_LILIDOG, .colors = { FF_COLOR_FG_BLUE, }, .colorKeys = FF_COLOR_FG_RED, .colorTitle = FF_COLOR_FG_DEFAULT, }, // Lingmo OS { .names = {"Lingmo", "LingmoOS"}, .lines = FASTFETCH_DATATEXT_LOGO_LINGMO, .colors = { FF_COLOR_FG_BLUE, FF_COLOR_FG_WHITE, }, .colorKeys = FF_COLOR_FG_BLUE, .colorTitle = FF_COLOR_FG_BLUE, }, // Linspire { .names = {"Linspire", "Lindows"}, .lines = FASTFETCH_DATATEXT_LOGO_LINSPIRE, .colors = { FF_COLOR_FG_BLUE, FF_COLOR_FG_GREEN, }, .colorKeys = FF_COLOR_FG_BLUE, .colorTitle = FF_COLOR_FG_GREEN, }, // Linux { .names = {"Linux", "linux-generic"}, .lines = FASTFETCH_DATATEXT_LOGO_LINUX, .colors = { FF_COLOR_FG_WHITE, FF_COLOR_FG_LIGHT_BLACK, FF_COLOR_FG_YELLOW, }, .colorKeys = FF_COLOR_FG_DEFAULT, .colorTitle = FF_COLOR_FG_YELLOW, }, // LinuxFromScratch { .names = {"LinuxFromScratch", "lfs"}, .lines = FASTFETCH_DATATEXT_LOGO_LFS, .colors = { FF_COLOR_FG_WHITE, FF_COLOR_FG_BLACK, FF_COLOR_FG_YELLOW, }, .colorKeys = FF_COLOR_FG_BLACK, .colorTitle = FF_COLOR_FG_YELLOW, }, // LinuxSmall { .names = {"Linux_small", "linux-generic_small"}, .type = FF_LOGO_LINE_TYPE_SMALL_BIT, .lines = FASTFETCH_DATATEXT_LOGO_LINUX_SMALL, .colors = { FF_COLOR_FG_BLACK, FF_COLOR_FG_WHITE, FF_COLOR_FG_YELLOW, }, .colorKeys = FF_COLOR_FG_DEFAULT, .colorTitle = FF_COLOR_FG_DEFAULT, }, // LinuxLite { .names = {"LinuxLite"}, .lines = FASTFETCH_DATATEXT_LOGO_LINUXLITE, .colors = { FF_COLOR_FG_GREEN, FF_COLOR_FG_WHITE, }, .colorKeys = FF_COLOR_FG_GREEN, .colorTitle = FF_COLOR_FG_DEFAULT, }, // LinuxLiteSmall { .names = {"LinuxLite_small"}, .type = FF_LOGO_LINE_TYPE_SMALL_BIT, .lines = FASTFETCH_DATATEXT_LOGO_LINUXLITE_SMALL, .colors = { FF_COLOR_FG_BLUE, FF_COLOR_FG_WHITE, }, .colorKeys = FF_COLOR_FG_BLUE, .colorTitle = FF_COLOR_FG_DEFAULT, }, // LinuxMint { .names = {"linuxmint"}, .lines = FASTFETCH_DATATEXT_LOGO_LINUXMINT, .colors = { FF_COLOR_FG_GREEN, FF_COLOR_FG_WHITE, }, .colorKeys = FF_COLOR_FG_GREEN, .colorTitle = FF_COLOR_FG_GREEN, }, // LinuxMintSmall { .names = {"linuxmint_small"}, .type = FF_LOGO_LINE_TYPE_SMALL_BIT, .lines = FASTFETCH_DATATEXT_LOGO_LINUXMINT_SMALL, .colors = { FF_COLOR_FG_GREEN, FF_COLOR_FG_WHITE, }, .colorKeys = FF_COLOR_FG_GREEN, .colorTitle = FF_COLOR_FG_GREEN, }, // LinuxMint2 { .names = {"linuxmint2"}, .type = FF_LOGO_LINE_TYPE_ALTER_BIT, .lines = FASTFETCH_DATATEXT_LOGO_LINUXMINT2, .colors = { FF_COLOR_FG_GREEN, FF_COLOR_FG_WHITE, }, .colorKeys = FF_COLOR_FG_GREEN, .colorTitle = FF_COLOR_FG_GREEN, }, // LinuxMintOld { .names = {"linuxmint_old"}, .type = FF_LOGO_LINE_TYPE_ALTER_BIT, .lines = FASTFETCH_DATATEXT_LOGO_LINUXMINT_OLD, .colors = { FF_COLOR_FG_GREEN, FF_COLOR_FG_WHITE, }, .colorKeys = FF_COLOR_FG_GREEN, .colorTitle = FF_COLOR_FG_GREEN, }, // Live_Raizo { .names = {"Live Raizo", "Live_Raizo"}, .lines = FASTFETCH_DATATEXT_LOGO_LIVE_RAIZO, .colors = { FF_COLOR_FG_YELLOW, }, .colorKeys = FF_COLOR_FG_YELLOW, .colorTitle = FF_COLOR_FG_DEFAULT, }, // LliureX { .names = {"LliureX"}, .lines = FASTFETCH_DATATEXT_LOGO_LLIUREX, .colors = { FF_COLOR_FG_BLUE, }, .colorKeys = FF_COLOR_FG_DEFAULT, .colorTitle = FF_COLOR_FG_BLUE, }, // LMDE { .names = {"LMDE"}, .lines = FASTFETCH_DATATEXT_LOGO_LMDE, .colors = { FF_COLOR_FG_GREEN, FF_COLOR_FG_WHITE, }, .colorKeys = FF_COLOR_FG_GREEN, .colorTitle = FF_COLOR_FG_DEFAULT, }, // Loc-OS { .names = {"locos", "loc-os", "Loc-OS Linux"}, .lines = FASTFETCH_DATATEXT_LOGO_LOCOS, .colors = { FF_COLOR_FG_BLACK, FF_COLOR_FG_YELLOW, FF_COLOR_FG_WHITE, }, .colorKeys = FF_COLOR_FG_YELLOW, .colorTitle = FF_COLOR_FG_RED, }, // Lubuntu { .names = {"lubuntu"}, .lines = FASTFETCH_DATATEXT_LOGO_LUBUNTU, .colors = { FF_COLOR_FG_BLUE, FF_COLOR_FG_WHITE, }, }, // Lunar { .names = {"Lunar"}, .lines = FASTFETCH_DATATEXT_LOGO_LUNAR, .colors = { FF_COLOR_FG_BLUE, FF_COLOR_FG_WHITE, FF_COLOR_FG_YELLOW, }, .colorKeys = FF_COLOR_FG_BLUE, .colorTitle = FF_COLOR_FG_DEFAULT, }, // LAST {}, }; static const FFlogo M[] = { // Macaroni { .names = {"Macaroni"}, .lines = FASTFETCH_DATATEXT_LOGO_MACARONIOS, .colors = { FF_COLOR_FG_YELLOW, FF_COLOR_FG_WHITE, }, .colorKeys = FF_COLOR_FG_YELLOW, .colorTitle = FF_COLOR_FG_GREEN, }, // MacOS { .names = {"macOS"}, .lines = FASTFETCH_DATATEXT_LOGO_MACOS, .colors = { FF_COLOR_FG_GREEN, FF_COLOR_FG_YELLOW, FF_COLOR_FG_LIGHT_RED, FF_COLOR_FG_RED, FF_COLOR_FG_MAGENTA, FF_COLOR_FG_BLUE, }, .colorKeys = FF_COLOR_FG_YELLOW, .colorTitle = FF_COLOR_FG_GREEN, }, // MacOSSmall { .names = {"macOS_small"}, .type = FF_LOGO_LINE_TYPE_SMALL_BIT, .lines = FASTFETCH_DATATEXT_LOGO_MACOS_SMALL, .colors = { FF_COLOR_FG_GREEN, FF_COLOR_FG_YELLOW, FF_COLOR_FG_RED, FF_COLOR_FG_MAGENTA, FF_COLOR_FG_BLUE, }, .colorKeys = FF_COLOR_FG_YELLOW, .colorTitle = FF_COLOR_FG_GREEN, }, // MacOS2 { .names = {"macOS2"}, .type = FF_LOGO_LINE_TYPE_ALTER_BIT, .lines = FASTFETCH_DATATEXT_LOGO_MACOS2, .colors = { FF_COLOR_FG_GREEN, FF_COLOR_FG_YELLOW, FF_COLOR_FG_LIGHT_RED, FF_COLOR_FG_RED, FF_COLOR_FG_MAGENTA, FF_COLOR_FG_BLUE, }, .colorKeys = FF_COLOR_FG_YELLOW, .colorTitle = FF_COLOR_FG_GREEN, }, // MacOS2Small { .names = {"macOS2_small"}, .type = FF_LOGO_LINE_TYPE_SMALL_BIT | FF_LOGO_LINE_TYPE_ALTER_BIT, .lines = FASTFETCH_DATATEXT_LOGO_MACOS2_SMALL, .colors = { FF_COLOR_FG_GREEN, FF_COLOR_FG_YELLOW, FF_COLOR_FG_RED, FF_COLOR_FG_MAGENTA, FF_COLOR_FG_BLUE, }, .colorKeys = FF_COLOR_FG_YELLOW, .colorTitle = FF_COLOR_FG_GREEN, }, // MacOS3 { .names = {"macOS3"}, .type = FF_LOGO_LINE_TYPE_ALTER_BIT, .lines = FASTFETCH_DATATEXT_LOGO_MACOS3, .colors = { FF_COLOR_FG_GREEN, FF_COLOR_FG_YELLOW, FF_COLOR_FG_LIGHT_RED, FF_COLOR_FG_RED, FF_COLOR_FG_MAGENTA, FF_COLOR_FG_BLUE, }, .colorKeys = FF_COLOR_FG_YELLOW, .colorTitle = FF_COLOR_FG_GREEN, }, // MainsailOS { .names = {"MainsailOS"}, .lines = FASTFETCH_DATATEXT_LOGO_MAINSAILOS, .colors = { FF_COLOR_FG_RED, }, }, // MainsailOSSmall { .names = {"MainsailOS_small"}, .type = FF_LOGO_LINE_TYPE_SMALL_BIT, .lines = FASTFETCH_DATATEXT_LOGO_MAINSAILOS_SMALL, .colors = { FF_COLOR_FG_RED, }, }, // Mageia { .names = {"Mageia"}, .lines = FASTFETCH_DATATEXT_LOGO_MAGEIA, .colors = { FF_COLOR_FG_CYAN, FF_COLOR_FG_WHITE, }, .colorKeys = FF_COLOR_FG_CYAN, .colorTitle = FF_COLOR_FG_DEFAULT, }, // MageiaSmall { .names = {"Mageia_small"}, .type = FF_LOGO_LINE_TYPE_SMALL_BIT, .lines = FASTFETCH_DATATEXT_LOGO_MAGEIA_SMALL, .colors = { FF_COLOR_FG_CYAN, FF_COLOR_FG_WHITE, }, .colorKeys = FF_COLOR_FG_CYAN, .colorTitle = FF_COLOR_FG_DEFAULT, }, // Magix { .names = {"Magix", "MagixOS"}, .lines = FASTFETCH_DATATEXT_LOGO_MAGIX, .colors = { FF_COLOR_FG_LIGHT_MAGENTA, FF_COLOR_FG_CYAN, }, .colorKeys = FF_COLOR_FG_CYAN, .colorTitle = FF_COLOR_FG_LIGHT_MAGENTA, }, // MagpieOS { .names = {"MagpieOS", "Magpie"}, .lines = FASTFETCH_DATATEXT_LOGO_MAGPIEOS, .colors = { FF_COLOR_FG_GREEN, FF_COLOR_FG_RED, FF_COLOR_FG_YELLOW, FF_COLOR_FG_MAGENTA, }, .colorKeys = FF_COLOR_FG_GREEN, .colorTitle = FF_COLOR_FG_RED, }, // Mandriva { .names = {"mandriva", "mandrake"}, .lines = FASTFETCH_DATATEXT_LOGO_MANDRIVA, .colors = { FF_COLOR_FG_BLUE, FF_COLOR_FG_YELLOW, }, .colorKeys = FF_COLOR_FG_BLUE, .colorTitle = FF_COLOR_FG_YELLOW, }, // Manjaro { .names = {"manjaro", "manjaro-arm"}, .lines = FASTFETCH_DATATEXT_LOGO_MANJARO, .colors = { FF_COLOR_FG_GREEN, }, .colorKeys = FF_COLOR_FG_GREEN, .colorTitle = FF_COLOR_FG_GREEN, }, // ManjaroSmall { .names = {"manjaro_small", "manjaro-arm_small"}, .type = FF_LOGO_LINE_TYPE_SMALL_BIT, .lines = FASTFETCH_DATATEXT_LOGO_MANJARO_SMALL, .colors = { FF_COLOR_FG_GREEN, }, .colorKeys = FF_COLOR_FG_GREEN, .colorTitle = FF_COLOR_FG_GREEN, }, // MassOS { .names = {"MassOS"}, .lines = FASTFETCH_DATATEXT_LOGO_MASSOS, .colors = { FF_COLOR_FG_DEFAULT, }, .colorKeys = FF_COLOR_FG_DEFAULT, .colorTitle = FF_COLOR_FG_DEFAULT, }, // MatuusOS { .names = {"MatuusOS", "Matuus"}, .lines = FASTFETCH_DATATEXT_LOGO_MATUUSOS, .colors = { FF_COLOR_FG_YELLOW, FF_COLOR_FG_RED, }, .colorKeys = FF_COLOR_FG_YELLOW, .colorTitle = FF_COLOR_FG_RED, }, // MaUI { .names = {"MaUI"}, .lines = FASTFETCH_DATATEXT_LOGO_MAUI, .colors = { FF_COLOR_FG_CYAN, }, .colorKeys = FF_COLOR_FG_CYAN, .colorTitle = FF_COLOR_FG_DEFAULT, }, // Mauna { .names = {"Mauna"}, .lines = FASTFETCH_DATATEXT_LOGO_MAUNA, .colors = { FF_COLOR_FG_BLUE, FF_COLOR_FG_CYAN, }, }, // Meowix { .names = {"Meowix"}, .lines = FASTFETCH_DATATEXT_LOGO_MEOWIX, .colors = { FF_COLOR_FG_RED, FF_COLOR_FG_YELLOW, FF_COLOR_FG_YELLOW, FF_COLOR_FG_BLUE, }, .colorKeys = FF_COLOR_FG_RED, .colorTitle = FF_COLOR_FG_YELLOW, }, // Mer { .names = {"Mer"}, .lines = FASTFETCH_DATATEXT_LOGO_MER, .colors = { FF_COLOR_FG_BLUE, }, .colorKeys = FF_COLOR_FG_BLUE, .colorTitle = FF_COLOR_FG_DEFAULT, }, // MidnightBSD { .names = {"MidnightBSD"}, .lines = FASTFETCH_DATATEXT_LOGO_MIDNIGHTBSD, .colors = { FF_COLOR_FG_DEFAULT, }, .colorKeys = FF_COLOR_FG_LIGHT_BLACK, .colorTitle = FF_COLOR_FG_DEFAULT, }, // MidOS { .names = {"MidOS"}, .lines = FASTFETCH_DATATEXT_LOGO_MIDOS, .colors = { FF_COLOR_FG_LIGHT_BLACK, }, .colorKeys = FF_COLOR_FG_LIGHT_BLACK, .colorTitle = FF_COLOR_FG_DEFAULT, }, // MidOSOld { .names = {"MidOS_old"}, .lines = FASTFETCH_DATATEXT_LOGO_MIDOS_OLD, .type = FF_LOGO_LINE_TYPE_ALTER_BIT, .colors = { FF_COLOR_FG_LIGHT_BLACK, FF_COLOR_FG_WHITE, }, .colorKeys = FF_COLOR_FG_LIGHT_BLACK, .colorTitle = FF_COLOR_FG_DEFAULT, }, // Minimal System { .names = {"Minimal_System"}, .lines = FASTFETCH_DATATEXT_LOGO_MINIMAL, .colors = { FF_COLOR_FG_RED, }, .colorKeys = FF_COLOR_FG_CYAN, .colorTitle = FF_COLOR_FG_DEFAULT, }, // Minix { .names = {"Minix"}, .lines = FASTFETCH_DATATEXT_LOGO_MINIX, .colors = { FF_COLOR_FG_RED, FF_COLOR_FG_WHITE, FF_COLOR_FG_YELLOW, }, .colorKeys = FF_COLOR_FG_RED, .colorTitle = FF_COLOR_FG_YELLOW, }, // MiracleLinux { .names = {"miraclelinux"}, .lines = FASTFETCH_DATATEXT_LOGO_MIRACLE_LINUX, .colors = { FF_COLOR_FG_256 "29", }, .colorKeys = FF_COLOR_FG_256 "29", .colorTitle = FF_COLOR_FG_DEFAULT, }, // MOS { .names = {"MOS"}, .lines = FASTFETCH_DATATEXT_LOGO_MOS, .colors = { FF_COLOR_FG_CYAN, FF_COLOR_FG_BLUE, }, }, // Msys2 { .names = {"Msys2"}, .lines = FASTFETCH_DATATEXT_LOGO_MSYS2, .colors = { FF_COLOR_FG_MAGENTA, FF_COLOR_FG_WHITE, FF_COLOR_FG_RED, }, .colorKeys = FF_COLOR_FG_MAGENTA, .colorTitle = FF_COLOR_FG_RED, }, // MX { .names = {"MX"}, .lines = FASTFETCH_DATATEXT_LOGO_MX, .colors = { FF_COLOR_FG_DEFAULT, }, .colorKeys = FF_COLOR_FG_BLUE, .colorTitle = FF_COLOR_FG_CYAN, }, // MXSmall { .names = {"MX_small"}, .type = FF_LOGO_LINE_TYPE_SMALL_BIT, .lines = FASTFETCH_DATATEXT_LOGO_MX_SMALL, .colors = { FF_COLOR_FG_DEFAULT, }, .colorKeys = FF_COLOR_FG_BLUE, .colorTitle = FF_COLOR_FG_CYAN, }, // MX2 { .names = {"MX2"}, .type = FF_LOGO_LINE_TYPE_ALTER_BIT, .lines = FASTFETCH_DATATEXT_LOGO_MX2, .colors = { FF_COLOR_FG_DEFAULT, }, .colorKeys = FF_COLOR_FG_BLUE, .colorTitle = FF_COLOR_FG_CYAN, }, // LAST {}, }; static const FFlogo N[] = { // Namib { .names = {"Namib"}, .lines = FASTFETCH_DATATEXT_LOGO_NAMIB, .colors = { FF_COLOR_FG_RED, FF_COLOR_FG_WHITE, }, }, // Nekos { .names = {"Nekos"}, .lines = FASTFETCH_DATATEXT_LOGO_NEKOS, .colors = { FF_COLOR_FG_YELLOW, FF_COLOR_FG_WHITE, FF_COLOR_FG_RED, }, }, // Neptune { .names = {"Neptune"}, .lines = FASTFETCH_DATATEXT_LOGO_NEPTUNE, .colors = { FF_COLOR_FG_WHITE, FF_COLOR_FG_WHITE, }, }, // NetRunner { .names = {"NetRunner"}, .lines = FASTFETCH_DATATEXT_LOGO_NETRUNNER, .colors = { FF_COLOR_FG_BLUE, FF_COLOR_FG_WHITE, }, }, // NexaLinux { .names = {"nexalinux"}, .lines = FASTFETCH_DATATEXT_LOGO_NEXALINUX, .colors = { FF_COLOR_FG_LIGHT_BLUE, FF_COLOR_FG_LIGHT_BLUE, }, }, // Nitrux { .names = {"Nitrux"}, .lines = FASTFETCH_DATATEXT_LOGO_NITRUX, .colors = { FF_COLOR_FG_BLUE, FF_COLOR_FG_WHITE, }, }, // NixOS { .names = {"NixOS"}, .lines = FASTFETCH_DATATEXT_LOGO_NIXOS, .colors = { FF_COLOR_FG_BLUE, FF_COLOR_FG_CYAN, FF_COLOR_FG_BLUE, FF_COLOR_FG_CYAN, FF_COLOR_FG_BLUE, FF_COLOR_FG_CYAN, }, }, // NixOSSmall { .names = {"NixOS_small"}, .type = FF_LOGO_LINE_TYPE_SMALL_BIT, .lines = FASTFETCH_DATATEXT_LOGO_NIXOS_SMALL, .colors = { FF_COLOR_FG_BLUE, FF_COLOR_FG_CYAN, FF_COLOR_FG_BLUE, FF_COLOR_FG_CYAN, FF_COLOR_FG_BLUE, FF_COLOR_FG_CYAN, }, }, // NixOSOld { .names = {"nixos_old"}, .type = FF_LOGO_LINE_TYPE_ALTER_BIT, .lines = FASTFETCH_DATATEXT_LOGO_NIXOS_OLD, .colors = { FF_COLOR_FG_BLUE, FF_COLOR_FG_CYAN, }, }, // NixOsOldSmall { .names = {"nixos_old_small"}, .type = FF_LOGO_LINE_TYPE_SMALL_BIT | FF_LOGO_LINE_TYPE_ALTER_BIT, .lines = FASTFETCH_DATATEXT_LOGO_NIXOS_OLD_SMALL, .colors = { FF_COLOR_FG_BLUE, FF_COLOR_FG_CYAN, }, }, // NetBSD { .names = {"NetBSD"}, .lines = FASTFETCH_DATATEXT_LOGO_NETBSD, .colors = { FF_COLOR_FG_RED, FF_COLOR_FG_WHITE, }, .colorKeys = FF_COLOR_FG_RED, .colorTitle = FF_COLOR_FG_DEFAULT, }, // NetBSD2 { .names = {"NetBSD2"}, .lines = FASTFETCH_DATATEXT_LOGO_NETBSD2, .type = FF_LOGO_LINE_TYPE_ALTER_BIT, .colors = { FF_COLOR_FG_RED, FF_COLOR_FG_WHITE, }, .colorKeys = FF_COLOR_FG_RED, .colorTitle = FF_COLOR_FG_DEFAULT, }, // NetBSD Small { .names = {"NetBSD_small"}, .lines = FASTFETCH_DATATEXT_LOGO_NETBSD_SMALL, .type = FF_LOGO_LINE_TYPE_SMALL_BIT, .colors = { FF_COLOR_FG_RED, FF_COLOR_FG_WHITE, }, .colorKeys = FF_COLOR_FG_RED, .colorTitle = FF_COLOR_FG_DEFAULT, }, // Nobara { .names = {"nobara"}, .lines = FASTFETCH_DATATEXT_LOGO_NOBARA, .colors = { FF_COLOR_FG_DEFAULT, }, .colorKeys = FF_COLOR_FG_DEFAULT, .colorTitle = FF_COLOR_FG_DEFAULT, }, // NomadBSD { .names = {"nomadbsd"}, .lines = FASTFETCH_DATATEXT_LOGO_NOMADBSD, .colors = { FF_COLOR_FG_BLUE, }, .colorKeys = FF_COLOR_FG_BLUE, .colorTitle = FF_COLOR_FG_DEFAULT, }, // NurOS { .names = {"NurOS"}, .lines = FASTFETCH_DATATEXT_LOGO_NUROS, .colors = { FF_COLOR_FG_BLUE, FF_COLOR_FG_WHITE, }, }, // Nurunner { .names = {"Nurunner"}, .lines = FASTFETCH_DATATEXT_LOGO_NURUNNER, .colors = { FF_COLOR_FG_BLUE, FF_COLOR_FG_WHITE, }, }, // NuTyX { .names = {"NuTyX"}, .lines = FASTFETCH_DATATEXT_LOGO_NUTYX, .colors = { FF_COLOR_FG_BLUE, FF_COLOR_FG_RED, }, }, // NetHydra { .names = {"NetHydra"}, .lines = FASTFETCH_DATATEXT_LOGO_NETHYDRA, .colors = { FF_COLOR_FG_RED, FF_COLOR_FG_DEFAULT, }, }, // LAST {}, }; static const FFlogo O[] = { // Obarun { .names = {"Obarun"}, .lines = FASTFETCH_DATATEXT_LOGO_OBARUN, .colors = { FF_COLOR_FG_CYAN, FF_COLOR_FG_WHITE, }, }, // OBRevenge { .names = {"OBRevenge"}, .lines = FASTFETCH_DATATEXT_LOGO_OBREVENGE, .colors = { FF_COLOR_FG_RED, FF_COLOR_FG_WHITE, }, }, // ObsidianOS { .names = {"ObsidianOS"}, .lines = FASTFETCH_DATATEXT_LOGO_OBSIDIANOS, .colors = { FF_COLOR_FG_MAGENTA, FF_COLOR_FG_CYAN, FF_COLOR_FG_LIGHT_BLUE, }, }, // OmniOS { .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"}, .lines = FASTFETCH_DATATEXT_LOGO_OPENKYLIN, .colors = { FF_COLOR_FG_GREEN, FF_COLOR_FG_WHITE, }, .colorKeys = FF_COLOR_FG_GREEN, .colorTitle = FF_COLOR_FG_DEFAULT, }, // OpenBSD { .names = {"openbsd"}, .lines = FASTFETCH_DATATEXT_LOGO_OPENBSD, .colors = { FF_COLOR_FG_YELLOW, FF_COLOR_FG_WHITE, FF_COLOR_FG_CYAN, FF_COLOR_FG_RED, FF_COLOR_FG_LIGHT_BLACK, }, .colorKeys = FF_COLOR_FG_YELLOW, .colorTitle = FF_COLOR_FG_DEFAULT, }, // OpenBSDSmall { .names = {"openbsd_small"}, .type = FF_LOGO_LINE_TYPE_SMALL_BIT, .lines = FASTFETCH_DATATEXT_LOGO_OPENBSD_SMALL, .colors = { FF_COLOR_FG_YELLOW, FF_COLOR_FG_WHITE, }, .colorKeys = FF_COLOR_FG_YELLOW, .colorTitle = FF_COLOR_FG_DEFAULT, }, // OpenEuler { .names = {"OpenEuler"}, .lines = FASTFETCH_DATATEXT_LOGO_OPENEULER, .colors = { FF_COLOR_FG_BLUE, FF_COLOR_FG_WHITE, }, }, // OpenIndiana { .names = {"OpenIndiana"}, .lines = FASTFETCH_DATATEXT_LOGO_OPENINDIANA, .colors = { FF_COLOR_FG_BLUE, FF_COLOR_FG_DEFAULT, }, }, // OpenMamba { .names = {"OpenMamba"}, .lines = FASTFETCH_DATATEXT_LOGO_OPENMAMBA, .colors = { FF_COLOR_FG_WHITE, FF_COLOR_FG_GREEN, }, }, // OpenStage { .names = {"OpenStage"}, .lines = FASTFETCH_DATATEXT_LOGO_OPENSTAGE, .colors = { FF_COLOR_FG_GREEN, FF_COLOR_FG_WHITE, }, }, // OpenSuse { .names = {"opensuse"}, .lines = FASTFETCH_DATATEXT_LOGO_OPENSUSE, .colors = { FF_COLOR_FG_GREEN, FF_COLOR_FG_WHITE, }, .colorKeys = FF_COLOR_FG_GREEN, .colorTitle = FF_COLOR_FG_GREEN, }, // OpenSuseSmall { .names = {"opensuse_small"}, .type = FF_LOGO_LINE_TYPE_SMALL_BIT, .lines = FASTFETCH_DATATEXT_LOGO_OPENSUSE_SMALL, .colors = { FF_COLOR_FG_GREEN, }, .colorKeys = FF_COLOR_FG_GREEN, .colorTitle = FF_COLOR_FG_GREEN, }, // openSuseMicroOS { .names = {"opensuse-microos"}, .lines = FASTFETCH_DATATEXT_LOGO_OPENSUSE_MICROOS, .colors = { FF_COLOR_FG_GREEN, }, }, // OpenSuseLeap { .names = {"opensuse-leap"}, .lines = FASTFETCH_DATATEXT_LOGO_OPENSUSE_LEAP, .colors = { FF_COLOR_FG_DEFAULT, }, .colorKeys = FF_COLOR_FG_GREEN, .colorTitle = FF_COLOR_FG_GREEN, }, // OpenSuseLeapOld { .names = {"opensuse-leap_old"}, .lines = FASTFETCH_DATATEXT_LOGO_OPENSUSE_LEAP_OLD, .type = FF_LOGO_LINE_TYPE_ALTER_BIT, .colors = { FF_COLOR_FG_DEFAULT, }, .colorKeys = FF_COLOR_FG_GREEN, .colorTitle = FF_COLOR_FG_GREEN, }, // OpenSuseTumbleweed { .names = {"opensuse-tumbleweed"}, .lines = FASTFETCH_DATATEXT_LOGO_OPENSUSE_TUMBLEWEED, .colors = { FF_COLOR_FG_DEFAULT, }, .colorKeys = FF_COLOR_FG_GREEN, .colorTitle = FF_COLOR_FG_GREEN, }, // OpenSuseTumbleweedSmall { .names = {"opensuse-tumbleweed_small"}, .lines = FASTFETCH_DATATEXT_LOGO_OPENSUSE_TUMBLEWEED_SMALL, .type = FF_LOGO_LINE_TYPE_SMALL_BIT, .colors = { FF_COLOR_FG_DEFAULT, }, .colorKeys = FF_COLOR_FG_GREEN, .colorTitle = FF_COLOR_FG_GREEN, }, // OpenSuseTumbleweedOld { .names = {"opensuse-tumbleweed_old"}, .lines = FASTFETCH_DATATEXT_LOGO_OPENSUSE_TUMBLEWEED_OLD, .type = FF_LOGO_LINE_TYPE_ALTER_BIT, .colors = { FF_COLOR_FG_DEFAULT, }, .colorKeys = FF_COLOR_FG_GREEN, .colorTitle = FF_COLOR_FG_GREEN, }, // OpenSuseTumbleweed2 { .names = {"opensuse-tumbleweed2"}, .lines = FASTFETCH_DATATEXT_LOGO_OPENSUSE_TUMBLEWEED2, .type = FF_LOGO_LINE_TYPE_ALTER_BIT, .colors = { FF_COLOR_FG_DEFAULT, }, .colorKeys = FF_COLOR_FG_GREEN, .colorTitle = FF_COLOR_FG_GREEN, }, // openSUSESlowroll { .names = {"opensuse-slowroll", "opensuse-tumbleweed-slowroll"}, .lines = FASTFETCH_DATATEXT_LOGO_OPENSUSE_SLOWROLL, .colors = { FF_COLOR_FG_DEFAULT, }, .colorKeys = FF_COLOR_FG_GREEN, .colorTitle = FF_COLOR_FG_GREEN, }, // OpenMandriva { .names = {"openmandriva", "open-mandriva", "open_mandriva", "openmandriva lx"}, .lines = FASTFETCH_DATATEXT_LOGO_OPENMANDRIVA, .colors = { FF_COLOR_FG_BLUE, }, .colorKeys = FF_COLOR_FG_BLUE, .colorTitle = FF_COLOR_FG_BLUE, }, // OpenWrt { .names = {"openwrt"}, .lines = FASTFETCH_DATATEXT_LOGO_OPENWRT, .colors = { FF_COLOR_FG_BLUE, }, .colorKeys = FF_COLOR_FG_BLUE, .colorTitle = FF_COLOR_FG_DEFAULT, }, // OPNsense { .names = {"OPNsense"}, .lines = FASTFETCH_DATATEXT_LOGO_OPNSENSE, .colors = { FF_COLOR_FG_WHITE, FF_COLOR_FG_256 "202", }, }, // Oracle { .names = {"oracle", "oracle linux", "oracle linux server"}, .lines = FASTFETCH_DATATEXT_LOGO_ORACLE, .colors = { FF_COLOR_FG_RED, }, .colorKeys = FF_COLOR_FG_RED, .colorTitle = FF_COLOR_FG_DEFAULT, }, // Orchid { .names = {"orchid"}, .lines = FASTFETCH_DATATEXT_LOGO_ORCHID, .colors = { FF_COLOR_FG_WHITE, FF_COLOR_FG_MAGENTA, FF_COLOR_FG_MAGENTA, }, .colorKeys = FF_COLOR_FG_DEFAULT, .colorTitle = FF_COLOR_FG_MAGENTA, }, // OrchidSmall { .names = {"orchid_small"}, .type = FF_LOGO_LINE_TYPE_SMALL_BIT, .lines = FASTFETCH_DATATEXT_LOGO_ORCHID_SMALL, .colors = { FF_COLOR_FG_WHITE, FF_COLOR_FG_MAGENTA, FF_COLOR_FG_MAGENTA, }, .colorKeys = FF_COLOR_FG_DEFAULT, .colorTitle = FF_COLOR_FG_MAGENTA, }, // Oreon { .names = {"Oreon"}, .lines = FASTFETCH_DATATEXT_LOGO_OREON, .colors = { FF_COLOR_FG_DEFAULT, FF_COLOR_FG_DEFAULT, }, }, // OS/2 Warp { .names = {"OS2Warp"}, .lines = FASTFETCH_DATATEXT_LOGO_OS2WARP, .colors = { FF_COLOR_FG_LIGHT_WHITE, FF_COLOR_FG_LIGHT_BLUE, } }, // OS_Elbrus { .names = {"OS Elbrus"}, .lines = FASTFETCH_DATATEXT_LOGO_OS_ELBRUS, .colors = { FF_COLOR_FG_BLUE, FF_COLOR_FG_WHITE, }, }, // OSMC { .names = {"OSMC", "Open Source Media Center"}, .lines = FASTFETCH_DATATEXT_LOGO_OSMC, .colors = { FF_COLOR_FG_BLUE, FF_COLOR_FG_WHITE, }, }, // OSX { .names = {"OSX"}, .lines = FASTFETCH_DATATEXT_LOGO_MACOS, .colors = { FF_COLOR_FG_GREEN, FF_COLOR_FG_YELLOW, FF_COLOR_FG_LIGHT_RED, FF_COLOR_FG_RED, FF_COLOR_FG_MAGENTA, FF_COLOR_FG_BLUE, }, .colorKeys = FF_COLOR_FG_YELLOW, .colorTitle = FF_COLOR_FG_GREEN, }, // OSXSmall { .names = {"OSX_small"}, .type = FF_LOGO_LINE_TYPE_SMALL_BIT, .lines = FASTFETCH_DATATEXT_LOGO_MACOS_SMALL, .colors = { FF_COLOR_FG_GREEN, FF_COLOR_FG_YELLOW, FF_COLOR_FG_RED, FF_COLOR_FG_MAGENTA, FF_COLOR_FG_BLUE, }, .colorKeys = FF_COLOR_FG_YELLOW, .colorTitle = FF_COLOR_FG_GREEN, }, // LAST {}, }; static const FFlogo P[] = { // PacBSD { .names = {"PacBSD"}, .lines = FASTFETCH_DATATEXT_LOGO_PACBSD, .colors = { FF_COLOR_FG_RED, FF_COLOR_FG_WHITE, }, }, // Panwah { .names = {"Panwah"}, .lines = FASTFETCH_DATATEXT_LOGO_PANWAH, .colors = { FF_COLOR_FG_WHITE, FF_COLOR_FG_RED, FF_COLOR_FG_BLACK, }, }, // Parabola { .names = {"parabola", "parabola-gnulinux"}, .lines = FASTFETCH_DATATEXT_LOGO_PARABOLA, .colors = { FF_COLOR_FG_MAGENTA, }, .colorKeys = FF_COLOR_FG_MAGENTA, .colorTitle = FF_COLOR_FG_MAGENTA, }, // ParabolaSmall { .names = {"parabola_small", "parabola-gnulinux_small"}, .type = FF_LOGO_LINE_TYPE_SMALL_BIT, .lines = FASTFETCH_DATATEXT_LOGO_PARABOLA_SMALL, .colors = { FF_COLOR_FG_MAGENTA, }, .colorKeys = FF_COLOR_FG_MAGENTA, .colorTitle = FF_COLOR_FG_MAGENTA, }, // Parch { .names = {"Parch"}, .lines = FASTFETCH_DATATEXT_LOGO_PARCH, .colors = { FF_COLOR_FG_BLUE, FF_COLOR_FG_WHITE, FF_COLOR_FG_RED, }, }, // Pardus { .names = {"Pardus"}, .lines = FASTFETCH_DATATEXT_LOGO_PARDUS, .colors = { FF_COLOR_FG_BLUE, FF_COLOR_FG_CYAN, }, }, // Parrot { .names = {"Parrot"}, .lines = FASTFETCH_DATATEXT_LOGO_PARROT, .colors = { FF_COLOR_FG_CYAN, FF_COLOR_FG_WHITE, }, }, // Parsix { .names = {"Parsix"}, .lines = FASTFETCH_DATATEXT_LOGO_PARSIX, .colors = { FF_COLOR_FG_YELLOW, FF_COLOR_FG_RED, FF_COLOR_FG_WHITE, FF_COLOR_FG_LIGHT_BLACK, }, }, // PCBSD { .names = {"PCBSD", "TrueOS"}, .lines = FASTFETCH_DATATEXT_LOGO_PCBSD, .colors = { FF_COLOR_FG_RED, FF_COLOR_FG_WHITE, }, }, // PCLinuxOS { .names = {"PCLinuxOS"}, .lines = FASTFETCH_DATATEXT_LOGO_PCLINUXOS, .colors = { FF_COLOR_FG_BLUE, FF_COLOR_FG_WHITE, }, }, // PearOS { .names = {"PearOS"}, .lines = FASTFETCH_DATATEXT_LOGO_PEAROS, .colors = { FF_COLOR_FG_GREEN, FF_COLOR_FG_YELLOW, FF_COLOR_FG_RED, FF_COLOR_FG_MAGENTA, FF_COLOR_FG_BLUE, }, }, // Pengwin { .names = {"Pengwin"}, .lines = FASTFETCH_DATATEXT_LOGO_PENGWIN, .colors = { FF_COLOR_FG_MAGENTA, FF_COLOR_FG_LIGHT_MAGENTA, FF_COLOR_FG_MAGENTA, }, }, // Pentoo { .names = {"Pentoo"}, .lines = FASTFETCH_DATATEXT_LOGO_PENTOO, .colors = { FF_COLOR_FG_MAGENTA, FF_COLOR_FG_WHITE, }, }, // Peppermint { .names = {"Peppermint"}, .lines = FASTFETCH_DATATEXT_LOGO_PEPPERMINT, .colors = { FF_COLOR_FG_RED, FF_COLOR_FG_WHITE, }, }, // Peropesis { .names = {"Peropesis", "Peropesis Linux"}, .lines = FASTFETCH_DATATEXT_LOGO_PEROPESIS, .colors = { FF_COLOR_FG_WHITE }, }, // PhyOS { .names = {"PhyOS"}, .lines = FASTFETCH_DATATEXT_LOGO_PHYOS, .colors = { FF_COLOR_FG_BLUE, FF_COLOR_FG_WHITE, }, }, // PikaOS { .names = {"PikaOS"}, .lines = FASTFETCH_DATATEXT_LOGO_PIKAOS, .colors = { FF_COLOR_FG_YELLOW, }, }, // PisiLinux { .names = {"PisiLinux"}, .lines = FASTFETCH_DATATEXT_LOGO_PISI, .colors = { FF_COLOR_FG_BLUE, FF_COLOR_FG_WHITE, }, }, // PNMLinux { .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" }, }, // Pop { .names = {"pop", "popos"}, .lines = FASTFETCH_DATATEXT_LOGO_POP, .colors = { FF_COLOR_FG_CYAN, FF_COLOR_FG_WHITE, }, .colorKeys = FF_COLOR_FG_CYAN, .colorTitle = FF_COLOR_FG_CYAN, }, // PopSmall { .names = {"pop_small", "popos_small"}, .type = FF_LOGO_LINE_TYPE_SMALL_BIT, .lines = FASTFETCH_DATATEXT_LOGO_POP_SMALL, .colors = { FF_COLOR_FG_CYAN, }, .colorKeys = FF_COLOR_FG_CYAN, .colorTitle = FF_COLOR_FG_CYAN, }, // Porteus { .names = {"Porteus"}, .lines = FASTFETCH_DATATEXT_LOGO_PORTEUS, .colors = { FF_COLOR_FG_CYAN, FF_COLOR_FG_WHITE, }, }, // PostMarketOS { .names = {"PostMarketOS"}, .lines = FASTFETCH_DATATEXT_LOGO_POSTMARKETOS, .colors = { FF_COLOR_FG_GREEN, FF_COLOR_FG_WHITE, }, }, // PostMarketOSSmall { .names = {"PostMarketOS_small"}, .type = FF_LOGO_LINE_TYPE_SMALL_BIT, .lines = FASTFETCH_DATATEXT_LOGO_POSTMARKETOS_SMALL, .colors = { FF_COLOR_FG_GREEN, FF_COLOR_FG_WHITE, }, }, // Proxmox { .names = {"Proxmox", "pve"}, .lines = FASTFETCH_DATATEXT_LOGO_PROXMOX, .colors = { FF_COLOR_FG_WHITE, FF_COLOR_FG_256 "202" }, .colorKeys = FF_COLOR_FG_DEFAULT, .colorTitle = FF_COLOR_FG_256 "202", }, // PuffOS { .names = {"PuffOS"}, .lines = FASTFETCH_DATATEXT_LOGO_PUFFOS, .colors = { FF_COLOR_FG_YELLOW, FF_COLOR_FG_WHITE, }, }, // Puppy { .names = {"Puppy"}, .lines = FASTFETCH_DATATEXT_LOGO_PUPPY, .colors = { FF_COLOR_FG_BLUE, FF_COLOR_FG_WHITE, }, }, // PureOS { .names = {"PureOS"}, .lines = FASTFETCH_DATATEXT_LOGO_PUREOS, .colors = { FF_COLOR_FG_GREEN, FF_COLOR_FG_WHITE, }, }, // PureOSSmall { .names = {"PureOS_small"}, .type = FF_LOGO_LINE_TYPE_SMALL_BIT, .lines = FASTFETCH_DATATEXT_LOGO_PUREOS_SMALL, .colors = { FF_COLOR_FG_GREEN, FF_COLOR_FG_WHITE, }, }, // PrismLinux { .names = {"PrismLinux"}, .lines = FASTFETCH_DATATEXT_LOGO_PRISMLINUX, .colors = { FF_COLOR_FG_BLUE, FF_COLOR_FG_BLUE, }, }, // PrismLinuxSmall { .names = {"PrismLinux_small"}, .type = FF_LOGO_LINE_TYPE_SMALL_BIT, .lines = FASTFETCH_DATATEXT_LOGO_PRISMLINUX_SMALL, .colors = { FF_COLOR_FG_BLUE, FF_COLOR_FG_BLUE, }, }, // LAST {}, }; static const FFlogo Q[] = { // QTS { .names = {"qts"}, .lines = FASTFETCH_DATATEXT_LOGO_QTS, .colors = { FF_COLOR_FG_BLUE, FF_COLOR_FG_RED, }, .colorKeys = FF_COLOR_FG_BLUE, .colorTitle = FF_COLOR_FG_BLUE, }, // Q4OS { .names = {"Q4OS"}, .lines = FASTFETCH_DATATEXT_LOGO_Q4OS, .colors = { FF_COLOR_FG_BLUE, FF_COLOR_FG_RED, }, }, // Qubes { .names = {"Qubes"}, .lines = FASTFETCH_DATATEXT_LOGO_QUBES, .colors = { FF_COLOR_FG_BLUE, FF_COLOR_FG_MAGENTA, }, }, // Qubyt { .names = {"Qubyt"}, .lines = FASTFETCH_DATATEXT_LOGO_QUBYT, .colors = { FF_COLOR_FG_BLUE, FF_COLOR_FG_MAGENTA, FF_COLOR_FG_BLACK, }, }, // Quibian { .names = {"Quibian"}, .lines = FASTFETCH_DATATEXT_LOGO_QUIBIAN, .colors = { FF_COLOR_FG_YELLOW, FF_COLOR_FG_WHITE, }, }, // Quirinux { .names = {"Quirinux"}, .lines = FASTFETCH_DATATEXT_LOGO_QUIRINUX, .colors = { FF_COLOR_FG_WHITE, FF_COLOR_FG_MAGENTA, }, }, // LAST {}, }; static const FFlogo R[] = { // Radix { .names = {"Radix"}, .lines = FASTFETCH_DATATEXT_LOGO_RADIX, .colors = { FF_COLOR_FG_GREEN, FF_COLOR_FG_RED, }, }, // Raspbian { .names = {"raspbian"}, .lines = FASTFETCH_DATATEXT_LOGO_RASPBIAN, .colors = { FF_COLOR_FG_RED, FF_COLOR_FG_GREEN, }, .colorKeys = FF_COLOR_FG_RED, .colorTitle = FF_COLOR_FG_GREEN, }, // RaspbianSmall { .names = {"raspbian_small"}, .type = FF_LOGO_LINE_TYPE_SMALL_BIT, .lines = FASTFETCH_DATATEXT_LOGO_RASPBIAN_SMALL, .colors = { FF_COLOR_FG_RED, FF_COLOR_FG_GREEN, }, .colorKeys = FF_COLOR_FG_RED, .colorTitle = FF_COLOR_FG_GREEN, }, // RavynOS { .names = {"RavynOS"}, .lines = FASTFETCH_DATATEXT_LOGO_RAVYNOS, .colors = { FF_COLOR_FG_256 "15", FF_COLOR_FG_WHITE, }, }, // RebornOS { .names = {"RebornOS"}, .lines = FASTFETCH_DATATEXT_LOGO_REBORNOS, .colors = { FF_COLOR_FG_BLACK, FF_COLOR_FG_BLUE, FF_COLOR_FG_CYAN, }, .colorKeys = FF_COLOR_FG_BLUE, .colorTitle = FF_COLOR_FG_BLUE, }, // RebornSmall { .names = {"RebornOS_small"}, .type = FF_LOGO_LINE_TYPE_SMALL_BIT, .lines = FASTFETCH_DATATEXT_LOGO_REBORNOS_SMALL, .colors = { FF_COLOR_FG_BLUE, }, .colorKeys = FF_COLOR_FG_BLUE, .colorTitle = FF_COLOR_FG_BLUE, }, // RedCore { .names = {"RedCore"}, .lines = FASTFETCH_DATATEXT_LOGO_REDCORE, .colors = { FF_COLOR_FG_RED, FF_COLOR_FG_WHITE, }, }, // RedHatEnterpriseLinux { .names = {"rhel", "redhat"}, .lines = FASTFETCH_DATATEXT_LOGO_RHEL, .colors = { FF_COLOR_FG_RED, }, .colorKeys = FF_COLOR_FG_RED, .colorTitle = FF_COLOR_FG_RED, }, // RedHatEnterpriseLinux { .names = {"rhel_small", "redhat_small"}, .type = FF_LOGO_LINE_TYPE_SMALL_BIT, .lines = FASTFETCH_DATATEXT_LOGO_RHEL_SMALL, .colors = { FF_COLOR_FG_RED, }, .colorKeys = FF_COLOR_FG_RED, .colorTitle = FF_COLOR_FG_RED, }, // RedHatEnterpriseLinux_old { .names = {"rhel_old", "redhat_old"}, .type = FF_LOGO_LINE_TYPE_ALTER_BIT, .lines = FASTFETCH_DATATEXT_LOGO_RHEL_OLD, .colors = { FF_COLOR_FG_RED, FF_COLOR_FG_WHITE, }, }, // RedOS { .names = {"RedOS"}, .lines = FASTFETCH_DATATEXT_LOGO_REDOS, .colors = { FF_COLOR_FG_RED, FF_COLOR_FG_WHITE, }, .colorTitle = FF_COLOR_FG_RED, .colorKeys = FF_COLOR_FG_RED, }, // RedOS small { .names = {"RedOS_small"}, .type = FF_LOGO_LINE_TYPE_SMALL_BIT, .lines = FASTFETCH_DATATEXT_LOGO_REDOS_SMALL, .colors = { FF_COLOR_FG_RED, FF_COLOR_FG_WHITE, }, .colorTitle = FF_COLOR_FG_RED, .colorKeys = FF_COLOR_FG_RED, }, // RedstarOS { .names = {"redstar", "redstar-os", "redstaros"}, .lines = FASTFETCH_DATATEXT_LOGO_REDSTAR, .colors = { FF_COLOR_FG_RED, }, .colorKeys = FF_COLOR_FG_RED, .colorTitle = FF_COLOR_FG_RED, }, // Refracta { .names = {"Refracta"}, .lines = FASTFETCH_DATATEXT_LOGO_REFRACTA, .colors = { FF_COLOR_FG_WHITE, FF_COLOR_FG_LIGHT_BLACK, }, .colorKeys = FF_COLOR_FG_LIGHT_BLACK, .colorTitle = FF_COLOR_FG_DEFAULT, }, // Regata { .names = {"Regata"}, .lines = FASTFETCH_DATATEXT_LOGO_REGATA, .colors = { FF_COLOR_FG_WHITE, FF_COLOR_FG_RED, FF_COLOR_FG_BLUE, FF_COLOR_FG_MAGENTA, FF_COLOR_FG_YELLOW, FF_COLOR_FG_GREEN, }, }, // Regolith { .names = {"Regolith"}, .lines = FASTFETCH_DATATEXT_LOGO_REGOLITH, .colors = { FF_COLOR_FG_RED, FF_COLOR_FG_WHITE, }, }, // RhaymOS { .names = {"RhaymOS"}, .lines = FASTFETCH_DATATEXT_LOGO_RHAYMOS, .colors = { FF_COLOR_FG_RED, FF_COLOR_FG_WHITE, }, }, // RockyLinux { .names = {"rocky"}, .lines = FASTFETCH_DATATEXT_LOGO_ROCKY, .colors = { FF_COLOR_FG_GREEN, }, .colorKeys = FF_COLOR_FG_GREEN, .colorTitle = FF_COLOR_FG_GREEN, }, // RockyLinuxSmall { .names = {"rocky_small"}, .type = FF_LOGO_LINE_TYPE_SMALL_BIT, .lines = FASTFETCH_DATATEXT_LOGO_ROCKY_SMALL, .colors = { FF_COLOR_FG_GREEN, }, .colorKeys = FF_COLOR_FG_GREEN, .colorTitle = FF_COLOR_FG_GREEN, }, // RosaLinux { .names = {"rosa", "rosa-linux", "rosalinux"}, .lines = FASTFETCH_DATATEXT_LOGO_ROSA, .colors = { FF_COLOR_FG_RGB "250;250;250", FF_COLOR_FG_RGB "100;165;225", }, .colorKeys = FF_COLOR_FG_RGB "100;165;225", .colorTitle = FF_COLOR_FG_RGB "100;165;225", }, // RhinoLinux { .names = {"Rhino Linux"}, .lines = FASTFETCH_DATATEXT_LOGO_RHINO, .colors = { FF_COLOR_FG_MAGENTA, FF_COLOR_FG_LIGHT_BLUE, FF_COLOR_FG_LIGHT_MAGENTA, FF_COLOR_FG_MAGENTA, }, .colorKeys = FF_COLOR_FG_MAGENTA, .colorTitle = FF_COLOR_FG_MAGENTA, }, { .names = {"RengeOS"}, .lines = FASTFETCH_DATATEXT_LOGO_RENGEOS, .colors = { FF_COLOR_FG_MAGENTA, FF_COLOR_FG_MAGENTA, }, }, // LAST {}, }; static const FFlogo S[] = { // Sabayon { .names = {"Sabayon"}, .lines = FASTFETCH_DATATEXT_LOGO_SABAYON, .colors = { FF_COLOR_FG_BLUE, FF_COLOR_FG_WHITE, }, }, // Sabotage { .names = {"Sabotage"}, .lines = FASTFETCH_DATATEXT_LOGO_SABOTAGE, .colors = { FF_COLOR_FG_DEFAULT, }, .colorKeys = FF_COLOR_FG_BLUE, .colorTitle = FF_COLOR_FG_DEFAULT, }, // Sailfish { .names = {"Sailfish"}, .lines = FASTFETCH_DATATEXT_LOGO_SAILFISH, .colors = { FF_COLOR_FG_BLUE, FF_COLOR_FG_MAGENTA, }, }, // SalentOS { .names = {"SalentOS"}, .lines = FASTFETCH_DATATEXT_LOGO_SALENTOS, .colors = { FF_COLOR_FG_GREEN, FF_COLOR_FG_RED, FF_COLOR_FG_YELLOW, FF_COLOR_FG_WHITE, }, }, // SalientOS { .names = {"Salient OS", "SalientOS"}, .lines = FASTFETCH_DATATEXT_LOGO_SALIENTOS, .colors = { FF_COLOR_FG_CYAN, FF_COLOR_FG_WHITE, }, }, // Salix { .names = {"Salix"}, .lines = FASTFETCH_DATATEXT_LOGO_SALIX, .colors = { FF_COLOR_FG_GREEN, FF_COLOR_FG_GREEN, }, }, // SambaBOX { .names = {"SambaBOX", "Profelis SambaBOX"}, .lines = FASTFETCH_DATATEXT_LOGO_SAMBABOX, .colors = { FF_COLOR_FG_YELLOW, FF_COLOR_FG_CYAN, }, }, // Sasanqua { .names = {"Sasanqua"}, .lines = FASTFETCH_DATATEXT_LOGO_SASANQUA, .colors = { FF_COLOR_FG_MAGENTA, FF_COLOR_FG_RED, }, }, // Scientific { .names = {"Scientific"}, .lines = FASTFETCH_DATATEXT_LOGO_SCIENTIFIC, .colors = { FF_COLOR_FG_BLUE, FF_COLOR_FG_WHITE, FF_COLOR_FG_RED, }, }, // Secureblue { .names = {"secureblue"}, .lines = FASTFETCH_DATATEXT_LOGO_SECUREBLUE, .colors = { FF_COLOR_FG_BLUE, FF_COLOR_FG_MAGENTA, FF_COLOR_FG_DEFAULT, }, }, // Serpent OS { .names = {"Serpent OS"}, .lines = FASTFETCH_DATATEXT_LOGO_SERPENT_OS, .colors = { FF_COLOR_FG_DEFAULT, }, }, // Semc { .names = {"semc"}, .lines = FASTFETCH_DATATEXT_LOGO_SEMC, .colors = { FF_COLOR_FG_GREEN, FF_COLOR_FG_LIGHT_BLACK, FF_COLOR_FG_RED, }, }, // Septor { .names = {"Septor"}, .lines = FASTFETCH_DATATEXT_LOGO_SEPTOR, .colors = { FF_COLOR_FG_BLUE, FF_COLOR_FG_WHITE, FF_COLOR_FG_BLUE, }, }, // Serene { .names = {"Serene"}, .lines = FASTFETCH_DATATEXT_LOGO_SERENE, .colors = { FF_COLOR_FG_CYAN, FF_COLOR_FG_CYAN, }, }, // SharkLinux { .names = {"SharkLinux"}, .lines = FASTFETCH_DATATEXT_LOGO_SHARKLINUX, .colors = { FF_COLOR_FG_BLUE, FF_COLOR_FG_WHITE, }, }, // ShastraOS { .names = {"ShastraOS"}, .lines = FASTFETCH_DATATEXT_LOGO_SHASTRAOS, .colors = { FF_COLOR_FG_CYAN, FF_COLOR_FG_WHITE, }, }, // Shebang { .names = {"Shebang"}, .lines = FASTFETCH_DATATEXT_LOGO_SHEBANG, .colors = { FF_COLOR_FG_WHITE, FF_COLOR_FG_WHITE, }, }, // Siduction { .names = {"Siduction"}, .lines = FASTFETCH_DATATEXT_LOGO_SIDUCTION, .colors = { FF_COLOR_FG_BLUE, FF_COLOR_FG_WHITE, } }, // SkiffOS { .names = {"SkiffOS"}, .lines = FASTFETCH_DATATEXT_LOGO_SKIFFOS, .colors = { FF_COLOR_FG_BLUE, FF_COLOR_FG_WHITE, }, }, // SleeperOS { .names = {"SleeperOS"}, .lines = FASTFETCH_DATATEXT_LOGO_SLEEPEROS, .colors = { FF_COLOR_FG_CYAN, FF_COLOR_FG_WHITE, } }, // SleeperOS { .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"}, .lines = FASTFETCH_DATATEXT_LOGO_SLITAZ, .colors = { FF_COLOR_FG_YELLOW, FF_COLOR_FG_YELLOW, }, }, // SpoinkOS { .names = {"SpoinkOS", "spoink-os"}, .lines = FASTFETCH_DATATEXT_LOGO_SPOINKOS, .colors = { FF_COLOR_FG_GREEN, }, }, // Slackel { .names = {"Slackel"}, .lines = FASTFETCH_DATATEXT_LOGO_SLACKEL, .colors = { FF_COLOR_FG_YELLOW, FF_COLOR_FG_YELLOW, }, }, // Slackware { .names = {"Slackware"}, .lines = FASTFETCH_DATATEXT_LOGO_SLACKWARE, .colors = { FF_COLOR_FG_BLUE, FF_COLOR_FG_WHITE, }, .colorKeys = FF_COLOR_FG_BLUE, .colorTitle = FF_COLOR_FG_BLUE, }, // SlackwareSmall { .names = {"Slackware_small"}, .type = FF_LOGO_LINE_TYPE_SMALL_BIT, .lines = FASTFETCH_DATATEXT_LOGO_SLACKWARE_SMALL, .colors = { FF_COLOR_FG_BLUE, FF_COLOR_FG_WHITE, }, .colorKeys = FF_COLOR_FG_BLUE, .colorTitle = FF_COLOR_FG_DEFAULT, }, // SmartOS { .names = {"SmartOS"}, .lines = FASTFETCH_DATATEXT_LOGO_SMARTOS, .colors = { FF_COLOR_FG_CYAN, FF_COLOR_FG_WHITE, }, }, // SnigdhaOS { .names = {"SnigdhaOS", "Snigdha"}, .lines = FASTFETCH_DATATEXT_LOGO_SNIGDHAOS, .colors = { FF_COLOR_FG_CYAN, FF_COLOR_FG_WHITE, }, }, // Soda { .names = {"Soda"}, .lines = FASTFETCH_DATATEXT_LOGO_SODA, .colors = { FF_COLOR_FG_RED, FF_COLOR_FG_WHITE, }, }, // Source Mage { .names = {"Source Mage", "Source Mage GNU/Linux", "source_mage", "sourcemage"}, .lines = FASTFETCH_DATATEXT_LOGO_SOURCE_MAGE, .colors = { FF_COLOR_FG_DEFAULT, }, .colorKeys = FF_COLOR_FG_RED, .colorTitle = FF_COLOR_FG_DEFAULT, }, // Solaris { .names = {"solaris", "sunos"}, .lines = FASTFETCH_DATATEXT_LOGO_SOLARIS, .colors = { FF_COLOR_FG_YELLOW, }, .colorKeys = FF_COLOR_FG_YELLOW, .colorTitle = FF_COLOR_FG_DEFAULT, }, // SolarisSmall { .names = {"solaris_small", "sunos_small"}, .type = FF_LOGO_LINE_TYPE_SMALL_BIT, .lines = FASTFETCH_DATATEXT_LOGO_SOLARIS_SMALL, .colors = { FF_COLOR_FG_YELLOW, }, .colorKeys = FF_COLOR_FG_YELLOW, .colorTitle = FF_COLOR_FG_DEFAULT, }, // Solus { .names = {"Solus", "solus-linux"}, .lines = FASTFETCH_DATATEXT_LOGO_SOLUS, .colors = { FF_COLOR_FG_BLUE, FF_COLOR_FG_WHITE, }, .colorKeys = FF_COLOR_FG_BLUE, .colorTitle = FF_COLOR_FG_DEFAULT, }, // Sparky { .names = {"Sparky"}, .lines = FASTFETCH_DATATEXT_LOGO_SPARKY, .colors = { FF_COLOR_FG_RED, FF_COLOR_FG_WHITE }, }, // Star { .names = {"Star"}, .lines = FASTFETCH_DATATEXT_LOGO_STAR, .colors = { FF_COLOR_FG_WHITE, FF_COLOR_FG_WHITE, }, }, // StockLinux { .names = {"Stock Linux"}, .lines = FASTFETCH_DATATEXT_LOGO_STOCK_LINUX, .colors = { FF_COLOR_FG_BLUE, FF_COLOR_FG_WHITE, }, }, // SteamOS { .names = {"SteamOS"}, .lines = FASTFETCH_DATATEXT_LOGO_STEAMOS, .colors = { FF_COLOR_FG_BLUE, FF_COLOR_FG_WHITE, }, .colorKeys = FF_COLOR_FG_BLUE, .colorTitle = FF_COLOR_FG_BLUE, }, // Steam Deck { .names = {"SteamDeck"}, .lines = FASTFETCH_DATATEXT_LOGO_STEAMDECK, .colors = { FF_COLOR_FG_BLUE, FF_COLOR_FG_WHITE }, .colorKeys = FF_COLOR_FG_BLUE, .colorTitle = FF_COLOR_FG_BLUE, }, // Steam Deck Small { .names = {"SteamDeck_small"}, .lines = FASTFETCH_DATATEXT_LOGO_STEAMDECK_SMALL, .colors = { FF_COLOR_FG_BLUE, FF_COLOR_FG_WHITE }, .colorKeys = FF_COLOR_FG_BLUE, .colorTitle = FF_COLOR_FG_BLUE, }, // Steam Deck OLED { .names = {"SteamDeckOled"}, .lines = FASTFETCH_DATATEXT_LOGO_STEAMDECK, .colors = { FF_COLOR_FG_RED, FF_COLOR_FG_WHITE }, .colorKeys = FF_COLOR_FG_RED, .colorTitle = FF_COLOR_FG_RED, }, // Sulin { .names = {"Sulin"}, .lines = FASTFETCH_DATATEXT_LOGO_SULIN, .colors = { FF_COLOR_FG_BLUE, FF_COLOR_FG_WHITE, }, }, // SummitOS { .names = {"SummitOS"}, .lines = FASTFETCH_DATATEXT_LOGO_SUMMITOS, .colors = { FF_COLOR_FG_RGB "143;191;80", FF_COLOR_FG_RGB "160;205;102", FF_COLOR_FG_RGB "181;225;102", }, }, // Suse { .names = {"suse", "suse-linux"}, .lines = FASTFETCH_DATATEXT_LOGO_SUSE, .colors = { FF_COLOR_FG_GREEN, FF_COLOR_FG_GREEN, }, }, // SuseSmall { .names = {"suse_small", "suse-linux_small"}, .type = FF_LOGO_LINE_TYPE_SMALL_BIT, .lines = FASTFETCH_DATATEXT_LOGO_OPENSUSE_SMALL, .colors = { FF_COLOR_FG_GREEN, FF_COLOR_FG_GREEN, }, }, // Swagarch { .names = {"Swagarch"}, .lines = FASTFETCH_DATATEXT_LOGO_SWAGARCH, .colors = { FF_COLOR_FG_BLUE, FF_COLOR_FG_WHITE, }, }, // LAST {}, }; static const FFlogo T[] = { // T2 { .names = {"T2", "T2 SDE", "T2/Linux"}, .lines = FASTFETCH_DATATEXT_LOGO_T2, .colors = { FF_COLOR_FG_RED, FF_COLOR_FG_WHITE, FF_COLOR_FG_YELLOW, FF_COLOR_FG_BLUE, }, }, // T2Small { .names = {"T2_small", "T2 SDE_small", "T2/Linux_small"}, .type = FF_LOGO_LINE_TYPE_SMALL_BIT, .lines = FASTFETCH_DATATEXT_LOGO_T2_SMALL, .colors = { FF_COLOR_FG_WHITE, FF_COLOR_FG_BLUE, }, }, // Tails { .names = {"Tails"}, .lines = FASTFETCH_DATATEXT_LOGO_TAILS, .colors = { FF_COLOR_FG_BLUE, FF_COLOR_FG_WHITE, }, }, // Tatra { .names = {"Tatra"}, .lines = FASTFETCH_DATATEXT_LOGO_TATRA, .colors = { FF_COLOR_FG_BLUE, FF_COLOR_FG_GREEN, }, }, // TeArch { .names = {"TeArch"}, .lines = FASTFETCH_DATATEXT_LOGO_TEARCH, .colors = { FF_COLOR_FG_256 "39", FF_COLOR_FG_WHITE, }, }, // TempleOS { .names = {"TempleOS"}, .lines = FASTFETCH_DATATEXT_LOGO_TEMPLEOS, .colors = { FF_COLOR_FG_YELLOW, FF_COLOR_FG_RED, FF_COLOR_FG_CYAN, }, }, // TileOS { .names = {"TileOS"}, .lines = FASTFETCH_DATATEXT_LOGO_TILEOS, .colors = { FF_COLOR_FG_MAGENTA, FF_COLOR_FG_BLUE, FF_COLOR_FG_GREEN, }, }, // Torizon OS { .names = {"Torizon OS", "TorizonCore"}, .lines = FASTFETCH_DATATEXT_LOGO_TORIZONCORE, .colors = { FF_COLOR_FG_LIGHT_WHITE, FF_COLOR_FG_YELLOW, FF_COLOR_FG_BLUE }, }, // Trisquel { .names = {"Trisquel"}, .lines = FASTFETCH_DATATEXT_LOGO_TRISQUEL, .colors = { FF_COLOR_FG_BLUE, FF_COLOR_FG_CYAN, }, }, // TrueNAS Scale { .names = {"TrueNAS-Scale"}, .lines = FASTFETCH_DATATEXT_LOGO_TRUENAS_SCALE, .colors = { FF_COLOR_FG_256 "39", FF_COLOR_FG_256 "32", FF_COLOR_FG_256 "248", }, .colorKeys = FF_COLOR_FG_256 "248", .colorTitle = FF_COLOR_FG_256 "32", }, // TuxedoOS { .names = {"Tuxedo OS", "tuxedo"}, .lines = FASTFETCH_DATATEXT_LOGO_TUXEDO_OS, .colors = { FF_COLOR_FG_WHITE, FF_COLOR_FG_RED, }, }, // Twister { .names = {"Twister"}, .lines = FASTFETCH_DATATEXT_LOGO_TWISTER, .colors = { FF_COLOR_FG_GREEN, FF_COLOR_FG_RED, FF_COLOR_FG_BLUE, FF_COLOR_FG_MAGENTA, FF_COLOR_FG_WHITE, }, }, // LAST {}, }; static const FFlogo U[] = { // UBLinux { .names = {"UBLinux"}, .lines = FASTFETCH_DATATEXT_LOGO_UBLINUX, .colors = { FF_COLOR_FG_256 "38", FF_COLOR_FG_WHITE, FF_COLOR_FG_LIGHT_BLACK, }, .colorKeys = FF_COLOR_FG_256 "38", .colorTitle = FF_COLOR_FG_DEFAULT, }, // UBLinuxSmall { .names = {"UBLinux_small"}, .type = FF_LOGO_LINE_TYPE_SMALL_BIT, .lines = FASTFETCH_DATATEXT_LOGO_UBLINUX_SMALL, .colors = { FF_COLOR_FG_256 "38", FF_COLOR_FG_WHITE, FF_COLOR_FG_LIGHT_BLACK, }, .colorKeys = FF_COLOR_FG_256 "38", .colorTitle = FF_COLOR_FG_DEFAULT, }, // Ubuntu { .names = {"ubuntu", "ubuntu-linux"}, .lines = FASTFETCH_DATATEXT_LOGO_UBUNTU, .colors = { FF_COLOR_FG_RED, FF_COLOR_FG_RED, }, }, // UbuntuSmall { .names = {"ubuntu_small", "ubuntu-linux_small"}, .type = FF_LOGO_LINE_TYPE_SMALL_BIT, .lines = FASTFETCH_DATATEXT_LOGO_UBUNTU_SMALL, .colors = { FF_COLOR_FG_RED, FF_COLOR_FG_RED, }, }, // UbuntuOld { .names = {"ubuntu_old", "ubuntu-linux_old"}, .type = FF_LOGO_LINE_TYPE_ALTER_BIT, .lines = FASTFETCH_DATATEXT_LOGO_UBUNTU_OLD, .colors = { FF_COLOR_FG_RED, FF_COLOR_FG_WHITE, }, .colorKeys = FF_COLOR_FG_RED, .colorTitle = FF_COLOR_FG_RED, }, // UbuntuOld2 { .names = {"ubuntu_old2"}, .type = FF_LOGO_LINE_TYPE_ALTER_BIT, .lines = FASTFETCH_DATATEXT_LOGO_UBUNTU_OLD2, .colors = { FF_COLOR_FG_RED, FF_COLOR_FG_WHITE, FF_COLOR_FG_YELLOW, }, .colorKeys = FF_COLOR_FG_RED, .colorTitle = FF_COLOR_FG_RED, }, // UbuntuOld2Small { .names = {"ubuntu_old2_small", "ubuntu_old2_small"}, .type = FF_LOGO_LINE_TYPE_SMALL_BIT | FF_LOGO_LINE_TYPE_ALTER_BIT, .lines = FASTFETCH_DATATEXT_LOGO_UBUNTU_OLD2_SMALL, .colors = { FF_COLOR_FG_RED, }, .colorKeys = FF_COLOR_FG_RED, .colorTitle = FF_COLOR_FG_RED, }, // UbuntuBudgie { .names = {"ubuntu budgie", "ubuntu-budgie"}, .lines = FASTFETCH_DATATEXT_LOGO_UBUNTU_BUDGIE, .colors = { FF_COLOR_FG_BLUE, FF_COLOR_FG_WHITE, FF_COLOR_FG_RED, }, .colorKeys = FF_COLOR_FG_BLUE, .colorTitle = FF_COLOR_FG_DEFAULT, }, // UbuntuCinnamon { .names = {"ubuntu cinnamon", "ubuntu-cinnamon"}, .lines = FASTFETCH_DATATEXT_LOGO_UBUNTU_CINNAMON, .colors = { FF_COLOR_FG_RED, FF_COLOR_FG_WHITE, }, }, // UbuntuGNOME { .names = {"ubuntu gnome", "ubuntu-gnome"}, .lines = FASTFETCH_DATATEXT_LOGO_UBUNTU_GNOME, .colors = { FF_COLOR_FG_BLUE, FF_COLOR_FG_MAGENTA, FF_COLOR_FG_WHITE, FF_COLOR_FG_CYAN, }, .colorKeys = FF_COLOR_FG_BLUE, .colorTitle = FF_COLOR_FG_MAGENTA, }, // UbuntuKylin { .names = {"ubuntu kylin", "ubuntu-kylin"}, .lines = FASTFETCH_DATATEXT_LOGO_UBUNTU_KYLIN, .colors = { FF_COLOR_FG_RED, FF_COLOR_FG_WHITE, FF_COLOR_FG_YELLOW, }, .colorKeys = FF_COLOR_FG_RED, .colorTitle = FF_COLOR_FG_DEFAULT, }, // UbuntuMate { .names = {"ubuntu mate", "ubuntu-mate"}, .lines = FASTFETCH_DATATEXT_LOGO_UBUNTU_MATE, .colors = { FF_COLOR_FG_GREEN, FF_COLOR_FG_WHITE, }, .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"}, .lines = FASTFETCH_DATATEXT_LOGO_UBUNTU_STUDIO, .colors = { FF_COLOR_FG_CYAN, FF_COLOR_FG_WHITE, }, }, // UbuntuSway { .names = {"ubuntu sway", "ubuntu-sway"}, .lines = FASTFETCH_DATATEXT_LOGO_UBUNTU_SWAY, .colors = { FF_COLOR_FG_CYAN, FF_COLOR_FG_WHITE, }, }, // UbuntuTouch { .names = {"ubuntu touch", "ubuntu-touch"}, .lines = FASTFETCH_DATATEXT_LOGO_UBUNTU_TOUCH, .colors = { FF_COLOR_FG_YELLOW, FF_COLOR_FG_WHITE, }, }, // UbuntuUnity { .names = {"ubuntu unity", "ubuntu-unity"}, .lines = FASTFETCH_DATATEXT_LOGO_UBUNTU_UNITY, .colors = { FF_COLOR_FG_MAGENTA, FF_COLOR_FG_WHITE, }, }, // Ultramarine { .names = {"Ultramarine", "Ultramarine Linux"}, .lines = FASTFETCH_DATATEXT_LOGO_ULTRAMARINE, .colors = { FF_COLOR_FG_BLUE, FF_COLOR_FG_WHITE, }, }, // Ultramarine Small { .names = {"Ultramarine_small"}, .lines = FASTFETCH_DATATEXT_LOGO_ULTRAMARINE_SMALL, .colors = { FF_COLOR_FG_BLUE, FF_COLOR_FG_WHITE, }, }, // Unifi { .names = {"Unifi"}, .lines = FASTFETCH_DATATEXT_LOGO_UNIFI, .colors = { FF_COLOR_FG_WHITE, FF_COLOR_FG_WHITE, }, }, // Univalent { .names = {"Univalent"}, .lines = FASTFETCH_DATATEXT_LOGO_UNIVALENT, .colors = { FF_COLOR_FG_CYAN, FF_COLOR_FG_CYAN, }, }, // Univention { .names = {"Univention"}, .lines = FASTFETCH_DATATEXT_LOGO_UNIVENTION, .colors = { FF_COLOR_FG_RED, FF_COLOR_FG_WHITE, }, }, // UOS { .names = {"UOS"}, .lines = FASTFETCH_DATATEXT_LOGO_UOS, .colors = { FF_COLOR_FG_BLUE, }, .colorKeys = FF_COLOR_FG_DEFAULT, .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, } }, // Uwuntu { .names = {"uwuntu"}, .lines = FASTFETCH_DATATEXT_LOGO_UWUNTU, .colors = { FF_COLOR_FG_256 "225", FF_COLOR_FG_256 "206", FF_COLOR_FG_256 "52", }, }, // LAST {}, }; static const FFlogo V[] = { // Valhalla { .names = {"Valhalla", "valhallaos", "valhalla-linux"}, .lines = FASTFETCH_DATATEXT_LOGO_VALHALLA, .colors = { FF_COLOR_FG_DEFAULT, }, .colorKeys = FF_COLOR_FG_DEFAULT, .colorTitle = FF_COLOR_FG_DEFAULT, }, // Vanilla { .names = {"vanilla"}, .lines = FASTFETCH_DATATEXT_LOGO_VANILLA, .colors = { FF_COLOR_FG_YELLOW, }, .colorKeys = FF_COLOR_FG_YELLOW, .colorTitle = FF_COLOR_FG_YELLOW, }, // Vanilla2 { .names = {"vanilla2"}, .type = FF_LOGO_LINE_TYPE_ALTER_BIT, .lines = FASTFETCH_DATATEXT_LOGO_VANILLA2, .colors = { FF_COLOR_FG_YELLOW, }, .colorKeys = FF_COLOR_FG_YELLOW, .colorTitle = FF_COLOR_FG_YELLOW, }, // VanillaSmall { .names = {"vanilla_small"}, .type = FF_LOGO_LINE_TYPE_SMALL_BIT, .lines = FASTFETCH_DATATEXT_LOGO_VANILLA_SMALL, .colors = { FF_COLOR_FG_LIGHT_YELLOW, FF_COLOR_FG_YELLOW, }, }, // Venom { .names = {"Venom"}, .lines = FASTFETCH_DATATEXT_LOGO_VENOM, .colors = { FF_COLOR_FG_LIGHT_BLACK, FF_COLOR_FG_BLUE, }, }, // VenomSmall { .names = {"Venom_small"}, .type = FF_LOGO_LINE_TYPE_SMALL_BIT, .lines = FASTFETCH_DATATEXT_LOGO_VENOM_SMALL, .colors = { FF_COLOR_FG_LIGHT_BLACK, FF_COLOR_FG_BLUE, }, }, // VincentOS { .names = {"VincentOS"}, .lines = FASTFETCH_DATATEXT_LOGO_VINCENTOS, .colors = { FF_COLOR_FG_GREEN, FF_COLOR_FG_DEFAULT, }, }, // Vnux { .names = {"Vnux"}, .lines = FASTFETCH_DATATEXT_LOGO_VNUX, .colors = { FF_COLOR_FG_256 "11", FF_COLOR_FG_256 "8", FF_COLOR_FG_256 "15", FF_COLOR_FG_RED, FF_COLOR_FG_WHITE, }, }, // Vzlinux { .names = {"Vzlinux"}, .lines = FASTFETCH_DATATEXT_LOGO_VZLINUX, .colors = { FF_COLOR_FG_RED, FF_COLOR_FG_WHITE, FF_COLOR_FG_YELLOW, }, }, // Void { .names = {"void"}, .lines = FASTFETCH_DATATEXT_LOGO_VOID, .colors = { FF_COLOR_FG_GREEN, FF_COLOR_FG_LIGHT_BLACK, }, .colorKeys = FF_COLOR_FG_DEFAULT, .colorTitle = FF_COLOR_FG_GREEN, }, // VoidSmall { .names = {"void_small"}, .type = FF_LOGO_LINE_TYPE_SMALL_BIT, .lines = FASTFETCH_DATATEXT_LOGO_VOID_SMALL, .colors = { FF_COLOR_FG_GREEN, }, .colorKeys = FF_COLOR_FG_DEFAULT, .colorTitle = FF_COLOR_FG_GREEN, }, // Void2 { .names = {"void2"}, .lines = FASTFETCH_DATATEXT_LOGO_VOID2, .colors = { FF_COLOR_FG_YELLOW, FF_COLOR_FG_DEFAULT, FF_COLOR_FG_GREEN, }, .colorKeys = FF_COLOR_FG_DEFAULT, .colorTitle = FF_COLOR_FG_GREEN, }, // Void2Small { .names = {"void2_small"}, .type = FF_LOGO_LINE_TYPE_SMALL_BIT | FF_LOGO_LINE_TYPE_ALTER_BIT, .lines = FASTFETCH_DATATEXT_LOGO_VOID2_SMALL, .colors = { FF_COLOR_FG_GREEN, }, .colorKeys = FF_COLOR_FG_DEFAULT, .colorTitle = FF_COLOR_FG_GREEN, }, // LAST {}, }; static const FFlogo W[] = { // WiiLinux { .names = {"WiiLinuxNgx", "WiiLinux", "Wii-Linux", "Wii Linux"}, .lines = FASTFETCH_DATATEXT_LOGO_WII_LINUX, .colors = { FF_COLOR_FG_CYAN, FF_COLOR_FG_WHITE, }, }, // Windows2025 { .names = {"Windows Server 2025"}, .lines = FASTFETCH_DATATEXT_LOGO_WINDOWS_2025, .colors = { FF_COLOR_FG_BLUE, FF_COLOR_FG_BLUE, FF_COLOR_FG_BLUE, FF_COLOR_FG_BLUE, }, .colorKeys = FF_COLOR_FG_YELLOW, .colorTitle = FF_COLOR_FG_CYAN, }, // Windows11 { .names = {"Windows 11", "Windows Server 2022"}, .lines = FASTFETCH_DATATEXT_LOGO_WINDOWS_11, .colors = { FF_COLOR_FG_BLUE, FF_COLOR_FG_BLUE, FF_COLOR_FG_BLUE, FF_COLOR_FG_BLUE, }, .colorKeys = FF_COLOR_FG_YELLOW, .colorTitle = FF_COLOR_FG_CYAN, }, // Windows11Small { .names = {"Windows 11_small"}, .type = FF_LOGO_LINE_TYPE_SMALL_BIT, .lines = FASTFETCH_DATATEXT_LOGO_WINDOWS_11_SMALL, .colors = { FF_COLOR_FG_BLUE, FF_COLOR_FG_BLUE, FF_COLOR_FG_BLUE, FF_COLOR_FG_BLUE, }, .colorKeys = FF_COLOR_FG_YELLOW, .colorTitle = FF_COLOR_FG_CYAN, }, // Windows8 { .names = {"Windows 8", "Windows 8.1", "Windows 10", "Windows Server 2012", "Windows Server 2012 R2", "Windows Server 2016", "Windows Server 2019"}, .lines = FASTFETCH_DATATEXT_LOGO_WINDOWS_8, .colors = { FF_COLOR_FG_CYAN, FF_COLOR_FG_CYAN, FF_COLOR_FG_CYAN, FF_COLOR_FG_CYAN, }, .colorKeys = FF_COLOR_FG_YELLOW, .colorTitle = FF_COLOR_FG_DEFAULT, }, // Windows { .names = {"Windows", "Windows 7", "Windows Server 2008", "Windows Server 2008 R2"}, .lines = FASTFETCH_DATATEXT_LOGO_WINDOWS, .colors = { FF_COLOR_FG_RED, FF_COLOR_FG_GREEN, FF_COLOR_FG_BLUE, FF_COLOR_FG_YELLOW, }, .colorKeys = FF_COLOR_FG_BLUE, .colorTitle = FF_COLOR_FG_GREEN, }, // Windows95 { .names = {"Windows 95", "Windows 9x"}, .lines = FASTFETCH_DATATEXT_LOGO_WINDOWS_95, .colors = { FF_COLOR_FG_CYAN, FF_COLOR_FG_BLUE, FF_COLOR_FG_YELLOW, FF_COLOR_FG_GREEN, FF_COLOR_FG_RED, FF_COLOR_FG_BLACK, }, .colorKeys = FF_COLOR_FG_CYAN, .colorTitle = FF_COLOR_FG_BLUE, }, // WolfOS { .names = {"WolfOS"}, .lines = FASTFETCH_DATATEXT_LOGO_WOLFOS, .colors = { FF_COLOR_FG_BLUE, FF_COLOR_FG_GREEN, }, }, // LAST {}, }; static const FFlogo X[] = { // XCP-ng { .names = {"XCP-ng", "xenenterprise"}, .lines = FASTFETCH_DATATEXT_LOGO_XCP_NG, .colors = { FF_COLOR_FG_RED, FF_COLOR_FG_RED, FF_COLOR_FG_BLACK, FF_COLOR_FG_BLACK, FF_COLOR_FG_BLUE, FF_COLOR_FG_YELLOW, } }, // Xenia { .names = {"Xenia"}, .lines = FASTFETCH_DATATEXT_LOGO_XENIA, .colors = { FF_COLOR_FG_RED, FF_COLOR_FG_LIGHT_BLACK, }, .colorKeys = FF_COLOR_FG_DEFAULT, .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, } }, //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", } }, // Xferience { .names = {"Xferience"}, .lines = FASTFETCH_DATATEXT_LOGO_XFERIENCE, .colors = { FF_COLOR_FG_CYAN, FF_COLOR_FG_CYAN, }, }, // Xubuntu { .names = {"Xubuntu"}, .lines = FASTFETCH_DATATEXT_LOGO_XUBUNTU, .colors = { FF_COLOR_FG_256 "25", FF_COLOR_FG_DEFAULT, }, }, //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", } }, // Xinux { .names = {"Xinux"}, .lines = FASTFETCH_DATATEXT_LOGO_XINUX, .colors = { FF_COLOR_FG_BLUE, FF_COLOR_FG_CYAN, } }, // LAST {}, }; static const FFlogo Y[] = { // YiffOS { .names = {"YiffOS"}, .lines = FASTFETCH_DATATEXT_LOGO_YIFFOS, .colors = { FF_COLOR_FG_256 "93", FF_COLOR_FG_256 "92", }, }, // LAST {}, }; static const FFlogo Z[] = { // Zorin { .names = {"Zorin"}, .lines = FASTFETCH_DATATEXT_LOGO_ZORIN, .colors = { FF_COLOR_FG_BLUE, }, .colorKeys = FF_COLOR_FG_BLUE, .colorTitle = FF_COLOR_FG_BLUE, }, // Z/OS { .names = {"z/OS", "zos"}, .lines = FASTFETCH_DATATEXT_LOGO_ZOS, .colors = { FF_COLOR_FG_BLUE, }, }, // Zraxyl { .names = {"Zraxyl" }, .lines = FASTFETCH_DATATEXT_LOGO_ZRAXYL, .colors = { FF_COLOR_FG_BLUE, }, .colorKeys = FF_COLOR_FG_BLUE, .colorTitle = FF_COLOR_FG_BLUE, }, // LAST {}, }; const FFlogo* ffLogoBuiltins[] = { A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P, Q, R, S, T, U, V, W, X, Y, Z, }; ================================================ FILE: src/logo/image/im6.c ================================================ #ifdef FF_HAVE_IMAGEMAGICK6 #include "image.h" #include "common/library.h" #include static FF_LIBRARY_SYMBOL(ResizeImage) static void* logoResize(const void* image, size_t width, size_t height, void* exceptionInfo) { return ffResizeImage(image, width, height, UndefinedFilter, 1.0, exceptionInfo); } FFLogoImageResult ffLogoPrintImageIM6(FFLogoRequestData* requestData) { FF_LIBRARY_LOAD(imageMagick, FF_LOGO_IMAGE_RESULT_INIT_ERROR, "libMagickCore-6.Q16HDRI" FF_LIBRARY_EXTENSION, 8, "libMagickCore-6.Q16" FF_LIBRARY_EXTENSION, 8) FF_LIBRARY_LOAD_SYMBOL_ADDRESS(imageMagick, ffResizeImage, ResizeImage, FF_LOGO_IMAGE_RESULT_INIT_ERROR) FFLogoImageResult result = ffLogoPrintImageImpl(requestData, &(FFIMData) { .resizeFunc = logoResize, .library = imageMagick, }); imageMagick = NULL; // leak imageMagick to prevent fastfetch from crashing #552 return result; } #endif ================================================ FILE: src/logo/image/im7.c ================================================ #ifdef FF_HAVE_IMAGEMAGICK7 #include "image.h" #include "common/library.h" #include static FF_LIBRARY_SYMBOL(ResizeImage) static void* logoResize(const void* image, size_t width, size_t height, void* exceptionInfo) { return ffResizeImage(image, width, height, UndefinedFilter, exceptionInfo); } FFLogoImageResult ffLogoPrintImageIM7(FFLogoRequestData* requestData) { FF_LIBRARY_LOAD(imageMagick, FF_LOGO_IMAGE_RESULT_INIT_ERROR, "libMagickCore-7.Q16HDRI" FF_LIBRARY_EXTENSION, 11, "libMagickCore-7.Q16" FF_LIBRARY_EXTENSION, 11, "libMagickCore-7.Q16HDRI-10" FF_LIBRARY_EXTENSION, -1 // Required for Windows ) FF_LIBRARY_LOAD_SYMBOL_ADDRESS(imageMagick, ffResizeImage, ResizeImage, FF_LOGO_IMAGE_RESULT_INIT_ERROR) FFLogoImageResult result = ffLogoPrintImageImpl(requestData, &(FFIMData) { .resizeFunc = logoResize, .library = imageMagick, }); imageMagick = NULL; // leak imageMagick to prevent fastfetch from crashing #552 return result; } #endif ================================================ FILE: src/logo/image/image.c ================================================ #include "image.h" #include "common/io.h" #include "common/printing.h" #include "common/processing.h" #include "common/stringUtils.h" #include "common/base64.h" #include "detection/terminalsize/terminalsize.h" #include #include #ifdef __APPLE__ #include #elif _WIN32 #include #elif __linux__ #include #elif __sun #include #endif static bool printImageIterm(bool printError) { const FFOptionsLogo* options = &instance.config.logo; FF_STRBUF_AUTO_DESTROY buf = ffStrbufCreate(); if(!ffAppendFileBuffer(options->source.chars, &buf)) { if (printError) fputs("Logo (iterm): Failed to load image file\n", stderr); return false; } fflush(stdout); bool inTmux = false; { const char* term = getenv("TERM"); inTmux = term && (ffStrStartsWith(term, "screen") || ffStrStartsWith(term, "tmux")); } FF_STRBUF_AUTO_DESTROY base64 = ffBase64EncodeStrbuf(&buf); ffStrbufClear(&buf); if (!options->width || !options->height) { if (options->position == FF_LOGO_POSITION_LEFT) { ffStrbufAppendF(&buf, "\e[2J\e[3J\e[%u;%uH", (unsigned) options->paddingTop + 1, (unsigned) options->paddingLeft + 1 ); } else if (options->position == FF_LOGO_POSITION_TOP) { ffStrbufAppendNC(&buf, options->paddingTop, '\n'); ffStrbufAppendNC(&buf, options->paddingLeft, ' '); } else if (options->position == FF_LOGO_POSITION_RIGHT) { if (!options->width) { if (printError) fputs("Logo (iterm): Must set logo width when using position right\n", stderr); return false; } ffStrbufAppendF(&buf, "\e[2J\e[3J\e[%u;9999999H\e[%uD", (unsigned) options->paddingTop + 1, (unsigned) options->paddingRight + options->width); } if (inTmux) ffStrbufAppendS(&buf, "\ePtmux;\e"); if (options->width) ffStrbufAppendF(&buf, "\e]1337;File=inline=1;width=%u:%s\a", (unsigned) options->width, base64.chars); else ffStrbufAppendF(&buf, "\e]1337;File=inline=1:%s\a", base64.chars); if (inTmux) ffStrbufAppendS(&buf, "\e\\"); ffWriteFDBuffer(FFUnixFD2NativeFD(STDOUT_FILENO), &buf); if (options->position == FF_LOGO_POSITION_LEFT || options->position == FF_LOGO_POSITION_RIGHT) { uint16_t X = 0, Y = 0; const char* error = ffGetTerminalResponse("\e[6n", 2, "%*[^0-9]%hu;%huR", &Y, &X); if (error) { fprintf(stderr, "\nLogo (iterm): fail to query cursor position: %s\n", error); return true; // We already printed image logo, don't print ascii logo then } if (X < options->paddingLeft + options->width) X = (uint16_t) (options->paddingLeft + options->width); if (options->position == FF_LOGO_POSITION_LEFT) instance.state.logoWidth = X + options->paddingRight - 1; instance.state.logoHeight = Y; fputs("\e[H", stdout); } else if (options->position == FF_LOGO_POSITION_TOP) { instance.state.logoWidth = instance.state.logoHeight = 0; ffPrintCharTimes('\n', options->paddingRight); } } else { ffStrbufAppendNC(&buf, options->paddingTop, '\n'); if (options->position == FF_LOGO_POSITION_RIGHT) ffStrbufAppendF(&buf, "\e[9999999C\e[%uD", (unsigned) options->paddingRight + options->width); else if (options->paddingLeft) ffStrbufAppendF(&buf, "\e[%uC", (unsigned) options->paddingLeft); if (inTmux) ffStrbufAppendS(&buf, "\ePtmux;\e"); ffStrbufAppendF(&buf, "\e]1337;File=inline=1;width=%u;height=%u;preserveAspectRatio=%u:%s\a", (unsigned) options->width, (unsigned) options->height, (unsigned) options->preserveAspectRatio, base64.chars ); if (inTmux) ffStrbufAppendS(&buf, "\e\\"); ffStrbufAppendC(&buf, '\n'); if (options->position == FF_LOGO_POSITION_LEFT) { instance.state.logoWidth = options->width + options->paddingLeft + options->paddingRight; instance.state.logoHeight = options->paddingTop + options->height; ffStrbufAppendF(&buf, "\e[%uA", (unsigned) instance.state.logoHeight); } else if (options->position == FF_LOGO_POSITION_TOP) { instance.state.logoWidth = instance.state.logoHeight = 0; ffStrbufAppendNC(&buf, options->paddingRight, '\n'); } else if (options->position == FF_LOGO_POSITION_RIGHT) { instance.state.logoWidth = instance.state.logoHeight = 0; ffStrbufAppendF(&buf, "\e[1G\e[%uA", (unsigned) options->height); } ffWriteFDBuffer(FFUnixFD2NativeFD(STDOUT_FILENO), &buf); } return true; } static bool printImageKittyIcat(bool printError) { const FFOptionsLogo* options = &instance.config.logo; if (!ffPathExists(options->source.chars, FF_PATHTYPE_FILE)) { if (printError) fputs("Logo (kitty-icat): Failed to load image file\n", stderr); return false; } fflush(stdout); FF_STRBUF_AUTO_DESTROY buf = ffStrbufCreate(); if (options->position == FF_LOGO_POSITION_LEFT) { ffStrbufAppendF(&buf, "\e[2J\e[3J\e[%u;%uH", (unsigned) options->paddingTop + 1, (unsigned) options->paddingLeft + 1 ); } else if (options->position == FF_LOGO_POSITION_TOP) { if (!options->width) { ffStrbufAppendNC(&buf, options->paddingTop, '\n'); ffStrbufAppendNC(&buf, options->paddingLeft, ' '); } else { if (printError) fputs("Logo (kitty-icat): position top is not supported when logo width is set\n", stderr); return false; } } else if (options->position == FF_LOGO_POSITION_RIGHT) { if (printError) fputs("Logo (kitty-icat): position right is not supported\n", stderr); return false; } uint32_t prevLength = buf.length; const char* error = NULL; if (options->width) { char place[64]; snprintf(place, ARRAY_SIZE(place), "--place=%ux%u@%ux%u", options->width, options->height == 0 ? 9999 : options->height, options->paddingLeft + 1, options->paddingTop + 1); error = ffProcessAppendStdOut(&buf, (char* []) { "kitten", "icat", "-n", "--align=center", place, "--scale-up", options->source.chars, NULL, }); } else { error = ffProcessAppendStdOut(&buf, (char* []) { "kitten", "icat", "-n", "--align=left", options->source.chars, NULL, }); } if (error) { if (printError) fprintf(stderr, "Logo (kitty-icat): running `kitten icat` failed %s\n", error); return false; } if (buf.length == prevLength) { if (printError) fputs("Logo (kitty-icat): `kitten icat` returned empty output\n", stderr); return false; } ffWriteFDBuffer(FFUnixFD2NativeFD(STDOUT_FILENO), &buf); if (options->position == FF_LOGO_POSITION_LEFT || options->position == FF_LOGO_POSITION_RIGHT) { uint16_t X = 0, Y = 0; const char* error = ffGetTerminalResponse("\e[6n", 2, "%*[^0-9]%hu;%huR", &Y, &X); if (error) { fprintf(stderr, "\nLogo (kitty-icat): fail to query cursor position: %s\n", error); return true; // We already printed image logo, don't print ascii logo then } if (X < options->paddingLeft + options->width) X = (uint16_t) (options->paddingLeft + options->width); if (options->position == FF_LOGO_POSITION_LEFT) instance.state.logoWidth = X + options->paddingRight - 1; instance.state.logoHeight = Y; fputs("\e[H", stdout); } else if (options->position == FF_LOGO_POSITION_TOP) { instance.state.logoWidth = instance.state.logoHeight = 0; ffPrintCharTimes('\n', options->paddingRight); } return true; } static bool printImageKittyDirect(bool printError) { const FFOptionsLogo* options = &instance.config.logo; if (!ffPathExists(options->source.chars, FF_PATHTYPE_FILE)) { if (printError) fputs("Logo (kitty-direct): Failed to load image file\n", stderr); return false; } fflush(stdout); bool inTmux = false; { const char* term = getenv("TERM"); inTmux = term && (ffStrStartsWith(term, "screen") || ffStrStartsWith(term, "tmux")); } FF_STRBUF_AUTO_DESTROY base64 = ffBase64EncodeStrbuf(&options->source); FF_STRBUF_AUTO_DESTROY buf = ffStrbufCreate(); if (!options->width || !options->height) { if (options->position == FF_LOGO_POSITION_LEFT) { // We must clear the entre screen to make sure that terminal buffer won't scroll up ffStrbufAppendF(&buf, "\e[2J\e[3J\e[%u;%uH", (unsigned) options->paddingTop + 1, (unsigned) options->paddingLeft + 1 ); } else if (options->position == FF_LOGO_POSITION_TOP) { ffStrbufAppendNC(&buf, options->paddingTop, '\n'); ffStrbufAppendNC(&buf, options->paddingLeft, ' '); } else if (options->position == FF_LOGO_POSITION_RIGHT) { if (!options->width) { if (printError) fputs("Logo (iterm): Must set logo width when using position right\n", stderr); return false; } ffStrbufAppendF(&buf, "\e[2J\e[3J\e[%u;9999999H\e[%uD", (unsigned) options->paddingTop + 1, (unsigned) options->paddingRight + options->width); } if (inTmux) ffStrbufAppendS(&buf, "\ePtmux;\e"); if (options->width) ffStrbufAppendF(&buf, "\e_Ga=T,f=100,t=f,c=%u;%s", (unsigned) options->width, base64.chars); else ffStrbufAppendF(&buf, "\e_Ga=T,f=100,t=f;%s", base64.chars); if (inTmux) ffStrbufAppendC(&buf, '\e'); ffStrbufAppendS(&buf, "\e\\"); if (inTmux) ffStrbufAppendS(&buf, "\e\\"); ffWriteFDBuffer(FFUnixFD2NativeFD(STDOUT_FILENO), &buf); if (options->position == FF_LOGO_POSITION_LEFT || options->position == FF_LOGO_POSITION_RIGHT) { uint16_t X = 0, Y = 0; const char* error = ffGetTerminalResponse("\e[6n", 2, "%*[^0-9]%hu;%huR", &Y, &X); if (error) { if (printError) fprintf(stderr, "\nLogo (kitty-direct): fail to query cursor position: %s\n", error); return true; // We already printed image logo, don't print ascii logo then } if (X < options->paddingLeft + options->width) X = (uint16_t) (options->paddingLeft + options->width); if (options->position == FF_LOGO_POSITION_LEFT) instance.state.logoWidth = X + options->paddingRight - 1; instance.state.logoHeight = Y; fputs("\e[H", stdout); } else if (options->position == FF_LOGO_POSITION_TOP) { instance.state.logoWidth = instance.state.logoHeight = 0; ffPrintCharTimes('\n', options->paddingRight); } } else { ffStrbufAppendNC(&buf, options->paddingTop, '\n'); if (options->position == FF_LOGO_POSITION_RIGHT) ffStrbufAppendF(&buf, "\e[9999999C\e[%uD", (unsigned) options->paddingRight + options->width); else if (options->paddingLeft) ffStrbufAppendF(&buf, "\e[%uC", (unsigned) options->paddingLeft); if (inTmux) ffStrbufAppendS(&buf, "\ePtmux;\e"); ffStrbufAppendF(&buf, "\e_Ga=T,f=100,t=f,c=%u,r=%u;%s\e\\", (unsigned) options->width, (unsigned) options->height, base64.chars ); if (inTmux) ffStrbufAppendS(&buf, "\e\\"); ffStrbufAppendC(&buf, '\n'); if (options->position == FF_LOGO_POSITION_LEFT) { instance.state.logoWidth = options->width + options->paddingLeft + options->paddingRight; instance.state.logoHeight = options->paddingTop + options->height; ffStrbufAppendF(&buf, "\e[%uA", (unsigned) instance.state.logoHeight); } else if (options->position == FF_LOGO_POSITION_TOP) { instance.state.logoWidth = instance.state.logoHeight = 0; ffStrbufAppendNC(&buf, options->paddingRight, '\n'); } else if (options->position == FF_LOGO_POSITION_RIGHT) { instance.state.logoWidth = instance.state.logoHeight = 0; ffStrbufAppendF(&buf, "\e[1G\e[%uA", (unsigned) options->height); } ffWriteFDBuffer(FFUnixFD2NativeFD(STDOUT_FILENO), &buf); } return true; } #if defined(FF_HAVE_IMAGEMAGICK7) || defined(FF_HAVE_IMAGEMAGICK6) #define FF_KITTY_MAX_CHUNK_SIZE 4096 #define FF_CACHE_FILE_HEIGHT "height" #define FF_CACHE_FILE_WIDTH "width" #define FF_CACHE_FILE_SIXEL "sixel" #define FF_CACHE_FILE_KITTY_COMPRESSED "kittyc" #define FF_CACHE_FILE_KITTY_UNCOMPRESSED "kittyu" #define FF_CACHE_FILE_CHAFA "chafa" #include #include #include #ifndef _WIN32 #include #else #include #include "common/path.h" #endif #ifdef FF_HAVE_ZLIB #include "common/library.h" #include #include static bool compressBlob(void** blob, size_t* length) { FF_LIBRARY_LOAD(zlib, false, "libz" FF_LIBRARY_EXTENSION, 2) FF_LIBRARY_LOAD_SYMBOL(zlib, compressBound, false) FF_LIBRARY_LOAD_SYMBOL(zlib, compress2, false) uLong compressedLength = ffcompressBound(*length); void* compressed = malloc(compressedLength); if(compressed == NULL) return false; if(ffcompress2(compressed, &compressedLength, *blob, *length, Z_BEST_COMPRESSION) != Z_OK) { free(compressed); return false; } free(*blob); *length = (size_t) compressedLength; *blob = compressed; return true; } #endif // FF_HAVE_ZLIB //We use only the defines from here, that are exactly the same in both versions #ifdef FF_HAVE_IMAGEMAGICK7 #include #else #include #endif typedef struct ImageData { FF_LIBRARY_SYMBOL(CopyMagickString) FF_LIBRARY_SYMBOL(ImageToBlob) FF_LIBRARY_SYMBOL(Base64Encode) ImageInfo* imageInfo; Image* image; ExceptionInfo* exceptionInfo; } ImageData; static inline bool checkAllocationResult(void* data, size_t length) { if(data == NULL) return false; if(length == 0) { free(data); return false; } return true; } static void writeCacheStrbuf(FFLogoRequestData* requestData, const FFstrbuf* value, const char* cacheFileName) { uint32_t cacheDirLength = requestData->cacheDir.length; ffStrbufAppendS(&requestData->cacheDir, cacheFileName); ffWriteFileBuffer(requestData->cacheDir.chars, value); ffStrbufSubstrBefore(&requestData->cacheDir, cacheDirLength); } static void writeCacheUint32(FFLogoRequestData* requestData, uint32_t value, const char* cacheFileName) { FFstrbuf content; content.chars = (char*) &value; content.length = sizeof(value); writeCacheStrbuf(requestData, &content, cacheFileName); } static void printImagePixels(FFLogoRequestData* requestData, const FFstrbuf* result, const char* cacheFileName) { const FFOptionsLogo* options = &instance.config.logo; //Calculate character dimensions instance.state.logoWidth = requestData->logoCharacterWidth + options->paddingLeft + options->paddingRight; instance.state.logoHeight = requestData->logoCharacterHeight + options->paddingTop - 1; //Write cache files writeCacheStrbuf(requestData, result, cacheFileName); if(options->width == 0) writeCacheUint32(requestData, requestData->logoCharacterWidth, FF_CACHE_FILE_WIDTH); if(options->height == 0) writeCacheUint32(requestData, requestData->logoCharacterHeight, FF_CACHE_FILE_HEIGHT); //Write result to stdout ffPrintCharTimes('\n', options->paddingTop); if (options->position == FF_LOGO_POSITION_RIGHT) printf("\e[9999999C\e[%uD", (unsigned) options->paddingRight + requestData->logoCharacterWidth); else if (options->paddingLeft) printf("\e[%uC", (unsigned) options->paddingLeft); fflush(stdout); ffWriteFDBuffer(FFUnixFD2NativeFD(STDOUT_FILENO), result); if (options->position != FF_LOGO_POSITION_TOP) { //Go to upper left corner printf("\e[1G\e[%uA", instance.state.logoHeight); } if (options->position != FF_LOGO_POSITION_LEFT) instance.state.logoWidth = instance.state.logoHeight = 0; } static bool printImageSixel(FFLogoRequestData* requestData, const ImageData* imageData) { imageData->ffCopyMagickString(imageData->imageInfo->magick, "SIXEL", 6); size_t length; void* blob = imageData->ffImageToBlob(imageData->imageInfo, imageData->image, &length, imageData->exceptionInfo); if(!checkAllocationResult(blob, length)) return false; FFstrbuf result; result.chars = (char*) blob; result.length = (uint32_t) length; printImagePixels(requestData, &result, FF_CACHE_FILE_SIXEL); free(blob); return true; } static void appendKittyChunk(FFstrbuf* result, const char** blob, size_t* length, bool printEscapeCode) { uint32_t chunkSize = *length > FF_KITTY_MAX_CHUNK_SIZE ? FF_KITTY_MAX_CHUNK_SIZE : (uint32_t) *length; if(printEscapeCode) ffStrbufAppendS(result, "\033_G"); else ffStrbufAppendC(result, ','); ffStrbufAppendS(result, chunkSize != *length ? "m=1" : "m=0"); ffStrbufAppendC(result, ';'); ffStrbufAppendNS(result, chunkSize, *blob); ffStrbufAppendS(result, "\033\\"); *length -= chunkSize; *blob += chunkSize; } static bool printImageKitty(FFLogoRequestData* requestData, const ImageData* imageData) { imageData->ffCopyMagickString(imageData->imageInfo->magick, "RGBA", 5); size_t length; void* blob = imageData->ffImageToBlob(imageData->imageInfo, imageData->image, &length, imageData->exceptionInfo); if(!checkAllocationResult(blob, length)) return false; #ifdef FF_HAVE_ZLIB bool isCompressed = compressBlob(&blob, &length); #else bool isCompressed = false; #endif char* chars = imageData->ffBase64Encode(blob, length, &length); free(blob); if(!checkAllocationResult(chars, length)) return false; FF_STRBUF_AUTO_DESTROY result = ffStrbufCreateA((uint32_t) (length + 1024)); const char* currentPos = chars; size_t remainingLength = length; ffStrbufAppendF(&result, "\033_Ga=T,f=32,s=%u,v=%u", requestData->logoPixelWidth, requestData->logoPixelHeight); if(isCompressed) ffStrbufAppendS(&result, ",o=z"); appendKittyChunk(&result, ¤tPos, &remainingLength, false); while(remainingLength > 0) appendKittyChunk(&result, ¤tPos, &remainingLength, true); printImagePixels(requestData, &result, isCompressed ? FF_CACHE_FILE_KITTY_COMPRESSED : FF_CACHE_FILE_KITTY_UNCOMPRESSED); free(chars); return true; } #ifdef FF_HAVE_CHAFA #include static bool printImageChafa(FFLogoRequestData* requestData, const ImageData* imageData) { FF_LIBRARY_LOAD(chafa, false, "libchafa" FF_LIBRARY_EXTENSION, 1, "libchafa-0" FF_LIBRARY_EXTENSION, -1 // Required for Windows ) FF_LIBRARY_LOAD_SYMBOL(chafa, chafa_symbol_map_new, false) FF_LIBRARY_LOAD_SYMBOL(chafa, chafa_symbol_map_apply_selectors, false) FF_LIBRARY_LOAD_SYMBOL(chafa, chafa_canvas_config_new, false) FF_LIBRARY_LOAD_SYMBOL(chafa, chafa_canvas_config_set_geometry, false) FF_LIBRARY_LOAD_SYMBOL(chafa, chafa_canvas_config_set_symbol_map, false) FF_LIBRARY_LOAD_SYMBOL(chafa, chafa_canvas_new, false) FF_LIBRARY_LOAD_SYMBOL(chafa, chafa_canvas_draw_all_pixels, false) FF_LIBRARY_LOAD_SYMBOL(chafa, chafa_canvas_print, false) FF_LIBRARY_LOAD_SYMBOL(chafa, chafa_canvas_unref, false) FF_LIBRARY_LOAD_SYMBOL(chafa, chafa_canvas_config_unref, false) FF_LIBRARY_LOAD_SYMBOL(chafa, chafa_symbol_map_unref, false) imageData->ffCopyMagickString(imageData->imageInfo->magick, "RGBA", 5); size_t length; void* blob = imageData->ffImageToBlob(imageData->imageInfo, imageData->image, &length, imageData->exceptionInfo); if(!checkAllocationResult(blob, length)) return false; ChafaSymbolMap* symbolMap = ffchafa_symbol_map_new(); GError* error = NULL; if(!ffchafa_symbol_map_apply_selectors(symbolMap, instance.config.logo.chafaSymbols.chars, &error)) fputs(error->message, stderr); ChafaCanvasConfig* canvasConfig = ffchafa_canvas_config_new(); ffchafa_canvas_config_set_geometry(canvasConfig, (gint) requestData->logoCharacterWidth, (gint) requestData->logoCharacterHeight); ffchafa_canvas_config_set_symbol_map(canvasConfig, symbolMap); if(instance.config.logo.chafaFgOnly) { FF_LIBRARY_LOAD_SYMBOL_LAZY(chafa, chafa_canvas_config_set_fg_only_enabled); if(ffchafa_canvas_config_set_fg_only_enabled) ffchafa_canvas_config_set_fg_only_enabled(canvasConfig, true); } if(instance.config.logo.chafaCanvasMode < CHAFA_CANVAS_MODE_MAX) { FF_LIBRARY_LOAD_SYMBOL_LAZY(chafa, chafa_canvas_config_set_canvas_mode); if(ffchafa_canvas_config_set_canvas_mode) ffchafa_canvas_config_set_canvas_mode(canvasConfig, (ChafaCanvasMode) instance.config.logo.chafaCanvasMode); } if(instance.config.logo.chafaColorSpace < CHAFA_COLOR_SPACE_MAX) { FF_LIBRARY_LOAD_SYMBOL_LAZY(chafa, chafa_canvas_config_set_color_space) if(ffchafa_canvas_config_set_color_space) ffchafa_canvas_config_set_color_space(canvasConfig, (ChafaColorSpace) instance.config.logo.chafaColorSpace); } if(instance.config.logo.chafaDitherMode < CHAFA_DITHER_MODE_MAX) { FF_LIBRARY_LOAD_SYMBOL_LAZY(chafa, chafa_canvas_config_set_dither_mode) if(ffchafa_canvas_config_set_dither_mode) ffchafa_canvas_config_set_dither_mode(canvasConfig, (ChafaDitherMode) instance.config.logo.chafaDitherMode); } ChafaCanvas* canvas = ffchafa_canvas_new(canvasConfig); ffchafa_canvas_draw_all_pixels( canvas, CHAFA_PIXEL_RGBA8_UNASSOCIATED, blob, (gint) imageData->image->columns, (gint) imageData->image->rows, (gint) imageData->image->columns * 4 ); GString* str = ffchafa_canvas_print(canvas, NULL); FFstrbuf result; result.allocated = (uint32_t) str->allocated_len; result.length = (uint32_t) str->len; result.chars = str->str; ffLogoPrintChars(result.chars, false); writeCacheStrbuf(requestData, &result, FF_CACHE_FILE_CHAFA); // FIXME: These functions must be imported from `libglib` dlls on Windows FF_LIBRARY_LOAD_SYMBOL_LAZY(chafa, g_string_free); if(ffg_string_free) ffg_string_free(str, TRUE); if(error) { FF_LIBRARY_LOAD_SYMBOL_LAZY(chafa, g_error_free) if(ffg_error_free) ffg_error_free(error); } ffchafa_canvas_unref(canvas); ffchafa_canvas_config_unref(canvasConfig); ffchafa_symbol_map_unref(symbolMap); return true; } #endif FFLogoImageResult ffLogoPrintImageImpl(FFLogoRequestData* requestData, const FFIMData* imData) { FF_LIBRARY_LOAD_SYMBOL(imData->library, MagickCoreGenesis, FF_LOGO_IMAGE_RESULT_INIT_ERROR) FF_LIBRARY_LOAD_SYMBOL(imData->library, MagickCoreTerminus, FF_LOGO_IMAGE_RESULT_INIT_ERROR) FF_LIBRARY_LOAD_SYMBOL(imData->library, AcquireExceptionInfo, FF_LOGO_IMAGE_RESULT_INIT_ERROR) FF_LIBRARY_LOAD_SYMBOL(imData->library, DestroyExceptionInfo, FF_LOGO_IMAGE_RESULT_INIT_ERROR) FF_LIBRARY_LOAD_SYMBOL(imData->library, AcquireImageInfo, FF_LOGO_IMAGE_RESULT_INIT_ERROR) FF_LIBRARY_LOAD_SYMBOL(imData->library, DestroyImageInfo, FF_LOGO_IMAGE_RESULT_INIT_ERROR) FF_LIBRARY_LOAD_SYMBOL(imData->library, ReadImage, FF_LOGO_IMAGE_RESULT_INIT_ERROR) FF_LIBRARY_LOAD_SYMBOL(imData->library, DestroyImage, FF_LOGO_IMAGE_RESULT_INIT_ERROR) ImageData imageData; FF_LIBRARY_LOAD_SYMBOL_VAR(imData->library, imageData, CopyMagickString, FF_LOGO_IMAGE_RESULT_INIT_ERROR) FF_LIBRARY_LOAD_SYMBOL_VAR(imData->library, imageData, ImageToBlob, FF_LOGO_IMAGE_RESULT_INIT_ERROR) FF_LIBRARY_LOAD_SYMBOL_VAR(imData->library, imageData, Base64Encode, FF_LOGO_IMAGE_RESULT_INIT_ERROR) ffMagickCoreGenesis(NULL, MagickFalse); imageData.exceptionInfo = ffAcquireExceptionInfo(); if(imageData.exceptionInfo == NULL) { ffMagickCoreTerminus(); return FF_LOGO_IMAGE_RESULT_RUN_ERROR; } ImageInfo* imageInfoIn = ffAcquireImageInfo(); if(imageInfoIn == NULL) { ffDestroyExceptionInfo(imageData.exceptionInfo); ffMagickCoreTerminus(); return FF_LOGO_IMAGE_RESULT_RUN_ERROR; } //+1, because we need to copy the null byte too imageData.ffCopyMagickString(imageInfoIn->filename, instance.config.logo.source.chars, instance.config.logo.source.length + 1); imageData.image = ffReadImage(imageInfoIn, imageData.exceptionInfo); ffDestroyImageInfo(imageInfoIn); if(imageData.image == NULL) { ffDestroyExceptionInfo(imageData.exceptionInfo); ffMagickCoreTerminus(); return FF_LOGO_IMAGE_RESULT_RUN_ERROR; } if(requestData->logoPixelWidth == 0 && requestData->logoPixelHeight == 0) { requestData->logoPixelWidth = (uint32_t) imageData.image->columns; requestData->logoPixelHeight = (uint32_t) imageData.image->rows; } else if(requestData->logoPixelWidth == 0) requestData->logoPixelWidth = (uint32_t) ((double) imageData.image->columns / (double) imageData.image->rows * requestData->logoPixelHeight); else if(requestData->logoPixelHeight == 0) requestData->logoPixelHeight = (uint32_t) ((double) imageData.image->rows / (double) imageData.image->columns * requestData->logoPixelWidth); requestData->logoCharacterWidth = (uint32_t) ceil((double) requestData->logoPixelWidth / requestData->characterPixelWidth); requestData->logoCharacterHeight = (uint32_t) ceil((double) requestData->logoPixelHeight / requestData->characterPixelHeight); if(requestData->logoPixelWidth == 0 || requestData->logoPixelHeight == 0 || requestData->logoCharacterWidth == 0 || requestData->logoCharacterHeight == 0) { ffDestroyImage(imageData.image); ffDestroyExceptionInfo(imageData.exceptionInfo); ffMagickCoreTerminus(); return FF_LOGO_IMAGE_RESULT_RUN_ERROR; } Image* resized = imData->resizeFunc(imageData.image, requestData->logoPixelWidth, requestData->logoPixelHeight, imageData.exceptionInfo); ffDestroyImage(imageData.image); if(resized == NULL) { ffDestroyExceptionInfo(imageData.exceptionInfo); ffMagickCoreTerminus(); return FF_LOGO_IMAGE_RESULT_RUN_ERROR; } imageData.image = resized; imageData.imageInfo = ffAcquireImageInfo(); if(imageData.imageInfo == NULL) { ffDestroyImage(imageData.image); ffDestroyExceptionInfo(imageData.exceptionInfo); ffMagickCoreTerminus(); return FF_LOGO_IMAGE_RESULT_RUN_ERROR; } bool printSuccessful = false; if(requestData->type == FF_LOGO_TYPE_IMAGE_CHAFA) { #ifdef FF_HAVE_CHAFA printSuccessful = printImageChafa(requestData, &imageData); #endif } else if(requestData->type == FF_LOGO_TYPE_IMAGE_KITTY) printSuccessful = printImageKitty(requestData, &imageData); else if(requestData->type == FF_LOGO_TYPE_IMAGE_SIXEL) printSuccessful = printImageSixel(requestData, &imageData); ffDestroyImageInfo(imageData.imageInfo); ffDestroyImage(imageData.image); ffDestroyExceptionInfo(imageData.exceptionInfo); ffMagickCoreTerminus(); return printSuccessful ? FF_LOGO_IMAGE_RESULT_SUCCESS : FF_LOGO_IMAGE_RESULT_RUN_ERROR; } static FFNativeFD getCacheFD(FFLogoRequestData* requestData, const char* fileName) { uint32_t cacheDirLength = requestData->cacheDir.length; ffStrbufAppendS(&requestData->cacheDir, fileName); #ifndef _WIN32 int fd = open(requestData->cacheDir.chars, O_RDONLY #ifdef O_CLOEXEC | O_CLOEXEC #endif ); #else HANDLE fd = CreateFileA(requestData->cacheDir.chars, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL); #endif ffStrbufSubstrBefore(&requestData->cacheDir, cacheDirLength); return fd; } static void readCachedStrbuf(FFLogoRequestData* requestData, FFstrbuf* result, const char* cacheFileName) { uint32_t cacheDirLength = requestData->cacheDir.length; ffStrbufAppendS(&requestData->cacheDir, cacheFileName); ffAppendFileBuffer(requestData->cacheDir.chars, result); ffStrbufSubstrBefore(&requestData->cacheDir, cacheDirLength); } static uint32_t readCachedUint32(FFLogoRequestData* requestData, const char* cacheFileName) { FF_STRBUF_AUTO_DESTROY content = ffStrbufCreate(); readCachedStrbuf(requestData, &content, cacheFileName); uint32_t result = 0; if(content.length != sizeof(result)) return 0; memcpy(&result, content.chars, sizeof(result)); return result; } static bool printCachedChars(FFLogoRequestData* requestData) { FF_STRBUF_AUTO_DESTROY content = ffStrbufCreateA(32768); if(requestData->type == FF_LOGO_TYPE_IMAGE_CHAFA) readCachedStrbuf(requestData, &content, FF_CACHE_FILE_CHAFA); if(content.length == 0) return false; ffLogoPrintChars(content.chars, false); return true; } static bool printCachedPixel(FFLogoRequestData* requestData) { FFOptionsLogo* options = &instance.config.logo; requestData->logoCharacterWidth = options->width; if(requestData->logoCharacterWidth == 0) { requestData->logoCharacterWidth = readCachedUint32(requestData, FF_CACHE_FILE_WIDTH); if(requestData->logoCharacterWidth == 0) return false; } requestData->logoCharacterHeight = options->height; if(requestData->logoCharacterHeight == 0) { requestData->logoCharacterHeight = readCachedUint32(requestData, FF_CACHE_FILE_HEIGHT); if(requestData->logoCharacterHeight == 0) return false; } FF_AUTO_CLOSE_FD FFNativeFD fd = FF_INVALID_FD; if(requestData->type == FF_LOGO_TYPE_IMAGE_KITTY) { fd = getCacheFD(requestData, FF_CACHE_FILE_KITTY_COMPRESSED); if(!ffIsValidNativeFD(fd)) fd = getCacheFD(requestData, FF_CACHE_FILE_KITTY_UNCOMPRESSED); } else if(requestData->type == FF_LOGO_TYPE_IMAGE_SIXEL) fd = getCacheFD(requestData, FF_CACHE_FILE_SIXEL); if(!ffIsValidNativeFD(fd)) return false; ffPrintCharTimes('\n', options->paddingTop); if (options->position == FF_LOGO_POSITION_RIGHT) printf("\e[9999999C\e[%uD", (unsigned) options->paddingRight + requestData->logoCharacterWidth); else if (options->paddingLeft) printf("\e[%uC", (unsigned) options->paddingLeft); fflush(stdout); bool sent = false; #ifdef __linux__ struct stat st; if (fstat(fd, &st) >= 0) { while (st.st_size > 0) { ssize_t bytes = sendfile(STDOUT_FILENO, fd, NULL, (size_t) st.st_size); if (bytes > 0) { sent = true; st.st_size -= bytes; } else break; } } #endif if (!sent) { char buffer[32768]; ssize_t readBytes; while((readBytes = ffReadFDData(fd, sizeof(buffer), buffer)) > 0) ffWriteFDData(FFUnixFD2NativeFD(STDOUT_FILENO), (size_t) readBytes, buffer); } instance.state.logoWidth = requestData->logoCharacterWidth + options->paddingLeft + options->paddingRight; instance.state.logoHeight = requestData->logoCharacterHeight + options->paddingTop; if (options->position != FF_LOGO_POSITION_TOP) { //Go to upper left corner printf("\e[1G\e[%uA", instance.state.logoHeight); } if (options->position != FF_LOGO_POSITION_LEFT) instance.state.logoWidth = instance.state.logoHeight = 0; return true; } static bool printCached(FFLogoRequestData* requestData) { if(requestData->type == FF_LOGO_TYPE_IMAGE_CHAFA) return printCachedChars(requestData); else return printCachedPixel(requestData); } static bool getCharacterPixelDimensions(FFLogoRequestData* requestData) { #ifdef _WIN32 CONSOLE_FONT_INFOEX cfi; if(GetCurrentConsoleFontEx(GetStdHandle(STD_OUTPUT_HANDLE), FALSE, &cfi)) // Only works for ConHost { requestData->characterPixelWidth = cfi.dwFontSize.X; requestData->characterPixelHeight = cfi.dwFontSize.Y; } if (requestData->characterPixelWidth > 1.0 && requestData->characterPixelHeight > 1.0) return true; #endif FFTerminalSizeResult termSize = {}; if (ffDetectTerminalSize(&termSize)) { requestData->characterPixelWidth = termSize.width / (double) termSize.columns; requestData->characterPixelHeight = termSize.height / (double) termSize.rows; } return requestData->characterPixelWidth > 1.0 && requestData->characterPixelHeight > 1.0; } static bool printImageIfExistsSlowPath(FFLogoType type, bool printError) { FFLogoRequestData requestData; requestData.type = type; requestData.characterPixelWidth = 1; requestData.characterPixelHeight = 1; if(!getCharacterPixelDimensions(&requestData)) { if(printError) fputs("Logo: getCharacterPixelDimensions() failed\n", stderr); return false; } requestData.logoPixelWidth = (uint32_t) ceil((double) instance.config.logo.width * requestData.characterPixelWidth); requestData.logoPixelHeight = (uint32_t) ceil((double) instance.config.logo.height * requestData.characterPixelHeight); ffStrbufInit(&requestData.cacheDir); ffStrbufAppend(&requestData.cacheDir, &instance.state.platform.cacheDir); ffStrbufAppendS(&requestData.cacheDir, "fastfetch/images"); ffStrbufEnsureFree(&requestData.cacheDir, PATH_MAX); char* filePath = requestData.cacheDir.chars + requestData.cacheDir.length; if(realpath(instance.config.logo.source.chars, filePath) == NULL) { //We can safely return here, because if realpath failed, we surely won't be able to read the file ffStrbufDestroy(&requestData.cacheDir); if(printError) fputs("Logo: Querying realpath of the image source failed\n", stderr); return false; } #ifdef _WIN32 filePath[1] = filePath[0]; // Drive Name filePath[0] = '/'; #endif ffStrbufRecalculateLength(&requestData.cacheDir); ffStrbufEnsureEndsWithC(&requestData.cacheDir, '/'); ffStrbufAppendF(&requestData.cacheDir, "%u*%u/", requestData.logoPixelWidth, requestData.logoPixelHeight); if(!instance.config.logo.recache && printCached(&requestData)) { ffStrbufDestroy(&requestData.cacheDir); return true; } FFLogoImageResult result = FF_LOGO_IMAGE_RESULT_INIT_ERROR; #ifdef FF_HAVE_IMAGEMAGICK7 result = ffLogoPrintImageIM7(&requestData); #endif #ifdef FF_HAVE_IMAGEMAGICK6 if(result == FF_LOGO_IMAGE_RESULT_INIT_ERROR) result = ffLogoPrintImageIM6(&requestData); #endif ffStrbufDestroy(&requestData.cacheDir); if(result == FF_LOGO_IMAGE_RESULT_SUCCESS) return true; if(printError) { if(result == FF_LOGO_IMAGE_RESULT_INIT_ERROR) fputs("Logo: Image Magick library not found\n", stderr); else fputs("Logo: Failed to load / convert the image source\n", stderr); } return false; } #endif //FF_HAVE_IMAGEMAGICK{6, 7} bool ffLogoPrintImageIfExists(FFLogoType type, bool printError) { if(instance.config.display.pipe) { if(printError) fputs("Logo: Image logo is not supported in pipe mode\n", stderr); return false; } if(!ffPathExists(instance.config.logo.source.chars, FF_PATHTYPE_FILE)) { if(printError) fprintf(stderr, "Logo: Image source \"%s\" does not exist\n", instance.config.logo.source.chars); return false; } const char* term = getenv("TERM"); if((term && ffStrEquals(term, "screen")) || getenv("ZELLIJ")) { if(printError) fputs("Logo: Image logo is not supported in terminal multiplexers\n", stderr); return false; } if(type == FF_LOGO_TYPE_IMAGE_ITERM) return printImageIterm(printError); if(type == FF_LOGO_TYPE_IMAGE_KITTY_DIRECT) return printImageKittyDirect(printError); if(type == FF_LOGO_TYPE_IMAGE_KITTY_ICAT) return printImageKittyIcat(printError); #if !defined(FF_HAVE_CHAFA) if(type == FF_LOGO_TYPE_IMAGE_CHAFA) { if(printError) fputs("Logo: Chafa support is not compiled in\n", stderr); return false; } #endif #if !defined(FF_HAVE_IMAGEMAGICK7) && !defined(FF_HAVE_IMAGEMAGICK6) if(printError) fputs("Logo: Image Magick support is not compiled in\n", stderr); return false; #else return printImageIfExistsSlowPath(type, printError); #endif } ================================================ FILE: src/logo/image/image.h ================================================ #pragma once #include "../logo.h" #if defined(FF_HAVE_IMAGEMAGICK7) || defined(FF_HAVE_IMAGEMAGICK6) typedef enum __attribute__((__packed__)) FFLogoImageResult { FF_LOGO_IMAGE_RESULT_SUCCESS, //Logo printed FF_LOGO_IMAGE_RESULT_INIT_ERROR, //Failed to load library, try again with next IM version FF_LOGO_IMAGE_RESULT_RUN_ERROR //Failed to load / convert image, cancel whole sixel code } FFLogoImageResult; typedef struct FFLogoRequestData { FFLogoType type; FFstrbuf cacheDir; double characterPixelWidth; double characterPixelHeight; uint32_t logoPixelWidth; uint32_t logoPixelHeight; uint32_t logoCharacterHeight; uint32_t logoCharacterWidth; } FFLogoRequestData; typedef struct FFIMData { void* library; void*(*resizeFunc)(const void* image, size_t width, size_t height, void* exceptionInfo); } FFIMData; FFLogoImageResult ffLogoPrintImageImpl(FFLogoRequestData* requestData, const FFIMData* imData); #endif #ifdef FF_HAVE_IMAGEMAGICK7 FFLogoImageResult ffLogoPrintImageIM7(FFLogoRequestData* requestData); #endif #ifdef FF_HAVE_IMAGEMAGICK6 #include FFLogoImageResult ffLogoPrintImageIM6(FFLogoRequestData* requestData); #endif ================================================ FILE: src/logo/logo.c ================================================ #include "logo/logo.h" #include "common/io.h" #include "common/printing.h" #include "common/processing.h" #include "common/textModifier.h" #include "common/stringUtils.h" #include "detection/media/media.h" #include "detection/os/os.h" #include "detection/terminalshell/terminalshell.h" #include #include #include static bool ffLogoPrintCharsRaw(const char* data, size_t length, bool printError) { FFOptionsLogo* options = &instance.config.logo; FF_STRBUF_AUTO_DESTROY buf = ffStrbufCreate(); if (!options->width || !options->height) { if (options->position == FF_LOGO_POSITION_LEFT) { ffStrbufAppendF(&buf, "\e[2J\e[3J\e[%u;%uH", (unsigned) options->paddingTop + 1, (unsigned) options->paddingLeft + 1 ); } else if (options->position == FF_LOGO_POSITION_TOP) { ffStrbufAppendNC(&buf, options->paddingTop, '\n'); ffStrbufAppendNC(&buf, options->paddingLeft, ' '); } else if (options->position == FF_LOGO_POSITION_RIGHT) { if (!options->width) { if (printError) fputs("Logo (image-raw): Must set logo width when using position right\n", stderr); return false; } ffStrbufAppendF(&buf, "\e[2J\e[3J\e[%u;9999999H\e[%uD", (unsigned) options->paddingTop + 1, (unsigned) options->paddingRight + options->width); } ffStrbufAppendNS(&buf, (uint32_t) length, data); ffWriteFDBuffer(FFUnixFD2NativeFD(STDOUT_FILENO), &buf); if (options->position == FF_LOGO_POSITION_LEFT || options->position == FF_LOGO_POSITION_RIGHT) { uint16_t X = 0, Y = 0; // Windows Terminal doesn't report `\e` for some reason const char* error = ffGetTerminalResponse("\e[6n", 2, "%*[^0-9]%hu;%huR", &Y, &X); // %*[^0-9]: ignore optional \e[ if (error) { if (printError) fprintf(stderr, "\nLogo (image-raw): fail to query cursor position: %s\n", error); return true; } if (options->position == FF_LOGO_POSITION_LEFT) { if (options->width + options->paddingLeft > X) X = (uint16_t) (options->width + options->paddingLeft); instance.state.logoWidth = X + instance.config.logo.paddingRight - 1; } instance.state.logoHeight = Y; fputs("\e[H", stdout); } else if (options->position == FF_LOGO_POSITION_TOP) { instance.state.logoWidth = instance.state.logoHeight = 0; ffPrintCharTimes('\n', options->paddingRight); } } else { ffStrbufAppendNC(&buf, options->paddingTop, '\n'); if (options->position == FF_LOGO_POSITION_RIGHT) ffStrbufAppendF(&buf, "\e[9999999C\e[%uD", (unsigned) options->paddingRight + options->width); else if (options->paddingLeft) ffStrbufAppendF(&buf, "\e[%uC", (unsigned) options->paddingLeft); ffStrbufAppendNS(&buf, (uint32_t) length, data); ffStrbufAppendC(&buf, '\n'); if (options->position == FF_LOGO_POSITION_LEFT) { instance.state.logoWidth = options->width + options->paddingLeft + options->paddingRight; instance.state.logoHeight = options->paddingTop + options->height; ffStrbufAppendF(&buf, "\e[%uA", (unsigned) instance.state.logoHeight); } else if (options->position == FF_LOGO_POSITION_TOP) { instance.state.logoWidth = instance.state.logoHeight = 0; ffStrbufAppendNC(&buf, options->paddingRight, '\n'); } else if (options->position == FF_LOGO_POSITION_RIGHT) { instance.state.logoWidth = instance.state.logoHeight = 0; ffStrbufAppendF(&buf, "\e[%uA", (unsigned) options->height); } ffWriteFDBuffer(FFUnixFD2NativeFD(STDOUT_FILENO), &buf); } return true; } // If result is NULL, calculate logo width // Returns logo height static uint32_t logoAppendChars(const char* data, bool doColorReplacement, FFstrbuf* result) { FFOptionsLogo* options = &instance.config.logo; uint32_t currentlineLength = options->type == FF_LOGO_TYPE_IMAGE_CHAFA ? 0 : options->width; // For chafa, unit of options->width is pixels uint32_t logoHeight = 0; if (result) { if (options->position != FF_LOGO_POSITION_RIGHT) ffStrbufAppendNC(result, options->paddingLeft, ' '); else ffStrbufAppendF(result, "\e[9999999C\e[%dD", options->paddingRight + instance.state.logoWidth); } while(*data != '\0') { //We are at the end of a line. Print paddings and update max line length if(*data == '\n' || (*data == '\r' && *(data + 1) == '\n')) { //We have \r\n, skip the \r if(*data == '\r') ++data; if(result) ffStrbufAppendC(result, '\n'); ++data; if (result) { if (options->position != FF_LOGO_POSITION_RIGHT) ffStrbufAppendNC(result, options->paddingLeft, ' '); else ffStrbufAppendF(result, "\e[9999999C\e[%dD", options->paddingRight + instance.state.logoWidth); } if(currentlineLength > instance.state.logoWidth) instance.state.logoWidth = currentlineLength; currentlineLength = 0; ++logoHeight; continue; } //Always print tabs as 4 spaces, to have consistent spacing if(*data == '\t') { if(result) ffStrbufAppendNC(result, 4, ' '); ++data; continue; } //We have an escape sequence directly as bytes. We print it, but don't increase the line length if(*data == '\e' && *(data + 1) == '[') { const char* start = data; if(result) ffStrbufAppendS(result, "\e["); data += 2; while(ffCharIsDigit(*data) || *data == ';') { if(result) ffStrbufAppendC(result, *data); // number ++data; } //We have a valid control sequence, print it and continue with next char if(isascii(*data)) { if(result) ffStrbufAppendC(result, *data); // single letter, end of control sequence ++data; continue; } //Invalid control sequence, try to get most accurate length currentlineLength += (uint32_t) (data - start - 1); //-1 for \033 which for sure doesn't take any space //Don't continue here, print the char after the letters with the unicode printing } //We have a fastfetch color placeholder. Replace it with the esacape sequence, don't increase the line length if(doColorReplacement && *data == '$') { ++data; //If we have $$, or $\0, print it as single $ if(*data == '$' || *data == '\0') { if(result) ffStrbufAppendC(result, '$'); ++currentlineLength; ++data; continue; } if(!instance.config.display.pipe) { //Map the number to an array index, so that '1' -> 0, '2' -> 1, etc. int index = *data - '1'; //If the index is valid, print the color. Otherwise continue as normal if(index < 0 || index >= FASTFETCH_LOGO_MAX_COLORS) { if(result) ffStrbufAppendC(result, '$'); ++currentlineLength; //Don't continue here, we want to print the current char as unicode } else { if(result) ffStrbufAppendF(result, "\e[%sm", options->colors[index].chars); ++data; continue; } } else { ++data; continue; } } //Do the printing, respecting unicode ++currentlineLength; int codepoint = (unsigned char) *data; uint8_t bytes; if(codepoint <= 127) bytes = 1; else if((codepoint & 0xE0) == 0xC0) bytes = 2; else if((codepoint & 0xF0) == 0xE0) bytes = 3; else if((codepoint & 0xF8) == 0xF0) bytes = 4; else bytes = 1; //Invalid utf8, print it as is, byte by byte for(uint8_t i = 0; i < bytes; ++i) { if(*data == '\0') break; if(result) ffStrbufAppendC(result, *data); ++data; } } //Happens if the last line is the longest if(currentlineLength > instance.state.logoWidth) instance.state.logoWidth = currentlineLength; return options->type != FF_LOGO_TYPE_IMAGE_CHAFA && options->height > logoHeight ? options->height : logoHeight; } void ffLogoPrintChars(const char* data, bool doColorReplacement) { FFOptionsLogo* options = &instance.config.logo; if (options->position == FF_LOGO_POSITION_RIGHT) logoAppendChars(data, doColorReplacement, NULL); FF_STRBUF_AUTO_DESTROY result = ffStrbufCreateA(2048); if (!instance.config.display.pipe && instance.config.display.brightColor) ffStrbufAppendS(&result, FASTFETCH_TEXT_MODIFIER_BOLT); ffStrbufAppendNC(&result, options->paddingTop, '\n'); //Use logoColor[0] as the default color if(doColorReplacement && !instance.config.display.pipe) ffStrbufAppendF(&result, "\e[%sm", options->colors[0].chars); instance.state.logoHeight = options->paddingTop + logoAppendChars(data, doColorReplacement, &result); if(!instance.config.display.pipe) ffStrbufAppendS(&result, FASTFETCH_TEXT_MODIFIER_RESET); if(options->position == FF_LOGO_POSITION_LEFT) { instance.state.logoWidth += options->paddingLeft + options->paddingRight; //Go to the leftmost position and go up the height ffStrbufAppendF(&result, "\e[1G\e[%uA", instance.state.logoHeight); } else if(options->position == FF_LOGO_POSITION_RIGHT) { instance.state.logoWidth = 0; //Go to the leftmost position and go up the height ffStrbufAppendF(&result, "\e[1G\e[%uA", instance.state.logoHeight); } else if (options->position == FF_LOGO_POSITION_TOP) { instance.state.logoWidth = instance.state.logoHeight = 0; ffStrbufAppendNC(&result, options->paddingRight, '\n'); } ffWriteFDBuffer(FFUnixFD2NativeFD(STDOUT_FILENO), &result); } static void logoApplyColors(const FFlogo* logo, bool replacement) { if(instance.config.display.colorTitle.length == 0) ffStrbufAppendS(&instance.config.display.colorTitle, logo->colorTitle ?: logo->colors[0]); if(instance.config.display.colorKeys.length == 0) ffStrbufAppendS(&instance.config.display.colorKeys, logo->colorKeys ?: logo->colors[1]); if (replacement) { FFOptionsLogo* options = &instance.config.logo; const char* const* colors = logo->colors; for(int i = 0; *colors != NULL && i < FASTFETCH_LOGO_MAX_COLORS; i++, colors++) { if(options->colors[i].length == 0) ffStrbufAppendS(&options->colors[i], *colors); } } } static bool logoHasName(const FFlogo* logo, const FFstrbuf* name, bool small) { for( const char* const* logoName = logo->names; *logoName != NULL && logoName <= &logo->names[FASTFETCH_LOGO_MAX_NAMES]; ++logoName ) { if(small) { uint32_t logoNameLength = (uint32_t) (strlen(*logoName) - strlen("_small")); if(name->length == logoNameLength && strncasecmp(*logoName, name->chars, logoNameLength) == 0) return true; } if(ffStrbufIgnCaseEqualS(name, *logoName)) return true; } return false; } static const FFlogo* logoGetBuiltin(const FFstrbuf* name, FFLogoSize size) { if (name->length == 0 || !isalpha(name->chars[0])) return NULL; for(const FFlogo* logo = ffLogoBuiltins[toupper(name->chars[0]) - 'A']; *logo->names; ++logo) { switch (size) { // Never use alternate logos case FF_LOGO_SIZE_NORMAL: if(logo->type != FF_LOGO_LINE_TYPE_NORMAL) continue; break; case FF_LOGO_SIZE_SMALL: if(logo->type != FF_LOGO_LINE_TYPE_SMALL_BIT) continue; break; default: break; } if(logoHasName(logo, name, size == FF_LOGO_SIZE_SMALL)) return logo; } return NULL; } static const FFlogo* logoGetBuiltinDetected(FFLogoSize size) { const FFOSResult* os = ffDetectOS(); const FFlogo* logo = logoGetBuiltin(&os->id, size); if(logo != NULL) return logo; logo = logoGetBuiltin(&os->name, size); if(logo != NULL) return logo; if (ffStrbufContainC(&os->idLike, ' ')) { FF_STRBUF_AUTO_DESTROY buf = ffStrbufCreate(); for ( uint32_t start = 0, end = ffStrbufFirstIndexC(&os->idLike, ' '); true; start = end + 1, end = ffStrbufNextIndexC(&os->idLike, start, ' ') ) { ffStrbufSetNS(&buf, end - start, os->idLike.chars + start); logo = logoGetBuiltin(&buf, size); if(logo != NULL) return logo; if (end >= os->idLike.length) break; } } else { logo = logoGetBuiltin(&os->idLike, size); if(logo != NULL) return logo; } logo = logoGetBuiltin(&instance.state.platform.sysinfo.name, size); if(logo != NULL) return logo; return &ffLogoUnknown; } static void logoPrintStruct(const FFlogo* logo) { logoApplyColors(logo, true); ffLogoPrintChars(logo->lines, true); } static void logoPrintNone(void) { if (!instance.config.display.pipe) logoApplyColors(logoGetBuiltinDetected(FF_LOGO_SIZE_NORMAL), false); instance.state.logoHeight = 0; instance.state.logoWidth = 0; } static bool logoPrintBuiltinIfExists(const FFstrbuf* name, FFLogoSize size) { if(name->chars[0] == '~' || name->chars[0] == '.' || name->chars[0] == '/' #if _WIN32 || (ffCharIsEnglishAlphabet(name->chars[0]) && name->chars[1] == ':') // Windows drive letter #endif ) return false; // Paths if(ffStrbufIgnCaseEqualS(name, "none")) { logoPrintNone(); return true; } const FFlogo* logo = ffLogoGetBuiltinForName(name, size); if(logo == NULL) return false; logoPrintStruct(logo); return true; } static inline void logoPrintDetected(FFLogoSize size) { logoPrintStruct(logoGetBuiltinDetected(size)); } static bool logoPrintData(bool doColorReplacement, FFstrbuf* source) { if(source->length == 0) return false; logoApplyColors(logoGetBuiltinDetected(FF_LOGO_SIZE_NORMAL), doColorReplacement); ffLogoPrintChars(source->chars, doColorReplacement); return true; } static bool updateLogoPath(void) { FFOptionsLogo* options = &instance.config.logo; if(ffPathExists(options->source.chars, FF_PATHTYPE_FILE)) return true; if (ffStrbufEqualS(&options->source, "-")) // stdin return true; if (ffStrbufIgnCaseEqualS(&options->source, "media-cover")) { const FFMediaResult* media = ffDetectMedia(true); if (media->cover.length == 0) return false; ffStrbufSet(&options->source, &media->cover); return true; } FF_STRBUF_AUTO_DESTROY fullPath = ffStrbufCreateA(128); if (ffPathExpandEnv(options->source.chars, &fullPath) && ffPathExists(fullPath.chars, FF_PATHTYPE_FILE)) { ffStrbufDestroy(&options->source); ffStrbufInitMove(&options->source, &fullPath); return true; } FF_LIST_FOR_EACH(FFstrbuf, dataDir, instance.state.platform.dataDirs) { //We need to copy it, because multiple threads might be using dataDirs at the same time ffStrbufSet(&fullPath, dataDir); ffStrbufAppendS(&fullPath, "fastfetch/logos/"); ffStrbufAppend(&fullPath, &options->source); if(ffPathExists(fullPath.chars, FF_PATHTYPE_FILE)) { ffStrbufDestroy(&options->source); ffStrbufInitMove(&options->source, &fullPath); return true; } } return false; } static bool logoPrintFileIfExists(bool doColorReplacement, bool raw) { FFOptionsLogo* options = &instance.config.logo; FF_STRBUF_AUTO_DESTROY content = ffStrbufCreate(); if(ffStrbufEqualS(&options->source, "-") ? !ffAppendFDBuffer(FFUnixFD2NativeFD(STDIN_FILENO), &content) : !ffAppendFileBuffer(options->source.chars, &content) ) { if (instance.config.display.showErrors) fprintf(stderr, "Logo: Failed to load file content from logo source: %s\n", options->source.chars); return false; } logoApplyColors(logoGetBuiltinDetected(FF_LOGO_SIZE_NORMAL), doColorReplacement); if(raw) return ffLogoPrintCharsRaw(content.chars, content.length, instance.config.display.showErrors); ffLogoPrintChars(content.chars, doColorReplacement); return true; } static bool logoPrintImageIfExists(FFLogoType logo, bool printError) { if(!ffLogoPrintImageIfExists(logo, printError)) return false; logoApplyColors(logoGetBuiltinDetected(FF_LOGO_SIZE_NORMAL), false); return true; } static bool logoTryKnownType(void) { FFOptionsLogo* options = &instance.config.logo; if(options->type == FF_LOGO_TYPE_NONE) { logoPrintNone(); return true; } if(options->type == FF_LOGO_TYPE_BUILTIN) return logoPrintBuiltinIfExists(&options->source, FF_LOGO_SIZE_UNKNOWN); if(options->type == FF_LOGO_TYPE_SMALL) return logoPrintBuiltinIfExists(&options->source, FF_LOGO_SIZE_SMALL); if(options->type == FF_LOGO_TYPE_DATA) return logoPrintData(true, &options->source); if(options->type == FF_LOGO_TYPE_DATA_RAW) return logoPrintData(false, &options->source); if(options->type == FF_LOGO_TYPE_COMMAND_RAW) { FF_STRBUF_AUTO_DESTROY source = ffStrbufCreate(); const char* error = ffProcessAppendStdOut(&source, (char* const[]){ #ifdef _WIN32 "cmd.exe", "/c", #else "/bin/sh", "-c", #endif options->source.chars, NULL }); if (error) { if (instance.config.display.showErrors) fprintf(stderr, "Logo: failed to execute command `%s`: %s\n", options->source.chars, error); return false; } return logoPrintData(false, &source); } //We sure have a file, resolve relative paths if (!updateLogoPath()) { if (instance.config.display.showErrors) fprintf(stderr, "Logo: Failed to resolve logo source: %s\n", options->source.chars); return false; } if(options->type == FF_LOGO_TYPE_FILE) return logoPrintFileIfExists(true, false); if(options->type == FF_LOGO_TYPE_FILE_RAW) return logoPrintFileIfExists(false, false); if(options->type == FF_LOGO_TYPE_IMAGE_RAW) return logoPrintFileIfExists(false, true); return logoPrintImageIfExists(options->type, instance.config.display.showErrors); } void ffLogoPrint(void) { const FFOptionsLogo* options = &instance.config.logo; if (options->type == FF_LOGO_TYPE_NONE) { logoPrintNone(); return; } //If the source is not set, we can directly print the detected logo. if(options->source.length == 0) { logoPrintDetected(options->type == FF_LOGO_TYPE_SMALL ? FF_LOGO_SIZE_SMALL : FF_LOGO_SIZE_NORMAL); return; } //If the source and source type is set to something else than auto, always print with the set type. if(options->source.length > 0 && options->type != FF_LOGO_TYPE_AUTO) { if(!logoTryKnownType()) { if (instance.config.display.showErrors) { // Image logo should have been handled if(options->type == FF_LOGO_TYPE_BUILTIN || options->type == FF_LOGO_TYPE_SMALL) fprintf(stderr, "Logo: Failed to load %s logo: %s\n", options->type == FF_LOGO_TYPE_BUILTIN ? "builtin" : "builtin small", options->source.chars); } logoPrintDetected(FF_LOGO_SIZE_UNKNOWN); } return; } //If source matches the name of a builtin logo, print it and return. if(logoPrintBuiltinIfExists(&options->source, FF_LOGO_SIZE_UNKNOWN)) return; //Make sure the logo path is set correctly. if (updateLogoPath()) { if (ffStrbufEndsWithIgnCaseS(&options->source, ".raw")) { if(logoPrintFileIfExists(false, true)) return; } if (!ffStrbufEndsWithIgnCaseS(&options->source, ".txt")) { const FFTerminalResult* terminal = ffDetectTerminal(); //Terminal emulators that support kitty graphics protocol. bool supportsKitty = ffStrbufIgnCaseEqualS(&terminal->processName, "kitty") || ffStrbufIgnCaseEqualS(&terminal->processName, "konsole") || ffStrbufIgnCaseEqualS(&terminal->processName, "wezterm") || ffStrbufIgnCaseEqualS(&terminal->processName, "wayst") || ffStrbufIgnCaseEqualS(&terminal->processName, "ghostty") || #ifdef __APPLE__ ffStrbufIgnCaseEqualS(&terminal->processName, "WarpTerminal") || #else ffStrbufIgnCaseEqualS(&terminal->processName, "warp") || #endif false; //Try to load the logo as an image. If it succeeds, print it and return. if(logoPrintImageIfExists(supportsKitty ? FF_LOGO_TYPE_IMAGE_KITTY : FF_LOGO_TYPE_IMAGE_CHAFA, false)) return; } //Try to load the logo as a file. If it succeeds, print it and return. if(logoPrintFileIfExists(true, false)) return; } else { if (instance.config.display.showErrors) fprintf(stderr, "Logo: Failed to resolve logo source: %s\n", options->source.chars); } logoPrintDetected(FF_LOGO_SIZE_UNKNOWN); } void ffLogoPrintLine(void) { if(instance.state.logoWidth > 0) printf("\033[%uC", instance.state.logoWidth); if (instance.state.dynamicInterval > 0) fputs("\033[K", stdout); // Clear to the end of the line ++instance.state.keysHeight; } void ffLogoPrintRemaining(void) { if (instance.state.keysHeight <= instance.state.logoHeight) ffPrintCharTimes('\n', instance.state.logoHeight - instance.state.keysHeight + 1); instance.state.keysHeight = instance.state.logoHeight + 1; } void ffLogoBuiltinPrint(void) { FFOptionsLogo* options = &instance.config.logo; options->position = FF_LOGO_POSITION_TOP; options->paddingRight = 2; // empty line after logo printing FF_STRBUF_AUTO_DESTROY buf = ffStrbufCreate(); for(uint8_t ch = 0; ch < 26; ++ch) { for(const FFlogo* logo = ffLogoBuiltins[ch]; *logo->names; ++logo) { if (instance.config.display.pipe) ffStrbufSetF(&buf, "%s:\n", logo->names[0]); else ffStrbufSetF(&buf, "\e[%sm%s:\e[0m\n", logo->colors[0], logo->names[0]); ffWriteFDBuffer(FFUnixFD2NativeFD(STDOUT_FILENO), &buf); logoPrintStruct(logo); for(uint8_t i = 0; i < FASTFETCH_LOGO_MAX_COLORS; i++) ffStrbufClear(&options->colors[i]); } } } void ffLogoBuiltinList(void) { uint32_t counter = 0; for(uint8_t ch = 0; ch < 26; ++ch) { for(const FFlogo* logo = ffLogoBuiltins[ch]; *logo->names; ++logo) { ++counter; printf("%u)%s ", counter, counter < 10 ? " " : ""); for( const char* const* names = logo->names; *names != NULL && names <= &logo->names[FASTFETCH_LOGO_MAX_NAMES]; ++names ) printf("\"%s\" ", *names); putchar('\n'); } } } void ffLogoBuiltinListAutocompletion(void) { for(uint8_t ch = 0; ch < 26; ++ch) { for(const FFlogo* logo = ffLogoBuiltins[ch]; *logo->names; ++logo) printf("%s\n", logo->names[0]); } } const FFlogo* ffLogoGetBuiltinForName(const FFstrbuf* name, FFLogoSize size) { return ffStrbufEqualS(name, "?") ? &ffLogoUnknown : logoGetBuiltin(name, size); } const FFlogo* ffLogoGetBuiltinDetected(FFLogoSize size) { return logoGetBuiltinDetected(size); } ================================================ FILE: src/logo/logo.h ================================================ #pragma once #include "fastfetch.h" typedef enum __attribute__((__packed__)) FFLogoLineType { FF_LOGO_LINE_TYPE_NORMAL = 0, FF_LOGO_LINE_TYPE_SMALL_BIT = 1 << 0, // The names of small logo must end with `_small` or `-small` FF_LOGO_LINE_TYPE_ALTER_BIT = 1 << 1, FF_LOGO_LINE_TYPE_FORCE_UNSIGNED = UINT8_MAX, } FFLogoLineType; typedef enum __attribute__((__packed__)) FFLogoSize { FF_LOGO_SIZE_UNKNOWN, FF_LOGO_SIZE_NORMAL, FF_LOGO_SIZE_SMALL, } FFLogoSize; typedef struct FFlogo { const char* lines; const char* names[FASTFETCH_LOGO_MAX_NAMES]; const char* colors[FASTFETCH_LOGO_MAX_COLORS]; const char* colorKeys; const char* colorTitle; FFLogoLineType type; } FFlogo; //logo.c void ffLogoPrint(void); void ffLogoPrintChars(const char* data, bool doColorReplacement); void ffLogoPrintLine(void); void ffLogoPrintRemaining(void); void ffLogoBuiltinPrint(void); void ffLogoBuiltinList(void); void ffLogoBuiltinListAutocompletion(void); const FFlogo* ffLogoGetBuiltinForName(const FFstrbuf* name, FFLogoSize size); const FFlogo* ffLogoGetBuiltinDetected(FFLogoSize size); //builtin.c extern const FFlogo* ffLogoBuiltins[]; extern const FFlogo ffLogoUnknown; //image/image.c bool ffLogoPrintImageIfExists(FFLogoType type, bool printError); ================================================ FILE: src/modules/battery/battery.c ================================================ #include "common/printing.h" #include "common/jsonconfig.h" #include "common/percent.h" #include "common/duration.h" #include "common/temps.h" #include "common/stringUtils.h" #include "detection/battery/battery.h" #include "modules/battery/battery.h" static void printBattery(FFBatteryOptions* options, FFBatteryResult* result, uint8_t index) { FF_STRBUF_AUTO_DESTROY key = ffStrbufCreate(); if (options->moduleArgs.key.length == 0) { if (result->modelName.length > 0) ffStrbufSetF(&key, "%s (%s)", FF_BATTERY_MODULE_NAME, result->modelName.chars); else ffStrbufSetS(&key, FF_BATTERY_MODULE_NAME); } else { ffStrbufClear(&key); FF_PARSE_FORMAT_STRING_CHECKED(&key, &options->moduleArgs.key, ((FFformatarg[]) { FF_ARG(index, "index"), FF_ARG(result->modelName, "name"), FF_ARG(options->moduleArgs.keyIcon, "icon"), })); } FFPercentageTypeFlags percentType = options->percent.type == 0 ? instance.config.display.percentType : options->percent.type; if(options->moduleArgs.outputFormat.length == 0) { ffPrintLogoAndKey(key.chars, 0, &options->moduleArgs, FF_PRINT_TYPE_NO_CUSTOM_KEY); FF_STRBUF_AUTO_DESTROY str = ffStrbufCreate(); bool showStatus = !(percentType & FF_PERCENTAGE_TYPE_HIDE_OTHERS_BIT) && result->status.length > 0 && ffStrbufIgnCaseCompS(&result->status, "Unknown") != 0; if(result->capacity >= 0) { if(percentType & FF_PERCENTAGE_TYPE_BAR_BIT) { ffPercentAppendBar(&str, result->capacity, options->percent, &options->moduleArgs); } if(percentType & FF_PERCENTAGE_TYPE_NUM_BIT) { if(str.length > 0) ffStrbufAppendC(&str, ' '); ffPercentAppendNum(&str, result->capacity, options->percent, str.length > 0, &options->moduleArgs); } if(result->timeRemaining > 0) { if(str.length > 0) ffStrbufAppendS(&str, " ("); ffDurationAppendNum((uint32_t) result->timeRemaining, &str); ffStrbufAppendS(&str, " remaining)"); } } if(showStatus) { if(str.length > 0) ffStrbufAppendF(&str, " [%s]", result->status.chars); else ffStrbufAppend(&str, &result->status); } if(result->temperature != FF_BATTERY_TEMP_UNSET) { if(str.length > 0) ffStrbufAppendS(&str, " - "); ffTempsAppendNum(result->temperature, &str, options->tempConfig, &options->moduleArgs); } ffStrbufPutTo(&str, stdout); } else { uint32_t timeRemaining = result->timeRemaining < 0 ? 0 : (uint32_t) result->timeRemaining; uint32_t seconds = timeRemaining % 60; timeRemaining /= 60; uint32_t minutes = timeRemaining % 60; timeRemaining /= 60; uint32_t hours = timeRemaining % 24; timeRemaining /= 24; uint32_t days = timeRemaining; FF_STRBUF_AUTO_DESTROY capacityNum = ffStrbufCreate(); if(percentType & FF_PERCENTAGE_TYPE_NUM_BIT) ffPercentAppendNum(&capacityNum, result->capacity, options->percent, false, &options->moduleArgs); FF_STRBUF_AUTO_DESTROY capacityBar = ffStrbufCreate(); if(percentType & FF_PERCENTAGE_TYPE_BAR_BIT) ffPercentAppendBar(&capacityBar, result->capacity, options->percent, &options->moduleArgs); FF_STRBUF_AUTO_DESTROY tempStr = ffStrbufCreate(); ffTempsAppendNum(result->temperature, &tempStr, options->tempConfig, &options->moduleArgs); FF_STRBUF_AUTO_DESTROY timeStr = ffStrbufCreate(); if (result->timeRemaining > 0) ffDurationAppendNum((uint32_t) result->timeRemaining, &timeStr); FF_PRINT_FORMAT_CHECKED(key.chars, 0, &options->moduleArgs, FF_PRINT_TYPE_NO_CUSTOM_KEY, ((FFformatarg[]) { FF_ARG(result->manufacturer, "manufacturer"), FF_ARG(result->modelName, "model-name"), FF_ARG(result->technology, "technology"), FF_ARG(capacityNum, "capacity"), FF_ARG(result->status, "status"), FF_ARG(tempStr, "temperature"), FF_ARG(result->cycleCount, "cycle-count"), FF_ARG(result->serial, "serial"), FF_ARG(result->manufactureDate, "manufacture-date"), FF_ARG(capacityBar, "capacity-bar"), FF_ARG(days, "time-days"), FF_ARG(hours, "time-hours"), FF_ARG(minutes, "time-minutes"), FF_ARG(seconds, "time-seconds"), FF_ARG(timeStr, "time-formatted"), })); } } bool ffPrintBattery(FFBatteryOptions* options) { FF_LIST_AUTO_DESTROY results = ffListCreate(sizeof(FFBatteryResult)); const char* error = ffDetectBattery(options, &results); if (error) { ffPrintError(FF_BATTERY_MODULE_NAME, 0, &options->moduleArgs, FF_PRINT_TYPE_DEFAULT, "%s", error); return false; } if(results.length == 0) { ffPrintError(FF_BATTERY_MODULE_NAME, 0, &options->moduleArgs, FF_PRINT_TYPE_DEFAULT, "%s", "No batteries found"); return false; } for(uint32_t i = 0; i < results.length; i++) { FFBatteryResult* result = FF_LIST_GET(FFBatteryResult, results, i); printBattery(options, result, results.length == 1 ? 0 : (uint8_t) (i + 1)); } FF_LIST_FOR_EACH(FFBatteryResult, result, results) { ffStrbufDestroy(&result->manufacturer); ffStrbufDestroy(&result->modelName); ffStrbufDestroy(&result->technology); ffStrbufDestroy(&result->status); ffStrbufDestroy(&result->serial); ffStrbufDestroy(&result->manufactureDate); } return true; } void ffParseBatteryJsonObject(FFBatteryOptions* options, yyjson_val* module) { yyjson_val *key, *val; size_t idx, max; yyjson_obj_foreach(module, idx, max, key, val) { if (ffJsonConfigParseModuleArgs(key, val, &options->moduleArgs)) 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; if (ffPercentParseJsonObject(key, val, &options->percent)) continue; ffPrintError(FF_BATTERY_MODULE_NAME, 0, &options->moduleArgs, FF_PRINT_TYPE_DEFAULT, "Unknown JSON key %s", unsafe_yyjson_get_str(key)); } } 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); } bool ffGenerateBatteryJsonResult(FFBatteryOptions* options, yyjson_mut_doc* doc, yyjson_mut_val* module) { FF_LIST_AUTO_DESTROY results = ffListCreate(sizeof(FFBatteryResult)); const char* error = ffDetectBattery(options, &results); if (error) { yyjson_mut_obj_add_str(doc, module, "error", error); return false; } yyjson_mut_val* arr = yyjson_mut_obj_add_arr(doc, module, "result"); FF_LIST_FOR_EACH(FFBatteryResult, battery, results) { yyjson_mut_val* obj = yyjson_mut_arr_add_obj(doc, arr); yyjson_mut_obj_add_real(doc, obj, "capacity", battery->capacity); yyjson_mut_obj_add_strbuf(doc, obj, "manufacturer", &battery->manufacturer); yyjson_mut_obj_add_strbuf(doc, obj, "manufactureDate", &battery->manufactureDate); yyjson_mut_obj_add_strbuf(doc, obj, "modelName", &battery->modelName); yyjson_mut_obj_add_strbuf(doc, obj, "status", &battery->status); yyjson_mut_obj_add_strbuf(doc, obj, "technology", &battery->technology); yyjson_mut_obj_add_strbuf(doc, obj, "serial", &battery->serial); if (battery->temperature != FF_BATTERY_TEMP_UNSET) yyjson_mut_obj_add_real(doc, obj, "temperature", battery->temperature); else yyjson_mut_obj_add_null(doc, obj, "temperature"); yyjson_mut_obj_add_uint(doc, obj, "cycleCount", battery->cycleCount); if (battery->timeRemaining > 0) yyjson_mut_obj_add_int(doc, obj, "timeRemaining", battery->timeRemaining); else yyjson_mut_obj_add_null(doc, obj, "timeRemaining"); } FF_LIST_FOR_EACH(FFBatteryResult, battery, results) { ffStrbufDestroy(&battery->manufacturer); ffStrbufDestroy(&battery->manufactureDate); ffStrbufDestroy(&battery->modelName); ffStrbufDestroy(&battery->technology); ffStrbufDestroy(&battery->status); ffStrbufDestroy(&battery->serial); } return true; } void ffInitBatteryOptions(FFBatteryOptions* options) { ffOptionInitModuleArg(&options->moduleArgs, ""); options->temp = false; options->tempConfig = (FFColorRangeConfig) { 60, 80 }; options->percent = (FFPercentageModuleConfig) { 50, 20, 0 }; #ifdef _WIN32 options->useSetupApi = false; #endif } void ffDestroyBatteryOptions(FFBatteryOptions* options) { ffOptionDestroyModuleArg(&options->moduleArgs); } FFModuleBaseInfo ffBatteryModuleInfo = { .name = FF_BATTERY_MODULE_NAME, .description = "Print battery capacity, status, etc", .initOptions = (void*) ffInitBatteryOptions, .destroyOptions = (void*) ffDestroyBatteryOptions, .parseJsonObject = (void*) ffParseBatteryJsonObject, .printModule = (void*) ffPrintBattery, .generateJsonResult = (void*) ffGenerateBatteryJsonResult, .generateJsonConfig = (void*) ffGenerateBatteryJsonConfig, .formatArgs = FF_FORMAT_ARG_LIST(((FFModuleFormatArg[]) { {"Battery manufacturer", "manufacturer"}, {"Battery model name", "model-name"}, {"Battery technology", "technology"}, {"Battery capacity (percentage num)", "capacity"}, {"Battery status", "status"}, {"Battery temperature (formatted)", "temperature"}, {"Battery cycle count", "cycle-count"}, {"Battery serial number", "serial"}, {"Battery manufactor date", "manufacture-date"}, {"Battery capacity (percentage bar)", "capacity-bar"}, {"Battery time remaining days", "time-days"}, {"Battery time remaining hours", "time-hours"}, {"Battery time remaining minutes", "time-minutes"}, {"Battery time remaining seconds", "time-seconds"}, {"Battery time remaining (formatted)", "time-formatted"}, })) }; ================================================ FILE: src/modules/battery/battery.h ================================================ #pragma once #include "option.h" #define FF_BATTERY_MODULE_NAME "Battery" bool ffPrintBattery(FFBatteryOptions* options); void ffInitBatteryOptions(FFBatteryOptions* options); void ffDestroyBatteryOptions(FFBatteryOptions* options); extern FFModuleBaseInfo ffBatteryModuleInfo; ================================================ FILE: src/modules/battery/option.h ================================================ #pragma once #include "common/option.h" #include "common/percent.h" typedef struct FFBatteryOptions { FFModuleArgs moduleArgs; 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"); ================================================ FILE: src/modules/bios/bios.c ================================================ #include "common/printing.h" #include "common/jsonconfig.h" #include "common/stringUtils.h" #include "detection/bios/bios.h" #include "modules/bios/bios.h" bool ffPrintBios(FFBiosOptions* options) { bool success = false; FFBiosResult bios; ffStrbufInit(&bios.date); ffStrbufInit(&bios.release); ffStrbufInit(&bios.vendor); ffStrbufInit(&bios.version); ffStrbufInit(&bios.type); const char* error = ffDetectBios(&bios); FF_STRBUF_AUTO_DESTROY key = ffStrbufCreate(); if(error) { ffPrintError(FF_BIOS_MODULE_NAME, 0, &options->moduleArgs, FF_PRINT_TYPE_DEFAULT, "%s", error); goto exit; } if(bios.version.length == 0) { ffPrintError(FF_BIOS_MODULE_NAME, 0, &options->moduleArgs, FF_PRINT_TYPE_DEFAULT, "bios_version is not set."); goto exit; } if(options->moduleArgs.key.length == 0) { if(bios.type.length == 0) ffStrbufSetStatic(&bios.type, "Unknown"); else if (ffStrbufIgnCaseEqualS(&bios.type, "BIOS")) ffStrbufSetStatic(&bios.type, "Legacy"); ffStrbufSetF(&key, FF_BIOS_MODULE_NAME " (%s)", bios.type.chars); } else { ffStrbufClear(&key); FF_PARSE_FORMAT_STRING_CHECKED(&key, &options->moduleArgs.key, ((FFformatarg[]) { FF_ARG(bios.type, "type"), FF_ARG(options->moduleArgs.keyIcon, "icon"), })); } if(options->moduleArgs.outputFormat.length == 0) { ffPrintLogoAndKey(key.chars, 0, &options->moduleArgs, FF_PRINT_TYPE_NO_CUSTOM_KEY); ffStrbufWriteTo(&bios.version, stdout); if (bios.release.length) printf(" (%s)\n", bios.release.chars); else putchar('\n'); } else { FF_PRINT_FORMAT_CHECKED(key.chars, 0, &options->moduleArgs, FF_PRINT_TYPE_NO_CUSTOM_KEY, ((FFformatarg[]) { FF_ARG(bios.date, "date"), FF_ARG(bios.release, "release"), FF_ARG(bios.vendor, "vendor"), FF_ARG(bios.version, "version"), FF_ARG(bios.type, "type"), })); } success = true; exit: ffStrbufDestroy(&bios.date); ffStrbufDestroy(&bios.release); ffStrbufDestroy(&bios.vendor); ffStrbufDestroy(&bios.version); ffStrbufDestroy(&bios.type); return success; } void ffParseBiosJsonObject(FFBiosOptions* options, yyjson_val* module) { yyjson_val *key, *val; size_t idx, max; yyjson_obj_foreach(module, idx, max, key, val) { if (ffJsonConfigParseModuleArgs(key, val, &options->moduleArgs)) continue; ffPrintError(FF_BIOS_MODULE_NAME, 0, &options->moduleArgs, FF_PRINT_TYPE_DEFAULT, "Unknown JSON key %s", unsafe_yyjson_get_str(key)); } } void ffGenerateBiosJsonConfig(FFBiosOptions* options, yyjson_mut_doc* doc, yyjson_mut_val* module) { ffJsonConfigGenerateModuleArgsConfig(doc, module, &options->moduleArgs); } bool ffGenerateBiosJsonResult(FF_MAYBE_UNUSED FFBiosOptions* options, yyjson_mut_doc* doc, yyjson_mut_val* module) { bool success = false; FFBiosResult bios; ffStrbufInit(&bios.date); ffStrbufInit(&bios.release); ffStrbufInit(&bios.vendor); ffStrbufInit(&bios.version); ffStrbufInit(&bios.type); const char* error = ffDetectBios(&bios); if (error) { yyjson_mut_obj_add_str(doc, module, "error", error); goto exit; } yyjson_mut_val* obj = yyjson_mut_obj_add_obj(doc, module, "result"); yyjson_mut_obj_add_strbuf(doc, obj, "date", &bios.date); yyjson_mut_obj_add_strbuf(doc, obj, "release", &bios.release); yyjson_mut_obj_add_strbuf(doc, obj, "vendor", &bios.vendor); yyjson_mut_obj_add_strbuf(doc, obj, "version", &bios.version); yyjson_mut_obj_add_strbuf(doc, obj, "type", &bios.type); success = true; exit: ffStrbufDestroy(&bios.date); ffStrbufDestroy(&bios.release); ffStrbufDestroy(&bios.vendor); ffStrbufDestroy(&bios.version); ffStrbufDestroy(&bios.type); return success; } void ffInitBiosOptions(FFBiosOptions* options) { ffOptionInitModuleArg(&options->moduleArgs, ""); } void ffDestroyBiosOptions(FFBiosOptions* options) { ffOptionDestroyModuleArg(&options->moduleArgs); } FFModuleBaseInfo ffBiosModuleInfo = { .name = FF_BIOS_MODULE_NAME, .description = "Print information of 1st-stage bootloader (name, version, release date, etc)", .initOptions = (void*) ffInitBiosOptions, .destroyOptions = (void*) ffDestroyBiosOptions, .parseJsonObject = (void*) ffParseBiosJsonObject, .printModule = (void*) ffPrintBios, .generateJsonResult = (void*) ffGenerateBiosJsonResult, .generateJsonConfig = (void*) ffGenerateBiosJsonConfig, .formatArgs = FF_FORMAT_ARG_LIST(((FFModuleFormatArg[]) { {"Bios date", "date"}, {"Bios release", "release"}, {"Bios vendor", "vendor"}, {"Bios version", "version"}, {"Firmware type", "type"}, })) }; ================================================ FILE: src/modules/bios/bios.h ================================================ #pragma once #include "option.h" #define FF_BIOS_MODULE_NAME "BIOS" bool ffPrintBios(FFBiosOptions* options); void ffInitBiosOptions(FFBiosOptions* options); void ffDestroyBiosOptions(FFBiosOptions* options); extern FFModuleBaseInfo ffBiosModuleInfo; ================================================ FILE: src/modules/bios/option.h ================================================ #pragma once #include "common/option.h" typedef struct FFBiosOptions { FFModuleArgs moduleArgs; } FFBiosOptions; static_assert(sizeof(FFBiosOptions) <= FF_OPTION_MAX_SIZE, "FFBiosOptions size exceeds maximum allowed size"); ================================================ FILE: src/modules/bluetooth/bluetooth.c ================================================ #include "common/percent.h" #include "common/printing.h" #include "common/jsonconfig.h" #include "common/stringUtils.h" #include "detection/bluetooth/bluetooth.h" #include "modules/bluetooth/bluetooth.h" static void printDevice(FFBluetoothOptions* options, const FFBluetoothResult* device, uint8_t index) { FFPercentageTypeFlags percentType = options->percent.type == 0 ? instance.config.display.percentType : options->percent.type; if(options->moduleArgs.outputFormat.length == 0) { ffPrintLogoAndKey(FF_BLUETOOTH_MODULE_NAME, index, &options->moduleArgs, FF_PRINT_TYPE_DEFAULT); FF_STRBUF_AUTO_DESTROY buffer = ffStrbufCreate(); bool showBatteryLevel = device->battery > 0 && device->battery <= 100; if (showBatteryLevel && (percentType & FF_PERCENTAGE_TYPE_BAR_BIT)) { ffPercentAppendBar(&buffer, device->battery, options->percent, &options->moduleArgs); ffStrbufAppendC(&buffer, ' '); } if (!(percentType & FF_PERCENTAGE_TYPE_HIDE_OTHERS_BIT)) ffStrbufAppend(&buffer, &device->name); if (showBatteryLevel && (percentType & FF_PERCENTAGE_TYPE_NUM_BIT)) { if (buffer.length) ffStrbufAppendC(&buffer, ' '); ffPercentAppendNum(&buffer, device->battery, options->percent, buffer.length > 0, &options->moduleArgs); } if (!device->connected) ffStrbufAppendS(&buffer, " [disconnected]"); ffStrbufPutTo(&buffer, stdout); } else { FF_STRBUF_AUTO_DESTROY percentageNum = ffStrbufCreate(); if(percentType & FF_PERCENTAGE_TYPE_NUM_BIT) ffPercentAppendNum(&percentageNum, device->battery, options->percent, false, &options->moduleArgs); FF_STRBUF_AUTO_DESTROY percentageBar = ffStrbufCreate(); if(percentType & FF_PERCENTAGE_TYPE_BAR_BIT) ffPercentAppendBar(&percentageBar, device->battery, options->percent, &options->moduleArgs); FF_PRINT_FORMAT_CHECKED(FF_BLUETOOTH_MODULE_NAME, index, &options->moduleArgs, FF_PRINT_TYPE_DEFAULT, ((FFformatarg[]) { FF_ARG(device->name, "name"), FF_ARG(device->address, "address"), FF_ARG(device->type, "type"), FF_ARG(percentageNum, "battery-percentage"), FF_ARG(device->connected, "connected"), FF_ARG(percentageBar, "battery-percentage-bar"), })); } } bool ffPrintBluetooth(FFBluetoothOptions* options) { FF_LIST_AUTO_DESTROY devices = ffListCreate(sizeof (FFBluetoothResult)); const char* error = ffDetectBluetooth(options, &devices); if(error) { ffPrintError(FF_BLUETOOTH_MODULE_NAME, 0, &options->moduleArgs, FF_PRINT_TYPE_DEFAULT, "%s", error); return false; } FF_LIST_AUTO_DESTROY filtered = ffListCreate(sizeof(FFBluetoothResult*)); FF_LIST_FOR_EACH(FFBluetoothResult, device, devices) { if(!device->connected && !options->showDisconnected) continue; *(FFBluetoothResult**)ffListAdd(&filtered) = device; } if(filtered.length == 0) { ffPrintError(FF_BLUETOOTH_MODULE_NAME, 0, &options->moduleArgs, FF_PRINT_TYPE_DEFAULT, "No bluetooth devices found"); } for(uint32_t i = 0; i < filtered.length; i++) { uint8_t index = (uint8_t) (filtered.length == 1 ? 0 : i + 1); printDevice(options, *FF_LIST_GET(FFBluetoothResult*, filtered, i), index); } FF_LIST_FOR_EACH(FFBluetoothResult, device, devices) { ffStrbufDestroy(&device->name); ffStrbufDestroy(&device->type); ffStrbufDestroy(&device->address); } return true; } void ffParseBluetoothJsonObject(FFBluetoothOptions* options, yyjson_val* module) { yyjson_val *key, *val; size_t idx, max; yyjson_obj_foreach(module, idx, max, key, val) { if (ffJsonConfigParseModuleArgs(key, val, &options->moduleArgs)) continue; if (unsafe_yyjson_equals_str(key, "showDisconnected")) { options->showDisconnected = yyjson_get_bool(val); continue; } if (ffPercentParseJsonObject(key, val, &options->percent)) continue; ffPrintError(FF_BLUETOOTH_MODULE_NAME, 0, &options->moduleArgs, FF_PRINT_TYPE_DEFAULT, "Unknown JSON key %s", unsafe_yyjson_get_str(key)); } } void ffGenerateBluetoothJsonConfig(FFBluetoothOptions* options, yyjson_mut_doc* doc, yyjson_mut_val* module) { ffJsonConfigGenerateModuleArgsConfig(doc, module, &options->moduleArgs); yyjson_mut_obj_add_bool(doc, module, "showDisconnected", options->showDisconnected); ffPercentGenerateJsonConfig(doc, module, options->percent); } bool ffGenerateBluetoothJsonResult(FFBluetoothOptions* options, yyjson_mut_doc* doc, yyjson_mut_val* module) { FF_LIST_AUTO_DESTROY results = ffListCreate(sizeof(FFBluetoothResult)); const char* error = ffDetectBluetooth(options, &results); if (error) { yyjson_mut_obj_add_str(doc, module, "error", error); return false; } yyjson_mut_val* arr = yyjson_mut_obj_add_arr(doc, module, "result"); FF_LIST_FOR_EACH(FFBluetoothResult, item, results) { yyjson_mut_val* obj = yyjson_mut_arr_add_obj(doc, arr); yyjson_mut_obj_add_strbuf(doc, obj, "address", &item->address); yyjson_mut_obj_add_uint(doc, obj, "battery", item->battery); yyjson_mut_obj_add_bool(doc, obj, "connected", item->connected); yyjson_mut_obj_add_strbuf(doc, obj, "name", &item->name); yyjson_mut_obj_add_strbuf(doc, obj, "type", &item->type); } FF_LIST_FOR_EACH(FFBluetoothResult, device, results) { ffStrbufDestroy(&device->name); ffStrbufDestroy(&device->type); ffStrbufDestroy(&device->address); } return true; } void ffInitBluetoothOptions(FFBluetoothOptions* options) { ffOptionInitModuleArg(&options->moduleArgs, ""); options->showDisconnected = false; options->percent = (FFPercentageModuleConfig) { 50, 20, 0 }; } void ffDestroyBluetoothOptions(FFBluetoothOptions* options) { ffOptionDestroyModuleArg(&options->moduleArgs); } FFModuleBaseInfo ffBluetoothModuleInfo = { .name = FF_BLUETOOTH_MODULE_NAME, .description = "List (connected) bluetooth devices", .initOptions = (void*) ffInitBluetoothOptions, .destroyOptions = (void*) ffDestroyBluetoothOptions, .parseJsonObject = (void*) ffParseBluetoothJsonObject, .printModule = (void*) ffPrintBluetooth, .generateJsonResult = (void*) ffGenerateBluetoothJsonResult, .generateJsonConfig = (void*) ffGenerateBluetoothJsonConfig, .formatArgs = FF_FORMAT_ARG_LIST(((FFModuleFormatArg[]) { {"Name", "name"}, {"Address", "address"}, {"Type", "type"}, {"Battery percentage number", "battery-percentage"}, {"Is connected", "connected"}, {"Battery percentage bar", "battery-percentage-bar"}, })) }; ================================================ FILE: src/modules/bluetooth/bluetooth.h ================================================ #pragma once #include "option.h" #define FF_BLUETOOTH_MODULE_NAME "Bluetooth" bool ffPrintBluetooth(FFBluetoothOptions* options); void ffInitBluetoothOptions(FFBluetoothOptions* options); void ffDestroyBluetoothOptions(FFBluetoothOptions* options); extern FFModuleBaseInfo ffBluetoothModuleInfo; ================================================ FILE: src/modules/bluetooth/option.h ================================================ #pragma once #include "common/option.h" #include "common/percent.h" typedef struct FFBluetoothOptions { FFModuleArgs moduleArgs; bool showDisconnected; FFPercentageModuleConfig percent; } FFBluetoothOptions; static_assert(sizeof(FFBluetoothOptions) <= FF_OPTION_MAX_SIZE, "FFBluetoothOptions size exceeds maximum allowed size"); ================================================ FILE: src/modules/bluetoothradio/bluetoothradio.c ================================================ #include "common/percent.h" #include "common/printing.h" #include "common/jsonconfig.h" #include "common/stringUtils.h" #include "detection/bluetoothradio/bluetoothradio.h" #include "modules/bluetoothradio/bluetoothradio.h" #define FF_BLUETOOTHRADIO_DISPLAY_NAME "Bluetooth Radio" static void printDevice(FFBluetoothRadioOptions* options, const FFBluetoothRadioResult* radio, uint8_t index) { FF_STRBUF_AUTO_DESTROY key = ffStrbufCreate(); if (options->moduleArgs.key.length == 0) { ffStrbufAppendF(&key, "%s (%s)", FF_BLUETOOTHRADIO_DISPLAY_NAME, radio->name.chars); } else { FF_PARSE_FORMAT_STRING_CHECKED(&key, &options->moduleArgs.key, ((FFformatarg[]) { FF_ARG(index, "index"), FF_ARG(radio->name, "name"), FF_ARG(options->moduleArgs.keyIcon, "icon"), })); } const char* version = NULL; switch (radio->lmpVersion < 0 ? -radio->lmpVersion : radio->lmpVersion) { case 0: version = "1.0b"; break; case 1: version = "1.1"; break; case 2: version = "1.2"; break; case 3: version = "2.0"; break; case 4: version = "2.1"; break; case 5: version = "3.0"; break; case 6: version = "4.0"; break; case 7: version = "4.1"; break; case 8: version = "4.2"; break; case 9: version = "5.0"; break; case 10: version = "5.1"; break; case 11: version = "5.2"; break; case 12: version = "5.3"; break; case 13: version = "5.4"; break; } if(options->moduleArgs.outputFormat.length == 0) { ffPrintLogoAndKey(key.chars, 0, &options->moduleArgs, FF_PRINT_TYPE_NO_CUSTOM_KEY); if (version) printf("Bluetooth %s%s (%s)\n", version, (radio->lmpVersion < 0 ? "+" : ""), radio->vendor.chars); else ffStrbufPutTo(&radio->vendor, stdout); } else { FF_PRINT_FORMAT_CHECKED(key.chars, 0, &options->moduleArgs, FF_PRINT_TYPE_NO_CUSTOM_KEY, ((FFformatarg[]) { FF_ARG(radio->name, "name"), FF_ARG(radio->address, "address"), FF_ARG(radio->lmpVersion, "lmp-version"), FF_ARG(radio->lmpSubversion, "lmp-subversion"), FF_ARG(version, "version"), FF_ARG(radio->vendor, "vendor"), FF_ARG(radio->discoverable, "discoverable"), FF_ARG(radio->connectable, "connectable"), })); } } bool ffPrintBluetoothRadio(FFBluetoothRadioOptions* options) { FF_LIST_AUTO_DESTROY radios = ffListCreate(sizeof (FFBluetoothRadioResult)); const char* error = ffDetectBluetoothRadio(&radios); if(error) { ffPrintError(FF_BLUETOOTHRADIO_DISPLAY_NAME, 0, &options->moduleArgs, FF_PRINT_TYPE_DEFAULT, "%s", error); return false; } uint8_t index = 0; FF_LIST_FOR_EACH(FFBluetoothRadioResult, radio, radios) { if (!radio->enabled) continue; index++; printDevice(options, radio, index); } if (index == 0) { if (radios.length > 0) ffPrintError(FF_BLUETOOTHRADIO_DISPLAY_NAME, 0, &options->moduleArgs, FF_PRINT_TYPE_DEFAULT, "Bluetooth radios found but none enabled"); else ffPrintError(FF_BLUETOOTHRADIO_DISPLAY_NAME, 0, &options->moduleArgs, FF_PRINT_TYPE_DEFAULT, "No devices detected"); } FF_LIST_FOR_EACH(FFBluetoothRadioResult, radio, radios) { ffStrbufDestroy(&radio->name); ffStrbufDestroy(&radio->address); ffStrbufDestroy(&radio->vendor); } return true; } void ffParseBluetoothRadioJsonObject(FFBluetoothRadioOptions* options, yyjson_val* module) { yyjson_val *key, *val; size_t idx, max; yyjson_obj_foreach(module, idx, max, key, val) { if (ffJsonConfigParseModuleArgs(key, val, &options->moduleArgs)) continue; ffPrintError(FF_BLUETOOTHRADIO_MODULE_NAME, 0, &options->moduleArgs, FF_PRINT_TYPE_DEFAULT, "Unknown JSON key %s", unsafe_yyjson_get_str(key)); } } void ffGenerateBluetoothRadioJsonConfig(FFBluetoothRadioOptions* options, yyjson_mut_doc* doc, yyjson_mut_val* module) { ffJsonConfigGenerateModuleArgsConfig(doc, module, &options->moduleArgs); } bool ffGenerateBluetoothRadioJsonResult(FF_MAYBE_UNUSED FFBluetoothRadioOptions* options, yyjson_mut_doc* doc, yyjson_mut_val* module) { FF_LIST_AUTO_DESTROY results = ffListCreate(sizeof(FFBluetoothRadioResult)); const char* error = ffDetectBluetoothRadio(&results); if (error) { yyjson_mut_obj_add_str(doc, module, "error", error); return false; } yyjson_mut_val* arr = yyjson_mut_obj_add_arr(doc, module, "result"); FF_LIST_FOR_EACH(FFBluetoothRadioResult, item, results) { yyjson_mut_val* obj = yyjson_mut_arr_add_obj(doc, arr); yyjson_mut_obj_add_strbuf(doc, obj, "name", &item->name); yyjson_mut_obj_add_strbuf(doc, obj, "address", &item->address); if (item->lmpVersion == INT_MIN) yyjson_mut_obj_add_null(doc, obj, "lmpVersion"); else yyjson_mut_obj_add_int(doc, obj, "lmpVersion", item->lmpVersion); if (item->lmpSubversion == INT_MIN) yyjson_mut_obj_add_null(doc, obj, "lmpSubversion"); else yyjson_mut_obj_add_int(doc, obj, "lmpSubversion", item->lmpSubversion); yyjson_mut_obj_add_strbuf(doc, obj, "vendor", &item->vendor); yyjson_mut_obj_add_bool(doc, obj, "enabled", item->enabled); yyjson_mut_obj_add_bool(doc, obj, "discoverable", item->discoverable); yyjson_mut_obj_add_bool(doc, obj, "connectable", item->connectable); } FF_LIST_FOR_EACH(FFBluetoothRadioResult, radio, results) { ffStrbufDestroy(&radio->name); ffStrbufDestroy(&radio->address); ffStrbufDestroy(&radio->vendor); } return true; } void ffInitBluetoothRadioOptions(FFBluetoothRadioOptions* options) { ffOptionInitModuleArg(&options->moduleArgs, "󰐻"); } void ffDestroyBluetoothRadioOptions(FFBluetoothRadioOptions* options) { ffOptionDestroyModuleArg(&options->moduleArgs); } FFModuleBaseInfo ffBluetoothRadioModuleInfo = { .name = FF_BLUETOOTHRADIO_MODULE_NAME, .description = "List bluetooth radios width supported version and vendor", .initOptions = (void*) ffInitBluetoothRadioOptions, .destroyOptions = (void*) ffDestroyBluetoothRadioOptions, .parseJsonObject = (void*) ffParseBluetoothRadioJsonObject, .printModule = (void*) ffPrintBluetoothRadio, .generateJsonResult = (void*) ffGenerateBluetoothRadioJsonResult, .generateJsonConfig = (void*) ffGenerateBluetoothRadioJsonConfig, .formatArgs = FF_FORMAT_ARG_LIST(((FFModuleFormatArg[]) { {"Radio name for discovering", "name"}, {"Address", "address"}, {"LMP version", "lmp-version"}, {"LMP subversion", "lmp-subversion"}, {"Bluetooth version", "version"}, {"Vendor", "vendor"}, {"Discoverable", "discoverable"}, {"Connectable / Pairable", "connectable"}, })) }; ================================================ FILE: src/modules/bluetoothradio/bluetoothradio.h ================================================ #pragma once #include "option.h" #define FF_BLUETOOTHRADIO_MODULE_NAME "BluetoothRadio" bool ffPrintBluetoothRadio(FFBluetoothRadioOptions* options); void ffInitBluetoothRadioOptions(FFBluetoothRadioOptions* options); void ffDestroyBluetoothRadioOptions(FFBluetoothRadioOptions* options); extern FFModuleBaseInfo ffBluetoothRadioModuleInfo; ================================================ FILE: src/modules/bluetoothradio/option.h ================================================ #pragma once #include "common/option.h" #include "common/percent.h" typedef struct FFBluetoothRadioOptions { FFModuleArgs moduleArgs; } FFBluetoothRadioOptions; static_assert(sizeof(FFBluetoothRadioOptions) <= FF_OPTION_MAX_SIZE, "FFBluetoothRadioOptions size exceeds maximum allowed size"); ================================================ FILE: src/modules/board/board.c ================================================ #include "common/printing.h" #include "common/jsonconfig.h" #include "common/stringUtils.h" #include "detection/board/board.h" #include "modules/board/board.h" bool ffPrintBoard(FFBoardOptions* options) { bool success = false; FFBoardResult result; ffStrbufInit(&result.name); ffStrbufInit(&result.vendor); ffStrbufInit(&result.version); ffStrbufInit(&result.serial); const char* error = ffDetectBoard(&result); if(error) { ffPrintError(FF_BOARD_MODULE_NAME, 0, &options->moduleArgs, FF_PRINT_TYPE_DEFAULT, "%s", error); goto exit; } if(result.name.length == 0) { ffPrintError(FF_BOARD_MODULE_NAME, 0, &options->moduleArgs, FF_PRINT_TYPE_DEFAULT, "board_name is not set."); goto exit; } if(options->moduleArgs.outputFormat.length == 0) { ffPrintLogoAndKey(FF_BOARD_MODULE_NAME, 0, &options->moduleArgs, FF_PRINT_TYPE_DEFAULT); ffStrbufWriteTo(&result.name, stdout); if (result.version.length) printf(" (%s)", result.version.chars); putchar('\n'); } else { FF_PRINT_FORMAT_CHECKED(FF_BOARD_MODULE_NAME, 0, &options->moduleArgs, FF_PRINT_TYPE_DEFAULT, ((FFformatarg[]) { FF_ARG(result.name, "name"), FF_ARG(result.vendor, "vendor"), FF_ARG(result.version, "version"), FF_ARG(result.serial, "serial"), })); } success = true; exit: ffStrbufDestroy(&result.name); ffStrbufDestroy(&result.vendor); ffStrbufDestroy(&result.version); ffStrbufDestroy(&result.serial); return success; } void ffParseBoardJsonObject(FFBoardOptions* options, yyjson_val* module) { yyjson_val *key, *val; size_t idx, max; yyjson_obj_foreach(module, idx, max, key, val) { if (ffJsonConfigParseModuleArgs(key, val, &options->moduleArgs)) continue; ffPrintError(FF_BOARD_MODULE_NAME, 0, &options->moduleArgs, FF_PRINT_TYPE_DEFAULT, "Unknown JSON key %s", unsafe_yyjson_get_str(key)); } } void ffGenerateBoardJsonConfig(FFBoardOptions* options, yyjson_mut_doc* doc, yyjson_mut_val* module) { ffJsonConfigGenerateModuleArgsConfig(doc, module, &options->moduleArgs); } bool ffGenerateBoardJsonResult(FF_MAYBE_UNUSED FFBoardOptions* options, yyjson_mut_doc* doc, yyjson_mut_val* module) { bool success = false; FFBoardResult board; ffStrbufInit(&board.name); ffStrbufInit(&board.vendor); ffStrbufInit(&board.version); ffStrbufInit(&board.serial); const char* error = ffDetectBoard(&board); if (error) { yyjson_mut_obj_add_str(doc, module, "error", error); goto exit; } if (board.name.length == 0) { yyjson_mut_obj_add_str(doc, module, "error", "board_name is not set."); goto exit; } yyjson_mut_val* obj = yyjson_mut_obj_add_obj(doc, module, "result"); yyjson_mut_obj_add_strbuf(doc, obj, "name", &board.name); yyjson_mut_obj_add_strbuf(doc, obj, "vendor", &board.vendor); yyjson_mut_obj_add_strbuf(doc, obj, "version", &board.version); yyjson_mut_obj_add_strbuf(doc, obj, "serial", &board.serial); success = true; exit: ffStrbufDestroy(&board.name); ffStrbufDestroy(&board.vendor); ffStrbufDestroy(&board.version); ffStrbufDestroy(&board.serial); return success; } void ffInitBoardOptions(FFBoardOptions* options) { ffOptionInitModuleArg(&options->moduleArgs, ""); } void ffDestroyBoardOptions(FFBoardOptions* options) { ffOptionDestroyModuleArg(&options->moduleArgs); } FFModuleBaseInfo ffBoardModuleInfo = { .name = FF_BOARD_MODULE_NAME, .description = "Print motherboard name and other info", .initOptions = (void*) ffInitBoardOptions, .destroyOptions = (void*) ffDestroyBoardOptions, .parseJsonObject = (void*) ffParseBoardJsonObject, .printModule = (void*) ffPrintBoard, .generateJsonResult = (void*) ffGenerateBoardJsonResult, .generateJsonConfig = (void*) ffGenerateBoardJsonConfig, .formatArgs = FF_FORMAT_ARG_LIST(((FFModuleFormatArg[]) { {"Board name", "name"}, {"Board vendor", "vendor"}, {"Board version", "version"}, {"Board serial number", "serial"}, })) }; ================================================ FILE: src/modules/board/board.h ================================================ #pragma once #include "option.h" #define FF_BOARD_MODULE_NAME "Board" bool ffPrintBoard(FFBoardOptions* options); void ffInitBoardOptions(FFBoardOptions* options); void ffDestroyBoardOptions(FFBoardOptions* options); extern FFModuleBaseInfo ffBoardModuleInfo; ================================================ FILE: src/modules/board/option.h ================================================ #pragma once #include "common/option.h" typedef struct FFBoardOptions { FFModuleArgs moduleArgs; } FFBoardOptions; static_assert(sizeof(FFBoardOptions) <= FF_OPTION_MAX_SIZE, "FFBoardOptions size exceeds maximum allowed size"); ================================================ FILE: src/modules/bootmgr/bootmgr.c ================================================ #include "common/printing.h" #include "common/jsonconfig.h" #include "common/stringUtils.h" #include "detection/bootmgr/bootmgr.h" #include "modules/bootmgr/bootmgr.h" bool ffPrintBootmgr(FFBootmgrOptions* options) { bool success = false; FFBootmgrResult bootmgr = { .name = ffStrbufCreate(), .firmware = ffStrbufCreate(), }; const char* error = ffDetectBootmgr(&bootmgr); if(error) { ffPrintError(FF_BOOTMGR_MODULE_NAME, 0, &options->moduleArgs, FF_PRINT_TYPE_DEFAULT, "%s", error); goto exit; } else { FF_STRBUF_AUTO_DESTROY firmwareName = ffStrbufCreateCopy(&bootmgr.firmware); #ifndef __APPLE__ ffStrbufSubstrAfterLastC(&firmwareName, '\\'); #else ffStrbufSubstrAfterLastC(&firmwareName, '/'); #endif if(options->moduleArgs.outputFormat.length == 0) { ffPrintLogoAndKey(FF_BOOTMGR_MODULE_NAME, 0, &options->moduleArgs, FF_PRINT_TYPE_DEFAULT); ffStrbufWriteTo(&bootmgr.name, stdout); if (firmwareName.length > 0) printf(" - %s\n", firmwareName.chars); else putchar('\n'); } else { FF_PRINT_FORMAT_CHECKED(FF_BOOTMGR_MODULE_NAME, 0, &options->moduleArgs, FF_PRINT_TYPE_DEFAULT, ((FFformatarg[]) { FF_ARG(bootmgr.name, "name"), FF_ARG(bootmgr.firmware, "firmware-path"), FF_ARG(firmwareName, "firmware-name"), FF_ARG(bootmgr.secureBoot, "secure-boot"), FF_ARG(bootmgr.order, "order"), })); } } success = true; exit: ffStrbufDestroy(&bootmgr.name); ffStrbufDestroy(&bootmgr.firmware); return success; } void ffParseBootmgrJsonObject(FFBootmgrOptions* options, yyjson_val* module) { yyjson_val *key, *val; size_t idx, max; yyjson_obj_foreach(module, idx, max, key, val) { if (ffJsonConfigParseModuleArgs(key, val, &options->moduleArgs)) continue; ffPrintError(FF_BOOTMGR_MODULE_NAME, 0, &options->moduleArgs, FF_PRINT_TYPE_DEFAULT, "Unknown JSON key %s", unsafe_yyjson_get_str(key)); } } void ffGenerateBootmgrJsonConfig(FFBootmgrOptions* options, yyjson_mut_doc* doc, yyjson_mut_val* module) { ffJsonConfigGenerateModuleArgsConfig(doc, module, &options->moduleArgs); } bool ffGenerateBootmgrJsonResult(FF_MAYBE_UNUSED FFBootmgrOptions* options, yyjson_mut_doc* doc, yyjson_mut_val* module) { bool success = false; FFBootmgrResult bootmgr = { .name = ffStrbufCreate(), .firmware = ffStrbufCreate(), }; const char* error = ffDetectBootmgr(&bootmgr); if (error) { yyjson_mut_obj_add_str(doc, module, "error", error); goto exit; } yyjson_mut_val* obj = yyjson_mut_obj_add_obj(doc, module, "result"); yyjson_mut_obj_add_strbuf(doc, obj, "name", &bootmgr.name); yyjson_mut_obj_add_strbuf(doc, obj, "firmware", &bootmgr.firmware); yyjson_mut_obj_add_uint(doc, obj, "order", bootmgr.order); yyjson_mut_obj_add_bool(doc, obj, "secureBoot", bootmgr.secureBoot); success = true; exit: ffStrbufDestroy(&bootmgr.name); ffStrbufDestroy(&bootmgr.firmware); return success; } void ffInitBootmgrOptions(FFBootmgrOptions* options) { ffOptionInitModuleArg(&options->moduleArgs, ""); } void ffDestroyBootmgrOptions(FFBootmgrOptions* options) { ffOptionDestroyModuleArg(&options->moduleArgs); } FFModuleBaseInfo ffBootmgrModuleInfo = { .name = FF_BOOTMGR_MODULE_NAME, .description = "Print information of 2nd-stage bootloader (name, firmware, etc)", .initOptions = (void*) ffInitBootmgrOptions, .destroyOptions = (void*) ffDestroyBootmgrOptions, .parseJsonObject = (void*) ffParseBootmgrJsonObject, .printModule = (void*) ffPrintBootmgr, .generateJsonResult = (void*) ffGenerateBootmgrJsonResult, .generateJsonConfig = (void*) ffGenerateBootmgrJsonConfig, .formatArgs = FF_FORMAT_ARG_LIST(((FFModuleFormatArg[]) { {"Name / description", "name"}, {"Firmware file path", "firmware-path"}, {"Firmware file name", "firmware-name"}, {"Is secure boot enabled", "secure-boot"}, {"Boot order", "order"}, })) }; ================================================ FILE: src/modules/bootmgr/bootmgr.h ================================================ #pragma once #include "option.h" #define FF_BOOTMGR_MODULE_NAME "Bootmgr" bool ffPrintBootmgr(FFBootmgrOptions* options); void ffInitBootmgrOptions(FFBootmgrOptions* options); void ffDestroyBootmgrOptions(FFBootmgrOptions* options); extern FFModuleBaseInfo ffBootmgrModuleInfo; ================================================ FILE: src/modules/bootmgr/option.h ================================================ #pragma once #include "common/option.h" typedef struct FFBootmgrOptions { FFModuleArgs moduleArgs; } FFBootmgrOptions; static_assert(sizeof(FFBootmgrOptions) <= FF_OPTION_MAX_SIZE, "FFBootmgrOptions size exceeds maximum allowed size"); ================================================ FILE: src/modules/break/break.c ================================================ #include "common/printing.h" #include "logo/logo.h" #include "modules/break/break.h" bool ffPrintBreak(FF_MAYBE_UNUSED FFBreakOptions* options) { ffLogoPrintLine(); putchar('\n'); return true; } void ffParseBreakJsonObject(FF_MAYBE_UNUSED FFBreakOptions* options, FF_MAYBE_UNUSED yyjson_val* module) { yyjson_val *key, *val; size_t idx, max; yyjson_obj_foreach(module, idx, max, key, val) { if (unsafe_yyjson_equals_str(key, "type") || unsafe_yyjson_equals_str(key, "condition")) continue; ffPrintError(FF_BREAK_MODULE_NAME, 0, NULL, FF_PRINT_TYPE_NO_CUSTOM_KEY, "Unknown JSON key %s", unsafe_yyjson_get_str(key)); } } void ffInitBreakOptions(FF_MAYBE_UNUSED FFBreakOptions* options) { } void ffDestroyBreakOptions(FF_MAYBE_UNUSED FFBreakOptions* options) { } FFModuleBaseInfo ffBreakModuleInfo = { .name = FF_BREAK_MODULE_NAME, .description = "Print a empty line", .initOptions = (void*) ffInitBreakOptions, .destroyOptions = (void*) ffDestroyBreakOptions, .parseJsonObject = (void*) ffParseBreakJsonObject, .printModule = (void*) ffPrintBreak, }; ================================================ FILE: src/modules/break/break.h ================================================ #pragma once #include "option.h" #define FF_BREAK_MODULE_NAME "Break" bool ffPrintBreak(FFBreakOptions* options); void ffInitBreakOptions(FFBreakOptions* options); void ffDestroyBreakOptions(FFBreakOptions* options); extern FFModuleBaseInfo ffBreakModuleInfo; ================================================ FILE: src/modules/break/option.h ================================================ #pragma once #include "common/option.h" typedef struct FFBreakOptions { } FFBreakOptions; static_assert(sizeof(FFBreakOptions) <= FF_OPTION_MAX_SIZE, "FFBreakOptions size exceeds maximum allowed size"); ================================================ FILE: src/modules/brightness/brightness.c ================================================ #include "common/percent.h" #include "common/printing.h" #include "common/jsonconfig.h" #include "common/stringUtils.h" #include "detection/brightness/brightness.h" #include "modules/brightness/brightness.h" bool ffPrintBrightness(FFBrightnessOptions* options) { FF_LIST_AUTO_DESTROY result = ffListCreate(sizeof(FFBrightnessResult)); const char* error = ffDetectBrightness(options, &result); if(error) { ffPrintError(FF_BRIGHTNESS_MODULE_NAME, 0, &options->moduleArgs, FF_PRINT_TYPE_DEFAULT, "%s", error); return false; } if(result.length == 0) { ffPrintError(FF_BRIGHTNESS_MODULE_NAME, 0, &options->moduleArgs, FF_PRINT_TYPE_DEFAULT, "No result is detected."); return false; } FFPercentageTypeFlags percentType = options->percent.type == 0 ? instance.config.display.percentType : options->percent.type; if (options->compact) { FF_STRBUF_AUTO_DESTROY str = ffStrbufCreate(); FF_LIST_FOR_EACH(FFBrightnessResult, item, result) { if(str.length > 0) ffStrbufAppendC(&str, ' '); const double percent = (item->current - item->min) / (item->max - item->min) * 100; ffPercentAppendNum(&str, percent, options->percent, false, &options->moduleArgs); } ffPrintLogoAndKey(FF_BRIGHTNESS_MODULE_NAME, 0, &options->moduleArgs, FF_PRINT_TYPE_DEFAULT); ffStrbufPutTo(&str, stdout); return true; } FF_STRBUF_AUTO_DESTROY key = ffStrbufCreate(); uint32_t index = 0; FF_LIST_FOR_EACH(FFBrightnessResult, item, result) { if (options->moduleArgs.key.length == 0) { ffStrbufAppendF(&key, "%s (%s)", FF_BRIGHTNESS_MODULE_NAME, item->name.chars); } else { uint32_t moduleIndex = result.length == 1 ? 0 : index + 1; FF_PARSE_FORMAT_STRING_CHECKED(&key, &options->moduleArgs.key, ((FFformatarg[]) { FF_ARG(moduleIndex, "index"), FF_ARG(item->name, "name"), FF_ARG(options->moduleArgs.keyIcon, "icon"), })); } const double percent = (item->current - item->min) / (item->max - item->min) * 100; if (options->moduleArgs.outputFormat.length == 0) { FF_STRBUF_AUTO_DESTROY str = ffStrbufCreate(); ffPrintLogoAndKey(key.chars, 0, &options->moduleArgs, FF_PRINT_TYPE_NO_CUSTOM_KEY); if (percentType & FF_PERCENTAGE_TYPE_BAR_BIT) { ffPercentAppendBar(&str, percent, options->percent, &options->moduleArgs); } if (percentType & FF_PERCENTAGE_TYPE_NUM_BIT) { if(str.length > 0) ffStrbufAppendC(&str, ' '); ffPercentAppendNum(&str, percent, options->percent, str.length > 0, &options->moduleArgs); } ffStrbufAppendS(&str, item->builtin ? " [Built-in]" : " [External]"); ffStrbufPutTo(&str, stdout); } else { FF_STRBUF_AUTO_DESTROY valueNum = ffStrbufCreate(); if (percentType & FF_PERCENTAGE_TYPE_NUM_BIT) ffPercentAppendNum(&valueNum, percent, options->percent, false, &options->moduleArgs); FF_STRBUF_AUTO_DESTROY valueBar = ffStrbufCreate(); if (percentType & FF_PERCENTAGE_TYPE_BAR_BIT) ffPercentAppendBar(&valueBar, percent, options->percent, &options->moduleArgs); FF_PRINT_FORMAT_CHECKED(key.chars, 0, &options->moduleArgs, FF_PRINT_TYPE_NO_CUSTOM_KEY, ((FFformatarg[]) { FF_ARG(valueNum, "percentage"), FF_ARG(item->name, "name"), FF_ARG(item->max, "max"), FF_ARG(item->min, "min"), FF_ARG(item->current, "current"), FF_ARG(valueBar, "percentage-bar"), FF_ARG(item->builtin, "is-builtin"), })); } ffStrbufClear(&key); ffStrbufDestroy(&item->name); ++index; } return true; } void ffParseBrightnessJsonObject(FFBrightnessOptions* options, yyjson_val* module) { yyjson_val *key, *val; size_t idx, max; yyjson_obj_foreach(module, idx, max, key, val) { if (ffJsonConfigParseModuleArgs(key, val, &options->moduleArgs)) continue; if (unsafe_yyjson_equals_str(key, "ddcciSleep")) { options->ddcciSleep = (uint32_t) yyjson_get_uint(val); continue; } if (unsafe_yyjson_equals_str(key, "compact")) { options->compact = (uint32_t) yyjson_get_bool(val); continue; } if (ffPercentParseJsonObject(key, val, &options->percent)) continue; ffPrintError(FF_BRIGHTNESS_MODULE_NAME, 0, &options->moduleArgs, FF_PRINT_TYPE_DEFAULT, "Unknown JSON key %s", unsafe_yyjson_get_str(key)); } } void ffGenerateBrightnessJsonConfig(FFBrightnessOptions* options, yyjson_mut_doc* doc, yyjson_mut_val* module) { ffJsonConfigGenerateModuleArgsConfig(doc, module, &options->moduleArgs); yyjson_mut_obj_add_uint(doc, module, "ddcciSleep", options->ddcciSleep); ffPercentGenerateJsonConfig(doc, module, options->percent); yyjson_mut_obj_add_bool(doc, module, "compact", options->compact); } bool ffGenerateBrightnessJsonResult(FF_MAYBE_UNUSED FFBrightnessOptions* options, yyjson_mut_doc* doc, yyjson_mut_val* module) { FF_LIST_AUTO_DESTROY result = ffListCreate(sizeof(FFBrightnessResult)); const char* error = ffDetectBrightness(options, &result); if (error) { yyjson_mut_obj_add_str(doc, module, "error", error); return false; } yyjson_mut_val* arr = yyjson_mut_arr(doc); yyjson_mut_obj_add_val(doc, module, "result", arr); FF_LIST_FOR_EACH(FFBrightnessResult, item, result) { yyjson_mut_val* obj = yyjson_mut_arr_add_obj(doc, arr); yyjson_mut_obj_add_strbuf(doc, obj, "name", &item->name); yyjson_mut_obj_add_real(doc, obj, "max", item->max); yyjson_mut_obj_add_real(doc, obj, "min", item->min); yyjson_mut_obj_add_real(doc, obj, "current", item->current); yyjson_mut_obj_add_bool(doc, obj, "builtin", item->builtin); } FF_LIST_FOR_EACH(FFBrightnessResult, item, result) { ffStrbufDestroy(&item->name); } return true; } void ffInitBrightnessOptions(FFBrightnessOptions* options) { ffOptionInitModuleArg(&options->moduleArgs, "󰯪"); options->ddcciSleep = 10; options->percent = (FFPercentageModuleConfig) { 100, 100, 0 }; options->compact = false; } void ffDestroyBrightnessOptions(FFBrightnessOptions* options) { ffOptionDestroyModuleArg(&options->moduleArgs); } FFModuleBaseInfo ffBrightnessModuleInfo = { .name = FF_BRIGHTNESS_MODULE_NAME, .description = "Print current brightness level of your monitors", .initOptions = (void*) ffInitBrightnessOptions, .destroyOptions = (void*) ffDestroyBrightnessOptions, .parseJsonObject = (void*) ffParseBrightnessJsonObject, .printModule = (void*) ffPrintBrightness, .generateJsonResult = (void*) ffGenerateBrightnessJsonResult, .generateJsonConfig = (void*) ffGenerateBrightnessJsonConfig, .formatArgs = FF_FORMAT_ARG_LIST(((FFModuleFormatArg[]) { {"Screen brightness (percentage num)", "percentage"}, {"Screen name", "name"}, {"Maximum brightness value", "max"}, {"Minimum brightness value", "min"}, {"Current brightness value", "current"}, {"Screen brightness (percentage bar)", "percentage-bar"}, {"Is built-in screen", "is-builtin"}, })) }; ================================================ FILE: src/modules/brightness/brightness.h ================================================ #pragma once #include "option.h" #define FF_BRIGHTNESS_MODULE_NAME "Brightness" bool ffPrintBrightness(FFBrightnessOptions* options); void ffInitBrightnessOptions(FFBrightnessOptions* options); void ffDestroyBrightnessOptions(FFBrightnessOptions* options); extern FFModuleBaseInfo ffBrightnessModuleInfo; ================================================ FILE: src/modules/brightness/option.h ================================================ #pragma once #include "common/option.h" #include "common/percent.h" typedef struct FFBrightnessOptions { FFModuleArgs moduleArgs; uint32_t ddcciSleep; // ms FFPercentageModuleConfig percent; bool compact; } FFBrightnessOptions; static_assert(sizeof(FFBrightnessOptions) <= FF_OPTION_MAX_SIZE, "FFBrightnessOptions size exceeds maximum allowed size"); ================================================ FILE: src/modules/btrfs/btrfs.c ================================================ #include "common/printing.h" #include "common/jsonconfig.h" #include "common/percent.h" #include "common/size.h" #include "common/stringUtils.h" #include "detection/btrfs/btrfs.h" #include "modules/btrfs/btrfs.h" static void printBtrfs(FFBtrfsOptions* options, FFBtrfsResult* result, uint8_t index) { FF_STRBUF_AUTO_DESTROY buffer = ffStrbufCreate(); if (options->moduleArgs.key.length == 0) { if (result->name.length > 0) ffStrbufSetF(&buffer, "%s (%s)", FF_BTRFS_MODULE_NAME, result->name.chars); else ffStrbufSetS(&buffer, FF_BTRFS_MODULE_NAME); } else { ffStrbufClear(&buffer); FF_PARSE_FORMAT_STRING_CHECKED(&buffer, &options->moduleArgs.key, ((FFformatarg[]) { FF_ARG(index, "index"), FF_ARG(result->name, "name"), FF_ARG(options->moduleArgs.keyIcon, "icon"), })); } uint64_t used = 0, allocated = 0, total = result->totalSize; for (uint32_t i = 0; i < ARRAY_SIZE(result->allocation); ++i) { uint64_t times = result->allocation[i].copies; used += result->allocation[i].used * times; allocated += result->allocation[i].total * times; } FF_STRBUF_AUTO_DESTROY usedPretty = ffStrbufCreate(); ffSizeAppendNum(used, &usedPretty); FF_STRBUF_AUTO_DESTROY allocatedPretty = ffStrbufCreate(); ffSizeAppendNum(allocated, &allocatedPretty); FF_STRBUF_AUTO_DESTROY totalPretty = ffStrbufCreate(); ffSizeAppendNum(total, &totalPretty); double usedPercentage = total > 0 ? (double) used / (double) total * 100.0 : 0; double allocatedPercentage = total > 0 ? (double) allocated / (double) total * 100.0 : 0; FFPercentageTypeFlags percentType = options->percent.type == 0 ? instance.config.display.percentType : options->percent.type; if(options->moduleArgs.outputFormat.length == 0) { ffPrintLogoAndKey(buffer.chars, index, &options->moduleArgs, FF_PRINT_TYPE_NO_CUSTOM_KEY); ffStrbufClear(&buffer); ffStrbufSetF(&buffer, "%s / %s (", usedPretty.chars, totalPretty.chars); ffPercentAppendNum(&buffer, usedPercentage, options->percent, false, &options->moduleArgs); ffStrbufAppendS(&buffer, ", "); ffPercentAppendNum(&buffer, allocatedPercentage, options->percent, false, &options->moduleArgs); ffStrbufAppendF(&buffer, " allocated)"); ffStrbufPutTo(&buffer, stdout); } else { FF_STRBUF_AUTO_DESTROY usedPercentageNum = ffStrbufCreate(); if (percentType & FF_PERCENTAGE_TYPE_NUM_BIT) ffPercentAppendNum(&usedPercentageNum, usedPercentage, options->percent, false, &options->moduleArgs); FF_STRBUF_AUTO_DESTROY usedPercentageBar = ffStrbufCreate(); if (percentType & FF_PERCENTAGE_TYPE_BAR_BIT) ffPercentAppendBar(&usedPercentageBar, usedPercentage, options->percent, &options->moduleArgs); FF_STRBUF_AUTO_DESTROY allocatedPercentageNum = ffStrbufCreate(); if (percentType & FF_PERCENTAGE_TYPE_NUM_BIT) ffPercentAppendNum(&allocatedPercentageNum, allocatedPercentage, options->percent, false, &options->moduleArgs); FF_STRBUF_AUTO_DESTROY allocatedPercentageBar = ffStrbufCreate(); if (percentType & FF_PERCENTAGE_TYPE_BAR_BIT) ffPercentAppendBar(&allocatedPercentageBar, allocatedPercentage, options->percent, &options->moduleArgs); FF_STRBUF_AUTO_DESTROY nodeSizePretty = ffStrbufCreate(); ffSizeAppendNum(result->nodeSize, &nodeSizePretty); FF_STRBUF_AUTO_DESTROY sectorSizePretty = ffStrbufCreate(); ffSizeAppendNum(result->sectorSize, §orSizePretty); FF_PRINT_FORMAT_CHECKED(buffer.chars, 0, &options->moduleArgs, FF_PRINT_TYPE_NO_CUSTOM_KEY, ((FFformatarg[]) { FF_ARG(result->name, "name"), FF_ARG(result->uuid, "uuid"), FF_ARG(result->devices, "devices"), FF_ARG(result->features, "features"), FF_ARG(usedPretty, "used"), FF_ARG(allocatedPretty, "allocated"), FF_ARG(totalPretty, "total"), FF_ARG(usedPercentageNum, "used-percentage"), FF_ARG(allocatedPercentageNum, "allocated-percentage"), FF_ARG(usedPercentageBar, "used-percentage-bar"), FF_ARG(allocatedPercentageBar, "allocated-percentage-bar"), FF_ARG(nodeSizePretty, "node-size"), FF_ARG(sectorSizePretty, "sector-size"), })); } } bool ffPrintBtrfs(FFBtrfsOptions* options) { FF_LIST_AUTO_DESTROY results = ffListCreate(sizeof(FFBtrfsResult)); const char* error = ffDetectBtrfs(&results); if (error) { ffPrintError(FF_BTRFS_MODULE_NAME, 0, &options->moduleArgs, FF_PRINT_TYPE_DEFAULT, "%s", error); return false; } if(results.length == 0) { ffPrintError(FF_BTRFS_MODULE_NAME, 0, &options->moduleArgs, FF_PRINT_TYPE_DEFAULT, "%s", "No btrfs drive found"); return false; } for(uint32_t i = 0; i < results.length; i++) { FFBtrfsResult* result = FF_LIST_GET(FFBtrfsResult, results, i); uint8_t index = results.length == 1 ? 0 : (uint8_t) (i + 1); printBtrfs(options, result, index); } FF_LIST_FOR_EACH(FFBtrfsResult, result, results) { ffStrbufDestroy(&result->name); ffStrbufDestroy(&result->uuid); ffStrbufDestroy(&result->devices); ffStrbufDestroy(&result->features); } return true; } void ffParseBtrfsJsonObject(FFBtrfsOptions* options, yyjson_val* module) { yyjson_val *key, *val; size_t idx, max; yyjson_obj_foreach(module, idx, max, key, val) { if (ffJsonConfigParseModuleArgs(key, val, &options->moduleArgs)) continue; if (ffPercentParseJsonObject(key, val, &options->percent)) continue; ffPrintError(FF_BTRFS_MODULE_NAME, 0, &options->moduleArgs, FF_PRINT_TYPE_DEFAULT, "Unknown JSON key %s", unsafe_yyjson_get_str(key)); } } void ffGenerateBtrfsJsonConfig(FFBtrfsOptions* options, yyjson_mut_doc* doc, yyjson_mut_val* module) { ffJsonConfigGenerateModuleArgsConfig(doc, module, &options->moduleArgs); ffPercentGenerateJsonConfig(doc, module, options->percent); } bool ffGenerateBtrfsJsonResult(FF_MAYBE_UNUSED FFBtrfsOptions* options, yyjson_mut_doc* doc, yyjson_mut_val* module) { FF_LIST_AUTO_DESTROY results = ffListCreate(sizeof(FFBtrfsResult)); const char* error = ffDetectBtrfs(&results); if (error) { yyjson_mut_obj_add_str(doc, module, "error", error); return false; } yyjson_mut_val* arr = yyjson_mut_obj_add_arr(doc, module, "result"); FF_LIST_FOR_EACH(FFBtrfsResult, btrfs, results) { yyjson_mut_val* obj = yyjson_mut_arr_add_obj(doc, arr); yyjson_mut_obj_add_strbuf(doc, obj, "name", &btrfs->name); yyjson_mut_obj_add_strbuf(doc, obj, "uuid", &btrfs->uuid); yyjson_mut_obj_add_strbuf(doc, obj, "devices", &btrfs->devices); yyjson_mut_obj_add_strbuf(doc, obj, "features", &btrfs->features); yyjson_mut_obj_add_uint(doc, obj, "generation", btrfs->generation); yyjson_mut_obj_add_uint(doc, obj, "nodeSize", btrfs->nodeSize); yyjson_mut_obj_add_uint(doc, obj, "sectorSize", btrfs->sectorSize); yyjson_mut_obj_add_uint(doc, obj, "totalSize", btrfs->totalSize); yyjson_mut_val* allocation = yyjson_mut_obj_add_arr(doc, obj, "allocation"); for (uint32_t i = 0; i < ARRAY_SIZE(btrfs->allocation); ++i) { yyjson_mut_val* item = yyjson_mut_arr_add_obj(doc, allocation); yyjson_mut_obj_add_str(doc, item, "type", btrfs->allocation[i].type); yyjson_mut_obj_add_str(doc, item, "profile", btrfs->allocation[i].profile); yyjson_mut_obj_add_uint(doc, item, "copies", btrfs->allocation[i].copies); yyjson_mut_obj_add_uint(doc, item, "used", btrfs->allocation[i].used); yyjson_mut_obj_add_uint(doc, item, "total", btrfs->allocation[i].total); } } FF_LIST_FOR_EACH(FFBtrfsResult, btrfs, results) { ffStrbufDestroy(&btrfs->name); ffStrbufDestroy(&btrfs->uuid); ffStrbufDestroy(&btrfs->devices); ffStrbufDestroy(&btrfs->features); } return true; } void ffInitBtrfsOptions(FFBtrfsOptions* options) { ffOptionInitModuleArg(&options->moduleArgs, "󱑛"); options->percent = (FFPercentageModuleConfig) { 50, 80, 0 }; } void ffDestroyBtrfsOptions(FFBtrfsOptions* options) { ffOptionDestroyModuleArg(&options->moduleArgs); } FFModuleBaseInfo ffBtrfsModuleInfo = { .name = FF_BTRFS_MODULE_NAME, .description = "Print Linux BTRFS volumes", .initOptions = (void*) ffInitBtrfsOptions, .destroyOptions = (void*) ffDestroyBtrfsOptions, .parseJsonObject = (void*) ffParseBtrfsJsonObject, .printModule = (void*) ffPrintBtrfs, .generateJsonResult = (void*) ffGenerateBtrfsJsonResult, .generateJsonConfig = (void*) ffGenerateBtrfsJsonConfig, .formatArgs = FF_FORMAT_ARG_LIST(((FFModuleFormatArg[]) { {"Name / Label", "name"}, {"UUID", "uuid"}, {"Associated devices", "devices"}, {"Enabled features", "features"}, {"Size used", "used"}, {"Size allocated", "allocated"}, {"Size total", "total"}, {"Used percentage num", "used-percentage"}, {"Allocated percentage num", "allocated-percentage"}, {"Used percentage bar", "used-percentage-bar"}, {"Allocated percentage bar", "allocated-percentage-bar"}, {"Node size", "node-size"}, {"Sector size", "sector-size"}, })) }; ================================================ FILE: src/modules/btrfs/btrfs.h ================================================ #pragma once #include "option.h" #define FF_BTRFS_MODULE_NAME "Btrfs" bool ffPrintBtrfs(FFBtrfsOptions* options); void ffInitBtrfsOptions(FFBtrfsOptions* options); void ffDestroyBtrfsOptions(FFBtrfsOptions* options); extern FFModuleBaseInfo ffBtrfsModuleInfo; ================================================ FILE: src/modules/btrfs/option.h ================================================ #pragma once #include "common/option.h" #include "common/percent.h" typedef struct FFBtrfsOptions { FFModuleArgs moduleArgs; FFPercentageModuleConfig percent; } FFBtrfsOptions; static_assert(sizeof(FFBtrfsOptions) <= FF_OPTION_MAX_SIZE, "FFBtrfsOptions size exceeds maximum allowed size"); ================================================ FILE: src/modules/camera/camera.c ================================================ #include "common/printing.h" #include "common/jsonconfig.h" #include "common/stringUtils.h" #include "detection/libc/libc.h" #include "detection/camera/camera.h" #include "modules/camera/camera.h" static void printDevice(FFCameraOptions* options, const FFCameraResult* device, uint8_t index) { if(options->moduleArgs.outputFormat.length == 0) { ffPrintLogoAndKey(FF_CAMERA_MODULE_NAME, index, &options->moduleArgs, FF_PRINT_TYPE_DEFAULT); ffStrbufWriteTo(&device->name, stdout); if (device->colorspace.length > 0) { fputs(" - ", stdout); ffStrbufWriteTo(&device->colorspace, stdout); } if (device->width > 0 && device->height > 0) printf(" (%ux%u px)\n", (unsigned) device->width, (unsigned) device->height); else putchar('\n'); } else { FF_PRINT_FORMAT_CHECKED(FF_CAMERA_MODULE_NAME, index, &options->moduleArgs, FF_PRINT_TYPE_DEFAULT, (((FFformatarg[]) { FF_ARG(device->name, "name"), FF_ARG(device->vendor, "vendor"), FF_ARG(device->colorspace, "colorspace"), FF_ARG(device->id, "id"), FF_ARG(device->width, "width"), FF_ARG(device->height, "height"), }))); } } bool ffPrintCamera(FFCameraOptions* options) { FF_LIST_AUTO_DESTROY result = ffListCreate(sizeof(FFCameraResult)); const char* error = ffDetectCamera(&result); if (error) { ffPrintError(FF_CAMERA_MODULE_NAME, 0, &options->moduleArgs, FF_PRINT_TYPE_DEFAULT, "%s", error); return false; } if (result.length == 0) { ffPrintError(FF_CAMERA_MODULE_NAME, 0, &options->moduleArgs, FF_PRINT_TYPE_DEFAULT, "No camera found"); return false; } for(uint32_t i = 0; i < result.length; i++) { uint8_t index = (uint8_t) (result.length == 1 ? 0 : i + 1); printDevice(options, FF_LIST_GET(FFCameraResult, result, i), index); } FF_LIST_FOR_EACH(FFCameraResult, dev, result) { ffStrbufDestroy(&dev->name); ffStrbufDestroy(&dev->id); ffStrbufDestroy(&dev->colorspace); } return true; } void ffParseCameraJsonObject(FFCameraOptions* options, yyjson_val* module) { yyjson_val *key, *val; size_t idx, max; yyjson_obj_foreach(module, idx, max, key, val) { if (ffJsonConfigParseModuleArgs(key, val, &options->moduleArgs)) continue; ffPrintError(FF_CAMERA_MODULE_NAME, 0, &options->moduleArgs, FF_PRINT_TYPE_DEFAULT, "Unknown JSON key %s", unsafe_yyjson_get_str(key)); } } void ffGenerateCameraJsonConfig(FFCameraOptions* options, yyjson_mut_doc* doc, yyjson_mut_val* module) { ffJsonConfigGenerateModuleArgsConfig(doc, module, &options->moduleArgs); } bool ffGenerateCameraJsonResult(FF_MAYBE_UNUSED FFCameraOptions* options, yyjson_mut_doc* doc, yyjson_mut_val* module) { FF_LIST_AUTO_DESTROY result = ffListCreate(sizeof(FFCameraResult)); const char* error = ffDetectCamera(&result); if (error) { yyjson_mut_obj_add_str(doc, module, "error", error); return false; } yyjson_mut_val* arr = yyjson_mut_obj_add_arr(doc, module, "result"); FF_LIST_FOR_EACH(FFCameraResult, dev, result) { yyjson_mut_val* obj = yyjson_mut_arr_add_obj(doc, arr); yyjson_mut_obj_add_strbuf(doc, obj, "name", &dev->name); yyjson_mut_obj_add_strbuf(doc, obj, "vendor", &dev->vendor); yyjson_mut_obj_add_strbuf(doc, obj, "colorSpace", &dev->colorspace); yyjson_mut_obj_add_strbuf(doc, obj, "id", &dev->id); yyjson_mut_obj_add_uint(doc, obj, "width", dev->width); yyjson_mut_obj_add_uint(doc, obj, "height", dev->height); } FF_LIST_FOR_EACH(FFCameraResult, dev, result) { ffStrbufDestroy(&dev->name); ffStrbufDestroy(&dev->id); ffStrbufDestroy(&dev->colorspace); } return true; } void ffInitCameraOptions(FFCameraOptions* options) { ffOptionInitModuleArg(&options->moduleArgs, "󰄀"); } void ffDestroyCameraOptions(FFCameraOptions* options) { ffOptionDestroyModuleArg(&options->moduleArgs); } FFModuleBaseInfo ffCameraModuleInfo = { .name = FF_CAMERA_MODULE_NAME, .description = "Print available cameras", .initOptions = (void*) ffInitCameraOptions, .destroyOptions = (void*) ffDestroyCameraOptions, .parseJsonObject = (void*) ffParseCameraJsonObject, .printModule = (void*) ffPrintCamera, .generateJsonResult = (void*) ffGenerateCameraJsonResult, .generateJsonConfig = (void*) ffGenerateCameraJsonConfig, .formatArgs = FF_FORMAT_ARG_LIST(((FFModuleFormatArg[]) { {"Device name", "name"}, {"Vendor", "vendor"}, {"Color space", "colorspace"}, {"Identifier", "id"}, {"Width (in px)", "width"}, {"Height (in px)", "height"}, })) }; ================================================ FILE: src/modules/camera/camera.h ================================================ #pragma once #include "option.h" #define FF_CAMERA_MODULE_NAME "Camera" bool ffPrintCamera(FFCameraOptions* options); void ffInitCameraOptions(FFCameraOptions* options); void ffDestroyCameraOptions(FFCameraOptions* options); extern FFModuleBaseInfo ffCameraModuleInfo; ================================================ FILE: src/modules/camera/option.h ================================================ #pragma once #include "common/option.h" typedef struct FFCameraOptions { FFModuleArgs moduleArgs; } FFCameraOptions; static_assert(sizeof(FFCameraOptions) <= FF_OPTION_MAX_SIZE, "FFCameraOptions size exceeds maximum allowed size"); ================================================ FILE: src/modules/chassis/chassis.c ================================================ #include "common/printing.h" #include "common/jsonconfig.h" #include "common/stringUtils.h" #include "detection/chassis/chassis.h" #include "modules/chassis/chassis.h" bool ffPrintChassis(FFChassisOptions* options) { bool success = false; FFChassisResult result; ffStrbufInit(&result.type); ffStrbufInit(&result.vendor); ffStrbufInit(&result.version); ffStrbufInit(&result.serial); const char* error = ffDetectChassis(&result); if(error) { ffPrintError(FF_CHASSIS_MODULE_NAME, 0, &options->moduleArgs, FF_PRINT_TYPE_DEFAULT, "%s", error); goto exit; } if(result.type.length == 0) { ffPrintError(FF_CHASSIS_MODULE_NAME, 0, &options->moduleArgs, FF_PRINT_TYPE_DEFAULT, "chassis_type is not set by O.E.M."); goto exit; } if(options->moduleArgs.outputFormat.length == 0) { ffPrintLogoAndKey(FF_CHASSIS_MODULE_NAME, 0, &options->moduleArgs, FF_PRINT_TYPE_DEFAULT); ffStrbufWriteTo(&result.type, stdout); if (result.version.length) printf(" (%s)", result.version.chars); putchar('\n'); } else { FF_PRINT_FORMAT_CHECKED(FF_CHASSIS_MODULE_NAME, 0, &options->moduleArgs, FF_PRINT_TYPE_DEFAULT, ((FFformatarg[]) { FF_ARG(result.type, "type"), FF_ARG(result.vendor, "vendor"), FF_ARG(result.version, "version"), FF_ARG(result.serial, "serial"), })); } success = true; exit: ffStrbufDestroy(&result.type); ffStrbufDestroy(&result.vendor); ffStrbufDestroy(&result.version); ffStrbufDestroy(&result.serial); return success; } void ffParseChassisJsonObject(FFChassisOptions* options, yyjson_val* module) { yyjson_val *key, *val; size_t idx, max; yyjson_obj_foreach(module, idx, max, key, val) { if (ffJsonConfigParseModuleArgs(key, val, &options->moduleArgs)) continue; ffPrintError(FF_CHASSIS_MODULE_NAME, 0, &options->moduleArgs, FF_PRINT_TYPE_DEFAULT, "Unknown JSON key %s", unsafe_yyjson_get_str(key)); } } void ffGenerateChassisJsonConfig(FFChassisOptions* options, yyjson_mut_doc* doc, yyjson_mut_val* module) { ffJsonConfigGenerateModuleArgsConfig(doc, module, &options->moduleArgs); } bool ffGenerateChassisJsonResult(FF_MAYBE_UNUSED FFChassisOptions* options, yyjson_mut_doc* doc, yyjson_mut_val* module) { bool success = false; FFChassisResult result; ffStrbufInit(&result.type); ffStrbufInit(&result.vendor); ffStrbufInit(&result.version); ffStrbufInit(&result.serial); const char* error = ffDetectChassis(&result); if (error) { yyjson_mut_obj_add_str(doc, module, "error", error); goto exit; } if(result.type.length == 0) { yyjson_mut_obj_add_str(doc, module, "error", "chassis_type is not set by O.E.M."); goto exit; } yyjson_mut_val* obj = yyjson_mut_obj_add_obj(doc, module, "result"); yyjson_mut_obj_add_strbuf(doc, obj, "type", &result.type); yyjson_mut_obj_add_strbuf(doc, obj, "vendor", &result.vendor); yyjson_mut_obj_add_strbuf(doc, obj, "version", &result.version); yyjson_mut_obj_add_strbuf(doc, obj, "serial", &result.serial); success = true; exit: ffStrbufDestroy(&result.type); ffStrbufDestroy(&result.vendor); ffStrbufDestroy(&result.version); ffStrbufDestroy(&result.serial); return success; } void ffInitChassisOptions(FFChassisOptions* options) { ffOptionInitModuleArg(&options->moduleArgs, ""); } void ffDestroyChassisOptions(FFChassisOptions* options) { ffOptionDestroyModuleArg(&options->moduleArgs); } FFModuleBaseInfo ffChassisModuleInfo = { .name = FF_CHASSIS_MODULE_NAME, .description = "Print chassis type (desktop, laptop, etc)", .initOptions = (void*) ffInitChassisOptions, .destroyOptions = (void*) ffDestroyChassisOptions, .parseJsonObject = (void*) ffParseChassisJsonObject, .printModule = (void*) ffPrintChassis, .generateJsonResult = (void*) ffGenerateChassisJsonResult, .generateJsonConfig = (void*) ffGenerateChassisJsonConfig, .formatArgs = FF_FORMAT_ARG_LIST(((FFModuleFormatArg[]) { {"Chassis type", "type"}, {"Chassis vendor", "vendor"}, {"Chassis version", "version"}, {"Chassis serial number", "serial"}, })), }; ================================================ FILE: src/modules/chassis/chassis.h ================================================ #pragma once #include "option.h" #define FF_CHASSIS_MODULE_NAME "Chassis" bool ffPrintChassis(FFChassisOptions* options); void ffInitChassisOptions(FFChassisOptions* options); void ffDestroyChassisOptions(FFChassisOptions* options); extern FFModuleBaseInfo ffChassisModuleInfo; ================================================ FILE: src/modules/chassis/option.h ================================================ #pragma once #include "common/option.h" typedef struct FFChassisOptions { FFModuleArgs moduleArgs; } FFChassisOptions; static_assert(sizeof(FFChassisOptions) <= FF_OPTION_MAX_SIZE, "FFChassisOptions size exceeds maximum allowed size"); ================================================ FILE: src/modules/colors/colors.c ================================================ #include "common/printing.h" #include "common/jsonconfig.h" #include "common/textModifier.h" #include "common/stringUtils.h" #include "logo/logo.h" #include "modules/colors/colors.h" static inline uint8_t min(uint8_t a, uint8_t b) { return a < b ? a : b; } static inline uint8_t max(uint8_t a, uint8_t b) { return a > b ? a : b; } bool ffPrintColors(FFColorsOptions* options) { bool flag = false; FF_STRBUF_AUTO_DESTROY result = ffStrbufCreateA(128); if (options->symbol == FF_COLORS_SYMBOL_BLOCK || options->symbol == FF_COLORS_SYMBOL_BACKGROUND) { // 3%d: Set the foreground color for(uint8_t i = options->block.range[0]; i <= min(options->block.range[1], 7); i++) { if (options->symbol == FF_COLORS_SYMBOL_BLOCK) { if (!instance.config.display.pipe) ffStrbufAppendF(&result, "\e[3%dm", i); for (uint8_t j = 0; j < options->block.width; j++) ffStrbufAppendS(&result, "█"); } else { ffStrbufAppendF(&result, "\e[4%dm", i); ffStrbufAppendNC(&result, options->block.width, ' '); } } if (result.length > 0) { ffPrintLogoAndKey(FF_COLORS_MODULE_NAME, 0, &options->moduleArgs, FF_PRINT_TYPE_DEFAULT); flag = true; if (options->paddingLeft > 0) ffPrintCharTimes(' ', options->paddingLeft); if (!instance.config.display.pipe || options->symbol == FF_COLORS_SYMBOL_BACKGROUND) ffStrbufAppendS(&result, FASTFETCH_TEXT_MODIFIER_RESET); ffStrbufPutTo(&result, stdout); ffStrbufClear(&result); } #ifdef __linux__ // Required by Linux Console for light background to work if (options->symbol == FF_COLORS_SYMBOL_BACKGROUND) { const char* term = getenv("TERM"); // Should be "linux", however some terminal mulitplexer overrides $TERM if (term && !ffStrStartsWith(term, "xterm")) ffStrbufAppendS(&result, "\e[5m"); } #endif // 9%d: Set the foreground to the bright color for(uint8_t i = max(options->block.range[0], 8); i <= options->block.range[1]; i++) { if (options->symbol == FF_COLORS_SYMBOL_BLOCK) { if(!instance.config.display.pipe) ffStrbufAppendF(&result, "\e[9%dm", i - 8); for (uint8_t j = 0; j < options->block.width; j++) ffStrbufAppendS(&result, "█"); } else { ffStrbufAppendF(&result, "\e[10%dm", i - 8); ffStrbufAppendNC(&result, options->block.width, ' '); } } } else { const char* symbol; switch (options->symbol) { case FF_COLORS_SYMBOL_CIRCLE: symbol = "● "; break; case FF_COLORS_SYMBOL_DIAMOND: symbol = "◆ "; break; case FF_COLORS_SYMBOL_TRIANGLE: symbol = "▲ "; break; case FF_COLORS_SYMBOL_SQUARE: symbol = "■ "; break; case FF_COLORS_SYMBOL_STAR: symbol = "★ "; break; default: symbol = "███ "; break; } for (int i = 8; i >= 1; --i) { if (!instance.config.display.pipe) ffStrbufAppendF(&result, "\e[3%dm", i); ffStrbufAppendS(&result, symbol); } ffStrbufTrimRight(&result, ' '); } if (result.length > 0) { if (flag) ffLogoPrintLine(); else { ffPrintLogoAndKey(FF_COLORS_MODULE_NAME, 0, &options->moduleArgs, FF_PRINT_TYPE_DEFAULT); flag = true; } if(options->paddingLeft > 0) ffPrintCharTimes(' ', options->paddingLeft); if(!instance.config.display.pipe || options->symbol == FF_COLORS_SYMBOL_BACKGROUND) ffStrbufAppendS(&result, FASTFETCH_TEXT_MODIFIER_RESET); ffStrbufPutTo(&result, stdout); } if (!flag) { ffPrintError(FF_COLORS_MODULE_NAME, 0, &options->moduleArgs, FF_PRINT_TYPE_DEFAULT, "%s", "Nothing to print"); return false; } return true; } void ffParseColorsJsonObject(FFColorsOptions* options, yyjson_val* module) { yyjson_val *key, *val; size_t idx, max; yyjson_obj_foreach(module, idx, max, key, val) { if (ffJsonConfigParseModuleArgs(key, val, &options->moduleArgs)) continue; if (unsafe_yyjson_equals_str(key, "symbol")) { int value; const char* error = ffJsonConfigParseEnum(val, &value, (FFKeyValuePair[]) { { "block", FF_COLORS_SYMBOL_BLOCK }, { "background", FF_COLORS_SYMBOL_BACKGROUND }, { "circle", FF_COLORS_SYMBOL_CIRCLE }, { "diamond", FF_COLORS_SYMBOL_DIAMOND }, { "triangle", FF_COLORS_SYMBOL_TRIANGLE }, { "square", FF_COLORS_SYMBOL_SQUARE }, { "star", FF_COLORS_SYMBOL_STAR }, {}, }); if (error) ffPrintError(FF_COLORS_MODULE_NAME, 0, NULL, FF_PRINT_TYPE_NO_CUSTOM_KEY, "Invalid %s value: %s", unsafe_yyjson_get_str(key), error); else options->symbol = (FFColorsSymbol) value; continue; } if (unsafe_yyjson_equals_str(key, "paddingLeft")) { options->paddingLeft = (uint32_t) yyjson_get_uint(val); continue; } if (unsafe_yyjson_equals_str(key, "block")) { if (!yyjson_is_obj(val)) ffPrintError(FF_COLORS_MODULE_NAME, 0, NULL, FF_PRINT_TYPE_NO_CUSTOM_KEY, "Invalid %s value: must be an object", unsafe_yyjson_get_str(key)); else { yyjson_val* width = yyjson_obj_get(val, "width"); if (width) options->block.width = (uint8_t) yyjson_get_uint(width); yyjson_val* range = yyjson_obj_get(val, "range"); if (range) { if (!yyjson_is_arr(range) || yyjson_arr_size(range) != 2) ffPrintError(FF_COLORS_MODULE_NAME, 0, NULL, FF_PRINT_TYPE_NO_CUSTOM_KEY, "Invalid %s.range value: must be an array of 2 elements", unsafe_yyjson_get_str(key)); else { uint8_t start = (uint8_t) yyjson_get_uint(yyjson_arr_get(range, 0)); uint8_t end = (uint8_t) yyjson_get_uint(yyjson_arr_get(range, 1)); if (start > end) ffPrintError(FF_COLORS_MODULE_NAME, 0, NULL, FF_PRINT_TYPE_NO_CUSTOM_KEY, "Invalid %s.range value: range[0] > range[1]", unsafe_yyjson_get_str(key)); else if (end > 15) ffPrintError(FF_COLORS_MODULE_NAME, 0, NULL, FF_PRINT_TYPE_NO_CUSTOM_KEY, "Invalid %s.range value: range[1] > 15", unsafe_yyjson_get_str(key)); else { options->block.range[0] = start; options->block.range[1] = end; } } } } continue; } ffPrintError(FF_COLORS_MODULE_NAME, 0, NULL, FF_PRINT_TYPE_NO_CUSTOM_KEY, "Unknown JSON key %s", unsafe_yyjson_get_str(key)); } } void ffGenerateColorsJsonConfig(FFColorsOptions* options, yyjson_mut_doc* doc, yyjson_mut_val* module) { ffJsonConfigGenerateModuleArgsConfig(doc, module, &options->moduleArgs); yyjson_mut_obj_remove_key(module, "format"); // Not supported switch (options->symbol) { case FF_COLORS_SYMBOL_CIRCLE: yyjson_mut_obj_add_str(doc, module, "symbol", "circle"); break; case FF_COLORS_SYMBOL_DIAMOND: yyjson_mut_obj_add_str(doc, module, "symbol", "diamond"); break; case FF_COLORS_SYMBOL_TRIANGLE: yyjson_mut_obj_add_str(doc, module, "symbol", "triangle"); break; case FF_COLORS_SYMBOL_SQUARE: yyjson_mut_obj_add_str(doc, module, "symbol", "square"); break; case FF_COLORS_SYMBOL_STAR: yyjson_mut_obj_add_str(doc, module, "symbol", "star"); break; default: yyjson_mut_obj_add_str(doc, module, "symbol", "block"); break; } yyjson_mut_obj_add_uint(doc, module, "paddingLeft", options->paddingLeft); { yyjson_mut_val* block = yyjson_mut_obj_add_obj(doc, module, "block"); yyjson_mut_obj_add_uint(doc, block, "width", options->block.width); yyjson_mut_val* range = yyjson_mut_obj_add_arr(doc, block, "range"); for (uint8_t i = 0; i < 2; i++) yyjson_mut_arr_add_uint(doc, range, options->block.range[i]); } } void ffInitColorsOptions(FFColorsOptions* options) { ffOptionInitModuleArg(&options->moduleArgs, ""); ffStrbufSetStatic(&options->moduleArgs.key, " "); options->symbol = FF_COLORS_SYMBOL_BACKGROUND; options->paddingLeft = 0; options->block = (FFBlockConfig) { .width = 3, .range = { 0, 15 }, }; } void ffDestroyColorsOptions(FFColorsOptions* options) { ffOptionDestroyModuleArg(&options->moduleArgs); } FFModuleBaseInfo ffColorsModuleInfo = { .name = FF_COLORS_MODULE_NAME, .description = "Display the terminal's 16-color palette", .initOptions = (void*) ffInitColorsOptions, .destroyOptions = (void*) ffDestroyColorsOptions, .parseJsonObject = (void*) ffParseColorsJsonObject, .printModule = (void*) ffPrintColors, .generateJsonConfig = (void*) ffGenerateColorsJsonConfig, }; ================================================ FILE: src/modules/colors/colors.h ================================================ #pragma once #include "option.h" #define FF_COLORS_MODULE_NAME "Colors" bool ffPrintColors(FFColorsOptions* options); void ffInitColorsOptions(FFColorsOptions* options); void ffDestroyColorsOptions(FFColorsOptions* options); extern FFModuleBaseInfo ffColorsModuleInfo; ================================================ FILE: src/modules/colors/option.h ================================================ #pragma once #include "common/option.h" typedef enum __attribute__((__packed__)) FFColorsSymbol { FF_COLORS_SYMBOL_BLOCK, FF_COLORS_SYMBOL_BACKGROUND, FF_COLORS_SYMBOL_CIRCLE, FF_COLORS_SYMBOL_DIAMOND, FF_COLORS_SYMBOL_SQUARE, FF_COLORS_SYMBOL_TRIANGLE, FF_COLORS_SYMBOL_STAR, } FFColorsSymbol; typedef struct FFBlockConfig { uint8_t width; uint8_t range[2]; } FFBlockConfig; typedef struct FFColorsOptions { FFModuleArgs moduleArgs; FFColorsSymbol symbol; uint32_t paddingLeft; FFBlockConfig block; } FFColorsOptions; static_assert(sizeof(FFColorsOptions) <= FF_OPTION_MAX_SIZE, "FFColorsOptions size exceeds maximum allowed size"); ================================================ FILE: src/modules/command/command.c ================================================ #include "common/printing.h" #include "common/jsonconfig.h" #include "modules/command/command.h" #include "detection/command/command.h" bool ffPrintCommand(FFCommandOptions* options) { FF_STRBUF_AUTO_DESTROY result = ffStrbufCreate(); const char* error = ffDetectCommand(options, &result); if (error) { ffPrintError(FF_COMMAND_MODULE_NAME, 0, &options->moduleArgs, FF_PRINT_TYPE_DEFAULT, "%s", error); return false; } if (!result.length) { ffPrintError(FF_COMMAND_MODULE_NAME, 0, &options->moduleArgs, FF_PRINT_TYPE_DEFAULT, "No result generated"); return false; } if (options->splitLines) { uint8_t index = 0; char* line = NULL; size_t len = 0; while (ffStrbufGetline(&line, &len, &result)) { if (options->moduleArgs.outputFormat.length == 0) { ffPrintLogoAndKey(FF_COMMAND_MODULE_NAME, ++index, &options->moduleArgs, FF_PRINT_TYPE_DEFAULT); puts(line); } else { FF_PRINT_FORMAT_CHECKED(FF_COMMAND_MODULE_NAME, ++index, &options->moduleArgs, FF_PRINT_TYPE_DEFAULT, ((FFformatarg[]){ FF_ARG(line, "result") })); } } } else { if (options->moduleArgs.outputFormat.length == 0) { ffPrintLogoAndKey(FF_COMMAND_MODULE_NAME, 0, &options->moduleArgs, FF_PRINT_TYPE_DEFAULT); ffStrbufPutTo(&result, stdout); } else { FF_PRINT_FORMAT_CHECKED(FF_COMMAND_MODULE_NAME, 0, &options->moduleArgs, FF_PRINT_TYPE_DEFAULT, ((FFformatarg[]){ FF_ARG(result, "result") })); } } return true; } void ffParseCommandJsonObject(FFCommandOptions* options, yyjson_val* module) { yyjson_val *key, *val; size_t idx, max; yyjson_obj_foreach(module, idx, max, key, val) { if (ffJsonConfigParseModuleArgs(key, val, &options->moduleArgs)) continue; if (unsafe_yyjson_equals_str(key, "shell")) { ffStrbufSetJsonVal(&options->shell, val); continue; } if (unsafe_yyjson_equals_str(key, "param")) { ffStrbufSetJsonVal(&options->param, val); continue; } if (unsafe_yyjson_equals_str(key, "text")) { ffStrbufSetJsonVal(&options->text, val); continue; } if (unsafe_yyjson_equals_str(key, "useStdErr")) { options->useStdErr = yyjson_get_bool(val); continue; } if (unsafe_yyjson_equals_str(key, "parallel")) { options->parallel = yyjson_get_bool(val); continue; } if (unsafe_yyjson_equals_str(key, "splitLines")) { options->splitLines = yyjson_get_bool(val); continue; } ffPrintError(FF_COMMAND_MODULE_NAME, 0, &options->moduleArgs, FF_PRINT_TYPE_DEFAULT, "Unknown JSON key %s", unsafe_yyjson_get_str(key)); } } void ffGenerateCommandJsonConfig(FFCommandOptions* options, yyjson_mut_doc* doc, yyjson_mut_val* module) { ffJsonConfigGenerateModuleArgsConfig(doc, module, &options->moduleArgs); yyjson_mut_obj_add_strbuf(doc, module, "shell", &options->shell); yyjson_mut_obj_add_strbuf(doc, module, "param", &options->param); yyjson_mut_obj_add_strbuf(doc, module, "text", &options->text); yyjson_mut_obj_add_bool(doc, module, "useStdErr", options->useStdErr); yyjson_mut_obj_add_bool(doc, module, "parallel", options->parallel); yyjson_mut_obj_add_bool(doc, module, "splitLines", options->splitLines); } bool ffGenerateCommandJsonResult(FF_MAYBE_UNUSED FFCommandOptions* options, yyjson_mut_doc* doc, yyjson_mut_val* module) { FF_STRBUF_AUTO_DESTROY result = ffStrbufCreate(); const char* error = ffDetectCommand(options, &result); if(error) { yyjson_mut_obj_add_str(doc, module, "error", error); return false; } if(!result.length) { yyjson_mut_obj_add_str(doc, module, "error", "No result generated"); return false; } if (options->splitLines) { yyjson_mut_val* jsonArray = yyjson_mut_obj_add_arr(doc, module, "result"); char* line = NULL; size_t len = 0; while (ffStrbufGetline(&line, &len, &result)) yyjson_mut_arr_add_strncpy(doc, jsonArray, line, len); } else yyjson_mut_obj_add_strbuf(doc, module, "result", &result); return true; } void ffInitCommandOptions(FFCommandOptions* options) { ffOptionInitModuleArg(&options->moduleArgs, ""); ffStrbufInitStatic(&options->shell, #ifdef _WIN32 "cmd.exe" #else "/bin/sh" #endif ); ffStrbufInitStatic(&options->param, #ifdef _WIN32 "/c" #else "-c" #endif ); ffStrbufInit(&options->text); options->useStdErr = false; options->parallel = true; options->splitLines = false; } void ffDestroyCommandOptions(FFCommandOptions* options) { ffOptionDestroyModuleArg(&options->moduleArgs); ffStrbufDestroy(&options->shell); ffStrbufDestroy(&options->param); ffStrbufDestroy(&options->text); } FFModuleBaseInfo ffCommandModuleInfo = { .name = FF_COMMAND_MODULE_NAME, .description = "Run custom shell scripts", .initOptions = (void*) ffInitCommandOptions, .destroyOptions = (void*) ffDestroyCommandOptions, .parseJsonObject = (void*) ffParseCommandJsonObject, .printModule = (void*) ffPrintCommand, .generateJsonResult = (void*) ffGenerateCommandJsonResult, .generateJsonConfig = (void*) ffGenerateCommandJsonConfig, .formatArgs = FF_FORMAT_ARG_LIST(((FFModuleFormatArg[]) { {"Command result", "result"}, })) }; ================================================ FILE: src/modules/command/command.h ================================================ #pragma once #include "option.h" #define FF_COMMAND_MODULE_NAME "Command" bool ffPrepareCommand(FFCommandOptions* options); bool ffPrintCommand(FFCommandOptions* options); void ffInitCommandOptions(FFCommandOptions* options); void ffDestroyCommandOptions(FFCommandOptions* options); extern FFModuleBaseInfo ffCommandModuleInfo; ================================================ FILE: src/modules/command/option.h ================================================ #pragma once #include "common/option.h" typedef struct FFCommandOptions { FFModuleArgs moduleArgs; FFstrbuf shell; FFstrbuf param; FFstrbuf text; bool useStdErr; bool parallel; bool splitLines; } FFCommandOptions; static_assert(sizeof(FFCommandOptions) <= FF_OPTION_MAX_SIZE, "FFCommandOptions size exceeds maximum allowed size"); ================================================ FILE: src/modules/cpu/cpu.c ================================================ #include "common/printing.h" #include "common/jsonconfig.h" #include "common/parsing.h" #include "common/temps.h" #include "common/frequency.h" #include "detection/cpu/cpu.h" #include "modules/cpu/cpu.h" static int sortCores(const FFCPUCore* a, const FFCPUCore* b) { return (int)b->freq - (int)a->freq; } bool ffPrintCPU(FFCPUOptions* options) { bool success = false; FFCPUResult cpu = { .temperature = FF_CPU_TEMP_UNSET, .frequencyMax = 0, .frequencyBase = 0, .name = ffStrbufCreate(), .vendor = ffStrbufCreate(), }; const char* error = ffDetectCPU(options, &cpu); if(error) { ffPrintError(FF_CPU_MODULE_NAME, 0, &options->moduleArgs, FF_PRINT_TYPE_DEFAULT, "%s", error); } else if(cpu.vendor.length == 0 && cpu.name.length == 0 && cpu.coresOnline <= 1) { ffPrintError(FF_CPU_MODULE_NAME, 0, &options->moduleArgs, FF_PRINT_TYPE_DEFAULT, "No CPU detected"); } else { FF_STRBUF_AUTO_DESTROY coreTypes = ffStrbufCreate(); if (options->showPeCoreCount) { uint32_t typeCount = 0; while (cpu.coreTypes[typeCount].count != 0 && typeCount < ARRAY_SIZE(cpu.coreTypes)) typeCount++; if (typeCount > 0) { qsort(cpu.coreTypes, typeCount, sizeof(cpu.coreTypes[0]), (void*) sortCores); for (uint32_t i = 0; i < typeCount; i++) ffStrbufAppendF(&coreTypes, "%s%u", i == 0 ? "" : "+", cpu.coreTypes[i].count); } } if(options->moduleArgs.outputFormat.length == 0) { ffPrintLogoAndKey(FF_CPU_MODULE_NAME, 0, &options->moduleArgs, FF_PRINT_TYPE_DEFAULT); FF_STRBUF_AUTO_DESTROY str = ffStrbufCreate(); if(cpu.packages > 1) ffStrbufAppendF(&str, "%u x ", cpu.packages); if(cpu.name.length > 0) ffStrbufAppend(&str, &cpu.name); else if(cpu.vendor.length > 0) { ffStrbufAppend(&str, &cpu.vendor); ffStrbufAppendS(&str, " CPU"); } else ffStrbufAppendS(&str, "Unknown"); if(coreTypes.length > 0) ffStrbufAppendF(&str, " (%s)", coreTypes.chars); else if(cpu.coresOnline > 1) ffStrbufAppendF(&str, " (%u)", cpu.coresOnline); uint32_t freq = cpu.frequencyMax; if(freq == 0) freq = cpu.frequencyBase; if(freq > 0) { ffStrbufAppendS(&str, " @ "); ffFreqAppendNum(freq, &str); } if(cpu.temperature != FF_CPU_TEMP_UNSET) { ffStrbufAppendS(&str, " - "); ffTempsAppendNum(cpu.temperature, &str, options->tempConfig, &options->moduleArgs); } ffStrbufPutTo(&str, stdout); } else { FF_STRBUF_AUTO_DESTROY freqBase = ffStrbufCreate(); ffFreqAppendNum(cpu.frequencyBase, &freqBase); FF_STRBUF_AUTO_DESTROY freqMax = ffStrbufCreate(); ffFreqAppendNum(cpu.frequencyMax, &freqMax); FF_STRBUF_AUTO_DESTROY tempStr = ffStrbufCreate(); ffTempsAppendNum(cpu.temperature, &tempStr, options->tempConfig, &options->moduleArgs); FF_PRINT_FORMAT_CHECKED(FF_CPU_MODULE_NAME, 0, &options->moduleArgs, FF_PRINT_TYPE_DEFAULT, ((FFformatarg[]){ FF_ARG(cpu.name, "name"), FF_ARG(cpu.vendor, "vendor"), FF_ARG(cpu.coresPhysical, "cores-physical"), FF_ARG(cpu.coresLogical, "cores-logical"), FF_ARG(cpu.coresOnline, "cores-online"), FF_ARG(freqBase, "freq-base"), FF_ARG(freqMax, "freq-max"), FF_ARG(tempStr, "temperature"), FF_ARG(coreTypes, "core-types"), FF_ARG(cpu.packages, "packages"), FF_ARG(cpu.march, "march"), FF_ARG(cpu.numaNodes, "numa-nodes"), })); } success = true; } ffStrbufDestroy(&cpu.name); ffStrbufDestroy(&cpu.vendor); return success; } void ffParseCPUJsonObject(FFCPUOptions* options, yyjson_val* module) { yyjson_val *key, *val; size_t idx, max; yyjson_obj_foreach(module, idx, max, key, val) { if (ffJsonConfigParseModuleArgs(key, val, &options->moduleArgs)) continue; if (ffTempsParseJsonObject(key, val, &options->temp, &options->tempConfig)) continue; if (unsafe_yyjson_equals_str(key, "tempSensor")) { ffStrbufSetJsonVal(&options->tempSensor, val); continue; } if (unsafe_yyjson_equals_str(key, "freqNdigits")) { ffPrintError(FF_CPU_MODULE_NAME, 0, &options->moduleArgs, FF_PRINT_TYPE_DEFAULT, "modules.CPU.freqNdigits has been moved to display.freq.ndigits"); continue; } if (unsafe_yyjson_equals_str(key, "showPeCoreCount")) { options->showPeCoreCount = yyjson_get_bool(val); continue; } ffPrintError(FF_CPU_MODULE_NAME, 0, &options->moduleArgs, FF_PRINT_TYPE_DEFAULT, "Unknown JSON key %s", unsafe_yyjson_get_str(key)); } } void ffGenerateCPUJsonConfig(FFCPUOptions* options, yyjson_mut_doc* doc, yyjson_mut_val* module) { ffJsonConfigGenerateModuleArgsConfig(doc, module, &options->moduleArgs); ffTempsGenerateJsonConfig(doc, module, options->temp, options->tempConfig); yyjson_mut_obj_add_bool(doc, module, "showPeCoreCount", options->showPeCoreCount); } bool ffGenerateCPUJsonResult(FFCPUOptions* options, yyjson_mut_doc* doc, yyjson_mut_val* module) { bool success = false; FFCPUResult cpu = { .temperature = FF_CPU_TEMP_UNSET, .frequencyMax = 0, .frequencyBase = 0, .name = ffStrbufCreate(), .vendor = ffStrbufCreate(), }; const char* error = ffDetectCPU(options, &cpu); if(error) { yyjson_mut_obj_add_str(doc, module, "error", error); } else if(cpu.vendor.length == 0 && cpu.name.length == 0 && cpu.coresOnline <= 1) { yyjson_mut_obj_add_str(doc, module, "error", "No CPU detected"); } else { yyjson_mut_val* obj = yyjson_mut_obj_add_obj(doc, module, "result"); yyjson_mut_obj_add_strbuf(doc, obj, "cpu", &cpu.name); yyjson_mut_obj_add_strbuf(doc, obj, "vendor", &cpu.vendor); if (cpu.packages == 0) yyjson_mut_obj_add_null(doc, obj, "packages"); else yyjson_mut_obj_add_uint(doc, obj, "packages", cpu.packages); yyjson_mut_val* cores = yyjson_mut_obj_add_obj(doc, obj, "cores"); yyjson_mut_obj_add_uint(doc, cores, "physical", cpu.coresPhysical); yyjson_mut_obj_add_uint(doc, cores, "logical", cpu.coresLogical); yyjson_mut_obj_add_uint(doc, cores, "online", cpu.coresOnline); yyjson_mut_val* frequency = yyjson_mut_obj_add_obj(doc, obj, "frequency"); yyjson_mut_obj_add_uint(doc, frequency, "base", cpu.frequencyBase); yyjson_mut_obj_add_uint(doc, frequency, "max", cpu.frequencyMax); yyjson_mut_val* coreTypes = yyjson_mut_obj_add_arr(doc, obj, "coreTypes"); for (uint32_t i = 0; i < ARRAY_SIZE(cpu.coreTypes) && cpu.coreTypes[i].count > 0; i++) { yyjson_mut_val* core = yyjson_mut_arr_add_obj(doc, coreTypes); yyjson_mut_obj_add_uint(doc, core, "count", cpu.coreTypes[i].count); yyjson_mut_obj_add_uint(doc, core, "freq", cpu.coreTypes[i].freq); } if (cpu.temperature != FF_CPU_TEMP_UNSET) yyjson_mut_obj_add_real(doc, obj, "temperature", cpu.temperature); else yyjson_mut_obj_add_null(doc, obj, "temperature"); if (cpu.march) yyjson_mut_obj_add_str(doc, obj, "march", cpu.march); else yyjson_mut_obj_add_null(doc, obj, "march"); if (cpu.numaNodes > 0) yyjson_mut_obj_add_uint(doc, obj, "numaNodes", cpu.numaNodes); else yyjson_mut_obj_add_null(doc, obj, "numaNodes"); success = true; } ffStrbufDestroy(&cpu.name); ffStrbufDestroy(&cpu.vendor); return success; } void ffInitCPUOptions(FFCPUOptions* options) { ffOptionInitModuleArg(&options->moduleArgs, ""); ffStrbufInit(&options->tempSensor); options->temp = false; options->tempConfig = (FFColorRangeConfig) { 60, 80 }; options->showPeCoreCount = false; } void ffDestroyCPUOptions(FFCPUOptions* options) { ffOptionDestroyModuleArg(&options->moduleArgs); ffStrbufDestroy(&options->tempSensor); } FFModuleBaseInfo ffCPUModuleInfo = { .name = FF_CPU_MODULE_NAME, .description = "Print CPU name, frequency, etc", .initOptions = (void*) ffInitCPUOptions, .destroyOptions = (void*) ffDestroyCPUOptions, .parseJsonObject = (void*) ffParseCPUJsonObject, .printModule = (void*) ffPrintCPU, .generateJsonResult = (void*) ffGenerateCPUJsonResult, .generateJsonConfig = (void*) ffGenerateCPUJsonConfig, .formatArgs = FF_FORMAT_ARG_LIST(((FFModuleFormatArg[]) { {"Name", "name"}, {"Vendor", "vendor"}, {"Physical core count", "cores-physical"}, {"Logical core count", "cores-logical"}, {"Online core count", "cores-online"}, {"Base frequency (formatted)", "freq-base"}, {"Max frequency (formatted)", "freq-max"}, {"Temperature (formatted)", "temperature"}, {"Logical core count grouped by frequency", "core-types"}, {"Processor package count", "packages"}, {"CPU microarchitecture", "march"}, {"NUMA node count", "numa-nodes"}, })) }; ================================================ FILE: src/modules/cpu/cpu.h ================================================ #pragma once #include "option.h" #define FF_CPU_MODULE_NAME "CPU" bool ffPrintCPU(FFCPUOptions* options); void ffInitCPUOptions(FFCPUOptions* options); void ffDestroyCPUOptions(FFCPUOptions* options); extern FFModuleBaseInfo ffCPUModuleInfo; ================================================ FILE: src/modules/cpu/option.h ================================================ #pragma once #include "common/option.h" typedef struct FFCPUOptions { FFModuleArgs moduleArgs; FFstrbuf tempSensor; bool temp; FFColorRangeConfig tempConfig; bool showPeCoreCount; } FFCPUOptions; static_assert(sizeof(FFCPUOptions) <= FF_OPTION_MAX_SIZE, "FFCPUOptions size exceeds maximum allowed size"); ================================================ FILE: src/modules/cpucache/cpucache.c ================================================ #include "common/printing.h" #include "common/jsonconfig.h" #include "common/size.h" #include "common/stringUtils.h" #include "detection/cpucache/cpucache.h" #include "modules/cpucache/cpucache.h" #define FF_CPUCACHE_DISPLAY_NAME "CPU Cache" static void printCPUCacheNormal(const FFCPUCacheResult* result, FFCPUCacheOptions* options) { FF_STRBUF_AUTO_DESTROY buffer = ffStrbufCreate(); FF_STRBUF_AUTO_DESTROY key = ffStrbufCreate(); char levelStr[4] = "L"; for (uint32_t i = 0; i < ARRAY_SIZE(result->caches) && result->caches[i].length > 0; i++) { ffStrbufClear(&key); levelStr[1] = (char) ('1' + i); if (options->moduleArgs.key.length == 0) ffStrbufAppendF(&key, "%s (%s)", FF_CPUCACHE_DISPLAY_NAME, levelStr); else { uint32_t index = i + 1; FF_PARSE_FORMAT_STRING_CHECKED(&key, &options->moduleArgs.key, ((FFformatarg[]) { FF_ARG(index, "index"), FF_ARG(levelStr, "level"), FF_ARG(options->moduleArgs.keyIcon, "icon"), })); } ffStrbufClear(&buffer); uint32_t sum = 0; FF_LIST_FOR_EACH(FFCPUCache, src, result->caches[i]) { char typeStr = '?'; switch (src->type) { case FF_CPU_CACHE_TYPE_DATA: typeStr = 'D'; break; case FF_CPU_CACHE_TYPE_INSTRUCTION: typeStr = 'I'; break; case FF_CPU_CACHE_TYPE_UNIFIED: typeStr = 'U'; break; case FF_CPU_CACHE_TYPE_TRACE: typeStr = 'T'; break; } if (buffer.length) ffStrbufAppendS(&buffer, ", "); if (src->num > 1) ffStrbufAppendF(&buffer, "%ux", src->num); ffSizeAppendNum(src->size, &buffer); ffStrbufAppendF(&buffer, " (%c)", typeStr); sum += src->size * src->num; } if(options->moduleArgs.outputFormat.length == 0) { ffPrintLogoAndKey(key.chars, 0, &options->moduleArgs, FF_PRINT_TYPE_NO_CUSTOM_KEY); ffStrbufPutTo(&buffer, stdout); } else { FF_STRBUF_AUTO_DESTROY buffer2 = ffStrbufCreate(); ffSizeAppendNum(sum, &buffer2); FF_PRINT_FORMAT_CHECKED(key.chars, 0, &options->moduleArgs, FF_PRINT_TYPE_NO_CUSTOM_KEY, ((FFformatarg[]) { FF_ARG(buffer, "result"), FF_ARG(buffer2, "sum"), })); } } } static void printCPUCacheCompact(const FFCPUCacheResult* result, FFCPUCacheOptions* options) { FF_STRBUF_AUTO_DESTROY buffer = ffStrbufCreate(); uint64_t sum = 0; for (uint32_t i = 0; i < ARRAY_SIZE(result->caches) && result->caches[i].length > 0; i++) { if (buffer.length) ffStrbufAppendS(&buffer, ", "); uint32_t value = 0; FF_LIST_FOR_EACH(FFCPUCache, src, result->caches[i]) value += src->size * src->num; ffSizeAppendNum(value, &buffer); ffStrbufAppendF(&buffer, " (L%u)", i + 1); sum += value; } if (options->moduleArgs.outputFormat.length == 0) { ffPrintLogoAndKey(FF_CPUCACHE_DISPLAY_NAME, 0, &options->moduleArgs, FF_PRINT_TYPE_DEFAULT); ffStrbufPutTo(&buffer, stdout); } else { FF_STRBUF_AUTO_DESTROY buffer2 = ffStrbufCreate(); ffSizeAppendNum(sum, &buffer2); FF_PRINT_FORMAT_CHECKED(FF_CPUCACHE_DISPLAY_NAME, 0, &options->moduleArgs, FF_PRINT_TYPE_DEFAULT, ((FFformatarg[]) { FF_ARG(buffer, "result"), FF_ARG(buffer2, "sum"), })); } } bool ffPrintCPUCache(FFCPUCacheOptions* options) { bool success = false; FFCPUCacheResult result = { .caches = { ffListCreate(sizeof(FFCPUCache)), ffListCreate(sizeof(FFCPUCache)), ffListCreate(sizeof(FFCPUCache)), ffListCreate(sizeof(FFCPUCache)), }, }; const char* error = ffDetectCPUCache(&result); if(error) { ffPrintError(FF_CPUCACHE_DISPLAY_NAME, 0, &options->moduleArgs, FF_PRINT_TYPE_DEFAULT, "%s", error); goto exit; } if (!options->compact) printCPUCacheNormal(&result, options); else printCPUCacheCompact(&result, options); success = true; exit: ffListDestroy(&result.caches[0]); ffListDestroy(&result.caches[1]); ffListDestroy(&result.caches[2]); ffListDestroy(&result.caches[3]); return success; } void ffParseCPUCacheJsonObject(FFCPUCacheOptions* options, yyjson_val* module) { yyjson_val *key, *val; size_t idx, max; yyjson_obj_foreach(module, idx, max, key, val) { if (ffJsonConfigParseModuleArgs(key, val, &options->moduleArgs)) continue; if (unsafe_yyjson_equals_str(key, "compact")) { options->compact = yyjson_get_bool(val); continue; } ffPrintError(FF_CPUCACHE_DISPLAY_NAME, 0, &options->moduleArgs, FF_PRINT_TYPE_DEFAULT, "Unknown JSON key %s", unsafe_yyjson_get_str(key)); } } void ffGenerateCPUCacheJsonConfig(FFCPUCacheOptions* options, yyjson_mut_doc* doc, yyjson_mut_val* module) { ffJsonConfigGenerateModuleArgsConfig(doc, module, &options->moduleArgs); yyjson_mut_obj_add_bool(doc, module, "compact", options->compact); } bool ffGenerateCPUCacheJsonResult(FF_MAYBE_UNUSED FFCPUCacheOptions* options, yyjson_mut_doc* doc, yyjson_mut_val* module) { bool success = false; FFCPUCacheResult result = { .caches = { ffListCreate(sizeof(FFCPUCache)), ffListCreate(sizeof(FFCPUCache)), ffListCreate(sizeof(FFCPUCache)), ffListCreate(sizeof(FFCPUCache)), }, }; const char* error = ffDetectCPUCache(&result); if (error) { yyjson_mut_obj_add_str(doc, module, "error", error); goto exit; } yyjson_mut_val* caches = yyjson_mut_obj_add_obj(doc, module, "result"); for (uint32_t i = 0; i < ARRAY_SIZE(result.caches) && result.caches[i].length > 0; i++) { yyjson_mut_val* level = yyjson_mut_obj_add_arr(doc, caches, &"l1\0l2\0l3\0l4\0"[i * 3]); FF_LIST_FOR_EACH(FFCPUCache, src, result.caches[i]) { yyjson_mut_val* item = yyjson_mut_arr_add_obj(doc, level); yyjson_mut_obj_add_uint(doc, item, "size", src->size); yyjson_mut_obj_add_uint(doc, item, "num", src->num); const char* typeStr = "unknown"; switch (src->type) { case FF_CPU_CACHE_TYPE_DATA: typeStr = "data"; break; case FF_CPU_CACHE_TYPE_INSTRUCTION: typeStr = "instruction"; break; case FF_CPU_CACHE_TYPE_UNIFIED: typeStr = "unified"; break; case FF_CPU_CACHE_TYPE_TRACE: typeStr = "trace"; break; } yyjson_mut_obj_add_uint(doc, item, "lineSize", src->lineSize); yyjson_mut_obj_add_str(doc, item, "type", typeStr); } } success = true; exit: ffListDestroy(&result.caches[0]); ffListDestroy(&result.caches[1]); ffListDestroy(&result.caches[2]); ffListDestroy(&result.caches[3]); return success; } void ffInitCPUCacheOptions(FFCPUCacheOptions* options) { ffOptionInitModuleArg(&options->moduleArgs, ""); options->compact = false; } void ffDestroyCPUCacheOptions(FFCPUCacheOptions* options) { ffOptionDestroyModuleArg(&options->moduleArgs); } FFModuleBaseInfo ffCPUCacheModuleInfo = { .name = FF_CPUCACHE_MODULE_NAME, .description = "Print CPU cache sizes", .initOptions = (void*) ffInitCPUCacheOptions, .destroyOptions = (void*) ffDestroyCPUCacheOptions, .parseJsonObject = (void*) ffParseCPUCacheJsonObject, .printModule = (void*) ffPrintCPUCache, .generateJsonResult = (void*) ffGenerateCPUCacheJsonResult, .generateJsonConfig = (void*) ffGenerateCPUCacheJsonConfig, .formatArgs = FF_FORMAT_ARG_LIST(((FFModuleFormatArg[]) { {"Separate result", "result"}, {"Sum result", "sum"}, })) }; ================================================ FILE: src/modules/cpucache/cpucache.h ================================================ #pragma once #include "option.h" #define FF_CPUCACHE_MODULE_NAME "CPUCache" bool ffPrintCPUCache(FFCPUCacheOptions* options); void ffInitCPUCacheOptions(FFCPUCacheOptions* options); void ffDestroyCPUCacheOptions(FFCPUCacheOptions* options); extern FFModuleBaseInfo ffCPUCacheModuleInfo; ================================================ FILE: src/modules/cpucache/option.h ================================================ #pragma once #include "common/option.h" typedef struct FFCPUCacheOptions { FFModuleArgs moduleArgs; bool compact; } FFCPUCacheOptions; static_assert(sizeof(FFCPUCacheOptions) <= FF_OPTION_MAX_SIZE, "FFCPUCacheOptions size exceeds maximum allowed size"); ================================================ FILE: src/modules/cpuusage/cpuusage.c ================================================ #include "common/printing.h" #include "common/jsonconfig.h" #include "common/percent.h" #include "common/stringUtils.h" #include "detection/cpuusage/cpuusage.h" #include "modules/cpuusage/cpuusage.h" #define FF_CPUUSAGE_DISPLAY_NAME "CPU Usage" bool ffPrintCPUUsage(FFCPUUsageOptions* options) { FF_LIST_AUTO_DESTROY percentages = ffListCreate(sizeof(double)); const char* error = ffGetCpuUsageResult(options, &percentages); if(error) { ffPrintError(FF_CPUUSAGE_DISPLAY_NAME, 0, &options->moduleArgs, FF_PRINT_TYPE_DEFAULT, "%s", error); return false; } double maxValue = -999, minValue = 999, sumValue = 0; uint32_t maxIndex = 999, minIndex = 999; uint32_t index = 0, valueCount = 0; FF_LIST_FOR_EACH(double, percent, percentages) { if (*percent == *percent) { sumValue += *percent; #if _WIN32 // Windows may return values greater than 100%, cap them to 100% if (*percent > 100) *percent = 100; #endif if (*percent > maxValue) { maxValue = *percent; maxIndex = index; } if (*percent < minValue) { minValue = *percent; minIndex = index; } ++valueCount; } ++index; } double avgValue = sumValue / (double) valueCount; #if _WIN32 // See above comment if (avgValue > 100) avgValue = 100; #endif FFPercentageTypeFlags percentType = options->percent.type == 0 ? instance.config.display.percentType : options->percent.type; if(options->moduleArgs.outputFormat.length == 0) { ffPrintLogoAndKey(FF_CPUUSAGE_DISPLAY_NAME, 0, &options->moduleArgs, FF_PRINT_TYPE_DEFAULT); FF_STRBUF_AUTO_DESTROY str = ffStrbufCreate(); if (!options->separate) { if(percentType & FF_PERCENTAGE_TYPE_BAR_BIT) ffPercentAppendBar(&str, avgValue, options->percent, &options->moduleArgs); if(percentType & FF_PERCENTAGE_TYPE_NUM_BIT) { if(str.length > 0) ffStrbufAppendC(&str, ' '); ffPercentAppendNum(&str, avgValue, options->percent, str.length > 0, &options->moduleArgs); } } else { FF_LIST_FOR_EACH(double, percent, percentages) { if(str.length > 0) ffStrbufAppendC(&str, ' '); ffPercentAppendNum(&str, *percent, options->percent, false, &options->moduleArgs); } } ffStrbufPutTo(&str, stdout); } else { FF_STRBUF_AUTO_DESTROY avgNum = ffStrbufCreate(); if (percentType & FF_PERCENTAGE_TYPE_NUM_BIT) ffPercentAppendNum(&avgNum, avgValue, options->percent, false, &options->moduleArgs); FF_STRBUF_AUTO_DESTROY avgBar = ffStrbufCreate(); if (percentType & FF_PERCENTAGE_TYPE_BAR_BIT) ffPercentAppendBar(&avgBar, avgValue, options->percent, &options->moduleArgs); FF_STRBUF_AUTO_DESTROY minNum = ffStrbufCreate(); if (percentType & FF_PERCENTAGE_TYPE_NUM_BIT) ffPercentAppendNum(&minNum, minValue, options->percent, false, &options->moduleArgs); FF_STRBUF_AUTO_DESTROY minBar = ffStrbufCreate(); if (percentType & FF_PERCENTAGE_TYPE_BAR_BIT) ffPercentAppendBar(&minBar, minValue, options->percent, &options->moduleArgs); FF_STRBUF_AUTO_DESTROY maxNum = ffStrbufCreate(); if (percentType & FF_PERCENTAGE_TYPE_NUM_BIT) ffPercentAppendNum(&maxNum, maxValue, options->percent, false, &options->moduleArgs); FF_STRBUF_AUTO_DESTROY maxBar = ffStrbufCreate(); if (percentType & FF_PERCENTAGE_TYPE_BAR_BIT) ffPercentAppendBar(&maxBar, maxValue, options->percent, &options->moduleArgs); FF_PRINT_FORMAT_CHECKED(FF_CPUUSAGE_DISPLAY_NAME, 0, &options->moduleArgs, FF_PRINT_TYPE_DEFAULT, ((FFformatarg[]){ FF_ARG(avgNum, "avg"), FF_ARG(maxNum, "max"), FF_ARG(maxIndex, "max-index"), FF_ARG(minNum, "min"), FF_ARG(minIndex, "min-index"), FF_ARG(avgBar, "avg-bar"), FF_ARG(maxBar, "max-bar"), FF_ARG(minBar, "min-bar"), })); } return true; } void ffParseCPUUsageJsonObject(FFCPUUsageOptions* options, yyjson_val* module) { yyjson_val *key, *val; size_t idx, max; yyjson_obj_foreach(module, idx, max, key, val) { if (ffJsonConfigParseModuleArgs(key, val, &options->moduleArgs)) continue; if (unsafe_yyjson_equals_str(key, "separate")) { options->separate = yyjson_get_bool(val); continue; } if (unsafe_yyjson_equals_str(key, "waitTime")) { options->waitTime = (uint32_t) yyjson_get_uint(val); continue; } if (ffPercentParseJsonObject(key, val, &options->percent)) continue; ffPrintError(FF_CPUUSAGE_MODULE_NAME, 0, &options->moduleArgs, FF_PRINT_TYPE_DEFAULT, "Unknown JSON key %s", unsafe_yyjson_get_str(key)); } } void ffGenerateCPUUsageJsonConfig(FFCPUUsageOptions* options, yyjson_mut_doc* doc, yyjson_mut_val* module) { ffJsonConfigGenerateModuleArgsConfig(doc, module, &options->moduleArgs); yyjson_mut_obj_add_bool(doc, module, "separate", options->separate); ffPercentGenerateJsonConfig(doc, module, options->percent); yyjson_mut_obj_add_uint(doc, module, "waitTime", options->waitTime); } bool ffGenerateCPUUsageJsonResult(FFCPUUsageOptions* options, yyjson_mut_doc* doc, yyjson_mut_val* module) { FF_LIST_AUTO_DESTROY percentages = ffListCreate(sizeof(double)); const char* error = ffGetCpuUsageResult(options, &percentages); if(error) { yyjson_mut_obj_add_str(doc, module, "error", error); return false; } yyjson_mut_val* result = yyjson_mut_obj_add_arr(doc, module, "result"); FF_LIST_FOR_EACH(double, percent, percentages) { yyjson_mut_arr_add_real(doc, result, *percent); } return true; } void ffInitCPUUsageOptions(FFCPUUsageOptions* options) { ffOptionInitModuleArg(&options->moduleArgs, "󰓅"); options->separate = false; options->percent = (FFPercentageModuleConfig) { 50, 80, 0 }; options->waitTime = 200; } void ffDestroyCPUUsageOptions(FFCPUUsageOptions* options) { ffOptionDestroyModuleArg(&options->moduleArgs); } FFModuleBaseInfo ffCPUUsageModuleInfo = { .name = FF_CPUUSAGE_MODULE_NAME, .description = "Print CPU usage. Costs some time to collect data", .initOptions = (void*) ffInitCPUUsageOptions, .destroyOptions = (void*) ffDestroyCPUUsageOptions, .parseJsonObject = (void*) ffParseCPUUsageJsonObject, .printModule = (void*) ffPrintCPUUsage, .generateJsonResult = (void*) ffGenerateCPUUsageJsonResult, .generateJsonConfig = (void*) ffGenerateCPUUsageJsonConfig, .formatArgs = FF_FORMAT_ARG_LIST(((FFModuleFormatArg[]) { {"CPU usage (percentage num, average)", "avg"}, {"CPU usage (percentage num, maximum)", "max"}, {"CPU core index of maximum usage", "max-index"}, {"CPU usage (percentage num, minimum)", "min"}, {"CPU core index of minimum usage", "min-index"}, {"CPU usage (percentage bar, average)", "avg-bar"}, {"CPU usage (percentage bar, maximum)", "max-bar"}, {"CPU usage (percentage bar, minimum)", "min-bar"}, })) }; ================================================ FILE: src/modules/cpuusage/cpuusage.h ================================================ #pragma once #include "option.h" #define FF_CPUUSAGE_MODULE_NAME "CPUUsage" void ffPrepareCPUUsage(); bool ffPrintCPUUsage(FFCPUUsageOptions* options); void ffInitCPUUsageOptions(FFCPUUsageOptions* options); void ffDestroyCPUUsageOptions(FFCPUUsageOptions* options); extern FFModuleBaseInfo ffCPUUsageModuleInfo; ================================================ FILE: src/modules/cpuusage/option.h ================================================ #pragma once #include "common/option.h" #include "common/percent.h" typedef struct FFCPUUsageOptions { FFModuleArgs moduleArgs; bool separate; FFPercentageModuleConfig percent; uint32_t waitTime; // in ms } FFCPUUsageOptions; static_assert(sizeof(FFCPUUsageOptions) <= FF_OPTION_MAX_SIZE, "FFCPUUsageOptions size exceeds maximum allowed size"); ================================================ FILE: src/modules/cursor/cursor.c ================================================ #include "common/printing.h" #include "common/jsonconfig.h" #include "common/stringUtils.h" #include "detection/cursor/cursor.h" #include "modules/cursor/cursor.h" bool ffPrintCursor(FFCursorOptions* options) { bool success = false; FFCursorResult result; ffStrbufInit(&result.error); ffStrbufInit(&result.theme); ffStrbufInit(&result.size); ffDetectCursor(&result); if(result.error.length) ffPrintError(FF_CURSOR_MODULE_NAME, 0, &options->moduleArgs, FF_PRINT_TYPE_DEFAULT, "%s", result.error.chars); else { ffStrbufRemoveIgnCaseEndS(&result.theme, "cursors"); ffStrbufRemoveIgnCaseEndS(&result.theme, "cursor"); ffStrbufTrimRight(&result.theme, '_'); ffStrbufTrimRight(&result.theme, '-'); if(result.theme.length == 0) ffStrbufAppendS(&result.theme, "default"); if(options->moduleArgs.outputFormat.length == 0) { ffPrintLogoAndKey(FF_CURSOR_MODULE_NAME, 0, &options->moduleArgs, FF_PRINT_TYPE_DEFAULT); ffStrbufWriteTo(&result.theme, stdout); if(result.size.length > 0 && !ffStrbufEqualS(&result.size, "0")) printf(" (%spx)", result.size.chars); putchar('\n'); } else { FF_PRINT_FORMAT_CHECKED(FF_CURSOR_MODULE_NAME, 0, &options->moduleArgs, FF_PRINT_TYPE_DEFAULT, ((FFformatarg[]) { FF_ARG(result.theme, "theme"), FF_ARG(result.size, "size"), })); } success = true; } ffStrbufDestroy(&result.error); ffStrbufDestroy(&result.theme); ffStrbufDestroy(&result.size); return success; } void ffParseCursorJsonObject(FFCursorOptions* options, yyjson_val* module) { yyjson_val *key, *val; size_t idx, max; yyjson_obj_foreach(module, idx, max, key, val) { if (ffJsonConfigParseModuleArgs(key, val, &options->moduleArgs)) continue; ffPrintError(FF_CURSOR_MODULE_NAME, 0, &options->moduleArgs, FF_PRINT_TYPE_DEFAULT, "Unknown JSON key %s", unsafe_yyjson_get_str(key)); } } void ffGenerateCursorJsonConfig(FFCursorOptions* options, yyjson_mut_doc* doc, yyjson_mut_val* module) { ffJsonConfigGenerateModuleArgsConfig(doc, module, &options->moduleArgs); } bool ffGenerateCursorJsonResult(FF_MAYBE_UNUSED FFCursorOptions* options, yyjson_mut_doc* doc, yyjson_mut_val* module) { bool success = false; FFCursorResult result; ffStrbufInit(&result.error); ffStrbufInit(&result.theme); ffStrbufInit(&result.size); ffDetectCursor(&result); if (result.error.length) { yyjson_mut_obj_add_strbuf(doc, module, "error", &result.error); } else { yyjson_mut_val* obj = yyjson_mut_obj_add_obj(doc, module, "result"); yyjson_mut_obj_add_strbuf(doc, obj, "theme", &result.theme); yyjson_mut_obj_add_strbuf(doc, obj, "size", &result.size); success = true; } ffStrbufDestroy(&result.error); ffStrbufDestroy(&result.theme); ffStrbufDestroy(&result.size); return success; } void ffInitCursorOptions(FFCursorOptions* options) { ffOptionInitModuleArg(&options->moduleArgs, "󰆿"); } void ffDestroyCursorOptions(FFCursorOptions* options) { ffOptionDestroyModuleArg(&options->moduleArgs); } FFModuleBaseInfo ffCursorModuleInfo = { .name = FF_CURSOR_MODULE_NAME, .description = "Print cursor style name", .initOptions = (void*) ffInitCursorOptions, .destroyOptions = (void*) ffDestroyCursorOptions, .parseJsonObject = (void*) ffParseCursorJsonObject, .printModule = (void*) ffPrintCursor, .generateJsonResult = (void*) ffGenerateCursorJsonResult, .generateJsonConfig = (void*) ffGenerateCursorJsonConfig, .formatArgs = FF_FORMAT_ARG_LIST(((FFModuleFormatArg[]) { {"Cursor theme", "theme"}, {"Cursor size", "size"}, })), }; ================================================ FILE: src/modules/cursor/cursor.h ================================================ #pragma once #include "option.h" #define FF_CURSOR_MODULE_NAME "Cursor" bool ffPrintCursor(FFCursorOptions* options); void ffInitCursorOptions(FFCursorOptions* options); void ffDestroyCursorOptions(FFCursorOptions* options); extern FFModuleBaseInfo ffCursorModuleInfo; ================================================ FILE: src/modules/cursor/option.h ================================================ #pragma once #include "common/option.h" typedef struct FFCursorOptions { FFModuleArgs moduleArgs; } FFCursorOptions; static_assert(sizeof(FFCursorOptions) <= FF_OPTION_MAX_SIZE, "FFCursorOptions size exceeds maximum allowed size"); ================================================ FILE: src/modules/custom/custom.c ================================================ #include "common/printing.h" #include "common/jsonconfig.h" #include "common/textModifier.h" #include "common/stringUtils.h" #include "modules/custom/custom.h" bool ffPrintCustom(FFCustomOptions* options) { ffPrintFormat(FF_CUSTOM_MODULE_NAME, 0, &options->moduleArgs, FF_PRINT_TYPE_DEFAULT, 0, ((FFformatarg[]) {})); return true; } void ffGenerateCustomJsonConfig(FFCustomOptions* options, yyjson_mut_doc* doc, yyjson_mut_val* module) { ffJsonConfigGenerateModuleArgsConfig(doc, module, &options->moduleArgs); } void ffParseCustomJsonObject(FFCustomOptions* options, yyjson_val* module) { yyjson_val *key, *val; size_t idx, max; yyjson_obj_foreach(module, idx, max, key, val) { if (ffJsonConfigParseModuleArgs(key, val, &options->moduleArgs)) continue; ffPrintError(FF_CUSTOM_MODULE_NAME, 0, &options->moduleArgs, FF_PRINT_TYPE_DEFAULT, "Unknown JSON key %s", unsafe_yyjson_get_str(key)); } } void ffInitCustomOptions(FFCustomOptions* options) { ffOptionInitModuleArg(&options->moduleArgs, ""); ffStrbufSetStatic(&options->moduleArgs.key, " "); } void ffDestroyCustomOptions(FFCustomOptions* options) { ffOptionDestroyModuleArg(&options->moduleArgs); } FFModuleBaseInfo ffCustomModuleInfo = { .name = FF_CUSTOM_MODULE_NAME, .description = "Print a custom string, with or without key", .initOptions = (void*) ffInitCustomOptions, .destroyOptions = (void*) ffDestroyCustomOptions, .parseJsonObject = (void*) ffParseCustomJsonObject, .printModule = (void*) ffPrintCustom, .generateJsonConfig = (void*) ffGenerateCustomJsonConfig, }; ================================================ FILE: src/modules/custom/custom.h ================================================ #pragma once #include "option.h" #define FF_CUSTOM_MODULE_NAME "Custom" bool ffPrintCustom(FFCustomOptions* options); void ffInitCustomOptions(FFCustomOptions* options); void ffDestroyCustomOptions(FFCustomOptions* options); extern FFModuleBaseInfo ffCustomModuleInfo; ================================================ FILE: src/modules/custom/option.h ================================================ #pragma once #include "common/option.h" typedef struct FFCustomOptions { FFModuleArgs moduleArgs; } FFCustomOptions; static_assert(sizeof(FFCustomOptions) <= FF_OPTION_MAX_SIZE, "FFCustomOptions size exceeds maximum allowed size"); ================================================ FILE: src/modules/datetime/datetime.c ================================================ #include "common/time.h" #include "common/printing.h" #include "common/jsonconfig.h" #include "common/stringUtils.h" #include "modules/datetime/datetime.h" #include #pragma GCC diagnostic ignored "-Wformat" // warning: unknown conversion type character 'F' in format #define FF_DATETIME_DISPLAY_NAME "Date & Time" typedef struct FFDateTimeResult { //Examples for 21.02.2022 - 15:18:37 uint16_t year; //2022 uint8_t yearShort; //22 uint8_t month; //2 char monthPretty[FASTFETCH_STRBUF_DEFAULT_ALLOC]; //02 char monthName[FASTFETCH_STRBUF_DEFAULT_ALLOC]; //February char monthNameShort[FASTFETCH_STRBUF_DEFAULT_ALLOC]; //Feb uint8_t week; //8 char weekday[FASTFETCH_STRBUF_DEFAULT_ALLOC]; //Monday char weekdayShort[FASTFETCH_STRBUF_DEFAULT_ALLOC]; //Mon uint16_t dayInYear; //52 uint8_t dayInMonth; //21 uint8_t dayInWeek; //1 char dayPretty[FASTFETCH_STRBUF_DEFAULT_ALLOC]; //01 uint8_t hour; //15 char hourPretty[FASTFETCH_STRBUF_DEFAULT_ALLOC]; //15 uint8_t hour12; //3 char hour12Pretty[FASTFETCH_STRBUF_DEFAULT_ALLOC]; //03 uint8_t minute; //18 char minutePretty[FASTFETCH_STRBUF_DEFAULT_ALLOC]; //18 uint8_t second; //37 char secondPretty[FASTFETCH_STRBUF_DEFAULT_ALLOC]; //37 char offsetFromUtc[FASTFETCH_STRBUF_DEFAULT_ALLOC]; char timezoneName[FASTFETCH_STRBUF_DEFAULT_ALLOC]; } FFDateTimeResult; static void printDateTimeFormat(struct tm* tm, const FFModuleArgs* moduleArgs) { FFDateTimeResult result; result.year = (uint16_t) (tm->tm_year + 1900); result.yearShort = (uint8_t) (result.year % 100); result.month = (uint8_t) (tm->tm_mon + 1); strftime(result.monthPretty, sizeof(result.monthPretty), "%m", tm); strftime(result.monthName, sizeof(result.monthName), "%B", tm); strftime(result.monthNameShort, sizeof(result.monthNameShort), "%b", tm); result.week = (uint8_t) (tm->tm_yday / 7 + 1); strftime(result.weekday, sizeof(result.weekday), "%A", tm); strftime(result.weekdayShort, sizeof(result.weekdayShort), "%a", tm); result.dayInYear = (uint8_t) (tm->tm_yday + 1); result.dayInMonth = (uint8_t) tm->tm_mday; result.dayInWeek = tm->tm_wday == 0 ? 7 : (uint8_t) tm->tm_wday; strftime(result.dayPretty, sizeof(result.dayPretty), "%d", tm); result.hour = (uint8_t) tm->tm_hour; strftime(result.hourPretty, sizeof(result.hourPretty), "%H", tm); result.hour12 = (uint8_t) (result.hour % 12); strftime(result.hour12Pretty, sizeof(result.hour12Pretty), "%I", tm); result.minute = (uint8_t) tm->tm_min; strftime(result.minutePretty, sizeof(result.minutePretty), "%M", tm); result.second = (uint8_t) tm->tm_sec; 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 })); } bool ffPrintDateTime(FFDateTimeOptions* options) { uint64_t msNow = ffTimeGetNow(); time_t sNow = (time_t) (msNow / 1000); struct tm* tm = localtime(&sNow); if(options->moduleArgs.outputFormat.length > 0) { printDateTimeFormat(tm, &options->moduleArgs); return true; } char buffer[32]; if (strftime(buffer, ARRAY_SIZE(buffer), "%F %T", tm) == 0) //yyyy-MM-dd HH:mm:ss { ffPrintError(FF_DATETIME_DISPLAY_NAME, 0, &options->moduleArgs, FF_PRINT_TYPE_DEFAULT, "strftime() failed"); return false; } ffPrintLogoAndKey(FF_DATETIME_DISPLAY_NAME, 0, &options->moduleArgs, FF_PRINT_TYPE_DEFAULT); puts(buffer); return true; } void ffParseDateTimeJsonObject(FFDateTimeOptions* options, yyjson_val* module) { yyjson_val *key, *val; size_t idx, max; yyjson_obj_foreach(module, idx, max, key, val) { if (ffJsonConfigParseModuleArgs(key, val, &options->moduleArgs)) continue; ffPrintError(FF_DATETIME_DISPLAY_NAME, 0, &options->moduleArgs, FF_PRINT_TYPE_DEFAULT, "Unknown JSON key %s", unsafe_yyjson_get_str(key)); } } void ffGenerateDateTimeJsonConfig(FFDateTimeOptions* options, yyjson_mut_doc* doc, yyjson_mut_val* module) { ffJsonConfigGenerateModuleArgsConfig(doc, module, &options->moduleArgs); } bool ffGenerateDateTimeJsonResult(FF_MAYBE_UNUSED FFDateTimeOptions* options, yyjson_mut_doc* doc, yyjson_mut_val* module) { yyjson_mut_obj_add_strcpy(doc, module, "result", ffTimeToFullStr(ffTimeGetNow())); return true; } void ffInitDateTimeOptions(FFDateTimeOptions* options) { ffOptionInitModuleArg(&options->moduleArgs, ""); } void ffDestroyDateTimeOptions(FFDateTimeOptions* options) { ffOptionDestroyModuleArg(&options->moduleArgs); } FFModuleBaseInfo ffDateTimeModuleInfo = { .name = FF_DATETIME_MODULE_NAME, .description = "Print current date and time", .initOptions = (void*) ffInitDateTimeOptions, .destroyOptions = (void*) ffDestroyDateTimeOptions, .parseJsonObject = (void*) ffParseDateTimeJsonObject, .printModule = (void*) ffPrintDateTime, .generateJsonResult = (void*) ffGenerateDateTimeJsonResult, .generateJsonConfig = (void*) ffGenerateDateTimeJsonConfig, .formatArgs = FF_FORMAT_ARG_LIST(((FFModuleFormatArg[]) { {"Year", "year"}, {"Last two digits of year", "year-short"}, {"Month", "month"}, {"Month with leading zero", "month-pretty"}, {"Month name", "month-name"}, {"Month name short", "month-name-short"}, {"Week number on year", "week"}, {"Weekday", "weekday"}, {"Weekday short", "weekday-short"}, {"Day in year", "day-in-year"}, {"Day in month", "day-in-month"}, {"Day in week", "day-in-week"}, {"Hour", "hour"}, {"Hour with leading zero", "hour-pretty"}, {"Hour 12h format", "hour-12"}, {"Hour 12h format with leading zero", "hour-12-pretty"}, {"Minute", "minute"}, {"Minute with leading zero", "minute-pretty"}, {"Second", "second"}, {"Second with leading zero", "second-pretty"}, {"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"}, })) }; ================================================ FILE: src/modules/datetime/datetime.h ================================================ #pragma once #include "option.h" #define FF_DATETIME_MODULE_NAME "DateTime" bool ffPrintDateTime(FFDateTimeOptions* options); void ffInitDateTimeOptions(FFDateTimeOptions* options); void ffDestroyDateTimeOptions(FFDateTimeOptions* options); extern FFModuleBaseInfo ffDateTimeModuleInfo; ================================================ FILE: src/modules/datetime/option.h ================================================ #pragma once #include "common/option.h" typedef struct FFDateTimeOptions { FFModuleArgs moduleArgs; } FFDateTimeOptions; static_assert(sizeof(FFDateTimeOptions) <= FF_OPTION_MAX_SIZE, "FFDateTimeOptions size exceeds maximum allowed size"); ================================================ FILE: src/modules/de/de.c ================================================ #include "common/printing.h" #include "common/jsonconfig.h" #include "common/stringUtils.h" #include "detection/displayserver/displayserver.h" #include "detection/de/de.h" #include "modules/de/de.h" bool ffPrintDE(FFDEOptions* options) { const FFDisplayServerResult* result = ffConnectDisplayServer(); if(result->dePrettyName.length == 0) { ffPrintError(FF_DE_MODULE_NAME, 0, &options->moduleArgs, FF_PRINT_TYPE_DEFAULT, "No DE found"); return false; } FF_STRBUF_AUTO_DESTROY version = ffStrbufCreate(); ffDetectDEVersion(&result->dePrettyName, &version, options); if(options->moduleArgs.outputFormat.length == 0) { ffPrintLogoAndKey(FF_DE_MODULE_NAME, 0, &options->moduleArgs, FF_PRINT_TYPE_DEFAULT); ffStrbufWriteTo(&result->dePrettyName, stdout); if(version.length > 0) { putchar(' '); ffStrbufWriteTo(&version, stdout); } putchar('\n'); } else { FF_PRINT_FORMAT_CHECKED(FF_DE_MODULE_NAME, 0, &options->moduleArgs, FF_PRINT_TYPE_DEFAULT, ((FFformatarg[]){ FF_ARG(result->deProcessName, "process-name"), FF_ARG(result->dePrettyName, "pretty-name"), FF_ARG(version, "version") })); } return true; } void ffParseDEJsonObject(FFDEOptions* options, yyjson_val* module) { yyjson_val *key, *val; size_t idx, max; yyjson_obj_foreach(module, idx, max, key, val) { if (ffJsonConfigParseModuleArgs(key, val, &options->moduleArgs)) continue; if (unsafe_yyjson_equals_str(key, "slowVersionDetection")) { ffPrintError(FF_DE_MODULE_NAME, 0, &options->moduleArgs, FF_PRINT_TYPE_DEFAULT, "Key `slowVersionDetection` is deprecated, it's always true"); continue; } ffPrintError(FF_DE_MODULE_NAME, 0, &options->moduleArgs, FF_PRINT_TYPE_DEFAULT, "Unknown JSON key %s", unsafe_yyjson_get_str(key)); } } void ffGenerateDEJsonConfig(FFDEOptions* options, yyjson_mut_doc* doc, yyjson_mut_val* module) { ffJsonConfigGenerateModuleArgsConfig(doc, module, &options->moduleArgs); } bool ffGenerateDEJsonResult(FF_MAYBE_UNUSED FFDEOptions* options, yyjson_mut_doc* doc, yyjson_mut_val* module) { const FFDisplayServerResult* result = ffConnectDisplayServer(); if(result->dePrettyName.length == 0) { yyjson_mut_obj_add_str(doc, module, "error", "No DE found"); return false; } FF_STRBUF_AUTO_DESTROY version = ffStrbufCreate(); ffDetectDEVersion(&result->dePrettyName, &version, options); yyjson_mut_val* obj = yyjson_mut_obj_add_obj(doc, module, "result"); yyjson_mut_obj_add_strbuf(doc, obj, "processName", &result->deProcessName); yyjson_mut_obj_add_strbuf(doc, obj, "prettyName", &result->dePrettyName); yyjson_mut_obj_add_strbuf(doc, obj, "version", &version); return true; } void ffInitDEOptions(FFDEOptions* options) { ffOptionInitModuleArg(&options->moduleArgs, ""); } void ffDestroyDEOptions(FFDEOptions* options) { ffOptionDestroyModuleArg(&options->moduleArgs); } FFModuleBaseInfo ffDEModuleInfo = { .name = FF_DE_MODULE_NAME, .description = "Print desktop environment name", .initOptions = (void*) ffInitDEOptions, .destroyOptions = (void*) ffDestroyDEOptions, .parseJsonObject = (void*) ffParseDEJsonObject, .printModule = (void*) ffPrintDE, .generateJsonResult = (void*) ffGenerateDEJsonResult, .generateJsonConfig = (void*) ffGenerateDEJsonConfig, .formatArgs = FF_FORMAT_ARG_LIST(((FFModuleFormatArg[]) { {"DE process name", "process-name"}, {"DE pretty name", "pretty-name"}, {"DE version", "version"}, })) }; ================================================ FILE: src/modules/de/de.h ================================================ #pragma once #include "option.h" #define FF_DE_MODULE_NAME "DE" bool ffPrintDE(FFDEOptions* options); void ffInitDEOptions(FFDEOptions* options); void ffDestroyDEOptions(FFDEOptions* options); extern FFModuleBaseInfo ffDEModuleInfo; ================================================ FILE: src/modules/de/option.h ================================================ #pragma once #include "common/option.h" typedef struct FFDEOptions { FFModuleArgs moduleArgs; } FFDEOptions; static_assert(sizeof(FFDEOptions) <= FF_OPTION_MAX_SIZE, "FFDEOptions size exceeds maximum allowed size"); ================================================ FILE: src/modules/disk/disk.c ================================================ #include "common/printing.h" #include "common/jsonconfig.h" #include "common/percent.h" #include "common/size.h" #include "common/time.h" #include "detection/disk/disk.h" #include "modules/disk/disk.h" #ifndef _WIN32 #include #endif #pragma GCC diagnostic ignored "-Wsign-conversion" static void printDisk(FFDiskOptions* options, const FFDisk* disk, uint32_t index) { FF_STRBUF_AUTO_DESTROY key = ffStrbufCreate(); if(options->moduleArgs.key.length == 0) { ffStrbufSetF(&key, "%s (%s)", FF_DISK_MODULE_NAME, disk->mountpoint.chars); } else { FF_STRBUF_AUTO_DESTROY mountpointLink = ffStrbufCreate(); FF_STRBUF_AUTO_DESTROY nameLink = ffStrbufCreate(); #ifdef __linux__ if (getenv("WSL_DISTRO_NAME") != NULL && getenv("WT_SESSION") != NULL) { if (ffStrbufEqualS(&disk->filesystem, "9p") && ffStrbufStartsWithS(&disk->mountpoint, "/mnt/")) { ffStrbufSetF(&mountpointLink, "\e]8;;file:///%c:/\e\\%s\e]8;;\e\\", disk->mountpoint.chars[5], disk->mountpoint.chars); ffStrbufSetF(&nameLink, "\e]8;;file:///%c:/\e\\%s\e]8;;\e\\", disk->mountpoint.chars[5], disk->name.chars); } else { ffStrbufSetF(&mountpointLink, "\e]8;;file:////wsl.localhost/%s%s\e\\%s\e]8;;\e\\", getenv("WSL_DISTRO_NAME"), disk->mountpoint.chars, disk->mountpoint.chars); ffStrbufSetF(&nameLink, "\e]8;;file:////wsl.localhost/%s%s\e\\%s\e]8;;\e\\", getenv("WSL_DISTRO_NAME"), disk->mountpoint.chars, disk->name.chars); } } else #endif { ffStrbufSetF(&mountpointLink, "\e]8;;file://%s\e\\%s\e]8;;\e\\", disk->mountpoint.chars, disk->mountpoint.chars); ffStrbufSetF(&nameLink, "\e]8;;file://%s\e\\%s\e]8;;\e\\", disk->mountpoint.chars, disk->name.chars); } FF_PARSE_FORMAT_STRING_CHECKED(&key, &options->moduleArgs.key, ((FFformatarg[]) { FF_ARG(disk->mountpoint, "mountpoint"), FF_ARG(disk->name, "name"), FF_ARG(disk->mountFrom, "mount-from"), FF_ARG(options->moduleArgs.keyIcon, "icon"), FF_ARG(index, "index"), FF_ARG(disk->filesystem, "filesystem"), FF_ARG(mountpointLink, "mountpoint-link"), FF_ARG(nameLink, "name-link"), })); } FF_STRBUF_AUTO_DESTROY usedPretty = ffStrbufCreate(); ffSizeAppendNum(disk->bytesUsed, &usedPretty); FF_STRBUF_AUTO_DESTROY totalPretty = ffStrbufCreate(); ffSizeAppendNum(disk->bytesTotal, &totalPretty); double bytesPercentage = disk->bytesTotal > 0 ? (double) disk->bytesUsed / (double) disk->bytesTotal * 100.0 : 0; FFPercentageTypeFlags percentType = options->percent.type == 0 ? instance.config.display.percentType : options->percent.type; if(options->moduleArgs.outputFormat.length == 0) { ffPrintLogoAndKey(key.chars, 0, &options->moduleArgs, FF_PRINT_TYPE_NO_CUSTOM_KEY); FF_STRBUF_AUTO_DESTROY str = ffStrbufCreate(); if(disk->bytesTotal > 0) { if(percentType & FF_PERCENTAGE_TYPE_BAR_BIT) { ffPercentAppendBar(&str, bytesPercentage, options->percent, &options->moduleArgs); ffStrbufAppendC(&str, ' '); } if(!(percentType & FF_PERCENTAGE_TYPE_HIDE_OTHERS_BIT)) ffStrbufAppendF(&str, "%s / %s ", usedPretty.chars, totalPretty.chars); if(percentType & FF_PERCENTAGE_TYPE_NUM_BIT) { ffPercentAppendNum(&str, bytesPercentage, options->percent, str.length > 0, &options->moduleArgs); ffStrbufAppendC(&str, ' '); } } else ffStrbufAppendS(&str, "Unknown "); if(!(percentType & FF_PERCENTAGE_TYPE_HIDE_OTHERS_BIT)) { if(disk->filesystem.length) ffStrbufAppendF(&str, "- %s ", disk->filesystem.chars); ffStrbufAppendC(&str, '['); if(disk->type & FF_DISK_VOLUME_TYPE_EXTERNAL_BIT) ffStrbufAppendS(&str, "External, "); if(disk->type & FF_DISK_VOLUME_TYPE_SUBVOLUME_BIT) ffStrbufAppendS(&str, "Subvolume, "); if(disk->type & FF_DISK_VOLUME_TYPE_HIDDEN_BIT) ffStrbufAppendS(&str, "Hidden, "); if(disk->type & FF_DISK_VOLUME_TYPE_READONLY_BIT) ffStrbufAppendS(&str, "Read-only, "); if (str.chars[str.length - 1] == '[') ffStrbufSubstrBefore(&str, str.length - 1); else { ffStrbufTrimRight(&str, ' '); str.chars[str.length - 1] = ']'; } } ffStrbufTrimRight(&str, ' '); ffStrbufPutTo(&str, stdout); } else { FF_STRBUF_AUTO_DESTROY bytesPercentageNum = ffStrbufCreate(); if (percentType & FF_PERCENTAGE_TYPE_NUM_BIT) ffPercentAppendNum(&bytesPercentageNum, bytesPercentage, options->percent, false, &options->moduleArgs); FF_STRBUF_AUTO_DESTROY bytesPercentageBar = ffStrbufCreate(); if (percentType & FF_PERCENTAGE_TYPE_BAR_BIT) ffPercentAppendBar(&bytesPercentageBar, bytesPercentage, options->percent, &options->moduleArgs); double filesPercentage = disk->filesTotal > 0 ? ((double) disk->filesUsed / (double) disk->filesTotal) * 100.0 : 0; FF_STRBUF_AUTO_DESTROY filesPercentageNum = ffStrbufCreate(); if (percentType & FF_PERCENTAGE_TYPE_NUM_BIT) ffPercentAppendNum(&filesPercentageNum, filesPercentage, options->percent, false, &options->moduleArgs); FF_STRBUF_AUTO_DESTROY filesPercentageBar = ffStrbufCreate(); if (percentType & FF_PERCENTAGE_TYPE_BAR_BIT) ffPercentAppendBar(&filesPercentageBar, filesPercentage, options->percent, &options->moduleArgs); bool isExternal = !!(disk->type & FF_DISK_VOLUME_TYPE_EXTERNAL_BIT); bool isHidden = !!(disk->type & FF_DISK_VOLUME_TYPE_HIDDEN_BIT); bool isReadOnly = !!(disk->type & FF_DISK_VOLUME_TYPE_READONLY_BIT); FF_STRBUF_AUTO_DESTROY freePretty = ffStrbufCreate(); ffSizeAppendNum(disk->bytesFree, &freePretty); FF_STRBUF_AUTO_DESTROY availPretty = ffStrbufCreate(); ffSizeAppendNum(disk->bytesAvailable, &availPretty); uint64_t now = ffTimeGetNow(); uint64_t duration = now - disk->createTime; uint32_t milliseconds = (uint32_t) (duration % 1000); duration /= 1000; uint32_t seconds = (uint32_t) (duration % 60); duration /= 60; uint32_t minutes = (uint32_t) (duration % 60); duration /= 60; uint32_t hours = (uint32_t) (duration % 24); duration /= 24; uint32_t days = (uint32_t) duration; FFTimeGetAgeResult age = ffTimeGetAge(disk->createTime, now); FF_PRINT_FORMAT_CHECKED(key.chars, 0, &options->moduleArgs, FF_PRINT_TYPE_NO_CUSTOM_KEY, ((FFformatarg[]) { FF_ARG(usedPretty, "size-used"), FF_ARG(totalPretty, "size-total"), FF_ARG(bytesPercentageNum, "size-percentage"), FF_ARG(disk->filesUsed, "files-used"), FF_ARG(disk->filesTotal, "files-total"), FF_ARG(filesPercentageNum, "files-percentage"), FF_ARG(isExternal, "is-external"), FF_ARG(isHidden, "is-hidden"), FF_ARG(disk->filesystem, "filesystem"), FF_ARG(disk->name, "name"), FF_ARG(isReadOnly, "is-readonly"), {FF_ARG_TYPE_STRING, ffTimeToShortStr(disk->createTime), "create-time"}, FF_ARG(bytesPercentageBar, "size-percentage-bar"), FF_ARG(filesPercentageBar, "files-percentage-bar"), FF_ARG(days, "days"), FF_ARG(hours, "hours"), FF_ARG(minutes, "minutes"), FF_ARG(seconds, "seconds"), FF_ARG(milliseconds, "milliseconds"), FF_ARG(disk->mountpoint, "mountpoint"), FF_ARG(disk->mountFrom, "mount-from"), FF_ARG(age.years, "years"), FF_ARG(age.daysOfYear, "days-of-year"), FF_ARG(age.yearsFraction, "years-fraction"), FF_ARG(freePretty, "size-free"), FF_ARG(availPretty, "size-available"), })); } } bool ffPrintDisk(FFDiskOptions* options) { FF_LIST_AUTO_DESTROY disks = ffListCreate(sizeof (FFDisk)); const char* error = ffDetectDisks(options, &disks); if(error) { ffPrintError(FF_DISK_MODULE_NAME, 0, &options->moduleArgs, FF_PRINT_TYPE_DEFAULT, "%s", error); return false; } if(disks.length == 0) { ffPrintError(FF_DISK_MODULE_NAME, 0, &options->moduleArgs, FF_PRINT_TYPE_DEFAULT, "No disks found"); return false; } uint32_t index = 0; FF_LIST_FOR_EACH(FFDisk, disk, disks) { if(__builtin_expect(options->folders.length == 0, 1) && (disk->type & ~options->showTypes)) continue; printDisk(options, disk, ++index); } FF_LIST_FOR_EACH(FFDisk, disk, disks) { ffStrbufDestroy(&disk->mountFrom); ffStrbufDestroy(&disk->mountpoint); ffStrbufDestroy(&disk->filesystem); ffStrbufDestroy(&disk->name); } return true; } static bool setSeparatedList(FFstrbuf* strbuf, yyjson_val* val, char separator) { if (yyjson_is_str(val)) { ffStrbufSetJsonVal(strbuf, val); return true; } if (yyjson_is_arr(val)) { ffStrbufClear(strbuf); yyjson_val *elem; size_t eidx, emax; yyjson_arr_foreach(val, eidx, emax, elem) { if (yyjson_is_str(elem)) { if (strbuf->length > 0) ffStrbufAppendC(strbuf, separator); ffStrbufAppendJsonVal(strbuf, elem); } } return true; } return false; } void ffParseDiskJsonObject(FFDiskOptions* options, yyjson_val* module) { yyjson_val *key, *val; size_t idx, max; yyjson_obj_foreach(module, idx, max, key, val) { if (ffJsonConfigParseModuleArgs(key, val, &options->moduleArgs)) continue; if (unsafe_yyjson_equals_str(key, "folders")) { setSeparatedList(&options->folders, val, FF_DISK_FOLDER_SEPARATOR); continue; } if (unsafe_yyjson_equals_str(key, "hideFolders")) { setSeparatedList(&options->hideFolders, val, FF_DISK_FOLDER_SEPARATOR); continue; } if (unsafe_yyjson_equals_str(key, "hideFS")) { setSeparatedList(&options->hideFS, val, ':'); continue; } if (unsafe_yyjson_equals_str(key, "showRegular")) { if (yyjson_get_bool(val)) options->showTypes |= FF_DISK_VOLUME_TYPE_REGULAR_BIT; else options->showTypes &= ~FF_DISK_VOLUME_TYPE_REGULAR_BIT; continue; } if (unsafe_yyjson_equals_str(key, "showExternal")) { if (yyjson_get_bool(val)) options->showTypes |= FF_DISK_VOLUME_TYPE_EXTERNAL_BIT; else options->showTypes &= ~FF_DISK_VOLUME_TYPE_EXTERNAL_BIT; continue; } if (unsafe_yyjson_equals_str(key, "showHidden")) { if (yyjson_get_bool(val)) options->showTypes |= FF_DISK_VOLUME_TYPE_HIDDEN_BIT; else options->showTypes &= ~FF_DISK_VOLUME_TYPE_HIDDEN_BIT; continue; } if (unsafe_yyjson_equals_str(key, "showSubvolumes")) { if (yyjson_get_bool(val)) options->showTypes |= FF_DISK_VOLUME_TYPE_SUBVOLUME_BIT; else options->showTypes &= ~FF_DISK_VOLUME_TYPE_SUBVOLUME_BIT; continue; } if (unsafe_yyjson_equals_str(key, "showReadOnly")) { if (yyjson_get_bool(val)) options->showTypes |= FF_DISK_VOLUME_TYPE_READONLY_BIT; else options->showTypes &= ~FF_DISK_VOLUME_TYPE_READONLY_BIT; continue; } if (unsafe_yyjson_equals_str(key, "showUnknown")) { if (yyjson_get_bool(val)) options->showTypes |= FF_DISK_VOLUME_TYPE_UNKNOWN_BIT; else options->showTypes &= ~FF_DISK_VOLUME_TYPE_UNKNOWN_BIT; continue; } if (unsafe_yyjson_equals_str(key, "useAvailable")) { if (yyjson_get_bool(val)) options->calcType = FF_DISK_CALC_TYPE_AVAILABLE; else options->calcType = FF_DISK_CALC_TYPE_FREE; continue; } if (ffPercentParseJsonObject(key, val, &options->percent)) continue; ffPrintError(FF_DISK_MODULE_NAME, 0, &options->moduleArgs, FF_PRINT_TYPE_DEFAULT, "Unknown JSON key %s", unsafe_yyjson_get_str(key)); } } void ffGenerateDiskJsonConfig(FFDiskOptions* options, yyjson_mut_doc* doc, yyjson_mut_val* module) { ffJsonConfigGenerateModuleArgsConfig(doc, module, &options->moduleArgs); yyjson_mut_obj_add_bool(doc, module, "showRegular", !!(options->showTypes & FF_DISK_VOLUME_TYPE_REGULAR_BIT)); yyjson_mut_obj_add_bool(doc, module, "showExternal", !!(options->showTypes & FF_DISK_VOLUME_TYPE_EXTERNAL_BIT)); yyjson_mut_obj_add_bool(doc, module, "showHidden", !!(options->showTypes & FF_DISK_VOLUME_TYPE_HIDDEN_BIT)); yyjson_mut_obj_add_bool(doc, module, "showSubvolumes", !!(options->showTypes & FF_DISK_VOLUME_TYPE_SUBVOLUME_BIT)); yyjson_mut_obj_add_bool(doc, module, "showReadOnly", !!(options->showTypes & FF_DISK_VOLUME_TYPE_READONLY_BIT)); yyjson_mut_obj_add_bool(doc, module, "showUnknown", !!(options->showTypes & FF_DISK_VOLUME_TYPE_UNKNOWN_BIT)); yyjson_mut_obj_add_strbuf(doc, module, "folders", &options->folders); yyjson_mut_obj_add_strbuf(doc, module, "hideFolders", &options->hideFolders); yyjson_mut_obj_add_strbuf(doc, module, "hideFS", &options->hideFS); yyjson_mut_obj_add_bool(doc, module, "useAvailable", options->calcType == FF_DISK_CALC_TYPE_AVAILABLE); ffPercentGenerateJsonConfig(doc, module, options->percent); } bool ffGenerateDiskJsonResult(FFDiskOptions* options, yyjson_mut_doc* doc, yyjson_mut_val* module) { FF_LIST_AUTO_DESTROY disks = ffListCreate(sizeof (FFDisk)); const char* error = ffDetectDisks(options, &disks); if(error) { yyjson_mut_obj_add_str(doc, module, "error", error); return false; } yyjson_mut_val* arr = yyjson_mut_obj_add_arr(doc, module, "result"); FF_LIST_FOR_EACH(FFDisk, item, disks) { yyjson_mut_val* obj = yyjson_mut_arr_add_obj(doc, arr); yyjson_mut_val* bytes = yyjson_mut_obj_add_obj(doc, obj, "bytes"); yyjson_mut_obj_add_uint(doc, bytes, "available", item->bytesAvailable); yyjson_mut_obj_add_uint(doc, bytes, "free", item->bytesFree); yyjson_mut_obj_add_uint(doc, bytes, "total", item->bytesTotal); yyjson_mut_obj_add_uint(doc, bytes, "used", item->bytesUsed); yyjson_mut_val* files = yyjson_mut_obj_add_obj(doc, obj, "files"); if (item->filesTotal == 0 && item->filesUsed == 0) { yyjson_mut_obj_add_null(doc, files, "total"); yyjson_mut_obj_add_null(doc, files, "used"); } else { yyjson_mut_obj_add_uint(doc, files, "total", item->filesTotal); yyjson_mut_obj_add_uint(doc, files, "used", item->filesUsed); } yyjson_mut_obj_add_strbuf(doc, obj, "filesystem", &item->filesystem); yyjson_mut_obj_add_strbuf(doc, obj, "mountpoint", &item->mountpoint); yyjson_mut_obj_add_strbuf(doc, obj, "mountFrom", &item->mountFrom); yyjson_mut_obj_add_strbuf(doc, obj, "name", &item->name); yyjson_mut_val* typeArr = yyjson_mut_obj_add_arr(doc, obj, "volumeType"); if(item->type & FF_DISK_VOLUME_TYPE_REGULAR_BIT) yyjson_mut_arr_add_str(doc, typeArr, "Regular"); if(item->type & FF_DISK_VOLUME_TYPE_EXTERNAL_BIT) yyjson_mut_arr_add_str(doc, typeArr, "External"); if(item->type & FF_DISK_VOLUME_TYPE_SUBVOLUME_BIT) yyjson_mut_arr_add_str(doc, typeArr, "Subvolume"); if(item->type & FF_DISK_VOLUME_TYPE_HIDDEN_BIT) yyjson_mut_arr_add_str(doc, typeArr, "Hidden"); if(item->type & FF_DISK_VOLUME_TYPE_READONLY_BIT) yyjson_mut_arr_add_str(doc, typeArr, "Read-only"); if(item->type & FF_DISK_VOLUME_TYPE_UNKNOWN_BIT) yyjson_mut_arr_add_str(doc, typeArr, "Unknown"); const char* pstr = ffTimeToFullStr(item->createTime); if (*pstr) yyjson_mut_obj_add_strcpy(doc, obj, "createTime", pstr); else yyjson_mut_obj_add_null(doc, obj, "createTime"); } FF_LIST_FOR_EACH(FFDisk, item, disks) { ffStrbufDestroy(&item->mountpoint); ffStrbufDestroy(&item->mountFrom); ffStrbufDestroy(&item->filesystem); ffStrbufDestroy(&item->name); } return true; } void ffInitDiskOptions(FFDiskOptions* options) { ffOptionInitModuleArg(&options->moduleArgs, ""); ffStrbufInit(&options->folders); #if _WIN32 || __APPLE__ || __ANDROID__ ffStrbufInit(&options->hideFolders); #else ffStrbufInitS(&options->hideFolders, "/efi:/boot:/boot/*"); #endif ffStrbufInit(&options->hideFS); options->showTypes = FF_DISK_VOLUME_TYPE_REGULAR_BIT | FF_DISK_VOLUME_TYPE_EXTERNAL_BIT | FF_DISK_VOLUME_TYPE_READONLY_BIT; options->calcType = FF_DISK_CALC_TYPE_FREE; options->percent = (FFPercentageModuleConfig) { 50, 80, 0 }; } void ffDestroyDiskOptions(FFDiskOptions* options) { ffOptionDestroyModuleArg(&options->moduleArgs); ffStrbufDestroy(&options->folders); ffStrbufDestroy(&options->hideFolders); ffStrbufDestroy(&options->hideFS); } FFModuleBaseInfo ffDiskModuleInfo = { .name = FF_DISK_MODULE_NAME, .description = "Print partitions, space usage, file system, etc", .initOptions = (void*) ffInitDiskOptions, .destroyOptions = (void*) ffDestroyDiskOptions, .parseJsonObject = (void*) ffParseDiskJsonObject, .printModule = (void*) ffPrintDisk, .generateJsonResult = (void*) ffGenerateDiskJsonResult, .generateJsonConfig = (void*) ffGenerateDiskJsonConfig, .formatArgs = FF_FORMAT_ARG_LIST(((FFModuleFormatArg[]) { {"Size used", "size-used"}, {"Size total", "size-total"}, {"Size percentage num", "size-percentage"}, {"Files used", "files-used"}, {"Files total", "files-total"}, {"Files percentage num", "files-percentage"}, {"True if external volume", "is-external"}, {"True if hidden volume", "is-hidden"}, {"Filesystem", "filesystem"}, {"Label / name", "name"}, {"True if read-only", "is-readonly"}, {"Create time in local timezone", "create-time"}, {"Size percentage bar", "size-percentage-bar"}, {"Files percentage bar", "files-percentage-bar"}, {"Days after creation", "days"}, {"Hours after creation", "hours"}, {"Minutes after creation", "minutes"}, {"Seconds after creation", "seconds"}, {"Milliseconds after creation", "milliseconds"}, {"Mount point / drive letter", "mountpoint"}, {"Mount from (device path)", "mount-from"}, {"Years integer after creation", "years"}, {"Days of year after creation", "days-of-year"}, {"Years fraction after creation", "years-fraction"}, {"Size free", "size-free"}, {"Size available", "size-available"}, })) }; ================================================ FILE: src/modules/disk/disk.h ================================================ #pragma once #include "option.h" #define FF_DISK_MODULE_NAME "Disk" bool ffPrintDisk(FFDiskOptions* options); void ffInitDiskOptions(FFDiskOptions* options); void ffDestroyDiskOptions(FFDiskOptions* options); extern FFModuleBaseInfo ffDiskModuleInfo; ================================================ FILE: src/modules/disk/option.h ================================================ #pragma once #include "common/option.h" #include "common/percent.h" typedef enum __attribute__((__packed__)) FFDiskVolumeType { FF_DISK_VOLUME_TYPE_NONE = 0, FF_DISK_VOLUME_TYPE_REGULAR_BIT = 1 << 0, FF_DISK_VOLUME_TYPE_HIDDEN_BIT = 1 << 1, FF_DISK_VOLUME_TYPE_EXTERNAL_BIT = 1 << 2, FF_DISK_VOLUME_TYPE_SUBVOLUME_BIT = 1 << 3, FF_DISK_VOLUME_TYPE_UNKNOWN_BIT = 1 << 4, FF_DISK_VOLUME_TYPE_READONLY_BIT = 1 << 5, FF_DISK_VOLUME_TYPE_FORCE_UNSIGNED = UINT8_MAX, } FFDiskVolumeType; typedef enum __attribute__((__packed__)) FFDiskCalcType { FF_DISK_CALC_TYPE_FREE, FF_DISK_CALC_TYPE_AVAILABLE, } FFDiskCalcType; typedef struct FFDiskOptions { FFModuleArgs moduleArgs; FFstrbuf folders; FFstrbuf hideFolders; FFstrbuf hideFS; FFDiskVolumeType showTypes; FFDiskCalcType calcType; FFPercentageModuleConfig percent; } FFDiskOptions; static_assert(sizeof(FFDiskOptions) <= FF_OPTION_MAX_SIZE, "FFDiskOptions size exceeds maximum allowed size"); ================================================ FILE: src/modules/diskio/diskio.c ================================================ #include "common/printing.h" #include "common/jsonconfig.h" #include "common/size.h" #include "common/stringUtils.h" #include "detection/diskio/diskio.h" #include "modules/diskio/diskio.h" #define FF_DISKIO_DISPLAY_NAME "Disk IO" static int sortDevices(const FFDiskIOResult* left, const FFDiskIOResult* right) { return ffStrbufComp(&left->name, &right->name); } static void formatKey(const FFDiskIOOptions* options, FFDiskIOResult* dev, uint32_t index, FFstrbuf* key) { if(options->moduleArgs.key.length == 0) { ffStrbufSetF(key, FF_DISKIO_DISPLAY_NAME " (%s)", dev->name.length ? dev->name.chars : dev->devPath.chars); } else { ffStrbufClear(key); FF_PARSE_FORMAT_STRING_CHECKED(key, &options->moduleArgs.key, ((FFformatarg[]){ FF_ARG(index, "index"), FF_ARG(dev->name, "name"), FF_ARG(dev->devPath, "dev-path"), FF_ARG(options->moduleArgs.keyIcon, "icon"), })); } } bool ffPrintDiskIO(FFDiskIOOptions* options) { FF_LIST_AUTO_DESTROY result = ffListCreate(sizeof(FFDiskIOResult)); const char* error = ffDetectDiskIO(&result, options); if(error) { ffPrintError(FF_DISKIO_DISPLAY_NAME, 0, &options->moduleArgs, FF_PRINT_TYPE_NO_CUSTOM_KEY, "%s", error); return false; } ffListSort(&result, (const void*) sortDevices); uint32_t index = 0; FF_STRBUF_AUTO_DESTROY key = ffStrbufCreate(); FF_STRBUF_AUTO_DESTROY buffer = ffStrbufCreate(); FF_STRBUF_AUTO_DESTROY buffer2 = ffStrbufCreate(); FF_LIST_FOR_EACH(FFDiskIOResult, dev, result) { formatKey(options, dev, result.length == 1 ? 0 : index + 1, &key); ffStrbufClear(&buffer); if(options->moduleArgs.outputFormat.length == 0) { ffPrintLogoAndKey(key.chars, 0, &options->moduleArgs, FF_PRINT_TYPE_NO_CUSTOM_KEY); ffSizeAppendNum(dev->bytesRead, &buffer); if (!options->detectTotal) ffStrbufAppendS(&buffer, "/s"); ffStrbufAppendS(&buffer, " (R) - "); ffSizeAppendNum(dev->bytesWritten, &buffer); if (!options->detectTotal) ffStrbufAppendS(&buffer, "/s"); ffStrbufAppendS(&buffer, " (W)"); ffStrbufPutTo(&buffer, stdout); } else { ffSizeAppendNum(dev->bytesRead, &buffer); if (!options->detectTotal) ffStrbufAppendS(&buffer, "/s"); ffStrbufClear(&buffer2); ffSizeAppendNum(dev->bytesWritten, &buffer2); if (!options->detectTotal) ffStrbufAppendS(&buffer2, "/s"); FF_PRINT_FORMAT_CHECKED(key.chars, 0, &options->moduleArgs, FF_PRINT_TYPE_NO_CUSTOM_KEY, ((FFformatarg[]){ FF_ARG(buffer, "size-read"), FF_ARG(buffer2, "size-written"), FF_ARG(dev->name, "name"), FF_ARG(dev->devPath, "dev-path"), FF_ARG(dev->bytesRead, "bytes-read"), FF_ARG(dev->bytesWritten, "bytes-written"), FF_ARG(dev->readCount, "read-count"), FF_ARG(dev->writeCount, "write-count"), })); } ++index; } FF_LIST_FOR_EACH(FFDiskIOResult, dev, result) { ffStrbufDestroy(&dev->name); ffStrbufDestroy(&dev->devPath); } return true; } void ffParseDiskIOJsonObject(FFDiskIOOptions* options, yyjson_val* module) { yyjson_val *key, *val; size_t idx, max; yyjson_obj_foreach(module, idx, max, key, val) { if (ffJsonConfigParseModuleArgs(key, val, &options->moduleArgs)) continue; if (unsafe_yyjson_equals_str(key, "namePrefix")) { ffStrbufSetJsonVal(&options->namePrefix, val); continue; } if (unsafe_yyjson_equals_str(key, "detectTotal")) { options->detectTotal = yyjson_get_bool(val); continue; } if (unsafe_yyjson_equals_str(key, "waitTime")) { options->waitTime = (uint32_t) yyjson_get_uint(val); continue; } ffPrintError(FF_DISKIO_MODULE_NAME, 0, &options->moduleArgs, FF_PRINT_TYPE_DEFAULT, "Unknown JSON key %s", unsafe_yyjson_get_str(key)); } } void ffGenerateDiskIOJsonConfig(FFDiskIOOptions* options, yyjson_mut_doc* doc, yyjson_mut_val* module) { ffJsonConfigGenerateModuleArgsConfig(doc, module, &options->moduleArgs); yyjson_mut_obj_add_strbuf(doc, module, "namePrefix", &options->namePrefix); yyjson_mut_obj_add_bool(doc, module, "detectTotal", options->detectTotal); yyjson_mut_obj_add_uint(doc, module, "waitTime", options->waitTime); } bool ffGenerateDiskIOJsonResult(FFDiskIOOptions* options, yyjson_mut_doc* doc, yyjson_mut_val* module) { FF_LIST_AUTO_DESTROY result = ffListCreate(sizeof(FFDiskIOResult)); const char* error = ffDetectDiskIO(&result, options); if(error) { yyjson_mut_obj_add_str(doc, module, "error", error); return false; } yyjson_mut_val* arr = yyjson_mut_obj_add_arr(doc, module, "result"); FF_LIST_FOR_EACH(FFDiskIOResult, dev, result) { yyjson_mut_val* obj = yyjson_mut_arr_add_obj(doc, arr); yyjson_mut_obj_add_strbuf(doc, obj, "name", &dev->name); yyjson_mut_obj_add_strbuf(doc, obj, "devPath", &dev->devPath); yyjson_mut_obj_add_uint(doc, obj, "bytesRead", dev->bytesRead); yyjson_mut_obj_add_uint(doc, obj, "bytesWritten", dev->bytesWritten); yyjson_mut_obj_add_uint(doc, obj, "readCount", dev->readCount); yyjson_mut_obj_add_uint(doc, obj, "writeCount", dev->writeCount); } FF_LIST_FOR_EACH(FFDiskIOResult, dev, result) { ffStrbufDestroy(&dev->name); ffStrbufDestroy(&dev->devPath); } return true; } void ffInitDiskIOOptions(FFDiskIOOptions* options) { ffOptionInitModuleArg(&options->moduleArgs, "󰓅"); ffStrbufInit(&options->namePrefix); options->detectTotal = false; options->waitTime = 1000; } void ffDestroyDiskIOOptions(FFDiskIOOptions* options) { ffOptionDestroyModuleArg(&options->moduleArgs); ffStrbufDestroy(&options->namePrefix); } FFModuleBaseInfo ffDiskIOModuleInfo = { .name = FF_DISKIO_MODULE_NAME, .description = "Print physical disk I/O throughput", .initOptions = (void*) ffInitDiskIOOptions, .destroyOptions = (void*) ffDestroyDiskIOOptions, .parseJsonObject = (void*) ffParseDiskIOJsonObject, .printModule = (void*) ffPrintDiskIO, .generateJsonResult = (void*) ffGenerateDiskIOJsonResult, .generateJsonConfig = (void*) ffGenerateDiskIOJsonConfig, .formatArgs = FF_FORMAT_ARG_LIST(((FFModuleFormatArg[]) { {"Size of data read [per second] (formatted)", "size-read"}, {"Size of data written [per second] (formatted)", "size-written"}, {"Device name", "name"}, {"Device raw file path", "dev-path"}, {"Size of data read [per second] (in bytes)", "bytes-read"}, {"Size of data written [per second] (in bytes)", "bytes-written"}, {"Number of reads", "read-count"}, {"Number of writes", "write-count"}, })) }; ================================================ FILE: src/modules/diskio/diskio.h ================================================ #pragma once #include "option.h" #define FF_DISKIO_MODULE_NAME "DiskIO" void ffPrepareDiskIO(FFDiskIOOptions* options); bool ffPrintDiskIO(FFDiskIOOptions* options); void ffInitDiskIOOptions(FFDiskIOOptions* options); void ffDestroyDiskIOOptions(FFDiskIOOptions* options); extern FFModuleBaseInfo ffDiskIOModuleInfo; ================================================ FILE: src/modules/diskio/option.h ================================================ #pragma once #include "common/option.h" typedef struct FFDiskIOOptions { FFModuleArgs moduleArgs; FFstrbuf namePrefix; uint32_t waitTime; bool detectTotal; } FFDiskIOOptions; static_assert(sizeof(FFDiskIOOptions) <= FF_OPTION_MAX_SIZE, "FFDiskIOOptions size exceeds maximum allowed size"); ================================================ FILE: src/modules/display/display.c ================================================ #include "common/printing.h" #include "common/jsonconfig.h" #include "common/size.h" #include "common/stringUtils.h" #include "detection/displayserver/displayserver.h" #include "modules/display/display.h" #include static int sortByNameAsc(FFDisplayResult* a, FFDisplayResult* b) { return ffStrbufComp(&a->name, &b->name); } static int sortByNameDesc(FFDisplayResult* a, FFDisplayResult* b) { return -ffStrbufComp(&a->name, &b->name); } bool ffPrintDisplay(FFDisplayOptions* options) { const FFDisplayServerResult* dsResult = ffConnectDisplayServer(); if(dsResult->displays.length == 0) { ffPrintError(FF_DISPLAY_MODULE_NAME, 0, &options->moduleArgs, FF_PRINT_TYPE_DEFAULT, "Couldn't detect display"); return false; } if (options->order != FF_DISPLAY_ORDER_NONE) { ffListSort((FFlist*) &dsResult->displays, (void*) (options->order == FF_DISPLAY_ORDER_ASC ? sortByNameAsc : sortByNameDesc)); } if (options->compactType != FF_DISPLAY_COMPACT_TYPE_NONE) { ffPrintLogoAndKey(FF_DISPLAY_MODULE_NAME, 0, &options->moduleArgs, FF_PRINT_TYPE_DEFAULT); FF_STRBUF_AUTO_DESTROY buffer = ffStrbufCreate(); FF_LIST_FOR_EACH(FFDisplayResult, result, dsResult->displays) { if (options->compactType & FF_DISPLAY_COMPACT_TYPE_ORIGINAL_BIT) { ffStrbufAppendF(&buffer, "%ix%i", result->width, result->height); } else { uint32_t scaledWidth = result->width * 96 / result->dpi; uint32_t scaledHeight = result->height * 96 / result->dpi; ffStrbufAppendF(&buffer, "%ix%i", scaledWidth, scaledHeight); } if (options->compactType & FF_DISPLAY_COMPACT_TYPE_REFRESH_RATE_BIT) { if (result->refreshRate > 0) { const char* space = instance.config.display.freqSpaceBeforeUnit == FF_SPACE_BEFORE_UNIT_ALWAYS ? " " : ""; if (options->preciseRefreshRate) ffStrbufAppendF(&buffer, " @ %g%sHz", result->refreshRate, space); else ffStrbufAppendF(&buffer, " @ %i%sHz", (uint32_t) (result->refreshRate + 0.5), space); } ffStrbufAppendS(&buffer, ", "); } else { ffStrbufAppendC(&buffer, ' '); } } ffStrbufTrimRight(&buffer, ' '); ffStrbufTrimRight(&buffer, ','); ffStrbufPutTo(&buffer, stdout); return true; } FF_STRBUF_AUTO_DESTROY key = ffStrbufCreate(); for(uint32_t i = 0; i < dsResult->displays.length; i++) { FFDisplayResult* result = FF_LIST_GET(FFDisplayResult, dsResult->displays, i); uint32_t moduleIndex = dsResult->displays.length == 1 ? 0 : i + 1; const char* displayType = result->type == FF_DISPLAY_TYPE_UNKNOWN ? NULL : result->type == FF_DISPLAY_TYPE_BUILTIN ? "Built-in" : "External"; ffStrbufClear(&key); if(options->moduleArgs.key.length == 0) { if (result->name.length) ffStrbufAppendF(&key, "%s (%s)", FF_DISPLAY_MODULE_NAME, result->name.chars); else if (moduleIndex > 0) ffStrbufAppendF(&key, "%s (%d)", FF_DISPLAY_MODULE_NAME, moduleIndex); else ffStrbufAppendS(&key, FF_DISPLAY_MODULE_NAME); } else { FF_PARSE_FORMAT_STRING_CHECKED(&key, &options->moduleArgs.key, ((FFformatarg[]) { FF_ARG(moduleIndex, "index"), FF_ARG(result->name, "name"), FF_ARG(displayType, "type"), FF_ARG(options->moduleArgs.keyIcon, "icon"), })); } FF_STRBUF_AUTO_DESTROY buffer = ffStrbufCreate(); double inch = sqrt(result->physicalWidth * result->physicalWidth + result->physicalHeight * result->physicalHeight) / 25.4; uint32_t scaledWidth = result->width * 96 / result->dpi; uint32_t scaledHeight = result->height * 96 / result->dpi; double scaleFactor = (double) result->dpi / 96.; if(options->moduleArgs.outputFormat.length == 0) { ffPrintLogoAndKey(key.chars, 0, &options->moduleArgs, FF_PRINT_TYPE_NO_CUSTOM_KEY); ffStrbufAppendF(&buffer, "%ix%i", result->width, result->height); if(result->dpi != 96) { ffStrbufAppendS(&buffer, " @ "); ffStrbufAppendDouble(&buffer, scaleFactor, instance.config.display.fractionNdigits, instance.config.display.fractionTrailingZeros == FF_FRACTION_TRAILING_ZEROS_TYPE_ALWAYS); ffStrbufAppendC(&buffer, 'x'); } if (inch > 1) ffStrbufAppendF(&buffer, " in %i\"", (uint32_t) (inch + 0.5)); if(result->refreshRate > 0) { ffStrbufAppendS(&buffer, ", "); if(options->preciseRefreshRate) ffStrbufAppendDouble(&buffer, result->refreshRate, 3, false); else ffStrbufAppendSInt(&buffer, (int) (result->refreshRate + 0.5)); ffStrbufAppendS(&buffer, instance.config.display.freqSpaceBeforeUnit == FF_SPACE_BEFORE_UNIT_NEVER ? "Hz" : " Hz"); } bool flag = false; if (result->type != FF_DISPLAY_TYPE_UNKNOWN) { ffStrbufAppendS(&buffer, result->type == FF_DISPLAY_TYPE_BUILTIN ? " [Built-in" : " [External"); flag = true; } if (result->hdrStatus == FF_DISPLAY_HDR_STATUS_ENABLED) { ffStrbufAppendS(&buffer, flag ? ", HDR" : " [HDR"); flag = true; } if (flag) ffStrbufAppendS(&buffer, "]"); if(moduleIndex > 0 && result->primary) ffStrbufAppendS(&buffer, " *"); ffStrbufPutTo(&buffer, stdout); ffStrbufClear(&buffer); } else { double ppi = inch == 0 ? 0 : sqrt(result->width * result->width + result->height * result->height) / inch; bool hdrEnabled = result->hdrStatus == FF_DISPLAY_HDR_STATUS_ENABLED; bool hdrCompatible = result->hdrStatus == FF_DISPLAY_HDR_STATUS_SUPPORTED || result->hdrStatus == FF_DISPLAY_HDR_STATUS_ENABLED; uint32_t iInch = (uint32_t) (inch + 0.5), iPpi = (uint32_t) (ppi + 0.5); char refreshRate[16]; if(result->refreshRate > 0) { if(options->preciseRefreshRate) snprintf(refreshRate, ARRAY_SIZE(refreshRate), "%g", ((int) (result->refreshRate * 1000 + 0.5)) / 1000.0); else snprintf(refreshRate, ARRAY_SIZE(refreshRate), "%i", (uint32_t) (result->refreshRate + 0.5)); } else refreshRate[0] = 0; char preferredRefreshRate[16]; if(result->preferredRefreshRate > 0) { if(options->preciseRefreshRate) snprintf(preferredRefreshRate, ARRAY_SIZE(preferredRefreshRate), "%g", ((int) (result->preferredRefreshRate * 1000 + 0.5)) / 1000.0); else snprintf(preferredRefreshRate, ARRAY_SIZE(preferredRefreshRate), "%i", (uint32_t) (result->preferredRefreshRate + 0.5)); } else preferredRefreshRate[0] = 0; char buf[32]; if (result->serial) { const uint8_t* nums = (uint8_t*) &result->serial; snprintf(buf, ARRAY_SIZE(buf), "%2X-%2X-%2X-%2X", nums[0], nums[1], nums[2], nums[3]); } else buf[0] = '\0'; FF_PRINT_FORMAT_CHECKED(key.chars, 0, &options->moduleArgs, FF_PRINT_TYPE_NO_CUSTOM_KEY, ((FFformatarg[]) { FF_ARG(result->width, "width"), FF_ARG(result->height, "height"), FF_ARG(refreshRate, "refresh-rate"), FF_ARG(scaledWidth, "scaled-width"), FF_ARG(scaledHeight, "scaled-height"), FF_ARG(result->name, "name"), FF_ARG(displayType, "type"), FF_ARG(result->rotation, "rotation"), FF_ARG(result->primary, "is-primary"), FF_ARG(result->physicalWidth, "physical-width"), FF_ARG(result->physicalHeight, "physical-height"), FF_ARG(iInch, "inch"), FF_ARG(iPpi, "ppi"), FF_ARG(result->bitDepth, "bit-depth"), FF_ARG(hdrEnabled, "hdr-enabled"), FF_ARG(result->manufactureYear, "manufacture-year"), FF_ARG(result->manufactureWeek, "manufacture-week"), FF_ARG(buf, "serial"), FF_ARG(result->platformApi, "platform-api"), FF_ARG(hdrCompatible, "hdr-compatible"), FF_ARG(scaleFactor, "scale-factor"), FF_ARG(result->preferredWidth, "preferred-width"), FF_ARG(result->preferredHeight, "preferred-height"), FF_ARG(preferredRefreshRate, "preferred-refresh-rate"), FF_ARG(result->dpi, "dpi"), })); } } return true; } void ffParseDisplayJsonObject(FFDisplayOptions* options, yyjson_val* module) { yyjson_val *key, *val; size_t idx, max; yyjson_obj_foreach(module, idx, max, key, val) { if (ffJsonConfigParseModuleArgs(key, val, &options->moduleArgs)) continue; if (unsafe_yyjson_equals_str(key, "compactType")) { if (yyjson_is_null(val)) options->compactType = FF_DISPLAY_COMPACT_TYPE_NONE; else { int value; const char* error = ffJsonConfigParseEnum(val, &value, (FFKeyValuePair[]) { { "none", FF_DISPLAY_COMPACT_TYPE_NONE }, { "original", FF_DISPLAY_COMPACT_TYPE_ORIGINAL_BIT }, { "scaled", FF_DISPLAY_COMPACT_TYPE_SCALED_BIT }, { "original-with-refresh-rate", FF_DISPLAY_COMPACT_TYPE_ORIGINAL_BIT | FF_DISPLAY_COMPACT_TYPE_REFRESH_RATE_BIT }, { "scaled-with-refresh-rate", FF_DISPLAY_COMPACT_TYPE_SCALED_BIT | FF_DISPLAY_COMPACT_TYPE_REFRESH_RATE_BIT }, {}, }); if (error) ffPrintError(FF_DISPLAY_MODULE_NAME, 0, &options->moduleArgs, FF_PRINT_TYPE_DEFAULT, "Invalid %s value: %s", unsafe_yyjson_get_str(key), error); else options->compactType = (FFDisplayCompactType) value; } continue; } if (unsafe_yyjson_equals_str(key, "preciseRefreshRate")) { options->preciseRefreshRate = yyjson_get_bool(val); continue; } if (unsafe_yyjson_equals_str(key, "order")) { if (yyjson_is_null(val)) options->order = FF_DISPLAY_ORDER_NONE; else { int value; const char* error = ffJsonConfigParseEnum(val, &value, (FFKeyValuePair[]) { { "asc", FF_DISPLAY_ORDER_ASC }, { "desc", FF_DISPLAY_ORDER_DESC }, { "none", FF_DISPLAY_ORDER_NONE }, {}, }); if (error) ffPrintError(FF_DISPLAY_MODULE_NAME, 0, &options->moduleArgs, FF_PRINT_TYPE_DEFAULT, "Invalid %s value: %s", unsafe_yyjson_get_str(key), error); else options->order = (FFDisplayOrder) value; } continue; } ffPrintError(FF_DISPLAY_MODULE_NAME, 0, &options->moduleArgs, FF_PRINT_TYPE_DEFAULT, "Unknown JSON key %s", unsafe_yyjson_get_str(key)); } } void ffGenerateDisplayJsonConfig(FFDisplayOptions* options, yyjson_mut_doc* doc, yyjson_mut_val* module) { ffJsonConfigGenerateModuleArgsConfig(doc, module, &options->moduleArgs); switch ((int) options->compactType) { case FF_DISPLAY_COMPACT_TYPE_NONE: yyjson_mut_obj_add_str(doc, module, "compactType", "none"); break; case FF_DISPLAY_COMPACT_TYPE_ORIGINAL_BIT: yyjson_mut_obj_add_str(doc, module, "compactType", "original"); break; case FF_DISPLAY_COMPACT_TYPE_SCALED_BIT: yyjson_mut_obj_add_str(doc, module, "compactType", "scaled"); break; case FF_DISPLAY_COMPACT_TYPE_ORIGINAL_BIT | FF_DISPLAY_COMPACT_TYPE_REFRESH_RATE_BIT: yyjson_mut_obj_add_str(doc, module, "compactType", "original-with-refresh-rate"); break; case FF_DISPLAY_COMPACT_TYPE_SCALED_BIT | FF_DISPLAY_COMPACT_TYPE_REFRESH_RATE_BIT: yyjson_mut_obj_add_str(doc, module, "compactType", "scaled-with-refresh-rate"); break; } yyjson_mut_obj_add_bool(doc, module, "preciseRefreshRate", options->preciseRefreshRate); switch (options->order) { case FF_DISPLAY_ORDER_NONE: yyjson_mut_obj_add_null(doc, module, "order"); break; case FF_DISPLAY_ORDER_ASC: yyjson_mut_obj_add_str(doc, module, "order", "asc"); break; case FF_DISPLAY_ORDER_DESC: yyjson_mut_obj_add_str(doc, module, "order", "desc"); break; } } bool ffGenerateDisplayJsonResult(FF_MAYBE_UNUSED FFDisplayOptions* options, yyjson_mut_doc* doc, yyjson_mut_val* module) { const FFDisplayServerResult* dsResult = ffConnectDisplayServer(); if(dsResult->displays.length == 0) { yyjson_mut_obj_add_str(doc, module, "error", "Couldn't detect display"); return false; } yyjson_mut_val* arr = yyjson_mut_obj_add_arr(doc, module, "result"); FF_LIST_FOR_EACH(FFDisplayResult, item, dsResult->displays) { yyjson_mut_val* obj = yyjson_mut_arr_add_obj(doc, arr); yyjson_mut_obj_add_uint(doc, obj, "id", item->id); yyjson_mut_obj_add_strbuf(doc, obj, "name", &item->name); yyjson_mut_obj_add_bool(doc, obj, "primary", item->primary); yyjson_mut_val* output = yyjson_mut_obj_add_obj(doc, obj, "output"); yyjson_mut_obj_add_uint(doc, output, "width", item->width); yyjson_mut_obj_add_uint(doc, output, "height", item->height); yyjson_mut_obj_add_real(doc, output, "refreshRate", item->refreshRate); if (item->drrStatus == FF_DISPLAY_DRR_STATUS_UNKNOWN) yyjson_mut_obj_add_null(doc, output, "drrStatus"); else switch (item->drrStatus) { case FF_DISPLAY_DRR_STATUS_DISABLED: yyjson_mut_obj_add_str(doc, output, "drrStatus", "Disabled"); break; case FF_DISPLAY_DRR_STATUS_ENABLED: yyjson_mut_obj_add_str(doc, output, "drrStatus", "Enabled"); break; default: yyjson_mut_obj_add_str(doc, output, "drrStatus", "Unknown"); break; } yyjson_mut_obj_add_uint(doc, output, "dpi", item->dpi); uint32_t scaledWidth = item->width * 96 / item->dpi; uint32_t scaledHeight = item->height * 96 / item->dpi; yyjson_mut_val* scaled = yyjson_mut_obj_add_obj(doc, obj, "scaled"); yyjson_mut_obj_add_uint(doc, scaled, "width", scaledWidth); yyjson_mut_obj_add_uint(doc, scaled, "height", scaledHeight); yyjson_mut_val* preferred = yyjson_mut_obj_add_obj(doc, obj, "preferred"); yyjson_mut_obj_add_uint(doc, preferred, "width", item->preferredWidth); yyjson_mut_obj_add_uint(doc, preferred, "height", item->preferredHeight); yyjson_mut_obj_add_real(doc, preferred, "refreshRate", item->preferredRefreshRate); yyjson_mut_val* physical = yyjson_mut_obj_add_obj(doc, obj, "physical"); yyjson_mut_obj_add_uint(doc, physical, "width", item->physicalWidth); yyjson_mut_obj_add_uint(doc, physical, "height", item->physicalHeight); yyjson_mut_obj_add_uint(doc, obj, "rotation", item->rotation); yyjson_mut_obj_add_uint(doc, obj, "bitDepth", item->bitDepth); if (item->hdrStatus == FF_DISPLAY_HDR_STATUS_UNKNOWN) yyjson_mut_obj_add_null(doc, obj, "hdrStatus"); else switch (item->hdrStatus) { case FF_DISPLAY_HDR_STATUS_UNSUPPORTED: yyjson_mut_obj_add_str(doc, obj, "hdrStatus", "Unsupported"); break; case FF_DISPLAY_HDR_STATUS_SUPPORTED: yyjson_mut_obj_add_str(doc, obj, "hdrStatus", "Supported"); break; case FF_DISPLAY_HDR_STATUS_ENABLED: yyjson_mut_obj_add_str(doc, obj, "hdrStatus", "Enabled"); break; default: yyjson_mut_obj_add_str(doc, obj, "hdrStatus", "Unknown"); break; } switch (item->type) { case FF_DISPLAY_TYPE_BUILTIN: yyjson_mut_obj_add_str(doc, obj, "type", "Builtin"); break; case FF_DISPLAY_TYPE_EXTERNAL: yyjson_mut_obj_add_str(doc, obj, "type", "External"); break; default: yyjson_mut_obj_add_str(doc, obj, "type", "Unknown"); break; } if (item->manufactureYear) { yyjson_mut_val* manufactureDate = yyjson_mut_obj_add_obj(doc, obj, "manufactureDate"); yyjson_mut_obj_add_uint(doc, manufactureDate, "year", item->manufactureYear); yyjson_mut_obj_add_uint(doc, manufactureDate, "week", item->manufactureWeek); } else { yyjson_mut_obj_add_null(doc, obj, "manufactureDate"); } if (item->serial) yyjson_mut_obj_add_uint(doc, obj, "serial", item->serial); else yyjson_mut_obj_add_null(doc, obj, "serial"); yyjson_mut_obj_add_str(doc, obj, "platformApi", item->platformApi); } return true; } void ffInitDisplayOptions(FFDisplayOptions* options) { ffOptionInitModuleArg(&options->moduleArgs, "󰍹"); options->compactType = FF_DISPLAY_COMPACT_TYPE_NONE; options->preciseRefreshRate = false; options->order = FF_DISPLAY_ORDER_NONE; } void ffDestroyDisplayOptions(FFDisplayOptions* options) { ffOptionDestroyModuleArg(&options->moduleArgs); } FFModuleBaseInfo ffDisplayModuleInfo = { .name = FF_DISPLAY_MODULE_NAME, .description = "Print resolutions, refresh rates, etc", .initOptions = (void*) ffInitDisplayOptions, .destroyOptions = (void*) ffDestroyDisplayOptions, .parseJsonObject = (void*) ffParseDisplayJsonObject, .printModule = (void*) ffPrintDisplay, .generateJsonResult = (void*) ffGenerateDisplayJsonResult, .generateJsonConfig = (void*) ffGenerateDisplayJsonConfig, .formatArgs = FF_FORMAT_ARG_LIST(((FFModuleFormatArg[]) { {"Screen configured width (in pixels)", "width"}, {"Screen configured height (in pixels)", "height"}, {"Screen configured refresh rate (in Hz)", "refresh-rate"}, {"Screen scaled width (in pixels)", "scaled-width"}, {"Screen scaled height (in pixels)", "scaled-height"}, {"Screen name", "name"}, {"Screen type (Built-in or External)", "type"}, {"Screen rotation (in degrees)", "rotation"}, {"True if being the primary screen", "is-primary"}, {"Screen physical width (in millimeters)", "physical-width"}, {"Screen physical height (in millimeters)", "physical-height"}, {"Physical diagonal length in inches", "inch"}, {"Pixels per inch (PPI)", "ppi"}, {"Bits per color channel", "bit-depth"}, {"True if high dynamic range (HDR) mode is enabled", "hdr-enabled"}, {"Year of manufacturing", "manufacture-year"}, {"Nth week of manufacturing in the year", "manufacture-week"}, {"Serial number", "serial"}, {"The platform API used when detecting the display", "platform-api"}, {"True if the display is HDR compatible", "hdr-compatible"}, {"HiDPI scale factor", "scale-factor"}, {"Screen preferred width (in pixels)", "preferred-width"}, {"Screen preferred height (in pixels)", "preferred-height"}, {"Screen preferred refresh rate (in Hz)", "preferred-refresh-rate"}, {"DPI", "dpi"}, })) }; ================================================ FILE: src/modules/display/display.h ================================================ #pragma once #include "option.h" #define FF_DISPLAY_MODULE_NAME "Display" bool ffPrintDisplay(FFDisplayOptions* options); void ffInitDisplayOptions(FFDisplayOptions* options); void ffDestroyDisplayOptions(FFDisplayOptions* options); extern FFModuleBaseInfo ffDisplayModuleInfo; ================================================ FILE: src/modules/display/option.h ================================================ #pragma once #include "common/option.h" typedef enum __attribute__((__packed__)) FFDisplayCompactType { FF_DISPLAY_COMPACT_TYPE_NONE = 0, FF_DISPLAY_COMPACT_TYPE_ORIGINAL_BIT = 1 << 0, FF_DISPLAY_COMPACT_TYPE_SCALED_BIT = 1 << 1, FF_DISPLAY_COMPACT_TYPE_REFRESH_RATE_BIT = 1 << 2, FF_DISPLAY_COMPACT_TYPE_UNSIGNED = UINT8_MAX, } FFDisplayCompactType; typedef enum __attribute__((__packed__)) FFDisplayOrder { FF_DISPLAY_ORDER_NONE, FF_DISPLAY_ORDER_ASC, FF_DISPLAY_ORDER_DESC, } FFDisplayOrder; typedef struct FFDisplayOptions { FFModuleArgs moduleArgs; FFDisplayCompactType compactType; bool preciseRefreshRate; FFDisplayOrder order; } FFDisplayOptions; static_assert(sizeof(FFDisplayOptions) <= FF_OPTION_MAX_SIZE, "FFDisplayOptions size exceeds maximum allowed size"); ================================================ FILE: src/modules/dns/dns.c ================================================ #include "common/printing.h" #include "common/jsonconfig.h" #include "common/stringUtils.h" #include "detection/dns/dns.h" #include "modules/dns/dns.h" bool ffPrintDNS(FFDNSOptions* options) { FF_LIST_AUTO_DESTROY result = ffListCreate(sizeof(FFstrbuf)); const char* error = ffDetectDNS(options, &result); if (error) { ffPrintError(FF_DNS_MODULE_NAME, 0, &options->moduleArgs, FF_PRINT_TYPE_DEFAULT, "%s", error); return false; } if (result.length == 0) { ffPrintError(FF_DNS_MODULE_NAME, 0, &options->moduleArgs, FF_PRINT_TYPE_DEFAULT, "NO DNS servers detected"); return false; } FF_STRBUF_AUTO_DESTROY buf = ffStrbufCreate(); FF_LIST_FOR_EACH(FFstrbuf, item, result) { if (!ffStrbufContainC(item, '.')) continue; // IPv4 if (buf.length) ffStrbufAppendC(&buf, ' '); ffStrbufAppend(&buf, item); } FF_LIST_FOR_EACH(FFstrbuf, item, result) { if (!ffStrbufContainC(item, ':')) continue; // IPv6 if (buf.length) ffStrbufAppendC(&buf, ' '); ffStrbufAppend(&buf, item); } if(options->moduleArgs.outputFormat.length == 0) { ffPrintLogoAndKey(FF_DNS_MODULE_NAME, 0, &options->moduleArgs, FF_PRINT_TYPE_DEFAULT); ffStrbufPutTo(&buf, stdout); } else { FF_PRINT_FORMAT_CHECKED(FF_DNS_MODULE_NAME, 0, &options->moduleArgs, FF_PRINT_TYPE_DEFAULT, ((FFformatarg[]) { FF_ARG(buf, "result"), })); } FF_LIST_FOR_EACH(FFstrbuf, item, result) { ffStrbufDestroy(item); } return true; } void ffParseDNSJsonObject(FFDNSOptions* options, yyjson_val* module) { yyjson_val *key, *val; size_t idx, max; yyjson_obj_foreach(module, idx, max, key, val) { if (ffJsonConfigParseModuleArgs(key, val, &options->moduleArgs)) continue; if (unsafe_yyjson_equals_str(key, "showType")) { int value; const char* error = ffJsonConfigParseEnum(val, &value, (FFKeyValuePair[]) { { "both", FF_DNS_TYPE_BOTH }, { "ipv4", FF_DNS_TYPE_IPV4_BIT }, { "ipv6", FF_DNS_TYPE_IPV6_BIT }, {}, }); if (error) ffPrintError(FF_DNS_MODULE_NAME, 0, NULL, FF_PRINT_TYPE_NO_CUSTOM_KEY, "Invalid %s value: %s", unsafe_yyjson_get_str(key), error); else options->showType = (FFDNSShowType) value; continue; } ffPrintError(FF_DNS_MODULE_NAME, 0, &options->moduleArgs, FF_PRINT_TYPE_DEFAULT, "Unknown JSON key %s", unsafe_yyjson_get_str(key)); } } void ffGenerateDNSJsonConfig(FFDNSOptions* options, yyjson_mut_doc* doc, yyjson_mut_val* module) { ffJsonConfigGenerateModuleArgsConfig(doc, module, &options->moduleArgs); switch ((uint8_t) options->showType) { case FF_DNS_TYPE_IPV4_BIT: yyjson_mut_obj_add_str(doc, module, "showType", "ipv4"); break; case FF_DNS_TYPE_IPV6_BIT: yyjson_mut_obj_add_str(doc, module, "showType", "ipv6"); break; case FF_DNS_TYPE_BOTH: yyjson_mut_obj_add_str(doc, module, "showType", "both"); break; } } bool ffGenerateDNSJsonResult(FFDNSOptions* options, yyjson_mut_doc* doc, yyjson_mut_val* module) { FF_LIST_AUTO_DESTROY result = ffListCreate(sizeof(FFstrbuf)); const char* error = ffDetectDNS(options, &result); if (error) { yyjson_mut_obj_add_str(doc, module, "error", error); return false; } yyjson_mut_val* arr = yyjson_mut_obj_add_arr(doc, module, "result"); FF_LIST_FOR_EACH(FFstrbuf, item, result) { yyjson_mut_arr_add_strbuf(doc, arr, item); } FF_LIST_FOR_EACH(FFstrbuf, item, result) { ffStrbufDestroy(item); } return true; } void ffInitDNSOptions(FFDNSOptions* options) { ffOptionInitModuleArg(&options->moduleArgs, "󰇖"); options->showType = FF_DNS_TYPE_BOTH; } void ffDestroyDNSOptions(FFDNSOptions* options) { ffOptionDestroyModuleArg(&options->moduleArgs); } FFModuleBaseInfo ffDNSModuleInfo = { .name = FF_DNS_MODULE_NAME, .description = "Print configured DNS servers", .initOptions = (void*) ffInitDNSOptions, .destroyOptions = (void*) ffDestroyDNSOptions, .parseJsonObject = (void*) ffParseDNSJsonObject, .printModule = (void*) ffPrintDNS, .generateJsonResult = (void*) ffGenerateDNSJsonResult, .generateJsonConfig = (void*) ffGenerateDNSJsonConfig, .formatArgs = FF_FORMAT_ARG_LIST(((FFModuleFormatArg[]) { {"DNS result", "result"}, })) }; ================================================ FILE: src/modules/dns/dns.h ================================================ #pragma once #include "option.h" #define FF_DNS_MODULE_NAME "DNS" bool ffPrintDNS(FFDNSOptions* options); void ffInitDNSOptions(FFDNSOptions* options); void ffDestroyDNSOptions(FFDNSOptions* options); extern FFModuleBaseInfo ffDNSModuleInfo; ================================================ FILE: src/modules/dns/option.h ================================================ #pragma once #include "common/option.h" typedef enum __attribute__((__packed__)) FFDNSShowType { FF_DNS_TYPE_IPV4_BIT = 1, FF_DNS_TYPE_IPV6_BIT = 2, FF_DNS_TYPE_BOTH = FF_DNS_TYPE_IPV4_BIT | FF_DNS_TYPE_IPV6_BIT, FF_DNS_TYPE_FORCE_UNSIGNED = UINT8_MAX, } FFDNSShowType; typedef struct FFDNSOptions { FFModuleArgs moduleArgs; FFDNSShowType showType; } FFDNSOptions; static_assert(sizeof(FFDNSOptions) <= FF_OPTION_MAX_SIZE, "FFDNSOptions size exceeds maximum allowed size"); ================================================ FILE: src/modules/editor/editor.c ================================================ #include "common/printing.h" #include "common/jsonconfig.h" #include "common/stringUtils.h" #include "detection/libc/libc.h" #include "detection/editor/editor.h" #include "modules/editor/editor.h" bool ffPrintEditor(FFEditorOptions* options) { FFEditorResult result = { .type = "Unknown", .name = ffStrbufCreate(), .path = ffStrbufCreate(), .exe = ffStrbufCreate(), .version = ffStrbufCreate(), }; const char* error = ffDetectEditor(&result); if (error) { ffPrintError(FF_EDITOR_MODULE_NAME, 0, &options->moduleArgs, FF_PRINT_TYPE_DEFAULT, "%s", error); return false; } if (options->moduleArgs.outputFormat.length == 0) { ffPrintLogoAndKey(FF_EDITOR_MODULE_NAME, 0, &options->moduleArgs, FF_PRINT_TYPE_DEFAULT); if (result.exe.length) { ffStrbufWriteTo(&result.exe, stdout); if (result.version.length) printf(" %s", result.version.chars); } else { ffStrbufWriteTo(&result.name, stdout); } putchar('\n'); } else { FF_PRINT_FORMAT_CHECKED(FF_EDITOR_MODULE_NAME, 0, &options->moduleArgs, FF_PRINT_TYPE_DEFAULT, ((FFformatarg[]){ FF_ARG(result.type, "type"), FF_ARG(result.name, "name"), FF_ARG(result.exe, "exe-name"), FF_ARG(result.path, "path"), FF_ARG(result.version, "version"), })); } ffStrbufDestroy(&result.name); ffStrbufDestroy(&result.path); ffStrbufDestroy(&result.exe); ffStrbufDestroy(&result.version); return true; } void ffParseEditorJsonObject(FFEditorOptions* options, yyjson_val* module) { yyjson_val *key, *val; size_t idx, max; yyjson_obj_foreach(module, idx, max, key, val) { if (ffJsonConfigParseModuleArgs(key, val, &options->moduleArgs)) continue; ffPrintError(FF_EDITOR_MODULE_NAME, 0, &options->moduleArgs, FF_PRINT_TYPE_DEFAULT, "Unknown JSON key %s", unsafe_yyjson_get_str(key)); } } void ffGenerateEditorJsonConfig(FFEditorOptions* options, yyjson_mut_doc* doc, yyjson_mut_val* module) { ffJsonConfigGenerateModuleArgsConfig(doc, module, &options->moduleArgs); } bool ffGenerateEditorJsonResult(FF_MAYBE_UNUSED FFEditorOptions* options, yyjson_mut_doc* doc, yyjson_mut_val* module) { FFEditorResult result = { .name = ffStrbufCreate(), .path = ffStrbufCreate(), .version = ffStrbufCreate(), }; const char* error = ffDetectEditor(&result); if (error) { yyjson_mut_obj_add_str(doc, module, "error", error); return false; } yyjson_mut_val* obj = yyjson_mut_obj_add_obj(doc, module, "result"); yyjson_mut_obj_add_str(doc, obj, "type", result.type); yyjson_mut_obj_add_strbuf(doc, obj, "name", &result.name); yyjson_mut_obj_add_strbuf(doc, obj, "path", &result.path); yyjson_mut_obj_add_strbuf(doc, obj, "exe", &result.exe); yyjson_mut_obj_add_strbuf(doc, obj, "version", &result.version); ffStrbufDestroy(&result.name); ffStrbufDestroy(&result.path); ffStrbufDestroy(&result.exe); ffStrbufDestroy(&result.version); return true; } void ffInitEditorOptions(FFEditorOptions* options) { ffOptionInitModuleArg(&options->moduleArgs, "󱞎"); } void ffDestroyEditorOptions(FFEditorOptions* options) { ffOptionDestroyModuleArg(&options->moduleArgs); } FFModuleBaseInfo ffEditorModuleInfo = { .name = FF_EDITOR_MODULE_NAME, .description = "Print information of the default editor ($VISUAL or $EDITOR)", .initOptions = (void*) ffInitEditorOptions, .destroyOptions = (void*) ffDestroyEditorOptions, .parseJsonObject = (void*) ffParseEditorJsonObject, .printModule = (void*) ffPrintEditor, .generateJsonResult = (void*) ffGenerateEditorJsonResult, .generateJsonConfig = (void*) ffGenerateEditorJsonConfig, .formatArgs = FF_FORMAT_ARG_LIST(((FFModuleFormatArg[]) { {"Type (Visual / Editor)", "type"}, {"Name", "name"}, {"Exe name of real path", "exe-name"}, {"Full path of real path", "path"}, {"Version", "version"}, })) }; ================================================ FILE: src/modules/editor/editor.h ================================================ #pragma once #include "option.h" #define FF_EDITOR_MODULE_NAME "Editor" bool ffPrintEditor(FFEditorOptions* options); void ffInitEditorOptions(FFEditorOptions* options); void ffDestroyEditorOptions(FFEditorOptions* options); extern FFModuleBaseInfo ffEditorModuleInfo; ================================================ FILE: src/modules/editor/option.h ================================================ #pragma once #include "common/option.h" typedef struct FFEditorOptions { FFModuleArgs moduleArgs; } FFEditorOptions; static_assert(sizeof(FFEditorOptions) <= FF_OPTION_MAX_SIZE, "FFEditorOptions size exceeds maximum allowed size"); ================================================ FILE: src/modules/font/font.c ================================================ #include "common/printing.h" #include "common/jsonconfig.h" #include "common/stringUtils.h" #include "detection/font/font.h" #include "modules/font/font.h" bool ffPrintFont(FFFontOptions* options) { bool success = false; FFFontResult font; for(uint32_t i = 0; i < FF_DETECT_FONT_NUM_FONTS; ++i) ffStrbufInit(&font.fonts[i]); ffStrbufInit(&font.display); const char* error = ffDetectFont(&font); if(error) { ffPrintError(FF_FONT_MODULE_NAME, 0, &options->moduleArgs, FF_PRINT_TYPE_DEFAULT, "%s", error); } else { if(options->moduleArgs.outputFormat.length == 0) { ffPrintLogoAndKey(FF_FONT_MODULE_NAME, 0, &options->moduleArgs, FF_PRINT_TYPE_DEFAULT); ffStrbufPutTo(&font.display, stdout); } else { FF_PRINT_FORMAT_CHECKED(FF_FONT_MODULE_NAME, 0, &options->moduleArgs, FF_PRINT_TYPE_DEFAULT, ((FFformatarg[]) { FF_ARG(font.fonts[0], "font1"), FF_ARG(font.fonts[1], "font2"), FF_ARG(font.fonts[2], "font3"), FF_ARG(font.fonts[3], "font4"), FF_ARG(font.display, "combined"), })); } success = true; } ffStrbufDestroy(&font.display); for (uint32_t i = 0; i < FF_DETECT_FONT_NUM_FONTS; ++i) ffStrbufDestroy(&font.fonts[i]); return success; } void ffParseFontJsonObject(FFFontOptions* options, yyjson_val* module) { yyjson_val *key, *val; size_t idx, max; yyjson_obj_foreach(module, idx, max, key, val) { if (ffJsonConfigParseModuleArgs(key, val, &options->moduleArgs)) continue; ffPrintError(FF_FONT_MODULE_NAME, 0, &options->moduleArgs, FF_PRINT_TYPE_DEFAULT, "Unknown JSON key %s", unsafe_yyjson_get_str(key)); } } void ffGenerateFontJsonConfig(FFFontOptions* options, yyjson_mut_doc* doc, yyjson_mut_val* module) { ffJsonConfigGenerateModuleArgsConfig(doc, module, &options->moduleArgs); } bool ffGenerateFontJsonResult(FF_MAYBE_UNUSED FFFontOptions* options, yyjson_mut_doc* doc, yyjson_mut_val* module) { bool success = false; FFFontResult font; for(uint32_t i = 0; i < FF_DETECT_FONT_NUM_FONTS; ++i) ffStrbufInit(&font.fonts[i]); ffStrbufInit(&font.display); const char* error = ffDetectFont(&font); if(error) { yyjson_mut_obj_add_str(doc, module, "error", error); } else { yyjson_mut_val* obj = yyjson_mut_obj_add_obj(doc, module, "result"); yyjson_mut_obj_add_strbuf(doc, obj, "display", &font.display); yyjson_mut_val* fontsArr = yyjson_mut_obj_add_arr(doc, obj, "fonts"); for (uint32_t i = 0; i < FF_DETECT_FONT_NUM_FONTS; ++i) yyjson_mut_arr_add_strbuf(doc, fontsArr, &font.fonts[i]); success = true; } ffStrbufDestroy(&font.display); for (uint32_t i = 0; i < FF_DETECT_FONT_NUM_FONTS; ++i) ffStrbufDestroy(&font.fonts[i]); return success; } void ffInitFontOptions(FFFontOptions* options) { ffOptionInitModuleArg(&options->moduleArgs, ""); } void ffDestroyFontOptions(FFFontOptions* options) { ffOptionDestroyModuleArg(&options->moduleArgs); } FFModuleBaseInfo ffFontModuleInfo = { .name = FF_FONT_MODULE_NAME, .description = "Print system font names", .initOptions = (void*) ffInitFontOptions, .destroyOptions = (void*) ffDestroyFontOptions, .parseJsonObject = (void*) ffParseFontJsonObject, .printModule = (void*) ffPrintFont, .generateJsonResult = (void*) ffGenerateFontJsonResult, .generateJsonConfig = (void*) ffGenerateFontJsonConfig, .formatArgs = FF_FORMAT_ARG_LIST(((FFModuleFormatArg[]) { {"Font 1", "font1"}, {"Font 2", "font2"}, {"Font 3", "font3"}, {"Font 4", "font4"}, {"Combined fonts for display", "combined"}, })) }; ================================================ FILE: src/modules/font/font.h ================================================ #pragma once #include "option.h" #define FF_FONT_MODULE_NAME "Font" bool ffPrintFont(FFFontOptions* options); void ffInitFontOptions(FFFontOptions* options); void ffDestroyFontOptions(FFFontOptions* options); extern FFModuleBaseInfo ffFontModuleInfo; ================================================ FILE: src/modules/font/option.h ================================================ #pragma once #include "common/option.h" typedef struct FFFontOptions { FFModuleArgs moduleArgs; } FFFontOptions; static_assert(sizeof(FFFontOptions) <= FF_OPTION_MAX_SIZE, "FFFontOptions size exceeds maximum allowed size"); ================================================ FILE: src/modules/gamepad/gamepad.c ================================================ #include "common/percent.h" #include "common/printing.h" #include "common/jsonconfig.h" #include "common/stringUtils.h" #include "detection/gamepad/gamepad.h" #include "modules/gamepad/gamepad.h" static void printDevice(FFGamepadOptions* options, const FFGamepadDevice* device, uint8_t index) { FFPercentageTypeFlags percentType = options->percent.type == 0 ? instance.config.display.percentType : options->percent.type; if(options->moduleArgs.outputFormat.length == 0) { ffPrintLogoAndKey(FF_GAMEPAD_MODULE_NAME, index, &options->moduleArgs, FF_PRINT_TYPE_DEFAULT); FF_STRBUF_AUTO_DESTROY buffer = ffStrbufCreate(); bool showBatteryLevel = device->battery > 0 && device->battery <= 100; if (showBatteryLevel && (percentType & FF_PERCENTAGE_TYPE_BAR_BIT)) { ffPercentAppendBar(&buffer, device->battery, options->percent, &options->moduleArgs); ffStrbufAppendC(&buffer, ' '); } if (!(percentType & FF_PERCENTAGE_TYPE_HIDE_OTHERS_BIT)) ffStrbufAppend(&buffer, &device->name); if (showBatteryLevel && (percentType & FF_PERCENTAGE_TYPE_NUM_BIT)) { if (buffer.length) ffStrbufAppendC(&buffer, ' '); ffPercentAppendNum(&buffer, device->battery, options->percent, buffer.length > 0, &options->moduleArgs); } ffStrbufPutTo(&buffer, stdout); } else { FF_STRBUF_AUTO_DESTROY percentageNum = ffStrbufCreate(); if (percentType & FF_PERCENTAGE_TYPE_NUM_BIT) ffPercentAppendNum(&percentageNum, device->battery, options->percent, false, &options->moduleArgs); FF_STRBUF_AUTO_DESTROY percentageBar = ffStrbufCreate(); if (percentType & FF_PERCENTAGE_TYPE_BAR_BIT) ffPercentAppendBar(&percentageBar, device->battery, options->percent, &options->moduleArgs); FF_PRINT_FORMAT_CHECKED(FF_GAMEPAD_MODULE_NAME, index, &options->moduleArgs, FF_PRINT_TYPE_DEFAULT, ((FFformatarg[]) { FF_ARG(device->name, "name"), FF_ARG(device->serial, "serial"), FF_ARG(percentageNum, "battery-percentage"), FF_ARG(percentageBar, "battery-percentage-bar"), })); } } bool ffPrintGamepad(FFGamepadOptions* options) { FF_LIST_AUTO_DESTROY result = ffListCreate(sizeof(FFGamepadDevice)); const char* error = ffDetectGamepad(&result); if(error) { ffPrintError(FF_GAMEPAD_MODULE_NAME, 0, &options->moduleArgs, FF_PRINT_TYPE_DEFAULT, "%s", error); return false; } if(!result.length) { ffPrintError(FF_GAMEPAD_MODULE_NAME, 0, &options->moduleArgs, FF_PRINT_TYPE_DEFAULT, "No devices detected"); return false; } FF_LIST_AUTO_DESTROY filtered = ffListCreate(sizeof(FFGamepadDevice*)); FF_LIST_FOR_EACH(FFGamepadDevice, device, result) { bool ignored = false; FF_LIST_FOR_EACH(FFstrbuf, ignore, options->ignores) { if(ffStrbufStartsWithIgnCase(&device->name, ignore)) { ignored = true; break; } } if(!ignored) { FFGamepadDevice** ptr = ffListAdd(&filtered); *ptr = device; } } bool ret = true; if(!filtered.length) { ffPrintError(FF_GAMEPAD_MODULE_NAME, 0, &options->moduleArgs, FF_PRINT_TYPE_DEFAULT, "All devices are ignored"); ret = false; } else { uint8_t index = 0; FF_LIST_FOR_EACH(FFGamepadDevice*, pdevice, filtered) { FFGamepadDevice* device = *pdevice; printDevice(options, device, filtered.length > 1 ? ++index : 0); } FF_LIST_FOR_EACH(FFGamepadDevice, device, result) { ffStrbufDestroy(&device->serial); ffStrbufDestroy(&device->name); } } return ret; } void ffParseGamepadJsonObject(FFGamepadOptions* options, yyjson_val* module) { yyjson_val *key, *val; size_t idx, max; yyjson_obj_foreach(module, idx, max, key, val) { if (ffJsonConfigParseModuleArgs(key, val, &options->moduleArgs)) continue; if (unsafe_yyjson_equals_str(key, "ignores")) { yyjson_val *elem; size_t eidx, emax; yyjson_arr_foreach(val, eidx, emax, elem) { if (yyjson_is_str(elem)) { FFstrbuf* strbuf = ffListAdd(&options->ignores); ffStrbufInitJsonVal(strbuf, elem); } } continue; } if (ffPercentParseJsonObject(key, val, &options->percent)) continue; ffPrintError(FF_GAMEPAD_MODULE_NAME, 0, &options->moduleArgs, FF_PRINT_TYPE_DEFAULT, "Unknown JSON key %s", unsafe_yyjson_get_str(key)); } } void ffGenerateGamepadJsonConfig(FFGamepadOptions* options, yyjson_mut_doc* doc, yyjson_mut_val* module) { ffJsonConfigGenerateModuleArgsConfig(doc, module, &options->moduleArgs); if (options->ignores.length > 0) { yyjson_mut_val* ignores = yyjson_mut_obj_add_arr(doc, module, "ignores"); FF_LIST_FOR_EACH(FFstrbuf, strbuf, options->ignores) yyjson_mut_arr_append(ignores, yyjson_mut_strncpy(doc, strbuf->chars, strbuf->length)); } ffPercentGenerateJsonConfig(doc, module, options->percent); } bool ffGenerateGamepadJsonResult(FF_MAYBE_UNUSED FFGamepadOptions* options, yyjson_mut_doc* doc, yyjson_mut_val* module) { FF_LIST_AUTO_DESTROY result = ffListCreate(sizeof(FFGamepadDevice)); const char* error = ffDetectGamepad(&result); if(error) { yyjson_mut_obj_add_str(doc, module, "error", error); return false; } yyjson_mut_val* arr = yyjson_mut_obj_add_arr(doc, module, "result"); FF_LIST_FOR_EACH(FFGamepadDevice, device, result) { yyjson_mut_val* obj = yyjson_mut_arr_add_obj(doc, arr); yyjson_mut_obj_add_strbuf(doc, obj, "serial", &device->serial); yyjson_mut_obj_add_strbuf(doc, obj, "name", &device->name); bool ignored = false; FF_LIST_FOR_EACH(FFstrbuf, ignore, options->ignores) { if(ffStrbufStartsWithIgnCase(&device->name, ignore)) { ignored = true; break; } } yyjson_mut_obj_add_bool(doc, obj, "ignored", ignored); } FF_LIST_FOR_EACH(FFGamepadDevice, device, result) { ffStrbufDestroy(&device->serial); ffStrbufDestroy(&device->name); } return true; } void ffInitGamepadOptions(FFGamepadOptions* options) { ffOptionInitModuleArg(&options->moduleArgs, "󰺵"); ffListInit(&options->ignores, sizeof(FFstrbuf)); options->percent = (FFPercentageModuleConfig) { 50, 20, 0 }; } void ffDestroyGamepadOptions(FFGamepadOptions* options) { ffOptionDestroyModuleArg(&options->moduleArgs); FF_LIST_FOR_EACH(FFstrbuf, str, options->ignores) ffStrbufDestroy(str); ffListDestroy(&options->ignores); } FFModuleBaseInfo ffGamepadModuleInfo = { .name = FF_GAMEPAD_MODULE_NAME, .description = "List (connected) gamepads", .initOptions = (void*) ffInitGamepadOptions, .destroyOptions = (void*) ffDestroyGamepadOptions, .parseJsonObject = (void*) ffParseGamepadJsonObject, .printModule = (void*) ffPrintGamepad, .generateJsonResult = (void*) ffGenerateGamepadJsonResult, .generateJsonConfig = (void*) ffGenerateGamepadJsonConfig, .formatArgs = FF_FORMAT_ARG_LIST(((FFModuleFormatArg[]) { {"Name", "name"}, {"Serial number", "serial"}, {"Battery percentage num", "battery-percentage"}, {"Battery percentage bar", "battery-percentage-bar"}, })) }; ================================================ FILE: src/modules/gamepad/gamepad.h ================================================ #pragma once #include "option.h" #define FF_GAMEPAD_MODULE_NAME "Gamepad" bool ffPrintGamepad(FFGamepadOptions* options); void ffInitGamepadOptions(FFGamepadOptions* options); void ffDestroyGamepadOptions(FFGamepadOptions* options); extern FFModuleBaseInfo ffGamepadModuleInfo; ================================================ FILE: src/modules/gamepad/option.h ================================================ #pragma once #include "common/option.h" #include "common/FFlist.h" typedef struct FFGamepadOptions { FFModuleArgs moduleArgs; FFlist ignores; // List of FFstrbuf FFPercentageModuleConfig percent; } FFGamepadOptions; static_assert(sizeof(FFGamepadOptions) <= FF_OPTION_MAX_SIZE, "FFGamepadOptions size exceeds maximum allowed size"); ================================================ FILE: src/modules/gpu/gpu.c ================================================ #include "common/percent.h" #include "common/printing.h" #include "common/jsonconfig.h" #include "common/temps.h" #include "common/size.h" #include "common/frequency.h" #include "common/stringUtils.h" #include "detection/host/host.h" #include "detection/gpu/gpu.h" #include "modules/gpu/gpu.h" #include static void printGPUResult(FFGPUOptions* options, uint8_t index, const FFGPUResult* gpu) { const char* type; switch (gpu->type) { case FF_GPU_TYPE_INTEGRATED: type = "Integrated"; break; case FF_GPU_TYPE_DISCRETE: type = "Discrete"; break; default: type = "Unknown"; break; } FFPercentageTypeFlags percentType = options->percent.type == 0 ? instance.config.display.percentType : options->percent.type; if(options->moduleArgs.outputFormat.length == 0) { ffPrintLogoAndKey(FF_GPU_MODULE_NAME, index, &options->moduleArgs, FF_PRINT_TYPE_DEFAULT); FF_STRBUF_AUTO_DESTROY output = ffStrbufCreate(); if(gpu->vendor.length > 0 && !ffStrbufStartsWithIgnCase(&gpu->name, &gpu->vendor)) { ffStrbufAppend(&output, &gpu->vendor); ffStrbufAppendC(&output, ' '); } ffStrbufAppend(&output, &gpu->name); if(gpu->coreCount != FF_GPU_CORE_COUNT_UNSET) ffStrbufAppendF(&output, " (%d)", gpu->coreCount); if(gpu->frequency > 0) { ffStrbufAppendS(&output, " @ "); ffFreqAppendNum(gpu->frequency, &output); } if(gpu->temperature != FF_GPU_TEMP_UNSET) { ffStrbufAppendS(&output, " - "); ffTempsAppendNum(gpu->temperature, &output, options->tempConfig, &options->moduleArgs); } if(gpu->dedicated.total != FF_GPU_VMEM_SIZE_UNSET && gpu->dedicated.total != 0) { ffStrbufAppendS(&output, " ("); if (!(percentType & FF_PERCENTAGE_TYPE_HIDE_OTHERS_BIT)) { if(gpu->dedicated.used != FF_GPU_VMEM_SIZE_UNSET) { ffSizeAppendNum(gpu->dedicated.used, &output); ffStrbufAppendS(&output, " / "); } ffSizeAppendNum(gpu->dedicated.total, &output); } if(gpu->dedicated.used != FF_GPU_VMEM_SIZE_UNSET) { double percent = (double) gpu->dedicated.used / (double) gpu->dedicated.total * 100.0; if (percentType & FF_PERCENTAGE_TYPE_NUM_BIT) { ffStrbufAppendS(&output, ", "); ffPercentAppendNum(&output, percent, options->percent, false, &options->moduleArgs); } if (percentType & FF_PERCENTAGE_TYPE_BAR_BIT) { ffStrbufAppendS(&output, " "); ffPercentAppendBar(&output, percent, options->percent, &options->moduleArgs); } } ffStrbufAppendC(&output, ')'); } if (gpu->type != FF_GPU_TYPE_UNKNOWN) ffStrbufAppendF(&output, " [%s]", type); ffStrbufPutTo(&output, stdout); } else { FF_STRBUF_AUTO_DESTROY tempStr = ffStrbufCreate(); ffTempsAppendNum(gpu->temperature, &tempStr, options->tempConfig, &options->moduleArgs); FF_STRBUF_AUTO_DESTROY dTotal = ffStrbufCreate(); FF_STRBUF_AUTO_DESTROY dUsed = ffStrbufCreate(); FF_STRBUF_AUTO_DESTROY dPercentNum = ffStrbufCreate(); FF_STRBUF_AUTO_DESTROY dPercentBar = ffStrbufCreate(); if (gpu->dedicated.total != FF_GPU_VMEM_SIZE_UNSET) ffSizeAppendNum(gpu->dedicated.total, &dTotal); if (gpu->dedicated.used != FF_GPU_VMEM_SIZE_UNSET) ffSizeAppendNum(gpu->dedicated.used, &dUsed); if (gpu->dedicated.total != FF_GPU_VMEM_SIZE_UNSET && gpu->dedicated.used != FF_GPU_VMEM_SIZE_UNSET) { double percent = (double) gpu->dedicated.used / (double) gpu->dedicated.total * 100.0; if (percentType & FF_PERCENTAGE_TYPE_NUM_BIT) ffPercentAppendNum(&dPercentNum, percent, options->percent, false, &options->moduleArgs); if (percentType & FF_PERCENTAGE_TYPE_BAR_BIT) ffPercentAppendBar(&dPercentBar, percent, options->percent, &options->moduleArgs); } FF_STRBUF_AUTO_DESTROY sTotal = ffStrbufCreate(); FF_STRBUF_AUTO_DESTROY sUsed = ffStrbufCreate(); FF_STRBUF_AUTO_DESTROY sPercentNum = ffStrbufCreate(); FF_STRBUF_AUTO_DESTROY sPercentBar = ffStrbufCreate(); if (gpu->shared.total != FF_GPU_VMEM_SIZE_UNSET) ffSizeAppendNum(gpu->shared.total, &sTotal); if (gpu->shared.used != FF_GPU_VMEM_SIZE_UNSET) ffSizeAppendNum(gpu->shared.used, &sUsed); if (gpu->shared.total != FF_GPU_VMEM_SIZE_UNSET && gpu->shared.used != FF_GPU_VMEM_SIZE_UNSET) { double percent = (double) gpu->shared.used / (double) gpu->shared.total * 100.0; if (percentType & FF_PERCENTAGE_TYPE_NUM_BIT) ffPercentAppendNum(&sPercentNum, percent, options->percent, false, &options->moduleArgs); if (percentType & FF_PERCENTAGE_TYPE_BAR_BIT) ffPercentAppendBar(&sPercentBar, percent, options->percent, &options->moduleArgs); } FF_STRBUF_AUTO_DESTROY frequency = ffStrbufCreate(); ffFreqAppendNum(gpu->frequency, &frequency); FF_STRBUF_AUTO_DESTROY coreUsageNum = ffStrbufCreate(); FF_STRBUF_AUTO_DESTROY coreUsageBar = ffStrbufCreate(); if (gpu->coreUsage != FF_GPU_CORE_USAGE_UNSET) { if (percentType & FF_PERCENTAGE_TYPE_NUM_BIT) ffPercentAppendNum(&coreUsageNum, gpu->coreUsage, options->percent, false, &options->moduleArgs); if (percentType & FF_PERCENTAGE_TYPE_BAR_BIT) ffPercentAppendBar(&coreUsageBar, gpu->coreUsage, options->percent, &options->moduleArgs); } FF_PRINT_FORMAT_CHECKED(FF_GPU_MODULE_NAME, index, &options->moduleArgs, FF_PRINT_TYPE_DEFAULT, ((FFformatarg[]) { FF_ARG(gpu->vendor, "vendor"), FF_ARG(gpu->name, "name"), FF_ARG(gpu->driver, "driver"), FF_ARG(tempStr, "temperature"), FF_ARG(gpu->coreCount, "core-count"), FF_ARG(type, "type"), FF_ARG(dTotal, "dedicated-total"), FF_ARG(dUsed, "dedicated-used"), FF_ARG(sTotal, "shared-total"), FF_ARG(sUsed, "shared-used"), FF_ARG(gpu->platformApi, "platform-api"), FF_ARG(frequency, "frequency"), FF_ARG(index, "index"), FF_ARG(dPercentNum, "dedicated-percentage-num"), FF_ARG(dPercentBar, "dedicated-percentage-bar"), FF_ARG(sPercentNum, "shared-percentage-num"), FF_ARG(sPercentBar, "shared-percentage-bar"), FF_ARG(coreUsageNum, "core-usage-num"), FF_ARG(coreUsageBar, "core-usage-bar"), FF_ARG(gpu->memoryType, "memory-type"), })); } } bool ffPrintGPU(FFGPUOptions* options) { FF_LIST_AUTO_DESTROY gpus = ffListCreate(sizeof (FFGPUResult)); const char* error = ffDetectGPU(options, &gpus); if (error) { ffPrintError(FF_GPU_MODULE_NAME, 0, &options->moduleArgs, FF_PRINT_TYPE_DEFAULT, "%s", error); return false; } FF_LIST_AUTO_DESTROY selectedGPUs; ffListInitA(&selectedGPUs, sizeof(const FFGPUResult*), gpus.length); FF_LIST_FOR_EACH(FFGPUResult, gpu, gpus) { if(gpu->type == FF_GPU_TYPE_UNKNOWN && options->hideType == FF_GPU_TYPE_UNKNOWN) continue; if(gpu->type == FF_GPU_TYPE_INTEGRATED && options->hideType == FF_GPU_TYPE_INTEGRATED) continue; if(gpu->type == FF_GPU_TYPE_DISCRETE && options->hideType == FF_GPU_TYPE_DISCRETE) continue; * (const FFGPUResult**) ffListAdd(&selectedGPUs) = gpu; } for(uint32_t i = 0; i < selectedGPUs.length; i++) printGPUResult(options, selectedGPUs.length == 1 ? 0 : (uint8_t) (i + 1), *FF_LIST_GET(const FFGPUResult*, selectedGPUs, i)); if(selectedGPUs.length == 0) ffPrintError(FF_GPU_MODULE_NAME, 0, &options->moduleArgs, FF_PRINT_TYPE_DEFAULT, "No GPUs found"); FF_LIST_FOR_EACH(FFGPUResult, gpu, gpus) { ffStrbufDestroy(&gpu->vendor); ffStrbufDestroy(&gpu->name); ffStrbufDestroy(&gpu->driver); ffStrbufDestroy(&gpu->platformApi); ffStrbufDestroy(&gpu->memoryType); } return true; } void ffParseGPUJsonObject(FFGPUOptions* options, yyjson_val* module) { yyjson_val *key, *val; size_t idx, max; yyjson_obj_foreach(module, idx, max, key, val) { if (ffJsonConfigParseModuleArgs(key, val, &options->moduleArgs)) continue; if (ffTempsParseJsonObject(key, val, &options->temp, &options->tempConfig)) continue; if (unsafe_yyjson_equals_str(key, "driverSpecific")) { options->driverSpecific = yyjson_get_bool(val); continue; } if (unsafe_yyjson_equals_str(key, "detectionMethod")) { int value; const char* error = ffJsonConfigParseEnum(val, &value, (FFKeyValuePair[]) { { "auto", FF_GPU_DETECTION_METHOD_AUTO }, { "pci", FF_GPU_DETECTION_METHOD_PCI }, { "vulkan", FF_GPU_DETECTION_METHOD_VULKAN }, { "opencl", FF_GPU_DETECTION_METHOD_OPENCL }, { "opengl", FF_GPU_DETECTION_METHOD_OPENGL }, {}, }); if (error) ffPrintError(FF_GPU_MODULE_NAME, 0, &options->moduleArgs, FF_PRINT_TYPE_DEFAULT, "Invalid %s value: %s", unsafe_yyjson_get_str(key), error); else options->detectionMethod = (FFGPUDetectionMethod) value; continue; } if (unsafe_yyjson_equals_str(key, "hideType")) { if (yyjson_is_null(val)) options->hideType = FF_GPU_TYPE_NONE; else { int value; const char* error = ffJsonConfigParseEnum(val, &value, (FFKeyValuePair[]) { { "none", FF_GPU_TYPE_NONE }, { "unknown", FF_GPU_TYPE_UNKNOWN }, { "integrated", FF_GPU_TYPE_INTEGRATED }, { "discrete", FF_GPU_TYPE_DISCRETE }, {}, }); if (error) ffPrintError(FF_GPU_MODULE_NAME, 0, &options->moduleArgs, FF_PRINT_TYPE_DEFAULT, "Invalid %s value: %s", unsafe_yyjson_get_str(key), error); else options->hideType = (FFGPUType) value; } continue; } if (ffPercentParseJsonObject(key, val, &options->percent)) continue; ffPrintError(FF_GPU_MODULE_NAME, 0, &options->moduleArgs, FF_PRINT_TYPE_DEFAULT, "Unknown JSON key %s", unsafe_yyjson_get_str(key)); } } void ffGenerateGPUJsonConfig(FFGPUOptions* options, yyjson_mut_doc* doc, yyjson_mut_val* module) { ffJsonConfigGenerateModuleArgsConfig(doc, module, &options->moduleArgs); yyjson_mut_obj_add_bool(doc, module, "driverSpecific", options->driverSpecific); switch (options->detectionMethod) { case FF_GPU_DETECTION_METHOD_AUTO: yyjson_mut_obj_add_str(doc, module, "detectionMethod", "auto"); break; case FF_GPU_DETECTION_METHOD_PCI: yyjson_mut_obj_add_str(doc, module, "detectionMethod", "pci"); break; case FF_GPU_DETECTION_METHOD_VULKAN: yyjson_mut_obj_add_str(doc, module, "detectionMethod", "vulkan"); break; case FF_GPU_DETECTION_METHOD_OPENCL: yyjson_mut_obj_add_str(doc, module, "detectionMethod", "opencl"); break; case FF_GPU_DETECTION_METHOD_OPENGL: yyjson_mut_obj_add_str(doc, module, "detectionMethod", "opengl"); break; } ffTempsGenerateJsonConfig(doc, module, options->temp, options->tempConfig); switch (options->hideType) { case FF_GPU_TYPE_NONE: yyjson_mut_obj_add_str(doc, module, "hideType", "none"); break; case FF_GPU_TYPE_UNKNOWN: yyjson_mut_obj_add_str(doc, module, "hideType", "unknown"); break; case FF_GPU_TYPE_INTEGRATED: yyjson_mut_obj_add_str(doc, module, "hideType", "integrated"); break; case FF_GPU_TYPE_DISCRETE: yyjson_mut_obj_add_str(doc, module, "hideType", "discrete"); break; } ffPercentGenerateJsonConfig(doc, module, options->percent); } bool ffGenerateGPUJsonResult(FFGPUOptions* options, yyjson_mut_doc* doc, yyjson_mut_val* module) { FF_LIST_AUTO_DESTROY gpus = ffListCreate(sizeof (FFGPUResult)); const char* error = ffDetectGPU(options, &gpus); if (error) { yyjson_mut_obj_add_str(doc, module, "error", error); return false; } yyjson_mut_val* arr = yyjson_mut_obj_add_arr(doc, module, "result"); FF_LIST_FOR_EACH(FFGPUResult, gpu, gpus) { yyjson_mut_val* obj = yyjson_mut_arr_add_obj(doc, arr); if (gpu->index != FF_GPU_INDEX_UNSET) yyjson_mut_obj_add_uint(doc, obj, "index", gpu->index); else yyjson_mut_obj_add_null(doc, obj, "index"); if (gpu->coreCount != FF_GPU_CORE_COUNT_UNSET) yyjson_mut_obj_add_int(doc, obj, "coreCount", gpu->coreCount); else yyjson_mut_obj_add_null(doc, obj, "coreCount"); if (gpu->coreUsage != FF_GPU_CORE_USAGE_UNSET) yyjson_mut_obj_add_real(doc, obj, "coreUsage", gpu->coreUsage); else yyjson_mut_obj_add_null(doc, obj, "coreUsage"); yyjson_mut_val* memoryObj = yyjson_mut_obj_add_obj(doc, obj, "memory"); yyjson_mut_val* dedicatedObj = yyjson_mut_obj_add_obj(doc, memoryObj, "dedicated"); if (gpu->dedicated.total != FF_GPU_VMEM_SIZE_UNSET) yyjson_mut_obj_add_uint(doc, dedicatedObj, "total", gpu->dedicated.total); else yyjson_mut_obj_add_null(doc, dedicatedObj, "total"); if (gpu->dedicated.used != FF_GPU_VMEM_SIZE_UNSET) yyjson_mut_obj_add_uint(doc, dedicatedObj, "used", gpu->dedicated.used); else yyjson_mut_obj_add_null(doc, dedicatedObj, "used"); yyjson_mut_val* sharedObj = yyjson_mut_obj_add_obj(doc, memoryObj, "shared"); if (gpu->shared.total != FF_GPU_VMEM_SIZE_UNSET) yyjson_mut_obj_add_uint(doc, sharedObj, "total", gpu->shared.total); else yyjson_mut_obj_add_null(doc, sharedObj, "total"); if (gpu->shared.used != FF_GPU_VMEM_SIZE_UNSET) yyjson_mut_obj_add_uint(doc, sharedObj, "used", gpu->shared.used); else yyjson_mut_obj_add_null(doc, sharedObj, "used"); if (gpu->memoryType.length) yyjson_mut_obj_add_strbuf(doc, memoryObj, "type", &gpu->memoryType); else yyjson_mut_obj_add_null(doc, memoryObj, "type"); yyjson_mut_obj_add_strbuf(doc, obj, "driver", &gpu->driver); yyjson_mut_obj_add_strbuf(doc, obj, "name", &gpu->name); if(gpu->temperature != FF_GPU_TEMP_UNSET) yyjson_mut_obj_add_real(doc, obj, "temperature", gpu->temperature); else yyjson_mut_obj_add_null(doc, obj, "temperature"); const char* type; switch (gpu->type) { case FF_GPU_TYPE_INTEGRATED: type = "Integrated"; break; case FF_GPU_TYPE_DISCRETE: type = "Discrete"; break; default: type = NULL; break; } if (type) yyjson_mut_obj_add_str(doc, obj, "type", type); else yyjson_mut_obj_add_null(doc, obj, "type"); yyjson_mut_obj_add_strbuf(doc, obj, "vendor", &gpu->vendor); yyjson_mut_obj_add_strbuf(doc, obj, "platformApi", &gpu->platformApi); if (gpu->frequency != FF_GPU_FREQUENCY_UNSET) yyjson_mut_obj_add_uint(doc, obj, "frequency", gpu->frequency); else yyjson_mut_obj_add_null(doc, obj, "frequency"); yyjson_mut_obj_add_uint(doc, obj, "deviceId", gpu->deviceId); } FF_LIST_FOR_EACH(FFGPUResult, gpu, gpus) { ffStrbufDestroy(&gpu->vendor); ffStrbufDestroy(&gpu->name); ffStrbufDestroy(&gpu->driver); ffStrbufDestroy(&gpu->platformApi); ffStrbufDestroy(&gpu->memoryType); } return true; } void ffInitGPUOptions(FFGPUOptions* options) { ffOptionInitModuleArg(&options->moduleArgs, "󰾲"); options->driverSpecific = false; options->detectionMethod = #if defined(__x86_64__) || defined(__i386__) FF_GPU_DETECTION_METHOD_PCI #else FF_GPU_DETECTION_METHOD_AUTO #endif ; options->temp = false; options->hideType = FF_GPU_TYPE_NONE; options->tempConfig = (FFColorRangeConfig) { 60, 80 }; options->percent = (FFPercentageModuleConfig) { 50, 80, 0 }; } void ffDestroyGPUOptions(FFGPUOptions* options) { ffOptionDestroyModuleArg(&options->moduleArgs); } FFModuleBaseInfo ffGPUModuleInfo = { .name = FF_GPU_MODULE_NAME, .description = "Print GPU names, graphic memory size, type, etc", .initOptions = (void*) ffInitGPUOptions, .destroyOptions = (void*) ffDestroyGPUOptions, .parseJsonObject = (void*) ffParseGPUJsonObject, .printModule = (void*) ffPrintGPU, .generateJsonResult = (void*) ffGenerateGPUJsonResult, .generateJsonConfig = (void*) ffGenerateGPUJsonConfig, .formatArgs = FF_FORMAT_ARG_LIST(((FFModuleFormatArg[]) { {"GPU vendor", "vendor"}, {"GPU name", "name"}, {"GPU driver", "driver"}, {"GPU temperature", "temperature"}, {"GPU core count", "core-count"}, {"GPU type", "type"}, {"GPU total dedicated memory", "dedicated-total"}, {"GPU used dedicated memory", "dedicated-used"}, {"GPU total shared memory", "shared-total"}, {"GPU used shared memory", "shared-used"}, {"The platform API used when detecting the GPU", "platform-api"}, {"Current frequency in GHz", "frequency"}, {"GPU vendor specific index", "index"}, {"Dedicated memory usage percentage num", "dedicated-percentage-num"}, {"Dedicated memory usage percentage bar", "dedicated-percentage-bar"}, {"Shared memory usage percentage num", "shared-percentage-num"}, {"Shared memory usage percentage bar", "shared-percentage-bar"}, {"Core usage percentage num", "core-usage-num"}, {"Core usage percentage bar", "core-usage-bar"}, {"Memory type (Windows only)", "memory-type"}, })), }; ================================================ FILE: src/modules/gpu/gpu.h ================================================ #pragma once #include "option.h" #define FF_GPU_MODULE_NAME "GPU" bool ffPrintGPU(FFGPUOptions* options); void ffInitGPUOptions(FFGPUOptions* options); void ffDestroyGPUOptions(FFGPUOptions* options); extern FFModuleBaseInfo ffGPUModuleInfo; ================================================ FILE: src/modules/gpu/option.h ================================================ #pragma once #include "common/option.h" #include "common/percent.h" typedef enum __attribute__((__packed__)) FFGPUType { FF_GPU_TYPE_NONE, // Indicates no specific GPU type. Useful as a hide filter only. FF_GPU_TYPE_UNKNOWN, // Indicates an unknown or unrecognized GPU type. FF_GPU_TYPE_INTEGRATED, FF_GPU_TYPE_DISCRETE, } FFGPUType; typedef enum __attribute__((__packed__)) FFGPUDetectionMethod { FF_GPU_DETECTION_METHOD_AUTO, FF_GPU_DETECTION_METHOD_PCI, FF_GPU_DETECTION_METHOD_VULKAN, FF_GPU_DETECTION_METHOD_OPENCL, FF_GPU_DETECTION_METHOD_OPENGL, } FFGPUDetectionMethod; typedef struct FFGPUOptions { FFModuleArgs moduleArgs; FFGPUType hideType; FFGPUDetectionMethod detectionMethod; bool temp; bool driverSpecific; FFColorRangeConfig tempConfig; FFPercentageModuleConfig percent; } FFGPUOptions; static_assert(sizeof(FFGPUOptions) <= FF_OPTION_MAX_SIZE, "FFGPUOptions size exceeds maximum allowed size"); ================================================ FILE: src/modules/host/host.c ================================================ #include "common/printing.h" #include "common/jsonconfig.h" #include "common/stringUtils.h" #include "detection/host/host.h" #include "modules/host/host.h" bool ffPrintHost(FFHostOptions* options) { bool success = false; FFHostResult host; ffStrbufInit(&host.family); ffStrbufInit(&host.name); ffStrbufInit(&host.version); ffStrbufInit(&host.sku); ffStrbufInit(&host.serial); ffStrbufInit(&host.uuid); ffStrbufInit(&host.vendor); const char* error = ffDetectHost(&host); if(error) { ffPrintError(FF_HOST_MODULE_NAME, 0, &options->moduleArgs, FF_PRINT_TYPE_DEFAULT, "%s", error); goto exit; } if(host.name.length == 0 && host.family.length == 0) { ffPrintError(FF_HOST_MODULE_NAME, 0, &options->moduleArgs, FF_PRINT_TYPE_DEFAULT, "neither product_family nor product_name is set by O.E.M."); goto exit; } if(options->moduleArgs.outputFormat.length == 0) { ffPrintLogoAndKey(FF_HOST_MODULE_NAME, 0, &options->moduleArgs, FF_PRINT_TYPE_DEFAULT); FF_STRBUF_AUTO_DESTROY output = ffStrbufCreate(); if(host.name.length > 0) ffStrbufAppend(&output, &host.name); else ffStrbufAppend(&output, &host.family); if(host.version.length > 0) ffStrbufAppendF(&output, " (%s)", host.version.chars); ffStrbufPutTo(&output, stdout); } else { FF_PRINT_FORMAT_CHECKED(FF_HOST_MODULE_NAME, 0, &options->moduleArgs, FF_PRINT_TYPE_DEFAULT, ((FFformatarg[]) { FF_ARG(host.family, "family"), FF_ARG(host.name, "name"), FF_ARG(host.version, "version"), FF_ARG(host.sku, "sku"), FF_ARG(host.vendor, "vendor"), FF_ARG(host.serial, "serial"), FF_ARG(host.uuid, "uuid"), })); } success = true; exit: ffStrbufDestroy(&host.family); ffStrbufDestroy(&host.name); ffStrbufDestroy(&host.version); ffStrbufDestroy(&host.sku); ffStrbufDestroy(&host.serial); ffStrbufDestroy(&host.uuid); ffStrbufDestroy(&host.vendor); return success; } void ffParseHostJsonObject(FFHostOptions* options, yyjson_val* module) { yyjson_val *key, *val; size_t idx, max; yyjson_obj_foreach(module, idx, max, key, val) { if (ffJsonConfigParseModuleArgs(key, val, &options->moduleArgs)) continue; ffPrintError(FF_HOST_MODULE_NAME, 0, &options->moduleArgs, FF_PRINT_TYPE_DEFAULT, "Unknown JSON key %s", unsafe_yyjson_get_str(key)); } } void ffGenerateHostJsonConfig(FFHostOptions* options, yyjson_mut_doc* doc, yyjson_mut_val* module) { ffJsonConfigGenerateModuleArgsConfig(doc, module, &options->moduleArgs); } bool ffGenerateHostJsonResult(FF_MAYBE_UNUSED FFHostOptions* options, yyjson_mut_doc* doc, yyjson_mut_val* module) { bool success = false; FFHostResult host; ffStrbufInit(&host.family); ffStrbufInit(&host.name); ffStrbufInit(&host.version); ffStrbufInit(&host.sku); ffStrbufInit(&host.serial); ffStrbufInit(&host.uuid); ffStrbufInit(&host.vendor); const char* error = ffDetectHost(&host); if (error) { yyjson_mut_obj_add_str(doc, module, "error", error); goto exit; } if (host.family.length == 0 && host.name.length == 0) { yyjson_mut_obj_add_str(doc, module, "error", "neither product_family nor product_name is set by O.E.M."); goto exit; } yyjson_mut_val* obj = yyjson_mut_obj_add_obj(doc, module, "result"); yyjson_mut_obj_add_strbuf(doc, obj, "family", &host.family); yyjson_mut_obj_add_strbuf(doc, obj, "name", &host.name); yyjson_mut_obj_add_strbuf(doc, obj, "version", &host.version); yyjson_mut_obj_add_strbuf(doc, obj, "sku", &host.sku); yyjson_mut_obj_add_strbuf(doc, obj, "vendor", &host.vendor); yyjson_mut_obj_add_strbuf(doc, obj, "serial", &host.serial); yyjson_mut_obj_add_strbuf(doc, obj, "uuid", &host.uuid); success = true; exit: ffStrbufDestroy(&host.family); ffStrbufDestroy(&host.name); ffStrbufDestroy(&host.version); ffStrbufDestroy(&host.sku); ffStrbufDestroy(&host.serial); ffStrbufDestroy(&host.uuid); ffStrbufDestroy(&host.vendor); return success; } void ffInitHostOptions(FFHostOptions* options) { ffOptionInitModuleArg(&options->moduleArgs, "󰌢"); } void ffDestroyHostOptions(FFHostOptions* options) { ffOptionDestroyModuleArg(&options->moduleArgs); } FFModuleBaseInfo ffHostModuleInfo = { .name = FF_HOST_MODULE_NAME, .description = "Print product name of your computer", .initOptions = (void*) ffInitHostOptions, .destroyOptions = (void*) ffDestroyHostOptions, .parseJsonObject = (void*) ffParseHostJsonObject, .printModule = (void*) ffPrintHost, .generateJsonResult = (void*) ffGenerateHostJsonResult, .generateJsonConfig = (void*) ffGenerateHostJsonConfig, .formatArgs = FF_FORMAT_ARG_LIST(((FFModuleFormatArg[]) { {"Product family", "family"}, {"Product name", "name"}, {"Product version", "version"}, {"Product sku", "sku"}, {"Product vendor", "vendor"}, {"Product serial number", "serial"}, {"Product uuid", "uuid"}, })) }; ================================================ FILE: src/modules/host/host.h ================================================ #pragma once #include "option.h" #define FF_HOST_MODULE_NAME "Host" bool ffPrintHost(FFHostOptions* options); void ffInitHostOptions(FFHostOptions* options); void ffDestroyHostOptions(FFHostOptions* options); extern FFModuleBaseInfo ffHostModuleInfo; ================================================ FILE: src/modules/host/option.h ================================================ #pragma once #include "common/option.h" typedef struct FFHostOptions { FFModuleArgs moduleArgs; } FFHostOptions; static_assert(sizeof(FFHostOptions) <= FF_OPTION_MAX_SIZE, "FFHostOptions size exceeds maximum allowed size"); ================================================ FILE: src/modules/icons/icons.c ================================================ #include "common/printing.h" #include "common/jsonconfig.h" #include "common/stringUtils.h" #include "detection/icons/icons.h" #include "modules/icons/icons.h" bool ffPrintIcons(FFIconsOptions* options) { bool success = false; FFIconsResult result = { .icons1 = ffStrbufCreate(), .icons2 = ffStrbufCreate(), }; const char* error = ffDetectIcons(&result); if(error) { ffPrintError(FF_ICONS_MODULE_NAME, 0, &options->moduleArgs, FF_PRINT_TYPE_DEFAULT, "%s", error); goto exit; } if(options->moduleArgs.outputFormat.length == 0) { ffPrintLogoAndKey(FF_ICONS_MODULE_NAME, 0, &options->moduleArgs, FF_PRINT_TYPE_DEFAULT); if (result.icons1.length) ffStrbufWriteTo(&result.icons1, stdout); if (result.icons2.length) { if (result.icons1.length) fputs(", ", stdout); ffStrbufWriteTo(&result.icons2, stdout); } putchar('\n'); } else { FF_PRINT_FORMAT_CHECKED(FF_ICONS_MODULE_NAME, 0, &options->moduleArgs, FF_PRINT_TYPE_DEFAULT, ((FFformatarg[]){ FF_ARG(result.icons1, "icons1"), FF_ARG(result.icons2, "icons2"), })); } success = true; exit: ffStrbufDestroy(&result.icons1); ffStrbufDestroy(&result.icons2); return success; } void ffParseIconsJsonObject(FFIconsOptions* options, yyjson_val* module) { yyjson_val *key, *val; size_t idx, max; yyjson_obj_foreach(module, idx, max, key, val) { if (ffJsonConfigParseModuleArgs(key, val, &options->moduleArgs)) continue; ffPrintError(FF_ICONS_MODULE_NAME, 0, &options->moduleArgs, FF_PRINT_TYPE_DEFAULT, "Unknown JSON key %s", unsafe_yyjson_get_str(key)); } } void ffGenerateIconsJsonConfig(FFIconsOptions* options, yyjson_mut_doc* doc, yyjson_mut_val* module) { ffJsonConfigGenerateModuleArgsConfig(doc, module, &options->moduleArgs); } bool ffGenerateIconsJsonResult(FF_MAYBE_UNUSED FFIconsOptions* options, yyjson_mut_doc* doc, yyjson_mut_val* module) { bool success = false; FFIconsResult result = { .icons1 = ffStrbufCreate(), .icons2 = ffStrbufCreate() }; const char* error = ffDetectIcons(&result); if(error) { yyjson_mut_obj_add_str(doc, module, "error", error); goto exit; } yyjson_mut_val* icons = yyjson_mut_obj_add_obj(doc, module, "result"); yyjson_mut_obj_add_strbuf(doc, icons, "icons1", &result.icons1); yyjson_mut_obj_add_strbuf(doc, icons, "icons2", &result.icons2); success = true; exit: ffStrbufDestroy(&result.icons1); ffStrbufDestroy(&result.icons2); return success; } void ffInitIconsOptions(FFIconsOptions* options) { ffOptionInitModuleArg(&options->moduleArgs, ""); } void ffDestroyIconsOptions(FFIconsOptions* options) { ffOptionDestroyModuleArg(&options->moduleArgs); } FFModuleBaseInfo ffIconsModuleInfo = { .name = FF_ICONS_MODULE_NAME, .description = "Print icon style name", .initOptions = (void*) ffInitIconsOptions, .destroyOptions = (void*) ffDestroyIconsOptions, .parseJsonObject = (void*) ffParseIconsJsonObject, .printModule = (void*) ffPrintIcons, .generateJsonResult = (void*) ffGenerateIconsJsonResult, .generateJsonConfig = (void*) ffGenerateIconsJsonConfig, .formatArgs = FF_FORMAT_ARG_LIST(((FFModuleFormatArg[]) { {"Icons part 1", "icons1"}, {"Icons part 2", "icons2"}, })) }; ================================================ FILE: src/modules/icons/icons.h ================================================ #pragma once #include "option.h" #define FF_ICONS_MODULE_NAME "Icons" bool ffPrintIcons(FFIconsOptions* options); void ffInitIconsOptions(FFIconsOptions* options); void ffDestroyIconsOptions(FFIconsOptions* options); extern FFModuleBaseInfo ffIconsModuleInfo; ================================================ FILE: src/modules/icons/option.h ================================================ #pragma once #include "common/option.h" typedef struct FFIconsOptions { FFModuleArgs moduleArgs; } FFIconsOptions; static_assert(sizeof(FFIconsOptions) <= FF_OPTION_MAX_SIZE, "FFIconsOptions size exceeds maximum allowed size"); ================================================ FILE: src/modules/initsystem/initsystem.c ================================================ #include "common/printing.h" #include "common/jsonconfig.h" #include "common/stringUtils.h" #include "detection/initsystem/initsystem.h" #include "modules/initsystem/initsystem.h" #define FF_INITSYSTEM_DISPLAY_NAME "Init System" bool ffPrintInitSystem(FFInitSystemOptions* options) { bool success = false; FFInitSystemResult result = { .name = ffStrbufCreate(), .exe = ffStrbufCreate(), .version = ffStrbufCreate(), .pid = 1, }; const char* error = ffDetectInitSystem(&result); if(error) { ffPrintError(FF_INITSYSTEM_DISPLAY_NAME, 0, &options->moduleArgs, FF_PRINT_TYPE_DEFAULT, "%s", error); goto exit; } if(options->moduleArgs.outputFormat.length == 0) { ffPrintLogoAndKey(FF_INITSYSTEM_DISPLAY_NAME, 0, &options->moduleArgs, FF_PRINT_TYPE_DEFAULT); ffStrbufWriteTo(&result.name, stdout); if (result.version.length) printf(" %s\n", result.version.chars); else putchar('\n'); } else { FF_PRINT_FORMAT_CHECKED(FF_INITSYSTEM_DISPLAY_NAME, 0, &options->moduleArgs, FF_PRINT_TYPE_DEFAULT, ((FFformatarg[]) { FF_ARG(result.name, "name"), FF_ARG(result.exe, "exe"), FF_ARG(result.version, "version"), FF_ARG(result.pid, "pid"), })); } success = true; exit: ffStrbufDestroy(&result.name); ffStrbufDestroy(&result.exe); ffStrbufDestroy(&result.version); return success; } void ffParseInitSystemJsonObject(FFInitSystemOptions* options, yyjson_val* module) { yyjson_val *key, *val; size_t idx, max; yyjson_obj_foreach(module, idx, max, key, val) { if (ffJsonConfigParseModuleArgs(key, val, &options->moduleArgs)) continue; ffPrintError(FF_INITSYSTEM_DISPLAY_NAME, 0, &options->moduleArgs, FF_PRINT_TYPE_DEFAULT, "Unknown JSON key %s", unsafe_yyjson_get_str(key)); } } void ffGenerateInitSystemJsonConfig(FFInitSystemOptions* options, yyjson_mut_doc* doc, yyjson_mut_val* module) { ffJsonConfigGenerateModuleArgsConfig(doc, module, &options->moduleArgs); } bool ffGenerateInitSystemJsonResult(FF_MAYBE_UNUSED FFInitSystemOptions* options, yyjson_mut_doc* doc, yyjson_mut_val* module) { bool success = false; FFInitSystemResult result = { .name = ffStrbufCreate(), .exe = ffStrbufCreate(), .version = ffStrbufCreate(), .pid = 1, }; const char* error = ffDetectInitSystem(&result); if (error) { yyjson_mut_obj_add_str(doc, module, "error", error); goto exit; } yyjson_mut_val* obj = yyjson_mut_obj_add_obj(doc, module, "result"); yyjson_mut_obj_add_strbuf(doc, obj, "name", &result.name); yyjson_mut_obj_add_strbuf(doc, obj, "exe", &result.exe); yyjson_mut_obj_add_strbuf(doc, obj, "version", &result.version); yyjson_mut_obj_add_uint(doc, obj, "pid", result.pid); success = true; exit: ffStrbufDestroy(&result.name); ffStrbufDestroy(&result.exe); ffStrbufDestroy(&result.version); return success; } void ffInitInitSystemOptions(FFInitSystemOptions* options) { ffOptionInitModuleArg(&options->moduleArgs, "󰿄"); } void ffDestroyInitSystemOptions(FFInitSystemOptions* options) { ffOptionDestroyModuleArg(&options->moduleArgs); } FFModuleBaseInfo ffInitSystemModuleInfo = { .name = FF_INITSYSTEM_MODULE_NAME, .description = "Print init system (pid 1) name and version", .initOptions = (void*) ffInitInitSystemOptions, .destroyOptions = (void*) ffDestroyInitSystemOptions, .parseJsonObject = (void*) ffParseInitSystemJsonObject, .printModule = (void*) ffPrintInitSystem, .generateJsonResult = (void*) ffGenerateInitSystemJsonResult, .generateJsonConfig = (void*) ffGenerateInitSystemJsonConfig, .formatArgs = FF_FORMAT_ARG_LIST(((FFModuleFormatArg[]) { {"Init system name", "name"}, {"Init system exe path", "exe"}, {"Init system version path", "version"}, {"Init system pid", "pid"}, })) }; ================================================ FILE: src/modules/initsystem/initsystem.h ================================================ #pragma once #include "option.h" #define FF_INITSYSTEM_MODULE_NAME "InitSystem" bool ffPrintInitSystem(FFInitSystemOptions* options); void ffInitInitSystemOptions(FFInitSystemOptions* options); void ffDestroyInitSystemOptions(FFInitSystemOptions* options); extern FFModuleBaseInfo ffInitSystemModuleInfo; ================================================ FILE: src/modules/initsystem/option.h ================================================ #pragma once #include "common/option.h" typedef struct FFInitSystemOptions { FFModuleArgs moduleArgs; } FFInitSystemOptions; static_assert(sizeof(FFInitSystemOptions) <= FF_OPTION_MAX_SIZE, "FFInitSystemOptions size exceeds maximum allowed size"); ================================================ FILE: src/modules/kernel/kernel.c ================================================ #include "common/printing.h" #include "common/jsonconfig.h" #include "common/size.h" #include "common/stringUtils.h" #include "modules/kernel/kernel.h" bool ffPrintKernel(FFKernelOptions* options) { const FFPlatformSysinfo* info = &instance.state.platform.sysinfo; if(options->moduleArgs.outputFormat.length == 0) { ffPrintLogoAndKey(FF_KERNEL_MODULE_NAME, 0, &options->moduleArgs, FF_PRINT_TYPE_DEFAULT); printf("%s %s\n", info->name.chars, info->release.chars); } else { FF_STRBUF_AUTO_DESTROY str = ffStrbufCreate(); ffSizeAppendNum(info->pageSize, &str); FF_PRINT_FORMAT_CHECKED(FF_KERNEL_MODULE_NAME, 0, &options->moduleArgs, FF_PRINT_TYPE_DEFAULT, ((FFformatarg[]){ FF_ARG(info->name, "sysname"), FF_ARG(info->release, "release"), FF_ARG(info->version, "version"), FF_ARG(info->architecture, "arch"), FF_ARG(str, "page-size"), })); } return true; } void ffParseKernelJsonObject(FFKernelOptions* options, yyjson_val* module) { yyjson_val *key, *val; size_t idx, max; yyjson_obj_foreach(module, idx, max, key, val) { if (ffJsonConfigParseModuleArgs(key, val, &options->moduleArgs)) continue; ffPrintError(FF_KERNEL_MODULE_NAME, 0, &options->moduleArgs, FF_PRINT_TYPE_DEFAULT, "Unknown JSON key %s", unsafe_yyjson_get_str(key)); } } void ffGenerateKernelJsonConfig(FFKernelOptions* options, yyjson_mut_doc* doc, yyjson_mut_val* module) { ffJsonConfigGenerateModuleArgsConfig(doc, module, &options->moduleArgs); } bool ffGenerateKernelJsonResult(FF_MAYBE_UNUSED FFKernelOptions* options, yyjson_mut_doc* doc, yyjson_mut_val* module) { const FFPlatformSysinfo* info = &instance.state.platform.sysinfo; yyjson_mut_val* obj = yyjson_mut_obj_add_obj(doc, module, "result"); yyjson_mut_obj_add_strbuf(doc, obj, "architecture", &info->architecture); yyjson_mut_obj_add_strbuf(doc, obj, "name", &info->name); yyjson_mut_obj_add_strbuf(doc, obj, "release", &info->release); yyjson_mut_obj_add_strbuf(doc, obj, "version", &info->version); yyjson_mut_obj_add_uint(doc, obj, "pageSize", info->pageSize); return true; } void ffInitKernelOptions(FFKernelOptions* options) { ffOptionInitModuleArg(&options->moduleArgs, ""); } void ffDestroyKernelOptions(FFKernelOptions* options) { ffOptionDestroyModuleArg(&options->moduleArgs); } FFModuleBaseInfo ffKernelModuleInfo = { .name = FF_KERNEL_MODULE_NAME, .description = "Print system kernel version", .initOptions = (void*) ffInitKernelOptions, .destroyOptions = (void*) ffDestroyKernelOptions, .parseJsonObject = (void*) ffParseKernelJsonObject, .printModule = (void*) ffPrintKernel, .generateJsonResult = (void*) ffGenerateKernelJsonResult, .generateJsonConfig = (void*) ffGenerateKernelJsonConfig, .formatArgs = FF_FORMAT_ARG_LIST(((FFModuleFormatArg[]) { {"Sysname", "sysname"}, {"Release", "release"}, {"Version", "version"}, {"Architecture", "arch"}, {"Display version", "display-version"}, {"Page size", "page-size"}, })) }; ================================================ FILE: src/modules/kernel/kernel.h ================================================ #pragma once #include "option.h" #define FF_KERNEL_MODULE_NAME "Kernel" bool ffPrintKernel(FFKernelOptions* options); void ffInitKernelOptions(FFKernelOptions* options); void ffDestroyKernelOptions(FFKernelOptions* options); extern FFModuleBaseInfo ffKernelModuleInfo; ================================================ FILE: src/modules/kernel/option.h ================================================ #pragma once #include "common/option.h" typedef struct FFKernelOptions { FFModuleArgs moduleArgs; } FFKernelOptions; static_assert(sizeof(FFKernelOptions) <= FF_OPTION_MAX_SIZE, "FFKernelOptions size exceeds maximum allowed size"); ================================================ FILE: src/modules/keyboard/keyboard.c ================================================ #include "common/percent.h" #include "common/printing.h" #include "common/jsonconfig.h" #include "common/stringUtils.h" #include "detection/keyboard/keyboard.h" #include "modules/keyboard/keyboard.h" static void printDevice(FFKeyboardOptions* options, const FFKeyboardDevice* device, uint8_t index) { if(options->moduleArgs.outputFormat.length == 0) { ffPrintLogoAndKey(FF_KEYBOARD_MODULE_NAME, index, &options->moduleArgs, FF_PRINT_TYPE_DEFAULT); ffStrbufPutTo(&device->name, stdout); } else { FF_PRINT_FORMAT_CHECKED(FF_KEYBOARD_MODULE_NAME, index, &options->moduleArgs, FF_PRINT_TYPE_DEFAULT, ((FFformatarg[]) { FF_ARG(device->name, "name"), FF_ARG(device->serial, "serial"), })); } } bool ffPrintKeyboard(FFKeyboardOptions* options) { FF_LIST_AUTO_DESTROY result = ffListCreate(sizeof(FFKeyboardDevice)); const char* error = ffDetectKeyboard(&result); if(error) { ffPrintError(FF_KEYBOARD_MODULE_NAME, 0, &options->moduleArgs, FF_PRINT_TYPE_DEFAULT, "%s", error); return false; } if(!result.length) { ffPrintError(FF_KEYBOARD_MODULE_NAME, 0, &options->moduleArgs, FF_PRINT_TYPE_DEFAULT, "No devices detected"); return false; } FF_LIST_AUTO_DESTROY filtered = ffListCreate(sizeof(FFKeyboardDevice*)); FF_LIST_FOR_EACH(FFKeyboardDevice, device, result) { bool ignored = false; FF_LIST_FOR_EACH(FFstrbuf, ignore, options->ignores) { if(ffStrbufStartsWithIgnCase(&device->name, ignore)) { ignored = true; break; } } if(!ignored) { FFKeyboardDevice** ptr = ffListAdd(&filtered); *ptr = device; } } bool ret = true; if(!filtered.length) { ffPrintError(FF_KEYBOARD_MODULE_NAME, 0, &options->moduleArgs, FF_PRINT_TYPE_DEFAULT, "All devices are ignored"); ret = false; } else { uint8_t index = 0; FF_LIST_FOR_EACH(FFKeyboardDevice*, pdevice, filtered) { FFKeyboardDevice* device = *pdevice; printDevice(options, device, filtered.length > 1 ? ++index : 0); ffStrbufDestroy(&device->serial); ffStrbufDestroy(&device->name); } } return ret; } void ffParseKeyboardJsonObject(FFKeyboardOptions* options, yyjson_val* module) { yyjson_val *key, *val; size_t idx, max; yyjson_obj_foreach(module, idx, max, key, val) { if (ffJsonConfigParseModuleArgs(key, val, &options->moduleArgs)) continue; if (unsafe_yyjson_equals_str(key, "ignores")) { yyjson_val *elem; size_t eidx, emax; yyjson_arr_foreach(val, eidx, emax, elem) { if (yyjson_is_str(elem)) { FFstrbuf* strbuf = ffListAdd(&options->ignores); ffStrbufInitJsonVal(strbuf, elem); } } continue; } ffPrintError(FF_KEYBOARD_MODULE_NAME, 0, &options->moduleArgs, FF_PRINT_TYPE_DEFAULT, "Unknown JSON key %s", unsafe_yyjson_get_str(key)); } } void ffGenerateKeyboardJsonConfig(FFKeyboardOptions* options, yyjson_mut_doc* doc, yyjson_mut_val* module) { ffJsonConfigGenerateModuleArgsConfig(doc, module, &options->moduleArgs); if (options->ignores.length > 0) { yyjson_mut_val* ignores = yyjson_mut_obj_add_arr(doc, module, "ignores"); FF_LIST_FOR_EACH(FFstrbuf, strbuf, options->ignores) yyjson_mut_arr_append(ignores, yyjson_mut_strncpy(doc, strbuf->chars, strbuf->length)); } } bool ffGenerateKeyboardJsonResult(FF_MAYBE_UNUSED FFKeyboardOptions* options, yyjson_mut_doc* doc, yyjson_mut_val* module) { FF_LIST_AUTO_DESTROY result = ffListCreate(sizeof(FFKeyboardDevice)); const char* error = ffDetectKeyboard(&result); if(error) { yyjson_mut_obj_add_str(doc, module, "error", error); return false; } yyjson_mut_val* arr = yyjson_mut_obj_add_arr(doc, module, "result"); FF_LIST_FOR_EACH(FFKeyboardDevice, device, result) { yyjson_mut_val* obj = yyjson_mut_arr_add_obj(doc, arr); yyjson_mut_obj_add_strbuf(doc, obj, "serial", &device->serial); yyjson_mut_obj_add_strbuf(doc, obj, "name", &device->name); bool ignored = false; FF_LIST_FOR_EACH(FFstrbuf, ignore, options->ignores) { if(ffStrbufStartsWithIgnCase(&device->name, ignore)) { ignored = true; break; } } yyjson_mut_obj_add_bool(doc, obj, "ignored", ignored); } FF_LIST_FOR_EACH(FFKeyboardDevice, device, result) { ffStrbufDestroy(&device->serial); ffStrbufDestroy(&device->name); } return true; } void ffInitKeyboardOptions(FFKeyboardOptions* options) { ffOptionInitModuleArg(&options->moduleArgs, ""); ffListInit(&options->ignores, sizeof(FFstrbuf)); } void ffDestroyKeyboardOptions(FFKeyboardOptions* options) { ffOptionDestroyModuleArg(&options->moduleArgs); FF_LIST_FOR_EACH(FFstrbuf, str, options->ignores) ffStrbufDestroy(str); ffListDestroy(&options->ignores); } FFModuleBaseInfo ffKeyboardModuleInfo = { .name = FF_KEYBOARD_MODULE_NAME, .description = "List (connected) keyboards", .initOptions = (void*) ffInitKeyboardOptions, .destroyOptions = (void*) ffDestroyKeyboardOptions, .parseJsonObject = (void*) ffParseKeyboardJsonObject, .printModule = (void*) ffPrintKeyboard, .generateJsonResult = (void*) ffGenerateKeyboardJsonResult, .generateJsonConfig = (void*) ffGenerateKeyboardJsonConfig, .formatArgs = FF_FORMAT_ARG_LIST(((FFModuleFormatArg[]) { {"Name", "name"}, {"Serial number", "serial"}, })) }; ================================================ FILE: src/modules/keyboard/keyboard.h ================================================ #pragma once #include "option.h" #define FF_KEYBOARD_MODULE_NAME "Keyboard" bool ffPrintKeyboard(FFKeyboardOptions* options); void ffInitKeyboardOptions(FFKeyboardOptions* options); void ffDestroyKeyboardOptions(FFKeyboardOptions* options); extern FFModuleBaseInfo ffKeyboardModuleInfo; ================================================ FILE: src/modules/keyboard/option.h ================================================ #pragma once #include "common/option.h" #include "common/FFlist.h" typedef struct FFKeyboardOptions { FFModuleArgs moduleArgs; FFlist ignores; // List of FFstrbuf } FFKeyboardOptions; static_assert(sizeof(FFKeyboardOptions) <= FF_OPTION_MAX_SIZE, "FFKeyboardOptions size exceeds maximum allowed size"); ================================================ FILE: src/modules/lm/lm.c ================================================ #include "common/printing.h" #include "common/jsonconfig.h" #include "common/stringUtils.h" #include "detection/lm/lm.h" #include "modules/lm/lm.h" bool ffPrintLM(FFLMOptions* options) { bool success = false; FFLMResult result; ffStrbufInit(&result.service); ffStrbufInit(&result.type); ffStrbufInit(&result.version); const char* error = ffDetectLM(&result); if(error) { ffPrintError(FF_LM_MODULE_NAME, 0, &options->moduleArgs, FF_PRINT_TYPE_DEFAULT, "%s", error); goto exit; } if(result.service.length == 0) { ffPrintError(FF_LM_MODULE_NAME, 0, &options->moduleArgs, FF_PRINT_TYPE_DEFAULT, "No LM service found"); goto exit; } if(options->moduleArgs.outputFormat.length == 0) { ffPrintLogoAndKey(FF_LM_MODULE_NAME, 0, &options->moduleArgs, FF_PRINT_TYPE_DEFAULT); ffStrbufWriteTo(&result.service, stdout); if(result.version.length) printf(" %s", result.version.chars); if(result.type.length) printf(" (%s)", result.type.chars); putchar('\n'); } else { FF_PRINT_FORMAT_CHECKED(FF_LM_MODULE_NAME, 0, &options->moduleArgs, FF_PRINT_TYPE_DEFAULT, ((FFformatarg[]){ FF_ARG(result.service, "service"), FF_ARG(result.type, "type"), FF_ARG(result.version, "version"), })); } success = true; exit: ffStrbufDestroy(&result.service); ffStrbufDestroy(&result.type); ffStrbufDestroy(&result.version); return success; } void ffParseLMJsonObject(FFLMOptions* options, yyjson_val* module) { yyjson_val *key, *val; size_t idx, max; yyjson_obj_foreach(module, idx, max, key, val) { if (ffJsonConfigParseModuleArgs(key, val, &options->moduleArgs)) continue; ffPrintError(FF_LM_MODULE_NAME, 0, &options->moduleArgs, FF_PRINT_TYPE_DEFAULT, "Unknown JSON key %s", unsafe_yyjson_get_str(key)); } } void ffGenerateLMJsonConfig(FFLMOptions* options, yyjson_mut_doc* doc, yyjson_mut_val* module) { ffJsonConfigGenerateModuleArgsConfig(doc, module, &options->moduleArgs); } bool ffGenerateLMJsonResult(FF_MAYBE_UNUSED FFLMOptions* options, yyjson_mut_doc* doc, yyjson_mut_val* module) { bool success = false; FFLMResult result; ffStrbufInit(&result.service); ffStrbufInit(&result.type); ffStrbufInit(&result.version); const char* error = ffDetectLM(&result); if(error) { yyjson_mut_obj_add_str(doc, module, "error", error); goto exit; } if(result.service.length == 0) { yyjson_mut_obj_add_str(doc, module, "error", "No LM service found"); goto exit; } yyjson_mut_val* obj = yyjson_mut_obj_add_obj(doc, module, "result"); yyjson_mut_obj_add_strbuf(doc, obj, "service", &result.service); yyjson_mut_obj_add_strbuf(doc, obj, "type", &result.type); yyjson_mut_obj_add_strbuf(doc, obj, "version", &result.version); success = true; exit: ffStrbufDestroy(&result.service); ffStrbufDestroy(&result.type); ffStrbufDestroy(&result.version); return success; } void ffInitLMOptions(FFLMOptions* options) { ffOptionInitModuleArg(&options->moduleArgs, "󰧨"); } void ffDestroyLMOptions(FFLMOptions* options) { ffOptionDestroyModuleArg(&options->moduleArgs); } FFModuleBaseInfo ffLMModuleInfo = { .name = FF_LM_MODULE_NAME, .description = "Print login manager (desktop manager) name and version", .initOptions = (void*) ffInitLMOptions, .destroyOptions = (void*) ffDestroyLMOptions, .parseJsonObject = (void*) ffParseLMJsonObject, .printModule = (void*) ffPrintLM, .generateJsonResult = (void*) ffGenerateLMJsonResult, .generateJsonConfig = (void*) ffGenerateLMJsonConfig, .formatArgs = FF_FORMAT_ARG_LIST(((FFModuleFormatArg[]) { {"LM service", "service"}, {"LM type", "type"}, {"LM version", "version"}, })) }; ================================================ FILE: src/modules/lm/lm.h ================================================ #pragma once #include "option.h" #define FF_LM_MODULE_NAME "LM" bool ffPrintLM(FFLMOptions* options); void ffInitLMOptions(FFLMOptions* options); void ffDestroyLMOptions(FFLMOptions* options); extern FFModuleBaseInfo ffLMModuleInfo; ================================================ FILE: src/modules/lm/option.h ================================================ #pragma once #include "common/option.h" typedef struct FFLMOptions { FFModuleArgs moduleArgs; } FFLMOptions; static_assert(sizeof(FFLMOptions) <= FF_OPTION_MAX_SIZE, "FFLMOptions size exceeds maximum allowed size"); ================================================ FILE: src/modules/loadavg/loadavg.c ================================================ #include "common/printing.h" #include "common/jsonconfig.h" #include "common/percent.h" #include "common/stringUtils.h" #include "detection/loadavg/loadavg.h" #include "detection/cpu/cpu.h" #include "modules/loadavg/loadavg.h" bool ffPrintLoadavg(FFLoadavgOptions* options) { double result[3] = { 0.0 / 0.0, 0.0 / 0.0, 0.0 / 0.0 }; const char* error = ffDetectLoadavg(result); if(error) { ffPrintError(FF_LOADAVG_MODULE_NAME, 0, &options->moduleArgs, FF_PRINT_TYPE_DEFAULT, "%s", error); return false; } if(options->moduleArgs.outputFormat.length == 0) { if (options->compact) { ffPrintLogoAndKey(FF_LOADAVG_MODULE_NAME, 0, &options->moduleArgs, FF_PRINT_TYPE_DEFAULT); printf("%.*f, %.*f, %.*f\n", options->ndigits, result[0], options->ndigits, result[1], options->ndigits, result[2]); } else { FFCPUResult cpu = { .temperature = FF_CPU_TEMP_UNSET, .frequencyMax = 0, .frequencyBase = 0, .name = ffStrbufCreate(), .vendor = ffStrbufCreate(), }; ffDetectCPU(&(FFCPUOptions) {}, &cpu); FF_STRBUF_AUTO_DESTROY buffer = ffStrbufCreate(); for (uint32_t index = 0; index < 3; index++) { uint32_t duration = index == 0 ? 1 : index == 1 ? 5 : 15; if (options->moduleArgs.key.length == 0) { ffStrbufSetF(&buffer, "%s (%d min)", FF_LOADAVG_MODULE_NAME, duration); } else { ffStrbufClear(&buffer); FF_PARSE_FORMAT_STRING_CHECKED(&buffer, &options->moduleArgs.key, ((FFformatarg[]) { FF_ARG(index, "index"), FF_ARG(duration, "duration"), FF_ARG(options->moduleArgs.keyIcon, "icon"), })); } ffPrintLogoAndKey(buffer.chars, 0, &options->moduleArgs, FF_PRINT_TYPE_NO_CUSTOM_KEY); ffStrbufClear(&buffer); double percent = result[index] * 100 / cpu.coresOnline; FFPercentageTypeFlags percentType = options->percent.type == 0 ? instance.config.display.percentType : options->percent.type; if (percentType & FF_PERCENTAGE_TYPE_BAR_BIT) ffPercentAppendBar(&buffer, percent, options->percent, &options->moduleArgs); if (!(percentType & FF_PERCENTAGE_TYPE_HIDE_OTHERS_BIT)) { if (buffer.length > 0) ffStrbufAppendC(&buffer, ' '); ffStrbufAppendF(&buffer, "%.*f", options->ndigits, result[index]); } if (percentType & FF_PERCENTAGE_TYPE_NUM_BIT) { if (buffer.length > 0) ffStrbufAppendC(&buffer, ' '); ffPercentAppendNum(&buffer, percent, options->percent, buffer.length > 0, &options->moduleArgs); } ffStrbufPutTo(&buffer, stdout); } } } else { FF_PRINT_FORMAT_CHECKED(FF_LOADAVG_MODULE_NAME, 0, &options->moduleArgs, FF_PRINT_TYPE_DEFAULT, ((FFformatarg[]){ FF_ARG(result[0], "loadavg1"), FF_ARG(result[1], "loadavg2"), FF_ARG(result[2], "loadavg3"), })); } return true; } void ffParseLoadavgJsonObject(FFLoadavgOptions* options, yyjson_val* module) { yyjson_val *key, *val; size_t idx, max; yyjson_obj_foreach(module, idx, max, key, val) { if (ffJsonConfigParseModuleArgs(key, val, &options->moduleArgs)) continue; if (unsafe_yyjson_equals_str(key, "ndigits")) { options->ndigits = (uint8_t) yyjson_get_uint(val); continue; } if (unsafe_yyjson_equals_str(key, "compact")) { options->compact = yyjson_get_bool(val); continue; } if (ffPercentParseJsonObject(key, val, &options->percent)) continue; ffPrintError(FF_LOADAVG_MODULE_NAME, 0, &options->moduleArgs, FF_PRINT_TYPE_DEFAULT, "Unknown JSON key %s", unsafe_yyjson_get_str(key)); } } void ffGenerateLoadavgJsonConfig(FFLoadavgOptions* options, yyjson_mut_doc* doc, yyjson_mut_val* module) { ffJsonConfigGenerateModuleArgsConfig(doc, module, &options->moduleArgs); yyjson_mut_obj_add_uint(doc, module, "ndigits", options->ndigits); yyjson_mut_obj_add_bool(doc, module, "compact", options->compact); ffPercentGenerateJsonConfig(doc, module, options->percent); } bool ffGenerateLoadavgJsonResult(FF_MAYBE_UNUSED FFLoadavgOptions* options, yyjson_mut_doc* doc, yyjson_mut_val* module) { double result[3] = { 0.0 / 0.0, 0.0 / 0.0, 0.0 / 0.0 }; const char* error = ffDetectLoadavg(result); if(error) { yyjson_mut_obj_add_str(doc, module, "error", error); return false; } yyjson_mut_val* arr = yyjson_mut_obj_add_arr(doc, module, "result"); for (size_t i = 0; i < 3; i++) yyjson_mut_arr_add_real(doc, arr, result[i]); return true; } void ffInitLoadavgOptions(FFLoadavgOptions* options) { ffOptionInitModuleArg(&options->moduleArgs, ""); options->percent = (FFPercentageModuleConfig) { 50, 80, 0 }; options->ndigits = 2; options->compact = true; } void ffDestroyLoadavgOptions(FFLoadavgOptions* options) { ffOptionDestroyModuleArg(&options->moduleArgs); } FFModuleBaseInfo ffLoadavgModuleInfo = { .name = FF_LOADAVG_MODULE_NAME, .description = "Print system load averages", .initOptions = (void*) ffInitLoadavgOptions, .destroyOptions = (void*) ffDestroyLoadavgOptions, .parseJsonObject = (void*) ffParseLoadavgJsonObject, .printModule = (void*) ffPrintLoadavg, .generateJsonResult = (void*) ffGenerateLoadavgJsonResult, .generateJsonConfig = (void*) ffGenerateLoadavgJsonConfig, .formatArgs = FF_FORMAT_ARG_LIST(((FFModuleFormatArg[]) { {"Load average over 1min", "loadavg1"}, {"Load average over 5min", "loadavg2"}, {"Load average over 15min", "loadavg3"}, })) }; ================================================ FILE: src/modules/loadavg/loadavg.h ================================================ #pragma once #include "option.h" #define FF_LOADAVG_MODULE_NAME "Loadavg" bool ffPrintLoadavg(FFLoadavgOptions* options); void ffInitLoadavgOptions(FFLoadavgOptions* options); void ffDestroyLoadavgOptions(FFLoadavgOptions* options); extern FFModuleBaseInfo ffLoadavgModuleInfo; ================================================ FILE: src/modules/loadavg/option.h ================================================ #pragma once #include "common/option.h" typedef struct FFLoadavgOptions { FFModuleArgs moduleArgs; FFPercentageModuleConfig percent; uint8_t ndigits; bool compact; } FFLoadavgOptions; static_assert(sizeof(FFLoadavgOptions) <= FF_OPTION_MAX_SIZE, "FFLoadavgOptions size exceeds maximum allowed size"); ================================================ FILE: src/modules/locale/locale.c ================================================ #include "common/printing.h" #include "common/jsonconfig.h" #include "detection/locale/locale.h" #include "modules/locale/locale.h" bool ffPrintLocale(FFLocaleOptions* options) { FF_STRBUF_AUTO_DESTROY locale = ffStrbufCreate(); const char* error = ffDetectLocale(&locale); if(error) { ffPrintError(FF_LOCALE_MODULE_NAME, 0, &options->moduleArgs, FF_PRINT_TYPE_DEFAULT, "%s", error); return false; } if(options->moduleArgs.outputFormat.length == 0) { ffPrintLogoAndKey(FF_LOCALE_MODULE_NAME, 0, &options->moduleArgs, FF_PRINT_TYPE_DEFAULT); ffStrbufPutTo(&locale, stdout); } else { FF_PRINT_FORMAT_CHECKED(FF_LOCALE_MODULE_NAME, 0, &options->moduleArgs, FF_PRINT_TYPE_DEFAULT, ((FFformatarg[]){ FF_ARG(locale, "result") })); } return true; } void ffParseLocaleJsonObject(FFLocaleOptions* options, yyjson_val* module) { yyjson_val *key, *val; size_t idx, max; yyjson_obj_foreach(module, idx, max, key, val) { if (ffJsonConfigParseModuleArgs(key, val, &options->moduleArgs)) continue; ffPrintError(FF_LOCALE_MODULE_NAME, 0, &options->moduleArgs, FF_PRINT_TYPE_DEFAULT, "Unknown JSON key %s", unsafe_yyjson_get_str(key)); } } void ffGenerateLocaleJsonConfig(FFLocaleOptions* options, yyjson_mut_doc* doc, yyjson_mut_val* module) { ffJsonConfigGenerateModuleArgsConfig(doc, module, &options->moduleArgs); } bool ffGenerateLocaleJsonResult(FF_MAYBE_UNUSED FFLocaleOptions* options, yyjson_mut_doc* doc, yyjson_mut_val* module) { FF_STRBUF_AUTO_DESTROY locale = ffStrbufCreate(); const char* error = ffDetectLocale(&locale); if(error) { yyjson_mut_obj_add_str(doc, module, "error", error); return false; } if(locale.length == 0) { yyjson_mut_obj_add_str(doc, module, "error", "No locale found"); return false; } yyjson_mut_obj_add_strbuf(doc, module, "result", &locale); return true; } void ffInitLocaleOptions(FFLocaleOptions* options) { ffOptionInitModuleArg(&options->moduleArgs, ""); } void ffDestroyLocaleOptions(FFLocaleOptions* options) { ffOptionDestroyModuleArg(&options->moduleArgs); } FFModuleBaseInfo ffLocaleModuleInfo = { .name = FF_LOCALE_MODULE_NAME, .description = "Print system locale name", .initOptions = (void*) ffInitLocaleOptions, .destroyOptions = (void*) ffDestroyLocaleOptions, .parseJsonObject = (void*) ffParseLocaleJsonObject, .printModule = (void*) ffPrintLocale, .generateJsonResult = (void*) ffGenerateLocaleJsonResult, .generateJsonConfig = (void*) ffGenerateLocaleJsonConfig, .formatArgs = FF_FORMAT_ARG_LIST(((FFModuleFormatArg[]) { {"Locale code", "result"}, })) }; ================================================ FILE: src/modules/locale/locale.h ================================================ #pragma once #include "option.h" #define FF_LOCALE_MODULE_NAME "Locale" bool ffPrintLocale(FFLocaleOptions* options); void ffInitLocaleOptions(FFLocaleOptions* options); void ffDestroyLocaleOptions(FFLocaleOptions* options); extern FFModuleBaseInfo ffLocaleModuleInfo; ================================================ FILE: src/modules/locale/option.h ================================================ #pragma once #include "common/option.h" typedef struct FFLocaleOptions { FFModuleArgs moduleArgs; } FFLocaleOptions; static_assert(sizeof(FFLocaleOptions) <= FF_OPTION_MAX_SIZE, "FFLocaleOptions size exceeds maximum allowed size"); ================================================ FILE: src/modules/localip/localip.c ================================================ #include "common/printing.h" #include "common/jsonconfig.h" #include "common/stringUtils.h" #include "detection/localip/localip.h" #include "modules/localip/localip.h" #define FF_LOCALIP_DISPLAY_NAME "Local IP" #pragma GCC diagnostic ignored "-Wsign-conversion" static int sortIps(const FFLocalIpResult* left, const FFLocalIpResult* right) { return ffStrbufComp(&left->name, &right->name); } static void formatKey(const FFLocalIpOptions* options, FFLocalIpResult* ip, uint32_t index, FFstrbuf* key) { if(options->moduleArgs.key.length == 0) { if(!ip->name.length) ffStrbufSetF(&ip->name, "unknown %u", (unsigned) index); ffStrbufSetF(key, FF_LOCALIP_DISPLAY_NAME " (%s)", ip->name.chars); } else { ffStrbufClear(key); FF_PARSE_FORMAT_STRING_CHECKED(key, &options->moduleArgs.key, ((FFformatarg[]) { FF_ARG(index, "index"), FF_ARG(ip->name, "ifname"), FF_ARG(ip->mac, "mac"), FF_ARG(options->moduleArgs.keyIcon, "icon"), })); } } static void appendSpeed(FFLocalIpResult* ip, FFstrbuf* strbuf) { if (ip->speed >= 1000000) { ffStrbufAppendDouble(strbuf, ip->speed / 1e6, instance.config.display.fractionNdigits, instance.config.display.fractionTrailingZeros == FF_FRACTION_TRAILING_ZEROS_TYPE_ALWAYS); ffStrbufAppendS(strbuf, " Tbps"); } else if (ip->speed >= 1000) { ffStrbufAppendDouble(strbuf, ip->speed / 1e3, instance.config.display.fractionNdigits, instance.config.display.fractionTrailingZeros == FF_FRACTION_TRAILING_ZEROS_TYPE_ALWAYS); ffStrbufAppendS(strbuf, " Gbps"); } else ffStrbufAppendF(strbuf, "%u Mbps", (unsigned) ip->speed); } static void printIp(FFLocalIpResult* ip, bool markDefaultRoute, FFstrbuf* buffer) { if (ip->ipv4.length) { ffStrbufAppend(buffer, &ip->ipv4); } if (ip->ipv6.length) { if (buffer->length) ffStrbufAppendC(buffer, ' '); ffStrbufAppend(buffer, &ip->ipv6); } if (ip->mac.length) { if (buffer->length) ffStrbufAppendF(buffer, " (%s)", ip->mac.chars); else ffStrbufAppend(buffer, &ip->mac); } if (ip->mtu > 0 || ip->speed > 0) { bool flag = buffer->length > 0; if (flag) ffStrbufAppendS(buffer, " ["); if (ip->speed > 0) { if (ip->mtu > 0) ffStrbufAppendS(buffer, "Speed "); appendSpeed(ip, buffer); if (ip->mtu > 0) ffStrbufAppendS(buffer, " / MTU "); } if (ip->mtu > 0) ffStrbufAppendF(buffer, "%u", (unsigned) ip->mtu); if (flag) ffStrbufAppendC(buffer, ']'); } if (ip->flags.length) { bool flag = buffer->length > 0; if (flag) ffStrbufAppendS(buffer, " <"); ffStrbufAppend(buffer, &ip->flags); if (flag) ffStrbufAppendC(buffer, '>'); } if (markDefaultRoute && ip->defaultRoute) ffStrbufAppendS(buffer, " *"); } bool ffPrintLocalIp(FFLocalIpOptions* options) { FF_LIST_AUTO_DESTROY results = ffListCreate(sizeof(FFLocalIpResult)); const char* error = ffDetectLocalIps(options, &results); if(error) { ffPrintError(FF_LOCALIP_DISPLAY_NAME, 0, &options->moduleArgs, FF_PRINT_TYPE_DEFAULT, "%s", error); return false; } if(results.length == 0) { ffPrintError(FF_LOCALIP_DISPLAY_NAME, 0, &options->moduleArgs, FF_PRINT_TYPE_DEFAULT, "Failed to detect any IPs"); return false; } ffListSort(&results, (const void*) sortIps); FF_STRBUF_AUTO_DESTROY buffer = ffStrbufCreate(); if (options->showType & FF_LOCALIP_TYPE_COMPACT_BIT) { ffPrintLogoAndKey(FF_LOCALIP_DISPLAY_NAME, 0, &options->moduleArgs, FF_PRINT_TYPE_DEFAULT); FF_LIST_FOR_EACH(FFLocalIpResult, ip, results) { if (buffer.length) ffStrbufAppendS(&buffer, " - "); printIp(ip, false, &buffer); } ffStrbufPutTo(&buffer, stdout); ffStrbufClear(&buffer); } else { FF_STRBUF_AUTO_DESTROY key = ffStrbufCreate(); uint32_t index = 0; FF_LIST_FOR_EACH(FFLocalIpResult, ip, results) { formatKey(options, ip, results.length == 1 ? 0 : index + 1, &key); if(options->moduleArgs.outputFormat.length == 0) { ffPrintLogoAndKey(key.chars, 0, &options->moduleArgs, FF_PRINT_TYPE_NO_CUSTOM_KEY); printIp(ip, !(options->showType & FF_LOCALIP_TYPE_DEFAULT_ROUTE_ONLY_BIT), &buffer); ffStrbufPutTo(&buffer, stdout); } else { if (ip->speed > 0) appendSpeed(ip, &buffer); FF_PRINT_FORMAT_CHECKED(key.chars, 0, &options->moduleArgs, FF_PRINT_TYPE_NO_CUSTOM_KEY, ((FFformatarg[]){ FF_ARG(ip->ipv4, "ipv4"), FF_ARG(ip->ipv6, "ipv6"), FF_ARG(ip->mac, "mac"), FF_ARG(ip->name, "ifname"), FF_ARG(ip->defaultRoute, "is-default-route"), FF_ARG(ip->mtu, "mtu"), FF_ARG(buffer, "speed"), FF_ARG(ip->flags, "flags"), })); } ++index; ffStrbufClear(&buffer); } } FF_LIST_FOR_EACH(FFLocalIpResult, ip, results) { ffStrbufDestroy(&ip->name); ffStrbufDestroy(&ip->ipv4); ffStrbufDestroy(&ip->ipv6); ffStrbufDestroy(&ip->mac); ffStrbufDestroy(&ip->flags); } return true; } void ffParseLocalIpJsonObject(FFLocalIpOptions* options, yyjson_val* module) { yyjson_val *key, *val; size_t idx, max; yyjson_obj_foreach(module, idx, max, key, val) { if (ffJsonConfigParseModuleArgs(key, val, &options->moduleArgs)) continue; if (unsafe_yyjson_equals_str(key, "showIpv4")) { if (yyjson_get_bool(val)) options->showType |= FF_LOCALIP_TYPE_IPV4_BIT; else options->showType &= ~FF_LOCALIP_TYPE_IPV4_BIT; continue; } if (unsafe_yyjson_equals_str(key, "showIpv6")) { if (yyjson_is_bool(val)) { options->ipv6Type = FF_LOCALIP_IPV6_TYPE_AUTO; if (unsafe_yyjson_get_bool(val)) options->showType |= FF_LOCALIP_TYPE_IPV6_BIT; else options->showType &= ~FF_LOCALIP_TYPE_IPV6_BIT; } else if (yyjson_is_str(val)) { int value; const char* error = ffJsonConfigParseEnum(val, &value, (FFKeyValuePair[]) { { "auto", FF_LOCALIP_IPV6_TYPE_AUTO }, { "gua", FF_LOCALIP_IPV6_TYPE_GUA_BIT }, { "ula", FF_LOCALIP_IPV6_TYPE_ULA_BIT }, { "lla", FF_LOCALIP_IPV6_TYPE_LLA_BIT }, { "unknown", FF_LOCALIP_IPV6_TYPE_UNKNOWN_BIT }, {}, }); if (error) ffPrintError(FF_LOCALIP_MODULE_NAME, 0, &options->moduleArgs, FF_PRINT_TYPE_DEFAULT, "Invalid %s value: %s", unsafe_yyjson_get_str(key), error); else { options->showType |= FF_LOCALIP_TYPE_IPV6_BIT; options->ipv6Type = (FFLocalIpIpv6Type) value; } } continue; } if (unsafe_yyjson_equals_str(key, "showMac")) { if (yyjson_get_bool(val)) options->showType |= FF_LOCALIP_TYPE_MAC_BIT; else options->showType &= ~FF_LOCALIP_TYPE_MAC_BIT; continue; } if (unsafe_yyjson_equals_str(key, "showLoop")) { if (yyjson_get_bool(val)) options->showType |= FF_LOCALIP_TYPE_LOOP_BIT; else options->showType &= ~FF_LOCALIP_TYPE_LOOP_BIT; continue; } if (unsafe_yyjson_equals_str(key, "showPrefixLen")) { if (yyjson_get_bool(val)) options->showType |= FF_LOCALIP_TYPE_PREFIX_LEN_BIT; else options->showType &= ~FF_LOCALIP_TYPE_PREFIX_LEN_BIT; continue; } if (unsafe_yyjson_equals_str(key, "showMtu")) { if (yyjson_get_bool(val)) options->showType |= FF_LOCALIP_TYPE_MTU_BIT; else options->showType &= ~FF_LOCALIP_TYPE_MTU_BIT; continue; } if (unsafe_yyjson_equals_str(key, "showSpeed")) { if (yyjson_get_bool(val)) options->showType |= FF_LOCALIP_TYPE_SPEED_BIT; else options->showType &= ~FF_LOCALIP_TYPE_SPEED_BIT; continue; } if (unsafe_yyjson_equals_str(key, "showFlags")) { if (yyjson_get_bool(val)) options->showType |= FF_LOCALIP_TYPE_FLAGS_BIT; else options->showType &= ~FF_LOCALIP_TYPE_FLAGS_BIT; continue; } if (unsafe_yyjson_equals_str(key, "compact")) { if (yyjson_get_bool(val)) options->showType |= FF_LOCALIP_TYPE_COMPACT_BIT; else options->showType &= ~FF_LOCALIP_TYPE_COMPACT_BIT; continue; } if (unsafe_yyjson_equals_str(key, "defaultRouteOnly")) { if (yyjson_get_bool(val)) options->showType |= FF_LOCALIP_TYPE_DEFAULT_ROUTE_ONLY_BIT; else options->showType &= ~FF_LOCALIP_TYPE_DEFAULT_ROUTE_ONLY_BIT; continue; } if (unsafe_yyjson_equals_str(key, "showAllIps")) { if (yyjson_get_bool(val)) options->showType |= FF_LOCALIP_TYPE_ALL_IPS_BIT; else options->showType &= ~FF_LOCALIP_TYPE_ALL_IPS_BIT; continue; } if (unsafe_yyjson_equals_str(key, "namePrefix")) { ffStrbufSetJsonVal(&options->namePrefix, val); continue; } ffPrintError(FF_LOCALIP_MODULE_NAME, 0, &options->moduleArgs, FF_PRINT_TYPE_DEFAULT, "Unknown JSON key %s", unsafe_yyjson_get_str(key)); } } void ffGenerateLocalIpJsonConfig(FFLocalIpOptions* options, yyjson_mut_doc* doc, yyjson_mut_val* module) { ffJsonConfigGenerateModuleArgsConfig(doc, module, &options->moduleArgs); yyjson_mut_obj_add_bool(doc, module, "showIpv4", !!(options->showType & FF_LOCALIP_TYPE_IPV4_BIT)); if (options->ipv6Type == FF_LOCALIP_IPV6_TYPE_AUTO) yyjson_mut_obj_add_bool(doc, module, "showIpv6", !!(options->showType & FF_LOCALIP_TYPE_IPV6_BIT)); else { const char* str = NULL; switch (options->ipv6Type) { case FF_LOCALIP_IPV6_TYPE_GUA_BIT: str = "gua"; break; case FF_LOCALIP_IPV6_TYPE_ULA_BIT: str = "ula"; break; case FF_LOCALIP_IPV6_TYPE_LLA_BIT: str = "lla"; break; case FF_LOCALIP_IPV6_TYPE_UNKNOWN_BIT: str = "unknown"; break; default: str = "auto"; break; } yyjson_mut_obj_add_str(doc, module, "showIpv6", str); } yyjson_mut_obj_add_bool(doc, module, "showMac", !!(options->showType & FF_LOCALIP_TYPE_MAC_BIT)); yyjson_mut_obj_add_bool(doc, module, "showLoop", !!(options->showType & FF_LOCALIP_TYPE_LOOP_BIT)); yyjson_mut_obj_add_bool(doc, module, "showPrefixLen", !!(options->showType & FF_LOCALIP_TYPE_PREFIX_LEN_BIT)); yyjson_mut_obj_add_bool(doc, module, "showMtu", !!(options->showType & FF_LOCALIP_TYPE_MTU_BIT)); yyjson_mut_obj_add_bool(doc, module, "showSpeed", !!(options->showType & FF_LOCALIP_TYPE_SPEED_BIT)); yyjson_mut_obj_add_bool(doc, module, "showFlags", !!(options->showType & FF_LOCALIP_TYPE_FLAGS_BIT)); yyjson_mut_obj_add_bool(doc, module, "compact", !!(options->showType & FF_LOCALIP_TYPE_COMPACT_BIT)); yyjson_mut_obj_add_bool(doc, module, "defaultRouteOnly", !!(options->showType & FF_LOCALIP_TYPE_DEFAULT_ROUTE_ONLY_BIT)); yyjson_mut_obj_add_bool(doc, module, "showAllIps", !!(options->showType & FF_LOCALIP_TYPE_ALL_IPS_BIT)); yyjson_mut_obj_add_strbuf(doc, module, "namePrefix", &options->namePrefix); } bool ffGenerateLocalIpJsonResult(FF_MAYBE_UNUSED FFLocalIpOptions* options, yyjson_mut_doc* doc, yyjson_mut_val* module) { FF_LIST_AUTO_DESTROY results = ffListCreate(sizeof(FFLocalIpResult)); const char* error = ffDetectLocalIps(options, &results); if(error) { yyjson_mut_obj_add_str(doc, module, "error", error); return false; } yyjson_mut_val* arr = yyjson_mut_obj_add_arr(doc, module, "result"); FF_LIST_FOR_EACH(FFLocalIpResult, ip, results) { yyjson_mut_val* obj = yyjson_mut_arr_add_obj(doc, arr); yyjson_mut_obj_add_strbuf(doc, obj, "name", &ip->name); if (options->showType & (FF_LOCALIP_TYPE_IPV4_BIT | FF_LOCALIP_TYPE_IPV6_BIT)) { yyjson_mut_val* defaultRoute = yyjson_mut_obj_add_obj(doc, obj, "defaultRoute"); if (options->showType & FF_LOCALIP_TYPE_IPV4_BIT) yyjson_mut_obj_add_bool(doc, defaultRoute, "ipv4", !!(ip->defaultRoute & FF_LOCALIP_TYPE_IPV4_BIT)); if (options->showType & FF_LOCALIP_TYPE_IPV6_BIT) yyjson_mut_obj_add_bool(doc, defaultRoute, "ipv6", !!(ip->defaultRoute & FF_LOCALIP_TYPE_IPV6_BIT)); } if (options->showType & FF_LOCALIP_TYPE_IPV4_BIT) yyjson_mut_obj_add_strbuf(doc, obj, "ipv4", &ip->ipv4); if (options->showType & FF_LOCALIP_TYPE_IPV6_BIT) yyjson_mut_obj_add_strbuf(doc, obj, "ipv6", &ip->ipv6); if (options->showType & FF_LOCALIP_TYPE_MAC_BIT) yyjson_mut_obj_add_strbuf(doc, obj, "mac", &ip->mac); if (options->showType & FF_LOCALIP_TYPE_MTU_BIT) yyjson_mut_obj_add_int(doc, obj, "mtu", ip->mtu); if (options->showType & FF_LOCALIP_TYPE_SPEED_BIT) yyjson_mut_obj_add_int(doc, obj, "speed", ip->speed); if (options->showType & FF_LOCALIP_TYPE_FLAGS_BIT) yyjson_mut_obj_add_strbuf(doc, obj, "flags", &ip->flags); } FF_LIST_FOR_EACH(FFLocalIpResult, ip, results) { ffStrbufDestroy(&ip->name); ffStrbufDestroy(&ip->ipv4); ffStrbufDestroy(&ip->ipv6); ffStrbufDestroy(&ip->mac); ffStrbufDestroy(&ip->flags); } return true; } void ffInitLocalIpOptions(FFLocalIpOptions* options) { ffOptionInitModuleArg(&options->moduleArgs, "󰩟"); options->showType = FF_LOCALIP_TYPE_IPV4_BIT | FF_LOCALIP_TYPE_PREFIX_LEN_BIT #if !__ANDROID__ /*Permission denied*/ | FF_LOCALIP_TYPE_DEFAULT_ROUTE_ONLY_BIT #endif ; options->ipv6Type = FF_LOCALIP_IPV6_TYPE_AUTO; ffStrbufInit(&options->namePrefix); } void ffDestroyLocalIpOptions(FFLocalIpOptions* options) { ffOptionDestroyModuleArg(&options->moduleArgs); ffStrbufDestroy(&options->namePrefix); } FFModuleBaseInfo ffLocalIPModuleInfo = { .name = FF_LOCALIP_MODULE_NAME, .description = "List local IP addresses (v4 or v6), MAC addresses, etc", .initOptions = (void*) ffInitLocalIpOptions, .destroyOptions = (void*) ffDestroyLocalIpOptions, .parseJsonObject = (void*) ffParseLocalIpJsonObject, .printModule = (void*) ffPrintLocalIp, .generateJsonResult = (void*) ffGenerateLocalIpJsonResult, .generateJsonConfig = (void*) ffGenerateLocalIpJsonConfig, .formatArgs = FF_FORMAT_ARG_LIST(((FFModuleFormatArg[]) { {"IPv4 address", "ipv4"}, {"IPv6 address", "ipv6"}, {"MAC address", "mac"}, {"Interface name", "ifname"}, {"Is default route", "is-default-route"}, {"MTU size in bytes", "mtu"}, {"Link speed (formatted)", "speed"}, {"Interface flags", "flags"}, })) }; ================================================ FILE: src/modules/localip/localip.h ================================================ #pragma once #include "option.h" #define FF_LOCALIP_MODULE_NAME "LocalIp" bool ffPrintLocalIp(FFLocalIpOptions* options); void ffInitLocalIpOptions(FFLocalIpOptions* options); void ffDestroyLocalIpOptions(FFLocalIpOptions* options); extern FFModuleBaseInfo ffLocalIPModuleInfo; ================================================ FILE: src/modules/localip/option.h ================================================ #pragma once #include "common/option.h" typedef enum __attribute__((__packed__)) FFLocalIpType { FF_LOCALIP_TYPE_NONE, FF_LOCALIP_TYPE_LOOP_BIT = 1 << 0, FF_LOCALIP_TYPE_IPV4_BIT = 1 << 1, FF_LOCALIP_TYPE_IPV6_BIT = 1 << 2, FF_LOCALIP_TYPE_MAC_BIT = 1 << 3, FF_LOCALIP_TYPE_PREFIX_LEN_BIT = 1 << 4, FF_LOCALIP_TYPE_MTU_BIT = 1 << 5, FF_LOCALIP_TYPE_SPEED_BIT = 1 << 6, FF_LOCALIP_TYPE_FLAGS_BIT = 1 << 7, FF_LOCALIP_TYPE_COMPACT_BIT = 1 << 10, FF_LOCALIP_TYPE_DEFAULT_ROUTE_ONLY_BIT = 1 << 11, FF_LOCALIP_TYPE_ALL_IPS_BIT = 1 << 12, FF_LOCALIP_TYPE_FORCE_UNSIGNED = UINT16_MAX, } FFLocalIpType; static_assert(sizeof(FFLocalIpType) == sizeof(uint16_t), ""); typedef enum __attribute__((__packed__)) FFLocalIpIpv6Type { FF_LOCALIP_IPV6_TYPE_NONE = 0b00000000, FF_LOCALIP_IPV6_TYPE_GUA_BIT = 0b00000001, FF_LOCALIP_IPV6_TYPE_ULA_BIT = 0b00000010, FF_LOCALIP_IPV6_TYPE_LLA_BIT = 0b00000100, FF_LOCALIP_IPV6_TYPE_UNKNOWN_BIT = 0b00001000, // IPv4-mapped, loopback, etc. FF_LOCALIP_IPV6_TYPE_SECONDARY_BIT = 0b01000000, // Temporary, duplicated, etc. FF_LOCALIP_IPV6_TYPE_PREFERRED_BIT = 0b10000000, // PREFER_SOURCE (manually set) FF_LOCALIP_IPV6_TYPE_TYPE_MASK = 0b00011111, FF_LOCALIP_IPV6_TYPE_AUTO = 0b11111111, // Used for detect option } FFLocalIpIpv6Type; static_assert(sizeof(FFLocalIpIpv6Type) == sizeof(uint8_t), ""); typedef struct FFLocalIpOptions { FFModuleArgs moduleArgs; FFLocalIpType showType; FFLocalIpIpv6Type ipv6Type; FFstrbuf namePrefix; } FFLocalIpOptions; static_assert(sizeof(FFLocalIpOptions) <= FF_OPTION_MAX_SIZE, "FFLocalIpOptions size exceeds maximum allowed size"); ================================================ FILE: src/modules/logo/logo.c ================================================ #include "common/printing.h" #include "logo/logo.h" #include "modules/logo/logo.h" #include "options/logo.h" bool ffPrintLogo(FF_MAYBE_UNUSED FFLogoOptions* options) { ffPrintError(FF_LOGO_MODULE_NAME, 0, NULL, FF_PRINT_TYPE_DEFAULT, "Supported in JSON format only"); return false; } void ffParseLogoJsonObject(FF_MAYBE_UNUSED FFLogoOptions* options, FF_MAYBE_UNUSED yyjson_val* module) { yyjson_val *key, *val; size_t idx, max; yyjson_obj_foreach(module, idx, max, key, val) { if (unsafe_yyjson_equals_str(key, "type") || unsafe_yyjson_equals_str(key, "condition")) continue; ffPrintError(FF_LOGO_MODULE_NAME, 0, NULL, FF_PRINT_TYPE_NO_CUSTOM_KEY, "Unknown JSON key %s", unsafe_yyjson_get_str(key)); } } bool ffGenerateLogoJsonResult(FF_MAYBE_UNUSED FFLogoOptions* options, yyjson_mut_doc* doc, yyjson_mut_val* module) { FFLogoSize size = FF_LOGO_SIZE_UNKNOWN; FFOptionsLogo* logoOptions = &instance.config.logo; if (logoOptions->type == FF_LOGO_TYPE_SMALL) size = FF_LOGO_SIZE_SMALL; else if (logoOptions->type != FF_LOGO_TYPE_BUILTIN && logoOptions->type != FF_LOGO_TYPE_AUTO) { yyjson_mut_obj_add_str(doc, module, "error", "Only 'builtin' and 'small' logo types are supported"); return false; } const FFlogo* logo = logoOptions->source.length > 0 ? ffLogoGetBuiltinForName(&logoOptions->source, size) : ffLogoGetBuiltinDetected(size); if (!logo) { yyjson_mut_obj_add_str(doc, module, "error", "No built-in logo found for the specified name/size"); return false; } yyjson_mut_val* obj = yyjson_mut_obj_add_obj(doc, module, "result"); yyjson_mut_obj_add_str(doc, obj, "lines", logo->lines); yyjson_mut_val* namesArr = yyjson_mut_obj_add_arr(doc, obj, "names"); for (size_t i = 0; i < FASTFETCH_LOGO_MAX_NAMES && logo->names[i]; i++) yyjson_mut_arr_add_str(doc, namesArr, logo->names[i]); yyjson_mut_val* colorsArr = yyjson_mut_obj_add_arr(doc, obj, "colors"); for (size_t i = 0; i < FASTFETCH_LOGO_MAX_COLORS && logo->colors[i]; i++) yyjson_mut_arr_add_str(doc, colorsArr, logo->colors[i]); yyjson_mut_obj_add_str(doc, obj, "colorKeys", logo->colorKeys); yyjson_mut_obj_add_str(doc, obj, "colorTitle", logo->colorTitle); yyjson_mut_val* typeArr = yyjson_mut_obj_add_arr(doc, obj, "type"); if (logo->type & FF_LOGO_LINE_TYPE_NORMAL) yyjson_mut_arr_add_str(doc, typeArr, "normal"); if (logo->type & FF_LOGO_LINE_TYPE_SMALL_BIT) yyjson_mut_arr_add_str(doc, typeArr, "small"); if (logo->type & FF_LOGO_LINE_TYPE_ALTER_BIT) yyjson_mut_arr_add_str(doc, typeArr, "alter"); return true; } void ffInitLogoOptions(FF_MAYBE_UNUSED FFLogoOptions* options) { } void ffDestroyLogoOptions(FF_MAYBE_UNUSED FFLogoOptions* options) { } FFModuleBaseInfo ffLogoModuleInfo = { .name = FF_LOGO_MODULE_NAME, .description = "Query built-in logo for JSON output", .initOptions = (void*) ffInitLogoOptions, .destroyOptions = (void*) ffDestroyLogoOptions, .parseJsonObject = (void*) ffParseLogoJsonObject, .printModule = (void*) ffPrintLogo, .generateJsonResult = (void*) ffGenerateLogoJsonResult, }; ================================================ FILE: src/modules/logo/logo.h ================================================ #pragma once #include "option.h" #define FF_LOGO_MODULE_NAME "Logo" bool ffPrintLogo(FFLogoOptions* options); void ffInitLogoOptions(FFLogoOptions* options); void ffDestroyLogoOptions(FFLogoOptions* options); extern FFModuleBaseInfo ffLogoModuleInfo; ================================================ FILE: src/modules/logo/option.h ================================================ #pragma once #include "common/option.h" typedef struct FFLogoOptions { } FFLogoOptions; static_assert(sizeof(FFLogoOptions) <= FF_OPTION_MAX_SIZE, "FFLogoOptions size exceeds maximum allowed size"); ================================================ FILE: src/modules/media/media.c ================================================ #include "common/printing.h" #include "common/jsonconfig.h" #include "common/stringUtils.h" #include "detection/media/media.h" #include "modules/media/media.h" #include static inline bool shouldIgnoreChar(char c) { return isblank(c) || c == '-' || c == '.'; } static bool artistInSongTitle(const FFstrbuf* song, const FFstrbuf* artist) { uint32_t artistIndex = 0; uint32_t songIndex = 0; while(true) { while(shouldIgnoreChar(song->chars[songIndex])) ++songIndex; while(shouldIgnoreChar(artist->chars[artistIndex])) ++artistIndex; if(artist->chars[artistIndex] == '\0') return true; if(song->chars[songIndex] == '\0') return false; if(tolower(song->chars[songIndex]) != tolower(artist->chars[artistIndex])) return false; ++artistIndex; ++songIndex; } //Unreachable return false; } bool ffPrintMedia(FFMediaOptions* options) { const FFMediaResult* media = ffDetectMedia(false); if(media->error.length > 0) { ffPrintError(FF_MEDIA_MODULE_NAME, 0, &options->moduleArgs, FF_PRINT_TYPE_DEFAULT, "%s", media->error.chars); return false; } FF_STRBUF_AUTO_DESTROY songPretty = ffStrbufCreateCopy(&media->song); const char* removeStrings[] = { "(Official Music Video)", "(Official Video)", "(Music Video)", "(Official HD Video)", "[Official Music Video]", "[Official Video]", "[Music Video]", "[Official HD Video]", "| Official Music Video", "| Official Video", "| Music Video", "| Official HD Video", "[Official Audio]", "[Audio]", "(Audio)", "| Official Audio", "| Audio", "| OFFICIAL AUDIO", "(Lyric Video)", "(Official Lyric Video)", "(Lyrics)", "[Lyric Video]", "[Official Lyric Video]", "[Lyrics]", "| Lyric Video", "| Official Lyric Video", "| Lyrics", }; ffStrbufRemoveStrings(&songPretty, ARRAY_SIZE(removeStrings), removeStrings); ffStrbufTrimRight(&songPretty, ' '); if(songPretty.length == 0) ffStrbufAppend(&songPretty, &media->song); 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); ffStrbufRemoveIgnCaseEndS(&artistPretty, " - Topic"); ffStrbufRemoveIgnCaseEndS(&artistPretty, "VEVO"); ffStrbufTrimRight(&artistPretty, ' '); if(artistInSongTitle(&songPretty, &artistPretty)) ffStrbufClear(&artistPretty); ffPrintLogoAndKey(FF_MEDIA_MODULE_NAME, 0, &options->moduleArgs, FF_PRINT_TYPE_DEFAULT); if(artistPretty.length > 0) { ffStrbufWriteTo(&artistPretty, stdout); fputs(" - ", stdout); } if (media->status.length > 0) 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_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(media->player, "player-name"), FF_ARG(media->playerId, "player-id"), FF_ARG(media->url, "url"), })); } return true; } void ffParseMediaJsonObject(FFMediaOptions* options, yyjson_val* module) { yyjson_val *key, *val; size_t idx, max; yyjson_obj_foreach(module, idx, max, key, val) { if (ffJsonConfigParseModuleArgs(key, val, &options->moduleArgs)) 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); } bool ffGenerateMediaJsonResult(FF_MAYBE_UNUSED FFMediaOptions* options, yyjson_mut_doc* doc, yyjson_mut_val* module) { const FFMediaResult* media = ffDetectMedia(false); if(media->error.length > 0) { yyjson_mut_obj_add_strbuf(doc, module, "error", &media->error); return false; } yyjson_mut_val* obj = yyjson_mut_obj_add_obj(doc, module, "result"); yyjson_mut_val* song = yyjson_mut_obj_add_obj(doc, obj, "song"); yyjson_mut_obj_add_strbuf(doc, song, "name", &media->song); 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); if (media->cover.length > 0) yyjson_mut_obj_add_strbuf(doc, song, "cover", &media->cover); else yyjson_mut_obj_add_null(doc, song, "cover"); yyjson_mut_val* player = yyjson_mut_obj_add_obj(doc, obj, "player"); yyjson_mut_obj_add_strbuf(doc, player, "name", &media->player); yyjson_mut_obj_add_strbuf(doc, player, "id", &media->playerId); yyjson_mut_obj_add_strbuf(doc, player, "url", &media->url); return true; } void ffInitMediaOptions(FFMediaOptions* options) { ffOptionInitModuleArg(&options->moduleArgs, ""); } void ffDestroyMediaOptions(FFMediaOptions* options) { ffOptionDestroyModuleArg(&options->moduleArgs); } FFModuleBaseInfo ffMediaModuleInfo = { .name = FF_MEDIA_MODULE_NAME, .description = "Print playing song name", .initOptions = (void*) ffInitMediaOptions, .destroyOptions = (void*) ffDestroyMediaOptions, .parseJsonObject = (void*) ffParseMediaJsonObject, .printModule = (void*) ffPrintMedia, .generateJsonResult = (void*) ffGenerateMediaJsonResult, .generateJsonConfig = (void*) ffGenerateMediaJsonConfig, .formatArgs = FF_FORMAT_ARG_LIST(((FFModuleFormatArg[]) { {"Pretty media name", "combined"}, {"Media name", "title"}, {"Artist name", "artist"}, {"Album name", "album"}, {"Status", "status"}, })) }; ================================================ FILE: src/modules/media/media.h ================================================ #pragma once #include "option.h" #define FF_MEDIA_MODULE_NAME "Media" bool ffPrintMedia(FFMediaOptions* options); void ffInitMediaOptions(FFMediaOptions* options); void ffDestroyMediaOptions(FFMediaOptions* options); extern FFModuleBaseInfo ffMediaModuleInfo; ================================================ FILE: src/modules/media/option.h ================================================ #pragma once #include "common/option.h" typedef struct FFMediaOptions { FFModuleArgs moduleArgs; } FFMediaOptions; static_assert(sizeof(FFMediaOptions) <= FF_OPTION_MAX_SIZE, "FFMediaOptions size exceeds maximum allowed size"); ================================================ FILE: src/modules/memory/memory.c ================================================ #include "common/printing.h" #include "common/jsonconfig.h" #include "common/percent.h" #include "common/size.h" #include "common/stringUtils.h" #include "detection/memory/memory.h" #include "modules/memory/memory.h" bool ffPrintMemory(FFMemoryOptions* options) { FFMemoryResult storage = {}; const char* error = ffDetectMemory(&storage); if(error) { ffPrintError(FF_MEMORY_MODULE_NAME, 0, &options->moduleArgs, FF_PRINT_TYPE_DEFAULT, "%s", error); return false; } FF_STRBUF_AUTO_DESTROY usedPretty = ffStrbufCreate(); ffSizeAppendNum(storage.bytesUsed, &usedPretty); FF_STRBUF_AUTO_DESTROY totalPretty = ffStrbufCreate(); ffSizeAppendNum(storage.bytesTotal, &totalPretty); double percentage = storage.bytesTotal == 0 ? 0 : (double) storage.bytesUsed / (double) storage.bytesTotal * 100.0; FFPercentageTypeFlags percentType = options->percent.type == 0 ? instance.config.display.percentType : options->percent.type; if(options->moduleArgs.outputFormat.length == 0) { ffPrintLogoAndKey(FF_MEMORY_MODULE_NAME, 0, &options->moduleArgs, FF_PRINT_TYPE_DEFAULT); if (storage.bytesTotal == 0) puts("Disabled"); else { FF_STRBUF_AUTO_DESTROY str = ffStrbufCreate(); if(percentType & FF_PERCENTAGE_TYPE_BAR_BIT) { ffPercentAppendBar(&str, percentage, options->percent, &options->moduleArgs); ffStrbufAppendC(&str, ' '); } if(!(percentType & FF_PERCENTAGE_TYPE_HIDE_OTHERS_BIT)) ffStrbufAppendF(&str, "%s / %s ", usedPretty.chars, totalPretty.chars); if(percentType & FF_PERCENTAGE_TYPE_NUM_BIT) ffPercentAppendNum(&str, percentage, options->percent, str.length > 0, &options->moduleArgs); ffStrbufTrimRight(&str, ' '); ffStrbufPutTo(&str, stdout); } } else { FF_STRBUF_AUTO_DESTROY percentageNum = ffStrbufCreate(); if (percentType & FF_PERCENTAGE_TYPE_NUM_BIT) ffPercentAppendNum(&percentageNum, percentage, options->percent, false, &options->moduleArgs); FF_STRBUF_AUTO_DESTROY percentageBar = ffStrbufCreate(); if (percentType & FF_PERCENTAGE_TYPE_BAR_BIT) ffPercentAppendBar(&percentageBar, percentage, options->percent, &options->moduleArgs); FF_PRINT_FORMAT_CHECKED(FF_MEMORY_MODULE_NAME, 0, &options->moduleArgs, FF_PRINT_TYPE_DEFAULT, ((FFformatarg[]){ FF_ARG(usedPretty, "used"), FF_ARG(totalPretty, "total"), FF_ARG(percentageNum, "percentage"), FF_ARG(percentageBar, "percentage-bar"), })); } return true; } void ffParseMemoryJsonObject(FFMemoryOptions* options, yyjson_val* module) { yyjson_val *key, *val; size_t idx, max; yyjson_obj_foreach(module, idx, max, key, val) { if (ffJsonConfigParseModuleArgs(key, val, &options->moduleArgs)) continue; if (ffPercentParseJsonObject(key, val, &options->percent)) continue; ffPrintError(FF_MEMORY_MODULE_NAME, 0, &options->moduleArgs, FF_PRINT_TYPE_DEFAULT, "Unknown JSON key %s", unsafe_yyjson_get_str(key)); } } void ffGenerateMemoryJsonConfig(FFMemoryOptions* options, yyjson_mut_doc* doc, yyjson_mut_val* module) { ffJsonConfigGenerateModuleArgsConfig(doc, module, &options->moduleArgs); ffPercentGenerateJsonConfig(doc, module, options->percent); } bool ffGenerateMemoryJsonResult(FF_MAYBE_UNUSED FFMemoryOptions* options, yyjson_mut_doc* doc, yyjson_mut_val* module) { FFMemoryResult storage = {}; const char* error = ffDetectMemory(&storage); if(error) { yyjson_mut_obj_add_str(doc, module, "error", error); return false; } yyjson_mut_val* obj = yyjson_mut_obj_add_obj(doc, module, "result"); yyjson_mut_obj_add_uint(doc, obj, "total", storage.bytesTotal); yyjson_mut_obj_add_uint(doc, obj, "used", storage.bytesUsed); return true; } void ffInitMemoryOptions(FFMemoryOptions* options) { ffOptionInitModuleArg(&options->moduleArgs, ""); options->percent = (FFPercentageModuleConfig) { 50, 80, 0 }; } void ffDestroyMemoryOptions(FFMemoryOptions* options) { ffOptionDestroyModuleArg(&options->moduleArgs); } FFModuleBaseInfo ffMemoryModuleInfo = { .name = FF_MEMORY_MODULE_NAME, .description = "Print system memory usage info", .initOptions = (void*) ffInitMemoryOptions, .destroyOptions = (void*) ffDestroyMemoryOptions, .parseJsonObject = (void*) ffParseMemoryJsonObject, .printModule = (void*) ffPrintMemory, .generateJsonResult = (void*) ffGenerateMemoryJsonResult, .generateJsonConfig = (void*) ffGenerateMemoryJsonConfig, .formatArgs = FF_FORMAT_ARG_LIST(((FFModuleFormatArg[]) { {"Used size", "used"}, {"Total size", "total"}, {"Percentage used (num)", "percentage"}, {"Percentage used (bar)", "percentage-bar"}, })) }; ================================================ FILE: src/modules/memory/memory.h ================================================ #pragma once #include "option.h" #define FF_MEMORY_MODULE_NAME "Memory" bool ffPrintMemory(FFMemoryOptions* options); void ffInitMemoryOptions(FFMemoryOptions* options); void ffDestroyMemoryOptions(FFMemoryOptions* options); extern FFModuleBaseInfo ffMemoryModuleInfo; ================================================ FILE: src/modules/memory/option.h ================================================ #pragma once #include "common/option.h" #include "common/percent.h" typedef struct FFMemoryOptions { FFModuleArgs moduleArgs; FFPercentageModuleConfig percent; } FFMemoryOptions; static_assert(sizeof(FFMemoryOptions) <= FF_OPTION_MAX_SIZE, "FFMemoryOptions size exceeds maximum allowed size"); ================================================ FILE: src/modules/modules.c ================================================ #include "modules/modules.h" static FFModuleBaseInfo* A[] = { NULL, }; static FFModuleBaseInfo* B[] = { &ffBatteryModuleInfo, &ffBiosModuleInfo, &ffBluetoothModuleInfo, &ffBluetoothRadioModuleInfo, &ffBoardModuleInfo, &ffBootmgrModuleInfo, &ffBreakModuleInfo, &ffBrightnessModuleInfo, &ffBtrfsModuleInfo, NULL, }; static FFModuleBaseInfo* C[] = { &ffCameraModuleInfo, &ffChassisModuleInfo, &ffCommandModuleInfo, &ffColorsModuleInfo, &ffCPUModuleInfo, &ffCPUCacheModuleInfo, &ffCPUUsageModuleInfo, &ffCursorModuleInfo, &ffCustomModuleInfo, NULL, }; static FFModuleBaseInfo* D[] = { &ffDateTimeModuleInfo, &ffDEModuleInfo, &ffDisplayModuleInfo, &ffDiskModuleInfo, &ffDiskIOModuleInfo, &ffDNSModuleInfo, NULL, }; static FFModuleBaseInfo* E[] = { &ffEditorModuleInfo, NULL, }; static FFModuleBaseInfo* F[] = { &ffFontModuleInfo, NULL, }; static FFModuleBaseInfo* G[] = { &ffGamepadModuleInfo, &ffGPUModuleInfo, NULL, }; static FFModuleBaseInfo* H[] = { &ffHostModuleInfo, NULL, }; static FFModuleBaseInfo* I[] = { &ffIconsModuleInfo, &ffInitSystemModuleInfo, NULL, }; static FFModuleBaseInfo* J[] = { NULL, }; static FFModuleBaseInfo* K[] = { &ffKernelModuleInfo, &ffKeyboardModuleInfo, NULL, }; static FFModuleBaseInfo* L[] = { &ffLMModuleInfo, &ffLoadavgModuleInfo, &ffLocaleModuleInfo, &ffLocalIPModuleInfo, &ffLogoModuleInfo, NULL, }; static FFModuleBaseInfo* M[] = { &ffMediaModuleInfo, &ffMemoryModuleInfo, &ffMonitorModuleInfo, &ffMouseModuleInfo, NULL, }; static FFModuleBaseInfo* N[] = { &ffNetIOModuleInfo, NULL, }; static FFModuleBaseInfo* O[] = { &ffOpenCLModuleInfo, &ffOpenGLModuleInfo, &ffOSModuleInfo, NULL, }; static FFModuleBaseInfo* P[] = { &ffPackagesModuleInfo, &ffPhysicalDiskModuleInfo, &ffPhysicalMemoryModuleInfo, &ffPlayerModuleInfo, &ffPowerAdapterModuleInfo, &ffProcessesModuleInfo, &ffPublicIPModuleInfo, NULL, }; static FFModuleBaseInfo* Q[] = { NULL, }; static FFModuleBaseInfo* R[] = { NULL, }; static FFModuleBaseInfo* S[] = { &ffSeparatorModuleInfo, &ffShellModuleInfo, &ffSoundModuleInfo, &ffSwapModuleInfo, NULL, }; static FFModuleBaseInfo* T[] = { &ffTerminalModuleInfo, &ffTerminalFontModuleInfo, &ffTerminalSizeModuleInfo, &ffTerminalThemeModuleInfo, &ffTitleModuleInfo, &ffThemeModuleInfo, &ffTPMModuleInfo, NULL, }; static FFModuleBaseInfo* U[] = { &ffUptimeModuleInfo, &ffUsersModuleInfo, NULL, }; static FFModuleBaseInfo* V[] = { &ffVersionModuleInfo, &ffVulkanModuleInfo, NULL, }; static FFModuleBaseInfo* W[] = { &ffWallpaperModuleInfo, &ffWeatherModuleInfo, &ffWMModuleInfo, &ffWifiModuleInfo, &ffWMThemeModuleInfo, NULL, }; static FFModuleBaseInfo* X[] = { NULL, }; static FFModuleBaseInfo* Y[] = { NULL, }; static FFModuleBaseInfo* Z[] = { &ffZpoolModuleInfo, NULL, }; FFModuleBaseInfo** ffModuleInfos[] = { A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P, Q, R, S, T, U, V, W, X, Y, Z, }; ================================================ FILE: src/modules/modules.h ================================================ #pragma once #include "modules/battery/battery.h" #include "modules/bios/bios.h" #include "modules/bluetooth/bluetooth.h" #include "modules/bluetoothradio/bluetoothradio.h" #include "modules/brightness/brightness.h" #include "modules/board/board.h" #include "modules/bootmgr/bootmgr.h" #include "modules/break/break.h" #include "modules/btrfs/btrfs.h" #include "modules/camera/camera.h" #include "modules/chassis/chassis.h" #include "modules/cpu/cpu.h" #include "modules/cpucache/cpucache.h" #include "modules/cpuusage/cpuusage.h" #include "modules/command/command.h" #include "modules/colors/colors.h" #include "modules/cursor/cursor.h" #include "modules/custom/custom.h" #include "modules/datetime/datetime.h" #include "modules/disk/disk.h" #include "modules/diskio/diskio.h" #include "modules/display/display.h" #include "modules/de/de.h" #include "modules/dns/dns.h" #include "modules/editor/editor.h" #include "modules/font/font.h" #include "modules/gamepad/gamepad.h" #include "modules/gpu/gpu.h" #include "modules/host/host.h" #include "modules/icons/icons.h" #include "modules/initsystem/initsystem.h" #include "modules/kernel/kernel.h" #include "modules/keyboard/keyboard.h" #include "modules/lm/lm.h" #include "modules/loadavg/loadavg.h" #include "modules/locale/locale.h" #include "modules/localip/localip.h" #include "modules/logo/logo.h" #include "modules/media/media.h" #include "modules/memory/memory.h" #include "modules/monitor/monitor.h" #include "modules/mouse/mouse.h" #include "modules/netio/netio.h" #include "modules/opengl/opengl.h" #include "modules/opencl/opencl.h" #include "modules/os/os.h" #include "modules/packages/packages.h" #include "modules/physicaldisk/physicaldisk.h" #include "modules/physicalmemory/physicalmemory.h" #include "modules/player/player.h" #include "modules/poweradapter/poweradapter.h" #include "modules/processes/processes.h" #include "modules/publicip/publicip.h" #include "modules/separator/separator.h" #include "modules/shell/shell.h" #include "modules/sound/sound.h" #include "modules/swap/swap.h" #include "modules/terminal/terminal.h" #include "modules/terminalfont/terminalfont.h" #include "modules/terminalsize/terminalsize.h" #include "modules/terminaltheme/terminaltheme.h" #include "modules/theme/theme.h" #include "modules/title/title.h" #include "modules/tpm/tpm.h" #include "modules/uptime/uptime.h" #include "modules/users/users.h" #include "modules/version/version.h" #include "modules/vulkan/vulkan.h" #include "modules/wallpaper/wallpaper.h" #include "modules/weather/weather.h" #include "modules/wifi/wifi.h" #include "modules/wm/wm.h" #include "modules/wmtheme/wmtheme.h" #include "modules/zpool/zpool.h" ================================================ FILE: src/modules/monitor/monitor.c ================================================ #include "common/printing.h" #include "common/jsonconfig.h" #include "common/stringUtils.h" #include "detection/displayserver/displayserver.h" #include "modules/monitor/monitor.h" #include bool ffPrintMonitor(FFMonitorOptions* options) { const FFDisplayServerResult* result = ffConnectDisplayServer(); if(!result->displays.length) { ffPrintError(FF_MONITOR_MODULE_NAME, 0, &options->moduleArgs, FF_PRINT_TYPE_DEFAULT, "No display detected"); return false; } FF_STRBUF_AUTO_DESTROY key = ffStrbufCreate(); uint32_t index = 0; FF_LIST_FOR_EACH(FFDisplayResult, display, result->displays) { double inch = sqrt(display->physicalWidth * display->physicalWidth + display->physicalHeight * display->physicalHeight) / 25.4; double ppi = sqrt(display->width * display->width + display->height * display->height) / inch; bool hdrCompatible = display->hdrStatus == FF_DISPLAY_HDR_STATUS_SUPPORTED || display->hdrStatus == FF_DISPLAY_HDR_STATUS_ENABLED; ffStrbufClear(&key); if(options->moduleArgs.key.length == 0) { ffStrbufAppendS(&key, FF_MONITOR_MODULE_NAME); if (display->name.length > 0) ffStrbufAppendF(&key, " (%s)", display->name.chars); } else { uint32_t moduleIndex = result->displays.length == 1 ? 0 : index + 1; FF_PARSE_FORMAT_STRING_CHECKED(&key, &options->moduleArgs.key, ((FFformatarg[]) { FF_ARG(moduleIndex, "index"), FF_ARG(display->name, "name"), FF_ARG(options->moduleArgs.keyIcon, "icon"), })); } if(options->moduleArgs.outputFormat.length == 0) { ffPrintLogoAndKey(key.chars, 0, &options->moduleArgs, FF_PRINT_TYPE_NO_CUSTOM_KEY); printf("%ux%u px", display->width, display->height); if (display->refreshRate > 0) printf(" @ %g Hz", ((int) (display->refreshRate * 1000 + 0.5)) / 1000.0); if (inch > 0) printf(" - %ux%u mm (%.2f inches, %.2f ppi)", display->physicalWidth, display->physicalHeight, inch, ppi); if (hdrCompatible) fputs(" [HDR Compatible]", stdout); putchar('\n'); } else { char buf[32]; if (display->serial) { const uint8_t* nums = (uint8_t*) &display->serial; snprintf(buf, sizeof(buf), "%2X-%2X-%2X-%2X", nums[0], nums[1], nums[2], nums[3]); } else buf[0] = '\0'; FF_PRINT_FORMAT_CHECKED(key.chars, 0, &options->moduleArgs, FF_PRINT_TYPE_NO_CUSTOM_KEY, ((FFformatarg[]) { FF_ARG(display->name, "name"), FF_ARG(display->width, "width"), FF_ARG(display->height, "height"), FF_ARG(display->physicalWidth, "physical-width"), FF_ARG(display->physicalHeight, "physical-height"), FF_ARG(inch, "inch"), FF_ARG(ppi, "ppi"), FF_ARG(display->manufactureYear, "manufacture-year"), FF_ARG(display->manufactureWeek, "manufacture-week"), FF_ARG(buf, "serial"), FF_ARG(display->refreshRate, "refresh-rate"), FF_ARG(hdrCompatible, "hdr-compatible"), })); } ffStrbufDestroy(&display->name); ++index; } return true; } void ffParseMonitorJsonObject(FFMonitorOptions* options, yyjson_val* module) { yyjson_val *key, *val; size_t idx, max; yyjson_obj_foreach(module, idx, max, key, val) { if (ffJsonConfigParseModuleArgs(key, val, &options->moduleArgs)) continue; ffPrintError(FF_MONITOR_MODULE_NAME, 0, &options->moduleArgs, FF_PRINT_TYPE_DEFAULT, "Unknown JSON key %s", unsafe_yyjson_get_str(key)); } } void ffGenerateMonitorJsonConfig(FFMonitorOptions* options, yyjson_mut_doc* doc, yyjson_mut_val* module) { ffJsonConfigGenerateModuleArgsConfig(doc, module, &options->moduleArgs); } bool ffGenerateMonitorJsonResult(FF_MAYBE_UNUSED FFMonitorOptions* options, yyjson_mut_doc* doc, yyjson_mut_val* module) { yyjson_mut_obj_add_str(doc, module, "error", "Monitor module is an alias of Display module"); return false; } void ffInitMonitorOptions(FFMonitorOptions* options) { ffOptionInitModuleArg(&options->moduleArgs, "󰹑"); } void ffDestroyMonitorOptions(FFMonitorOptions* options) { ffOptionDestroyModuleArg(&options->moduleArgs); } FFModuleBaseInfo ffMonitorModuleInfo = { .name = FF_MONITOR_MODULE_NAME, .description = "Alias of Display module", .initOptions = (void*) ffInitMonitorOptions, .destroyOptions = (void*) ffDestroyMonitorOptions, .parseJsonObject = (void*) ffParseMonitorJsonObject, .printModule = (void*) ffPrintMonitor, .generateJsonResult = (void*) ffGenerateMonitorJsonResult, .generateJsonConfig = (void*) ffGenerateMonitorJsonConfig, .formatArgs = FF_FORMAT_ARG_LIST(((FFModuleFormatArg[]) { {"Display name", "name"}, {"Native resolution width in pixels", "width"}, {"Native resolution height in pixels", "height"}, {"Physical width in millimeters", "physical-width"}, {"Physical height in millimeters", "physical-height"}, {"Physical diagonal length in inches", "inch"}, {"Pixels per inch (PPI)", "ppi"}, {"Year of manufacturing", "manufacture-year"}, {"Nth week of manufacturing in the year", "manufacture-week"}, {"Serial number", "serial"}, {"Maximum refresh rate in Hz", "refresh-rate"}, {"True if the display is HDR compatible", "hdr-compatible"}, })) }; ================================================ FILE: src/modules/monitor/monitor.h ================================================ #pragma once #include "option.h" #define FF_MONITOR_MODULE_NAME "Monitor" bool ffPrintMonitor(FFMonitorOptions* options); void ffInitMonitorOptions(FFMonitorOptions* options); void ffDestroyMonitorOptions(FFMonitorOptions* options); extern FFModuleBaseInfo ffMonitorModuleInfo; ================================================ FILE: src/modules/monitor/option.h ================================================ #pragma once #include "common/option.h" typedef struct FFMonitorOptions { FFModuleArgs moduleArgs; } FFMonitorOptions; static_assert(sizeof(FFMonitorOptions) <= FF_OPTION_MAX_SIZE, "FFMonitorOptions size exceeds maximum allowed size"); ================================================ FILE: src/modules/mouse/mouse.c ================================================ #include "common/percent.h" #include "common/printing.h" #include "common/jsonconfig.h" #include "common/stringUtils.h" #include "detection/mouse/mouse.h" #include "modules/mouse/mouse.h" static void printDevice(FFMouseOptions* options, const FFMouseDevice* device, uint8_t index) { if(options->moduleArgs.outputFormat.length == 0) { ffPrintLogoAndKey(FF_MOUSE_MODULE_NAME, index, &options->moduleArgs, FF_PRINT_TYPE_DEFAULT); ffStrbufPutTo(&device->name, stdout); } else { FF_PRINT_FORMAT_CHECKED(FF_MOUSE_MODULE_NAME, index, &options->moduleArgs, FF_PRINT_TYPE_DEFAULT, ((FFformatarg[]) { FF_ARG(device->name, "name"), FF_ARG(device->serial, "serial"), })); } } bool ffPrintMouse(FFMouseOptions* options) { FF_LIST_AUTO_DESTROY result = ffListCreate(sizeof(FFMouseDevice)); const char* error = ffDetectMouse(&result); if(error) { ffPrintError(FF_MOUSE_MODULE_NAME, 0, &options->moduleArgs, FF_PRINT_TYPE_DEFAULT, "%s", error); return false; } if(!result.length) { ffPrintError(FF_MOUSE_MODULE_NAME, 0, &options->moduleArgs, FF_PRINT_TYPE_DEFAULT, "No devices detected"); return false; } FF_LIST_AUTO_DESTROY filtered = ffListCreate(sizeof(FFMouseDevice*)); FF_LIST_FOR_EACH(FFMouseDevice, device, result) { bool ignored = false; FF_LIST_FOR_EACH(FFstrbuf, ignore, options->ignores) { if(ffStrbufStartsWithIgnCase(&device->name, ignore)) { ignored = true; break; } } if(!ignored) { FFMouseDevice** ptr = ffListAdd(&filtered); *ptr = device; } } bool ret = true; if(!filtered.length) { ffPrintError(FF_MOUSE_MODULE_NAME, 0, &options->moduleArgs, FF_PRINT_TYPE_DEFAULT, "All devices are ignored"); ret = false; } else { uint8_t index = 0; FF_LIST_FOR_EACH(FFMouseDevice*, pdevice, filtered) { FFMouseDevice* device = *pdevice; printDevice(options, device, filtered.length > 1 ? ++index : 0); } } FF_LIST_FOR_EACH(FFMouseDevice, device, result) { ffStrbufDestroy(&device->serial); ffStrbufDestroy(&device->name); } return ret; } void ffParseMouseJsonObject(FFMouseOptions* options, yyjson_val* module) { yyjson_val *key, *val; size_t idx, max; yyjson_obj_foreach(module, idx, max, key, val) { if (ffJsonConfigParseModuleArgs(key, val, &options->moduleArgs)) continue; if (unsafe_yyjson_equals_str(key, "ignores")) { yyjson_val *elem; size_t eidx, emax; yyjson_arr_foreach(val, eidx, emax, elem) { if (yyjson_is_str(elem)) { FFstrbuf* strbuf = ffListAdd(&options->ignores); ffStrbufInitJsonVal(strbuf, elem); } } continue; } ffPrintError(FF_MOUSE_MODULE_NAME, 0, &options->moduleArgs, FF_PRINT_TYPE_DEFAULT, "Unknown JSON key %s", unsafe_yyjson_get_str(key)); } } void ffGenerateMouseJsonConfig(FFMouseOptions* options, yyjson_mut_doc* doc, yyjson_mut_val* module) { ffJsonConfigGenerateModuleArgsConfig(doc, module, &options->moduleArgs); if (options->ignores.length > 0) { yyjson_mut_val* ignores = yyjson_mut_obj_add_arr(doc, module, "ignores"); FF_LIST_FOR_EACH(FFstrbuf, strbuf, options->ignores) yyjson_mut_arr_append(ignores, yyjson_mut_strncpy(doc, strbuf->chars, strbuf->length)); } } bool ffGenerateMouseJsonResult(FF_MAYBE_UNUSED FFMouseOptions* options, yyjson_mut_doc* doc, yyjson_mut_val* module) { FF_LIST_AUTO_DESTROY result = ffListCreate(sizeof(FFMouseDevice)); const char* error = ffDetectMouse(&result); if(error) { yyjson_mut_obj_add_str(doc, module, "error", error); return false; } yyjson_mut_val* arr = yyjson_mut_obj_add_arr(doc, module, "result"); FF_LIST_FOR_EACH(FFMouseDevice, device, result) { yyjson_mut_val* obj = yyjson_mut_arr_add_obj(doc, arr); yyjson_mut_obj_add_strbuf(doc, obj, "serial", &device->serial); yyjson_mut_obj_add_strbuf(doc, obj, "name", &device->name); bool ignored = false; FF_LIST_FOR_EACH(FFstrbuf, ignore, options->ignores) { if(ffStrbufStartsWithIgnCase(&device->name, ignore)) { ignored = true; break; } } yyjson_mut_obj_add_bool(doc, obj, "ignored", ignored); } FF_LIST_FOR_EACH(FFMouseDevice, device, result) { ffStrbufDestroy(&device->serial); ffStrbufDestroy(&device->name); } return true; } void ffInitMouseOptions(FFMouseOptions* options) { ffOptionInitModuleArg(&options->moduleArgs, "󰍽"); ffListInit(&options->ignores, sizeof(FFstrbuf)); } void ffDestroyMouseOptions(FFMouseOptions* options) { ffOptionDestroyModuleArg(&options->moduleArgs); FF_LIST_FOR_EACH(FFstrbuf, str, options->ignores) ffStrbufDestroy(str); ffListDestroy(&options->ignores); } FFModuleBaseInfo ffMouseModuleInfo = { .name = FF_MOUSE_MODULE_NAME, .description = "List connected mouses", .initOptions = (void*) ffInitMouseOptions, .destroyOptions = (void*) ffDestroyMouseOptions, .parseJsonObject = (void*) ffParseMouseJsonObject, .printModule = (void*) ffPrintMouse, .generateJsonResult = (void*) ffGenerateMouseJsonResult, .generateJsonConfig = (void*) ffGenerateMouseJsonConfig, .formatArgs = FF_FORMAT_ARG_LIST(((FFModuleFormatArg[]) { {"Mouse name", "name"}, {"Mouse serial number", "serial"}, })) }; ================================================ FILE: src/modules/mouse/mouse.h ================================================ #pragma once #include "option.h" #define FF_MOUSE_MODULE_NAME "Mouse" bool ffPrintMouse(FFMouseOptions* options); void ffInitMouseOptions(FFMouseOptions* options); void ffDestroyMouseOptions(FFMouseOptions* options); extern FFModuleBaseInfo ffMouseModuleInfo; ================================================ FILE: src/modules/mouse/option.h ================================================ #pragma once #include "common/option.h" #include "common/FFlist.h" typedef struct FFMouseOptions { FFModuleArgs moduleArgs; FFlist ignores; // List of FFstrbuf } FFMouseOptions; static_assert(sizeof(FFMouseOptions) <= FF_OPTION_MAX_SIZE, "FFMouseOptions size exceeds maximum allowed size"); ================================================ FILE: src/modules/netio/netio.c ================================================ #include "common/printing.h" #include "common/jsonconfig.h" #include "common/size.h" #include "common/stringUtils.h" #include "detection/netio/netio.h" #include "modules/netio/netio.h" #define FF_NETIO_DISPLAY_NAME "Network IO" static int sortInfs(const FFNetIOResult* left, const FFNetIOResult* right) { return ffStrbufComp(&left->name, &right->name); } static void formatKey(const FFNetIOOptions* options, FFNetIOResult* inf, uint32_t index, FFstrbuf* key) { if(options->moduleArgs.key.length == 0) { if(!inf->name.length) ffStrbufSetF(&inf->name, "unknown %u", (unsigned) index); ffStrbufSetF(key, FF_NETIO_DISPLAY_NAME " (%s)", inf->name.chars); } else { ffStrbufClear(key); FF_PARSE_FORMAT_STRING_CHECKED(key, &options->moduleArgs.key, ((FFformatarg[]){ FF_ARG(index, "index"), FF_ARG(inf->name, "name"), FF_ARG(options->moduleArgs.keyIcon, "icon"), })); } } bool ffPrintNetIO(FFNetIOOptions* options) { FF_LIST_AUTO_DESTROY result = ffListCreate(sizeof(FFNetIOResult)); const char* error = ffDetectNetIO(&result, options); if(error) { ffPrintError(FF_NETIO_DISPLAY_NAME, 0, &options->moduleArgs, FF_PRINT_TYPE_DEFAULT, "%s", error); return false; } ffListSort(&result, (const void*) sortInfs); uint32_t index = 0; FF_STRBUF_AUTO_DESTROY key = ffStrbufCreate(); FF_STRBUF_AUTO_DESTROY buffer = ffStrbufCreate(); FF_STRBUF_AUTO_DESTROY buffer2 = ffStrbufCreate(); FF_LIST_FOR_EACH(FFNetIOResult, inf, result) { formatKey(options, inf, result.length == 1 ? 0 : index + 1, &key); ffStrbufClear(&buffer); if(options->moduleArgs.outputFormat.length == 0) { ffPrintLogoAndKey(key.chars, 0, &options->moduleArgs, FF_PRINT_TYPE_NO_CUSTOM_KEY); ffSizeAppendNum(inf->rxBytes, &buffer); if (!options->detectTotal) ffStrbufAppendS(&buffer, "/s"); ffStrbufAppendS(&buffer, " (IN) - "); ffSizeAppendNum(inf->txBytes, &buffer); if (!options->detectTotal) ffStrbufAppendS(&buffer, "/s"); ffStrbufAppendS(&buffer, " (OUT)"); if (inf->defaultRoute && !options->defaultRouteOnly) ffStrbufAppendS(&buffer, " *"); ffStrbufPutTo(&buffer, stdout); } else { ffStrbufClear(&buffer2); ffSizeAppendNum(inf->rxBytes, &buffer); if (!options->detectTotal) ffStrbufAppendS(&buffer, "/s"); ffSizeAppendNum(inf->txBytes, &buffer2); if (!options->detectTotal) ffStrbufAppendS(&buffer2, "/s"); FF_PRINT_FORMAT_CHECKED(key.chars, 0, &options->moduleArgs, FF_PRINT_TYPE_NO_CUSTOM_KEY, ((FFformatarg[]){ FF_ARG(buffer, "rx-size"), FF_ARG(buffer2, "tx-size"), FF_ARG(inf->name, "ifname"), FF_ARG(inf->defaultRoute, "is-default-route"), FF_ARG(inf->txBytes, "tx-bytes"), FF_ARG(inf->rxBytes, "rx-bytes"), FF_ARG(inf->txPackets, "tx-packets"), FF_ARG(inf->rxPackets, "rx-packets"), FF_ARG(inf->rxErrors, "rx-errors"), FF_ARG(inf->txErrors, "tx-errors"), FF_ARG(inf->rxDrops, "rx-drops"), FF_ARG(inf->txDrops, "tx-drops"), })); } ++index; } FF_LIST_FOR_EACH(FFNetIOResult, inf, result) { ffStrbufDestroy(&inf->name); } return true; } void ffParseNetIOJsonObject(FFNetIOOptions* options, yyjson_val* module) { yyjson_val *key, *val; size_t idx, max; yyjson_obj_foreach(module, idx, max, key, val) { if (ffJsonConfigParseModuleArgs(key, val, &options->moduleArgs)) continue; if (unsafe_yyjson_equals_str(key, "namePrefix")) { ffStrbufSetJsonVal(&options->namePrefix, val); continue; } if (unsafe_yyjson_equals_str(key, "defaultRouteOnly")) { options->defaultRouteOnly = yyjson_get_bool(val); continue; } if (unsafe_yyjson_equals_str(key, "detectTotal")) { options->detectTotal = yyjson_get_bool(val); continue; } if (unsafe_yyjson_equals_str(key, "waitTime")) { options->waitTime = (uint32_t) yyjson_get_uint(val); continue; } ffPrintError(FF_NETIO_MODULE_NAME, 0, &options->moduleArgs, FF_PRINT_TYPE_DEFAULT, "Unknown JSON key %s", unsafe_yyjson_get_str(key)); } } void ffGenerateNetIOJsonConfig(FFNetIOOptions* options, yyjson_mut_doc* doc, yyjson_mut_val* module) { ffJsonConfigGenerateModuleArgsConfig(doc, module, &options->moduleArgs); yyjson_mut_obj_add_strbuf(doc, module, "namePrefix", &options->namePrefix); yyjson_mut_obj_add_bool(doc, module, "defaultRouteOnly", options->defaultRouteOnly); yyjson_mut_obj_add_bool(doc, module, "detectTotal", options->detectTotal); yyjson_mut_obj_add_uint(doc, module, "waitTime", options->waitTime); } bool ffGenerateNetIOJsonResult(FFNetIOOptions* options, yyjson_mut_doc* doc, yyjson_mut_val* module) { FF_LIST_AUTO_DESTROY result = ffListCreate(sizeof(FFNetIOResult)); const char* error = ffDetectNetIO(&result, options); if(error) { yyjson_mut_obj_add_str(doc, module, "error", error); return false; } yyjson_mut_val* arr = yyjson_mut_obj_add_arr(doc, module, "result"); FF_LIST_FOR_EACH(FFNetIOResult, counter, result) { yyjson_mut_val* obj = yyjson_mut_arr_add_obj(doc, arr); yyjson_mut_obj_add_strbuf(doc, obj, "name", &counter->name); yyjson_mut_obj_add_bool(doc, obj, "defaultRoute", counter->defaultRoute); yyjson_mut_obj_add_uint(doc, obj, "txBytes", counter->txBytes); yyjson_mut_obj_add_uint(doc, obj, "rxBytes", counter->rxBytes); yyjson_mut_obj_add_uint(doc, obj, "txPackets", counter->txPackets); yyjson_mut_obj_add_uint(doc, obj, "rxPackets", counter->rxPackets); yyjson_mut_obj_add_uint(doc, obj, "rxErrors", counter->rxErrors); yyjson_mut_obj_add_uint(doc, obj, "txErrors", counter->txErrors); yyjson_mut_obj_add_uint(doc, obj, "rxDrops", counter->rxDrops); yyjson_mut_obj_add_uint(doc, obj, "txDrops", counter->txDrops); } FF_LIST_FOR_EACH(FFNetIOResult, inf, result) { ffStrbufDestroy(&inf->name); } return true; } void ffInitNetIOOptions(FFNetIOOptions* options) { ffOptionInitModuleArg(&options->moduleArgs, "󰾆"); ffStrbufInit(&options->namePrefix); options->defaultRouteOnly = #if __ANDROID__ false #else true #endif ; options->detectTotal = false; options->waitTime = 1000; } void ffDestroyNetIOOptions(FFNetIOOptions* options) { ffOptionDestroyModuleArg(&options->moduleArgs); ffStrbufDestroy(&options->namePrefix); } FFModuleBaseInfo ffNetIOModuleInfo = { .name = FF_NETIO_MODULE_NAME, .description = "Print network I/O throughput", .initOptions = (void*) ffInitNetIOOptions, .destroyOptions = (void*) ffDestroyNetIOOptions, .parseJsonObject = (void*) ffParseNetIOJsonObject, .printModule = (void*) ffPrintNetIO, .generateJsonResult = (void*) ffGenerateNetIOJsonResult, .generateJsonConfig = (void*) ffGenerateNetIOJsonConfig, .formatArgs = FF_FORMAT_ARG_LIST(((FFModuleFormatArg[]) { {"Size of data received [per second] (formatted)", "rx-size"}, {"Size of data sent [per second] (formatted)", "tx-size"}, {"Interface name", "ifname"}, {"Is default route", "is-default-route"}, {"Size of data received [per second] (in bytes)", "rx-bytes"}, {"Size of data sent [per second] (in bytes)", "tx-bytes"}, {"Number of packets received [per second]", "rx-packets"}, {"Number of packets sent [per second]", "tx-packets"}, {"Number of errors received [per second]", "rx-errors"}, {"Number of errors sent [per second]", "tx-errors"}, {"Number of packets dropped when receiving [per second]", "rx-drops"}, {"Number of packets dropped when sending [per second]", "tx-drops"}, })) }; ================================================ FILE: src/modules/netio/netio.h ================================================ #pragma once #include "option.h" #define FF_NETIO_MODULE_NAME "NetIO" void ffPrepareNetIO(FFNetIOOptions* options); bool ffPrintNetIO(FFNetIOOptions* options); void ffInitNetIOOptions(FFNetIOOptions* options); void ffDestroyNetIOOptions(FFNetIOOptions* options); extern FFModuleBaseInfo ffNetIOModuleInfo; ================================================ FILE: src/modules/netio/option.h ================================================ #pragma once #include "common/option.h" typedef struct FFNetIOOptions { FFModuleArgs moduleArgs; FFstrbuf namePrefix; uint32_t waitTime; bool defaultRouteOnly; bool detectTotal; } FFNetIOOptions; static_assert(sizeof(FFNetIOOptions) <= FF_OPTION_MAX_SIZE, "FFNetIOOptions size exceeds maximum allowed size"); ================================================ FILE: src/modules/opencl/opencl.c ================================================ #include "common/printing.h" #include "common/jsonconfig.h" #include "common/stringUtils.h" #include "detection/opencl/opencl.h" #include "detection/gpu/gpu.h" #include "modules/opencl/opencl.h" bool ffPrintOpenCL(FFOpenCLOptions* options) { FFOpenCLResult* result = ffDetectOpenCL(); if(result->error != NULL) { ffPrintError(FF_OPENCL_MODULE_NAME, 0, &options->moduleArgs, FF_PRINT_TYPE_DEFAULT, "%s", result->error); return false; } if(options->moduleArgs.outputFormat.length == 0) { ffPrintLogoAndKey(FF_OPENCL_MODULE_NAME, 0, &options->moduleArgs, FF_PRINT_TYPE_DEFAULT); ffStrbufPutTo(&result->version, stdout); } else { FF_PRINT_FORMAT_CHECKED(FF_OPENCL_MODULE_NAME, 0, &options->moduleArgs, FF_PRINT_TYPE_DEFAULT, ((FFformatarg[]) { FF_ARG(result->version, "version"), FF_ARG(result->name, "name"), FF_ARG(result->vendor, "vendor"), })); } return true; } void ffParseOpenCLJsonObject(FFOpenCLOptions* options, yyjson_val* module) { yyjson_val *key, *val; size_t idx, max; yyjson_obj_foreach(module, idx, max, key, val) { if (ffJsonConfigParseModuleArgs(key, val, &options->moduleArgs)) continue; ffPrintError(FF_OPENCL_MODULE_NAME, 0, &options->moduleArgs, FF_PRINT_TYPE_DEFAULT, "Unknown JSON key %s", unsafe_yyjson_get_str(key)); } } void ffGenerateOpenCLJsonConfig(FFOpenCLOptions* options, yyjson_mut_doc* doc, yyjson_mut_val* module) { ffJsonConfigGenerateModuleArgsConfig(doc, module, &options->moduleArgs); } bool ffGenerateOpenCLJsonResult(FF_MAYBE_UNUSED FFOpenCLOptions* options, yyjson_mut_doc* doc, yyjson_mut_val* module) { FFOpenCLResult* result = ffDetectOpenCL(); if(result->error != NULL) { yyjson_mut_obj_add_str(doc, module, "error", result->error); return false; } yyjson_mut_val* obj = yyjson_mut_obj_add_obj(doc, module, "result"); yyjson_mut_obj_add_strbuf(doc, obj, "version", &result->version); yyjson_mut_obj_add_strbuf(doc, obj, "name", &result->name); yyjson_mut_obj_add_strbuf(doc, obj, "vendor", &result->vendor); yyjson_mut_val* gpus = yyjson_mut_obj_add_arr(doc, obj, "gpus"); FF_LIST_FOR_EACH(FFGPUResult, gpu, result->gpus) { yyjson_mut_val* gpuObj = yyjson_mut_arr_add_obj(doc, gpus); yyjson_mut_obj_add_str(doc, gpuObj, "type", gpu->type == FF_GPU_TYPE_UNKNOWN ? "Unknown" : gpu->type == FF_GPU_TYPE_INTEGRATED ? "Integrated" : "Discrete"); yyjson_mut_obj_add_strbuf(doc, gpuObj, "vendor", &gpu->vendor); yyjson_mut_obj_add_strbuf(doc, gpuObj, "name", &gpu->name); yyjson_mut_obj_add_strbuf(doc, gpuObj, "driver", &gpu->driver); yyjson_mut_obj_add_strbuf(doc, gpuObj, "platformApi", &gpu->platformApi); if (gpu->coreCount != FF_GPU_CORE_COUNT_UNSET) yyjson_mut_obj_add_int(doc, gpuObj, "coreCount", gpu->coreCount); else yyjson_mut_obj_add_null(doc, gpuObj, "coreCount"); yyjson_mut_obj_add_uint(doc, gpuObj, "frequency", gpu->frequency); yyjson_mut_val* memoryObj = yyjson_mut_obj_add_obj(doc, gpuObj, "memory"); { yyjson_mut_val* dedicatedMemory = yyjson_mut_obj_add_obj(doc, memoryObj, "dedicated"); if (gpu->dedicated.total != FF_GPU_VMEM_SIZE_UNSET) yyjson_mut_obj_add_uint(doc, dedicatedMemory, "total", gpu->dedicated.total); else yyjson_mut_obj_add_null(doc, dedicatedMemory, "total"); if (gpu->dedicated.used != FF_GPU_VMEM_SIZE_UNSET) yyjson_mut_obj_add_uint(doc, dedicatedMemory, "used", gpu->dedicated.total); else yyjson_mut_obj_add_null(doc, dedicatedMemory, "used"); } { yyjson_mut_val* sharedMemory = yyjson_mut_obj_add_obj(doc, memoryObj, "shared"); if (gpu->shared.total != FF_GPU_VMEM_SIZE_UNSET) yyjson_mut_obj_add_uint(doc, sharedMemory, "total", gpu->shared.total); else yyjson_mut_obj_add_null(doc, sharedMemory, "total"); if (gpu->shared.used != FF_GPU_VMEM_SIZE_UNSET) yyjson_mut_obj_add_uint(doc, sharedMemory, "used", gpu->shared.used); else yyjson_mut_obj_add_null(doc, sharedMemory, "used"); } yyjson_mut_obj_add_uint(doc, gpuObj, "deviceId", gpu->deviceId); } return true; } void ffInitOpenCLOptions(FFOpenCLOptions* options) { ffOptionInitModuleArg(&options->moduleArgs, ""); } void ffDestroyOpenCLOptions(FFOpenCLOptions* options) { ffOptionDestroyModuleArg(&options->moduleArgs); } FFModuleBaseInfo ffOpenCLModuleInfo = { .name = FF_OPENCL_MODULE_NAME, .description = "Print highest OpenCL version supported by the GPU", .initOptions = (void*) ffInitOpenCLOptions, .destroyOptions = (void*) ffDestroyOpenCLOptions, .parseJsonObject = (void*) ffParseOpenCLJsonObject, .printModule = (void*) ffPrintOpenCL, .generateJsonResult = (void*) ffGenerateOpenCLJsonResult, .generateJsonConfig = (void*) ffGenerateOpenCLJsonConfig, .formatArgs = FF_FORMAT_ARG_LIST(((FFModuleFormatArg[]) { {"Platform version", "version"}, {"Platform name", "name"}, {"Platform vendor", "vendor"}, })) }; ================================================ FILE: src/modules/opencl/opencl.h ================================================ #pragma once #include "option.h" #define FF_OPENCL_MODULE_NAME "OpenCL" bool ffPrintOpenCL(FFOpenCLOptions* options); void ffInitOpenCLOptions(FFOpenCLOptions* options); void ffDestroyOpenCLOptions(FFOpenCLOptions* options); extern FFModuleBaseInfo ffOpenCLModuleInfo; ================================================ FILE: src/modules/opencl/option.h ================================================ #pragma once #include "common/option.h" typedef struct FFOpenCLOptions { FFModuleArgs moduleArgs; } FFOpenCLOptions; static_assert(sizeof(FFOpenCLOptions) <= FF_OPTION_MAX_SIZE, "FFOpenCLOptions size exceeds maximum allowed size"); ================================================ FILE: src/modules/opengl/opengl.c ================================================ #include "common/printing.h" #include "common/jsonconfig.h" #include "common/stringUtils.h" #include "detection/opengl/opengl.h" #include "modules/opengl/opengl.h" bool ffPrintOpenGL(FFOpenGLOptions* options) { bool success = false; FFOpenGLResult result; ffStrbufInit(&result.version); ffStrbufInit(&result.renderer); ffStrbufInit(&result.vendor); ffStrbufInit(&result.slv); ffStrbufInit(&result.library); const char* error = ffDetectOpenGL(options, &result); if(error) { ffPrintError(FF_OPENGL_MODULE_NAME, 0, &options->moduleArgs, FF_PRINT_TYPE_DEFAULT, "%s", error); } else { if(options->moduleArgs.outputFormat.length == 0) { ffPrintLogoAndKey(FF_OPENGL_MODULE_NAME, 0, &options->moduleArgs, FF_PRINT_TYPE_DEFAULT); puts(result.version.chars); } else { FF_PRINT_FORMAT_CHECKED(FF_OPENGL_MODULE_NAME, 0, &options->moduleArgs, FF_PRINT_TYPE_DEFAULT, ((FFformatarg[]) { FF_ARG(result.version, "version"), FF_ARG(result.renderer, "renderer"), FF_ARG(result.vendor, "vendor"), FF_ARG(result.slv, "slv"), FF_ARG(result.library, "library"), })); } success = true; } ffStrbufDestroy(&result.version); ffStrbufDestroy(&result.renderer); ffStrbufDestroy(&result.vendor); ffStrbufDestroy(&result.slv); ffStrbufDestroy(&result.library); return success; } void ffParseOpenGLJsonObject(FFOpenGLOptions* options, yyjson_val* module) { yyjson_val *key, *val; size_t idx, max; yyjson_obj_foreach(module, idx, max, key, val) { if (ffJsonConfigParseModuleArgs(key, val, &options->moduleArgs)) continue; if (unsafe_yyjson_equals_str(key, "library")) { int value; const char* error = ffJsonConfigParseEnum(val, &value, (FFKeyValuePair[]) { { "auto", FF_OPENGL_LIBRARY_AUTO }, { "egl", FF_OPENGL_LIBRARY_EGL }, { "glx", FF_OPENGL_LIBRARY_GLX }, {}, }); if (error) ffPrintError(FF_OPENGL_MODULE_NAME, 0, &options->moduleArgs, FF_PRINT_TYPE_DEFAULT, "Invalid %s value: %s", unsafe_yyjson_get_str(key), error); else options->library = (FFOpenGLLibrary) value; continue; } ffPrintError(FF_OPENGL_MODULE_NAME, 0, &options->moduleArgs, FF_PRINT_TYPE_DEFAULT, "Unknown JSON key %s", unsafe_yyjson_get_str(key)); } } void ffGenerateOpenGLJsonConfig(FFOpenGLOptions* options, yyjson_mut_doc* doc, yyjson_mut_val* module) { ffJsonConfigGenerateModuleArgsConfig(doc, module, &options->moduleArgs); switch (options->library) { case FF_OPENGL_LIBRARY_AUTO: yyjson_mut_obj_add_str(doc, module, "library", "auto"); break; case FF_OPENGL_LIBRARY_EGL: yyjson_mut_obj_add_str(doc, module, "library", "egl"); break; case FF_OPENGL_LIBRARY_GLX: yyjson_mut_obj_add_str(doc, module, "library", "glx"); break; } } bool ffGenerateOpenGLJsonResult(FF_MAYBE_UNUSED FFOpenGLOptions* options, yyjson_mut_doc* doc, yyjson_mut_val* module) { bool success = false; FFOpenGLResult result; ffStrbufInit(&result.version); ffStrbufInit(&result.renderer); ffStrbufInit(&result.vendor); ffStrbufInit(&result.slv); ffStrbufInit(&result.library); const char* error = ffDetectOpenGL(options, &result); if(error != NULL) { yyjson_mut_obj_add_str(doc, module, "error", error); } else { yyjson_mut_val* obj = yyjson_mut_obj_add_obj(doc, module, "result"); yyjson_mut_obj_add_strbuf(doc, obj, "version", &result.version); yyjson_mut_obj_add_strbuf(doc, obj, "renderer", &result.renderer); yyjson_mut_obj_add_strbuf(doc, obj, "vendor", &result.vendor); yyjson_mut_obj_add_strbuf(doc, obj, "slv", &result.slv); yyjson_mut_obj_add_strbuf(doc, obj, "library", &result.library); success = true; } ffStrbufDestroy(&result.version); ffStrbufDestroy(&result.renderer); ffStrbufDestroy(&result.vendor); ffStrbufDestroy(&result.slv); ffStrbufDestroy(&result.library); return success; } void ffInitOpenGLOptions(FFOpenGLOptions* options) { ffOptionInitModuleArg(&options->moduleArgs, ""); options->library = FF_OPENGL_LIBRARY_AUTO; } void ffDestroyOpenGLOptions(FFOpenGLOptions* options) { ffOptionDestroyModuleArg(&options->moduleArgs); } FFModuleBaseInfo ffOpenGLModuleInfo = { .name = FF_OPENGL_MODULE_NAME, .description = "Print highest OpenGL version supported by the GPU", .initOptions = (void*) ffInitOpenGLOptions, .destroyOptions = (void*) ffDestroyOpenGLOptions, .parseJsonObject = (void*) ffParseOpenGLJsonObject, .printModule = (void*) ffPrintOpenGL, .generateJsonResult = (void*) ffGenerateOpenGLJsonResult, .generateJsonConfig = (void*) ffGenerateOpenGLJsonConfig, .formatArgs = FF_FORMAT_ARG_LIST(((FFModuleFormatArg[]) { {"OpenGL version", "version"}, {"OpenGL renderer", "renderer"}, {"OpenGL vendor", "vendor"}, {"OpenGL shading language version", "slv"}, {"OpenGL library used", "library"}, })) }; ================================================ FILE: src/modules/opengl/opengl.h ================================================ #pragma once #include "option.h" #define FF_OPENGL_MODULE_NAME "OpenGL" bool ffPrintOpenGL(FFOpenGLOptions* options); void ffInitOpenGLOptions(FFOpenGLOptions* options); void ffDestroyOpenGLOptions(FFOpenGLOptions* options); extern FFModuleBaseInfo ffOpenGLModuleInfo; ================================================ FILE: src/modules/opengl/option.h ================================================ #pragma once #include "common/option.h" typedef enum __attribute__((__packed__)) FFOpenGLLibrary { FF_OPENGL_LIBRARY_AUTO, FF_OPENGL_LIBRARY_EGL, FF_OPENGL_LIBRARY_GLX, } FFOpenGLLibrary; typedef struct FFOpenGLOptions { FFModuleArgs moduleArgs; FFOpenGLLibrary library; } FFOpenGLOptions; static_assert(sizeof(FFOpenGLOptions) <= FF_OPTION_MAX_SIZE, "FFOpenGLOptions size exceeds maximum allowed size"); ================================================ FILE: src/modules/options.h ================================================ #pragma once // For "fastfetch.h" #include "modules/battery/option.h" #include "modules/bios/option.h" #include "modules/bluetooth/option.h" #include "modules/bluetoothradio/option.h" #include "modules/board/option.h" #include "modules/bootmgr/option.h" #include "modules/break/option.h" #include "modules/brightness/option.h" #include "modules/btrfs/option.h" #include "modules/camera/option.h" #include "modules/chassis/option.h" #include "modules/cpu/option.h" #include "modules/cpucache/option.h" #include "modules/cpuusage/option.h" #include "modules/colors/option.h" #include "modules/cursor/option.h" #include "modules/custom/option.h" #include "modules/command/option.h" #include "modules/datetime/option.h" #include "modules/de/option.h" #include "modules/disk/option.h" #include "modules/diskio/option.h" #include "modules/display/option.h" #include "modules/dns/option.h" #include "modules/editor/option.h" #include "modules/font/option.h" #include "modules/host/option.h" #include "modules/gamepad/option.h" #include "modules/gpu/option.h" #include "modules/icons/option.h" #include "modules/initsystem/option.h" #include "modules/kernel/option.h" #include "modules/keyboard/option.h" #include "modules/loadavg/option.h" #include "modules/locale/option.h" #include "modules/lm/option.h" #include "modules/localip/option.h" #include "modules/media/option.h" #include "modules/memory/option.h" #include "modules/monitor/option.h" #include "modules/mouse/option.h" #include "modules/netio/option.h" #include "modules/opengl/option.h" #include "modules/opencl/option.h" #include "modules/os/option.h" #include "modules/packages/option.h" #include "modules/physicaldisk/option.h" #include "modules/physicalmemory/option.h" #include "modules/player/option.h" #include "modules/poweradapter/option.h" #include "modules/processes/option.h" #include "modules/publicip/option.h" #include "modules/separator/option.h" #include "modules/shell/option.h" #include "modules/sound/option.h" #include "modules/swap/option.h" #include "modules/terminal/option.h" #include "modules/terminalfont/option.h" #include "modules/terminalsize/option.h" #include "modules/terminaltheme/option.h" #include "modules/theme/option.h" #include "modules/title/option.h" #include "modules/tpm/option.h" #include "modules/uptime/option.h" #include "modules/users/option.h" #include "modules/version/option.h" #include "modules/vulkan/option.h" #include "modules/wallpaper/option.h" #include "modules/weather/option.h" #include "modules/wifi/option.h" #include "modules/wm/option.h" #include "modules/wmtheme/option.h" #include "modules/zpool/option.h" ================================================ FILE: src/modules/os/option.h ================================================ #pragma once #include "common/option.h" typedef struct FFOSOptions { FFModuleArgs moduleArgs; } FFOSOptions; static_assert(sizeof(FFOSOptions) <= FF_OPTION_MAX_SIZE, "FFOSOptions size exceeds maximum allowed size"); ================================================ FILE: src/modules/os/os.c ================================================ #include "common/printing.h" #include "common/jsonconfig.h" #include "common/option.h" #include "common/stringUtils.h" #include "detection/os/os.h" #include "modules/os/os.h" #include static void buildOutputDefault(const FFOSResult* os, FFstrbuf* result) { //Create the basic output if(os->name.length > 0) ffStrbufAppend(result, &os->name); else if(os->prettyName.length > 0) ffStrbufAppend(result, &os->prettyName); else if(os->id.length > 0) ffStrbufAppend(result, &os->id); else ffStrbufAppend(result, &instance.state.platform.sysinfo.name); //Append code name if it is missing if(os->codename.length > 0 && !ffStrbufContainIgnCase(result, &os->codename)) { ffStrbufAppendC(result, ' '); ffStrbufAppend(result, &os->codename); } //Append version if it is missing if(os->versionID.length > 0 && !ffStrbufContainIgnCase(result, &os->versionID)) { ffStrbufAppendC(result, ' '); ffStrbufAppend(result, &os->versionID); } else if(os->versionID.length == 0 && os->version.length > 0 && !ffStrbufContainIgnCase(result, &os->version)) { ffStrbufAppendC(result, ' '); ffStrbufAppend(result, &os->version); } //Append variant if it is missing if(os->variant.length > 0 && !ffStrbufContainIgnCase(result, &os->variant)) { ffStrbufAppendS(result, " ("); ffStrbufAppend(result, &os->variant); ffStrbufAppendC(result, ')'); } else if(os->variant.length == 0 && os->variantID.length > 0 && !ffStrbufContainIgnCase(result, &os->variantID)) { ffStrbufAppendS(result, " ("); ffStrbufAppend(result, &os->variantID); ffStrbufAppendC(result, ')'); } } bool ffPrintOS(FFOSOptions* options) { const FFOSResult* os = ffDetectOS(); if(os->name.length == 0 && os->prettyName.length == 0 && os->id.length == 0) { ffPrintError(FF_OS_MODULE_NAME, 0, &options->moduleArgs, FF_PRINT_TYPE_DEFAULT, "Could not detect OS"); return false; } FF_STRBUF_AUTO_DESTROY key = ffStrbufCreate(); if(options->moduleArgs.key.length == 0) ffStrbufSetStatic(&key, FF_OS_MODULE_NAME); else { FF_PARSE_FORMAT_STRING_CHECKED(&key, &options->moduleArgs.key, ((FFformatarg[]) { FF_ARG(instance.state.platform.sysinfo.name, "sysname"), FF_ARG(os->name, "name"), FF_ARG(options->moduleArgs.keyIcon, "icon"), })); } if(options->moduleArgs.outputFormat.length == 0) { FF_STRBUF_AUTO_DESTROY result = ffStrbufCreate(); if(os->prettyName.length > 0) ffStrbufAppend(&result, &os->prettyName); else buildOutputDefault(os, &result); //Append architecture if it is missing if(!ffStrbufContainIgnCase(&result, &instance.state.platform.sysinfo.architecture)) { ffStrbufAppendC(&result, ' '); ffStrbufAppend(&result, &instance.state.platform.sysinfo.architecture); } ffPrintLogoAndKey(key.chars, 0, &options->moduleArgs, FF_PRINT_TYPE_NO_CUSTOM_KEY); ffStrbufPutTo(&result, stdout); } else { FF_PRINT_FORMAT_CHECKED(key.chars, 0, &options->moduleArgs, FF_PRINT_TYPE_NO_CUSTOM_KEY, ((FFformatarg[]){ FF_ARG(instance.state.platform.sysinfo.name, "sysname"), FF_ARG(os->name, "name"), FF_ARG(os->prettyName, "pretty-name"), FF_ARG(os->id, "id"), FF_ARG(os->idLike, "id-like"), FF_ARG(os->variant, "variant"), FF_ARG(os->variantID, "variant-id"), FF_ARG(os->version, "version"), FF_ARG(os->versionID, "version-id"), FF_ARG(os->codename, "codename"), FF_ARG(os->buildID, "build-id"), FF_ARG(instance.state.platform.sysinfo.architecture, "arch"), FF_ARG(instance.state.platform.sysinfo.release, "kernel-release"), })); } return true; } void ffParseOSJsonObject(FFOSOptions* options, yyjson_val* module) { yyjson_val *key, *val; size_t idx, max; yyjson_obj_foreach(module, idx, max, key, val) { if (ffJsonConfigParseModuleArgs(key, val, &options->moduleArgs)) continue; ffPrintError(FF_OS_MODULE_NAME, 0, &options->moduleArgs, FF_PRINT_TYPE_DEFAULT, "Unknown JSON key %s", unsafe_yyjson_get_str(key)); } } void ffGenerateOSJsonConfig(FFOSOptions* options, yyjson_mut_doc* doc, yyjson_mut_val* module) { ffJsonConfigGenerateModuleArgsConfig(doc, module, &options->moduleArgs); } bool ffGenerateOSJsonResult(FF_MAYBE_UNUSED FFOSOptions* options, yyjson_mut_doc* doc, yyjson_mut_val* module) { const FFOSResult* os = ffDetectOS(); if(os->name.length == 0 && os->prettyName.length == 0 && os->id.length == 0) { yyjson_mut_obj_add_str(doc, module, "error", "Could not detect OS"); return false; } yyjson_mut_val* obj = yyjson_mut_obj_add_obj(doc, module, "result"); yyjson_mut_obj_add_strbuf(doc, obj, "buildID", &os->buildID); yyjson_mut_obj_add_strbuf(doc, obj, "codename", &os->codename); yyjson_mut_obj_add_strbuf(doc, obj, "id", &os->id); yyjson_mut_obj_add_strbuf(doc, obj, "idLike", &os->idLike); yyjson_mut_obj_add_strbuf(doc, obj, "name", &os->name); yyjson_mut_obj_add_strbuf(doc, obj, "prettyName", &os->prettyName); yyjson_mut_obj_add_strbuf(doc, obj, "variant", &os->variant); yyjson_mut_obj_add_strbuf(doc, obj, "variantID", &os->variantID); yyjson_mut_obj_add_strbuf(doc, obj, "version", &os->version); yyjson_mut_obj_add_strbuf(doc, obj, "versionID", &os->versionID); return true; } void ffInitOSOptions(FFOSOptions* options) { ffOptionInitModuleArg(&options->moduleArgs, #ifdef _WIN32 "" #elif __APPLE__ "" #elif __FreeBSD__ "󰣠" #elif __ANDROID__ "" #elif __linux__ "" #elif __sun "" #elif __OpenBSD__ "" #elif __Haiku__ "" #else "󰢻" #endif ); } void ffDestroyOSOptions(FFOSOptions* options) { ffOptionDestroyModuleArg(&options->moduleArgs); } FFModuleBaseInfo ffOSModuleInfo = { .name = FF_OS_MODULE_NAME, .description = "Print operating system name and version", .initOptions = (void*) ffInitOSOptions, .destroyOptions = (void*) ffDestroyOSOptions, .parseJsonObject = (void*) ffParseOSJsonObject, .printModule = (void*) ffPrintOS, .generateJsonResult = (void*) ffGenerateOSJsonResult, .generateJsonConfig = (void*) ffGenerateOSJsonConfig, .formatArgs = FF_FORMAT_ARG_LIST(((FFModuleFormatArg[]) { {"Name of the kernel", "sysname"}, {"Name of the OS", "name"}, {"Pretty name of the OS, if available", "pretty-name"}, {"ID of the OS", "id"}, {"ID like of the OS", "id-like"}, {"Variant of the OS", "variant"}, {"Variant ID of the OS", "variant-id"}, {"Version of the OS", "version"}, {"Version ID of the OS", "version-id"}, {"Version codename of the OS", "codename"}, {"Build ID of the OS", "build-id"}, {"Architecture of the OS", "arch"}, })) }; ================================================ FILE: src/modules/os/os.h ================================================ #pragma once #include "option.h" #define FF_OS_MODULE_NAME "OS" bool ffPrintOS(FFOSOptions* options); void ffInitOSOptions(FFOSOptions* options); void ffDestroyOSOptions(FFOSOptions* options); extern FFModuleBaseInfo ffOSModuleInfo; ================================================ FILE: src/modules/packages/option.h ================================================ #pragma once #include "common/option.h" typedef enum __attribute__((__packed__)) FFPackagesFlags { FF_PACKAGES_FLAG_NONE = 0, FF_PACKAGES_FLAG_APK_BIT = 1ULL << 0, FF_PACKAGES_FLAG_BREW_BIT = 1ULL << 1, FF_PACKAGES_FLAG_CHOCO_BIT = 1ULL << 2, FF_PACKAGES_FLAG_DPKG_BIT = 1ULL << 3, FF_PACKAGES_FLAG_EMERGE_BIT = 1ULL << 4, FF_PACKAGES_FLAG_EOPKG_BIT = 1ULL << 5, FF_PACKAGES_FLAG_FLATPAK_BIT = 1ULL << 6, FF_PACKAGES_FLAG_NIX_BIT = 1ULL << 7, FF_PACKAGES_FLAG_OPKG_BIT = 1ULL << 8, FF_PACKAGES_FLAG_PACMAN_BIT = 1ULL << 9, FF_PACKAGES_FLAG_PALUDIS_BIT = 1ULL << 10, FF_PACKAGES_FLAG_PKG_BIT = 1ULL << 11, FF_PACKAGES_FLAG_PKGTOOL_BIT = 1ULL << 12, FF_PACKAGES_FLAG_MACPORTS_BIT = 1ULL << 13, FF_PACKAGES_FLAG_RPM_BIT = 1ULL << 14, FF_PACKAGES_FLAG_SCOOP_BIT = 1ULL << 15, FF_PACKAGES_FLAG_SNAP_BIT = 1ULL << 16, FF_PACKAGES_FLAG_WINGET_BIT = 1ULL << 17, FF_PACKAGES_FLAG_XBPS_BIT = 1ULL << 18, FF_PACKAGES_FLAG_AM_BIT = 1ULL << 19, FF_PACKAGES_FLAG_SORCERY_BIT = 1ULL << 20, FF_PACKAGES_FLAG_LPKG_BIT = 1ULL << 21, FF_PACKAGES_FLAG_LPKGBUILD_BIT = 1ULL << 22, FF_PACKAGES_FLAG_GUIX_BIT = 1ULL << 23, FF_PACKAGES_FLAG_LINGLONG_BIT = 1ULL << 24, FF_PACKAGES_FLAG_PACSTALL_BIT = 1ULL << 25, FF_PACKAGES_FLAG_MPORT_BIT = 1ULL << 26, FF_PACKAGES_FLAG_PKGSRC_BIT = 1ULL << 27, FF_PACKAGES_FLAG_HPKG_BIT = 1ULL << 28, FF_PACKAGES_FLAG_PISI_BIT = 1ULL << 29, FF_PACKAGES_FLAG_SOAR_BIT = 1ULL << 30, FF_PACKAGES_FLAG_KISS_BIT = 1ULL << 31, FF_PACKAGES_FLAG_MOSS_BIT = 1ULL << 32, FF_PACKAGES_FLAG_FORCE_UNSIGNED = UINT64_MAX, } FFPackagesFlags; static_assert(sizeof(FFPackagesFlags) == sizeof(uint64_t), ""); typedef struct FFPackagesOptions { FFModuleArgs moduleArgs; FFPackagesFlags disabled; bool combined; } FFPackagesOptions; static_assert(sizeof(FFPackagesOptions) <= FF_OPTION_MAX_SIZE, "FFPackagesOptions size exceeds maximum allowed size"); ================================================ FILE: src/modules/packages/packages.c ================================================ #include "common/printing.h" #include "common/jsonconfig.h" #include "common/stringUtils.h" #include "detection/packages/packages.h" #include "modules/packages/packages.h" bool ffPrintPackages(FFPackagesOptions* options) { FFPackagesResult counts = {}; ffStrbufInit(&counts.pacmanBranch); const char* error = ffDetectPackages(&counts, options); if(error) { ffPrintError(FF_PACKAGES_MODULE_NAME, 0, &options->moduleArgs, FF_PRINT_TYPE_DEFAULT, "%s", error); return false; } uint32_t nixAll = counts.nixDefault + counts.nixSystem + counts.nixUser; uint32_t flatpakAll = counts.flatpakSystem + counts.flatpakUser; uint32_t brewAll = counts.brew + counts.brewCask; uint32_t guixAll = counts.guixSystem + counts.guixUser + counts.guixHome; uint32_t hpkgAll = counts.hpkgSystem + counts.hpkgUser; uint32_t amAll = counts.amSystem + counts.amUser; uint32_t scoopAll = counts.scoopUser + counts.scoopGlobal; if(options->moduleArgs.outputFormat.length == 0) { ffPrintLogoAndKey(FF_PACKAGES_MODULE_NAME, 0, &options->moduleArgs, FF_PRINT_TYPE_DEFAULT); #define FF_PRINT_PACKAGE_NAME(var, name) {\ if(counts.var > 0) \ { \ printf("%u (%s)", counts.var, (name)); \ if((all -= counts.var) > 0) \ fputs(", ", stdout); \ } \ } #define FF_PRINT_PACKAGE(name) FF_PRINT_PACKAGE_NAME(name, #name) #define FF_PRINT_PACKAGE_ALL(name) {\ if(name ## All > 0) \ { \ printf("%u (%s)", name ## All, #name); \ if((all -= name ## All) > 0) \ fputs(", ", stdout); \ } \ } uint32_t all = counts.all; if(counts.pacman > 0) { printf("%u (pacman)", counts.pacman); if(counts.pacmanBranch.length > 0) printf("[%s]", counts.pacmanBranch.chars); if((all -= counts.pacman) > 0) printf(", "); }; FF_PRINT_PACKAGE(dpkg) FF_PRINT_PACKAGE(rpm) FF_PRINT_PACKAGE(emerge) FF_PRINT_PACKAGE(eopkg) FF_PRINT_PACKAGE(xbps) if (options->combined) { FF_PRINT_PACKAGE_ALL(nix); } else { FF_PRINT_PACKAGE_NAME(nixSystem, "nix-system") FF_PRINT_PACKAGE_NAME(nixUser, "nix-user") FF_PRINT_PACKAGE_NAME(nixDefault, "nix-default") } FF_PRINT_PACKAGE(apk) FF_PRINT_PACKAGE(pkg) FF_PRINT_PACKAGE(pkgsrc) FF_PRINT_PACKAGE(kiss) if (options->combined) { FF_PRINT_PACKAGE_ALL(hpkg) } else { FF_PRINT_PACKAGE_NAME(hpkgSystem, counts.hpkgUser ? "hpkg-system" : "hpkg") FF_PRINT_PACKAGE_NAME(hpkgUser, "hpkg-user") } if (options->combined) { FF_PRINT_PACKAGE_ALL(flatpak); } else { FF_PRINT_PACKAGE_NAME(flatpakSystem, counts.flatpakUser ? "flatpak-system" : "flatpak") FF_PRINT_PACKAGE_NAME(flatpakUser, "flatpak-user") } FF_PRINT_PACKAGE(snap) if (options->combined) { FF_PRINT_PACKAGE_ALL(brew); } else { FF_PRINT_PACKAGE_NAME(brew, "brew") FF_PRINT_PACKAGE_NAME(brewCask, "brew-cask") } FF_PRINT_PACKAGE(macports) if (options->combined) { FF_PRINT_PACKAGE_ALL(scoop); } else { FF_PRINT_PACKAGE_NAME(scoopUser, counts.scoopGlobal ? "scoop-user" : "scoop") FF_PRINT_PACKAGE_NAME(scoopGlobal, "scoop-global") } FF_PRINT_PACKAGE(choco) FF_PRINT_PACKAGE(pkgtool) FF_PRINT_PACKAGE(paludis) FF_PRINT_PACKAGE(winget) FF_PRINT_PACKAGE(opkg) if (options->combined) { FF_PRINT_PACKAGE_ALL(am); } else { FF_PRINT_PACKAGE_NAME(amSystem, "am") FF_PRINT_PACKAGE_NAME(amUser, "appman") } FF_PRINT_PACKAGE(sorcery) FF_PRINT_PACKAGE(lpkg) FF_PRINT_PACKAGE(lpkgbuild) if (options->combined) { FF_PRINT_PACKAGE_ALL(guix); } else { FF_PRINT_PACKAGE_NAME(guixSystem, "guix-system") FF_PRINT_PACKAGE_NAME(guixUser, "guix-user") FF_PRINT_PACKAGE_NAME(guixHome, "guix-home") } FF_PRINT_PACKAGE(linglong) FF_PRINT_PACKAGE(pacstall) FF_PRINT_PACKAGE(mport) FF_PRINT_PACKAGE(pisi) FF_PRINT_PACKAGE(soar) FF_PRINT_PACKAGE(moss) putchar('\n'); } else { FF_PRINT_FORMAT_CHECKED(FF_PACKAGES_MODULE_NAME, 0, &options->moduleArgs, FF_PRINT_TYPE_DEFAULT, ((FFformatarg[]){ FF_ARG(counts.all, "all"), FF_ARG(counts.pacman, "pacman"), FF_ARG(counts.pacmanBranch, "pacman-branch"), FF_ARG(counts.dpkg, "dpkg"), FF_ARG(counts.rpm, "rpm"), FF_ARG(counts.emerge, "emerge"), FF_ARG(counts.eopkg, "eopkg"), FF_ARG(counts.xbps, "xbps"), FF_ARG(counts.nixSystem, "nix-system"), FF_ARG(counts.nixUser, "nix-user"), FF_ARG(counts.nixDefault, "nix-default"), FF_ARG(counts.apk, "apk"), FF_ARG(counts.pkg, "pkg"), FF_ARG(counts.flatpakSystem, "flatpak-system"), FF_ARG(counts.flatpakUser, "flatpak-user"), FF_ARG(counts.snap, "snap"), FF_ARG(counts.brew, "brew"), FF_ARG(counts.brewCask, "brew-cask"), FF_ARG(counts.macports, "macports"), FF_ARG(counts.scoopUser, "scoop-user"), FF_ARG(counts.scoopGlobal, "scoop-global"), FF_ARG(counts.choco, "choco"), FF_ARG(counts.pkgtool, "pkgtool"), FF_ARG(counts.paludis, "paludis"), FF_ARG(counts.winget, "winget"), FF_ARG(counts.opkg, "opkg"), FF_ARG(counts.amSystem, "am-system"), FF_ARG(counts.sorcery, "sorcery"), FF_ARG(counts.lpkg, "lpkg"), FF_ARG(counts.lpkgbuild, "lpkgbuild"), FF_ARG(counts.guixSystem, "guix-system"), FF_ARG(counts.guixUser, "guix-user"), FF_ARG(counts.guixHome, "guix-home"), FF_ARG(counts.linglong, "linglong"), FF_ARG(counts.pacstall, "pacstall"), FF_ARG(counts.mport, "mport"), FF_ARG(counts.amUser, "am-user"), FF_ARG(counts.pkgsrc, "pkgsrc"), FF_ARG(counts.hpkgSystem, "hpkg-system"), FF_ARG(counts.hpkgUser, "hpkg-user"), FF_ARG(counts.pisi, "pisi"), FF_ARG(counts.soar, "soar"), FF_ARG(counts.kiss, "kiss"), FF_ARG(counts.moss, "moss"), FF_ARG(nixAll, "nix-all"), FF_ARG(flatpakAll, "flatpak-all"), FF_ARG(brewAll, "brew-all"), FF_ARG(guixAll, "guix-all"), FF_ARG(hpkgAll, "hpkg-all"), })); } ffStrbufDestroy(&counts.pacmanBranch); return true; } void ffParsePackagesJsonObject(FFPackagesOptions* options, yyjson_val* module) { yyjson_val *key, *val; size_t idx, max; yyjson_obj_foreach(module, idx, max, key, val) { if (ffJsonConfigParseModuleArgs(key, val, &options->moduleArgs)) continue; if (unsafe_yyjson_equals_str(key, "disabled")) { if (!yyjson_is_null(val) && !yyjson_is_arr(val)) { ffPrintError(FF_PACKAGES_MODULE_NAME, 0, &options->moduleArgs, FF_PRINT_TYPE_DEFAULT, "Invalid JSON value for %s", unsafe_yyjson_get_str(key)); continue; } options->disabled = FF_PACKAGES_FLAG_NONE; if (yyjson_is_arr(val)) { yyjson_val* flagObj; size_t flagIdx, flagMax; yyjson_arr_foreach(val, flagIdx, flagMax, flagObj) { if (!yyjson_is_str(flagObj)) { ffPrintError(FF_PACKAGES_MODULE_NAME, 0, &options->moduleArgs, FF_PRINT_TYPE_DEFAULT, "Invalid JSON value for %s", unsafe_yyjson_get_str(key)); continue; } const char* flag = unsafe_yyjson_get_str(flagObj); #define FF_TEST_PACKAGE_NAME(name) else if (ffStrEqualsIgnCase(flag, #name)) { options->disabled |= FF_PACKAGES_FLAG_ ## name ## _BIT; } switch (toupper(flag[0])) { case 'A': if (false); FF_TEST_PACKAGE_NAME(APK) FF_TEST_PACKAGE_NAME(AM) break; case 'B': if (false); FF_TEST_PACKAGE_NAME(BREW) break; case 'C': if (false); FF_TEST_PACKAGE_NAME(CHOCO) break; case 'D': if (false); FF_TEST_PACKAGE_NAME(DPKG) break; case 'E': if (false); FF_TEST_PACKAGE_NAME(EMERGE) FF_TEST_PACKAGE_NAME(EOPKG) break; case 'F': if (false); FF_TEST_PACKAGE_NAME(FLATPAK) break; case 'G': if (false); FF_TEST_PACKAGE_NAME(GUIX) break; case 'H': if (false); FF_TEST_PACKAGE_NAME(HPKG) break; case 'K': if (false); FF_TEST_PACKAGE_NAME(KISS) break; case 'L': if (false); FF_TEST_PACKAGE_NAME(LPKG) FF_TEST_PACKAGE_NAME(LPKGBUILD) FF_TEST_PACKAGE_NAME(LINGLONG) break; case 'M': if (false); FF_TEST_PACKAGE_NAME(MACPORTS) FF_TEST_PACKAGE_NAME(MPORT) FF_TEST_PACKAGE_NAME(MOSS) break; case 'N': if (false); FF_TEST_PACKAGE_NAME(NIX) break; case 'O': if (false); FF_TEST_PACKAGE_NAME(OPKG) break; case 'P': if (false); FF_TEST_PACKAGE_NAME(PACMAN) FF_TEST_PACKAGE_NAME(PACSTALL) FF_TEST_PACKAGE_NAME(PALUDIS) FF_TEST_PACKAGE_NAME(PISI) FF_TEST_PACKAGE_NAME(PKG) FF_TEST_PACKAGE_NAME(PKGTOOL) FF_TEST_PACKAGE_NAME(PKGSRC) break; case 'R': if (false); FF_TEST_PACKAGE_NAME(RPM) break; case 'S': if (false); FF_TEST_PACKAGE_NAME(SCOOP) FF_TEST_PACKAGE_NAME(SNAP) FF_TEST_PACKAGE_NAME(SOAR) FF_TEST_PACKAGE_NAME(SORCERY) break; case 'W': if (false); FF_TEST_PACKAGE_NAME(WINGET) break; case 'X': if (false); FF_TEST_PACKAGE_NAME(XBPS) break; } #undef FF_TEST_PACKAGE_NAME } continue; } } if (unsafe_yyjson_equals_str(key, "combined")) { options->combined = yyjson_get_bool(val); continue; } ffPrintError(FF_PACKAGES_MODULE_NAME, 0, &options->moduleArgs, FF_PRINT_TYPE_DEFAULT, "Unknown JSON key %s", unsafe_yyjson_get_str(key)); } } void ffGeneratePackagesJsonConfig(FFPackagesOptions* options, yyjson_mut_doc* doc, yyjson_mut_val* module) { ffJsonConfigGenerateModuleArgsConfig(doc, module, &options->moduleArgs); FF_STRBUF_AUTO_DESTROY buf = ffStrbufCreate(); yyjson_mut_val* arr = yyjson_mut_obj_add_arr(doc, module, "disabled"); #define FF_TEST_PACKAGE_NAME(name) else if ((options->disabled & FF_PACKAGES_FLAG_ ## name ## _BIT)) { \ ffStrbufSetS(&buf, #name); \ ffStrbufLowerCase(&buf); \ yyjson_mut_arr_add_strbuf(doc, arr, &buf); \ } if (false); FF_TEST_PACKAGE_NAME(AM) FF_TEST_PACKAGE_NAME(APK) FF_TEST_PACKAGE_NAME(BREW) FF_TEST_PACKAGE_NAME(CHOCO) FF_TEST_PACKAGE_NAME(DPKG) FF_TEST_PACKAGE_NAME(EMERGE) FF_TEST_PACKAGE_NAME(EOPKG) FF_TEST_PACKAGE_NAME(FLATPAK) FF_TEST_PACKAGE_NAME(GUIX) FF_TEST_PACKAGE_NAME(HPKG) FF_TEST_PACKAGE_NAME(KISS) FF_TEST_PACKAGE_NAME(LINGLONG) FF_TEST_PACKAGE_NAME(LPKG) FF_TEST_PACKAGE_NAME(LPKGBUILD) FF_TEST_PACKAGE_NAME(MACPORTS) FF_TEST_PACKAGE_NAME(MPORT) FF_TEST_PACKAGE_NAME(MOSS) FF_TEST_PACKAGE_NAME(NIX) FF_TEST_PACKAGE_NAME(OPKG) FF_TEST_PACKAGE_NAME(PACMAN) FF_TEST_PACKAGE_NAME(PACSTALL) FF_TEST_PACKAGE_NAME(PALUDIS) FF_TEST_PACKAGE_NAME(PISI) FF_TEST_PACKAGE_NAME(PKG) FF_TEST_PACKAGE_NAME(PKGTOOL) FF_TEST_PACKAGE_NAME(PKGSRC) FF_TEST_PACKAGE_NAME(RPM) FF_TEST_PACKAGE_NAME(SCOOP) FF_TEST_PACKAGE_NAME(SNAP) FF_TEST_PACKAGE_NAME(SOAR) FF_TEST_PACKAGE_NAME(SORCERY) FF_TEST_PACKAGE_NAME(WINGET) FF_TEST_PACKAGE_NAME(XBPS) #undef FF_TEST_PACKAGE_NAME yyjson_mut_obj_add_bool(doc, module, "combined", options->combined); } bool ffGeneratePackagesJsonResult(FF_MAYBE_UNUSED FFPackagesOptions* options, yyjson_mut_doc* doc, yyjson_mut_val* module) { FFPackagesResult counts = {}; ffStrbufInit(&counts.pacmanBranch); const char* error = ffDetectPackages(&counts, options); if(error) { yyjson_mut_obj_add_str(doc, module, "error", error); return false; } yyjson_mut_val* obj = yyjson_mut_obj_add_obj(doc, module, "result"); #define FF_APPEND_PACKAGE_COUNT(name) yyjson_mut_obj_add_uint(doc, obj, #name, counts.name); FF_APPEND_PACKAGE_COUNT(all) FF_APPEND_PACKAGE_COUNT(amSystem) FF_APPEND_PACKAGE_COUNT(amUser) FF_APPEND_PACKAGE_COUNT(apk) FF_APPEND_PACKAGE_COUNT(brew) FF_APPEND_PACKAGE_COUNT(brewCask) FF_APPEND_PACKAGE_COUNT(choco) FF_APPEND_PACKAGE_COUNT(dpkg) FF_APPEND_PACKAGE_COUNT(emerge) FF_APPEND_PACKAGE_COUNT(eopkg) FF_APPEND_PACKAGE_COUNT(flatpakSystem) FF_APPEND_PACKAGE_COUNT(flatpakUser) FF_APPEND_PACKAGE_COUNT(guixSystem) FF_APPEND_PACKAGE_COUNT(guixUser) FF_APPEND_PACKAGE_COUNT(guixHome) FF_APPEND_PACKAGE_COUNT(hpkgSystem) FF_APPEND_PACKAGE_COUNT(hpkgUser) FF_APPEND_PACKAGE_COUNT(kiss) FF_APPEND_PACKAGE_COUNT(linglong) FF_APPEND_PACKAGE_COUNT(macports) FF_APPEND_PACKAGE_COUNT(mport) FF_APPEND_PACKAGE_COUNT(moss) FF_APPEND_PACKAGE_COUNT(nixDefault) FF_APPEND_PACKAGE_COUNT(nixSystem) FF_APPEND_PACKAGE_COUNT(nixUser) FF_APPEND_PACKAGE_COUNT(opkg) FF_APPEND_PACKAGE_COUNT(pacman) FF_APPEND_PACKAGE_COUNT(pacstall) FF_APPEND_PACKAGE_COUNT(paludis) FF_APPEND_PACKAGE_COUNT(pisi) FF_APPEND_PACKAGE_COUNT(pkg) FF_APPEND_PACKAGE_COUNT(pkgtool) FF_APPEND_PACKAGE_COUNT(pkgsrc) FF_APPEND_PACKAGE_COUNT(rpm) FF_APPEND_PACKAGE_COUNT(scoopUser) FF_APPEND_PACKAGE_COUNT(scoopGlobal) FF_APPEND_PACKAGE_COUNT(snap) FF_APPEND_PACKAGE_COUNT(soar) FF_APPEND_PACKAGE_COUNT(sorcery) FF_APPEND_PACKAGE_COUNT(winget) FF_APPEND_PACKAGE_COUNT(xbps) yyjson_mut_obj_add_strbuf(doc, obj, "pacmanBranch", &counts.pacmanBranch); return true; } void ffInitPackagesOptions(FFPackagesOptions* options) { ffOptionInitModuleArg(&options->moduleArgs, "󰏖"); options->disabled = FF_PACKAGES_DISABLE_LIST; options->combined = false; } void ffDestroyPackagesOptions(FFPackagesOptions* options) { ffOptionDestroyModuleArg(&options->moduleArgs); } FFModuleBaseInfo ffPackagesModuleInfo = { .name = FF_PACKAGES_MODULE_NAME, .description = "List installed package managers and count of installed packages", .initOptions = (void*) ffInitPackagesOptions, .destroyOptions = (void*) ffDestroyPackagesOptions, .parseJsonObject = (void*) ffParsePackagesJsonObject, .printModule = (void*) ffPrintPackages, .generateJsonResult = (void*) ffGeneratePackagesJsonResult, .generateJsonConfig = (void*) ffGeneratePackagesJsonConfig, .formatArgs = FF_FORMAT_ARG_LIST(((FFModuleFormatArg[]) { {"Number of all packages", "all"}, {"Number of pacman packages", "pacman"}, {"Pacman branch on manjaro", "pacman-branch"}, {"Number of dpkg packages", "dpkg"}, {"Number of rpm packages", "rpm"}, {"Number of emerge packages", "emerge"}, {"Number of eopkg packages", "eopkg"}, {"Number of xbps packages", "xbps"}, {"Number of nix-system packages", "nix-system"}, {"Number of nix-user packages", "nix-user"}, {"Number of nix-default packages", "nix-default"}, {"Number of apk packages", "apk"}, {"Number of pkg packages", "pkg"}, {"Number of flatpak-system app packages", "flatpak-system"}, {"Number of flatpak-user app packages", "flatpak-user"}, {"Number of snap packages", "snap"}, {"Number of brew packages", "brew"}, {"Number of brew-cask packages", "brew-cask"}, {"Number of macports packages", "macports"}, {"Number of scoop-user packages", "scoop-user"}, {"Number of scoop-global packages", "scoop-global"}, {"Number of choco packages", "choco"}, {"Number of pkgtool packages", "pkgtool"}, {"Number of paludis packages", "paludis"}, {"Number of winget packages", "winget"}, {"Number of opkg packages", "opkg"}, {"Number of am-system packages", "am-system"}, {"Number of sorcery packages", "sorcery"}, {"Number of lpkg packages", "lpkg"}, {"Number of lpkgbuild packages", "lpkgbuild"}, {"Number of guix-system packages", "guix-system"}, {"Number of guix-user packages", "guix-user"}, {"Number of guix-home packages", "guix-home"}, {"Number of linglong packages", "linglong"}, {"Number of pacstall packages", "pacstall"}, {"Number of mport packages", "mport"}, {"Number of am-user (aka appman) packages", "am-user"}, {"Number of pkgsrc packages", "pkgsrc"}, {"Number of hpkg-system packages", "hpkg-system"}, {"Number of hpkg-user packages", "hpkg-user"}, {"Number of pisi packages", "pisi"}, {"Number of soar packages", "soar"}, {"Number of kiss packages", "kiss"}, {"Number of moss packages", "moss"}, {"Total number of all nix packages", "nix-all"}, {"Total number of all flatpak app packages", "flatpak-all"}, {"Total number of all brew packages", "brew-all"}, {"Total number of all guix packages", "guix-all"}, {"Total number of all hpkg packages", "hpkg-all"}, })) }; ================================================ FILE: src/modules/packages/packages.h ================================================ #pragma once #include "option.h" #define FF_PACKAGES_MODULE_NAME "Packages" bool ffPrintPackages(FFPackagesOptions* options); void ffInitPackagesOptions(FFPackagesOptions* options); void ffDestroyPackagesOptions(FFPackagesOptions* options); extern FFModuleBaseInfo ffPackagesModuleInfo; ================================================ FILE: src/modules/physicaldisk/option.h ================================================ #pragma once #include "common/option.h" typedef struct FFPhysicalDiskOptions { FFModuleArgs moduleArgs; FFstrbuf namePrefix; bool temp; FFColorRangeConfig tempConfig; } FFPhysicalDiskOptions; static_assert(sizeof(FFPhysicalDiskOptions) <= FF_OPTION_MAX_SIZE, "FFPhysicalDiskOptions size exceeds maximum allowed size"); ================================================ FILE: src/modules/physicaldisk/physicaldisk.c ================================================ #include "common/printing.h" #include "common/jsonconfig.h" #include "common/temps.h" #include "common/size.h" #include "common/stringUtils.h" #include "detection/physicaldisk/physicaldisk.h" #include "modules/physicaldisk/physicaldisk.h" #define FF_PHYSICALDISK_DISPLAY_NAME "Physical Disk" static int sortDevices(const FFPhysicalDiskResult* left, const FFPhysicalDiskResult* right) { return ffStrbufComp(&left->name, &right->name); } static void formatKey(const FFPhysicalDiskOptions* options, FFPhysicalDiskResult* dev, uint32_t index, FFstrbuf* key) { if(options->moduleArgs.key.length == 0) { ffStrbufSetF(key, FF_PHYSICALDISK_DISPLAY_NAME " (%s)", dev->name.length ? dev->name.chars : dev->devPath.chars); } else { ffStrbufClear(key); FF_PARSE_FORMAT_STRING_CHECKED(key, &options->moduleArgs.key, ((FFformatarg[]){ FF_ARG(index, "index"), FF_ARG(dev->name, "name"), FF_ARG(dev->devPath, "dev-path"), FF_ARG(options->moduleArgs.keyIcon, "icon"), })); } } bool ffPrintPhysicalDisk(FFPhysicalDiskOptions* options) { FF_LIST_AUTO_DESTROY result = ffListCreate(sizeof(FFPhysicalDiskResult)); const char* error = ffDetectPhysicalDisk(&result, options); if(error) { ffPrintError(FF_PHYSICALDISK_DISPLAY_NAME, 0, &options->moduleArgs, FF_PRINT_TYPE_DEFAULT, "%s", error); return false; } ffListSort(&result, (const void*) sortDevices); uint32_t index = 0; FF_STRBUF_AUTO_DESTROY key = ffStrbufCreate(); FF_STRBUF_AUTO_DESTROY buffer = ffStrbufCreate(); FF_LIST_FOR_EACH(FFPhysicalDiskResult, dev, result) { formatKey(options, dev, result.length == 1 ? 0 : index + 1, &key); ffStrbufClear(&buffer); ffSizeAppendNum(dev->size, &buffer); const char* physicalType = dev->type & FF_PHYSICALDISK_TYPE_HDD ? "HDD" : dev->type & FF_PHYSICALDISK_TYPE_SSD ? "SSD" : ""; const char* removableType = dev->type & FF_PHYSICALDISK_TYPE_REMOVABLE ? "Removable" : dev->type & FF_PHYSICALDISK_TYPE_FIXED ? "Fixed" : ""; const char* readOnlyType = dev->type & FF_PHYSICALDISK_TYPE_READONLY ? "Read-only" : ""; if(options->moduleArgs.outputFormat.length == 0) { ffPrintLogoAndKey(key.chars, 0, &options->moduleArgs, FF_PRINT_TYPE_NO_CUSTOM_KEY); if (physicalType[0] || removableType[0] || readOnlyType[0]) { ffStrbufAppendS(&buffer, " ["); if (physicalType[0]) ffStrbufAppendS(&buffer, physicalType); if (removableType[0]) { if (buffer.chars[buffer.length - 1] != '[') ffStrbufAppendS(&buffer, ", "); ffStrbufAppendS(&buffer, removableType); } if (readOnlyType[0]) { if (buffer.chars[buffer.length - 1] != '[') ffStrbufAppendS(&buffer, ", "); ffStrbufAppendS(&buffer, readOnlyType); } ffStrbufAppendC(&buffer, ']'); } if (dev->temperature != FF_PHYSICALDISK_TEMP_UNSET) { if(buffer.length > 0) ffStrbufAppendS(&buffer, " - "); ffTempsAppendNum(dev->temperature, &buffer, options->tempConfig, &options->moduleArgs); } ffStrbufPutTo(&buffer, stdout); } else { FF_STRBUF_AUTO_DESTROY tempStr = ffStrbufCreate(); ffTempsAppendNum(dev->temperature, &tempStr, options->tempConfig, &options->moduleArgs); if (dev->type & FF_PHYSICALDISK_TYPE_READWRITE) readOnlyType = "Read-write"; FF_PRINT_FORMAT_CHECKED(key.chars, 0, &options->moduleArgs, FF_PRINT_TYPE_NO_CUSTOM_KEY, ((FFformatarg[]){ FF_ARG(buffer, "size"), FF_ARG(dev->name, "name"), FF_ARG(dev->interconnect, "interconnect"), FF_ARG(dev->devPath, "dev-path"), FF_ARG(dev->serial, "serial"), FF_ARG(physicalType, "physical-type"), FF_ARG(removableType, "removable-type"), FF_ARG(readOnlyType, "readonly-type"), FF_ARG(dev->revision, "revision"), FF_ARG(tempStr, "temperature"), })); } ++index; } FF_LIST_FOR_EACH(FFPhysicalDiskResult, dev, result) { ffStrbufDestroy(&dev->name); ffStrbufDestroy(&dev->interconnect); ffStrbufDestroy(&dev->devPath); ffStrbufDestroy(&dev->serial); ffStrbufDestroy(&dev->revision); } return true; } void ffParsePhysicalDiskJsonObject(FFPhysicalDiskOptions* options, yyjson_val* module) { yyjson_val *key, *val; size_t idx, max; yyjson_obj_foreach(module, idx, max, key, val) { if (ffJsonConfigParseModuleArgs(key, val, &options->moduleArgs)) continue; if (unsafe_yyjson_equals_str(key, "namePrefix")) { ffStrbufSetJsonVal(&options->namePrefix, val); continue; } if (ffTempsParseJsonObject(key, val, &options->temp, &options->tempConfig)) continue; ffPrintError(FF_PHYSICALDISK_MODULE_NAME, 0, &options->moduleArgs, FF_PRINT_TYPE_DEFAULT, "Unknown JSON key %s", unsafe_yyjson_get_str(key)); } } void ffGeneratePhysicalDiskJsonConfig(FFPhysicalDiskOptions* options, yyjson_mut_doc* doc, yyjson_mut_val* module) { ffJsonConfigGenerateModuleArgsConfig(doc, module, &options->moduleArgs); yyjson_mut_obj_add_strbuf(doc, module, "namePrefix", &options->namePrefix); ffTempsGenerateJsonConfig(doc, module, options->temp, options->tempConfig); } bool ffGeneratePhysicalDiskJsonResult(FFPhysicalDiskOptions* options, yyjson_mut_doc* doc, yyjson_mut_val* module) { FF_LIST_AUTO_DESTROY result = ffListCreate(sizeof(FFPhysicalDiskResult)); const char* error = ffDetectPhysicalDisk(&result, options); if(error) { yyjson_mut_obj_add_str(doc, module, "error", error); return false; } yyjson_mut_val* arr = yyjson_mut_obj_add_arr(doc, module, "result"); FF_LIST_FOR_EACH(FFPhysicalDiskResult, dev, result) { yyjson_mut_val* obj = yyjson_mut_arr_add_obj(doc, arr); yyjson_mut_obj_add_strbuf(doc, obj, "name", &dev->name); yyjson_mut_obj_add_strbuf(doc, obj, "devPath", &dev->devPath); yyjson_mut_obj_add_strbuf(doc, obj, "interconnect", &dev->interconnect); if (dev->type & FF_PHYSICALDISK_TYPE_HDD) yyjson_mut_obj_add_str(doc, obj, "kind", "HDD"); else if (dev->type & FF_PHYSICALDISK_TYPE_SSD) yyjson_mut_obj_add_str(doc, obj, "kind", "SSD"); else yyjson_mut_obj_add_null(doc, obj, "kind"); yyjson_mut_obj_add_uint(doc, obj, "size", dev->size); yyjson_mut_obj_add_strbuf(doc, obj, "serial", &dev->serial); if (dev->type & FF_PHYSICALDISK_TYPE_REMOVABLE) yyjson_mut_obj_add_bool(doc, obj, "removable", true); else if (dev->type & FF_PHYSICALDISK_TYPE_FIXED) yyjson_mut_obj_add_bool(doc, obj, "removable", false); else yyjson_mut_obj_add_null(doc, obj, "removable"); if (dev->type & FF_PHYSICALDISK_TYPE_READONLY) yyjson_mut_obj_add_bool(doc, obj, "readOnly", true); else if (dev->type & FF_PHYSICALDISK_TYPE_READWRITE) yyjson_mut_obj_add_bool(doc, obj, "readOnly", false); else yyjson_mut_obj_add_null(doc, obj, "readOnly"); yyjson_mut_obj_add_strbuf(doc, obj, "revision", &dev->revision); if (dev->temperature != FF_PHYSICALDISK_TEMP_UNSET) yyjson_mut_obj_add_real(doc, obj, "temperature", dev->temperature); else yyjson_mut_obj_add_null(doc, obj, "temperature"); } FF_LIST_FOR_EACH(FFPhysicalDiskResult, dev, result) { ffStrbufDestroy(&dev->name); ffStrbufDestroy(&dev->interconnect); ffStrbufDestroy(&dev->devPath); ffStrbufDestroy(&dev->serial); ffStrbufDestroy(&dev->revision); } return true; } void ffInitPhysicalDiskOptions(FFPhysicalDiskOptions* options) { ffOptionInitModuleArg(&options->moduleArgs, "󰋊"); ffStrbufInit(&options->namePrefix); options->temp = false; options->tempConfig = (FFColorRangeConfig) { 50, 70 }; } void ffDestroyPhysicalDiskOptions(FFPhysicalDiskOptions* options) { ffOptionDestroyModuleArg(&options->moduleArgs); ffStrbufDestroy(&options->namePrefix); } FFModuleBaseInfo ffPhysicalDiskModuleInfo = { .name = FF_PHYSICALDISK_MODULE_NAME, .description = "Print physical disk information", .initOptions = (void*) ffInitPhysicalDiskOptions, .destroyOptions = (void*) ffDestroyPhysicalDiskOptions, .parseJsonObject = (void*) ffParsePhysicalDiskJsonObject, .printModule = (void*) ffPrintPhysicalDisk, .generateJsonResult = (void*) ffGeneratePhysicalDiskJsonResult, .generateJsonConfig = (void*) ffGeneratePhysicalDiskJsonConfig, .formatArgs = FF_FORMAT_ARG_LIST(((FFModuleFormatArg[]) { {"Device size (formatted)", "size"}, {"Device name", "name"}, {"Device interconnect type", "interconnect"}, {"Device raw file path", "dev-path"}, {"Serial number", "serial"}, {"Device kind (SSD or HDD)", "physical-type"}, {"Device kind (Removable or Fixed)", "removable-type"}, {"Device kind (Read-only or Read-write)", "readonly-type"}, {"Product revision", "revision"}, {"Device temperature (formatted)", "temperature"}, })) }; ================================================ FILE: src/modules/physicaldisk/physicaldisk.h ================================================ #pragma once #include "option.h" #define FF_PHYSICALDISK_MODULE_NAME "PhysicalDisk" bool ffPrintPhysicalDisk(FFPhysicalDiskOptions* options); void ffInitPhysicalDiskOptions(FFPhysicalDiskOptions* options); void ffDestroyPhysicalDiskOptions(FFPhysicalDiskOptions* options); extern FFModuleBaseInfo ffPhysicalDiskModuleInfo; ================================================ FILE: src/modules/physicalmemory/option.h ================================================ #pragma once #include "common/option.h" typedef struct FFPhysicalMemoryOptions { FFModuleArgs moduleArgs; bool showEmptySlots; } FFPhysicalMemoryOptions; static_assert(sizeof(FFPhysicalMemoryOptions) <= FF_OPTION_MAX_SIZE, "FFPhysicalMemoryOptions size exceeds maximum allowed size"); ================================================ FILE: src/modules/physicalmemory/physicalmemory.c ================================================ #include "common/printing.h" #include "common/jsonconfig.h" #include "common/size.h" #include "detection/physicalmemory/physicalmemory.h" #include "modules/physicalmemory/physicalmemory.h" #define FF_PHYSICALMEMORY_DISPLAY_NAME "Physical Memory" bool ffPrintPhysicalMemory(FFPhysicalMemoryOptions* options) { FF_LIST_AUTO_DESTROY result = ffListCreate(sizeof(FFPhysicalMemoryResult)); const char* error = ffDetectPhysicalMemory(&result); if(error) { ffPrintError(FF_PHYSICALMEMORY_DISPLAY_NAME, 0, &options->moduleArgs, FF_PRINT_TYPE_DEFAULT, "%s", error); return false; } if (result.length == 0) { ffPrintError(FF_PHYSICALMEMORY_DISPLAY_NAME, 0, &options->moduleArgs, FF_PRINT_TYPE_DEFAULT, "No physical memory detected"); return false; } FF_LIST_AUTO_DESTROY filtered = ffListCreate(sizeof(FFPhysicalMemoryResult*)); FF_LIST_FOR_EACH(FFPhysicalMemoryResult, device, result) { if (!options->showEmptySlots && !device->installed) continue; *(FFPhysicalMemoryResult**) ffListAdd(&filtered) = device; } if (filtered.length == 0) { ffPrintError(FF_PHYSICALMEMORY_DISPLAY_NAME, 0, &options->moduleArgs, FF_PRINT_TYPE_DEFAULT, "No installed physical memory detected"); return false; } FF_STRBUF_AUTO_DESTROY prettySize = ffStrbufCreate(); for (uint32_t i = 0; i < filtered.length; ++i) { FFPhysicalMemoryResult* device = *FF_LIST_GET(FFPhysicalMemoryResult*, filtered, i); ffStrbufClear(&prettySize); if (device->installed) ffSizeAppendNum(device->size, &prettySize); if (options->moduleArgs.outputFormat.length == 0) { ffPrintLogoAndKey(FF_PHYSICALMEMORY_DISPLAY_NAME, filtered.length == 1 ? 0 : (uint8_t) (i + 1), &options->moduleArgs, FF_PRINT_TYPE_DEFAULT); if (device->installed) { fputs(prettySize.chars, stdout); fputs(" - ", stdout); ffStrbufWriteTo(&device->type, stdout); if (device->maxSpeed > 0) printf("-%u", device->maxSpeed); if (device->runningSpeed > 0 && device->runningSpeed != device->maxSpeed) printf(" @ %u MT/s", device->runningSpeed); if (device->vendor.length > 0) printf(" (%s)", device->vendor.chars); if (device->ecc) fputs(" - ECC", stdout); } else { fputs("Empty", stdout); if (device->formFactor.length > 0) printf(" - %s", device->formFactor.chars); if (device->locator.length > 0) printf(" (%s)", device->locator.chars); } putchar('\n'); } else { FF_PRINT_FORMAT_CHECKED(FF_PHYSICALMEMORY_DISPLAY_NAME, (uint8_t) (i + 1), &options->moduleArgs, FF_PRINT_TYPE_DEFAULT, ((FFformatarg[]) { FF_ARG(device->size, "bytes"), FF_ARG(prettySize, "size"), FF_ARG(device->maxSpeed, "max-speed"), FF_ARG(device->runningSpeed, "running-speed"), FF_ARG(device->type, "type"), FF_ARG(device->formFactor, "form-factor"), FF_ARG(device->locator, "locator"), FF_ARG(device->vendor, "vendor"), FF_ARG(device->serial, "serial"), FF_ARG(device->partNumber, "part-number"), FF_ARG(device->ecc, "is-ecc-enabled"), FF_ARG(device->installed, "is-installed"), })); } } FF_LIST_FOR_EACH(FFPhysicalMemoryResult, device, result) { ffStrbufDestroy(&device->type); ffStrbufDestroy(&device->locator); ffStrbufDestroy(&device->formFactor); ffStrbufDestroy(&device->vendor); ffStrbufDestroy(&device->serial); ffStrbufDestroy(&device->partNumber); } return true; } void ffParsePhysicalMemoryJsonObject(FFPhysicalMemoryOptions* options, yyjson_val* module) { yyjson_val *key, *val; size_t idx, max; yyjson_obj_foreach(module, idx, max, key, val) { if (ffJsonConfigParseModuleArgs(key, val, &options->moduleArgs)) continue; if (unsafe_yyjson_equals_str(key, "showEmptySlots")) { options->showEmptySlots = yyjson_get_bool(val); continue; } ffPrintError(FF_PHYSICALMEMORY_MODULE_NAME, 0, &options->moduleArgs, FF_PRINT_TYPE_DEFAULT, "Unknown JSON key %s", unsafe_yyjson_get_str(key)); } } void ffGeneratePhysicalMemoryJsonConfig(FFPhysicalMemoryOptions* options, yyjson_mut_doc* doc, yyjson_mut_val* module) { ffJsonConfigGenerateModuleArgsConfig(doc, module, &options->moduleArgs); yyjson_mut_obj_add_bool(doc, module, "showEmptySlots", options->showEmptySlots); } bool ffGeneratePhysicalMemoryJsonResult(FF_MAYBE_UNUSED FFPhysicalMemoryOptions* options, yyjson_mut_doc* doc, yyjson_mut_val* module) { FF_LIST_AUTO_DESTROY result = ffListCreate(sizeof(FFPhysicalMemoryResult)); const char* error = ffDetectPhysicalMemory(&result); if(error) { yyjson_mut_obj_add_str(doc, module, "error", error); return false; } yyjson_mut_val* arr = yyjson_mut_obj_add_arr(doc, module, "result"); FF_LIST_FOR_EACH(FFPhysicalMemoryResult, device, result) { yyjson_mut_val* obj = yyjson_mut_arr_add_obj(doc, arr); yyjson_mut_obj_add_uint(doc, obj, "size", device->size); yyjson_mut_obj_add_bool(doc, obj, "installed", device->installed); yyjson_mut_obj_add_uint(doc, obj, "maxSpeed", device->maxSpeed); yyjson_mut_obj_add_uint(doc, obj, "runningSpeed", device->runningSpeed); yyjson_mut_obj_add_strbuf(doc, obj, "type", &device->type); yyjson_mut_obj_add_strbuf(doc, obj, "locator", &device->locator); yyjson_mut_obj_add_strbuf(doc, obj, "formFactor", &device->formFactor); yyjson_mut_obj_add_strbuf(doc, obj, "vendor", &device->vendor); yyjson_mut_obj_add_strbuf(doc, obj, "serial", &device->serial); yyjson_mut_obj_add_strbuf(doc, obj, "partNumber", &device->partNumber); yyjson_mut_obj_add_bool(doc, obj, "ecc", device->ecc); } FF_LIST_FOR_EACH(FFPhysicalMemoryResult, device, result) { ffStrbufDestroy(&device->type); ffStrbufDestroy(&device->locator); ffStrbufDestroy(&device->formFactor); ffStrbufDestroy(&device->vendor); ffStrbufDestroy(&device->serial); ffStrbufDestroy(&device->partNumber); } return true; } void ffInitPhysicalMemoryOptions(FFPhysicalMemoryOptions* options) { ffOptionInitModuleArg(&options->moduleArgs, "󰑭"); options->showEmptySlots = false; } void ffDestroyPhysicalMemoryOptions(FFPhysicalMemoryOptions* options) { ffOptionDestroyModuleArg(&options->moduleArgs); } FFModuleBaseInfo ffPhysicalMemoryModuleInfo = { .name = FF_PHYSICALMEMORY_MODULE_NAME, .description = "Print system physical memory devices", .initOptions = (void*) ffInitPhysicalMemoryOptions, .destroyOptions = (void*) ffDestroyPhysicalMemoryOptions, .parseJsonObject = (void*) ffParsePhysicalMemoryJsonObject, .printModule = (void*) ffPrintPhysicalMemory, .generateJsonConfig = (void*) ffGeneratePhysicalMemoryJsonConfig, .generateJsonResult = (void*) ffGeneratePhysicalMemoryJsonResult, .formatArgs = FF_FORMAT_ARG_LIST(((FFModuleFormatArg[]) { {"Size (in bytes)", "bytes"}, {"Size formatted", "size"}, {"Max speed (in MT/s)", "max-speed"}, {"Running speed (in MT/s)", "running-speed"}, {"Type (DDR4, DDR5, etc.)", "type"}, {"Form factor (SODIMM, DIMM, etc.)", "form-factor"}, {"Bank/Device Locator (BANK0/SIMM0, BANK0/SIMM1, etc.)", "locator"}, {"Vendor", "vendor"}, {"Serial number", "serial"}, {"Part number", "part-number"}, {"True if ECC enabled", "is-ecc-enabled"}, {"True if a memory module is installed in the slot", "is-installed"}, })) }; ================================================ FILE: src/modules/physicalmemory/physicalmemory.h ================================================ #pragma once #include "option.h" #define FF_PHYSICALMEMORY_MODULE_NAME "PhysicalMemory" bool ffPrintPhysicalMemory(FFPhysicalMemoryOptions* options); void ffInitPhysicalMemoryOptions(FFPhysicalMemoryOptions* options); void ffDestroyPhysicalMemoryOptions(FFPhysicalMemoryOptions* options); extern FFModuleBaseInfo ffPhysicalMemoryModuleInfo; ================================================ FILE: src/modules/player/option.h ================================================ #pragma once #include "common/option.h" typedef struct FFPlayerOptions { FFModuleArgs moduleArgs; } FFPlayerOptions; static_assert(sizeof(FFPlayerOptions) <= FF_OPTION_MAX_SIZE, "FFPlayerOptions size exceeds maximum allowed size"); ================================================ FILE: src/modules/player/player.c ================================================ #include "common/printing.h" #include "common/jsonconfig.h" #include "common/stringUtils.h" #include "detection/media/media.h" #include "modules/player/player.h" #include #define FF_PLAYER_DISPLAY_NAME "Media Player" bool ffPrintPlayer(FFPlayerOptions* options) { const FFMediaResult* media = ffDetectMedia(false); if(media->error.length > 0) { ffPrintError(FF_PLAYER_DISPLAY_NAME, 0, &options->moduleArgs, FF_PRINT_TYPE_DEFAULT, "%s", media->error.chars); return false; } if (media->player.length == 0) { ffPrintError(FF_PLAYER_DISPLAY_NAME, 0, &options->moduleArgs, FF_PRINT_TYPE_DEFAULT, "No media player detected"); return false; } FF_STRBUF_AUTO_DESTROY playerPretty = ffStrbufCreate(); if (media->url.length > 0) { //If we are on a website, prepend the website name if( ffStrbufIgnCaseEqualS(&media->playerId, "spotify") || ffStrbufIgnCaseEqualS(&media->playerId, "vlc") ) {} // do noting, surely not a website, even if the url is set else if(ffStrbufStartsWithS(&media->url, "https://www.")) ffStrbufAppendS(&playerPretty, media->url.chars + 12); else if(ffStrbufStartsWithS(&media->url, "http://www.")) ffStrbufAppendS(&playerPretty, media->url.chars + 11); else if(ffStrbufStartsWithS(&media->url, "https://")) ffStrbufAppendS(&playerPretty, media->url.chars + 8); else if(ffStrbufStartsWithS(&media->url, "http://")) ffStrbufAppendS(&playerPretty, media->url.chars + 7); } //If we found a website name, make it more pretty if(playerPretty.length > 0) { ffStrbufSubstrBeforeFirstC(&playerPretty, '/'); //Remove the path ffStrbufSubstrBeforeLastC(&playerPretty, '.'); //Remove the TLD } //Check again for length, as we may have removed everything. bool playerPrettyIsCustom = playerPretty.length > 0; //If we don't have subdomains, it is usually more pretty to capitalize the first letter. if(playerPrettyIsCustom && ffStrbufFirstIndexC(&playerPretty, '.') == playerPretty.length) playerPretty.chars[0] = (char) toupper(playerPretty.chars[0]); if(playerPrettyIsCustom) ffStrbufAppendS(&playerPretty, " ("); ffStrbufAppend(&playerPretty, &media->player); if(playerPrettyIsCustom) ffStrbufAppendC(&playerPretty, ')'); if(options->moduleArgs.outputFormat.length == 0) { ffPrintLogoAndKey(FF_PLAYER_DISPLAY_NAME, 0, &options->moduleArgs, FF_PRINT_TYPE_DEFAULT); ffStrbufPutTo(&playerPretty, stdout); } else { FF_PRINT_FORMAT_CHECKED(FF_PLAYER_DISPLAY_NAME, 0, &options->moduleArgs, FF_PRINT_TYPE_DEFAULT, ((FFformatarg[]){ FF_ARG(playerPretty, "player"), FF_ARG(media->player, "name"), FF_ARG(media->playerId, "id"), FF_ARG(media->url, "url"), })); } return true; } void ffParsePlayerJsonObject(FFPlayerOptions* options, yyjson_val* module) { yyjson_val *key, *val; size_t idx, max; yyjson_obj_foreach(module, idx, max, key, val) { if (ffJsonConfigParseModuleArgs(key, val, &options->moduleArgs)) continue; ffPrintError(FF_PLAYER_MODULE_NAME, 0, &options->moduleArgs, FF_PRINT_TYPE_DEFAULT, "Unknown JSON key %s", unsafe_yyjson_get_str(key)); } } void ffGeneratePlayerJsonConfig(FFPlayerOptions* options, yyjson_mut_doc* doc, yyjson_mut_val* module) { ffJsonConfigGenerateModuleArgsConfig(doc, module, &options->moduleArgs); } bool ffGeneratePlayerJsonResult(FF_MAYBE_UNUSED FFMediaOptions* options, yyjson_mut_doc* doc, yyjson_mut_val* module) { yyjson_mut_obj_add_str(doc, module, "error", "Player module is an alias of Media module"); return false; } void ffInitPlayerOptions(FFPlayerOptions* options) { ffOptionInitModuleArg(&options->moduleArgs, "󰥠"); } void ffDestroyPlayerOptions(FFPlayerOptions* options) { ffOptionDestroyModuleArg(&options->moduleArgs); } FFModuleBaseInfo ffPlayerModuleInfo = { .name = FF_PLAYER_MODULE_NAME, .description = "Print music player name", .initOptions = (void*) ffInitPlayerOptions, .destroyOptions = (void*) ffDestroyPlayerOptions, .parseJsonObject = (void*) ffParsePlayerJsonObject, .printModule = (void*) ffPrintPlayer, .generateJsonResult = (void*) ffGeneratePlayerJsonResult, .generateJsonConfig = (void*) ffGeneratePlayerJsonConfig, .formatArgs = FF_FORMAT_ARG_LIST(((FFModuleFormatArg[]) { {"Pretty player name", "player"}, {"Player name", "name"}, {"Player Identifier", "id"}, {"URL name", "url"}, })) }; ================================================ FILE: src/modules/player/player.h ================================================ #pragma once #include "option.h" #define FF_PLAYER_MODULE_NAME "Player" bool ffPrintPlayer(FFPlayerOptions* options); void ffInitPlayerOptions(FFPlayerOptions* options); void ffDestroyPlayerOptions(FFPlayerOptions* options); extern FFModuleBaseInfo ffPlayerModuleInfo; ================================================ FILE: src/modules/poweradapter/option.h ================================================ #pragma once #include "common/option.h" typedef struct FFPowerAdapterOptions { FFModuleArgs moduleArgs; } FFPowerAdapterOptions; static_assert(sizeof(FFPowerAdapterOptions) <= FF_OPTION_MAX_SIZE, "FFPowerAdapterOptions size exceeds maximum allowed size"); ================================================ FILE: src/modules/poweradapter/poweradapter.c ================================================ #include "common/printing.h" #include "common/jsonconfig.h" #include "common/stringUtils.h" #include "detection/poweradapter/poweradapter.h" #include "modules/poweradapter/poweradapter.h" #define FF_POWERADAPTER_DISPLAY_NAME "Power Adapter" bool ffPrintPowerAdapter(FFPowerAdapterOptions* options) { FF_LIST_AUTO_DESTROY results = ffListCreate(sizeof(FFPowerAdapterResult)); const char* error = ffDetectPowerAdapter(&results); if (error) { ffPrintError(FF_POWERADAPTER_DISPLAY_NAME, 0, &options->moduleArgs, FF_PRINT_TYPE_DEFAULT, "%s", error); return false; } if(results.length == 0) { ffPrintError(FF_POWERADAPTER_DISPLAY_NAME, 0, &options->moduleArgs, FF_PRINT_TYPE_DEFAULT, "No power adapters found"); return false; } for(uint8_t i = 0; i < (uint8_t) results.length; i++) { FFPowerAdapterResult* result = FF_LIST_GET(FFPowerAdapterResult, results, i); if(options->moduleArgs.outputFormat.length == 0) { ffPrintLogoAndKey(FF_POWERADAPTER_DISPLAY_NAME, i, &options->moduleArgs, FF_PRINT_TYPE_DEFAULT); if(result->name.length > 0) puts(result->name.chars); else printf("%dW\n", result->watts); } else { FF_PRINT_FORMAT_CHECKED(FF_POWERADAPTER_DISPLAY_NAME, i, &options->moduleArgs, FF_PRINT_TYPE_DEFAULT, ((FFformatarg[]){ FF_ARG(result->watts, "watts"), FF_ARG(result->name, "name"), FF_ARG(result->manufacturer, "manufacturer"), FF_ARG(result->modelName, "model-name"), FF_ARG(result->description, "description"), FF_ARG(result->serial, "serial"), })); } ffStrbufDestroy(&result->manufacturer); ffStrbufDestroy(&result->description); ffStrbufDestroy(&result->modelName); ffStrbufDestroy(&result->name); ffStrbufDestroy(&result->serial); } return true; } void ffGeneratePowerAdapterJsonConfig(FFPowerAdapterOptions* options, yyjson_mut_doc* doc, yyjson_mut_val* module) { ffJsonConfigGenerateModuleArgsConfig(doc, module, &options->moduleArgs); } void ffParsePowerAdapterJsonObject(FFPowerAdapterOptions* options, yyjson_val* module) { yyjson_val *key, *val; size_t idx, max; yyjson_obj_foreach(module, idx, max, key, val) { if (ffJsonConfigParseModuleArgs(key, val, &options->moduleArgs)) continue; ffPrintError(FF_POWERADAPTER_MODULE_NAME, 0, &options->moduleArgs, FF_PRINT_TYPE_DEFAULT, "Unknown JSON key %s", unsafe_yyjson_get_str(key)); } } bool ffGeneratePowerAdapterJsonResult(FF_MAYBE_UNUSED FFPowerAdapterOptions* options, yyjson_mut_doc* doc, yyjson_mut_val* module) { FF_LIST_AUTO_DESTROY results = ffListCreate(sizeof(FFPowerAdapterResult)); const char* error = ffDetectPowerAdapter(&results); if (error) { yyjson_mut_obj_add_str(doc, module, "error", error); return false; } yyjson_mut_val* arr = yyjson_mut_obj_add_arr(doc, module, "result"); FF_LIST_FOR_EACH(FFPowerAdapterResult, item, results) { yyjson_mut_val* obj = yyjson_mut_arr_add_obj(doc, arr); yyjson_mut_obj_add_strbuf(doc, obj, "description", &item->description); yyjson_mut_obj_add_strbuf(doc, obj, "manufacturer", &item->manufacturer); yyjson_mut_obj_add_strbuf(doc, obj, "modelName", &item->modelName); yyjson_mut_obj_add_strbuf(doc, obj, "name", &item->name); yyjson_mut_obj_add_strbuf(doc, obj, "serial", &item->serial); yyjson_mut_obj_add_int(doc, obj, "watts", item->watts); } FF_LIST_FOR_EACH(FFPowerAdapterResult, item, results) { ffStrbufDestroy(&item->manufacturer); ffStrbufDestroy(&item->description); ffStrbufDestroy(&item->modelName); ffStrbufDestroy(&item->name); ffStrbufDestroy(&item->serial); } return true; } void ffInitPowerAdapterOptions(FFPowerAdapterOptions* options) { ffOptionInitModuleArg(&options->moduleArgs, "󰚥"); } void ffDestroyPowerAdapterOptions(FFPowerAdapterOptions* options) { ffOptionDestroyModuleArg(&options->moduleArgs); } FFModuleBaseInfo ffPowerAdapterModuleInfo = { .name = FF_POWERADAPTER_MODULE_NAME, .description = "Print power adapter name and charging watts", .initOptions = (void*) ffInitPowerAdapterOptions, .destroyOptions = (void*) ffDestroyPowerAdapterOptions, .parseJsonObject = (void*) ffParsePowerAdapterJsonObject, .printModule = (void*) ffPrintPowerAdapter, .generateJsonResult = (void*) ffGeneratePowerAdapterJsonResult, .generateJsonConfig = (void*) ffGeneratePowerAdapterJsonConfig, .formatArgs = FF_FORMAT_ARG_LIST(((FFModuleFormatArg[]) { {"Power adapter watts", "watts"}, {"Power adapter name", "name"}, {"Power adapter manufacturer", "manufacturer"}, {"Power adapter model", "model"}, {"Power adapter description", "description"}, {"Power adapter serial number", "serial"}, })) }; ================================================ FILE: src/modules/poweradapter/poweradapter.h ================================================ #pragma once #include "option.h" #define FF_POWERADAPTER_MODULE_NAME "PowerAdapter" bool ffPrintPowerAdapter(FFPowerAdapterOptions* options); void ffInitPowerAdapterOptions(FFPowerAdapterOptions* options); void ffDestroyPowerAdapterOptions(FFPowerAdapterOptions* options); extern FFModuleBaseInfo ffPowerAdapterModuleInfo; ================================================ FILE: src/modules/processes/option.h ================================================ #pragma once #include "common/option.h" typedef struct FFProcessesOptions { FFModuleArgs moduleArgs; } FFProcessesOptions; static_assert(sizeof(FFProcessesOptions) <= FF_OPTION_MAX_SIZE, "FFProcessesOptions size exceeds maximum allowed size"); ================================================ FILE: src/modules/processes/processes.c ================================================ #include "common/printing.h" #include "common/jsonconfig.h" #include "common/stringUtils.h" #include "detection/processes/processes.h" #include "modules/processes/processes.h" bool ffPrintProcesses(FFProcessesOptions* options) { uint32_t numProcesses = 0; const char* error = ffDetectProcesses(&numProcesses); if(error) { ffPrintError(FF_PROCESSES_MODULE_NAME, 0, &options->moduleArgs, FF_PRINT_TYPE_DEFAULT, "%s", error); return false; } if(options->moduleArgs.outputFormat.length == 0) { ffPrintLogoAndKey(FF_PROCESSES_MODULE_NAME, 0, &options->moduleArgs, FF_PRINT_TYPE_DEFAULT); printf("%u\n", numProcesses); } else { FF_PRINT_FORMAT_CHECKED(FF_PROCESSES_MODULE_NAME, 0, &options->moduleArgs, FF_PRINT_TYPE_DEFAULT, ((FFformatarg[]){ FF_ARG(numProcesses, "result") })); } return true; } void ffParseProcessesJsonObject(FFProcessesOptions* options, yyjson_val* module) { yyjson_val *key, *val; size_t idx, max; yyjson_obj_foreach(module, idx, max, key, val) { if (ffJsonConfigParseModuleArgs(key, val, &options->moduleArgs)) continue; ffPrintError(FF_PROCESSES_MODULE_NAME, 0, &options->moduleArgs, FF_PRINT_TYPE_DEFAULT, "Unknown JSON key %s", unsafe_yyjson_get_str(key)); } } void ffGenerateProcessesJsonConfig(FFProcessesOptions* options, yyjson_mut_doc* doc, yyjson_mut_val* module) { ffJsonConfigGenerateModuleArgsConfig(doc, module, &options->moduleArgs); } bool ffGenerateProcessesJsonResult(FF_MAYBE_UNUSED FFProcessesOptions* options, yyjson_mut_doc* doc, yyjson_mut_val* module) { uint32_t result; const char* error = ffDetectProcesses(&result); if(error) { yyjson_mut_obj_add_str(doc, module, "error", error); return false; } yyjson_mut_obj_add_uint(doc, module, "result", result); return true; } void ffInitProcessesOptions(FFProcessesOptions* options) { ffOptionInitModuleArg(&options->moduleArgs, ""); } void ffDestroyProcessesOptions(FFProcessesOptions* options) { ffOptionDestroyModuleArg(&options->moduleArgs); } FFModuleBaseInfo ffProcessesModuleInfo = { .name = FF_PROCESSES_MODULE_NAME, .description = "Print number of running processes", .initOptions = (void*) ffInitProcessesOptions, .destroyOptions = (void*) ffDestroyProcessesOptions, .parseJsonObject = (void*) ffParseProcessesJsonObject, .printModule = (void*) ffPrintProcesses, .generateJsonResult = (void*) ffGenerateProcessesJsonResult, .generateJsonConfig = (void*) ffGenerateProcessesJsonConfig, .formatArgs = FF_FORMAT_ARG_LIST(((FFModuleFormatArg[]) { {"Process count", "result"} })) }; ================================================ FILE: src/modules/processes/processes.h ================================================ #pragma once #include "option.h" #define FF_PROCESSES_MODULE_NAME "Processes" bool ffPrintProcesses(FFProcessesOptions* options); void ffInitProcessesOptions(FFProcessesOptions* options); void ffDestroyProcessesOptions(FFProcessesOptions* options); extern FFModuleBaseInfo ffProcessesModuleInfo; ================================================ FILE: src/modules/publicip/option.h ================================================ #pragma once #include "common/option.h" typedef struct FFPublicIPOptions { FFModuleArgs moduleArgs; FFstrbuf url; uint32_t timeout; bool ipv6; } FFPublicIPOptions; static_assert(sizeof(FFPublicIPOptions) <= FF_OPTION_MAX_SIZE, "FFPublicIPOptions size exceeds maximum allowed size"); ================================================ FILE: src/modules/publicip/publicip.c ================================================ #include "common/printing.h" #include "common/jsonconfig.h" #include "common/stringUtils.h" #include "modules/publicip/publicip.h" #include "detection/publicip/publicip.h" #define FF_PUBLICIP_DISPLAY_NAME "Public IP" bool ffPrintPublicIp(FFPublicIPOptions* options) { FFPublicIpResult result; ffStrbufInit(&result.ip); ffStrbufInit(&result.location); const char* error = ffDetectPublicIp(options, &result); if (error) { ffPrintError(FF_PUBLICIP_DISPLAY_NAME, 0, &options->moduleArgs, FF_PRINT_TYPE_DEFAULT, "%s", error); return false; } if (options->moduleArgs.outputFormat.length == 0) { ffPrintLogoAndKey(FF_PUBLICIP_DISPLAY_NAME, 0, &options->moduleArgs, FF_PRINT_TYPE_DEFAULT); if (result.location.length) printf("%s (%s)\n", result.ip.chars, result.location.chars); else ffStrbufPutTo(&result.ip, stdout); } else { FF_PRINT_FORMAT_CHECKED(FF_PUBLICIP_DISPLAY_NAME, 0, &options->moduleArgs, FF_PRINT_TYPE_DEFAULT, ((FFformatarg[]) { FF_ARG(result.ip, "ip"), FF_ARG(result.location, "location"), })); } ffStrbufDestroy(&result.ip); ffStrbufDestroy(&result.location); return true; } void ffParsePublicIpJsonObject(FFPublicIPOptions* options, yyjson_val* module) { yyjson_val *key, *val; size_t idx, max; yyjson_obj_foreach(module, idx, max, key, val) { if (ffJsonConfigParseModuleArgs(key, val, &options->moduleArgs)) continue; if (unsafe_yyjson_equals_str(key, "url")) { ffStrbufSetJsonVal(&options->url, val); continue; } if (unsafe_yyjson_equals_str(key, "timeout")) { options->timeout = (uint32_t) yyjson_get_uint(val); continue; } if (unsafe_yyjson_equals_str(key, "ipv6")) { options->ipv6 = yyjson_get_bool(val); continue; } ffPrintError(FF_PUBLICIP_MODULE_NAME, 0, &options->moduleArgs, FF_PRINT_TYPE_DEFAULT, "Unknown JSON key %s", unsafe_yyjson_get_str(key)); } } void ffGeneratePublicIpJsonConfig(FFPublicIPOptions* options, yyjson_mut_doc* doc, yyjson_mut_val* module) { ffJsonConfigGenerateModuleArgsConfig(doc, module, &options->moduleArgs); yyjson_mut_obj_add_strbuf(doc, module, "url", &options->url); yyjson_mut_obj_add_uint(doc, module, "timeout", options->timeout); yyjson_mut_obj_add_bool(doc, module, "ipv6", options->ipv6); } bool ffGeneratePublicIpJsonResult(FFPublicIPOptions* options, yyjson_mut_doc* doc, yyjson_mut_val* module) { FFPublicIpResult result; ffStrbufInit(&result.ip); ffStrbufInit(&result.location); const char* error = ffDetectPublicIp(options, &result); if (error) { yyjson_mut_obj_add_str(doc, module, "error", error); return false; } yyjson_mut_val* obj = yyjson_mut_obj_add_obj(doc, module, "result"); yyjson_mut_obj_add_strbuf(doc, obj, "ip", &result.ip); yyjson_mut_obj_add_strbuf(doc, obj, "location", &result.location); ffStrbufDestroy(&result.ip); ffStrbufDestroy(&result.location); return true; } void ffInitPublicIpOptions(FFPublicIPOptions* options) { ffOptionInitModuleArg(&options->moduleArgs, "󰩠"); ffStrbufInit(&options->url); options->timeout = 0; options->ipv6 = false; } void ffDestroyPublicIpOptions(FFPublicIPOptions* options) { ffOptionDestroyModuleArg(&options->moduleArgs); ffStrbufDestroy(&options->url); } FFModuleBaseInfo ffPublicIPModuleInfo = { .name = FF_PUBLICIP_MODULE_NAME, .description = "Print your public IP address, etc", .initOptions = (void*) ffInitPublicIpOptions, .destroyOptions = (void*) ffDestroyPublicIpOptions, .parseJsonObject = (void*) ffParsePublicIpJsonObject, .printModule = (void*) ffPrintPublicIp, .generateJsonResult = (void*) ffGeneratePublicIpJsonResult, .generateJsonConfig = (void*) ffGeneratePublicIpJsonConfig, .formatArgs = FF_FORMAT_ARG_LIST(((FFModuleFormatArg[]) { {"Public IP address", "ip"}, {"Location", "location"}, })) }; ================================================ FILE: src/modules/publicip/publicip.h ================================================ #pragma once #include "option.h" #define FF_PUBLICIP_MODULE_NAME "PublicIp" void ffPreparePublicIp(FFPublicIPOptions* options); bool ffPrintPublicIp(FFPublicIPOptions* options); void ffInitPublicIpOptions(FFPublicIPOptions* options); void ffDestroyPublicIpOptions(FFPublicIPOptions* options); extern FFModuleBaseInfo ffPublicIPModuleInfo; ================================================ FILE: src/modules/separator/option.h ================================================ #pragma once #include "common/option.h" typedef struct FFSeparatorOptions { FFstrbuf string; FFstrbuf outputColor; uint32_t times; } FFSeparatorOptions; static_assert(sizeof(FFSeparatorOptions) <= FF_OPTION_MAX_SIZE, "FFSeparatorOptions size exceeds maximum allowed size"); ================================================ FILE: src/modules/separator/separator.c ================================================ #include "common/printing.h" #include "common/jsonconfig.h" #include "common/stringUtils.h" #include "common/mallocHelper.h" #include "common/wcwidth.h" #include "common/textModifier.h" #include "logo/logo.h" #include "modules/separator/separator.h" #include #if __SIZEOF_WCHAR_T__ == 4 static inline size_t mbrtoc32(uint32_t* restrict pc32, const char* restrict s, size_t n, mbstate_t* restrict ps) { return mbrtowc((wchar_t*) pc32, s, n, ps); } #else #include #endif static uint8_t getMbrWidth(const char* mbstr, uint32_t length, const char** next, mbstate_t* state) { if (__builtin_expect((uint8_t) *mbstr < 0x80, true)) // ASCII fast path { if (next) *next = mbstr + 1; return 1; } uint32_t c32; uint32_t len = (uint32_t) mbrtoc32(&c32, mbstr, length, state); if (len >= (uint32_t) -3) { // Invalid or incomplete multibyte sequence if (next) *next = mbstr + 1; return 1; } if (next) *next = mbstr + len; int width = mk_wcwidth(c32); return width < 0 ? 0 : (uint8_t) width; } static uint32_t getWcsWidth(const FFstrbuf* mbstr) { mbstate_t state = {}; uint32_t remainLength = mbstr->length; uint32_t result = 0; const char* ptr = mbstr->chars; while (remainLength > 0 && *ptr != '\0') { const char* lastPtr = NULL; result += getMbrWidth(ptr, remainLength, &lastPtr, &state); remainLength -= (uint32_t)(lastPtr - ptr); ptr = lastPtr; } return result > 0 ? (uint32_t) result : mbstr->length; } bool ffPrintSeparator(FFSeparatorOptions* options) { ffLogoPrintLine(); if(options->outputColor.length && !instance.config.display.pipe) ffPrintColor(&options->outputColor); if (options->times > 0) { if(__builtin_expect(options->string.length == 1, 1)) ffPrintCharTimes(options->string.chars[0], options->times); else { for (uint32_t i = 0; i < options->times; i++) { fputs(options->string.chars, stdout); } } } else { setlocale(LC_CTYPE, ""); const FFPlatform* platform = &instance.state.platform; uint32_t titleLength = 1 // @ + getWcsWidth(&platform->userName) // user name + (instance.state.titleFqdn ? platform->hostName.length : ffStrbufFirstIndexC(&platform->hostName, '.')); // host name if(__builtin_expect(options->string.length == 1, 1)) { ffPrintCharTimes(options->string.chars[0], titleLength); } else { uint32_t wcsLength = getWcsWidth(&options->string); int remaining = (int) titleLength; //Write the whole separator as often as it fits fully into titleLength for (; remaining >= (int) wcsLength; remaining -= (int) wcsLength) ffStrbufWriteTo(&options->string, stdout); if (remaining > 0) { //Write as much of the separator as needed to fill titleLength if (wcsLength != options->string.length) { // Unicode chars const char* ptr = options->string.chars; mbstate_t state = {}; const char* next = NULL; while (remaining > 0 && *ptr != '\0') { remaining -= (int) getMbrWidth(ptr, (uint32_t)(options->string.length - (ptr - options->string.chars)), &next, &state); ptr = next; } fwrite(options->string.chars, (size_t) (ptr - options->string.chars), 1, stdout); } else { fwrite(options->string.chars, (size_t) remaining, 1, stdout); } } } setlocale(LC_CTYPE, "C"); } if(options->outputColor.length && !instance.config.display.pipe) fputs(FASTFETCH_TEXT_MODIFIER_RESET, stdout); putchar('\n'); return true; } void ffParseSeparatorJsonObject(FFSeparatorOptions* options, yyjson_val* module) { yyjson_val *key, *val; size_t idx, max; yyjson_obj_foreach(module, idx, max, key, val) { if (unsafe_yyjson_equals_str(key, "type") || unsafe_yyjson_equals_str(key, "condition")) continue; if (unsafe_yyjson_equals_str(key, "string")) { ffStrbufSetJsonVal(&options->string, val); continue; } if (unsafe_yyjson_equals_str(key, "outputColor")) { ffOptionParseColor(yyjson_get_str(val), &options->outputColor); continue; } if (unsafe_yyjson_equals_str(key, "times")) { options->times = (uint32_t) yyjson_get_uint(val); continue; } if (unsafe_yyjson_equals_str(key, "length")) { ffPrintError(FF_SEPARATOR_MODULE_NAME, 0, NULL, FF_PRINT_TYPE_NO_CUSTOM_KEY, "The option length has been renamed to times."); continue; } ffPrintError(FF_SEPARATOR_MODULE_NAME, 0, NULL, FF_PRINT_TYPE_NO_CUSTOM_KEY, "Unknown JSON key %s", unsafe_yyjson_get_str(key)); } } void ffGenerateSeparatorJsonConfig(FFSeparatorOptions* options, yyjson_mut_doc* doc, yyjson_mut_val* module) { yyjson_mut_obj_add_strbuf(doc, module, "string", &options->string); yyjson_mut_obj_add_strbuf(doc, module, "outputColor", &options->outputColor); yyjson_mut_obj_add_uint(doc, module, "times", options->times); } void ffInitSeparatorOptions(FFSeparatorOptions* options) { ffStrbufInitStatic(&options->string, "-"); ffStrbufInit(&options->outputColor); options->times = 0; } void ffDestroySeparatorOptions(FFSeparatorOptions* options) { ffStrbufDestroy(&options->string); } FFModuleBaseInfo ffSeparatorModuleInfo = { .name = FF_SEPARATOR_MODULE_NAME, .description = "Print a separator line", .initOptions = (void*) ffInitSeparatorOptions, .destroyOptions = (void*) ffDestroySeparatorOptions, .parseJsonObject = (void*) ffParseSeparatorJsonObject, .printModule = (void*) ffPrintSeparator, .generateJsonConfig = (void*) ffGenerateSeparatorJsonConfig, }; ================================================ FILE: src/modules/separator/separator.h ================================================ #pragma once #include "option.h" #define FF_SEPARATOR_MODULE_NAME "Separator" bool ffPrintSeparator(FFSeparatorOptions* options); void ffInitSeparatorOptions(FFSeparatorOptions* options); void ffDestroySeparatorOptions(FFSeparatorOptions* options); extern FFModuleBaseInfo ffSeparatorModuleInfo; ================================================ FILE: src/modules/shell/option.h ================================================ #pragma once #include "common/option.h" typedef struct FFShellOptions { FFModuleArgs moduleArgs; } FFShellOptions; static_assert(sizeof(FFShellOptions) <= FF_OPTION_MAX_SIZE, "FFShellOptions size exceeds maximum allowed size"); ================================================ FILE: src/modules/shell/shell.c ================================================ #include "common/printing.h" #include "common/jsonconfig.h" #include "common/stringUtils.h" #include "detection/terminalshell/terminalshell.h" #include "modules/shell/shell.h" bool ffPrintShell(FFShellOptions* options) { const FFShellResult* result = ffDetectShell(); if(result->processName.length == 0) { ffPrintError(FF_SHELL_MODULE_NAME, 0, &options->moduleArgs, FF_PRINT_TYPE_DEFAULT, "Couldn't detect shell"); return false; } if(options->moduleArgs.outputFormat.length == 0) { ffPrintLogoAndKey(FF_SHELL_MODULE_NAME, 0, &options->moduleArgs, FF_PRINT_TYPE_DEFAULT); ffStrbufWriteTo(&result->prettyName, stdout); if(result->version.length > 0) { putchar(' '); ffStrbufWriteTo(&result->version, stdout); } putchar('\n'); } else { FF_PRINT_FORMAT_CHECKED(FF_SHELL_MODULE_NAME, 0, &options->moduleArgs, FF_PRINT_TYPE_DEFAULT, ((FFformatarg[]) { FF_ARG(result->processName, "process-name"), FF_ARG(result->exe, "exe"), FF_ARG(result->exeName, "exe-name"), FF_ARG(result->version, "version"), FF_ARG(result->pid, "pid"), FF_ARG(result->prettyName, "pretty-name"), FF_ARG(result->exePath, "exe-path"), FF_ARG(result->tty, "tty"), })); } return true; } void ffParseShellJsonObject(FFShellOptions* options, yyjson_val* module) { yyjson_val *key, *val; size_t idx, max; yyjson_obj_foreach(module, idx, max, key, val) { if (ffJsonConfigParseModuleArgs(key, val, &options->moduleArgs)) continue; ffPrintError(FF_SHELL_MODULE_NAME, 0, &options->moduleArgs, FF_PRINT_TYPE_DEFAULT, "Unknown JSON key %s", unsafe_yyjson_get_str(key)); } } void ffGenerateShellJsonConfig(FFShellOptions* options, yyjson_mut_doc* doc, yyjson_mut_val* module) { ffJsonConfigGenerateModuleArgsConfig(doc, module, &options->moduleArgs); } bool ffGenerateShellJsonResult(FF_MAYBE_UNUSED FFShellOptions* options, yyjson_mut_doc* doc, yyjson_mut_val* module) { const FFShellResult* result = ffDetectShell(); if(result->processName.length == 0) { yyjson_mut_obj_add_str(doc, module, "error", "Couldn't detect shell"); return false; } yyjson_mut_val* obj = yyjson_mut_obj_add_obj(doc, module, "result"); yyjson_mut_obj_add_strbuf(doc, obj, "exe", &result->exe); yyjson_mut_obj_add_strcpy(doc, obj, "exeName", result->exeName); yyjson_mut_obj_add_strbuf(doc, obj, "exePath", &result->exePath); yyjson_mut_obj_add_uint(doc, obj, "pid", result->pid); yyjson_mut_obj_add_uint(doc, obj, "ppid", result->ppid); yyjson_mut_obj_add_strbuf(doc, obj, "processName", &result->processName); yyjson_mut_obj_add_strbuf(doc, obj, "prettyName", &result->prettyName); yyjson_mut_obj_add_strbuf(doc, obj, "version", &result->version); if (result->tty >= 0) yyjson_mut_obj_add_int(doc, obj, "tty", result->tty); else yyjson_mut_obj_add_null(doc, obj, "tty"); return true; } void ffInitShellOptions(FFShellOptions* options) { ffOptionInitModuleArg(&options->moduleArgs, ""); } void ffDestroyShellOptions(FFShellOptions* options) { ffOptionDestroyModuleArg(&options->moduleArgs); } FFModuleBaseInfo ffShellModuleInfo = { .name = FF_SHELL_MODULE_NAME, .description = "Print current shell name and version", .initOptions = (void*) ffInitShellOptions, .destroyOptions = (void*) ffDestroyShellOptions, .parseJsonObject = (void*) ffParseShellJsonObject, .printModule = (void*) ffPrintShell, .generateJsonResult = (void*) ffGenerateShellJsonResult, .generateJsonConfig = (void*) ffGenerateShellJsonConfig, .formatArgs = FF_FORMAT_ARG_LIST(((FFModuleFormatArg[]) { {"Shell process name", "process-name"}, {"The first argument of the command line when running the shell", "exe"}, {"Shell base name of arg0", "exe-name"}, {"Shell version", "version"}, {"Shell pid", "pid"}, {"Shell pretty name", "pretty-name"}, {"Shell full exe path", "exe-path"}, {"Shell tty used", "tty"}, })) }; ================================================ FILE: src/modules/shell/shell.h ================================================ #pragma once #include "option.h" #define FF_SHELL_MODULE_NAME "Shell" bool ffPrintShell(FFShellOptions* options); void ffInitShellOptions(FFShellOptions* options); void ffDestroyShellOptions(FFShellOptions* options); extern FFModuleBaseInfo ffShellModuleInfo; ================================================ FILE: src/modules/sound/option.h ================================================ #pragma once #include "common/option.h" #include "common/percent.h" typedef enum __attribute__((__packed__)) FFSoundType { FF_SOUND_TYPE_MAIN, FF_SOUND_TYPE_ACTIVE, FF_SOUND_TYPE_ALL, } FFSoundType; typedef struct FFSoundOptions { FFModuleArgs moduleArgs; FFSoundType soundType; FFPercentageModuleConfig percent; } FFSoundOptions; static_assert(sizeof(FFSoundOptions) <= FF_OPTION_MAX_SIZE, "FFSoundOptions size exceeds maximum allowed size"); ================================================ FILE: src/modules/sound/sound.c ================================================ #include "common/percent.h" #include "common/printing.h" #include "common/jsonconfig.h" #include "common/stringUtils.h" #include "detection/sound/sound.h" #include "modules/sound/sound.h" static void printDevice(FFSoundOptions* options, const FFSoundDevice* device, uint8_t index) { FFPercentageTypeFlags percentType = options->percent.type == 0 ? instance.config.display.percentType : options->percent.type; if(options->moduleArgs.outputFormat.length == 0) { ffPrintLogoAndKey(FF_SOUND_MODULE_NAME, index, &options->moduleArgs, FF_PRINT_TYPE_DEFAULT); FF_STRBUF_AUTO_DESTROY str = ffStrbufCreate(); if (!(percentType & FF_PERCENTAGE_TYPE_HIDE_OTHERS_BIT)) ffStrbufAppend(&str, &device->name); if(device->volume != FF_SOUND_VOLUME_UNKNOWN) { if (percentType & FF_PERCENTAGE_TYPE_BAR_BIT) { if (str.length) ffStrbufAppendC(&str, ' '); ffPercentAppendBar(&str, device->volume, options->percent, &options->moduleArgs); } if (percentType & FF_PERCENTAGE_TYPE_NUM_BIT) { if (str.length) ffStrbufAppendC(&str, ' '); ffPercentAppendNum(&str, device->volume, options->percent, str.length > 0, &options->moduleArgs); } } if (!(percentType & FF_PERCENTAGE_TYPE_HIDE_OTHERS_BIT)) { if (device->main && index > 0) ffStrbufAppendS(&str, " (*)"); } ffStrbufPutTo(&str, stdout); } else { FF_STRBUF_AUTO_DESTROY percentageNum = ffStrbufCreate(); FF_STRBUF_AUTO_DESTROY percentageBar = ffStrbufCreate(); if(device->volume != FF_SOUND_VOLUME_UNKNOWN) { if (percentType & FF_PERCENTAGE_TYPE_NUM_BIT) ffPercentAppendNum(&percentageNum, device->volume, options->percent, false, &options->moduleArgs); if (percentType & FF_PERCENTAGE_TYPE_BAR_BIT) ffPercentAppendBar(&percentageBar, device->volume, options->percent, &options->moduleArgs); } FF_PRINT_FORMAT_CHECKED(FF_SOUND_MODULE_NAME, index, &options->moduleArgs, FF_PRINT_TYPE_DEFAULT, ((FFformatarg[]) { FF_ARG(device->main, "is-main"), FF_ARG(device->name, "name"), FF_ARG(percentageNum, "volume-percentage"), FF_ARG(device->identifier, "identifier"), FF_ARG(percentageBar, "volume-percentage-bar"), FF_ARG(device->platformApi, "platform-api"), })); } } bool ffPrintSound(FFSoundOptions* options) { bool success = false; FF_LIST_AUTO_DESTROY result = ffListCreate(sizeof(FFSoundDevice)); const char* error = ffDetectSound(&result); if(error) { ffPrintError(FF_SOUND_MODULE_NAME, 0, &options->moduleArgs, FF_PRINT_TYPE_DEFAULT, "%s", error); goto exit; } { FF_LIST_AUTO_DESTROY filtered = ffListCreate(sizeof(FFSoundDevice*)); FF_LIST_FOR_EACH(FFSoundDevice, device, result) { switch (options->soundType) { case FF_SOUND_TYPE_MAIN: if (!device->main) continue; break; case FF_SOUND_TYPE_ACTIVE: if (!device->active) continue; break; case FF_SOUND_TYPE_ALL: break; } *(FFSoundDevice**)ffListAdd(&filtered) = device; } if(filtered.length == 0) { ffPrintError(FF_SOUND_MODULE_NAME, 0, &options->moduleArgs, FF_PRINT_TYPE_DEFAULT, "No active sound devices found"); goto exit; } uint8_t index = 1; FF_LIST_FOR_EACH(FFSoundDevice*, device, filtered) { printDevice(options, *device, filtered.length == 1 ? 0 : index++); } } success = true; exit: FF_LIST_FOR_EACH(FFSoundDevice, device, result) { ffStrbufDestroy(&device->identifier); ffStrbufDestroy(&device->name); ffStrbufDestroy(&device->platformApi); } return success; } void ffParseSoundJsonObject(FFSoundOptions* options, yyjson_val* module) { yyjson_val *key, *val; size_t idx, max; yyjson_obj_foreach(module, idx, max, key, val) { if (ffJsonConfigParseModuleArgs(key, val, &options->moduleArgs)) continue; if (unsafe_yyjson_equals_str(key, "soundType")) { int value; const char* error = ffJsonConfigParseEnum(val, &value, (FFKeyValuePair[]) { { "main", FF_SOUND_TYPE_MAIN }, { "active", FF_SOUND_TYPE_ACTIVE }, { "all", FF_SOUND_TYPE_ALL }, {}, }); if (error) ffPrintError(FF_SOUND_MODULE_NAME, 0, &options->moduleArgs, FF_PRINT_TYPE_DEFAULT, "Invalid %s value: %s", unsafe_yyjson_get_str(key), error); else options->soundType = (FFSoundType) value; continue; } if (ffPercentParseJsonObject(key, val, &options->percent)) continue; ffPrintError(FF_SOUND_MODULE_NAME, 0, &options->moduleArgs, FF_PRINT_TYPE_DEFAULT, "Unknown JSON key %s", unsafe_yyjson_get_str(key)); } } void ffGenerateSoundJsonConfig(FFSoundOptions* options, yyjson_mut_doc* doc, yyjson_mut_val* module) { ffJsonConfigGenerateModuleArgsConfig(doc, module, &options->moduleArgs); switch (options->soundType) { case FF_SOUND_TYPE_MAIN: yyjson_mut_obj_add_str(doc, module, "soundType", "main"); break; case FF_SOUND_TYPE_ACTIVE: yyjson_mut_obj_add_str(doc, module, "soundType", "active"); break; case FF_SOUND_TYPE_ALL: yyjson_mut_obj_add_str(doc, module, "soundType", "all"); break; } ffPercentGenerateJsonConfig(doc, module, options->percent); } bool ffGenerateSoundJsonResult(FF_MAYBE_UNUSED FFSoundOptions* options, yyjson_mut_doc* doc, yyjson_mut_val* module) { FF_LIST_AUTO_DESTROY result = ffListCreate(sizeof(FFSoundDevice)); const char* error = ffDetectSound(&result); if(error) { yyjson_mut_obj_add_str(doc, module, "error", error); return false; } yyjson_mut_val* arr = yyjson_mut_obj_add_arr(doc, module, "result"); FF_LIST_FOR_EACH(FFSoundDevice, item, result) { yyjson_mut_val* obj = yyjson_mut_arr_add_obj(doc, arr); yyjson_mut_obj_add_bool(doc, obj, "active", item->active); yyjson_mut_obj_add_bool(doc, obj, "main", item->main); if (item->volume != FF_SOUND_VOLUME_UNKNOWN) yyjson_mut_obj_add_uint(doc, obj, "volume", item->volume); else yyjson_mut_obj_add_null(doc, obj, "volume"); yyjson_mut_obj_add_strbuf(doc, obj, "name", &item->name); yyjson_mut_obj_add_strbuf(doc, obj, "identifier", &item->identifier); yyjson_mut_obj_add_strbuf(doc, obj, "platformApi", &item->platformApi); } FF_LIST_FOR_EACH(FFSoundDevice, device, result) { ffStrbufDestroy(&device->identifier); ffStrbufDestroy(&device->name); ffStrbufDestroy(&device->platformApi); } return true; } void ffInitSoundOptions(FFSoundOptions* options) { ffOptionInitModuleArg(&options->moduleArgs, ""); options->soundType = FF_SOUND_TYPE_MAIN; options->percent = (FFPercentageModuleConfig) { 80, 90, 0 }; } void ffDestroySoundOptions(FFSoundOptions* options) { ffOptionDestroyModuleArg(&options->moduleArgs); } FFModuleBaseInfo ffSoundModuleInfo = { .name = FF_SOUND_MODULE_NAME, .description = "Print sound devices, volume, etc", .initOptions = (void*) ffInitSoundOptions, .destroyOptions = (void*) ffDestroySoundOptions, .parseJsonObject = (void*) ffParseSoundJsonObject, .printModule = (void*) ffPrintSound, .generateJsonResult = (void*) ffGenerateSoundJsonResult, .generateJsonConfig = (void*) ffGenerateSoundJsonConfig, .formatArgs = FF_FORMAT_ARG_LIST(((FFModuleFormatArg[]) { {"Is main sound device", "is-main"}, {"Device name", "name"}, {"Volume (in percentage num)", "volume-percentage"}, {"Identifier", "identifier"}, {"Volume (in percentage bar)", "volume-percentage-bar"}, {"Platform API used", "platform-api"}, })) }; ================================================ FILE: src/modules/sound/sound.h ================================================ #pragma once #include "option.h" #define FF_SOUND_MODULE_NAME "Sound" bool ffPrintSound(FFSoundOptions* options); void ffInitSoundOptions(FFSoundOptions* options); void ffDestroySoundOptions(FFSoundOptions* options); extern FFModuleBaseInfo ffSoundModuleInfo; ================================================ FILE: src/modules/swap/option.h ================================================ #pragma once #include "common/option.h" #include "common/percent.h" typedef struct FFSwapOptions { FFModuleArgs moduleArgs; FFPercentageModuleConfig percent; bool separate; } FFSwapOptions; static_assert(sizeof(FFSwapOptions) <= FF_OPTION_MAX_SIZE, "FFSwapOptions size exceeds maximum allowed size"); ================================================ FILE: src/modules/swap/swap.c ================================================ #include "common/printing.h" #include "common/jsonconfig.h" #include "common/percent.h" #include "common/size.h" #include "common/stringUtils.h" #include "detection/swap/swap.h" #include "modules/swap/swap.h" void printSwap(FFSwapOptions* options, uint8_t index, FFSwapResult* storage) { FF_STRBUF_AUTO_DESTROY key = ffStrbufCreate(); if (options->moduleArgs.key.length == 0) { if (storage->name.length > 0) ffStrbufSetF(&key, "%s (%s)", FF_SWAP_MODULE_NAME, storage->name.chars); else ffStrbufSetS(&key, FF_SWAP_MODULE_NAME); } else { ffStrbufClear(&key); FF_PARSE_FORMAT_STRING_CHECKED(&key, &options->moduleArgs.key, ((FFformatarg[]) { FF_ARG(index, "index"), FF_ARG(storage->name, "name"), FF_ARG(options->moduleArgs.keyIcon, "icon"), })); } FF_STRBUF_AUTO_DESTROY usedPretty = ffStrbufCreate(); ffSizeAppendNum(storage->bytesUsed, &usedPretty); FF_STRBUF_AUTO_DESTROY totalPretty = ffStrbufCreate(); ffSizeAppendNum(storage->bytesTotal, &totalPretty); double percentage = storage->bytesTotal == 0 ? 0 : (double) storage->bytesUsed / (double) storage->bytesTotal * 100.0; FFPercentageTypeFlags percentType = options->percent.type == 0 ? instance.config.display.percentType : options->percent.type; if(options->moduleArgs.outputFormat.length == 0) { ffPrintLogoAndKey(key.chars, 0, &options->moduleArgs, FF_PRINT_TYPE_NO_CUSTOM_KEY); FF_STRBUF_AUTO_DESTROY str = ffStrbufCreate(); if (storage->bytesTotal == 0) { if(percentType & FF_PERCENTAGE_TYPE_BAR_BIT) { ffPercentAppendBar(&str, 0, options->percent, &options->moduleArgs); ffStrbufAppendC(&str, ' '); } if(!(percentType & FF_PERCENTAGE_TYPE_HIDE_OTHERS_BIT)) ffStrbufAppendS(&str, "Disabled"); else ffPercentAppendNum(&str, 0, options->percent, str.length > 0, &options->moduleArgs); } else { if(percentType & FF_PERCENTAGE_TYPE_BAR_BIT) { ffPercentAppendBar(&str, percentage, options->percent, &options->moduleArgs); ffStrbufAppendC(&str, ' '); } if(!(percentType & FF_PERCENTAGE_TYPE_HIDE_OTHERS_BIT)) ffStrbufAppendF(&str, "%s / %s ", usedPretty.chars, totalPretty.chars); if(percentType & FF_PERCENTAGE_TYPE_NUM_BIT) ffPercentAppendNum(&str, percentage, options->percent, str.length > 0, &options->moduleArgs); } ffStrbufTrimRight(&str, ' '); ffStrbufPutTo(&str, stdout); } else { FF_STRBUF_AUTO_DESTROY percentageNum = ffStrbufCreate(); if (percentType & FF_PERCENTAGE_TYPE_NUM_BIT) ffPercentAppendNum(&percentageNum, percentage, options->percent, false, &options->moduleArgs); FF_STRBUF_AUTO_DESTROY percentageBar = ffStrbufCreate(); if (percentType & FF_PERCENTAGE_TYPE_BAR_BIT) ffPercentAppendBar(&percentageBar, percentage, options->percent, &options->moduleArgs); FF_PRINT_FORMAT_CHECKED(key.chars, index, &options->moduleArgs, FF_PRINT_TYPE_NO_CUSTOM_KEY, ((FFformatarg[]){ FF_ARG(usedPretty, "used"), FF_ARG(totalPretty, "total"), FF_ARG(percentageNum, "percentage"), FF_ARG(percentageBar, "percentage-bar"), FF_ARG(storage->name, "name"), })); } } bool ffPrintSwap(FFSwapOptions* options) { FF_LIST_AUTO_DESTROY result = ffListCreate(sizeof(FFSwapResult)); const char* error = ffDetectSwap(&result); if(error) { ffPrintError(FF_SWAP_MODULE_NAME, 0, &options->moduleArgs, FF_PRINT_TYPE_DEFAULT, "%s", error); return false; } if (options->separate) { uint8_t index = 0; FF_LIST_FOR_EACH(FFSwapResult, storage, result) { ++index; printSwap(options, index, storage); } } else { FFSwapResult total = { .name = ffStrbufCreate(), }; FF_LIST_FOR_EACH(FFSwapResult, storage, result) { total.bytesUsed += storage->bytesUsed; total.bytesTotal += storage->bytesTotal; } printSwap(options, 0, &total); ffStrbufDestroy(&total.name); } FF_LIST_FOR_EACH(FFSwapResult, storage, result) { ffStrbufDestroy(&storage->name); } return true; } void ffParseSwapJsonObject(FFSwapOptions* options, yyjson_val* module) { yyjson_val *key, *val; size_t idx, max; yyjson_obj_foreach(module, idx, max, key, val) { if (ffJsonConfigParseModuleArgs(key, val, &options->moduleArgs)) continue; if (ffPercentParseJsonObject(key, val, &options->percent)) continue; if (unsafe_yyjson_equals_str(key, "separate")) { options->separate = yyjson_get_bool(val); continue; } ffPrintError(FF_SWAP_MODULE_NAME, 0, &options->moduleArgs, FF_PRINT_TYPE_DEFAULT, "Unknown JSON key %s", unsafe_yyjson_get_str(key)); } } void ffGenerateSwapJsonConfig(FFSwapOptions* options, yyjson_mut_doc* doc, yyjson_mut_val* module) { ffPercentGenerateJsonConfig(doc, module, options->percent); ffJsonConfigGenerateModuleArgsConfig(doc, module, &options->moduleArgs); yyjson_mut_obj_add_bool(doc, module, "separate", options->separate); } bool ffGenerateSwapJsonResult(FF_MAYBE_UNUSED FFSwapOptions* options, yyjson_mut_doc* doc, yyjson_mut_val* module) { FF_LIST_AUTO_DESTROY result = ffListCreate(sizeof(FFSwapResult)); const char* error = ffDetectSwap(&result); if(error) { yyjson_mut_obj_add_str(doc, module, "error", error); return false; } yyjson_mut_val* arr = yyjson_mut_obj_add_arr(doc, module, "result"); FF_LIST_FOR_EACH(FFSwapResult, storage, result) { yyjson_mut_val* obj = yyjson_mut_arr_add_obj(doc, arr); yyjson_mut_obj_add_strbuf(doc, obj, "name", &storage->name); yyjson_mut_obj_add_uint(doc, obj, "used", storage->bytesUsed); yyjson_mut_obj_add_uint(doc, obj, "total", storage->bytesTotal); } FF_LIST_FOR_EACH(FFSwapResult, storage, result) { ffStrbufDestroy(&storage->name); } return true; } void ffInitSwapOptions(FFSwapOptions* options) { ffOptionInitModuleArg(&options->moduleArgs, "󰓡"); options->percent = (FFPercentageModuleConfig) { 50, 80, 0 }; options->separate = false; } void ffDestroySwapOptions(FFSwapOptions* options) { ffOptionDestroyModuleArg(&options->moduleArgs); } FFModuleBaseInfo ffSwapModuleInfo = { .name = FF_SWAP_MODULE_NAME, .description = "Print swap (paging file) space usage", .initOptions = (void*) ffInitSwapOptions, .destroyOptions = (void*) ffDestroySwapOptions, .parseJsonObject = (void*) ffParseSwapJsonObject, .printModule = (void*) ffPrintSwap, .generateJsonResult = (void*) ffGenerateSwapJsonResult, .generateJsonConfig = (void*) ffGenerateSwapJsonConfig, .formatArgs = FF_FORMAT_ARG_LIST(((FFModuleFormatArg[]) { {"Used size", "used"}, {"Total size", "total"}, {"Percentage used (num)", "percentage"}, {"Percentage used (bar)", "percentage-bar"}, {"Name", "name"}, })) }; ================================================ FILE: src/modules/swap/swap.h ================================================ #pragma once #include "option.h" #define FF_SWAP_MODULE_NAME "Swap" bool ffPrintSwap(FFSwapOptions* options); void ffInitSwapOptions(FFSwapOptions* options); void ffDestroySwapOptions(FFSwapOptions* options); extern FFModuleBaseInfo ffSwapModuleInfo; ================================================ FILE: src/modules/terminal/option.h ================================================ #pragma once #include "common/option.h" typedef struct FFTerminalOptions { FFModuleArgs moduleArgs; } FFTerminalOptions; static_assert(sizeof(FFTerminalOptions) <= FF_OPTION_MAX_SIZE, "FFTerminalOptions size exceeds maximum allowed size"); ================================================ FILE: src/modules/terminal/terminal.c ================================================ #include "common/printing.h" #include "common/jsonconfig.h" #include "common/stringUtils.h" #include "detection/terminalshell/terminalshell.h" #include "modules/terminal/terminal.h" bool ffPrintTerminal(FFTerminalOptions* options) { const FFTerminalResult* result = ffDetectTerminal(); if(result->processName.length == 0) { ffPrintError(FF_TERMINAL_MODULE_NAME, 0, &options->moduleArgs, FF_PRINT_TYPE_DEFAULT, "Couldn't detect terminal"); return false; } if(options->moduleArgs.outputFormat.length == 0) { ffPrintLogoAndKey(FF_TERMINAL_MODULE_NAME, 0, &options->moduleArgs, FF_PRINT_TYPE_DEFAULT); if(result->version.length) printf("%s %s\n", result->prettyName.chars, result->version.chars); else ffStrbufPutTo(&result->prettyName, stdout); } else { FF_PRINT_FORMAT_CHECKED(FF_TERMINAL_MODULE_NAME, 0, &options->moduleArgs, FF_PRINT_TYPE_DEFAULT, ((FFformatarg[]){ FF_ARG(result->processName, "process-name"), FF_ARG(result->exe, "exe"), FF_ARG(result->exeName, "exe-name"), FF_ARG(result->pid, "pid"), FF_ARG(result->prettyName, "pretty-name"), FF_ARG(result->version, "version"), FF_ARG(result->exePath, "exe-path"), FF_ARG(result->tty, "tty"), })); } return true; } void ffParseTerminalJsonObject(FFTerminalOptions* options, yyjson_val* module) { yyjson_val *key, *val; size_t idx, max; yyjson_obj_foreach(module, idx, max, key, val) { if (ffJsonConfigParseModuleArgs(key, val, &options->moduleArgs)) continue; ffPrintError(FF_TERMINAL_MODULE_NAME, 0, &options->moduleArgs, FF_PRINT_TYPE_DEFAULT, "Unknown JSON key %s", unsafe_yyjson_get_str(key)); } } void ffGenerateTerminalJsonConfig(FFTerminalOptions* options, yyjson_mut_doc* doc, yyjson_mut_val* module) { ffJsonConfigGenerateModuleArgsConfig(doc, module, &options->moduleArgs); } bool ffGenerateTerminalJsonResult(FF_MAYBE_UNUSED FFTerminalOptions* options, yyjson_mut_doc* doc, yyjson_mut_val* module) { const FFTerminalResult* result = ffDetectTerminal(); if(result->processName.length == 0) { yyjson_mut_obj_add_str(doc, module, "error", "Couldn't detect terminal"); return false; } yyjson_mut_val* obj = yyjson_mut_obj_add_obj(doc, module, "result"); yyjson_mut_obj_add_strbuf(doc, obj, "processName", &result->processName); yyjson_mut_obj_add_strbuf(doc, obj, "exe", &result->exe); yyjson_mut_obj_add_strcpy(doc, obj, "exeName", result->exeName); yyjson_mut_obj_add_strbuf(doc, obj, "exePath", &result->exePath); yyjson_mut_obj_add_uint(doc, obj, "pid", result->pid); yyjson_mut_obj_add_uint(doc, obj, "ppid", result->ppid); yyjson_mut_obj_add_strbuf(doc, obj, "prettyName", &result->prettyName); yyjson_mut_obj_add_strbuf(doc, obj, "version", &result->version); yyjson_mut_obj_add_strbuf(doc, obj, "tty", &result->tty); return true; } void ffInitTerminalOptions(FFTerminalOptions* options) { ffOptionInitModuleArg(&options->moduleArgs, ""); } void ffDestroyTerminalOptions(FFTerminalOptions* options) { ffOptionDestroyModuleArg(&options->moduleArgs); } FFModuleBaseInfo ffTerminalModuleInfo = { .name = FF_TERMINAL_MODULE_NAME, .description = "Print current terminal name and version", .initOptions = (void*) ffInitTerminalOptions, .destroyOptions = (void*) ffDestroyTerminalOptions, .parseJsonObject = (void*) ffParseTerminalJsonObject, .printModule = (void*) ffPrintTerminal, .generateJsonResult = (void*) ffGenerateTerminalJsonResult, .generateJsonConfig = (void*) ffGenerateTerminalJsonConfig, .formatArgs = FF_FORMAT_ARG_LIST(((FFModuleFormatArg[]) { {"Terminal process name", "process-name"}, {"The first argument of the command line when running the terminal", "exe"}, {"Terminal base name of arg0", "exe-name"}, {"Terminal pid", "pid"}, {"Terminal pretty name", "pretty-name"}, {"Terminal version", "version"}, {"Terminal full exe path", "exe-path"}, {"Terminal tty / pts used", "tty"}, })) }; ================================================ FILE: src/modules/terminal/terminal.h ================================================ #pragma once #include "option.h" #define FF_TERMINAL_MODULE_NAME "Terminal" bool ffPrintTerminal(FFTerminalOptions* options); void ffInitTerminalOptions(FFTerminalOptions* options); void ffDestroyTerminalOptions(FFTerminalOptions* options); extern FFModuleBaseInfo ffTerminalModuleInfo; ================================================ FILE: src/modules/terminalfont/option.h ================================================ #pragma once #include "common/option.h" typedef struct FFTerminalFontOptions { FFModuleArgs moduleArgs; } FFTerminalFontOptions; static_assert(sizeof(FFTerminalFontOptions) <= FF_OPTION_MAX_SIZE, "FFTerminalFontOptions size exceeds maximum allowed size"); ================================================ FILE: src/modules/terminalfont/terminalfont.c ================================================ #include "common/printing.h" #include "common/jsonconfig.h" #include "common/stringUtils.h" #include "detection/terminalfont/terminalfont.h" #include "modules/terminalfont/terminalfont.h" #define FF_TERMINALFONT_DISPLAY_NAME "Terminal Font" bool ffPrintTerminalFont(FFTerminalFontOptions* options) { bool success = false; FFTerminalFontResult terminalFont; ffFontInit(&terminalFont.font); ffFontInit(&terminalFont.fallback); ffStrbufInit(&terminalFont.error); if(!ffDetectTerminalFont(&terminalFont)) { ffPrintError(FF_TERMINALFONT_DISPLAY_NAME, 0, &options->moduleArgs, FF_PRINT_TYPE_DEFAULT, "%s", terminalFont.error.chars); } else { if(options->moduleArgs.outputFormat.length == 0) { ffPrintLogoAndKey(FF_TERMINALFONT_DISPLAY_NAME, 0, &options->moduleArgs, FF_PRINT_TYPE_DEFAULT); ffStrbufWriteTo(&terminalFont.font.pretty, stdout); if(terminalFont.fallback.pretty.length) { fputs(" / ", stdout); ffStrbufWriteTo(&terminalFont.fallback.pretty, stdout); } putchar('\n'); } else { FF_PRINT_FORMAT_CHECKED(FF_TERMINALFONT_DISPLAY_NAME, 0, &options->moduleArgs, FF_PRINT_TYPE_DEFAULT, ((FFformatarg[]){ FF_ARG(terminalFont.font.pretty, "combined"), FF_ARG(terminalFont.font.name, "name"), FF_ARG(terminalFont.font.size, "size"), FF_ARG(terminalFont.font.styles, "styles"), })); } success = true; } ffStrbufDestroy(&terminalFont.error); ffFontDestroy(&terminalFont.font); ffFontDestroy(&terminalFont.fallback); return success; } void ffParseTerminalFontJsonObject(FFTerminalFontOptions* options, yyjson_val* module) { yyjson_val *key, *val; size_t idx, max; yyjson_obj_foreach(module, idx, max, key, val) { if (ffJsonConfigParseModuleArgs(key, val, &options->moduleArgs)) continue; ffPrintError(FF_TERMINALFONT_MODULE_NAME, 0, &options->moduleArgs, FF_PRINT_TYPE_DEFAULT, "Unknown JSON key %s", unsafe_yyjson_get_str(key)); } } void ffGenerateTerminalFontJsonConfig(FFTerminalFontOptions* options, yyjson_mut_doc* doc, yyjson_mut_val* module) { ffJsonConfigGenerateModuleArgsConfig(doc, module, &options->moduleArgs); } bool ffGenerateTerminalFontJsonResult(FF_MAYBE_UNUSED FFTerminalFontOptions* options, yyjson_mut_doc* doc, yyjson_mut_val* module) { bool success = false; FFTerminalFontResult result; ffFontInit(&result.font); ffFontInit(&result.fallback); ffStrbufInit(&result.error); if(!ffDetectTerminalFont(&result)) yyjson_mut_obj_add_strbuf(doc, module, "error", &result.error); else { yyjson_mut_val* obj = yyjson_mut_obj_add_obj(doc, module, "result"); yyjson_mut_val* font = yyjson_mut_obj_add_obj(doc, obj, "font"); yyjson_mut_obj_add_strbuf(doc, font, "name", &result.font.name); yyjson_mut_obj_add_strbuf(doc, font, "size", &result.font.size); yyjson_mut_val* fontStyles = yyjson_mut_obj_add_arr(doc, font, "styles"); FF_LIST_FOR_EACH(FFstrbuf, style, result.font.styles) { yyjson_mut_arr_add_strbuf(doc, fontStyles, style); } yyjson_mut_obj_add_strbuf(doc, font, "pretty", &result.font.pretty); yyjson_mut_val* fallback = yyjson_mut_obj_add_obj(doc, obj, "fallback"); yyjson_mut_obj_add_strbuf(doc, fallback, "name", &result.fallback.name); yyjson_mut_obj_add_strbuf(doc, fallback, "size", &result.fallback.size); yyjson_mut_val* fallbackStyles = yyjson_mut_obj_add_arr(doc, fallback, "styles"); FF_LIST_FOR_EACH(FFstrbuf, style, result.fallback.styles) { yyjson_mut_arr_add_strbuf(doc, fallbackStyles, style); } yyjson_mut_obj_add_strbuf(doc, fallback, "pretty", &result.fallback.pretty); success = true; } ffStrbufDestroy(&result.error); ffFontDestroy(&result.font); ffFontDestroy(&result.fallback); return success; } void ffInitTerminalFontOptions(FFTerminalFontOptions* options) { ffOptionInitModuleArg(&options->moduleArgs, ""); } void ffDestroyTerminalFontOptions(FFTerminalFontOptions* options) { ffOptionDestroyModuleArg(&options->moduleArgs); } FFModuleBaseInfo ffTerminalFontModuleInfo = { .name = FF_TERMINALFONT_MODULE_NAME, .description = "Print font name and size used by current terminal", .initOptions = (void*) ffInitTerminalFontOptions, .destroyOptions = (void*) ffDestroyTerminalFontOptions, .parseJsonObject = (void*) ffParseTerminalFontJsonObject, .printModule = (void*) ffPrintTerminalFont, .generateJsonResult = (void*) ffGenerateTerminalFontJsonResult, .generateJsonConfig = (void*) ffGenerateTerminalFontJsonConfig, .formatArgs = FF_FORMAT_ARG_LIST(((FFModuleFormatArg[]) { {"Terminal font combined", "combined"}, {"Terminal font name", "name"}, {"Terminal font size", "size"}, {"Terminal font styles", "styles"}, })), }; ================================================ FILE: src/modules/terminalfont/terminalfont.h ================================================ #pragma once #include "option.h" #define FF_TERMINALFONT_MODULE_NAME "TerminalFont" bool ffPrintTerminalFont(FFTerminalFontOptions* options); void ffInitTerminalFontOptions(FFTerminalFontOptions* options); void ffDestroyTerminalFontOptions(FFTerminalFontOptions* options); extern FFModuleBaseInfo ffTerminalFontModuleInfo; ================================================ FILE: src/modules/terminalsize/option.h ================================================ #pragma once #include "common/option.h" typedef struct FFTerminalSizeOptions { FFModuleArgs moduleArgs; } FFTerminalSizeOptions; static_assert(sizeof(FFTerminalSizeOptions) <= FF_OPTION_MAX_SIZE, "FFTerminalSizeOptions size exceeds maximum allowed size"); ================================================ FILE: src/modules/terminalsize/terminalsize.c ================================================ #include "common/printing.h" #include "common/jsonconfig.h" #include "common/stringUtils.h" #include "detection/terminalsize/terminalsize.h" #include "modules/terminalsize/terminalsize.h" #define FF_TERMINALSIZE_DISPLAY_NAME "Terminal Size" bool ffPrintTerminalSize(FFTerminalSizeOptions* options) { FFTerminalSizeResult result = {}; if(!ffDetectTerminalSize(&result)) { ffPrintError(FF_TERMINALSIZE_DISPLAY_NAME, 0, &options->moduleArgs, FF_PRINT_TYPE_DEFAULT, "Failed to detect terminal size"); return false; } if(options->moduleArgs.outputFormat.length == 0) { ffPrintLogoAndKey(FF_TERMINALSIZE_DISPLAY_NAME, 0, &options->moduleArgs, FF_PRINT_TYPE_DEFAULT); printf("%u columns x %u rows", result.columns, result.rows); if (result.width != 0 && result.height != 0) printf(" (%upx x %upx)", result.width, result.height); putchar('\n'); } else { FF_PRINT_FORMAT_CHECKED(FF_TERMINALSIZE_DISPLAY_NAME, 0, &options->moduleArgs, FF_PRINT_TYPE_DEFAULT, ((FFformatarg[]){ FF_ARG(result.rows, "rows"), FF_ARG(result.columns, "columns"), FF_ARG(result.width, "width"), FF_ARG(result.height, "height"), })); } return true; } void ffParseTerminalSizeJsonObject(FFTerminalSizeOptions* options, yyjson_val* module) { yyjson_val *key, *val; size_t idx, max; yyjson_obj_foreach(module, idx, max, key, val) { if (ffJsonConfigParseModuleArgs(key, val, &options->moduleArgs)) continue; ffPrintError(FF_TERMINALSIZE_MODULE_NAME, 0, &options->moduleArgs, FF_PRINT_TYPE_DEFAULT, "Unknown JSON key %s", unsafe_yyjson_get_str(key)); } } void ffGenerateTerminalSizeJsonConfig(FFTerminalSizeOptions* options, yyjson_mut_doc* doc, yyjson_mut_val* module) { ffJsonConfigGenerateModuleArgsConfig(doc, module, &options->moduleArgs); } bool ffGenerateTerminalSizeJsonResult(FF_MAYBE_UNUSED FFTerminalSizeOptions* options, yyjson_mut_doc* doc, yyjson_mut_val* module) { FFTerminalSizeResult result; if(!ffDetectTerminalSize(&result)) { yyjson_mut_obj_add_str(doc, module, "error", "Failed to detect terminal size"); return false; } yyjson_mut_val* obj = yyjson_mut_obj_add_obj(doc, module, "result"); yyjson_mut_obj_add_uint(doc, obj, "columns", result.columns); yyjson_mut_obj_add_uint(doc, obj, "rows", result.rows); yyjson_mut_obj_add_uint(doc, obj, "width", result.width); yyjson_mut_obj_add_uint(doc, obj, "height", result.height); return true; } void ffInitTerminalSizeOptions(FFTerminalSizeOptions* options) { ffOptionInitModuleArg(&options->moduleArgs, "󰲎"); } void ffDestroyTerminalSizeOptions(FFTerminalSizeOptions* options) { ffOptionDestroyModuleArg(&options->moduleArgs); } FFModuleBaseInfo ffTerminalSizeModuleInfo = { .name = FF_TERMINALSIZE_MODULE_NAME, .description = "Print current terminal size", .initOptions = (void*) ffInitTerminalSizeOptions, .destroyOptions = (void*) ffDestroyTerminalSizeOptions, .parseJsonObject = (void*) ffParseTerminalSizeJsonObject, .printModule = (void*) ffPrintTerminalSize, .generateJsonResult = (void*) ffGenerateTerminalSizeJsonResult, .generateJsonConfig = (void*) ffGenerateTerminalSizeJsonConfig, .formatArgs = FF_FORMAT_ARG_LIST(((FFModuleFormatArg[]) { {"Terminal rows", "rows"}, {"Terminal columns", "columns"}, {"Terminal width (in pixels)", "width"}, {"Terminal height (in pixels)", "height"}, })), }; ================================================ FILE: src/modules/terminalsize/terminalsize.h ================================================ #pragma once #include "option.h" #define FF_TERMINALSIZE_MODULE_NAME "TerminalSize" bool ffPrintTerminalSize(FFTerminalSizeOptions* options); void ffInitTerminalSizeOptions(FFTerminalSizeOptions* options); void ffDestroyTerminalSizeOptions(FFTerminalSizeOptions* options); extern FFModuleBaseInfo ffTerminalSizeModuleInfo; ================================================ FILE: src/modules/terminaltheme/option.h ================================================ #pragma once #include "common/option.h" typedef struct FFTerminalThemeOptions { FFModuleArgs moduleArgs; } FFTerminalThemeOptions; static_assert(sizeof(FFTerminalThemeOptions) <= FF_OPTION_MAX_SIZE, "FFTerminalThemeOptions size exceeds maximum allowed size"); ================================================ FILE: src/modules/terminaltheme/terminaltheme.c ================================================ #include "common/printing.h" #include "common/jsonconfig.h" #include "common/stringUtils.h" #include "detection/terminaltheme/terminaltheme.h" #include "modules/terminaltheme/terminaltheme.h" #include #define FF_TERMINALTHEME_DISPLAY_NAME "Terminal Theme" bool ffPrintTerminalTheme(FFTerminalThemeOptions* options) { FFTerminalThemeResult result = {}; if(!ffDetectTerminalTheme(&result, false)) { ffPrintError(FF_TERMINALTHEME_DISPLAY_NAME, 0, &options->moduleArgs, FF_PRINT_TYPE_DEFAULT, "Failed to detect terminal theme"); return false; } if(options->moduleArgs.outputFormat.length == 0) { ffPrintLogoAndKey(FF_TERMINALTHEME_DISPLAY_NAME, 0, &options->moduleArgs, FF_PRINT_TYPE_DEFAULT); printf("#%02" PRIX16 "%02" PRIX16 "%02" PRIX16 " (FG) - #%02" PRIX16 "%02" PRIX16 "%02" PRIX16 " (BG) [%s]\n", result.fg.r, result.fg.g, result.fg.b, result.bg.r, result.bg.g, result.bg.b, result.bg.dark ? "Dark" : "Light"); } else { char fg[32], bg[32]; const char* fgType = result.fg.dark ? "Dark" : "Light"; const char* bgType = result.bg.dark ? "Dark" : "Light"; snprintf(fg, ARRAY_SIZE(fg), "#%02" PRIX16 "%02" PRIX16 "%02" PRIX16, result.fg.r, result.fg.g, result.fg.b); snprintf(bg, ARRAY_SIZE(bg), "#%02" PRIX16 "%02" PRIX16 "%02" PRIX16, result.bg.r, result.bg.g, result.bg.b); FF_PRINT_FORMAT_CHECKED(FF_TERMINALTHEME_DISPLAY_NAME, 0, &options->moduleArgs, FF_PRINT_TYPE_DEFAULT, ((FFformatarg[]){ FF_ARG(fg, "fg-color"), FF_ARG(fgType, "fg-type"), FF_ARG(bg, "bg-color"), FF_ARG(bgType, "bg-type"), })); } return true; } void ffParseTerminalThemeJsonObject(FFTerminalThemeOptions* options, yyjson_val* module) { yyjson_val *key, *val; size_t idx, max; yyjson_obj_foreach(module, idx, max, key, val) { if (ffJsonConfigParseModuleArgs(key, val, &options->moduleArgs)) continue; ffPrintError(FF_TERMINALTHEME_DISPLAY_NAME, 0, &options->moduleArgs, FF_PRINT_TYPE_DEFAULT, "Unknown JSON key %s", unsafe_yyjson_get_str(key)); } } void ffGenerateTerminalThemeJsonConfig(FFTerminalThemeOptions* options, yyjson_mut_doc* doc, yyjson_mut_val* module) { ffJsonConfigGenerateModuleArgsConfig(doc, module, &options->moduleArgs); } bool ffGenerateTerminalThemeJsonResult(FF_MAYBE_UNUSED FFTerminalThemeOptions* options, yyjson_mut_doc* doc, yyjson_mut_val* module) { FFTerminalThemeResult result = {}; if(!ffDetectTerminalTheme(&result, false)) { yyjson_mut_obj_add_str(doc, module, "error", "Failed to detect terminal theme"); return false; } yyjson_mut_val* obj = yyjson_mut_obj_add_obj(doc, module, "result"); yyjson_mut_val* fg = yyjson_mut_obj_add_obj(doc, obj, "fg"); yyjson_mut_obj_add_uint(doc, fg, "r", result.fg.r); yyjson_mut_obj_add_uint(doc, fg, "g", result.fg.g); yyjson_mut_obj_add_uint(doc, fg, "b", result.fg.b); yyjson_mut_obj_add_bool(doc, fg, "dark", result.fg.dark); yyjson_mut_val* bg = yyjson_mut_obj_add_obj(doc, obj, "bg"); yyjson_mut_obj_add_uint(doc, bg, "r", result.bg.r); yyjson_mut_obj_add_uint(doc, bg, "g", result.bg.g); yyjson_mut_obj_add_uint(doc, bg, "b", result.bg.b); yyjson_mut_obj_add_bool(doc, bg, "dark", result.bg.dark); return true; } void ffInitTerminalThemeOptions(FFTerminalThemeOptions* options) { ffOptionInitModuleArg(&options->moduleArgs, "󰔎"); } void ffDestroyTerminalThemeOptions(FFTerminalThemeOptions* options) { ffOptionDestroyModuleArg(&options->moduleArgs); } FFModuleBaseInfo ffTerminalThemeModuleInfo = { .name = FF_TERMINALTHEME_MODULE_NAME, .description = "Print current terminal theme (foreground and background colors)", .initOptions = (void*) ffInitTerminalThemeOptions, .destroyOptions = (void*) ffDestroyTerminalThemeOptions, .parseJsonObject = (void*) ffParseTerminalThemeJsonObject, .printModule = (void*) ffPrintTerminalTheme, .generateJsonResult = (void*) ffGenerateTerminalThemeJsonResult, .generateJsonConfig = (void*) ffGenerateTerminalThemeJsonConfig, .formatArgs = FF_FORMAT_ARG_LIST(((FFModuleFormatArg[]) { {"Terminal foreground color", "fg-color"}, {"Terminal foreground type (Dark / Light)", "fg-type"}, {"Terminal background color", "bg-color"}, {"Terminal background type (Dark / Light)", "bg-type"}, })) }; ================================================ FILE: src/modules/terminaltheme/terminaltheme.h ================================================ #pragma once #include "option.h" #define FF_TERMINALTHEME_MODULE_NAME "TerminalTheme" bool ffPrintTerminalTheme(FFTerminalThemeOptions* options); void ffInitTerminalThemeOptions(FFTerminalThemeOptions* options); void ffDestroyTerminalThemeOptions(FFTerminalThemeOptions* options); extern FFModuleBaseInfo ffTerminalThemeModuleInfo; ================================================ FILE: src/modules/theme/option.h ================================================ #pragma once #include "common/option.h" typedef struct FFThemeOptions { FFModuleArgs moduleArgs; } FFThemeOptions; static_assert(sizeof(FFThemeOptions) <= FF_OPTION_MAX_SIZE, "FFThemeOptions size exceeds maximum allowed size"); ================================================ FILE: src/modules/theme/theme.c ================================================ #include "common/printing.h" #include "common/jsonconfig.h" #include "common/stringUtils.h" #include "detection/theme/theme.h" #include "modules/theme/theme.h" bool ffPrintTheme(FFThemeOptions* options) { FFThemeResult result = { .theme1 = ffStrbufCreate(), .theme2 = ffStrbufCreate() }; const char* error = ffDetectTheme(&result); if(error) { ffPrintError(FF_THEME_MODULE_NAME, 0, &options->moduleArgs, FF_PRINT_TYPE_DEFAULT, "%s", error); return false; } if(options->moduleArgs.outputFormat.length == 0) { ffPrintLogoAndKey(FF_THEME_MODULE_NAME, 0, &options->moduleArgs, FF_PRINT_TYPE_DEFAULT); if (result.theme1.length) ffStrbufWriteTo(&result.theme1, stdout); if (result.theme2.length) { if (result.theme1.length) fputs(", ", stdout); ffStrbufWriteTo(&result.theme2, stdout); } putchar('\n'); } else { FF_PRINT_FORMAT_CHECKED(FF_THEME_MODULE_NAME, 0, &options->moduleArgs, FF_PRINT_TYPE_DEFAULT, ((FFformatarg[]){ FF_ARG(result.theme1, "theme1"), FF_ARG(result.theme2, "theme2"), })); } ffStrbufDestroy(&result.theme1); ffStrbufDestroy(&result.theme2); return true; } void ffParseThemeJsonObject(FFThemeOptions* options, yyjson_val* module) { yyjson_val *key, *val; size_t idx, max; yyjson_obj_foreach(module, idx, max, key, val) { if (ffJsonConfigParseModuleArgs(key, val, &options->moduleArgs)) continue; ffPrintError(FF_THEME_MODULE_NAME, 0, &options->moduleArgs, FF_PRINT_TYPE_DEFAULT, "Unknown JSON key %s", unsafe_yyjson_get_str(key)); } } void ffGenerateThemeJsonConfig(FFThemeOptions* options, yyjson_mut_doc* doc, yyjson_mut_val* module) { ffJsonConfigGenerateModuleArgsConfig(doc, module, &options->moduleArgs); } bool ffGenerateThemeJsonResult(FF_MAYBE_UNUSED FFThemeOptions* options, yyjson_mut_doc* doc, yyjson_mut_val* module) { FFThemeResult result = { .theme1 = ffStrbufCreate(), .theme2 = ffStrbufCreate() }; const char* error = ffDetectTheme(&result); if(error) { yyjson_mut_obj_add_str(doc, module, "error", error); return false; } yyjson_mut_val* theme = yyjson_mut_obj_add_obj(doc, module, "result"); yyjson_mut_obj_add_strbuf(doc, theme, "theme1", &result.theme1); yyjson_mut_obj_add_strbuf(doc, theme, "theme2", &result.theme2); ffStrbufDestroy(&result.theme1); ffStrbufDestroy(&result.theme2); return true; } void ffInitThemeOptions(FFThemeOptions* options) { ffOptionInitModuleArg(&options->moduleArgs, "󰉼"); } void ffDestroyThemeOptions(FFThemeOptions* options) { ffOptionDestroyModuleArg(&options->moduleArgs); } FFModuleBaseInfo ffThemeModuleInfo = { .name = FF_THEME_MODULE_NAME, .description = "Print current theme of desktop environment", .initOptions = (void*) ffInitThemeOptions, .destroyOptions = (void*) ffDestroyThemeOptions, .parseJsonObject = (void*) ffParseThemeJsonObject, .printModule = (void*) ffPrintTheme, .generateJsonResult = (void*) ffGenerateThemeJsonResult, .generateJsonConfig = (void*) ffGenerateThemeJsonConfig, .formatArgs = FF_FORMAT_ARG_LIST(((FFModuleFormatArg[]) { {"Theme part 1", "theme1"}, {"Theme part 2", "theme2"}, })) }; ================================================ FILE: src/modules/theme/theme.h ================================================ #pragma once #include "option.h" #define FF_THEME_MODULE_NAME "Theme" bool ffPrintTheme(FFThemeOptions* options); void ffInitThemeOptions(FFThemeOptions* options); void ffDestroyThemeOptions(FFThemeOptions* options); extern FFModuleBaseInfo ffThemeModuleInfo; ================================================ FILE: src/modules/title/option.h ================================================ #pragma once #include "common/option.h" typedef struct FFTitleOptions { FFModuleArgs moduleArgs; FFstrbuf colorUser; FFstrbuf colorAt; FFstrbuf colorHost; bool fqdn; } FFTitleOptions; static_assert(sizeof(FFTitleOptions) <= FF_OPTION_MAX_SIZE, "FFTitleOptions size exceeds maximum allowed size"); ================================================ FILE: src/modules/title/title.c ================================================ #include "common/printing.h" #include "common/jsonconfig.h" #include "common/textModifier.h" #include "modules/title/title.h" static void appendText(FFstrbuf* output, const FFstrbuf* text, const FFstrbuf* color) { if (!instance.config.display.pipe) { if (instance.config.display.brightColor) ffStrbufAppendS(output, FASTFETCH_TEXT_MODIFIER_BOLT); if (color->length > 0) ffStrbufAppendF(output, "\e[%sm", color->chars); else if (instance.config.display.colorTitle.length > 0) ffStrbufAppendF(output, "\e[%sm", instance.config.display.colorTitle.chars); } ffStrbufAppend(output, text); if(!instance.config.display.pipe) ffStrbufAppendS(output, FASTFETCH_TEXT_MODIFIER_RESET); } bool ffPrintTitle(FFTitleOptions* options) { FF_STRBUF_AUTO_DESTROY userNameColored = ffStrbufCreate(); appendText(&userNameColored, &instance.state.platform.userName, &options->colorUser); FF_STRBUF_AUTO_DESTROY hostName = ffStrbufCreateCopy(&instance.state.platform.hostName); if (!options->fqdn) ffStrbufSubstrBeforeFirstC(&hostName, '.'); instance.state.titleFqdn = options->fqdn; FF_STRBUF_AUTO_DESTROY hostNameColored = ffStrbufCreate(); appendText(&hostNameColored, &hostName, &options->colorHost); FF_STRBUF_AUTO_DESTROY atColored = ffStrbufCreate(); if (!instance.config.display.pipe && options->colorAt.length > 0) { ffStrbufAppendF(&atColored, "\e[%sm", options->colorAt.chars); ffStrbufAppendC(&atColored, '@'); ffStrbufAppendS(&atColored, FASTFETCH_TEXT_MODIFIER_RESET); } else ffStrbufAppendC(&atColored, '@'); if (options->moduleArgs.outputFormat.length == 0) { ffPrintLogoAndKey(FF_TITLE_MODULE_NAME, 0, &options->moduleArgs, FF_PRINT_TYPE_DEFAULT); ffStrbufWriteTo(&userNameColored, stdout); ffStrbufWriteTo(&atColored, stdout); ffStrbufPutTo(&hostNameColored, stdout); } else { FF_STRBUF_AUTO_DESTROY cwdTilde = ffStrbufCreate(); if ( #if _WIN32 ffStrbufStartsWithIgnCase #else ffStrbufStartsWith #endif (&instance.state.platform.cwd, &instance.state.platform.homeDir)) { ffStrbufAppendS(&cwdTilde, "~/"); ffStrbufAppendNS(&cwdTilde, instance.state.platform.cwd.length - instance.state.platform.homeDir.length, &instance.state.platform.cwd.chars[instance.state.platform.homeDir.length]); } else ffStrbufSet(&cwdTilde, &instance.state.platform.cwd); if (cwdTilde.length > 1) ffStrbufTrimRight(&cwdTilde, '/'); FF_PRINT_FORMAT_CHECKED(FF_TITLE_MODULE_NAME, 0, &options->moduleArgs, FF_PRINT_TYPE_DEFAULT, ((FFformatarg[]){ FF_ARG(instance.state.platform.userName, "user-name"), FF_ARG(hostName, "host-name"), FF_ARG(instance.state.platform.homeDir, "home-dir"), FF_ARG(instance.state.platform.exePath, "exe-path"), FF_ARG(instance.state.platform.userShell, "user-shell"), FF_ARG(userNameColored, "user-name-colored"), FF_ARG(atColored, "at-symbol-colored"), FF_ARG(hostNameColored, "host-name-colored"), FF_ARG(instance.state.platform.fullUserName, "full-user-name"), #ifndef _WIN32 FF_ARG(instance.state.platform.uid, "user-id"), #else FF_ARG(instance.state.platform.sid, "user-id"), #endif FF_ARG(instance.state.platform.pid, "pid"), FF_ARG(cwdTilde, "cwd"), })); } return true; } void ffParseTitleJsonObject(FFTitleOptions* options, yyjson_val* module) { yyjson_val *key, *val; size_t idx, max; yyjson_obj_foreach(module, idx, max, key, val) { if (ffJsonConfigParseModuleArgs(key, val, &options->moduleArgs)) continue; if (unsafe_yyjson_equals_str(key, "fqdn")) { options->fqdn = yyjson_get_bool(val); continue; } if (unsafe_yyjson_equals_str(key, "color")) { if (!yyjson_is_obj(val)) continue; yyjson_val* color = yyjson_obj_get(val, "user"); if (color) ffOptionParseColor(yyjson_get_str(color), &options->colorUser); color = yyjson_obj_get(val, "at"); if (color) ffOptionParseColor(yyjson_get_str(color), &options->colorAt); color = yyjson_obj_get(val, "host"); if (color) ffOptionParseColor(yyjson_get_str(color), &options->colorHost); continue; } ffPrintError(FF_TITLE_MODULE_NAME, 0, NULL, FF_PRINT_TYPE_NO_CUSTOM_KEY, "Unknown JSON key %s", unsafe_yyjson_get_str(key)); } } void ffGenerateTitleJsonConfig(FFTitleOptions* options, yyjson_mut_doc* doc, yyjson_mut_val* module) { ffJsonConfigGenerateModuleArgsConfig(doc, module, &options->moduleArgs); yyjson_mut_obj_add_bool(doc, module, "fqdn", options->fqdn); yyjson_mut_val* color = yyjson_mut_obj_add_obj(doc, module, "color"); yyjson_mut_obj_add_strbuf(doc, color, "user", &options->colorUser); yyjson_mut_obj_add_strbuf(doc, color, "at", &options->colorAt); yyjson_mut_obj_add_strbuf(doc, color, "host", &options->colorHost); } bool ffGenerateTitleJsonResult(FF_MAYBE_UNUSED FFTitleOptions* options, yyjson_mut_doc* doc, yyjson_mut_val* module) { yyjson_mut_val* obj = yyjson_mut_obj_add_obj(doc, module, "result"); #ifdef _WIN32 yyjson_mut_obj_add_strbuf(doc, obj, "userId", &instance.state.platform.sid); #else yyjson_mut_obj_add_uint(doc, obj, "userId", instance.state.platform.uid); #endif yyjson_mut_obj_add_strbuf(doc, obj, "userName", &instance.state.platform.userName); yyjson_mut_obj_add_strbuf(doc, obj, "fullUserName", &instance.state.platform.fullUserName); yyjson_mut_obj_add_strbuf(doc, obj, "hostName", &instance.state.platform.hostName); yyjson_mut_obj_add_strbuf(doc, obj, "homeDir", &instance.state.platform.homeDir); yyjson_mut_obj_add_strbuf(doc, obj, "exePath", &instance.state.platform.exePath); yyjson_mut_obj_add_strbuf(doc, obj, "userShell", &instance.state.platform.userShell); yyjson_mut_obj_add_uint(doc, obj, "pid", instance.state.platform.pid); yyjson_mut_obj_add_strbuf(doc, obj, "cwd", &instance.state.platform.cwd); return true; } void ffInitTitleOptions(FFTitleOptions* options) { ffOptionInitModuleArg(&options->moduleArgs, ""); ffStrbufSetStatic(&options->moduleArgs.key, " "); options->fqdn = false; ffStrbufInit(&options->colorUser); ffStrbufInit(&options->colorAt); ffStrbufInit(&options->colorHost); } void ffDestroyTitleOptions(FFTitleOptions* options) { ffOptionDestroyModuleArg(&options->moduleArgs); ffStrbufDestroy(&options->colorUser); ffStrbufDestroy(&options->colorAt); ffStrbufDestroy(&options->colorHost); } FFModuleBaseInfo ffTitleModuleInfo = { .name = FF_TITLE_MODULE_NAME, .description = "Print title, which contains your user name, hostname", .initOptions = (void*) ffInitTitleOptions, .destroyOptions = (void*) ffDestroyTitleOptions, .parseJsonObject = (void*) ffParseTitleJsonObject, .printModule = (void*) ffPrintTitle, .generateJsonResult = (void*) ffGenerateTitleJsonResult, .generateJsonConfig = (void*) ffGenerateTitleJsonConfig, .formatArgs = FF_FORMAT_ARG_LIST(((FFModuleFormatArg[]) { {"User name", "user-name"}, {"Host name", "host-name"}, {"Home directory", "home-dir"}, {"Executable path of current process", "exe-path"}, {"User's default shell", "user-shell"}, {"User name (colored)", "user-name-colored"}, {"@ symbol (colored)", "at-symbol-colored"}, {"Host name (colored)", "host-name-colored"}, {"Full user name", "full-user-name"}, {"UID (*nix) / SID (Windows)", "user-id"}, {"PID of current process", "pid"}, {"CWD with home dir replaced by `~`", "cwd"}, })) }; ================================================ FILE: src/modules/title/title.h ================================================ #pragma once #include "option.h" #define FF_TITLE_MODULE_NAME "Title" bool ffPrintTitle(FFTitleOptions* options); void ffInitTitleOptions(FFTitleOptions* options); void ffDestroyTitleOptions(FFTitleOptions* options); extern FFModuleBaseInfo ffTitleModuleInfo; ================================================ FILE: src/modules/tpm/option.h ================================================ #pragma once #include "common/option.h" typedef struct FFTPMOptions { FFModuleArgs moduleArgs; } FFTPMOptions; static_assert(sizeof(FFTPMOptions) <= FF_OPTION_MAX_SIZE, "FFTPMOptions size exceeds maximum allowed size"); ================================================ FILE: src/modules/tpm/tpm.c ================================================ #include "common/printing.h" #include "common/jsonconfig.h" #include "common/stringUtils.h" #include "detection/tpm/tpm.h" #include "modules/tpm/tpm.h" bool ffPrintTPM(FFTPMOptions* options) { FFTPMResult result = { .version = ffStrbufCreate(), .description = ffStrbufCreate() }; const char* error = ffDetectTPM(&result); if(error) { ffPrintError(FF_TPM_MODULE_NAME, 0, &options->moduleArgs, FF_PRINT_TYPE_DEFAULT, "%s", error); return false; } if(options->moduleArgs.outputFormat.length == 0) { ffPrintLogoAndKey(FF_TPM_MODULE_NAME, 0, &options->moduleArgs, FF_PRINT_TYPE_DEFAULT); if (result.description.length > 0) ffStrbufPutTo(&result.description, stdout); else ffStrbufPutTo(&result.version, stdout); } else { FF_PRINT_FORMAT_CHECKED(FF_TPM_MODULE_NAME, 0, &options->moduleArgs, FF_PRINT_TYPE_DEFAULT, ((FFformatarg[]){ FF_ARG(result.version, "version"), FF_ARG(result.description, "description"), })); } ffStrbufDestroy(&result.version); ffStrbufDestroy(&result.description); return true; } void ffParseTPMJsonObject(FFTPMOptions* options, yyjson_val* module) { yyjson_val *key, *val; size_t idx, max; yyjson_obj_foreach(module, idx, max, key, val) { if (ffJsonConfigParseModuleArgs(key, val, &options->moduleArgs)) continue; ffPrintError(FF_TPM_MODULE_NAME, 0, &options->moduleArgs, FF_PRINT_TYPE_DEFAULT, "Unknown JSON key %s", unsafe_yyjson_get_str(key)); } } void ffGenerateTPMJsonConfig(FFTPMOptions* options, yyjson_mut_doc* doc, yyjson_mut_val* module) { ffJsonConfigGenerateModuleArgsConfig(doc, module, &options->moduleArgs); } bool ffGenerateTPMJsonResult(FF_MAYBE_UNUSED FFTPMOptions* options, yyjson_mut_doc* doc, yyjson_mut_val* module) { FFTPMResult result = { .version = ffStrbufCreate(), .description = ffStrbufCreate() }; const char* error = ffDetectTPM(&result); if(error) { yyjson_mut_obj_add_str(doc, module, "error", error); return false; } yyjson_mut_val* obj = yyjson_mut_obj_add_obj(doc, module, "result"); yyjson_mut_obj_add_strbuf(doc, obj, "version", &result.version); yyjson_mut_obj_add_strbuf(doc, obj, "description", &result.description); ffStrbufDestroy(&result.version); ffStrbufDestroy(&result.description); return true; } void ffInitTPMOptions(FFTPMOptions* options) { ffOptionInitModuleArg(&options->moduleArgs, ""); } void ffDestroyTPMOptions(FFTPMOptions* options) { ffOptionDestroyModuleArg(&options->moduleArgs); } FFModuleBaseInfo ffTPMModuleInfo = { .name = FF_TPM_MODULE_NAME, .description = "Print info of Trusted Platform Module (TPM) Security Device", .initOptions = (void*) ffInitTPMOptions, .destroyOptions = (void*) ffDestroyTPMOptions, .parseJsonObject = (void*) ffParseTPMJsonObject, .printModule = (void*) ffPrintTPM, .generateJsonResult = (void*) ffGenerateTPMJsonResult, .generateJsonConfig = (void*) ffGenerateTPMJsonConfig, .formatArgs = FF_FORMAT_ARG_LIST(((FFModuleFormatArg[]) { {"TPM device version", "version"}, {"TPM general description", "description"}, })) }; ================================================ FILE: src/modules/tpm/tpm.h ================================================ #pragma once #include "option.h" #define FF_TPM_MODULE_NAME "TPM" bool ffPrintTPM(FFTPMOptions* options); void ffInitTPMOptions(FFTPMOptions* options); void ffDestroyTPMOptions(FFTPMOptions* options); extern FFModuleBaseInfo ffTPMModuleInfo; ================================================ FILE: src/modules/uptime/option.h ================================================ #pragma once #include "common/option.h" typedef struct FFUptimeOptions { FFModuleArgs moduleArgs; } FFUptimeOptions; static_assert(sizeof(FFUptimeOptions) <= FF_OPTION_MAX_SIZE, "FFUptimeOptions size exceeds maximum allowed size"); ================================================ FILE: src/modules/uptime/uptime.c ================================================ #include "common/duration.h" #include "common/printing.h" #include "common/jsonconfig.h" #include "common/time.h" #include "common/stringUtils.h" #include "detection/uptime/uptime.h" #include "modules/uptime/uptime.h" bool ffPrintUptime(FFUptimeOptions* options) { FFUptimeResult result = {}; const char* error = ffDetectUptime(&result); if(error) { ffPrintError(FF_UPTIME_MODULE_NAME, 0, &options->moduleArgs, FF_PRINT_TYPE_DEFAULT, "%s", error); return false; } uint64_t uptime = result.uptime; FF_STRBUF_AUTO_DESTROY buffer = ffStrbufCreate(); ffDurationAppendNum((uptime + 500) / 1000, &buffer); if(options->moduleArgs.outputFormat.length == 0) { ffPrintLogoAndKey(FF_UPTIME_MODULE_NAME, 0, &options->moduleArgs, FF_PRINT_TYPE_DEFAULT); ffStrbufPutTo(&buffer, stdout); } else { uint32_t milliseconds = (uint32_t) (uptime % 1000); uptime /= 1000; uint32_t seconds = (uint32_t) (uptime % 60); uptime /= 60; uint32_t minutes = (uint32_t) (uptime % 60); uptime /= 60; uint32_t hours = (uint32_t) (uptime % 24); uptime /= 24; uint32_t days = (uint32_t) uptime; FFTimeGetAgeResult age = ffTimeGetAge(result.bootTime, ffTimeGetNow()); FF_PRINT_FORMAT_CHECKED(FF_UPTIME_MODULE_NAME, 0, &options->moduleArgs, FF_PRINT_TYPE_DEFAULT, ((FFformatarg[]){ FF_ARG(days, "days"), FF_ARG(hours, "hours"), FF_ARG(minutes, "minutes"), FF_ARG(seconds, "seconds"), FF_ARG(milliseconds, "milliseconds"), {FF_ARG_TYPE_STRING, ffTimeToShortStr(result.bootTime), "boot-time"}, FF_ARG(age.years, "years"), FF_ARG(age.daysOfYear, "days-of-year"), FF_ARG(age.yearsFraction, "years-fraction"), FF_ARG(buffer, "formatted") })); } return true; } void ffParseUptimeJsonObject(FFUptimeOptions* options, yyjson_val* module) { yyjson_val *key, *val; size_t idx, max; yyjson_obj_foreach(module, idx, max, key, val) { if (ffJsonConfigParseModuleArgs(key, val, &options->moduleArgs)) continue; ffPrintError(FF_UPTIME_MODULE_NAME, 0, &options->moduleArgs, FF_PRINT_TYPE_DEFAULT, "Unknown JSON key %s", unsafe_yyjson_get_str(key)); } } void ffGenerateUptimeJsonConfig(FFUptimeOptions* options, yyjson_mut_doc* doc, yyjson_mut_val* module) { ffJsonConfigGenerateModuleArgsConfig(doc, module, &options->moduleArgs); } bool ffGenerateUptimeJsonResult(FF_MAYBE_UNUSED FFUptimeOptions* options, yyjson_mut_doc* doc, yyjson_mut_val* module) { FFUptimeResult result; const char* error = ffDetectUptime(&result); if(error) { yyjson_mut_obj_add_str(doc, module, "error", error); return false; } yyjson_mut_val* obj = yyjson_mut_obj_add_obj(doc, module, "result"); yyjson_mut_obj_add_uint(doc, obj, "uptime", result.uptime); yyjson_mut_obj_add_strcpy(doc, obj, "bootTime", ffTimeToFullStr(result.bootTime)); return true; } void ffInitUptimeOptions(FFUptimeOptions* options) { ffOptionInitModuleArg(&options->moduleArgs, ""); } void ffDestroyUptimeOptions(FFUptimeOptions* options) { ffOptionDestroyModuleArg(&options->moduleArgs); } FFModuleBaseInfo ffUptimeModuleInfo = { .name = FF_UPTIME_MODULE_NAME, .description = "Print how long system has been running", .initOptions = (void*) ffInitUptimeOptions, .destroyOptions = (void*) ffDestroyUptimeOptions, .parseJsonObject = (void*) ffParseUptimeJsonObject, .printModule = (void*) ffPrintUptime, .generateJsonResult = (void*) ffGenerateUptimeJsonResult, .generateJsonConfig = (void*) ffGenerateUptimeJsonConfig, .formatArgs = FF_FORMAT_ARG_LIST(((FFModuleFormatArg[]) { {"Days after boot", "days"}, {"Hours after boot", "hours"}, {"Minutes after boot", "minutes"}, {"Seconds after boot", "seconds"}, {"Milliseconds after boot", "milliseconds"}, {"Boot time in local timezone", "boot-time"}, {"Years integer after boot", "years"}, {"Days of year after boot", "days-of-year"}, {"Years fraction after boot", "years-fraction"}, {"Formatted uptime", "formatted"}, })) }; ================================================ FILE: src/modules/uptime/uptime.h ================================================ #pragma once #include "option.h" #define FF_UPTIME_MODULE_NAME "Uptime" bool ffPrintUptime(FFUptimeOptions* options); void ffInitUptimeOptions(FFUptimeOptions* options); void ffDestroyUptimeOptions(FFUptimeOptions* options); extern FFModuleBaseInfo ffUptimeModuleInfo; ================================================ FILE: src/modules/users/option.h ================================================ #pragma once #include "common/option.h" typedef struct FFUsersOptions { FFModuleArgs moduleArgs; bool compact; bool myselfOnly; } FFUsersOptions; static_assert(sizeof(FFUsersOptions) <= FF_OPTION_MAX_SIZE, "FFUsersOptions size exceeds maximum allowed size"); ================================================ FILE: src/modules/users/users.c ================================================ #include "common/printing.h" #include "common/jsonconfig.h" #include "common/time.h" #include "common/stringUtils.h" #include "detection/users/users.h" #include "modules/users/users.h" #pragma GCC diagnostic ignored "-Wformat" // warning: unknown conversion type character 'F' in format bool ffPrintUsers(FFUsersOptions* options) { FF_LIST_AUTO_DESTROY users = ffListCreate(sizeof(FFUserResult)); const char* error = ffDetectUsers(options, &users); if(error) { ffPrintError(FF_USERS_MODULE_NAME, 0, &options->moduleArgs, FF_PRINT_TYPE_DEFAULT, "%s", error); return false; } if(users.length == 0) { ffPrintError(FF_USERS_MODULE_NAME, 0, &options->moduleArgs, FF_PRINT_TYPE_DEFAULT, "%s", "Unable to detect any users"); return false; } if(options->moduleArgs.outputFormat.length == 0) { if(options->compact) { ffPrintLogoAndKey(FF_USERS_MODULE_NAME, 0, &options->moduleArgs, FF_PRINT_TYPE_DEFAULT); FF_STRBUF_AUTO_DESTROY result = ffStrbufCreate(); for(uint32_t i = 0; i < users.length; ++i) { if(i > 0) ffStrbufAppendS(&result, ", "); FFUserResult* user = FF_LIST_GET(FFUserResult, users, i); ffStrbufAppend(&result, &user->name); } ffStrbufPutTo(&result, stdout); } else { for(uint32_t i = 0; i < users.length; ++i) { FFUserResult* user = FF_LIST_GET(FFUserResult, users, i); ffPrintLogoAndKey(FF_USERS_MODULE_NAME, users.length == 1 ? 0 : (uint8_t) (i + 1), &options->moduleArgs, FF_PRINT_TYPE_DEFAULT); FF_STRBUF_AUTO_DESTROY result = ffStrbufCreateCopy(&user->name); if(user->hostName.length) ffStrbufAppendF(&result, "@%s", user->hostName.chars); if(user->loginTime) ffStrbufAppendF(&result, " - login time %s", ffTimeToShortStr(user->loginTime)); ffStrbufPutTo(&result, stdout); } } } else { uint64_t now = ffTimeGetNow(); for(uint32_t i = 0; i < users.length; ++i) { FFUserResult* user = FF_LIST_GET(FFUserResult, users, i); uint64_t duration = now - user->loginTime; uint32_t milliseconds = (uint32_t) (duration % 1000); duration /= 1000; uint32_t seconds = (uint32_t) (duration % 60); duration /= 60; uint32_t minutes = (uint32_t) (duration % 60); duration /= 60; uint32_t hours = (uint32_t) (duration % 24); duration /= 24; uint32_t days = (uint32_t) duration; FFTimeGetAgeResult age = ffTimeGetAge(user->loginTime, ffTimeGetNow()); FF_PRINT_FORMAT_CHECKED(FF_USERS_MODULE_NAME, users.length == 1 ? 0 : (uint8_t) (i + 1), &options->moduleArgs, FF_PRINT_TYPE_DEFAULT, ((FFformatarg[]){ FF_ARG(user->name, "name"), FF_ARG(user->hostName, "host-name"), FF_ARG(user->sessionName, "session-name"), FF_ARG(user->clientIp, "client-ip"), {FF_ARG_TYPE_STRING, ffTimeToShortStr(user->loginTime), "login-time"}, FF_ARG(days, "days"), FF_ARG(hours, "hours"), FF_ARG(minutes, "minutes"), FF_ARG(seconds, "seconds"), FF_ARG(milliseconds, "milliseconds"), FF_ARG(age.years, "years"), FF_ARG(age.daysOfYear, "days-of-year"), FF_ARG(age.yearsFraction, "years-fraction"), })); } } FF_LIST_FOR_EACH(FFUserResult, user, users) { ffStrbufDestroy(&user->clientIp); ffStrbufDestroy(&user->hostName); ffStrbufDestroy(&user->sessionName); ffStrbufDestroy(&user->name); } return true; } void ffParseUsersJsonObject(FFUsersOptions* options, yyjson_val* module) { yyjson_val *key, *val; size_t idx, max; yyjson_obj_foreach(module, idx, max, key, val) { if (unsafe_yyjson_equals_str(key, "type") || unsafe_yyjson_equals_str(key, "condition")) continue; if (ffJsonConfigParseModuleArgs(key, val, &options->moduleArgs)) continue; if (unsafe_yyjson_equals_str(key, "compact")) { options->compact = yyjson_get_bool(val); continue; } if (unsafe_yyjson_equals_str(key, "myselfOnly")) { options->myselfOnly = yyjson_get_bool(val); continue; } ffPrintError(FF_USERS_MODULE_NAME, 0, &options->moduleArgs, FF_PRINT_TYPE_DEFAULT, "Unknown JSON key %s", unsafe_yyjson_get_str(key)); } } void ffGenerateUsersJsonConfig(FFUsersOptions* options, yyjson_mut_doc* doc, yyjson_mut_val* module) { ffJsonConfigGenerateModuleArgsConfig(doc, module, &options->moduleArgs); yyjson_mut_obj_add_bool(doc, module, "compact", options->compact); yyjson_mut_obj_add_bool(doc, module, "myselfOnly", options->myselfOnly); } bool ffGenerateUsersJsonResult(FFUsersOptions* options, yyjson_mut_doc* doc, yyjson_mut_val* module) { FF_LIST_AUTO_DESTROY results = ffListCreate(sizeof(FFUserResult)); const char* error = ffDetectUsers(options, &results); if(error) { yyjson_mut_obj_add_str(doc, module, "error", error); return false; } yyjson_mut_val* arr = yyjson_mut_obj_add_arr(doc, module, "result"); FF_LIST_FOR_EACH(FFUserResult, user, results) { yyjson_mut_val* obj = yyjson_mut_arr_add_obj(doc, arr); yyjson_mut_obj_add_strbuf(doc, obj, "name", &user->name); yyjson_mut_obj_add_strbuf(doc, obj, "hostName", &user->hostName); yyjson_mut_obj_add_strbuf(doc, obj, "sessionName", &user->sessionName); yyjson_mut_obj_add_strbuf(doc, obj, "clientIp", &user->clientIp); const char* pstr = ffTimeToFullStr(user->loginTime); if (*pstr) yyjson_mut_obj_add_strcpy(doc, obj, "loginTime", pstr); else yyjson_mut_obj_add_null(doc, obj, "loginTime"); } FF_LIST_FOR_EACH(FFUserResult, user, results) { ffStrbufDestroy(&user->clientIp); ffStrbufDestroy(&user->hostName); ffStrbufDestroy(&user->sessionName); ffStrbufDestroy(&user->name); } return true; } void ffInitUsersOptions(FFUsersOptions* options) { ffOptionInitModuleArg(&options->moduleArgs, ""); options->compact = false; options->myselfOnly = false; } void ffDestroyUsersOptions(FFUsersOptions* options) { ffOptionDestroyModuleArg(&options->moduleArgs); } FFModuleBaseInfo ffUsersModuleInfo = { .name = FF_USERS_MODULE_NAME, .description = "Print users currently logged in", .initOptions = (void*) ffInitUsersOptions, .destroyOptions = (void*) ffDestroyUsersOptions, .parseJsonObject = (void*) ffParseUsersJsonObject, .printModule = (void*) ffPrintUsers, .generateJsonResult = (void*) ffGenerateUsersJsonResult, .generateJsonConfig = (void*) ffGenerateUsersJsonConfig, .formatArgs = FF_FORMAT_ARG_LIST(((FFModuleFormatArg[]) { {"User name", "name"}, {"Host name", "host-name"}, {"Session name", "session-name"}, {"Client IP", "client-ip"}, {"Login Time in local timezone", "login-time"}, {"Days after login", "days"}, {"Hours after login", "hours"}, {"Minutes after login", "minutes"}, {"Seconds after login", "seconds"}, {"Milliseconds after login", "milliseconds"}, {"Years integer after login", "years"}, {"Days of year after login", "days-of-year"}, {"Years fraction after login", "years-fraction"}, })) }; ================================================ FILE: src/modules/users/users.h ================================================ #pragma once #include "option.h" #define FF_USERS_MODULE_NAME "Users" bool ffPrintUsers(FFUsersOptions* options); void ffInitUsersOptions(FFUsersOptions* options); void ffDestroyUsersOptions(FFUsersOptions* options); extern FFModuleBaseInfo ffUsersModuleInfo; ================================================ FILE: src/modules/version/option.h ================================================ #pragma once #include "common/option.h" typedef struct FFVersionOptions { FFModuleArgs moduleArgs; } FFVersionOptions; static_assert(sizeof(FFVersionOptions) <= FF_OPTION_MAX_SIZE, "FFVersionOptions size exceeds maximum allowed size"); ================================================ FILE: src/modules/version/version.c ================================================ #include "common/printing.h" #include "common/jsonconfig.h" #include "common/stringUtils.h" #include "detection/libc/libc.h" #include "detection/version/version.h" #include "modules/version/version.h" bool ffPrintVersion(FFVersionOptions* options) { FFVersionResult* result = &ffVersionResult; if(options->moduleArgs.outputFormat.length == 0) { ffPrintLogoAndKey(FF_VERSION_MODULE_NAME, 0, &options->moduleArgs, FF_PRINT_TYPE_DEFAULT); printf("%s %s%s%s (%s)\n", result->projectName, result->version, result->versionTweak, result->debugMode ? "-debug" : "", result->architecture); } else { FFLibcResult libcResult; FF_STRBUF_AUTO_DESTROY buf = ffStrbufCreate(); if (!ffDetectLibc(&libcResult)) { ffStrbufSetS(&buf, libcResult.name); if (libcResult.version) { ffStrbufAppendC(&buf, ' '); ffStrbufAppendS(&buf, libcResult.version); } } const char* buildType = result->debugMode ? "debug" : "release"; FF_PRINT_FORMAT_CHECKED(FF_VERSION_MODULE_NAME, 0, &options->moduleArgs, FF_PRINT_TYPE_DEFAULT, ((FFformatarg[]){ FF_ARG(result->projectName, "project-name"), FF_ARG(result->version, "version"), FF_ARG(result->versionTweak, "version-tweak"), FF_ARG(buildType, "build-type"), FF_ARG(result->sysName, "sysname"), FF_ARG(result->architecture, "arch"), FF_ARG(result->cmakeBuiltType, "cmake-built-type"), FF_ARG(result->compileTime, "compile-time"), FF_ARG(result->compiler, "compiler"), FF_ARG(buf, "libc"), })); } return true; } void ffParseVersionJsonObject(FFVersionOptions* options, yyjson_val* module) { yyjson_val *key, *val; size_t idx, max; yyjson_obj_foreach(module, idx, max, key, val) { if (ffJsonConfigParseModuleArgs(key, val, &options->moduleArgs)) continue; ffPrintError(FF_VERSION_MODULE_NAME, 0, &options->moduleArgs, FF_PRINT_TYPE_DEFAULT, "Unknown JSON key %s", unsafe_yyjson_get_str(key)); } } void ffGenerateVersionJsonConfig(FFVersionOptions* options, yyjson_mut_doc* doc, yyjson_mut_val* module) { ffJsonConfigGenerateModuleArgsConfig(doc, module, &options->moduleArgs); } bool ffGenerateVersionJsonResult(FF_MAYBE_UNUSED FFVersionOptions* options, yyjson_mut_doc* doc, yyjson_mut_val* module) { FFVersionResult* result = &ffVersionResult; yyjson_mut_val* obj = yyjson_mut_obj_add_obj(doc, module, "result"); yyjson_mut_obj_add_str(doc, obj, "projectName", result->projectName); yyjson_mut_obj_add_str(doc, obj, "sysName", result->sysName); yyjson_mut_obj_add_str(doc, obj, "architecture", result->architecture); yyjson_mut_obj_add_str(doc, obj, "version", result->version); yyjson_mut_obj_add_str(doc, obj, "versionGit", result->versionGit); yyjson_mut_obj_add_str(doc, obj, "cmakeBuiltType", result->cmakeBuiltType); yyjson_mut_obj_add_str(doc, obj, "compileTime", result->compileTime); yyjson_mut_obj_add_str(doc, obj, "compiler", result->compiler); yyjson_mut_obj_add_bool(doc, obj, "debugMode", result->debugMode); FFLibcResult libcResult; if (ffDetectLibc(&libcResult)) { yyjson_mut_obj_add_null(doc, obj, "libc"); } else { FF_STRBUF_AUTO_DESTROY buf = ffStrbufCreateS(libcResult.name); if (libcResult.version) { ffStrbufAppendC(&buf, ' '); ffStrbufAppendS(&buf, libcResult.version); } yyjson_mut_obj_add_strbuf(doc, obj, "libc", &buf); } return true; } void ffInitVersionOptions(FFVersionOptions* options) { ffOptionInitModuleArg(&options->moduleArgs, ""); } void ffDestroyVersionOptions(FFVersionOptions* options) { ffOptionDestroyModuleArg(&options->moduleArgs); } FFModuleBaseInfo ffVersionModuleInfo = { .name = FF_VERSION_MODULE_NAME, .description = "Print Fastfetch version", .initOptions = (void*) ffInitVersionOptions, .destroyOptions = (void*) ffDestroyVersionOptions, .parseJsonObject = (void*) ffParseVersionJsonObject, .printModule = (void*) ffPrintVersion, .generateJsonResult = (void*) ffGenerateVersionJsonResult, .generateJsonConfig = (void*) ffGenerateVersionJsonConfig, .formatArgs = FF_FORMAT_ARG_LIST(((FFModuleFormatArg[]) { {"Project name", "project-name"}, {"Version", "version"}, {"Version tweak", "version-tweak"}, {"Build type (debug or release)", "build-type"}, {"System name", "sysname"}, {"Architecture", "arch"}, {"CMake build type when compiling (Debug, Release, RelWithDebInfo, MinSizeRel)", "cmake-built-type"}, {"Date time when compiling", "compile-time"}, {"Compiler used when compiling", "compiler"}, {"Libc used when compiling", "libc"}, })) }; ================================================ FILE: src/modules/version/version.h ================================================ #pragma once #include "option.h" #define FF_VERSION_MODULE_NAME "Version" bool ffPrintVersion(FFVersionOptions* options); void ffInitVersionOptions(FFVersionOptions* options); void ffDestroyVersionOptions(FFVersionOptions* options); extern FFModuleBaseInfo ffVersionModuleInfo; ================================================ FILE: src/modules/vulkan/option.h ================================================ #pragma once #include "common/option.h" typedef struct FFVulkanOptions { FFModuleArgs moduleArgs; } FFVulkanOptions; static_assert(sizeof(FFVulkanOptions) <= FF_OPTION_MAX_SIZE, "FFVulkanOptions size exceeds maximum allowed size"); ================================================ FILE: src/modules/vulkan/vulkan.c ================================================ #include "common/printing.h" #include "common/jsonconfig.h" #include "common/stringUtils.h" #include "detection/gpu/gpu.h" #include "detection/vulkan/vulkan.h" #include "modules/vulkan/vulkan.h" bool ffPrintVulkan(FFVulkanOptions* options) { const FFVulkanResult* vulkan = ffDetectVulkan(); if(vulkan->error) { ffPrintError(FF_VULKAN_MODULE_NAME, 0, &options->moduleArgs, FF_PRINT_TYPE_DEFAULT, "%s", vulkan->error); return false; } if(options->moduleArgs.outputFormat.length == 0) { ffPrintLogoAndKey(FF_VULKAN_MODULE_NAME, 0, &options->moduleArgs, FF_PRINT_TYPE_DEFAULT); if (vulkan->apiVersion.length == 0 && vulkan->driver.length == 0) { ffStrbufWriteTo(&vulkan->instanceVersion, stdout); puts(" [Software only]"); } else { if(vulkan->apiVersion.length > 0) { ffStrbufWriteTo(&vulkan->apiVersion, stdout); if(vulkan->driver.length > 0) fputs(" - ", stdout); } if(vulkan->driver.length > 0) ffStrbufWriteTo(&vulkan->driver, stdout); putchar('\n'); } } else { FF_PRINT_FORMAT_CHECKED(FF_VULKAN_MODULE_NAME, 0, &options->moduleArgs, FF_PRINT_TYPE_DEFAULT, ((FFformatarg[]) { FF_ARG(vulkan->driver, "driver"), FF_ARG(vulkan->apiVersion, "api-version"), FF_ARG(vulkan->conformanceVersion, "conformance-version"), FF_ARG(vulkan->instanceVersion, "instance-version"), })); } return true; } void ffParseVulkanJsonObject(FFVulkanOptions* options, yyjson_val* module) { yyjson_val *key, *val; size_t idx, max; yyjson_obj_foreach(module, idx, max, key, val) { if (ffJsonConfigParseModuleArgs(key, val, &options->moduleArgs)) continue; ffPrintError(FF_VULKAN_MODULE_NAME, 0, &options->moduleArgs, FF_PRINT_TYPE_DEFAULT, "Unknown JSON key %s", unsafe_yyjson_get_str(key)); } } void ffGenerateVulkanJsonConfig(FFVulkanOptions* options, yyjson_mut_doc* doc, yyjson_mut_val* module) { ffJsonConfigGenerateModuleArgsConfig(doc, module, &options->moduleArgs); } bool ffGenerateVulkanJsonResult(FF_MAYBE_UNUSED FFVulkanOptions* options, yyjson_mut_doc* doc, yyjson_mut_val* module) { const FFVulkanResult* result = ffDetectVulkan(); if(result->error) { yyjson_mut_obj_add_str(doc, module, "error", result->error); return false; } yyjson_mut_val* obj = yyjson_mut_obj_add_obj(doc, module, "result"); yyjson_mut_obj_add_strbuf(doc, obj, "apiVersion", &result->apiVersion); yyjson_mut_obj_add_strbuf(doc, obj, "conformanceVersion", &result->conformanceVersion); yyjson_mut_obj_add_strbuf(doc, obj, "driver", &result->driver); yyjson_mut_val* gpus = yyjson_mut_obj_add_arr(doc, obj, "gpus"); FF_LIST_FOR_EACH(FFGPUResult, vulkanGpu, result->gpus) { yyjson_mut_val* gpuObj = yyjson_mut_arr_add_obj(doc, gpus); yyjson_mut_obj_add_str(doc, gpuObj, "type", vulkanGpu->type == FF_GPU_TYPE_UNKNOWN ? "Unknown" : vulkanGpu->type == FF_GPU_TYPE_INTEGRATED ? "Integrated" : "Discrete"); yyjson_mut_obj_add_strbuf(doc, gpuObj, "vendor", &vulkanGpu->vendor); yyjson_mut_obj_add_strbuf(doc, gpuObj, "name", &vulkanGpu->name); yyjson_mut_obj_add_strbuf(doc, gpuObj, "driver", &vulkanGpu->driver); yyjson_mut_obj_add_strbuf(doc, gpuObj, "platformApi", &vulkanGpu->platformApi); yyjson_mut_obj_add_uint(doc, gpuObj, "deviceId", vulkanGpu->deviceId); yyjson_mut_val* memoryObj = yyjson_mut_obj_add_obj(doc, gpuObj, "memory"); { yyjson_mut_val* dedicatedMemory = yyjson_mut_obj_add_obj(doc, memoryObj, "dedicated"); if (vulkanGpu->dedicated.total != FF_GPU_VMEM_SIZE_UNSET) yyjson_mut_obj_add_uint(doc, dedicatedMemory, "total", vulkanGpu->dedicated.total); else yyjson_mut_obj_add_null(doc, dedicatedMemory, "total"); if (vulkanGpu->dedicated.used != FF_GPU_VMEM_SIZE_UNSET) yyjson_mut_obj_add_uint(doc, dedicatedMemory, "used", vulkanGpu->dedicated.total); else yyjson_mut_obj_add_null(doc, dedicatedMemory, "used"); } { yyjson_mut_val* sharedMemory = yyjson_mut_obj_add_obj(doc, memoryObj, "shared"); if (vulkanGpu->shared.total != FF_GPU_VMEM_SIZE_UNSET) yyjson_mut_obj_add_uint(doc, sharedMemory, "total", vulkanGpu->shared.total); else yyjson_mut_obj_add_null(doc, sharedMemory, "total"); if (vulkanGpu->shared.used != FF_GPU_VMEM_SIZE_UNSET) yyjson_mut_obj_add_uint(doc, sharedMemory, "used", vulkanGpu->shared.used); else yyjson_mut_obj_add_null(doc, sharedMemory, "used"); } yyjson_mut_obj_add_uint(doc, gpuObj, "deviceId", vulkanGpu->deviceId); } return true; } void ffInitVulkanOptions(FFVulkanOptions* options) { ffOptionInitModuleArg(&options->moduleArgs, ""); } void ffDestroyVulkanOptions(FFVulkanOptions* options) { ffOptionDestroyModuleArg(&options->moduleArgs); } FFModuleBaseInfo ffVulkanModuleInfo = { .name = FF_VULKAN_MODULE_NAME, .description = "Print highest Vulkan version supported by the GPU", .initOptions = (void*) ffInitVulkanOptions, .destroyOptions = (void*) ffDestroyVulkanOptions, .parseJsonObject = (void*) ffParseVulkanJsonObject, .printModule = (void*) ffPrintVulkan, .generateJsonResult = (void*) ffGenerateVulkanJsonResult, .generateJsonConfig = (void*) ffGenerateVulkanJsonConfig, .formatArgs = FF_FORMAT_ARG_LIST(((FFModuleFormatArg[]) { {"Driver name", "driver"}, {"API version", "api-version"}, {"Conformance version", "conformance-version"}, {"Instance version", "instance-version"}, })) }; ================================================ FILE: src/modules/vulkan/vulkan.h ================================================ #pragma once #include "option.h" #define FF_VULKAN_MODULE_NAME "Vulkan" bool ffPrintVulkan(FFVulkanOptions* options); void ffInitVulkanOptions(FFVulkanOptions* options); void ffDestroyVulkanOptions(FFVulkanOptions* options); extern FFModuleBaseInfo ffVulkanModuleInfo; ================================================ FILE: src/modules/wallpaper/option.h ================================================ #pragma once #include "common/option.h" typedef struct FFWallpaperOptions { FFModuleArgs moduleArgs; } FFWallpaperOptions; static_assert(sizeof(FFWallpaperOptions) <= FF_OPTION_MAX_SIZE, "FFWallpaperOptions size exceeds maximum allowed size"); ================================================ FILE: src/modules/wallpaper/wallpaper.c ================================================ #include "common/printing.h" #include "common/jsonconfig.h" #include "common/stringUtils.h" #include "detection/wallpaper/wallpaper.h" #include "modules/wallpaper/wallpaper.h" bool ffPrintWallpaper(FFWallpaperOptions* options) { FF_STRBUF_AUTO_DESTROY fullpath = ffStrbufCreate(); const char* error = ffDetectWallpaper(&fullpath); const uint32_t index = ffStrbufLastIndexC(&fullpath, #ifndef _WIN32 '/' #else '\\' #endif ) + 1; const char* filename = index >= fullpath.length ? fullpath.chars : fullpath.chars + index; if(error) { ffPrintError(FF_WALLPAPER_MODULE_NAME, 0, &options->moduleArgs, FF_PRINT_TYPE_DEFAULT, "%s", error); return false; } if(options->moduleArgs.outputFormat.length == 0) { ffPrintLogoAndKey(FF_WALLPAPER_MODULE_NAME, 0, &options->moduleArgs, FF_PRINT_TYPE_DEFAULT); puts(filename); } else { FF_PRINT_FORMAT_CHECKED(FF_WALLPAPER_MODULE_NAME, 0, &options->moduleArgs, FF_PRINT_TYPE_DEFAULT, ((FFformatarg[]){ FF_ARG(filename, "file-name"), FF_ARG(fullpath, "full-path"), })); } return true; } void ffParseWallpaperJsonObject(FFWallpaperOptions* options, yyjson_val* module) { yyjson_val *key, *val; size_t idx, max; yyjson_obj_foreach(module, idx, max, key, val) { if (ffJsonConfigParseModuleArgs(key, val, &options->moduleArgs)) continue; ffPrintError(FF_WALLPAPER_MODULE_NAME, 0, &options->moduleArgs, FF_PRINT_TYPE_DEFAULT, "Unknown JSON key %s", unsafe_yyjson_get_str(key)); } } void ffGenerateWallpaperJsonConfig(FFWallpaperOptions* options, yyjson_mut_doc* doc, yyjson_mut_val* module) { ffJsonConfigGenerateModuleArgsConfig(doc, module, &options->moduleArgs); } bool ffGenerateWallpaperJsonResult(FF_MAYBE_UNUSED FFWallpaperOptions* options, yyjson_mut_doc* doc, yyjson_mut_val* module) { FF_STRBUF_AUTO_DESTROY fullpath = ffStrbufCreate(); const char* error = ffDetectWallpaper(&fullpath); if(error) { yyjson_mut_obj_add_str(doc, module, "error", error); return false; } yyjson_mut_obj_add_strbuf(doc, module, "result", &fullpath); return true; } void ffInitWallpaperOptions(FFWallpaperOptions* options) { ffOptionInitModuleArg(&options->moduleArgs, "󰸉"); } void ffDestroyWallpaperOptions(FFWallpaperOptions* options) { ffOptionDestroyModuleArg(&options->moduleArgs); } FFModuleBaseInfo ffWallpaperModuleInfo = { .name = FF_WALLPAPER_MODULE_NAME, .description = "Print image file path of current wallpaper", .initOptions = (void*) ffInitWallpaperOptions, .destroyOptions = (void*) ffDestroyWallpaperOptions, .parseJsonObject = (void*) ffParseWallpaperJsonObject, .printModule = (void*) ffPrintWallpaper, .generateJsonResult = (void*) ffGenerateWallpaperJsonResult, .generateJsonConfig = (void*) ffGenerateWallpaperJsonConfig, .formatArgs = FF_FORMAT_ARG_LIST(((FFModuleFormatArg[]) { {"File name", "file-name"}, {"Full path", "full-path"}, })) }; ================================================ FILE: src/modules/wallpaper/wallpaper.h ================================================ #pragma once #include "option.h" #define FF_WALLPAPER_MODULE_NAME "Wallpaper" bool ffPrintWallpaper(FFWallpaperOptions* options); void ffInitWallpaperOptions(FFWallpaperOptions* options); void ffDestroyWallpaperOptions(FFWallpaperOptions* options); extern FFModuleBaseInfo ffWallpaperModuleInfo; ================================================ FILE: src/modules/weather/option.h ================================================ #pragma once #include "common/option.h" typedef struct FFWeatherOptions { FFModuleArgs moduleArgs; FFstrbuf location; FFstrbuf outputFormat; uint32_t timeout; } FFWeatherOptions; static_assert(sizeof(FFWeatherOptions) <= FF_OPTION_MAX_SIZE, "FFWeatherOptions size exceeds maximum allowed size"); ================================================ FILE: src/modules/weather/weather.c ================================================ #include "common/printing.h" #include "common/jsonconfig.h" #include "common/stringUtils.h" #include "detection/weather/weather.h" #include "modules/weather/weather.h" bool ffPrintWeather(FFWeatherOptions* options) { FF_STRBUF_AUTO_DESTROY result = ffStrbufCreate(); const char* error = ffDetectWeather(options, &result); if(error) { ffPrintError(FF_WEATHER_MODULE_NAME, 0, &options->moduleArgs, FF_PRINT_TYPE_DEFAULT, "%s", error); return false; } if(options->moduleArgs.outputFormat.length == 0) { ffPrintLogoAndKey(FF_WEATHER_MODULE_NAME, 0, &options->moduleArgs, FF_PRINT_TYPE_DEFAULT); ffStrbufPutTo(&result, stdout); } else { FF_PRINT_FORMAT_CHECKED(FF_WEATHER_MODULE_NAME, 0, &options->moduleArgs, FF_PRINT_TYPE_DEFAULT, ((FFformatarg[]) { FF_ARG(result, "result"), })); } return true; } void ffParseWeatherJsonObject(FFWeatherOptions* options, yyjson_val* module) { yyjson_val *key, *val; size_t idx, max; yyjson_obj_foreach(module, idx, max, key, val) { if (ffJsonConfigParseModuleArgs(key, val, &options->moduleArgs)) continue; if (unsafe_yyjson_equals_str(key, "location")) { ffStrbufSetJsonVal(&options->location, val); continue; } if (unsafe_yyjson_equals_str(key, "outputFormat")) { ffStrbufSetJsonVal(&options->outputFormat, val); continue; } if (unsafe_yyjson_equals_str(key, "timeout")) { options->timeout = (uint32_t) yyjson_get_uint(val); continue; } ffPrintError(FF_WEATHER_MODULE_NAME, 0, &options->moduleArgs, FF_PRINT_TYPE_DEFAULT, "Unknown JSON key %s", unsafe_yyjson_get_str(key)); } } void ffGenerateWeatherJsonConfig(FFWeatherOptions* options, yyjson_mut_doc* doc, yyjson_mut_val* module) { ffJsonConfigGenerateModuleArgsConfig(doc, module, &options->moduleArgs); yyjson_mut_obj_add_strbuf(doc, module, "location", &options->location); yyjson_mut_obj_add_strbuf(doc, module, "outputFormat", &options->outputFormat); yyjson_mut_obj_add_uint(doc, module, "timeout", options->timeout); } bool ffGenerateWeatherJsonResult(FFWeatherOptions* options, yyjson_mut_doc* doc, yyjson_mut_val* module) { FF_STRBUF_AUTO_DESTROY result = ffStrbufCreate(); const char* error = ffDetectWeather(options, &result); if (error) { yyjson_mut_obj_add_str(doc, module, "error", error); return false; } yyjson_mut_obj_add_strbuf(doc, module, "result", &result); return true; } void ffInitWeatherOptions(FFWeatherOptions* options) { ffOptionInitModuleArg(&options->moduleArgs, "󰖙"); ffStrbufInit(&options->location); ffStrbufInitStatic(&options->outputFormat, "%t+-+%C+(%l)"); options->timeout = 0; } void ffDestroyWeatherOptions(FFWeatherOptions* options) { ffOptionDestroyModuleArg(&options->moduleArgs); ffStrbufDestroy(&options->outputFormat); } FFModuleBaseInfo ffWeatherModuleInfo = { .name = FF_WEATHER_MODULE_NAME, .description = "Print weather information", .initOptions = (void*) ffInitWeatherOptions, .destroyOptions = (void*) ffDestroyWeatherOptions, .parseJsonObject = (void*) ffParseWeatherJsonObject, .printModule = (void*) ffPrintWeather, .generateJsonResult = (void*) ffGenerateWeatherJsonResult, .generateJsonConfig = (void*) ffGenerateWeatherJsonConfig, .formatArgs = FF_FORMAT_ARG_LIST(((FFModuleFormatArg[]) { {"Weather result", "result"}, })) }; ================================================ FILE: src/modules/weather/weather.h ================================================ #pragma once #include "option.h" #define FF_WEATHER_MODULE_NAME "Weather" void ffPrepareWeather(FFWeatherOptions* options); bool ffPrintWeather(FFWeatherOptions* options); void ffInitWeatherOptions(FFWeatherOptions* options); void ffDestroyWeatherOptions(FFWeatherOptions* options); extern FFModuleBaseInfo ffWeatherModuleInfo; ================================================ FILE: src/modules/wifi/option.h ================================================ #pragma once #include "common/option.h" typedef struct FFWifiOptions { FFModuleArgs moduleArgs; FFPercentageModuleConfig percent; } FFWifiOptions; static_assert(sizeof(FFWifiOptions) <= FF_OPTION_MAX_SIZE, "FFWifiOptions size exceeds maximum allowed size"); ================================================ FILE: src/modules/wifi/wifi.c ================================================ #include "common/printing.h" #include "common/jsonconfig.h" #include "common/stringUtils.h" #include "detection/wifi/wifi.h" #include "modules/wifi/wifi.h" bool ffPrintWifi(FFWifiOptions* options) { FF_LIST_AUTO_DESTROY result = ffListCreate(sizeof(FFWifiResult)); const char* error = ffDetectWifi(&result); if(error) { ffPrintError(FF_WIFI_MODULE_NAME, 0, &options->moduleArgs, FF_PRINT_TYPE_DEFAULT, "%s", error); return false; } if(!result.length) { ffPrintError(FF_WIFI_MODULE_NAME, 0, &options->moduleArgs, FF_PRINT_TYPE_DEFAULT, "No Wifi interfaces found"); return false; } FFPercentageTypeFlags percentType = options->percent.type == 0 ? instance.config.display.percentType : options->percent.type; for(uint32_t index = 0; index < result.length; ++index) { FFWifiResult* item = FF_LIST_GET(FFWifiResult, result, index); uint8_t moduleIndex = result.length == 1 ? 0 : (uint8_t)(index + 1); // https://en.wikipedia.org/wiki/List_of_WLAN_channels char bandStr[8]; if (item->conn.frequency > 58000) strcpy(bandStr, "60"); if (item->conn.frequency > 40000) strcpy(bandStr, "45"); else if (item->conn.frequency > 5900) strcpy(bandStr, "6"); else if (item->conn.frequency > 5100) strcpy(bandStr, "5"); else if (item->conn.frequency > 4900) strcpy(bandStr, "4.9"); else if (item->conn.frequency > 3600) strcpy(bandStr, "3.65"); else if (item->conn.frequency > 2000) strcpy(bandStr, "2.4"); else if (item->conn.frequency > 800) strcpy(bandStr, "0.9"); else bandStr[0] = '\0'; if(options->moduleArgs.outputFormat.length == 0) { ffPrintLogoAndKey(FF_WIFI_MODULE_NAME, moduleIndex, &options->moduleArgs, FF_PRINT_TYPE_DEFAULT); FF_STRBUF_AUTO_DESTROY buffer = ffStrbufCreate(); if(item->conn.ssid.length) { if(item->conn.signalQuality != -DBL_MAX) { if(percentType & FF_PERCENTAGE_TYPE_BAR_BIT) { ffPercentAppendBar(&buffer, item->conn.signalQuality, options->percent, &options->moduleArgs); ffStrbufAppendC(&buffer, ' '); } } if (!(percentType & FF_PERCENTAGE_TYPE_HIDE_OTHERS_BIT)) { ffStrbufAppend(&buffer, &item->conn.ssid); if(item->conn.protocol.length) { ffStrbufAppendS(&buffer, " - "); ffStrbufAppend(&buffer, &item->conn.protocol); } if (bandStr[0]) { ffStrbufAppendF(&buffer, " - %s%sGHz", bandStr, instance.config.display.freqSpaceBeforeUnit == FF_SPACE_BEFORE_UNIT_NEVER ? "" : " "); } if(item->conn.security.length) { ffStrbufAppendS(&buffer, " - "); ffStrbufAppend(&buffer, &item->conn.security); } ffStrbufAppendC(&buffer, ' '); } if(item->conn.signalQuality != -DBL_MAX) { if(percentType & FF_PERCENTAGE_TYPE_NUM_BIT) ffPercentAppendNum(&buffer, item->conn.signalQuality, options->percent, buffer.length > 0, &options->moduleArgs); } ffStrbufTrimRight(&buffer, ' '); } else { ffStrbufAppend(&buffer, &item->inf.status); } ffStrbufPutTo(&buffer, stdout); } else { FF_STRBUF_AUTO_DESTROY percentNum = ffStrbufCreate(); if (percentType & FF_PERCENTAGE_TYPE_NUM_BIT) ffPercentAppendNum(&percentNum, item->conn.signalQuality, options->percent, false, &options->moduleArgs); FF_STRBUF_AUTO_DESTROY percentBar = ffStrbufCreate(); if (percentType & FF_PERCENTAGE_TYPE_BAR_BIT) ffPercentAppendBar(&percentBar, item->conn.signalQuality, options->percent, &options->moduleArgs); FF_PRINT_FORMAT_CHECKED(FF_WIFI_MODULE_NAME, moduleIndex, &options->moduleArgs, FF_PRINT_TYPE_DEFAULT, ((FFformatarg[]){ FF_ARG(item->inf.description, "inf-desc"), FF_ARG(item->inf.status, "inf-status"), FF_ARG(item->conn.status, "status"), FF_ARG(item->conn.ssid, "ssid"), FF_ARG(item->conn.bssid, "bssid"), FF_ARG(item->conn.protocol, "protocol"), FF_ARG(percentNum, "signal-quality"), FF_ARG(item->conn.rxRate, "rx-rate"), FF_ARG(item->conn.txRate, "tx-rate"), FF_ARG(item->conn.security, "security"), FF_ARG(percentBar, "signal-quality-bar"), FF_ARG(item->conn.channel, "channel"), FF_ARG(bandStr, "band"), })); } } FF_LIST_FOR_EACH(FFWifiResult, item, result) { ffStrbufDestroy(&item->inf.description); ffStrbufDestroy(&item->inf.status); ffStrbufDestroy(&item->conn.status); ffStrbufDestroy(&item->conn.ssid); ffStrbufDestroy(&item->conn.bssid); ffStrbufDestroy(&item->conn.protocol); ffStrbufDestroy(&item->conn.security); } return true; } void ffParseWifiJsonObject(FFWifiOptions* options, yyjson_val* module) { yyjson_val *key, *val; size_t idx, max; yyjson_obj_foreach(module, idx, max, key, val) { if (ffJsonConfigParseModuleArgs(key, val, &options->moduleArgs)) continue; if (ffPercentParseJsonObject(key, val, &options->percent)) continue; ffPrintError(FF_WIFI_MODULE_NAME, 0, &options->moduleArgs, FF_PRINT_TYPE_DEFAULT, "Unknown JSON key %s", unsafe_yyjson_get_str(key)); } } void ffGenerateWifiJsonConfig(FFWifiOptions* options, yyjson_mut_doc* doc, yyjson_mut_val* module) { ffJsonConfigGenerateModuleArgsConfig(doc, module, &options->moduleArgs); ffPercentGenerateJsonConfig(doc, module, options->percent); } bool ffGenerateWifiJsonResult(FF_MAYBE_UNUSED FFWifiOptions* options, yyjson_mut_doc* doc, yyjson_mut_val* module) { FF_LIST_AUTO_DESTROY result = ffListCreate(sizeof(FFWifiResult)); const char* error = ffDetectWifi(&result); if(error) { yyjson_mut_obj_add_str(doc, module, "error", error); return false; } yyjson_mut_val* arr = yyjson_mut_obj_add_arr(doc, module, "result"); FF_LIST_FOR_EACH(FFWifiResult, wifi, result) { yyjson_mut_val* obj = yyjson_mut_arr_add_obj(doc, arr); yyjson_mut_val* inf = yyjson_mut_obj_add_obj(doc, obj, "inf"); yyjson_mut_obj_add_strbuf(doc, inf, "description", &wifi->inf.description); yyjson_mut_obj_add_strbuf(doc, inf, "status", &wifi->inf.status); yyjson_mut_val* conn = yyjson_mut_obj_add_obj(doc, obj, "conn"); yyjson_mut_obj_add_strbuf(doc, conn, "status", &wifi->conn.status); yyjson_mut_obj_add_strbuf(doc, conn, "ssid", &wifi->conn.ssid); yyjson_mut_obj_add_strbuf(doc, conn, "bssid", &wifi->conn.bssid); yyjson_mut_obj_add_strbuf(doc, conn, "protocol", &wifi->conn.protocol); yyjson_mut_obj_add_strbuf(doc, conn, "security", &wifi->conn.security); if (wifi->conn.signalQuality != -DBL_MAX) yyjson_mut_obj_add_real(doc, conn, "signalQuality", wifi->conn.signalQuality); else yyjson_mut_obj_add_null(doc, conn, "signalQuality"); if (wifi->conn.rxRate != -DBL_MAX) yyjson_mut_obj_add_real(doc, conn, "rxRate", wifi->conn.rxRate); else yyjson_mut_obj_add_null(doc, conn, "rxRate"); if (wifi->conn.txRate != -DBL_MAX) yyjson_mut_obj_add_real(doc, conn, "txRate", wifi->conn.txRate); else yyjson_mut_obj_add_null(doc, conn, "txRate"); yyjson_mut_obj_add_uint(doc, conn, "channel", wifi->conn.channel); yyjson_mut_obj_add_uint(doc, conn, "frequency", wifi->conn.frequency); } FF_LIST_FOR_EACH(FFWifiResult, item, result) { ffStrbufDestroy(&item->inf.description); ffStrbufDestroy(&item->inf.status); ffStrbufDestroy(&item->conn.status); ffStrbufDestroy(&item->conn.ssid); ffStrbufDestroy(&item->conn.bssid); ffStrbufDestroy(&item->conn.protocol); ffStrbufDestroy(&item->conn.security); } return true; } void ffInitWifiOptions(FFWifiOptions* options) { ffOptionInitModuleArg(&options->moduleArgs, ""); options->percent = (FFPercentageModuleConfig) { 75, 50, 0 }; } void ffDestroyWifiOptions(FFWifiOptions* options) { ffOptionDestroyModuleArg(&options->moduleArgs); } FFModuleBaseInfo ffWifiModuleInfo = { .name = FF_WIFI_MODULE_NAME, .description = "Print connected Wi-Fi info (SSID, connection and security protocol)", .initOptions = (void*) ffInitWifiOptions, .destroyOptions = (void*) ffDestroyWifiOptions, .parseJsonObject = (void*) ffParseWifiJsonObject, .printModule = (void*) ffPrintWifi, .generateJsonResult = (void*) ffGenerateWifiJsonResult, .generateJsonConfig = (void*) ffGenerateWifiJsonConfig, .formatArgs = FF_FORMAT_ARG_LIST(((FFModuleFormatArg[]) { {"Interface description", "inf-desc"}, {"Interface status", "inf-status"}, {"Connection status", "status"}, {"Connection SSID", "ssid"}, {"Connection BSSID", "bssid"}, {"Connection protocol", "protocol"}, {"Connection signal quality (percentage num)", "signal-quality"}, {"Connection RX rate", "rx-rate"}, {"Connection TX rate", "tx-rate"}, {"Connection Security algorithm", "security"}, {"Connection signal quality (percentage bar)", "signal-quality-bar"}, {"Connection channel number", "channel"}, {"Connection channel band in GHz", "band"}, })) }; ================================================ FILE: src/modules/wifi/wifi.h ================================================ #pragma once #include "option.h" #define FF_WIFI_MODULE_NAME "Wifi" bool ffPrintWifi(FFWifiOptions* options); void ffInitWifiOptions(FFWifiOptions* options); void ffDestroyWifiOptions(FFWifiOptions* options); extern FFModuleBaseInfo ffWifiModuleInfo; ================================================ FILE: src/modules/wm/option.h ================================================ #pragma once #include "common/option.h" typedef struct FFWMOptions { FFModuleArgs moduleArgs; bool detectPlugin; } FFWMOptions; static_assert(sizeof(FFWMOptions) <= FF_OPTION_MAX_SIZE, "FFWMOptions size exceeds maximum allowed size"); ================================================ FILE: src/modules/wm/wm.c ================================================ #include "common/printing.h" #include "common/jsonconfig.h" #include "common/stringUtils.h" #include "detection/displayserver/displayserver.h" #include "detection/wm/wm.h" #include "modules/wm/wm.h" bool ffPrintWM(FFWMOptions* options) { const FFDisplayServerResult* result = ffConnectDisplayServer(); if(result->wmPrettyName.length == 0) { ffPrintError(FF_WM_MODULE_NAME, 0, &options->moduleArgs, FF_PRINT_TYPE_DEFAULT, "No WM found"); return false; } FF_STRBUF_AUTO_DESTROY pluginName = ffStrbufCreate(); if(options->detectPlugin) ffDetectWMPlugin(&pluginName); FF_STRBUF_AUTO_DESTROY version = ffStrbufCreate(); if (instance.config.general.detectVersion) ffDetectWMVersion(&result->wmProcessName, &version, options); if(options->moduleArgs.outputFormat.length == 0) { ffPrintLogoAndKey(FF_WM_MODULE_NAME, 0, &options->moduleArgs, FF_PRINT_TYPE_DEFAULT); ffStrbufWriteTo(&result->wmPrettyName, stdout); if(version.length > 0) { putchar(' '); ffStrbufWriteTo(&version, stdout); } if(result->wmProtocolName.length > 0) { fputs(" (", stdout); ffStrbufWriteTo(&result->wmProtocolName, stdout); putchar(')'); } if(pluginName.length > 0) { fputs(" (with ", stdout); ffStrbufWriteTo(&pluginName, stdout); putchar(')'); } putchar('\n'); } else { FF_PRINT_FORMAT_CHECKED(FF_WM_MODULE_NAME, 0, &options->moduleArgs, FF_PRINT_TYPE_DEFAULT, ((FFformatarg[]){ FF_ARG(result->wmProcessName, "process-name"), FF_ARG(result->wmPrettyName, "pretty-name"), FF_ARG(result->wmProtocolName, "protocol-name"), FF_ARG(pluginName, "plugin-name"), FF_ARG(version, "version"), })); } return true; } void ffParseWMJsonObject(FFWMOptions* options, yyjson_val* module) { yyjson_val *key, *val; size_t idx, max; yyjson_obj_foreach(module, idx, max, key, val) { if (ffJsonConfigParseModuleArgs(key, val, &options->moduleArgs)) continue; if (unsafe_yyjson_equals_str(key, "detectPlugin")) { options->detectPlugin = yyjson_get_bool(val); continue; } ffPrintError(FF_WM_MODULE_NAME, 0, &options->moduleArgs, FF_PRINT_TYPE_DEFAULT, "Unknown JSON key %s", unsafe_yyjson_get_str(key)); } } void ffGenerateWMJsonConfig(FFWMOptions* options, yyjson_mut_doc* doc, yyjson_mut_val* module) { ffJsonConfigGenerateModuleArgsConfig(doc, module, &options->moduleArgs); yyjson_mut_obj_add_bool(doc, module, "detectPlugin", options->detectPlugin); } bool ffGenerateWMJsonResult(FF_MAYBE_UNUSED FFWMOptions* options, yyjson_mut_doc* doc, yyjson_mut_val* module) { const FFDisplayServerResult* result = ffConnectDisplayServer(); if(result->wmPrettyName.length == 0) { yyjson_mut_obj_add_str(doc, module, "error", "No WM found"); return false; } FF_STRBUF_AUTO_DESTROY pluginName = ffStrbufCreate(); if(options->detectPlugin) ffDetectWMPlugin(&pluginName); FF_STRBUF_AUTO_DESTROY version = ffStrbufCreate(); if (instance.config.general.detectVersion) ffDetectWMVersion(&result->wmProcessName, &version, options); yyjson_mut_val* obj = yyjson_mut_obj_add_obj(doc, module, "result"); yyjson_mut_obj_add_strbuf(doc, obj, "processName", &result->wmProcessName); yyjson_mut_obj_add_strbuf(doc, obj, "prettyName", &result->wmPrettyName); yyjson_mut_obj_add_strbuf(doc, obj, "protocolName", &result->wmProtocolName); yyjson_mut_obj_add_strbuf(doc, obj, "pluginName", &pluginName); yyjson_mut_obj_add_strbuf(doc, obj, "version", &version); return true; } void ffInitWMOptions(FFWMOptions* options) { ffOptionInitModuleArg(&options->moduleArgs, ""); options->detectPlugin = true; } void ffDestroyWMOptions(FFWMOptions* options) { ffOptionDestroyModuleArg(&options->moduleArgs); } FFModuleBaseInfo ffWMModuleInfo = { .name = FF_WM_MODULE_NAME, .description = "Print window manager name and version", .initOptions = (void*) ffInitWMOptions, .destroyOptions = (void*) ffDestroyWMOptions, .parseJsonObject = (void*) ffParseWMJsonObject, .printModule = (void*) ffPrintWM, .generateJsonResult = (void*) ffGenerateWMJsonResult, .generateJsonConfig = (void*) ffGenerateWMJsonConfig, .formatArgs = FF_FORMAT_ARG_LIST(((FFModuleFormatArg[]) { {"WM process name", "process-name"}, {"WM pretty name", "pretty-name"}, {"WM protocol name", "protocol-name"}, {"WM plugin name", "plugin-name"}, {"WM version", "version"}, })) }; ================================================ FILE: src/modules/wm/wm.h ================================================ #pragma once #include "option.h" #define FF_WM_MODULE_NAME "WM" bool ffPrintWM(FFWMOptions* options); void ffInitWMOptions(FFWMOptions* options); void ffDestroyWMOptions(FFWMOptions* options); extern FFModuleBaseInfo ffWMModuleInfo; ================================================ FILE: src/modules/wmtheme/option.h ================================================ #pragma once #include "common/option.h" typedef struct FFWMThemeOptions { FFModuleArgs moduleArgs; } FFWMThemeOptions; static_assert(sizeof(FFWMThemeOptions) <= FF_OPTION_MAX_SIZE, "FFWMThemeOptions size exceeds maximum allowed size"); ================================================ FILE: src/modules/wmtheme/wmtheme.c ================================================ #include "common/printing.h" #include "common/jsonconfig.h" #include "common/stringUtils.h" #include "detection/wmtheme/wmtheme.h" #include "modules/wmtheme/wmtheme.h" #define FF_WMTHEME_DISPLAY_NAME "WM Theme" bool ffPrintWMTheme(FFWMThemeOptions* options) { FF_STRBUF_AUTO_DESTROY themeOrError = ffStrbufCreate(); if(!ffDetectWmTheme(&themeOrError)) { ffPrintError(FF_WMTHEME_DISPLAY_NAME, 0, &options->moduleArgs, FF_PRINT_TYPE_DEFAULT, "%s", themeOrError.chars); return false; } if(options->moduleArgs.outputFormat.length == 0) { ffPrintLogoAndKey(FF_WMTHEME_DISPLAY_NAME, 0, &options->moduleArgs, FF_PRINT_TYPE_DEFAULT); puts(themeOrError.chars); } else { FF_PRINT_FORMAT_CHECKED(FF_WMTHEME_DISPLAY_NAME, 0, &options->moduleArgs, FF_PRINT_TYPE_DEFAULT, ((FFformatarg[]){ FF_ARG(themeOrError, "result"), })); } return true; } void ffParseWMThemeJsonObject(FFWMThemeOptions* options, yyjson_val* module) { yyjson_val *key, *val; size_t idx, max; yyjson_obj_foreach(module, idx, max, key, val) { if (ffJsonConfigParseModuleArgs(key, val, &options->moduleArgs)) continue; ffPrintError(FF_WMTHEME_DISPLAY_NAME, 0, &options->moduleArgs, FF_PRINT_TYPE_DEFAULT, "Unknown JSON key %s", unsafe_yyjson_get_str(key)); } } void ffGenerateWMThemeJsonConfig(FFWMThemeOptions* options, yyjson_mut_doc* doc, yyjson_mut_val* module) { ffJsonConfigGenerateModuleArgsConfig(doc, module, &options->moduleArgs); } bool ffGenerateWMThemeJsonResult(FF_MAYBE_UNUSED FFWMThemeOptions* options, yyjson_mut_doc* doc, yyjson_mut_val* module) { FF_STRBUF_AUTO_DESTROY themeOrError = ffStrbufCreate(); if(!ffDetectWmTheme(&themeOrError)) { yyjson_mut_obj_add_strbuf(doc, module, "error", &themeOrError); return false; } yyjson_mut_obj_add_strbuf(doc, module, "result", &themeOrError); return true; } void ffInitWMThemeOptions(FFWMThemeOptions* options) { ffOptionInitModuleArg(&options->moduleArgs, "󰓸"); } void ffDestroyWMThemeOptions(FFWMThemeOptions* options) { ffOptionDestroyModuleArg(&options->moduleArgs); } FFModuleBaseInfo ffWMThemeModuleInfo = { .name = FF_WMTHEME_MODULE_NAME, .description = "Print current theme of window manager", .initOptions = (void*) ffInitWMThemeOptions, .destroyOptions = (void*) ffDestroyWMThemeOptions, .parseJsonObject = (void*) ffParseWMThemeJsonObject, .printModule = (void*) ffPrintWMTheme, .generateJsonResult = (void*) ffGenerateWMThemeJsonResult, .generateJsonConfig = (void*) ffGenerateWMThemeJsonConfig, .formatArgs = FF_FORMAT_ARG_LIST(((FFModuleFormatArg[]) { {"WM theme", "result"}, })) }; ================================================ FILE: src/modules/wmtheme/wmtheme.h ================================================ #pragma once #include "option.h" #define FF_WMTHEME_MODULE_NAME "WMTheme" bool ffPrintWMTheme(FFWMThemeOptions* options); void ffInitWMThemeOptions(FFWMThemeOptions* options); void ffDestroyWMThemeOptions(FFWMThemeOptions* options); extern FFModuleBaseInfo ffWMThemeModuleInfo; ================================================ FILE: src/modules/zpool/option.h ================================================ #pragma once #include "common/option.h" #include "common/percent.h" typedef struct FFZpoolOptions { FFModuleArgs moduleArgs; FFPercentageModuleConfig percent; } FFZpoolOptions; static_assert(sizeof(FFZpoolOptions) <= FF_OPTION_MAX_SIZE, "FFZpoolOptions size exceeds maximum allowed size"); ================================================ FILE: src/modules/zpool/zpool.c ================================================ #include "common/printing.h" #include "common/jsonconfig.h" #include "common/percent.h" #include "common/size.h" #include "common/FFstrbuf.h" #include "common/stringUtils.h" #include "detection/zpool/zpool.h" #include "modules/zpool/zpool.h" static void printZpool(FFZpoolOptions* options, FFZpoolResult* result, uint8_t index) { FF_STRBUF_AUTO_DESTROY buffer = ffStrbufCreate(); if (options->moduleArgs.key.length == 0) { if (result->name.length > 0) ffStrbufSetF(&buffer, "%s (%s)", FF_ZPOOL_MODULE_NAME, result->name.chars); else ffStrbufSetS(&buffer, FF_ZPOOL_MODULE_NAME); } else { ffStrbufClear(&buffer); FF_PARSE_FORMAT_STRING_CHECKED(&buffer, &options->moduleArgs.key, ((FFformatarg[]) { FF_ARG(index, "index"), FF_ARG(result->name, "name"), FF_ARG(result->guid, "guid"), FF_ARG(options->moduleArgs.keyIcon, "icon"), })); } FF_STRBUF_AUTO_DESTROY usedPretty = ffStrbufCreate(); ffSizeAppendNum(result->used, &usedPretty); FF_STRBUF_AUTO_DESTROY allocatedPretty = ffStrbufCreate(); ffSizeAppendNum(result->allocated, &allocatedPretty); FF_STRBUF_AUTO_DESTROY totalPretty = ffStrbufCreate(); ffSizeAppendNum(result->total, &totalPretty); double usedPercentage = result->total > 0 ? (double) result->used / (double) result->total * 100.0 : 0; double allocatedPercentage = result->total > 0 ? (double) result->allocated / (double) result->total * 100.0 : 0; FFPercentageTypeFlags percentType = options->percent.type == 0 ? instance.config.display.percentType : options->percent.type; if(options->moduleArgs.outputFormat.length == 0) { ffPrintLogoAndKey(buffer.chars, index, &options->moduleArgs, FF_PRINT_TYPE_NO_CUSTOM_KEY); ffStrbufClear(&buffer); ffStrbufSetF(&buffer, "%s / %s (", usedPretty.chars, totalPretty.chars); ffPercentAppendNum(&buffer, usedPercentage, options->percent, false, &options->moduleArgs); ffStrbufAppendS(&buffer, ", "); ffPercentAppendNum(&buffer, allocatedPercentage, options->percent, false, &options->moduleArgs); ffStrbufAppendS(&buffer, " allocated, "); ffPercentAppendNum(&buffer, result->fragmentation, options->percent, false, &options->moduleArgs); ffStrbufAppendF(&buffer, " frag) - %s", result->state.chars); if (result->readOnly) ffStrbufAppendS(&buffer, " [Read-only]"); ffStrbufPutTo(&buffer, stdout); } else { FF_STRBUF_AUTO_DESTROY usedPercentageNum = ffStrbufCreate(); if (percentType & FF_PERCENTAGE_TYPE_NUM_BIT) ffPercentAppendNum(&usedPercentageNum, usedPercentage, options->percent, false, &options->moduleArgs); FF_STRBUF_AUTO_DESTROY usedPercentageBar = ffStrbufCreate(); if (percentType & FF_PERCENTAGE_TYPE_BAR_BIT) ffPercentAppendBar(&usedPercentageBar, usedPercentage, options->percent, &options->moduleArgs); FF_STRBUF_AUTO_DESTROY allocatedPercentageNum = ffStrbufCreate(); if (percentType & FF_PERCENTAGE_TYPE_NUM_BIT) ffPercentAppendNum(&allocatedPercentageNum, allocatedPercentage, options->percent, false, &options->moduleArgs); FF_STRBUF_AUTO_DESTROY allocatedPercentageBar = ffStrbufCreate(); if (percentType & FF_PERCENTAGE_TYPE_BAR_BIT) ffPercentAppendBar(&allocatedPercentageBar, allocatedPercentage, options->percent, &options->moduleArgs); FF_STRBUF_AUTO_DESTROY fragPercentageNum = ffStrbufCreate(); if (percentType & FF_PERCENTAGE_TYPE_NUM_BIT) ffPercentAppendNum(&fragPercentageNum, result->fragmentation, options->percent, false, &options->moduleArgs); FF_STRBUF_AUTO_DESTROY fragPercentageBar = ffStrbufCreate(); if (percentType & FF_PERCENTAGE_TYPE_BAR_BIT) ffPercentAppendBar(&fragPercentageBar, result->fragmentation, options->percent, &options->moduleArgs); FF_PRINT_FORMAT_CHECKED(buffer.chars, 0, &options->moduleArgs, FF_PRINT_TYPE_NO_CUSTOM_KEY, ((FFformatarg[]) { FF_ARG(result->name, "name"), FF_ARG(result->guid, "guid"), FF_ARG(result->state, "state"), FF_ARG(usedPretty, "size-used"), FF_ARG(allocatedPretty, "size-allocated"), FF_ARG(totalPretty, "size-total"), FF_ARG(usedPercentageNum, "used-percentage"), FF_ARG(allocatedPercentageNum, "allocated-percentage"), FF_ARG(fragPercentageNum, "frag-percentage"), FF_ARG(usedPercentageBar, "used-percentage-bar"), FF_ARG(allocatedPercentageBar, "allocated-percentage-bar"), FF_ARG(fragPercentageBar, "frag-percentage-bar"), FF_ARG(result->readOnly, "is-readonly"), })); } } bool ffPrintZpool(FFZpoolOptions* options) { FF_LIST_AUTO_DESTROY results = ffListCreate(sizeof(FFZpoolResult)); const char* error = ffDetectZpool(&results); if (error) { ffPrintError(FF_ZPOOL_MODULE_NAME, 0, &options->moduleArgs, FF_PRINT_TYPE_DEFAULT, "%s", error); return false; } if(results.length == 0) { ffPrintError(FF_ZPOOL_MODULE_NAME, 0, &options->moduleArgs, FF_PRINT_TYPE_DEFAULT, "%s", "No zpool found"); return false; } for(uint32_t i = 0; i < results.length; i++) { FFZpoolResult* result = FF_LIST_GET(FFZpoolResult, results, i); uint8_t index = results.length == 1 ? 0 : (uint8_t) (i + 1); printZpool(options, result, index); } FF_LIST_FOR_EACH(FFZpoolResult, result, results) { ffStrbufDestroy(&result->name); ffStrbufDestroy(&result->state); } return true; } void ffParseZpoolJsonObject(FFZpoolOptions* options, yyjson_val* module) { yyjson_val *key, *val; size_t idx, max; yyjson_obj_foreach(module, idx, max, key, val) { if (ffJsonConfigParseModuleArgs(key, val, &options->moduleArgs)) continue; if (ffPercentParseJsonObject(key, val, &options->percent)) continue; ffPrintError(FF_ZPOOL_MODULE_NAME, 0, &options->moduleArgs, FF_PRINT_TYPE_DEFAULT, "Unknown JSON key %s", unsafe_yyjson_get_str(key)); } } void ffGenerateZpoolJsonConfig(FFZpoolOptions* options, yyjson_mut_doc* doc, yyjson_mut_val* module) { ffJsonConfigGenerateModuleArgsConfig(doc, module, &options->moduleArgs); ffPercentGenerateJsonConfig(doc, module, options->percent); } bool ffGenerateZpoolJsonResult(FF_MAYBE_UNUSED FFZpoolOptions* options, yyjson_mut_doc* doc, yyjson_mut_val* module) { FF_LIST_AUTO_DESTROY results = ffListCreate(sizeof(FFZpoolResult)); const char* error = ffDetectZpool(&results); if (error) { yyjson_mut_obj_add_str(doc, module, "error", error); return false; } yyjson_mut_val* arr = yyjson_mut_obj_add_arr(doc, module, "result"); FF_LIST_FOR_EACH(FFZpoolResult, zpool, results) { yyjson_mut_val* obj = yyjson_mut_arr_add_obj(doc, arr); yyjson_mut_obj_add_strbuf(doc, obj, "name", &zpool->name); yyjson_mut_obj_add_strbuf(doc, obj, "state", &zpool->state); yyjson_mut_obj_add_uint(doc, obj, "guid", zpool->guid); yyjson_mut_obj_add_uint(doc, obj, "used", zpool->used); yyjson_mut_obj_add_uint(doc, obj, "allocated", zpool->allocated); yyjson_mut_obj_add_uint(doc, obj, "total", zpool->total); if (zpool->fragmentation != -DBL_MAX) yyjson_mut_obj_add_real(doc, obj, "fragmentation", zpool->fragmentation); else yyjson_mut_obj_add_null(doc, obj, "fragmentation"); yyjson_mut_obj_add_bool(doc, obj, "readOnly", zpool->readOnly); } FF_LIST_FOR_EACH(FFZpoolResult, zpool, results) { ffStrbufDestroy(&zpool->name); ffStrbufDestroy(&zpool->state); } return true; } void ffInitZpoolOptions(FFZpoolOptions* options) { ffOptionInitModuleArg(&options->moduleArgs, "󱑛"); options->percent = (FFPercentageModuleConfig) { 50, 80, 0 }; } void ffDestroyZpoolOptions(FFZpoolOptions* options) { ffOptionDestroyModuleArg(&options->moduleArgs); } FFModuleBaseInfo ffZpoolModuleInfo = { .name = FF_ZPOOL_MODULE_NAME, .description = "Print ZFS storage pools", .initOptions = (void*) ffInitZpoolOptions, .destroyOptions = (void*) ffDestroyZpoolOptions, .parseJsonObject = (void*) ffParseZpoolJsonObject, .printModule = (void*) ffPrintZpool, .generateJsonResult = (void*) ffGenerateZpoolJsonResult, .generateJsonConfig = (void*) ffGenerateZpoolJsonConfig, .formatArgs = FF_FORMAT_ARG_LIST(((FFModuleFormatArg[]) { {"Zpool name", "name"}, {"Zpool guid", "guid"}, {"Zpool state", "state"}, {"Size used", "used"}, {"Size allocated", "allocated"}, {"Size total", "total"}, {"Size used percentage num", "used-percentage"}, {"Size allocated percentage num", "allocated-percentage"}, {"Fragmentation percentage num", "fragmentation-percentage"}, {"Size used percentage bar", "used-percentage-bar"}, {"Size allocated percentage bar", "allocated-percentage-bar"}, {"Fragmentation percentage bar", "fragmentation-percentage-bar"}, {"Is read-only", "is-readonly"}, })) }; ================================================ FILE: src/modules/zpool/zpool.h ================================================ #pragma once #include "option.h" #define FF_ZPOOL_MODULE_NAME "Zpool" bool ffPrintZpool(FFZpoolOptions* options); void ffInitZpoolOptions(FFZpoolOptions* options); void ffDestroyZpoolOptions(FFZpoolOptions* options); extern FFModuleBaseInfo ffZpoolModuleInfo; ================================================ FILE: src/options/display.c ================================================ #include "fastfetch.h" #include "common/color.h" #include "common/jsonconfig.h" #include "common/percent.h" #include "common/stringUtils.h" #include "options/display.h" #include const char* ffOptionsParseDisplayJsonConfig(FFOptionsDisplay* options, yyjson_val* root) { yyjson_val* object = yyjson_obj_get(root, "display"); if (!object) return NULL; if (!yyjson_is_obj(object)) return "Property 'display' must be an object"; yyjson_val *key, *val; size_t idx, max; yyjson_obj_foreach(object, idx, max, key, val) { if (unsafe_yyjson_equals_str(key, "stat")) { if (yyjson_is_bool(val)) { if (yyjson_get_bool(val)) { options->stat = 0; options->showErrors = true; } else options->stat = -1; } else if (yyjson_is_uint(val)) { options->stat = (int) yyjson_get_uint(val); options->showErrors = true; } else return "display.stat must be a boolean or a positive integer"; } else if (unsafe_yyjson_equals_str(key, "pipe")) options->pipe = yyjson_get_bool(val); else if (unsafe_yyjson_equals_str(key, "showErrors")) options->showErrors = yyjson_get_bool(val); else if (unsafe_yyjson_equals_str(key, "disableLinewrap")) options->disableLinewrap = yyjson_get_bool(val); else if (unsafe_yyjson_equals_str(key, "hideCursor")) options->hideCursor = yyjson_get_bool(val); else if (unsafe_yyjson_equals_str(key, "separator")) ffStrbufSetJsonVal(&options->keyValueSeparator, val); else if (unsafe_yyjson_equals_str(key, "color")) { if (yyjson_is_str(val)) { ffOptionParseColor(unsafe_yyjson_get_str(val), &options->colorKeys); ffStrbufSet(&options->colorTitle, &options->colorKeys); } else if (yyjson_is_obj(val)) { yyjson_val* colorKeys = yyjson_obj_get(val, "keys"); if (colorKeys) ffOptionParseColor(yyjson_get_str(colorKeys), &options->colorKeys); yyjson_val* colorTitle = yyjson_obj_get(val, "title"); if (colorTitle) ffOptionParseColor(yyjson_get_str(colorTitle), &options->colorTitle); yyjson_val* colorOutput = yyjson_obj_get(val, "output"); if (colorOutput) ffOptionParseColor(yyjson_get_str(colorOutput), &options->colorOutput); yyjson_val* colorSeparator = yyjson_obj_get(val, "separator"); if (colorSeparator) ffOptionParseColor(yyjson_get_str(colorSeparator), &options->colorSeparator); } else return "display.color must be either a string or an object"; } else if (unsafe_yyjson_equals_str(key, "brightColor")) options->brightColor = yyjson_get_bool(val); else if (unsafe_yyjson_equals_str(key, "duration")) { if (!yyjson_is_obj(val)) return "display.duration must be an object"; yyjson_val* abbreviation = yyjson_obj_get(val, "abbreviation"); if (abbreviation) options->durationAbbreviation = yyjson_get_bool(abbreviation); yyjson_val* spaceBeforeUnit = yyjson_obj_get(val, "spaceBeforeUnit"); if (spaceBeforeUnit) { int value; const char* error = ffJsonConfigParseEnum(spaceBeforeUnit, &value, (FFKeyValuePair[]) { { "default", FF_SPACE_BEFORE_UNIT_DEFAULT }, { "always", FF_SPACE_BEFORE_UNIT_ALWAYS }, { "never", FF_SPACE_BEFORE_UNIT_NEVER }, {}, }); if (error) return error; options->durationSpaceBeforeUnit = (FFSpaceBeforeUnitType) value; } } else if (unsafe_yyjson_equals_str(key, "size")) { if (!yyjson_is_obj(val)) return "display.size must be an object"; 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 }, {} }); if (error) return error; options->sizeMaxPrefix = (uint8_t) value; } yyjson_val* binaryPrefix = yyjson_obj_get(val, "binaryPrefix"); if (binaryPrefix) { int value; const char* error = ffJsonConfigParseEnum(binaryPrefix, &value, (FFKeyValuePair[]) { { "iec", FF_SIZE_BINARY_PREFIX_TYPE_IEC }, { "si", FF_SIZE_BINARY_PREFIX_TYPE_SI }, { "jedec", FF_SIZE_BINARY_PREFIX_TYPE_JEDEC }, {}, }); if (error) return error; options->sizeBinaryPrefix = (FFSizeBinaryPrefixType) value; } yyjson_val* ndigits = yyjson_obj_get(val, "ndigits"); if (ndigits) { if (!yyjson_is_uint(ndigits)) return "display.size.ndigits must be an unsigned integer"; uint64_t val = yyjson_get_uint(ndigits); if (val > 9) return "display.size.ndigits must be between 0 and 9"; options->sizeNdigits = (uint8_t) val; } yyjson_val* spaceBeforeUnit = yyjson_obj_get(val, "spaceBeforeUnit"); if (spaceBeforeUnit) { int value; const char* error = ffJsonConfigParseEnum(spaceBeforeUnit, &value, (FFKeyValuePair[]) { { "default", FF_SPACE_BEFORE_UNIT_DEFAULT }, { "always", FF_SPACE_BEFORE_UNIT_ALWAYS }, { "never", FF_SPACE_BEFORE_UNIT_NEVER }, {}, }); if (error) return error; options->sizeSpaceBeforeUnit = (FFSpaceBeforeUnitType) value; } } else if (unsafe_yyjson_equals_str(key, "temp")) { if (!yyjson_is_obj(val)) return "display.temp must be an object"; yyjson_val* unit = yyjson_obj_get(val, "unit"); if (unit) { int value; const char* error = ffJsonConfigParseEnum(unit, &value, (FFKeyValuePair[]) { { "DEFAULT", FF_TEMPERATURE_UNIT_DEFAULT }, { "D", FF_TEMPERATURE_UNIT_DEFAULT }, { "CELSIUS", FF_TEMPERATURE_UNIT_CELSIUS }, { "C", FF_TEMPERATURE_UNIT_CELSIUS }, { "FAHRENHEIT", FF_TEMPERATURE_UNIT_FAHRENHEIT }, { "F", FF_TEMPERATURE_UNIT_FAHRENHEIT }, { "KELVIN", FF_TEMPERATURE_UNIT_KELVIN }, { "K", FF_TEMPERATURE_UNIT_KELVIN }, {}, }); if (error) return error; options->tempUnit = (FFTemperatureUnit) value; } yyjson_val* ndigits = yyjson_obj_get(val, "ndigits"); if (ndigits) { if (!yyjson_is_uint(ndigits)) return "display.temperature.ndigits must be an unsigned integer"; uint64_t val = yyjson_get_uint(ndigits); if (val > 9) return "display.temperature.ndigits must be between 0 and 9"; options->tempNdigits = (uint8_t) val; } yyjson_val* color = yyjson_obj_get(val, "color"); if (color) { if (!yyjson_is_obj(color)) return "display.temperature.color must be an object"; yyjson_val* green = yyjson_obj_get(color, "green"); if (green) ffOptionParseColor(yyjson_get_str(green), &options->tempColorGreen); yyjson_val* yellow = yyjson_obj_get(color, "yellow"); if (yellow) ffOptionParseColor(yyjson_get_str(yellow), &options->tempColorYellow); yyjson_val* red = yyjson_obj_get(color, "red"); if (red) ffOptionParseColor(yyjson_get_str(red), &options->tempColorRed); } yyjson_val* spaceBeforeUnit = yyjson_obj_get(val, "spaceBeforeUnit"); if (spaceBeforeUnit) { int value; const char* error = ffJsonConfigParseEnum(spaceBeforeUnit, &value, (FFKeyValuePair[]) { { "default", FF_SPACE_BEFORE_UNIT_DEFAULT }, { "always", FF_SPACE_BEFORE_UNIT_ALWAYS }, { "never", FF_SPACE_BEFORE_UNIT_NEVER }, {}, }); if (error) return error; options->tempSpaceBeforeUnit = (FFSpaceBeforeUnitType) value; } } else if (unsafe_yyjson_equals_str(key, "percent")) { if (!yyjson_is_obj(val)) return "display.percent must be an object"; yyjson_val* type = yyjson_obj_get(val, "type"); if (type) { const char* error = ffPercentParseTypeJsonConfig(type, &options->percentType); if (error) return error; } yyjson_val* ndigits = yyjson_obj_get(val, "ndigits"); if (ndigits) { if (!yyjson_is_uint(ndigits)) return "display.percent.ndigits must be an unsigned integer"; uint64_t val = yyjson_get_uint(ndigits); if (val > 9) return "display.percent.ndigits must be between 0 and 9"; options->percentNdigits = (uint8_t) val; } yyjson_val* color = yyjson_obj_get(val, "color"); if (color) { if (!yyjson_is_obj(color)) return "display.percent.color must be an object"; yyjson_val* green = yyjson_obj_get(color, "green"); if (green) ffOptionParseColor(yyjson_get_str(green), &options->percentColorGreen); yyjson_val* yellow = yyjson_obj_get(color, "yellow"); if (yellow) ffOptionParseColor(yyjson_get_str(yellow), &options->percentColorYellow); yyjson_val* red = yyjson_obj_get(color, "red"); if (red) ffOptionParseColor(yyjson_get_str(red), &options->percentColorRed); } yyjson_val* spaceBeforeUnit = yyjson_obj_get(val, "spaceBeforeUnit"); if (spaceBeforeUnit) { int value; const char* error = ffJsonConfigParseEnum(spaceBeforeUnit, &value, (FFKeyValuePair[]) { { "default", FF_SPACE_BEFORE_UNIT_DEFAULT }, { "always", FF_SPACE_BEFORE_UNIT_ALWAYS }, { "never", FF_SPACE_BEFORE_UNIT_NEVER }, {}, }); if (error) return error; options->percentSpaceBeforeUnit = (FFSpaceBeforeUnitType) value; } yyjson_val* width = yyjson_obj_get(val, "width"); if (width) options->percentWidth = (uint8_t) yyjson_get_uint(width); } else if (unsafe_yyjson_equals_str(key, "bar")) { if (yyjson_is_obj(val)) { yyjson_val* char_ = yyjson_obj_get(val, "char"); if (char_) { if (!yyjson_is_obj(char_)) return "display.bar.char must be an object"; yyjson_val* charElapsed = yyjson_obj_get(char_, "elapsed"); if (charElapsed) ffStrbufSetJsonVal(&options->barCharElapsed, charElapsed); yyjson_val* charTotal = yyjson_obj_get(char_, "total"); if (charTotal) ffStrbufSetJsonVal(&options->barCharTotal, charTotal); } else { yyjson_val* charElapsed = yyjson_obj_get(val, "charElapsed"); if (charElapsed) return "display.bar.charElapsed has been renamed to display.bar.char.elapsed."; yyjson_val* charTotal = yyjson_obj_get(val, "charTotal"); if (charTotal) return "display.bar.charTotal has been renamed to display.bar.char.total."; } yyjson_val* border = yyjson_obj_get(val, "border"); if (border) { if (yyjson_is_null(border)) { ffStrbufClear(&options->barBorderLeft); ffStrbufClear(&options->barBorderRight); ffStrbufClear(&options->barBorderLeftElapsed); ffStrbufClear(&options->barBorderRightElapsed); } else { if (!yyjson_is_obj(border)) return "display.bar.border must be an object"; yyjson_val* borderLeft = yyjson_obj_get(border, "left"); if (borderLeft) ffStrbufSetJsonVal(&options->barBorderLeft, borderLeft); yyjson_val* borderRight = yyjson_obj_get(border, "right"); if (borderRight) ffStrbufSetJsonVal(&options->barBorderRight, borderRight); yyjson_val* borderLeftElapsed = yyjson_obj_get(border, "leftElapsed"); if (borderLeftElapsed) ffStrbufSetJsonVal(&options->barBorderLeftElapsed, borderLeftElapsed); yyjson_val* borderRightElapsed = yyjson_obj_get(border, "rightElapsed"); if (borderRightElapsed) ffStrbufSetJsonVal(&options->barBorderRightElapsed, borderRightElapsed); } } else { yyjson_val* borderLeft = yyjson_obj_get(val, "borderLeft"); if (borderLeft) return "display.bar.borderLeft has been renamed to display.bar.border.left."; yyjson_val* borderRight = yyjson_obj_get(val, "borderRight"); if (borderRight) return "display.bar.borderRight has been renamed to display.bar.border.right."; } yyjson_val* color = yyjson_obj_get(val, "color"); if (color) { if (yyjson_is_null(color)) { ffStrbufClear(&options->barColorElapsed); ffStrbufClear(&options->barColorTotal); ffStrbufClear(&options->barColorBorder); } else { if (!yyjson_is_obj(color)) return "display.bar.color must be an object"; yyjson_val* colorElapsed = yyjson_obj_get(color, "elapsed"); if (colorElapsed) { const char* value = yyjson_get_str(colorElapsed); if (!value) ffStrbufClear(&options->barColorElapsed); else if (ffStrEqualsIgnCase(value, "auto")) ffStrbufSetStatic(&options->barColorElapsed, "auto"); else ffOptionParseColor(value, &options->barColorElapsed); } yyjson_val* colorTotal = yyjson_obj_get(color, "total"); if (colorTotal) ffOptionParseColor(yyjson_get_str(colorTotal), &options->barColorTotal); yyjson_val* colorBorder = yyjson_obj_get(color, "border"); if (colorBorder) ffOptionParseColor(yyjson_get_str(colorBorder), &options->barColorBorder); } } yyjson_val* width = yyjson_obj_get(val, "width"); if (width) options->barWidth = (uint8_t) yyjson_get_uint(width); } else return "display.bar must be an object"; } else if (unsafe_yyjson_equals_str(key, "fraction")) { if (yyjson_is_obj(val)) { yyjson_val* ndigits = yyjson_obj_get(val, "ndigits"); if (ndigits) { if (yyjson_is_null(ndigits)) options->fractionNdigits = -1; else { if (!yyjson_is_int(ndigits)) return "display.fraction.ndigits must be an integer"; int64_t val = yyjson_get_int(ndigits); if (val < -1 || val > 9) return "display.fraction.ndigits must be between -1 and 9"; options->fractionNdigits = (int8_t) val; } } yyjson_val* trailingZeros = yyjson_obj_get(val, "trailingZeros"); if (trailingZeros) { if (yyjson_is_null(trailingZeros)) options->fractionTrailingZeros = FF_FRACTION_TRAILING_ZEROS_TYPE_DEFAULT; else { int value; const char* error = ffJsonConfigParseEnum(trailingZeros, &value, (FFKeyValuePair[]) { { "default", FF_FRACTION_TRAILING_ZEROS_TYPE_DEFAULT }, { "always", FF_FRACTION_TRAILING_ZEROS_TYPE_ALWAYS }, { "never", FF_FRACTION_TRAILING_ZEROS_TYPE_NEVER }, {}, }); if (error) return error; options->fractionTrailingZeros = (FFFractionTrailingZerosType) value; } } } else return "display.fraction must be an object"; } else if (unsafe_yyjson_equals_str(key, "noBuffer")) options->noBuffer = yyjson_get_bool(val); else if (unsafe_yyjson_equals_str(key, "key")) { if (yyjson_is_obj(val)) { yyjson_val* width = yyjson_obj_get(val, "width"); if (width) options->keyWidth = (uint16_t) yyjson_get_uint(width); yyjson_val* type = yyjson_obj_get(val, "type"); if (type) { int value; const char* error = ffJsonConfigParseEnum(type, &value, (FFKeyValuePair[]) { { "none", FF_MODULE_KEY_TYPE_NONE }, { "string", FF_MODULE_KEY_TYPE_STRING }, { "icon", FF_MODULE_KEY_TYPE_ICON }, { "both", FF_MODULE_KEY_TYPE_BOTH }, { "both-0", FF_MODULE_KEY_TYPE_BOTH_0 }, { "both-1", FF_MODULE_KEY_TYPE_BOTH_1 }, { "both-2", FF_MODULE_KEY_TYPE_BOTH_2 }, { "both-3", FF_MODULE_KEY_TYPE_BOTH_3 }, { "both-4", FF_MODULE_KEY_TYPE_BOTH_4 }, {} }); if (error) return error; options->keyType = (uint8_t) value; } yyjson_val* paddingLeft = yyjson_obj_get(val, "paddingLeft"); if (paddingLeft) options->keyPaddingLeft = (uint16_t) yyjson_get_uint(paddingLeft); } else return "display.key must be an object"; } else if (unsafe_yyjson_equals_str(key, "constants")) { if (!yyjson_is_arr(val)) return "display.constants must be an array"; yyjson_val* item; size_t idx, max; yyjson_arr_foreach(val, idx, max, item) ffStrbufInitJsonVal(ffListAdd(&options->constants), item); } else if (unsafe_yyjson_equals_str(key, "freq")) { if (!yyjson_is_obj(val)) return "display.freq must be an object"; yyjson_val* ndigits = yyjson_obj_get(val, "ndigits"); if (ndigits) { if (yyjson_is_null(ndigits)) options->freqNdigits = -1; else { if (!yyjson_is_int(ndigits)) return "display.freq.ndigits must be an integer"; int64_t val = yyjson_get_int(ndigits); if (val < -1 || val > 9) return "display.freq.ndigits must be between -1 and 9"; options->freqNdigits = (int8_t) val; } } yyjson_val* spaceBeforeUnit = yyjson_obj_get(val, "spaceBeforeUnit"); if (spaceBeforeUnit) { int value; const char* error = ffJsonConfigParseEnum(spaceBeforeUnit, &value, (FFKeyValuePair[]) { { "default", FF_SPACE_BEFORE_UNIT_DEFAULT }, { "always", FF_SPACE_BEFORE_UNIT_ALWAYS }, { "never", FF_SPACE_BEFORE_UNIT_NEVER }, {}, }); if (error) return error; options->freqSpaceBeforeUnit = (FFSpaceBeforeUnitType) value; } } else return "Unknown display property"; } return NULL; } static inline void optionCheckString(const char* key, const char* value, FFstrbuf* buffer) { if(value == NULL) { fprintf(stderr, "Error: usage: %s \n", key); exit(477); } ffStrbufEnsureFree(buffer, 63); //This is not needed, as ffStrbufSetS will resize capacity if needed, but giving a higher start should improve performance } bool ffOptionsParseDisplayCommandLine(FFOptionsDisplay* options, const char* key, const char* value) { if(ffStrEqualsIgnCase(key, "--stat")) { if(ffOptionParseBoolean(value)) { options->stat = 0; options->showErrors = true; } else if (value) { char* end; uint32_t num = (uint32_t) strtoul(value, &end, 10); if (*end == '\0') { options->stat = (int32_t) num; options->showErrors = true; } else options->stat = -1; } else options->stat = -1; } else if(ffStrEqualsIgnCase(key, "--pipe")) options->pipe = ffOptionParseBoolean(value); else if(ffStrEqualsIgnCase(key, "--show-errors")) options->showErrors = ffOptionParseBoolean(value); else if(ffStrEqualsIgnCase(key, "--debug")) #ifndef NDEBUG options->debugMode = ffOptionParseBoolean(value); #else { fprintf(stderr, "--debug is only available in debug builds\n"); exit(477); } #endif else if(ffStrEqualsIgnCase(key, "--disable-linewrap")) options->disableLinewrap = ffOptionParseBoolean(value); else if(ffStrEqualsIgnCase(key, "--hide-cursor")) options->hideCursor = ffOptionParseBoolean(value); else if(ffStrEqualsIgnCase(key, "--separator")) ffOptionParseString(key, value, &options->keyValueSeparator); else if(ffStrEqualsIgnCase(key, "--color")) { optionCheckString(key, value, &options->colorKeys); ffOptionParseColor(value, &options->colorKeys); ffStrbufSet(&options->colorTitle, &options->colorKeys); } else if(ffStrStartsWithIgnCase(key, "--color-")) { const char* subkey = key + strlen("--color-"); if(ffStrEqualsIgnCase(subkey, "keys")) { optionCheckString(key, value, &options->colorKeys); ffOptionParseColor(value, &options->colorKeys); } else if(ffStrEqualsIgnCase(subkey, "title")) { optionCheckString(key, value, &options->colorTitle); ffOptionParseColor(value, &options->colorTitle); } else if(ffStrEqualsIgnCase(subkey, "output")) { optionCheckString(key, value, &options->colorOutput); ffOptionParseColor(value, &options->colorOutput); } else if(ffStrEqualsIgnCase(subkey, "separator")) { optionCheckString(key, value, &options->colorSeparator); ffOptionParseColor(value, &options->colorSeparator); } else return false; } else if(ffStrStartsWithIgnCase(key, "--key-")) { const char* subkey = key + strlen("--key-"); if(ffStrEqualsIgnCase(subkey, "width")) options->keyWidth = (uint16_t) ffOptionParseUInt32(key, value); else if(ffStrEqualsIgnCase(subkey, "type")) { options->keyType = (FFModuleKeyType) ffOptionParseEnum(key, value, (FFKeyValuePair[]) { { "none", FF_MODULE_KEY_TYPE_NONE }, { "string", FF_MODULE_KEY_TYPE_STRING }, { "icon", FF_MODULE_KEY_TYPE_ICON }, { "both", FF_MODULE_KEY_TYPE_BOTH }, { "both-0", FF_MODULE_KEY_TYPE_BOTH_0 }, { "both-1", FF_MODULE_KEY_TYPE_BOTH_1 }, { "both-2", FF_MODULE_KEY_TYPE_BOTH_2 }, { "both-3", FF_MODULE_KEY_TYPE_BOTH_3 }, { "both-4", FF_MODULE_KEY_TYPE_BOTH_4 }, {} }); } else if(ffStrEqualsIgnCase(subkey, "padding-left")) options->keyPaddingLeft = (uint16_t) ffOptionParseUInt32(key, value); else return false; } else if(ffStrEqualsIgnCase(key, "--bright-color")) options->brightColor = ffOptionParseBoolean(value); else if(ffStrEqualsIgnCase(key, "--binary-prefix")) { fprintf(stderr, "--binary-prefix has been renamed to --size-binary-prefix\n"); exit(477); } else if(ffStrStartsWithIgnCase(key, "--duration-")) { const char* subkey = key + strlen("--duration-"); if(ffStrEqualsIgnCase(subkey, "abbreviation")) options->durationAbbreviation = ffOptionParseBoolean(value); else if(ffStrEqualsIgnCase(subkey, "space-before-unit")) { options->durationSpaceBeforeUnit = (FFSpaceBeforeUnitType) ffOptionParseEnum(key, value, (FFKeyValuePair[]) { { "default", FF_SPACE_BEFORE_UNIT_DEFAULT }, { "always", FF_SPACE_BEFORE_UNIT_ALWAYS }, { "never", FF_SPACE_BEFORE_UNIT_NEVER }, {}, }); } else return false; } else if(ffStrStartsWithIgnCase(key, "--size-")) { const char* subkey = key + strlen("--size-"); 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); 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 }, {} }); } else if(ffStrEqualsIgnCase(subkey, "space-before-unit")) { options->sizeSpaceBeforeUnit = (FFSpaceBeforeUnitType) ffOptionParseEnum(key, value, (FFKeyValuePair[]) { { "default", FF_SPACE_BEFORE_UNIT_DEFAULT }, { "always", FF_SPACE_BEFORE_UNIT_ALWAYS }, { "never", FF_SPACE_BEFORE_UNIT_NEVER }, {}, }); } else return false; } else if(ffStrStartsWithIgnCase(key, "--temp-")) { const char* subkey = key + strlen("--temp-"); if(ffStrEqualsIgnCase(subkey, "unit")) { options->tempUnit = (FFTemperatureUnit) ffOptionParseEnum(key, value, (FFKeyValuePair[]) { { "DEFAULT", FF_TEMPERATURE_UNIT_DEFAULT }, { "D", FF_TEMPERATURE_UNIT_DEFAULT }, { "CELSIUS", FF_TEMPERATURE_UNIT_CELSIUS }, { "C", FF_TEMPERATURE_UNIT_CELSIUS }, { "FAHRENHEIT", FF_TEMPERATURE_UNIT_FAHRENHEIT }, { "F", FF_TEMPERATURE_UNIT_FAHRENHEIT }, { "KELVIN", FF_TEMPERATURE_UNIT_KELVIN }, { "K", FF_TEMPERATURE_UNIT_KELVIN }, {}, }); } else if (ffStrEqualsIgnCase(subkey, "ndigits")) options->tempNdigits = (uint8_t) ffOptionParseUInt32(key, value); else if(ffStrEqualsIgnCase(subkey, "color-green")) ffOptionParseColor(value, &options->tempColorGreen); else if(ffStrEqualsIgnCase(subkey, "color-yellow")) ffOptionParseColor(value, &options->tempColorYellow); else if(ffStrEqualsIgnCase(subkey, "color-red")) ffOptionParseColor(value, &options->tempColorRed); else if(ffStrEqualsIgnCase(subkey, "space-before-unit")) { options->tempSpaceBeforeUnit = (FFSpaceBeforeUnitType) ffOptionParseEnum(key, value, (FFKeyValuePair[]) { { "default", FF_SPACE_BEFORE_UNIT_DEFAULT }, { "always", FF_SPACE_BEFORE_UNIT_ALWAYS }, { "never", FF_SPACE_BEFORE_UNIT_NEVER }, {}, }); } else return false; } else if(ffStrStartsWithIgnCase(key, "--percent-")) { const char* subkey = key + strlen("--percent-"); if(ffStrEqualsIgnCase(subkey, "type")) options->percentType = (uint8_t) ffOptionParseUInt32(key, value); else if(ffStrEqualsIgnCase(subkey, "ndigits")) options->percentNdigits = (uint8_t) ffOptionParseUInt32(key, value); else if(ffStrEqualsIgnCase(subkey, "color-green")) ffOptionParseColor(value, &options->percentColorGreen); else if(ffStrEqualsIgnCase(subkey, "color-yellow")) ffOptionParseColor(value, &options->percentColorYellow); else if(ffStrEqualsIgnCase(subkey, "color-red")) ffOptionParseColor(value, &options->percentColorRed); else if(ffStrEqualsIgnCase(subkey, "space-before-unit")) { options->percentSpaceBeforeUnit = (FFSpaceBeforeUnitType) ffOptionParseEnum(key, value, (FFKeyValuePair[]) { { "default", FF_SPACE_BEFORE_UNIT_DEFAULT }, { "always", FF_SPACE_BEFORE_UNIT_ALWAYS }, { "never", FF_SPACE_BEFORE_UNIT_NEVER }, {}, }); } else if(ffStrEqualsIgnCase(subkey, "width")) options->percentWidth = (uint8_t) ffOptionParseUInt32(key, value); else return false; } else if(ffStrEqualsIgnCase(key, "--fraction-ndigits")) options->fractionNdigits = (int8_t) ffOptionParseInt32(key, value); else if(ffStrEqualsIgnCase(key, "--fraction-trailing-zeros")) { options->fractionTrailingZeros = (FFFractionTrailingZerosType) ffOptionParseEnum(key, value, (FFKeyValuePair[]) { { "default", FF_FRACTION_TRAILING_ZEROS_TYPE_DEFAULT }, { "always", FF_FRACTION_TRAILING_ZEROS_TYPE_ALWAYS }, { "never", FF_FRACTION_TRAILING_ZEROS_TYPE_NEVER }, {}, }); } else if(ffStrEqualsIgnCase(key, "--no-buffer")) options->noBuffer = ffOptionParseBoolean(value); else if(ffStrStartsWithIgnCase(key, "--bar-")) { const char* subkey = key + strlen("--bar-"); if(ffStrEqualsIgnCase(subkey, "char-elapsed")) ffOptionParseString(key, value, &options->barCharElapsed); else if(ffStrEqualsIgnCase(subkey, "char-total")) ffOptionParseString(key, value, &options->barCharTotal); else if(ffStrEqualsIgnCase(subkey, "width")) options->barWidth = (uint8_t) ffOptionParseUInt32(key, value); else if(ffStrEqualsIgnCase(subkey, "border-left")) ffOptionParseString(key, value, &options->barBorderLeft); else if(ffStrEqualsIgnCase(subkey, "border-right")) ffOptionParseString(key, value, &options->barBorderRight); else if(ffStrEqualsIgnCase(subkey, "border-left-elapsed")) ffOptionParseString(key, value, &options->barBorderLeftElapsed); else if(ffStrEqualsIgnCase(subkey, "border-right-elapsed")) ffOptionParseString(key, value, &options->barBorderRightElapsed); else if(ffStrEqualsIgnCase(subkey, "color-elapsed")) { if (!value) ffStrbufClear(&options->barColorElapsed); else if (ffStrEqualsIgnCase(value, "auto")) ffStrbufSetStatic(&options->barColorElapsed, "auto"); else ffOptionParseColor(value, &options->barColorElapsed); } else if(ffStrEqualsIgnCase(subkey, "color-total")) ffOptionParseColor(value, &options->barColorTotal); else if(ffStrEqualsIgnCase(subkey, "color-border")) ffOptionParseColor(value, &options->barColorBorder); else return false; } else if(ffStrStartsWithIgnCase(key, "--freq-")) { const char* subkey = key + strlen("--freq-"); if(ffStrEqualsIgnCase(subkey, "ndigits")) options->freqNdigits = (int8_t) ffOptionParseInt32(key, value); else if(ffStrEqualsIgnCase(subkey, "space-before-unit")) { options->freqSpaceBeforeUnit = (FFSpaceBeforeUnitType) ffOptionParseEnum(key, value, (FFKeyValuePair[]) { { "default", FF_SPACE_BEFORE_UNIT_DEFAULT }, { "always", FF_SPACE_BEFORE_UNIT_ALWAYS }, { "never", FF_SPACE_BEFORE_UNIT_NEVER }, {}, }); } else return false; } else return false; return true; } void ffOptionsInitDisplay(FFOptionsDisplay* options) { ffStrbufInit(&options->colorKeys); ffStrbufInit(&options->colorTitle); ffStrbufInit(&options->colorOutput); ffStrbufInit(&options->colorSeparator); options->brightColor = !instance.state.terminalLightTheme; ffStrbufInitStatic(&options->keyValueSeparator, ": "); options->showErrors = false; options->pipe = !isatty(STDOUT_FILENO) || !!getenv("NO_COLOR"); #ifdef NDEBUG options->disableLinewrap = !options->pipe; #else options->disableLinewrap = false; options->debugMode = !!getenv("FF_DEBUG"); #endif options->durationSpaceBeforeUnit = FF_SPACE_BEFORE_UNIT_DEFAULT; options->hideCursor = false; options->sizeBinaryPrefix = FF_SIZE_BINARY_PREFIX_TYPE_IEC; options->sizeNdigits = 2; options->sizeMaxPrefix = 8; // YB options->sizeSpaceBeforeUnit = FF_SPACE_BEFORE_UNIT_DEFAULT; options->stat = -1; options->noBuffer = false; options->keyWidth = 0; options->keyPaddingLeft = 0; options->keyType = FF_MODULE_KEY_TYPE_STRING; options->tempUnit = FF_TEMPERATURE_UNIT_DEFAULT; options->tempNdigits = 1; ffStrbufInitStatic(&options->tempColorGreen, FF_COLOR_FG_GREEN); ffStrbufInitStatic(&options->tempColorYellow, instance.state.terminalLightTheme ? FF_COLOR_FG_YELLOW : FF_COLOR_FG_LIGHT_YELLOW); ffStrbufInitStatic(&options->tempColorRed, instance.state.terminalLightTheme ? FF_COLOR_FG_RED : FF_COLOR_FG_LIGHT_RED); options->tempSpaceBeforeUnit = FF_SPACE_BEFORE_UNIT_DEFAULT; ffStrbufInitStatic(&options->barCharElapsed, "■"); ffStrbufInitStatic(&options->barCharTotal, "-"); ffStrbufInitStatic(&options->barBorderLeft, "[ "); ffStrbufInitStatic(&options->barBorderRight, " ]"); ffStrbufInit(&options->barBorderLeftElapsed); ffStrbufInit(&options->barBorderRightElapsed); ffStrbufInitStatic(&options->barColorElapsed, "auto"); ffStrbufInitStatic(&options->barColorTotal, instance.state.terminalLightTheme ? FF_COLOR_FG_WHITE : FF_COLOR_FG_LIGHT_WHITE); ffStrbufInitStatic(&options->barColorBorder, instance.state.terminalLightTheme ? FF_COLOR_FG_WHITE : FF_COLOR_FG_LIGHT_WHITE); options->barWidth = 10; options->durationAbbreviation = false; options->durationSpaceBeforeUnit = FF_SPACE_BEFORE_UNIT_DEFAULT; options->percentType = 9; options->percentNdigits = 0; ffStrbufInitStatic(&options->percentColorGreen, FF_COLOR_FG_GREEN); ffStrbufInitStatic(&options->percentColorYellow, instance.state.terminalLightTheme ? FF_COLOR_FG_YELLOW : FF_COLOR_FG_LIGHT_YELLOW); ffStrbufInitStatic(&options->percentColorRed, instance.state.terminalLightTheme ? FF_COLOR_FG_RED : FF_COLOR_FG_LIGHT_RED); options->percentSpaceBeforeUnit = FF_SPACE_BEFORE_UNIT_DEFAULT; options->percentWidth = 0; options->freqNdigits = 2; options->freqSpaceBeforeUnit = FF_SPACE_BEFORE_UNIT_DEFAULT; options->fractionNdigits = 2; options->fractionTrailingZeros = FF_FRACTION_TRAILING_ZEROS_TYPE_DEFAULT; ffListInit(&options->constants, sizeof(FFstrbuf)); } void ffOptionsDestroyDisplay(FFOptionsDisplay* options) { ffStrbufDestroy(&options->colorKeys); ffStrbufDestroy(&options->colorTitle); ffStrbufDestroy(&options->colorOutput); ffStrbufDestroy(&options->colorSeparator); ffStrbufDestroy(&options->keyValueSeparator); ffStrbufDestroy(&options->barCharElapsed); ffStrbufDestroy(&options->barCharTotal); FF_LIST_FOR_EACH(FFstrbuf, item, options->constants) ffStrbufDestroy(item); ffListDestroy(&options->constants); } void ffOptionsGenerateDisplayJsonConfig(FFdata* data, FFOptionsDisplay* options) { yyjson_mut_doc* doc = data->resultDoc; yyjson_mut_val* obj = yyjson_mut_obj_add_obj(doc, doc->root, "display"); if (options->stat <= 0) yyjson_mut_obj_add_bool(doc, obj, "stat", options->stat == 0); else yyjson_mut_obj_add_int(doc, obj, "stat", options->stat); yyjson_mut_obj_add_bool(doc, obj, "pipe", options->pipe); yyjson_mut_obj_add_bool(doc, obj, "showErrors", options->showErrors); yyjson_mut_obj_add_bool(doc, obj, "disableLinewrap", options->disableLinewrap); yyjson_mut_obj_add_bool(doc, obj, "hideCursor", options->hideCursor); yyjson_mut_obj_add_strbuf(doc, obj, "separator", &options->keyValueSeparator); { yyjson_mut_val* color = yyjson_mut_obj_add_obj(doc, obj, "color"); yyjson_mut_obj_add_strbuf(doc, color, "keys", &options->colorKeys); yyjson_mut_obj_add_strbuf(doc, color, "title", &options->colorTitle); yyjson_mut_obj_add_strbuf(doc, color, "output", &options->colorOutput); yyjson_mut_obj_add_strbuf(doc, color, "separator", &options->colorSeparator); } yyjson_mut_obj_add_bool(doc, obj, "brightColor", options->brightColor); { yyjson_mut_val* duration = yyjson_mut_obj_add_obj(doc, obj, "duration"); yyjson_mut_obj_add_bool(doc, duration, "abbreviation", options->durationAbbreviation); switch (options->durationSpaceBeforeUnit) { case FF_SPACE_BEFORE_UNIT_DEFAULT: yyjson_mut_obj_add_str(doc, duration, "spaceBeforeUnit", "default"); break; case FF_SPACE_BEFORE_UNIT_ALWAYS: yyjson_mut_obj_add_str(doc, duration, "spaceBeforeUnit", "always"); break; case FF_SPACE_BEFORE_UNIT_NEVER: yyjson_mut_obj_add_str(doc, duration, "spaceBeforeUnit", "never"); break; } } { yyjson_mut_val* size = yyjson_mut_obj_add_obj(doc, obj, "size"); yyjson_mut_obj_add_str(doc, size, "maxPrefix", ((const char* []) { "B", "kB", "MB", "GB", "TB", "PB", "EB", "ZB", "YB", })[options->sizeMaxPrefix]); switch (options->sizeBinaryPrefix) { case FF_SIZE_BINARY_PREFIX_TYPE_IEC: yyjson_mut_obj_add_str(doc, size, "binaryPrefix", "iec"); break; case FF_SIZE_BINARY_PREFIX_TYPE_SI: yyjson_mut_obj_add_str(doc, size, "binaryPrefix", "si"); break; case FF_SIZE_BINARY_PREFIX_TYPE_JEDEC: yyjson_mut_obj_add_str(doc, size, "binaryPrefix", "jedec"); break; } yyjson_mut_obj_add_uint(doc, size, "ndigits", options->sizeNdigits); switch (options->sizeSpaceBeforeUnit) { case FF_SPACE_BEFORE_UNIT_DEFAULT: yyjson_mut_obj_add_str(doc, size, "spaceBeforeUnit", "default"); break; case FF_SPACE_BEFORE_UNIT_ALWAYS: yyjson_mut_obj_add_str(doc, size, "spaceBeforeUnit", "always"); break; case FF_SPACE_BEFORE_UNIT_NEVER: yyjson_mut_obj_add_str(doc, size, "spaceBeforeUnit", "never"); break; } } { yyjson_mut_val* temperature = yyjson_mut_obj_add_obj(doc, obj, "temp"); switch (options->tempUnit) { case FF_TEMPERATURE_UNIT_DEFAULT: yyjson_mut_obj_add_str(doc, temperature, "unit", "D"); break; case FF_TEMPERATURE_UNIT_CELSIUS: yyjson_mut_obj_add_str(doc, obj, "unit", "C"); break; case FF_TEMPERATURE_UNIT_FAHRENHEIT: yyjson_mut_obj_add_str(doc, obj, "unit", "F"); break; case FF_TEMPERATURE_UNIT_KELVIN: yyjson_mut_obj_add_str(doc, obj, "unit", "K"); break; } yyjson_mut_obj_add_uint(doc, temperature, "ndigits", options->tempNdigits); { yyjson_mut_val* color = yyjson_mut_obj_add_obj(doc, temperature, "color"); yyjson_mut_obj_add_strbuf(doc, color, "green", &options->tempColorGreen); yyjson_mut_obj_add_strbuf(doc, color, "yellow", &options->tempColorYellow); yyjson_mut_obj_add_strbuf(doc, color, "red", &options->tempColorRed); } switch (options->tempSpaceBeforeUnit) { case FF_SPACE_BEFORE_UNIT_DEFAULT: yyjson_mut_obj_add_str(doc, temperature, "spaceBeforeUnit", "default"); break; case FF_SPACE_BEFORE_UNIT_ALWAYS: yyjson_mut_obj_add_str(doc, temperature, "spaceBeforeUnit", "always"); break; case FF_SPACE_BEFORE_UNIT_NEVER: yyjson_mut_obj_add_str(doc, temperature, "spaceBeforeUnit", "never"); break; } } { yyjson_mut_val* percent = yyjson_mut_obj_add_obj(doc, obj, "percent"); { yyjson_mut_val* type = yyjson_mut_obj_add_arr(doc, percent, "type"); if (options->percentType & FF_PERCENTAGE_TYPE_NUM_BIT) yyjson_mut_arr_add_str(doc, type, "num"); if (options->percentType & FF_PERCENTAGE_TYPE_BAR_BIT) yyjson_mut_arr_add_str(doc, type, "var"); if (options->percentType & FF_PERCENTAGE_TYPE_HIDE_OTHERS_BIT) yyjson_mut_arr_add_str(doc, type, "hide-others"); if (options->percentType & FF_PERCENTAGE_TYPE_NUM_COLOR_BIT) yyjson_mut_arr_add_str(doc, type, "num-color"); if (options->percentType & FF_PERCENTAGE_TYPE_BAR_MONOCHROME_BIT) yyjson_mut_arr_add_str(doc, type, "bar-monochrome"); } yyjson_mut_obj_add_uint(doc, percent, "ndigits", options->percentNdigits); { yyjson_mut_val* color = yyjson_mut_obj_add_obj(doc, percent, "color"); yyjson_mut_obj_add_strbuf(doc, color, "green", &options->percentColorGreen); yyjson_mut_obj_add_strbuf(doc, color, "yellow", &options->percentColorYellow); yyjson_mut_obj_add_strbuf(doc, color, "red", &options->percentColorRed); } switch (options->percentSpaceBeforeUnit) { case FF_SPACE_BEFORE_UNIT_DEFAULT: yyjson_mut_obj_add_str(doc, percent, "spaceBeforeUnit", "default"); break; case FF_SPACE_BEFORE_UNIT_ALWAYS: yyjson_mut_obj_add_str(doc, percent, "spaceBeforeUnit", "always"); break; case FF_SPACE_BEFORE_UNIT_NEVER: yyjson_mut_obj_add_str(doc, percent, "spaceBeforeUnit", "never"); break; } yyjson_mut_obj_add_uint(doc, percent, "width", options->percentWidth); } { yyjson_mut_val* bar = yyjson_mut_obj_add_obj(doc, obj, "bar"); yyjson_mut_val* char_ = yyjson_mut_obj_add_obj(doc, bar, "char"); yyjson_mut_obj_add_strbuf(doc, char_, "elapsed", &options->barCharElapsed); yyjson_mut_obj_add_strbuf(doc, char_, "total", &options->barCharTotal); yyjson_mut_val* border = yyjson_mut_obj_add_obj(doc, bar, "border"); yyjson_mut_obj_add_strbuf(doc, border, "left", &options->barBorderLeft); yyjson_mut_obj_add_strbuf(doc, border, "right", &options->barBorderRight); yyjson_mut_obj_add_strbuf(doc, border, "leftElapsed", &options->barBorderLeftElapsed); yyjson_mut_obj_add_strbuf(doc, border, "rightElapsed", &options->barBorderRightElapsed); yyjson_mut_val* color = yyjson_mut_obj_add_obj(doc, bar, "color"); yyjson_mut_obj_add_strbuf(doc, color, "elapsed", &options->barColorElapsed); yyjson_mut_obj_add_strbuf(doc, color, "total", &options->barColorTotal); yyjson_mut_obj_add_strbuf(doc, color, "border", &options->barColorBorder); yyjson_mut_obj_add_uint(doc, bar, "width", options->barWidth); } { yyjson_mut_val* fraction = yyjson_mut_obj_add_obj(doc, obj, "fraction"); if (options->fractionNdigits < 0) yyjson_mut_obj_add_null(doc, fraction, "ndigits"); else yyjson_mut_obj_add_uint(doc, fraction, "ndigits", (uint8_t) options->fractionNdigits); } yyjson_mut_obj_add_bool(doc, obj, "noBuffer", options->noBuffer); { yyjson_mut_val* key = yyjson_mut_obj_add_obj(doc, obj, "key"); yyjson_mut_obj_add_uint(doc, key, "width", options->keyWidth); switch ((uint8_t) options->keyType) { case FF_MODULE_KEY_TYPE_NONE: yyjson_mut_obj_add_str(doc, key, "type", "none"); break; case FF_MODULE_KEY_TYPE_STRING: yyjson_mut_obj_add_str(doc, key, "type", "string"); break; case FF_MODULE_KEY_TYPE_ICON: yyjson_mut_obj_add_str(doc, key, "type", "icon"); break; case FF_MODULE_KEY_TYPE_BOTH: yyjson_mut_obj_add_str(doc, key, "type", "both"); break; } yyjson_mut_obj_add_uint(doc, key, "paddingLeft", options->keyPaddingLeft); } { yyjson_mut_val* freq = yyjson_mut_obj_add_obj(doc, obj, "freq"); yyjson_mut_obj_add_int(doc, freq, "ndigits", options->freqNdigits); switch (options->percentSpaceBeforeUnit) { case FF_SPACE_BEFORE_UNIT_DEFAULT: yyjson_mut_obj_add_str(doc, freq, "spaceBeforeUnit", "default"); break; case FF_SPACE_BEFORE_UNIT_ALWAYS: yyjson_mut_obj_add_str(doc, freq, "spaceBeforeUnit", "always"); break; case FF_SPACE_BEFORE_UNIT_NEVER: yyjson_mut_obj_add_str(doc, freq, "spaceBeforeUnit", "never"); break; } } { yyjson_mut_val* constants = yyjson_mut_obj_add_arr(doc, obj, "constants"); FF_LIST_FOR_EACH(FFstrbuf, item, options->constants) yyjson_mut_arr_add_strbuf(doc, constants, item); } } ================================================ FILE: src/options/display.h ================================================ #pragma once #include "common/ffdata.h" #include "common/percent.h" #include "common/FFstrbuf.h" #include "common/FFlist.h" typedef enum __attribute__((__packed__)) FFSizeBinaryPrefixType { FF_SIZE_BINARY_PREFIX_TYPE_IEC, // 1024 Bytes = 1 KiB, 1024 KiB = 1 MiB, ... (standard) FF_SIZE_BINARY_PREFIX_TYPE_SI, // 1000 Bytes = 1 kB, 1000 kB = 1 MB, ... FF_SIZE_BINARY_PREFIX_TYPE_JEDEC, // 1024 Bytes = 1 KB, 1024 KB = 1 MB, ... } FFSizeBinaryPrefixType; typedef enum __attribute__((__packed__)) FFTemperatureUnit { FF_TEMPERATURE_UNIT_DEFAULT, FF_TEMPERATURE_UNIT_CELSIUS, FF_TEMPERATURE_UNIT_FAHRENHEIT, FF_TEMPERATURE_UNIT_KELVIN, } FFTemperatureUnit; typedef enum __attribute__((__packed__)) FFSpaceBeforeUnitType { FF_SPACE_BEFORE_UNIT_DEFAULT, FF_SPACE_BEFORE_UNIT_ALWAYS, FF_SPACE_BEFORE_UNIT_NEVER, } FFSpaceBeforeUnitType; typedef enum __attribute__((__packed__)) FFFractionTrailingZerosType { FF_FRACTION_TRAILING_ZEROS_TYPE_DEFAULT, FF_FRACTION_TRAILING_ZEROS_TYPE_ALWAYS, FF_FRACTION_TRAILING_ZEROS_TYPE_NEVER, } FFFractionTrailingZerosType; typedef struct FFOptionsDisplay { //If one of those is empty, ffLogoPrint will set them FFstrbuf colorKeys; FFstrbuf colorTitle; FFstrbuf colorOutput; FFstrbuf colorSeparator; bool brightColor; FFstrbuf keyValueSeparator; int32_t stat; // <0: disable stat; 0: no threshold; >0: threshold in ms bool pipe; //disables all escape sequences bool showErrors; #ifndef NDEBUG bool debugMode; #endif bool disableLinewrap; bool durationAbbreviation; FFSpaceBeforeUnitType durationSpaceBeforeUnit; bool hideCursor; FFSizeBinaryPrefixType sizeBinaryPrefix; uint8_t sizeNdigits; uint8_t sizeMaxPrefix; FFSpaceBeforeUnitType sizeSpaceBeforeUnit; FFTemperatureUnit tempUnit; uint8_t tempNdigits; FFstrbuf tempColorGreen; FFstrbuf tempColorYellow; FFstrbuf tempColorRed; FFSpaceBeforeUnitType tempSpaceBeforeUnit; FFstrbuf barCharElapsed; FFstrbuf barCharTotal; FFstrbuf barBorderLeft; FFstrbuf barBorderRight; FFstrbuf barBorderLeftElapsed; FFstrbuf barBorderRightElapsed; FFstrbuf barColorElapsed; // "auto" for auto selection from percent config; empty for no custom color (inherits) FFstrbuf barColorTotal; // empty for no custom color (inherits) FFstrbuf barColorBorder; // empty for no custom color (inherits) uint8_t barWidth; FFPercentageTypeFlags percentType; uint8_t percentNdigits; FFstrbuf percentColorGreen; FFstrbuf percentColorYellow; FFstrbuf percentColorRed; FFSpaceBeforeUnitType percentSpaceBeforeUnit; uint8_t percentWidth; bool noBuffer; FFModuleKeyType keyType; uint16_t keyWidth; uint16_t keyPaddingLeft; int8_t freqNdigits; FFSpaceBeforeUnitType freqSpaceBeforeUnit; int8_t fractionNdigits; FFFractionTrailingZerosType fractionTrailingZeros; FFlist constants; // list of FFstrbuf } FFOptionsDisplay; const char* ffOptionsParseDisplayJsonConfig(FFOptionsDisplay* options, yyjson_val* root); bool ffOptionsParseDisplayCommandLine(FFOptionsDisplay* options, const char* key, const char* value); void ffOptionsInitDisplay(FFOptionsDisplay* options); void ffOptionsDestroyDisplay(FFOptionsDisplay* options); void ffOptionsGenerateDisplayJsonConfig(FFdata* data, FFOptionsDisplay* options); ================================================ FILE: src/options/general.c ================================================ #include "fastfetch.h" #include "common/jsonconfig.h" #include "common/processing.h" #include "common/stringUtils.h" #include "options/general.h" #include const char* ffOptionsParseGeneralJsonConfig(FFOptionsGeneral* options, yyjson_val* root) { yyjson_val* object = yyjson_obj_get(root, "general"); if (!object) return NULL; if (!yyjson_is_obj(object)) return "Property 'general' must be an object"; yyjson_val *key, *val; size_t idx, max; yyjson_obj_foreach(object, idx, max, key, val) { if (unsafe_yyjson_equals_str(key, "thread")) options->multithreading = yyjson_get_bool(val); else if (unsafe_yyjson_equals_str(key, "processingTimeout")) options->processingTimeout = (int32_t) yyjson_get_int(val); else if (unsafe_yyjson_equals_str(key, "preRun")) { if (!yyjson_is_str(val)) return "general.preRun must be a string"; if (system(unsafe_yyjson_get_str(val)) < 0) return "Failed to execute preRun command"; } else if (unsafe_yyjson_equals_str(key, "detectVersion")) options->detectVersion = yyjson_get_bool(val); #if defined(__linux__) || defined(__FreeBSD__) || defined(__sun) || defined(__OpenBSD__) || defined(__NetBSD__) || defined(__HAIKU__) || defined(__GNU__) else if (unsafe_yyjson_equals_str(key, "playerName")) ffStrbufSetJsonVal(&options->playerName, val); else if (unsafe_yyjson_equals_str(key, "dsForceDrm")) { if (yyjson_is_str(val)) { int value; const char* error = ffJsonConfigParseEnum(val, &value, (FFKeyValuePair[]) { { "sysfs-only", FF_DS_FORCE_DRM_TYPE_SYSFS_ONLY }, { "false", FF_DS_FORCE_DRM_TYPE_FALSE }, { "true", FF_DS_FORCE_DRM_TYPE_TRUE }, {}, }); if (error) return "Invalid enum value of `dsForceDrm`"; else options->dsForceDrm = (FFDsForceDrmType) value; } else options->dsForceDrm = yyjson_get_bool(val) ? FF_DS_FORCE_DRM_TYPE_TRUE : FF_DS_FORCE_DRM_TYPE_FALSE; } #elif defined(_WIN32) else if (unsafe_yyjson_equals_str(key, "wmiTimeout")) options->wmiTimeout = (int32_t) yyjson_get_int(val); #endif else return "Unknown general property"; } return NULL; } bool ffOptionsParseGeneralCommandLine(FFOptionsGeneral* options, const char* key, const char* value) { if(ffStrEqualsIgnCase(key, "--thread") || ffStrEqualsIgnCase(key, "--multithreading")) options->multithreading = ffOptionParseBoolean(value); else if(ffStrEqualsIgnCase(key, "--processing-timeout")) options->processingTimeout = ffOptionParseInt32(key, value); else if(ffStrEqualsIgnCase(key, "--detect-version")) options->detectVersion = ffOptionParseBoolean(value); #if defined(__linux__) || defined(__FreeBSD__) || defined(__sun) || defined(__OpenBSD__) || defined(__NetBSD__) || defined(__HAIKU__) || defined(__GNU__) else if(ffStrEqualsIgnCase(key, "--player-name")) ffOptionParseString(key, value, &options->playerName); else if(ffStrEqualsIgnCase(key, "--ds-force-drm")) { if (ffOptionParseBoolean(value)) options->dsForceDrm = FF_DS_FORCE_DRM_TYPE_TRUE; else if (ffStrEqualsIgnCase(value, "sysfs-only")) options->dsForceDrm = FF_DS_FORCE_DRM_TYPE_SYSFS_ONLY; else options->dsForceDrm = FF_DS_FORCE_DRM_TYPE_FALSE; } #elif defined(_WIN32) else if (ffStrEqualsIgnCase(key, "--wmi-timeout")) options->wmiTimeout = ffOptionParseInt32(key, value); #endif else return false; return true; } void ffOptionsInitGeneral(FFOptionsGeneral* options) { options->processingTimeout = 5000; options->multithreading = true; options->detectVersion = true; #if defined(__linux__) || defined(__FreeBSD__) || defined(__sun) || defined(__OpenBSD__) || defined(__NetBSD__) || defined(__HAIKU__) || defined(__GNU__) ffStrbufInit(&options->playerName); options->dsForceDrm = FF_DS_FORCE_DRM_TYPE_FALSE; #elif defined(_WIN32) options->wmiTimeout = 5000; #endif } void ffOptionsDestroyGeneral(FF_MAYBE_UNUSED FFOptionsGeneral* options) { #if defined(__linux__) || defined(__FreeBSD__) || defined(__sun) || defined(__OpenBSD__) || defined(__NetBSD__) || defined(__HAIKU__) || defined(__GNU__) ffStrbufDestroy(&options->playerName); #endif } void ffOptionsGenerateGeneralJsonConfig(FFdata* data, FFOptionsGeneral* options) { yyjson_mut_doc* doc = data->resultDoc; yyjson_mut_val* obj = yyjson_mut_obj_add_obj(doc, doc->root, "general"); yyjson_mut_obj_add_bool(doc, obj, "thread", options->multithreading); yyjson_mut_obj_add_int(doc, obj, "processingTimeout", options->processingTimeout); yyjson_mut_obj_add_bool(doc, obj, "detectVersion", options->detectVersion); #if defined(__linux__) || defined(__FreeBSD__) || defined(__sun) || defined(__OpenBSD__) || defined(__NetBSD__) || defined(__HAIKU__) || defined(__GNU__) yyjson_mut_obj_add_strbuf(doc, obj, "playerName", &options->playerName); switch (options->dsForceDrm) { case FF_DS_FORCE_DRM_TYPE_FALSE: yyjson_mut_obj_add_bool(doc, obj, "dsForceDrm", false); break; case FF_DS_FORCE_DRM_TYPE_SYSFS_ONLY: yyjson_mut_obj_add_str(doc, obj, "dsForceDrm", "sysfs-only"); break; case FF_DS_FORCE_DRM_TYPE_TRUE: yyjson_mut_obj_add_bool(doc, obj, "dsForceDrm", true); break; } #elif defined(_WIN32) yyjson_mut_obj_add_int(doc, obj, "wmiTimeout", options->wmiTimeout); #endif } ================================================ FILE: src/options/general.h ================================================ #pragma once #include "common/ffdata.h" typedef enum __attribute__((__packed__)) FFDsForceDrmType { FF_DS_FORCE_DRM_TYPE_FALSE = 0, // Disable FF_DS_FORCE_DRM_TYPE_TRUE = 1, // Try `libdrm`, then `sysfs` if libdrm failed FF_DS_FORCE_DRM_TYPE_SYSFS_ONLY, // Use `/sys/class/drm` only } FFDsForceDrmType; typedef struct FFOptionsGeneral { bool multithreading; int32_t processingTimeout; bool detectVersion; // Module options that cannot be put in module option structure #if defined(__linux__) || defined(__FreeBSD__) || defined(__sun) || defined(__OpenBSD__) || defined(__NetBSD__) || defined(__HAIKU__) || defined(__GNU__) FFstrbuf playerName; FFDsForceDrmType dsForceDrm; #elif defined(_WIN32) int32_t wmiTimeout; #endif } FFOptionsGeneral; const char* ffOptionsParseGeneralJsonConfig(FFOptionsGeneral* options, yyjson_val* root); bool ffOptionsParseGeneralCommandLine(FFOptionsGeneral* options, const char* key, const char* value); void ffOptionsInitGeneral(FFOptionsGeneral* options); void ffOptionsDestroyGeneral(FFOptionsGeneral* options); void ffOptionsGenerateGeneralJsonConfig(FFdata* data, FFOptionsGeneral* options); ================================================ FILE: src/options/logo.c ================================================ #include "logo/logo.h" #include "common/jsonconfig.h" #include "common/stringUtils.h" void ffOptionsInitLogo(FFOptionsLogo* options) { ffStrbufInit(&options->source); options->type = FF_LOGO_TYPE_AUTO; for(uint8_t i = 0; i < (uint8_t) FASTFETCH_LOGO_MAX_COLORS; ++i) ffStrbufInit(&options->colors[i]); options->width = 0; options->height = 0; //preserve aspect ratio options->paddingTop = 0; options->paddingLeft = 0; options->paddingRight = 4; options->printRemaining = true; options->preserveAspectRatio = false; options->recache = false; options->position = FF_LOGO_POSITION_LEFT; options->chafaFgOnly = false; ffStrbufInitStatic(&options->chafaSymbols, "block+border+space-wide-inverted"); // Chafa default options->chafaCanvasMode = UINT32_MAX; options->chafaColorSpace = UINT32_MAX; options->chafaDitherMode = UINT32_MAX; } bool ffOptionsParseLogoCommandLine(FFOptionsLogo* options, const char* key, const char* value) { if (ffStrEqualsIgnCase(key, "-l")) goto logoType; const char* subKey = ffOptionTestPrefix(key, "logo"); if(subKey) { if (subKey[0] == '\0') { logoType: if(value == NULL) { fprintf(stderr, "Error: usage: %s \n", key); exit(477); } //this is usually wanted when disabling logo if(ffStrEqualsIgnCase(value, "none")) options->type = FF_LOGO_TYPE_NONE; else if(ffStrEqualsIgnCase(value, "small")) options->type = FF_LOGO_TYPE_SMALL; else ffOptionParseString(key, value, &options->source); } else if(ffStrEqualsIgnCase(subKey, "type")) { options->type = (FFLogoType) ffOptionParseEnum(key, value, (FFKeyValuePair[]) { { "auto", FF_LOGO_TYPE_AUTO }, { "builtin", FF_LOGO_TYPE_BUILTIN }, { "small", FF_LOGO_TYPE_SMALL }, { "file", FF_LOGO_TYPE_FILE }, { "file-raw", FF_LOGO_TYPE_FILE_RAW }, { "data", FF_LOGO_TYPE_DATA }, { "data-raw", FF_LOGO_TYPE_DATA_RAW }, { "command-raw", FF_LOGO_TYPE_COMMAND_RAW }, { "sixel", FF_LOGO_TYPE_IMAGE_SIXEL }, { "kitty", FF_LOGO_TYPE_IMAGE_KITTY }, { "kitty-direct", FF_LOGO_TYPE_IMAGE_KITTY_DIRECT }, { "kitty-icat", FF_LOGO_TYPE_IMAGE_KITTY_ICAT }, { "iterm", FF_LOGO_TYPE_IMAGE_ITERM }, { "chafa", FF_LOGO_TYPE_IMAGE_CHAFA }, { "raw", FF_LOGO_TYPE_IMAGE_RAW }, { "none", FF_LOGO_TYPE_NONE }, {}, }); } else if(ffStrStartsWithIgnCase(subKey, "color-") && subKey[6] != '\0' && subKey[7] == '\0') // matches "--logo-color-*" { //Map the number to an array index, so that '1' -> 0, '2' -> 1, etc. int index = (int)subKey[6] - '0' - 1; //Match only --logo-color-[1-9] if(index < 0 || index >= FASTFETCH_LOGO_MAX_COLORS) { fprintf(stderr, "Error: invalid --color-[1-9] index: %c\n", key[13]); exit(472); } if(value == NULL) { fprintf(stderr, "Error: usage: %s \n", key); exit(477); } ffOptionParseColor(value, &options->colors[index]); } else if(ffStrEqualsIgnCase(subKey, "width")) options->width = ffOptionParseUInt32(key, value); else if(ffStrEqualsIgnCase(subKey, "height")) options->height = ffOptionParseUInt32(key, value); else if(ffStrEqualsIgnCase(subKey, "padding")) { uint32_t padding = ffOptionParseUInt32(key, value); options->paddingLeft = padding; options->paddingRight = padding; } else if(ffStrEqualsIgnCase(subKey, "padding-top")) options->paddingTop = ffOptionParseUInt32(key, value); else if(ffStrEqualsIgnCase(subKey, "padding-left")) options->paddingLeft = ffOptionParseUInt32(key, value); else if(ffStrEqualsIgnCase(subKey, "padding-right")) options->paddingRight = ffOptionParseUInt32(key, value); else if(ffStrEqualsIgnCase(subKey, "print-remaining")) options->printRemaining = ffOptionParseBoolean(value); else if(ffStrEqualsIgnCase(subKey, "preserve-aspect-ratio")) options->preserveAspectRatio = ffOptionParseBoolean(value); else if(ffStrEqualsIgnCase(subKey, "recache")) options->recache = ffOptionParseBoolean(value); else if(ffStrEqualsIgnCase(subKey, "separate")) { fputs("--logo-separate has been renamed to --logo-position\n", stderr); exit(477); } else if(ffStrEqualsIgnCase(subKey, "position")) { options->position = (FFLogoPosition) ffOptionParseEnum(key, value, (FFKeyValuePair[]) { { "left", FF_LOGO_POSITION_LEFT }, { "right", FF_LOGO_POSITION_RIGHT }, { "top", FF_LOGO_POSITION_TOP }, {}, }); } else return false; } else if((subKey = ffOptionTestPrefix(key, "file"))) { if(subKey[0] == '\0') { ffOptionParseString(key, value, &options->source); options->type = FF_LOGO_TYPE_FILE; } else if(ffStrEqualsIgnCase(subKey, "raw")) { ffOptionParseString(key, value, &options->source); options->type = FF_LOGO_TYPE_FILE_RAW; } else return false; } else if((subKey = ffOptionTestPrefix(key, "data"))) { if(subKey[0] == '\0') { ffOptionParseString(key, value, &options->source); options->type = FF_LOGO_TYPE_DATA; } else if(ffStrEqualsIgnCase(subKey, "raw")) { ffOptionParseString(key, value, &options->source); options->type = FF_LOGO_TYPE_DATA_RAW; } else return false; } else if(ffStrEqualsIgnCase(key, "--sixel")) { ffOptionParseString(key, value, &options->source); options->type = FF_LOGO_TYPE_IMAGE_SIXEL; } else if(ffStrEqualsIgnCase(key, "--kitty")) { ffOptionParseString(key, value, &options->source); options->type = FF_LOGO_TYPE_IMAGE_KITTY; } else if(ffStrEqualsIgnCase(key, "--kitty-direct")) { ffOptionParseString(key, value, &options->source); options->type = FF_LOGO_TYPE_IMAGE_KITTY_DIRECT; } else if(ffStrEqualsIgnCase(key, "--kitty-icat")) { ffOptionParseString(key, value, &options->source); options->type = FF_LOGO_TYPE_IMAGE_KITTY_ICAT; } else if(ffStrEqualsIgnCase(key, "--iterm")) { ffOptionParseString(key, value, &options->source); options->type = FF_LOGO_TYPE_IMAGE_ITERM; } else if(ffStrEqualsIgnCase(key, "--raw")) { ffOptionParseString(key, value, &options->source); options->type = FF_LOGO_TYPE_IMAGE_RAW; } else if((subKey = ffOptionTestPrefix(key, "chafa"))) { if(subKey[0] == '\0') { ffOptionParseString(key, value, &options->source); options->type = FF_LOGO_TYPE_IMAGE_CHAFA; } else if(ffStrEqualsIgnCase(subKey, "fg-only")) options->chafaFgOnly = ffOptionParseBoolean(value); else if(ffStrEqualsIgnCase(subKey, "symbols")) ffOptionParseString(key, value, &options->chafaSymbols); else if(ffStrEqualsIgnCase(subKey, "canvas-mode")) { options->chafaCanvasMode = (uint32_t) ffOptionParseEnum(key, value, (FFKeyValuePair[]) { { "TRUECOLOR", 0 }, { "INDEXED_256", 1 }, { "INDEXED_240", 2 }, { "INDEXED_16", 3 }, { "FGBG_BGFG", 4 }, { "FGBG", 5 }, { "INDEXED_8", 6 }, { "INDEXED_16_8", 7 }, {}, }); } else if(ffStrEqualsIgnCase(subKey, "color-space")) { options->chafaColorSpace = (uint32_t) ffOptionParseEnum(key, value, (FFKeyValuePair[]) { { "RGB", 0 }, { "DIN99D", 1 }, {}, }); } else if(ffStrEqualsIgnCase(subKey, "dither-mode")) { options->chafaDitherMode = (uint32_t) ffOptionParseEnum(key, value, (FFKeyValuePair[]) { { "NONE", 0 }, { "ORDERED", 1 }, { "DIFFUSION", 2 }, {}, }); } else return false; } else return false; return true; } void ffOptionsDestroyLogo(FFOptionsLogo* options) { ffStrbufDestroy(&options->source); ffStrbufDestroy(&options->chafaSymbols); for(uint8_t i = 0; i < (uint8_t) FASTFETCH_LOGO_MAX_COLORS; ++i) ffStrbufDestroy(&options->colors[i]); } const char* ffOptionsParseLogoJsonConfig(FFOptionsLogo* options, yyjson_val* root) { yyjson_val* object = yyjson_obj_get(root, "logo"); if (!object) return NULL; if (yyjson_is_null(object)) { options->type = FF_LOGO_TYPE_NONE; options->paddingTop = 0; options->paddingRight = 0; options->paddingLeft = 0; return NULL; } if (yyjson_is_str(object)) { ffStrbufSetJsonVal(&options->source, object); return NULL; } if (!yyjson_is_obj(object)) return "Property 'logo' must be an object"; yyjson_val *key, *val; size_t idx, max; yyjson_obj_foreach(object, idx, max, key, val) { if (unsafe_yyjson_equals_str(key, "type")) { int value; const char* error = ffJsonConfigParseEnum(val, &value, (FFKeyValuePair[]) { { "auto", FF_LOGO_TYPE_AUTO }, { "builtin", FF_LOGO_TYPE_BUILTIN }, { "small", FF_LOGO_TYPE_SMALL }, { "file", FF_LOGO_TYPE_FILE }, { "file-raw", FF_LOGO_TYPE_FILE_RAW }, { "data", FF_LOGO_TYPE_DATA }, { "data-raw", FF_LOGO_TYPE_DATA_RAW }, { "command-raw", FF_LOGO_TYPE_COMMAND_RAW }, { "sixel", FF_LOGO_TYPE_IMAGE_SIXEL }, { "kitty", FF_LOGO_TYPE_IMAGE_KITTY }, { "kitty-direct", FF_LOGO_TYPE_IMAGE_KITTY_DIRECT }, { "kitty-icat", FF_LOGO_TYPE_IMAGE_KITTY_ICAT }, { "iterm", FF_LOGO_TYPE_IMAGE_ITERM }, { "chafa", FF_LOGO_TYPE_IMAGE_CHAFA }, { "raw", FF_LOGO_TYPE_IMAGE_RAW }, { "none", FF_LOGO_TYPE_NONE }, {}, }); if (error) return error; options->type = (FFLogoType) value; continue; } else if (unsafe_yyjson_equals_str(key, "source")) { ffStrbufSetJsonVal(&options->source, val); continue; } else if (unsafe_yyjson_equals_str(key, "color")) { if (!yyjson_is_obj(val)) return "Property 'color' must be an object"; yyjson_val *keyc, *valc; size_t idxc, maxc; yyjson_obj_foreach(val, idxc, maxc, keyc, valc) { uint32_t index = (uint32_t) strtoul(unsafe_yyjson_get_str(keyc), NULL, 10); if (index < 1 || index > FASTFETCH_LOGO_MAX_COLORS) return "Keys of property 'color' must be a number between 1 to 9"; ffOptionParseColor(yyjson_get_str(valc), &options->colors[index - 1]); } continue; } else if (unsafe_yyjson_equals_str(key, "width")) { if (yyjson_is_null(val)) options->width = 0; else { uint32_t value = (uint32_t) yyjson_get_uint(val); if (value == 0) return "Logo width must be a positive integer"; options->width = value; } continue; } else if (unsafe_yyjson_equals_str(key, "height")) { if (yyjson_is_null(val)) options->height = 0; else { uint32_t value = (uint32_t) yyjson_get_uint(val); if (value == 0) return "Logo height must be a positive integer"; options->height = value; } continue; } else if (unsafe_yyjson_equals_str(key, "padding")) { if (!yyjson_is_obj(val)) return "Logo padding must be an object"; #define FF_PARSE_PADDING_POSITON(pos, paddingPos) \ yyjson_val* pos = yyjson_obj_get(val, #pos); \ if (pos) \ { \ if (!yyjson_is_uint(pos)) \ return "Logo padding values must be positive integers"; \ options->paddingPos = (uint32_t) yyjson_get_uint(pos); \ } FF_PARSE_PADDING_POSITON(left, paddingLeft); FF_PARSE_PADDING_POSITON(top, paddingTop); FF_PARSE_PADDING_POSITON(right, paddingRight); #undef FF_PARSE_PADDING_POSITON continue; } else if (unsafe_yyjson_equals_str(key, "printRemaining")) { options->printRemaining = yyjson_get_bool(val); continue; } else if (unsafe_yyjson_equals_str(key, "preserveAspectRatio")) { options->preserveAspectRatio = yyjson_get_bool(val); continue; } else if (unsafe_yyjson_equals_str(key, "recache")) { options->recache = yyjson_get_bool(val); continue; } else if (unsafe_yyjson_equals_str(key, "position")) { int value; const char* error = ffJsonConfigParseEnum(val, &value, (FFKeyValuePair[]) { { "left", FF_LOGO_POSITION_LEFT }, { "top", FF_LOGO_POSITION_TOP }, { "right", FF_LOGO_POSITION_RIGHT }, {}, }); if (error) return error; options->position = (FFLogoPosition) value; continue; } else if (unsafe_yyjson_equals_str(key, "chafa")) { if (!yyjson_is_obj(val)) return "Chafa config must be an object"; yyjson_val* fgOnly = yyjson_obj_get(val, "fgOnly"); if (fgOnly) options->chafaFgOnly = yyjson_get_bool(fgOnly); yyjson_val* symbols = yyjson_obj_get(val, "symbols"); if (symbols) ffStrbufSetJsonVal(&options->chafaSymbols, symbols); yyjson_val* canvasMode = yyjson_obj_get(val, "canvasMode"); if (canvasMode) { int value; const char* error = ffJsonConfigParseEnum(canvasMode, &value, (FFKeyValuePair[]) { { "TRUECOLOR", 0 }, { "INDEXED_256", 1 }, { "INDEXED_240", 2 }, { "INDEXED_16", 3 }, { "FGBG_BGFG", 4 }, { "FGBG", 5 }, { "INDEXED_8", 6 }, { "INDEXED_16_8", 7 }, {}, }); if (error) return error; options->chafaCanvasMode = (uint32_t) value; } yyjson_val* colorSpace = yyjson_obj_get(val, "colorSpace"); if (colorSpace) { int value; const char* error = ffJsonConfigParseEnum(colorSpace, &value, (FFKeyValuePair[]) { { "RGB", 0 }, { "DIN99D", 1 }, {}, }); if (error) return error; options->chafaColorSpace = (uint32_t) value; } yyjson_val* ditherMode = yyjson_obj_get(val, "ditherMode"); if (ditherMode) { int value; const char* error = ffJsonConfigParseEnum(ditherMode, &value, (FFKeyValuePair[]) { { "NONE", 0 }, { "ORDERED", 1 }, { "DIFFUSION", 2 }, {}, }); if (error) return error; options->chafaDitherMode = (uint32_t) value; } continue; } else return "Unknown logo key"; } return NULL; } void ffOptionsGenerateLogoJsonConfig(FFdata* data, FFOptionsLogo* options) { yyjson_mut_doc* doc = data->resultDoc; yyjson_mut_val* obj = yyjson_mut_obj(doc); switch (options->type) { case FF_LOGO_TYPE_NONE: yyjson_mut_obj_add_null(doc, doc->root, "logo"); return; case FF_LOGO_TYPE_BUILTIN: yyjson_mut_obj_add_str(doc, obj, "type", "builtin"); break; case FF_LOGO_TYPE_SMALL: yyjson_mut_obj_add_str(doc, obj, "type", "small"); break; case FF_LOGO_TYPE_FILE: yyjson_mut_obj_add_str(doc, obj, "type", "file"); break; case FF_LOGO_TYPE_FILE_RAW: yyjson_mut_obj_add_str(doc, obj, "type", "file-raw"); break; case FF_LOGO_TYPE_DATA: yyjson_mut_obj_add_str(doc, obj, "type", "data"); break; case FF_LOGO_TYPE_DATA_RAW: yyjson_mut_obj_add_str(doc, obj, "type", "data-raw"); break; case FF_LOGO_TYPE_COMMAND_RAW: yyjson_mut_obj_add_str(doc, obj, "type", "command-raw"); break; case FF_LOGO_TYPE_IMAGE_SIXEL: yyjson_mut_obj_add_str(doc, obj, "type", "sixel"); break; case FF_LOGO_TYPE_IMAGE_KITTY: yyjson_mut_obj_add_str(doc, obj, "type", "kitty"); break; case FF_LOGO_TYPE_IMAGE_KITTY_DIRECT: yyjson_mut_obj_add_str(doc, obj, "type", "kitty-direct"); break; case FF_LOGO_TYPE_IMAGE_KITTY_ICAT: yyjson_mut_obj_add_str(doc, obj, "type", "kitty-icat"); break; case FF_LOGO_TYPE_IMAGE_ITERM: yyjson_mut_obj_add_str(doc, obj, "type", "iterm"); break; case FF_LOGO_TYPE_IMAGE_CHAFA: yyjson_mut_obj_add_str(doc, obj, "type", "chafa"); break; case FF_LOGO_TYPE_IMAGE_RAW: yyjson_mut_obj_add_str(doc, obj, "type", "raw"); break; default: yyjson_mut_obj_add_str(doc, obj, "type", "auto"); break; } yyjson_mut_obj_add_str(doc, obj, "source", options->source.chars); { yyjson_mut_val* color = yyjson_mut_obj(doc); for (int i = 0; i < FASTFETCH_LOGO_MAX_COLORS; i++) { char c = (char)('1' + i); yyjson_mut_obj_add(color, yyjson_mut_strncpy(doc, &c, 1), yyjson_mut_strbuf(doc, &options->colors[i])); } yyjson_mut_obj_add_val(doc, obj, "color", color); } if (options->width == 0) yyjson_mut_obj_add_null(doc, obj, "width"); else yyjson_mut_obj_add_uint(doc, obj, "width", options->width); if (options->height == 0) yyjson_mut_obj_add_null(doc, obj, "height"); else yyjson_mut_obj_add_uint(doc, obj, "height", options->height); { yyjson_mut_val* padding = yyjson_mut_obj_add_obj(doc, obj, "padding"); yyjson_mut_obj_add_uint(doc, padding, "top", options->paddingTop); yyjson_mut_obj_add_uint(doc, padding, "left", options->paddingLeft); yyjson_mut_obj_add_uint(doc, padding, "right", options->paddingRight); } yyjson_mut_obj_add_bool(doc, obj, "printRemaining", options->printRemaining); yyjson_mut_obj_add_bool(doc, obj, "preserveAspectRatio", options->preserveAspectRatio); yyjson_mut_obj_add_bool(doc, obj, "recache", options->recache); yyjson_mut_obj_add_str(doc, obj, "position", ((const char* []) { "left", "top", "right", })[options->position]); { yyjson_mut_val* chafa = yyjson_mut_obj(doc); yyjson_mut_obj_add_bool(doc, chafa, "fgOnly", options->chafaFgOnly); yyjson_mut_obj_add_strbuf(doc, chafa, "symbols", &options->chafaSymbols); if (options->chafaCanvasMode <= 7) { yyjson_mut_obj_add_str(doc, chafa, "canvasMode", ((const char* []) { "TRUECOLOR", "INDEXED_256", "INDEXED_240", "INDEXED_16", "FGBG_BGFG", "FGBG", "INDEXED_8", "INDEXED_16_8", })[options->chafaCanvasMode]); } if (options->chafaColorSpace <= 1) { yyjson_mut_obj_add_str(doc, chafa, "colorSpace", ((const char* []) { "RGB", "DIN99D", })[options->chafaColorSpace]); } if (options->chafaDitherMode <= 2) { yyjson_mut_obj_add_str(doc, chafa, "ditherMode", ((const char* []) { "NONE", "ORDERED", "DIFFUSION", })[options->chafaDitherMode]); } yyjson_mut_obj_add_val(doc, obj, "chafa", chafa); } yyjson_mut_obj_add_val(doc, doc->root, "logo", obj); } ================================================ FILE: src/options/logo.h ================================================ #pragma once #include "common/ffdata.h" #define FASTFETCH_LOGO_MAX_NAMES 9 #define FASTFETCH_LOGO_MAX_COLORS 9 // two digits would make parsing much more complicated (index 1 - 9) typedef enum __attribute__((__packed__)) FFLogoType { FF_LOGO_TYPE_AUTO, // if something is given, first try builtin, then file. Otherwise detect logo FF_LOGO_TYPE_BUILTIN, // builtin ascii art FF_LOGO_TYPE_SMALL, // builtin ascii art, small version FF_LOGO_TYPE_FILE, // text file, printed with color code replacement FF_LOGO_TYPE_FILE_RAW, // text file, printed as is FF_LOGO_TYPE_DATA, // text data, printed with color code replacement FF_LOGO_TYPE_DATA_RAW, // text data, printed as is FF_LOGO_TYPE_COMMAND_RAW, // command to generate text data, printed as is FF_LOGO_TYPE_IMAGE_SIXEL, // image file, printed as sixel codes FF_LOGO_TYPE_IMAGE_KITTY, // image file, printed as kitty graphics protocol FF_LOGO_TYPE_IMAGE_KITTY_DIRECT, // image file, tell the terminal emulator to read image data from the specified file (Supported by kitty and wezterm) FF_LOGO_TYPE_IMAGE_KITTY_ICAT, // image file, use `kitten icat` to display the image. Requires binary `kitten` to be installed" FF_LOGO_TYPE_IMAGE_ITERM, // image file, printed as iterm graphics protocol FF_LOGO_TYPE_IMAGE_CHAFA, // image file, printed as ascii art using libchafa FF_LOGO_TYPE_IMAGE_RAW, // image file, printed as raw binary string FF_LOGO_TYPE_NONE, // `--logo none`, but still applies colors to the system information output (unless `--pipe` is set) } FFLogoType; typedef enum __attribute__((__packed__)) FFLogoPosition { FF_LOGO_POSITION_LEFT, FF_LOGO_POSITION_TOP, FF_LOGO_POSITION_RIGHT, } FFLogoPosition; typedef struct FFOptionsLogo { FFstrbuf source; FFLogoType type; FFLogoPosition position; FFstrbuf colors[FASTFETCH_LOGO_MAX_COLORS]; uint32_t width; uint32_t height; uint32_t paddingTop; uint32_t paddingLeft; uint32_t paddingRight; bool printRemaining; bool preserveAspectRatio; bool recache; bool chafaFgOnly; FFstrbuf chafaSymbols; uint32_t chafaCanvasMode; uint32_t chafaColorSpace; uint32_t chafaDitherMode; } FFOptionsLogo; void ffOptionsInitLogo(FFOptionsLogo* options); bool ffOptionsParseLogoCommandLine(FFOptionsLogo* options, const char* key, const char* value); void ffOptionsDestroyLogo(FFOptionsLogo* options); const char* ffOptionsParseLogoJsonConfig(FFOptionsLogo* options, yyjson_val* root); void ffOptionsGenerateLogoJsonConfig(FFdata* data, FFOptionsLogo* options); ================================================ FILE: tests/color.c ================================================ #include "common/format.h" #include "common/textModifier.h" #include "fastfetch.h" #include static void verify(const char* color, const char* expected, int lineNo) { FF_STRBUF_AUTO_DESTROY result = ffStrbufCreate(); ffOptionParseColorNoClear(color, &result); if (!ffStrbufEqualS(&result, expected)) { fprintf(stderr, FASTFETCH_TEXT_MODIFIER_ERROR "[%d] %s: expected \"%s\", got \"%s\"\n" FASTFETCH_TEXT_MODIFIER_RESET, lineNo, color, expected, result.chars); exit(1); } } #define VERIFY(color, expected) verify((color), (expected), __LINE__) int main(void) { instance.config.display.pipe = true; // Initialize dummy config colors for property tests ffStrbufInitS(&instance.config.display.colorKeys, "94"); // light_blue ffStrbufInitS(&instance.config.display.colorTitle, "95"); // light_magenta { VERIFY("", ""); VERIFY("1", "1"); VERIFY("red", "31"); VERIFY("light_green", "92"); VERIFY("default", "39"); VERIFY("blue", "34"); VERIFY("light_cyan", "96"); VERIFY("bold_red", "1;31"); VERIFY("dim_light_yellow", "2;93"); VERIFY("italic_underline_green", "3;4;32"); VERIFY("reset_blue", "0;34"); // Reset followed by color VERIFY("#ff0000", "38;2;255;0;0"); // RRGGBB VERIFY("#0f0", "38;2;0;255;0"); // RGB VERIFY("#123456", "38;2;18;52;86"); VERIFY("#abc", "38;2;170;187;204"); // xterm 256 color codes VERIFY("@0", "38;5;0"); VERIFY("@1", "38;5;1"); VERIFY("@255", "38;5;255"); VERIFY("bold_#ff00ff", "1;38;2;255;0;255"); VERIFY("underline_#123", "4;38;2;17;34;51"); VERIFY("\e[32m", "32"); // Direct ANSI code VERIFY("\e[1;94m", "1;94"); // Direct ANSI code with mode // Property colors (ensure dummy config colors are set) VERIFY("keys", "94"); VERIFY("title", "95"); VERIFY("bold_keys", "1;94"); } // Clean up dummy config colors ffStrbufDestroy(&instance.config.display.colorKeys); ffStrbufDestroy(&instance.config.display.colorTitle); //Success puts("\033[32mAll tests passed!" FASTFETCH_TEXT_MODIFIER_RESET); } ================================================ FILE: tests/duration.c ================================================ #include "common/duration.h" #include "common/textModifier.h" #include "fastfetch.h" #include static void verify(uint64_t totalSeconds, const char* expected, int lineNo) { FF_STRBUF_AUTO_DESTROY result = ffStrbufCreate(); ffDurationAppendNum(totalSeconds, &result); if (!ffStrbufEqualS(&result, expected)) { fprintf(stderr, FASTFETCH_TEXT_MODIFIER_ERROR "[%d] %llu: expected \"%s\", got \"%s\"\n" FASTFETCH_TEXT_MODIFIER_RESET, lineNo, (unsigned long long) totalSeconds, expected, result.chars); exit(1); } } #define VERIFY(color, expected) verify((color), (expected), __LINE__) int main(void) { // Test seconds less than 60 VERIFY(0, "0 seconds"); VERIFY(1, "1 second"); VERIFY(2, "2 seconds"); VERIFY(59, "59 seconds"); // Test minute rounding (when seconds >= 30) VERIFY(60, "1 min"); VERIFY(60 + 29, "1 min"); VERIFY(60 + 30, "2 mins"); // Test only minutes VERIFY(60 * 2 - 1, "2 mins"); VERIFY(60 * 2, "2 mins"); VERIFY(60 * 59 + 29, "59 mins"); // Test only hours (no minutes) VERIFY(60 * 59 + 30, "1 hour"); VERIFY(60 * 60, "1 hour"); VERIFY(2 * 60 * 60, "2 hours"); VERIFY(23 * 60 * 60, "23 hours"); // Test combination of hours and minutes VERIFY(60 * 60 + 60, "1 hour, 1 min"); VERIFY(60 * 60 + 60 * 2, "1 hour, 2 mins"); VERIFY(60 * 60 * 2 + 60 + 29, "2 hours, 1 min"); VERIFY(60 * 60 * 2 + 60 + 30, "2 hours, 2 mins"); // Test days VERIFY(60 * 60 * 24, "1 day"); VERIFY(60 * 60 * 24 - 1, "1 day"); VERIFY(60 * 60 * 24 * 2, "2 days"); // Test combination of days and hours VERIFY(60 * 60 * 24 + 60 * 60, "1 day, 1 hour"); VERIFY(60 * 60 * 24 * 2 + 60 * 60, "2 days, 1 hour"); VERIFY(60 * 60 * 24 * 2 + 60 * 60 * 2, "2 days, 2 hours"); // Test combination of days, hours, and minutes VERIFY(60 * 60 * 24 + 60 * 60 + 60, "1 day, 1 hour, 1 min"); VERIFY(60 * 60 * 24 * 2 + 60 * 60 + 60 * 2, "2 days, 1 hour, 2 mins"); VERIFY(60 * 60 * 24 * 2 + 60 * 2, "2 days, 2 mins"); // Test very large number of days VERIFY(60 * 60 * 24 * 100, "100 days(!)"); VERIFY(60 * 60 * 24 * 200, "200 days(!)"); instance.config.display.durationAbbreviation = true; instance.config.display.durationSpaceBeforeUnit = FF_SPACE_BEFORE_UNIT_NEVER; // Test seconds less than 60 VERIFY(0, "0secs"); VERIFY(1, "1sec"); VERIFY(2, "2secs"); VERIFY(59, "59secs"); // Test minute rounding (when seconds >= 30) VERIFY(60, "1m"); VERIFY(60 + 29, "1m"); VERIFY(60 + 30, "2m"); // Test only minutes VERIFY(60 * 2 - 1, "2m"); VERIFY(60 * 2, "2m"); VERIFY(60 * 59 + 29, "59m"); // Test only hours (no minutes) VERIFY(60 * 59 + 30, "1h"); VERIFY(60 * 60, "1h"); VERIFY(2 * 60 * 60, "2h"); VERIFY(23 * 60 * 60, "23h"); // Test combination of hours and minutes VERIFY(60 * 60 + 60, "1h 1m"); VERIFY(60 * 60 + 60 * 2, "1h 2m"); VERIFY(60 * 60 * 2 + 60 + 29, "2h 1m"); VERIFY(60 * 60 * 2 + 60 + 30, "2h 2m"); // Test days VERIFY(60 * 60 * 24, "1d"); VERIFY(60 * 60 * 24 - 1, "1d"); VERIFY(60 * 60 * 24 * 2, "2d"); // Test combination of days and hours VERIFY(60 * 60 * 24 + 60 * 60, "1d 1h"); VERIFY(60 * 60 * 24 * 2 + 60 * 60, "2d 1h"); VERIFY(60 * 60 * 24 * 2 + 60 * 60 * 2, "2d 2h"); // Test combination of days, hours, and minutes VERIFY(60 * 60 * 24 + 60 * 60 + 60, "1d 1h 1m"); VERIFY(60 * 60 * 24 * 2 + 60 * 60 + 60 * 2, "2d 1h 2m"); VERIFY(60 * 60 * 24 * 2 + 60 * 2, "2d 2m"); // Test very large number of days VERIFY(60 * 60 * 24 * 100, "100d"); VERIFY(60 * 60 * 24 * 200, "200d"); //Success puts("\033[32mAll tests passed!" FASTFETCH_TEXT_MODIFIER_RESET); } ================================================ FILE: tests/format.c ================================================ #include "common/format.h" #include "common/textModifier.h" #include "fastfetch.h" #include static void verify(const char* format, const char* arg, const char* expected, int lineNo) { FF_STRBUF_AUTO_DESTROY result = ffStrbufCreate(); FF_STRBUF_AUTO_DESTROY formatter = ffStrbufCreateStatic(format); const FFformatarg arguments[] = { { .type = FF_ARG_TYPE_STRING, arg } }; ffParseFormatString(&result, &formatter, 1, arguments); if (!ffStrbufEqualS(&result, expected)) { fprintf(stderr, FASTFETCH_TEXT_MODIFIER_ERROR "[%d] %s: expected \"%s\", got \"%s\"\n" FASTFETCH_TEXT_MODIFIER_RESET, lineNo, format, expected, result.chars); exit(1); } } #define VERIFY(format, argument, expected) verify((format), (argument), (expected), __LINE__) int main(void) { instance.config.display.pipe = true; { VERIFY("output({})", "12345 67890", "output(12345 67890)"); VERIFY("output({1})", "12345 67890", "output(12345 67890)"); VERIFY("output({})", "", "output()"); VERIFY("output({1})", "", "output()"); } { VERIFY("output({1:20})", "12345 67890", "output(12345 67890)"); VERIFY("output({1:11})", "12345 67890", "output(12345 67890)"); VERIFY("output({1:-11})", "12345 67890", "output(12345 67890)"); VERIFY("output({1:6})", "12345 67890", "output(12345)"); VERIFY("output({:6})", "12345 67890", "output(12345)"); VERIFY("output({:-6})", "12345 67890", "output(12345…)"); VERIFY("output({:0})", "12345 67890", "output()"); VERIFY("output({:})", "12345 67890", "output()"); } { VERIFY("output({1<20})", "12345 67890", "output(12345 67890 )"); VERIFY("output({1<-20})", "12345 67890", "output(12345 67890 )"); VERIFY("output({1<11})", "12345 67890", "output(12345 67890)"); VERIFY("output({1<-11})", "12345 67890", "output(12345 67890)"); VERIFY("output({1<6})", "12345 67890", "output(12345 )"); VERIFY("output({<6})", "12345 67890", "output(12345 )"); VERIFY("output({<-6})", "12345 67890", "output(12345…)"); VERIFY("output({<0})", "12345 67890", "output()"); VERIFY("output({<})", "12345 67890", "output()"); } { VERIFY("output({1>20})", "12345 67890", "output( 12345 67890)"); VERIFY("output({1>-20})", "12345 67890", "output( 12345 67890)"); VERIFY("output({1>11})", "12345 67890", "output(12345 67890)"); VERIFY("output({1>-11})", "12345 67890", "output(12345 67890)"); VERIFY("output({1>6})", "12345 67890", "output(12345 )"); VERIFY("output({>6})", "12345 67890", "output(12345 )"); VERIFY("output({>-6})", "12345 67890", "output(12345…)"); VERIFY("output({>0})", "12345 67890", "output()"); VERIFY("output({>})", "12345 67890", "output()"); } { VERIFY("output({1n>20})", "12345 67890", "output({1n>20})"); VERIFY("output({120})", "12345 67890", "output({120})"); VERIFY("output({1:11})", "", "output()"); VERIFY("output({2:2})", "", "output({2:2})"); } { VERIFY("output({1~0})", "12345 67890", "output(12345 67890)"); VERIFY("output({1~1})", "12345 67890", "output(2345 67890)"); VERIFY("output({1~10})", "12345 67890", "output(0)"); VERIFY("output({1~20})", "12345 67890", "output()"); VERIFY("output({1~-5})", "12345 67890", "output(67890)"); VERIFY("output({1~-50})", "12345 67890", "output()"); VERIFY("output({1~0,1})", "12345 67890", "output(1)"); VERIFY("output({1~0,6})", "12345 67890", "output(12345 )"); VERIFY("output({1~0,10})", "12345 67890", "output(12345 6789)"); VERIFY("output({1~5,10})", "12345 67890", "output( 6789)"); VERIFY("output({1~5,100})", "12345 67890", "output( 67890)"); VERIFY("output({1~10,10})", "12345 67890", "output()"); VERIFY("output({1~10,5})", "12345 67890", "output()"); VERIFY("output({1~3,-3})", "12345 67890", "output(45 67)"); VERIFY("output({1~-5,-3})", "12345 67890", "output(67)"); VERIFY("output({1~-0,10})", "12345 67890", "output(12345 6789)"); VERIFY("output({1~-3,-5})", "12345 67890", "output()"); VERIFY("output({1~})", "12345 67890", "output(12345 67890)"); // Same as {1~0} VERIFY("output({1~-0})", "12345 67890", "output(12345 67890)"); // Same as {1~0} VERIFY("output({1~,-1})", "12345 67890", "output(12345 6789)"); // Same as {1~0,-1} VERIFY("output({1~,})", "12345 67890", "output()"); // Same as {1~0,0} } { VERIFY("output({1n~0})", "12345 67890", "output({1n~0})"); VERIFY("output({1~<0})", "12345 67890", "output({1~<0})"); VERIFY("output({1~-})", "12345 67890", "output({1~-})"); VERIFY("output({1~-,1})", "12345 67890", "output({1~-,1})"); VERIFY("output({1~0,,1})", "12345 67890", "output({1~0,,1})"); VERIFY("output({1~0,1,})", "12345 67890", "output({1~0,1,})"); VERIFY("output({1~,0,1})", "12345 67890", "output({1~,0,1})"); VERIFY("output({2,0})", "12345 67890", "output({2,0})"); } { VERIFY("output({1:20}{1<20}{1>20})", "12345 67890", "output(12345 6789012345 67890 12345 67890)"); VERIFY("output({?1}OK{?}{/1}NOT OK{/})", "12345 67890", "output(OK)"); VERIFY("output({?1}OK{?}{/1}NOT OK{/})", "", "output(NOT OK)"); } #ifndef _WIN32 // Windows doesn't have setenv { ffListInit(&instance.config.display.constants, sizeof(FFstrbuf)); ffStrbufInitStatic(ffListAdd(&instance.config.display.constants), "CONST1"); ffStrbufInitStatic(ffListAdd(&instance.config.display.constants), "CONST2"); setenv("FF_TEST", "ENVVAR", 1); VERIFY("output({$FF_TEST})", "", "output(ENVVAR)"); VERIFY("output({$1})", "", "output(CONST1)"); VERIFY("output({$FF_TEST}{$1})", "", "output(ENVVARCONST1)"); VERIFY("output({$1}{$FF_TEST})", "", "output(CONST1ENVVAR)"); VERIFY("output({$FF_TEST}{$FF_TEST})", "", "output(ENVVARENVVAR)"); VERIFY("output({$1}{$-1})", "", "output(CONST1CONST2)"); VERIFY("output({$FF_INVAL})", "", "output({$FF_INVAL})"); VERIFY("output({$9}{$0}${-9})", "", "output({$9}{$0}${-9})"); VERIFY("output({$1NO})", "", "output({$1NO})"); ffListDestroy(&instance.config.display.constants); } #endif //Success puts("\033[32mAll tests passed!" FASTFETCH_TEXT_MODIFIER_RESET); } ================================================ FILE: tests/list.c ================================================ #include "common/FFlist.h" #include "common/textModifier.h" #include #include #include #include __attribute__((__noreturn__)) static void testFailed(const FFlist* list, const char* expression, int lineNo) { fputs(FASTFETCH_TEXT_MODIFIER_ERROR, stderr); fprintf(stderr, "[%d] %s, list:", lineNo, expression); for (uint32_t i = 0; i < list->length; ++i) { fprintf(stderr, "%u ", *(uint32_t*)ffListGet(list, i)); } fputc('\n', stderr); fputs(FASTFETCH_TEXT_MODIFIER_RESET, stderr); fputc('\n', stderr); exit(1); } static bool numEqualsAdapter(const void* first, const void* second) { return *(uint32_t*)first == *(uint32_t*)second; } #define VERIFY(expression) if(!(expression)) testFailed(&list, #expression, __LINE__) int main(void) { FFlist list; uint32_t n = 0; //initA ffListInit(&list, sizeof(uint32_t)); VERIFY(list.elementSize == sizeof(uint32_t)); VERIFY(list.capacity == 0); VERIFY(list.length == 0); //forEach FF_LIST_FOR_EACH(uint32_t, item, list) VERIFY(false); //shift VERIFY(!ffListShift(&list, &n)); VERIFY(list.length == 0); //pop VERIFY(!ffListPop(&list, &n)); VERIFY(list.length == 0); //add for (uint32_t i = 1; i <= FF_LIST_DEFAULT_ALLOC + 1; ++i) { *(uint32_t*)ffListAdd(&list) = i; VERIFY(list.elementSize == sizeof(uint32_t)); VERIFY(list.length == i); if(i <= FF_LIST_DEFAULT_ALLOC) { VERIFY(list.capacity == FF_LIST_DEFAULT_ALLOC); } else { VERIFY(list.capacity == FF_LIST_DEFAULT_ALLOC * 2); } VERIFY(*(uint32_t*)ffListGet(&list, 0) == 1); VERIFY(*(uint32_t*)ffListGet(&list, i - 1) == i); } VERIFY(list.length == FF_LIST_DEFAULT_ALLOC + 1); uint32_t sum = 0; //forEach FF_LIST_FOR_EACH(uint32_t, item, list) sum += *item; VERIFY(sum == (1 + FF_LIST_DEFAULT_ALLOC + 1) * (FF_LIST_DEFAULT_ALLOC + 1) / 2); // ffListFirstIndexComp n = 10; VERIFY(ffListFirstIndexComp(&list, &n, numEqualsAdapter) == 9); n = 999; VERIFY(ffListFirstIndexComp(&list, &n, numEqualsAdapter) == list.length); // ffListContains n = 10; VERIFY(ffListContains(&list, &n, numEqualsAdapter)); n = 999; VERIFY(!ffListContains(&list, &n, numEqualsAdapter)); //shift VERIFY(ffListShift(&list, &n)); VERIFY(n == 1); VERIFY(list.length == FF_LIST_DEFAULT_ALLOC); VERIFY(*(uint32_t*) ffListGet(&list, 0) == 2); VERIFY(*(uint32_t*) ffListGet(&list, list.length - 1) == FF_LIST_DEFAULT_ALLOC + 1); //pop VERIFY(ffListPop(&list, &n)); VERIFY(n == FF_LIST_DEFAULT_ALLOC + 1); VERIFY(list.length == FF_LIST_DEFAULT_ALLOC - 1); VERIFY(*(uint32_t*) ffListGet(&list, 0) == 2); VERIFY(*(uint32_t*) ffListGet(&list, list.length - 1) == FF_LIST_DEFAULT_ALLOC); //Destroy ffListDestroy(&list); VERIFY(list.elementSize == sizeof(uint32_t)); VERIFY(list.capacity == 0); VERIFY(list.length == 0); { FF_LIST_AUTO_DESTROY test = ffListCreate(1); VERIFY(test.elementSize == 1); VERIFY(test.capacity == 0); VERIFY(test.length == 0); } //Success puts("\033[32mAll tests passed!"FASTFETCH_TEXT_MODIFIER_RESET); } ================================================ FILE: tests/strbuf.c ================================================ #include "common/FFstrbuf.h" #include "common/textModifier.h" #include #include #include __attribute__((__noreturn__)) static void testFailed(const FFstrbuf* strbuf, const char* expression, int lineNo) { fputs(FASTFETCH_TEXT_MODIFIER_ERROR, stderr); fprintf(stderr, "[%d] %s, strbuf:", lineNo, expression); ffStrbufWriteTo(strbuf, stderr); fputs(FASTFETCH_TEXT_MODIFIER_RESET, stderr); fputc('\n', stderr); exit(1); } #define VERIFY(expression) if(!(expression)) testFailed(&strbuf, #expression, __LINE__) int shouldNotBeCalled(int c) { (void)c; exit(1); } int main(void) { FFstrbuf strbuf; //destroy 0 ffStrbufInit(&strbuf); ffStrbufDestroy(&strbuf); //initA ffStrbufInit(&strbuf); VERIFY(strbuf.chars[0] == 0); VERIFY(strbuf.allocated == 0); VERIFY(strbuf.length == 0); VERIFY(!ffStrbufStartsWithC(&strbuf, '0')); VERIFY(!ffStrbufEndsWithC(&strbuf, '0')); VERIFY(!ffStrbufStartsWithS(&strbuf, "0")); VERIFY(!ffStrbufEndsWithS(&strbuf, "0")); VERIFY(ffStrbufCountC(&strbuf, '0') == 0); //Ensure following functions work with non-allocated string ffStrbufAppendS(&strbuf, ""); ffStrbufAppendF(&strbuf, "%s", ""); ffStrbufAppendTransformS(&strbuf, "", shouldNotBeCalled); ffStrbufPrependS(&strbuf, ""); ffStrbufTrim(&strbuf, ' '); VERIFY(strbuf.chars[0] == 0); VERIFY(strbuf.allocated == 0); VERIFY(strbuf.length == 0); //append(N)C ffStrbufAppendC(&strbuf, '1'); VERIFY(ffStrbufEqualS(&strbuf, "1")); VERIFY(strbuf.allocated >= 1); ffStrbufAppendNC(&strbuf, 5, '2'); VERIFY(ffStrbufEqualS(&strbuf, "122222")); VERIFY(strbuf.allocated >= 6); ffStrbufClear(&strbuf); //appendS ffStrbufAppendS(&strbuf, "12345"); ffStrbufAppendS(&strbuf, NULL); VERIFY(strbuf.length == 5); VERIFY(strbuf.allocated >= 6); VERIFY(ffStrbufEqualS(&strbuf, "12345")); //appendNS ffStrbufAppendNS(&strbuf, 4, "67890"); ffStrbufAppendNS(&strbuf, 0, NULL); VERIFY(strbuf.length == 9); VERIFY(strbuf.allocated >= 10); VERIFY(ffStrbufEqualS(&strbuf, "123456789")); //appendS long ffStrbufAppendS(&strbuf, "1234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890"); VERIFY(strbuf.length == 109); VERIFY(strbuf.allocated >= 110); VERIFY(strbuf.chars[strbuf.length] == 0); //substr VERIFY(ffStrbufSubstrBefore(&strbuf, 9)); VERIFY(strbuf.length == 9); VERIFY(strbuf.allocated >= 110); VERIFY(strbuf.chars[strbuf.length] == 0); VERIFY(ffStrbufEqualS(&strbuf, "123456789")); VERIFY(!ffStrbufSubstrBeforeFirstC(&strbuf, '0')); VERIFY(!ffStrbufSubstrBeforeLastC(&strbuf, '0')); VERIFY(!ffStrbufSubstrAfterFirstC(&strbuf, '0')); VERIFY(!ffStrbufSubstrAfterLastC(&strbuf, '0')); VERIFY(ffStrbufEqualS(&strbuf, "123456789")); VERIFY(ffStrbufSubstrBeforeFirstC(&strbuf, '9')); VERIFY(ffStrbufSubstrBeforeLastC(&strbuf, '8')); VERIFY(ffStrbufSubstrAfterFirstC(&strbuf, '1')); VERIFY(ffStrbufSubstrAfterLastC(&strbuf, '2')); VERIFY(ffStrbufEqualS(&strbuf, "34567")); ffStrbufSetS(&strbuf, "123456789"); //startsWithC VERIFY(ffStrbufStartsWithC(&strbuf, '1')); VERIFY(!ffStrbufStartsWithC(&strbuf, '2')); //startsWithS VERIFY(ffStrbufStartsWithS(&strbuf, "123")); VERIFY(ffStrbufStartsWithS(&strbuf, "123456789")); VERIFY(!ffStrbufStartsWithS(&strbuf, "1234567890123")); //endsWithC VERIFY(ffStrbufEndsWithC(&strbuf, '9')); VERIFY(!ffStrbufEndsWithC(&strbuf, '1')); //endsWithS VERIFY(ffStrbufEndsWithS(&strbuf, "789")); VERIFY(ffStrbufEndsWithS(&strbuf, "123456789")); VERIFY(!ffStrbufEndsWithS(&strbuf, "1234567890123")); //toNumber VERIFY(ffStrbufToDouble(&strbuf, -DBL_MAX) == 123456789.0); VERIFY(ffStrbufToUInt(&strbuf, 999) == 123456789); //countC VERIFY(ffStrbufCountC(&strbuf, '1') == 1); VERIFY(ffStrbufCountC(&strbuf, '0') == 0); //removeS ffStrbufRemoveS(&strbuf, "78"); VERIFY(strbuf.length == 7); VERIFY(strcmp(strbuf.chars, "1234569") == 0); //removeStrings ffStrbufRemoveStrings(&strbuf, 3, (const char*[]) { "23", "45", "9" }); VERIFY(strbuf.length == 2); VERIFY(strcmp(strbuf.chars, "16") == 0); //PrependS ffStrbufPrependS(&strbuf, "123"); ffStrbufPrependS(&strbuf, NULL); VERIFY(strbuf.length == 5); VERIFY(strcmp(strbuf.chars, "12316") == 0); //indexC VERIFY(ffStrbufFirstIndexC(&strbuf, '1') == 0); VERIFY(ffStrbufNextIndexC(&strbuf, 1, '1') == 3); VERIFY(ffStrbufNextIndexC(&strbuf, 4, '1') == 5); VERIFY(ffStrbufLastIndexC(&strbuf, '1') == 3); VERIFY(ffStrbufPreviousIndexC(&strbuf, 2, '1') == 0); VERIFY(ffStrbufPreviousIndexC(&strbuf, 0, '1') == 0); VERIFY(ffStrbufPreviousIndexC(&strbuf, 0, '2') == 5); //indexS VERIFY(ffStrbufFirstIndexS(&strbuf, "12316") == 0); VERIFY(ffStrbufNextIndexS(&strbuf, 1, "1") == 3); VERIFY(ffStrbufNextIndexS(&strbuf, 4, "1") == 5); //ignCase ffStrbufSetS(&strbuf, "AbCdEfG"); VERIFY(ffStrbufIgnCaseCompS(&strbuf, "aBcDeFg") == 0); VERIFY(ffStrbufStartsWithIgnCaseS(&strbuf, "ABCD")); VERIFY(ffStrbufEndsWithIgnCaseS(&strbuf, "defg")); VERIFY(!ffStrbufStartsWithIgnCaseS(&strbuf, "aBcDeFgH")); VERIFY(!ffStrbufEndsWithIgnCaseS(&strbuf, "0aBcDeFg")); //ensure ffStrbufEnsureEndsWithC(&strbuf, '$'); VERIFY(ffStrbufEqualS(&strbuf, "AbCdEfG$")); ffStrbufEnsureEndsWithC(&strbuf, '$'); VERIFY(ffStrbufEqualS(&strbuf, "AbCdEfG$")); //trimRight ffStrbufTrimRight(&strbuf, '$'); VERIFY(ffStrbufEqualS(&strbuf, "AbCdEfG")); ffStrbufTrimRight(&strbuf, '$'); VERIFY(ffStrbufEqualS(&strbuf, "AbCdEfG")); //clear ffStrbufClear(&strbuf); VERIFY(strbuf.allocated > 0); VERIFY(strbuf.length == 0); VERIFY(strbuf.chars && strbuf.chars[0] == 0); //Destroy ffStrbufDestroy(&strbuf); VERIFY(strbuf.allocated == 0); VERIFY(strbuf.length == 0); VERIFY(strbuf.chars && strbuf.chars[0] == 0); //initA ffStrbufInitA(&strbuf, 32); VERIFY(strbuf.allocated == 32); VERIFY(strbuf.length == 0); VERIFY(strbuf.chars && strbuf.chars[0] == 0); //appendF ffStrbufAppendF(&strbuf, "%s", "1234567890123456789012345678901"); VERIFY(strbuf.allocated == 32); VERIFY(ffStrbufEqualS(&strbuf, "1234567890123456789012345678901")); ffStrbufDestroy(&strbuf); //initMoveS { char* heapStr = strdup("1234567890"); ffStrbufInitMoveS(&strbuf, heapStr); VERIFY(ffStrbufEqualS(&strbuf, "1234567890")); VERIFY(strbuf.allocated >= 11); ffStrbufDestroy(&strbuf); } //initF ffStrbufInitF(&strbuf, "%s", "1234567890123456789012345678901"); VERIFY(strbuf.allocated >= 32); VERIFY(ffStrbufEqualS(&strbuf, "1234567890123456789012345678901")); //containC VERIFY(ffStrbufContainC(&strbuf, '1')); VERIFY(!ffStrbufContainC(&strbuf, '-')); //replaceAllC ffStrbufReplaceAllC(&strbuf, '1', '-'); VERIFY(ffStrbufEqualS(&strbuf, "-234567890-234567890-234567890-")); ffStrbufReplaceAllC(&strbuf, '1', '-'); VERIFY(ffStrbufEqualS(&strbuf, "-234567890-234567890-234567890-")); //trim ffStrbufTrim(&strbuf, '-'); VERIFY(ffStrbufEqualS(&strbuf, "234567890-234567890-234567890")); ffStrbufDestroy(&strbuf); //ffStrbufCreateS { FF_STRBUF_AUTO_DESTROY testCreate = ffStrbufCreateS("TEST"); VERIFY(ffStrbufEqualS(&testCreate, "TEST")); } //ffStrbufCreateStatic ffStrbufInitStatic(&strbuf, "TEST"); VERIFY(ffStrbufEqualS(&strbuf, "TEST")); VERIFY(strbuf.length == 4); VERIFY(strbuf.allocated == 0); ffStrbufDestroy(&strbuf); VERIFY(strbuf.length == 0); VERIFY(strbuf.allocated == 0); //ffStrbufCreateStatic / Allocate ffStrbufInitStatic(&strbuf, "TEST"); ffStrbufEnsureFree(&strbuf, 0); VERIFY(ffStrbufEqualS(&strbuf, "TEST")); VERIFY(strbuf.length == 4); VERIFY(strbuf.allocated > 0); //ffStrbufCreateStatic / Append ffStrbufInitStatic(&strbuf, "TEST"); ffStrbufAppendS(&strbuf, "_TEST"); VERIFY(ffStrbufEqualS(&strbuf, "TEST_TEST")); VERIFY(strbuf.length == 9); VERIFY(strbuf.allocated >= 10); ffStrbufAppendC(&strbuf, '_'); VERIFY(ffStrbufEqualS(&strbuf, "TEST_TEST_")); ffStrbufDestroy(&strbuf); VERIFY(strbuf.length == 0); VERIFY(strbuf.allocated == 0); //ffStrbufCreateStatic / Prepend ffStrbufInitStatic(&strbuf, "TEST"); ffStrbufPrependS(&strbuf, "TEST_"); VERIFY(ffStrbufEqualS(&strbuf, "TEST_TEST")); VERIFY(strbuf.length == 9); VERIFY(strbuf.allocated >= 10); ffStrbufPrependC(&strbuf, '_'); VERIFY(ffStrbufEqualS(&strbuf, "_TEST_TEST")); ffStrbufDestroy(&strbuf); VERIFY(strbuf.length == 0); VERIFY(strbuf.allocated == 0); //ffStrbufCreateStatic / Clear ffStrbufInitStatic(&strbuf, "TEST"); ffStrbufClear(&strbuf); VERIFY(strbuf.length == 0); VERIFY(strbuf.allocated == 0); ffStrbufDestroy(&strbuf); //ffStrbufCreateStatic / Set ffStrbufInitStatic(&strbuf, "TEST"); // static ffStrbufSetStatic(&strbuf, "test"); VERIFY(ffStrbufEqualS(&strbuf, "test")); VERIFY(strbuf.length == 4); VERIFY(strbuf.allocated == 0); ffStrbufDestroy(&strbuf); //ffStrbufCreateStatic / Set ffStrbufInitS(&strbuf, "TEST"); // allocated ffStrbufSetStatic(&strbuf, "test"); VERIFY(ffStrbufEqualS(&strbuf, "test")); VERIFY(strbuf.length == 4); VERIFY(strbuf.allocated == 0); ffStrbufDestroy(&strbuf); //ffStrbufCreateStatic / TrimL ffStrbufInitStatic(&strbuf, "_TEST_"); ffStrbufTrimLeft(&strbuf, '_'); VERIFY(ffStrbufEqualS(&strbuf, "TEST_")); VERIFY(strbuf.length == 5); VERIFY(strbuf.allocated == 0); ffStrbufDestroy(&strbuf); //ffStrbufCreateStatic / TrimR ffStrbufInitStatic(&strbuf, "_TEST_"); ffStrbufTrimRight(&strbuf, ' '); VERIFY(strbuf.allocated == 0); VERIFY(ffStrbufEqualS(&strbuf, "_TEST_")); ffStrbufTrimRight(&strbuf, '_'); VERIFY(ffStrbufEqualS(&strbuf, "_TEST")); VERIFY(strbuf.length == 5); VERIFY(strbuf.allocated > 0); ffStrbufDestroy(&strbuf); //ffStrbufCreateStatic / Substr ffStrbufInitStatic(&strbuf, "__TEST__"); VERIFY(ffStrbufRemoveSubstr(&strbuf, 0, 6)); VERIFY(ffStrbufEqualS(&strbuf, "__")); VERIFY(strbuf.length == 2); VERIFY(strbuf.allocated > 0); ffStrbufDestroy(&strbuf); //ffStrbufCreateStatic / Substr ffStrbufInitStatic(&strbuf, "__TEST__"); VERIFY(ffStrbufRemoveSubstr(&strbuf, 2, 8)); VERIFY(ffStrbufEqualS(&strbuf, "__")); VERIFY(strbuf.length == 2); VERIFY(strbuf.allocated > 0); ffStrbufDestroy(&strbuf); //ffStrbufCreateStatic / Substr ffStrbufInitStatic(&strbuf, "__TEST__"); VERIFY(ffStrbufRemoveSubstr(&strbuf, 2, 6)); VERIFY(ffStrbufEqualS(&strbuf, "____")); VERIFY(strbuf.length == 4); VERIFY(strbuf.allocated > 0); ffStrbufDestroy(&strbuf); //ffStrbufCreateStatic / ReplaceAllC ffStrbufInitStatic(&strbuf, "__TEST__"); ffStrbufReplaceAllC(&strbuf, '_', '-'); VERIFY(ffStrbufEqualS(&strbuf, "--TEST--")); VERIFY(strbuf.length == 8); VERIFY(strbuf.allocated > 0); ffStrbufDestroy(&strbuf); //ffStrbufCreateStatic / TrimSpace ffStrbufInitStatic(&strbuf, "\n TEST\n "); ffStrbufTrimSpace(&strbuf); VERIFY(strbuf.length == 4); VERIFY(strbuf.allocated > 0); VERIFY(ffStrbufEqualS(&strbuf, "TEST")); ffStrbufDestroy(&strbuf); //ffStrbufCreate / TrimSpace ffStrbufInitS(&strbuf, "\n TEST\n "); ffStrbufTrimSpace(&strbuf); VERIFY(strbuf.length == 4); VERIFY(strbuf.allocated > 0); VERIFY(ffStrbufEqualS(&strbuf, "TEST")); ffStrbufDestroy(&strbuf); //ffStrbufEnsureFixedLengthFree / empty buffer ffStrbufInit(&strbuf); ffStrbufEnsureFixedLengthFree(&strbuf, 10); VERIFY(strbuf.length == 0); VERIFY(strbuf.allocated == 11); ffStrbufDestroy(&strbuf); ffStrbufInitA(&strbuf, 10); ffStrbufEnsureFixedLengthFree(&strbuf, 10); VERIFY(strbuf.length == 0); VERIFY(strbuf.allocated == 11); ffStrbufEnsureFixedLengthFree(&strbuf, 12); VERIFY(strbuf.length == 0); VERIFY(strbuf.allocated == 13); ffStrbufDestroy(&strbuf); //ffStrbufEnsureFixedLengthFree / empty buffer with zero free length ffStrbufInit(&strbuf); ffStrbufEnsureFixedLengthFree(&strbuf, 0); VERIFY(strbuf.length == 0); VERIFY(strbuf.allocated == 0); ffStrbufDestroy(&strbuf); //ffStrbufEnsureFixedLengthFree / empty buffer but oldFree >= newFree ffStrbufInitA(&strbuf, 11); ffStrbufEnsureFixedLengthFree(&strbuf, 10); VERIFY(strbuf.length == 0); VERIFY(strbuf.allocated == 11); ffStrbufDestroy(&strbuf); ffStrbufInitA(&strbuf, 12); ffStrbufEnsureFixedLengthFree(&strbuf, 10); VERIFY(strbuf.length == 0); VERIFY(strbuf.allocated == 12); ffStrbufDestroy(&strbuf); //ffStrbufEnsureFixedLengthFree / non empty buffer ffStrbufAppendF(&strbuf, "%s", "1234567890"); VERIFY(strbuf.length == 10); VERIFY(strbuf.allocated == 32); ffStrbufEnsureFixedLengthFree(&strbuf, 0); VERIFY(strbuf.length == 10); VERIFY(strbuf.allocated == 32); ffStrbufEnsureFixedLengthFree(&strbuf, 20); // less than oldFree (=21) VERIFY(strbuf.length == 10); VERIFY(strbuf.allocated == 32); ffStrbufEnsureFixedLengthFree(&strbuf, 21); // equal to oldFree (=21) VERIFY(strbuf.length == 10); VERIFY(strbuf.allocated == 32); ffStrbufEnsureFixedLengthFree(&strbuf, 22); // greater than oldFree (=21) VERIFY(strbuf.length == 10); VERIFY(strbuf.allocated == 33); ffStrbufDestroy(&strbuf); //ffStrbufEnsureFixedLengthFree / static buffer ffStrbufInitStatic(&strbuf, "__TEST__"); VERIFY(strbuf.length > 0); VERIFY(strbuf.allocated == 0); ffStrbufEnsureFixedLengthFree(&strbuf, 10); VERIFY(strbuf.length == strlen("__TEST__")); VERIFY(strbuf.allocated == strlen("__TEST__") + 1 + 10); VERIFY(ffStrbufEqualS(&strbuf, "__TEST__")); ffStrbufDestroy(&strbuf); //ffStrbufInsertNC ffStrbufInitStatic(&strbuf, "123456"); ffStrbufInsertNC(&strbuf, 0, 2, 'A'); VERIFY(ffStrbufEqualS(&strbuf, "AA123456")); ffStrbufInsertNC(&strbuf, 4, 2, 'B'); VERIFY(ffStrbufEqualS(&strbuf, "AA12BB3456")); ffStrbufInsertNC(&strbuf, strbuf.length, 2, 'C'); VERIFY(ffStrbufEqualS(&strbuf, "AA12BB3456CC")); ffStrbufInsertNC(&strbuf, 999, 2, 'D'); VERIFY(ffStrbufEqualS(&strbuf, "AA12BB3456CCDD")); ffStrbufDestroy(&strbuf); // smallest allocation test { FF_STRBUF_AUTO_DESTROY strbuf1 = ffStrbufCreateA(10); VERIFY(strbuf1.allocated == 10); ffStrbufEnsureFree(&strbuf1, 16); VERIFY(strbuf1.allocated == 32); FF_STRBUF_AUTO_DESTROY strbuf2 = ffStrbufCreate(); VERIFY(strbuf2.allocated == 0); ffStrbufEnsureFree(&strbuf2, 16); VERIFY(strbuf2.allocated == 32); } { int i = 0; char* lineptr = NULL; size_t n = 0; const char* text = "Processor\t: ARMv7\nprocessor\t: 0\nBogoMIPS\t: 38.00\n\nprocessor\t: 1\nBogoMIPS\t: 38.00"; ffStrbufSetS(&strbuf, text); while (ffStrbufGetline(&lineptr, &n, &strbuf)) { ++i; switch (i) { case 1: VERIFY(strcmp(lineptr, "Processor\t: ARMv7") == 0); VERIFY(n == strlen("Processor\t: ARMv7")); break; case 2: VERIFY(strcmp(lineptr, "processor\t: 0") == 0); VERIFY(n == strlen("processor\t: 0")); break; case 3: VERIFY(strcmp(lineptr, "BogoMIPS\t: 38.00") == 0); VERIFY(n == strlen("BogoMIPS\t: 38.00")); break; case 4: VERIFY(strcmp(lineptr, "") == 0); VERIFY(n == 0); break; case 5: VERIFY(strcmp(lineptr, "processor\t: 1") == 0); VERIFY(n == strlen("processor\t: 1")); break; case 6: VERIFY(strcmp(lineptr, "BogoMIPS\t: 38.00") == 0); VERIFY(n == strlen("BogoMIPS\t: 38.00")); break; default: VERIFY(false); break; } } VERIFY(ffStrbufEqualS(&strbuf, text)); VERIFY(*lineptr == '\0'); VERIFY(i == 6); lineptr = NULL; n = 0; i = 0; text = "\n"; ffStrbufSetS(&strbuf, text); while (ffStrbufGetline(&lineptr, &n, &strbuf)) { ++i; switch (i) { case 1: VERIFY(strcmp(lineptr, "") == 0); VERIFY(n == 0); break; default: VERIFY(false); break; } } VERIFY(ffStrbufEqualS(&strbuf, text)); VERIFY(*lineptr == '\0'); VERIFY(i == 1); lineptr = NULL; n = 0; i = 0; text = "abcd"; ffStrbufSetS(&strbuf, text); while (ffStrbufGetline(&lineptr, &n, &strbuf)) { ++i; switch (i) { case 1: VERIFY(strcmp(lineptr, "abcd") == 0); VERIFY(n == strlen("abcd")); break; default: VERIFY(false); break; } } VERIFY(ffStrbufEqualS(&strbuf, text)); VERIFY(*lineptr == '\0'); VERIFY(i == 1); lineptr = NULL; n = 0; i = 0; text = ""; ffStrbufSetS(&strbuf, text); while (ffStrbufGetline(&lineptr, &n, &strbuf)) { ++i; VERIFY(false); } VERIFY(ffStrbufEqualS(&strbuf, text)); VERIFY(*lineptr == '\0'); VERIFY(i == 0); } ffStrbufSetS(&strbuf, "Hello World"); VERIFY(ffStrbufRemoveDupWhitespaces(&strbuf) == false); VERIFY(strcmp(strbuf.chars, "Hello World") == 0); ffStrbufSetS(&strbuf, "Hello World"); VERIFY(ffStrbufRemoveDupWhitespaces(&strbuf) == true); VERIFY(strcmp(strbuf.chars, "Hello World") == 0); ffStrbufSetS(&strbuf, " Hello World "); VERIFY(ffStrbufRemoveDupWhitespaces(&strbuf) == true); VERIFY(strcmp(strbuf.chars, " Hello World ") == 0); ffStrbufSetS(&strbuf, " Hello World "); VERIFY(ffStrbufRemoveDupWhitespaces(&strbuf) == true); VERIFY(strcmp(strbuf.chars, " Hello World ") == 0); ffStrbufSetS(&strbuf, " "); VERIFY(ffStrbufRemoveDupWhitespaces(&strbuf) == true); VERIFY(strcmp(strbuf.chars, " ") == 0); ffStrbufClear(&strbuf); VERIFY(ffStrbufRemoveDupWhitespaces(&strbuf) == false); VERIFY(strcmp(strbuf.chars, "") == 0); ffStrbufSetStatic(&strbuf, " "); VERIFY(ffStrbufRemoveDupWhitespaces(&strbuf) == false); VERIFY(strcmp(strbuf.chars, " ") == 0); { ffStrbufSetStatic(&strbuf, "abcdef"); FF_STRBUF_AUTO_DESTROY newStr = ffStrbufCreateCopy(&strbuf); VERIFY(newStr.allocated == 0); VERIFY(newStr.chars == strbuf.chars); } { ffStrbufSetStatic(&strbuf, "abcdef"); FF_STRBUF_AUTO_DESTROY newStr = ffStrbufCreateS("123456"); ffStrbufSet(&newStr, &strbuf); VERIFY(newStr.allocated == 0); VERIFY(newStr.chars == strbuf.chars); VERIFY(ffStrbufEqualS(&newStr, "abcdef")); } { ffStrbufClear(&strbuf); FF_STRBUF_AUTO_DESTROY newStr = ffStrbufCreateS("123456"); uint32_t oldAlloc = newStr.allocated; VERIFY(oldAlloc > newStr.length); ffStrbufSet(&newStr, &strbuf); VERIFY(newStr.allocated == oldAlloc); VERIFY(newStr.chars != strbuf.chars); VERIFY(ffStrbufEqualS(&newStr, "")); } { ffStrbufSetStatic(&strbuf, "abcdefghijkl"); FF_STRBUF_AUTO_DESTROY newStr = ffStrbufCreateS("123456"); ffStrbufSet(&newStr, &strbuf); VERIFY(newStr.allocated == 0); VERIFY(newStr.chars == strbuf.chars); VERIFY(ffStrbufEqualS(&newStr, "abcdefghijkl")); } { ffStrbufClear(&strbuf); FF_STRBUF_AUTO_DESTROY newStr = ffStrbufCreateCopy(&strbuf); VERIFY(newStr.allocated == 0); VERIFY(newStr.chars == strbuf.chars); VERIFY(newStr.chars[0] == '\0'); } { ffStrbufClear(&strbuf); FF_STRBUF_AUTO_DESTROY newStr = ffStrbufCreateS("123456"); ffStrbufSet(&newStr, &strbuf); VERIFY(newStr.allocated > 0); VERIFY(newStr.chars != strbuf.chars); VERIFY(ffStrbufEqualS(&newStr, "")); } { ffStrbufSetStatic(&strbuf, "abc"); VERIFY(ffStrbufMatchSeparatedS(&strbuf, "abc:def:ghi", ' ') == false); VERIFY(ffStrbufMatchSeparatedS(&strbuf, "abc:def:ghi", ':') == true); VERIFY(ffStrbufMatchSeparatedS(&strbuf, "def:ghi", ' ') == false); VERIFY(ffStrbufMatchSeparatedS(&strbuf, "def:ghi", ':') == false); VERIFY(ffStrbufMatchSeparatedS(&strbuf, "def", ':') == false); VERIFY(ffStrbufMatchSeparatedS(&strbuf, "abc", ':') == true); VERIFY(ffStrbufMatchSeparatedS(&strbuf, "", ' ') == false); VERIFY(ffStrbufMatchSeparatedS(&strbuf, ":abc:", ':') == true); VERIFY(ffStrbufMatchSeparatedS(&strbuf, "abc:", ':') == true); VERIFY(ffStrbufMatchSeparatedS(&strbuf, ":abc", ':') == true); VERIFY(ffStrbufMatchSeparatedS(&strbuf, ":ABC", ':') == false); VERIFY(ffStrbufMatchSeparatedS(&strbuf, ":abcdef", ':') == false); } { ffStrbufSetStatic(&strbuf, "ABC"); VERIFY(ffStrbufMatchSeparatedIgnCaseS(&strbuf, "abc:def:ghi", ' ') == false); VERIFY(ffStrbufMatchSeparatedIgnCaseS(&strbuf, "abc:def:ghi", ':') == true); VERIFY(ffStrbufMatchSeparatedIgnCaseS(&strbuf, "def:ghi", ' ') == false); VERIFY(ffStrbufMatchSeparatedIgnCaseS(&strbuf, "def:ghi", ':') == false); VERIFY(ffStrbufMatchSeparatedIgnCaseS(&strbuf, "def", ':') == false); VERIFY(ffStrbufMatchSeparatedIgnCaseS(&strbuf, "abc", ':') == true); VERIFY(ffStrbufMatchSeparatedIgnCaseS(&strbuf, "", ' ') == false); VERIFY(ffStrbufMatchSeparatedIgnCaseS(&strbuf, ":abc:", ':') == true); VERIFY(ffStrbufMatchSeparatedIgnCaseS(&strbuf, "abc:", ':') == true); VERIFY(ffStrbufMatchSeparatedIgnCaseS(&strbuf, ":abc", ':') == true); VERIFY(ffStrbufMatchSeparatedIgnCaseS(&strbuf, ":ABC", ':') == true); VERIFY(ffStrbufMatchSeparatedIgnCaseS(&strbuf, ":abcdef", ':') == false); } { ffStrbufSetStatic(&strbuf, "abc:def:ghi"); VERIFY(ffStrbufSeparatedContainS(&strbuf, "abc", ' ') == false); VERIFY(ffStrbufSeparatedContainS(&strbuf, "abc", ':') == true); VERIFY(ffStrbufSeparatedContainS(&strbuf, "def", ' ') == false); VERIFY(ffStrbufSeparatedContainS(&strbuf, "def", ':') == true); VERIFY(ffStrbufSeparatedContainS(&strbuf, "DEF", ':') == false); VERIFY(ffStrbufSeparatedContainS(&strbuf, "a", ':') == false); VERIFY(ffStrbufSeparatedContainS(&strbuf, "e", ':') == false); VERIFY(ffStrbufSeparatedContainS(&strbuf, "i", ':') == false); } { ffStrbufSetStatic(&strbuf, "ABC:DEF:GHI"); VERIFY(ffStrbufSeparatedContainIgnCaseS(&strbuf, "abc", ' ') == false); VERIFY(ffStrbufSeparatedContainIgnCaseS(&strbuf, "abc", ':') == true); VERIFY(ffStrbufSeparatedContainIgnCaseS(&strbuf, "def", ' ') == false); VERIFY(ffStrbufSeparatedContainIgnCaseS(&strbuf, "def", ':') == true); VERIFY(ffStrbufSeparatedContainIgnCaseS(&strbuf, "DEF", ':') == true); VERIFY(ffStrbufSeparatedContainIgnCaseS(&strbuf, "a", ':') == false); VERIFY(ffStrbufSeparatedContainIgnCaseS(&strbuf, "e", ':') == false); VERIFY(ffStrbufSeparatedContainIgnCaseS(&strbuf, "i", ':') == false); } { ffStrbufSetStatic(&strbuf, "abc"); ffStrbufSubstr(&strbuf, 0, 1); // start, end VERIFY(ffStrbufEqualS(&strbuf, "a")); ffStrbufSetStatic(&strbuf, "abc"); ffStrbufSubstr(&strbuf, 1, 1); VERIFY(ffStrbufEqualS(&strbuf, "")); ffStrbufSetStatic(&strbuf, "abc"); ffStrbufSubstr(&strbuf, 2, 1); VERIFY(ffStrbufEqualS(&strbuf, "")); ffStrbufSetStatic(&strbuf, "abc"); ffStrbufSubstr(&strbuf, 2, 3); VERIFY(ffStrbufEqualS(&strbuf, "c")); ffStrbufSetStatic(&strbuf, "abc"); ffStrbufSubstr(&strbuf, 0, 3); VERIFY(ffStrbufEqualS(&strbuf, "abc")); } { ffStrbufSetS(&strbuf, "abc"); ffStrbufSubstr(&strbuf, 0, 1); // start, end VERIFY(ffStrbufEqualS(&strbuf, "a")); ffStrbufSetS(&strbuf, "abc"); ffStrbufSubstr(&strbuf, 1, 1); VERIFY(ffStrbufEqualS(&strbuf, "")); ffStrbufSetS(&strbuf, "abc"); ffStrbufSubstr(&strbuf, 2, 1); VERIFY(ffStrbufEqualS(&strbuf, "")); ffStrbufSetS(&strbuf, "abc"); ffStrbufSubstr(&strbuf, 2, 3); VERIFY(ffStrbufEqualS(&strbuf, "c")); ffStrbufSetS(&strbuf, "abc"); ffStrbufSubstr(&strbuf, 0, 3); VERIFY(ffStrbufEqualS(&strbuf, "abc")); ffStrbufDestroy(&strbuf); } { ffStrbufAppendUtf32CodePoint(&strbuf, 0x6587); ffStrbufAppendUtf32CodePoint(&strbuf, 0x6cc9); ffStrbufAppendUtf32CodePoint(&strbuf, 0x9a7f); VERIFY(ffStrbufEqualS(&strbuf, u8"文泉驿")); ffStrbufDestroy(&strbuf); } { ffStrbufAppendSInt(&strbuf, 1234567890); VERIFY(ffStrbufEqualS(&strbuf, "1234567890")); ffStrbufClear(&strbuf); ffStrbufAppendSInt(&strbuf, -1234567890); VERIFY(ffStrbufEqualS(&strbuf, "-1234567890")); ffStrbufClear(&strbuf); ffStrbufAppendSInt(&strbuf, 0); VERIFY(ffStrbufEqualS(&strbuf, "0")); ffStrbufClear(&strbuf); ffStrbufAppendUInt(&strbuf, 1234567890); VERIFY(ffStrbufEqualS(&strbuf, "1234567890")); ffStrbufClear(&strbuf); ffStrbufAppendUInt(&strbuf, 0); VERIFY(ffStrbufEqualS(&strbuf, "0")); ffStrbufDestroy(&strbuf); } { ffStrbufAppendDouble(&strbuf, 120.0, 0, true); VERIFY(ffStrbufEqualS(&strbuf, "120")); ffStrbufClear(&strbuf); ffStrbufAppendDouble(&strbuf, 120.0, 1, true); VERIFY(ffStrbufEqualS(&strbuf, "120.0")); ffStrbufClear(&strbuf); ffStrbufAppendDouble(&strbuf, 120.0, 5, true); VERIFY(ffStrbufEqualS(&strbuf, "120.00000")); ffStrbufClear(&strbuf); ffStrbufAppendDouble(&strbuf, 120.123456789, 5, true); VERIFY(ffStrbufEqualS(&strbuf, "120.12346")); ffStrbufClear(&strbuf); ffStrbufAppendDouble(&strbuf, 120.888888, 0, true); VERIFY(ffStrbufEqualS(&strbuf, "121")); ffStrbufClear(&strbuf); ffStrbufAppendDouble(&strbuf, 120.999999, 2, true); VERIFY(ffStrbufEqualS(&strbuf, "121.00")); ffStrbufClear(&strbuf); ffStrbufAppendDouble(&strbuf, 120.123456789, 0, true); VERIFY(ffStrbufEqualS(&strbuf, "120")); ffStrbufClear(&strbuf); ffStrbufAppendDouble(&strbuf, 120.123456789, -1, true); VERIFY(ffStrbufEqualS(&strbuf, "120.123456789")); ffStrbufClear(&strbuf); ffStrbufAppendDouble(&strbuf, 120.123, 5, true); VERIFY(ffStrbufEqualS(&strbuf, "120.12300")); ffStrbufClear(&strbuf); ffStrbufAppendDouble(&strbuf, -120.0, 0, true); VERIFY(ffStrbufEqualS(&strbuf, "-120")); ffStrbufClear(&strbuf); ffStrbufAppendDouble(&strbuf, -120.0, 1, true); VERIFY(ffStrbufEqualS(&strbuf, "-120.0")); ffStrbufClear(&strbuf); ffStrbufAppendDouble(&strbuf, -120.0, 5, true); VERIFY(ffStrbufEqualS(&strbuf, "-120.00000")); ffStrbufClear(&strbuf); ffStrbufAppendDouble(&strbuf, -120.123456789, 5, true); VERIFY(ffStrbufEqualS(&strbuf, "-120.12346")); ffStrbufClear(&strbuf); ffStrbufAppendDouble(&strbuf, -120.123456789, 0, true); VERIFY(ffStrbufEqualS(&strbuf, "-120")); ffStrbufClear(&strbuf); ffStrbufAppendDouble(&strbuf, -120.123456789, -1, true); VERIFY(ffStrbufEqualS(&strbuf, "-120.123456789")); ffStrbufClear(&strbuf); ffStrbufAppendDouble(&strbuf, -120.123, 5, true); VERIFY(ffStrbufEqualS(&strbuf, "-120.12300")); ffStrbufClear(&strbuf); ffStrbufAppendDouble(&strbuf, -120.888888, 0, true); VERIFY(ffStrbufEqualS(&strbuf, "-121")); ffStrbufClear(&strbuf); ffStrbufAppendDouble(&strbuf, -120.999999, 2, true); VERIFY(ffStrbufEqualS(&strbuf, "-121.00")); ffStrbufClear(&strbuf); ffStrbufAppendDouble(&strbuf, 1.2345e50, 1, true); VERIFY(ffStrbufEqualS(&strbuf, "1.2345e50")); ffStrbufClear(&strbuf); ffStrbufAppendDouble(&strbuf, -1.2345e50, 1, true); VERIFY(ffStrbufEqualS(&strbuf, "-1.2345e50")); ffStrbufClear(&strbuf); ffStrbufAppendDouble(&strbuf, 1.2345e50, 0, true); VERIFY(ffStrbufEqualS(&strbuf, "1.2345e50")); ffStrbufClear(&strbuf); ffStrbufAppendDouble(&strbuf, -1.2345e50, 0, true); VERIFY(ffStrbufEqualS(&strbuf, "-1.2345e50")); ffStrbufClear(&strbuf); ffStrbufAppendDouble(&strbuf, 1.2345e50, -1, true); VERIFY(ffStrbufEqualS(&strbuf, "1.2345e50")); ffStrbufClear(&strbuf); ffStrbufAppendDouble(&strbuf, -1.2345e50, -1, true); VERIFY(ffStrbufEqualS(&strbuf, "-1.2345e50")); ffStrbufClear(&strbuf); ffStrbufAppendDouble(&strbuf, 1.2345e20, 1, true); VERIFY(ffStrbufEqualS(&strbuf, "123450000000000000000.0")); ffStrbufClear(&strbuf); ffStrbufAppendDouble(&strbuf, -1.2345e20, 1, true); VERIFY(ffStrbufEqualS(&strbuf, "-123450000000000000000.0")); ffStrbufClear(&strbuf); ffStrbufAppendDouble(&strbuf, +0.0, 0, true); VERIFY(ffStrbufEqualS(&strbuf, "0")); ffStrbufClear(&strbuf); ffStrbufAppendDouble(&strbuf, -0.0, 0, true); VERIFY(ffStrbufEqualS(&strbuf, "-0")); ffStrbufDestroy(&strbuf); } { ffStrbufAppendDouble(&strbuf, 120.0, 0, false); VERIFY(ffStrbufEqualS(&strbuf, "120")); ffStrbufClear(&strbuf); ffStrbufAppendDouble(&strbuf, 120.0, 1, false); VERIFY(ffStrbufEqualS(&strbuf, "120")); ffStrbufClear(&strbuf); ffStrbufAppendDouble(&strbuf, 120.0, 5, false); VERIFY(ffStrbufEqualS(&strbuf, "120")); ffStrbufClear(&strbuf); ffStrbufAppendDouble(&strbuf, 120.123456789, 5, false); VERIFY(ffStrbufEqualS(&strbuf, "120.12346")); ffStrbufClear(&strbuf); ffStrbufAppendDouble(&strbuf, 120.888888, 0, false); VERIFY(ffStrbufEqualS(&strbuf, "121")); ffStrbufClear(&strbuf); ffStrbufAppendDouble(&strbuf, 120.999999, 2, false); VERIFY(ffStrbufEqualS(&strbuf, "121")); ffStrbufClear(&strbuf); ffStrbufAppendDouble(&strbuf, 120.123456789, 0, false); VERIFY(ffStrbufEqualS(&strbuf, "120")); ffStrbufClear(&strbuf); ffStrbufAppendDouble(&strbuf, 120.123456789, -1, false); VERIFY(ffStrbufEqualS(&strbuf, "120.123456789")); ffStrbufClear(&strbuf); ffStrbufAppendDouble(&strbuf, 120.123, 5, false); VERIFY(ffStrbufEqualS(&strbuf, "120.123")); ffStrbufClear(&strbuf); ffStrbufAppendDouble(&strbuf, -120.0, 0, false); VERIFY(ffStrbufEqualS(&strbuf, "-120")); ffStrbufClear(&strbuf); ffStrbufAppendDouble(&strbuf, -120.0, 1, false); VERIFY(ffStrbufEqualS(&strbuf, "-120")); ffStrbufClear(&strbuf); ffStrbufAppendDouble(&strbuf, -120.0, 5, false); VERIFY(ffStrbufEqualS(&strbuf, "-120")); ffStrbufClear(&strbuf); ffStrbufAppendDouble(&strbuf, -120.123456789, 5, false); VERIFY(ffStrbufEqualS(&strbuf, "-120.12346")); ffStrbufClear(&strbuf); ffStrbufAppendDouble(&strbuf, -120.123456789, 0, false); VERIFY(ffStrbufEqualS(&strbuf, "-120")); ffStrbufClear(&strbuf); ffStrbufAppendDouble(&strbuf, -120.123456789, -1, false); VERIFY(ffStrbufEqualS(&strbuf, "-120.123456789")); ffStrbufClear(&strbuf); ffStrbufAppendDouble(&strbuf, -120.123, 5, false); VERIFY(ffStrbufEqualS(&strbuf, "-120.123")); ffStrbufClear(&strbuf); ffStrbufAppendDouble(&strbuf, -120.888888, 0, false); VERIFY(ffStrbufEqualS(&strbuf, "-121")); ffStrbufClear(&strbuf); ffStrbufAppendDouble(&strbuf, -120.999999, 2, false); VERIFY(ffStrbufEqualS(&strbuf, "-121")); ffStrbufClear(&strbuf); ffStrbufAppendDouble(&strbuf, 1.2345e50, 1, false); VERIFY(ffStrbufEqualS(&strbuf, "1.2345e50")); ffStrbufClear(&strbuf); ffStrbufAppendDouble(&strbuf, -1.2345e50, 1, false); VERIFY(ffStrbufEqualS(&strbuf, "-1.2345e50")); ffStrbufClear(&strbuf); ffStrbufAppendDouble(&strbuf, 1.2345e50, 0, false); VERIFY(ffStrbufEqualS(&strbuf, "1.2345e50")); ffStrbufClear(&strbuf); ffStrbufAppendDouble(&strbuf, -1.2345e50, 0, false); VERIFY(ffStrbufEqualS(&strbuf, "-1.2345e50")); ffStrbufClear(&strbuf); ffStrbufAppendDouble(&strbuf, 1.2345e50, -1, false); VERIFY(ffStrbufEqualS(&strbuf, "1.2345e50")); ffStrbufClear(&strbuf); ffStrbufAppendDouble(&strbuf, -1.2345e50, -1, false); VERIFY(ffStrbufEqualS(&strbuf, "-1.2345e50")); ffStrbufClear(&strbuf); ffStrbufAppendDouble(&strbuf, 1.2345e20, 1, false); VERIFY(ffStrbufEqualS(&strbuf, "123450000000000000000")); ffStrbufClear(&strbuf); ffStrbufAppendDouble(&strbuf, -1.2345e20, 1, false); VERIFY(ffStrbufEqualS(&strbuf, "-123450000000000000000")); ffStrbufClear(&strbuf); ffStrbufAppendDouble(&strbuf, +0.0, 0, false); VERIFY(ffStrbufEqualS(&strbuf, "0")); ffStrbufClear(&strbuf); ffStrbufAppendDouble(&strbuf, -0.0, 0, false); VERIFY(ffStrbufEqualS(&strbuf, "-0")); ffStrbufDestroy(&strbuf); } //setS ffStrbufInitStatic(&strbuf, "STATIC"); ffStrbufSetS(&strbuf, "DYNAMIC"); VERIFY(ffStrbufEqualS(&strbuf, "DYNAMIC")); VERIFY(strbuf.allocated > 0); ffStrbufDestroy(&strbuf); ffStrbufInit(&strbuf); ffStrbufSetS(&strbuf, ""); VERIFY(ffStrbufEqualS(&strbuf, "")); VERIFY(strbuf.allocated == 0); ffStrbufDestroy(&strbuf); ffStrbufInitStatic(&strbuf, "STATIC"); ffStrbufSetS(&strbuf, ""); VERIFY(ffStrbufEqualS(&strbuf, "")); VERIFY(strbuf.allocated == 0); ffStrbufDestroy(&strbuf); ffStrbufInitStatic(&strbuf, "STATIC"); ffStrbufSetStatic(&strbuf, ""); VERIFY(ffStrbufEqualS(&strbuf, "")); VERIFY(strbuf.allocated == 0); ffStrbufDestroy(&strbuf); ffStrbufInitS(&strbuf, "DYNAMIC"); ffStrbufSetStatic(&strbuf, ""); VERIFY(ffStrbufEqualS(&strbuf, "")); VERIFY(strbuf.allocated == 0); ffStrbufDestroy(&strbuf); ffStrbufInitS(&strbuf, "DYNAMIC"); ffStrbufSetS(&strbuf, "ANOTHER DYNAMIC"); VERIFY(ffStrbufEqualS(&strbuf, "ANOTHER DYNAMIC")); VERIFY(strbuf.allocated > 0); ffStrbufDestroy(&strbuf); ffStrbufInit(&strbuf); ffStrbufSetStatic(&strbuf, ""); VERIFY(ffStrbufEqualS(&strbuf, "")); VERIFY(strbuf.allocated == 0); ffStrbufDestroy(&strbuf); //set ffStrbufInitStatic(&strbuf, "STATIC"); { FF_STRBUF_AUTO_DESTROY other = ffStrbufCreateS("DYNAMIC"); ffStrbufSet(&strbuf, &other); } VERIFY(ffStrbufEqualS(&strbuf, "DYNAMIC")); VERIFY(strbuf.allocated > 0); ffStrbufDestroy(&strbuf); ffStrbufInit(&strbuf); { FF_STRBUF_AUTO_DESTROY other = ffStrbufCreateS(""); ffStrbufSet(&strbuf, &other); } VERIFY(ffStrbufEqualS(&strbuf, "")); VERIFY(strbuf.allocated == 0); ffStrbufDestroy(&strbuf); ffStrbufInitStatic(&strbuf, "STATIC"); { FF_STRBUF_AUTO_DESTROY other = ffStrbufCreateS(""); ffStrbufSet(&strbuf, &other); } VERIFY(ffStrbufEqualS(&strbuf, "")); VERIFY(strbuf.allocated == 0); ffStrbufDestroy(&strbuf); ffStrbufInitS(&strbuf, "DYNAMIC"); { FF_STRBUF_AUTO_DESTROY other = ffStrbufCreateS(""); ffStrbufSet(&strbuf, &other); } VERIFY(ffStrbufEqualS(&strbuf, "")); VERIFY(strbuf.allocated > 0); ffStrbufDestroy(&strbuf); ffStrbufInitS(&strbuf, "DYNAMIC"); { FF_STRBUF_AUTO_DESTROY other = ffStrbufCreateStatic("STATIC"); ffStrbufSet(&strbuf, &other); } VERIFY(ffStrbufEqualS(&strbuf, "STATIC")); VERIFY(strbuf.allocated == 0); ffStrbufDestroy(&strbuf); //Success puts("\e[32mAll tests passed!" FASTFETCH_TEXT_MODIFIER_RESET); } ================================================ FILE: tests/testlogo-hardcolors.fflogo ================================================  .  ...  .-|-. .-| |-. ================================================ FILE: tests/testlogo-softcolors.fflogo ================================================ $1 . $1 ... $1 .-|-. $1.-| |-.